最近,我写公司项目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[].Value.Trim();
var engine = m.Groups[].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[].Value);
continue;
}
var m3 = _simplifyMatch.Match(text);
if (m3.Success) {
tempMatches.Add(m3.Groups[].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-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<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[].Value;
var eval = txt.Substring(, txt.Length - ).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 ? : ;
_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生成后:

完整代码:https://github.com/toolgood/ToolGood.OutputWord

该组件已上传到Nuget:Install-Package ToolGood.OutputWord

Excel公式参考:https://github.com/toolgood/ToolGood.Algorithm

JAVA版本:暂时没有,ToolGood.Algorithm已支持JAVA版本。

简单快速导出word文档的更多相关文章

  1. C# 导出word文档及批量导出word文档(1)

         这里用到了两个dll,一个是aspose.word.dll,另外一个是ICSharpCode.SharpZipLib.dll,ICSharpCode.SharpZipLib.dll是用于批量 ...

  2. 【Java】导出word文档之freemarker导出

    Java导出word文档有很多种方式,本例介绍freemarker导出,根据现有的word模板进行导出 一.简单导出(不含循环导出) 1.新建一个word文件.如下图: 2.使用word将文件另存为x ...

  3. Java使用freemarker导出word文档

    通过freemarker,以及JAVA,导出word文档. 共分为三步: 第一步:创建模板文件 第二步:通过JAVA创建返回值. 第三步:执行 分别介绍如下: 第一步: 首先创建word文档,按照想要 ...

  4. MindManager导出Word文档功能介绍

    Mindmanager思维导图软件作为一款能与Microsoft office软件无缝集成的思维导图软件,支持Word文档的快速导入与导出,并支持Word文档的目录生成.模板套用等,极大地方便了用户完 ...

  5. .NET通过调用Office组件导出Word文档

    .NET通过调用Office组件导出Word文档 最近做项目需要实现一个客户端下载word表格的功能,该功能是用户点击"下载表格",服务端将该用户的数据查询出来并生成数据到Word ...

  6. C# 导出word文档及批量导出word文档(4)

          接下来是批量导出word文档和批量打印word文件,批量导出word文档和批量打印word文件的思路差不多,只是批量打印不用打包压缩文件,而是把所有文件合成一个word,然后通过js来调用 ...

  7. C#导出Word文档开源组件DocX

    1.帮助文档,这东西找了很久,而且它版本很旧,还是英文,W8.1系统上打不开 http://download.csdn.net/detail/zuofangyouyuan/7673573 2.开源网址 ...

  8. freemarker导出word文档——WordXML格式解析

    前不久,公司一个项目需要实现导出文档的功能,之前是一个同事在做,做了3个星期,终于完成了,但是在项目上线之后却发现导出的文档有问题,此时,这个同事已经离职,我自然成为接班者,要把导出功能实现,但是我看 ...

  9. 自动生成并导出word文档

    今天很荣幸又破解一现实难题:自动生成并导出word文档 先看页面效果: word效果: 代码: 先搭建struts2项目 创建action,并在struts.xml完成注册 <?xml vers ...

随机推荐

  1. Linux中内存、CPU使用情况查看

    1.背景 在实际生产中我们为了保证系统能稳定运行,我们经常要查看当前的CPU和系统使用情况 建议使用top,简单丰富,快捷 2.使用free查看内存使用情况 3.使用 top查看内存.cpu内存占比 ...

  2. Git的忽略文件

    *.iml.gradle.idea/local.properties/.idea/workspace.xml/.idea/libraries.DS_Store/build/captures.exter ...

  3. 统计M

    链接:https://vjudge.net/problem/UVA-1586 题意:给出一分子化学式,包含C,N,O,H四种元素,求M 题解:这是字符串题.分为几种情况:第一种是一个原子:第二种是多原 ...

  4. JAVA基础1(语法)

    一.标识符和关键字 在程序中用于定义名称的都为标识符,如文件名称.类名称.方法名称或变量名称等. 在Java中标识符的定义格式由字母.数字._(下划线),$所组成,其中不能重复,不能以数字开头,不能是 ...

  5. LQB2013A05前缀判断

    上一道题,,,把if条件写错了,,,,找了半天的bug我都快哭了, 好了好了 看见这种填空题,先理解题意 然后把代码copy下来,把空格注释掉,然后运行到编译没有错. 再理一下它的思路 // // C ...

  6. express,node.js实现获取本地文件夹下面的全部图片文件

    http://www.luyixian.cn/javascript_show_169354.aspx 按照网上的教程试了多次,处理了各种结果后还有报错, 最后的报错是cant find module ...

  7. [C++]类的空指针调用成员函数后,会发生什么事?

    类的实例调用成员函数的原理 其实不管是通过对象实例或指针实例调用,其实底层调用的过程都是一样的,都是把当前对象的指针作为一个参数传递给被调用的成员函数.通过下面的相关实例代码进行检验: 实验的C++代 ...

  8. PHP array_uintersect_uassoc() 函数

    实例 比较两个数组的键名和键值(使用用户自定义函数进行比较),并返回交集: <?phpfunction myfunction_key($a,$b){if ($a===$b){return 0;} ...

  9. PHP exp() 函数

    实例 返回 'e' 的不同次方: <?phpecho(exp(0) . "<br>");echo(exp(1) . "<br>") ...

  10. Qt自定义控件之仪表盘3--雷达扫描图

    1.设计思想 雷达扫描图,在影视作品中见到较多,比如飞机雷达.舰艇雷达,有一个扫描线转圈代表雷达一周旋转或一个批次的收发,发现目标就在表盘上标记位置.和汽车仪表盘类似,汽车仪表盘有底盘背景图.同圆.刻 ...