使用SimpleXML解析带有命名空间的XML

60

我有以下的xml:

<root xmlns:event="http://www.webex.com/schemas/2002/06/service/event">
    <event:event>
        <event:sessionKey></event:sessionKey>
        <event:sessionName>Learn QB in Minutes</event:sessionName>
        <event:sessionType>9</event:sessionType>
        <event:hostWebExID></event:hostWebExID>
        <event:startDate>02/12/2009</event:startDate>
        <event:endDate>02/12/2009</event:endDate>
        <event:timeZoneID>11</event:timeZoneID>
        <event:duration>30</event:duration>
        <event:description></event:description>
        <event:status>NOT_INPROGRESS</event:status>
        <event:panelists></event:panelists>
        <event:listStatus>PUBLIC</event:listStatus>
    </event:event>
    ...
</root>
我如何循环遍历所有的事件:event节点并显示所有事件:SessionKey呢?这样做是不起作用的:
$xml = new SimpleXMLElement($r);
$xml->registerXPathNamespace('e', 'http://www.webex.com/schemas/2002/06/service/event');

foreach($xml->xpath('//e:event') as $event) {
 var_export($event->xpath('//e:sessionKey'));
}

3
这可能会有所帮助:http://www.lornajane.net/posts/2010/Fetching-Namespaced-XML-Elements-With-SimpleXML。 - Ajinkya Kulkarni
1
“不起作用”从来都不是一个正确的错误描述。PHP会给出以下错误提示:“警告:SimpleXMLElement::xpath():未定义命名空间前缀”。如果你是软件开发人员,应该听取PHP的警告。同时请参考:如何在PHP中获取有用的错误信息? - hakre
6个回答

32

在没有使用registerXPathNamespace和xpath查询中的完整命名空间前缀的情况下,它确实可以工作:

$xml = new SimpleXMLElement($r);

foreach($xml->xpath('//event:event') as $event) {
 var_export($event->xpath('event:sessionKey'));
}

10
实际上,我的观点是" Yes" 和 "No" 都有一些道理。PHP会自动注册当前上下文中的命名空间,这甚至会覆盖你自己注册的命名空间。你的源代码基于你加载的文档中使用的前缀。但是这些前缀是来自一个外部资源,你无法控制它们。这些前缀是可选的、模糊的,而且随时可能变化。相比之下,命名空间是被定义、唯一和稳定的,前缀或别名则不然。 - ThW

23
你必须为每个你使用的simpleXMLElement对象注册命名空间。
$xml = new SimpleXMLElement($r);
$xml->registerXPathNamespace('e', 'http://www.webex.com/schemas/2002/06/service/event');

foreach($xml->xpath('//e:event') as $event) {
    $event->registerXPathNamespace('e', 'http://www.webex.com/schemas/2002/06/service/event');
    var_export($event->xpath('//e:sessionKey'));
}

命名空间也应该在xml文件的某个地方声明。
<event:event xmlns:event="http://www.webex.com/schemas/2002/06/service/event">
...

这种方法也是可行的。如果你知道 XML 文件总是使用相同的前缀,你可以跳过 registerXPathNamespace。

20

在使用simplexml进行了很多工作后,这是我的做法。

如果你已经有一个元素,只想获取其不同命名空间的子元素,可以使用以下魔法技巧,比如对于这样的结构:

<entry>
<title type="text">My test entry</title>
<gd:when startTime="2017-02-26T02:00:00Z" endTime="2017-02-26T03:00:00Z"/>
<gc:notes type="string">A type</gc:notes>
</entry>

将TRUE作为第二个参数发送给children函数。
  $title = (string) $entry->title;
  $gd = $entry->children('gd', TRUE);
  $attrs = $gd->when->attributes();
  $startTime = (string) $attrs->startTime;
  $gc = $entry->children('gc', TRUE);
  $notes = (string) $gc->notes();

19

这是对我起作用的另一种选择。

$xml = simplexml_load_string($r);
$ns = $xml->getNamespaces(true);

foreach ($xml->children($ns['event'])->event as $skey) {
    $sessionKey = $skey->children($ns['event'])->sessionKey;
    echo $sessionKey;
}

诀窍是至少使用children()的第一个参数。如果您事先了解文档结构,甚至可以手动键入命名空间名称(或其URI)作为getNamespaces()的替代方法。 - Álvaro González
为了更轻松地进行输入,诀窍是将其转换为对象,即$ns = (object) $xml->getNamespaces(true);,然后您可以像这样使用$sessionKey = $skey->children($ns->event)->sessionKey; - PeterM

8
另一种方法是使用SimpleXML进行解析和DOMDocument进行操作/访问,这样可以完全避开命名空间问题:
$xml = new SimpleXMLElement($r);
$xml = dom_import_simplexml($xml);
$nodelist= $xml->getElementsByTagName('event');  
for($i = 0; $i < $nodelist->length; $i++) {
    $sessions = $nodelist->item($i)->getElementsByTagName('sessionKey');
    echo $sessions->item(0)->nodeValue;
}

5
使用registerXPathNamespace并调用xpath实际上对我没有起作用。 我必须采用这篇伟大文章中提供的解决方案:http://blog.preinheimer.com/index.php?/archives/172-SimpleXML,-Namespaces-Hair-loss.html 所以在你的情况下,这样做:
echo $xml->children('http://www.webex.com/schemas/2002/06/service/event')->sessionName;

将输出:

在几分钟内学习QB

(注意:保留了HTML标签,但没有解释)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接