/* 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