我正在编写一个将 HTML 表格转换为 CSV 的工具,我注意到一些奇怪的行为。鉴于此代码
$html = <<<HTML
<table>
<tr><td>A</td><td>Rose</td></tr>
</table>
<h1>Leave me behind</h1>
<table>
<tr><td>By</td><td>Any</td></tr>
</table>
<table>
<tr><td>Other</td><td>Name</td></tr>
</table>
HTML;
$dom = new \DOMDocument();
\libxml_use_internal_errors(true);
$dom->loadHTML($html, LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED);
\libxml_clear_errors();
$tables = $dom->getElementsByTagName('table');
$stream = \fopen('php://output', 'w+');
for ($i = 0; $i < $tables->length; ++$i) {
$rows = $tables->item($i)->getElementsByTagName('tr');
for ($j = 0; $j < $rows->length; ++$j) {
echo trim($rows->item($j)->nodeValue) . PHP_EOL;
}
}
\fclose($stream);
我期望这样的输出:
ARose
ByAny
OtherName
但是我得到的是这样的:
ARose
ByAny
OtherName
ByAny
OtherName
如果省略第一个结束标签,我会得到相同的结果。看来 DOMDocument 将第二个和第三个
<table>
嵌套在第一个中。
事实上,如果我使用 xpath 仅从每个表中获取直接子项,我会得到正确的输出:
$xpath = new \DOMXPath($dom);
for ($i = 0; $i < $tables->length; ++$i) {
$rows = $xpath->query('./tr', $tables->item($i));
for ($j = 0; $j < $rows->length; ++$j) {
echo trim($rows->item($j)->nodeValue) . PHP_EOL;
}
}
您发布的第一个代码中的 DOMDocument 没有任何问题,它已经忠实地处理了您的 $html
请注意,您将循环遍历所有表,然后为每个表显示节点值
因此,如果您想知道实际发生了什么,请使用以下内容
<?php
$html = <<<HTML
<table>
<tr><td>A</td><td>Rose</td></tr>
</table>
<h1>Leave me behind</h1>
<table>
<tr><td>By</td><td>Any</td></tr>
</table>
<table>
<tr><td>Other</td><td>Name</td></tr>
</table>
HTML;
$dom = new \DOMDocument();
\libxml_use_internal_errors(true);
$dom->loadHTML($html, LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED);
\libxml_clear_errors();
$tables = $dom->getElementsByTagName('table');
$stream = \fopen('php://output', 'w+');
for ($i = 0; $i < $tables->length; ++$i) {
$rows = $tables->item($i)->getElementsByTagName('tr');
echo "Now for table " . $i ."<br>" ;
for ($j = 0; $j < $rows->length; ++$j) {
echo trim($rows->item($j)->nodeValue) . "<br>";
}
echo "<hr>";
}
fclose($stream);
?>