[实用]【更新中】Java Apache POI 打印Word文档工具(含文本替换,动态表格功能)
- 基于Apache POI对Word进行操作
- 一、基于Apache POI封装的word文档工具V1.0介绍
- 二、Apache POI 知识
- ==apache poi官方文档:http://poi.apache.org/==
- 1. jar包(maven的,这个不多做解释了)
- 2. poi的类
- 3.常用的方法:
- 三、工具使用教程(不需要了解基础知识,直接快速使用)
- 1. 占位符的约定规则
- 2. word模板编辑
- 3. Java准备数据和导出word
- 四、GIT-HUB 地址
基于Apache POI对Word进行操作
你好!这是由一个刚毕业的学生,由于项目所需,需要通过Java后台的方式打印Word文档,因此在对大量能操作word的Java API中,选择了Apache POI。以下将简单分享一下这个在学习和开发这个基于POI的word文档打印工具时,一些心得:
- Apache POI在操作word上非常费劲,在选型的过程中还遇到过很多,如Freemarker,freemarker本人没有研究,但是大概知道是基于word保存为xml后,然后用占位符替换的方式,对xml中整段整段的内容进行文本替换,最终输出word文档,就能得到word文档。
- Freemarker的缺点(只是看别人总结的,自己没有求证) 1.freemark在进行文本替换的时候,很难保持原有的样式 2.在好不容易编辑好word模板后,转成xml的时候,还需要打开xml对里面的内容进行核对,听说会由于word文档一些字符串处理不好,倒是xml中 标签的缺失or错误,需要手动处理。如果word文档少还好,但是如果文档内容多,那就很麻烦(up主的项目所需打印的word文档就很多内容)
- Apache POI能很好的保持原来的样式,在理解底层接口原理后,还是挺好操作的,但是对于使用者来说,你们就不需要理解底层原理,因为我已经高度封装好了。
接下来,我将会对Apache POI进行讲解。以及我这套工具的一些底层原理,目的是为了和各位大牛交流,以及有人有定制需求的话,可以基于我这个工具进行改写,来适应不同的项目。如果你不想学原理,则只需要跳过本段内容,到最后一小节,我会用最黑盒的方式,来快速教大家上手使用我这套工具。
(由于本人技术有限,而且公文写作能力一般,因此有口误的地方请大家指出,并且欢迎大家提出更好的解决建议) (本工具现在是V1.0版本,代码方面也还没进行过多优化,性能暂时还OK,但还有很大优化空间 )
一、基于Apache POI封装的word文档工具V1.0介绍
已实现的功能:
- 文本替换
- 静态表格的文本替换
- 动态表格(行的变化)
- 动态表格(整个表格动态增减)
- 动态表格(整个表格动态增减,与上面不同的是,这个表格会附带表格标题以及跟随文本)
- 图片插入
后期可能扩展的方向
- 富文本
本工具与网上其他POI打印工具类对比 特点:
- 文本替换可以灵活的在word文档的任意位置,并且不会受到左右其他文字的影响(网上绝大部分,只是简单封装POI,实际上他们的文本替换需要占据一整行,这是极度不灵活的)
- 文本替换功能,在编辑模板的时候,可以设置它的样式。文本替换的时候,会根据你给定的样式替换文本。
- 表格内支持样式自定义,很多百度其他封装工具,都不支持样式自定义
- 动态表格比较灵活,支持一整块的扩展。
- 插入图片支持自定义大小
简单例子 (1)word模板
(2)通过apache poi打印后
二、Apache POI 知识
apache poi官方文档:http://poi.apache.org/
1. jar包(maven的,这个不多做解释了)
代码语言:javascript复制<properties>
<java.poi.version>4.0.0</java.poi.version>
</properties>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${java.poi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>${java.poi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>${java.poi.version}</version>
</dependency>
2. poi的类
- XWPFDocument:一个word文档对应一个document
- XWPFHeaderFooterPolicy:文档的页眉页脚(可以设置每一页的页眉页脚不同,也可以统一一个默认的页眉页脚作为全局,一般来说,后者用的比较多,因此我的工具里也是后者)
- XWPFTable:一个表格对应一个XWPFTable对象
- XWPFTableRow:一个表格的每一行对应一个XWPFTableRow
- XWPFTableCell:table中的每一个单元格对应一个XWPFTableCell(Cell特别特殊,他的里面相当于一个XWPFDocument,也就是说,一个单元格里面,可以进行插入文字,图片,表格等操作,类似于document)
- XWPFParagraph:一段文本对应一个XWPFParagraph(注意,是一段文本,后面会解释合为一段文本)
- XWPFRun:一处具有相同样式的文本(一个XWPFParagraph里面包含多个XWPFRun)
他们之间的关系结构如图所示(为了更方便大家的理解,用一个图表示)
因此:
- 一个Document包含多段Paragraph和多个Table。
- 一个Paragraph包含多个Run(一个Paragraph也可能只有一个Run,需要参考这一段文字中是否有样式不同的文字)
- 一个Run就一个text(一段文字中相同样式的一段文字)
- 一个Table包含多个TableRow(也就是一个表格有多少行,就有多少个TableRow。注意:一个Table没有行,这个table还是存在,只不过不会显示,如果要让一个table完全消息,必须调用document的removeBodyElement(int index))
- 一个TableRow包含多个TableCell(也就是一行中有很多个单元格)
- 一个TableCell,就相当于一个小的document。(一般不会对单元格进行特殊的操作,都是一段文字,因此tableCell里面的Paragraph起显示文字的作用)
- 附加:document里面维持一个bodyElement的数组,一个Paragraph或者一个Table对象就对应一个IBodyElement。由于document将段落和表格分开了两个List保存,因此我们无法知道,一个表格在两段文字中的位置或者一段文字在两个表格的位置。因此这一个bodyElement[]就起了能对word文档每个元素进行定位的功能。举了例子: 文档结构:段落1,表格1,段落2 。他的段落列表:段落1,段落2 。他的表格列表:表格1。我们无法通过两个数组,知道表格1在两段文字的哪里,因此借助document的bodyElement[]可以得到这样的关系,段落1,表格1,段落2 。在这里可以清晰的知道,表格1位于两段文字中间。而bodyElement也是非常重要的,能让你定位文档任何一个位置,操作文档内容
3.常用的方法:
- 获取XWPFDocument的段落列表
- 获取XWPFDocument的表格列表
- 读取整一个段落的所有文字内容
String text = paragraphs.get(0).getText();
4.设置段落的样式
代码语言:javascript复制//每一个XWPFParagraph可以设置对齐方式,边框,加粗等等,自己看里面的方法即可
String text = paragraphs.get(0).setXXX();
5.获取段落的Run,并修改这段Run的文字
代码语言:javascript复制//获取段落的所有Run
List<XWPFRun> runs = paragraph.getRuns();
//删除某一个Run
paragraph.removeRun(下标);
//增加一个没有文字的Run
paragraph.createRun();
//修改文字(覆盖原文本,追加的话则不使用第二个参数)
runs.get(0).setText("第二个参数表示从哪个下标开始修改字符串", 0);
//一个Run里面又有很多样式可以选择,如加粗,斜体等等
runs.get(0).setXXX();
//如果需要将一整个段落都替换成一个新的文本
while (paragraph.getRuns().isEmpty()){
paragraph.removeRun(0);
}
paragraph.createRun().setText("新文本");
//上面的代码会导致,原来的Run样式都没了,新的Run使用默认的样式。如果想保留原来Run的样式,可以删除的时候不要删掉全部Run,如
while (paragraph.getRuns().size() > 1){
paragraph.removeRun(1);
}
paragraph.getRuns().get(0).setText("新文本", 0);
由于要工作,暂没时间写完(后续更新)
三、工具使用教程(不需要了解基础知识,直接快速使用)
1. 占位符的约定规则
- 段落文本替换:@${t_*}@
- 静态表格(文本替换): ${at_static_*}
- 静态文档里面需要文本替换的地方,使用@${t_*}@
- 动态表格(行动态): ${at_row_*}
- 动态表格(整个表格增减): ${at_max01_*}
- 动态表格(整个表格增减,附带标题和跟随文本): ${at_max02_*}
其中:
- 不同表格类型的命名定义,需要放在每个表格的第一行第一列,任何表格除非不需要替换内容,否则都需要在原表格的上方增加一行,并在第一行第一列设置表格名(打印时,第一行会被去掉)
- 普通文本:@${t_*}@ 是替换文本的内容,这几个字符都必须使用相同的样式,并且他的样式决定了打印后文本替换的样式。两边的@字符需要设置独立的样式,并且必须独占一个XWPFRun(也就是@与的相邻的字符,样式不一样,我的做法是给@加粗并且变为指数)
- 静态表格(文本替换): ${at_static_*} 。说明表格的行数列数固定,只是需要填充不同的文字内容。
- 动态表格(行动态): ${at_row_*} ,表格的列是固定的,行数不固定。根据给定的List数组决定有多少行。
- 动态表格(整个表格增减): ${at_max01_*} 。表格的行,列是固定的。
- 动态表格(整个表格增减,但会携带标题和随后文本): ${at_max02_*} 。表格行列固定,但是不同的是,表格上方和下方会跟随一段文字
- 在了解Apache POI后,是可以自己自定义各种各样的规则,上面的规则仅是针对我遇到的项目所需,大部分情况下,是已经够用了。可能会有人需要,动态增减整个表格,并且每个表格里面的行不固定,这些都是可以定制的。
2. word模板编辑
建议使用WPS编辑word模板,因为目前Apache Poi对office不太友好,在我约束的规则下,我发现一个 占位符无法对应一个XWPFRun,在处理上非常不方便
( * 表示通配符,可以是任意字符) (1) 文本替换,使用@${t_*}@的方式(其中两边的@,需要独占一种样式)
PS : 两边的@是必不可少的,并且需要使用一种与周围字符样式不同的样式。我的做法通常是,加粗 变为指数。在进行打印的过程中, ${xxx}的内容会被你指定的文本替换掉,两边的@也会被删掉。
(2) 静态表格(文本替换) 表格上方多增加一行,在第一行第一列中指定静态表格 ${at_static_*} 表格内需要进行文本替换的地方,与普通文本替换的规则一样
(3) 动态表格(行动态)
- 表格上方增加一行,指定动态表格(行动态) ${at_row_*}-
- 表格一定要有3行,第一行指定动态表格,第二行是表格头的标题,第三行则是允许你设置每一个单元格内容的样式,在后续动态生成的每一行,都与这一行对应单元格的样式一致
(4) 动态表格(整个表格动态)
- 表格的行列固定,表格最上方新增一行指定动态表格规则 ${at_max01_*}
- 目前只允许整个表格行列固定的形式动态增减表格,若有定制需求,可以在简单研究POI原理后,对工具代码进行定制。
(5) 动态表格(携带标题和跟随文本)
- 表格的行列固定,表格上方新增一行指定规则 ${at_max02_*}
- 请注意看,最外层有一层虚线,它是一个 1行1列的Table,边框使用虚线,在打印时,虚线是不会被显示的(实际上这个不是虚线,是边框设置为none后的效果,它和真正的虚线边框是不同的)
- 之所以要设计用一个一行一列的单元格包住整个 动态表格。是因为,POI的原理是 段落和表格 分开处理的,为了让整个表格更加方便的复制,因此用了一个 单元格包住整个内容进行动态增减。
- PS:标题文字紧挨着表格紧挨着跟随文本 。表格样式,单元格样式以及文本样式都可以自定义。如果不需要标题或跟随文本,在Java可以设空串。(如有定制需求,可以询问up或者自行研究源代码)
3. Java准备数据和导出word
(1)封装好的工具简单介绍
- PoiWordUtil 封装好的打印word工具,里面只有一个公共方法。
- PoiWordKeyMatchRule 这里设置了4种输出规则即对应上方的文本替换,静态表格,动态表格等。使用的是通配符匹配算法对 ${xxx} 进行规则的匹配。
- IPoiWordTable接口:所有Table表格的接口,里面简单的定义了 行,列,以及每个单元格内容的二维数组。
- PoiWordAutoTable实现类:这个对应动态表格(整个表格动态) at_max01_*
- PWATwithHeaderBottom实现类:这个对应动态表格(携带标题和跟随文本) at_max02_*
(2) Java对应word模板DEMO的示例
代码语言:javascript复制 //word模板的路径
String inputUrl = "F:\poidemo\TESTPOI.docx";
//输出的位置(可以不存在文件)
String outputUrl = "F:\poidemo\OUTPUT.docx";
//文本替换Map
Map<String, String> textMap = new HashMap<>();
//动态表格Map
Map<String, List<IPoiWordTable>> tableMap = new HashMap<>();
//表格为空时,文本替换的Map(这个的用法是,如果某一个表格是不需要显示的,则把他规则的名字放进key里面,value如果设为null,则该表格不显示,如果是文本内容,则这个表格的位置,会被一段文字替换)
Map<String, String> noneTableMap = new HashMap<>();
//准备数据
//文本替换 and 静态表格文本替换 都是放在textMap里
textMap.put("t_author", "走在刀剑上的羊");
textMap.put("t_email", "448241091@qq.com");
textMap.put("t_year", "2018");
textMap.put("t_month", "11");
textMap.put("t_day", "30");
textMap.put("t_poi_cool", "[我不会影响左右两边文字]";
//动态表格都放入tableMap中
//动态表格(行)
PoiWordAutoTable rowTable = new PoiWordAutoTable(2, 3); //指定2行3列的动态行table
rowTable.setCell(0, 0, "row1col1");
rowTable.setCell(0, 1, "row1col2");
rowTable.setCell(0, 2, "row1col3");
rowTable.setCell(1, 0, "row2col1");
rowTable.setCell(1, 1, "row2col2");
rowTable.setCell(1, 2, "row2col3");
tableMap.put("at_row_autoRow", Arrays.asList(rowTable));
//如果不需要显示这个表格。表格会隐藏,并在相应位置出现一段文字提示
//noneTableMap.put("at_row_autoRow", "暂无数据");
//动态表格01,使用PoiWordAutoTable,行列根据原表格固定
PoiWordAutoTable data1 = new PoiWordAutoTable(2,2);
data1.setCell(0, 0, "企业名称");
data1.setCell(0, 1, "xxx");
data1.setCell(1, 0, "注册号");
data1.setCell(1, 1, "XXX123");
PoiWordAutoTable data2 = new PoiWordAutoTable(2,2);
data2.setCell(0, 0, "企业名称");
data2.setCell(0, 1, "xxx");
data2.setCell(1, 0, "注册号");
data2.setCell(1, 1, "---x2---");
tableMap.put("at_max01_auto", Arrays.asList(data1, data2));
//动态表格02,使用PWATwithHeaderBottom
PWATwithHeaderBottom pwat1 = new PWATwithHeaderBottom(3,2);
//如果标题 或 跟随文本不需要显示内容,则用"" 或 null代替
pwat1.setTitle("1.实际控制人:xxx(身份证号:441900XXXXXXX)查询日期:1995年11月23日");
//pwat1.setTitle(null);
pwat1.setBottom("底部跟随文本");
pwat1.setCell(0, 1, "信用卡");
pwat1.setCell(1, 0, "账户数");
pwat1.setCell(1, 1, "2个");
pwat1.setCell(2, 0, "未结清/未注销账户数");
pwat1.setCell(2, 1, "2个");
PWATwithHeaderBottom pwat2 = new PWATwithHeaderBottom(3,2);
pwat2.setTitle("2.实际控制人:xxx(身份证号:xxx)查询日期:2018年11月22日");
pwat2.setBottom("底部跟随文本");
pwat2.setCell(0, 1, "信用卡");
pwat2.setCell(1, 0, "账户数");
pwat2.setCell(1, 1, "255个");
pwat2.setCell(2, 0, "未结清/未注销账户数");
pwat2.setCell(2, 1, "255个");
tableMap.put("at_max02_auto", Arrays.asList(pwat1, pwat2));
//最后使用工具
PoiWordUtil.changWord(inputUrl, outputUrl, textMap, tableMap, noneTableMap);
四、GIT-HUB 地址
再次强调:建议使用WPS编辑word模板,因为我发现如果用office编辑模板,一个占位符无法对应一个XWPFRun,如果各位发现office也能正常编辑 占位符,请留言
https://github.com/YellowWinterSun/poiWordUtil
最近更新:
- 2018年12月26日:更新了 图片替换功能