/* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for Additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ namespace HH.WMS.Utils.NPOI.HSSF.UserModel { using System; using HH.WMS.Utils.NPOI.HSSF.Model; using HH.WMS.Utils.NPOI.SS.Formula.PTG; using HH.WMS.Utils.NPOI.SS.Formula; using HH.WMS.Utils.NPOI.SS.UserModel; using System.Text; using HH.WMS.Utils.NPOI.SS.Util; using System.Globalization; /** * * @author Josh Micich */ public class DVConstraint : IDataValidationConstraint { /* package */ public class FormulaPair { private Ptg[] _formula1; private Ptg[] _formula2; public FormulaPair(Ptg[] formula1, Ptg[] formula2) { _formula1 = formula1; _formula2 = formula2; } public Ptg[] Formula1 { get { return _formula1; } } public Ptg[] Formula2 { get { return _formula2; } } } // convenient access to ValidationType namespace //private static ValidationType VT = null; private int _validationType; private int _operator; private String[] _explicitListValues; private String _formula1; private String _formula2; private Double _value1; private Double _value2; private DVConstraint(int validationType, int comparisonOperator, String formulaA, String formulaB, Double value1, Double value2, String[] excplicitListValues) { _validationType = validationType; _operator = comparisonOperator; _formula1 = formulaA; _formula2 = formulaB; _value1 = value1; _value2 = value2; _explicitListValues = excplicitListValues; } /** * Creates a list constraint */ private DVConstraint(String listFormula, String[] excplicitListValues) : this(ValidationType.LIST, OperatorType.IGNORED, listFormula, null, Double.NaN, Double.NaN, excplicitListValues) { ; } /** * Creates a number based data validation constraint. The text values entered for expr1 and expr2 * can be either standard Excel formulas or formatted number values. If the expression starts * with '=' it is Parsed as a formula, otherwise it is Parsed as a formatted number. * * @param validationType one of {@link HH.WMS.Utils.NPOI.SS.UserModel.DataValidationConstraint.ValidationType#ANY}, * {@link HH.WMS.Utils.NPOI.SS.UserModel.DataValidationConstraint.ValidationType#DECIMAL}, * {@link HH.WMS.Utils.NPOI.SS.UserModel.DataValidationConstraint.ValidationType#INTEGER}, * {@link HH.WMS.Utils.NPOI.SS.UserModel.DataValidationConstraint.ValidationType#TEXT_LENGTH} * @param comparisonOperator any constant from {@link HH.WMS.Utils.NPOI.SS.UserModel.DataValidationConstraint.OperatorType} enum * @param expr1 date formula (when first char is '=') or formatted number value * @param expr2 date formula (when first char is '=') or formatted number value */ public static DVConstraint CreateNumericConstraint(int validationType, int comparisonOperator, String expr1, String expr2) { switch (validationType) { case ValidationType.ANY: if (expr1 != null || expr2 != null) { throw new ArgumentException("expr1 and expr2 must be null for validation type 'any'"); } break; case ValidationType.DECIMAL: case ValidationType.INTEGER: case ValidationType.TEXT_LENGTH: if (expr1 == null) { throw new ArgumentException("expr1 must be supplied"); } OperatorType.ValidateSecondArg(comparisonOperator, expr2); break; default: throw new ArgumentException("Validation Type (" + validationType + ") not supported with this method"); } // formula1 and value1 are mutually exclusive String formula1 = GetFormulaFromTextExpression(expr1); Double value1 = formula1 == null ? ConvertNumber(expr1) : double.NaN; // formula2 and value2 are mutually exclusive String formula2 = GetFormulaFromTextExpression(expr2); Double value2 = formula2 == null ? ConvertNumber(expr2) : double.NaN; return new DVConstraint(validationType, comparisonOperator, formula1, formula2, value1, value2, null); } public static DVConstraint CreateFormulaListConstraint(String listFormula) { return new DVConstraint(listFormula, null); } public static DVConstraint CreateExplicitListConstraint(String[] explicitListValues) { return new DVConstraint(null, explicitListValues); } /** * Creates a time based data validation constraint. The text values entered for expr1 and expr2 * can be either standard Excel formulas or formatted time values. If the expression starts * with '=' it is Parsed as a formula, otherwise it is Parsed as a formatted time. To parse * formatted times, two formats are supported: "HH:MM" or "HH:MM:SS". This is contrary to * Excel which uses the default time format from the OS. * * @param comparisonOperator constant from {@link HH.WMS.Utils.NPOI.SS.UserModel.DataValidationConstraint.OperatorType} enum * @param expr1 date formula (when first char is '=') or formatted time value * @param expr2 date formula (when first char is '=') or formatted time value */ public static DVConstraint CreateTimeConstraint(int comparisonOperator, String expr1, String expr2) { if (expr1 == null) { throw new ArgumentException("expr1 must be supplied"); } OperatorType.ValidateSecondArg(comparisonOperator, expr1); // formula1 and value1 are mutually exclusive String formula1 = GetFormulaFromTextExpression(expr1); Double value1 = formula1 == null ? ConvertTime(expr1) : Double.NaN; // formula2 and value2 are mutually exclusive String formula2 = GetFormulaFromTextExpression(expr2); Double value2 = formula2 == null ? ConvertTime(expr2) : Double.NaN; return new DVConstraint(ValidationType.TIME, comparisonOperator, formula1, formula2, value1, value2, null); } /** * Creates a date based data validation constraint. The text values entered for expr1 and expr2 * can be either standard Excel formulas or formatted date values. If the expression starts * with '=' it is Parsed as a formula, otherwise it is Parsed as a formatted date (Excel uses * the same convention). To parse formatted dates, a date format needs to be specified. This * is contrary to Excel which uses the default short date format from the OS. * * @param comparisonOperator constant from {@link HH.WMS.Utils.NPOI.SS.UserModel.DataValidationConstraint.OperatorType} enum * @param expr1 date formula (when first char is '=') or formatted date value * @param expr2 date formula (when first char is '=') or formatted date value * @param dateFormat ignored if both expr1 and expr2 are formulas. Default value is "YYYY/MM/DD" * otherwise any other valid argument for SimpleDateFormat can be used * @see SimpleDateFormat */ public static DVConstraint CreateDateConstraint(int comparisonOperator, String expr1, String expr2, String dateFormat) { if (expr1 == null) { throw new ArgumentException("expr1 must be supplied"); } OperatorType.ValidateSecondArg(comparisonOperator, expr2); SimpleDateFormat df = dateFormat == null ? null : new SimpleDateFormat(dateFormat); // formula1 and value1 are mutually exclusive String formula1 = GetFormulaFromTextExpression(expr1); Double value1 = formula1 == null ? ConvertDate(expr1, df) : Double.NaN; // formula2 and value2 are mutually exclusive String formula2 = GetFormulaFromTextExpression(expr2); Double value2 = formula2 == null ? ConvertDate(expr2, df) : Double.NaN; return new DVConstraint(ValidationType.DATE, comparisonOperator, formula1, formula2, value1, value2, null); } /** * Distinguishes formula expressions from simple value expressions. This logic is only * required by a few factory methods in this class that create data validation constraints * from more or less the same parameters that would have been entered in the Excel UI. The * data validation dialog box uses the convention that formulas begin with '='. Other methods * in this class follow the POI convention (formulas and values are distinct), so the '=' * convention is not used there. * * @param textExpr a formula or value expression * @return all text After '=' if textExpr begins with '='. Otherwise null if textExpr does not begin with '=' */ private static String GetFormulaFromTextExpression(String textExpr) { if (textExpr == null) { return null; } if (textExpr.Length < 1) { throw new ArgumentException("Empty string is not a valid formula/value expression"); } if (textExpr[0] == '=') { return textExpr.Substring(1); } return null; } /** * @return null if numberStr is null */ private static Double ConvertNumber(String numberStr) { if (numberStr == null) { return Double.NaN; } try { return double.Parse(numberStr, CultureInfo.CurrentCulture); } catch (FormatException) { throw new InvalidOperationException("The supplied text '" + numberStr + "' could not be parsed as a number"); } } /** * @return null if timeStr is null */ private static Double ConvertTime(String timeStr) { if (timeStr == null) { return Double.NaN; } return HSSFDateUtil.ConvertTime(timeStr); } /** * @param dateFormat pass null for default YYYYMMDD * @return null if timeStr is null */ private static Double ConvertDate(String dateStr, SimpleDateFormat dateFormat) { if (dateStr == null) { return Double.NaN; } DateTime dateVal; if (dateFormat == null) { dateVal = HSSFDateUtil.ParseYYYYMMDDDate(dateStr); } else { try { dateVal = DateTime.Parse(dateStr, CultureInfo.CurrentCulture); } catch (FormatException e) { throw new InvalidOperationException("Failed to parse date '" + dateStr + "' using specified format '" + dateFormat + "'", e); } } return HSSFDateUtil.GetExcelDate(dateVal); } public static DVConstraint CreateCustomFormulaConstraint(String formula) { if (formula == null) { throw new ArgumentException("formula must be supplied"); } return new DVConstraint(ValidationType.FORMULA, OperatorType.IGNORED, formula, null, double.NaN, double.NaN, null); } /* (non-Javadoc) * @see HH.WMS.Utils.NPOI.HSSF.UserModel.DataValidationConstraint#getValidationType() */ public int GetValidationType() { return _validationType; } /** * Convenience method * @return true if this constraint is a 'list' validation */ public bool IsListValidationType { get { return _validationType == ValidationType.LIST; } } /** * Convenience method * @return true if this constraint is a 'list' validation with explicit values */ public bool IsExplicitList { get { return _validationType == ValidationType.LIST && _explicitListValues != null; } } public int Operator { get { return _operator; } set { _operator = value; } } public String[] ExplicitListValues { get { return _explicitListValues; } set { if (_validationType != ValidationType.LIST) { throw new InvalidOperationException("Cannot setExplicitListValues on non-list constraint"); } _formula1 = null; _explicitListValues = value; } } /* (non-Javadoc) * @see HH.WMS.Utils.NPOI.HSSF.UserModel.DataValidationConstraint#getFormula1() */ public String Formula1 { get { return _formula1; } set { _value1 = double.NaN; _explicitListValues = null; _formula1 = value; } } /* (non-Javadoc) * @see HH.WMS.Utils.NPOI.HSSF.UserModel.DataValidationConstraint#getFormula2() */ public String Formula2 { get { return _formula2; } set { _value2 = double.NaN; _formula2 = value; } } /** * @return the numeric value for expression 1. May be null */ public Double Value1 { get { return _value1; } set { _formula1 = null; _value1 = value; } } /** * @return the numeric value for expression 2. May be null */ public Double Value2 { get { return _value2; } set { _formula2 = null; _value2 = value; } } /** * @return both Parsed formulas (for expression 1 and 2). */ /* package */ public FormulaPair CreateFormulas(HSSFSheet sheet) { Ptg[] formula1; Ptg[] formula2; if (IsListValidationType) { formula1 = CreateListFormula(sheet); formula2 = Ptg.EMPTY_PTG_ARRAY; } else { formula1 = ConvertDoubleFormula(_formula1, _value1, sheet); formula2 = ConvertDoubleFormula(_formula2, _value2, sheet); } return new FormulaPair(formula1, formula2); } private Ptg[] CreateListFormula(HSSFSheet sheet) { if (_explicitListValues == null) { IWorkbook wb = sheet.Workbook; // formula is Parsed with slightly different RVA rules: (root node type must be 'reference') return HSSFFormulaParser.Parse(_formula1, (HSSFWorkbook)wb, FormulaType.DATAVALIDATION_LIST, wb.GetSheetIndex(sheet)); // To do: Excel places restrictions on the available operations within a list formula. // Some things like union and intersection are not allowed. } // explicit list was provided StringBuilder sb = new StringBuilder(_explicitListValues.Length * 16); for (int i = 0; i < _explicitListValues.Length; i++) { if (i > 0) { sb.Append('\0'); // list delimiter is the nul char } sb.Append(_explicitListValues[i]); } return new Ptg[] { new StringPtg(sb.ToString()), }; } /** * @return The Parsed token array representing the formula or value specified. * Empty array if both formula and value are null */ private static Ptg[] ConvertDoubleFormula(String formula, Double value, HSSFSheet sheet) { if (formula == null) { if (double.IsNaN(value)) { return Ptg.EMPTY_PTG_ARRAY; } return new Ptg[] { new NumberPtg(value), }; } if (!double.IsNaN(value)) { throw new InvalidOperationException("Both formula and value cannot be present"); } IWorkbook wb = sheet.Workbook; return HSSFFormulaParser.Parse(formula, (HSSFWorkbook)wb, FormulaType.CELL, wb.GetSheetIndex(sheet)); } } }