Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/HeuristicLab.ExtLibs/HeuristicLab.EPPlus/4.0.3/EPPlus-4.0.3/FormulaParsing/Excel/Functions/ExcelFunction.cs @ 17401

Last change on this file since 17401 was 12074, checked in by sraggl, 10 years ago

#2341: Added EPPlus-4.0.3 to ExtLibs

File size: 19.5 KB
Line 
1/* Copyright (C) 2011  Jan Källman
2 *
3 * This library is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 2.1 of the License, or (at your option) any later version.
7
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
11 * See the GNU Lesser General Public License for more details.
12 *
13 * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
14 * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
15 *
16 * All code and executables are provided "as is" with no warranty either express or implied.
17 * The author accepts no liability for any damage or loss of business that this product may cause.
18 *
19 * Code change notes:
20 *
21 * Author             Change            Date
22 *******************************************************************************
23 * Mats Alm                       Added                   2013-12-03
24 *******************************************************************************/
25using System;
26using System.Collections.Generic;
27using System.Linq;
28using System.Text;
29using System.Text.RegularExpressions;
30using OfficeOpenXml.FormulaParsing.ExpressionGraph;
31using System.Globalization;
32using OfficeOpenXml.FormulaParsing.Utilities;
33using OfficeOpenXml.FormulaParsing.Exceptions;
34using System.Collections;
35
36namespace OfficeOpenXml.FormulaParsing.Excel.Functions
37{
38    /// <summary>
39    /// Base class for Excel function implementations.
40    /// </summary>
41    public abstract class ExcelFunction
42    {
43        public ExcelFunction()
44            : this(new ArgumentCollectionUtil(), new ArgumentParsers(), new CompileResultValidators())
45        {
46
47        }
48
49        public ExcelFunction(
50            ArgumentCollectionUtil argumentCollectionUtil,
51            ArgumentParsers argumentParsers,
52            CompileResultValidators compileResultValidators)
53        {
54            _argumentCollectionUtil = argumentCollectionUtil;
55            _argumentParsers = argumentParsers;
56            _compileResultValidators = compileResultValidators;
57        }
58
59        private readonly ArgumentCollectionUtil _argumentCollectionUtil;
60        private readonly ArgumentParsers _argumentParsers;
61        private readonly CompileResultValidators _compileResultValidators;
62
63        /// <summary>
64        ///
65        /// </summary>
66        /// <param name="arguments">Arguments to the function, each argument can contain primitive types, lists or <see cref="ExcelDataProvider.IRangeInfo">Excel ranges</see></param>
67        /// <param name="context">The <see cref="ParsingContext"/> contains various data that can be useful in functions.</param>
68        /// <returns>A <see cref="CompileResult"/> containing the calculated value</returns>
69        public abstract CompileResult Execute(IEnumerable<FunctionArgument> arguments, ParsingContext context);
70
71        /// <summary>
72        /// If overridden, this method is called before Execute is called.
73        /// </summary>
74        /// <param name="context"></param>
75        public virtual void BeforeInvoke(ParsingContext context) { }
76
77        public virtual bool IsLookupFuction
78        {
79            get
80            {
81                return false;
82            }
83        }
84
85        public virtual bool IsErrorHandlingFunction
86        {
87            get
88            {
89                return false;
90            }
91        }
92       
93        /// <summary>
94        /// Used for some Lookupfunctions to indicate that function arguments should
95        /// not be compiled before the function is called.
96        /// </summary>
97        public bool SkipArgumentEvaluation { get; set; }
98        protected object GetFirstValue(IEnumerable<FunctionArgument> val)
99        {
100            var arg = ((IEnumerable<FunctionArgument>)val).FirstOrDefault();
101            if(arg.Value is ExcelDataProvider.IRangeInfo)
102            {
103                //var r=((ExcelDataProvider.IRangeInfo)arg);
104                var r = arg.ValueAsRangeInfo;
105                return r.GetValue(r.Address._fromRow, r.Address._fromCol);
106            }
107            else
108            {
109                return arg==null?null:arg.Value;
110            }
111        }
112        /// <summary>
113        /// This functions validates that the supplied <paramref name="arguments"/> contains at least
114        /// (the value of) <paramref name="minLength"/> elements. If one of the arguments is an
115        /// <see cref="ExcelDataProvider.IRangeInfo">Excel range</see> the number of cells in
116        /// that range will be counted as well.
117        /// </summary>
118        /// <param name="arguments"></param>
119        /// <param name="minLength"></param>
120        /// <param name="errorTypeToThrow">The <see cref="eErrorType"/> of the <see cref="ExcelErrorValueException"/> that will be thrown if <paramref name="minLength"/> is not met.</param>
121        protected void ValidateArguments(IEnumerable<FunctionArgument> arguments, int minLength,
122                                         eErrorType errorTypeToThrow)
123        {
124            Require.That(arguments).Named("arguments").IsNotNull();
125            ThrowExcelErrorValueExceptionIf(() =>
126                {
127                    var nArgs = 0;
128                    if (arguments.Any())
129                    {
130                        foreach (var arg in arguments)
131                        {
132                            nArgs++;
133                            if (nArgs >= minLength) return false;
134                            if (arg.IsExcelRange)
135                            {
136                                nArgs += arg.ValueAsRangeInfo.GetNCells();
137                                if (nArgs >= minLength) return false;
138                            }
139                        }
140                    }
141                    return true;
142                }, errorTypeToThrow);
143        }
144
145        /// <summary>
146        /// This functions validates that the supplied <paramref name="arguments"/> contains at least
147        /// (the value of) <paramref name="minLength"/> elements. If one of the arguments is an
148        /// <see cref="ExcelDataProvider.IRangeInfo">Excel range</see> the number of cells in
149        /// that range will be counted as well.
150        /// </summary>
151        /// <param name="arguments"></param>
152        /// <param name="minLength"></param>
153        /// <exception cref="ArgumentException"></exception>
154        protected void ValidateArguments(IEnumerable<FunctionArgument> arguments, int minLength)
155        {
156            Require.That(arguments).Named("arguments").IsNotNull();
157            ThrowArgumentExceptionIf(() =>
158                {
159                    var nArgs = 0;
160                    if (arguments.Any())
161                    {
162                        foreach (var arg in arguments)
163                        {
164                            nArgs++;
165                            if (nArgs >= minLength) return false;
166                            if (arg.IsExcelRange)
167                            {
168                                nArgs += arg.ValueAsRangeInfo.GetNCells();
169                                if (nArgs >= minLength) return false;
170                            }
171                        }
172                    }
173                    return true;
174                }, "Expecting at least {0} arguments", minLength.ToString());
175        }
176
177        /// <summary>
178        /// Returns the value of the argument att the position of the 0-based
179        /// <paramref name="index"/> as an integer.
180        /// </summary>
181        /// <param name="arguments"></param>
182        /// <param name="index"></param>
183        /// <returns>Value of the argument as an integer.</returns>
184        /// <exception cref="ExcelErrorValueException"></exception>
185        protected int ArgToInt(IEnumerable<FunctionArgument> arguments, int index)
186        {
187            var val = arguments.ElementAt(index).ValueFirst;
188            return (int)_argumentParsers.GetParser(DataType.Integer).Parse(val);
189        }
190
191        /// <summary>
192        /// Returns the value of the argument att the position of the 0-based
193        /// <paramref name="index"/> as a string.
194        /// </summary>
195        /// <param name="arguments"></param>
196        /// <param name="index"></param>
197        /// <returns>Value of the argument as a string.</returns>
198        protected string ArgToString(IEnumerable<FunctionArgument> arguments, int index)
199        {
200            var obj = arguments.ElementAt(index).ValueFirst;
201            return obj != null ? obj.ToString() : string.Empty;
202        }
203
204        /// <summary>
205        /// Returns the value of the argument att the position of the 0-based
206        /// </summary>
207        /// <param name="obj"></param>
208        /// <returns>Value of the argument as a double.</returns>
209        /// <exception cref="ExcelErrorValueException"></exception>
210        protected double ArgToDecimal(object obj)
211        {
212            return (double)_argumentParsers.GetParser(DataType.Decimal).Parse(obj);
213        }
214
215        /// <summary>
216        /// Returns the value of the argument att the position of the 0-based
217        /// <paramref name="index"/> as a <see cref="System.Double"/>.
218        /// </summary>
219        /// <param name="arguments"></param>
220        /// <param name="index"></param>
221        /// <returns>Value of the argument as an integer.</returns>
222        /// <exception cref="ExcelErrorValueException"></exception>
223        protected double ArgToDecimal(IEnumerable<FunctionArgument> arguments, int index)
224        {
225            return ArgToDecimal(arguments.ElementAt(index).Value);
226        }
227
228        protected double Divide(double left, double right)
229        {
230            if (System.Math.Abs(right - 0d) < double.Epsilon)
231            {
232                throw new ExcelErrorValueException(eErrorType.Div0);
233            }
234            return left/right;
235        }
236
237        protected bool IsNumericString(object value)
238        {
239            if (value == null || string.IsNullOrEmpty(value.ToString())) return false;
240            return Regex.IsMatch(value.ToString(), @"^[\d]+(\,[\d])?");
241        }
242
243        /// <summary>
244        /// If the argument is a boolean value its value will be returned.
245        /// If the argument is an integer value, true will be returned if its
246        /// value is not 0, otherwise false.
247        /// </summary>
248        /// <param name="arguments"></param>
249        /// <param name="index"></param>
250        /// <returns></returns>
251        protected bool ArgToBool(IEnumerable<FunctionArgument> arguments, int index)
252        {
253            var obj = arguments.ElementAt(index).Value ?? string.Empty;
254            return (bool)_argumentParsers.GetParser(DataType.Boolean).Parse(obj);
255        }
256
257        /// <summary>
258        /// Throws an <see cref="ArgumentException"/> if <paramref name="condition"/> evaluates to true.
259        /// </summary>
260        /// <param name="condition"></param>
261        /// <param name="message"></param>
262        /// <exception cref="ArgumentException"></exception>
263        protected void ThrowArgumentExceptionIf(Func<bool> condition, string message)
264        {
265            if (condition())
266            {
267                throw new ArgumentException(message);
268            }
269        }
270
271        /// <summary>
272        /// Throws an <see cref="ArgumentException"/> if <paramref name="condition"/> evaluates to true.
273        /// </summary>
274        /// <param name="condition"></param>
275        /// <param name="message"></param>
276        /// <param name="formats">Formats to the message string.</param>
277        protected void ThrowArgumentExceptionIf(Func<bool> condition, string message, params object[] formats)
278        {
279            message = string.Format(message, formats);
280            ThrowArgumentExceptionIf(condition, message);
281        }
282
283        /// <summary>
284        /// Throws an <see cref="ExcelErrorValueException"/> with the given <paramref name="errorType"/> set.
285        /// </summary>
286        /// <param name="errorType"></param>
287        protected void ThrowExcelErrorValueException(eErrorType errorType)
288        {
289            throw new ExcelErrorValueException("An excel function error occurred", ExcelErrorValue.Create(errorType));
290        }
291
292        /// <summary>
293        /// Throws an <see cref="ArgumentException"/> if <paramref name="condition"/> evaluates to true.
294        /// </summary>
295        /// <param name="condition"></param>
296        /// <param name="errorType"></param>
297        /// <exception cref="ExcelErrorValueException"></exception>
298        protected void ThrowExcelErrorValueExceptionIf(Func<bool> condition, eErrorType errorType)
299        {
300            if (condition())
301            {
302                throw new ExcelErrorValueException("An excel function error occurred", ExcelErrorValue.Create(errorType));
303            }
304        }
305
306        protected bool IsNumeric(object val)
307        {
308            if (val == null) return false;
309            return (val.GetType().IsPrimitive || val is double || val is decimal  || val is System.DateTime || val is TimeSpan);
310        }
311
312        //protected virtual bool IsNumber(object obj)
313        //{
314        //    if (obj == null) return false;
315        //    return (obj is int || obj is double || obj is short || obj is decimal || obj is long);
316        //}
317
318        /// <summary>
319        /// Helper method for comparison of two doubles.
320        /// </summary>
321        /// <param name="d1"></param>
322        /// <param name="d2"></param>
323        /// <returns></returns>
324        protected bool AreEqual(double d1, double d2)
325        {
326            return System.Math.Abs(d1 - d2) < double.Epsilon;
327        }
328
329        /// <summary>
330        /// Will return the arguments as an enumerable of doubles.
331        /// </summary>
332        /// <param name="arguments"></param>
333        /// <param name="context"></param>
334        /// <returns></returns>
335        protected virtual IEnumerable<double> ArgsToDoubleEnumerable(IEnumerable<FunctionArgument> arguments,
336                                                                     ParsingContext context)
337        {
338            return ArgsToDoubleEnumerable(false, arguments, context);
339        }
340
341        /// <summary>
342        /// Will return the arguments as an enumerable of doubles.
343        /// </summary>
344        /// <param name="ignoreHiddenCells">If a cell is hidden and this value is true the value of that cell will be ignored</param>
345        /// <param name="ignoreErrors">If a cell contains an error, that error will be ignored if this method is set to true</param>
346        /// <param name="arguments"></param>
347        /// <param name="context"></param>
348        /// <returns></returns>
349        protected virtual IEnumerable<double> ArgsToDoubleEnumerable(bool ignoreHiddenCells, bool ignoreErrors, IEnumerable<FunctionArgument> arguments, ParsingContext context)
350        {
351            return _argumentCollectionUtil.ArgsToDoubleEnumerable(ignoreHiddenCells, ignoreErrors, arguments, context);
352        }
353
354        /// <summary>
355        /// Will return the arguments as an enumerable of doubles.
356        /// </summary>
357        /// <param name="ignoreHiddenCells">If a cell is hidden and this value is true the value of that cell will be ignored</param>
358        /// <param name="arguments"></param>
359        /// <param name="context"></param>
360        /// <returns></returns>
361        protected virtual IEnumerable<double> ArgsToDoubleEnumerable(bool ignoreHiddenCells, IEnumerable<FunctionArgument> arguments, ParsingContext context)
362        {
363            return ArgsToDoubleEnumerable(ignoreHiddenCells, true, arguments, context);
364        }
365
366        /// <summary>
367        /// Will return the arguments as an enumerable of objects.
368        /// </summary>
369        /// <param name="ignoreHiddenCells">If a cell is hidden and this value is true the value of that cell will be ignored</param>
370        /// <param name="arguments"></param>
371        /// <param name="context"></param>
372        /// <returns></returns>
373        protected virtual IEnumerable<object> ArgsToObjectEnumerable(bool ignoreHiddenCells, IEnumerable<FunctionArgument> arguments, ParsingContext context)
374        {
375            return _argumentCollectionUtil.ArgsToObjectEnumerable(ignoreHiddenCells, arguments, context);
376        }
377
378        /// <summary>
379        /// Use this method to create a result to return from Excel functions.
380        /// </summary>
381        /// <param name="result"></param>
382        /// <param name="dataType"></param>
383        /// <returns></returns>
384        protected CompileResult CreateResult(object result, DataType dataType)
385        {
386            var validator = _compileResultValidators.GetValidator(dataType);
387            validator.Validate(result);
388            return new CompileResult(result, dataType);
389        }
390
391        /// <summary>
392        /// Use this method to apply a function on a collection of arguments. The <paramref name="result"/>
393        /// should be modifyed in the supplied <paramref name="action"/> and will contain the result
394        /// after this operation has been performed.
395        /// </summary>
396        /// <param name="collection"></param>
397        /// <param name="result"></param>
398        /// <param name="action"></param>
399        /// <returns></returns>
400        protected virtual double CalculateCollection(IEnumerable<FunctionArgument> collection, double result, Func<FunctionArgument,double,double> action)
401        {
402            return _argumentCollectionUtil.CalculateCollection(collection, result, action);
403        }
404
405        /// <summary>
406        /// if the supplied <paramref name="arg">argument</paramref> contains an Excel error
407        /// an <see cref="ExcelErrorValueException"/> with that errorcode will be thrown
408        /// </summary>
409        /// <param name="arg"></param>
410        /// <exception cref="ExcelErrorValueException"></exception>
411        protected void CheckForAndHandleExcelError(FunctionArgument arg)
412        {
413            if (arg.ValueIsExcelError)
414            {
415                throw (new ExcelErrorValueException(arg.ValueAsExcelErrorValue));
416            }
417        }
418
419        /// <summary>
420        /// If the supplied <paramref name="cell"/> contains an Excel error
421        /// an <see cref="ExcelErrorValueException"/> with that errorcode will be thrown
422        /// </summary>
423        /// <param name="cell"></param>
424        protected void CheckForAndHandleExcelError(ExcelDataProvider.ICellInfo cell)
425        {
426            if (cell.IsExcelError)
427            {
428                throw (new ExcelErrorValueException(ExcelErrorValue.Parse(cell.Value.ToString())));
429            }
430        }
431
432        protected CompileResult GetResultByObject(object result)
433        {
434            if (IsNumeric(result))
435            {
436                return CreateResult(result, DataType.Decimal);
437            }
438            if (result is string)
439            {
440                return CreateResult(result, DataType.String);
441            }
442            if (ExcelErrorValue.Values.IsErrorValue(result))
443            {
444                return CreateResult(result, DataType.ExcelAddress);
445            }
446            if (result == null)
447            {
448                return CompileResult.Empty;
449            }
450            return CreateResult(result, DataType.Enumerable);
451        }
452    }
453}
Note: See TracBrowser for help on using the repository browser.