You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Apache POI在Java中替换Word首页及目录的占位符?

解决Apache POI替换Word首页标题页和目录占位符的问题

一、处理首页标题页的占位符

首页标题页的占位符无法替换,大概率是因为Word将长占位符拆分成了多个XWPFRun(比如«SOURCE_VERSION»被拆成«、SOURCE_VERSION、»三个独立Run),导致原有代码逐个处理Run时无法匹配完整占位符。

修改replaceMacrosInParagraph方法,先合并段落所有Run的文本,匹配到占位符后重新生成Run:

private static void replaceMacrosInParagraph(XWPFParagraph paragraph, Map<String, String> replacements) {
    // 合并段落内所有Run的文本
    StringBuilder fullTextSb = new StringBuilder();
    for (XWPFRun run : paragraph.getRuns()) {
        String text = run.getText(0);
        if (text != null) {
            fullTextSb.append(text);
        }
    }
    String fullText = fullTextSb.toString();

    // 执行占位符替换
    boolean hasChanged = false;
    for (Map.Entry<String, String> entry : replacements.entrySet()) {
        if (fullText.contains(entry.getKey())) {
            fullText = fullText.replace(entry.getKey(), entry.getValue());
            hasChanged = true;
        }
    }

    // 替换后更新段落内容
    if (hasChanged) {
        // 清空原有Run
        while (!paragraph.getRuns().isEmpty()) {
            paragraph.removeRun(0);
        }
        // 创建新Run并设置文本,可保留原段落样式(按需调整)
        XWPFRun newRun = paragraph.createRun();
        newRun.setFontFamily("宋体");
        newRun.setFontSize(14);
        newRun.setText(fullText, 0);
    }
}

二、处理目录中的占位符

目录属于Word域(TOC域),分两种场景处理:

场景1:占位符来自目录引用的标题

如果目录中的占位符是文档标题里的内容,先替换标题中的占位符,再标记TOC域需要更新:

private static void updateTableOfContents(XWPFDocument document) {
    // 遍历所有段落,找到TOC域并标记为需要更新
    for (XWPFParagraph paragraph : document.getParagraphs()) {
        for (XWPFRun run : paragraph.getRuns()) {
            if (run.getCTR().getFldCharList() != null) {
                for (org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFldChar fldChar : run.getCTR().getFldCharList()) {
                    if (fldChar.getFldCharType() == org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType.UPDATE) {
                        fldChar.setDirty(true);
                    }
                }
            }
        }
    }
}

在replaceMacrosInDocument方法末尾调用该方法,打开生成的Word文档时会提示更新目录,确认后即可看到替换后的内容。

场景2:占位符直接写在目录域中

如果占位符是直接嵌入在目录的域代码或显示文本里,需要遍历域代码进行替换:

private static void replaceMacrosInFields(XWPFDocument document, Map<String, String> replacements) {
    for (XWPFParagraph paragraph : document.getParagraphs()) {
        List<XWPFRun> runsToRemove = new ArrayList<>();
        StringBuilder fieldCodeSb = new StringBuilder();
        boolean inFieldCode = false;

        for (XWPFRun run : paragraph.getRuns()) {
            if (run.getCTR().getFldCharList() != null) {
                for (org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFldChar fldChar : run.getCTR().getFldCharList()) {
                    if (fldChar.getFldCharType() == org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType.BEGIN) {
                        inFieldCode = true;
                        runsToRemove.add(run);
                    } else if (fldChar.getFldCharType() == org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType.SEPARATE) {
                        // 替换域代码中的占位符
                        for (Map.Entry<String, String> entry : replacements.entrySet()) {
                            fieldCodeSb = new StringBuilder(fieldCodeSb.toString().replace(entry.getKey(), entry.getValue()));
                        }
                        // 插入修改后的域代码
                        XWPFRun newRun = paragraph.insertNewRun(paragraph.getRuns().indexOf(run));
                        newRun.getCTR().addNewInstrText().setStringValue(fieldCodeSb.toString());
                        inFieldCode = false;
                        runsToRemove.add(run);
                    } else if (fldChar.getFldCharType() == org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType.END) {
                        runsToRemove.add(run);
                    }
                }
            }
            if (inFieldCode && run.getCTR().getInstrText() != null) {
                fieldCodeSb.append(run.getCTR().getInstrText().getStringValue());
                runsToRemove.add(run);
            }
        }

        // 移除旧的域相关Run
        for (XWPFRun run : runsToRemove) {
            int index = paragraph.getRuns().indexOf(run);
            if (index != -1) {
                paragraph.removeRun(index);
            }
        }
    }
}

在replaceMacrosInDocument方法中调用该方法,即可替换目录域内的占位符。

完整修改后的代码

import java.awt.Desktop;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFFooter;
import org.apache.poi.xwpf.usermodel.XWPFHeader;
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;

public class WordMacroReplacement {

    public static void main(String[] args) {
        try {
            FileInputStream fis = new FileInputStream("C:\\Users\\prsh01\\Downloads\\Test.docx");
            XWPFDocument document = new XWPFDocument(fis);

            Map<String, String> macroReplacements = new HashMap<>();
            macroReplacements.put("«SOURCE_VERSION»", "2018");
            macroReplacements.put("«TARGET_VERSION»", "2023");
            macroReplacements.put("«CLIENT_NAME»", "MyClient");

            replaceMacrosInDocument(document, macroReplacements);
            updateTableOfContents(document);

            File targetFile = new File("Updated.docx");
            FileOutputStream fos = new FileOutputStream(targetFile);
            document.write(fos);

            fis.close();
            fos.close();
            Desktop.getDesktop().open(targetFile);
            System.out.println("Word document updated successfully.");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void replaceMacrosInDocument(XWPFDocument document, Map<String, String> replacements) {
        // 替换普通段落
        for (XWPFParagraph paragraph : document.getParagraphs()) {
            replaceMacrosInParagraph(paragraph, replacements);
        }

        // 替换表格内段落
        for (XWPFTable table : document.getTables()) {
            for (XWPFTableRow row : table.getRows()) {
                for (XWPFTableCell cell : row.getTableCells()) {
                    for (XWPFParagraph paragraph : cell.getParagraphs()) {
                        replaceMacrosInParagraph(paragraph, replacements);
                    }
                }
            }
        }

        // 替换页眉页脚
        for (XWPFHeader header : document.getHeaderList()) {
            for (XWPFParagraph paragraph : header.getParagraphs()) {
                replaceMacrosInParagraph(paragraph, replacements);
            }
        }
        for (XWPFFooter footer : document.getFooterList()) {
            for (XWPFParagraph paragraph : footer.getParagraphs()) {
                replaceMacrosInParagraph(paragraph, replacements);
            }
        }

        // 替换域内占位符
        replaceMacrosInFields(document, replacements);
    }

    private static void replaceMacrosInParagraph(XWPFParagraph paragraph, Map<String, String> replacements) {
        StringBuilder fullTextSb = new StringBuilder();
        for (XWPFRun run : paragraph.getRuns()) {
            String text = run.getText(0);
            if (text != null) {
                fullTextSb.append(text);
            }
        }
        String fullText = fullTextSb.toString();

        boolean hasChanged = false;
        for (Map.Entry<String, String> entry : replacements.entrySet()) {
            if (fullText.contains(entry.getKey())) {
                fullText = fullText.replace(entry.getKey(), entry.getValue());
                hasChanged = true;
            }
        }

        if (hasChanged) {
            while (!paragraph.getRuns().isEmpty()) {
                paragraph.removeRun(0);
            }
            XWPFRun newRun = paragraph.createRun();
            newRun.setFontFamily("宋体");
            newRun.setFontSize(14);
            newRun.setText(fullText, 0);
        }
    }

    private static void replaceMacrosInFields(XWPFDocument document, Map<String, String> replacements) {
        for (XWPFParagraph paragraph : document.getParagraphs()) {
            List<XWPFRun> runsToRemove = new ArrayList<>();
            StringBuilder fieldCodeSb = new StringBuilder();
            boolean inFieldCode = false;

            for (XWPFRun run : paragraph.getRuns()) {
                if (run.getCTR().getFldCharList() != null) {
                    for (org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFldChar fldChar : run.getCTR().getFldCharList()) {
                        if (fldChar.getFldCharType() == org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType.BEGIN) {
                            inFieldCode = true;
                            runsToRemove.add(run);
                        } else if (fldChar.getFldCharType() == org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType.SEPARATE) {
                            for (Map.Entry<String, String> entry : replacements.entrySet()) {
                                fieldCodeSb = new StringBuilder(fieldCodeSb.toString().replace(entry.getKey(), entry.getValue()));
                            }
                            XWPFRun newRun = paragraph.insertNewRun(paragraph.getRuns().indexOf(run));
                            newRun.getCTR().addNewInstrText().setStringValue(fieldCodeSb.toString());
                            inFieldCode = false;
                            runsToRemove.add(run);
                        } else if (fldChar.getFldCharType() == org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType.END) {
                            runsToRemove.add(run);
                        }
                    }
                }
                if (inFieldCode && run.getCTR().getInstrText() != null) {
                    fieldCodeSb.append(run.getCTR().getInstrText().getStringValue());
                    runsToRemove.add(run);
                }
            }

            for (XWPFRun run : runsToRemove) {
                int index = paragraph.getRuns().indexOf(run);
                if (index != -1) {
                    paragraph.removeRun(index);
                }
            }
        }
    }

    private static void updateTableOfContents(XWPFDocument document) {
        for (XWPFParagraph paragraph : document.getParagraphs()) {
            for (XWPFRun run : paragraph.getRuns()) {
                if (run.getCTR().getFldCharList() != null) {
                    for (org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFldChar fldChar : run.getCTR().getFldCharList()) {
                        if (fldChar.getFldCharType() == org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType.UPDATE) {
                            fldChar.setDirty(true);
                        }
                    }
                }
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者pranavs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 18:34:56