1

我正在查询 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 对象(注意复数“问题”)。然后我遍历这些问题:

foreach ($issuesObj as $issueObj) {
    [...]
} //foreach

在这个循环中(注意 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->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>

到目前为止,这完全符合预期。但是,这就是它变得奇怪的地方。我想在问题中选择名称为“类型”的字段,所以我使用 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]->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 为何如此行事的任何线索?

4

2 回答 2

3

问题在于 XPath 如何处理上下文MSDN 上的这个页面,虽然显然是在谈论完全不同的实现,但有一个简洁的解释:

使用双正斜杠 ( //) 的表达式表示可以包含零个或多个层次结构级别的搜索。当此运算符出现在模式的开头时,上下文相对于文档的根。

所以foo//bar从“当前”节点开始,找到被调用的子节点,foo然后递归地搜索他们的后代bar;但//bar跳转到文档的顶部,并递归搜索任何被调用的节点bar

要显式引用当前上下文,您可以使用 a .,因此.//field[@name="Type"]应该按照您的意愿工作。

由于在您的情况下,该元素是当前节点的直接子节点,因此无论如何field您都不需要递归,因此也应该可以工作。//./field[@name="Type"]field[@name="Type"]


附加说明:标题中的短语“不在对象中”表明您认为 SimpleXML 已将 XML 文件变成了一堆 PHP 对象。事实并非如此。相反,可以将 SimpleXML 视为一种 API,用于在内存中操作已解析的 XML 文档,而每个 SimpleXMLElement 都是指向该已解析文档的指针。API 被设计成在 PHP 中感觉“自然”,但比大多数 PHP 对象包含更多“魔法”。

于 2013-10-26T13:42:07.320 回答
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"]');
于 2013-10-26T01:39:57.447 回答