如何使用POI获取嵌套在sdt/sdtContent中的Word文本Run?
递归获取Word段落中所有Run元素(含SDT嵌套内容)
在POI 5.2.5中,XWPFParagraph.getIRuns()只能获取段落直接嵌套的Run元素,对于嵌套在**内容控件(XWPFSDT)**里的Run,由于XWPFSDTContent的bodyElements是私有变量,无法直接访问,可通过以下两种方式递归获取所有Run:
方法一:利用底层XML结构(推荐,兼容性好)
通过XWPFSDTContent.getCTSdtContent()获取底层XML对象,遍历其中的段落节点并转换为XWPFParagraph,再递归收集Run元素:
import org.apache.poi.xwpf.usermodel.*; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSdtContent; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP; import java.util.ArrayList; import java.util.List; public static List<XWPFRun> getAllRunsFromParagraph(XWPFParagraph paragraph) { List<XWPFRun> runs = new ArrayList<>(); for (IRunElement runElement : paragraph.getIRuns()) { if (runElement instanceof XWPFRun run) { // 直接收集普通Run runs.add(run); } else if (runElement instanceof XWPFSDT sdt) { Object content = sdt.getContent(); if (content instanceof XWPFSDTContent sdtContent) { CTSdtContent ctSdtContent = sdtContent.getCTSdtContent(); // 遍历SDT内容中的所有段落节点 for (CTP ctP : ctSdtContent.getPList()) { // 将XML节点转换为XWPFParagraph对象 XWPFParagraph nestedPara = new XWPFParagraph(ctP, paragraph.getBody()); // 递归收集嵌套段落中的Run runs.addAll(getAllRunsFromParagraph(nestedPara)); } // 若SDT中包含表格等其他元素,可在此扩展处理逻辑 } } } return runs; }
使用示例:
final Iterator<XWPFParagraph> it = doc.getParagraphsIterator(); while (it.hasNext()) { XWPFParagraph paragraph = it.next(); List<XWPFRun> allRuns = getAllRunsFromParagraph(paragraph); // 处理所有收集到的Run for (XWPFRun run : allRuns) { String text = run.getText(0); // 执行你的业务逻辑,比如打印文本、修改样式等 if (text != null) { System.out.println(text); } } }
方法二:反射获取私有变量(简洁但依赖内部实现)
通过Java反射机制直接访问XWPFSDTContent的私有bodyElements变量,适合快速开发,但需注意POI版本更新可能导致变量名变更:
import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import org.apache.poi.xwpf.usermodel.*; // 反射工具方法:获取XWPFSDTContent的私有bodyElements private static List<IBodyElement> getSDTContentBodyElements(XWPFSDTContent sdtContent) throws Exception { Field field = XWPFSDTContent.class.getDeclaredField("bodyElements"); field.setAccessible(true); return (List<IBodyElement>) field.get(sdtContent); } // 递归收集所有Run public static List<XWPFRun> getAllRunsFromParagraph(XWPFParagraph paragraph) throws Exception { List<XWPFRun> runs = new ArrayList<>(); for (IRunElement runElement : paragraph.getIRuns()) { if (runElement instanceof XWPFRun run) { runs.add(run); } else if (runElement instanceof XWPFSDT sdt) { Object content = sdt.getContent(); if (content instanceof XWPFSDTContent sdtContent) { List<IBodyElement> bodyElements = getSDTContentBodyElements(sdtContent); for (IBodyElement element : bodyElements) { if (element instanceof XWPFParagraph nestedPara) { runs.addAll(getAllRunsFromParagraph(nestedPara)); } } } } } return runs; }
使用时需处理反射可能抛出的异常(如NoSuchFieldException、IllegalAccessException)。
内容的提问来源于stack exchange,提问作者Jesse Barnum
相关产品推荐
相关产品推荐

