前言

在日常工作中,我们经常需要在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 形式表示,并且在公式中多次出现。我们可以通过简单的查找和替换操作来替换所有这些出现的情况,如下面的代码所示:

  1. 了替换公式中的销售代表姓名,我们从他们的姓名列表开始。我们使用 UNIQUE 函数从原始数据中过滤掉唯一名称列表。然后使用这个 UNIQUE 函数的结果来解析和修改所有销售代表的销售分析公式。
  2. 我们使用 TextNode 类修改销售代表姓名。下面的代码初始化 TextNode 类的实例,并将要在公式中搜索的销售代表姓名作为参数传递。该实例可以称为查找节点。
  3. 接下来,我们初始化 TextNode 类的另一个实例,并将公式中要替换的销售代表姓名作为参数传递。该实例可以称为替换节点。
  4. 下面的代码中定义了一个递归函数 replaceNode,用于遍历语法树的所有子节点,并将每个出现的 Find 节点替换为 Replace 节点。每个销售代表都会重复此操作。
  5. 修改公式后,新公式将分配给工作表中的单元格以生成预期的销售报告。

下面的代码包含一些格式化代码来格式化销售报告内容。

//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的全过程,如果您想了解更多信息,欢迎点击这里查看更多资料。

扩展链接:

轻松构建低代码工作流程:简化繁琐任务的利器

优化预算管理流程:Web端实现预算编制的利器

如何在.NET电子表格应用程序中创建流程图

如何在C#中解析Excel公式的更多相关文章

  1. 面试官问我:如何在 Python 中解析和修改 XML

    摘要:我们经常需要解析用不同语言编写的数据.Python提供了许多库来解析或拆分用其他语言编写的数据.在此 Python XML 解析器教程中,您将学习如何使用 Python 解析 XML. 本文分享 ...

  2. 如何在Elasticsearch中解析未分配的分片(unassigned shards)

    一.精确定位到有问题的shards 1.查看哪些分片未被分配 curl -XGET localhost:9200/_cat/shards?h=index,shard,prirep,state,unas ...

  3. java中解析excel 批量插入数据库

    Facade 层 实现类 (@Service("samePeriodModelImportFacade")) 1.  获取cells 的方法 public Cells getCel ...

  4. C# 如何在winform中嵌入Excel,内嵌Excel,word

    近使用.net做一个小软件遇到一个问题,就是想实现把excel表格在winform中打开,同时可以操作,不单单是打开.或者就提取数据.在网上找了好多资料,发现这方面的资料比较少,即使有,都是旧版本的使 ...

  5. 如何在office2010中的EXCEL表格使用求和公式

    EXCEL做表格非常方便,有时我们需要对表格中的很多数字进行求和计算,如果用计算器算会非常麻烦,别担心,用求和公式计算,非常简单的 工具/原料   电脑一台 offic2010软件一套 方法/步骤   ...

  6. 如何在FineReport中解析数据库内XML文件

    在数据库表中,其中字段XML所存的为xml格式数据在表xmltest中.那么在使用该表进行报表制作时,需要将存于xml字段中的值读取出来作为报表数据源. XML每条记录数据格式如下: <Fiel ...

  7. C# Dsoframer.ocx 如何在winform中嵌入Excel,内嵌Excel,word

    如果你还不太清楚Dspframer.ocx怎么放到窗体上就看上一篇文章,里面详细介绍了是如何放到窗体上的. 链接:http://www.cnblogs.com/pingming/p/4182045.h ...

  8. JXL解析Excel表格内容到数据库

    java中常用的解析Excel表格的工具一种是POI一种是JXL,POI功能强大,相比JXL稍嫌复杂,对表格样式的处理非常好:而JXL解析简单方便,对中文支持比较好. 工作中解析Excel内容上传到数 ...

  9. go 中解析JSON的三种姿势

    背景 这是一篇写给0-1年新人的文章,短平快的教会你如何解析json字符串. 示例Json 假设有如下json字符串: { "userName":"admin" ...

  10. POI使用:用poi接口不区分xls/xlsx格式解析Excel文档(41种日期格式解析方法,5种公式结果类型解析方法,3种常用数值类型精度控制办法)

    一.使用poi解析excel文档 注:全部采用poi接口进行解析,不需要区分xls.xlsx格式,不需要判断文档类型. poi中的日期格式判断仅支持欧美日期习惯,对国内的日期格式并不支持判断,怎么办? ...

随机推荐

  1. [转帖] 使用socat反向Shell多台机器

    https://www.cnblogs.com/codelogs/p/16012319.html 场景# 很多时候,我们需要批量操作多台机器,业界一般使用Ansible来实现,但使用Ansible来操 ...

  2. 庖丁解牛:最全babel-plugin-import源码详解

    庖丁解牛:最全 babel-plugin-import 源码详解 序言:在用 babel-plugin 实现按需加载一文中笔者用作用域链思路实现了按需加载组件.此思路是统一式处理,进入 ImportD ...

  3. 图片三像素问题如何解决css

    一.提出问题 在浏览器中,图片有一个下间隙问题,有人也称之为图片3像素BUG 1.这并不是什么浏览器bug,而只是英文字母书写时有个基线的问题,基线决定了图片的对其方式.这才是造成浏览器中图片下间隙的 ...

  4. 从零开始配置 vim(3)—— 键盘映射进阶

    严格意义上来说,快捷键的绑定应该是键盘映射,将某些键映射为另一些键. 在上篇我们介绍了基本的键盘映射操作,知道了如何 :map.:imap.:vmap.:nmap这些命令来映射键盘快捷键.它们很方便, ...

  5. tensorflow语法【tf.concat()详解】

    相关文章: [一]tensorflow安装.常用python镜像源.tensorflow 深度学习强化学习教学 [二]tensorflow调试报错.tensorflow 深度学习强化学习教学 [三]t ...

  6. c#树结构转npoi复杂表头

    Vue 前端框架框架中采用树结构打印表头,为了前后端适配NPOI导出. 这里重点做树结构转换 NPOI 复杂表头的结构数据( 跨行.跨列),其它具体导出功能请参考  https://www.cnblo ...

  7. Mybatis(一对一、一对多、多对多)操作

    * 首先列出示例中用到的数据库表 user表: accout表: role表: user_role表: 建表语句如下: DROP TABLE IF EXISTS `user`; CREATE TABL ...

  8. Django之FBV和CBV模式

    FBV就是 url路由>>>业务处理函数的方式,CBV就是url路由>>>类 的处理业务方式. 最常用的就是FBV模式,就不用过多赘述,直接上CBV的实用代码. 1 ...

  9. (python)每日代码||2024.1.27||类方法与实例方法

    class test(): aaa = 111 bbb = 222 ccc = 333 @classmethod def cm(cls): cls.aaa="***" def im ...

  10. POJ1080 滑雪

    题目链接 题目 Description Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激.可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你.Mi ...