网上搜索的不管是docx4j还是poi都只是实现了占位符在同一个文本中(读取word行数据后,行数据会分为多个文本)的替换,针对占位符没有在同一个文本或者换行了都没有实现,总结docx4j和poi两种方式终极实现占位符替换生成新word,两种方式源码如下
import cn.hutool.core.util.ObjectUtil;
import com.google.common.collect.Lists;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.wml.ContentAccessor;
import org.docx4j.wml.Text;
import org.junit.Test;
import javax.xml.bind.JAXBElement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author liuchao
* @date 2020/6/8
*/
public class Test01 {
/**
* 设置最大Text类型节点个数 如果超过此值,在删除占位符时可能会重复计算导致错误
*/
private static int MAX_TEXT_SIZE = 1000000;
@Test
public void test() throws Exception {
String docxFile = "/Users/liuchao/java/temp/1.docx";
WordprocessingMLPackage template = WordprocessingMLPackage.load(new java.io.File(docxFile));
List
依赖jar
org.docx4j
docx4j-JAXB-ReferenceImpl
8.1.7
cn.hutool
hutool-all
4.6.0
com.alibaba
fastjson
1.2.60
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.junit.Test;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author liuchao
* @date 2020/6/9
*/
public class Test03 {
// 标识忽略
static final int ignoreTg = 0;
// 标识已读取到'$'字符
static final int startTg = 1;
// 标识已读取到'{'字符
static final int readTg = 2;
@Test
public void test() throws Exception {
Map map = new HashMap<>();
map.put("${company}", "Company Name here...");
map.put("${address}", "xxxfffadfasdf");
map.put("${secondParty}", "xxxfffadfasdf");
map.put("${year}", "xxxfffadfasdf");
map.put("${month}", "xxxfffadfasdf");
map.put("${day}", "xxxfffadfasdf");
map.put("${money}", "1000");
XWPFDocument doc = new XWPFDocument(new FileInputStream(new File("/Users/liuchao/java/temp/1.docx")));
List paragraphList = Lists.newArrayList(doc.getParagraphs());
// 获取表格中的占位符信息
getTableParagraphs(doc, paragraphList, placeholderValues);
//获取占位符,并且将占位符需要替换的值写入
List placeholderList = getPlaceholderList(paragraphList, map);
//清除占位符信息
clearPlaceholder(placeholderList, paragraphList);
BufferedOutputStream bos = FileUtil.getOutputStream(new File("/Users/liuchao/java/temp/NEW.docx"));
doc.write(bos);
bos.flush();
bos.close();
doc.close();
}
/**
* 获取表格中占位符信息
*
* @param doc 当前文档
* @param list 存储占位符信息
* @param placeholderValues 需要替换的值
* @return void
* @author liuchao
* @date 2020/7/6
*/
private void getTableParagraphs(XWPFDocument doc, List list, Map placeholderValues) {
List tables = doc.getTables();
if (ObjectUtil.isEmpty(tables)) {
return;
}
tables.forEach(table -> table.getRows().forEach(row -> row.getTableCells().forEach(cell -> {
String text = cell.getText();
if (ObjectUtil.isEmpty(text)) {
return;
}
Iterator it = placeholderValues.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
if (text.indexOf(key) != -1) {
list.addAll(cell.getParagraphs());
}
}
})));
}
/**
* 清除占位符信息
*
* @param placeholderList 占位符位置信息
* @param paragraphList 行数据
* @return void
* @author liuchao
* @date 2020/6/10
*/
public static void clearPlaceholder(List placeholderList, List paragraphList) {
if (ObjectUtil.isEmpty(placeholderList)) {
return;
}
int[] currentPlaceholder = placeholderList.get(0);
StringBuilder tempSb = new StringBuilder();
for (int i = 0; i < paragraphList.size(); i++) {
XWPFParagraph p = paragraphList.get(i);
List runs = p.getRuns();
for (int j = 0; j < runs.size(); j++) {
XWPFRun run = runs.get(j);
String text = run.getText(run.getTextPosition());
StringBuilder nval = new StringBuilder();
char[] textChars = text.toCharArray();
for (int m = 0; m < textChars.length; m++) {
char c = textChars[m];
if (null == currentPlaceholder) {
nval.append(c);
continue;
}
// 排除'$'和'}'两个字符之间的字符
int start = currentPlaceholder[0] * 1000000 + currentPlaceholder[1] * 500 + currentPlaceholder[2];
int end = currentPlaceholder[3] * 1000000 + currentPlaceholder[4] * 500 + currentPlaceholder[5];
int cur = i * 1000000 + j * 500 + m;
if (!(cur >= start && cur <= end)) {
nval.append(c);
} else {
tempSb.append(c);
}
//判断是否是占位符结尾,如果是那获取新的占位符
if (tempSb.toString().endsWith("}")) {
placeholderList.remove(0);
if (ObjectUtil.isEmpty(placeholderList)) {
currentPlaceholder = null;
continue;
}
currentPlaceholder = placeholderList.get(0);
tempSb = new StringBuilder();
}
}
run.setText(nval.toString(), run.getTextPosition());
}
}
}
/**
* 获取占位符信息,并且在占位符后面填充值
*
* @param paragraphList 行数据
* @param map 要替换的占位符key\value
* @return java.util.List
* @author liuchao
* @date 2020/6/10
*/
public static List getPlaceholderList(List paragraphList, Map map) {
// 存储占位符 位置信息集合
List placeholderList = new ArrayList<>();
// 当前占位符 0:'$'字符在XWPFParagraph集合中下标
// 1:'$'字符在XWPFRun集合中下标
// 2:'$'字符在text.toCharArray()数组下标
// 3: '}'字符在XWPFParagraph集合中下标
// 4: '}'字符在XWPFRun集合中下标
// 5:'}'字符在text.toCharArray()数组下标
int[] currentPlaceholder = new int[6];
// 当前标识
int modeTg = ignoreTg;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < paragraphList.size(); i++) {
XWPFParagraph p = paragraphList.get(i);
List runs = p.getRuns();
for (int j = 0; j < runs.size(); j++) {
XWPFRun run = runs.get(j);
String text = run.getText(run.getTextPosition());
char[] textChars = text.toCharArray();
String newVal = "";
StringBuilder textSofar = new StringBuilder();
for (int m = 0; m < textChars.length; m++) {
char c = textChars[m];
textSofar.append(c);
switch (c) {
case '$': {
modeTg = startTg;
sb.append(c);
}
break;
case '{': {
if (modeTg == startTg) {
sb.append(c);
modeTg = readTg;
currentPlaceholder[0] = i;
currentPlaceholder[1] = j;
currentPlaceholder[2] = m - 1;
} else {
if (modeTg == readTg) {
sb = new StringBuilder();
modeTg = ignoreTg;
}
}
}
break;
case '}': {
if (modeTg == readTg) {
modeTg = ignoreTg;
sb.append(c);
String val = map.get(sb.toString());
if (ObjectUtil.isNotEmpty(val)) {
newVal += textSofar.toString() + val;
placeholderList.add(currentPlaceholder);
textSofar = new StringBuilder();
}
currentPlaceholder[3] = i;
currentPlaceholder[4] = j;
currentPlaceholder[5] = m;
currentPlaceholder = new int[6];
sb = new StringBuilder();
} else if (modeTg == startTg) {
modeTg = ignoreTg;
sb = new StringBuilder();
}
}
default: {
if (modeTg == readTg) {
sb.append(c);
} else if (modeTg == startTg) {
modeTg = ignoreTg;
sb = new StringBuilder();
}
}
}
}
newVal += textSofar.toString();
run.setTextPosition(0);
run.setText(newVal, run.getTextPosition());
}
}
return placeholderList;
}
依赖jar
cn.hutool
hutool-all
4.6.0
com.alibaba
fastjson
1.2.60
org.apache.poi
poi
4.1.1
org.apache.poi
poi-ooxml
4.1.1
org.apache.poi
poi-ooxml-schemas
4.1.1