デベロッパー

デベロッパー

PHPでDOMを操作する

Octavia Andreea Anghel
2009年9月15日 / 10:00
 
 

はじめに

 Document Object Model(DOM)とは、XML(またはHTML)文書をツリー構造のオブジェクトとして表現するための一連のインターフェースを定めたW3C規格です。DOMツリーは文書の論理的な構造を定義し、プログラムによる文書の操作を制御します。開発者はDOMを使用して、XML文書やHTML文書の作成、ツリー構造内での移動、要素やコンテンツの追加、変更、削除を行うことができます。DOMは任意のプログラミング言語から操作できます。本稿ではPHP 5のDOMエクステンションを使います。これはPHPコアに既に実装されているので、別途インストールすべきものは特にありません。

 DOMツリーはXML規約に基づいて命名されたノードで構成されます。よく知られているDOMノードには次のものがあります。

  • Documentノード(DOMDocumentインターフェースで表現)
  • Elementノード(DOMElementインターフェースで表現)
  • Attributeノード(DOMAttrインターフェースで表現)
  • Commentノード(DOMCommentインターフェースで表現)
  • Textノード(DOMTextインターフェースで表現)

要素を抽出する

 DOMツリーから要素と値を抽出する方法を具体的に説明します。本稿で使用するサンプル文書「Book.xml」をリスト1に示します。

リスト1 Book.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<book>
<!--XML Processing [part I] -->
  <name>XML Processing I</name>
  <author>John Smith Jr.</author>
  <publisher>HisOwnTM</publisher>
  <ISBN>111-222-333-4441</ISBN>
  <contents>
    <chapter_I>
      <title>What is XML about ?</title>
      <content>XML (Extensible Markup Language) is a ...</content>
    </chapter_I>
    <chapter_II>
      <title>SAX</title>
      <content>SAX is a simple API for ...</content>
    </chapter_II>
    <chapter_III>
      <title>StAX</title>
      <content>Much powerful and flexible, StAX, is very...</content>
    </chapter_III>
    <chapter_IV>
      <title>DOM
        <subtitle>DOM concept
          <continut>Starting to use DOM...</continut>
        </subtitle>
        <subchapter_IV_I>
          <title>First DOM application...</title>
          <content>Here it is your first DOM application...</content>
        </subchapter_IV_I> 
      </title>            
    </chapter_IV>         
    <end>The end...</end>         
  </contents>
<!-- See you in XML Processing [part II] -->
</book>
 以降の説明に従って作業を進めるには、このBook.xmlを本稿のPHPサンプルアプリケーションと同じディレクトリに置いておきます(サンプルアプリケーションは記事先頭のリンクからダウンロード可能)。

 最初のアプリケーションではBook.xmlを使って、関連付けられたツリーを抽出し、いくつかの子ノードの最初のオカレンスをDOMElementインターフェースのgetElementsByTagNameメソッドを使って表示します。

getElementsByTagNameメソッド
DOMNodeList DOMElement::getElementsByTagName(string $name)
 このメソッドは、$nameパラメータで指定したタグ名を持つすべての子要素のリストを返します。次の例では、<book>ルートノードを検索し、そのすべての子要素<author><publisher><name>を検索して各要素の最初のオカレンスを選択し、それらのノードの値を出力します。

<?php
 // Create a document instance 
  $doc = new DOMDocument();
  //Load the Book.xml file
  $doc->load( 'Book.xml' );
  
  //Searches for all elements with the "book" tag name
  $books = $doc->getElementsByTagName( "book" );
 
  //Searches for all elements with the "author" tag name
  $authors = $doc->getElementsByTagName( "author" );
  //Returns the first element found having the tag name "author"
  $author = $authors->item(0)->nodeValue;
  
  //Searches for all elements with the "publisher" tag name
  $publishers = $doc->getElementsByTagName( "publisher" );
  //Returns the first element found 
  //having the tag name "publisher"
  $publisher = $publishers->item(0)->nodeValue;
  
  //Searches for all elements with the "name" tag name
  $titles = $doc->getElementsByTagName( "name" );
  //Returns the first element found having the tag name "name"
  $title = $titles->item(0)->nodeValue;
  
  //Printing the found values
  echo "$title - $author - $publisher \n";
?>
 最後の行では、検索された最初の書名、最初の著者、最初の出版社をハイフン(−)で区切って出力します。結果は次のようになります。

XML Processing I - John Smith Jr. - HisOwnTM

DOMツリーを再帰的に走査する

 XML構造におけるタグは、他のタグ(ツリーの枝)を含むものと、子タグを1つも含まないもの(=リーフ(葉)タグ)のどちらかに分類されます。そのため、任意のノードから開始してそれぞれの枝を末端の葉までたどっていけば、ツリー全体や部分ツリーを再帰的に走査できます。次の例では、与えられたルートノード($node)から任意のXML部分ツリーを走査し、見つかった各ノードの名前と値を出力します。

function getNodesInfo($node)
{
   if ($node->hasChildNodes())
   {
      $subNodes = $node->childNodes;
      foreach ($subNodes as $subNode)
      {
         if (($subNode->nodeType != 3) || 
            (($subNode->nodeType == 3) &&
            (strlen(trim($subNode->wholeText))>=1)))   
         {
            echo "Node name: ".$subNode->nodeName."\n";
            echo "Node value: ".$subNode->nodeValue."\n";
         }
         getNodesInfo($subNode);         
      }
   }      
}   
 上の例では、次の条件判定に基づいて空のテキストノードを削除し、出力を整形しています。

if (($subNode->nodeType != 3) || 
   (($subNode->nodeType == 3) &&
   (strlen(trim($subNode->wholeText))>=1)))
 このコードは、処理中のノードがテキストノード以外(nodeType != 3)か、テキストノードでかつ空でないテキストを持つかをチェックするものです。こうしなくても定義済みのpreserveWhiteSpaceプロパティをFALSEに設定すれば、余計な空白は取り除かれます。このデフォルト値はTRUEです。

 次の例では、テストのためにBook.xml文書のルートノードを再帰関数getNodesInfoに渡してDOMツリー全体のタグと値を出力します。

<?php
//Create a document instance 
$doc = new DOMDocument();
//Load the Book.xml file
$doc->load( 'Book.xml' );
  
//Setting the objects tree root 
$root = $dom->firstChild;
// Recursive function to list all nodes of a subtree
function getNodesInfo($node)
{
   if ($node->hasChildNodes())
   {
      $subNodes = $node->childNodes;
      foreach ($subNodes as $subNode)
      {
         if (($subNode->nodeType != 3) || 
            (($subNode->nodeType == 3)  
            &&(strlen(trim($subNode->wholeText))>=1)))   
         {
         echo "Node name: ".$subNode->nodeName."\n";
         echo "Node value: ".$subNode->nodeValue."\n";
      }
      getNodesInfo($subNode);         
      }
   }      
}   
//The getNodesInfo function call
getNodesInfo($root);
?>
 図1に出力の一部を示します。

図1 文書の内容: Book.xmlファイルを再帰メソッドgetNodesInfoで処理して得られた出力の一部
図1 文書の内容: Book.xmlファイルを再帰メソッドgetNodesInfoで処理して得られた出力の一部

新しいノードを追加する

 DOMNodeインターフェースには、ノードを作成するメソッドや、作成したノードをDOMツリーに挿入するメソッドが用意されています。新しいノードを作成するには、createElementメソッドかcreateTextNodeメソッドを使います。作成したノードをDOMツリーに挿入するには、appendChildメソッドかinsertBeforeメソッドを使います。appendChildメソッドは指定した子ノードのリストの最後に新しい子ノードを追加し、insertBeforeメソッドは指定したノードの前に新しい子を挿入します。

 次に、これらのメソッドのプロトタイプを示します。

createElementメソッド
DOMElement createElement(string $name [, string $value ])
 このメソッドはDOMElementクラスのインスタンスを生成します。引数$nameは新しい要素に付けるタグ名、引数$valueは要素の値です。DOMElement->nodeValueプロパティを使えば、この値を後で設定することもできます。

createTextNodeメソッド
DOMText createTextNode(string $content)
 このメソッドはDOMTextクラスのインスタンスを生成します。引数$contentは新しいテキストノードに与えるテキストコンテンツです。

appendChildメソッド
DOMNode DOMNode::appendChild(DOMNode $newnode)
 この関数は引数$newnodeを既存の子ノードリストの最後に付加します。または指定したノードを持つ新しい子ノードリストを作成します。

insertBeforeメソッド
DOMNode DOMNode::insertBefore(DOMNode $newnode [,DOMNode $refnode])
 このメソッドは$refnodeで指定した参照ノードの前に引数$newnodeを挿入します。$refnodeを省略した場合、新しいノードはノードの子ノードリストの前に付加されます。

 次の例では<bibliography>ノードを作成し、ツリーの最後に付加します。

//Create a new element 
$newElement = $dom->createElement('bibliography','Martin Didier, Professional XML');
// Add it to the root using the appendChild method
//The appendChild function call
appendNewChild($root,$newElement);
//This function appends a new child node
function appendNewChild($currentNode, $node)
{
   $currentNode->appendChild($node);
}
 この結果をgetNodeInfo()メソッドで処理すると、図2のような出力が得られます。

図2 付加されたノード: 新しいノード<bibliography>とそのコンテンツが文書の最後に付加されている
図2 付加されたノード: 新しいノード<bibliography>とそのコンテンツが文書の最後に付加されている
 次の例では、新しい子<foreword><publisher>ノードの前に追加します。

//create a new <foreword> element
$newElement = $dom->createElement('foreword',
   'What I love about this book is that it '.
   'grew out of just such a process, '.
   'and shows it on every page.');
//Set the reference node
$allContents = $dom->getElementsByTagName('publisher');
$contents = $allContents->item(0);
//Call the insertNewChild function 
insertNewChild($contents,$newElement);
//This function inserts a new child 
//as the first child of $currentNode 
function insertNewChild($currentNode, $node)
{
   $currentNode->insertBefore(
      $node, $currentNode->firstChild);   
}
 変更後の文書をgetNodesInfoで処理すると新しいノードが表示されます(図3を参照)。

図3 ノードの挿入: 新しい子ノード<foreward>が<publisher>ノードの前に挿入されている
図3 ノードの挿入: 新しい子ノード<foreward>が<publisher>ノードの前に挿入されている

ノードを複製する

 ノードの複製とは、同じ型の新しいノードを作成し、(必要なら)現在のノードと同じコンテンツに設定することを意味します。ノードを複製するにはcloneNodeメソッドを使います。

cloneNodeメソッド
DOMNode DOMNode::cloneNode([ bool $deep])
 現在のノードの複製を作成します。引数$deepは、現在のノードの子もコピーするかどうかを指定するものです。デフォルト値はFALSE(コピーしない)です。例えば、次のコードは要素<author>を複製し、元の要素<author>の子として付加します。図4に結果を示します。

//Set the reference node
$author = $root->getElementsByTagName('author')->item(0);
//Call the cloningNode function 
cloningNode($author); 
//This function clone the $currentNode
function cloningNode($currentNode)
   {
      
   $clonenode = $currentNode -> cloneNode(true);
      $newnode = $currentNode->appendChild($clonenode);   
   }
図4 ノードの複製: 子ノード<author>を複製して元のノード<author>に付加した結果。ノードのテキスト値を取得すると子ノードのテキスト値も取得されるので、元のノードのテキスト値は二重になる
図4 ノードの複製: 子ノード<author>を複製して元のノード<author>に付加した結果。ノードのテキスト値を取得すると子ノードのテキスト値も取得されるので、元のノードのテキスト値は二重になる

子ノードを削除する

 ノードをDOMツリーから削除するにはremoveChildメソッドを使います。

removeChildメソッド
DOMNode DOMNode::removeChild(DOMNode $oldnode)
 この関数は子ノードを削除します。引数$oldnodeは削除する子ノードです。例えば、次のコードは子ノード<bibliography>をBooks.xml文書から削除します。結果(図5)を見ると、bibliographyノードがなくなっていることが分かります。

//Get a reference to the bibliography node
$bibliography = $root->getElementsByTagName(
   'bibliography')->item(0);
//Call the removingChild function 
removingChild($bibliography);
//This function remove the $currentNode node
function removingChild($currentNode)
{
   $oldbibliography = $root->removeChild($currentNode);   
}   
図5 ノードの削除: 最後の子ノード(先ほどappendChildメソッドで挿入した<bibliography>)を削除してからノードの名前と値をリストすると、ノードが本当に削除されたことが分かる
図5 ノードの削除: 最後の子ノード(先ほどappendChildメソッドで挿入した<bibliography>)を削除してからノードの名前と値をリストすると、ノードが本当に削除されたことが分かる

ノードを置換する

 既存のノードを新しいノードに置き換えるには、replaceChildメソッドを使います。

replaceChildメソッド
DOMNode DOMNode::replaceChild(DOMNode $newnode, DOMNode $oldnode)
 この関数は新しいノードがまだ別の親の子でなければ、$oldnodeを新しい子ノード$newnodeに置き換えます。

 例えば、子ノードISBNを新しい子ノードcodeに置き換えるには次のようにします。

//Get the ISBN node
$element = $dom->getElementsByTagName('ISBN')->item(0); 
//Create the new <code> element 
$code = $dom->createElement('code', '909090');
//Call the replacingNode function 
replacingNode($code,$element);
//This function replaces $currentNode with $node 
function replacingNode($currentNode, $node)
{
   $node->parentNode->replaceChild($currentNode, $node);   
}   
 結果(図6)を見ると、ノードが置き換えられていることが分かります。

図6 ノードの置換: 文書のこの部分でノード<ISBN>が新しいノード<code>に置き換えられている
図6 ノードの置換: 文書のこの部分でノード<ISBN>が新しいノード<code>に置き換えられている

ノードをインポートする

 現在のツリーに別のツリーからノードをコピーするにはimportNodeメソッドを使います。

importNodeメソッド
DOMNode DOMDocument::importNode(DOMNode $importedNode [,bool $deep])
 この関数は新しいノードがまだ別の親の子でなければ、$oldnodeを新しい子ノード$newnodeに置き換えます。このメソッドはノードを別のXML文書からインポートし、現在の文書のDOMツリーに挿入します。引数$importedNodeはインポートするノードです。インポートされるノードは元のノードの''コピー''なので、インポートによって外部のツリーが変更されることはありません。引数$deepはインポートするノードをディープコピーするかどうかを指示するものです。TRUEのときは、ノードの部分ツリー全体がインポートされ、FALSEのときは、直接のノードだけがインポートされます。

 例として、ノード<continue>をBook_continue.xmlファイルからBook.xmlにインポートすることを考えます。Book_continue.xml文書の内容は次のとおりです。

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!--chapter V-->
<continue>
  <chapter_V>
   <title>XPath</title>
   <content>XPath is language for...</content>
  </chapter_V>
  
  <![CDATA[
     This chaper is a bonus to...
     ]]>
  
  <printing cap_I="click_here_for_chapter_I" 
    cap_II="click_here_for_chapter_II"   
    cap_III="click_here_for_chapter_III"
    cap_IV="click_here_for_chapter_IV" 
    cap_V="click_here_for_chapter_V" />
  
</continue>
 ノード<continue>をインポートするコードは次のようになります。

<?php
$olddoc = new DOMDocument;
$olddoc->load("Book_continue.xml");
// The node we want to import to a new document
$node = $olddoc->getElementsByTagName("continue")->item(0);
$newdoc = new DOMDocument;
$newdoc->formatOutput = true;
$newdoc->load("Book.xml");
// Import the node, and all its children, to the document
$node = $newdoc->importNode($node, true);
// And then append it to the root node
$newdoc->documentElement->appendChild($node);
echo "\nThe 'new document' after copying the nodes into it:\n";
$root = $newdoc->firstChild;
function getNodesInfo($node)
{
   if ($node->hasChildNodes())
   {
      $subNodes = $node->childNodes;
      foreach ($subNodes as $subNode)
      {
         if (($subNode->nodeType != 3) || 
            (($subNode->nodeType ==3) &&
            (strlen(trim($subNode->wholeText))>=1)))   
         {
         echo "Node name: ".$subNode->nodeName."\n";
         echo "Node value: ".$subNode->nodeValue."\n";
      }
      getNodesInfo($subNode);         
      }
   }      
}
getNodesInfo($root);   
?>
 図7に結果を示します。

図7 ノードのインポート: <continue>ノードをBook_continue.xmlからインポートしてBook.xmlツリーの最後に挿入すると、Book.xmlはこのようになる
図7 ノードのインポート: <continue>ノードをBook_continue.xmlからインポートしてBook.xmlツリーの最後に挿入すると、Book.xmlはこのようになる

ノードの等値性をチェックする

 2つのノードが同じかチェックするには関数isSameNodeを使います。

isSameNodeメソッド
bool DOMNode::isSameNode(DOMNode $node)
 この関数は、ノードが等しいときは論理値のTRUEを返し、等しくないときはFALSEを返します。引数$nodeは現在のノードと比較するノードです。この比較はノードのコンテンツに基づいて行われるものではないことに注意してください。

//Checking if two nodes are equals
$author1 = $root->getElementsByTagName('autor')->item(0);
$author2 = $root->getElementsByTagName('autor')->item(1);
//The verifyNodes function call
verifyNodes($author1,$author2);
function verifyNodes($currentNode, $node)
{
   if (($currentNode->isSameNode($node))==true)
   {
      echo "These two nodes are the same";
   }   
}

新しいツリーを作成する

 既存のツリーを最初から使わなくてもかまいません。PHP 5のDOMエクステンションではツリーをゼロから作ることもできます。次の例では、まったく新しいXML文書を作成します。ここではコメントノードを作成する関数とCDATAノードを作成する関数を使用しています。

createCommentメソッド
DOMComment DOMDocument::createComment(string $data)
 新しいコメントノードを作成します。引数$dataはノードのコンテンツです。

createCDATASectionメソッド
DOMCDATASection DOMDocument::createCDATASection(string $data)
 新しいCDATAノードを作成します。引数$dataはノードのコンテンツです。

 リスト2の例ではオブジェクトツリーを作成し、Flowers.xmlという名前で保存します。

リスト2 新しいDOMツリーの作成
<?php 
  
  //Create a document instance 
  $document = new DOMDocument();
  
  //Formats output with indentation
  $document->formatOutput = true;
  
  //Create a comment
  $comment = $document->createComment('Beautiful flowers!!!');
  $document->appendChild( $comment ); 

  //Create the <flowers> root element 
  $root = $document->createElement( 'flowers' );
  $document->appendChild( $root );
  
  //Create the <tulips> children of the root
  $tulips = $document->createElement( 'tulips' );
  
  //Create the first child of the <tulips> element,<bulbs>, 
  // and set its attribute
  $bulbs_1 = $document->createElement( 'bulbs' );   
  $bulbs_1->setAttribute('price','€ 7.65');
  $bulbs_1->appendChild($document->createTextNode( 'Parrot'));
  $tulips->appendChild( $bulbs_1 );
  
  //Create the second child of the <tulips> element,<bulbs>, 
  // and set its attribute
  $bulbs_2 = $document->createElement( 'bulbs' );    
  $bulbs_2->setAttribute('color','magenta');
  $bulbs_2->appendChild($document->createTextNode( 'Lily flowering' ));
  $tulips->appendChild( $bulbs_2 );
  
  //Append the <tulips> node to the root
  $root->appendChild( $tulips );
  
  //Create a CDATA section
  $cdata = $document->createCDATASection(
    '<gladiolus><species>Sword Lily</species>'.
    '<species>Starface</species></gladiolus>');
  $document->appendChild( $cdata ); 
  
  //Save the object tree to Flowers.xml 
  echo $document->saveXML();
  $document->save('Flowers.xml');
?>
 新しい文書Flower.xmlは次のようになります。

<?xml version="1.0" encoding="ISO-8859-1"?>
<!--Beautiful flowers!!!-->
<flowers>
  <tulips>
    <bulbs price="€ 7.65">Parrot</bulbs>
    <bulbs color="magenta">Lily flowering</bulbs>
  </tulips>
</flowers>
<![CDATA[<gladiolus>
    <species>Sword Lily</species>
    <species>Starface</species>
  </gladiolus>
]]>
 以上、PHP 5のDOMエクステンションを簡単に紹介しました。これらの情報は既存のXML(HTML)文書を操作したり、文書をゼロから作成したりするときに役立つはずです。

著者紹介

Octavia Andreea Anghel(Octavia Andreea Anghel)
経験豊富なPHP開発者。現在は、国内外のソフトウェア開発コンテストに参加するプログラミングチームの主任トレーナーを務める。国レベルの教育プロジェクト開発のコンサルティングも担当している。共著書に『XML technologies?XML in Java』があり、XML部分の執筆を担当。PHPやXMLのほか、ソフトウェアアーキテクチャ、Webサービス、UML、ハイパフォーマンスな単体テストについても関心を寄せている。
【関連記事】
Windows環境でのPerlとPHPの連携
PHP 5でSOAPおよびRPC Webサービスを開発する
【書評】『PHP×PostgreSQL で作る最強 Web システム』――老練エンジニアによる素敵な PostgreSQL&PHP ガイドブック
Sun、統合開発環境の最新版『NetBeans IDE 6.7』をリリース
JavaScriptとDOMによる動的なWebページの作成

New Topics

Special Ad

ゆりかごからロケットまで、すべての乗り物をエンジョイ
ゆりかごからロケットまで、すべての乗り物をエンジョイ えん乗り」は、ゆりかごからロケットまで、すべての乗り物をエンジョイする、ニュース、コラム、動画などをお届けします! てんこ盛りをエンジョイするのは こちらから

Hot Topics

IT Job

Interviews / Specials

Popular

Access Ranking

Partner Sites