我找到了一种方法来使用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 上以尝试算法的实现。
我希望它有帮助。