【问题标题】:Parsing HTML with XPath and PHP使用 XPath 和 PHP 解析 HTML
【发布时间】:2011-06-03 06:59:23
【问题描述】:

有没有办法(使用 XPath 和 PHP)执行以下操作(没有外部 XSLT 文件)?

  • 删除所有表格及其内容
  • 删除第一个 h1 标记之后的所有内容
  • 仅保留段落(包括其内部 HTML(链接、列表等))

我收到了 XSLT 答复 here,但我正在寻找不需要外部文件的 XPATH 查询。

目前,我已通过以下方式将相关 HTML 加载到 SimpleXmlElement 中:

$doc = @DOMDocument::loadHTML($xml);
$data = simplexml_import_dom($doc);

现在我需要帮助:

$data = $data->xpath('??????');

这几天一直在努力,但无济于事。非常感谢您的帮助。

编辑:我并不特别关心段落中的内容,因为我可以使用 strip_tags 来消除我不想要的内容。我需要做的就是将段落与源代码的其余部分隔离开来。我想一个更具体、更准确的要求是:

仅返回不包含在表格中的段落(及其 html 内容),并且仅在第一个 h1 标记之前

编辑 2:

我想我已经掌握了大部分内容:
$query = $xpath->query('//p[not(ancestor::table) and not(preceding::h2)]');

唯一的问题是内部 HTML 的丢失。

【问题讨论】:

  • 根据你的假设,可以说you just need <p>, including table inside a p吗?
  • 但我不想要表格内的任何段落。

标签: php regex xpath html-parsing


【解决方案1】:

要获取不在表中且仅在第一个 h1 之前的所有 P 元素,您可以这样做

$xp = new DOMXPath($dom);
$expression = '//p[not(preceding::h1[1]) and not(ancestor::table)]';
foreach ($xp->query($expression) as $node) {
    echo $dom->saveXml($node);
}

Demo on codepad

一般来说,如果您知道文档中第一个 h1 的位置,则使用指向该元素的直接路径而不是在文档中搜索任何位置的 // 查询会更高效。例如,作为替代方案,您还可以使用 Alejandro 在以下 cmets 中提供的 XPath:

/descendant::h1[1]/preceding::p[not(ancestor::table)]

如果要从源文档中的节点创建新的 DOM 文档,则必须将节点导入到新文档中。

// src document
$dom = new DOMDocument;
$dom->loadXML($xml);

// dest document
$new = new DOMDocument;
$new->formatOutput = TRUE;

// xpath setup
$xp = new DOMXPath($dom);
$expr = '//p[not(preceding::h1[1]) and not(ancestor::table)]';

// importing nodes into dest document
foreach ($xp->query($expr) as $node) {
    $new->appendChild($new->importNode($node, TRUE));
}

// output dest document
echo $new->saveXML();

Demo on codepad


更多补充

在您的示例中,您使用了错误抑制运算符。这是不好的做法。如果您想忽略来自 DOM 的任何解析错误,请使用

libxml_use_internal_errors(TRUE); // catch any DOM errors with libxml
$dom = new DOMDocument;           // remove the @ as it is bad practise
$dom->loadXML($xhtml);            // use loadHTML if it's not valid XHTML
libxml_clear_errors();            // disregards any DOM related errors

使用 DOM 删除节点始终是相同的方法。找到要删除的节点。获取它的 parentNode 并在其上调用 removeChild,并将要删除的节点作为参数。

foreach ($dom->getElementsByTagName('foo') as $node) {
    $node->parentNode->removeChild($node);
}

您也可以在没有 XPath 的情况下导航到同级节点(和子节点)。以下是如何在第一个 h1 元素之后删除所有后续兄弟

$firstH1 = $dom->getElementsByTagName('h1')->item(0);
while ($firstH1->nextSibling !== NULL) {
    $firstH1->parentNode->removeChild($firstH1->nextSibling);
}
echo $dom->saveXml();

从DOMDocument 中删除节点将立即影响DOMDocument。在上面的代码中,我们总是查询第一个 h1 的第一个后续兄弟。如果有,则从DOMDocument 中删除。然后nextSibling 将指向刚刚删除的兄弟姐妹(如果有的话)。


获取和打印所有段落同样容易。要获取outerXML,只需将您想要outerXML 的节点传递给saveXML 方法。

foreach ($dom->getElementsByTagName('p') as $paragraph)
{
    echo $dom->saveXml($paragraph);
}

无论如何,这应该会让你继续前进。我建议你familiarize yourself with the DOM API。这并不难。你会发现你要做的大部分事情都围绕DOMDocument、DOMNode 和DOMElement(DOMNode 的子类)中的属性和方法。

【讨论】:

  • +1 好答案。也许/descendant::h1[1]/preceding::p[not(ancestor::table)] 会更快(不为每个p 测试所有前面的内容)
  • @Alejandro 谢谢。是的,这可能会更快。我已将其添加为上述答案的替代方法
【解决方案2】:

谢谢你,戈登。

解决办法:

    $dom = @DOMDocument::loadHTML($xml);
    $xpath = new DOMXPath($dom);
    $query = $xpath->query('//p[
        not(ancestor::table) and
        not(preceding::h1[1])
        ]');

    foreach ($query as $node){
        $result .= $dom->saveXml($node);
    }  

    echo $result;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-07-19
    • 2012-08-23
    • 2014-07-13
    • 1970-01-01
    • 2014-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多