最近搞一个向word模板中替换占位符 填充数据,然后将多个word合并在一起的方法。网上一搜有很多资料,现在在这儿对过程中遇到的一些问题进行描述。 测试工程在这儿需要的可以点击下载.包括所需要的所有jar包和测试程序和word模板等。
java 调用poi操作word,我用的是2007以上的word,格式是.docx,不适用于2007以前的.doc 格式的 word ,遇到的问题主要有三个:
占位符替换不成功,主要是因为 段落的中的占位符 解析成块时 被拆分开了,所以没匹配上。
解决办法是,对段落XWPFParagraph进行解析的时候,对解析出来的块XWPFRun的内容进行判断,若存在占位符的开始标记,则开始存储块XWPFRun,并将块的内容进行拼接,最后遇到占位符的结束标记,则将存储的块 保留第一个,后面的块的内容都置为空,再将拼接的内容进行占位符替换 后赋给保留的第一个块。详细逻辑 看后面代码。
表格中的替换,之前使用的是 在遍历到表格的单元格的时候,直接将内容清空,然后将替换好的内容填充进去,导致样式丢失了,所以不能这样替换。应该是遍历到单元格后,获取单元格中段落,调用段落中的替换占位符的方法,这样就能够保留表格内容的样式了。
相关代码如下:
/**
* 替换表格里面的变量
* @param doc 要替换的文档
* @param params 参数
*/
private static void replaceInTable(XWPFDocument doc, Map<String, String> params) {
Iterator<XWPFTable> iterator = doc.getTablesIterator();
XWPFTable table;
List<XWPFTableRow> rows;
List<XWPFTableCell> cells;
List<XWPFParagraph> paras;
while (iterator.hasNext()) {
table = iterator.next();
rows = table.getRows();
for (XWPFTableRow row : rows) {
cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
//这种方法会导致表格中的格式丢失
/*String cellTextString = cell.getText();
for (Entry e : params.entrySet()) {
if (cellTextString.contains("${"+e.getKey()+"}"))
cellTextString = cellTextString.replace("${"+e.getKey()+"}", e.getValue().toString());
}
cell.removeParagraph(0);
if(cellTextString.contains("${") && cellTextString.contains("}")){
cellTextString = "";
}
cell.setText(cellTextString);*/
//调用段落替换占位符的方式
paras = cell.getParagraphs();
for (XWPFParagraph para : paras) {
replaceInPara(para, params);
}
}
}
}
}
相关代码如下:
//两个对象进行追加
public static XWPFDocument mergeWord(XWPFDocument document1,XWPFDocument document2) throws Exception {
//设置分页符---当刚好一页数据时,会导致出现空白页,后面出现分页符导致格式有一点差异
//解决方法是,在模板头上增加分页符
//XWPFParagraph p = document2.createParagraph();
//p.setPageBreak(true);
CTBody src1Body = document1.getDocument().getBody();
CTBody src2Body = document2.getDocument().getBody();
XmlOptions optionsOuter = new XmlOptions();
optionsOuter.setSaveOuter();
String appendString = src2Body.xmlText(optionsOuter);
String srcString = src1Body.xmlText();
String prefix = srcString.substring(0,srcString.indexOf(">")+1);
String mainPart = srcString.substring(srcString.indexOf(">")+1,srcString.lastIndexOf("<"));
String sufix = srcString.substring( srcString.lastIndexOf("<") );
String addPart = appendString.substring(appendString.indexOf(">") + 1, appendString.lastIndexOf("<"));
CTBody makeBody = CTBody.Factory.parse(prefix+mainPart+addPart+sufix);
src1Body.set(makeBody);
return document1;
}
最后将整个逻辑调整了下,做了测试感觉还行,问题是都解决了,但是后续还要和项目整合,还需要将一些方法给拆开。上面给了 测试工程的下载链接,资源都是网上找的。
模板:
替换后的效果:
下面是完整代码:
package com.cn.gwssi.demo;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.apache.poi.POIXMLDocument;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.apache.xmlbeans.XmlOptions;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody;
/**
* 对word模板填充数据,并合并成一个word
*/
public class POIMergeDocUtil {
public static void main(String[] args) throws Exception {
List<String> paths = new ArrayList<String>();
paths.add("d:\\1.docx");
paths.add("d:\\2.docx");
paths.add("d:\\3.docx");
Map<String, String> param = new HashMap<String, String>();
param.put("username", "梅西");
param.put("country", "阿根廷");
param.put("type", "挖煤");
String destDocx = "d:\\test.docx";
exportWordUtils(paths,param,destDocx);
System.out.println("执行成功=====");
}
/**
* 对多个word模板填充数据,并合并成一个word输出
* @param paths
* @param param
* @param destDocx
* @throws Exception
*/
public static void exportWordUtils(List<String> paths,Map<String,String> param,String destDocx) throws Exception{
OutputStream dest = null;
List<XWPFDocument> xwpfDocuments = new ArrayList<XWPFDocument>();
//循环向word填充数据
for (String path : paths) {
XWPFDocument xwpfDocument = generateWord(param,path);
xwpfDocuments.add(xwpfDocument);
}
//合并word
XWPFDocument xwpfDocument = xwpfDocuments.get(0);
for (int i = 1; i < xwpfDocuments.size(); i++) {
xwpfDocument=mergeWord(xwpfDocument,xwpfDocuments.get(i));
}
//输出word
dest = new FileOutputStream(destDocx);
xwpfDocument.write(dest);
IOUtils.closeQuietly(dest);
}
/**
* 替换word占位符的内容
* @param param
* @param filePath
* @return
*/
public static XWPFDocument generateWord(Map<String, String> param, String filePath) {
XWPFDocument doc = null;
try {
doc = new XWPFDocument(POIXMLDocument.openPackage(filePath));
if (param != null && param.size() > 0){
//处理段落
replaceInPara(doc, param);
//替换表格里面的变量
replaceInTable(doc, param);
}
} catch (Exception e) {
e.printStackTrace();
}
return doc;
}
/**
* 遍历word段落信息
* @param doc 要替换的文档
* @param params 参数
*/
private static void replaceInPara(XWPFDocument doc, Map<String, String> params) {
Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
XWPFParagraph para;
while (iterator.hasNext()) {
para = iterator.next();
replaceInPara(para, params);
}
}
/**
* 替换段落里面的变量
* @param para 要替换的段落
* @param params 参数
*/
private static void replaceInPara(XWPFParagraph para, Map<String, String> param) {
List<XWPFRun> runs;
String tempString = "";
char lastChar = ' ';
if (matcher(para.getParagraphText()).find()) {
runs = para.getRuns();
Set<XWPFRun> runSet = new HashSet<XWPFRun>();
for (XWPFRun run : runs) {
String text = run.getText(0);
System.out.println("=======>"+text);
if(text==null)continue;
text = replaceText(text,param);
run.setText("",0);
run.setText(text,0);
for(int i=0;i<text.length();i++){
char ch = text.charAt(i);
if(ch == '$'){
runSet = new HashSet<XWPFRun>();
runSet.add(run);
tempString = text;
}else if(ch == '{'){
if(lastChar == '$'){
if(runSet.contains(run)){
}else{
runSet.add(run);
tempString = tempString+text;
}
}else{
runSet = new HashSet<XWPFRun>();
tempString = "";
}
}else if(ch == '}'){
if(tempString!=null&&tempString.indexOf("${")>=0){
if(runSet.contains(run)){
}else{
runSet.add(run);
tempString = tempString+text;
}
}else{
runSet = new HashSet<XWPFRun>();
tempString = "";
}
if(runSet.size()>0){
String replaceText = replaceText(tempString,param);
if(!replaceText.equals(tempString)){
int index = 0;
XWPFRun aRun = null;
for(XWPFRun tempRun:runSet){
tempRun.setText("",0);
if(index==0){
aRun = tempRun;
}
index++;
}
aRun.setText(replaceText,0);
}
runSet = new HashSet<XWPFRun>();
tempString = "";
}
}else{
if(runSet.size()<=0)continue;
if(runSet.contains(run))continue;
runSet.add(run);
tempString = tempString+text;
}
lastChar = ch;
}
}
//这种方法会导致占位符被拆开解析,不能识别替换掉
/*for (int i = 0; i < runs.size(); i++) {
XWPFRun run = runs.get(i);
String runText = run.toString();
System.out.println("====>run:"+runText);
matcher = this.matcher(runText);
if (matcher.find()) {
while ((matcher = this.matcher(runText)).find()) {
runText = matcher.replaceFirst(String.valueOf(param.get(matcher.group(1))));
}
// 直接调用XWPFRun的setText()方法设置文本时,在底层会重新创建一个XWPFRun,把文本附加在当前文本后面,
// 所以我们不能直接设值,需要先删除当前run,然后再自己手动插入一个新的run。
para.removeRun(i);
if(runText.equals("null")){
runText="";
}
para.insertNewRun(i).setText(runText);
}
}*/
}
}
/**
* 替换表格里面的变量
* @param doc 要替换的文档
* @param params 参数
*/
private static void replaceInTable(XWPFDocument doc, Map<String, String> params) {
Iterator<XWPFTable> iterator = doc.getTablesIterator();
XWPFTable table;
List<XWPFTableRow> rows;
List<XWPFTableCell> cells;
List<XWPFParagraph> paras;
while (iterator.hasNext()) {
table = iterator.next();
rows = table.getRows();
for (XWPFTableRow row : rows) {
cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
//这种方法会导致表格中的格式丢失
/*String cellTextString = cell.getText();
for (Entry e : params.entrySet()) {
if (cellTextString.contains("${"+e.getKey()+"}"))
cellTextString = cellTextString.replace("${"+e.getKey()+"}", e.getValue().toString());
}
cell.removeParagraph(0);
if(cellTextString.contains("${") && cellTextString.contains("}")){
cellTextString = "";
}
cell.setText(cellTextString);*/
//调用段落替换占位符的方式
paras = cell.getParagraphs();
for (XWPFParagraph para : paras) {
replaceInPara(para, params);
}
}
}
}
}
/**
* 替换占位符
* @param text
* @param map
* @return
*/
private static String replaceText(String text, Map<String, String> map) {
if(text != null){
/*for (Entry entry : map.entrySet()) {
if (text.contains("${"+entry.getKey()+"}")){
text = text.replace("${"+entry.getKey()+"}", entry.getValue().toString());
}
}*/
Matcher matcher = matcher(text);
if (matcher.find()) {
while ((matcher = matcher(text)).find()) {
text = matcher.replaceFirst(String.valueOf(map.get(matcher.group(1))));
}
if(text.equals("null")){
text="";
}
}
}
return text;
}
/**
* 正则匹配字符串
* @param str
* @return
*/
private static Matcher matcher(String str) {
Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(str);
return matcher;
}
//两个对象进行追加
public static XWPFDocument mergeWord(XWPFDocument document1,XWPFDocument document2) throws Exception {
//设置分页符---当刚好一页数据时,会导致出现空白页,后面出现分页符导致格式有一点差异
//解决方法是,在模板头上增加分页符
//XWPFParagraph p = document2.createParagraph();
//p.setPageBreak(true);
CTBody src1Body = document1.getDocument().getBody();
CTBody src2Body = document2.getDocument().getBody();
XmlOptions optionsOuter = new XmlOptions();
optionsOuter.setSaveOuter();
String appendString = src2Body.xmlText(optionsOuter);
String srcString = src1Body.xmlText();
String prefix = srcString.substring(0,srcString.indexOf(">")+1);
String mainPart = srcString.substring(srcString.indexOf(">")+1,srcString.lastIndexOf("<"));
String sufix = srcString.substring( srcString.lastIndexOf("<") );
String addPart = appendString.substring(appendString.indexOf(">") + 1, appendString.lastIndexOf("<"));
CTBody makeBody = CTBody.Factory.parse(prefix+mainPart+addPart+sufix);
src1Body.set(makeBody);
return document1;
}
}