引入POM依赖:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-excelant</artifactId>
<version>3.12</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.12</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.8</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.8</version>
</dependency>
<!-- 生成图片-->
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.0.19</version>
</dependency>
<dependency>
<!--支持插入图片-->
<groupId>org.docx4j</groupId>
<artifactId>docx4j</artifactId>
<version>3.3.1</version>
</dependency>
现在项目有个需求,用户只需要输入相关信息,就可以根据现有的word模板自动生成一个word文件。现有word模板如下:
现在我要往word空白处插入对应的数据,那么如何插入呢?首先java无法直接定位到word具体某一位置,这个不用多解释都能明白。那我们就需要通过另一种思维方式。也就是替换文本的方式来实现在具体的位置插入数据的效果。
我们可以在word空白处要插入数据的位置输入一些文本数据,可以把这些文本数据看成是占位数据。这些文本数据可以自己随意输入。但必须是word模板中不存在的文本。这个道理很简单,如果输入的文本在word中已经存在,那么替换操作可能会将之前的文本也给替换了。
根据word模板,我插入了如下数据作为占位:
接下来这个步骤很重要。我们在插入了占位文本后,还需要再占位文本的前后插入书签。我们可以选中占位文本。选中占位文本——>插入——>书签——>输入书签名(随意起)——>添加书签
那么为什么要插入书签呢?不插入行不行?
必须要插入!因为JAVA操作word的对象有 表格对象,有段落对象,有行对象。但是没有字对象!什么意思?也就是说java操作Word文本的最小单位是一行数据。JAVA无法精确定位到要操作的是哪个字,只能定位到要操作的是哪一行!那么插入书签有什么用呢?在文本的前后加入了书签,可以让java在解析这串文本的时候,认为他是独立的一行数据。也就是说,当我们给占位文本的前后加入了书签以后,占位文本会独占一行!,这也就意味着我可以直接将占位文本替换。
接下来直接上代码:
/**
*将word中某些标签替换成指定的值,并生成一个新的word文档。
* @param templateFilePath word模板文件路径
* @param outFilePath 填充后输出文件路径
* @param map key:word中的占位标签,value对应标签要替换的值。
* @throws IOException
*/
public static void insertAndOutFile(String templateFilePath,String outFilePath,Map<String,String> map) throws IOException {
//准备工作,生成docx对象
String templatePath=templateFilePath;
InputStream is=new FileInputStream(templatePath);
XWPFDocument docx=new XWPFDocument(is);
//获取表格
List<XWPFTable> tables=docx.getTables();
//定位到第一个表格
XWPFTable table=tables.get(0);
//遍历该表格所有的行
for(int i=0;i<table.getRows().size();i++) {
XWPFTableRow row=table.getRow(i);
//遍历该行所有的列
for(int j=0;j<row.getTableCells().size();j++) {
XWPFTableCell cell=row.getTableCells().get(j);
//获取该格子里所有的段
List<XWPFParagraph> paragraphs=cell.getParagraphs();
for(XWPFParagraph p:paragraphs) {
//遍历该格子里的段
List<XWPFRun> runs=p.getRuns();
for(XWPFRun run:runs) {
//遍历该段里的所有文本
String str=run.toString();
System.out.println(str);
//如果该段文本包含map中的key,则替换为map中的value值。
Set<String> keySet = map.keySet();
for(String key:keySet){
if(str.trim().equals(key)){
System.out.println("key"+map.get(key));
//替换该文本0位置的数据。
run.setText(map.get(key),0);
}
}
}
}
}
}
//输出
OutputStream os=new FileOutputStream(outFilePath);
docx.write(os);
is.close();
os.close();
}
代码解释:
总的来说就是先获取表格对象XWPFTable,再获取表格对象里的行对象XWPFTableRow,根据行对象获取到单元格对象XWPFTableCel,根据单元格对象获取单元格里的段落对象XWPFParagraph,根据段落对象获取里面的行对象XWPFRun,由于我们在在占位文本中添加了书签,所以一个占位文本就会对应一个行对象XWPFRun,我通过一个Map把占位文本作为key,value存储要替换成的值。通过遍历,替换每一个行对象XWPFRun,最终完成word指定位置插入数据的操作。
测试:
public static void main(String[] args) throws IOException {
Map<String,String> map = new HashMap<>();
map.put("wirtNum","4500");
map.put("bank","中国银行");
map.put("clue","157********,157********");
map.put("day","15");
map.put("month","9");
map.put("year","2021");
map.put("clueType","手机号");
map.put("birth","2021-08-30");
map.put("name","张三");
map.put("sex","男");
insertAndOutFile("D:/template.docx","D:/out.docx",map);
}
效果: