使用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 </strong>2</li> <li><em>UL 3</em></li> </ul> <ol> <li style="font-weight: bold;"><strong>First statement</strong></li> <li><strong>Second </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 </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 </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 </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
相关产品推荐
相关产品推荐

