/* 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 2013-12-03 *******************************************************************************/ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using OfficeOpenXml.FormulaParsing.ExpressionGraph; using System.Globalization; using OfficeOpenXml.FormulaParsing.Utilities; using OfficeOpenXml.FormulaParsing.Exceptions; using System.Collections; namespace OfficeOpenXml.FormulaParsing.Excel.Functions { /// /// Base class for Excel function implementations. /// public abstract class ExcelFunction { public ExcelFunction() : this(new ArgumentCollectionUtil(), new ArgumentParsers(), new CompileResultValidators()) { } public ExcelFunction( ArgumentCollectionUtil argumentCollectionUtil, ArgumentParsers argumentParsers, CompileResultValidators compileResultValidators) { _argumentCollectionUtil = argumentCollectionUtil; _argumentParsers = argumentParsers; _compileResultValidators = compileResultValidators; } private readonly ArgumentCollectionUtil _argumentCollectionUtil; private readonly ArgumentParsers _argumentParsers; private readonly CompileResultValidators _compileResultValidators; /// /// /// /// Arguments to the function, each argument can contain primitive types, lists or Excel ranges /// The contains various data that can be useful in functions. /// A containing the calculated value public abstract CompileResult Execute(IEnumerable arguments, ParsingContext context); /// /// If overridden, this method is called before Execute is called. /// /// public virtual void BeforeInvoke(ParsingContext context) { } public virtual bool IsLookupFuction { get { return false; } } public virtual bool IsErrorHandlingFunction { get { return false; } } /// /// Used for some Lookupfunctions to indicate that function arguments should /// not be compiled before the function is called. /// public bool SkipArgumentEvaluation { get; set; } protected object GetFirstValue(IEnumerable val) { var arg = ((IEnumerable)val).FirstOrDefault(); if(arg.Value is ExcelDataProvider.IRangeInfo) { //var r=((ExcelDataProvider.IRangeInfo)arg); var r = arg.ValueAsRangeInfo; return r.GetValue(r.Address._fromRow, r.Address._fromCol); } else { return arg==null?null:arg.Value; } } /// /// This functions validates that the supplied contains at least /// (the value of) elements. If one of the arguments is an /// Excel range the number of cells in /// that range will be counted as well. /// /// /// /// The of the that will be thrown if is not met. protected void ValidateArguments(IEnumerable arguments, int minLength, eErrorType errorTypeToThrow) { Require.That(arguments).Named("arguments").IsNotNull(); ThrowExcelErrorValueExceptionIf(() => { var nArgs = 0; if (arguments.Any()) { foreach (var arg in arguments) { nArgs++; if (nArgs >= minLength) return false; if (arg.IsExcelRange) { nArgs += arg.ValueAsRangeInfo.GetNCells(); if (nArgs >= minLength) return false; } } } return true; }, errorTypeToThrow); } /// /// This functions validates that the supplied contains at least /// (the value of) elements. If one of the arguments is an /// Excel range the number of cells in /// that range will be counted as well. /// /// /// /// protected void ValidateArguments(IEnumerable arguments, int minLength) { Require.That(arguments).Named("arguments").IsNotNull(); ThrowArgumentExceptionIf(() => { var nArgs = 0; if (arguments.Any()) { foreach (var arg in arguments) { nArgs++; if (nArgs >= minLength) return false; if (arg.IsExcelRange) { nArgs += arg.ValueAsRangeInfo.GetNCells(); if (nArgs >= minLength) return false; } } } return true; }, "Expecting at least {0} arguments", minLength.ToString()); } /// /// Returns the value of the argument att the position of the 0-based /// as an integer. /// /// /// /// Value of the argument as an integer. /// protected int ArgToInt(IEnumerable arguments, int index) { var val = arguments.ElementAt(index).ValueFirst; return (int)_argumentParsers.GetParser(DataType.Integer).Parse(val); } /// /// Returns the value of the argument att the position of the 0-based /// as a string. /// /// /// /// Value of the argument as a string. protected string ArgToString(IEnumerable arguments, int index) { var obj = arguments.ElementAt(index).ValueFirst; return obj != null ? obj.ToString() : string.Empty; } /// /// Returns the value of the argument att the position of the 0-based /// /// /// Value of the argument as a double. /// protected double ArgToDecimal(object obj) { return (double)_argumentParsers.GetParser(DataType.Decimal).Parse(obj); } /// /// Returns the value of the argument att the position of the 0-based /// as a . /// /// /// /// Value of the argument as an integer. /// protected double ArgToDecimal(IEnumerable arguments, int index) { return ArgToDecimal(arguments.ElementAt(index).Value); } protected double Divide(double left, double right) { if (System.Math.Abs(right - 0d) < double.Epsilon) { throw new ExcelErrorValueException(eErrorType.Div0); } return left/right; } protected bool IsNumericString(object value) { if (value == null || string.IsNullOrEmpty(value.ToString())) return false; return Regex.IsMatch(value.ToString(), @"^[\d]+(\,[\d])?"); } /// /// If the argument is a boolean value its value will be returned. /// If the argument is an integer value, true will be returned if its /// value is not 0, otherwise false. /// /// /// /// protected bool ArgToBool(IEnumerable arguments, int index) { var obj = arguments.ElementAt(index).Value ?? string.Empty; return (bool)_argumentParsers.GetParser(DataType.Boolean).Parse(obj); } /// /// Throws an if evaluates to true. /// /// /// /// protected void ThrowArgumentExceptionIf(Func condition, string message) { if (condition()) { throw new ArgumentException(message); } } /// /// Throws an if evaluates to true. /// /// /// /// Formats to the message string. protected void ThrowArgumentExceptionIf(Func condition, string message, params object[] formats) { message = string.Format(message, formats); ThrowArgumentExceptionIf(condition, message); } /// /// Throws an with the given set. /// /// protected void ThrowExcelErrorValueException(eErrorType errorType) { throw new ExcelErrorValueException("An excel function error occurred", ExcelErrorValue.Create(errorType)); } /// /// Throws an if evaluates to true. /// /// /// /// protected void ThrowExcelErrorValueExceptionIf(Func condition, eErrorType errorType) { if (condition()) { throw new ExcelErrorValueException("An excel function error occurred", ExcelErrorValue.Create(errorType)); } } protected bool IsNumeric(object val) { if (val == null) return false; return (val.GetType().IsPrimitive || val is double || val is decimal || val is System.DateTime || val is TimeSpan); } //protected virtual bool IsNumber(object obj) //{ // if (obj == null) return false; // return (obj is int || obj is double || obj is short || obj is decimal || obj is long); //} /// /// Helper method for comparison of two doubles. /// /// /// /// protected bool AreEqual(double d1, double d2) { return System.Math.Abs(d1 - d2) < double.Epsilon; } /// /// Will return the arguments as an enumerable of doubles. /// /// /// /// protected virtual IEnumerable ArgsToDoubleEnumerable(IEnumerable arguments, ParsingContext context) { return ArgsToDoubleEnumerable(false, arguments, context); } /// /// Will return the arguments as an enumerable of doubles. /// /// If a cell is hidden and this value is true the value of that cell will be ignored /// If a cell contains an error, that error will be ignored if this method is set to true /// /// /// protected virtual IEnumerable ArgsToDoubleEnumerable(bool ignoreHiddenCells, bool ignoreErrors, IEnumerable arguments, ParsingContext context) { return _argumentCollectionUtil.ArgsToDoubleEnumerable(ignoreHiddenCells, ignoreErrors, arguments, context); } /// /// Will return the arguments as an enumerable of doubles. /// /// If a cell is hidden and this value is true the value of that cell will be ignored /// /// /// protected virtual IEnumerable ArgsToDoubleEnumerable(bool ignoreHiddenCells, IEnumerable arguments, ParsingContext context) { return ArgsToDoubleEnumerable(ignoreHiddenCells, true, arguments, context); } /// /// Will return the arguments as an enumerable of objects. /// /// If a cell is hidden and this value is true the value of that cell will be ignored /// /// /// protected virtual IEnumerable ArgsToObjectEnumerable(bool ignoreHiddenCells, IEnumerable arguments, ParsingContext context) { return _argumentCollectionUtil.ArgsToObjectEnumerable(ignoreHiddenCells, arguments, context); } /// /// Use this method to create a result to return from Excel functions. /// /// /// /// protected CompileResult CreateResult(object result, DataType dataType) { var validator = _compileResultValidators.GetValidator(dataType); validator.Validate(result); return new CompileResult(result, dataType); } /// /// Use this method to apply a function on a collection of arguments. The /// should be modifyed in the supplied and will contain the result /// after this operation has been performed. /// /// /// /// /// protected virtual double CalculateCollection(IEnumerable collection, double result, Func action) { return _argumentCollectionUtil.CalculateCollection(collection, result, action); } /// /// if the supplied argument contains an Excel error /// an with that errorcode will be thrown /// /// /// protected void CheckForAndHandleExcelError(FunctionArgument arg) { if (arg.ValueIsExcelError) { throw (new ExcelErrorValueException(arg.ValueAsExcelErrorValue)); } } /// /// If the supplied contains an Excel error /// an with that errorcode will be thrown /// /// protected void CheckForAndHandleExcelError(ExcelDataProvider.ICellInfo cell) { if (cell.IsExcelError) { throw (new ExcelErrorValueException(ExcelErrorValue.Parse(cell.Value.ToString()))); } } protected CompileResult GetResultByObject(object result) { if (IsNumeric(result)) { return CreateResult(result, DataType.Decimal); } if (result is string) { return CreateResult(result, DataType.String); } if (ExcelErrorValue.Values.IsErrorValue(result)) { return CreateResult(result, DataType.ExcelAddress); } if (result == null) { return CompileResult.Empty; } return CreateResult(result, DataType.Enumerable); } } }