| /******************************************************************************* |
| * 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(); |
| } |
| } |