|  | /******************************************************************************* | 
|  | * 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 | 
|  | * Jan Källman		                License changed GPL-->LGPL  2011-12-27 | 
|  | *******************************************************************************/ | 
|  | using System; | 
|  | using System.Collections.Generic; | 
|  | using System.Globalization; | 
|  | using System.Linq; | 
|  | using System.Text; | 
|  | using OfficeOpenXml.Utils; | 
|  | using System.Xml; | 
|  | using System.Text.RegularExpressions; | 
|  | using OfficeOpenXml.DataValidation.Formulas.Contracts; | 
|  | using OfficeOpenXml.DataValidation.Contracts; | 
|  |  | 
|  | namespace OfficeOpenXml.DataValidation | 
|  | { | 
|  | /// <summary> | 
|  | /// Excel datavalidation | 
|  | /// </summary> | 
|  | public abstract class ExcelDataValidation : XmlHelper, IExcelDataValidation | 
|  | { | 
|  | private const string _itemElementNodeName = "d:dataValidation"; | 
|  |  | 
|  |  | 
|  | private readonly string _errorStylePath = "@errorStyle"; | 
|  | private readonly string _errorTitlePath = "@errorTitle"; | 
|  | private readonly string _errorPath = "@error"; | 
|  | private readonly string _promptTitlePath = "@promptTitle"; | 
|  | private readonly string _promptPath = "@prompt"; | 
|  | private readonly string _operatorPath = "@operator"; | 
|  | private readonly string _showErrorMessagePath = "@showErrorMessage"; | 
|  | private readonly string _showInputMessagePath = "@showInputMessage"; | 
|  | private readonly string _typeMessagePath = "@type"; | 
|  | private readonly string _sqrefPath = "@sqref"; | 
|  | private readonly string _allowBlankPath = "@allowBlank"; | 
|  | protected readonly string _formula1Path = "d:formula1"; | 
|  | protected readonly string _formula2Path = "d:formula2"; | 
|  |  | 
|  | internal ExcelDataValidation(ExcelWorksheet worksheet, string address, ExcelDataValidationType validationType) | 
|  | : this(worksheet, address, validationType, null) | 
|  | { } | 
|  |  | 
|  | /// <summary> | 
|  | /// Constructor | 
|  | /// </summary> | 
|  | /// <param name="worksheet">worksheet that owns the validation</param> | 
|  | /// <param name="itemElementNode">Xml top node (dataValidations)</param> | 
|  | /// <param name="validationType">Data validation type</param> | 
|  | /// <param name="address">address for data validation</param> | 
|  | internal ExcelDataValidation(ExcelWorksheet worksheet, string address, ExcelDataValidationType validationType, XmlNode itemElementNode) | 
|  | : this(worksheet, address, validationType, itemElementNode, null) | 
|  | { | 
|  |  | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Constructor | 
|  | /// </summary> | 
|  | /// <param name="worksheet">worksheet that owns the validation</param> | 
|  | /// <param name="itemElementNode">Xml top node (dataValidations) when importing xml</param> | 
|  | /// <param name="validationType">Data validation type</param> | 
|  | /// <param name="address">address for data validation</param> | 
|  | /// <param name="namespaceManager">Xml Namespace manager</param> | 
|  | internal ExcelDataValidation(ExcelWorksheet worksheet, string address, ExcelDataValidationType validationType, XmlNode itemElementNode, XmlNamespaceManager namespaceManager) | 
|  | : base(namespaceManager != null ? namespaceManager : worksheet.NameSpaceManager) | 
|  | { | 
|  | Require.Argument(address).IsNotNullOrEmpty("address"); | 
|  | address = CheckAndFixRangeAddress(address); | 
|  | if (itemElementNode == null) | 
|  | { | 
|  | //var xmlDoc = worksheet.WorksheetXml; | 
|  | TopNode = worksheet.WorksheetXml.SelectSingleNode("//d:dataValidations", worksheet.NameSpaceManager); | 
|  | // did not succeed using the XmlHelper methods here... so I'm creating the new node using XmlDocument... | 
|  | var nsUri = NameSpaceManager.LookupNamespace("d"); | 
|  | //itemElementNode = TopNode.OwnerDocument.CreateElement(_itemElementNodeName, nsUri); | 
|  | itemElementNode = TopNode.OwnerDocument.CreateElement(_itemElementNodeName.Split(':')[1], nsUri); | 
|  | TopNode.AppendChild(itemElementNode); | 
|  | } | 
|  | TopNode = itemElementNode; | 
|  | ValidationType = validationType; | 
|  | Address = new ExcelAddress(address); | 
|  | Init(); | 
|  | } | 
|  |  | 
|  | private void Init() | 
|  | { | 
|  | // set schema node order | 
|  | SchemaNodeOrder = new string[]{ | 
|  | "type", | 
|  | "errorStyle", | 
|  | "operator", | 
|  | "allowBlank", | 
|  | "showInputMessage", | 
|  | "showErrorMessage", | 
|  | "errorTitle", | 
|  | "error", | 
|  | "promptTitle", | 
|  | "prompt", | 
|  | "sqref", | 
|  | "formula1", | 
|  | "formula2" | 
|  | }; | 
|  | } | 
|  |  | 
|  | private string CheckAndFixRangeAddress(string address) | 
|  | { | 
|  | if (address.Contains(',')) | 
|  | { | 
|  | throw new FormatException("Multiple addresses may not be commaseparated, use space instead"); | 
|  | } | 
|  | address = address.ToUpper(CultureInfo.InvariantCulture); | 
|  | if (Regex.IsMatch(address, @"[A-Z]+:[A-Z]+")) | 
|  | { | 
|  | address = AddressUtility.ParseEntireColumnSelections(address); | 
|  | } | 
|  | return address; | 
|  | } | 
|  |  | 
|  | private void SetNullableBoolValue(string path, bool? val) | 
|  | { | 
|  | if (val.HasValue) | 
|  | { | 
|  | SetXmlNodeBool(path, val.Value); | 
|  | } | 
|  | else | 
|  | { | 
|  | DeleteNode(path); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// This method will validate the state of the validation | 
|  | /// </summary> | 
|  | /// <exception cref="InvalidOperationException">If the state breaks the rules of the validation</exception> | 
|  | public virtual void Validate() | 
|  | { | 
|  | var address = Address.Address; | 
|  | // validate Formula1 | 
|  | if (string.IsNullOrEmpty(Formula1Internal)) | 
|  | { | 
|  | throw new InvalidOperationException("Validation of " + address + " failed: Formula1 cannot be empty"); | 
|  | } | 
|  | } | 
|  |  | 
|  | #region Public properties | 
|  |  | 
|  | /// <summary> | 
|  | /// True if the validation type allows operator to be set. | 
|  | /// </summary> | 
|  | public bool AllowsOperator | 
|  | { | 
|  | get | 
|  | { | 
|  | return ValidationType.AllowOperator; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Address of data validation | 
|  | /// </summary> | 
|  | public ExcelAddress Address | 
|  | { | 
|  | get | 
|  | { | 
|  | return new ExcelAddress(GetXmlNodeString(_sqrefPath)); | 
|  | } | 
|  | private set | 
|  | { | 
|  | var address = AddressUtility.ParseEntireColumnSelections(value.Address); | 
|  | SetXmlNodeString(_sqrefPath, address); | 
|  | } | 
|  | } | 
|  | /// <summary> | 
|  | /// Validation type | 
|  | /// </summary> | 
|  | public ExcelDataValidationType ValidationType | 
|  | { | 
|  | get | 
|  | { | 
|  | var typeString = GetXmlNodeString(_typeMessagePath); | 
|  | return ExcelDataValidationType.GetBySchemaName(typeString); | 
|  | } | 
|  | private set | 
|  | { | 
|  | SetXmlNodeString(_typeMessagePath, value.SchemaName, true); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Operator for comparison between the entered value and Formula/Formulas. | 
|  | /// </summary> | 
|  | public ExcelDataValidationOperator Operator | 
|  | { | 
|  | get | 
|  | { | 
|  | var operatorString = GetXmlNodeString(_operatorPath); | 
|  | if (!string.IsNullOrEmpty(operatorString)) | 
|  | { | 
|  | return (ExcelDataValidationOperator)Enum.Parse(typeof(ExcelDataValidationOperator), operatorString); | 
|  | } | 
|  | return default(ExcelDataValidationOperator); | 
|  | } | 
|  | set | 
|  | { | 
|  | if (!ValidationType.AllowOperator) | 
|  | { | 
|  | throw new InvalidOperationException("The current validation type does not allow operator to be set"); | 
|  | } | 
|  | SetXmlNodeString(_operatorPath, value.ToString()); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Warning style | 
|  | /// </summary> | 
|  | public ExcelDataValidationWarningStyle ErrorStyle | 
|  | { | 
|  | get | 
|  | { | 
|  | var errorStyleString = GetXmlNodeString(_errorStylePath); | 
|  | if (!string.IsNullOrEmpty(errorStyleString)) | 
|  | { | 
|  | return (ExcelDataValidationWarningStyle)Enum.Parse(typeof(ExcelDataValidationWarningStyle), errorStyleString); | 
|  | } | 
|  | return ExcelDataValidationWarningStyle.undefined; | 
|  | } | 
|  | set | 
|  | { | 
|  | if (value == ExcelDataValidationWarningStyle.undefined) | 
|  | { | 
|  | DeleteNode(_errorStylePath); | 
|  | } | 
|  | SetXmlNodeString(_errorStylePath, value.ToString()); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// True if blanks should be allowed | 
|  | /// </summary> | 
|  | public bool? AllowBlank | 
|  | { | 
|  | get | 
|  | { | 
|  | return GetXmlNodeBoolNullable(_allowBlankPath); | 
|  | } | 
|  | set | 
|  | { | 
|  | SetNullableBoolValue(_allowBlankPath, value); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// True if input message should be shown | 
|  | /// </summary> | 
|  | public bool? ShowInputMessage | 
|  | { | 
|  | get | 
|  | { | 
|  | return GetXmlNodeBoolNullable(_showInputMessagePath); | 
|  | } | 
|  | set | 
|  | { | 
|  | SetNullableBoolValue(_showInputMessagePath, value); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// True if error message should be shown | 
|  | /// </summary> | 
|  | public bool? ShowErrorMessage | 
|  | { | 
|  | get | 
|  | { | 
|  | return GetXmlNodeBoolNullable(_showErrorMessagePath); | 
|  | } | 
|  | set | 
|  | { | 
|  | SetNullableBoolValue(_showErrorMessagePath, value); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Title of error message box | 
|  | /// </summary> | 
|  | public string ErrorTitle | 
|  | { | 
|  | get | 
|  | { | 
|  | return GetXmlNodeString(_errorTitlePath); | 
|  | } | 
|  | set | 
|  | { | 
|  | SetXmlNodeString(_errorTitlePath, value); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Error message box text | 
|  | /// </summary> | 
|  | public string Error | 
|  | { | 
|  | get | 
|  | { | 
|  | return GetXmlNodeString(_errorPath); | 
|  | } | 
|  | set | 
|  | { | 
|  | SetXmlNodeString(_errorPath, value); | 
|  | } | 
|  | } | 
|  |  | 
|  | public string PromptTitle | 
|  | { | 
|  | get | 
|  | { | 
|  | return GetXmlNodeString(_promptTitlePath); | 
|  | } | 
|  | set | 
|  | { | 
|  | SetXmlNodeString(_promptTitlePath, value); | 
|  | } | 
|  | } | 
|  |  | 
|  | public string Prompt | 
|  | { | 
|  | get | 
|  | { | 
|  | return GetXmlNodeString(_promptPath); | 
|  | } | 
|  | set | 
|  | { | 
|  | SetXmlNodeString(_promptPath, value); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Formula 1 | 
|  | /// </summary> | 
|  | protected string Formula1Internal | 
|  | { | 
|  | get | 
|  | { | 
|  | return GetXmlNodeString(_formula1Path); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Formula 2 | 
|  | /// </summary> | 
|  | protected string Formula2Internal | 
|  | { | 
|  | get | 
|  | { | 
|  | return GetXmlNodeString(_formula2Path); | 
|  | } | 
|  | } | 
|  |  | 
|  | #endregion | 
|  |  | 
|  | protected void SetValue<T>(Nullable<T> val, string path) | 
|  | where T : struct | 
|  | { | 
|  | if (!val.HasValue) | 
|  | { | 
|  | DeleteNode(path); | 
|  | } | 
|  | var stringValue = val.Value.ToString().Replace(',', '.'); | 
|  | SetXmlNodeString(path, stringValue); | 
|  | } | 
|  | } | 
|  | } |