一、概述
docxtemplater 插件是将文档模板结合数据生成真正文档的强大工具,对于仅数据发生变化而文档格式固定的文档生成需求,该工具非常好用。
其强大体现在以下三点:
- 生成的文档会继承模板的字体样式。模板是什么样,生成出来的文档就是什么样,拿来即用。因为其所作的不过是把数据插入模板所在位置
- 支持常用的模板语法。比如条件判断、数组遍历
- 支持自定义过滤器。过滤器会在数据插入模板前执行,便于我们对数据进行二次处理,比如复杂或者 需复用的计算逻辑和判断逻辑等,都可以单独创建一个过滤器以我们更熟悉的js语法处理,同时过滤器还支持链式调用
二、如何使用docxtemplater
以下是官方文档的demo,我将结合demo逐步分析docxtemplater所做的事情。
1、使用docxtemplater的两个前提是docx模板文件和json数据(这里强调必须是docx,也就是doc不行,具体原因第二步会解释)。
2、利用PizZip包对将模板文件转换为zip包。参考DOCX文档结构分析可知docx与zip是可以相互转换的,但doc则不行,既然无法转化为zip包,自然后续模板解析和文档生成也无法实现。
3、利用docxtemplater处理docx模板文件的zip包。其核心操作就是解压zip包并找到document.xml文件,然后找出其中所有的模板占位符,为后续数据插入做准备。
4、执行doc.render,顾名思义就是执行渲染操作,这里所做的就是用数据替换模板占位符。(该处理操作类似vue的template,react的jsx渲染数据所做的事情)。
5、以上操作都是对docx文件内部的xml文件进行的,不清楚可以参考前几个博客内容【基于docx-merger实现内容合并、基于docx实现文档生成】,这里不再累述。了解之后,对下面步骤做的事情就很清楚了。将第2、3步里对docx的zip包解压得到的文件夹再度压缩得到新的zip包,该zip包所包含的就是新鲜出炉的结合模板和数据的文档。
6、最后将生成的文档数据写入docx文件中。
三、详细介绍
1、数据源
结构:{ data: {} }
定义:data 中是键值对的数据(因为模板中会使用的字段都在 data 对象内,所以都是 data.xxx 格式)
2、模板写法
以下模板写法的功能需结合 angular-parser 词法解析器使用才能发挥功效,未使用 angular-parser 或者使用自定义的词法解析器会存在效果不一致的问题。
2.1、普通变量
{codeName}
默认的模板标识符是花括号(花括号的内容会被视为模板进行解析并从数据源取值),也可以自定义,规则如下:
- 基础语法支持基本的算术运算符(+、-、*、/)、关系运算符(>、<、>=、<=)、逻辑运算符(&&、||、!);
- 支持==(类型隐式转换),===(三个等号的类型匹配);
- 支持布尔值;
- 支持三元运算式;
- 支持数组的 length 等属性,但不支持 indexOf/includes 等方法;
示例:
注意:数据都在 data 下,因此字段代码都需要以 data. 开头;配置空值处理函数后,字段值为 undefined/null,或者字段代码没有匹配到数据,都会显示空。
2.2、判断条件
{#codeName}内容{/}
注意:满足条件才会显示内容,不满足条件则不会显示内容。
2.3、循环遍历
{#codeName}{$index}{col1}{col2}{col3}{/}
循环遍历与判断条件的模板写法相同。此外官方文档给出的示例是以 {#字符代码} 开头,以 {/字符代码} 结尾的,但经过实际测试, {/}也是行得通的,个人觉得更简单。
2.3.1、表格数据遍历
循环遍历不光能针对具体数组值,也可以用于表格增行。
示例:
注意:表格中每列的值不需要再加前缀,只需填入每列的字段代码;可以通过 $index 获取数组索引,默认从0开始;
2.3.2、字符串数组数据遍历
因为 {#字符代码}{/} 内部可以直接获取遍历项的属性,比如{a:1,b:true}可以直接取{a}、{b},而不用写作{obj.a},{obj.b}。而对于每一项都是基础类型值时,可用{.}指代。
示例:
2.4、图片组件
{%codeName}
示例:
3、词法解析器
推荐采用 angular-parser 处理词法解析,angular-parser 处理词法解析具有如下优势:
- 其支持的模板语法规则为花括号包裹内容,如:{user.name}。
- 增强判断条件和运算能力,支持如下:
- 支持自定义过滤器。
4、空值处理函数
为了处理当数据源中无值情况,会增加空值处理函数处理undefined和null,也可以自定义处理其他特殊值的显示问题,以下是默认空值处理函数(当值为undefined或null时显示空字符串,在生成的文档中会什么都不显示)。
5、过滤器
{xxx|filter1|filter2|...}
过滤器能接受参数(在过滤器名称后以 : 分隔),能链式调用(多个过滤器之间以 | 分隔)。
示例:
四、问题处理
1、docxtemplater 支持过滤器接受对象字符串
由于 docxtemplater 模板解析默认通过{}进行变量取值,因此当过滤器中存在对象字符串时,词法解析会识别到连续两个{{的情况,导致语法错误出现。
分析
docxtemplater在对docx文档进行解析并插入数据时,首先会进行词法解析(利用到了分割符和indexOf方法。fullText是docx文档内容字符串,delimiters是取值符。通过getAllIndexes方法分析,词法分析是从左往右依次处理,因此如果存在分割符相互包含的情况就导致词法解析出错)。
解决方法
通过修改 getAllIndexes 方法源码,令词法解析时能够忽略模板字符串内的分割符,使其能正确进行词法分析(通过在过滤器的对象参数前后用``进行包裹,标识其内部内容为参数,在词法解析时忽略其内部存在的所有分割符)。
但``符号在后续对变量解析的过程中会报编译错误(暂还未查明原因),因此需要在自定义的angularParser编译方法中将`符号转换为’(由于angularParser方法的调用发生在词法分析之后,因此此时将`转化为’没有问题)。
2、docxtemplater 支持图片渲染
docxtemplater官网提供 docxtemplater-image-module 插件实现图片处理,但高版本是收费的,我采用的是免费的低版本,因此存在一些bug得自己处理了(注意:该说法仅做参考,本文档只记录我当时遇到的问题,若官方开放了更高版本,该问题可能就不存在了)。
安装:yarn add docxtemplater-image-module
引入:import ImageModule from ‘docxtemplater-image-module’;
集成:
模板:{%data.image}
3、解析模板语言后渲染图片报错的问题
由于采用的是免费的低版本的 docxtemplater-image-module,发现其在渲染图片时存在bug,通过查看源码定位了问题并找到解决办法。
分析
由于如下 docxtemplater-image-module 源码中 options.scopeManager.getValue(part.value) 未传递 part 作为第二个参数,而 docxtemplater 中处理第二个参数,因而找不到 part 导致的报错。
正常的调用 scopeManager.getValue 的逻辑如下:
解决方法
修改 docxtemplater-image-module 源码。