在 Java 中从复杂的 HTML 表格中提取数据到二维数组

2024-04-30

如何转换 HTML 表格带有 colspan 和 rowspanJava中的二维数组(矩阵)?

我在 Python 和 jQuery 中找到了很好的解决方案,但在 Java 中却没有(只有通过 jsoup 的非常简单的表)。 XSLT 有一种很好的解决方案,但由于输入 HTML 文件格式错误,这对我来说不合适。

输入表示例:

  <body>
    <table border="1">
        <tr><td>H1</td><td colspan="2">H2</td><tr>
        <tr><td></td><td>SubH2_1</td><td>SubH2_2</td><tr>
       <tr><td rowspan="3">A1</td><td>B1</td><td rowspan="2">C1</td></tr>
       <tr><td rowspan="2">B2</td></tr>
       <tr><td>C3</td></tr>
       <tr><td>C4</td><td>C5</td><td>C6</td></tr>
        <tr><td>D7</td><td colspan="2">D9</td></tr>
        <tr><td  colspan="3">Notes</td></tr>
   </table>
</body>

期望的输出:

    [['H1', 'H2', 'H2'],
     ['', 'SubH2_1', 'SubH2_2'],
     ['A1', 'B1', 'C1'],
     ['A1', 'B2', 'C3'],
     ['C4', 'C5', 'C6'],
     ['D7', 'D9', 'D9'],
     ['Notes', 'Notes', 'Notes']]

我找到了一种方法来使用Jsoup https://jsoup.org/和 Java 8 流 API:

//given:
final InputStream html = getClass().getClassLoader().getResourceAsStream("table.html");

//when:
final Document document = Jsoup.parse(html, "UTF-8", "/");

final List<List<String>> result = document.select("table tr")
    .stream()
    // Select all <td> tags in single row
    .map(tr -> tr.select("td"))
    // Repeat n-times those <td> that have `colspan="n"` attribute
    .map(rows -> rows.stream()
        .map(td -> Collections.nCopies(td.hasAttr("colspan") ? Integer.valueOf(td.attr("colspan")) : 1, td))
        .flatMap(Collection::stream)
        .collect(Collectors.toList())
    )
    // Fold final structure to 2D List<List<Element>>
    .reduce(new ArrayList<List<Element>>(), (acc, row) -> {
        // First iteration - just add current row to a final structure
        if (acc.isEmpty()) {
            acc.add(row);
            return acc;
        }

        // If last array in 2D array does not contain element with `rowspan` - append current
        // row and skip to next iteration step
        final List<Element> last = acc.get(acc.size() - 1);
        if (last.stream().noneMatch(td -> td.hasAttr("rowspan"))) {
            acc.add(row);
            return acc;
        }

        // In this case last array in 2D array contains an element with `rowspan` - we are going to
        // add this element n-times to current rows where n == rowspan - 1
        final AtomicInteger index = new AtomicInteger(0);
        last.stream()
            // Map to a helper list of (index in array, rowspan value or 0 if not present, Jsoup element)
            .map(td -> Arrays.asList(index.getAndIncrement(), Integer.valueOf(td.hasAttr("rowspan") ? td.attr("rowspan") : "0"), td))
            // Filter out all elements without rowspan
            .filter(it -> ((int) it.get(1)) > 1)
            // Add all elements with rowspan to current row at the index they are present 
            // (add them with `rowspan="n-1"`)
            .forEach(it -> {
                final int idx = (int) it.get(0);
                final int rowspan = (int) it.get(1);
                final Element td = (Element) it.get(2);

                row.add(idx, rowspan - 1 == 0 ? (Element) td.removeAttr("rowspan") : td.attr("rowspan", String.valueOf(rowspan - 1)));
            });

        acc.add(row);
        return acc;
    }, (a, b) -> a)
    .stream()
    // Extract inner HTML text from Jsoup elements in 2D array
    .map(tr -> tr.stream()
        .map(Element::text)
        .collect(Collectors.toList())
    )
    .collect(Collectors.toList());

我添加了很多注释来解释特定算法步骤中发生的情况。

在此示例中,我使用了以下 html 文件:

<body>
<table border="1">
    <tr><td>H1</td><td colspan="2">H2</td></tr>
    <tr><td></td><td>SubH2_1</td><td>SubH2_2</td></tr>
    <tr><td rowspan="2">A1</td><td>B1</td><td>C1</td></tr>
    <tr><td>B2</td><td>C3</td></tr>
    <tr><td>C4</td><td>C5</td><td>C6</td></tr>
    <tr><td>D7</td><td colspan="2">D9</td></tr>
    <tr><td  colspan="3">Notes</td></tr>
</table>
</body>

和你的一样,唯一的区别是它有rowspan用法固定 - 在您的示例中A1重复三次而不是两次。还有两个<tr>在此示例中已正确关闭,否则最终结构中会显示两个额外的空数组。

这是控制台输出:

[H1, H2, H2]
[, SubH2_1, SubH2_2]
[A1, B1, C1]
[A1, B2, C3]
[C4, C5, C6]
[D7, D9, D9]
[Notes, Notes, Notes]

您可以使用粘贴到问题中的精确 HTML 来运行此示例,它将产生一些不同的输出:

[H1, H2, H2]
[]
[, SubH2_1, SubH2_2]
[]
[A1, B1, C1]
[A1, B2, C1]
[A1, B2, C3]
[C4, C5, C6]
[D7, D9, D9]
[Notes, Notes, Notes]

出现这些空数组是因为有两个未封闭的数组<tr>HTML 中的元素。

<tr><td>H1</td><td colspan="2">H2</td><tr>
<tr><td></td><td>SubH2_1</td><td>SubH2_2</td><tr>

关闭它们并再次运行算法将创建以下输出:

[H1, H2, H2]
[, SubH2_1, SubH2_2]
[A1, B1, C1]
[A1, B2, C1]
[A1, B2, C3]
[C4, C5, C6]
[D7, D9, D9]
[Notes, Notes, Notes]

如你看到的A1存在3次,因为它有一个属性rowspan="3" and B2 has rowspan="2" and C1 has rowspan="2"以及。它生成的 HTML 看起来“几乎”与我的第一个示例中的 HTML 相同,但是当您仔细查看这 3 行时,您会发现它们不在相同的像素级别。根据您的预期响应,我已修复输入 HTML,使其外观和行为符合您的预期。

如果我无法修改输入 HTML 怎么办?

好吧,如果您无法修改输入 HTML,那么您将不得不:

  • 过滤掉所有由于未关闭而创建的空数组<tr> tags
  • 检查您的输出期望A1, B2 and C3- HTML 视图不显示用 HTML 编写的此表的确切结构。

示例项目源代码

在这里你可以找到完整源代码 https://github.com/wololock/stackoverflow-answers/blob/master/45233038/src/test/java/com/github/wololock/q45233038/JsoupComplexHtmlTableExtractionTest.java我曾经找到过你问题的答案。欢迎下载这个示例 Maven 项目 https://github.com/wololock/stackoverflow-answers/tree/master/45233038托管在 GitHub 上以尝试算法的实现。

我希望它有帮助。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Java 中从复杂的 HTML 表格中提取数据到二维数组 的相关文章

随机推荐

  • 是否可以在 SignalR hub 方法中返回预编码的 JSON 字符串?

    在 MVC 项目中 我在集线器上有一个与此类似的方法 public string Foo return DoCrazyThingThatReturnsJson 不幸的是 SignalR 或其他东西 接受编码的 JSON 字符串并愉快地对其进
  • 使某些代码只运行一次

    我有一些代码只想在 MainViewController 中运行一次 它应该在每次用户启动应用程序时运行 但仅在 MainViewController 加载后运行 我不想运行它 void applicationDidFinishLaunch
  • 在 Google App Engine 上运行的 Spring Boot Web 应用程序 - 引发 jetty 异常

    当我执行时gradlew appengineRun 我一直得到 Starting a Gradle Daemon subsequent builds will be faster Mar 26 2018 5 47 48 PM java ut
  • 来自 alpine 软件包存储库的 Numpy 无法导入 c 扩展

    我正在制作一个需要 pandas 和 numpy 的 docker 映像 但通过 pip 安装大约需要 20 分钟 这对于我的用例来说太长了 然后我选择从 alpine 软件包存储库安装 pandas 和 numpy 但似乎无法正确导入 n
  • Java日期格式转换

    我目前正在从请求对象中提取三个表单字段输入 日 月 年 该月的第 11 天为 Day 11 12 月为 12 月 今年将是 2010 年 我需要将其转换为 Java Date 对象 但由于发生了很多变化 我不确定将其存储在 java 对象中
  • Code::Blocks 中的单个项目中有多个主要 C++ 文件?

    我不经常写代码 有时我连续 6 个月每天编写代码 然后长达 2 年不编写代码 这种方法迫使我保留一堆我 以及其他更好的程序员 编写的代码或参考代码 时隔很长一段时间 我在写代码时都会提到这个 库 我读了它 我执行了它 这对我精神焕发有很大帮
  • 查找 system.out.println 代码中的位置

    假设我正在一个非常大的项目中工作 并且注意到有一个空的打印行 所以我假设有一个 System out println 位于代码中的某个位置 除了在整个项目中搜索所有出现的 System out println 之外 我该如何尝试找出它在哪里
  • 在 C++ 中类继承的情况下强制延迟方法解析

    考虑以下类结构 class foo public int fun cout lt lt in foo lt lt endl class bar class1 public foo public int fun cout lt lt in b
  • 编译 XNA 项目时,无法执行请求的操作错误

    第二次编译项目时 出现以下错误消息 我必须关闭 VS 2010 重新加载时它会编译 如果我做出改变 那么问题又回来了 无法复制文件 obj x86 Debug MyFile dll 无法对打开的用户映射部分的文件执行请求的操作 我因为另一个
  • Swift NSPredicate 不在

    我有一个包含 X 个项目的数组 我需要通过 uid 过滤掉特定项目 我写了以下谓词 我认为它是正确的 我面临的问题是 Swift 编译器只允许我使用接受 argumentArray 的初始化程序 let uids 34885a9f0897f
  • 首选的跨平台 IPC Perl 模块是什么?

    我想创建一个简单的 IO 对象 它代表一个向另一个程序打开的管道 我可以在应用程序运行时定期写入另一个程序的 STDIN 我希望它是防弹的 因为它可以捕获所有错误 并且是跨平台的 我能找到的最佳选择是 open sub io read lo
  • 如何避免 Selenium 中的“StaleElementReferenceException”?

    我正在使用 Java 实现大量 Selenium 测试 有时 我的测试由于以下原因失败StaleElementReferenceException https developer mozilla org en US docs Web Web
  • App Store 上是否允许嵌入 dylib 的 iOS 8 应用程序?

    iOS 8 现在支持动态框架 是否意味着 App Store 提交允许这样做 似乎以前的开发人员能够在内部应用程序中使用 dylib 但在提交到 App Store 的内容中使用它们会导致您被拒绝 情况仍然如此 还是 iOS 8 中的这一更
  • 如何在快速人工智能中获得给定测试集的预测并计算准确性?

    我正在尝试加载由导出的学习者learn export 我想针对测试集运行它 我希望我的测试集有标签 以便我可以测量其准确性 这是我的代码 test src TextList from df df path cols texts split
  • 重新审视混合字符串值的字母数字排序

    请注意 我之前提出了一个非常相似的问题 但要求已发生变化 对混合字符串值进行字母数字排序 https stackoverflow com questions 3842719 alphanumeric sort on mixed string
  • TypeScript 中多个互斥参数

    给定以下 JavaScript 函数 function x foo fooId bar barId 我想将其转换为 TypeScript 以便调用者必须传入foo or fooId 但不能两者兼而有之 同样对于bar and barId 例
  • 在 python 中将 blob 保存到文件中

    我正在尝试将通过 ajax 发送的 blob 保存为 python 中的文件 以前经历过这个Python 如何在二进制和 Base 64 之间相互转换 https stackoverflow com questions 5305456 py
  • 如何配置 Ransack Rails Gem 以添加 NULLS LAST 进行排序

    我希望兰萨克总是添加NULLS LAST这会将空值放在排序列的最后 有办法做到这一点吗 我在 github 上开了一个问题 https github com activerecord hackery ransack issues 443 h
  • 从 Windows 命令行连接到 websocket

    是否可以从 Windows 命令行连接到 websocket 我已经从 Mac 终端使用了 WSCAT 但我似乎找不到替代方案 任何帮助 将不胜感激 Windows 中没有内置可与 WebSocket 配合使用的工具 虽然你可以使用teln
  • 在 Java 中从复杂的 HTML 表格中提取数据到二维数组

    如何转换 HTML 表格带有 colspan 和 rowspanJava中的二维数组 矩阵 我在 Python 和 jQuery 中找到了很好的解决方案 但在 Java 中却没有 只有通过 jsoup 的非常简单的表 XSLT 有一种很好的