| /* 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 |
| { |
| /// <summary> |
| /// Base class for Excel function implementations. |
| /// </summary> |
| 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; |
| |
| /// <summary> |
| /// |
| /// </summary> |
| /// <param name="arguments">Arguments to the function, each argument can contain primitive types, lists or <see cref="ExcelDataProvider.IRangeInfo">Excel ranges</see></param> |
| /// <param name="context">The <see cref="ParsingContext"/> contains various data that can be useful in functions.</param> |
| /// <returns>A <see cref="CompileResult"/> containing the calculated value</returns> |
| public abstract CompileResult Execute(IEnumerable<FunctionArgument> arguments, ParsingContext context); |
| |
| /// <summary> |
| /// If overridden, this method is called before Execute is called. |
| /// </summary> |
| /// <param name="context"></param> |
| public virtual void BeforeInvoke(ParsingContext context) { } |
| |
| public virtual bool IsLookupFuction |
| { |
| get |
| { |
| return false; |
| } |
| } |
| |
| public virtual bool IsErrorHandlingFunction |
| { |
| get |
| { |
| return false; |
| } |
| } |
| |
| /// <summary> |
| /// Used for some Lookupfunctions to indicate that function arguments should |
| /// not be compiled before the function is called. |
| /// </summary> |
| public bool SkipArgumentEvaluation { get; set; } |
| protected object GetFirstValue(IEnumerable<FunctionArgument> val) |
| { |
| var arg = ((IEnumerable<FunctionArgument>)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; |
| } |
| } |
| /// <summary> |
| /// This functions validates that the supplied <paramref name="arguments"/> contains at least |
| /// (the value of) <paramref name="minLength"/> elements. If one of the arguments is an |
| /// <see cref="ExcelDataProvider.IRangeInfo">Excel range</see> the number of cells in |
| /// that range will be counted as well. |
| /// </summary> |
| /// <param name="arguments"></param> |
| /// <param name="minLength"></param> |
| /// <param name="errorTypeToThrow">The <see cref="eErrorType"/> of the <see cref="ExcelErrorValueException"/> that will be thrown if <paramref name="minLength"/> is not met.</param> |
| protected void ValidateArguments(IEnumerable<FunctionArgument> 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); |
| } |
| |
| /// <summary> |
| /// This functions validates that the supplied <paramref name="arguments"/> contains at least |
| /// (the value of) <paramref name="minLength"/> elements. If one of the arguments is an |
| /// <see cref="ExcelDataProvider.IRangeInfo">Excel range</see> the number of cells in |
| /// that range will be counted as well. |
| /// </summary> |
| /// <param name="arguments"></param> |
| /// <param name="minLength"></param> |
| /// <exception cref="ArgumentException"></exception> |
| protected void ValidateArguments(IEnumerable<FunctionArgument> 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()); |
| } |
| |
| /// <summary> |
| /// Returns the value of the argument att the position of the 0-based |
| /// <paramref name="index"/> as an integer. |
| /// </summary> |
| /// <param name="arguments"></param> |
| /// <param name="index"></param> |
| /// <returns>Value of the argument as an integer.</returns> |
| /// <exception cref="ExcelErrorValueException"></exception> |
| protected int ArgToInt(IEnumerable<FunctionArgument> arguments, int index) |
| { |
| var val = arguments.ElementAt(index).ValueFirst; |
| return (int)_argumentParsers.GetParser(DataType.Integer).Parse(val); |
| } |
| |
| /// <summary> |
| /// Returns the value of the argument att the position of the 0-based |
| /// <paramref name="index"/> as a string. |
| /// </summary> |
| /// <param name="arguments"></param> |
| /// <param name="index"></param> |
| /// <returns>Value of the argument as a string.</returns> |
| protected string ArgToString(IEnumerable<FunctionArgument> arguments, int index) |
| { |
| var obj = arguments.ElementAt(index).ValueFirst; |
| return obj != null ? obj.ToString() : string.Empty; |
| } |
| |
| /// <summary> |
| /// Returns the value of the argument att the position of the 0-based |
| /// </summary> |
| /// <param name="obj"></param> |
| /// <returns>Value of the argument as a double.</returns> |
| /// <exception cref="ExcelErrorValueException"></exception> |
| protected double ArgToDecimal(object obj) |
| { |
| return (double)_argumentParsers.GetParser(DataType.Decimal).Parse(obj); |
| } |
| |
| /// <summary> |
| /// Returns the value of the argument att the position of the 0-based |
| /// <paramref name="index"/> as a <see cref="System.Double"/>. |
| /// </summary> |
| /// <param name="arguments"></param> |
| /// <param name="index"></param> |
| /// <returns>Value of the argument as an integer.</returns> |
| /// <exception cref="ExcelErrorValueException"></exception> |
| protected double ArgToDecimal(IEnumerable<FunctionArgument> 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])?"); |
| } |
| |
| /// <summary> |
| /// 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. |
| /// </summary> |
| /// <param name="arguments"></param> |
| /// <param name="index"></param> |
| /// <returns></returns> |
| protected bool ArgToBool(IEnumerable<FunctionArgument> arguments, int index) |
| { |
| var obj = arguments.ElementAt(index).Value ?? string.Empty; |
| return (bool)_argumentParsers.GetParser(DataType.Boolean).Parse(obj); |
| } |
| |
| /// <summary> |
| /// Throws an <see cref="ArgumentException"/> if <paramref name="condition"/> evaluates to true. |
| /// </summary> |
| /// <param name="condition"></param> |
| /// <param name="message"></param> |
| /// <exception cref="ArgumentException"></exception> |
| protected void ThrowArgumentExceptionIf(Func<bool> condition, string message) |
| { |
| if (condition()) |
| { |
| throw new ArgumentException(message); |
| } |
| } |
| |
| /// <summary> |
| /// Throws an <see cref="ArgumentException"/> if <paramref name="condition"/> evaluates to true. |
| /// </summary> |
| /// <param name="condition"></param> |
| /// <param name="message"></param> |
| /// <param name="formats">Formats to the message string.</param> |
| protected void ThrowArgumentExceptionIf(Func<bool> condition, string message, params object[] formats) |
| { |
| message = string.Format(message, formats); |
| ThrowArgumentExceptionIf(condition, message); |
| } |
| |
| /// <summary> |
| /// Throws an <see cref="ExcelErrorValueException"/> with the given <paramref name="errorType"/> set. |
| /// </summary> |
| /// <param name="errorType"></param> |
| protected void ThrowExcelErrorValueException(eErrorType errorType) |
| { |
| throw new ExcelErrorValueException("An excel function error occurred", ExcelErrorValue.Create(errorType)); |
| } |
| |
| /// <summary> |
| /// Throws an <see cref="ArgumentException"/> if <paramref name="condition"/> evaluates to true. |
| /// </summary> |
| /// <param name="condition"></param> |
| /// <param name="errorType"></param> |
| /// <exception cref="ExcelErrorValueException"></exception> |
| protected void ThrowExcelErrorValueExceptionIf(Func<bool> 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); |
| //} |
| |
| /// <summary> |
| /// Helper method for comparison of two doubles. |
| /// </summary> |
| /// <param name="d1"></param> |
| /// <param name="d2"></param> |
| /// <returns></returns> |
| protected bool AreEqual(double d1, double d2) |
| { |
| return System.Math.Abs(d1 - d2) < double.Epsilon; |
| } |
| |
| /// <summary> |
| /// Will return the arguments as an enumerable of doubles. |
| /// </summary> |
| /// <param name="arguments"></param> |
| /// <param name="context"></param> |
| /// <returns></returns> |
| protected virtual IEnumerable<double> ArgsToDoubleEnumerable(IEnumerable<FunctionArgument> arguments, |
| ParsingContext context) |
| { |
| return ArgsToDoubleEnumerable(false, arguments, context); |
| } |
| |
| /// <summary> |
| /// Will return the arguments as an enumerable of doubles. |
| /// </summary> |
| /// <param name="ignoreHiddenCells">If a cell is hidden and this value is true the value of that cell will be ignored</param> |
| /// <param name="ignoreErrors">If a cell contains an error, that error will be ignored if this method is set to true</param> |
| /// <param name="arguments"></param> |
| /// <param name="context"></param> |
| /// <returns></returns> |
| protected virtual IEnumerable<double> ArgsToDoubleEnumerable(bool ignoreHiddenCells, bool ignoreErrors, IEnumerable<FunctionArgument> arguments, ParsingContext context) |
| { |
| return _argumentCollectionUtil.ArgsToDoubleEnumerable(ignoreHiddenCells, ignoreErrors, arguments, context); |
| } |
| |
| /// <summary> |
| /// Will return the arguments as an enumerable of doubles. |
| /// </summary> |
| /// <param name="ignoreHiddenCells">If a cell is hidden and this value is true the value of that cell will be ignored</param> |
| /// <param name="arguments"></param> |
| /// <param name="context"></param> |
| /// <returns></returns> |
| protected virtual IEnumerable<double> ArgsToDoubleEnumerable(bool ignoreHiddenCells, IEnumerable<FunctionArgument> arguments, ParsingContext context) |
| { |
| return ArgsToDoubleEnumerable(ignoreHiddenCells, true, arguments, context); |
| } |
| |
| /// <summary> |
| /// Will return the arguments as an enumerable of objects. |
| /// </summary> |
| /// <param name="ignoreHiddenCells">If a cell is hidden and this value is true the value of that cell will be ignored</param> |
| /// <param name="arguments"></param> |
| /// <param name="context"></param> |
| /// <returns></returns> |
| protected virtual IEnumerable<object> ArgsToObjectEnumerable(bool ignoreHiddenCells, IEnumerable<FunctionArgument> arguments, ParsingContext context) |
| { |
| return _argumentCollectionUtil.ArgsToObjectEnumerable(ignoreHiddenCells, arguments, context); |
| } |
| |
| /// <summary> |
| /// Use this method to create a result to return from Excel functions. |
| /// </summary> |
| /// <param name="result"></param> |
| /// <param name="dataType"></param> |
| /// <returns></returns> |
| protected CompileResult CreateResult(object result, DataType dataType) |
| { |
| var validator = _compileResultValidators.GetValidator(dataType); |
| validator.Validate(result); |
| return new CompileResult(result, dataType); |
| } |
| |
| /// <summary> |
| /// Use this method to apply a function on a collection of arguments. The <paramref name="result"/> |
| /// should be modifyed in the supplied <paramref name="action"/> and will contain the result |
| /// after this operation has been performed. |
| /// </summary> |
| /// <param name="collection"></param> |
| /// <param name="result"></param> |
| /// <param name="action"></param> |
| /// <returns></returns> |
| protected virtual double CalculateCollection(IEnumerable<FunctionArgument> collection, double result, Func<FunctionArgument,double,double> action) |
| { |
| return _argumentCollectionUtil.CalculateCollection(collection, result, action); |
| } |
| |
| /// <summary> |
| /// if the supplied <paramref name="arg">argument</paramref> contains an Excel error |
| /// an <see cref="ExcelErrorValueException"/> with that errorcode will be thrown |
| /// </summary> |
| /// <param name="arg"></param> |
| /// <exception cref="ExcelErrorValueException"></exception> |
| protected void CheckForAndHandleExcelError(FunctionArgument arg) |
| { |
| if (arg.ValueIsExcelError) |
| { |
| throw (new ExcelErrorValueException(arg.ValueAsExcelErrorValue)); |
| } |
| } |
| |
| /// <summary> |
| /// If the supplied <paramref name="cell"/> contains an Excel error |
| /// an <see cref="ExcelErrorValueException"/> with that errorcode will be thrown |
| /// </summary> |
| /// <param name="cell"></param> |
| 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); |
| } |
| } |
| } |