前回、基本的なファイルの読み書きに関してまとめたので、今回は XML の要素を取得したり、検索する方法についてのメモです。
要素を名前から取得するには、getElementsByTagName(“要素名”) を使います。 <data> ノードの中にある最初の <sample> ノードの内容を表示するサンプルがこちらです。
<?xml version="1.0" encoding="utf-8"?> <data> <sample id="1">red</sample> <sample id="2">green</sample> <sample id="3">test</sample> <sample id="4">blue</sample> <sample id="5">white</sample> <sample id="6">yellow</sample> </data>
<?php $dom = new DOMDocument('1.0', 'UTF-8'); $dom->preserveWhiteSpace = false; $dom->formatOutput = true; $dom->load("sample.xml"); $root = $dom->getElementsByTagName("data")->item(0); $sampleNode = $root->getElementsByTagName("sample")->item(0); echo $sampleNode->nodeValue;
red
getElementsByTagName() を使うと、その親ノードより深いすべてのノードが配列として取得されます。
一番最初(0番目)のアイテムを選択する場合は「item(0)」のように指定します。
ノードから値を取り出す時は「nodeValue」を使えば取得できます。ノードの内容を書き換える際も直接 nodeValue 書き換えます。
$sampleNode->nodeValue = 'New Text';
上の例ではルートノード <data> を getElementsByTagName() で取得しましたが、
documentElement を使うとルートノードの名前がわからない場合でも取得できます。
ルートノードとはドキュメント内で最も浅い親ノードのことで、この場合は <data> です。
よって下記のように記述しても同じ結果を得ることができます。
$root = $dom->documentElement; $sample = $root->getElementsByTagName("sample")->item(0)->nodeValue;
ノードの検索
複数ある <sample> 要素の内、内容が「test」であるノードを取得してみます。
<?php $dom = new DOMDocument('1.0', 'UTF-8'); $dom->preserveWhiteSpace = false; $dom->formatOutput = true; $dom->load("sample.xml"); $root = $dom->documentElement; $search = "test"; $result = null; $sampleNodeList = $root->getElementsByTagName("sample"); foreach($sampleNodeList as $sampleNode){ if($sampleNode->nodeValue === $search){ $result = $sampleNode; break; } } if( !is_null($result) ) echo $result->getAttribute("id");
3
サンプルの $sampleNodes の中には getElementsByTagName() で見つかった全ての sample ノードを格納されています。 foreach でループさせ、値が見つかったら break でループを抜けます。 見つかった要素の属性名「id」の内容を取得するには getAttribute(“属性名”) を使います。
XPath を使ったノード探索
上記の方法が間違っているというわけでは有りませんが、ループで検索するのは原始的すぎると思うのであれば、XPath を使うと綺麗にまとまります。
<?php $dom = new DOMDocument('1.0', 'UTF-8'); $dom->preserveWhiteSpace = false; $dom->formatOutput = true; $dom->load("sample.xml"); $xpath = new DOMXPath($dom); $result = $xpath->query("/data/sample[text()='test']")->item(0); echo $result->nodeValue;
随分すっきりしました。
基本的には query() に検索条件を指定して抽出します。
サンプルでは、値「test」を持つ sample ノードを取得しています。
「/」は階層を表します。「/data/sample」の時、<data> 要素が持つ子ノードの内、<sample> という名前の要素を全て選択します。
ディレクトリパスの書き方と同様に「.」は現在のノードを表し、「..」と書けばその親を指定することも出来ます。
条件は [] の中に記述します。テキスト内容は text() で得られるので、「text()=’test’」とするとテキストが「test」である場合という条件になります。
n 番目に見つかったノードという条件であれば「/data/sample[1]」のように指定できます。数値は 0 ではなく 1 から始まるため、この場合最初に見つかった sample ノードという意味になります。「/data/sample[position()=1]」と書いても同じです。最後に見つかったノードであれば「/data/sample[last()]」という書き方ができます。
id などの属性(Attribute)からノードを選択する場合は属性名の前に @ をつけて指定します。
ドキュメント内の全ての <sample> のうち、id が 3 であるものを取得してみます。
<?php $dom = new DOMDocument('1.0', 'UTF-8'); $dom->preserveWhiteSpace = false; $dom->formatOutput = true; $dom->load("sample.xml"); $xpath = new DOMXPath($dom); $result = $xpath->query('//sample[@id = "3"]')->item(0); echo $result->nodeValue;
条件は「and」「or」を使って複数指定することもできます。
[@id="3" or @class="sample"]
この場合 id が 3 又は クラスが sample であるという条件になります。
XPathクエリの「/」は子ノード(一つ下の階層)までを検索対象にしますが、クエリに「//」をつかうとそのノード以下の子孫ノード(すべての階層のノード)が対象になります。「/data//sample」のように探索することも出来ます。この場合は <data> 以下の全ての深さの階層から <sample> という名前のノードを選択することになります。
条件は and や or で組み合わせることができます。
$result = $xpath->query('//sample[text()="green" or text()="red"]'); foreach($result as $node){ echo $node->nodeValue . "<br />"; }
「red green」と表示されます。 red か green の値を持つすべての sample ノードが抽出されているのがわかります。
XPath の query() によって取得した DOMElement から、さらに query を使って絞り込む場合、query() の第2引数として絞込元となるノード(コンテキストノード)を渡します。そのさいクエリを「//title」のようにしてしまうとコンテキストノードを指定する意味がなくなってしまうので、「.//title」などの相対的な書き方にします。
<?php $xml = <<<EOD <?xml version="1.0" encoding="utf-8"?> <data> <article> <title>sample1</title> </article> <article> <title>sample2</title> </article> </data> EOD; $dom = new DOMDocument('1.0', 'UTF-8'); $dom->preserveWhiteSpace = false; $dom->formatOutput = true; $dom->loadXml($xml); $xpath = new DOMXPath($dom); $article = $xpath->query('//article')->item(0); $title = $xpath->query('.//title', $article)->item(0); echo $title->nodeValue;
結果: sample1
XPath の文法の詳細については、こちらのサイト(http://www.techscore.com/tech/XML/XPath/xpath02.html/)が わかりやすくまとめてくださっています。
子ノードの削除
親ノードから子ノードを削除するには removeChild() を使います。
$xpath = new DOMXPath($dom); $result = $xpath->query('/data/sample[text()="test"]')->item(0); $result->parentNode->removeChild($result); $root->removeChild($result);
子ノードの削除は親ノードから行います。親ノードの参照は parentNode に格納されているのでそれを利用します。あるいは '/data/sample/..' のようにしてパスを遡って親ノードを取得する方法もあります。