我有这样的Google购物Feed(摘录):
<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">
...
<g:id><![CDATA[Blah]]></g:id>
<title><![CDATA[Blah]]></title>
<description><![CDATA[Blah]]></description>
<g:product_type><![CDATA[Blah]]></g:product_type>
现在,SimpleXML可以读取“标题”和“描述”标签,但它无法读取带有“g:”前缀的标签。
对于这种特定情况,使用“children”函数在stackoverflow上有解决方案。但是我不仅想要阅读Google Shopping XMLs,我还需要它来自结构或名称空间,我对文件一无所知(我以递归方式循环遍历节点作为多维数组)。
有没有办法用SimpleXML做到这一点?我可以替换冒号,但我希望能够存储数组并重新组合XML(在这种情况下专门用于Google购物),所以我不想丢失信息。
您希望使用SimpleXMLElement从XML中提取数据并将其转换为数组。
这通常是可能的,但有一些警告。在XML命名空间之前,您的XML附带CDATA。对于使用Simplexml进行XML到数组转换,您需要在加载XML字符串时将CDATA转换为文本。这是通过LIBXML_NOCDATA
旗帜完成的。例:
$xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA);
print_r($xml); // print_r shows how SimpleXMLElement does array conversion
这为您提供以下输出:
SimpleXMLElement Object
(
[@attributes] => Array
(
[version] => 2.0
)
[title] => Blah
[description] => Blah
)
正如您已经看到的,没有很好的形式来呈现数组中的属性,因此按惯例,Simplexml将这些放入@attributes
键中。
您遇到的另一个问题是处理这些多个XML命名空间。在前面的示例中,未使用特定的命名空间。这是默认命名空间。将SimpleXMLElement转换为数组时,将使用SimpleXMLElement的命名空间。由于没有明确指定,因此已采用默认命名空间。
但是,如果在创建数组时指定了命名空间,则会使用该命名空间。
例:
$xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA, "http://base.google.com/ns/1.0");
print_r($xml);
这为您提供以下输出:
SimpleXMLElement Object
(
[id] => Blah
[product_type] => Blah
)
如您所见,这次创建SimpleXMLElement时指定的命名空间用于数组转换:http://base.google.com/ns/1.0
。
在编写时,您希望将文档中的所有命名空间考虑在内,您需要先获取这些命名空间 - 包括默认的:
$xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA);
$namespaces = [null] + $xml->getDocNamespaces(true);
然后你可以遍历下面显示的所有名称空间和recursively merge them into the same array:
$array = [];
foreach ($namespaces as $namespace) {
$xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA, $namespace);
$array = array_merge_recursive($array, (array) $xml);
}
print_r($array);
然后最终应该创建并输出您选择的数组:
Array
(
[@attributes] => Array
(
[version] => 2.0
)
[title] => Blah
[description] => Blah
[id] => Blah
[product_type] => Blah
)
如您所见,使用SimpleXMLElement完全可以实现。但是,了解SimpleXMLElement如何转换为数组(或序列化为遵循相同规则的JSON)非常重要。要模拟SimpleXMLElement到数组的转换,您可以使用print_r
进行快速输出。
请注意,并非所有XML构造都可以很好地转换为数组。这并不是Simplexml的特别限制,而是XML可以表示的结构的性质以及数组可以表示的结构。
因此,最好将XML保存在像SimpleXMLElement(或DOMDocument)这样的对象中以访问和处理数据 - 而不是使用数组。
然而,只要您知道自己做了什么并且不需要编写太多代码来访问结构中树下更深层的成员,将数据转换为数组就完全没问题了。否则,SimpleXMLElement比数组更受欢迎,因为它不仅允许专用访问许多XML功能,而且还可以使用SimpleXMLElement::xpath
method查询数据库。您需要编写许多自己的代码行来访问XML树中适合数组的数据。
为了充分利用这两个方面,您可以扩展SimpleXMLElement以满足您的特定转换需求:
$buffer = <<<BUFFER
<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">
...
<g:id><![CDATA[Blah]]></g:id>
<title><![CDATA[Blah]]></title>
<description><![CDATA[Blah]]></description>
<g:product_type><![CDATA[Blah]]></g:product_type>
</rss>
BUFFER;
$feed = new Feed($buffer, LIBXML_NOCDATA);
print_r($feed->toArray());
哪个输出:
Array
(
[@attributes] => stdClass Object
(
[version] => 2.0
)
[title] => Blah
[description] => Blah
[id] => Blah
[product_type] => Blah
[@text] => ...
)
对于底层实现:
class Feed extends SimpleXMLElement implements JsonSerializable
{
public function jsonSerialize()
{
$array = array();
// json encode attributes if any.
if ($attributes = $this->attributes()) {
$array['@attributes'] = iterator_to_array($attributes);
}
$namespaces = [null] + $this->getDocNamespaces(true);
// json encode child elements if any. group on duplicate names as an array.
foreach ($namespaces as $namespace) {
foreach ($this->children($namespace) as $name => $element) {
if (isset($array[$name])) {
if (!is_array($array[$name])) {
$array[$name] = [$array[$name]];
}
$array[$name][] = $element;
} else {
$array[$name] = $element;
}
}
}
// json encode non-whitespace element simplexml text values.
$text = trim($this);
if (strlen($text)) {
if ($array) {
$array['@text'] = $text;
} else {
$array = $text;
}
}
// return empty elements as NULL (self-closing or empty tags)
if (!$array) {
$array = NULL;
}
return $array;
}
public function toArray() {
return (array) json_decode(json_encode($this));
}
}
这是采用SimpleXML and JSON Encode in PHP – Part III and End中给出的更改JSON编码规则示例的名称空间。