├── .gitignore ├── ExcelFormulaParser Solution.sln ├── Images ├── Excel.icns ├── Excel.ico ├── Excel.png ├── Excel_64x64.ico ├── Excel_64x64.png └── readme.txt ├── LICENSE ├── README.md ├── appveyor.yml ├── codecov-local.cmd ├── examples ├── ExcelFormulaParser.Expressions.Console │ ├── Book.xlsx │ ├── ExcelFormulaParser.Expressions.Console.csproj │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── log4net.config └── ExcelFormulaParserSample │ ├── ExcelFormulaParserSample.csproj │ ├── Program.cs │ ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings │ ├── SampleForm.Designer.cs │ ├── SampleForm.cs │ └── SampleForm.resx ├── original └── ExcelFormulaParser.v1.1.zip ├── src ├── ExcelFormulaExpressionParser │ ├── Compatibility │ │ └── TypeExtensions.cs │ ├── Constants │ │ ├── Errors.cs │ │ └── MathConstants.cs │ ├── ExcelFormulaContext.cs │ ├── ExcelFormulaExpressionParser.csproj │ ├── ExpressionParser.cs │ ├── Expressions │ │ ├── XArgExpression.cs │ │ └── XRangeExpression.cs │ ├── Extensions │ │ └── ExpressionExtensions.cs │ ├── Functions │ │ ├── DateFunctions.cs │ │ ├── LogicalFunctions.cs │ │ ├── LookupAndReferenceFunctions.cs │ │ └── MathFunctions.cs │ ├── Helpers │ │ └── DateTimeHelpers.cs │ ├── ICellFinder.cs │ ├── Models │ │ ├── CellAddress.cs │ │ ├── XArg.cs │ │ ├── XCell.cs │ │ ├── XNamedRange.cs │ │ ├── XRange.cs │ │ ├── XRow.cs │ │ ├── XSheet.cs │ │ └── XWorkbook.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── Utils │ │ ├── CellFinder.cs │ │ └── ExcelUtils.cs └── ExcelFormulaParser │ ├── Copyright.txt │ ├── ExcelFormula.cs │ ├── ExcelFormulaParser.csproj │ ├── ExcelFormulaStack.cs │ ├── ExcelFormulaToken.cs │ ├── ExcelFormulaTokenSubtype.cs │ ├── ExcelFormulaTokenType.cs │ ├── ExcelFormulaTokens.cs │ ├── IExcelFormulaContext.cs │ └── Properties │ └── AssemblyInfo.cs └── tests ├── ExcelFormulaExpressionParser.Tests ├── DateFunctionsTests.cs ├── ExcelFormulaExpressionParser.Tests.csproj ├── LookupAndReferenceFunctionsTests.cs ├── OperandFunctionTests.Date.cs ├── OperandFunctionTests.Logical.cs ├── OperandFunctionTests.Math.cs ├── OperandFunctionTests.Nested.cs ├── OperandFunctionTests.VLookup.cs └── OperatorPrefixLogicalTests.cs └── ExcelFormulaParser.Tests ├── ExcelFormulaParser.Tests.csproj ├── ExcelFormulaTests.cs └── OperatorMathTests.cs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /ExcelFormulaParser Solution.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{BCA71646-7799-4A23-9279-18C87E785645}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{26813EB3-A699-43E2-BA62-602FC8F0E9B2}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExcelFormulaParser", "src\ExcelFormulaParser\ExcelFormulaParser.csproj", "{53490EAA-4A87-43E6-9A3F-A6E8891CE152}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExcelFormulaParserSample", "examples\ExcelFormulaParserSample\ExcelFormulaParserSample.csproj", "{D05ECA40-6315-4339-BC32-8E922981218D}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Solution", "_Solution", "{95BDEA56-0346-4DC0-B2E2-E39E55D9CEFB}" 15 | ProjectSection(SolutionItems) = preProject 16 | appveyor.yml = appveyor.yml 17 | README.md = README.md 18 | EndProjectSection 19 | EndProject 20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{08EB3110-9DD0-42AF-87FC-6AB911DBC9F4}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExcelFormulaParser.Tests", "tests\ExcelFormulaParser.Tests\ExcelFormulaParser.Tests.csproj", "{DBC87F72-9E61-47DE-BB8C-35018DD9E9CA}" 23 | EndProject 24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExcelFormulaParser.Expressions.Console", "examples\ExcelFormulaParser.Expressions.Console\ExcelFormulaParser.Expressions.Console.csproj", "{4B2CC684-5281-43E2-A29C-04252381531B}" 25 | EndProject 26 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExcelFormulaExpressionParser", "src\ExcelFormulaExpressionParser\ExcelFormulaExpressionParser.csproj", "{935AF390-A30A-495A-BD54-730A0F012358}" 27 | EndProject 28 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExcelFormulaExpressionParser.Tests", "tests\ExcelFormulaExpressionParser.Tests\ExcelFormulaExpressionParser.Tests.csproj", "{DAF7A07A-C12F-4A33-83CF-F27703987C8A}" 29 | EndProject 30 | Global 31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 32 | Debug|Any CPU = Debug|Any CPU 33 | Release|Any CPU = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 36 | {53490EAA-4A87-43E6-9A3F-A6E8891CE152}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {53490EAA-4A87-43E6-9A3F-A6E8891CE152}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {53490EAA-4A87-43E6-9A3F-A6E8891CE152}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {53490EAA-4A87-43E6-9A3F-A6E8891CE152}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {D05ECA40-6315-4339-BC32-8E922981218D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {D05ECA40-6315-4339-BC32-8E922981218D}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {D05ECA40-6315-4339-BC32-8E922981218D}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {D05ECA40-6315-4339-BC32-8E922981218D}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {DBC87F72-9E61-47DE-BB8C-35018DD9E9CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {DBC87F72-9E61-47DE-BB8C-35018DD9E9CA}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {DBC87F72-9E61-47DE-BB8C-35018DD9E9CA}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {DBC87F72-9E61-47DE-BB8C-35018DD9E9CA}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {4B2CC684-5281-43E2-A29C-04252381531B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {4B2CC684-5281-43E2-A29C-04252381531B}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {4B2CC684-5281-43E2-A29C-04252381531B}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {4B2CC684-5281-43E2-A29C-04252381531B}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {935AF390-A30A-495A-BD54-730A0F012358}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {935AF390-A30A-495A-BD54-730A0F012358}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {935AF390-A30A-495A-BD54-730A0F012358}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {935AF390-A30A-495A-BD54-730A0F012358}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {DAF7A07A-C12F-4A33-83CF-F27703987C8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {DAF7A07A-C12F-4A33-83CF-F27703987C8A}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {DAF7A07A-C12F-4A33-83CF-F27703987C8A}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {DAF7A07A-C12F-4A33-83CF-F27703987C8A}.Release|Any CPU.Build.0 = Release|Any CPU 60 | EndGlobalSection 61 | GlobalSection(SolutionProperties) = preSolution 62 | HideSolutionNode = FALSE 63 | EndGlobalSection 64 | GlobalSection(NestedProjects) = preSolution 65 | {53490EAA-4A87-43E6-9A3F-A6E8891CE152} = {BCA71646-7799-4A23-9279-18C87E785645} 66 | {D05ECA40-6315-4339-BC32-8E922981218D} = {26813EB3-A699-43E2-BA62-602FC8F0E9B2} 67 | {DBC87F72-9E61-47DE-BB8C-35018DD9E9CA} = {08EB3110-9DD0-42AF-87FC-6AB911DBC9F4} 68 | {4B2CC684-5281-43E2-A29C-04252381531B} = {26813EB3-A699-43E2-BA62-602FC8F0E9B2} 69 | {935AF390-A30A-495A-BD54-730A0F012358} = {BCA71646-7799-4A23-9279-18C87E785645} 70 | {DAF7A07A-C12F-4A33-83CF-F27703987C8A} = {08EB3110-9DD0-42AF-87FC-6AB911DBC9F4} 71 | EndGlobalSection 72 | EndGlobal 73 | -------------------------------------------------------------------------------- /Images/Excel.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StefH/ExcelFormulaParser/9e9d624c3232837ce32f9d64e1cb95dccd0eba72/Images/Excel.icns -------------------------------------------------------------------------------- /Images/Excel.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StefH/ExcelFormulaParser/9e9d624c3232837ce32f9d64e1cb95dccd0eba72/Images/Excel.ico -------------------------------------------------------------------------------- /Images/Excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StefH/ExcelFormulaParser/9e9d624c3232837ce32f9d64e1cb95dccd0eba72/Images/Excel.png -------------------------------------------------------------------------------- /Images/Excel_64x64.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StefH/ExcelFormulaParser/9e9d624c3232837ce32f9d64e1cb95dccd0eba72/Images/Excel_64x64.ico -------------------------------------------------------------------------------- /Images/Excel_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StefH/ExcelFormulaParser/9e9d624c3232837ce32f9d64e1cb95dccd0eba72/Images/Excel_64x64.png -------------------------------------------------------------------------------- /Images/readme.txt: -------------------------------------------------------------------------------- 1 | Images are from Dakirby309-Simply-Styled-Microsoft-Excel-2013 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Stef Heyenrath 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Build and Code Coverage 2 | [![Build status](https://ci.appveyor.com/api/projects/status/3rh96qm7840gvjuf?svg=true)](https://ci.appveyor.com/project/StefH/excelformulaparser) 3 | [![codecov](https://codecov.io/gh/StefH/ExcelFormulaParser/branch/master/graph/badge.svg)](https://codecov.io/gh/StefH/ExcelFormulaParser) 4 | 5 | # ExcelFormulaParser 6 | An Excel Formula Parser, based on http://ewbi.blogs.com/develops/2007/03/excel_formula_p.html. 7 | 8 | [![NuGet Badge](https://buildstats.info/nuget/ExcelFormulaParser)](https://www.nuget.org/packages/ExcelFormulaParser) 9 | 10 | 11 | Supported frameworks: 12 | - net 2.0 13 | - netstandard 1.0 14 | 15 | # ExcelFormulaExpressionParser -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: Visual Studio 2017 2 | 3 | version: 1.0.0.{build} 4 | 5 | configuration: 6 | - Debug 7 | 8 | init: 9 | - ps: $Env:LABEL = "CI" + $Env:APPVEYOR_BUILD_NUMBER.PadLeft(5, "0") 10 | 11 | install: 12 | - ps: Start-FileDownload 'https://download.microsoft.com/download/8/F/9/8F9659B9-E628-4D1A-B6BF-C3004C8C954B/dotnet-1.1.1-sdk-win-x64.exe' 13 | - cmd: dotnet-1.1.1-sdk-win-x64.exe /quiet 14 | - ps: $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = "true" 15 | 16 | environment: 17 | PATH: $(PATH);$(PROGRAMFILES)\dotnet\ 18 | COVERALLS_REPO_TOKEN: 19 | secure: Eq/3VV5DVAeQAlQhe6hvy21IYPo5uY4fWKxvC4pxdq3giJzcwFp1QxBvRpXJ8Wkw 20 | 21 | before_build: 22 | # restore src and examples 23 | - dotnet restore .\src\ExcelFormulaParser\ExcelFormulaParser.csproj 24 | - dotnet restore .\src\ExcelFormulaExpressionParser\ExcelFormulaExpressionParser.csproj 25 | - nuget restore .\examples\ExcelFormulaParserSample\ExcelFormulaParserSample.csproj -PackagesDirectory packages 26 | 27 | build_script: 28 | # build ExcelFormulaParser 29 | - dotnet build .\src\ExcelFormulaParser\ExcelFormulaParser.csproj -c %CONFIGURATION% --framework net452 30 | 31 | # build ExcelFormulaExpressionParser 32 | - dotnet build .\src\ExcelFormulaExpressionParser\ExcelFormulaExpressionParser.csproj -c %CONFIGURATION% --framework net452 33 | 34 | # build ExcelFormulaParserSample 35 | # - cmd: msbuild .\examples\ExcelFormulaParserSample\ExcelFormulaParserSample.csproj /p:Configuration=%CONFIGURATION% 36 | 37 | # restore and build Tests 38 | - dotnet restore .\tests\ExcelFormulaParser.Tests\ExcelFormulaParser.Tests.csproj 39 | - dotnet build .\tests\ExcelFormulaParser.Tests\ExcelFormulaParser.Tests.csproj -c %CONFIGURATION% 40 | - dotnet restore .\tests\ExcelFormulaExpressionParser.Tests\ExcelFormulaExpressionParser.Tests.csproj 41 | - dotnet build .\tests\ExcelFormulaExpressionParser.Tests\ExcelFormulaExpressionParser.Tests.csproj -c %CONFIGURATION% 42 | 43 | test_script: 44 | - nuget.exe install OpenCover -ExcludeVersion 45 | - nuget.exe install coveralls.net -ExcludeVersion 46 | - pip install codecov 47 | 48 | - cmd: '"OpenCover\tools\OpenCover.Console.exe" -register:user -target:dotnet.exe -targetargs:"test tests\ExcelFormulaParser.Tests\ExcelFormulaParser.Tests.csproj --no-build" -returntargetcode -filter:"+[ExcelFormulaParser]* -[ExcelFormulaParser.Tests*]*" -mergeoutput -output:coverage.xml -oldstyle -searchdirs:".\tests\ExcelFormulaParser.Tests\bin\%CONFIGURATION%\net452"' 49 | - cmd: '"OpenCover\tools\OpenCover.Console.exe" -register:user -target:dotnet.exe -targetargs:"test tests\ExcelFormulaExpressionParser.Tests\ExcelFormulaExpressionParser.Tests.csproj --no-build" -returntargetcode -filter:"+[ExcelFormulaExpressionParser]* -[ExcelFormulaExpressionParser.Tests*]*" -mergeoutput -output:coverage.xml -oldstyle -searchdirs:".\tests\ExcelFormulaExpressionParser.Tests\bin\%CONFIGURATION%\net452"' 50 | - codecov -f "coverage.xml" 51 | - coveralls.net\tools\csmacnz.Coveralls.exe --opencover -i .\coverage.xml -------------------------------------------------------------------------------- /codecov-local.cmd: -------------------------------------------------------------------------------- 1 | rem https://www.appveyor.com/blog/2017/03/17/codecov/ 2 | 3 | %USERPROFILE%\.nuget\packages\opencover\4.6.519\tools\OpenCover.Console.exe -target:dotnet.exe -targetargs:"test tests\ExcelFormulaParser.Tests\ExcelFormulaParser.Tests.csproj --no-build" -filter:"+[ExcelFormulaParser]* -[ExcelFormulaParser.Tests*]*" -output:coverage.xml -register:user -oldStyle -searchdirs:"tests\ExcelFormulaParser.Tests\bin\debug\net452" 4 | 5 | %USERPROFILE%\.nuget\packages\ReportGenerator\2.5.6\tools\ReportGenerator.exe -reports:"coverage.xml" -targetdir:"report" 6 | 7 | start report\index.htm -------------------------------------------------------------------------------- /examples/ExcelFormulaParser.Expressions.Console/Book.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StefH/ExcelFormulaParser/9e9d624c3232837ce32f9d64e1cb95dccd0eba72/examples/ExcelFormulaParser.Expressions.Console/Book.xlsx -------------------------------------------------------------------------------- /examples/ExcelFormulaParser.Expressions.Console/ExcelFormulaParser.Expressions.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp1.0 6 | 1.0.1 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | PreserveNewest 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 4.3.0 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /examples/ExcelFormulaParser.Expressions.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using ExcelFormulaExpressionParser; 6 | using ExcelFormulaExpressionParser.Extensions; 7 | using ExcelFormulaExpressionParser.Helpers; 8 | using ExcelFormulaExpressionParser.Models; 9 | using OfficeOpenXml; 10 | using OfficeOpenXml.FormulaParsing.Utilities; 11 | using System.Globalization; 12 | using System.Reflection; 13 | using log4net; 14 | using log4net.Config; 15 | 16 | namespace ExcelFormulaParser.Expressions.Console 17 | { 18 | class Program 19 | { 20 | private static readonly ILog Log = LogManager.GetLogger(typeof(Program)); 21 | 22 | static void Main(string[] args) 23 | { 24 | var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); 25 | XmlConfigurator.Configure(logRepository, new FileInfo("log4net.config")); 26 | 27 | Log.Info("Entering application."); 28 | Test(); 29 | //CalcTest(); 30 | //ExcelTest(); 31 | } 32 | 33 | private static void Test() 34 | { 35 | using (var package = new ExcelPackage(new FileInfo("c:\\temp\\test.xlsx"))) 36 | { 37 | var wb = new XWorkbook(); 38 | 39 | foreach (var worksheet in package.Workbook.Worksheets) 40 | { 41 | var sheet = new XSheet(worksheet.Name); 42 | 43 | // Obtain the worksheet size 44 | ExcelCellAddress endCell = worksheet.Dimension.End; 45 | 46 | for (int r = 1; r <= endCell.Row; r++) 47 | { 48 | var xrow = new XRow(sheet, r); 49 | for (int c = 1; c <= endCell.Column; c++) 50 | { 51 | xrow.Cells.Add(ToXCell(sheet, xrow, worksheet.Cells[r, c])); 52 | } 53 | 54 | sheet.Rows.Add(xrow); 55 | } 56 | 57 | wb.Sheets.Add(sheet); 58 | } 59 | 60 | var names = package.Workbook.Names; 61 | foreach (var name in names) 62 | { 63 | wb.Names.Add(name.Name, name.Address); 64 | } 65 | 66 | var calcCell = wb.Sheets[2].Rows[16].Cells[2]; 67 | var parser = new ExpressionParser(calcCell.ExcelFormula, 0, (ExcelFormulaContext)calcCell.ExcelFormula.Context, wb); 68 | 69 | Expression x = parser.Parse(); 70 | System.Console.WriteLine($"Expression = `{x}`"); 71 | 72 | var o = x.Optimize(); 73 | 74 | System.Console.WriteLine($"Expression Optimize = `{o}`"); 75 | 76 | var result = o.LambdaInvoke(); 77 | 78 | System.Console.WriteLine($"result = `{result}`"); 79 | } 80 | } 81 | 82 | private static void ExcelTest() 83 | { 84 | using (var package = new ExcelPackage(new FileInfo("Book.xlsx"))) 85 | { 86 | var wb = new XWorkbook(); 87 | 88 | package.Workbook.Worksheets[1].Cells["B1"].Value = 77; 89 | 90 | foreach (var worksheet in package.Workbook.Worksheets) 91 | { 92 | var sheet = new XSheet(worksheet.Name); 93 | 94 | // Obtain the worksheet size 95 | ExcelCellAddress endCell = worksheet.Dimension.End; 96 | 97 | for (int r = 1; r <= endCell.Row; r++) 98 | { 99 | var xrow = new XRow(sheet, r); 100 | for (int c = 1; c <= endCell.Column; c++) 101 | { 102 | xrow.Cells.Add(ToXCell(sheet, xrow, worksheet.Cells[r, c])); 103 | } 104 | 105 | sheet.Rows.Add(xrow); 106 | } 107 | 108 | wb.Sheets.Add(sheet); 109 | } 110 | 111 | var names = package.Workbook.Names; 112 | foreach (var name in names) 113 | { 114 | wb.Names.Add(name.Name, name.Address); 115 | } 116 | 117 | var calcCell = wb.Sheets[0].Rows[3].Cells[1]; 118 | var parser = new ExpressionParser(calcCell.ExcelFormula, (ExcelFormulaContext)calcCell.ExcelFormula.Context, wb); 119 | 120 | Expression x = parser.Parse(); 121 | System.Console.WriteLine($"Expression = `{x}`"); 122 | 123 | var o = x.Optimize(); 124 | 125 | System.Console.WriteLine($"Expression Optimize = `{o}`"); 126 | 127 | LambdaExpression le = Expression.Lambda(o); 128 | 129 | var result = le.Compile().DynamicInvoke(); 130 | 131 | System.Console.WriteLine($"result = `{result}`"); 132 | 133 | var bool2 = wb.Sheets[0].Rows[4].Cells[1]; 134 | var boolParser = new ExpressionParser(bool2.ExcelFormula, (ExcelFormulaContext)bool2.ExcelFormula.Context, wb); 135 | 136 | Expression bx = boolParser.Parse(); 137 | System.Console.WriteLine($"Expression = `{bx}`"); 138 | 139 | var o2 = bx.Optimize(); 140 | 141 | System.Console.WriteLine($"Expression Optimize = `{o2}`"); 142 | 143 | var bresult = Expression.Lambda(o2).Compile().DynamicInvoke(); 144 | System.Console.WriteLine($"bresult = `{bresult}`"); 145 | 146 | 147 | var sum = wb.Sheets[0].Rows[6].Cells[1]; 148 | var sumParser = new ExpressionParser(sum.ExcelFormula, (ExcelFormulaContext)sum.ExcelFormula.Context, wb); 149 | 150 | Expression sumE = sumParser.Parse(); 151 | System.Console.WriteLine($"Expression = `{sumE}`"); 152 | 153 | var sumEOpt = sumE.Optimize(); 154 | 155 | System.Console.WriteLine($"Expression Optimize = `{sumEOpt}`"); 156 | 157 | var sumresult = Expression.Lambda(sumEOpt).Compile().DynamicInvoke(); 158 | System.Console.WriteLine($"sumresult = `{sumresult}`"); 159 | 160 | 161 | var sumSqrt = wb.Sheets[3].Rows[0].Cells[1]; 162 | var sumSqrtParser = new ExpressionParser(sumSqrt.ExcelFormula, (ExcelFormulaContext)sumSqrt.ExcelFormula.Context, wb); 163 | 164 | Expression sumSqrtE = sumSqrtParser.Parse(); 165 | //System.Console.WriteLine($"Expression = `{sumSqrtE}`"); 166 | 167 | var sumSqrtEOpt = sumSqrtE.Optimize(); 168 | 169 | //System.Console.WriteLine($"Expression Optimize = `{sumSqrtEOpt}`"); 170 | 171 | var sumSqrtresult = Expression.Lambda(sumSqrtEOpt).Compile().DynamicInvoke(); 172 | System.Console.WriteLine($"sumresult = `{sumSqrtresult}`"); 173 | 174 | 175 | var dateyear = wb.Sheets[0].Rows[7].Cells[2]; 176 | var dateyearParser = new ExpressionParser(dateyear.ExcelFormula, (ExcelFormulaContext)dateyear.ExcelFormula.Context, wb); 177 | 178 | Expression dateyearE = dateyearParser.Parse(); 179 | //System.Console.WriteLine($"Expression = `{sumSqrtE}`"); 180 | 181 | var dateyearO = dateyearE.Optimize(); 182 | 183 | //System.Console.WriteLine($"Expression Optimize = `{sumSqrtEOpt}`"); 184 | 185 | var dateyearResult = Expression.Lambda(dateyearO).Compile().DynamicInvoke(); 186 | System.Console.WriteLine($"dateyearResult = `{dateyearResult}`"); 187 | 188 | var dateyearnow = wb.Sheets[0].Rows[8].Cells[2]; 189 | var dateyearnowParser = new ExpressionParser(dateyearnow.ExcelFormula, (ExcelFormulaContext)dateyearnow.ExcelFormula.Context, wb); 190 | 191 | var dateyearnowResult = dateyearnowParser.Parse().LambdaInvoke(); 192 | System.Console.WriteLine($"dateyearnowResult = `{dateyearnowResult}`"); 193 | 194 | var named1 = wb.Sheets[0].Rows[9].Cells[1]; 195 | var namedParser1 = new ExpressionParser(named1.ExcelFormula, (ExcelFormulaContext)named1.ExcelFormula.Context, wb); 196 | 197 | var namedResult1 = namedParser1.Parse().LambdaInvoke(); 198 | System.Console.WriteLine($"namedResult1 = `{namedResult1}`"); 199 | 200 | var named2 = wb.Sheets[0].Rows[10].Cells[1]; 201 | var namedParser2 = new ExpressionParser(named2.ExcelFormula, (ExcelFormulaContext)named2.ExcelFormula.Context, wb); 202 | 203 | var namedResult2 = namedParser2.Parse().LambdaInvoke(); 204 | System.Console.WriteLine($"namedResult2 = `{namedResult2}`"); 205 | 206 | 207 | var vlookup = wb.Sheets[3].Rows[0].Cells[4]; 208 | var vlookupParser = new ExpressionParser(vlookup.ExcelFormula, (ExcelFormulaContext)vlookup.ExcelFormula.Context, wb); 209 | var vlookupResult = vlookupParser.Parse().LambdaInvoke(); 210 | System.Console.WriteLine($"vlookupResult = `{vlookupResult}`"); 211 | 212 | var vlookup2 = wb.Sheets[3].Rows[1].Cells[4]; 213 | var vlookupParser2 = new ExpressionParser(vlookup2.ExcelFormula, (ExcelFormulaContext)vlookup2.ExcelFormula.Context, wb); 214 | var vlookupResult2 = vlookupParser2.Parse().LambdaInvoke(); 215 | System.Console.WriteLine($"vlookupResult2 = `{vlookupResult2}`"); 216 | 217 | int u = 0; 218 | } 219 | } 220 | 221 | private static XCell ToXCell(XSheet sheet, XRow row, ExcelRange range) 222 | { 223 | var cell = new XCell(row, range.Address) 224 | { 225 | FullAddress = $"{range.Worksheet.Name}!{range.Address}" 226 | }; 227 | 228 | var context = new ExcelFormulaContext 229 | { 230 | Sheet = sheet 231 | }; 232 | 233 | if (!string.IsNullOrEmpty(range.Formula)) 234 | { 235 | cell.Formula = "=" + range.Formula; 236 | cell.ExcelFormula = new ExcelFormula(cell.Formula, context); 237 | } 238 | else 239 | { 240 | cell.Value = range.Value; 241 | 242 | if (range.Value == null) 243 | { 244 | return cell; 245 | } 246 | 247 | if (range.Value is bool) 248 | { 249 | cell.ExcelFormula = new ExcelFormula((bool)range.Value ? "=TRUE" : "=FALSE", context); 250 | } 251 | else if (range.Value is DateTime) 252 | { 253 | double value = DateTimeHelpers.ToOADate((DateTime)range.Value); 254 | cell.ExcelFormula = new ExcelFormula("=" + value.ToString(CultureInfo.InvariantCulture), context); 255 | } 256 | else if (range.Value.IsNumeric()) 257 | { 258 | double value = Convert.ToDouble(range.Value); 259 | cell.ExcelFormula = new ExcelFormula("=" + value.ToString(CultureInfo.InvariantCulture), context); 260 | } 261 | else 262 | { 263 | cell.ExcelFormula = new ExcelFormula(string.Format("=\"{0}\"", range.Text), context); 264 | } 265 | } 266 | 267 | return cell; 268 | } 269 | 270 | private static void CalcTest() 271 | { 272 | // haakjes, machtsverheffen, vermenigvuldigen, delen, worteltrekken, optellen, aftrekken 273 | //var excelFormula = new ExcelFormula("=2^3 - -(1+1+1) * ROUND(4/2.7,2) + POWER(1+1,4) + 500 + SIN(3.1415926) + COS(3.1415926/2) + ABS(-1)"); 274 | //var parser = new ExpressionParser(excelFormula); 275 | 276 | //Expression x = parser.Parse(); 277 | //System.Console.WriteLine($"Expression = `{x}`"); 278 | 279 | //var o = x.Optimize(); 280 | 281 | //System.Console.WriteLine($"Expression = `{o}`"); 282 | 283 | //LambdaExpression le = Expression.Lambda(o); 284 | 285 | //var result = le.Compile().DynamicInvoke(); 286 | 287 | //System.Console.WriteLine($"result = `{result}`"); 288 | 289 | //var trunc2 = new ExcelFormula("=TRUNC(91.789, 2)"); 290 | //System.Console.WriteLine("trunc2 = `{0}`", Expression.Lambda(new ExpressionParser(trunc2).Parse()).Compile().DynamicInvoke()); 291 | 292 | //var trunc0 = new ExcelFormula("=TRUNC(91.789)"); 293 | //System.Console.WriteLine("trunc0 = `{0}`", Expression.Lambda(new ExpressionParser(trunc0).Parse()).Compile().DynamicInvoke()); 294 | 295 | //var gt = new ExcelFormula("=1>2"); 296 | //System.Console.WriteLine("gt = `{0}`", Expression.Lambda(new ExpressionParser(gt).Parse()).Compile().DynamicInvoke()); 297 | 298 | //var sum = new ExcelFormula("=SUM(1,2,3)"); 299 | //System.Console.WriteLine("sum = `{0}`", Expression.Lambda(new ExpressionParser(sum).Parse()).Compile().DynamicInvoke()); 300 | 301 | var now = new ExcelFormula("=NOW()"); 302 | System.Console.WriteLine("{0} : `{1}`", now.Formula, Expression.Lambda(new ExpressionParser(now).Parse()).Compile().DynamicInvoke()); 303 | } 304 | } 305 | } -------------------------------------------------------------------------------- /examples/ExcelFormulaParser.Expressions.Console/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: log4net.Config.XmlConfigurator(Watch = true)] -------------------------------------------------------------------------------- /examples/ExcelFormulaParser.Expressions.Console/log4net.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/ExcelFormulaParserSample/ExcelFormulaParserSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Debug 4 | AnyCPU 5 | 8.0.50727 6 | 2.0 7 | {D05ECA40-6315-4339-BC32-8E922981218D} 8 | WinExe 9 | Properties 10 | ExcelFormulaParserSample 11 | ExcelFormulaParserSample 12 | v2.0 13 | 14 | 15 | 16 | 17 | 2.0 18 | publish\ 19 | true 20 | Disk 21 | false 22 | Foreground 23 | 7 24 | Days 25 | false 26 | false 27 | true 28 | 0 29 | 1.0.0.%2a 30 | false 31 | false 32 | true 33 | 34 | 35 | true 36 | full 37 | false 38 | bin\Debug\ 39 | DEBUG;TRACE 40 | prompt 41 | 4 42 | 43 | 44 | pdbonly 45 | true 46 | bin\Release\ 47 | TRACE 48 | prompt 49 | 4 50 | 51 | 52 | ../../Images/Excel_64x64.ico 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | Form 62 | 63 | 64 | SampleForm.cs 65 | 66 | 67 | 68 | 69 | Designer 70 | SampleForm.cs 71 | 72 | 73 | ResXFileCodeGenerator 74 | Resources.Designer.cs 75 | Designer 76 | 77 | 78 | True 79 | Resources.resx 80 | True 81 | 82 | 83 | SettingsSingleFileGenerator 84 | Settings.Designer.cs 85 | 86 | 87 | True 88 | Settings.settings 89 | True 90 | 91 | 92 | 93 | 94 | False 95 | .NET Framework 3.5 SP1 96 | true 97 | 98 | 99 | 100 | 101 | {53490eaa-4a87-43e6-9a3f-a6e8891ce152} 102 | ExcelFormulaParser 103 | 104 | 105 | 106 | 113 | -------------------------------------------------------------------------------- /examples/ExcelFormulaParserSample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows.Forms; 4 | 5 | namespace ExcelFormulaParserSample 6 | { 7 | static class Program 8 | { 9 | /// 10 | /// The main entry point for the application. 11 | /// 12 | [STAThread] 13 | static void Main() 14 | { 15 | Application.EnableVisualStyles(); 16 | Application.SetCompatibleTextRenderingDefault(false); 17 | Application.Run(new SampleForm()); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /examples/ExcelFormulaParserSample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ExcelFormulaParserSample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("ewbi.com")] 12 | [assembly: AssemblyProduct("ExcelFormulaParserSample")] 13 | [assembly: AssemblyCopyright("Copyright © 2007 E. W. Bachtal, Inc.")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("b7a4d8b2-c955-426a-8184-ca941465f2fb")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | -------------------------------------------------------------------------------- /examples/ExcelFormulaParserSample/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ExcelFormulaParserSample.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ExcelFormulaParserSample.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/ExcelFormulaParserSample/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /examples/ExcelFormulaParserSample/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ExcelFormulaParserSample.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/ExcelFormulaParserSample/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/ExcelFormulaParserSample/SampleForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace ExcelFormulaParserSample 2 | { 3 | partial class SampleForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); 32 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); 33 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle(); 34 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle4 = new System.Windows.Forms.DataGridViewCellStyle(); 35 | this.ExcelFormulaTextBox = new System.Windows.Forms.TextBox(); 36 | this.label1 = new System.Windows.Forms.Label(); 37 | this.ParseButton = new System.Windows.Forms.Button(); 38 | this.panel1 = new System.Windows.Forms.Panel(); 39 | this.ExcelFormulaTokensGridView = new System.Windows.Forms.DataGridView(); 40 | this.index = new System.Windows.Forms.DataGridViewTextBoxColumn(); 41 | this.type = new System.Windows.Forms.DataGridViewTextBoxColumn(); 42 | this.subtype = new System.Windows.Forms.DataGridViewTextBoxColumn(); 43 | this.token = new System.Windows.Forms.DataGridViewTextBoxColumn(); 44 | this.tokentree = new System.Windows.Forms.DataGridViewTextBoxColumn(); 45 | this.panel1.SuspendLayout(); 46 | ((System.ComponentModel.ISupportInitialize) (this.ExcelFormulaTokensGridView)).BeginInit(); 47 | this.SuspendLayout(); 48 | // 49 | // ExcelFormulaTextBox 50 | // 51 | this.ExcelFormulaTextBox.Location = new System.Drawing.Point(11, 33); 52 | this.ExcelFormulaTextBox.Name = "ExcelFormulaTextBox"; 53 | this.ExcelFormulaTextBox.Size = new System.Drawing.Size(658, 21); 54 | this.ExcelFormulaTextBox.TabIndex = 0; 55 | this.ExcelFormulaTextBox.Enter += new System.EventHandler(this.ExcelFormulaTextBox_Enter); 56 | // 57 | // label1 58 | // 59 | this.label1.AutoSize = true; 60 | this.label1.Location = new System.Drawing.Point(11, 16); 61 | this.label1.Margin = new System.Windows.Forms.Padding(0); 62 | this.label1.Name = "label1"; 63 | this.label1.Size = new System.Drawing.Size(75, 13); 64 | this.label1.TabIndex = 1; 65 | this.label1.Text = "Excel formula:"; 66 | // 67 | // ParseButton 68 | // 69 | this.ParseButton.Location = new System.Drawing.Point(675, 31); 70 | this.ParseButton.Name = "ParseButton"; 71 | this.ParseButton.Size = new System.Drawing.Size(65, 24); 72 | this.ParseButton.TabIndex = 2; 73 | this.ParseButton.Text = "Parse"; 74 | this.ParseButton.UseVisualStyleBackColor = true; 75 | this.ParseButton.Click += new System.EventHandler(this.ParseButton_Click); 76 | // 77 | // panel1 78 | // 79 | this.panel1.AutoSize = true; 80 | this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; 81 | this.panel1.Controls.Add(this.ExcelFormulaTokensGridView); 82 | this.panel1.Location = new System.Drawing.Point(11, 64); 83 | this.panel1.Name = "panel1"; 84 | this.panel1.Size = new System.Drawing.Size(729, 580); 85 | this.panel1.TabIndex = 4; 86 | // 87 | // ExcelFormulaTokensGridView 88 | // 89 | this.ExcelFormulaTokensGridView.AllowUserToAddRows = false; 90 | this.ExcelFormulaTokensGridView.AllowUserToDeleteRows = false; 91 | this.ExcelFormulaTokensGridView.AllowUserToResizeRows = false; 92 | this.ExcelFormulaTokensGridView.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.AllCellsExceptHeaders; 93 | this.ExcelFormulaTokensGridView.BorderStyle = System.Windows.Forms.BorderStyle.None; 94 | this.ExcelFormulaTokensGridView.CellBorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.SingleHorizontal; 95 | this.ExcelFormulaTokensGridView.ColumnHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.None; 96 | dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; 97 | dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Control; 98 | dataGridViewCellStyle1.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte) (0))); 99 | dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.WindowText; 100 | dataGridViewCellStyle1.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2); 101 | dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight; 102 | dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText; 103 | dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True; 104 | this.ExcelFormulaTokensGridView.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1; 105 | this.ExcelFormulaTokensGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; 106 | this.ExcelFormulaTokensGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { 107 | this.index, 108 | this.type, 109 | this.subtype, 110 | this.token, 111 | this.tokentree}); 112 | dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; 113 | dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Window; 114 | dataGridViewCellStyle2.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte) (0))); 115 | dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.ControlText; 116 | dataGridViewCellStyle2.Padding = new System.Windows.Forms.Padding(1, 0, 0, 0); 117 | dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Window; 118 | dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.ControlText; 119 | dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False; 120 | this.ExcelFormulaTokensGridView.DefaultCellStyle = dataGridViewCellStyle2; 121 | this.ExcelFormulaTokensGridView.Dock = System.Windows.Forms.DockStyle.Fill; 122 | this.ExcelFormulaTokensGridView.Location = new System.Drawing.Point(0, 0); 123 | this.ExcelFormulaTokensGridView.Name = "ExcelFormulaTokensGridView"; 124 | this.ExcelFormulaTokensGridView.ReadOnly = true; 125 | dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; 126 | dataGridViewCellStyle3.BackColor = System.Drawing.SystemColors.Control; 127 | dataGridViewCellStyle3.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte) (0))); 128 | dataGridViewCellStyle3.ForeColor = System.Drawing.SystemColors.WindowText; 129 | dataGridViewCellStyle3.SelectionBackColor = System.Drawing.SystemColors.Highlight; 130 | dataGridViewCellStyle3.SelectionForeColor = System.Drawing.SystemColors.HighlightText; 131 | dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.True; 132 | this.ExcelFormulaTokensGridView.RowHeadersDefaultCellStyle = dataGridViewCellStyle3; 133 | this.ExcelFormulaTokensGridView.RowHeadersVisible = false; 134 | dataGridViewCellStyle4.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte) (0))); 135 | this.ExcelFormulaTokensGridView.RowsDefaultCellStyle = dataGridViewCellStyle4; 136 | this.ExcelFormulaTokensGridView.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; 137 | this.ExcelFormulaTokensGridView.ShowCellErrors = false; 138 | this.ExcelFormulaTokensGridView.ShowCellToolTips = false; 139 | this.ExcelFormulaTokensGridView.ShowEditingIcon = false; 140 | this.ExcelFormulaTokensGridView.ShowRowErrors = false; 141 | this.ExcelFormulaTokensGridView.Size = new System.Drawing.Size(725, 576); 142 | this.ExcelFormulaTokensGridView.StandardTab = true; 143 | this.ExcelFormulaTokensGridView.TabIndex = 4; 144 | // 145 | // index 146 | // 147 | this.index.HeaderText = "index"; 148 | this.index.Name = "index"; 149 | this.index.ReadOnly = true; 150 | this.index.Width = 50; 151 | // 152 | // type 153 | // 154 | this.type.DataPropertyName = "Type"; 155 | this.type.HeaderText = "type"; 156 | this.type.Name = "type"; 157 | this.type.ReadOnly = true; 158 | this.type.Width = 130; 159 | // 160 | // subtype 161 | // 162 | this.subtype.DataPropertyName = "Subtype"; 163 | this.subtype.HeaderText = "subtype"; 164 | this.subtype.Name = "subtype"; 165 | this.subtype.ReadOnly = true; 166 | this.subtype.Width = 120; 167 | // 168 | // token 169 | // 170 | this.token.DataPropertyName = "Value"; 171 | this.token.HeaderText = "token"; 172 | this.token.Name = "token"; 173 | this.token.ReadOnly = true; 174 | this.token.Width = 200; 175 | // 176 | // tokentree 177 | // 178 | this.tokentree.HeaderText = "tokentree"; 179 | this.tokentree.Name = "tokentree"; 180 | this.tokentree.ReadOnly = true; 181 | this.tokentree.Width = 225; 182 | // 183 | // SampleForm 184 | // 185 | this.AcceptButton = this.ParseButton; 186 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 187 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 188 | this.ClientSize = new System.Drawing.Size(750, 656); 189 | this.Controls.Add(this.panel1); 190 | this.Controls.Add(this.ParseButton); 191 | this.Controls.Add(this.label1); 192 | this.Controls.Add(this.ExcelFormulaTextBox); 193 | this.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte) (0))); 194 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 195 | this.MaximizeBox = false; 196 | this.Name = "SampleForm"; 197 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 198 | this.Text = "ExcelFormulaParserSample"; 199 | this.panel1.ResumeLayout(false); 200 | ((System.ComponentModel.ISupportInitialize) (this.ExcelFormulaTokensGridView)).EndInit(); 201 | this.ResumeLayout(false); 202 | this.PerformLayout(); 203 | 204 | } 205 | 206 | #endregion 207 | 208 | private System.Windows.Forms.TextBox ExcelFormulaTextBox; 209 | private System.Windows.Forms.Label label1; 210 | private System.Windows.Forms.Button ParseButton; 211 | private System.Windows.Forms.Panel panel1; 212 | private System.Windows.Forms.DataGridView ExcelFormulaTokensGridView; 213 | private System.Windows.Forms.DataGridViewTextBoxColumn index; 214 | private System.Windows.Forms.DataGridViewTextBoxColumn type; 215 | private System.Windows.Forms.DataGridViewTextBoxColumn subtype; 216 | private System.Windows.Forms.DataGridViewTextBoxColumn token; 217 | private System.Windows.Forms.DataGridViewTextBoxColumn tokentree; 218 | } 219 | } 220 | 221 | -------------------------------------------------------------------------------- /examples/ExcelFormulaParserSample/SampleForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Text; 4 | using System.Windows.Forms; 5 | using ExcelFormulaParser; 6 | 7 | namespace ExcelFormulaParserSample 8 | { 9 | public partial class SampleForm : Form 10 | { 11 | public SampleForm() 12 | { 13 | InitializeComponent(); 14 | } 15 | 16 | private void ParseButton_Click(object sender, EventArgs e) 17 | { 18 | ExcelFormula excelFormula = new ExcelFormula(ExcelFormulaTextBox.Text); 19 | 20 | ExcelFormulaTokensGridView.DataSource = new BindingList(excelFormula); 21 | 22 | int indentCount = 0; 23 | for (int i = 0; i < excelFormula.Count; i++) 24 | { 25 | ExcelFormulaTokensGridView.Rows[i].Cells["index"].Value = i + 1; 26 | ExcelFormulaToken token = excelFormula[i]; 27 | if (token.Subtype == ExcelFormulaTokenSubtype.Stop) 28 | { 29 | indentCount -= indentCount > 0 ? 1 : 0; 30 | } 31 | 32 | StringBuilder indent = new StringBuilder(); 33 | indent.Append("|"); 34 | for (int ind = 0; ind < indentCount; ind++) 35 | { 36 | indent.Append(" |"); 37 | } 38 | indent.Append(token.Value); 39 | 40 | ExcelFormulaTokensGridView.Rows[i].Cells["tokentree"].Value = indent.ToString(); 41 | if (token.Subtype == ExcelFormulaTokenSubtype.Start) 42 | { 43 | indentCount += 1; 44 | } 45 | } 46 | } 47 | 48 | private void ExcelFormulaTextBox_Enter(object sender, EventArgs e) 49 | { 50 | ExcelFormulaTextBox.SelectAll(); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /examples/ExcelFormulaParserSample/SampleForm.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | True 122 | 123 | 124 | True 125 | 126 | 127 | True 128 | 129 | 130 | True 131 | 132 | 133 | True 134 | 135 | -------------------------------------------------------------------------------- /original/ExcelFormulaParser.v1.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StefH/ExcelFormulaParser/9e9d624c3232837ce32f9d64e1cb95dccd0eba72/original/ExcelFormulaParser.v1.1.zip -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Compatibility/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace ExcelFormulaExpressionParser.Compatibility 6 | { 7 | public static class TypeExtensions 8 | { 9 | public static MethodInfo FindMethod(this Type type, string name, Type[] types) 10 | { 11 | #if NETSTANDARD 12 | return type.GetTypeInfo().GetDeclaredMethods(name).Single(mi => mi.GetParameters().Select(p => p.ParameterType).SequenceEqual(types)); 13 | #else 14 | return type.GetMethod(name, types); 15 | #endif 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Constants/Errors.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExcelFormulaExpressionParser.Constants 4 | { 5 | internal static class Errors 6 | { 7 | public static readonly Expression Div0 = Expression.Constant("#DIV/0!"); 8 | public static readonly Expression NA = Expression.Constant("#N/A"); 9 | public static readonly Expression Name = Expression.Constant("#NAME?"); 10 | public static readonly Expression Null = Expression.Constant("#NULL!"); 11 | public static readonly Expression Num = Expression.Constant("#NUM!"); 12 | public static readonly Expression Ref = Expression.Constant("#REF!"); 13 | public static readonly Expression Value = Expression.Constant("#VALUE!"); 14 | } 15 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Constants/MathConstants.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace ExcelFormulaExpressionParser.Constants 4 | { 5 | internal static class MathConstants 6 | { 7 | public static readonly Expression Constant0 = Expression.Constant(0d); 8 | public static readonly Expression Constant10 = Expression.Constant(10d); 9 | } 10 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/ExcelFormulaContext.cs: -------------------------------------------------------------------------------- 1 | using ExcelFormulaExpressionParser.Models; 2 | using ExcelFormulaParser; 3 | 4 | namespace ExcelFormulaExpressionParser 5 | { 6 | public class ExcelFormulaContext : IExcelFormulaContext 7 | { 8 | public XSheet Sheet { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/ExcelFormulaExpressionParser.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net40;net452;netstandard1.3 5 | 1.0.0.0 6 | Stef Heyenrath 7 | Stef Heyenrath 8 | ../../Images/Excel.ico 9 | ewbi.com 10 | A library to parse Excel formulas to System.Linq.Expressions 11 | https://github.com/StefH/ExcelFormulaParser 12 | git 13 | excel;formula;parser 14 | 15 | https://github.com/StefH/ExcelFormulaParser 16 | https://raw.githubusercontent.com/StefH/ExcelFormulaParser/master/LICENSE 17 | https://raw.githubusercontent.com/StefH/ExcelFormulaParser/master/Images/Excel_64x64.png 18 | True 19 | full 20 | 21 | 22 | 23 | NETSTANDARD 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/ExpressionParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using ExcelFormulaExpressionParser.Functions; 7 | using ExcelFormulaExpressionParser.Models; 8 | using ExcelFormulaExpressionParser.Utils; 9 | using ExcelFormulaParser; 10 | using JetBrains.Annotations; 11 | using TokenType = ExcelFormulaParser.ExcelFormulaTokenType; 12 | using TokenSubtype = ExcelFormulaParser.ExcelFormulaTokenSubtype; 13 | using ExcelFormulaExpressionParser.Expressions; 14 | using log4net; 15 | 16 | namespace ExcelFormulaExpressionParser 17 | { 18 | public class ExpressionParser 19 | { 20 | private static readonly ILog Log = LogManager.GetLogger(typeof(ExpressionParser)); 21 | 22 | private int _index; 23 | private int _level; 24 | 25 | private ExcelFormulaToken CT => _list[_index]; 26 | 27 | private readonly IList _list; 28 | private readonly ExcelFormulaContext _context; 29 | private readonly ICellFinder _finder; 30 | 31 | /// 32 | /// ExpressionParser 33 | /// 34 | /// The ExcelFormula or a list from ExcelFormulaTokens. 35 | public ExpressionParser([NotNull] IList tokens) : 36 | this(tokens, 0, null, (XWorkbook)null) 37 | { 38 | } 39 | 40 | /// 41 | /// ExpressionParser 42 | /// 43 | /// The ExcelFormula or a list from ExcelFormulaTokens. 44 | /// The level (default 0). 45 | /// The ExcelFormulaContext. (Optional if no real Excel Workbook is parsed.) 46 | /// The Excel Workbook. (Optional if no real Excel Workbook is parsed.) 47 | public ExpressionParser([NotNull] IList tokens, int level, [CanBeNull] ExcelFormulaContext context = null, [CanBeNull] XWorkbook workbook = null) : 48 | this(tokens, level, context, workbook != null ? new CellFinder(workbook) : null) 49 | { 50 | } 51 | 52 | /// 53 | /// ExpressionParser 54 | /// 55 | /// The ExcelFormula or a list from ExcelFormulaTokens. 56 | /// The ExcelFormulaContext. (Optional if no real Excel Workbook is parsed.) 57 | /// The Excel Workbook. (Optional if no real Excel Workbook is parsed.) 58 | public ExpressionParser([NotNull] IList tokens, [CanBeNull] ExcelFormulaContext context = null, [CanBeNull] XWorkbook workbook = null) : 59 | this(tokens, 0, context, workbook != null ? new CellFinder(workbook) : null) 60 | { 61 | } 62 | 63 | /// 64 | /// ExpressionParser 65 | /// 66 | /// The ExcelFormula or a list from ExcelFormulaTokens. 67 | /// The level (default 0). 68 | /// The ExcelFormulaContext. (Optional if no real Excel Workbook is parsed.) 69 | /// The cellfinder to finds cells in a workbook. (Optional if no real Excel Workbook is parsed.) 70 | public ExpressionParser([NotNull] IList tokens, int level = 0, [CanBeNull] ExcelFormulaContext context = null, [CanBeNull] ICellFinder finder = null) 71 | { 72 | _list = tokens; 73 | _level = level; 74 | _context = context; 75 | _finder = finder; 76 | 77 | // Log.InfoFormat("Formula : '{0}', Sheet : '{1}'", GetFormula(), context != null ? context.Sheet.Name : ""); 78 | } 79 | 80 | public Expression Parse() 81 | { 82 | _index = 0; 83 | 84 | return ParseArgs(); 85 | } 86 | 87 | private string GetFormula() 88 | { 89 | string formula = new string(' ', _level); 90 | foreach (var token in _list) 91 | { 92 | if (token.Type == TokenType.Subexpression && token.Subtype == TokenSubtype.Start) 93 | { 94 | formula += "("; 95 | } 96 | else if (token.Type == TokenType.Function && token.Subtype == TokenSubtype.Start) 97 | { 98 | formula += token.Value + "("; 99 | } 100 | else if (token.Subtype == TokenSubtype.Stop) 101 | { 102 | formula += ")"; 103 | } 104 | else 105 | { 106 | formula += token.Value; 107 | } 108 | } 109 | 110 | return formula; 111 | } 112 | 113 | private void Next() 114 | { 115 | if (_index + 1 < _list.Count) 116 | { 117 | _index++; 118 | } 119 | } 120 | 121 | private Expression ParseArgs() 122 | { 123 | Expression left = ParseAdditive(); 124 | 125 | while (CT.Type == TokenType.Argument) 126 | { 127 | Next(); 128 | Expression right = ParseAdditive(); 129 | 130 | var argExpression = left as XArgExpression; 131 | if (argExpression != null) 132 | { 133 | left = argExpression.Add(right); 134 | } 135 | else 136 | { 137 | argExpression = XArgExpression.Create(left); 138 | left = argExpression.Add(right); 139 | } 140 | } 141 | 142 | return left; 143 | } 144 | 145 | // +, - 146 | private Expression ParseAdditive() 147 | { 148 | Expression left = ParseMultiplication(); 149 | 150 | while (CT.Type == TokenType.OperatorInfix && CT.Subtype == TokenSubtype.Math && (CT.Value == "+" || CT.Value == "-")) 151 | { 152 | var op = CT; 153 | Next(); 154 | Expression right = Expression.Convert(ParseMultiplication(), typeof(double)); 155 | 156 | switch (op.Value) 157 | { 158 | case "+": 159 | left = Expression.Add(left, right); 160 | break; 161 | case "-": 162 | left = Expression.Subtract(left, right); 163 | break; 164 | } 165 | } 166 | 167 | return left; 168 | } 169 | 170 | // *, /, ^ 171 | private Expression ParseMultiplication() 172 | { 173 | Expression left = ParseLogical(); 174 | 175 | while (CT.Type == TokenType.OperatorInfix && CT.Subtype == TokenSubtype.Math && (CT.Value == "^" || CT.Value == "*" || CT.Value == "/")) 176 | { 177 | var op = CT; 178 | Next(); 179 | Expression right = ParseLogical(); 180 | 181 | switch (op.Value) 182 | { 183 | case "^": 184 | left = MathFunctions.Power(left, right); 185 | break; 186 | case "*": 187 | left = Expression.Multiply(left, right); 188 | break; 189 | case "/": 190 | left = Expression.Divide(left, right); 191 | break; 192 | default: 193 | throw new NotSupportedException(op.Value); 194 | } 195 | } 196 | 197 | return left; 198 | } 199 | 200 | // >, >=, <, <=, <>, = 201 | private Expression ParseLogical() 202 | { 203 | Expression left = ParseFunction(); 204 | 205 | while (CT.Type == TokenType.OperatorInfix && CT.Subtype == TokenSubtype.Logical) 206 | { 207 | var op = CT; 208 | Next(); 209 | Expression right = ParseFunction(); 210 | 211 | switch (op.Value) 212 | { 213 | case ">": 214 | left = Expression.GreaterThan(left, right); 215 | break; 216 | case ">=": 217 | left = Expression.GreaterThanOrEqual(left, right); 218 | break; 219 | case "<": 220 | left = Expression.LessThan(left, right); 221 | break; 222 | case "<=": 223 | left = Expression.LessThanOrEqual(left, right); 224 | break; 225 | case "<>": 226 | left = Expression.NotEqual(left, right); 227 | break; 228 | case "=": 229 | left = Expression.Equal(left, right); 230 | break; 231 | default: 232 | throw new NotSupportedException(op.Value); 233 | } 234 | } 235 | 236 | return left; 237 | } 238 | 239 | // =ROUND(-3 * ROUND(1.001 * 2, 1) + 7.001, 1) + 11 240 | private Expression ParseFunction() 241 | { 242 | Expression left = ParseOperatorPrefix(); 243 | 244 | while (CT.Type == TokenType.Function || CT.Type == TokenType.Subexpression) 245 | { 246 | var op = CT; 247 | Next(); 248 | 249 | int indent = 0; 250 | var tokens = new List(); 251 | while (!(CT.Subtype == TokenSubtype.Stop && indent == 0)) 252 | { 253 | if (CT.Subtype == TokenSubtype.Start) 254 | { 255 | indent++; 256 | } 257 | 258 | if (CT.Subtype == TokenSubtype.Stop) 259 | { 260 | indent--; 261 | } 262 | 263 | tokens.Add(CT); 264 | 265 | Next(); 266 | } 267 | 268 | Next(); 269 | var right = ParseOperatorPrefix(); 270 | 271 | // If there is more to be done (right != null), return right, else return the value from this function. 272 | return right ?? ParseFunctionWithArgs(op.Value, tokens, _context); 273 | } 274 | 275 | return left; 276 | } 277 | 278 | private Expression ParseFunctionWithArgs(string functionName, IList tokens, ExcelFormulaContext context) 279 | { 280 | void AddToArgumentsList(ICollection args, Expression expression) 281 | { 282 | var argExpression = expression as XArgExpression; 283 | if (argExpression != null) 284 | { 285 | foreach (var xargExpression in argExpression.Expressions) 286 | { 287 | AddToArgumentsList(args, xargExpression); 288 | } 289 | 290 | return; 291 | } 292 | 293 | var rangeExpression = expression as XRangeExpression; 294 | if (rangeExpression != null) 295 | { 296 | args.Add(rangeExpression.XRange); 297 | return; 298 | } 299 | 300 | args.Add(expression); 301 | } 302 | 303 | var arguments = new List(); 304 | 305 | if (tokens.Any()) 306 | { 307 | AddToArgumentsList(arguments, Parse(tokens, context)); 308 | } 309 | 310 | var expressions = arguments.Where(a => a is Expression).Cast().ToArray(); 311 | var xranges = arguments.Where(a => a is XRange).Cast().ToArray(); 312 | 313 | switch (functionName) 314 | { 315 | case "": return expressions[0]; 316 | case "ABS": return MathFunctions.Abs(expressions[0]); 317 | case "AND": return LogicalFunctions.And(expressions); 318 | case "COS": return MathFunctions.Cos(expressions[0]); 319 | case "DATE": return DateFunctions.Date(expressions[0], expressions[1], expressions[2]); 320 | case "DAY": return DateFunctions.Day(expressions[0]); 321 | case "EDATE": return DateFunctions.EDate(expressions[0], expressions[1]); 322 | case "EOMONTH": return DateFunctions.EndOfMonth(expressions[0], expressions[1]); 323 | case "IF": return Expression.Condition(expressions[0], expressions[1], expressions[2]); 324 | case "MAX": return MathFunctions.Max(expressions[0], expressions[1]); 325 | case "MIN": return MathFunctions.Min(expressions[0], expressions[1]); 326 | case "MONTH": return DateFunctions.Month(expressions[0]); 327 | case "NOW": return DateFunctions.Now(); 328 | case "OR": return LogicalFunctions.Or(expressions); 329 | case "POWER": return MathFunctions.Power(expressions[0], expressions[1]); 330 | case "ROUND": return MathFunctions.Round(expressions[0], expressions.Length == 2 ? expressions[1] : null); 331 | case "SIN": return MathFunctions.Sin(expressions[0]); 332 | case "SQRT": return MathFunctions.Sqrt(expressions[0]); 333 | case "SUM": return MathFunctions.Sum(expressions, xranges); 334 | case "TODAY": return DateFunctions.Today(); 335 | case "TRUNC": return MathFunctions.Trunc(expressions); 336 | case "VLOOKUP": return LookupAndReferenceFunctions.VLookup(expressions[0], xranges[0], expressions[1], expressions.Length == 3 ? expressions[2] : null); 337 | case "YEAR": return DateFunctions.Year(expressions[0]); 338 | case "YEARFRAC": return DateFunctions.YearFrac(expressions[0], expressions[1], expressions.Length == 3 ? expressions[2] : null); 339 | 340 | default: 341 | throw new NotImplementedException(functionName); 342 | } 343 | } 344 | 345 | // - 346 | private Expression ParseOperatorPrefix() 347 | { 348 | Expression left = ParseRange(); 349 | 350 | while (CT.Type == TokenType.OperatorPrefix && CT.Value == "-") 351 | { 352 | var op = CT; 353 | Next(); 354 | Expression right = ParseRange(); 355 | 356 | switch (op.Value) 357 | { 358 | case "-": 359 | return Expression.Negate(right); 360 | default: 361 | throw new NotSupportedException(op.Value); 362 | } 363 | } 364 | 365 | return left; 366 | } 367 | 368 | private Expression ParseRange() 369 | { 370 | Expression left = ParseValue(); 371 | 372 | if (CT.Type == TokenType.Operand && CT.Subtype == TokenSubtype.Range) 373 | { 374 | var op = CT; 375 | Next(); 376 | 377 | if (_finder != null) 378 | { 379 | var xrange = _finder.Find(_context.Sheet, op.Value); 380 | foreach (var cell in xrange.Cells) 381 | { 382 | cell.Expression = Parse(cell.ExcelFormula, new ExcelFormulaContext { Sheet = xrange.Sheet }); 383 | } 384 | 385 | return xrange.Cells.Length == 1 ? xrange.Cells[0].Expression : XRangeExpression.Create(xrange); 386 | } 387 | 388 | throw new Exception("ExcelFormulaTokenSubtype is a Range, but no 'CellFinder' class is provided."); 389 | } 390 | 391 | return left; 392 | } 393 | 394 | private Expression ParseValue() 395 | { 396 | if (CT.Type == TokenType.Operand) 397 | { 398 | if (CT.Subtype == TokenSubtype.Logical) 399 | { 400 | var op = CT; 401 | Next(); 402 | 403 | return Expression.Constant(bool.Parse(op.Value)); 404 | } 405 | 406 | if (CT.Subtype == TokenSubtype.Number) 407 | { 408 | var op = CT; 409 | Next(); 410 | 411 | return Expression.Constant(double.Parse(op.Value, NumberStyles.Any, CultureInfo.InvariantCulture)); 412 | } 413 | 414 | if (CT.Subtype == TokenSubtype.Text) 415 | { 416 | var op = CT; 417 | Next(); 418 | 419 | return Expression.Constant(op.Value); 420 | } 421 | } 422 | 423 | return null; 424 | } 425 | 426 | private Expression Parse(IList tokens, ExcelFormulaContext context) 427 | { 428 | if (tokens == null) 429 | { 430 | return null; 431 | } 432 | 433 | return new ExpressionParser(tokens, _level + 1, context, _finder).Parse(); 434 | } 435 | } 436 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Expressions/XArgExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace ExcelFormulaExpressionParser.Expressions 7 | { 8 | internal class XArgExpression : Expression 9 | { 10 | private readonly List _expressions; 11 | 12 | public IEnumerable Expressions => _expressions; 13 | 14 | private XArgExpression(Expression expression) 15 | { 16 | _expressions = new[] { expression }.ToList(); 17 | } 18 | 19 | public static XArgExpression Create(Expression expression) 20 | { 21 | return new XArgExpression(expression); 22 | } 23 | 24 | public XArgExpression Add(Expression expression) 25 | { 26 | _expressions.Add(expression); 27 | return this; 28 | } 29 | 30 | public override Type Type => typeof(IList); 31 | 32 | public sealed override ExpressionType NodeType => ExpressionType.Constant; 33 | } 34 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Expressions/XRangeExpression.cs: -------------------------------------------------------------------------------- 1 | using ExcelFormulaExpressionParser.Models; 2 | using System; 3 | using System.Linq.Expressions; 4 | 5 | namespace ExcelFormulaExpressionParser.Expressions 6 | { 7 | internal class XRangeExpression : Expression 8 | { 9 | public readonly XRange XRange; 10 | 11 | private XRangeExpression(XRange range) 12 | { 13 | XRange = range; 14 | } 15 | 16 | public static XRangeExpression Create(XRange range) 17 | { 18 | return new XRangeExpression(range); 19 | } 20 | 21 | public override Type Type => typeof(XRange); 22 | } 23 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Extensions/ExpressionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace ExcelFormulaExpressionParser.Extensions 5 | { 6 | public static class ExpressionExtensions 7 | { 8 | public static T LambdaInvoke(this Expression expression) 9 | { 10 | var value = Expression.Lambda(expression).Compile().DynamicInvoke(); 11 | if (value == null) 12 | { 13 | return default(T); 14 | } 15 | 16 | if (value is T) 17 | { 18 | return (T)value; 19 | } 20 | 21 | return (T)Convert.ChangeType(value, typeof(T)); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Functions/DateFunctions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using ExcelFormulaExpressionParser.Extensions; 4 | using ExcelFormulaExpressionParser.Helpers; 5 | 6 | namespace ExcelFormulaExpressionParser.Functions 7 | { 8 | internal static class DateFunctions 9 | { 10 | public static Expression Date(Expression yearExpression, Expression monthExpression, Expression dayExpression) 11 | { 12 | int year = yearExpression.LambdaInvoke(); 13 | int month = monthExpression.LambdaInvoke(); 14 | int day = dayExpression.LambdaInvoke(); 15 | 16 | var date = new DateTime(year, month, day); 17 | return Expression.Constant(DateTimeHelpers.ToOADate(date)); 18 | } 19 | 20 | public static Expression Day(Expression expression) 21 | { 22 | return Expression.Constant((double)Invoke(expression).Day); 23 | } 24 | 25 | public static Expression EDate(Expression dateExpression, Expression monthExpression) 26 | { 27 | DateTime date = Invoke(dateExpression); 28 | int month = monthExpression.LambdaInvoke(); 29 | 30 | var endDate = date.AddMonths(month); 31 | return Expression.Constant(DateTimeHelpers.ToOADate(endDate)); 32 | } 33 | 34 | public static Expression EndOfMonth(Expression dateExpression, Expression monthExpression) 35 | { 36 | DateTime date = Invoke(dateExpression); 37 | int month = monthExpression.LambdaInvoke(); 38 | 39 | var endofMonthDate = new DateTime(date.Year, date.Month, 1).AddMonths(month + 1).AddDays(-1); 40 | return Expression.Constant(DateTimeHelpers.ToOADate(endofMonthDate)); 41 | } 42 | 43 | public static Expression Month(Expression expression) 44 | { 45 | return Expression.Constant((double)Invoke(expression).Month); 46 | } 47 | 48 | public static Expression Now() 49 | { 50 | return Expression.Constant(DateTimeHelpers.ToOADate(DateTime.UtcNow)); 51 | } 52 | 53 | public static Expression Today() 54 | { 55 | return Expression.Constant(DateTimeHelpers.ToOADate(DateTime.UtcNow.Date)); 56 | } 57 | 58 | public static Expression Year(Expression expression) 59 | { 60 | return Expression.Constant((double)Invoke(expression).Year); 61 | } 62 | 63 | public static Expression YearFrac(Expression startYearExpression, Expression endYearExpression, Expression basisExpression = null) 64 | { 65 | DateTime date1 = Invoke(startYearExpression); 66 | DateTime date2 = Invoke(startYearExpression); 67 | 68 | // int basis = basisExpression?.LambdaInvoke() ?? 0 ; 69 | 70 | return Expression.Constant(GetDayCount(date1, date2)); 71 | } 72 | 73 | 74 | public static double GetDayCount(DateTime startDate, DateTime endDate) 75 | { 76 | if (startDate > endDate) 77 | { 78 | var t = startDate; 79 | startDate = endDate; 80 | endDate = t; 81 | } 82 | 83 | return (endDate - startDate).TotalDays / 360.0; 84 | } 85 | 86 | private static DateTime Invoke(Expression expression) 87 | { 88 | double value = expression.LambdaInvoke(); 89 | 90 | return DateTimeHelpers.FromOADate(value); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Functions/LogicalFunctions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | 5 | namespace ExcelFormulaExpressionParser.Functions 6 | { 7 | internal static class LogicalFunctions 8 | { 9 | public static Expression And(IList expressions) 10 | { 11 | return expressions.Aggregate(Expression.And); 12 | } 13 | 14 | public static Expression Or(IList expressions) 15 | { 16 | return expressions.Aggregate(Expression.Or); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Functions/LookupAndReferenceFunctions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using ExcelFormulaExpressionParser.Constants; 5 | using ExcelFormulaExpressionParser.Extensions; 6 | using ExcelFormulaExpressionParser.Models; 7 | 8 | namespace ExcelFormulaExpressionParser.Functions 9 | { 10 | internal static class LookupAndReferenceFunctions 11 | { 12 | public static Expression VLookup(Expression search, XRange range, Expression column, Expression matchmodeExpression = null) 13 | { 14 | object matchmode = matchmodeExpression != null ? Expression.Lambda(matchmodeExpression).Compile().DynamicInvoke() : true; 15 | bool approximateMatch; 16 | if (matchmode is bool) 17 | { 18 | approximateMatch = (bool)matchmode; 19 | } 20 | else 21 | { 22 | approximateMatch = Convert.ToBoolean((double)matchmode); 23 | } 24 | 25 | foreach (var keyCell in range.Cells.Where(c => c.Column == range.Start.Column)) 26 | { 27 | int row = keyCell.Row; 28 | var keyExpression = keyCell.Expression; 29 | if (keyExpression != null) 30 | { 31 | Expression checkExpression = approximateMatch ? Expression.GreaterThanOrEqual(keyExpression, search) : Expression.Equal(keyExpression, search); 32 | bool match = checkExpression.LambdaInvoke(); 33 | 34 | if (match) 35 | { 36 | int columnIndex = MathFunctions.ToInt(column).LambdaInvoke(); 37 | 38 | // return (previous) match 39 | int matchRow = !approximateMatch ? row : row - 1; 40 | var cell = range.Cells.FirstOrDefault(c => c.Row == matchRow && c.Column == range.Start.Column + columnIndex - 1); 41 | 42 | return cell != null ? cell.Expression : Errors.NA; 43 | } 44 | } 45 | } 46 | 47 | return Errors.NA; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Functions/MathFunctions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using ExcelFormulaExpressionParser.Compatibility; 6 | using ExcelFormulaExpressionParser.Constants; 7 | using ExcelFormulaExpressionParser.Models; 8 | 9 | namespace ExcelFormulaExpressionParser.Functions 10 | { 11 | internal static class MathFunctions 12 | { 13 | public static Expression Abs(Expression value) 14 | { 15 | return Expression.Call(null, typeof(Math).FindMethod("Abs", new[] { typeof(double) }), value); 16 | } 17 | 18 | public static Expression Cos(Expression value) 19 | { 20 | return Expression.Call(null, typeof(Math).FindMethod("Cos", new[] { typeof(double) }), value); 21 | } 22 | 23 | public static Expression Max(Expression value1, Expression value2) 24 | { 25 | return Expression.Call(null, typeof(Math).FindMethod("Max", new[] { typeof(double), typeof(double) }), value1, value2); 26 | } 27 | 28 | public static Expression Min(Expression value1, Expression value2) 29 | { 30 | return Expression.Call(null, typeof(Math).FindMethod("Min", new[] { typeof(double), typeof(double) }), value1, value2); 31 | } 32 | 33 | public static Expression Power(Expression number, Expression power) 34 | { 35 | return Expression.Power(number, power); 36 | } 37 | 38 | public static Expression Round(Expression number, Expression digits = null) 39 | { 40 | return Expression.Call(null, typeof(Math).FindMethod("Round", new[] { typeof(double), typeof(int) }), number, digits != null ? ToInt(digits) : Expression.Constant(0)); 41 | } 42 | 43 | public static Expression Sin(Expression value) 44 | { 45 | return Expression.Call(null, typeof(Math).FindMethod("Sin", new[] { typeof(double) }), value); 46 | } 47 | 48 | public static Expression Sqrt(Expression value) 49 | { 50 | return Expression.Call(null, typeof(Math).FindMethod("Sqrt", new[] { typeof(double) }), value); 51 | } 52 | 53 | public static Expression Sum(IList expressions, IList ranges) 54 | { 55 | Expression left = MathConstants.Constant0; 56 | Expression right = MathConstants.Constant0; 57 | 58 | if (expressions.Any()) 59 | { 60 | left = expressions.Aggregate(Expression.Add); 61 | } 62 | 63 | if (ranges.Any()) 64 | { 65 | right = ranges.SelectMany(r => r.Cells).Select(c => c.Expression).Aggregate(Expression.Add); 66 | } 67 | 68 | return Expression.Add(left, right); 69 | } 70 | 71 | public static Expression Trunc(IList expressions) 72 | { 73 | var truncateMethod = typeof(Math).FindMethod("Truncate", new[] { typeof(double) }); 74 | 75 | Expression digits = expressions.Count == 1 ? MathConstants.Constant0 : expressions[1]; 76 | Expression first = Expression.Multiply(expressions[0], Power(MathConstants.Constant10, digits)); 77 | Expression truncate = Expression.Call(null, truncateMethod, first); 78 | 79 | return Expression.Divide(truncate, Power(MathConstants.Constant10, digits)); 80 | } 81 | 82 | public static Expression ToInt(Expression value) 83 | { 84 | return Expression.Convert(value, typeof(int)); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Helpers/DateTimeHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ExcelFormulaExpressionParser.Helpers 4 | { 5 | public static class DateTimeHelpers 6 | { 7 | // Number of 100ns ticks per time unit 8 | private const long TicksPerMillisecond = 10000; 9 | private const long TicksPerSecond = TicksPerMillisecond * 1000; 10 | private const long TicksPerMinute = TicksPerSecond * 60; 11 | private const long TicksPerHour = TicksPerMinute * 60; 12 | private const long TicksPerDay = TicksPerHour * 24; 13 | 14 | // Number of days in a non-leap year 15 | private const int DaysPerYear = 365; 16 | 17 | // Number of days in 4 years 18 | private const int DaysPer4Years = DaysPerYear * 4 + 1; // 1461 19 | 20 | // Number of days in 100 years 21 | private const int DaysPer100Years = DaysPer4Years * 25 - 1; // 36524 22 | 23 | // Number of days in 400 years 24 | private const int DaysPer400Years = DaysPer100Years * 4 + 1; // 146097 25 | 26 | // Number of days from 1/1/0001 to 12/30/1899 27 | private const int DaysTo1899 = DaysPer400Years * 4 + DaysPer100Years * 3 - 367; 28 | 29 | private const long DoubleDateOffset = DaysTo1899 * TicksPerDay; 30 | 31 | // The minimum OA date is 0100/01/01 (Note it's year 100). 32 | // The maximum OA date is 9999/12/31 33 | private const long OADateMinAsTicks = (DaysPer100Years - DaysPerYear) * TicksPerDay; 34 | 35 | // Number of milliseconds per time unit 36 | private const int MillisPerSecond = 1000; 37 | private const int MillisPerMinute = MillisPerSecond * 60; 38 | private const int MillisPerHour = MillisPerMinute * 60; 39 | private const int MillisPerDay = MillisPerHour * 24; 40 | 41 | /// 42 | /// Converts the DateTime instance into an OLE Automation compatible 43 | /// 44 | public static double ToOADate(DateTime dateTime) 45 | { 46 | return TicksToOADate(dateTime.Ticks); 47 | } 48 | 49 | private static double TicksToOADate(long value) 50 | { 51 | if (value == 0) 52 | { 53 | // Returns OleAut's zero'ed date value. 54 | return 0.0; 55 | } 56 | 57 | // This is a fix for VB. They want the default day to be 1/1/0001 rathar then 12/30/1899. 58 | if (value < TicksPerDay) 59 | { 60 | value += DoubleDateOffset; // We could have moved this fix down but we would like to keep the bounds check. 61 | } 62 | 63 | if (value < OADateMinAsTicks) 64 | { 65 | throw new OverflowException(); 66 | } 67 | 68 | // Currently, our max date == OA's max date (12/31/9999), so we don't 69 | // need an overflow check in that direction. 70 | long millis = (value - DoubleDateOffset) / TicksPerMillisecond; 71 | if (millis < 0) 72 | { 73 | long frac = millis % MillisPerDay; 74 | if (frac != 0) millis -= (MillisPerDay + frac) * 2; 75 | } 76 | 77 | return (double)millis / MillisPerDay; 78 | } 79 | 80 | public static DateTime FromOADate(double d) 81 | { 82 | #if NETCORE || NETSTANDARD 83 | return new DateTime(DoubleDateToTicks(d), DateTimeKind.Unspecified); 84 | #else 85 | return System.DateTime.FromOADate(d); 86 | #endif 87 | } 88 | 89 | private static long DoubleDateToTicks(double value) 90 | { 91 | if (value >= 2958466.0 || value <= -657435.0) 92 | { 93 | throw new ArgumentException(); 94 | } 95 | 96 | long num = (long)(value * 86400000.0 + ((value >= 0.0) ? 0.5 : -0.5)); 97 | if (num < 0L) 98 | { 99 | long expr_5C = num; 100 | num = expr_5C - expr_5C % 86400000L * 2L; 101 | } 102 | 103 | num += 59926435200000L; 104 | if (num < 0L || num >= 315537897600000L) 105 | { 106 | throw new ArgumentException(); 107 | } 108 | 109 | return num * 10000L; 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/ICellFinder.cs: -------------------------------------------------------------------------------- 1 | using ExcelFormulaExpressionParser.Models; 2 | 3 | namespace ExcelFormulaExpressionParser 4 | { 5 | public interface ICellFinder 6 | { 7 | XRange Find(XSheet sheet, string address); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Models/CellAddress.cs: -------------------------------------------------------------------------------- 1 | namespace ExcelFormulaExpressionParser.Models 2 | { 3 | public class CellAddress 4 | { 5 | public int Column { get; set; } 6 | 7 | public int Row { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Models/XArg.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq.Expressions; 3 | 4 | namespace ExcelFormulaExpressionParser.Models 5 | { 6 | internal class XArg 7 | { 8 | public List Expressions { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Models/XCell.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using ExcelFormulaExpressionParser.Utils; 3 | using ExcelFormulaParser; 4 | 5 | namespace ExcelFormulaExpressionParser.Models 6 | { 7 | public class XCell 8 | { 9 | public int Column { get; } 10 | 11 | public int Row { get; } 12 | 13 | public string Address { get; } 14 | 15 | public string FullAddress { get; set; } 16 | 17 | public object Value { get; set; } 18 | 19 | public string Formula { get; set; } 20 | 21 | public ExcelFormula ExcelFormula { get; set; } 22 | 23 | public Expression Expression { get; set; } 24 | 25 | public XRow XRow { get; } 26 | 27 | public XCell(XRow xRow, string addres) 28 | { 29 | XRow = xRow; 30 | Address = addres; 31 | 32 | Column = ExcelUtils.GetExcelColumnNumber(addres); 33 | Row = xRow.Row; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Models/XNamedRange.cs: -------------------------------------------------------------------------------- 1 | namespace ExcelFormulaExpressionParser.Models 2 | { 3 | public class XNamedRange 4 | { 5 | public string Name { get; set; } 6 | 7 | public string Address { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Models/XRange.cs: -------------------------------------------------------------------------------- 1 | namespace ExcelFormulaExpressionParser.Models 2 | { 3 | public class XRange 4 | { 5 | public XSheet Sheet { get; set; } 6 | 7 | public string Address { get; set; } 8 | 9 | public CellAddress Start { get; set; } 10 | 11 | public CellAddress End { get; set; } 12 | 13 | public XCell[] Cells { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Models/XRow.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ExcelFormulaExpressionParser.Models 4 | { 5 | public class XRow 6 | { 7 | private XSheet Sheet { get; } 8 | 9 | public int Row { get; } 10 | 11 | public List Cells { get; set; } 12 | 13 | public XRow(XSheet sheet, int row) 14 | { 15 | Sheet = sheet; 16 | Row = row; 17 | Cells = new List(); 18 | } 19 | 20 | //public XCell this[string address] 21 | //{ 22 | // get 23 | // { 24 | // if (!address.Contains('!')) 25 | // { 26 | // return Cells.FirstOrDefault(c => c.Address == address); 27 | // } 28 | 29 | // { 30 | // var rows = sheets.SelectMany(s => s.Rows); 31 | // return rows.SelectMany(r => r.Cells).FirstOrDefault(c => c.FullAddress == address); 32 | // } 33 | // } 34 | //} 35 | } 36 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Models/XSheet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ExcelFormulaExpressionParser.Models 5 | { 6 | public class XSheet 7 | { 8 | public string Name { get; } 9 | 10 | public List Rows { get; set; } 11 | 12 | public XSheet(string name) 13 | { 14 | if (string.IsNullOrEmpty(name)) 15 | { 16 | throw new ArgumentNullException(nameof(name)); 17 | } 18 | 19 | Name = name; 20 | Rows = new List(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Models/XWorkbook.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ExcelFormulaExpressionParser.Models 4 | { 5 | public class XWorkbook 6 | { 7 | public List Sheets { get; set; } 8 | 9 | public IDictionary Names { get; set; } 10 | 11 | public XWorkbook() 12 | { 13 | Names = new Dictionary(); 14 | Sheets = new List(); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("ExcelFormulaExpressionParser.Tests")] -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Utils/CellFinder.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using ExcelFormulaExpressionParser.Models; 3 | 4 | namespace ExcelFormulaExpressionParser.Utils 5 | { 6 | internal class CellFinder : ICellFinder 7 | { 8 | private readonly XWorkbook _workbook; 9 | 10 | private readonly bool _hasNames; 11 | 12 | public CellFinder(XWorkbook workbook) 13 | { 14 | _workbook = workbook; 15 | _hasNames = workbook.Names.Count > 0; 16 | } 17 | 18 | public XRange Find(XSheet sheet, string address) 19 | { 20 | if (_hasNames && _workbook.Names.ContainsKey(address)) 21 | { 22 | address = _workbook.Names[address]; 23 | } 24 | 25 | address = address.Replace("$", ""); 26 | 27 | var range = new XRange(); 28 | 29 | if (!address.Contains(':')) 30 | { 31 | if (!address.Contains('!')) 32 | { 33 | // Same sheet 34 | range.Sheet = _workbook.Sheets.First(s => s.Name == sheet.Name); 35 | } 36 | else 37 | { 38 | // Other sheet 39 | string[] parts = address.Split('!'); 40 | range.Sheet = _workbook.Sheets.First(s => s.Name == parts[0]); 41 | address = parts[1]; 42 | } 43 | 44 | range.Start = ExcelUtils.ParseExcelAddress(address); 45 | range.End = ExcelUtils.ParseExcelAddress(address); 46 | } 47 | else 48 | { 49 | string[] partsRange; 50 | if (!address.Contains('!')) 51 | { 52 | // Same sheet 53 | partsRange = address.Split(':'); 54 | range.Sheet = _workbook.Sheets.First(s => s.Name == sheet.Name); 55 | } 56 | else 57 | { 58 | // Other sheet 59 | string[] parts = address.Split('!'); 60 | range.Sheet = _workbook.Sheets.First(s => s.Name == parts[0]); 61 | address = parts[1]; 62 | 63 | partsRange = address.Split(':'); 64 | } 65 | 66 | range.Start = ExcelUtils.ParseExcelAddress(partsRange[0]); 67 | range.End = ExcelUtils.ParseExcelAddress(partsRange[1]); 68 | } 69 | 70 | var rows = range.Sheet.Rows.GetRange(range.Start.Row - 1, range.End.Row - range.Start.Row + 1); 71 | 72 | range.Cells = rows.SelectMany(r => r.Cells) 73 | .Where(c => c.Column >= range.Start.Column && c.Column <= range.End.Column).ToArray(); 74 | 75 | range.Address = address; 76 | return range; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /src/ExcelFormulaExpressionParser/Utils/ExcelUtils.cs: -------------------------------------------------------------------------------- 1 | using ExcelFormulaExpressionParser.Models; 2 | 3 | namespace ExcelFormulaExpressionParser.Utils 4 | { 5 | public static class ExcelUtils 6 | { 7 | private static readonly char[] Numbers = "0123456789".ToCharArray(); 8 | 9 | public static CellAddress ParseExcelAddress(string value) 10 | { 11 | int startIndex = value.IndexOfAny(Numbers); 12 | 13 | return new CellAddress 14 | { 15 | Column = ExcelColumnNameToNumber(value.Substring(0, startIndex)), 16 | Row = int.Parse(value.Substring(startIndex)) 17 | }; 18 | } 19 | 20 | public static int GetExcelColumnNumber(string value) 21 | { 22 | int startIndex = value.IndexOfAny(Numbers); 23 | 24 | return ExcelColumnNameToNumber(value.Substring(0, startIndex)); 25 | } 26 | 27 | private static int ExcelColumnNameToNumber(string columnName) 28 | { 29 | int sum = 0; 30 | foreach (char c in columnName) 31 | { 32 | sum *= 26; 33 | sum += c - 'A' + 1; 34 | } 35 | 36 | return sum; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/ExcelFormulaParser/Copyright.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007 E. W. Bachtal, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | and associated documentation files (the "Software"), to deal in the Software without restriction, 5 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 6 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 7 | subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial 10 | portions of the Software. 11 | 12 | The software is provided "as is", without warranty of any kind, express or implied, including but not 13 | limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In 14 | no event shall the authors or copyright holders be liable for any claim, damages or other liability, 15 | whether in an action of contract, tort or otherwise, arising from, out of or in connection with the 16 | software or the use or other dealings in the software. 17 | 18 | http://ewbi.blogs.com/develops/2007/03/excel_formula_p.html 19 | http://ewbi.blogs.com/develops/2004/12/excel_formula_p.html 20 | 21 | v1.0 Original 22 | v1.1 Added support for in-formula scientific notation. -------------------------------------------------------------------------------- /src/ExcelFormulaParser/ExcelFormula.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | using System.Text.RegularExpressions; 7 | 8 | namespace ExcelFormulaParser 9 | { 10 | public class ExcelFormula : IList 11 | { 12 | const char QuoteDouble = '"'; 13 | const char QuoteSingle = '\''; 14 | const char BracketClose = ']'; 15 | const char BracketOpen = '['; 16 | const char BraceOpen = '{'; 17 | const char BraceClose = '}'; 18 | const char ParenOpen = '('; 19 | const char ParenClose = ')'; 20 | const char Semicolon = ';'; 21 | const char Whitespace = ' '; 22 | const char Comma = ','; 23 | const char ErrorStart = '#'; 24 | 25 | const string OperatorsSn = "+-"; 26 | const string OperatorsInfix = "+-*/^&=><"; 27 | const string OperatorsPostfix = "%"; 28 | 29 | internal static readonly string[] ExcelErrors = { "#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A" }; 30 | internal static readonly string[] ComparatorsMulti = { ">=", "<=", "<>" }; 31 | 32 | private readonly string _formula; 33 | private List _tokens = new List(); 34 | 35 | /// 36 | /// Gets the number of ExcelFormulaToken which are parsed for this ExcelFormula. 37 | /// 38 | public int Count => _tokens.Count; 39 | 40 | /// 41 | /// Gets a value indicating whether this ExcelFormula is read-only. 42 | /// 43 | public bool IsReadOnly => true; 44 | 45 | /// 46 | /// The ExcelFormula. 47 | /// 48 | public string Formula => _formula; 49 | 50 | /// 51 | /// The optional context for this formula, can be anything, e.g. the Sheet or Workbook. 52 | /// 53 | public IExcelFormulaContext Context { get; } 54 | 55 | /// 56 | /// Gets or sets the ExcelFormulaToken at the specified index. 57 | /// 58 | /// The zero-based index of the element to get or set. 59 | /// The ExcelFormulaToken at the specified index. 60 | public ExcelFormulaToken this[int index] 61 | { 62 | get => _tokens[index]; 63 | set => throw new NotSupportedException(); 64 | } 65 | 66 | /// 67 | /// Constructs a ExcelFormula. 68 | /// 69 | /// The Excel formula. 70 | /// The optional context (can be anything, e.g. the Sheet or Workbook) 71 | public ExcelFormula([NotNull] string formula, [CanBeNull] IExcelFormulaContext context = null) 72 | { 73 | if (string.IsNullOrEmpty(formula)) 74 | { 75 | throw new ArgumentException(nameof(formula)); 76 | } 77 | 78 | _formula = formula.Trim(); 79 | Context = context; 80 | 81 | ParseToTokens(); 82 | } 83 | 84 | public int IndexOf(ExcelFormulaToken item) 85 | { 86 | return _tokens.IndexOf(item); 87 | } 88 | 89 | public void Insert(int index, ExcelFormulaToken item) 90 | { 91 | throw new NotSupportedException(); 92 | } 93 | 94 | public void RemoveAt(int index) 95 | { 96 | throw new NotSupportedException(); 97 | } 98 | 99 | public void Add(ExcelFormulaToken item) 100 | { 101 | throw new NotSupportedException(); 102 | } 103 | 104 | public void Clear() 105 | { 106 | throw new NotSupportedException(); 107 | } 108 | 109 | public bool Contains(ExcelFormulaToken item) 110 | { 111 | return _tokens.Contains(item); 112 | } 113 | 114 | public bool Remove(ExcelFormulaToken item) 115 | { 116 | throw new NotSupportedException(); 117 | } 118 | 119 | public void CopyTo(ExcelFormulaToken[] array, int arrayIndex) 120 | { 121 | _tokens.CopyTo(array, arrayIndex); 122 | } 123 | 124 | public IEnumerator GetEnumerator() 125 | { 126 | return _tokens.GetEnumerator(); 127 | } 128 | 129 | IEnumerator IEnumerable.GetEnumerator() 130 | { 131 | return GetEnumerator(); 132 | } 133 | 134 | private void ParseToTokens() 135 | { 136 | // No attempt is made to verify formulas; assumes formulas are derived from Excel, where 137 | // they can only exist if valid; stack overflows/underflows sunk as nulls without exceptions. 138 | if (_formula.Length < 2 || _formula[0] != '=') 139 | { 140 | return; 141 | } 142 | 143 | var tokens1 = new ExcelFormulaTokens(); 144 | var stack = new ExcelFormulaStack(); 145 | 146 | bool inString = false; 147 | bool inPath = false; 148 | bool inRange = false; 149 | bool inError = false; 150 | 151 | int index = 1; 152 | string value = string.Empty; 153 | 154 | while (index < _formula.Length) 155 | { 156 | // state-dependent character evaluation (order is important) 157 | // double-quoted strings 158 | // embeds are doubled 159 | // end marks token 160 | if (inString) 161 | { 162 | if (_formula[index] == QuoteDouble) 163 | { 164 | if (index + 2 <= _formula.Length && _formula[index + 1] == QuoteDouble) 165 | { 166 | value += QuoteDouble; 167 | index++; 168 | } 169 | else 170 | { 171 | inString = false; 172 | tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Operand, ExcelFormulaTokenSubtype.Text)); 173 | value = string.Empty; 174 | } 175 | } 176 | else 177 | { 178 | value += _formula[index]; 179 | } 180 | index++; 181 | continue; 182 | } 183 | 184 | // single-quoted strings (links) 185 | // embeds are double 186 | // end does not mark a token 187 | if (inPath) 188 | { 189 | if (_formula[index] == QuoteSingle) 190 | { 191 | if (index + 2 <= _formula.Length && _formula[index + 1] == QuoteSingle) 192 | { 193 | value += QuoteSingle; 194 | index++; 195 | } 196 | else 197 | { 198 | inPath = false; 199 | } 200 | } 201 | else 202 | { 203 | value += _formula[index]; 204 | } 205 | index++; 206 | continue; 207 | } 208 | 209 | // bracked strings (R1C1 range index or linked workbook name) 210 | // no embeds (changed to "()" by Excel) 211 | // end does not mark a token 212 | if (inRange) 213 | { 214 | if (_formula[index] == BracketClose) 215 | { 216 | inRange = false; 217 | } 218 | value += _formula[index]; 219 | index++; 220 | continue; 221 | } 222 | 223 | // error values 224 | // end marks a token, determined from absolute list of values 225 | if (inError) 226 | { 227 | value += _formula[index]; 228 | index++; 229 | if (Array.IndexOf(ExcelErrors, value) != -1) 230 | { 231 | inError = false; 232 | tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Operand, ExcelFormulaTokenSubtype.Error)); 233 | value = string.Empty; 234 | } 235 | continue; 236 | } 237 | 238 | // scientific notation check 239 | if (OperatorsSn.IndexOf(_formula[index]) != -1) 240 | { 241 | if (value.Length > 1) 242 | { 243 | if (Regex.IsMatch(value, @"^[1-9]{1}(\.[0-9]+)?E{1}$")) 244 | { 245 | value += _formula[index]; 246 | index++; 247 | continue; 248 | } 249 | } 250 | } 251 | 252 | // independent character evaluation (order not important) 253 | // establish state-dependent character evaluations 254 | if (_formula[index] == QuoteDouble) 255 | { 256 | if (value.Length > 0) 257 | { 258 | // unexpected 259 | tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Unknown)); 260 | value = string.Empty; 261 | } 262 | inString = true; 263 | index++; 264 | continue; 265 | } 266 | 267 | if (_formula[index] == QuoteSingle) 268 | { 269 | if (value.Length > 0) 270 | { 271 | // unexpected 272 | tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Unknown)); 273 | value = string.Empty; 274 | } 275 | inPath = true; 276 | index++; 277 | continue; 278 | } 279 | 280 | if (_formula[index] == BracketOpen) 281 | { 282 | inRange = true; 283 | value += BracketOpen; 284 | index++; 285 | continue; 286 | } 287 | 288 | if (_formula[index] == ErrorStart) 289 | { 290 | if (value.Length > 0) 291 | { 292 | // unexpected 293 | tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Unknown)); 294 | value = string.Empty; 295 | } 296 | inError = true; 297 | value += ErrorStart; 298 | index++; 299 | continue; 300 | } 301 | 302 | // mark start and end of arrays and array rows 303 | if (_formula[index] == BraceOpen) 304 | { 305 | if (value.Length > 0) 306 | { 307 | // unexpected 308 | tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Unknown)); 309 | value = string.Empty; 310 | } 311 | stack.Push(tokens1.Add(new ExcelFormulaToken("ARRAY", ExcelFormulaTokenType.Function, ExcelFormulaTokenSubtype.Start))); 312 | stack.Push(tokens1.Add(new ExcelFormulaToken("ARRAYROW", ExcelFormulaTokenType.Function, ExcelFormulaTokenSubtype.Start))); 313 | index++; 314 | continue; 315 | } 316 | 317 | if (_formula[index] == Semicolon) 318 | { 319 | if (value.Length > 0) 320 | { 321 | tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Operand)); 322 | value = string.Empty; 323 | } 324 | tokens1.Add(stack.Pop()); 325 | tokens1.Add(new ExcelFormulaToken(",", ExcelFormulaTokenType.Argument)); 326 | stack.Push(tokens1.Add(new ExcelFormulaToken("ARRAYROW", ExcelFormulaTokenType.Function, ExcelFormulaTokenSubtype.Start))); 327 | index++; 328 | continue; 329 | } 330 | 331 | if (_formula[index] == BraceClose) 332 | { 333 | if (value.Length > 0) 334 | { 335 | tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Operand)); 336 | value = string.Empty; 337 | } 338 | tokens1.Add(stack.Pop()); 339 | tokens1.Add(stack.Pop()); 340 | index++; 341 | continue; 342 | } 343 | 344 | // trim white-space 345 | if (_formula[index] == Whitespace) 346 | { 347 | if (value.Length > 0) 348 | { 349 | tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Operand)); 350 | value = string.Empty; 351 | } 352 | tokens1.Add(new ExcelFormulaToken(string.Empty, ExcelFormulaTokenType.Whitespace)); 353 | index++; 354 | while (_formula[index] == Whitespace && index < _formula.Length) 355 | { 356 | index++; 357 | } 358 | continue; 359 | } 360 | 361 | // multi-character comparators 362 | if (index + 2 <= _formula.Length) 363 | { 364 | if (Array.IndexOf(ComparatorsMulti, _formula.Substring(index, 2)) != -1) 365 | { 366 | if (value.Length > 0) 367 | { 368 | tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Operand)); 369 | value = string.Empty; 370 | } 371 | tokens1.Add(new ExcelFormulaToken(_formula.Substring(index, 2), ExcelFormulaTokenType.OperatorInfix, ExcelFormulaTokenSubtype.Logical)); 372 | index += 2; 373 | continue; 374 | } 375 | } 376 | 377 | // standard infix operators 378 | if (OperatorsInfix.IndexOf(_formula[index]) != -1) 379 | { 380 | if (value.Length > 0) 381 | { 382 | tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Operand)); 383 | value = string.Empty; 384 | } 385 | tokens1.Add(new ExcelFormulaToken(_formula[index].ToString(), ExcelFormulaTokenType.OperatorInfix)); 386 | index++; 387 | continue; 388 | } 389 | 390 | // standard postfix operators (only one) 391 | if (OperatorsPostfix.IndexOf(_formula[index]) != -1) 392 | { 393 | if (value.Length > 0) 394 | { 395 | tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Operand)); 396 | value = string.Empty; 397 | } 398 | tokens1.Add(new ExcelFormulaToken(_formula[index].ToString(), ExcelFormulaTokenType.OperatorPostfix)); 399 | index++; 400 | continue; 401 | } 402 | 403 | // start subexpression or function 404 | if (_formula[index] == ParenOpen) 405 | { 406 | if (value.Length > 0) 407 | { 408 | stack.Push(tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Function, ExcelFormulaTokenSubtype.Start))); 409 | value = string.Empty; 410 | } 411 | else 412 | { 413 | stack.Push(tokens1.Add(new ExcelFormulaToken(String.Empty, ExcelFormulaTokenType.Subexpression, ExcelFormulaTokenSubtype.Start))); 414 | } 415 | index++; 416 | continue; 417 | } 418 | 419 | // function, subexpression, or array parameters, or operand unions 420 | if (_formula[index] == Comma) 421 | { 422 | if (value.Length > 0) 423 | { 424 | tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Operand)); 425 | value = string.Empty; 426 | } 427 | 428 | if (stack.Current.Type != ExcelFormulaTokenType.Function) 429 | { 430 | tokens1.Add(new ExcelFormulaToken(",", ExcelFormulaTokenType.OperatorInfix, ExcelFormulaTokenSubtype.Union)); 431 | } 432 | else 433 | { 434 | tokens1.Add(new ExcelFormulaToken(",", ExcelFormulaTokenType.Argument)); 435 | } 436 | index++; 437 | continue; 438 | } 439 | 440 | // stop subexpression 441 | if (_formula[index] == ParenClose) 442 | { 443 | if (value.Length > 0) 444 | { 445 | tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Operand)); 446 | value = string.Empty; 447 | } 448 | tokens1.Add(stack.Pop()); 449 | index++; 450 | continue; 451 | } 452 | 453 | // token accumulation 454 | value += _formula[index]; 455 | index++; 456 | } 457 | 458 | // dump remaining accumulation 459 | if (value.Length > 0) 460 | { 461 | tokens1.Add(new ExcelFormulaToken(value, ExcelFormulaTokenType.Operand)); 462 | } 463 | 464 | // move tokenList to new set, excluding unnecessary white-space tokens and converting necessary ones to intersections 465 | var tokens2 = new ExcelFormulaTokens(); 466 | while (tokens1.MoveNext()) 467 | { 468 | ExcelFormulaToken token = tokens1.Current; 469 | 470 | if (token == null) 471 | { 472 | continue; 473 | } 474 | 475 | if (token.Type != ExcelFormulaTokenType.Whitespace) 476 | { 477 | tokens2.Add(token); 478 | continue; 479 | } 480 | 481 | if (tokens1.BOF || tokens1.EOF) 482 | { 483 | continue; 484 | } 485 | 486 | ExcelFormulaToken previous = tokens1.Previous; 487 | 488 | if (previous == null) 489 | { 490 | continue; 491 | } 492 | 493 | if (!( 494 | previous.Type == ExcelFormulaTokenType.Function && 495 | previous.Subtype == ExcelFormulaTokenSubtype.Stop || 496 | previous.Type == ExcelFormulaTokenType.Subexpression && 497 | previous.Subtype == ExcelFormulaTokenSubtype.Stop || 498 | previous.Type == ExcelFormulaTokenType.Operand 499 | )) 500 | { 501 | continue; 502 | } 503 | 504 | ExcelFormulaToken next = tokens1.Next; 505 | 506 | if (next == null) 507 | { 508 | continue; 509 | } 510 | 511 | if (!( 512 | next.Type == ExcelFormulaTokenType.Function && next.Subtype == ExcelFormulaTokenSubtype.Start || 513 | next.Type == ExcelFormulaTokenType.Subexpression && 514 | next.Subtype == ExcelFormulaTokenSubtype.Start || 515 | next.Type == ExcelFormulaTokenType.Operand 516 | ) 517 | ) 518 | { 519 | continue; 520 | } 521 | 522 | tokens2.Add(new ExcelFormulaToken(string.Empty, ExcelFormulaTokenType.OperatorInfix, ExcelFormulaTokenSubtype.Intersection)); 523 | } 524 | 525 | // move tokens to final list, switching infix "-" operators to prefix when appropriate, switching infix "+" operators 526 | // to noop when appropriate, identifying operand and infix-operator subtypes, and pulling "@" from function names 527 | _tokens = new List(tokens2.Count); 528 | while (tokens2.MoveNext()) 529 | { 530 | ExcelFormulaToken token = tokens2.Current; 531 | 532 | if (token == null) 533 | { 534 | continue; 535 | } 536 | 537 | ExcelFormulaToken previous = tokens2.Previous; 538 | ExcelFormulaToken next = tokens2.Next; 539 | 540 | if (token.Type == ExcelFormulaTokenType.OperatorInfix && token.Value == "-") 541 | { 542 | if (tokens2.BOF) 543 | { 544 | token.Type = ExcelFormulaTokenType.OperatorPrefix; 545 | } 546 | else if ( 547 | previous.Type == ExcelFormulaTokenType.Function && 548 | previous.Subtype == ExcelFormulaTokenSubtype.Stop || 549 | previous.Type == ExcelFormulaTokenType.Subexpression && 550 | previous.Subtype == ExcelFormulaTokenSubtype.Stop || 551 | previous.Type == ExcelFormulaTokenType.OperatorPostfix || 552 | previous.Type == ExcelFormulaTokenType.Operand 553 | ) 554 | { 555 | token.Subtype = ExcelFormulaTokenSubtype.Math; 556 | } 557 | else 558 | { 559 | token.Type = ExcelFormulaTokenType.OperatorPrefix; 560 | } 561 | 562 | _tokens.Add(token); 563 | continue; 564 | } 565 | 566 | if (token.Type == ExcelFormulaTokenType.OperatorInfix && token.Value == "+") 567 | { 568 | if (tokens2.BOF) 569 | { 570 | continue; 571 | } 572 | 573 | if ( 574 | previous.Type == ExcelFormulaTokenType.Function && 575 | previous.Subtype == ExcelFormulaTokenSubtype.Stop || 576 | previous.Type == ExcelFormulaTokenType.Subexpression && 577 | previous.Subtype == ExcelFormulaTokenSubtype.Stop || 578 | previous.Type == ExcelFormulaTokenType.OperatorPostfix || 579 | previous.Type == ExcelFormulaTokenType.Operand 580 | ) 581 | { 582 | token.Subtype = ExcelFormulaTokenSubtype.Math; 583 | } 584 | else 585 | { 586 | continue; 587 | } 588 | 589 | _tokens.Add(token); 590 | continue; 591 | } 592 | 593 | if (token.Type == ExcelFormulaTokenType.OperatorInfix && token.Subtype == ExcelFormulaTokenSubtype.Nothing) 594 | { 595 | if ("<>=".IndexOf(token.Value.Substring(0, 1), StringComparison.Ordinal) != -1) 596 | { 597 | token.Subtype = ExcelFormulaTokenSubtype.Logical; 598 | } 599 | else if (token.Value == "&") 600 | { 601 | token.Subtype = ExcelFormulaTokenSubtype.Concatenation; 602 | } 603 | else 604 | { 605 | token.Subtype = ExcelFormulaTokenSubtype.Math; 606 | } 607 | 608 | _tokens.Add(token); 609 | continue; 610 | } 611 | 612 | if (token.Type == ExcelFormulaTokenType.Operand && token.Subtype == ExcelFormulaTokenSubtype.Nothing) 613 | { 614 | double d; 615 | bool isNumber = double.TryParse(token.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out d); 616 | if (!isNumber) 617 | { 618 | if (token.Value == "TRUE" || token.Value == "FALSE") 619 | { 620 | token.Subtype = ExcelFormulaTokenSubtype.Logical; 621 | } 622 | else 623 | { 624 | token.Subtype = ExcelFormulaTokenSubtype.Range; 625 | } 626 | } 627 | else 628 | { 629 | token.Subtype = ExcelFormulaTokenSubtype.Number; 630 | } 631 | 632 | _tokens.Add(token); 633 | continue; 634 | } 635 | 636 | if (token.Type == ExcelFormulaTokenType.Function) 637 | { 638 | if (token.Value.Length > 0) 639 | { 640 | if (token.Value.Substring(0, 1) == "@") 641 | { 642 | token.Value = token.Value.Substring(1); 643 | } 644 | } 645 | } 646 | 647 | _tokens.Add(token); 648 | } 649 | } 650 | } 651 | } 652 | -------------------------------------------------------------------------------- /src/ExcelFormulaParser/ExcelFormulaParser.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net20;net452;netstandard1.0 5 | 1.0.2.0 6 | E. W. Bachtal;Stef Heyenrath 7 | E. W. Bachtal 8 | ../../Images/Excel.ico 9 | ewbi.com 10 | A library to parse Excel formulas 11 | https://github.com/StefH/ExcelFormulaParser 12 | git 13 | excel;formula;parser 14 | Fix for parsing double numbers. 15 | https://github.com/StefH/ExcelFormulaParser 16 | https://raw.githubusercontent.com/StefH/ExcelFormulaParser/master/LICENSE 17 | https://raw.githubusercontent.com/StefH/ExcelFormulaParser/master/Images/Excel_64x64.png 18 | True 19 | full 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/ExcelFormulaParser/ExcelFormulaStack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ExcelFormulaParser 5 | { 6 | internal class ExcelFormulaStack 7 | { 8 | private readonly Stack _stack = new Stack(); 9 | 10 | public ExcelFormulaToken Current => _stack.Count > 0 ? _stack.Peek() : null; 11 | 12 | public void Push(ExcelFormulaToken token) 13 | { 14 | _stack.Push(token); 15 | } 16 | 17 | public ExcelFormulaToken Pop() 18 | { 19 | if (_stack.Count == 0) 20 | { 21 | return null; 22 | } 23 | 24 | return new ExcelFormulaToken(String.Empty, _stack.Pop().Type, ExcelFormulaTokenSubtype.Stop); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/ExcelFormulaParser/ExcelFormulaToken.cs: -------------------------------------------------------------------------------- 1 | namespace ExcelFormulaParser 2 | { 3 | public class ExcelFormulaToken 4 | { 5 | public string Value { get; set; } 6 | 7 | public ExcelFormulaTokenType Type { get; set; } 8 | 9 | public ExcelFormulaTokenSubtype Subtype { get; set; } 10 | 11 | internal ExcelFormulaToken(string value, ExcelFormulaTokenType type) : this(value, type, ExcelFormulaTokenSubtype.Nothing) 12 | { 13 | } 14 | 15 | internal ExcelFormulaToken(string value, ExcelFormulaTokenType type, ExcelFormulaTokenSubtype subtype) 16 | { 17 | Value = value; 18 | Type = type; 19 | Subtype = subtype; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/ExcelFormulaParser/ExcelFormulaTokenSubtype.cs: -------------------------------------------------------------------------------- 1 | namespace ExcelFormulaParser 2 | { 3 | public enum ExcelFormulaTokenSubtype 4 | { 5 | Nothing, 6 | Start, 7 | Stop, 8 | Text, 9 | Number, 10 | Logical, 11 | Error, 12 | Range, 13 | Math, 14 | Concatenation, 15 | Intersection, 16 | Union 17 | } 18 | } -------------------------------------------------------------------------------- /src/ExcelFormulaParser/ExcelFormulaTokenType.cs: -------------------------------------------------------------------------------- 1 | namespace ExcelFormulaParser 2 | { 3 | public enum ExcelFormulaTokenType 4 | { 5 | Noop, 6 | Operand, 7 | Function, 8 | Subexpression, 9 | Argument, 10 | OperatorPrefix, 11 | OperatorInfix, 12 | OperatorPostfix, 13 | Whitespace, 14 | Unknown 15 | } 16 | } -------------------------------------------------------------------------------- /src/ExcelFormulaParser/ExcelFormulaTokens.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ExcelFormulaParser 4 | { 5 | internal class ExcelFormulaTokens 6 | { 7 | private int _index = -1; 8 | private readonly List _tokens = new List(); 9 | 10 | public int Count => _tokens.Count; 11 | 12 | public bool BOF => _index <= 0; 13 | 14 | public bool EOF => _index >= _tokens.Count - 1; 15 | 16 | public ExcelFormulaToken Current 17 | { 18 | get 19 | { 20 | if (_index == -1) 21 | { 22 | return null; 23 | } 24 | 25 | return _tokens[_index]; 26 | } 27 | } 28 | 29 | public ExcelFormulaToken Next 30 | { 31 | get 32 | { 33 | if (EOF) 34 | { 35 | return null; 36 | } 37 | 38 | return _tokens[_index + 1]; 39 | } 40 | } 41 | 42 | public ExcelFormulaToken Previous 43 | { 44 | get 45 | { 46 | if (_index < 1) 47 | { 48 | return null; 49 | } 50 | 51 | return _tokens[_index - 1]; 52 | } 53 | } 54 | 55 | public ExcelFormulaToken Add(ExcelFormulaToken token) 56 | { 57 | _tokens.Add(token); 58 | return token; 59 | } 60 | 61 | public bool MoveNext() 62 | { 63 | if (EOF) 64 | { 65 | return false; 66 | } 67 | 68 | _index++; 69 | return true; 70 | } 71 | 72 | public void Reset() 73 | { 74 | _index = -1; 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/ExcelFormulaParser/IExcelFormulaContext.cs: -------------------------------------------------------------------------------- 1 | namespace ExcelFormulaParser 2 | { 3 | public interface IExcelFormulaContext 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /src/ExcelFormulaParser/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("ExcelFormulaExpressionParser")] -------------------------------------------------------------------------------- /tests/ExcelFormulaExpressionParser.Tests/DateFunctionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using ExcelFormulaExpressionParser.Extensions; 3 | using ExcelFormulaExpressionParser.Functions; 4 | using NFluent; 5 | using Xunit; 6 | 7 | namespace ExcelFormulaExpressionParser.Tests 8 | { 9 | public class DateFunctionsTests 10 | { 11 | [Fact] 12 | public void Date() 13 | { 14 | // Assign 15 | 16 | // Act 17 | var resultExpression = DateFunctions.Date(Expression.Constant(2017), Expression.Constant(2), Expression.Constant(3)); 18 | var result = resultExpression.LambdaInvoke(); 19 | 20 | // Assert 21 | Check.That(result).IsEqualTo(42769.0); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/ExcelFormulaExpressionParser.Tests/ExcelFormulaExpressionParser.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp1.1 5 | 1.1.1 6 | full 7 | Stef Heyenrath 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/ExcelFormulaExpressionParser.Tests/LookupAndReferenceFunctionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq.Expressions; 3 | using ExcelFormulaExpressionParser.Extensions; 4 | using ExcelFormulaExpressionParser.Functions; 5 | using ExcelFormulaExpressionParser.Models; 6 | using ExcelFormulaParser; 7 | using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.EventHandlers; 8 | using NFluent; 9 | using Xunit; 10 | using Xunit.Sdk; 11 | 12 | namespace ExcelFormulaExpressionParser.Tests 13 | { 14 | public class LookupAndReferenceFunctionsTests 15 | { 16 | private readonly XRange _range; 17 | 18 | public LookupAndReferenceFunctionsTests() 19 | { 20 | var sheet = new XSheet("sheet1"); 21 | var cells = new List(); 22 | var row1 = new XRow(sheet, 1); 23 | cells.Add(new XCell(row1, "A10") { ExcelFormula = new ExcelFormula("=1"), Expression = Expression.Constant(1.0) }); 24 | cells.Add(new XCell(row1, "B10") { ExcelFormula = new ExcelFormula("=SUM(1,9)"), Expression = Expression.Add(Expression.Constant(1.0), Expression.Constant(9.0)) }); 25 | var row2 = new XRow(sheet, 2); 26 | cells.Add(new XCell(row2, "A11") { ExcelFormula = new ExcelFormula("=2"), Expression = Expression.Constant(2.0) }); 27 | cells.Add(new XCell(row2, "B11") { ExcelFormula = new ExcelFormula("=20"), Expression = Expression.Constant(20.0) }); 28 | var row3 = new XRow(sheet, 3); 29 | cells.Add(new XCell(row3, "A12") { ExcelFormula = new ExcelFormula("=3"), Expression = Expression.Constant(3.0) }); 30 | cells.Add(new XCell(row3, "B12") { ExcelFormula = null, Expression = null }); 31 | 32 | _range = new XRange 33 | { 34 | Cells = cells.ToArray(), 35 | Address = "A10:B12", 36 | Sheet = sheet, 37 | Start = new CellAddress { Row = 10, Column = 1 }, 38 | End = new CellAddress { Row = 12, Column = 2 } 39 | }; 40 | } 41 | 42 | [Fact] 43 | public void VLookup_Approximate_With_FALSE() 44 | { 45 | // Assign 46 | 47 | // Act 48 | var resultExpression = LookupAndReferenceFunctions.VLookup(Expression.Constant(2.1), _range, Expression.Constant(2), Expression.Constant(true)); 49 | var result = resultExpression.LambdaInvoke(); 50 | 51 | // Assert 52 | Check.That(result).IsEqualTo(20.0); 53 | } 54 | 55 | [Fact] 56 | public void VLookup_Approximate_With_1() 57 | { 58 | // Assign 59 | 60 | // Act 61 | var resultExpression = LookupAndReferenceFunctions.VLookup(Expression.Constant(2.1), _range, Expression.Constant(2), Expression.Constant(1)); 62 | var result = resultExpression.LambdaInvoke(); 63 | 64 | // Assert 65 | Check.That(result).IsEqualTo(20.0); 66 | } 67 | 68 | [Fact] 69 | public void VLookup_ExactMatch_Found() 70 | { 71 | // Assign 72 | 73 | // Act 74 | var resultExpression = LookupAndReferenceFunctions.VLookup(Expression.Constant(1.0), _range, Expression.Constant(2), Expression.Constant(false)); 75 | var result = resultExpression.LambdaInvoke(); 76 | 77 | // Assert 78 | Check.That(result).IsEqualTo(10.0); 79 | } 80 | 81 | [Fact] 82 | public void VLookup_ExactMatch_NotFound() 83 | { 84 | // Assign 85 | 86 | // Act 87 | var resultExpression = LookupAndReferenceFunctions.VLookup(Expression.Constant(1.1), _range, Expression.Constant(2), Expression.Constant(false)); 88 | var result = resultExpression.LambdaInvoke(); 89 | 90 | // Assert 91 | Check.That(result).IsEqualTo("#N/A"); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /tests/ExcelFormulaExpressionParser.Tests/OperandFunctionTests.Date.cs: -------------------------------------------------------------------------------- 1 | using ExcelFormulaParser; 2 | using NFluent; 3 | using System.Linq.Expressions; 4 | using Xunit; 5 | using ExcelFormulaExpressionParser.Extensions; 6 | using System; 7 | using ExcelFormulaExpressionParser.Helpers; 8 | 9 | namespace ExcelFormulaExpressionParser.Tests 10 | { 11 | public partial class OperandFunctionTests 12 | { 13 | [Fact] 14 | public void OperandFunction_Date_Now() 15 | { 16 | // Assign 17 | var now = DateTimeHelpers.ToOADate(DateTime.UtcNow.AddMilliseconds(-1)); 18 | var formula = new ExcelFormula("=NOW()"); 19 | 20 | // Act 21 | Expression expression = new ExpressionParser(formula).Parse(); 22 | var result = expression.LambdaInvoke(); 23 | 24 | // Assert 25 | Check.That(result).IsStrictlyGreaterThan(now); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /tests/ExcelFormulaExpressionParser.Tests/OperandFunctionTests.Logical.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using ExcelFormulaExpressionParser.Extensions; 3 | using ExcelFormulaExpressionParser.Models; 4 | using ExcelFormulaParser; 5 | using Moq; 6 | using NFluent; 7 | using Xunit; 8 | 9 | namespace ExcelFormulaExpressionParser.Tests 10 | { 11 | public partial class OperandFunctionTests 12 | { 13 | private readonly ExcelFormulaContext _context; 14 | private readonly Mock _finder; 15 | 16 | public OperandFunctionTests() 17 | { 18 | _context = new ExcelFormulaContext 19 | { 20 | Sheet = new XSheet("sheet1") 21 | }; 22 | 23 | _finder = new Mock(); 24 | } 25 | 26 | [Fact] 27 | public void OperandFunction_And() 28 | { 29 | // Assign 30 | var formula = new ExcelFormula("=AND(TRUE, FALSE, TRUE, TRUE, FALSE, FALSE)"); 31 | 32 | // Act 33 | Expression expression = new ExpressionParser(formula).Parse(); 34 | var result = expression.LambdaInvoke(); 35 | 36 | // Assert 37 | Check.That(result).IsEqualTo(false); 38 | } 39 | 40 | [Fact] 41 | public void OperandFunction_If_False() 42 | { 43 | // Assign 44 | var formula = new ExcelFormula("=IF(FALSE, 42, -1)"); 45 | 46 | // Act 47 | Expression expression = new ExpressionParser(formula).Parse(); 48 | var result = expression.LambdaInvoke(); 49 | 50 | // Assert 51 | Check.That(result).IsEqualTo(-1); 52 | } 53 | 54 | [Fact] 55 | public void OperandFunction_If_True() 56 | { 57 | // Assign 58 | var formula = new ExcelFormula("=IF(TRUE, 42, -1)"); 59 | 60 | // Act 61 | Expression expression = new ExpressionParser(formula).Parse(); 62 | var result = expression.LambdaInvoke(); 63 | 64 | // Assert 65 | Check.That(result).IsEqualTo(42); 66 | } 67 | 68 | [Fact] 69 | public void OperandFunction_Or() 70 | { 71 | // Assign 72 | var formula = new ExcelFormula("=OR(TRUE, TRUE, FALSE)"); 73 | 74 | // Act 75 | Expression expression = new ExpressionParser(formula).Parse(); 76 | var result = Expression.Lambda(expression).Compile().DynamicInvoke(); 77 | 78 | // Assert 79 | Check.That(result).IsEqualTo(true); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /tests/ExcelFormulaExpressionParser.Tests/OperandFunctionTests.Math.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using ExcelFormulaExpressionParser.Extensions; 3 | using ExcelFormulaParser; 4 | using NFluent; 5 | using Xunit; 6 | 7 | namespace ExcelFormulaExpressionParser.Tests 8 | { 9 | partial class OperandFunctionTests 10 | { 11 | [Fact] 12 | public void OperandFunction_Math_Multiply() 13 | { 14 | // Assign 15 | var formula = new ExcelFormula("=3 * 7 * 9"); 16 | 17 | // Act 18 | Expression expression = new ExpressionParser(formula).Parse(); 19 | var result = expression.LambdaInvoke(); 20 | 21 | // Assert 22 | Check.That(result).IsEqualTo(189); 23 | } 24 | 25 | [Fact] 26 | public void OperandFunction_Math_Multiply_Negative() 27 | { 28 | // Assign 29 | var formula = new ExcelFormula("=-3 * 7"); 30 | 31 | // Act 32 | Expression expression = new ExpressionParser(formula).Parse(); 33 | var result = expression.LambdaInvoke(); 34 | 35 | // Assert 36 | Check.That(result).IsEqualTo(-21); 37 | } 38 | 39 | [Fact] 40 | public void OperandFunction_Math_Multiply_Negative_Both() 41 | { 42 | // Assign 43 | var formula = new ExcelFormula("=-3 * -7"); 44 | 45 | // Act 46 | Expression expression = new ExpressionParser(formula).Parse(); 47 | var result = expression.LambdaInvoke(); 48 | 49 | // Assert 50 | Check.That(result).IsEqualTo(21); 51 | } 52 | 53 | [Fact] 54 | public void OperandFunction_Math_Order() 55 | { 56 | // Assign 57 | var formula = new ExcelFormula("=10 * 50 + 20"); 58 | 59 | // Act 60 | Expression expression = new ExpressionParser(formula).Parse(); 61 | var result = expression.LambdaInvoke(); 62 | 63 | // Assert 64 | Check.That(result).IsEqualTo(520); 65 | } 66 | 67 | [Fact] 68 | public void OperandFunction_Math_Sum() 69 | { 70 | // Assign 71 | var formula = new ExcelFormula("=SUM(1,2,3)"); 72 | 73 | // Act 74 | Expression expression = new ExpressionParser(formula).Parse(); 75 | var result = expression.LambdaInvoke(); 76 | 77 | // Assert 78 | Check.That(result).IsEqualTo(6); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /tests/ExcelFormulaExpressionParser.Tests/OperandFunctionTests.Nested.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using ExcelFormulaExpressionParser.Extensions; 3 | using ExcelFormulaParser; 4 | using NFluent; 5 | using Xunit; 6 | 7 | namespace ExcelFormulaExpressionParser.Tests 8 | { 9 | public partial class OperandFunctionTests 10 | { 11 | [Fact] 12 | public void OperandFunction_NestedFunctions0() 13 | { 14 | // Assign 15 | var formula = new ExcelFormula("=ABS(-2) + 7 + 11"); 16 | 17 | // Act 18 | Expression expression = new ExpressionParser(formula).Parse(); 19 | var result = expression.LambdaInvoke(); 20 | 21 | // Assert 22 | Check.That(result).IsEqualTo(20); 23 | } 24 | 25 | [Fact] 26 | public void OperandFunction_NestedFunctions1() 27 | { 28 | // Assign 29 | var formula = new ExcelFormula("=ROUND(-10 * 1.001 + 7, 0) + 1 * 7"); 30 | 31 | // Act 32 | Expression expression = new ExpressionParser(formula).Parse(); 33 | var result = expression.LambdaInvoke(); 34 | 35 | // Assert 36 | Check.That(result).IsEqualTo(4); 37 | } 38 | 39 | [Fact] 40 | public void OperandFunction_NestedFunctions2() 41 | { 42 | // Assign 43 | var formula = new ExcelFormula("=ROUND(-3 * ROUND(1.001 * 2, 1) + 7.001, 1) + 11 - 3 * 3"); 44 | 45 | // Act 46 | Expression expression = new ExpressionParser(formula).Parse(); 47 | var result = expression.LambdaInvoke(); 48 | 49 | // Assert 50 | Check.That(result).IsEqualTo(3); 51 | } 52 | 53 | [Fact] 54 | public void OperandFunction_NestedSubexpressions1() 55 | { 56 | // Assign 57 | var formula = new ExcelFormula("=5 * (1 + 2)"); 58 | 59 | // Act 60 | Expression expression = new ExpressionParser(formula).Parse(); 61 | var result = expression.LambdaInvoke(); 62 | 63 | // Assert 64 | Check.That(result).IsEqualTo(15); 65 | } 66 | 67 | [Fact] 68 | public void OperandFunction_NestedSubexpressions2() 69 | { 70 | // Assign 71 | var formula = new ExcelFormula("=(5 * (1 + 2)) + (-1 * (-7 * -9))"); 72 | 73 | // Act 74 | Expression expression = new ExpressionParser(formula).Parse(); 75 | var result = expression.LambdaInvoke(); 76 | 77 | // Assert 78 | Check.That(result).IsEqualTo(-48); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /tests/ExcelFormulaExpressionParser.Tests/OperandFunctionTests.VLookup.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq.Expressions; 3 | using ExcelFormulaExpressionParser.Extensions; 4 | using ExcelFormulaExpressionParser.Models; 5 | using ExcelFormulaParser; 6 | using Moq; 7 | using NFluent; 8 | using Xunit; 9 | 10 | namespace ExcelFormulaExpressionParser.Tests 11 | { 12 | public partial class OperandFunctionTests 13 | { 14 | [Fact] 15 | public void OperandFunction_VLookup() 16 | { 17 | // Assign 18 | var range = CreateXRange(); 19 | 20 | _finder.Setup(f => f.Find(It.IsAny(), "A10:B12")).Returns(range); 21 | var formula = new ExcelFormula("=VLOOKUP(2.1, A10:B12, 2)"); 22 | 23 | // Act 24 | Expression expression = new ExpressionParser(formula, 0, _context, _finder.Object).Parse(); 25 | var result = expression.LambdaInvoke(); 26 | 27 | // Assert 28 | Check.That(result).IsEqualTo(20.0); 29 | } 30 | 31 | [Fact] 32 | public void OperandFunction_VLookup_ExactMatch_Found() 33 | { 34 | // Assign 35 | var range = CreateXRange(); 36 | 37 | _finder.Setup(f => f.Find(It.IsAny(), "A10:B12")).Returns(range); 38 | var formula = new ExcelFormula("=VLOOKUP(1.0, A10:B12, 2, FALSE)"); 39 | 40 | // Act 41 | Expression expression = new ExpressionParser(formula, 0, _context, _finder.Object).Parse(); 42 | var result = expression.LambdaInvoke(); 43 | 44 | // Assert 45 | Check.That(result).IsEqualTo(10.0); 46 | } 47 | 48 | private static XRange CreateXRange() 49 | { 50 | var sheet = new XSheet("sheet1"); 51 | var cells = new List(); 52 | var row1 = new XRow(sheet, 1); 53 | cells.Add(new XCell(row1, "A10") { ExcelFormula = new ExcelFormula("=1"), Expression = Expression.Constant(1.0) }); 54 | cells.Add(new XCell(row1, "B10") { ExcelFormula = new ExcelFormula("=SUM(1,9)"), Expression = Expression.Add(Expression.Constant(1.0), Expression.Constant(9.0)) }); 55 | var row2 = new XRow(sheet, 2); 56 | cells.Add(new XCell(row2, "A11") { ExcelFormula = new ExcelFormula("=2"), Expression = Expression.Constant(2.0) }); 57 | cells.Add(new XCell(row2, "B11") { ExcelFormula = new ExcelFormula("=20"), Expression = Expression.Constant(20.0) }); 58 | var row3 = new XRow(sheet, 3); 59 | cells.Add(new XCell(row3, "A12") { ExcelFormula = new ExcelFormula("=3"), Expression = Expression.Constant(3.0) }); 60 | cells.Add(new XCell(row3, "B12") { ExcelFormula = null, Expression = null }); 61 | 62 | return new XRange 63 | { 64 | Cells = cells.ToArray(), 65 | Address = "A10:B12", 66 | Sheet = sheet, 67 | Start = new CellAddress { Row = 10, Column = 1 }, 68 | End = new CellAddress { Row = 12, Column = 2 } 69 | }; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /tests/ExcelFormulaExpressionParser.Tests/OperatorPrefixLogicalTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using ExcelFormulaExpressionParser.Extensions; 3 | using ExcelFormulaParser; 4 | using NFluent; 5 | using Xunit; 6 | 7 | namespace ExcelFormulaExpressionParser.Tests 8 | { 9 | public class OperatorPrefixLogicalTests 10 | { 11 | [Fact] 12 | public void OperatorPrefixLogical_Equal() 13 | { 14 | // Assign 15 | var formula = new ExcelFormula("=1=2"); 16 | 17 | // Act 18 | Expression expression = new ExpressionParser(formula).Parse(); 19 | var result = expression.LambdaInvoke(); 20 | 21 | // Assert 22 | Check.That(result).IsEqualTo(false); 23 | } 24 | 25 | [Fact] 26 | public void OperatorPrefixLogical_Gt() 27 | { 28 | // Assign 29 | var formula = new ExcelFormula("=1>2"); 30 | 31 | // Act 32 | Expression expression = new ExpressionParser(formula).Parse(); 33 | var result = expression.LambdaInvoke(); 34 | 35 | // Assert 36 | Check.That(result).IsEqualTo(false); 37 | } 38 | 39 | [Fact] 40 | public void OperatorPrefixLogical_Gte() 41 | { 42 | // Assign 43 | var formula = new ExcelFormula("=1>=1"); 44 | 45 | // Act 46 | Expression expression = new ExpressionParser(formula).Parse(); 47 | var result = expression.LambdaInvoke(); 48 | 49 | // Assert 50 | Check.That(result).IsEqualTo(true); 51 | } 52 | 53 | [Fact] 54 | public void OperatorPrefixLogical_Lt() 55 | { 56 | // Assign 57 | var formula = new ExcelFormula("=1<2"); 58 | 59 | // Act 60 | Expression expression = new ExpressionParser(formula).Parse(); 61 | var result = expression.LambdaInvoke(); 62 | 63 | // Assert 64 | Check.That(result).IsEqualTo(true); 65 | } 66 | 67 | [Fact] 68 | public void OperatorPrefixLogical_Lte() 69 | { 70 | // Assign 71 | var formula = new ExcelFormula("=1<=1"); 72 | 73 | // Act 74 | Expression expression = new ExpressionParser(formula).Parse(); 75 | var result = expression.LambdaInvoke(); 76 | 77 | // Assert 78 | Check.That(result).IsEqualTo(true); 79 | } 80 | 81 | [Fact] 82 | public void OperatorPrefixLogical_NotEqual() 83 | { 84 | // Assign 85 | var formula = new ExcelFormula("=1<>2"); 86 | 87 | // Act 88 | Expression expression = new ExpressionParser(formula).Parse(); 89 | var result = expression.LambdaInvoke(); 90 | 91 | // Assert 92 | Check.That(result).IsEqualTo(true); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /tests/ExcelFormulaParser.Tests/ExcelFormulaParser.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Stef Heyenrath 4 | net452 5 | ExcelFormulaParser.Tests 6 | full 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | $(DefineConstants);NETSTANDARD 29 | 30 | -------------------------------------------------------------------------------- /tests/ExcelFormulaParser.Tests/ExcelFormulaTests.cs: -------------------------------------------------------------------------------- 1 | using NFluent; 2 | using Xunit; 3 | 4 | namespace ExcelFormulaParser.Tests 5 | { 6 | public class ExcelFormulaTests 7 | { 8 | // ReSharper disable once InconsistentNaming 9 | private ExcelFormula ef; 10 | 11 | [Fact] 12 | public void ExcelFormula_ParseNumber() 13 | { 14 | // Assign 15 | const string formula = "=42"; 16 | 17 | // Act 18 | ef = new ExcelFormula(formula); 19 | 20 | // Assert 21 | Check.That(ef.Count).Equals(1); 22 | Check.That(ef[0].Value).Equals("42"); 23 | Check.That(ef[0].Type).Equals(ExcelFormulaTokenType.Operand); 24 | Check.That(ef[0].Subtype).Equals(ExcelFormulaTokenSubtype.Number); 25 | } 26 | 27 | [Fact] 28 | public void ExcelFormula_ParseAddIntegerNumbers() 29 | { 30 | // Assign 31 | const string formula = "=1+2"; 32 | 33 | // Act 34 | ef = new ExcelFormula(formula); 35 | 36 | // Assert 37 | Check.That(ef.Count).Equals(3); 38 | Check.That(ef[0].Value).Equals("1"); 39 | Check.That(ef[0].Type).Equals(ExcelFormulaTokenType.Operand); 40 | Check.That(ef[0].Subtype).Equals(ExcelFormulaTokenSubtype.Number); 41 | 42 | Check.That(ef[1].Value).Equals("+"); 43 | Check.That(ef[1].Type).Equals(ExcelFormulaTokenType.OperatorInfix); 44 | Check.That(ef[1].Subtype).Equals(ExcelFormulaTokenSubtype.Math); 45 | 46 | Check.That(ef[2].Value).Equals("2"); 47 | Check.That(ef[2].Type).Equals(ExcelFormulaTokenType.Operand); 48 | Check.That(ef[2].Subtype).Equals(ExcelFormulaTokenSubtype.Number); 49 | } 50 | 51 | [Fact] 52 | public void ExcelFormula_ParseDoubleNumbers() 53 | { 54 | // Assign 55 | const string formula = "=1.1+2.2"; 56 | 57 | // Act 58 | ef = new ExcelFormula(formula); 59 | 60 | // Assert 61 | Check.That(ef.Count).Equals(3); 62 | Check.That(ef[0].Value).Equals("1.1"); 63 | Check.That(ef[2].Value).Equals("2.2"); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /tests/ExcelFormulaParser.Tests/OperatorMathTests.cs: -------------------------------------------------------------------------------- 1 | using NFluent; 2 | using Xunit; 3 | 4 | namespace ExcelFormulaParser.Tests 5 | { 6 | public class OperatorMathTests 7 | { 8 | // ReSharper disable once InconsistentNaming 9 | private ExcelFormula ef; 10 | 11 | [Fact] 12 | public void ExcelFormula_Math_Add() 13 | { 14 | // Assign 15 | const string formula = "=1+"; 16 | 17 | // Act 18 | ef = new ExcelFormula(formula); 19 | 20 | // Assert 21 | Check.That(ef.Count).Equals(2); 22 | 23 | Check.That(ef[0].Value).Equals("1"); 24 | Check.That(ef[0].Type).Equals(ExcelFormulaTokenType.Operand); 25 | Check.That(ef[0].Subtype).Equals(ExcelFormulaTokenSubtype.Number); 26 | 27 | Check.That(ef[1].Value).Equals("+"); 28 | Check.That(ef[1].Type).Equals(ExcelFormulaTokenType.OperatorInfix); 29 | Check.That(ef[1].Subtype).Equals(ExcelFormulaTokenSubtype.Math); 30 | } 31 | 32 | [Fact] 33 | public void ExcelFormula_Math_Multiply() 34 | { 35 | // Assign 36 | const string formula = "=1*"; 37 | 38 | // Act 39 | ef = new ExcelFormula(formula); 40 | 41 | // Assert 42 | Check.That(ef.Count).Equals(2); 43 | 44 | Check.That(ef[0].Value).Equals("1"); 45 | Check.That(ef[0].Type).Equals(ExcelFormulaTokenType.Operand); 46 | Check.That(ef[0].Subtype).Equals(ExcelFormulaTokenSubtype.Number); 47 | 48 | Check.That(ef[1].Value).Equals("*"); 49 | Check.That(ef[1].Type).Equals(ExcelFormulaTokenType.OperatorInfix); 50 | Check.That(ef[1].Subtype).Equals(ExcelFormulaTokenSubtype.Math); 51 | } 52 | } 53 | } --------------------------------------------------------------------------------