广州 深圳 外贸网站建设,微信微网站开通,那里做网站,绿色推广盒子app最近#xff0c;我写公司项目word导出功能#xff0c;应该只有2小时的工作量#xff0c;却被硬生生的拉长2天#xff0c;项目上线到业务正常运行也被拉长到2个星期。为什么如此浪费时间呢#xff1f;1#xff09;公司的项目比较老#xff0c;采用硬编码模式#xff0c;… 最近我写公司项目word导出功能应该只有2小时的工作量却被硬生生的拉长2天项目上线到业务正常运行也被拉长到2个星期。 为什么如此浪费时间呢 1公司的项目比较老采用硬编码模式意味着word改一个字就要发布一次代码。发布检验就浪时间了。 2由于硬编码采用的是html这种格式手写代码比较废时而且编写表格时会遇到单元格字数变多被撑大表格变形的情况。表格长度需要人工计算。这类意想不到的问题。 3公司测试库数据不全测试库数据无法全面覆盖线上环境。这又拉长了检验时间。 4项目分支被正在开发的分支合并了一下子被拉长了4天。 这简单功能浪费太多时间了我在网上搜了一下word导出的方案 第一种硬编码就是公司的方案问题太多了不用考虑。 第二种通过Sql查询数据存入字典再通过第三方组件替换word的文字。这种方案简单容操作sql查询可以换成存储过程也存在缺点1存储过程要写提很细逻辑算法都写在存储过程存储过程可能变得很复杂。2不支持表格内插入多条数据。 第三种通过Sql查询数据使用Razor模板引擎生成word。这种方案解决了存储过程复杂问题但Razor模板内使用html这种格式所以写模板时很麻烦。 第四种通过Sql查询数据存入字典再通过第三方组件替换word的域。这种方案与第二种方案类似对我个人来说我不喜欢修改域。 但是我想要一个简单、容易控制、表格内能插入多条数据、可商用的方案。 简单类似第二种方案数据存入字典循环替换word的文字存储过程可以写得简单。 容易控制模板不能使用html这种格式最好能用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 Liststring();ListParagraph deleteParagraph new ListParagraph();foreach (var paragraph in body.DescendantsParagraph()) {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.DescendantsParagraph()) {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 Liststring listNames new Liststring();private void ReplaceTable(Body body){foreach (Table table in body.DescendantsTable()) {foreach (TableRow row in table.DescendantsTableRow()) {bool isRowData false;foreach (var paragraph in row.DescendantsParagraph()) {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));Dictionarystring, string tempMatches new Dictionarystring, string();foreach (Paragraph ph in row.DescendantsParagraph()) {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.DescendantsParagraph()) {ReplaceText(ph, m.Key, value);}}if (isMatchfalse) {//当数据为空时清空数据if (_idx startIndex) {foreach (var ph in opRow.DescendantsParagraph()) {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.SelectTableTest(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该组件已上传到NugetInstall-Package ToolGood.OutputWordExcel公式参考https://github.com/toolgood/ToolGood.Algorithm