itext word模板替换_.NET 简单快速导出Word文档

(给DotNet加星标,提升.Net技能)

转自: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-documentprivate 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模板

c309e0ae323b7d3bdd723fb1d450b27c.png

Word生成后

efc7dcc02df3b6135afe4cb25abade68.png

后记

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

- EOF -

推荐阅读  点击标题可跳转ASP.NET 开源导入导出库Magicodes.IE导出ExcelASP.NET开源导入导出库Magicodes.IE导出Pdf.NET Core一行代码导入导出Excel生成Word

看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

732e31ed8e6986ec2fecc94b20b8d4d3.png

好文章,我在看❤️

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值