如何使用CDATA将xml文件解析为PHP中的关联数组

时间:2014-07-31 12:10:08

标签: php arrays xml

所有

我有一个inputXml.xml文件,如下所示:

<content>
<item  name="book" label="Book">
<![CDATA[ book name ]]>
</item>
<item  name="price" label="Price">
<![CDATA[ 35 ]]>
</item>
</content>

当我使用下面的代码解析xml文件时:

$obj = simplexml_load_string(file_get_contents($inputXml),'SimpleXMLElement', LIBXML_NOCDATA);
$json = json_encode($obj);
$inputArray = json_decode($json,TRUE);

我得到如下数组:

[content] => Array
            (
                [item] => Array
                    (
                        [0] => book name
                        [1] => 35
                    )

            )

我想知道,是否可以通过使用属性的值来获得关联数组&#34; name&#34;或&#34;标签&#34;作为关键如下:

[content] => Array
            (
                [item] => Array
                    (
                        [name] => book name
                        [price] => 35
                    )

            )

2 个答案:

答案 0 :(得分:1)

首先,您被json_encodejson_decode所需的其他代码所愚弄,以便从 SimpleXMLElement 中获取数组。相反,您只需要转换为数组:

$inputArray = (array) $obj;

然后您遇到的问题是,您正在寻找的阵列序列化不是 SimpleXMLElement 为该XML提供的默认序列化。

此外,您遇到的另一个小问题是依赖于使用LIBXML_NOCDATA,否则您将无法获得接近的格式。但是,不依赖于那个标志(因此,如果基础XML将使用CDATA或不使用元素值XML编码),那么也可以获得代码的某种稳定性。

由于 SimpleXMLElement 无法提供您想要的行为,因此您通常有两个选项:从 SimpleXMLElement 扩展或装饰它。我通常建议装饰,因为延伸是有限的。例如。你不能通过扩展来干扰(array)转换,但是你可以进行JSON序列化。但这不是你想要的,你正在寻找阵列序列化。

因此,对于 SimpleXMLElement 的标准数组序列化,您可以使用序列化程序策略对象来实现对数组进行数组序列化。

首先需要序列化程序

interface ArraySerializer
{
    public function arraySerialize();
}

class SimpleXMLArraySerializer implements ArraySerializer
{
    /**
     * @var SimpleXMLElement
     */
    private $subject;

    /**
     * @var SimpleXMLArraySerializeStrategy
     */
    private $strategy;

    public function __construct(SimpleXMLElement $element, SimpleXMLArraySerializeStrategy $strategy = NULL) {
        $this->subject  = $element;
        $this->strategy = $strategy ?: new DefaultSimpleXMLArraySerializeStrategy();
    }

    public function arraySerialize() {
        $strategy = $this->getStrategy();
        return $strategy->serialize($this->subject);
    }

    /**
     * @return SimpleXMLArraySerializeStrategy
     */
    public function getStrategy() {
        return $this->strategy;
    }
}

此阵列序列化程序缺少序列化功能。这已经针对一种策略,以便以后可以轻松交换。这是一个默认策略:

abstract class SimpleXMLArraySerializeStrategy
{
    abstract public function serialize(SimpleXMLElement $element);
}

class DefaultSimpleXMLArraySerializeStrategy extends SimpleXMLArraySerializeStrategy
{
    public function serialize(SimpleXMLElement $element) {
        $array = array();

        // create array of child elements if any. group on duplicate names as an array.
        foreach ($element as $name => $child) {
            if (isset($array[$name])) {
                if (!is_array($array[$name])) {
                    $array[$name] = [$array[$name]];
                }
                $array[$name][] = $this->serialize($child);
            } else {
                $array[$name] = $this->serialize($child);
            }
        }

        // handle SimpleXMLElement text values.
        if (!$array) {
            $array = (string)$element;
        }

        // return empty elements as NULL (self-closing or empty tags)
        if (!$array) {
            $array = NULL;
        }

        return $array;
    }
}

此对象包含将 SimpleXMLElement 转换为数组的常用方法。它的行为与您的XML相似, SimpleXMLElement LIBXML_NOCDATA已经相同。但它没有CDATA的问题。为了显示这一点,以下示例已经给出了您的输出:

$obj        = new SimpleXMLElement($xml);
$serializer = new SimpleXMLArraySerializer($obj);
print_r($serializer->arraySerialize());

现在到目前为止,阵列序列化已经以它自己的类型实现,很容易根据需要进行更改。对于 content 元素,您可以使用不同的策略将其转换为数组。它也容易得多:

class ContentXMLArraySerializeStrategy extends SimpleXMLArraySerializeStrategy
{
    public function serialize(SimpleXMLElement $element) {
        $array = array();

        foreach ($element->item as $item) {
            $array[(string) $item['name']] = (string) $item;
        }

        return array('item' => $array);
    }
}

剩下的就是在正确的情况下将其连接到SimpleXMLArraySerializer。例如。取决于元素的名称:

...

    /**
     * @return SimpleXMLArraySerializeStrategy
     */
    public function getStrategy() {
        if ($this->subject->getName() === 'content') {
            return new ContentXMLArraySerializeStrategy();
        }

        return $this->strategy;
    }
}

现在来自上面的相同例子:

$obj        = new SimpleXMLElement($xml);
$serializer = new SimpleXMLArraySerializer($obj);
print_r($serializer->arraySerialize());

会给你想要的输出(美化):

Array
(
    [item] => Array
        (
            [book]  => book name
            [price] => 35 
        )
)

由于您的XML可能只有这一个元素,我会说这样的抽象级别可能有点多。但是,如果XML将要更改并且您在同一文档中实际存在多个数组格式需求,那么这是一种合理的方式。

我在我的示例中使用的默认序列化基于SimpleXML and JSON Encode in PHP – Part III and End中的装饰示例。

答案 1 :(得分:0)

我快速浏览了the SimpleXMLElement docs,这表明构建一个你想要的数组实际上非常容易:

$xml = simplexml_load_file($file, 'SimpleXMLElement', LIBXML_NOCDATA);
$result = array();//store assoc array here
foreach ($xml->item as $item)
{//iterate over item nodes
    if (isset($item['name']))
    {//attributes are accessible as array keys
        $result[(string) $item['name']] = (string) $item;//casts required!
    }
}
var_dump($result);

这是因为SimpleXMLElement是一个可遍历的对象,因此您可以像访问数组一样访问其属性。但是,我们确实需要强制转换属性,因为它们都是SimpleXMLElement类的实例 上面的代码是我最初编写的简化版本:

$xml = simplexml_load_file($fileName, 'SimpleXMLElement', LIBXML_NOCDATA);
foreach ($xml as $name => $node)
{
    if ($name === 'item')
    {
        $key = false;
        foreach ($node->attributes() as $name => $attr)
        {
            if ($name == 'name')
            {
                $key = (string) $attr;//attr is object, still
                break;
            }
        }
        if ($key !== false)
            $result[$key] = (string) $node;
    }
}

这也有效。然而,代码看起来,我认为你会同意,相当混乱。我坚持我在这里发布的第一个版本......


初步回答(使用DOMDocument

我将使用simpleXML研究如何执行此操作,但就目前而言,我是如何设置使用DOMDocument API获取CDATA值的业务:

$dom = new DOMDocument;
$dom->load($file);
//get items
$items = $dom->getElementsByTagName('item');
$cData = array();
foreach ($items as $node)
{
    if ($node->hasChildNodes())
    {
        foreach ($node->childNodes as $cNode)
        {
            if ($cNode->nodeType === XML_CDATA_SECTION_NODE)
               $cData[] = $cNode->textContent;//get contents
        }
    }
}

将此功能与$node->attributes->getNamedItem('name');等其他方法结合使用,可获取节点的属性$node->attributes->getNamedItem('name')->nodeValue;以获取该属性的值。
我承认,DOMDocument api看起来相当冗长(因为它是),感觉有点笨拙(就像它一直做的那样),但是一旦你{{3 }}