如何在C#中解析Excel公式
前言
在日常工作中,我们经常需要在Excel中使用公式对表中数据进行计算(求和、求差和求均值等)和分析,从而实现对数据的分类,通常情况下,当数据量较少或场景变化单一的情况下,使用公式可以满足用户的要求,但当数据量较大或者场景变化复杂的情况下,使用公式也无法满足用户的需求的情况。这个时候就可以用编码的方式来解决,以下面的背景需求为例,小编将为大家介绍如何使用葡萄城公司基于 .NET 和 .NET Core 平台的服务端高性能表格组件组件GrapeCity Documents for Excel (以下简称GcExcel)解析Excel中的现有公式并根据需求对其进行修改。
背景需求
下图是一张销售数据表,左侧显示原始销售数据,包括销售代表的姓名、地区、产品和销售数量,右侧显示了从原始数据中提取的特定的销售代表对应的销售分析结果,以及每个产品区域组合的月度销售目标进度。目标进度的标准如下:
- 低于 2500:低于目标
- 超过 3000:达到目标
- 超过 5000:高于目标

一般情况下,我们使用Excel中的 IF、ISNUMBER 和 FILTER 函数就可以实现将左侧的销售原始数据转化为右侧的销售分析结果,如下所示:
=IF(ISNUMBER(FILTER(A2:D19,A2:A19="Fritz")),IFS(FILTER(A2:D19,A2:A19="Fritz")>5000,"Above Target",FILTER(A2:D19,A2:A19="Fritz")>3000,"On Target",FILTER(A2:D19,A2:A19="Fritz")<2500,"Below Target"),FILTER(A2:D19,A2:A19="Fritz"))
但是这样的话就会出现一个问题,对于不同的人名,小编需要将上面公式中销售代表的姓名进行替换,也就是需要不断地手动改变姓名执行操作,这一举动不仅枯燥,而且很容易出错。因此这个时候就可以使用GcExcel通过解析公式并使用解析的语法树轻松替换销售代表姓名,可以简化此任务。
使用 C# 解析和修改 Excel 公式
首先,创建一个新的 C#(.NET Core) 项目,并使用NuGet 包管理器安装 GcExcel 包,然后按照前面的步骤操作。
1、使用示例数据初始化工作簿
实例化 Workbook 类的实例并从 Excel 文件导入示例数据,如下所示。
//Create a new workbook
var workbook = new GrapeCity.Documents.Excel.Workbook();
//Load sample data from excel file
workbook.Open("SampleData.xlsx");
//Enable dynamic array formula
workbook.AllowDynamicArray = true;
2、提取公式
在工作簿加载示例数据和预期公式后,我们从工作表中提取所需的公式,以便使用 Formula 属性进行解析和修改。
GcExcel API 提供的公式解析器希望传递的公式不带“=”(等于)运算符,以便成功进行公式解析。因此,请注意如何在不使用“=”运算符的情况下提取公式。
//Fetch worksheet
var worksheet = workbook.Worksheets[0];
//Fetch the original formula which needs to be parsed.
var originalFormula = worksheet.Range["H3"].Formula.Substring(1);
3、解析公式
调用 FormulaSynatxTree 类的 Parse 方法来解析公式并生成语法树,帮助您理解公式包含的所有不同类型的值、运算符和函数。
公式语法树的每个标记都由 GcExcel API 中的其他类表示,例如函数的 FunctionNode、运算符的 OperatorNode 等。
下面的代码解析了上一步中提取的销售分析公式。然后,它将生成的 FormulaSyntaxTree 中的值附加到工作簿,该工作簿随后保存为 Excel 文件,以帮助您了解公式的语法树。
//Method to parse a formula and print the syntax tree
public static void ParseAndPrint(IWorksheet worksheet, string formula)
{
// Get syntax tree
var syntaxTree = FormulaSyntaxTree.Parse(formula);
// Flatten nodes
var displayItems = new List<(string TypeName, int IndentLevel, string Content)>();
void flatten(SyntaxNode node, int level)
{
displayItems.Add((node.GetType().Name, level, node.ToString()));
foreach (var child in node.Children)
{
flatten(child, level + 1);
}
}
flatten(syntaxTree.Root, 0);
// Output
worksheet.ShowRowOutline = false;
worksheet.OutlineColumn.ColumnIndex = 1;
// Header
worksheet.Range["A1"].Value = "Formula";
worksheet.Range["A3"].Value = "Syntax node";
worksheet.Range["B3"].Value = "Part";
// Values
worksheet.Range["B1"].Value = "'=" + formula;
for (var i = 0; i < displayItems.Count; i++)
{
var item = displayItems[i];
var text = "'" + item.TypeName;
worksheet.Range[i + 4, 0].Value = text;
worksheet.Range[i + 4, 0].IndentLevel = item.IndentLevel;
worksheet.Range[i + 4, 1].Value = "'" + item.Content;
}
//Apply styling
worksheet.Range["A1:B3"].Interior.Color = System.Drawing.Color.FromArgb(68, 114, 196);
worksheet.Range["A1:B3"].Font.Color = System.Drawing.Color.White;
worksheet.Range["A1:B3"].Borders.Color = System.Drawing.Color.FromArgb(91, 155, 213);
worksheet.Range["A1:B3"].Borders.LineStyle = BorderLineStyle.Thin;
worksheet.Range["A1,A3,B3"].Font.Size = 14;
worksheet.Range["A1,A3,B3"].Font.Bold = true;
worksheet.Range["A:C"].EntireColumn.AutoFit();
}
下图是生成的 FormulaSyntaxTree 的效果图图。请注意,这只是完整语法树的一部分:

4、修改公式
从上一步生成的语法树中,您可以看到销售代表姓名以 TextNode 形式表示,并且在公式中多次出现。我们可以通过简单的查找和替换操作来替换所有这些出现的情况,如下面的代码所示:
- 了替换公式中的销售代表姓名,我们从他们的姓名列表开始。我们使用 UNIQUE 函数从原始数据中过滤掉唯一名称列表。然后使用这个 UNIQUE 函数的结果来解析和修改所有销售代表的销售分析公式。
- 我们使用 TextNode 类修改销售代表姓名。下面的代码初始化 TextNode 类的实例,并将要在公式中搜索的销售代表姓名作为参数传递。该实例可以称为查找节点。
- 接下来,我们初始化 TextNode 类的另一个实例,并将公式中要替换的销售代表姓名作为参数传递。该实例可以称为替换节点。
- 下面的代码中定义了一个递归函数 replaceNode,用于遍历语法树的所有子节点,并将每个出现的 Find 节点替换为 Replace 节点。每个销售代表都会重复此操作。
- 修改公式后,新公式将分配给工作表中的单元格以生成预期的销售报告。
下面的代码包含一些格式化代码来格式化销售报告内容。
//Method to parse and modify the formula
public static void ModifyFormula(IWorksheet worksheet, string originalFormula)
{
//Apply UNIQUE formula to get unique sales representatives list
worksheet.Range["F1"].Value = "Unique Rep";
worksheet.Range["F2"].Formula = "=UNIQUE(A2:A19)";
var uniqueRep = worksheet.Range["F2#"];
// Apply Styling
worksheet.Range["F:F"].EntireColumn.AutoFit();
worksheet.Range["F1"].Interior.Color = System.Drawing.Color.FromArgb(68, 114, 196);
worksheet.Range["F1"].Font.Color = System.Drawing.Color.White;
worksheet.Range["F2#"].Borders.Color = System.Drawing.Color.FromArgb(91, 155, 213);
worksheet.Range["F2#"].Borders.LineStyle = BorderLineStyle.Thin;
//Get syntax tree
var syntaxTree = FormulaSyntaxTree.Parse(originalFormula);
//Find
var findText = new TextNode("Fritz");
//Replacement
var replaceText = new TextNode("");
//Loop through names list to modify the formula for each sales representative
for (int r = 0, resultRow = 3; r < uniqueRep.Cells.Count; r++, resultRow = resultRow + 4)
{
//Get name to be replaced in the formula
var cval = uniqueRep.Cells[r].Value.ToString();
if (findText.Value != cval)
{
//Assign name to be replaced to Replace TextNode
replaceText.Value = cval;
//Invoke the recursive method to perform find and replace operation
replaceNode(syntaxTree.Root, findText, replaceText);
//Assign the modified formula to a cell in the worksheet
var resultRange = "H" + resultRow.ToString();
worksheet.Range[resultRange].Formula = "=" + syntaxTree.ToString();
worksheet.Range[resultRange + "#"].Borders.Color = System.Drawing.Color.FromArgb(91, 155, 213);
worksheet.Range[resultRange + "#"].Borders.LineStyle = BorderLineStyle.Thin;
//Update the value of Find node to perform find and replace operation for next sales representative name
findText = replaceText;
}
}
//Find and replace
void replaceNode(SyntaxNode lookIn, SyntaxNode find, SyntaxNode replacement)
{
var children = lookIn.Children;
for (var i = 0; i < children.Count; i++)
{
var child = children[i];
if (child.Equals(find))
{
children[i] = replacement;
}
else
{
replaceNode(child, find, replacement);
}
}
}
}
这是修改后的公式之一:
=IF(ISNUMBER(FILTER(A2:D19,A2:A19="Xi")),IFS(FILTER(A2:D19,A2:A19="Xi")>5000,"Above Target",FILTER(A2:D19,A2:A19="Xi")>3000,"On Target",FILTER(A2:D19,A2:A19="Xi")<2500,"Below Target"),FILTER(A2:D19,A2:A19="Xi"))
5、保存 Excel 文件
将所有修改的公式添加到工作表后,将调用 Workbook 类的 Save 方法来保存 Excel 文件,如下面的代码所示:
//Save modified Excel file
workbook.Save("ModifiedFormula.xlsx", SaveFileFormat.Xlsx);
打开保存的 Excel 文件可以看到下图:

总结
以上就是使用C#实现解析Excel的全过程,如果您想了解更多信息,欢迎点击这里查看更多资料。
扩展链接:
如何在C#中解析Excel公式的更多相关文章
- 面试官问我:如何在 Python 中解析和修改 XML
摘要:我们经常需要解析用不同语言编写的数据.Python提供了许多库来解析或拆分用其他语言编写的数据.在此 Python XML 解析器教程中,您将学习如何使用 Python 解析 XML. 本文分享 ...
- 如何在Elasticsearch中解析未分配的分片(unassigned shards)
一.精确定位到有问题的shards 1.查看哪些分片未被分配 curl -XGET localhost:9200/_cat/shards?h=index,shard,prirep,state,unas ...
- java中解析excel 批量插入数据库
Facade 层 实现类 (@Service("samePeriodModelImportFacade")) 1. 获取cells 的方法 public Cells getCel ...
- C# 如何在winform中嵌入Excel,内嵌Excel,word
近使用.net做一个小软件遇到一个问题,就是想实现把excel表格在winform中打开,同时可以操作,不单单是打开.或者就提取数据.在网上找了好多资料,发现这方面的资料比较少,即使有,都是旧版本的使 ...
- 如何在office2010中的EXCEL表格使用求和公式
EXCEL做表格非常方便,有时我们需要对表格中的很多数字进行求和计算,如果用计算器算会非常麻烦,别担心,用求和公式计算,非常简单的 工具/原料 电脑一台 offic2010软件一套 方法/步骤 ...
- 如何在FineReport中解析数据库内XML文件
在数据库表中,其中字段XML所存的为xml格式数据在表xmltest中.那么在使用该表进行报表制作时,需要将存于xml字段中的值读取出来作为报表数据源. XML每条记录数据格式如下: <Fiel ...
- C# Dsoframer.ocx 如何在winform中嵌入Excel,内嵌Excel,word
如果你还不太清楚Dspframer.ocx怎么放到窗体上就看上一篇文章,里面详细介绍了是如何放到窗体上的. 链接:http://www.cnblogs.com/pingming/p/4182045.h ...
- JXL解析Excel表格内容到数据库
java中常用的解析Excel表格的工具一种是POI一种是JXL,POI功能强大,相比JXL稍嫌复杂,对表格样式的处理非常好:而JXL解析简单方便,对中文支持比较好. 工作中解析Excel内容上传到数 ...
- go 中解析JSON的三种姿势
背景 这是一篇写给0-1年新人的文章,短平快的教会你如何解析json字符串. 示例Json 假设有如下json字符串: { "userName":"admin" ...
- POI使用:用poi接口不区分xls/xlsx格式解析Excel文档(41种日期格式解析方法,5种公式结果类型解析方法,3种常用数值类型精度控制办法)
一.使用poi解析excel文档 注:全部采用poi接口进行解析,不需要区分xls.xlsx格式,不需要判断文档类型. poi中的日期格式判断仅支持欧美日期习惯,对国内的日期格式并不支持判断,怎么办? ...
随机推荐
- 【转帖】SmartNIC — TSO、GSO、LRO、GRO 技术
目录 文章目录 目录 TSO(TCP Segmentation Offload) GSO(Generic Segmentation Offload) LRO(Large Receive Offload ...
- [转帖]【JVM】堆内存与栈内存详解
堆和栈的定义 java把内存分成栈内存和堆内存. (1)栈内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配. 当在一段代码块中定义一个变量时,java就在栈中为这个变量分 ...
- [转帖]NOHZ = ON如何影响Linux内核中的do_timer()?
https://www.jb51.cc/faq/897483.html 如何解决NOHZ = ON如何影响Linux内核中的do_timer()?? 首先,让我们了解什么是tickless kerne ...
- [转帖]CentOS8安装MySQL8详细教程,爬坑必备
https://www.ab62.cn/article/23022.html 安装环境 CentOS:8.5.2111MySQL:8.0.30 MySQL Community Server 安装过程 ...
- 《Javascript高级程序设计》读书笔记——继承与原型链
继承与原型链 原型链 在原型那一节中,讲到了用于搜索对象属性的原型搜索机制:而原型链,本质上 就是对原型搜索机制的扩充: 回想下之前的内容,我们要读取一个Person的实例p属性,会先搜索实例p:如果 ...
- Mac 版的 Quicker CirMenu
之前在Windows上用过一款圆盘菜单工具Quicker, 感觉非常方便, 换成Macos后,一直没有找到类似应用. 最近终于发现,一款好用的快捷键收集,触发工具CirMenu. 其核心功能是可以根据 ...
- js中toFixed 并不是你想的那样进行四舍五入
toFixed 的简单介绍 toFixed() 方法可把 Number 类型的数字通过四舍五入为指定小数位的字符串.(将数字类型转化为字符串类型) 也就是说toFixed只能够处理数字类型的. 字符串 ...
- 【分享笔记】druid存储系统-思维导图
作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu 公众号:一本正经的瞎扯 源于:<Druid实时大数据分析原理与实践>这本书的阅读笔记 ...
- dwm 美化
在之前的博客中,我们将arch linux这个系统进行了一些美化,当然也是仅仅做到能看这个地步,要说跟网上其他那些惊艳的特效对比,肯定是不如的.但是我一直秉持一个观点,美化应该适可而止,只要不是丑的你 ...
- 获取Visual Studio所用MSVC编译器版本:_MSC_VER数值
本文介绍查看Visual Studio软件_MSC_VER值的方法. _MSC_VER是微软公司推出的C/C++编译器--MSVC编译器的一个内置宏,其值表示当前Visual Studio软件 ...