简单快速导出word文档

最近,我写公司项目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 List<string>();List<Paragraph> deleteParagraph = new List<Paragraph>();foreach (var paragraph in body.Descendants<Paragraph>()) {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<Paragraph>()) {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<Table>()) {foreach (TableRow row in table.Descendants<TableRow>()) {bool isRowData = false;foreach (var paragraph in row.Descendants<Paragraph>()) {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<Paragraph>()) {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<Paragraph>()) {ReplaceText(ph, m.Key, value);}}if (isMatch==false) {//当数据为空时,清空数据if (_idx == startIndex) {foreach (var ph in opRow.Descendants<Paragraph>()) {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<TableTest>("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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/308217.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何让多端口网站用一个nginx进行反向代理实际场景分析

前段时间公司要整合服务器资源&#xff0c;刚好趁这次机会将这些乱七八糟的服务器做一次梳理和整合&#xff0c;断断续续一个月迁移完成大概优化掉了1/3的机器&#xff0c;完成之后遇到了一些问题&#xff0c;比如曾今零零散散部署在生产上一些可视化UI&#xff1a;apollo&…

【Azure Show】|第五期(下)当下最火热的Blazor与App Service, 嘉宾闫晓迪Alan Tsai...

欢迎来到Azure Show!Azure Show欢迎来到Azure Show第五期&#xff08;下&#xff09;&#xff0c;继上集分享彭爱华和阿法兔两位老师的跨界经验之后&#xff0c;本期我们【MVP面对面】栏目邀请到远在新西兰的微软最有价值专家闫晓迪和大家聊聊新西兰的技术生态&#xff0c;当然…

台式计算机听音乐,配置一台4000元的台式电脑,主要用于日常办公及上网查资料,听音乐,看电影及学习....

满意答案asshnjgs2014.04.02采纳率&#xff1a;44% 等级&#xff1a;9已帮助&#xff1a;861人CPU&#xff1a;Intel 赛扬D341 2.93G(散) CPU适用类型:台式CPU CPU内核:Prescott 主频(MHz):2930MHz 插槽类型:Socket 775 制作工艺(微米):0.09 微米 L2缓存(KB):256KB FSB(MHz)…

ASP.NET Core Blazor Webassembly 之 组件

关于组件现在前端几大轮子全面组件化。组件让我们可以对常用的功能进行封装&#xff0c;以便复用。组件这东西对于搞.NET的同学其实并不陌生&#xff0c;以前ASP.NET WebForm的用户控件其实也是一种组件。它封装html代码&#xff0c;封装业务逻辑&#xff0c;对外提供属性事件等…

122. 买卖股票的最佳时机 II008(贪心算法+思路)

一&#xff1a;题目 给定一个数组 prices &#xff0c;其中 prices[i] 是一支给定股票第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易&#xff08;多次买卖一支股票&#xff09;。 注意&#xff1a;你不能同时参与多笔交易&#xf…

广东省计算机应用考试题,广东省计算机等级考试一级试题

广东省计算机等级考试一级试题一、理论部分 (共20分&#xff0c;每空1分)将正确答案填写在考试文件夹中“第一题答案.XLS”的Sheet1相应的位置上1. 解释程序的功能是 ( )。A解释执行高级语言源程序 B将高级语言源程序翻译成目标程序C解释执行汇编语言源程序 D将汇编语言源程序翻…

麒麟系统兼容安卓生态 弥补生态短板

日前&#xff0c;麒麟软件发布了银河麒麟操作系统最新版本V10。据媒体报道&#xff0c;麒麟软件是国内唯一一个通过CMMI5级质量评估的操作系统企业&#xff0c;在XC市场份额占有率达到70%以上。麒麟V10系统可以兼容安卓生态。集成了自研的UKUI桌面环境&#xff0c;支持多壁纸、…

763. 划分字母区间009(贪心算法+思路+详解+图示)

一&#xff1a;题目&#xff1a; 字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段&#xff0c;同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。 示例&#xff1a; 输入&#xff1a;S "ababcbacadefegdehijhklij" 输出…

初识ABP vNext(4):vue用户登录菜单权限

点击上方蓝字"小黑在哪里"关注我吧登录菜单权限运行测试前言上一篇已经创建好了前后端项目&#xff0c;本篇开始编码部分。开始几乎所有的系统都绕不开登录功能&#xff0c;那么就从登录开始&#xff0c;完成用户登录以及用户菜单权限控制。登录首先用户输入账号密码…

满汉楼(德鲁伊连接池+DBUtils+DAO+Mysql)保姆级别分析+代码实现

一&#xff1a;需求 1.完成一个酒店后台管理系统&#xff0c;当然只是模拟&#xff0c;在控制台就行模拟 2.实现管理人员的登录和酒店成本账单的查看&#xff0c;以及正常的点餐&#xff0c;结账和查看账单等等功能 二&#xff1a;实现的功能展示 1.用户登录&#xff08;这里…

[Hei-Ocelot-Gateway ].Net Core Api网关Ocelot的开箱即用版本

&#xfeff;写在前面很多neter都有在用Ocelot做Api网关&#xff0c;但是Ocelot又不像kong或者其他网关一样&#xff0c;开箱即用。它需要你单独开一个web项目来部署&#xff0c;这样很多同学都在做重复的事了。这里[Hei.Ocelot.ApiGateway] 就把这件事给做了&#xff0c;以后有…

聊聊常见的服务(接口)认证授权

&#xfeff;写在前面头发掉得多了&#xff0c;总有机会接触/调到各种各样的接口&#xff0c;各种面向Api编程实际上已经嵌入到我们的习惯中&#xff0c;没办法现在服务端通信还得是http(s)&#xff0c;其他协议还未能成为通用的。大厂的开发平台api我先不敢说&#xff0c;各种…

二分查找(划分时左右元素个数不相等)解析+代码

一:问题描述 当我们在用二分法查找元素的时候&#xff0c;我们往往特希望遇到是奇数个元素个数的数组&#xff0c;因为划分完左右两边的个数相等&#xff0c;所以在以前刚学二分法的时候就有这个疑问&#xff0c;当时就是模模糊糊过去了&#xff0c;再遇到其实还是会有疑问。现…

网络计算机室电源线怎么布,网吧综合布线(电源和网络)经验谈

电源系统布线篇网吧目前所提供的服务&#xff0c;像网页浏览、网络游戏、在线电影、远程教育等最基本的服务都与网络有关&#xff0c;网络质量的好坏直接决定了网吧的生存能力。所以&#xff0c;如何规划一个优质的网络环境&#xff0c;是网吧经营者必须要考虑的一个要点&#…

69. Sqrt(x)010(二分法求解+详解注释)

一&#xff1a;题目 ‘给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。 由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。 注意&#xff1a;不允许使用任何内置指数函数和算符&#xff0c;例如 pow(x, 0.5) 或者 x ** 0…

计算机网络 哪个教材好,学习计算机网络哪本教材最好?

benxiuxian高分答主12-29TA获得超过8057个赞地理期末复习计划临近期末&#xff0c;为帮助学生理顺知识&#xff0c;培养学生灵活运用知识分析问题&#xff0c;解决问题的能力&#xff0c;形成完整的知识体系&#xff0c;特作复习计划如下&#xff1a;一、复习目的&#xff1a;1…

安装VSCode作为常用的文本编辑器

作为程序员&#xff0c;跟文本编辑器打交道那是天天都在做的事情&#xff0c;一个趁手的文本编辑器能大大地提供工作效率&#xff0c;减少996福报。笔者使用过各种文本编辑器&#xff0c;或是xx版&#xff0c;或是免费版&#xff0c;多多少少都有一些不便之处&#xff0c;如今 …

35. 搜索插入位置011(二分查找)

一&#xff1a;题目&#xff1a; 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n) 的算法。 示例 1: 输入: nums [1,3,5,6], …

利用 Github Actions 自动更新 docfx 文档

利用 Github Actions 自动更新 docfx 文档Introdocfx 是微软出品一个 .NET API 文档框架&#xff0c;有一个理念是代码即文档&#xff0c;会根据项目代码自动生成 API 文档&#xff0c;即使没有写任何注释也会生成 API 文档&#xff0c;也有一些默认的主题可以配置&#xff0c;…

34. 在排序数组中查找元素的第一个和最后一个位置012(二分查找+思路+详解+两种方法)Come Baby!!!!!!!! !

一&#xff1a;题目 给定一个按照升序排列的整数数组 nums&#xff0c;和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 进阶&#xff1a; 你可以设计并实现时间复杂度为 O(log n) 的算法解…