| /******************************************************************************* |
| * You may amend and distribute as you like, but don't remove this header! |
| * |
| * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets. |
| * See http://www.codeplex.com/EPPlus for details. |
| * |
| * 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-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser) |
| *******************************************************************************/ |
| |
| using System; |
| using System.Collections.Generic; |
| using System.Linq; |
| using OfficeOpenXml.FormulaParsing.Excel.Functions; |
| using OfficeOpenXml.FormulaParsing.ExcelUtilities; |
| using OfficeOpenXml.FormulaParsing.Exceptions; |
| using OfficeOpenXml.FormulaParsing.ExpressionGraph; |
| using OfficeOpenXml.FormulaParsing.LexicalAnalysis; |
| using OfficeOpenXml.FormulaParsing.Utilities; |
| |
| namespace OfficeOpenXml.FormulaParsing; |
| |
| public class FormulaParser { |
| private readonly ParsingContext _parsingContext; |
| private readonly ExcelDataProvider _excelDataProvider; |
| |
| public FormulaParser(ExcelDataProvider excelDataProvider) |
| : this(excelDataProvider, ParsingContext.Create()) {} |
| |
| public FormulaParser(ExcelDataProvider excelDataProvider, ParsingContext parsingContext) { |
| parsingContext.Parser = this; |
| parsingContext.ExcelDataProvider = excelDataProvider; |
| parsingContext.NameValueProvider = new EpplusNameValueProvider(excelDataProvider); |
| parsingContext.RangeAddressFactory = new(excelDataProvider); |
| _parsingContext = parsingContext; |
| _excelDataProvider = excelDataProvider; |
| Configure(configuration => { |
| configuration |
| .SetLexer( |
| new Lexer( |
| _parsingContext.Configuration.FunctionRepository, |
| _parsingContext.NameValueProvider)) |
| .SetGraphBuilder(new ExpressionGraphBuilder(excelDataProvider, _parsingContext)) |
| .SetExpresionCompiler(new ExpressionCompiler()) |
| .FunctionRepository.LoadModule(new BuiltInFunctions()); |
| }); |
| } |
| |
| public void Configure(Action<ParsingConfiguration> configMethod) { |
| configMethod.Invoke(_parsingContext.Configuration); |
| _lexer = _parsingContext.Configuration.Lexer ?? _lexer; |
| _graphBuilder = _parsingContext.Configuration.GraphBuilder ?? _graphBuilder; |
| _compiler = _parsingContext.Configuration.ExpressionCompiler ?? _compiler; |
| } |
| |
| private ILexer _lexer; |
| private IExpressionGraphBuilder _graphBuilder; |
| private IExpressionCompiler _compiler; |
| |
| public ILexer Lexer => _lexer; |
| |
| public IEnumerable<string> FunctionNames => |
| _parsingContext.Configuration.FunctionRepository.FunctionNames; |
| |
| internal virtual object Parse(string formula, RangeAddress rangeAddress) { |
| using (var scope = _parsingContext.Scopes.NewScope(rangeAddress)) { |
| var tokens = _lexer.Tokenize(formula); |
| var graph = _graphBuilder.Build(tokens); |
| if (graph.Expressions.Count() == 0) { |
| return null; |
| } |
| return _compiler.Compile(graph.Expressions).Result; |
| } |
| } |
| |
| internal virtual object Parse(IEnumerable<Token> tokens, string worksheet, string address) { |
| var rangeAddress = _parsingContext.RangeAddressFactory.Create(address); |
| using (var scope = _parsingContext.Scopes.NewScope(rangeAddress)) { |
| var graph = _graphBuilder.Build(tokens); |
| if (graph.Expressions.Count() == 0) { |
| return null; |
| } |
| return _compiler.Compile(graph.Expressions).Result; |
| } |
| } |
| |
| internal virtual object ParseCell( |
| IEnumerable<Token> tokens, |
| string worksheet, |
| int row, |
| int column) { |
| var rangeAddress = _parsingContext.RangeAddressFactory.Create(worksheet, column, row); |
| using (var scope = _parsingContext.Scopes.NewScope(rangeAddress)) { |
| // _parsingContext.Dependencies.AddFormulaScope(scope); |
| var graph = _graphBuilder.Build(tokens); |
| if (graph.Expressions.Count() == 0) { |
| return 0d; |
| } |
| try { |
| var compileResult = _compiler.Compile(graph.Expressions); |
| // quick solution for the fact that an excelrange can be returned. |
| var rangeInfo = compileResult.Result as ExcelDataProvider.IRangeInfo; |
| if (rangeInfo == null) { |
| return compileResult.Result ?? 0d; |
| } |
| if (rangeInfo.IsEmpty) { |
| return 0d; |
| } |
| if (!rangeInfo.IsMulti) { |
| return rangeInfo.First().Value ?? 0d; |
| } |
| // ok to return multicell if it is a workbook scoped name. |
| if (string.IsNullOrEmpty(worksheet)) { |
| return rangeInfo; |
| } |
| if (_parsingContext.Debug) { |
| var msg = string.Format( |
| "A range with multiple cell was returned at row {0}, column {1}", |
| row, |
| column); |
| _parsingContext.Configuration.Logger.Log(_parsingContext, msg); |
| } |
| return ExcelErrorValue.Create(eErrorType.Value); |
| } catch (ExcelErrorValueException ex) { |
| if (_parsingContext.Debug) { |
| _parsingContext.Configuration.Logger.Log(_parsingContext, ex); |
| } |
| return ex.ErrorValue; |
| } |
| } |
| } |
| |
| public virtual object Parse(string formula, string address) { |
| return Parse(formula, _parsingContext.RangeAddressFactory.Create(address)); |
| } |
| |
| public virtual object Parse(string formula) { |
| return Parse(formula, RangeAddress.Empty); |
| } |
| |
| public virtual object ParseAt(string address) { |
| Require.That(address).Named("address").IsNotNullOrEmpty(); |
| var rangeAddress = _parsingContext.RangeAddressFactory.Create(address); |
| return ParseAt(rangeAddress.Worksheet, rangeAddress.FromRow, rangeAddress.FromCol); |
| } |
| |
| public virtual object ParseAt(string worksheetName, int row, int col) { |
| var f = _excelDataProvider.GetRangeFormula(worksheetName, row, col); |
| if (string.IsNullOrEmpty(f)) { |
| return _excelDataProvider.GetRangeValue(worksheetName, row, col); |
| } |
| return Parse(f, _parsingContext.RangeAddressFactory.Create(worksheetName, col, row)); |
| //var dataItem = _excelDataProvider.GetRangeValues(address).FirstOrDefault(); |
| //if (dataItem == null /*|| (dataItem.Value == null && dataItem.Formula == null)*/) return null; |
| //if (!string.IsNullOrEmpty(dataItem.Formula)) |
| //{ |
| // return Parse(dataItem.Formula, _parsingContext.RangeAddressFactory.Create(address)); |
| //} |
| //return Parse(dataItem.Value.ToString(), _parsingContext.RangeAddressFactory.Create(address)); |
| } |
| |
| internal void InitNewCalc() { |
| if (_excelDataProvider != null) { |
| _excelDataProvider.Reset(); |
| } |
| } |
| |
| // Praveen's Formula Parser |
| public ExpressionGraph.ExpressionGraph ParseToGraph(string formula) { |
| using (var scope = _parsingContext.Scopes.NewScope(RangeAddress.Empty)) { |
| var tokens = _lexer.Tokenize(formula); |
| var graph = _graphBuilder.Build(tokens); |
| return graph; |
| } |
| } |
| } |