word模板動態替換佔位符(eg:${placeholder})然後生成新的word
網上搜索的不管是docx4j還是poi都只是實現了佔位符在同一個文本中(讀取word行數據後,行數據會分爲多個文本)的替換,針對佔位符沒有在同一個文本或者換行了都沒有實現,總結docx4j和poi兩種方式終極實現佔位符替換生成新word,兩種方式源碼如下
1、Docx4J實現代碼
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<Object> texts = getAllElementFromObject(
template.getMainDocumentPart(), Text.class);
Map<String, String> 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");
searchAndReplace(texts, map);
template.save(new java.io.File("/Users/liuchao/java/temp/NEW.docx"));
}
/**
* 遞歸獲取所有的節點
*
* @param obj 當前文檔
* @param toSearch 要查詢的節點類型
* @return java.util.List<java.lang.Object>
* @author liuchao
* @date 2020/6/9
*/
private static List<Object> getAllElementFromObject(Object obj,
Class<?> toSearch) {
List<Object> result = Lists.newArrayListWithCapacity(60);
if (obj instanceof JAXBElement) {
obj = ((JAXBElement<?>) obj).getValue();
}
if (obj.getClass().equals(toSearch)) {
result.add(obj);
} else if (obj instanceof ContentAccessor) {
List<?> children = ((ContentAccessor) obj).getContent();
for (Object child : children) {
result.addAll(getAllElementFromObject(child, toSearch));
}
}
return result;
}
/**
* 查找並且替換佔位符
*
* @param texts 當前文檔所有的Text類型節點
* @param values 要替換的佔位符key\value
* @return void
* @author liuchao
* @date 2020/6/9
*/
public static void searchAndReplace(List<Object> texts, Map<String, String> values) {
// 存儲佔位符 位置信息集合
List<int[]> placeholderList = getPlaceholderList(texts, values);
if (ObjectUtil.isEmpty(placeholderList)) {
return;
}
int[] currentPlaceholder;
// 刪除元素佔位符
for (int i = 0; i < texts.size(); i++) {
if (ObjectUtil.isEmpty(placeholderList)) {
break;
}
currentPlaceholder = placeholderList.get(0);
Text textElement = (Text) texts.get(i);
String v = textElement.getValue();
StringBuilder nval = new StringBuilder();
char[] textChars = v.toCharArray();
for (int j = 0; j < textChars.length; j++) {
char c = textChars[j];
if (null == currentPlaceholder) {
nval.append(c);
continue;
}
// 計算是否需要排除當前節點
int start = currentPlaceholder[0] * MAX_TEXT_SIZE + currentPlaceholder[1];
int end = currentPlaceholder[2] * MAX_TEXT_SIZE + currentPlaceholder[3];
int cur = i * MAX_TEXT_SIZE + j;
// 排除'$'和'}'兩個字符之間的字符
if (!(cur >= start && cur <= end)) {
nval.append(c);
}
if (j > currentPlaceholder[3] && i >= currentPlaceholder[2]) {
placeholderList.remove(0);
if (ObjectUtil.isEmpty(placeholderList)) {
currentPlaceholder = null;
continue;
}
currentPlaceholder = placeholderList.get(0);
}
}
textElement.setValue(nval.toString());
}
}
/**
* 獲取佔位符信息,並且在佔位符後面填充值
*
* @param texts Text類型節點集合
* @param values 要替換的佔位符key\value
* @return java.util.List<int [ ]>
* @author liuchao
* @date 2020/6/9
*/
public static List<int[]> getPlaceholderList(List<Object> texts, Map<String, String> values) {
// 標識忽略
int ignoreTg = 0;
// 標識已讀取到'$'字符
int startTg = 1;
// 標識已讀取到'{'字符
int readTg = 2;
// 當前標識
int modeTg = ignoreTg;
// 存儲佔位符 位置信息集合
List<int[]> placeholderList = new ArrayList<>();
// 當前佔位符 0:'$'字符Text在texts中下標
// 1:'$'字符在Text.getValue().toCharArray()數組下標
// 2: '}'字符Text在texts中下標
// 3:'}'字符在Text.getValue().toCharArray()數組下標
int[] currentPlaceholder = new int[4];
StringBuilder sb = new StringBuilder();
for (int i = 0; i < texts.size(); i++) {
Text textElement = (Text) texts.get(i);
String newVal = "";
String text = textElement.getValue();
StringBuilder textSofar = new StringBuilder();
char[] textChars = text.toCharArray();
for (int col = 0; col < textChars.length; col++) {
char c = textChars[col];
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] = col - 1;
} else {
if (modeTg == readTg) {
sb = new StringBuilder();
modeTg = ignoreTg;
}
}
}
break;
case '}': {
if (modeTg == readTg) {
modeTg = ignoreTg;
sb.append(c);
newVal += textSofar.toString()
+ (null == values.get(sb.toString()) ? sb.toString() : values.get(sb.toString()));
textSofar = new StringBuilder();
currentPlaceholder[2] = i;
currentPlaceholder[3] = col;
placeholderList.add(currentPlaceholder);
currentPlaceholder = new int[4];
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();
textElement.setValue(newVal);
}
return placeholderList;
}
依賴jar
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-JAXB-ReferenceImpl</artifactId>
<version>8.1.7</version>
</dependency>
<!--hutool工具類-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.0</version>
</dependency>
<!--json格式化-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
2、Poi實現代碼
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<String, String> 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<XWPFParagraph> paragraphList = doc.getParagraphs();
//獲取佔位符,並且將佔位符需要替換的值寫入
List<int[]> 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 placeholderList 佔位符位置信息
* @param paragraphList 行數據
* @return void
* @author liuchao
* @date 2020/6/10
*/
public static void clearPlaceholder(List<int[]> placeholderList, List<XWPFParagraph> 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<XWPFRun> 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<int [ ]>
* @author liuchao
* @date 2020/6/10
*/
public static List<int[]> getPlaceholderList(List<XWPFParagraph> paragraphList, Map<String, String> map) {
// 存儲佔位符 位置信息集合
List<int[]> 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<XWPFRun> 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
<!--hutool工具類-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.0</version>
</dependency>
<!--json格式化-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.1</version>
</dependency>