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

Apache POI:如何提取批注对应的高亮选中文本?

提取Word文档批注及对应高亮选中文本(Apache POI实现)

你当前的代码已经能成功提取批注内容,但要获取批注对应的高亮选中文本,需要利用Word文档中CommentRangeStart和CommentRangeEnd的配对关系——这两个标记分别定义了批注覆盖文本的起始和结束位置,且二者拥有相同的ID。

下面是修改后的代码,实现同时提取批注文本和对应的高亮选中文本:

import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTMarkupRange;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;

import java.io.FileInputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CommentExtractor {
    public static void main(String[] args) throws Exception {
        String fileName = "test.docx";
        FileInputStream fis = new FileInputStream(fileName);
        XWPFDocument xdoc = new XWPFDocument(OPCPackage.open(fis));
        
        // 先收集所有段落中的CommentRangeEnd,按ID存储
        Map<String, CTMarkupRange> endMarkupMap = new HashMap<>();
        for (XWPFParagraph p : xdoc.getParagraphs()) {
            List<CTMarkupRange> endMarkups = p.getCTP().getCommentRangeEndList();
            for (CTMarkupRange endMarkup : endMarkups) {
                endMarkupMap.put(endMarkup.getId().toString(), endMarkup);
            }
        }
        
        // 遍历段落处理CommentRangeStart,匹配对应End并提取文本
        for (XWPFParagraph p : xdoc.getParagraphs()) {
            List<CTMarkupRange> startMarkups = p.getCTP().getCommentRangeStartList();
            for (CTMarkupRange startMarkup : startMarkups) {
                String commentId = startMarkup.getId().toString();
                XWPFComment comment = xdoc.getCommentByID(commentId);
                CTMarkupRange endMarkup = endMarkupMap.get(commentId);
                
                if (endMarkup != null) {
                    // 提取当前段落中Start和End之间的文本
                    StringBuilder highlightedText = new StringBuilder();
                    List<CTR> runs = p.getCTP().getRList();
                    boolean isInRange = false;
                    
                    for (CTR run : runs) {
                        // 检查当前run是否进入批注范围
                        if (run.getCommentRangeStartList().stream().anyMatch(s -> s.getId().equals(startMarkup.getId()))) {
                            isInRange = true;
                        }
                        if (isInRange) {
                            highlightedText.append(run.xmlText().replaceAll("<[^>]+>", "")); // 提取纯文本
                        }
                        // 检查当前run是否离开批注范围
                        if (run.getCommentRangeEndList().stream().anyMatch(e -> e.getId().equals(endMarkup.getId()))) {
                            isInRange = false;
                            break;
                        }
                    }
                    
                    // 输出结果
                    System.out.println("批注内容:" + comment.getText());
                    System.out.println("对应高亮文本:" + highlightedText.toString().trim());
                    System.out.println("------------------------");
                }
            }
        }
        
        xdoc.close();
        fis.close();
    }
}

关键说明

  • ID配对逻辑:每个批注的起始和结束标记共享同一个ID,通过Map提前存储所有结束标记,可快速匹配对应范围
  • 文本范围提取:遍历段落中的文本Run(CTR),判断当前Run是否处于批注标记的起止区间内,收集这段范围内的文本
  • 跨段落扩展:上述代码仅处理同段落内的批注文本,如果批注跨多个段落,需要额外遍历后续段落,直到找到对应的CommentRangeEnd标记,可基于现有逻辑扩展实现

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 17:55:20