blob: 15484097ae78b39402fbee4ffec1be14c073be27 [file] [log] [blame]
/*******************************************************************************
* 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();
}
}