/******************************************************************************* * 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 System.Text; using OfficeOpenXml.FormulaParsing.ExpressionGraph; using OfficeOpenXml.FormulaParsing; using OfficeOpenXml.FormulaParsing.Excel.Operators; using OfficeOpenXml.FormulaParsing.LexicalAnalysis; using OfficeOpenXml.FormulaParsing.Excel; using OfficeOpenXml.FormulaParsing.Excel.Functions; using OfficeOpenXml.FormulaParsing.ExcelUtilities; using OfficeOpenXml.FormulaParsing.Logging; using OfficeOpenXml.FormulaParsing.Utilities; using System.Diagnostics; using OfficeOpenXml.FormulaParsing.Exceptions; namespace OfficeOpenXml.FormulaParsing { public class FormulaParser : IDisposable { 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 RangeAddressFactory(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 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 { get { return _lexer; } } public IEnumerable FunctionNames { get { return _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 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 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; } else { 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); } else { 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(); } } public IFormulaParserLogger Logger { get { return _parsingContext.Configuration.Logger; } } public void Dispose() { if (_parsingContext.Debug) { _parsingContext.Configuration.Logger.Dispose(); } } } }