我查询YouTrack的网络服务以获取问题列表。响应是XML,如下所示:
<issueCompacts>
<issue id="XX-1">
<field xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CustomField" name="Type">
<value>Bug</value>
</field>
<field xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CustomField" name="Bill-to">
<value>NBS</value>
</field>
</issue>
<issue id="XX-2">
<field xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CustomField" name="Type">
<value>New Feature</value>
</field>
<field xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CustomField" name="Bill-to">
<value>NBS</value>
</field>
</issue>
[...]
</issueCompacts>
我采用这个实际包含五个问题的XML,并从中创建一个名为$ issuesObj的SimpleXMLElement对象(注意复数&#39;问题&#39;)。然后我重复讨论这些问题:
foreach ($issuesObj as $issueObj) {
[...]
} //foreach
在这个循环中(注意单数&#39;问题&#39; as变量),如果我var_dump()$ issueObj,我得到这个:
object(SimpleXMLElement)#4 (2) {
["@attributes"]=>
array(1) {
["id"]=>
string(4) "XX-1"
}
["field"]=>
array(2) {
[0]=>
object(SimpleXMLElement)#16 (2) {
["@attributes"]=>
array(1) {
["name"]=>
string(4) "Type"
}
["value"]=>
string(3) "Bug"
}
[1]=>
object(SimpleXMLElement)#17 (2) {
["@attributes"]=>
array(1) {
["name"]=>
string(7) "Bill-to"
}
["value"]=>
string(3) "NBS"
}
}
}
这里是$ issueObj-&gt; asXml()的转储:
<?xml version="1.0"?>
<issue id="XX-1">
<field xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CustomField" name="Type">
<value>Bug</value>
</field>
<field xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CustomField" name="Bill-to">
<value>NBS</value>
</field>
</issue>
到目前为止,这完全符合预期。但是,这就是它变得奇怪的地方。我想在名称为&#39; Type&#39;的问题中挑选字段,因此我使用xpath()方法:
$typeField = $issueObj->xpath('//field[@name="Type"]')
现在,如果我转储$ typeField,我得到这个:
array(5) {
[0]=>
object(SimpleXMLElement)#10 (2) {
["@attributes"]=>
array(1) {
["name"]=>
string(4) "Type"
}
["value"]=>
string(3) "Bug"
}
[1]=>
object(SimpleXMLElement)#11 (2) {
["@attributes"]=>
array(1) {
["name"]=>
string(4) "Type"
}
["value"]=>
string(11) "New Feature"
}
[2]=>
object(SimpleXMLElement)#13 (2) {
["@attributes"]=>
array(1) {
["name"]=>
string(4) "Type"
}
["value"]=>
string(11) "New Feature"
}
[3]=>
object(SimpleXMLElement)#14 (2) {
["@attributes"]=>
array(1) {
["name"]=>
string(4) "Type"
}
["value"]=>
string(3) "Bug"
}
[4]=>
object(SimpleXMLElement)#15 (2) {
["@attributes"]=>
array(1) {
["name"]=>
string(4) "Type"
}
["value"]=>
string(11) "New Feature"
}
}
请注意,有五个元素而不是预期的两个元素。似乎正在发生的是xpath()方法作用于原始的$ issuesObj,而不是作为子集的$ issueObj。但是,它变得更加奇怪。如果我将$ issueObj转换为XML,然后使用该XML直接返回到对象,它就可以工作。因此:
$issueObj = new \SimpleXMLElement($issueObj->asXml());
$typeField = $issueObj->xpath('//field[@name="Type"]');
var_dump($typeField); exit;
得出这个:
array(1) {
[0]=>
object(SimpleXMLElement)#17 (2) {
["@attributes"]=>
array(1) {
["name"]=>
string(4) "Type"
}
["value"]=>
string(3) "Bug"
}
}
这是正确的。并且调用$ typeField [0] - &gt; asXml()现在产生这个:
<?xml version="1.0"?>
<field xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CustomField" name="Type">
<value>Bug</value>
</field>
再次与预期完全一样。
有关SimpleXMLElement为何如此表现的任何线索?
答案 0 :(得分:3)
问题在于XPath如何处理 context 。 This page on MSDN虽然显然是在谈论完全不同的实现,但却有一个简洁的解释:
使用双正斜杠(
//
)的表达式表示可以包含零级或多级层次结构的搜索。当此运算符出现在模式的开头时,上下文相对于文档的根目录。
所以foo//bar
从“当前”节点开始,找到名为foo
的子项,然后递归搜索其后代bar
;但//bar
跳转到文档的顶部,并递归搜索任何名为bar
的节点。
要明确引用当前上下文,您可以使用.
,因此.//field[@name="Type"]
应该可以正常工作。
因为在您的情况下,field
元素是当前节点的直接子元素,所以无论如何都不需要递归//
,因此./field[@name="Type"]
和{{1}也应该工作。
Aditional note :标题中的“不在对象中”这句话表明您认为SimpleXML已将XML文件转换为一堆PHP对象。实际情况并非如此;相反,将SimpleXML视为用于在内存中处理已解析的XML文档的API,并将每个SimpleXMLElement视为指向该已解析文档的指针。 API被设计为在PHP中感觉“自然”,但包含比大多数PHP对象更多的“魔力”。
答案 1 :(得分:1)
查看xpath语法:http://www.w3schools.com/xpath/xpath_syntax.asp 例如:// title [@lang]选择具有名为lang
的属性的所有标题元素改变这个:
$typeField = $issueObj->xpath('//field[@name="Type"]');
到此:
$typeField = $issueObj->xpath('field[@name="Type"]');