要获取不在表中且仅在第一个 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 的子类)中的属性和方法。