如何用Jsoup将含有序/无序列表的HTML转为保留格式的纯文本?
Jsoup HTML转纯文本保留格式解决方案
直接用wholeText()会丢失列表、段落等结构,要保留格式需要自定义节点遍历逻辑,针对不同HTML标签做针对性处理,以下是实用实现:
核心思路
- 移除不需要的元素(图片、脚本、样式、内嵌框架)
- 递归遍历节点,根据标签类型添加格式标记:
- 无序列表项(
<li>)添加-前缀 - 有序列表项(
<li>)添加数字前缀(如1.) - 列表(
<ul>/<ol>)增加缩进层级 - 块级元素(
<p>/<div>/<h1~h6>)前后添加换行 - 粗体/斜体标签对应转换为
**/*包裹的文本 - 引用标签添加
>前缀
- 无序列表项(
实现代码
import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; import org.jsoup.nodes.TextNode; import java.util.List; public class HtmlToPlainText { public static String convert(String html) { Document doc = Jsoup.parse(html); // 移除无需保留的元素 doc.select("img, script, style, iframe").remove(); StringBuilder result = new StringBuilder(); traverseNode(doc.body(), result, 0); return result.toString().trim(); } private static void traverseNode(Node node, StringBuilder sb, int indentLevel) { if (node instanceof TextNode) { String text = ((TextNode) node).text().trim(); if (!text.isEmpty()) { // 列表项添加缩进 if (indentLevel > 0) { sb.append(" ".repeat(indentLevel)); } sb.append(text); } return; } if (!(node instanceof Element)) { return; } Element elem = (Element) node; String tag = elem.tagName().toLowerCase(); boolean isBlockElement = List.of("p", "div", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol").contains(tag); // 块级元素前加换行(避免连续无间隔) if (isBlockElement && sb.length() > 0 && !endsWithNewLine(sb)) { sb.append("\n"); } switch (tag) { case "li": // 父节点是ol则用数字前缀,否则用减号 boolean isOrderedList = "ol".equals(elem.parent().tagName()); if (isOrderedList) { int index = elem.siblingIndex() + 1; sb.append(index).append(". "); } else { sb.append("- "); } // 遍历li的子节点 for (Node child : elem.childNodes()) { traverseNode(child, sb, indentLevel); } break; case "ul": case "ol": // 列表内部子项增加缩进 for (Node child : elem.childNodes()) { traverseNode(child, sb, indentLevel + 1); // 每个列表项后加换行 if (child instanceof Element && "li".equals(((Element) child).tagName())) { sb.append("\n"); } } break; case "b": case "strong": // 粗体用**包裹 sb.append("**"); for (Node child : elem.childNodes()) { traverseNode(child, sb, indentLevel); } sb.append("**"); break; case "i": case "em": // 斜体用*包裹 sb.append("*"); for (Node child : elem.childNodes()) { traverseNode(child, sb, indentLevel); } sb.append("*"); break; case "blockquote": // 引用用>前缀 sb.append("> "); for (Node child : elem.childNodes()) { traverseNode(child, sb, indentLevel); } break; default: // 其他元素直接遍历子节点 for (Node child : elem.childNodes()) { traverseNode(child, sb, indentLevel); } break; } // 块级元素后加换行(列表本身除外) if (isBlockElement && !List.of("ul", "ol").contains(tag) && !endsWithNewLine(sb)) { sb.append("\n"); } } private static boolean endsWithNewLine(StringBuilder sb) { return sb.length() > 0 && sb.charAt(sb.length() - 1) == '\n'; } }
使用效果示例
原HTML:
<div> <p>这是一段段落文本</p> <ul> <li>无序列表项1</li> <li>无序列表项2</li> </ul> <ol> <li>有序列表项1</li> <li>有序列表项2</li> </ol> <p>带<strong>粗体</strong>和<em>斜体</em>的文本</p> </div>转换后纯文本:
这是一段段落文本 - 无序列表项1 - 无序列表项2 1. 有序列表项1 2. 有序列表项2 带**粗体**和*斜体*的文本
内容的提问来源于stack exchange,提问作者Johannes Pertl
相关产品推荐
相关产品推荐

