DOM で XML を操作する - PHP5 プログラミング

PROGRAM


DOM で XML を操作する

PHP には、XML を DOM で操作するためのクラスが用意されています。

php では、他にも XML ファイルを変数に取り込む で記したように、SimpleXMLElement を使用して XML を扱う方法もありますけど、要素や属性の削除や置換などの高度な操作を行うためには DOM による操作が必要になってきます。

 

XML を DOM で操作するといえば JavaScript での操作が比較的多く利用されているように思いますけど、それと同じような感覚で php でも XML を操作することができます。

ただし、言語による構文の違いもそうですし、php ではより厳密な操作を求められるようで、それぞれで慣れが必要そうです。

 

php の DOM では、主に次のクラスを使用して、DOM 操作を行う感じになります。

DOMDocument DOM のドキュメント全体となるオブジェクトです。要素に値を追加したりする場合には、編集する要素は必ずこの DOMDocument に所属させる必要があるようです。
DOMElement DOM の要素(タグ)となるオブジェクトです。属性や子要素を管理するメソッドが用意されています。
DOMText DOM のテキストデータとなるオブジェクトです。
DOMNodeList getElementsByTagName メソッドなどで要素を取得した場合に、該当する複数のタグが格納されるオブジェクトです。
DOMNode DOM のノードを管理するメソッドが備わっています。DOMDocument や DOMElement といったクラスは、このクラスから派生されています。

それでは、これらのクラスを使って XML を操作する方法について、見て行きたいと思います。

 

DOM オブジェクトを用意する

XML データを取り込む

php の DOM では、次のような形で XML データを取り込めます。

まず、XML ファイルを読み込んで DOM を取得したい場合には、次のように DOMDocument クラスの load メソッドを使用します。

$doc = new DOMDocument();

$doc->load($filename);

これで、$filename で指定した XML ファイルの内容を $doc に取り込むことができました。

この load メソッドは、読み込みに成功した場合には TRUE を、失敗した場合は FALSE を返すようになっています。

 

また、次のように loadXML メソッドを使用すれば、ファイルではなく文字列として用意された XML データを使用して、それを DOM オブジェクトとして利用することも可能です。

$doc = new DOMDocument();

$doc->loadXML($xmlString);

このようにして、あらかじめ $xmlString に文字列として取得しておいた XML データの内容を使って DOM オブジェクトを作ることができました。

DOM オブジェクトを新規に作成する

DOM オブジェクトを、XML ファイルやデータではなく、新規に作成したい場合には、次のようにします。

$doc = new DOMDocument();

これで、何も格納されていない空の DOM オブジェクトを作成することができました。

要素を作りたい場合だけなら DOMElement を新規に作るだけでも大丈夫ですが、それを子要素として追加したりする場合には、必ずこの DOMDocument が必要になってくるようです。

 

ちなみに、要素を作成したい場合には、次のような感じになります。

$node = new DOMElement('div');

このようにすることで <div> という要素が作成されます。

 

同様にして、テキストノードを作成したい場合には、次のようにします。

$textNode = new DOMText('テキストデータ');

これらのオブジェクトを使用して、DOM オブジェクトを組み上げて行く感じになります。

 

DOM オブジェクトを参照する

子要素を取得する

DOM の要素が持っている子要素を知るには、childNodes プロパティを参照します。

例えば、とある DOM 要素 $node がいくつの子要素を持っているかを知るには、次のように length プロパティを参照します。

$count = $node->childNodes->length;

これで $count に、$node が持つ子要素の数が格納されます。

 

また、childNodes プロパティは、存在する子要素を DOMNodeList オブジェクトで取得できるので、このオブジェクトが持つ item メソッドを使用して、子要素を取り出すことが可能です。

$child = $node->childNodes->item(0);

子要素の番号は 0 から始まる通し番号でつけられているので、上記の例では最初の子要素ということになります。

 

これらを踏まえて、$node が持つ子要素を順次取得して処理をしたい場合には、次のような感じにします。

for ($i = 0; $i < $node->childNodes->length; $i++)

{

$child = $node->childNodes->item($i);

 

if ($child instanceof DOMElement)

{

// ここで、各要素についての処理を行います。

}

}

この例では、インスタンスが DOMElement クラスのものだけを対象に処理をするようにしています。

これは、タグとタグとの間に改行を挟んで見やすくしていたような場合に、childNodes のその部分が DOMText となるためです。もちろん、意識的にテキストノードを入れているのであれば、それを考慮した処理を書く必要があります。

他にも、存在しない要素番号を指定した場合には NULL が取得されることになります。

 

ちなみに、最初の要素や最後の要素を取得したい場合には、childNodes を使わなくても、次のように firstChild プロパティや lastChild プロパティを使って間単に取得することも可能です。

$child = $node->lastChild;

また、そもそも子要素を持っているかは、hasChildNodes メソッドを使って調べることも可能です。

if ($node->hasChildNodes())

{

}

 

タグ名を取得する

DOM の要素に設定されている要素名(タグ名)を取得したい場合は、tagName プロパティを参照します。

$name = $node->tagName;

このようにすることで、例えば <div> タグであれば 'div' という値が取得できます。

なお、この tagName プロパティは DOMElement クラスが持つプロパティなので、DOMText などのその他のノードで取得しようとするとエラーになります。

関係する要素を取得する

DOM では、ある要素に関係する要素を取得できるようになっています。

例えば、その要素が所属している親の要素を取得したい場合には、parentNode プロパティを参照します。

$parent = $node->parentNode;

また、同じ階層の要素で、ひとつ前のものを取得したい場合には previousSibling プロパティを参照します。

$previous = $node->previousSibling;

次の要素を取得したい場合は nextSibling プロパティを参照します。

$next = $node->nextSibling;

そもそも、大元の DOMDocument を取得したい場合は ownerDocument プロパティを参照します。

$doc = $node->ownerDocument;

もちろん、その要素の子要素を取得したい場合には、先ほどお話した childNodes や firstChild, lastChild といったプロパティを参照します。

このような感じで、ある要素との位置関係を使用して、DOM 階層を辿って行くことができます。

テキストノードの値を取得する

例えば "<div>テキスト</div>" のような、内側にテキストデータを持つ要素は、その子要素としてテキストノードを持っています。

このテキストの値を取得したい場合には、外側の要素が持つ textContents プロパティを参照します。

$text = $node->textContents;

このようにすることで、要素 $node が内側に持つ子要素や、さらにそれよりも深い子要素が持つテキストノードの値を、単純な文字列として取得することが可能です。

この textContents プロパティは DOMNode クラスのプロパティなので、DOMElement や DOMText などから参照できます。

要素名を指定して要素を取得する

DOM では、要素名(タグ名)を使用して、要素の一覧を取得できるようになっています。

$nodes = $node->getElementsByTagName('div');

このようにすることで、$node より下の階層に存在する <div> 要素を取得することができます。

このようにして取得した要素は複数存在する場合があるため、戻り値は DOMNodeList オブジェクトで取得できます。上でお話した childNodes と同じデータ型なので、それと同じように length プロパティで要素の数を取得したり、item メソッドで目的の要素を取得したりといったことが可能です。

 

なお、この getElementsByTagName では、その要素が持つ子要素やさらにその下の孫要素など、全ての要素の中から該当する要素を見つけてきます。

そのため、例えば <ul> タグ内の <a> タグだけを取得したいような場合には、あらかじめ目的の <ul> タグを取得した上で、そこの getElementsByTagName メソッドを使用して <a> タグを取得するようにするなどの配慮が必要になります。

ID を指定して要素を取得する

DOM では、要素につけられた一意の ID を使用して、目的の要素を取得することができます。

$node = $doc->getElementById('IDENTIFIER');

ID はひとつの文書全体で、重複してはいけない決まりになっているので、ID が定まればタグがひとつに定まります。

そのため getElementsByTagName メソッドの時のような DOMNodeList ではなく、DOMElement オブジェクトの値が得られます。

 

この getElementById メソッドを使用する上での注意としては、このメソッドは DOMDocument にしか備わっていないところでしょうか。

途中の要素からこのメソッドを呼び出そうとしてもエラーになるので、途中で必要になった場合は ownerDocument プロパティを参照するなどして、DOMDocument オブジェクトを取得するようにします。

要素の属性を取得する

要素が持っている属性の値は、getAttribute メソッドを使って取得します。

$name = $node->getAttribute('name');

たとえばこのようにすることで、$node 要素が持つ 'name' 属性の値を、文字列型で取得することができました。

 

ちなみに、要素が目的の属性を持っているかどうかは、次のように hasAttribute メソッドを使って調べることが可能です。

if ($node->hasAttribute('name'))

{

}

そもそも、その要素が属性を持っているかどうかについては、次のように hasAttributes メソッドを使用することで調べられます。

if ($node->hasAttributes())

{

}

 

また、持っている属性の一覧を取得したい場合は、attributes プロパティを参照します。

$attrs = $node->attributes;

属性の一覧は DOMNamedNodeMap オブジェクトで取得することができます。

この DOMNamedNodeMap オブジェクトには、DOMNodeList と同じように、個数を知る length プロパティと、目的のアイテムを取り出す item メソッドが用意されているので、これらを使用して、全ての属性を順に辿ることができます。

$attrs = $node->attributes;

 

for ($i = 0; $i < $attrs->length; $i++)

{

$attr = $attrs->item($i);

 

$name = $attr->name;

$value = $attr->value;

}

ちなみに DOMNamedNodeMap オブジェクトで取得できる属性は、DOMAttr オブジェクトに格納されて取得されます。

このとき、存在しない番号を指定した場合は NULL が取得されます。

 

DOM オブジェクトに要素や属性を追加する

要素に子要素を追加する

たとえば DOMDocument 内に存在している要素 $node に "<div></div>" 要素を追加したい場合は、次のようにします。

$newNode = new DOMElement('div');

$node->appendChild($newNode);

このようにすることで、新しい <div> 要素が $node の子要素として追加されました。

さらにこの <div> 要素にテキストノードを加えて "<div>文字列</div>" という形にしたい場合は、さらに次のように続けます。

$newText = new DOMText('文字列');

$newNode->appendChild($newText);

このような感じで、必要な要素やテキストノードを、目的の要素の子要素として加えて行くことで、DOM オブジェクトを編集して行きます。

"No Modification Allowed Error" が発生する場合

ただ、このとき "No Modification Allowed Error" というエラーが発生することがあります。

これは、DOMDocument に所属していない要素に対して、子要素や属性を追加・削除するといった、編集をしようとしたことが原因になっている様子でした。

 

そのため new 演算子で DOMElement を作成した場合は必ず、DOMDocument に所属している要素に appendChild してから、編集操作をする必要が出てきます。

このような感じから、php での DOM 操作は JavaScript と違って、親から順に追加して行く必要がありそうです。

ちなみに、ある要素が所属している DOMDocument を取得したい場合には、要素の ownerDocument プロパティを参照することで取得できます。

 

もし、部分的な DOM オブジェクトを作成していて、本流の DOMDocument にそれを追加したくない場合には、新しく DOMDocument を作成してそれに追加することで、要素を編集できるようにすることも可能です。

function getParts()

{

$container = new DOMDocument();

$resultNode = new DOMElement('div');

 

$container->appendChild($resultNode);

 

// これ以下で $resultNode に要素や属性を設定します。

 

 

return $resultNode;

}

そのため、例えばこのような感じで、一時的に使用するドキュメント $container を作成して、そこに、戻り値として返したい <div> 要素 $resultNode を追加してあげれば、引き続き $resultNode に要素を追加して行くことができるようになります。

これで $resultNode として、部分的な DOM オブジェクトを作成することが出来ました。

"Wrong Document Error" が発生する場合

また、appendChild や replaceChild を実行したときに "Wrong Document Error" というエラーが発生する場合があります。

これは、追加しようとしているノードが、追加するドキュメントに所属していないことが原因のことが多いようです。先ほどお話ししたように部分的な要素を作成して、本流のドキュメントに追加するといった場合にも発生します。

 

このようなときには、追加したい要素 ($newNode) をいったん、追加する DOMDocument ($doc) の importNode メソッドを通して、取得できたインスタンスを、目的の要素 ($targetNode) appendChild するようにします。

$node = $doc->importNode($newNode, true);

$targetNode->appendChild($node);

このようにすることで "Wrong Document Error" を回避することができました。

ちなみに importNode メソッドの第二引数で true を指定しているのは、第一引数で指定した全ての要素を取り込めるようにするためです。

要素の属性を設定する

要素の属性を設定するには、次のように setAttribute メソッドを使用します。

$node->setAttribute('名前', '値');

このようにすることで、要素 $node に '名前' という属性が、'値' という属性値で設定されます。

ちなみに、設定されている属性の中から ID として使用したい属性を選択するには "setIdAttribute($name, true)" メソッドを呼び出します。

 

DOM オブジェクトから要素や属性を削除する

要素から属性を削除する

要素から属性を削除したい場合には removeAttribute メソッドを使用します。

$node->removeAttribute('属性名');

このようにすることで、要素 $node から、指定した名前の属性が削除されました。

要素から子要素を削除する

要素から、それが持っている子要素を削除したい場合には removeChild メソッドを使用します。

$node->removeChild($targetNode);

このようにすることで、$node に所属している $targetNode が、$node から削除されることになります。

このとき、$targetNode は DOMNode や DOMElement 型のオブジェクトである必要があります。そのため、通常は childNodes や getElementsByTagName などを使って削除したい要素を特定することになると思います。

 

そのような理由から、場合によっては目的の要素がどの要素に所属しているかが分らない場合もあるかもしれません。

その場合でも、要素が持つ parentNode プロパティを参照すれば親の要素を調べることができるので、次のようにして removeChild を実行することが可能です。

$targetNode->parentNode->removeChild($targetNode);

このようにすれば、わざわざ親のノードが何であるかを意識しなくて済むので簡単です。

要素を別の要素に置き換える

ある要素を別の要素に置き換えたい場合には replaceChild メソッドを使用します。

こちらも removeChild のときと同様、置き換え対象のメソッドを持っている親の要素の replaceChild メソッドを呼び出す必要があります。

$targetNode->parentNode->removeChild($newNode, $targetNode);

このようにすることで、置き換えたい要素 $targetNode を、新しい要素 $newNode に置き換えることができました。

 

もしここで、要素の置き換えを、例えば $node に存在している <div> 要素すべてについて順次行っていきたいとします。

このような場合は、最初の <div> 要素から順に replaceChild で置き換えて行ってしまうと、置き換えによって順番が狂ってしまい、全ての要素を正しく置き換えることが出来なくなってしまうようでした。

全ての要素を適切に置き換えるためには、次のようにして、後ろから順に置き換えて行く必要があるようです。

$doc = $node->ownerDocument;

$nodes = $node->getElementsByTagName('div');

 

for ($i = $nodes->length; $i > 0; $i--)

{

$targetNode = $nodes->item($i-1);

 

$newNode = $doc->importNode($newNodes[$i-1], true);

$targetNode->parentNode->replaceChild($newNode, $targetNode);

}

ここでは $newNodes に、各要素と交換したい要素が配列で用意されているものとして、それらを順に、要素 $node が持っている <div> 要素と置き換えています。

このとき、置き換え後の新しい要素が、元の要素が所属している DOMDocument に所属している必要があるため、置き換え前に importNode メソッドを使って、新しい要素を文書に取り込んでいます。

 

DOM オブジェクトをデータに書き出す

作成した DOM オブジェクトは、テキストデータとして書き出すことができます。

そのためには、DOM オブジェクトが所属している DOMDocument の save メソッドや saveXML メソッドなどを使用します。

XML データをファイルに保存する

例えば、DOM オブジェクトのデータを XML ファイルに書き出したい場合には、次のようにします。

$doc->save($filename);

このようにすることで、引数で指定したファイル名のファイルに、DOMDocument 型のインスタンス $doc が持っている DOM の内容を XML テキスト形式で保存することができます。

同じように saveHTMLFile メソッドを使用すると、XML 形式の代わりに HTML 形式でテキストを保存することが可能です。

$doc->save($filename);

 

XML データを変数に保存する

また、DOM オブジェクトの内容を単純に XML テキストに変換したい場合には saveXML メソッドを使用します。

$xmlString = $doc->saveXML();

これで $doc が持つ XML データの値が、文字列として $xmlString に保存されました。

この saveXML メソッドは、引数に要素を指定することで、その要素だけを XML 文字列に変換することができるので、XML データを部分的に作り出したいような場合にも利用することができます。


[ もどる ]