如何为同一段落设置不同样式?Apache POI HTML转Word表格样式问题
嘿,这个问题我熟!你现在的核心问题是只用了一个XWPFRun来处理所有文本,但Word里每个Run只能对应一套样式——所以嵌套的HTML标签(比如<i>里套<b>)肯定没法正确渲染。咱们得换个思路:把HTML拆成一个个带样式的文本片段,每个片段对应一个独立的XWPFRun,然后给每个Run设置对应的属性。
核心思路
HTML是嵌套结构,手动用indexOf处理嵌套标签会非常麻烦,我推荐用轻量的HTML解析库Jsoup来帮我们遍历节点,这样能轻松处理嵌套、转义字符这些问题。核心逻辑是:解析HTML的每个文本节点,根据它所在的标签层级,为它创建对应的XWPFRun并设置样式。
步骤1:引入Jsoup依赖(Maven为例)
<dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.17.2</version> <!-- 用最新稳定版即可 --> </dependency>
步骤2:编写递归处理HTML节点的方法
这个方法会遍历Jsoup解析后的HTML节点,为每个文本片段创建XWPFRun,并根据父标签的样式设置Run的属性:
private static void processElement(Element element, XWPFParagraph paragraph, int baseFontSize) { // 遍历当前节点的所有子节点 for (Node node : element.childNodes()) { if (node instanceof TextNode) { // 如果是文本节点,创建Run并设置当前样式 TextNode textNode = (TextNode) node; String text = textNode.text().trim(); if (text.isEmpty()) continue; // 跳过空文本 XWPFRun run = paragraph.createRun(); run.setFontSize(baseFontSize); // 从当前节点往上遍历,收集所有生效的样式 Element current = element; while (current != null) { switch (current.tagName()) { case "b": case "strong": run.setBold(true); break; case "i": case "em": run.setItalic(true); break; case "font": // 处理font标签的size和color属性 String fontSizeStr = current.attr("size"); if (!fontSizeStr.isEmpty()) { try { run.setFontSize(Integer.parseInt(fontSizeStr)); } catch (NumberFormatException e) { // 无效的字体大小,用默认值 run.setFontSize(baseFontSize); } } String color = current.attr("color"); if (!color.isEmpty()) { run.setColor(color); } break; // 可以继续扩展其他标签,比如u(下划线)、strike(删除线)等 } current = current.parent(); } run.setText(text); } else if (node instanceof Element) { // 如果是元素节点,递归处理 Element childElement = (Element) node; if ("br".equals(childElement.tagName())) { // 处理换行标签,添加换行符 XWPFRun run = paragraph.createRun(); run.addBreak(); } else { processElement(childElement, paragraph, baseFontSize); } } } }
步骤3:修改你的
getTableParagraph方法 现在用Jsoup解析HTML文本,然后调用上面的处理方法:
private static XWPFParagraph getTableParagraph(XWPFTableCell cell, String htmlText) { int baseFontSize = 11; XWPFParagraph paragraph = cell.addParagraph(); cell.removeParagraph(0); // 移除默认创建的空段落 paragraph.setSpacingAfterLines(0); paragraph.setSpacingAfter(0); if (htmlText == null || htmlText.isEmpty()) { // 处理空文本,创建一个空Run XWPFRun emptyRun = paragraph.createRun(); emptyRun.setFontSize(baseFontSize); return paragraph; } // 把转义的<和>转成真实的HTML标签 String unescapedHtml = org.jsoup.parser.Parser.unescapeEntities(htmlText, true); Document doc = Jsoup.parseBodyFragment(unescapedHtml); Element body = doc.body(); // 递归处理所有节点 processElement(body, paragraph, baseFontSize); return paragraph; }
手动解析的思路(如果不想用Jsoup)
如果不想引入第三方库,你需要用栈结构来跟踪当前的样式状态:
- 遍历HTML文本,遇到开始标签(比如
<b>)时,把对应的样式(加粗)压入栈。 - 遇到结束标签(比如
</b>)时,把对应的样式从栈中弹出。 - 遇到文本片段时,创建一个
XWPFRun,应用栈中所有当前有效的样式(比如同时有斜体和加粗),然后设置文本。 - 处理
<br>时添加换行。
不过手动处理需要考虑很多边界情况:比如标签的嵌套顺序、转义字符、不闭合的标签等等,所以还是推荐用Jsoup更省心。
测试你的示例文本
对于你给出的例子:
This is my text <i> which now is in italic<b> but also in bold</b> depending on its importance</i>
用上面的代码处理后,会生成4个Run:
- "This is my text ":默认样式(11号字,无加粗斜体)
- " which now is in italic":斜体
- " but also in bold":斜体+加粗
- " depending on its importance":斜体
完全符合你的需求!
内容的提问来源于stack exchange,提问作者Gerard Martinelli
相关产品推荐
相关产品推荐

