[PHP] xmlを配列にする。

PHPでXML形式のテキストデータを連想配列にする関数を作りました。

PHPでXMLを配列にするものは既に存在します。

PHPで、XML形式のテキストデータを、連想配列にするというと、似たようなものとしてこういうのがありますね。

はい。そういうわけで、実は既にあるんです。
既にあるのに、同じようなものを作る。
こういう行為を「車輪の再発明」なんて言ったりします。

PHPでXMLを配列にするものは既に存在するけど・・・

とはいえ、今回作ったのには、やはり理由がありまして、
その、
なんていうか、メンドクサイじゃないですか?

↑ であげた既存のものを使っていらっしゃるかたはいるでしょうか?
ご存知のない方は、Googleで軽く調べて頂ければと思うのですが、なんだかややこしくないですか?

XMLを連想配列にしたいだけなのに。

で、PHPでXMLを配列にする関数を作りました

これが、今回作った関数になります。

/*
 * simplexml形式を連想配列にする。
 * $sxml = simplexml_load_string($xml);
 * $retArray = xml2array($sxml);
 */
function xml2array(&$sxml, $isRoot = true)
{
    if ($isRoot) {
        return array(
            $sxml->getName() => array(
                xml2array($sxml, false)
            )
        );
    }

    $r = array();
    foreach($sxml->children() as $cld){
        $a = &$r[(string)$cld->getName()];
        $a = &$a[count($a)];
        if (count($cld->children()) == 0) {
            $a['_value'] = (string)$cld;
        } else {
            $a = xml2array($cld, false);
        }
        foreach($cld->attributes() as $at) {
            $a['_attr'][(string)$at->getName()] = (string)$at;
        }
    }

    return $r;
}

これだけです。

その代わりと言ってはなんですが、なんでもかんでも対応しているわけではありません。
[CDATA] など、凝ったものは省いております。
それと、simpel_xmlを扱いますので、PHP5以上の対応となります。

PHPでXMLを配列にするxml2array()で対応していること

まず、XMLのツリー形式そのままで、多階層な連想配列にします。
値(value)と属性値(attribute)を取り込みます。

PHPでXMLを配列にするxml2array()の使い方例

サンプルとして実際のコードを書いてみます。

// XMLを用意します。
// ファイルから読みこんでもOKです。
$xml =<<<EOT
<?xml version="1.0" encoding="utf-8" ?>
<area>
    <pref code="0001">
        <name no="1">北海道</name>
        <kana>ほっかいどう</kana>
    </pref>
    <pref code="0002">
        <name no="2">青森県</name>
        <kana>あおもりけん</kana>
    </pref>
    <pref code="0003">
        <name no="3">岩手県</name>
        <kana>いわてけん</kana>
    </pref>
</area>
EOT;

// simplexml として、読みこみます。
$sxml = simplexml_load_string($xml);

// xml2array に渡します。
$retArray = xml2array($sxml);

ここまでで、$xmlは、$retArray として連想配列になっています。

試しに出力してみましょう。

// for文で、取り出してみます。
foreach($retArray['area'] as $area){
    foreach($area['pref'] as $pref){
        $name = $pref['name'][0];
        $kana = $pref['kana'][0];
        echo $pref['_attr']['code'];
        echo ' : ' . $name['_value'];
        echo '(' . $name['_attr']['no'] . ')';
        echo ' : ' . $kana['_value'];
        echo '<br />';
    }
}

↓下記のように出力されます。

0001 : 北海道(1) : ほっかいどう
0002 : 青森県(2) : あおもりけん
0003 : 岩手県(3) : いわてけん

print_rで、配列の中を覗いてみます。

// print_r で、配列の中を出力します。
echo '<pre>';
print_r($retArray);
echo '</pre>';

↓ 下記が出力されます。

Array (
    [area] => Array (
        [0] => Array (
            [pref] => Array (
                [0] => Array (
                    [name] => Array (
                        [0] => Array (
                            [_value] => 北海道
                            [_attr] => Array (
                                [no] => 1
                            )
                        )
                    )
                    [kana] => Array (
                        [0] => Array (
                            [_value] => ほっかいどう
                        )
                    )
                    [_attr] => Array (
                        [code] => 0001
                    )
                )
                [1] => Array (
                    [name] => Array (
                        [0] => Array (
                            [_value] => 青森県
                            [_attr] => Array (
                                [no] => 2
                            )
                        )
                    )
                    [kana] => Array (
                        [0] => Array (
                            [_value] => あおもりけん
                        )
                    )
                    [_attr] => Array (
                        [code] => 0002
                    )
                )
                [2] => Array (
                    [name] => Array (
                        [0] => Array (
                            [_value] => 岩手県
                            [_attr] => Array (
                                [no] => 3
                            )
                        )
                    )
                    [kana] => Array (
                        [0] => Array (
                            [_value] => いわてけん
                        )
                    )
                    [_attr] => Array (
                        [code] => 0003
                    )
                )
            )
        )
    )
)

PHPでXMLを配列にするポイント

print_rの結果をご覧頂いたように、XMLのツリー構造を保ったまま連想配列にしています。
ここでいくつかポイントを書いておきます。

ノードの扱いについて

ノードは、自身の直下に、配列を作ります。
ノードでは、 pref[0], pref[1], ppref[2] と3つ作られています。
pref[0]が、XMLで最初に現れるに対応します。
pref[1]は2番目に、pref[2]は3番目のに対応・・・と続きます。

末端ノードのとき

ノードが末端ノードの時は、その値を['_value'] に、値がセットされます。
※ _value とアンダーバーから始まることに注意してください。

属性の参照

属性値(Attribute) は、 ['_attr'] にセットされます。
['_attr'] の中の、添え字は、XMLで使われている属性名となります。
※ _attr とアンダーバーから始まることに注意してください。

XMLに名前空間が使われていたら・・・

simple_xmlで、名前空間を扱おうとすると少々面倒な記述になります。
具体的には、 $simple_xml->children('名前空間のURI'); といった感じで、単純にchildrenだけでは取れません。

ちなみに、XMLで定義されている名前空間は、$simple_xml->getDocNamespaces(true); これで取ることができます。

ただ・・・、実際の場面において、名前空間まで意識することはあまり無いかと思います。
少なくとも、私が扱うなかでは、名前空間まで厳密にチェックして値を取得する、ほどのことは必要とすることがまずありません。

そこで、元のXMLから、名前空間の指定を単純に取り外すことで、名前空間付きのXMLに対応することにします。

$xml = preg_replace('/<([/]?)[A-Za-z0-9]+:([A-Za-z0-9]+)( xmlns:[^>]+)?>/', '<12>', $xml);
$sxml = simplexml_load_string($xml);

このように、simplexml_load する前に、preg_replace で、名前空間の指定を取り外します。

※ 名前空間まで厳密にチェックする必要があるかどうかは、案件ごとに異なると思います。
ご使用の際は、案件の仕様を満たすかどうか、ご自身の判断でお使いください。