php里实现xml转数组,数组转xml的互转代码

近日MyQEE里加入了xml和数组互转的功能,调用代码分别是:

  • XML字符串转数组: Text::xml_to_array($xmlstr);
  • 数组转XML:Arr::to_xml($array);

其中XML转数组支持直接获取XML的URL转换,例如:

$arr = Text::xml_to_array('http://flash.weather.com.cn/wmaps/xml/china.xml');
print_r($arr)

XML代码示例:

<?xml version="1.0" encoding="UTF-8"?>
<TrafficEventResponse aa="true" bb="aaaa">
    <status>success</status>
    <currentCity aaa="aaaa">北京</currentCity>
    <test>true</test>
    <dateTime>2013/07/23/18/32/41</dateTime>
    <results>
        <result aa="11">
            <startTime>2013/03/08/10/51/00</startTime>
            <endTime>2013/10/08/12/00/00</endTime>
            <title ID="12" active="1" permission="13">东四西大街1车道限行(长期)</title>
            <description>东四西大街因为道路施工,导致1车道限行。</description>
            <location ID="12" active="1" permission="13">
                <lng>116.420497</lng>
                <lat>39.930511</lat>
                <root>
                    <folder ID="aaa" active="1" permission="13"><![CDATA[aaaaaaaaaaaaaaaaaaaa]]></folder>
                    <folder ID="bbb" active="1" permission="1"><![CDATA[bbbbbbbbbbbbbbbbbbbbbb]]></folder>
                </root>
            </location>
            <type>2</type>
        </result>
        <result aa="22">
            <startTime>2013/01/01/00/00/00</startTime>
            <endTime>2013/12/31/23/59/00</endTime>
            <title>清华东路3车道限行(长期)</title>
            <description>清华东路因为道路施工,导致3车道限行。</description>
            <location>
                <lng>116.37618</lng>
                <lat>40.007609</lat>
                <root>
                    <folder ID="ccc" active="1" permission="1"><![CDATA[ccccccccccccccccccc]]></folder>
                    <folder ID="ddd" active="1" permission="1"><![CDATA[dddddddddddddddddd]]></folder>
                </root>
            </location>
            <type>2</type>
        </result>
    </results>
</TrafficEventResponse>

转换成数组后:

Array
(
    [@name] => TrafficEventResponse
    [@attributes] => Array
        (
            [aa] => 1
            [bb] => aaaa
        )

    [status] => success
    [currentCity] => Array
        (
            [@attributes] => Array
                (
                    [aaa] => aaaa
                )

            [@data] => 北京
        )

    [test] => 1
    [dateTime] => 2013/07/23/18/32/41
    [results] => Array
        (
            [0] => Array
                (
                    [@name] => result
                    [@attributes] => Array
                        (
                            [aa] => 11
                        )

                    [startTime] => 2013/03/08/10/51/00
                    [endTime] => 2013/10/08/12/00/00
                    [title] => Array
                        (
                            [@attributes] => Array
                                (
                                    [ID] => 12
                                    [active] => 1
                                    [permission] => 13
                                )

                            [@data] => 东四西大街1车道限行(长期)
                        )

                    [description] => 东四西大街因为道路施工,导致1车道限行。
                    [location] => Array
                        (
                            [@attributes] => Array
                                (
                                    [ID] => 12
                                    [active] => 1
                                    [permission] => 13
                                )

                            [lng] => 116.420497
                            [lat] => 39.930511
                            [root] => Array
                                (
                                    [0] => Array
                                        (
                                            [@name] => folder
                                            [@attributes] => Array
                                                (
                                                    [ID] => aaa
                                                    [active] => 1
                                                    [permission] => 13
                                                )

                                            [@tdata] => aaaaaaaaaaaaaaaaaaaa
                                        )

                                    [1] => Array
                                        (
                                            [@name] => folder
                                            [@attributes] => Array
                                                (
                                                    [ID] => bbb
                                                    [active] => 1
                                                    [permission] => 1
                                                )

                                            [@tdata] => bbbbbbbbbbbbbbbbbbbbbb
                                        )

                                )

                        )

                    [type] => 2
                )

            [1] => Array
                (
                    [@name] => result
                    [@attributes] => Array
                        (
                            [aa] => 22
                        )

                    [startTime] => 2013/01/01/00/00/00
                    [endTime] => 2013/12/31/23/59/00
                    [title] => 清华东路3车道限行(长期)
                    [description] => 清华东路因为道路施工,导致3车道限行。
                    [location] => Array
                        (
                            [lng] => 116.37618
                            [lat] => 40.007609
                            [root] => Array
                                (
                                    [0] => Array
                                        (
                                            [@name] => folder
                                            [@attributes] => Array
                                                (
                                                    [ID] => ccc
                                                    [active] => 1
                                                    [permission] => 1
                                                )

                                            [@tdata] => ccccccccccccccccccc
                                        )

                                    [1] => Array
                                        (
                                            [@name] => folder
                                            [@attributes] => Array
                                                (
                                                    [ID] => ddd
                                                    [active] => 1
                                                    [permission] => 1
                                                )

                                            [@tdata] => dddddddddddddddddd
                                        )

                                )

                        )

                    [type] => 2
                )

        )

)

接下来是整理后的代码,完全独立使用

<?php
/**
 * 将一个XML字符串解析成一个数组
 *
 * 如果需要将数组转换成XML字符串,可使用 `array_to_xml($arr)` 方法
 *
 * ** 特殊的key **
 *
 *  key            | 说明
 * ----------------|-------------------------------
 *  `@attributes`  | XML里所有的 attributes 都存放在 `@attributes` key里,可自定义参数 `$attribute_key` 修改,设置成true则和标签里的内容合并
 *  `@name`        | 循环数组XML的标签(tag)存放在 `@name` 的key里
 *  `@tdata`       | CDATA内容存放在 `@tdata` 的key里
 *  `@data`        | 如果本来的值是字符串,但是又有 attributes,则内容被转移至 `@data` 的key里
 *
 *
 *     print_r(xml_to_array('http://flash.weather.com.cn/wmaps/xml/china.xml'));
 *
 * @param string|SimpleXMLElement $xml_string XML字符串,支持http的XML路径,接受 SimpleXMLElement 对象
 * @param string $attribute_key attributes所使用的key,默认 @attributes,设置成 true 则和内容自动合并
 * @param int $max_recursion_depth 解析最高层次,默认25
 * @return array | false 失败则返回false
 */

function xml_to_array($xml_string, $attribute_key = '@attributes', $max_recursion_depth = 25)
{
    if (is_string($xml_string))
    {
        if (preg_match('#^http(s)?://#i', $xml_string))
        {
            $xml_string = file_get_contents($xml_string);
        }
        $xml_object = simplexml_load_string($xml_string, 'SimpleXMLElement', LIBXML_NOCDATA);
    }
    elseif (is_object($xml_string) && $xml_string instanceof SimpleXMLElement)
    {
        $xml_object = $xml_string;
    }
    else
    {
        return false;
    }

    if (!$attribute_key)$attribute_key = '@attributes';
    if (null===$max_recursion_depth || false===$max_recursion_depth)$max_recursion_depth = 25;

    $format_attribute_value = function (& $tmp_value)
    {
        switch ($tmp_value)
        {
            case 'true':
                $tmp_value = true;
                break;
            case 'false':
                $tmp_value = false;
                break;
            case 'null':
                $tmp_value = null;
                break;
            default:
                $tmp_value = trim($tmp_value);
        }
    };

    $exec_xml_to_array = function ($xml_object, $attribute_key, $recursion_depth, $max_recursion_depth) use(&$exec_xml_to_array, $format_attribute_value)
    {
       

        /**
         * @var $xml_object SimpleXMLElement
         * @var $value SimpleXMLElement
         */

        $rs = array
        (
            '@name' => $xml_object->getName(),
        );

        $attr = get_object_vars($xml_object->attributes());

        if ($attr)
        {
            foreach($attr['@attributes'] as &$tmp_value)
            {
                $format_attribute_value($tmp_value);
            }
            unset($tmp_value);

            if (true===$attribute_key)
            {
                # 合并到一起
               $rs += $attr['@attributes'];
            }
            else
            {
                $rs[$attribute_key] = $attr['@attributes'];
            }
        }
        $tdata = trim("$xml_object");
        if (strlen($tdata)>0)
        {
            $rs['@tdata'] = $tdata;
        }

        $xml_object_var = get_object_vars($xml_object);

        foreach($xml_object as $key => $value)
        {
            $obj_value = $xml_object_var[$key];

            $attr = null;
            if (is_object($value))
            {
                $attr = get_object_vars($value->attributes());

                if ($attr)
                {
                    foreach($attr['@attributes'] as &$tmp_value)
                    {
                        $format_attribute_value($tmp_value);
                    }
                    unset($tmp_value);
                    $attr = $attr['@attributes'];
                }
            }

            if (is_string($obj_value))
            {
                $format_attribute_value($obj_value);

                if ($attr)
                {
                    if (true===$attribute_key)
                    {
                        # 合并到一起
                       $rs[$key] = $attr + array('@data' => $obj_value);
                    }
                    else
                    {
                        $rs[$key] = array
                        (
                            $attribute_key => $attr,
                            '@data'        => $obj_value,
                        );
                    }
                }
                else
                {
                    $rs[$key] = $obj_value;
                }
            }
            else
            {
                if (is_array($obj_value))
                {
                    if ($recursion_depth>0)unset($rs['@name']);
                    $rs[] = $exec_xml_to_array($value, $attribute_key, $recursion_depth+1, $max_recursion_depth);
                }
                else
                {
                    $rs[$key] = $exec_xml_to_array($value, $attribute_key, $recursion_depth+1, $max_recursion_depth);
                    if (is_array($rs[$key]) && !isset($rs[$key][0]))
                    {
                        unset($rs[$key]['@name']);
                    }
                }
            }
        }

        return $rs;
    };

    return $exec_xml_to_array($xml_object, $attribute_key, 0, $max_recursion_depth);
}



/**
 * 将数组转换成XML字符串
 *
 * 本方法的反向方法为 `xml_to_array($xml_string)`
 *
 *     // 返回格式化好的XML字符串
 *     array_to_xml($arr);
 *
 *     // XML缩进使用4个空格
 *     array_to_xml($arr, '    ');
 *
 *     // 返回不带任何换行符、空格的XML
 *     array_to_xml($arr, '', '');
 *
 * @param array $array 数组
 * @param string $tab 缩进字符,默认 tab 符
 * @param string $crlf 换行符,默认window换行符
 * @param string $attribute_key XML的attributes所在key,默认 `@attributes`
 * @param string $xml_header_string XML第一行声明的字符串
 * @return string
 */

function array_to_xml(array $array, $tab = "\t", $crlf = "\r\n", $attribute_key = '@attributes', $xml_header_string = null)
{
    if (!$xml_header_string)
    {
        $w = '?';
        $xml_header_string = '<'. $w . 'xml version="1.0" encoding="UTF-8"'. $w .'>';
    }

    $format_attribute_value = function (& $value)
    {
        if (true===$value)
        {
            $value = 'true';
        }
        elseif (false===$value)
        {
            $value = 'false';
        }
        elseif (null===$value)
        {
            $value = 'null';
        }
    };

    $format_to_xml_string = function($array, $attribute_key, $crlf, $tab, $left_str = '') use(&$format_to_xml_string, $format_attribute_value)
    {
        $str = '';

        if (isset($array['@name']))
        {
            $str .= "{$crlf}{$left_str}<{$array['@name']}";

            if (isset($array[$attribute_key]))
            {
                foreach($array[$attribute_key] as $k=>$v)
                {
                    $format_attribute_value($v);
                    $str .= " $k="{$v}"";
                }
            }

            $str .= ">";

            $close_str = "{$crlf}{$left_str}</{$array['@name']}>";

            $left_str .= $tab;
        }
        else
        {
            $close_str = '';
        }

        if (isset($array['@tdata']))
        {
            $str .= "<![CDATA[{$array['@tdata']}]]></{$array['@name']}>";
        }
        else
        {
            $have_str = false;
            foreach($array as $key => $value)
            {
                if ($key === '@name' || $key === $attribute_key || $key === '@data' || $key === '@tdata')continue;

                $have_str = true;

                if (is_array($value))
                {
                    if (!is_numeric($key))
                    {
                        $str .= "{$crlf}{$left_str}<{$key}";

                        if (isset($value[$attribute_key]))
                        {
                            foreach($value[$attribute_key] as $k=>$v)
                            {
                                $str .= " $k="{$v}"";
                            }
                        }
                        $str .= ">";

                        if (isset($value['@data']))
                        {
                            $format_attribute_value($value['@data']);
                            $str .= "{$value['@data']}";
                        }
                        elseif (isset($value['@tdata']))
                        {
                            $str .= "<![CDATA[{$value['@tdata']}]]>";
                        }
                        else
                        {
                            $tmp_str = $format_to_xml_string($value, $attribute_key, $crlf, $tab, $left_str . $tab);
                            if (''!==$tmp_str)
                            {
                                $str .= $tmp_str."{$crlf}{$left_str}";
                            }
                            unset($tmp_str);
                        }
                        $str .= "</{$key}>";
                    }
                    else
                    {
                        $str .= $format_to_xml_string($value, $attribute_key, $crlf, $tab, $left_str);
                    }
                }
                else
                {
                    $format_attribute_value($value);
                    $str .= "{$crlf}{$left_str}<{$key}>{$value}</{$key}>";
                }
            }

            if ($have_str)
            {
                if ($close_str)
                {
                    $str .= $close_str;
                }
            }
            else
            {
                $str .= "</{$array['@name']}>";
            }
        }

        return $str;
    };


    return $xml_header_string . $format_to_xml_string($array, $attribute_key, $crlf, $tab, '');
}






print_r($arr = xml_to_array('http://flash.weather.com.cn/wmaps/xml/china.xml'));

echo array_to_xml($arr);