如何用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
相关产品推荐
相关产品推荐

