.NET 简单快速导出Word文档
共 6849字,需浏览 14分钟
· 2020-08-23
转自:ToolGood cnblogs.com/toolgood/p/13512091.html
前言
最近,我写公司项目word导出功能,应该只有2小时的工作量,却被硬生生的拉长2天,项目上线到业务正常运行也被拉长到2个星期。
为什么如此浪费时间呢?
1、公司的项目比较老,采用硬编码模式,意味着word改一个字就要发布一次代码。发布检验就浪时间了。
2、由于硬编码,采用的是这种格式,手写代码比较废时,而且编写表格时会遇到单元格字数变多被撑大,表格变形的情况。表格长度需要人工计算。这类意想不到的问题。
3、公司测试库数据不全,测试库数据无法全面覆盖线上环境。这又拉长了检验时间。
4、项目分支被正在开发的分支合并了,一下子被拉长了4天。
这简单功能浪费太多时间了,我在网上搜了一下word导出的方案:
第一种:硬编码,就是公司的方案,问题太多了不用考虑。
第二种:通过Sql查询数据,存入字典,再通过第三方组件替换word的文字。这种方案,简单容操作,sql查询可以换成存储过程,也存在缺点,1)存储过程要写提很细,逻辑算法都写在存储过程,存储过程可能变得很复杂。2)不支持表格内插入多条数据。
第三种:通过Sql查询数据,使用Razor模板引擎生成word。这种方案解决了存储过程复杂问题,但Razor模板内使用这种格式,所以写模板时很麻烦。
第四种:通过Sql查询数据,存入字典,再通过第三方组件替换word的域。这种方案与第二种方案类似,对我个人来说,我不喜欢修改域。
但是,我想要一个简单、容易控制、表格内能插入多条数据、可商用的方案。
简单:类似第二种方案,数据存入字典,循环替换word的文字,存储过程可以写得简单。
容易控制:模板不能使用这种格式,最好能用office直接控制表格文字大小、颜色。
表格内能插入多条数据:我写的组件内必须有索引。
可商用:拒绝商用组件。
经过几天琢磨,我找到可行的方案:存储过程+模板+算法可控
依赖组件
DocumentFormat.OpenXml,微软官方开源组件,支持docx文件,MIT协议。
ToolGood.Algorithm,本人的Excel计算引擎组件,MIT协议,可简化存储过程。
核心代码
ReplaceTemplate 替换Word文字
ReplaceTable 替换Word表格并支持插入
ReplaceTemplate 替换Word文字
public class WordTemplate : AlgorithmEngine
{
private readonly static Regex _tempEngine = new Regex("^###([^::]*)[::](.*)$");// 定义临时变量
private readonly static Regex _tempMatch = new Regex("(#[^#]+#)");//
private readonly static Regex _simplifyMatch = new Regex(@"(\{[^\{\}]*\})");//简化文本 只读取字段
private void ReplaceTemplate(Body body)
{
var tempMatches = new List<string>();
List deleteParagraph = new List();
foreach (var paragraph in body.Descendants()) {
var text = paragraph.InnerText.Trim();
var m = _tempEngine.Match(text);
if (m.Success) {
var name = m.Groups[1].Value.Trim();
var engine = m.Groups[2].Value.Trim();
var value = this.TryEvaluate(engine, "");
this.AddParameter(name, value);
deleteParagraph.Add(paragraph);
continue;
}
var m2 = _tempMatch.Match(text);
if (m2.Success) {
tempMatches.Add(m2.Groups[1].Value);
continue;
}
var m3 = _simplifyMatch.Match(text);
if (m3.Success) {
tempMatches.Add(m3.Groups[1].Value);
continue;
}
}
foreach (var paragraph in deleteParagraph) {
paragraph.Remove();
}
Regex nameReg = new Regex(string.Join("|", listNames));
foreach (var m in tempMatches) {
string value;
if (m.StartsWith("#")) {
var eval = m.Trim('#');
……
value = this.TryEvaluate(eval, "");
} else {
value = this.TryEvaluate(m.Replace("{", "[").Replace("}", "]"), "");
}
foreach (var paragraph in body.Descendants()) {
ReplaceText(paragraph, m, value);
}
}
}
//代码来源 https://stackoverflow.com/questions/19094388/openxml-replace-text-in-all-document
private void ReplaceText(Paragraph paragraph, string find, string replaceWith){….}
}
ReplaceTable 替换Word表格并支持插入
private readonly static Regex _rowMatch = new Regex(@"({{(.*?)}})");//
private int _idx;
private List<string> listNames = new List<string>();
private void ReplaceTable(Body body)
{
foreach (Table table in body.Descendants()) {
foreach (TableRow row in table.Descendants()) {
bool isRowData = false;
foreach (var paragraph in row.Descendants()) {
var text = paragraph.InnerText.Trim();
if (_rowMatch.IsMatch(text)) {
isRowData = true;
break;
}
}
if (isRowData) {
// 防止 list[i].Id 写成 [list][[i]].Id 这种繁杂的方式
Regex nameReg = new Regex(string.Join("|", listNames));
Dictionary<string, string> tempMatches = new Dictionary<string, string>();
foreach (Paragraph ph in row.Descendants()) {
var m2 = _rowMatch.Match(ph.InnerText.Trim());
if (m2.Success) {
var txt = m2.Groups[1].Value;
var eval = txt.Substring(2, txt.Length - 4).Trim();
eval = nameReg.Replace(eval, new MatchEvaluator((k) => {
return "[" + k.Value + "]";
}));
tempMatches[txt] = eval;
}
}
TableRow tpl = row.CloneNode(true) as TableRow;
TableRow lastRow = row;
TableRow opRow = row;
var startIndex = UseExcelIndex ? 1 : 0;
_idx = startIndex;
while (true) {
if (_idx > startIndex) { opRow = tpl.CloneNode(true) as TableRow; }
bool isMatch = true;
foreach (var m in tempMatches) {
string value = this.TryEvaluate(m.Value, null);
if (value == null) {
isMatch = false;
break;
}
foreach (var ph in opRow.Descendants()) {
ReplaceText(ph, m.Key, value);
}
}
if (isMatch==false) {
//当数据为空时,清空数据
if (_idx == startIndex) {
foreach (var ph in opRow.Descendants()) {
ph.RemoveAllChildren();
}
}
break;
}
if (_idx > startIndex) { table.InsertAfter(opRow, lastRow); }
lastRow = opRow;
_idx++;
}
}
}
}
}
案例上手
后台代码
// 获取数据
var helper = SqlHelperFactory.OpenSqliteFile("test.db");
...
var dt = helper.ExecuteDataTable("select * from Introduction");
var tableTests = helper.Select("select * from TableTest");
ToolGood.OutputWord.WordTemplate openXmlTemplate = new ToolGood.OutputWord.WordTemplate();
// 加载数据
openXmlTemplate.SetData(dt);
openXmlTemplate.SetListData("list", JsonConvert.SerializeObject(tableTests));
// 生成模板 一
openXmlTemplate.BuildTemplate("test.docx", "openxml_2.docx");
// 生成模板 二
var bs = openXmlTemplate.BuildTemplate("test.docx");
File.WriteAllBytes("openxml_1.docx", bs);
Word模板
Word生成后
后记
WordTemplate 类,主要实现了三个功能:
1、自定义替换word中的文字标签,当标签不存在,则设置为空字符串;
2、可以有word中定义公式,替换所对应的值;
3、在表格插入多行数据,当数据为0时清空单元格。
通过上面三个功能,WordTemplate 类将代码中的word生成方法分离出来。
系统后台需要配置 存储过程与word模板信息,就可以将word生成与系统更新完成分离开了。
系统后台可以配置公式,则公式修改不需要更新word模板。
注:一般业务人员是看得懂四则运算的,部分财务人员更是了解Excel公式,可以减少开发协助时间。
完整代码:https://github.com/toolgood/ToolGood.OutputWord
该组件已上传到Nuget:Install-Package ToolGood.OutputWord
Excel公式参考:https://github.com/toolgood/ToolGood.Algorithm
浏览
90评论测试新人,如何快速上手一个陌生的系统!大家好,我是狂师!作为刚入行不久的测试新人,面对一个陌生的系统时,可能会感到有些手足无措。面对一个全新的系统系统,如何快速上手并展开有效的测试工作是一个重要的挑战。本文将探讨测试新人如何通过一系列步骤和策略,快速熟悉并掌握新系统的测试要点,从而提高测试效率和质量。本文旨在为测试新手提供一份指导,帮助测试开发技术0多人同时导出 Excel 干崩服务器!新来的阿里大佬给出的解决方案太优雅了!点击关注公众号,Java 干货及时推送↓推荐阅读:面试辅导,我们出大成果了!来源:juejin.cn/post/7259249904777838629前言 业务诉求:考虑到数据库数据日渐增多,导出会有全量数据的导出,多人同时导出可以会对服务性能造成影响,导出涉及到mysql查询的io操作,Java技术栈1好未来测开一面,挺简单!(0428面试原题解析)大家好,我是二哥呀。今天继续给大家分享春招面试题《好未来测开一面原题》,附详细答案,我会用通俗易懂+手绘图的方式,让天下所有的面渣都能逆袭 😁二哥的 Java 面试指南内容较长,建议正在冲刺 24 届春招和 25 届暑期实习、秋招的同学先收藏起来,面试的时候大概率会碰到,1、二哥的 Linux 速查沉默王二01000Mbps换算成MB/s是多少?除以8?想简单了!原文链接:https://post.smzdm.com/p/azoqenzp/在网络传输的时候,往往会用到Mbps这个单位,GbE or 1 GigE 的网卡现在很流行,这个东西被大家叫做“千兆网卡”。同时,大家特别习惯用GB或者MB来描述一个磁盘的大小。这个叫做Gigabyte或者Megabyte测试开发技术0Java与lua互相调用简单教程来源:网络👉 欢迎加入小哈的星球 ,你将获得: 专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡 / 赠书福利全栈前后端分离博客项目 2.0 版本完结啦, 演示链接:http://116.62.199.48/ ,新项目小哈学Java0美团社招一面,比预想的简单。面试题大全:www.javacn.site面试这件事就很玄学,有时候你觉得他可能很难,但面完之后竟然出奇的顺利,问的问题你都会;有些你觉得这次面试应该很简单,但去了之后就被问懵了,所以面试这件事有很多一部分运气的成分。所以说,在没有 Offer 之前就是多准备、楞怂面,主打一个大力出奇迹。这不,逛牛Java中文社群0.NET 开源工具库,集成超过1000个扩展方法前言推荐一个.NET 开源项目,集成了超过1000个扩展方法。项目简介Z.ExtensionMethods是由zzzprojects公司开发并维护的一款开源库,为.NET开发人员提供一系列实用的扩展方法,可以减少重复劳动、提高开发效率,支持.NET Framework 和 .NET Core。该项目dotNET全栈开发10浅谈几款XML文档解析工具以及优缺点一、简介XML,一种可扩展标记语言,通常被开发人员用来传输和存储数据,定义也比较简单,通常如下方式开头,用来表述文档的一些信息。<?xml version="1.0" encoding="UTF-8"?>例如下面这个简单的文档。<?xml versioStephen1