以递归方式遍历嵌套的 HTML 列表

Traversing nested HTML lists recursively

提问人:speci5 提问时间:6/10/2022 最后编辑:Janez Kuharspeci5 更新时间:6/12/2022 访问量:340

问:

我无法获取下面的 HTML 并将其放入如下列表中:

List<String> output = Arrays.asList(
    new String[] {
        "First Level-Second Level--Third Level",
        "a-b--c1",
        "a-b--c2",
        "a-b--c3",
        "a-b2--c1",
        "a-b2--c2",
        "a-b2--c3"
    });

<ul>
    <li>First Level</li>
    <ul>
        <li>Second Level</li>
        <ul>
            <li>Third Level</li>
        </ul>
    </ul>
    <li>a</li>
    <ul>
        <li>b</li>
        <ul>
            <li>c</li>
            <li>c</li>
            <li>c</li>
        </ul>
        <li>b2</li>
        <ul>
            <li>c1</li>
            <li>c2</li>
            <li>c3</li>
        </ul>
    </ul>
</ul>

我已将其加载到 Jsoup 中以像元素一样遍历它们。我的代码如下:

public static String recHTML(Element element, String str) { 
    str += "-" + element.ownText(); 
    if (element.children().size() == 0) return str + "--" + element.ownText(); 
    else { 
        for (int i = 0; i < element.children().size(); i++) { 
            Element next = element.child(i); 
            str += recHTML(next, str); 
        }
    }
}

我将返回带有破折号的字符串,以使用正则表达式将它们拆分为一个数组以具有不同级别的缩进。我正在努力让我的输出匹配,无论我尝试什么,我都无法让它工作。任何帮助将不胜感激,谢谢。

Java HTML 递归 jsoup 嵌套列表

评论

0赞 Janez Kuhar 6/12/2022
你的不编译。您是否缺少退货声明?recHTML
0赞 Janez Kuhar 6/12/2022
您是否认为这样的东西是方法的有效 HTML 输入:?<ul><ul><li>a</li></ul><li>b</li></ul>
0赞 speci5 6/12/2022
是的,我相信是这样
0赞 Janez Kuhar 6/12/2022
但这不被认为是有效的 HTML,您的示例也不被认为是有效的。请参阅 stackoverflow.com/questions/5899337/...
0赞 Janez Kuhar 6/12/2022
我认为,一旦您将 HTML 编辑为在验证器的帮助下有效的内容(例如,这个),您的任务将大大简化。

答:

1赞 Janez Kuhar 6/12/2022 #1

假设您创建了一个类似 bellow 的程序存根:


import java.util.ArrayList;
import java.util.List;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;

public class App {
    private static List<String> output = new ArrayList<String>();
    public static void main(String[] args) {
        Element input = Jsoup.parse(/*your HTML list*/).selectFirst("body").child(0);
        recHTML(input, "", "");
        System.out.println(output);
    }

    public static void recHTML(Element element, String chain, String link) {
        // Todo! 
    }
}

现在让我们考虑一下无效的 HTML 示例及其有效的等效项

无效的 HTML 输入列表

<ul>
    <li>First Level</li>
    <ul>
        <li>Second Level</li>
        <ul>
            <li>Third Level</li>
        </ul>
    </ul>
    <li>a</li>
    <ul>
        <li>b</li>
        <ul>
            <li>c</li>
            <li>c</li>
            <li>c</li>
        </ul>
        <li>b2</li>
        <ul>
            <li>c1</li>
            <li>c2</li>
            <li>c3</li>
        </ul>
    </ul>
</ul>

目前,你正在尝试实现深度优先搜索 (DFS) 之类的功能。典型的DFS是行不通的。为什么?因为您提供的 HTML 结构具有多个不终止的叶节点。

假设我们将你的每个元素称为。当您以 DFS 方式遍历 HTML 树时,请注意,是完整的 一旦你找到一个没有兄弟姐妹的元素。这应该是你的最终条件。至于递归步骤,请考虑如何处理和元素。定义并不容易;目前,我的解决方案仅适用于您的特定输入。outputliululli

你的实现注定是丑陋的,因为从根节点到叶节点没有直接的嵌套。新的边缘情况将不断出现,您的代码将不起作用,您将继续进行编辑。recHTML

public static void recHTML(Element element, String chain, String link) {
    if (element.parent().children().select("ul").isEmpty()) {
        output.add(chain);
    }

    for (int i = 0; i < element.children().size(); i++) {
        Element next = element.child(i);
        if (next.tagName().equals("ul")) {
            recHTML(next, chain  + link + element.child(i - 1).ownText(), link + '-');
        } else {
            recHTML(next, chain  + link + next.ownText(), link);
        }
    }
}

有效的 HTML 输入列表

<ul>
  <li>
    First level
    <ul>
      <li>
        Second Level
        <ul>
          <li>Third Level</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    a
    <ul>
      <li>
        b
        <ul>
          <li>c</li>
          <li>c</li>
          <li>c</li>
        </ul>
      </li>
      <li>
        b2
        <ul>
          <li>c1</li>
          <li>c2</li>
          <li>c3</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

正如我的评论中所建议的,您的问题出在输入中。假设我们以某种方式将您的输入 HTML 转换为上述标记。然后解决方案变得更干净、更直接 - 一个简单的 DFS 就可以了!

public static void recHTML(Element element, String chain, String link) { 
    if (element.children().isEmpty()) {
        output.add(chain);
    }

    for (Element child : element.children()) {
        if (child.tagName().equals("li")) {
            recHTML(child, chain + link + child.ownText(), link + "-");
        } else {
            recHTML(child, chain, link);
        }
    }
}

有趣的事实:Java HTML 整洁工具 JTidy 以与我略有不同的方式更正了您的 HTML 输入:

<!-- original snippet -->
<li>First Level</li>
<ul>
  <li>Second Level</li>
  <!-- omitted elements for brevity -->
</ul>

<!-- snippet, corrected by jtidy -->
<li>First Level</li>
<li style="list-style: none">
  <ul>
    <li>Second Level</li>
    <!-- omitted elements for brevity -->
  </ul>
</li>

就个人而言,我不认为它以正确的方式纠正它 - 制作 HTML 嵌套列表的正确方法?