使用poi操作word文档实现套打功能
本文目的是为了分享一个实现套打功能,但是不同于简单的word的文本替换而是采用poi对word的文本框就行操作实现的功能:
- poi中各种jar的说明
- 套打的实现思路
- poi操作word文本框中的数据
- *代码实现
POI各个jar的说明以及本文使用jar包
这里引用一个表格,只要看一下这个表格就知道自己需要哪个jar包了,本文中需要引入的是如下几个jar包(maven项目导入方式)。
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>1.1</version>
</dependency>
<!-- word poi-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.9</version>
</dependency>
项目 | 价格 |
---|---|
Computer | $1600 |
Phone | $12 |
Pipe | $1 |
可以使用冒号来定义对齐方式:
Component | Application type | Maven artifactId | Notes |
---|---|---|---|
POIFS | OLE2 Filesystem | poi | Required to work with OLE2 / POIFS based files |
HPSF | OLE2 | Property Sets | poi |
HSSF | Excel XLS | poi | For HSSF only, if common SS is needed see below |
HSLF | PowerPoint | PPT | poi-scratchpad |
HWPF | Word DOC | poi-scratchpad | |
HDGF | Visio VSD | poi-scratchpad | |
HPBF | Publisher PUB | poi-scratchpad | |
HSMF | Outlook MSG | poi-scratchpad | |
OpenXML4J | OOXML | poi-ooxml | plus one of |
poi-ooxml-schemas | ooxml-schemas | Only one schemas jar is needed, see below for differences | |
XSSF | Excel XLSX | poi-ooxml | |
XSLF | PowerPoint PPTX | poi-ooxml | |
XWPF | Word DOCX | poi-ooxml | |
Common SS | Excel XLS and XLSX | poi-ooxml | WorkbookFactory and friends all require poi-ooxml, not just core poi |
套打实现思路
既然是套打,那么打印的文本一定有一个固定的格式,如果通过前端的html的css去控制,那么有个问题就是可能出现预留长度不够那么打印出来的排版就会出现问题,如果使用word文档的空格和tab去实现格式,然后用打印的文本去替换模板文件中的内容也同样会出现css一样的问题,那现在有一个方法就是将需要打印的内容放到word文档的文本框中,不管预留的长度是否足够,都不会出现整个布局乱套的问题,因为word文档中的文本框的布局是固定的。有可能有人会问,这么简单不就直接使用replaceText就可以替换了么?那就很可能是因为你小看了word的文本框。
这篇博客讲解了poi操作word,我认为讲的已经很详细了(但是没有对文本框的操作),再加上本文,那么你就掌握了poi对word操作的大部分技能。
POI操作word文本框
word文档其实也是一种特殊的xml文件,有其他的方法操作word文档的时候是先要将word转换成xml文档,然后在对xml文档进行操作。同样的,poi操作word文档内部实现其实也是讲word文档转换成了xml文档(只不过该步骤不需要我们手动去完成,poi帮助我们自动完成了)。既然要解析xml,所以本文中的代码就需要用到poi的解析xml的jar包了(ooxml-schemas)。既然poi是对xml文件操作,那文本框也一定是解析成了xml的一部分,我们只要找到文本框对应的xml标签,然后找到文本在使用替换文本的方式,是不是就可以实现文本框的套打功能了。那如何才能找到文本框的标签呢,那就是游标,poi操作word文档的游标便可实现。
代码如下
File file = new File(filePath + File.separator + fileName);
InputStream is = new FileInputStream(file);
XWPFDocument doc = new XWPFDocument(is);
List<XWPFParagraph> paragraphList = doc.getParagraphs();
String startTime = home.getIntBd();
String endTime = home.getIntDl();
String apprTime = home.getApprDt();
//开始日期
Calendar calendar = Calendar.getInstance();
Date bdDt = DateUtil.string2Date(startTime, DateUtil.FORMAT0);
calendar.setTime(bdDt);
String y1 = String.valueOf(calendar.get(Calendar.YEAR));
String m1 = String.valueOf(calendar.get(Calendar.MONTH));
String d1 = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH));
//结束日期
Date dlDt = DateUtil.string2Date(endTime, DateUtil.FORMAT0);
calendar.setTime(dlDt);
String y2 = String.valueOf(calendar.get(Calendar.YEAR));
String m2 = String.valueOf(calendar.get(Calendar.MONTH));
String d2 = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH));
//提交日期
Date apprDt = DateUtil.string2Date(apprTime, DateUtil.FORMAT0);
calendar.setTime(apprDt);
String y3 = String.valueOf(calendar.get(Calendar.YEAR));
String m3 = String.valueOf(calendar.get(Calendar.MONTH));
String d3 = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH));
String sb = “测试数据”;
Field[] fields = home.getClass().getDeclaredFields();
for (XWPFParagraph paragraph : paragraphList) {
CTP ctp = paragraph.getCTP();
List<CTR> object = ctp.getRList();
for (int i = 0; i < object.size(); i++) {
XmlObject xo = object.get(i);
//在此处数据xo的字符串文本(xml文本),借助xml格式化工具格式化xml文本,然后通过标签移动游标找到需要替换的值
XmlCursor cursor = xo.newCursor();
if (cursor.isStart() || cursor.isAttr() || cursor.isProcinst()) {
cursor.toChild(1);
cursor.toChild(0);
cursor.toChild(0);
cursor.toChild(0);
cursor.toChild(8);
cursor.toChild(0);
cursor.toChild(0);
cursor.toChild(2);
cursor.toChild(0);
cursor.toChild(0);
cursor.toChild(1);
cursor.toChild(1);
String textValue = cursor.getTextValue();
switch (textValue) {
case "y1":
cursor.setTextValue(y1);
break;
case "m1":
cursor.setTextValue(m1);
break;
case "d1":
cursor.setTextValue(d1);
break;
case "y2":
cursor.setTextValue(y2);
break;
case "m2":
cursor.setTextValue(m2);
break;
case "d2":
cursor.setTextValue(d2);
break;
case "y3":
cursor.setTextValue(y3);
break;
case "m3":
cursor.setTextValue(m3);
break;
case "d3":
cursor.setTextValue(d3);
break;
case "abb":
cursor.setTextValue(abb);
break;
case "nb":
cursor.setTextValue(nb);
break;
case "no":
cursor.setTextValue(no);
break;
}
for (Field field : fields) {
field.setAccessible(true);
String name = field.getName();
Object ov = field.get(home);
String value = "";
if (null != ov) {
value = ov.toString();
}
if (null != textValue && textValue.equalsIgnoreCase(name)) {
cursor.setTextValue(value);
break;
}
}
}
}
}
response.setContentType("multipart/form-data");
response.setHeader("Content-Disposition", "attachment;fileName=" + home.getWrpNm() + "-" + fileName);
OutputStream os = response.getOutputStream();
doc.write(os);
is.close();
os.close();
<w:r>
<w:rPr>
<w:noProof/>
</w:rPr>
<mc:AlternateContent>
<mc:Choice Requires="wps">
<w:drawing>
<wp:anchor distT="0" distB="0" distL="114300" distR="114300" simplePos="0" relativeHeight="251680004" behindDoc="0" locked="0" layoutInCell="1" allowOverlap="1" wp14:anchorId="79BA2A69" wp14:editId="75F9FABF">
<wp:simplePos x="0" y="0"/>
<wp:positionH relativeFrom="column">
<wp:posOffset>4561840</wp:posOffset>
</wp:positionH>
<wp:positionV relativeFrom="page">
<wp:posOffset>2543175</wp:posOffset>
</wp:positionV>
<wp:extent cx="752475" cy="396240"/>
<wp:effectExtent l="0" t="0" r="0" b="3810"/>
<wp:wrapNone/>
<wp:docPr id="13" name="abb"/>
<wp:cNvGraphicFramePr>
<a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"></a:graphicFrameLocks>
</wp:cNvGraphicFramePr>
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<a:graphicData uri="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
<wps:wsp>
<wps:cNvSpPr>
<a:spLocks noChangeArrowheads="1"/>
</wps:cNvSpPr>
<wps:spPr bwMode="auto">
<a:xfrm>
<a:off x="0" y="0"/>
<a:ext cx="752475" cy="396240"/>
</a:xfrm>
<a:prstGeom prst="flowChartProcess">
<a:avLst/>
</a:prstGeom>
<a:noFill/>
<a:ln>
<a:noFill/>
</a:ln>
</wps:spPr>
<wps:txbx>
<w:txbxContent>
<w:p w:rsidR="003A7D59" w:rsidRPr="008A7CBF" w:rsidRDefault="00ED4204" w:rsidP="00987190">
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:hAnsi="宋体"/>
<w:szCs w:val="21"/>
</w:rPr>
</w:pPr>
<w:r w:rsidRPr="008A7CBF">
<w:rPr>
<w:rFonts w:ascii="宋体" w:hAnsi="宋体" w:hint="eastAsia"/>
<w:szCs w:val="21"/>
</w:rPr>
<w:t>abb</w:t>
</w:r>
</w:p>
</w:txbxContent>
重点:游标的移动规则,在打印出xml之后,需要将xml的文本copy出来借助xml格式化工具,格式化之后便可以分析游标的移动步骤,第一层是0,然后依次进入子标签。例如:有这么一段xml
<w:r w:rsidRPr="008A7CBF">
<w:rPr>
<w:rFonts w:ascii="宋体" w:hAnsi="宋体" w:hint="eastAsia"/>
<w:szCs w:val="21"/>
</w:rPr>
<w:t>abb</w:t>
</w:r>
现在我需要替换abb,那么游标移动的顺序则是
cursor.toChild(1);因为标签“w:rPr”的索引是0,“w:t”的索引则是1.在游标的移动分析中才是对文本框操作的重点,需要自己按照上面的规则测试分析。祝您好运。