ラベル simplexml の投稿を表示しています。 すべての投稿を表示
ラベル simplexml の投稿を表示しています。 すべての投稿を表示

2011年3月23日水曜日

PHPの SimpleXMLを配列に変換する

シンプルなXMLなら、SimpleXMLElementオブジェクトをarrayにキャストするだけで連想配列に変換できる。

$xml = '<?xml version="1.0" ?>
<root>
<test>TEST1</test>
<test>TEST2</test>
<attr trouble="MISSING">WHY?</attr>
</root>';

$sx = simplexml_load_string($xml);

var_dump((array)$sx);

array(2) {
  ["test"]=>
  array(2) {
    [0]=>
    string(5) "TEST1"
    [1]=>
    string(5) "TEST2"
  }
  ["attr"]=>
  string(4) "WHY?"
}

同じ要素名の要素がある場合(上記の例ではtest要素)、まとめて添字が0から始まる配列になる。
しかし、なぜか属性とテキストノードの両方がある要素の属性(上記の例ではattr要素のtrouble="MISSING")が無視される。

また、子要素や属性のあるXMLの場合、配列の中まで再帰的にキャストしていないのでSimpleXMLElementのままになってしまう。
$xml = '<?xml version="1.0" ?>
<root>
<parent>
<child>MUSUKO</child>
</parent>
<attr foo="FOO" bar="BAR" />
</root>';

$sx = simplexml_load_string($xml);

var_dump((array)$sx);

array(2) {
  ["parent"]=>
  object(SimpleXMLElement)#3 (1) {
    ["child"]=>
    string(6) "MUSUKO"
  }
  ["attr"]=>
  object(SimpleXMLElement)#2 (1) {
    ["@attributes"]=>
    array(2) {
      ["foo"]=>
      string(3) "FOO"
      ["bar"]=>
      string(3) "BAR"
    }
  }
}




これに対して、PHPマニュアルのコメント欄に一発で配列に変換する方法が書いてあった。
$xml = '<?xml version="1.0" ?>
<root>
<parent>
<child>MUSUKO</child>
</parent>
<attr foo="FOO" bar="BAR" />
</root>';

$sx = simplexml_load_string($xml);

var_dump(json_decode(json_encode($sx), true));

array(2) {
  ["parent"]=>
  array(1) {
    ["child"]=>
    string(6) "MUSUKO"
  }
  ["attr"]=>
  array(1) {
    ["@attributes"]=>
    array(2) {
      ["foo"]=>
      string(3) "FOO"
      ["bar"]=>
      string(3) "BAR"
    }
  }
}



json_decode()の第2引数(戻り値を連想配列にするかどうか)をtrueにするのがポイント。
属性は@attributesというKEYの連想配列に格納される。


しかし、属性とテキストノードの両方がある要素の属性が無視される問題は、このjson_encode/json_decodeを使った方法でも発症する。
$xml = '<?xml version="1.0" ?>
<root>
<attr trouble="MISSING">WHY?</attr>
</root>';

$sx = simplexml_load_string($xml);

var_dump(json_decode(json_encode($sx), true));

array(1) {
  ["attr"]=>
  string(4) "WHY?"
}

Why?
まあでも属性値がないことが分かっているXMLならこれで使えそう。(後から属性値を使いたいということになったら大変だけど。)

きちんとやりたいなら、SimpleXMLで取得したオブジェクトを属性も含めて配列に変換 | とりさんのソフト屋さんのやり方がスマートで良さそう。

2009年2月11日水曜日

SimpleXMLで子要素を取り出すいろいろな方法

PHPのSimpleXMLオブジェクトで子要素を取り出すためのいろいろな方法のメモ。

元のXMLがこんな感じの場合、

$str = <<< XML_END
<?xml version="1.0" encoding="UTF-8" ?>
<root>

 <parent>

  <child>aaa</child>
  <child>bbb</child>
  <child>ccc</child>
  <count>3</count>
 </parent>
 <hy-phen>hyphen</hy-phen>
</root>

XML_END;

$xml = simplexml_load_string($str);

普通に子要素を取り出す場合
var_dump($xml->parent->count);

object(SimpleXMLElement)[5] string '3' (length=1)

繰り返しの子要素を同じように取り出そうとすると、1つ目だけ取り出してしまう
var_dump($xml->parent->child);

object(SimpleXMLElement)[4] string 'aaa' (length=3)

1つ目以外が欲しい場合はインデックスで指定できる
var_dump($xml->parent->child[2]);

object(SimpleXMLElement)[5] string 'ccc' (length=3)

繰り返しの子要素の全てを取り出す場合、foreachが使える
foreach ($xml->parent->child as $c) {
var_dump($c);
}

object(SimpleXMLElement)[3] string 'aaa' (length=3)
object(SimpleXMLElement)[6] string 'bbb' (length=3)
object(SimpleXMLElement)[3] string 'ccc' (length=3)

さらに、繰り返しの子要素だけでなく、子要素全てを取り出す場合はchildren()を使う
foreach ($xml->parent->children() as $c) {
var_dump($c);
}

object(SimpleXMLElement)[4] string 'aaa' (length=3)
object(SimpleXMLElement)[3] string 'bbb' (length=3)
object(SimpleXMLElement)[4] string 'ccc' (length=3)
object(SimpleXMLElement)[3] string '3' (length=1) ← childだけでなく、countも取れている

children()を使うと、繰り返しの子要素は配列にまとめられるようだ(foreachを使う分には意識することは無いけど)
var_dump($xml->parent->children());

object(SimpleXMLElement)[4]
 public 'child' => array
  0 => string 'aaa' (length=3)
  1 => string 'bbb' (length=3)
  2 => string 'ccc' (length=3)
 public 'count' => string '3' (length=1)


XML要素(タグ)の名前がPHPの変数名として使えない文字列の場合、{' '}で囲えばOK
var_dump($xml->{'hy-phen'});

object(SimpleXMLElement)[5] string 'hyphen' (length=6)

NameSpaceを使っている要素の場合、children()のパラメータとしてNameSpaceを指定しないとうまく子要素が取得できない
$children = $xml->children('http://...');
このとき、$childrenのさらに子要素を取得するためには$children->children()しないとなぜかうまく取得できない。
NameSpaceの指定がその子要素に引き継がれて、そのNameSpaceに該当する子要素のみ取得するようにフィルターがかかっている?
それがchidren()をパラメータ無しで呼ぶことによってフィルターが解除されるから?


参考:PHP: SimpleXML - Manual

ブログ アーカイブ

tags