![](http://oshiete.xgoo.jp/images/v2/pc/qa/question_title.png?8acaa2e)
<?xml version="1.0"?>
<root>
<items>
<item>
<id>1</id>
<name>商品(赤)</name>
<price>1500</price>
<color>red</color>
<size>8</size>
<option>A</option>
</item>
<item>
<id>2</id>
<name>商品(青)</name>
<price>700</price>
<color>blue</color>
<size>3</size>
</item>
<item>
<id>3</id>
<name>商品(緑)</name>
<price>1200</price>
<color>green</color>
<size>5</size>
<option>C</option>
</item>
</items>
</root>
上記のようなXMLデータをPHPでパースし、
1つずつ、配列に入れていきたいと考えていますが、
問題なのは、
上から2番目の商品(青)には、<option>データが無いということです。
これは、この<option>が、オプションが有る時にだけ存在するタグだからです。
//-----------------------------
<?php
$doc->load($url); //上記xmlデータのあるURL:$url
$array = array();
$i = 1;
while($i <= 3){ //実際には、もっと商品数はあります。ここでは例として、3。
//配列の初期化
$item_array = array();
$items = $doc->getElementsByTagName('id');
$item_array['id'] = $items->item($i)->nodeValue;
$items = $doc->getElementsByTagName('name');
$item_array['name'] = $items->item($i)->nodeValue;
//*
//*同様のコードが続く
//*
$items = $doc->getElementsByTagName('option');
$item_array['option'] = $items->item($i)->nodeValue;
//*
//*同様のコードが続く
//*
//商品データが詰まった配列を、さらに配列に入れる
$array[] = $item_array;
}
//全ての商品のデータが詰まった配列を表示する。
print_r($array);
?>
//-----------------------------
このようなことがしたいわけです。
なお、タグ名を$tag_array = array('id','name',,,,,'option',,,)として、
foreach($tag_array as $tag){
$items = $doc->getElementsByTagName($tag);
$item_array[$tag] = $items->item($i)->nodeValue;
}
という風にした方が冗長ではないかとは思いますが、
それは、まぁ横に置いておきまして、
このコードで問題なのは、<option>が途中、つまり2番目で抜けると、
3番目の<option>データが、2番目の商品の<option>データとして配列に入ってしまうことです。
上から順に詰めて、配列に格納されてしまい、困っている、ということです。
<option>データが無い場合は、「ナシ」とでもしておきたいのですが、
どうしたら良いでしょうか?
<item>を1つずつ
$items = $doc->getElementsByTagName('item');
で取得して、
各<item>の中の、<option>にアクセスする、なんて方法があるとベストなのですが、
そんな方法はありますでしょうか?
firstChildですと、<item>の場合、<id>になってしまうため、<option>にはアクセスできません。
firstChild以降のChildには、どうやってアクセスしたら良いのでしょうね?
どなたか、宜しくお願い致します。
No.2ベストアンサー
- 回答日時:
getElementsByTagNameが返すのは、DOMNodeListクラスで、
http://www.php.net/manual/ja/class.domnodelist.php
DOMNodeListクラスは、lengthというプロパティを持っています。
<?php
$items = $dom->getElementsByTagName('item');
$results();
$item_props = array('id'=>null, 'name'=>null, 'price'=>null, 'color'=>null, 'size'=>null, 'option'=>null);
$item_keys = array_keys($item_props);
for($i=0; $i<$items->length; $i++){
$item_array = array();
/*
下記が返すのはDOMNode
http://www.php.net/manual/ja/class.domnode.php
*/
$item = $items->item($i);
//DOMNodeにはchildNodesプロパティがある
$children = $item->childNodes;
for($j=0; $j<$children->length; $j++){
$child = $children->item($j);
$name = $child->nodeName;
//念のため小文字にしてみる
$name = strtolower($name);
if(in_array($name, $item_keys)){
$item_array[$name] = $child->nodeValue;
}
}
$results[] = array_merge($item_props, $item_array); //初期値にマージしてやればなければ初期値が入る
}
?>
とか、ですかね。
非常に分かりやすい回答をありがとうございます。
xml文書のパースの仕方には色々な方法があるようなので、
質問文では、とりあえず、私のしたいことを書かせて頂きました。
質問文に挙げたxml文書の例は、あくまで1例で、
実際にはもっと階層が深かったりと複雑なため、
どのようなxml文書でも対応できる、根本的な理解と、
それに基づいた方法を教えて頂けると良いなと考えていました。
また、私の好き好き(多くの場合、知識がないことによる食わず嫌い)にも
よるかとは思いますが、
こちらの回答の方法が私には合っていると感じました。
xmlのパースの方法には、
simpleXML、DOM, XPathを使用した方法、などなど
色々あるようですが、
今までの所、三者三様の回答をして頂き、
比較しながら勉強させて頂きました。
どの方法でも、目的は果たせる、
しかし、長所短所がそれぞれにはありそうだなと思っています。
もし、この辺りのことについて、ご存知でしたら教えて頂けると嬉しいです。
それと、細かいことになりますが、数点質問をさせて下さい。
(1)$results(); とありましたが、
これは、配列変数として初期化しているということでしょうか。
初歩的な質問で、すみません。
なぜ初期化しているのか、ではなく、
配列の初期化にはこういう方法もあるんですね、というのが質問の主旨です。
(2)for($i=0; $i<$items->length; $i++){ のところで、
これは、foreach($items as $item){ とすることと意味はほぼ同じだと
考えてもよろしいでしょうか。
$items->length; を使わずに全件処理する場合には、foreachを使いたいかなと
個人的には思ったのですが、$items->length;を使うメリットがもしありましたら、
教えて下さい。
以上、おかげさまで、
NodeとNodelistの違いと、それらの扱い方について理解できたことで、
したかったことができるようになりました。問題解決です。
No.8
- 回答日時:
> あらかじめ、どの階層に、どんなタグがあるか分かっている場合に
そのために、DTD や スキーマがあるんだけど、そこまで書く人は少ないわな。
XPath の場合、相対的な指定ができるから(上がることも可能)指標(必要な情報)が分かれば十分なんだけどね。
XPath の魅力を語っても仕方ない。イテレーションするか、ピンポイントで拾うか、XSL、XQuery でまとめて拾うかは、作成者の自由だし。
jQuery はバージョンいくつからか(元々その傾向はあったけれど)、Selecters API を採用している。あれはあれで使いやすい。
イテレーションの場合、Traversal API が使えると一番いいんだけど、PHP のライブラリとしてはまだ存在しないから、地味に 参照を辿っていくしかないのが残念。
何だかんだいって現状は、既存 API のバージョンアップと、各種モジュールの追加に期待するしかない。
お返事が遅くなり、すみませんでした。
私にはまだ難しい用語もいくつかありましたが、
今回、とても勉強になりました。
特にXPathの記述例をいくつか挙げて頂けたことに感謝しています。
またXMLのことで分からないことがありましたら、教えて下さい。
以上、最後まで丁寧に付き合って頂き、本当にありがとうございました。
No.7
- 回答日時:
基本的には、文字列操作を行う場合に、文字列を上から順番に読み解いていくのか、正規表現を使うのかの違いぐらいの内容、と思ってもらえればいいのかなと思います。
または、JavascriptでDOMをゴリゴリ操作するのか、jQueryを使うのか、といったことに意味合いとしては似てると思います。
そう考えれば、XPath構文のわかりやすさは、yuu_xさんの仰るように、慣れです。
分かってしまえば、こまわりも効くし扱いやすい。
でも、XPathは、アクセス後に返してくるオブジェクトは、やっぱり基本のDOMNodeListだったりするわけですので、
そこから先の操作は基本のDOMDocumentを使ったときと同じ構文を使うので、
両方の特性を上手く組み合わせて、上手く付き合っていけばいいかなと。
下記、余談です。このXMLやHTMLを操作するためのライブラリって色々あるので、軽くご紹介を。
ライブラリの挙動を追えば、どうやって操作したらやりやすいのかとか、理解も深まるかも。
■XML_Unserializer
XMLを単純に配列に落とすだけだったら、PEARに、XML_Unserializerというライブラリもあって、
コレはもともとPHP4でも使えるように成っているので、DOMDocumentやXPathなんかは使われておらず、
最初に書いたような文字列操作と正規表現で行っています。
URL:http://pear.php.net/manual/ja/package.xml.xml-se …
■CakePHP::XMLクラス
CakePHPも自前で、XMLクラスといったものを搭載してますが、同様な理由(PHP4でも使える)で、
DOMDocumentやXPathは使ってなかったりします。
■PHP Simple HTML DOM Parser
コレもDOMDocumentやXPathなどは使ってないものです。jQueryと同じ構文でかけるので扱いやすい
URL:http://simplehtmldom.sourceforge.net/
■phpQuery
こちらは上記の物とは違ってPHP5用ということで、内部的にはDOMElementを使っているし、XPathも使います。
http://code.google.com/p/phpquery/
ちょいちょいバグがあるので安定性はかけますが・・・
回答ありがとうございます。
>XPathは、アクセス後に返してくるオブジェクトは、やっぱり基本のDOMNodeListだったりするわけですので、
そこから先の操作は基本のDOMDocumentを使ったときと同じ構文を使うので、
両方の特性を上手く組み合わせて、上手く付き合っていけばいいかなと。
なるほど、納得です。
よくわかりました。
また、余談の件も参考にさせていただきます。
大変、勉強になりました。
No.6
- 回答日時:
> ゴチャゴチャ
w。
慣れるまではそう見えるかもしれない。どんな言語に対しても言えそうだけど、何が書いてあるか分からないうちは、ただの羅列にしか見えない。
ある程度までなら、省略構文が使えるので、それを利用してもいい。
> ある1つの<item>に対してメソッド実行すると、その<item>の中にある<price>1つが対象となる、
# items の直属の子の文脈順の最初の item の子の price ノード
$xpath->evaluate('/root/items/item[1]/price')->item(0);
// 半強制的に DOMNodeList が返る。最初の price ノードが必要な場合は、やや間抜けだけど、戻り値に対して item(0) を指定する。
# items の直属の子の文脈順最初の item の price の値
$xpath->evaluate('number(/root/items/item[1]/price)');
// PHP の場合、戻り値を指定できないため、キャストの手間が入るのは仕方ない。
# name 直下のみ
$xpath->evaluate('string(/root/items/item[1]/name/text())');
<item>
<name>商品<ruby>しょうひん</ruby></name>
</item>
# id=1 の item
$xpath->evaluate('number(/root/items/item[id=1]/price)');
# 最初の item を軸とし、その直属の子 items を選択
$item = $xpath->evaluate('/root/items/item[1]')->item(0);
$xpath->evaluate('child::items', $item);
<item>
<items>
</items>
</item>
<item/>
<item/>
# $item の次の兄弟 item
$xpath->evaluate('following-sibling::item[1]', $item);
後はマニュアルを見てください。
http://www.w3.org/TR/xpath/ - XML Path Language (XPath) Version 1.0
http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html - Document Object Model XPath
補足、ありがとうございます。
お返事が遅くなり、すみません。
今回、XPathによる方法で対処することはほとんど考えていなかったのですが、
このように分かりやすく解説して頂けると、
そうかXPathも捨てたもんじゃないな、いやむしろ、素晴らしいかも?!
と思えてきてしまいますね。
XPathではなく、DOMDocumentを使う方法で、
$items = $doc->getElementsByTagName('item');
foreach($items as $item){
$child_nodes = $item->childNodes;
foreach($child_nodes as $child){
//…この階層でのデータ抽出
foreach(…){
//…この階層でのデータ抽出
//…さらに深く掘り下げ、この下の階層にも手を伸ばし、
//…目的のタグのnodeValueの抽出を行う
}
}
}
のように、今のところは書いているわけですが、
1階層で、複数のタグのデータを抽出できるならともかく、
深い階層まで手を伸ばしているにもかかわらず、
抽出するデータは1つか2つなんて場合には、
foreachの繰り返しばかりが増えて、なんだかコードが冗長になってしまい、
これで良いのだろうかと今、ちょっと疑問に思っています。
このような場合、
DOMDocumentではなく、やはりXPathを使用した方が良いのでしょうかね。
XPathだと、あらかじめ、どの階層に、どんなタグがあるか分かっている場合に、
遠回りせずに、直接そのノードへ手を伸ばせる感じがいいなと思っています。
色々と、すみません。
ご迷惑でなければ、また教えて下さい。
No.5
- 回答日時:
■$results();の件
ごめんなさい・・・完全にtypoです・・・・
$results = array();
と書きたかったのでした。
■lengthプロパティを使ってforで回した理由
とりあえずPHPマニュアルのDOMNodeListクラスの最初の説明におきまして、
特にIteratorインターフェイスを実装していたりして無さそうだったので、
プロパティとメソッドから判断して書きました。
よく見たらforeachで回せるんですね。すみません。
■SimpleXMLを使うのかDOMDocumentを使うのかXPathを使うのか
基本的にSimpleXMLは、入力されている各要素が分かっている、既知のフォーマットのときに使うと便利かなと。(RSSのパースとか)
DOMDocumentは、読み込めるのがXMLだけではなく、HTMLも読むことが出来るので、
HTMLを読み込んで、抽出だとか置換だとかをするのに適しています。
丁度javascriptでHTMLを書き換えるような感じで使ったりします。
XPath、ある特定の属性をもつ要素をすべて抽出したいとかといった場合に便利ですね。
<?php
$xpath = new DOMXPath($dom);
$elements = $xpath->query("//*[@class='hoge']"); //class="hoge"のものを全部取得する
?>
などといった記述をすれば、NodeListを返しますので、非常に簡単です。
補足をありがとうございます。
お返事が遅れ、すみませんでした。
>■SimpleXMLを使うのかDOMDocumentを使うのかXPathを使うのか
分かりやすくまとめて頂き、非常に参考になりました。
ありがとうございます。
hogehoge78さんに教えて頂きました方法、
つまり、DOMDocumentを使って、
ループ処理で、ノードリストからノードを抽出し、
その中のノードをノードネームで判定して、
適宜、必要なデータを抜いていく、という方法が、
XML文書のパースの基本だと私は考えていまして、
実際に、その方法で、今、私の目の前にあるXML文書に対しても、
パースを試みようとして、以下のようなコードを書き、
問題なくプログラムを稼働させられています。
//--------------------------------------------
$items = $doc->getElementsByTagName('item');
foreach($items as $item){
$child_nodes = $item->childNodes;
foreach($child_nodes as $child){
//…この階層でのデータ抽出
foreach(…){
//…この階層でのデータ抽出
//…さらに深く掘り下げ、この下の階層にも手を伸ばし、
//…目的のタグのnodeValueの抽出を行う
}
}
}
このコードは、動作的には問題ないのですが、
質問で挙げましたXML文書の例とは異なり、
もっと階層が深かったり、階層の深浅が複雑な文書の場合には、
コードの美しさの観点から、あまりオススメとは言えないのでしょうか?
つまり、
foreachで回すようなコードを書くことになるDOMDocumentを使うよりも、
ピンポイントで必要箇所のみを容易に抜くことが可能なXPathを
使用する方がより実際的なのでしょうか?
それとも、
私に知識がないために、このような考え方しかできていないのでしょうか。
質問で挙げたXML文書が単純だったので、今回は、
DOMDocumentによる方法を教えて下さったのだと思います。
私も当初、それを望んでいましたし、
また、その理解を得たおかげで、
今、こうして新たな疑問に直面できてもいるわけです。
というわけで、まとまりのない文章ですが、
何かアドバイスをして頂けますと、とても嬉しいです。
ご迷惑でなければ、また宜しくお願い致します。
No.4
- 回答日時:
XPath の利点は細かな調節ができるところにあるけど、平文 XML を配列に落とすだけなら、微調整もへったくれもない。
一点挙げるとすれば、getElementsByTagName はデフォルト空間の子孫ノードを全て拾うため、階層構造になったときに対応しきれなくなる。
> どのようなxml文書でも対応できる、根本的な理解と、それに基づいた方法を教えて頂けると良いなと考えていました。
XML の抽出には強力な XSL 及び XQuery があるため、抽出を自由に行いたいのであれば、そちらをお勧めします。
PHP では XSLT 1.0 しか利用できないけど、幸い、php:function を利用できるため、それほど不自由は感じない。
XML であることの利点は、階層構造は元より、環境を問わず(この利点は大きい)部分的な抽出、取り込み、関連付け、要素への重み付け等が自由に行えるところにある。
XML であることを放棄して、配列に落とすなら、PHP でロジックを組むことになるのだけれど、その辺は好きなように。
PHP だけで狭視的に利用するだけなら、CSV で十分だと思うんだけどね。必要に応じてデータベースに落とせるし。
回答ありがとうございます。
>XPath の利点は細かな調節ができるところ
そうですね、利点はそのようなところにあるのだと思っていました。
ただ、XPathのコード記述は見た目がゴチャゴチャしていて、
個人的には好きではないんですよね。
あと、処理速度は遅くならないのかなぁ、という気もしています。
>一点挙げるとすれば、getElementsByTagName はデフォルト空間の子孫ノードを全て拾うため、階層構造になったときに対応しきれなくなる。
ここ、そうでした、ここです。
この点で、「こんなことはできないんですか?」
という疑問がありまして、
つまり、
getElementsByTagNameをルート要素以外に対しても使いたいのですが、
そんなことはできないんですよね?
というか、それと似たようなことを可能にしたのが、XPathなのでしょうか。
getElementsByTagName('price')
というのを、<root>に対してメソッド実行すると、
各<item>の中にある<price>が
全てが対象になりますが、これを、
ある1つの<item>に対してメソッド実行すると、
その<item>の中にある<price>1つが対象となる、
みたいな感じで使えたらいいなと思っているわけです。
分かりにくかったら、すみません。
No.3
- 回答日時:
$ary = array(
'id'=> 'number'
,'name'=> 'string'
,'price'=> 'number'
,'color'=> 'string'
,'size'=> 'number'
,'option'=> 'string'
);
$result = array();
$doc = new DOMDocument;
$doc->loadXML($xml);
$xpath = new DOMXPath($doc);
foreach ($xpath->query('/child::root/child::items/child::item') as $item) {
$params = array();
foreach ($ary as $name => $type) {
$params[$name] = $xpath->evaluate("{$type}(child::{$name}/child::text())", $item);
}
$result[] = $params;
}
var_dump($result);
配列に置き換えるくらいなら、正直 simple_xml のが楽。
XML の構造が単純だし、文字列変換して eval したい気分にもなる。
回答ありがとうございます。
DOMXPathを用いた方法を教えて頂き、大変参考になりました。
私に知識がないために、
本件において、DOMXPath を使う意義が分からなかったのですが、
もし何らかの意図なり目的があるようでしたら、教えてください。
最善かどうかは別として、
「他にも、こんな方法がありますよ」
という意味で、DOMXPathによる方法を教えて頂いたのでしょうか。
>配列に置き換えるくらいなら、正直 simple_xml のが楽。
とありましたから、
「回答NO.1の方法で良いが、他に方法はないかと問われれば、
DOMXPathによる方法を書きますが。」
といったお気持ちだったのでしょうか。
(失礼な書き方をしていたとしたら、すみません!)
もし宜しければ、教えて下さい。
No.1
- 回答日時:
simplexml_load_file を使います。
-- テストソース
$xml = simplexml_load_file('sample.xml');
print_r($xml);
print $xml->items->item[0]->option;
print isset($xml->items->item[0]->option)? "true":"false";
print isset($xml->items->item[1]->option)? "true":"false";
-- 出力結果(カッコなど整形してます)
[items] => SimpleXMLElement Object
[item] => Array
[0] => SimpleXMLElement Object
[id] => 1
[name] => 商品(赤)
[price] => 1500
[color] => red
[size] => 8
[option] => A
[1] => SimpleXMLElement Object
[id] => 2
[name] => 商品(青)
[price] => 700
[color] => blue
[size] => 3
[2] => SimpleXMLElement Object
[id] => 3
[name] => 商品(緑)
[price] => 1200
[color] => green
[size] => 5
[option] => C
A
true
false
大変わかりやすい回答をありがとうございます。
>simplexml_load_file を使います。
こちらの方法について、よく分かりました。
ありがとうございます。
childNodesを用いた方法による回答を想定していましたが、
教えて頂いた方法でも、私の希望を叶えてくれますね。
とても勉強になりました。
ありがとうございます。
一方で、
simplexmlを使わない方法でも、
何か方法がないか知りたいと思っています。
もしどなたかご存知でしたら、教えて下さい。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- PHP PHPで画像の渡しが上手く行きません。 1 2023/02/02 09:39
- JavaScript 2段階プルダウンで1段階目の選択を終えた後に選択ボックスを見えなくしたい 2 2022/07/05 21:58
- JavaScript sessionStorageを調べています。 1 2023/06/20 12:41
- オープンソース cssで中央寄せ 1 2023/05/19 06:25
- JavaScript 以前の質問だと、どの条件でも配列が表示されてしまいます。 1 2022/07/09 11:40
- HTML・CSS FC2カートのテンプレートでの商品表示について 1 2023/03/02 18:05
- HTML・CSS cssが効かなくて困ってます 1 2023/01/01 23:57
- C言語・C++・C# c言語の問題です 課題1 (二分探索木とセット) 大きさ size の配列 array を考える。す 2 2023/01/10 21:08
- JavaScript セレクトボックスで配列を呼び出したい。 1 2022/07/08 20:14
- JavaScript console.logがどうしても2つ機能しないのでアドバイスをくださいお願いします 2 2022/07/07 22:13
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
【PHP】配列のキー名の修正は可...
-
am()の使い方
-
C言語の配列をPush(追加)する...
-
mb_encode_numericentityでタグ...
-
Smarty ループ回数を 可変にし...
-
これって何ソート?
-
連想配列の要素を追加
-
行列
-
PHPのセレクトボックスの初期値...
-
CArrayの要素としてCStringArra...
-
配列を指定した順序でソート
-
CArrayのソート
-
PHP5でxml文書をパースする方法
-
np.stack()とnp.array()の違い
-
特定の文字で分割する再帰関数
-
重複確認
-
別ファイルの構造体の値を読み...
-
総当り表
-
PHPで変数名にハイフンを使うに...
-
foreachで上限回数指定方法また...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
プログラミングのPythonのnoteb...
-
C言語の配列をPush(追加)する...
-
php で1から100までの素数の表...
-
STLのvectorで作った配列をメン...
-
スカラーのベクトル微分
-
行列
-
Perlで重複行を削除したい
-
配列の要素(value)に、変数を...
-
特定の文からメールアドレスの...
-
forとかで連番の変数を一気に格...
-
C言語 最大値と最小値を求めて...
-
PHPでユニークIDを作る
-
CArrayの要素としてCStringArra...
-
fgetc関数について
-
読み(あ行~わ行)ごとに分け...
-
verilogで、配列の一部をタスク...
-
Pager::factory()の使い方がわ...
-
mallocで char *型の配列を確...
-
連想配列で値が空だったら、要...
-
c言語 単位行列
おすすめ情報