热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

word导出表格ui组件_简单快速导出word文档

最近,我写公司项目word导出功能,应该只有2小时的工作量,却被硬生生的拉长2天,项目上线到业务正常运行也被拉长到2个星期。

最近,我写公司项目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(); 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 listNames = new List(); 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 tempMatches = new Dictionary(); 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模板:

3e74ca7db75b69737e9910c8ee521e4a.png

Word生成后:

675ea142744b01e60cccc5bbe0fefbb8.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




推荐阅读
  • 带添加按钮的GridView,item的删除事件
    先上图片效果;gridView无数据时显示添加按钮,有数据时,第一格显示添加按钮,后面显示数据:布局文件:addr_manage.xml<?xmlve ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • 推荐系统遇上深度学习(十七)详解推荐系统中的常用评测指标
    原创:石晓文小小挖掘机2018-06-18笔者是一个痴迷于挖掘数据中的价值的学习人,希望在平日的工作学习中,挖掘数据的价值, ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • MySQL多表数据库操作方法及子查询详解
    本文详细介绍了MySQL数据库的多表操作方法,包括增删改和单表查询,同时还解释了子查询的概念和用法。文章通过示例和步骤说明了如何进行数据的插入、删除和更新操作,以及如何执行单表查询和使用聚合函数进行统计。对于需要对MySQL数据库进行操作的读者来说,本文是一个非常实用的参考资料。 ... [详细]
  • Python基础知识:注释、输出和input交互
    本文介绍了Python基础知识,包括注释的使用、输出函数print的用法以及input函数的交互功能。其中涉及到字符串和整数的类型转换等内容。 ... [详细]
  • 本文介绍了一种求解最小权匹配问题的方法,使用了拆点和KM算法。通过将机器拆成多个点,表示加工的顺序,然后使用KM算法求解最小权匹配,得到最优解。文章给出了具体的代码实现,并提供了一篇题解作为参考。 ... [详细]
  • Python教学练习二Python1-12练习二一、判断季节用户输入月份,判断这个月是哪个季节?3,4,5月----春 ... [详细]
  • 在本教程中,我们将看到如何使用FLASK制作第一个用于机器学习模型的RESTAPI。我们将从创建机器学习模型开始。然后,我们将看到使用Flask创建AP ... [详细]
  • 引号快捷键_首选项和设置——自定义快捷键
    3.3自定义快捷键(CustomizingHotkeys)ChemDraw快捷键由一个XML文件定义,我们可以根据自己的需要, ... [详细]
author-avatar
梧桐树信息科技
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有