XML_Unserializerの制御

phpXMLを設定ファイルに使うとき、php5なら何だかんだ言ってもSimpleXMLが楽ちんです。一方、php4では自前でパースなんてやってられないので、PEAR::XML_Unserializerが有力な候補になってきます。今日はそんなXML_Unserializerのお話。

<?xml version="1.0" encoding="UTF-8" ?>
<form>
 <element>
  <item value="hoge">hogehoge</item>
 </element>

 <element>
  <item value="foo">FOO</item>
  <item value="bar">BAR</item>
 </element>
</form>

こういう内容のtest.xmlを以下のスクリプトでパースします。

$uns =& new XML_Unserializer(array('parseAttributes' => true));
$status = $uns->unserialize(file_get_contents('test.xml'));
$xml = $uns->getUnserializedData();

この状態で、$xml['element'][0]['item'] と $xml['element'][1]['item'] をダンプしてみると、

array(2) {
  ["value"]=>
  string(4) "hoge"
  ["_content"]=>
  string(8) "hogehoge"
}
array(2) {
  [0]=>
  array(2) {
    ["value"]=>
    string(3) "foo"
    ["_content"]=>
    string(3) "FOO"
  }
  [1]=>
  array(2) {
    ["value"]=>
    string(3) "bar"
    ["_content"]=>
    string(3) "BAR"
  }
}

のように、要素の数によって配列の階層が変わってしまいます。これが以前から悩みの種で、 if (isset($item[0])) 〜 とかで処理の振り分けをしてはモヤモヤした気持ちを抱えていました。
SimpleXMLだと、 foreach ($xml->element->item as $item) {} とかやればよきにはからってくれて快適なので、「やっぱりphp4はXML弱いなぁ」とか思ってました。

ところがどっこい、XML_Unserializerのオプションでこれが解決できることが判明。そのオプションの名は「forceEnum」。

XML_Unserializerのオプション を見ていると、

タグの反復数とは無関係に、順序づけされた配列に自動変換される タグのリストを指定することができます。これによりコード中の if 文を節約することができます。

という微妙によく分からない記述を見付けたのですが、ひょっとしてこれかな…と思ってググってみたら大当たり。
PHP4でAWSのXMLをパースする | ザピースカ2

$uns =& new XML_Unserializer(array('parseAttributes' => true,
                                   'forceEnum'       => array('item')));
$status = $uns->unserialize(file_get_contents('test.xml'));
$xml = $uns->getUnserializedData();

これで、$xml['element'][0]['item'] も

array(1) {
  [0]=>
  array(2) {
    ["value"]=>
    string(4) "hoge"
    ["_content"]=>
    string(8) "hogehoge"
  }
}

と、一つだけの要素でも配列になってくれました。必ず配列として扱うノードをUnserializerに指定する必要はあるけど、これは便利。他のノードを見て階層を揃える、みたいな処理をしてくれてもいい気がするけど、実行速度と実装の手間を考えるとこうなるのも分かる。
何はともあれ、わりと長い間の心のつかえが取れました。やるじゃん、XML_Unserializer。

「forceEnum xml_unserializer」でググっても日本語で3件しかかからないところを見ると、これってレア情報? しかも、リンク先のエントリもつい最近だし。