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

使用Apache POI将HTML转Excel文本框富文本的嵌套标签解析问题

HTML转Excel文本框富文本实现修复

需求说明

将给定的带格式HTML字符串转换为Excel文本框内的富文本,示例输入HTML如下:

<p><strong>This is a text Message.</strong></p>
<ul>
    <li>UL 1</li>
    <li><strong>UL&nbsp;</strong>2</li>
    <li><em>UL 3</em></li>
</ul>
<ol>
    <li style="font-weight: bold;"><strong>First statement</strong></li>
    <li><strong>Second&nbsp;</strong>Statement</li>
    <li>Third <strong>Statement</strong></li>
</ol>
<p>This is another <em>text </em>message.</p>

原代码问题分析

原实现采用全元素扁平遍历的逻辑,document.body().children().select("*")会把所有DOM元素按父元素优先、子元素后置的顺序返回,导致嵌套标签内的文本顺序颠倒:比如<li><strong>UL&nbsp;</strong>2</li>会先取li的自有文本2,再取strong的自有文本UL,最终拼接为2 UL。同时原逻辑只提取文本没有保留格式信息,无法实现富文本转换。

修复方案

改用深度优先遍历DOM节点的方式,按文本实际出现顺序处理内容,同时维护格式栈记录当前文本的样式,最终输出带格式标记的文本片段,可直接对接Apache POI等Excel操作库的富文本接口。

修复后代码示例

import org.apache.poi.xssf.usermodel.XSSFFont;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.nodes.Element;

import java.util.Stack;
import java.util.Set;

public void CreateHtmlToRichText() {
    String htmlString = "<p><strong>This is a text Message.</strong></p>\n" +
            "<ul>\n" +
            "    <li>UL 1</li>\n" +
            "    <li><strong>UL&nbsp;</strong>2</li>\n" +
            "    <li><em>UL 3</em></li>\n" +
            "</ul>\n" +
            "<ol>\n" +
            "    <li style=\"font-weight: bold;\"><strong>First statement</strong></li>\n" +
            "    <li><strong>Second&nbsp;</strong>Statement</li>\n" +
            "    <li>Third <strong>Statement</strong></li>\n" +
            "</ol>\n" +
            "<p>This is another <em>text </em>message.</p>";
    
    Document document = Jsoup.parse(htmlString);
    StringBuilder contentBuilder = new StringBuilder();
    Stack<XSSFFont> formatStack = new Stack<>();
    // 初始化不同样式的POI字体,可根据实际需求扩展
    XSSFFont defaultFont = new XSSFFont();
    XSSFFont boldFont = new XSSFFont();
    boldFont.setBold(true);
    XSSFFont italicFont = new XSSFFont();
    italicFont.setItalic(true);
    formatStack.push(defaultFont);
    
    // 触发换行的标签集合
    Set<String> newLineTags = Set.of("p", "li", "br");
    
    // 深度优先遍历所有节点
    traverseNode(document.body(), contentBuilder, formatStack, newLineTags, boldFont, italicFont);
    
    // 生成Excel富文本对象,可直接赋值给文本框
    XSSFRichTextString richText = new XSSFRichTextString(contentBuilder.toString().trim());
    // 可根据遍历过程中记录的文本片段位置+字体信息,调用richText.applyFont()设置对应位置样式
    // 以下为纯文本输出验证
    System.out.println(contentBuilder.toString().trim());
}

private void traverseNode(Node node, StringBuilder contentBuilder, Stack<XSSFFont> formatStack,
                         Set<String> newLineTags, XSSFFont boldFont, XSSFFont italicFont) {
    if (node instanceof TextNode) {
        // 处理文本节点,直接按顺序追加
        String text = ((TextNode) node).text();
        if (!text.isBlank()) {
            contentBuilder.append(text);
            // 可在此记录当前文本的起止索引、对应formatStack.peek()的字体,后续用于富文本样式设置
        }
        return;
    }
    
    if (node instanceof Element) {
        Element element = (Element) node;
        String tagName = element.tagName();
        boolean pushedFormat = false;
        
        // 遇到格式标签,将对应字体压入栈
        if ("strong".equals(tagName) || "b".equals(tagName) 
                || (element.hasAttr("style") && element.attr("style").contains("font-weight: bold"))) {
            formatStack.push(boldFont);
            pushedFormat = true;
        } else if ("em".equals(tagName) || "i".equals(tagName)) {
            formatStack.push(italicFont);
            pushedFormat = true;
        }
        
        // 递归遍历所有子节点,保证文本顺序正确
        for (Node child : node.childNodes()) {
            traverseNode(child, contentBuilder, formatStack, newLineTags, boldFont, italicFont);
        }
        
        // 标签处理完成,弹出格式栈恢复父级样式
        if (pushedFormat) {
            formatStack.pop();
        }
        
        // 触发换行
        if (newLineTags.contains(tagName)) {
            contentBuilder.append("\n");
        }
    }
}

正确输出验证

运行修复后代码的纯文本输出如下,顺序完全符合预期:

This is a text Message.
UL 1
UL 2
UL 3
First statement
Second Statement
Third Statement
This is another text message.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 21:54:05