From 9b37e2e907edec0437ad267e0c8b214afc96d391 Mon Sep 17 00:00:00 2001 From: swmal <897655+swmal@users.noreply.github.com> Date: Thu, 27 Dec 2018 15:16:26 +0100 Subject: [PATCH] Rewrote handling om exceladdresses in functions. Introduced a cache for addresses, available via ParsingContext. ExcelRanges are now always compiled in ExcelAddressExpression, the SkipArgumentEvaluation parameter is removed, and addresses can be accessed via cache references --- .../Excel/Functions/BuiltInFunctions.cs | 10 +-- .../Excel/Functions/ExcelFunction.cs | 12 ++- .../Excel/Functions/FunctionArgument.cs | 2 + .../Excel/Functions/RefAndLookup/Column.cs | 2 +- .../Excel/Functions/RefAndLookup/Columns.cs | 2 +- .../Excel/Functions/RefAndLookup/Lookup.cs | 6 +- .../Excel/Functions/RefAndLookup/Match.cs | 2 +- .../Excel/Functions/RefAndLookup/Offset.cs | 6 +- .../Excel/Functions/RefAndLookup/Row.cs | 2 +- .../Excel/Functions/RefAndLookup/Rows.cs | 2 +- EPPlus/FormulaParsing/ExcelAddressCache.cs | 82 +++++++++++++++++++ .../ExpressionGraph/CompileResult.cs | 7 ++ .../ExpressionGraph/ExcelAddressExpression.cs | 21 +++-- .../ExpressionGraph/Expression.cs | 6 -- .../FunctionArgumentExpression.cs | 16 ---- .../FunctionCompilers/DefaultCompiler.cs | 12 +-- .../ErrorHandlingFunctionCompiler.cs | 12 +-- .../FunctionCompilers/FunctionCompiler.cs | 28 +++++-- .../FunctionCompilerFactory.cs | 19 +++-- .../IfErrorFunctionCompiler.cs | 10 +-- .../FunctionCompilers/IfFunctionCompiler.cs | 10 +-- .../FunctionCompilers/IfNaFunctionCompiler.cs | 10 +-- .../LookupFunctionCompiler.cs | 28 +++---- .../ExpressionGraph/FunctionExpression.cs | 4 +- EPPlus/FormulaParsing/ParsingContext.cs | 6 +- .../Functions/FunctionRepositoryTests.cs | 6 +- .../RefAndLookup/RefAndLookupTests.cs | 2 +- .../FormulaParsing/ExcelAddressCacheTests.cs | 63 ++++++++++++++ .../FunctionCompilerFactoryTests.cs | 31 ++++--- .../BuiltInFunctions/RefAndLookupTests.cs | 65 +++++++++++---- 30 files changed, 344 insertions(+), 140 deletions(-) create mode 100644 EPPlus/FormulaParsing/ExcelAddressCache.cs create mode 100644 EPPlusTest/FormulaParsing/ExcelAddressCacheTests.cs diff --git a/EPPlus/FormulaParsing/Excel/Functions/BuiltInFunctions.cs b/EPPlus/FormulaParsing/Excel/Functions/BuiltInFunctions.cs index 42e59e9f..9b2de026 100644 --- a/EPPlus/FormulaParsing/Excel/Functions/BuiltInFunctions.cs +++ b/EPPlus/FormulaParsing/Excel/Functions/BuiltInFunctions.cs @@ -162,14 +162,14 @@ public BuiltInFunctions() Functions["vlookup"] = new VLookup(); Functions["lookup"] = new Lookup(); Functions["match"] = new Match(); - Functions["row"] = new Row(){SkipArgumentEvaluation = true}; - Functions["rows"] = new Rows(){SkipArgumentEvaluation = true}; - Functions["column"] = new Column(){SkipArgumentEvaluation = true}; - Functions["columns"] = new Columns(){SkipArgumentEvaluation = true}; + Functions["row"] = new Row(); + Functions["rows"] = new Rows(); + Functions["column"] = new Column(); + Functions["columns"] = new Columns(); Functions["choose"] = new Choose(); Functions["index"] = new Index(); Functions["indirect"] = new Indirect(); - Functions["offset"] = new Offset(){SkipArgumentEvaluation = true}; + Functions["offset"] = new Offset(); // Date Functions["date"] = new Date(); Functions["today"] = new Today(); diff --git a/EPPlus/FormulaParsing/Excel/Functions/ExcelFunction.cs b/EPPlus/FormulaParsing/Excel/Functions/ExcelFunction.cs index c38435de..620ff43c 100644 --- a/EPPlus/FormulaParsing/Excel/Functions/ExcelFunction.cs +++ b/EPPlus/FormulaParsing/Excel/Functions/ExcelFunction.cs @@ -97,7 +97,7 @@ public virtual bool IsErrorHandlingFunction /// Used for some Lookupfunctions to indicate that function arguments should /// not be compiled before the function is called. /// - public bool SkipArgumentEvaluation { get; set; } + //public bool SkipArgumentEvaluation { get; set; } protected object GetFirstValue(IEnumerable val) { var arg = ((IEnumerable)val).FirstOrDefault(); @@ -181,6 +181,16 @@ protected string ArgToAddress(IEnumerable arguments, int index return arguments.ElementAt(index).IsExcelRange ? arguments.ElementAt(index).ValueAsRangeInfo.Address.FullAddress : ArgToString(arguments, index); } + protected string ArgToAddress(IEnumerable arguments, int index, ParsingContext context) + { + var arg = arguments.ElementAt(index); + if(arg.ExcelAddressReferenceId > 0) + { + return context.AddressCache.Get(arg.ExcelAddressReferenceId); + } + return ArgToAddress(arguments, index); + } + /// /// Returns the value of the argument att the position of the 0-based /// as an integer. diff --git a/EPPlus/FormulaParsing/Excel/Functions/FunctionArgument.cs b/EPPlus/FormulaParsing/Excel/Functions/FunctionArgument.cs index b876d9d7..7cd20548 100644 --- a/EPPlus/FormulaParsing/Excel/Functions/FunctionArgument.cs +++ b/EPPlus/FormulaParsing/Excel/Functions/FunctionArgument.cs @@ -65,6 +65,8 @@ public Type Type get { return Value != null ? Value.GetType() : null; } } + public int ExcelAddressReferenceId { get; set; } + public bool IsExcelRange { get { return Value != null && Value is EpplusExcelDataProvider.IRangeInfo; } diff --git a/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Column.cs b/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Column.cs index bca28fc9..c966704a 100644 --- a/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Column.cs +++ b/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Column.cs @@ -41,7 +41,7 @@ public override CompileResult Execute(IEnumerable arguments, P { return CreateResult(context.Scopes.Current.Address.FromCol, DataType.Integer); } - var rangeAddress = ArgToString(arguments, 0); + var rangeAddress = ArgToAddress(arguments, 0, context); if (!ExcelAddressUtil.IsValidAddress(rangeAddress)) throw new ArgumentException("An invalid argument was supplied"); var factory = new RangeAddressFactory(context.ExcelDataProvider); diff --git a/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Columns.cs b/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Columns.cs index 909b3cf2..c4372c66 100644 --- a/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Columns.cs +++ b/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Columns.cs @@ -45,7 +45,7 @@ public override CompileResult Execute(IEnumerable arguments, P } else { - var range = ArgToString(arguments, 0); + var range = ArgToAddress(arguments, 0, context); if (ExcelAddressUtil.IsValidAddress(range)) { var factory = new RangeAddressFactory(context.ExcelDataProvider); diff --git a/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Lookup.cs b/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Lookup.cs index bb2f8960..b4eecadd 100644 --- a/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Lookup.cs +++ b/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Lookup.cs @@ -55,7 +55,7 @@ private CompileResult HandleSingleRange(IEnumerable arguments, { var searchedValue = arguments.ElementAt(0).Value; Require.That(arguments.ElementAt(1).Value).Named("firstAddress").IsNotNull(); - var firstAddress = ArgToString(arguments, 1); + var firstAddress = ArgToAddress(arguments, 1, context); var rangeAddressFactory = new RangeAddressFactory(context.ExcelDataProvider); var address = rangeAddressFactory.Create(firstAddress); var nRows = address.ToRow - address.FromRow; @@ -77,8 +77,8 @@ private CompileResult HandleTwoRanges(IEnumerable arguments, P var searchedValue = arguments.ElementAt(0).Value; Require.That(arguments.ElementAt(1).Value).Named("firstAddress").IsNotNull(); Require.That(arguments.ElementAt(2).Value).Named("secondAddress").IsNotNull(); - var firstAddress = ArgToString(arguments, 1); - var secondAddress = ArgToString(arguments, 2); + var firstAddress = ArgToAddress(arguments, 1, context); + var secondAddress = ArgToAddress(arguments, 2, context); var rangeAddressFactory = new RangeAddressFactory(context.ExcelDataProvider); var address1 = rangeAddressFactory.Create(firstAddress); var address2 = rangeAddressFactory.Create(secondAddress); diff --git a/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Match.cs b/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Match.cs index 0d5f28b4..8448abda 100644 --- a/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Match.cs +++ b/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Match.cs @@ -51,7 +51,7 @@ public override CompileResult Execute(IEnumerable arguments, P ValidateArguments(arguments, 2); var searchedValue = arguments.ElementAt(0).Value; - var address = ArgToAddress(arguments,1); + var address = ArgToAddress(arguments,1, context); var rangeAddressFactory = new RangeAddressFactory(context.ExcelDataProvider); var rangeAddress = rangeAddressFactory.Create(address); var matchType = GetMatchType(arguments); diff --git a/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Offset.cs b/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Offset.cs index 98de588f..e3f8d561 100644 --- a/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Offset.cs +++ b/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Offset.cs @@ -36,19 +36,19 @@ public override CompileResult Execute(IEnumerable arguments, P { var functionArguments = arguments as FunctionArgument[] ?? arguments.ToArray(); ValidateArguments(functionArguments, 3); - var startRange = ArgToAddress(functionArguments, 0); + var startRange = ArgToAddress(functionArguments, 0, context); var rowOffset = ArgToInt(functionArguments, 1); var colOffset = ArgToInt(functionArguments, 2); int width = 0, height = 0; if (functionArguments.Length > 3) { height = ArgToInt(functionArguments, 3); - ThrowExcelErrorValueExceptionIf(() => height == 0, eErrorType.Ref); + if (height == 0) return new CompileResult(eErrorType.Ref); } if (functionArguments.Length > 4) { width = ArgToInt(functionArguments, 4); - ThrowExcelErrorValueExceptionIf(() => width == 0, eErrorType.Ref); + if (width == 0) return new CompileResult(eErrorType.Ref); } var ws = context.Scopes.Current.Address.Worksheet; var r =context.ExcelDataProvider.GetRange(ws,startRange); diff --git a/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Row.cs b/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Row.cs index 624d5145..8844c9ec 100644 --- a/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Row.cs +++ b/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Row.cs @@ -41,7 +41,7 @@ public override CompileResult Execute(IEnumerable arguments, P { return CreateResult(context.Scopes.Current.Address.FromRow, DataType.Integer); } - var rangeAddress = ArgToString(arguments, 0); + var rangeAddress = ArgToAddress(arguments, 0, context); if (!ExcelAddressUtil.IsValidAddress(rangeAddress)) throw new ArgumentException("An invalid argument was supplied"); var factory = new RangeAddressFactory(context.ExcelDataProvider); diff --git a/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Rows.cs b/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Rows.cs index bbc4ebcd..308c2750 100644 --- a/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Rows.cs +++ b/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Rows.cs @@ -45,7 +45,7 @@ public override CompileResult Execute(IEnumerable arguments, P } else { - var range = ArgToString(arguments, 0); + var range = ArgToAddress(arguments, 0, context); if (ExcelAddressUtil.IsValidAddress(range)) { var factory = new RangeAddressFactory(context.ExcelDataProvider); diff --git a/EPPlus/FormulaParsing/ExcelAddressCache.cs b/EPPlus/FormulaParsing/ExcelAddressCache.cs new file mode 100644 index 00000000..201a766e --- /dev/null +++ b/EPPlus/FormulaParsing/ExcelAddressCache.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace EPPlus.FormulaParsing +{ + /// + /// Caches string by generated id's. + /// + public class ExcelAddressCache + { + private readonly object _myLock = new object(); + private readonly Dictionary _addressCache = new Dictionary(); + private readonly Dictionary _lookupCache = new Dictionary(); + private int _nextId = 1; + private const bool EnableLookupCache = false; + + /// + /// Returns an id to use for caching (when the method is called) + /// + /// + public int GetNewId() + { + lock(_myLock) + { + return _nextId++; + } + } + + /// + /// Adds an address to the cache + /// + /// + /// + /// + public bool Add(int id, string address) + { + lock(_myLock) + { + if (_addressCache.ContainsKey(id)) return false; + _addressCache.Add(id, address); + if(EnableLookupCache && !_lookupCache.ContainsKey(address)) + _lookupCache.Add(address, id); + return true; + } + + } + + /// + /// Number of items in the cache + /// + public int Count + { + get { return _addressCache.Count; } + } + + /// + /// Returns an address by its cache id + /// + /// + /// + public string Get(int id) + { + if (!_addressCache.ContainsKey(id)) return string.Empty; + return _addressCache[id]; + } + + /// + /// Clears the cache + /// + public void Clear() + { + lock(_myLock) + { + _addressCache.Clear(); + _lookupCache.Clear(); + _nextId = 1; + } + } + + } +} diff --git a/EPPlus/FormulaParsing/ExpressionGraph/CompileResult.cs b/EPPlus/FormulaParsing/ExpressionGraph/CompileResult.cs index 4eac5af8..b90bda14 100644 --- a/EPPlus/FormulaParsing/ExpressionGraph/CompileResult.cs +++ b/EPPlus/FormulaParsing/ExpressionGraph/CompileResult.cs @@ -185,5 +185,12 @@ public bool IsDateString public bool IsResultOfSubtotal { get; set; } public bool IsHiddenCell { get; set; } + + public int ExcelAddressReferenceId { get; set; } + + public bool IsResultOfResolvedExcelRange + { + get { return ExcelAddressReferenceId > 0; } + } } } diff --git a/EPPlus/FormulaParsing/ExpressionGraph/ExcelAddressExpression.cs b/EPPlus/FormulaParsing/ExpressionGraph/ExcelAddressExpression.cs index 6abfb27a..ab941b2e 100644 --- a/EPPlus/FormulaParsing/ExpressionGraph/ExcelAddressExpression.cs +++ b/EPPlus/FormulaParsing/ExpressionGraph/ExcelAddressExpression.cs @@ -82,14 +82,23 @@ public override bool IsGroupedExpression public override CompileResult Compile() { - if (ParentIsLookupFunction) + //if (ParentIsLookupFunction) + //{ + // return new CompileResult(ExpressionString, DataType.ExcelAddress); + //} + //else + //{ + // return CompileRangeValues(); + //} + var cache = _parsingContext.AddressCache; + var cacheId = cache.GetNewId(); + if(!cache.Add(cacheId, ExpressionString)) { - return new CompileResult(ExpressionString, DataType.ExcelAddress); - } - else - { - return CompileRangeValues(); + throw new InvalidOperationException("Catastropic error occurred, address caching failed"); } + var compileResult = CompileRangeValues(); + compileResult.ExcelAddressReferenceId = cacheId; + return compileResult; } private CompileResult CompileRangeValues() diff --git a/EPPlus/FormulaParsing/ExpressionGraph/Expression.cs b/EPPlus/FormulaParsing/ExpressionGraph/Expression.cs index 074788be..b7e85e45 100644 --- a/EPPlus/FormulaParsing/ExpressionGraph/Expression.cs +++ b/EPPlus/FormulaParsing/ExpressionGraph/Expression.cs @@ -58,12 +58,6 @@ public Expression(string expression) Operator = null; } - public virtual bool ParentIsLookupFunction - { - get; - set; - } - public virtual bool HasChildren { get { return _children.Any(); } diff --git a/EPPlus/FormulaParsing/ExpressionGraph/FunctionArgumentExpression.cs b/EPPlus/FormulaParsing/ExpressionGraph/FunctionArgumentExpression.cs index f5d631c9..7f11d1e7 100644 --- a/EPPlus/FormulaParsing/ExpressionGraph/FunctionArgumentExpression.cs +++ b/EPPlus/FormulaParsing/ExpressionGraph/FunctionArgumentExpression.cs @@ -45,22 +45,6 @@ public FunctionArgumentExpression(Expression function) _function = function; } - public override bool ParentIsLookupFunction - { - get - { - return base.ParentIsLookupFunction; - } - set - { - base.ParentIsLookupFunction = value; - foreach (var child in Children) - { - child.ParentIsLookupFunction = value; - } - } - } - public override bool IsGroupedExpression { get { return false; } diff --git a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/DefaultCompiler.cs b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/DefaultCompiler.cs index 19b07ee0..8a2bd5ad 100644 --- a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/DefaultCompiler.cs +++ b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/DefaultCompiler.cs @@ -39,16 +39,16 @@ namespace OfficeOpenXml.FormulaParsing.ExpressionGraph.FunctionCompilers { public class DefaultCompiler : FunctionCompiler { - public DefaultCompiler(ExcelFunction function) - : base(function) + public DefaultCompiler(ExcelFunction function, ParsingContext context) + : base(function, context) { } - public override CompileResult Compile(IEnumerable children, ParsingContext context) + public override CompileResult Compile(IEnumerable children) { var args = new List(); - Function.BeforeInvoke(context); + Function.BeforeInvoke(Context); foreach (var child in children) { var compileResult = child.Compile(); @@ -60,10 +60,10 @@ public override CompileResult Compile(IEnumerable children, ParsingC } else { - BuildFunctionArguments(compileResult.Result, args); + BuildFunctionArguments(compileResult, args); } } - return Function.Execute(args, context); + return Function.Execute(args, Context); } } } diff --git a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/ErrorHandlingFunctionCompiler.cs b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/ErrorHandlingFunctionCompiler.cs index 6fdb0bd7..7dd3b2b0 100644 --- a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/ErrorHandlingFunctionCompiler.cs +++ b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/ErrorHandlingFunctionCompiler.cs @@ -39,21 +39,21 @@ namespace OfficeOpenXml.FormulaParsing.ExpressionGraph.FunctionCompilers { public class ErrorHandlingFunctionCompiler : FunctionCompiler { - public ErrorHandlingFunctionCompiler(ExcelFunction function) - : base(function) + public ErrorHandlingFunctionCompiler(ExcelFunction function, ParsingContext context) + : base(function, context) { } - public override CompileResult Compile(IEnumerable children, ParsingContext context) + public override CompileResult Compile(IEnumerable children) { var args = new List(); - Function.BeforeInvoke(context); + Function.BeforeInvoke(Context); foreach (var child in children) { try { var arg = child.Compile(); - BuildFunctionArguments(arg != null ? arg.Result : null, args); + BuildFunctionArguments(arg != null ? arg : null, args); } catch (ExcelErrorValueException efe) { @@ -65,7 +65,7 @@ public override CompileResult Compile(IEnumerable children, ParsingC } } - return Function.Execute(args, context); + return Function.Execute(args, Context); } } } diff --git a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/FunctionCompiler.cs b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/FunctionCompiler.cs index a9c7f079..bdce6a9e 100644 --- a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/FunctionCompiler.cs +++ b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/FunctionCompiler.cs @@ -46,35 +46,47 @@ protected ExcelFunction Function private set; } - public FunctionCompiler(ExcelFunction function) + protected ParsingContext Context + { + get; + private set; + } + + public FunctionCompiler(ExcelFunction function, ParsingContext context) { Require.That(function).Named("function").IsNotNull(); + Require.That(context).Named("context").IsNotNull(); Function = function; + Context = context; } - protected void BuildFunctionArguments(object result, DataType dataType, List args) + protected void BuildFunctionArguments(CompileResult compileResult, DataType dataType, List args) { - if (result is IEnumerable && !(result is ExcelDataProvider.IRangeInfo)) + if (compileResult.Result is IEnumerable && !(compileResult.Result is ExcelDataProvider.IRangeInfo)) { + var compileResultFactory = new CompileResultFactory(); var argList = new List(); - var objects = result as IEnumerable; + var objects = compileResult.Result as IEnumerable; foreach (var arg in objects) { - BuildFunctionArguments(arg, dataType, argList); + var cr = compileResultFactory.Create(arg); + BuildFunctionArguments(cr, dataType, argList); } args.Add(new FunctionArgument(argList)); } else { - args.Add(new FunctionArgument(result, dataType)); + var funcArg = new FunctionArgument(compileResult.Result, dataType); + funcArg.ExcelAddressReferenceId = compileResult.ExcelAddressReferenceId; + args.Add(funcArg); } } - protected void BuildFunctionArguments(object result, List args) + protected void BuildFunctionArguments(CompileResult result, List args) { BuildFunctionArguments(result, DataType.Unknown, args); } - public abstract CompileResult Compile(IEnumerable children, ParsingContext context); + public abstract CompileResult Compile(IEnumerable children); } } diff --git a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/FunctionCompilerFactory.cs b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/FunctionCompilerFactory.cs index 28454962..f3668807 100644 --- a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/FunctionCompilerFactory.cs +++ b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/FunctionCompilerFactory.cs @@ -34,18 +34,21 @@ using System.Text; using OfficeOpenXml.FormulaParsing.Excel.Functions; using OfficeOpenXml.FormulaParsing.Excel.Functions.Logical; +using OfficeOpenXml.FormulaParsing.Utilities; namespace OfficeOpenXml.FormulaParsing.ExpressionGraph.FunctionCompilers { public class FunctionCompilerFactory { private readonly Dictionary _specialCompilers = new Dictionary(); - - public FunctionCompilerFactory(FunctionRepository repository) + private readonly ParsingContext _context; + public FunctionCompilerFactory(FunctionRepository repository, ParsingContext context) { - _specialCompilers.Add(typeof(If), new IfFunctionCompiler(repository.GetFunction("if"))); - _specialCompilers.Add(typeof(IfError), new IfErrorFunctionCompiler(repository.GetFunction("iferror"))); - _specialCompilers.Add(typeof(IfNa), new IfNaFunctionCompiler(repository.GetFunction("ifna"))); + Require.That(context).Named("context").IsNotNull(); + _context = context; + _specialCompilers.Add(typeof(If), new IfFunctionCompiler(repository.GetFunction("if"), context)); + _specialCompilers.Add(typeof(IfError), new IfErrorFunctionCompiler(repository.GetFunction("iferror"), context)); + _specialCompilers.Add(typeof(IfNa), new IfNaFunctionCompiler(repository.GetFunction("ifna"), context)); foreach (var key in repository.CustomCompilers.Keys) { _specialCompilers.Add(key, repository.CustomCompilers[key]); @@ -59,12 +62,12 @@ private FunctionCompiler GetCompilerByType(ExcelFunction function) { return _specialCompilers[funcType]; } - return new DefaultCompiler(function); + return new DefaultCompiler(function, _context); } public virtual FunctionCompiler Create(ExcelFunction function) { - if (function.IsLookupFuction) return new LookupFunctionCompiler(function); - if (function.IsErrorHandlingFunction) return new ErrorHandlingFunctionCompiler(function); + if (function.IsLookupFuction) return new LookupFunctionCompiler(function, _context); + if (function.IsErrorHandlingFunction) return new ErrorHandlingFunctionCompiler(function, _context); return GetCompilerByType(function); } } diff --git a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/IfErrorFunctionCompiler.cs b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/IfErrorFunctionCompiler.cs index 8ac921e4..9b202fc9 100644 --- a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/IfErrorFunctionCompiler.cs +++ b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/IfErrorFunctionCompiler.cs @@ -11,18 +11,18 @@ namespace OfficeOpenXml.FormulaParsing.ExpressionGraph.FunctionCompilers { public class IfErrorFunctionCompiler : FunctionCompiler { - public IfErrorFunctionCompiler(ExcelFunction function) - : base(function) + public IfErrorFunctionCompiler(ExcelFunction function, ParsingContext context) + : base(function, context) { Require.That(function).Named("function").IsNotNull(); } - public override CompileResult Compile(IEnumerable children, ParsingContext context) + public override CompileResult Compile(IEnumerable children) { if (children.Count() != 2) throw new ExcelErrorValueException(eErrorType.Value); var args = new List(); - Function.BeforeInvoke(context); + Function.BeforeInvoke(Context); var firstChild = children.First(); var lastChild = children.ElementAt(1); try @@ -42,7 +42,7 @@ public override CompileResult Compile(IEnumerable children, ParsingC { args.Add(new FunctionArgument(lastChild.Compile().Result)); } - return Function.Execute(args, context); + return Function.Execute(args, Context); } } } diff --git a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/IfFunctionCompiler.cs b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/IfFunctionCompiler.cs index 6aeb6f0c..ae50411b 100644 --- a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/IfFunctionCompiler.cs +++ b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/IfFunctionCompiler.cs @@ -47,19 +47,19 @@ namespace OfficeOpenXml.FormulaParsing.ExpressionGraph.FunctionCompilers /// public class IfFunctionCompiler : FunctionCompiler { - public IfFunctionCompiler(ExcelFunction function) - : base(function) + public IfFunctionCompiler(ExcelFunction function, ParsingContext context) + : base(function, context) { Require.That(function).Named("function").IsNotNull(); if (!(function is If)) throw new ArgumentException("function must be of type If"); } - public override CompileResult Compile(IEnumerable children, ParsingContext context) + public override CompileResult Compile(IEnumerable children) { // 2 is allowed, Excel returns FALSE if false is the outcome of the expression if(children.Count() < 2) throw new ExcelErrorValueException(eErrorType.Value); var args = new List(); - Function.BeforeInvoke(context); + Function.BeforeInvoke(Context); var firstChild = children.ElementAt(0); var v = firstChild.Compile().Result; @@ -119,7 +119,7 @@ public override CompileResult Compile(IEnumerable children, ParsingC args.Add(new FunctionArgument(null)); args.Add(new FunctionArgument(val)); } - return Function.Execute(args, context); + return Function.Execute(args, Context); } } } diff --git a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/IfNaFunctionCompiler.cs b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/IfNaFunctionCompiler.cs index 46261fe3..e05bc228 100644 --- a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/IfNaFunctionCompiler.cs +++ b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/IfNaFunctionCompiler.cs @@ -9,17 +9,17 @@ namespace OfficeOpenXml.FormulaParsing.ExpressionGraph.FunctionCompilers { public class IfNaFunctionCompiler : FunctionCompiler { - public IfNaFunctionCompiler(ExcelFunction function) - :base(function) + public IfNaFunctionCompiler(ExcelFunction function, ParsingContext context) + :base(function, context) { } - public override CompileResult Compile(IEnumerable children, ParsingContext context) + public override CompileResult Compile(IEnumerable children) { if (children.Count() != 2) return new CompileResult(eErrorType.Value); var args = new List(); - Function.BeforeInvoke(context); + Function.BeforeInvoke(Context); var firstChild = children.First(); var lastChild = children.ElementAt(1); try @@ -40,7 +40,7 @@ public override CompileResult Compile(IEnumerable children, ParsingC { args.Add(new FunctionArgument(lastChild.Compile().Result)); } - return Function.Execute(args, context); + return Function.Execute(args, Context); } } } diff --git a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/LookupFunctionCompiler.cs b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/LookupFunctionCompiler.cs index e4b2bb43..130faa63 100644 --- a/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/LookupFunctionCompiler.cs +++ b/EPPlus/FormulaParsing/ExpressionGraph/FunctionCompilers/LookupFunctionCompiler.cs @@ -38,38 +38,34 @@ namespace OfficeOpenXml.FormulaParsing.ExpressionGraph.FunctionCompilers { public class LookupFunctionCompiler : FunctionCompiler { - public LookupFunctionCompiler(ExcelFunction function) - : base(function) + public LookupFunctionCompiler(ExcelFunction function, ParsingContext context) + : base(function, context) { } - public override CompileResult Compile(IEnumerable children, ParsingContext context) + public override CompileResult Compile(IEnumerable children) { var args = new List(); - Function.BeforeInvoke(context); - var firstChild = true; - foreach (var child in children) + Function.BeforeInvoke(Context); + for(var x = 0; x < children.Count(); x++) { - if (!firstChild || Function.SkipArgumentEvaluation) - { - child.ParentIsLookupFunction = Function.IsLookupFuction; - } - else - { - firstChild = false; - } + var child = children.ElementAt(x); + //if (x > 0 || Function.SkipArgumentEvaluation) + //{ + // child.ParentIsLookupFunction = Function.IsLookupFuction; + //} var arg = child.Compile(); if (arg != null) { - BuildFunctionArguments(arg.Result, arg.DataType, args); + BuildFunctionArguments(arg, arg.DataType, args); } else { BuildFunctionArguments(null, DataType.Unknown, args); } } - return Function.Execute(args, context); + return Function.Execute(args, Context); } } } diff --git a/EPPlus/FormulaParsing/ExpressionGraph/FunctionExpression.cs b/EPPlus/FormulaParsing/ExpressionGraph/FunctionExpression.cs index 0a91ef3b..72e728c1 100644 --- a/EPPlus/FormulaParsing/ExpressionGraph/FunctionExpression.cs +++ b/EPPlus/FormulaParsing/ExpressionGraph/FunctionExpression.cs @@ -56,7 +56,7 @@ public FunctionExpression(string expression, ParsingContext parsingContext, bool : base(expression) { _parsingContext = parsingContext; - _functionCompilerFactory = new FunctionCompilerFactory(parsingContext.Configuration.FunctionRepository); + _functionCompilerFactory = new FunctionCompilerFactory(parsingContext.Configuration.FunctionRepository, parsingContext); _isNegated = isNegated; base.AddChild(new FunctionArgumentExpression(this)); } @@ -84,7 +84,7 @@ public override CompileResult Compile() _parsingContext.Configuration.Logger.LogFunction(ExpressionString); } var compiler = _functionCompilerFactory.Create(function); - var result = compiler.Compile(HasChildren ? Children : Enumerable.Empty(), _parsingContext); + var result = compiler.Compile(HasChildren ? Children : Enumerable.Empty()); if (_isNegated) { if (!result.IsNumeric) diff --git a/EPPlus/FormulaParsing/ParsingContext.cs b/EPPlus/FormulaParsing/ParsingContext.cs index ecda7ba5..8f7eb404 100644 --- a/EPPlus/FormulaParsing/ParsingContext.cs +++ b/EPPlus/FormulaParsing/ParsingContext.cs @@ -7,6 +7,7 @@ using OfficeOpenXml.FormulaParsing.ExcelUtilities; using OfficeOpenXml.FormulaParsing; using OfficeOpenXml.FormulaParsing.Logging; +using EPPlus.FormulaParsing; namespace OfficeOpenXml.FormulaParsing { @@ -48,6 +49,8 @@ private ParsingContext() { } /// public ParsingScopes Scopes { get; private set; } + public ExcelAddressCache AddressCache { get; private set; } + /// /// Returns true if a is attached to the parser. /// @@ -65,12 +68,13 @@ public static ParsingContext Create() var context = new ParsingContext(); context.Configuration = ParsingConfiguration.Create(); context.Scopes = new ParsingScopes(context); + context.AddressCache = new ExcelAddressCache(); return context; } void IParsingLifetimeEventHandler.ParsingCompleted() { - + AddressCache.Clear(); } } } diff --git a/EPPlusTest/FormulaParsing/Excel/Functions/FunctionRepositoryTests.cs b/EPPlusTest/FormulaParsing/Excel/Functions/FunctionRepositoryTests.cs index 1f028875..b4493ed2 100644 --- a/EPPlusTest/FormulaParsing/Excel/Functions/FunctionRepositoryTests.cs +++ b/EPPlusTest/FormulaParsing/Excel/Functions/FunctionRepositoryTests.cs @@ -32,7 +32,7 @@ public class TestFunctionModule : FunctionsModule public TestFunctionModule() { var myFunction = new MyFunction(); - var customCompiler = new MyFunctionCompiler(myFunction); + var customCompiler = new MyFunctionCompiler(myFunction, ParsingContext.Create()); base.Functions.Add(MyFunction.Name, myFunction); base.CustomCompilers.Add(typeof(MyFunction), customCompiler); } @@ -49,8 +49,8 @@ public override CompileResult Execute(IEnumerable arguments, P public class MyFunctionCompiler : FunctionCompiler { - public MyFunctionCompiler(MyFunction function) : base(function) { } - public override CompileResult Compile(IEnumerable children, ParsingContext context) + public MyFunctionCompiler(MyFunction function, ParsingContext context) : base(function, context) { } + public override CompileResult Compile(IEnumerable children) { throw new NotImplementedException(); } diff --git a/EPPlusTest/FormulaParsing/Excel/Functions/RefAndLookup/RefAndLookupTests.cs b/EPPlusTest/FormulaParsing/Excel/Functions/RefAndLookup/RefAndLookupTests.cs index a2144388..a71fe6fd 100644 --- a/EPPlusTest/FormulaParsing/Excel/Functions/RefAndLookup/RefAndLookupTests.cs +++ b/EPPlusTest/FormulaParsing/Excel/Functions/RefAndLookup/RefAndLookupTests.cs @@ -351,7 +351,7 @@ public void MatchShouldReturnFirstItemWhenExactMatch_MatchTypeClosestAbove() Assert.AreEqual(1, result.Result); } - [TestMethod, Ignore] + [TestMethod] public void MatchShouldHandleAddressOnOtherSheet() { using (var package = new ExcelPackage()) diff --git a/EPPlusTest/FormulaParsing/ExcelAddressCacheTests.cs b/EPPlusTest/FormulaParsing/ExcelAddressCacheTests.cs new file mode 100644 index 00000000..c61d2c1c --- /dev/null +++ b/EPPlusTest/FormulaParsing/ExcelAddressCacheTests.cs @@ -0,0 +1,63 @@ +using EPPlus.FormulaParsing; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Text; + +namespace EPPlusTest.FormulaParsing +{ + [TestClass] + public class ExcelAddressCacheTests + { + [TestMethod] + public void ShouldGenerateNewIds() + { + var cache = new ExcelAddressCache(); + var firstId = cache.GetNewId(); + Assert.AreEqual(1, firstId); + + var secondId = cache.GetNewId(); + Assert.AreEqual(2, secondId); + } + + [TestMethod] + public void ShouldReturnCachedAddress() + { + var cache = new ExcelAddressCache(); + var id = cache.GetNewId(); + var address = "A1"; + var result = cache.Add(id, address); + Assert.IsTrue(result); + Assert.AreEqual(address, cache.Get(id)); + } + + [TestMethod] + public void AddShouldReturnFalseIfUsedId() + { + var cache = new ExcelAddressCache(); + var id = cache.GetNewId(); + var address = "A1"; + var result = cache.Add(id, address); + Assert.IsTrue(result); + var result2 = cache.Add(id, address); + Assert.IsFalse(result2); + } + + [TestMethod] + public void ClearShouldResetId() + { + var cache = new ExcelAddressCache(); + var id = cache.GetNewId(); + Assert.AreEqual(1, id); + var address = "A1"; + var result = cache.Add(id, address); + Assert.AreEqual(1, cache.Count); + var id2 = cache.GetNewId(); + Assert.AreEqual(2, id2); + cache.Clear(); + var id3 = cache.GetNewId(); + Assert.AreEqual(1, id3); + + } + } +} diff --git a/EPPlusTest/FormulaParsing/ExpressionGraph/FunctionCompilers/FunctionCompilerFactoryTests.cs b/EPPlusTest/FormulaParsing/ExpressionGraph/FunctionCompilers/FunctionCompilerFactoryTests.cs index 700054d9..12bb5abe 100644 --- a/EPPlusTest/FormulaParsing/ExpressionGraph/FunctionCompilers/FunctionCompilerFactoryTests.cs +++ b/EPPlusTest/FormulaParsing/ExpressionGraph/FunctionCompilers/FunctionCompilerFactoryTests.cs @@ -18,12 +18,19 @@ namespace EPPlusTest.FormulaParsing.ExpressionGraph.FunctionCompilers [TestClass] public class FunctionCompilerFactoryTests { + private ParsingContext _context; + + [TestInitialize] + public void Initialize() + { + _context = ParsingContext.Create(); + } #region Create Tests [TestMethod] public void CreateHandlesStandardFunctionCompiler() { var functionRepository = FunctionRepository.Create(); - var functionCompilerFactory = new FunctionCompilerFactory(functionRepository); + var functionCompilerFactory = new FunctionCompilerFactory(functionRepository, _context); var function = new Sum(); var functionCompiler = functionCompilerFactory.Create(function); Assert.IsInstanceOfType(functionCompiler, typeof(DefaultCompiler)); @@ -33,7 +40,7 @@ public void CreateHandlesStandardFunctionCompiler() public void CreateHandlesSpecialIfCompiler() { var functionRepository = FunctionRepository.Create(); - var functionCompilerFactory = new FunctionCompilerFactory(functionRepository); + var functionCompilerFactory = new FunctionCompilerFactory(functionRepository, _context); var function = new If(); var functionCompiler = functionCompilerFactory.Create(function); Assert.IsInstanceOfType(functionCompiler, typeof(IfFunctionCompiler)); @@ -43,7 +50,7 @@ public void CreateHandlesSpecialIfCompiler() public void CreateHandlesSpecialIfErrorCompiler() { var functionRepository = FunctionRepository.Create(); - var functionCompilerFactory = new FunctionCompilerFactory(functionRepository); + var functionCompilerFactory = new FunctionCompilerFactory(functionRepository, _context); var function = new IfError(); var functionCompiler = functionCompilerFactory.Create(function); Assert.IsInstanceOfType(functionCompiler, typeof(IfErrorFunctionCompiler)); @@ -53,7 +60,7 @@ public void CreateHandlesSpecialIfErrorCompiler() public void CreateHandlesSpecialIfNaCompiler() { var functionRepository = FunctionRepository.Create(); - var functionCompilerFactory = new FunctionCompilerFactory(functionRepository); + var functionCompilerFactory = new FunctionCompilerFactory(functionRepository, _context); var function = new IfNa(); var functionCompiler = functionCompilerFactory.Create(function); Assert.IsInstanceOfType(functionCompiler, typeof(IfNaFunctionCompiler)); @@ -63,7 +70,7 @@ public void CreateHandlesSpecialIfNaCompiler() public void CreateHandlesLookupFunctionCompiler() { var functionRepository = FunctionRepository.Create(); - var functionCompilerFactory = new FunctionCompilerFactory(functionRepository); + var functionCompilerFactory = new FunctionCompilerFactory(functionRepository, _context); var function = new Column(); var functionCompiler = functionCompilerFactory.Create(function); Assert.IsInstanceOfType(functionCompiler, typeof(LookupFunctionCompiler)); @@ -73,7 +80,7 @@ public void CreateHandlesLookupFunctionCompiler() public void CreateHandlesErrorFunctionCompiler() { var functionRepository = FunctionRepository.Create(); - var functionCompilerFactory = new FunctionCompilerFactory(functionRepository); + var functionCompilerFactory = new FunctionCompilerFactory(functionRepository, _context); var function = new IsError(); var functionCompiler = functionCompilerFactory.Create(function); Assert.IsInstanceOfType(functionCompiler, typeof(ErrorHandlingFunctionCompiler)); @@ -83,8 +90,8 @@ public void CreateHandlesErrorFunctionCompiler() public void CreateHandlesCustomFunctionCompiler() { var functionRepository = FunctionRepository.Create(); - functionRepository.LoadModule(new TestFunctionModule()); - var functionCompilerFactory = new FunctionCompilerFactory(functionRepository); + functionRepository.LoadModule(new TestFunctionModule(_context)); + var functionCompilerFactory = new FunctionCompilerFactory(functionRepository, _context); var function = new MyFunction(); var functionCompiler = functionCompilerFactory.Create(function); Assert.IsInstanceOfType(functionCompiler, typeof(MyFunctionCompiler)); @@ -94,10 +101,10 @@ public void CreateHandlesCustomFunctionCompiler() #region Nested Classes public class TestFunctionModule : FunctionsModule { - public TestFunctionModule() + public TestFunctionModule(ParsingContext context) { var myFunction = new MyFunction(); - var customCompiler = new MyFunctionCompiler(myFunction); + var customCompiler = new MyFunctionCompiler(myFunction, context); base.Functions.Add(MyFunction.Name, myFunction); base.CustomCompilers.Add(typeof(MyFunction), customCompiler); } @@ -114,8 +121,8 @@ public override CompileResult Execute(IEnumerable arguments, P public class MyFunctionCompiler : FunctionCompiler { - public MyFunctionCompiler(MyFunction function) : base(function) { } - public override CompileResult Compile(IEnumerable children, ParsingContext context) + public MyFunctionCompiler(MyFunction function, ParsingContext context) : base(function, context) { } + public override CompileResult Compile(IEnumerable children) { throw new NotImplementedException(); } diff --git a/EPPlusTest/FormulaParsing/IntegrationTests/BuiltInFunctions/RefAndLookupTests.cs b/EPPlusTest/FormulaParsing/IntegrationTests/BuiltInFunctions/RefAndLookupTests.cs index f1d7107f..ef452343 100644 --- a/EPPlusTest/FormulaParsing/IntegrationTests/BuiltInFunctions/RefAndLookupTests.cs +++ b/EPPlusTest/FormulaParsing/IntegrationTests/BuiltInFunctions/RefAndLookupTests.cs @@ -21,11 +21,12 @@ public class RefAndLookupTests : FormulaParserTestBase [TestInitialize] public void Initialize() { - _excelDataProvider = A.Fake(); - A.CallTo(() => _excelDataProvider.GetDimensionEnd(A.Ignored)).Returns(new ExcelCellAddress(10, 1)); - _parser = new FormulaParser(_excelDataProvider); _package = new ExcelPackage(); _worksheet = _package.Workbook.Worksheets.Add("Test"); + _excelDataProvider = A.Fake(); + A.CallTo(() => _excelDataProvider.GetDimensionEnd(A.Ignored)).Returns(new ExcelCellAddress(10, 1)); + A.CallTo(() => _excelDataProvider.GetWorkbookNameValues()).Returns(new ExcelNamedRangeCollection(_package.Workbook)); + _parser = new FormulaParser(_excelDataProvider); } [TestCleanup] @@ -88,27 +89,42 @@ public void HLookupShouldReturnCorrespondingValue() public void HLookupShouldReturnClosestValueBelowIfLastArgIsTrue() { var lookupAddress = "A1:B2"; - A.CallTo(() => _excelDataProvider.GetDimensionEnd(A.Ignored)).Returns(new ExcelCellAddress(5, 5)); - A.CallTo(() => _excelDataProvider.GetCellValue(WorksheetName,1, 1)).Returns(3); - A.CallTo(() => _excelDataProvider.GetCellValue(WorksheetName,1, 2)).Returns(5); - A.CallTo(() => _excelDataProvider.GetCellValue(WorksheetName,2, 1)).Returns(1); - A.CallTo(() => _excelDataProvider.GetCellValue(WorksheetName,2, 2)).Returns(2); - var result = _parser.Parse("HLOOKUP(4, " + lookupAddress + ", 2, true)"); - Assert.AreEqual(1, result); + using (var package = new ExcelPackage()) + { + var s = package.Workbook.Worksheets.Add("test"); + s.Cells[1, 1].Value = 3; + s.Cells[1, 2].Value = 5; + s.Cells[2, 1].Value = 1; + s.Cells[2, 2].Value = 2; + s.Cells[5, 5].Formula = "HLOOKUP(4, " + lookupAddress + ", 2, true)"; + s.Calculate(); + Assert.AreEqual(1, s.Cells[5,5].Value); + } } [TestMethod] public void LookupShouldReturnMatchingValue() { var lookupAddress = "A1:B2"; - A.CallTo(() => _excelDataProvider.GetCellValue(WorksheetName,1, 1)).Returns(3); - A.CallTo(() => _excelDataProvider.GetCellValue(WorksheetName,1, 2)).Returns(5); - A.CallTo(() => _excelDataProvider.GetCellValue(WorksheetName,2, 1)).Returns(4); - A.CallTo(() => _excelDataProvider.GetCellValue(WorksheetName,2, 2)).Returns(1); - var result = _parser.Parse("LOOKUP(4, " + lookupAddress + ")"); - Assert.AreEqual(1, result); + using (var package = new ExcelPackage()) + { + var s = package.Workbook.Worksheets.Add("test"); + s.Cells[1, 1].Value = 3; + s.Cells[1, 2].Value = 5; + s.Cells[2, 1].Value = 4; + s.Cells[2, 2].Value = 1; + s.Cells[5, 5].Formula = "LOOKUP(4, " + lookupAddress + ")"; + s.Calculate(); + Assert.AreEqual(1, s.Cells[5, 5].Value); + } + // A.CallTo(() => _excelDataProvider.GetCellValue(WorksheetName,1, 1)).Returns(3); + //A.CallTo(() => _excelDataProvider.GetCellValue(WorksheetName,1, 2)).Returns(5); + //A.CallTo(() => _excelDataProvider.GetCellValue(WorksheetName,2, 1)).Returns(4); + //A.CallTo(() => _excelDataProvider.GetCellValue(WorksheetName,2, 2)).Returns(1); + //var result = _parser.Parse("LOOKUP(4, " + lookupAddress + ")"); + //Assert.AreEqual(1, result); } - + [TestMethod] public void MatchShouldReturnIndexOfMatchingValue() { @@ -235,6 +251,21 @@ public void OffsetShouldReturnARange() } } + [TestMethod] + public void OffsetShouldReturnARange2() + { + using (var package = new ExcelPackage()) + { + var s1 = package.Workbook.Worksheets.Add("Test"); + s1.Cells["B1"].Value = 10d; + s1.Cells["B2"].Value = 10d; + s1.Cells["B3"].Value = 10d; + s1.Cells["A5"].Formula = "COUNTA(OFFSET(Test!B1, 0, 0, Test!B2, Test!B3))"; + s1.Calculate(); + Assert.AreEqual(3d, s1.Cells["A5"].Value); + } + } + [TestMethod] public void OffsetDirectReferenceToMultiRangeShouldSetValueError() {