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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 21:36:34