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