POI基于Docx模板 插值渲染(文本)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

接上一篇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();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值