老项目使用tomcat7.0.92、spring3.2.6、poi3.13。
新增功能,word证书导出,包含文本框(文本框可以拖拽到任意位置,比文本、表格位置更自由)。
不管是poi 3.13或者最新版 5.2.2都没有很方便的针对文本框的输入或者替换方法。
尝试方案1:
升级poi到5.2.2,添加新的依赖poi-tl(Word模板引擎,基于Apache POI,提供更友好的API)。非常的简洁方便,新项目建议用这个。
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>5.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-excelant</artifactId>
<version>5.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.2</version>
</dependency>
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.2</version>
</dependency>
Map<String, Object> datas = new HashMap<String, Object>() {
{
put("name", "hfq");
put("department", "hfq.COM");
put("y", "2024");
put("m", "1");
put("d", "1");
put("img", Pictures.ofLocal("src/test/resources/lu.png").size(120, 120).create());
}
};
XWPFTemplate.compile(new File("src/test/resources/certificate/certificate (1).docx"))
.render(datas)
.writeToFile("out_example_certificate.docx");
无法运行,tomcat7.0.92、spring3.2.6 无法支持poi-tl 。
尝试方案2:
不升级,还是使用poi3.13,虽然poi没有提供直接操作文本框的相关API。但是获取到段落XWPFParagraph,再获取需要编辑的CTR(XmlObejct,等于直接操作段落文字中的run)。
文本框XmlObejct(run)结构
<xml-fragment xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData">
<w:rPr>
<w:lang/>
</w:rPr>
<mc:AlternateContent>
<mc:Choice Requires="wps">
<w:drawing>
<wp:anchor distT="0" distB="0" distL="114300" distR="114300" simplePos="0" relativeHeight="251659264" behindDoc="0" locked="0" layoutInCell="1" allowOverlap="1">
<wp:simplePos x="0" y="0"/>
<wp:positionH relativeFrom="column">
<wp:posOffset>-355600</wp:posOffset>
</wp:positionH>
<wp:positionV relativeFrom="paragraph">
<wp:posOffset>242570</wp:posOffset>
</wp:positionV>
<wp:extent cx="6210300" cy="944245"/>
<wp:effectExtent l="0" t="0" r="0" b="0"/>
<wp:wrapNone/>
<wp:docPr id="1" name="文本框 5"/>
<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 txBox="1"/>
<wps:spPr>
<a:xfrm>
<a:off x="0" y="0"/>
<a:ext cx="6210300" cy="944245"/>
</a:xfrm>
<a:prstGeom prst="rect">
<a:avLst/>
</a:prstGeom>
<a:noFill/>
<a:ln>
<a:noFill/>
</a:ln>
</wps:spPr>
<wps:txbx>
<w:txbxContent>
<w:p>
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:hint="eastAsia" w:eastAsia="隶书"/>
<w:szCs w:val="52"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
</w:rPr>
<w:fldChar w:fldCharType="begin"/>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
</w:rPr>
<w:instrText xml:space="preserve"> MERGEFIELD 单位名称 </w:instrText>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
</w:rPr>
<w:fldChar w:fldCharType="separate"/>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia" w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t>{</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia" w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t>{</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia" w:ascii="隶书" w:hAnsi="隶书" w:eastAsia="隶书" w:cs="隶书"/>
<w:b/>
<w:bCs/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t>distributorName</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia" w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t>}</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
</w:rPr>
<w:fldChar w:fldCharType="end"/>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia" w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t>}</w:t>
</w:r>
</w:p>
</w:txbxContent>
</wps:txbx>
<wps:bodyPr wrap="square" upright="1"/>
</wps:wsp>
</a:graphicData>
</a:graphic>
</wp:anchor>
</w:drawing>
</mc:Choice>
<mc:Fallback>
<w:pict>
<v:shape id="文本框 5" o:spid="_x0000_s1026" o:spt="202" type="#_x0000_t202" style="position:absolute;left:0pt;margin-left:-28pt;margin-top:19.1pt;height:74.35pt;width:489pt;z-index:251659264;mso-width-relative:page;mso-height-relative:page;" filled="f" stroked="f" coordsize="21600,21600" o:gfxdata="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
AAAGAAYAWQEAAE8FAAAAAA==
">
<v:fill on="f" focussize="0,0"/>
<v:stroke on="f"/>
<v:imagedata o:title=""/>
<o:lock v:ext="edit" aspectratio="f"/>
<v:textbox>
<w:txbxContent>
<w:p>
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:hint="eastAsia" w:eastAsia="隶书"/>
<w:szCs w:val="52"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
</w:rPr>
<w:fldChar w:fldCharType="begin"/>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
</w:rPr>
<w:instrText xml:space="preserve"> MERGEFIELD 单位名称 </w:instrText>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
</w:rPr>
<w:fldChar w:fldCharType="separate"/>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia" w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t>{</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia" w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t>{</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia" w:ascii="隶书" w:hAnsi="隶书" w:eastAsia="隶书" w:cs="隶书"/>
<w:b/>
<w:bCs/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t>distributorName</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia" w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t>}</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
</w:rPr>
<w:fldChar w:fldCharType="end"/>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia" w:ascii="隶书" w:hAnsi="宋体" w:eastAsia="隶书" w:cs="宋体"/>
<w:b/>
<w:bCs/>
<w:kern w:val="0"/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t>}</w:t>
</w:r>
</w:p>
</w:txbxContent>
</v:textbox>
</v:shape>
</w:pict>
</mc:Fallback>
</mc:AlternateContent>
</xml-fragment>
JumpToP 通用跳转到 <w:p>,在根据实际往下跳动游标到替换文本位置,写入文本。
public static void main(String[] args) throws Exception {
File templateFile = new File("C:\\Users\\hfq\\Desktop\\cert.docx");
String outputFileName = "短授权区域.docx";
String text = "授权区域、授权区域、授权区域、授权";
FileInputStream fis = new FileInputStream(templateFile);
XWPFDocument document = new XWPFDocument(fis);
for (XWPFParagraph itemParagraph :document.getParagraphs()) {
for (XWPFRun itemRun : itemParagraph.getRuns()) {
for (XWPFPicture itemEmbeddedPicture : itemRun.getEmbeddedPictures()) {
// 待替换的图片文件
File toReplaceImage = new File("C:\\Users\\hfq\\Desktop\\erweima2.jpg");
//只有一张图片,简单替换处理了
try (InputStream toReplaceImageInputStream = Files.newInputStream(toReplaceImage.toPath())) {
// 替换图片
itemEmbeddedPicture.getCTPicture().getBlipFill().getBlip().setEmbed(document.addPictureData(
toReplaceImageInputStream,
XWPFDocument.PICTURE_TYPE_JPEG));
}
// }
}
}
}
int i =1;
//遍历所有段落
for (XWPFParagraph paragraph : document.getParagraphs()) {
if(paragraph.getCTP().getRArray().length>0){
if(i == 1){ //这一行是图片跳过了
i++;
continue;
}
XmlObject xmlObject = paragraph.getCTP().getRArray(0);
System.out.println("====================================="+i);
//打印段落
System.out.println(xmlObject);
//获取游标
XmlCursor cursor = xmlObject.newCursor();
if(i==2 ){ // 2-6是需要替换的文本框
JumpToP(cursor);
cursor.toChild(4);//r
cursor.toChild(1);//t
cursor.setTextValue("有限公司有限公司有限公司有限公司有限公司有限公司有限公司");
}if(i==3){
JumpToP(cursor);
cursor.toChild(4);//r
cursor.toChild(1);//t
cursor.setTextValue(text);
// cursor.setTextValue("授权区域、授权区域、授权区域、授权区域、授权区域、授权区域、授权区域");
}if(i==4){
JumpToP(cursor);
cursor.toChild(4);//r
cursor.toChild(1);//t
cursor.setTextValue("签约产品线、签约产品线");
}else if (i==5) {
JumpToP(cursor);
cursor.toChild(4);
System.out.println(cursor.getName());//r
System.out.println(cursor.getTextValue());
cursor.toChild(1);
System.out.println(cursor.getName());//t
System.out.println(cursor.getTextValue());
cursor.setTextValue("ZF072024005");
}else if(i==6) {
JumpToP(cursor);
cursor.toChild(1);
System.out.println(cursor.getName());//r
System.out.println(cursor.getTextValue());
cursor.toChild(1);
System.out.println(cursor.getName());//t
System.out.println(cursor.getTextValue());
cursor.setTextValue("2024年1月1日至2025年3月1日");
}
i++;
}
}
// }
File output = new File(outputFileName);
FileOutputStream out = new FileOutputStream(output);
document.write(out);
out.close();
document.close();
}
private static void JumpToP(XmlCursor cursor) {
System.out.println(cursor.getName()); // r
System.out.println(cursor.getTextValue());
cursor.toChild(1);
System.out.println(cursor.getName());//AlternateContent
System.out.println(cursor.getTextValue());
cursor.toChild(0);
System.out.println(cursor.getName());//Choice
System.out.println(cursor.getTextValue());
cursor.toChild(0);
System.out.println(cursor.getName());//drawing
System.out.println(cursor.getTextValue());
cursor.toChild(0);
System.out.println(cursor.getName());//anchor
System.out.println(cursor.getTextValue());
cursor.toChild(8);
System.out.println(cursor.getName());//graphic
System.out.println(cursor.getTextValue());
cursor.toChild(0);
System.out.println(cursor.getName());//graphicData
System.out.println(cursor.getTextValue());
cursor.toChild(0);
System.out.println(cursor.getName());//wsp
System.out.println(cursor.getTextValue());
cursor.toChild(2);
System.out.println(cursor.getName());//txbx
System.out.println(cursor.getTextValue());
cursor.toChild(0);
System.out.println(cursor.getName());//txbxContent
System.out.println(cursor.getTextValue());
// cursor.toChild(0);
cursor.toLastChild();
System.out.println(cursor.getName());//p
System.out.println(cursor.getTextValue());
}
在本地tomcat7.0.59、spring3.2.6 运行生成成功。