| /******************************************************************************* | |
| * You may amend and distribute as you like, but don't remove this header! | |
| * | |
| * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets. | |
| * See http://www.codeplex.com/EPPlus for details. | |
| * | |
| * Copyright (C) 2011 Jan Källman | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
| * See the GNU Lesser General Public License for more details. | |
| * | |
| * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php | |
| * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html | |
| * | |
| * All code and executables are provided "as is" with no warranty either express or implied. | |
| * The author accepts no liability for any damage or loss of business that this product may cause. | |
| * | |
| * Code change notes: | |
| * | |
| * Author Change Date | |
| * ****************************************************************************** | |
| * Mats Alm Added 2011-01-01 | |
| * Mats Alm Applying patch submitted 2011-11-14 | |
| * by Ted Heatherington | |
| * Jan Källman License changed GPL-->LGPL 2011-12-27 | |
| * Raziq York Added support for Any type 2014-08-08 | |
| *******************************************************************************/ | |
| using System; | |
| using System.Collections; | |
| using System.Collections.Generic; | |
| using System.Collections.Immutable; | |
| using System.Xml; | |
| namespace AppsheetEpplus; | |
| /// <summary> | |
| /// <para> | |
| /// Collection of <see cref="ExcelDataValidation"/>. This class is providing the API for EPPlus data validation. | |
| /// </para> | |
| /// <para> | |
| /// The public methods of this class (Add[...]Validation) will create a datavalidation entry in the worksheet. When this | |
| /// validation has been created changes to the properties will affect the workbook immediately. | |
| /// </para> | |
| /// <para> | |
| /// Each type of validation has either a formula or a typed value/values, except for custom validation which has a formula only. | |
| /// </para> | |
| /// <code> | |
| /// // Add a date time validation | |
| /// var validation = worksheet.DataValidation.AddDateTimeValidation("A1"); | |
| /// // set validation properties | |
| /// validation.ShowErrorMessage = true; | |
| /// validation.ErrorTitle = "An invalid date was entered"; | |
| /// validation.Error = "The date must be between 2011-01-31 and 2011-12-31"; | |
| /// validation.Prompt = "Enter date here"; | |
| /// validation.Formula.Value = DateTime.Parse("2011-01-01"); | |
| /// validation.Formula2.Value = DateTime.Parse("2011-12-31"); | |
| /// validation.Operator = ExcelDataValidationOperator.between; | |
| /// </code> | |
| /// </summary> | |
| public class ExcelDataValidationCollection : XmlHelper, IEnumerable<IExcelDataValidation> { | |
| private readonly List<IExcelDataValidation> _validations = new(); | |
| private readonly ExcelWorksheet _worksheet; | |
| private const string _dataValidationPath = "//d:dataValidations"; | |
| private readonly string DataValidationItemsPath = string.Format( | |
| "{0}/d:dataValidation", | |
| _dataValidationPath); | |
| protected override ImmutableArray<string> SchemaNodeOrder => | |
| ExcelWorksheet.WorksheetSchemaNodeOrder; | |
| /// <summary> | |
| /// Constructor | |
| /// </summary> | |
| /// <param name="worksheet"></param> | |
| internal ExcelDataValidationCollection(ExcelWorksheet worksheet) | |
| : base(worksheet.NameSpaceManager, worksheet.WorksheetXml.DocumentElement) { | |
| ArgumentNullException.ThrowIfNull(worksheet); | |
| _worksheet = worksheet; | |
| // check existing nodes and load them | |
| var dataValidationNodes = worksheet.WorksheetXml.SelectNodes( | |
| DataValidationItemsPath, | |
| worksheet.NameSpaceManager); | |
| if (dataValidationNodes != null && dataValidationNodes.Count > 0) { | |
| foreach (XmlNode node in dataValidationNodes) { | |
| if (node.Attributes["sqref"] == null) { | |
| continue; | |
| } | |
| var addr = node.Attributes["sqref"].Value; | |
| var typeSchema = node.Attributes["type"] != null ? node.Attributes["type"].Value : ""; | |
| var type = ExcelDataValidationType.GetBySchemaName(typeSchema); | |
| _validations.Add(ExcelDataValidationFactory.Create(type, worksheet, addr, node)); | |
| } | |
| } | |
| } | |
| private void EnsureRootElementExists() { | |
| var node = _worksheet.WorksheetXml.SelectSingleNode( | |
| _dataValidationPath, | |
| _worksheet.NameSpaceManager); | |
| if (node == null) { | |
| CreateNode(_dataValidationPath.TrimStart('/')); | |
| } | |
| } | |
| /// <summary> | |
| /// Validates address - not empty, collisions | |
| /// </summary> | |
| /// <param name="address"></param> | |
| /// <param name="validatingValidation"></param> | |
| private void ValidateAddress(string address, IExcelDataValidation validatingValidation) { | |
| ArgumentException.ThrowIfNullOrEmpty(address); | |
| // ensure that the new address does not collide with an existing validation. | |
| var newAddress = new ExcelAddress(address); | |
| if (_validations.Count > 0) { | |
| foreach (var validation in _validations) { | |
| if (validatingValidation != null && validatingValidation == validation) { | |
| continue; | |
| } | |
| var result = validation.Address.Collide(newAddress); | |
| if (result != ExcelAddressBase.eAddressCollition.No) { | |
| throw new InvalidOperationException( | |
| string.Format( | |
| "The address ({0}) collides with an existing validation ({1})", | |
| address, | |
| validation.Address.Address)); | |
| } | |
| } | |
| } | |
| } | |
| private void ValidateAddress(string address) { | |
| ValidateAddress(address, null); | |
| } | |
| /// <summary> | |
| /// Validates all data validations. | |
| /// </summary> | |
| internal void ValidateAll() { | |
| foreach (var validation in _validations) { | |
| validation.Validate(); | |
| ValidateAddress(validation.Address.Address, validation); | |
| } | |
| } | |
| /// <summary> | |
| /// Adds a <see cref="ExcelDataValidationAny"/> to the worksheet. | |
| /// </summary> | |
| /// <param name="address">The range/address to validate</param> | |
| /// <returns></returns> | |
| public IExcelDataValidationAny AddAnyValidation(string address) { | |
| ValidateAddress(address); | |
| EnsureRootElementExists(); | |
| var item = new ExcelDataValidationAny(_worksheet, address, ExcelDataValidationType.Any); | |
| _validations.Add(item); | |
| return item; | |
| } | |
| /// <summary> | |
| /// Adds an <see cref="IExcelDataValidationInt"/> to the worksheet. Whole means that the only accepted values | |
| /// are integer values. | |
| /// </summary> | |
| /// <param name="address">the range/address to validate</param> | |
| public IExcelDataValidationInt AddIntegerValidation(string address) { | |
| ValidateAddress(address); | |
| EnsureRootElementExists(); | |
| var item = new ExcelDataValidationInt(_worksheet, address, ExcelDataValidationType.Whole); | |
| _validations.Add(item); | |
| return item; | |
| } | |
| /// <summary> | |
| /// Addes an <see cref="IExcelDataValidationDecimal"/> to the worksheet. The only accepted values are | |
| /// decimal values. | |
| /// </summary> | |
| /// <param name="address">The range/address to validate</param> | |
| /// <returns></returns> | |
| public IExcelDataValidationDecimal AddDecimalValidation(string address) { | |
| ValidateAddress(address); | |
| EnsureRootElementExists(); | |
| var item = new ExcelDataValidationDecimal(_worksheet, address, ExcelDataValidationType.Decimal); | |
| _validations.Add(item); | |
| return item; | |
| } | |
| /// <summary> | |
| /// Adds an <see cref="IExcelDataValidationList"/> to the worksheet. The accepted values are defined | |
| /// in a list. | |
| /// </summary> | |
| /// <param name="address">The range/address to validate</param> | |
| /// <returns></returns> | |
| public IExcelDataValidationList AddListValidation(string address) { | |
| ValidateAddress(address); | |
| EnsureRootElementExists(); | |
| var item = new ExcelDataValidationList(_worksheet, address, ExcelDataValidationType.List); | |
| _validations.Add(item); | |
| return item; | |
| } | |
| /// <summary> | |
| /// Adds an <see cref="IExcelDataValidationInt"/> regarding text length to the worksheet. | |
| /// </summary> | |
| /// <param name="address">The range/address to validate</param> | |
| /// <returns></returns> | |
| public IExcelDataValidationInt AddTextLengthValidation(string address) { | |
| ValidateAddress(address); | |
| EnsureRootElementExists(); | |
| var item = new ExcelDataValidationInt(_worksheet, address, ExcelDataValidationType.TextLength); | |
| _validations.Add(item); | |
| return item; | |
| } | |
| /// <summary> | |
| /// Adds an <see cref="IExcelDataValidationDateTime"/> to the worksheet. | |
| /// </summary> | |
| /// <param name="address">The range/address to validate</param> | |
| /// <returns></returns> | |
| public IExcelDataValidationDateTime AddDateTimeValidation(string address) { | |
| ValidateAddress(address); | |
| EnsureRootElementExists(); | |
| var item = new ExcelDataValidationDateTime( | |
| _worksheet, | |
| address, | |
| ExcelDataValidationType.DateTime); | |
| _validations.Add(item); | |
| return item; | |
| } | |
| public IExcelDataValidationTime AddTimeValidation(string address) { | |
| ValidateAddress(address); | |
| EnsureRootElementExists(); | |
| var item = new ExcelDataValidationTime(_worksheet, address, ExcelDataValidationType.Time); | |
| _validations.Add(item); | |
| return item; | |
| } | |
| /// <summary> | |
| /// Adds a <see cref="ExcelDataValidationCustom"/> to the worksheet. | |
| /// </summary> | |
| /// <param name="address">The range/address to validate</param> | |
| /// <returns></returns> | |
| public IExcelDataValidationCustom AddCustomValidation(string address) { | |
| ValidateAddress(address); | |
| EnsureRootElementExists(); | |
| var item = new ExcelDataValidationCustom(_worksheet, address, ExcelDataValidationType.Custom); | |
| _validations.Add(item); | |
| return item; | |
| } | |
| /// <summary> | |
| /// Removes an <see cref="ExcelDataValidation"/> from the collection. | |
| /// </summary> | |
| /// <param name="item">The item to remove</param> | |
| /// <returns>True if remove succeeds, otherwise false</returns> | |
| /// <exception cref="ArgumentNullException">if <paramref name="item"/> is null</exception> | |
| public bool Remove(IExcelDataValidation item) { | |
| if (!(item is ExcelDataValidation validation)) { | |
| throw new InvalidCastException( | |
| "The supplied item must inherit OfficeOpenXml.DataValidation.ExcelDataValidation"); | |
| } | |
| ArgumentNullException.ThrowIfNull(item); | |
| TopNode.RemoveChild(validation.TopNode); | |
| return _validations.Remove(validation); | |
| } | |
| /// <summary> | |
| /// Number of validations | |
| /// </summary> | |
| public int Count => _validations.Count; | |
| /// <summary> | |
| /// Index operator, returns by 0-based index | |
| /// </summary> | |
| /// <param name="index"></param> | |
| /// <returns></returns> | |
| public IExcelDataValidation this[int index] { | |
| get => _validations[index]; | |
| set => _validations[index] = value; | |
| } | |
| /// <summary> | |
| /// Index operator, returns a data validation which address partly or exactly matches the searched address. | |
| /// </summary> | |
| /// <param name="address">A cell address or range</param> | |
| /// <returns>A <see cref="ExcelDataValidation"/> or null if no match</returns> | |
| public IExcelDataValidation this[string address] { | |
| get { | |
| var searchedAddress = new ExcelAddress(address); | |
| return _validations.Find(x => | |
| x.Address.Collide(searchedAddress) != ExcelAddressBase.eAddressCollition.No); | |
| } | |
| } | |
| /// <summary> | |
| /// Returns all validations that matches the supplied predicate <paramref name="match"/>. | |
| /// </summary> | |
| /// <param name="match">predicate to filter out matching validations</param> | |
| /// <returns></returns> | |
| public IEnumerable<IExcelDataValidation> FindAll(Predicate<IExcelDataValidation> match) { | |
| return _validations.FindAll(match); | |
| } | |
| /// <summary> | |
| /// Returns the first matching validation. | |
| /// </summary> | |
| /// <param name="match"></param> | |
| /// <returns></returns> | |
| public IExcelDataValidation Find(Predicate<IExcelDataValidation> match) { | |
| return _validations.Find(match); | |
| } | |
| /// <summary> | |
| /// Removes all validations from the collection. | |
| /// </summary> | |
| public void Clear() { | |
| DeleteAllNode(DataValidationItemsPath.TrimStart('/')); | |
| _validations.Clear(); | |
| } | |
| /// <summary> | |
| /// Removes the validations that matches the predicate | |
| /// </summary> | |
| /// <param name="match"></param> | |
| public void RemoveAll(Predicate<IExcelDataValidation> match) { | |
| var matches = _validations.FindAll(match); | |
| foreach (var m in matches) { | |
| if (!(m is ExcelDataValidation validation)) { | |
| throw new InvalidCastException( | |
| "The supplied item must inherit OfficeOpenXml.DataValidation.ExcelDataValidation"); | |
| } | |
| TopNode | |
| .SelectSingleNode(_dataValidationPath.TrimStart('/'), NameSpaceManager) | |
| .RemoveChild(validation.TopNode); | |
| } | |
| _validations.RemoveAll(match); | |
| } | |
| IEnumerator<IExcelDataValidation> IEnumerable<IExcelDataValidation>.GetEnumerator() { | |
| return _validations.GetEnumerator(); | |
| } | |
| IEnumerator IEnumerable.GetEnumerator() { | |
| return _validations.GetEnumerator(); | |
| } | |
| } |