参考 - 如何在 SimpleXML 中处理名称空间(名称中带有冒号的标签和属性)?

Reference - How do I handle Namespaces (Tags and Attributes with a Colon in their Name) in SimpleXML?

此问题旨在作为回答一个特别常见问题的参考,这些问题可能采用不同的形式:

如果您的问题已作为此问题的副本关闭,它可能与这些示例不相同,但此页面应该会告诉您您需要了解的内容。

这是一个说明性的例子:

$xml = 
    <<<XML
    <?xml version="1.0" encoding="utf-8"?>
    <document xmlns="http://example.com" xmlns:ns2="https://namespaces.example.org/two" xmlns:seq="urn:example:sequences">
        <list type="short">
            <ns2:item seq:position="1">A thing</ns2:item>
            <ns2:item seq:position="2">Another thing</ns2:item>
        </list>
    </document>
    XML;
$sx = simplexml_load_string($xml);

此代码无效;为什么不呢?

foreach ( $sx->list->ns2:item as $item ) {
    echo 'Position: ' . $item['seq:position'] . "\n";
    echo 'Item: ' . (string)$item . "\n";
}

第一个问题是->ns2:item是无效语法;但将其更改为 也不起作用:

foreach ( $sx->list->{'ns2:item'} as $item ) { ... }

为什么不,你应该用什么来代替?

什么是 XML 命名空间?

标记或属性名称中的冒号 (:) 表示该元素或属性位于 XML 命名空间 中。命名空间是一种在一个文档中组合不同 XML 格式/标准,并跟踪哪些名称来自哪种格式的方法。冒号及其前面的部分实际上并不是标记/属性名称的一部分,它们只是表明它位于哪个命名空间中。

XML 命名空间有一个 命名空间标识符 ,它由 URI(URL 或 URN)标识。 URI 不指向任何东西,它只是某人“拥有”命名空间的一种方式。例如,SOAP 标准使用命名空间 http://www.w3.org/2003/05/soap-envelope,而 OpenDocument 文件使用(除其他外)urn:oasis:names:tc:opendocument:xmlns:meta:1.0。问题中的示例使用命名空间 http://example.comhttps://namespaces.example.org/two.

在文档或文档的一部分中,命名空间被赋予 本地前缀,这是您在冒号之前看到的部分。例如,在不同的文档中,SOAP 命名空间可能会被赋予本​​地前缀 soap:SOAP:SOAP-ENV:env: 或只是 ns1:。这些名称使用特殊的 xmlns 属性链接回命名空间的标识符,例如xmlns:soap="http://www.w3.org/2003/05/soap-envelope"。特定文档中前缀的选择完全是任意的,每次生成时都可以更改而不会改变含义。

最后,每个文档或文档的部分中都有一个默认命名空间,这是用于没有前缀的元素的命名空间。它由没有 :xmlns 属性定义,例如xmlns="http://www.w3.org/2003/05/soap-envelope"。在上面的示例中,<list> 在默认命名空间中,定义为 http://example.com.

有点奇怪的是,没有前缀的属性从来不在默认命名空间中,而是在一种标准没有明确定义的“无效命名空间”中。参见:XML Namespaces and Unprefixed Attributes

简单XML 给我一个空对象;怎么了?

如果您在具有命名空间的 SimpleXML 对象上使用 print_rvar_dump 或类似的“转储结构”函数,某些内容将不会显示。 它仍然存在,并且可以按如下所述进行访问。

如何在 SimpleXML 中访问命名空间?

SimpleXML 提供了两种使用命名空间的主要方法:

  • The ->children() method 允许您访问特定命名空间中的子元素。它有效地切换您的对象以查看该命名空间,直到您再次调用它以切换回或切换到另一个命名空间。
  • The ->attributes() method 以类似的方式工作,但允许您访问特定命名空间中的 属性

例如,上面的例子可能会变成:

define('XMLNS_EG1', 'http://example.com');
define('XMLNS_EG2', 'https://namespaces.example.org/two');
define('XMLNS_SEQ', 'urn:example:sequences');

foreach ( $sx->children(XMLNS_EG1)->list->children(XMLNS_EG2)->item as $item ) {
    echo 'Position: ' . $item->attributes(XMLNS_SEQ)->position . "\n";
    echo 'Item: ' . (string)$item . "\n";
}

你也可以在第一次解析XML时selectinitial命名空间,使用$namespace_or_prefix参数,也就是第四个simplexml_load_stringsimplexml_load_filenew SimpleXMLElement.

的参数

例如,如果我们以这种方式创建对象,就不需要 ->children(XMLNS_EG1) 调用来访问 list 元素:

$sx = simplexml_load_string($xml, null, 0, XMLNS_EG1);

(请注意,如果根元素使用 default 名称空间而不是前缀,SimpleXML 将自动 select 它;但是因为您可以不要预测将来哪个命名空间将成为默认命名空间,最好始终包含 $namespace_or_prefix 参数或初始 ->children() 调用。)

速记(不推荐)

作为简写,您还可以将命名空间的 本地别名 传递给方法,方法是将第二个参数指定为 true。请记住,此前缀可能随时更改,例如,生成器可能会分配前缀 ns1ns2 等,如果代码略有更改,则以不同的顺序分配它们。 依靠完整的命名空间 URI 始终是最好的方法

使用这个简写,代码将变成:

foreach ( $sx->list->children('ns2', true)->item as $item ) {
    echo 'Position: ' . $item->attributes('seq', true)->position . "\n";
    echo 'Item: ' . (string)$item . "\n";
}

(这个简写是在 PHP 5.2 中添加的,你可能会看到非常老的例子使用更冗长的版本,使用 $sx->getNamespaces 来获取前缀标识符对的列表。这是两个世界中最糟糕的情况,因为您仍在对前缀而不是标识符进行硬编码。)

在 XPath 中使用命名空间

简单XML有an xpath() method which allows you to search an element with XPath 1.0 syntax. To access namespaced nodes, you have to choose your own prefixes by calling the registerXPathNamespace() method.

请记住,即使元素没有前缀和冒号,它也可以位于用 xmlns 声明的“默认名称空间”中。

例如:

define('XMLNS_EG2', 'https://namespaces.example.org/two');
define('XMLNS_SEQ', 'urn:example:sequences');

$sx->registerXPathNamespace('EG2', XMLNS_EG2);
$sx->registerXPathNamespace('SEQ', XMLNS_SEQ);
foreach ( $sx->xpath('//EG2:item[@SEQ:position=2]') as $item ) {
    echo 'Item: ' . (string)$item . "\n";
}

请注意,您选择的前缀不需要与 XML 中使用的前缀匹配,它是您感兴趣的命名空间的您的本地别名

另请注意,registerXPathNamespacexpath 方法以外的任何内容都没有影响。如果您不使用 XPath,则需要使用本页其他地方讨论的 children()attributes()

限制

  • XPath 1.0 没有“默认名称空间”的概念(并且 XML 库 SimpleXML 所基于的不支持 XPath 2.0),因此您必须使用您要匹配的每个元素和属性名称的前缀符号。
  • 注册的名称空间必须在您要在 上调用的特定对象xpath() 上注册,并且不会继承或复制到其他对象。如果要根据不同的起点进行搜索,则每次都必须 运行 registerXPathNamespace