使用Apache POI提取.doc文件关键词间文本并实现条件渲染的方案咨询
基于Apache POI实现Word条件块渲染的可靠方案
核心解决思路是绕过直接匹配XWPFRun/HWPFRun的限制,先构建文本和字符位置的映射关系,再基于纯文本匹配标签位置,最后反查对应文本片段所属的Run进行修改,完全不受Run随机拆分的影响。
具体实现步骤
1. 构建字符位置映射表
不管是处理.doc(HWPF)还是.docx(XWPF)格式,都先按以下逻辑处理每个段落:
- 拼接当前段落所有Run的文本内容,得到段落完整纯文本
- 同时记录每个字符对应的「所属Run对象、在Run内的偏移下标」,形成映射数组,后续只要拿到纯文本中任意字符的下标,就能直接定位到它所在的Run和具体位置
2. 精准匹配条件标签
在拼接得到的纯文本中用正则匹配${if 条件}和${endif}的起止下标:
- 正则参考:
\$\{if\s+([^}]+)\}匹配if标签,\$\{endif\}匹配结束标签 - 如果存在跨段落的条件块,可以把整个文档所有段落的文本按顺序拼接为全局文本流,同时记录每个字符对应的「所属段落、所属Run、Run内偏移」即可实现跨段落匹配
- 存在嵌套条件块的场景,用栈结构来配对if和endif,避免配对错误
3. 按需修改文档内容
根据条件判断结果,对定位到的文本区间做处理:
- 条件满足时:只删除
${if 条件}和${endif}两个标签对应的字符,保留两个标签中间的所有内容 - 条件不满足时:直接删除从
${if 条件}起始位置到${endif}结束位置之间的所有字符
注意:如果一个Run只有部分内容在待删除区间,只删除对应偏移的字符即可;如果Run的全部内容都在待删除区间,直接移除整个Run提升效率
核心代码示例(XWPF场景)
// 定义字符位置映射类 class CharPosition { XWPFRun run; int runOffset; public CharPosition(XWPFRun run, int runOffset) { this.run = run; this.runOffset = runOffset; } } // 处理单个段落的条件块 public void processParagraph(XWPFParagraph paragraph, Map<String, Boolean> conditionMap) { List<CharPosition> charPosList = new ArrayList<>(); StringBuilder fullText = new StringBuilder(); // 构建映射表和全量文本 for (XWPFRun run : paragraph.getRuns()) { String text = run.getText(0); if (text == null) text = ""; for (int i = 0; i < text.length(); i++) { charPosList.add(new CharPosition(run, i)); fullText.append(text.charAt(i)); } } // 正则匹配if和endif标签,这里简化为单组标签的场景,嵌套场景自行加栈处理 Pattern ifPattern = Pattern.compile("\\$\\{if\\s+([^}]+)\\}"); Matcher ifMatcher = ifPattern.matcher(fullText); if (!ifMatcher.find()) return; String condition = ifMatcher.group(1).trim(); int ifStart = ifMatcher.start(); int ifEnd = ifMatcher.end(); Pattern endPattern = Pattern.compile("\\$\\{endif\\}"); Matcher endMatcher = endPattern.matcher(fullText); if (!endMatcher.find(ifEnd)) return; int endStart = endMatcher.start(); int endEnd = endMatcher.end(); Boolean conditionPass = conditionMap.getOrDefault(condition, false); if (conditionPass) { // 条件满足,只删if和endif标签 deleteRange(charPosList, ifStart, ifEnd); deleteRange(charPosList, endStart, endEnd); } else { // 条件不满足,删整个区间 deleteRange(charPosList, ifStart, endEnd); } } // 删除指定区间的字符 private void deleteRange(List<CharPosition> charPosList, int start, int end) { // 按Run分组处理需要删除的字符,避免重复修改同一个Run Map<XWPFRun, List<Integer>> runToDeleteOffsets = new HashMap<>(); for (int i = start; i < end; i++) { CharPosition cp = charPosList.get(i); runToDeleteOffsets.computeIfAbsent(cp.run, k -> new ArrayList<>()).add(cp.runOffset); } // 每个Run的删除下标倒序处理,避免前面删除导致后面下标偏移 for (Map.Entry<XWPFRun, List<Integer>> entry : runToDeleteOffsets.entrySet()) { XWPFRun run = entry.getKey(); List<Integer> offsets = entry.getValue(); offsets.sort(Collections.reverseOrder()); StringBuilder sb = new StringBuilder(run.getText(0)); for (int offset : offsets) { sb.deleteCharAt(offset); } if (sb.length() == 0) { // Run内容为空直接移除 run.getParent().removeRun(run); } else { run.setText(sb.toString(), 0); } } }
.doc格式(HWPF)的处理逻辑完全一致,仅需将
XWPFParagraph替换为Paragraph,XWPFRun替换为CharacterRun即可适配。
方案优势
完全不受Word内部Run拆分逻辑的影响,只要文档中存在对应的标签文本,就能100%精准定位,兼容性和可靠性远高于直接匹配Run的方案。
内容的提问来源于stack exchange,提问作者NickAth
相关产品推荐
相关产品推荐

