| /******************************************************************************* | |
| * 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; | |
| namespace AppsheetEpplus; | |
| 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 ParseToGraph(string formula) { | |
| using (var scope = _parsingContext.Scopes.NewScope(RangeAddress.Empty)) { | |
| var tokens = _lexer.Tokenize(formula); | |
| var graph = _graphBuilder.Build(tokens); | |
| return graph; | |
| } | |
| } | |
| } |