blob: 06bd4a67a64c5a904fbf33d63bb0e0288460b17b [file] [log] [blame]
/*******************************************************************************
* 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;
}
}
}