ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PHP registerXPathNamespace()函数讲解

PHP registerXPathNamespace()函数讲解 前言用 SimpleXML 处理带命名空间namespace的 XML 时最典型的症状是「明明文档里就有这个标签xpath()却什么都查不到」。很多人第一反应是 XPath 表达式写错了于是反复改路径最后发现路径怎么改都查不出来。真正的原因通常不在 XPath 语法而在 XML 命名空间只要文档里写了xmlns:bkhttp://example.com/book元素的全名就不再是book而是「URI 本地名」这样一个二元组。XPath 1.0 规定带命名空间的元素必须用前缀来限定无前缀的名字只匹配「不在任何命名空间中」的元素。也就是说//book永远匹配不到bk:book。SimpleXMLElement::registerXPathNamespace()就是用来补上这个映射的它把一个前缀和一段 URI 绑定起来供接下来的 XPath 查询使用。需要说明的是SimpleXML 扩展从 PHP 5.0 起就内置提供属于 ext-simplexml默认开启依赖 libxml2本文示例在 PHP 8.x 下可直接运行PHP 7.x 基本同样适用。本文讲清三件事命名空间为什么会让 XPath 失效、这个方法的确切签名和生效范围、以及实战中容易踩的坑。一、命名空间为什么会让 XPath 失效XML 命名空间的作用是给「同名标签」分组。xmlns:bkhttp://example.com/book声明之后写作bk:book的元素其真实身份是「命名空间 URI 为http://example.com/book、本地名为book」的元素。前缀bk只是这份文档里给它起的别名换一份文档完全可以叫x。XPath 1.0 的匹配规则是无前缀的节点测试node test只匹配没有命名空间的节点。所以XPath 表达式能否匹配到带命名空间的元素说明//book否无前缀只匹配「无命名空间」的 book//bk:book是前提是前缀已注册前缀必须能被解析成一段 URI//*[local-name()book]是绕过前缀用函数取本地名但会丢失命名空间信息//book元素用xmlns默认命名空间否默认命名空间同样属于命名空间无前缀照样匹配不到注意最后一行默认命名空间xmlns...没有前缀的那种也是命名空间。这是新手最容易忽略的一点——文档里明明没写前缀//book却依然查不到原因就在这里。二、方法签名与参数以 PHP 官方手册为准签名如下public SimpleXMLElement::registerXPathNamespace(string $prefix, string $namespace): bool参数类型含义$prefixstring你打算在 XPath 表达式里使用的任意前缀不必和文档里的前缀一样$namespacestring命名空间 URI必须和文档中xmlns声明里的值逐字符一致返回值是bool注册成功为true失败为false。两个关键点必须记住第一个参数是你自己起的前缀不是文档里的前缀。文档里写bk:book你完全可以注册registerXPathNamespace(b, http://example.com/book)然后在表达式里写//b:book。XPath 里的前缀只是一个指向 URI 的记号名字随意。它只为「紧接着的下一次 XPath 查询」建立命名空间上下文。官方手册对该方法的描述就是「为下一次 XPath 查询创建命名空间上下文」。所以稳妥的写法是每次执行xpath()之前都重新注册一遍不要依赖「注册一次就一直有效」。与之配套的查询方法是SimpleXMLElement::xpath()签名是public SimpleXMLElement::xpath(string $expression): array|false成功返回 SimpleXMLElement 对象组成的数组出错返回false。三、完整示例查询带前缀的命名空间元素下面这份 XML 里所有业务标签都带bk:前缀直接写//book是查不到的。?php// 适用于 PHP 8.0PHP 7.x 亦可写法未使用 8.x 独有语法$xmlString XML?xml version1.0 encodingUTF-8?catalog xmlns:bkhttp://example.com/bookbk:book isbn978-7-111-0001bk:titlePHP 入门/bk:title/bk:bookbk:book isbn978-7-111-0002bk:titlePHP 进阶/bk:title/bk:book/catalogXML;$sxe simplexml_load_string($xmlString);if ($sxe false) {// simplexml_load_string() 失败时返回 false 并已发出 E_WARNINGforeach (libxml_get_errors() as $error) {fwrite(STDERR, trim($error-message) . PHP_EOL);}exit(1);}// 未注册前缀这一句查不到任何东西返回空数组$empty $sxe-xpath(//book);printf(未注册前缀时 //book 命中数%d\n, count($empty));// 注册前缀前缀名可以随便取URI 必须和文档里的 xmlns 值完全一致$ok $sxe-registerXPathNamespace(b, http://example.com/book);var_dump($ok);$books $sxe-xpath(//b:book);if ($books false) {exit(XPath 表达式有误\n);}foreach ($books as $book) {// $book 处在 bk 命名空间中访问同名子元素也会落在同一命名空间里printf(ISBN%s 标题%s\n, (string) $book[isbn], (string) $book-title);}// 带谓词的 XPath 同样受益于已注册的前缀$one $sxe-xpath(//b:book[isbn978-7-111-0002]/b:title);printf(按 isbn 过滤命中%d标题%s\n, count($one), (string) $one[0]);预期输出text形式未注册前缀时 //book 命中数0bool(true)ISBN978-7-111-0001 标题PHP 入门ISBN978-7-111-0002 标题PHP 进阶按 isbn 过滤命中1标题PHP 进阶这里有一个值得单独说明的现象$book-title竟然能直接取到bk:title。这不是巧合——SimpleXML 在访问「无前缀子元素」时会先在当前节点所在的命名空间里查找所以处在bk命名空间中的$book访问title时找的是同一命名空间下的title。如果子元素和父元素不在同一命名空间就必须显式用children(b, true)去取第二个参数true表示第一个参数是「前缀」而不是「URI」。四、默认命名空间的处理方式如果 XML 用的是默认命名空间xmlnshttp://example.com/base没有前缀处理思路完全一样先给这段 URI 注册一个你自己起的前缀再在表达式里用这个前缀。?php// 适用于 PHP 8.0$xmlString XML?xml version1.0 encodingUTF-8?catalog xmlnshttp://example.com/basebookPHP 入门/book/catalogXML;$sxe simplexml_load_string($xmlString);// 默认命名空间同样要用前缀才能匹配前缀名随意$sxe-registerXPathNamespace(d, http://example.com/base);$books $sxe-xpath(//d:book);printf(命中%d内容%s\n, count($books), (string) $books[0]);最后补充一个容易混淆的点DOM 扩展里也有一个名字几乎一样的方法叫DOMXPath::registerNamespace()。两者不能混用——SimpleXML 的方法注册到的是 SimpleXML 的 XPath 上下文DOM 的方法注册到的是 DOMXPath 对象。如果你的代码里同时有 DOM 和 SimpleXML务必分清是哪一个对象在调用。另外SimpleXML 没有提供「列出文档中所有命名空间」的方法需要自己从原始字符串里读xmlns声明或者改用 DOM 的DOMXPath配合DOMNode::lookupNamespaceURI()来探索。常见坑点❌ 把文档里的前缀当成必须照抄的值$sxe-xpath(//bk:book)却只注册了b。✅ XPath 里的前缀由你自己决定注册什么前缀就用什么前缀两个名字对不上就是查不到。❌ 注册时 URI 末尾多写一个斜杠http://example.com/book/或大小写不一致。✅ URI 是纯字符串比较必须和xmlns声明里的值逐字符相同多一个字符都不行。❌ 遇到xmlns...默认命名空间时以为「没有前缀就等于没有命名空间」直接写//book。✅ 默认命名空间也是命名空间先registerXPathNamespace(d, 那段 URI)再写//d:book。❌ 在脚本开头注册一次之后几十次xpath()都指望它还生效。✅ 该方法的作用范围是「下一次 XPath 查询」稳妥做法是每次查询前重新注册。❌ 写了未注册的前缀例如根本没用registerXPathNamespace()就写//b:book。✅ libxml 会报Undefined namespace prefix这类错误结果是拿不到数据先注册再查询并用$result false判断表达式是否出错。❌ 把简写的xpath()结果直接当数组用下标[0]不判断false或是否为空。✅SimpleXMLElement::xpath()出错返回false命中为空返回空数组先判断再取值。❌ 大文件里每次都重新simplexml_load_string()再注册前缀或在循环里反复注册。✅ 解析一次、拿到 SimpleXMLElement 后复用前缀注册只和「查询」有关和「解析」无关。❌ 拿 SimpleXML 的方法名去DOMXPath上调用或反过来。✅SimpleXMLElement::registerXPathNamespace()与DOMXPath::registerNamespace()是两套 API各自注册在各自对象上。总结项目内容所属类SimpleXMLElementext-simplexml签名registerXPathNamespace(string $prefix, string $namespace): bool作用建立「XPath 前缀 → 命名空间 URI」的映射生效范围为紧接着的下一次 XPath 查询建立命名空间上下文配套方法SimpleXMLElement::xpath(string $expression): array前缀从哪来你自己起不必与文档中的前缀一致URI 要求必须与文档xmlns声明逐字符一致默认命名空间同样需要注册前缀才能用//p:tag匹配到易混 APIDOMXPath::registerNamespace()另一套对象不能混用命名空间不是「可有可无的装饰」它直接改变元素的全名因此无前缀的 XPath 路径根本匹配不到带命名空间的元素。凡是 XML 里出现过xmlns就养成「先registerXPathNamespace()再xpath()」的习惯并且把注册写在每次查询之前能省掉绝大多数「查不到但看不出哪里错」的排查时间。
返回列表