提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
接上一篇poi-tl的文章,自己手痒痒,还是想自己造一遍轮子,因为我自己项目的渲染需求只有文本和HTML代码段,所以可以参考poi-tl自己造一个简单的轮子。大概就是这么个想法。目前是文本渲染的部分,一些不常用的段落我直接pass掉了。没有整个文档遍历,有需求的话可以自行调整,这部分基本上是完工了,整体上的拓展性也可以,后面有时间再迭代HTML的渲染。
使用步骤
1、依赖(JDK17)
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.18.3</version>
</dependency>
<dependency>
<groupId>io.github.draco1023</groupId>
<artifactId>poi-tl-ext</artifactId>
<version>0.4.19-poi5</version>
</dependency>
2、代码(JDK17,一些不适用的语法自行调整)
import org.apache.commons.io.FileUtils;
import org.apache.poi.xwpf.usermodel.*;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Pattern;
public class DocxTemplateRenderer {
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{(.+?)\\}");
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
public void render(InputStream templateInput, OutputStream output, Map<String, Object> data) throws IOException {
Objects.requireNonNull(templateInput, "templateInput must not be null");
Objects.requireNonNull(output, "output must not be null");
Objects.requireNonNull(data, "data must not be null");
try (XWPFDocument doc = new XWPFDocument(templateInput)) {
Map<String, List<TemplatePosition>> placeholders = findPlaceholders(doc);
placeholders.forEach((key, positions) -> {
Object value = data.get(key);
if (value == null) return;
positions.forEach(pos -> replacePlaceholder(pos, value.toString()));
});
doc.write(output);
}
}
private Map<String, List<TemplatePosition>> findPlaceholders(XWPFDocument doc) {
Map<String, List<TemplatePosition>> map = new HashMap<>();
processParagraphs(doc.getParagraphs(), map);
processTables(doc.getTables(), map);
processHeadersAndFooters(doc.getHeaderList(), map);
processHeadersAndFooters(doc.getFooterList(), map);
return map;
}
private void processTables(List<XWPFTable> tables, Map<String, List<TemplatePosition>> map) {
tables.stream()
.flatMap(table -> table.getRows().stream())
.flatMap(row -> row.getTableCells().stream())
.forEach(cell -> processParagraphs(cell.getParagraphs(), map));
}
private void processHeadersAndFooters(List<? extends IBody> containers, Map<String, List<TemplatePosition>> map) {
containers.forEach(container -> processParagraphs(container.getParagraphs(), map));
}
private void processParagraphs(List<XWPFParagraph> paragraphs, Map<String, List<TemplatePosition>> map) {
paragraphs.forEach(para -> processParagraph(para, map));
}
private void processParagraph(XWPFParagraph para, Map<String, List<TemplatePosition>> map) {
para.getRuns().stream()
.filter(run -> run.getText(0) != null)
.forEach(run -> findPlaceholdersInRun(run, map));
}
private void findPlaceholdersInRun(XWPFRun run, Map<String, List<TemplatePosition>> map) {
String text = run.getText(0);
if (text == null) return;
PLACEHOLDER_PATTERN.matcher(text)
.results()
.forEach(matchResult -> map.computeIfAbsent(matchResult.group(1), k -> new ArrayList<>())
.add(new TemplatePosition(run.getParagraph(), run, matchResult.start(), matchResult.end())));
}
private void replacePlaceholder(TemplatePosition pos, String value) {
XWPFRun run = pos.getRun();
String currentText = run.getText(0);
if (currentText == null) return;
StringBuilder newText = new StringBuilder(currentText);
newText.replace(pos.getStart(), pos.getEnd(), value);
run.setText(newText.toString(), 0);
}
public static class TemplatePosition {
private final XWPFParagraph paragraph;
private final XWPFRun run;
private final int start;
private final int end;
public TemplatePosition(XWPFParagraph paragraph, XWPFRun run, int start, int end) {
this.paragraph = paragraph;
this.run = run;
this.start = start;
this.end = end;
}
public XWPFParagraph getParagraph() {
return paragraph;
}
public XWPFRun getRun() {
return run;
}
public int getStart() {
return start;
}
public int getEnd() {
return end;
}
}
// 示例用法
public static void main(String[] args) throws IOException {
// 准备数据
String html = FileUtils.readFileToString(new File("content.html"), DEFAULT_CHARSET);
Map<String, Object> data = new HashMap<>();
data.put("title", "Hi, title");
data.put("production", "production");
data.put("creator", "creator");
data.put("author", "author");
//TODO HTML渲染
data.put("content", html);
data.put("date", new Date().toString());
//渲染输出
InputStream in = new FileInputStream("exportDocx.docx");
OutputStream out = new FileOutputStream("output.docx");
new DocxTemplateRenderer().render(in, out, data);
out.close();
in.close();
}
}