无论结构或名称空间如何,都使用SimpleXML解析名称空间

tyf*_*ler 0 php xml simplexml xml-namespaces

我有这样的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>
Run Code Online (Sandbox Code Playgroud)

现在,SimpleXML可以读取"标题"和"描述"标签,但它无法读取带有"g:"前缀的标签.

对于这种特定情况,使用"children"函数在stackoverflow上有解决方案.但是我不仅想要阅读Google Shopping XMLs,我还需要它来自结构或名称空间,我对文件一无所知(我以递归方式循环遍历节点作为多维数组).

有没有办法用SimpleXML做到这一点?我可以替换冒号,但我希望能够存储数组并重新组合XML(在这种情况下专门用于Google购物),所以我不想丢失信息.

hak*_*kre 9

您希望使用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
Run Code Online (Sandbox Code Playgroud)

这为您提供以下输出:

SimpleXMLElement Object
(
    [@attributes] => Array
        (
            [version] => 2.0
        )

    [title] => Blah
    [description] => Blah
)
Run Code Online (Sandbox Code Playgroud)

正如您已经看到的,没有很好的表单来呈现数组中的属性,因此Simplexml按惯例将这些放入@attributes键中.

您遇到的另一个问题是处理这些多个XML命名空间.在前面的示例中,未使用特定的命名空间.这是默认命名空间.当转换的SimpleXMLElement到一个数组,该的命名空间的SimpleXMLElement使用.由于没有明确指定,因此已采用默认命名空间.

但是,如果你在创建数组指定一个命名空间,命名空间拍摄.

例:

$xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA, "http://base.google.com/ns/1.0");
print_r($xml);
Run Code Online (Sandbox Code Playgroud)

这为您提供以下输出:

SimpleXMLElement Object
(
    [id] => Blah
    [product_type] => Blah
)
Run Code Online (Sandbox Code Playgroud)

如您所见,这次创建SimpleXMLElement时指定的命名空间用于数组转换:http://base.google.com/ns/1.0.

在编写时,您希望将文档中的所有命名空间考虑在内,您需要先获取这些命名空间 - 包括默认的:

$xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA);
$namespaces = [null] + $xml->getDocNamespaces(true);
Run Code Online (Sandbox Code Playgroud)

然后,您可以遍历所有名称空间并递归地将它们合并到下面显示的相同数组中:

$array = [];
foreach ($namespaces as $namespace) {
    $xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA, $namespace);
    $array = array_merge_recursive($array, (array) $xml);
}
print_r($array);
Run Code Online (Sandbox Code Playgroud)

然后最终应该创建并输出您选择的数组:

Array
(
    [@attributes] => Array
        (
            [version] => 2.0
        )

    [title] => Blah
    [description] => Blah
    [id] => Blah
    [product_type] => Blah
)
Run Code Online (Sandbox Code Playgroud)

如您所见,使用SimpleXMLElement完全可以实现.但是,了解SimpleXMLElement如何转换为数组(或序列化为遵循相同规则的JSON)非常重要.要模拟SimpleXMLElement -to-array转换,您可以使用print_r快速输出.

请注意,并非所有XML构造都可以很好地转换为数组.这并不是Simplexml的特别限制,而是XML可以表示的结构的性质以及数组可以表示的结构.

因此,最好将XML保存在像SimpleXMLElement(或DOMDocument)这样的对象中以访问和处理数据 - 而不是使用数组.

然而,只要您知道自己做了什么并且不需要编写太多代码来访问结构树中更深层次的成员,将数据转换为数组就完全没问题了.否则,SimpleXMLElement比数组更受青睐,因为它不仅允许对许多XML功能进行专用访问,而且还允许使用该SimpleXMLElement::xpath方法查询数据库.您需要编写许多自己的代码行来访问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());
Run Code Online (Sandbox Code Playgroud)

哪个输出:

Array
(
    [@attributes] => stdClass Object
        (
            [version] => 2.0
        )

    [title] => Blah
    [description] => Blah
    [id] => Blah
    [product_type] => Blah
    [@text] => ...
)
Run Code Online (Sandbox Code Playgroud)

对于底层实现:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

这是采用PHP中的SimpleXML和JSON编码中给出的更改JSON编码规则示例的命名空间- 第III部分和结束部分.