核心要点
query()
和 evaluate()
。虽然两者都执行查询,但区别在于它们返回的结果类型,query()
返回 DOMNodeList
,而 evaluate()
则尽可能返回类型化结果。本文将深入探讨 XPath,包括其功能和在 PHP 中的实现方式。您将发现 XPath 可以大大减少编写查询和过滤 XML 数据所需的代码量,并且通常也能提高性能。我将使用上一篇文章中相同的 DTD 和 XML 来演示 PHP DOM XPath 功能。为了快速回顾,以下是 DTD 和 XML 的样子:
<!DOCTYPE library [ <!ELEMENT library (book*)> <!ELEMENT book (title, author, genre, chapter*)> <!ATTLIST book isbn ID #REQUIRED> <!ELEMENT title (#PCDATA)> <!ELEMENT author (#PCDATA)> <!ELEMENT genre (#PCDATA)> <!ELEMENT chapter (chaptitle,text)> <!ATTLIST chapter position NMTOKEN #REQUIRED> <!ELEMENT chaptitle (#PCDATA)> <!ELEMENT text (#PCDATA)> ]>
<?xml version="1.0" encoding="utf-8"?> <library> <book isbn="isbn1234"> <title>A Book</title> <author>An Author</author> <genre>Horror</genre> <chapter position="first"> <chaptitle>chapter one</chaptitle> <text></text> </chapter> </book> <book isbn="isbn1235"> <title>Another Book</title> <author>Another Author</author> <genre>Science Fiction</genre> <chapter position="first"> <chaptitle>chapter one</chaptitle> <text>Sit Dolor Amet...</text> </chapter> </book> </library>
基本的 XPath 查询
XPath 是一种用于查询 XML 文档的语法。最简单的形式是定义您想要访问的元素的路径。使用上面的 XML 文档,以下 XPath 查询将返回所有存在的 book
元素的集合:
//library/book
就是这样。两个正斜杠表示 library
是文档的根元素,单个斜杠表示 book
是其子元素。非常简单,不是吗?但是,如果您想指定特定的书籍呢?假设您想返回任何由“An Author”撰写的书籍。该 XPath 将是:
//library/book/author[text() = "An Author"]/..
您可以在方括号中使用 text()
对节点的值执行比较,尾随的“/..”表示我们想要父元素(即向上移动一个节点)。XPath 查询可以使用两个函数之一执行:query()
和 evaluate()
。两者都执行查询,但区别在于它们返回的结果类型。query()
将始终返回 DOMNodeList
,而 evaluate()
则尽可能返回类型化结果。例如,如果您的 XPath 查询是返回特定作者撰写的书籍数量而不是实际的书籍本身,那么 query()
将返回一个空的 DOMNodeList
。evaluate()
将直接返回数字,因此您可以立即使用它,而不必从节点中提取数据。
XPath 的代码和速度优势
让我们做一个快速演示,返回特定作者撰写的书籍数量。我们将首先查看一种可行的方法,但它不使用 XPath。这是为了向您展示如何在不使用 XPath 的情况下完成此操作,以及为什么 XPath 如此强大。
<!DOCTYPE library [ <!ELEMENT library (book*)> <!ELEMENT book (title, author, genre, chapter*)> <!ATTLIST book isbn ID #REQUIRED> <!ELEMENT title (#PCDATA)> <!ELEMENT author (#PCDATA)> <!ELEMENT genre (#PCDATA)> <!ELEMENT chapter (chaptitle,text)> <!ATTLIST chapter position NMTOKEN #REQUIRED> <!ELEMENT chaptitle (#PCDATA)> <!ELEMENT text (#PCDATA)> ]>
下一种方法实现了相同的结果,但使用 XPath 来选择仅由特定作者撰写的书籍:
<?xml version="1.0" encoding="utf-8"?> <library> <book isbn="isbn1234"> <title>A Book</title> <author>An Author</author> <genre>Horror</genre> <chapter position="first"> <chaptitle>chapter one</chaptitle> <text></text> </chapter> </book> <book isbn="isbn1235"> <title>Another Book</title> <author>Another Author</author> <genre>Science Fiction</genre> <chapter position="first"> <chaptitle>chapter one</chaptitle> <text>Sit Dolor Amet...</text> </chapter> </book> </library>
请注意,我们这次消除了 PHP 对作者值进行测试的需要。但是,我们还可以更进一步,使用 XPath 函数 count()
来计算此路径的出现次数。
//library/book
我们只需一行 XPath 就能检索到所需信息,无需使用 PHP 执行费力的过滤。事实上,这是一种编写此功能的更简单、更简洁的方法!请注意,在最后一个示例中使用了 evaluate()
。这是因为函数 count()
返回类型化结果。使用 query()
将返回 DOMNodeList
,但您会发现它是一个空列表。这不仅使您的代码更简洁,而且还具有速度优势。我发现版本 1 的平均速度比版本 2 快 30%,但版本 3 比版本 2 快约 10%(比版本 1 快约 15%)。虽然这些测量结果会根据您的服务器和查询而有所不同,但使用纯 XPath 通常会带来相当大的速度优势,同时还能使您的代码更易于阅读和维护。
XPath 函数
XPath 可以使用相当多的函数,并且有很多优秀的资源详细说明了可用的函数。如果您发现自己正在迭代 DOMNodeLists
或比较 nodeValues
,您可能会发现一个 XPath 函数可以消除很多 PHP 代码。您已经看到了 count()
函数的用法。让我们使用 id()
函数来返回具有给定 ISBN 的书籍的标题。您需要使用的 XPath 表达式是:
//library/book/author[text() = "An Author"]/..
请注意,此处要搜索的值用引号括起来并用空格分隔;无需使用逗号分隔术语。
<?php public function getNumberOfBooksByAuthor($author) { $total = 0; $elements = $this->domDocument->getElementsByTagName("author"); foreach ($elements as $element) { if ($element->nodeValue == $author) { $total++; } } return $total; // 修正:这里应该是 $total,而不是 $number } ?>
在 XPath 中执行复杂函数相对简单;诀窍是熟悉可用的函数。
在 XPath 中使用 PHP 函数
有时您可能会发现自己需要一些标准 XPath 函数无法提供的更强大的功能。幸运的是,PHP DOM 还允许您将 PHP 自身函数整合到 XPath 查询中。让我们考虑返回书籍标题中的单词数量。最简单的函数,我们可以这样编写方法:
<!DOCTYPE library [ <!ELEMENT library (book*)> <!ELEMENT book (title, author, genre, chapter*)> <!ATTLIST book isbn ID #REQUIRED> <!ELEMENT title (#PCDATA)> <!ELEMENT author (#PCDATA)> <!ELEMENT genre (#PCDATA)> <!ELEMENT chapter (chaptitle,text)> <!ATTLIST chapter position NMTOKEN #REQUIRED> <!ELEMENT chaptitle (#PCDATA)> <!ELEMENT text (#PCDATA)> ]>
但是,我们也可以将函数 str_word_count()
直接整合到 XPath 查询中。为此需要完成几个步骤。首先,我们必须使用 XPath 对象注册一个命名空间。XPath 查询中的 PHP 函数以“php:functionString
”开头,然后是您想要使用的函数的名称,括在括号中。此外,要定义的命名空间是 http://php.net/xpath
。命名空间必须设置为这个;任何其他值都会导致错误。然后,我们需要调用 registerPHPFunctions()
,它告诉 PHP 每当遇到以“php:
”为命名空间的函数时,都应该由 PHP 处理它。调用函数的实际语法是:
<?xml version="1.0" encoding="utf-8"?> <library> <book isbn="isbn1234"> <title>A Book</title> <author>An Author</author> <genre>Horror</genre> <chapter position="first"> <chaptitle>chapter one</chaptitle> <text></text> </chapter> </book> <book isbn="isbn1235"> <title>Another Book</title> <author>Another Author</author> <genre>Science Fiction</genre> <chapter position="first"> <chaptitle>chapter one</chaptitle> <text>Sit Dolor Amet...</text> </chapter> </book> </library>
将所有这些放在一起,得到 getNumberOfWords()
的以下重新实现:
//library/book
请注意,您不需要调用 XPath 函数 text()
来提供节点的文本。registerPHPFunctions()
方法会自动执行此操作。但是,以下同样有效:
//library/book/author[text() = "An Author"]/..
注册 PHP 函数不仅限于 PHP 自带的函数。您可以定义自己的函数并在 XPath 中提供这些函数。唯一的区别是,在定义函数时,您使用“php:function
”而不是“php:functionString
”。此外,只能提供函数本身或静态方法。不支持调用实例方法。让我们使用一个超出类范围的常规函数来演示基本功能。我们将使用的函数将仅返回“乔治·奥威尔”的书籍。对于您希望包含在查询中的每个节点,它必须返回 true
。
<?php public function getNumberOfBooksByAuthor($author) { $total = 0; $elements = $this->domDocument->getElementsByTagName("author"); foreach ($elements as $element) { if ($element->nodeValue == $author) { $total++; } } return $total; // 修正:这里应该是 $total,而不是 $number } ?>
传递给函数的参数是 DOMElements
数组。函数负责迭代数组并确定要测试的节点是否应在 DOMNodeList
中返回。在此示例中,要测试的节点是 /book
,我们使用 /author
来进行确定。现在我们可以创建方法 getGeorgeOrwellBooks()
:
<?php public function getNumberOfBooksByAuthor($author) { $query = "//library/book/author[text() = '$author']/.."; $xpath = new DOMXPath($this->domDocument); $result = $xpath->query($query); return $result->length; } ?>
如果 compare()
是一个静态方法,那么您需要修改 XPath 查询,使其读取:
<?php public function getNumberOfBooksByAuthor($author) { $query = "count(//library/book/author[text() = '$author']/..)"; $xpath = new DOMXPath($this->domDocument); return $xpath->evaluate($query); } ?>
事实上,所有这些功能都可以轻松地仅用 XPath 编写,但该示例展示了如何扩展 XPath 查询以使其更复杂。在 XPath 中无法调用对象方法。如果您发现需要访问某些对象属性或方法来完成 XPath 查询,最好的解决方案是使用 XPath 完成您能做到的部分,然后根据需要使用任何对象方法或属性处理生成的 DOMNodeList
。
总结
XPath 是一种在处理 XML 数据时减少代码编写量并加快代码执行速度的好方法。虽然不是官方 DOM 规范的一部分,但 PHP DOM 提供的附加功能允许您使用自定义功能扩展标准 XPath 函数。这是一个非常强大的功能,随着您对 XPath 函数的熟悉程度提高,您可能会发现自己越来越少地依赖它。
(图片来自 Fotolia)
关于使用 XPath 的 PHP DOM 的常见问题解答 (FAQ)
XPath(XML 路径语言)是一种查询语言,用于从 XML 文档中选择节点。在 PHP DOM 中,XPath 用于遍历 XML 文档中的元素和属性。它允许您通过多种方法找到并选择 XML 文档的特定部分,例如按名称选择节点、按其属性值选择节点或按其在文档中的位置选择节点。这使得它成为在 PHP 中解析和操作 XML 数据的强大工具。
要创建 DOMXPath 的实例,您首先需要创建一个 DOMDocument 类的实例。获得 DOMDocument 对象后,您可以通过将 DOMDocument 对象传递给 DOMXPath 构造函数来创建一个新的 DOMXPath 对象。这是一个示例:
<!DOCTYPE library [ <!ELEMENT library (book*)> <!ELEMENT book (title, author, genre, chapter*)> <!ATTLIST book isbn ID #REQUIRED> <!ELEMENT title (#PCDATA)> <!ELEMENT author (#PCDATA)> <!ELEMENT genre (#PCDATA)> <!ELEMENT chapter (chaptitle,text)> <!ATTLIST chapter position NMTOKEN #REQUIRED> <!ELEMENT chaptitle (#PCDATA)> <!ELEMENT text (#PCDATA)> ]>
您可以使用 DOMXPath 对象的 query()
方法选择节点。query()
方法将 XPath 表达式作为参数,并返回一个包含与表达式匹配的所有节点的 DOMNodeList 对象。例如:
<?xml version="1.0" encoding="utf-8"?> <library> <book isbn="isbn1234"> <title>A Book</title> <author>An Author</author> <genre>Horror</genre> <chapter position="first"> <chaptitle>chapter one</chaptitle> <text></text> </chapter> </book> <book isbn="isbn1235"> <title>Another Book</title> <author>Another Author</author> <genre>Science Fiction</genre> <chapter position="first"> <chaptitle>chapter one</chaptitle> <text>Sit Dolor Amet...</text> </chapter> </book> </library>
这将选择所有作为 <book>
元素子元素的 <title>
元素。
query()
和 evaluate()
方法的区别是什么?query()
和 evaluate()
方法都用于评估 XPath 表达式。区别在于它们返回的结果类型。query()
方法返回与 XPath 表达式匹配的所有节点的 DOMNodeList。另一方面,evaluate()
返回类型化结果,例如布尔值、数字或字符串,具体取决于 XPath 表达式。如果表达式结果为节点集,evaluate()
将返回 DOMNodeList。
要在 XPath 查询中处理命名空间,您需要使用 registerNamespace()
方法将命名空间注册到 DOMXPath 对象。此方法有两个参数:前缀和命名空间 URI。注册命名空间后,您可以在 XPath 查询中使用前缀。例如:
//library/book
您可以使用 @
符号后跟属性名称来选择 XPath 中的属性。例如,要选择 <a></a>
元素的所有 href
属性,您可以使用以下 XPath 表达式://a/@href
。
XPath 提供了许多可以在 XPath 表达式中使用的函数。这些函数可用于操作字符串、数字、节点集等等。要在 PHP DOM 中使用 XPath 函数,只需在 XPath 表达式中包含该函数即可。例如,要选择所有具有价格元素且值大于 30 的 <book>
元素,您可以使用 number()
函数,如下所示://book[number(price) > 30]
。
是的,您可以在 PHP DOM 中将 XPath 与 HTML 文档一起使用。但是,由于 HTML 不总是格式良好的 XML,因此在尝试将 XPath 与 HTML 一起使用时可能会遇到问题。为了避免这些问题,您可以使用 DOMDocument 类的 loadHTML()
方法加载 HTML 文档。此方法将解析 HTML 并纠正任何格式错误,允许您将 XPath 与生成的 DOMDocument 对象一起使用。
在 PHP DOM 中使用 XPath 时,可能会由于多种原因发生错误,例如 XPath 表达式格式错误或无法加载 XML 文档。为了处理这些错误,您可以使用 libxml_use_internal_errors()
函数启用用户错误处理。此函数将导致 libxml 错误存储在内部,允许您在代码中处理它们。然后,您可以使用 libxml_get_errors()
函数检索错误并根据需要处理它们。
虽然 XPath 本身不提供修改 XML 文档的方法,但您可以将 XPath 与 DOM API 结合使用来修改 XML 文档。您可以使用 XPath 选择要修改的节点,然后使用 DOM API 提供的方法进行修改。例如,您可以使用 DOMNode 类的 removeChild()
方法删除节点,或使用 DOMElement 类的 setAttribute()
方法更改属性的值。
以上是PHP DOM:使用XPATH的详细内容。更多信息请关注PHP中文网其他相关文章!