レベル: 初級 Cliff Morgan, Writer, Freelance
2007年 03月 06日 3 回シリーズ第 2 回目の今回は、大きな XML 文書、あるいは複雑な XML 文書の構文解析に焦点を当てながら、PHP5 での XML 構文解析方法について説明します。ここでは構文解析の拡張モジュールについて背景を少し紹介し、また具体的に、どのタイプの XML 文書にどの構文解析方法が最適なのか、その理由は何かについて説明します。
はじめに
PHP5 では、改善されたさまざまな XML 構文解析方法が提供されています。現在完全に機能するものは、James Clark による Expat SAX パーサー (現在は libxml2 がベース) だけではありません。W3C 標準に完全準拠した、DOM による構文解析は、おなじみの選択肢となっており、第 1 回で見た SimpleXML (「参考文献」を参照してください) も、SAX よりも容易で高速な XMLReader も、新たに追加された構文解析方法です。すべての XML 拡張モジュールは、現在は GNOME プロジェクトによる libxml2 ライブラリーをベースにしています。この統一ライブラリーによって、さまざまな拡張モジュールの間での相互運用性が実現されています。この記事は、大きな XML 文書、あるいは複雑な XML 文書の構文解析に焦点を当てながら、PHP5 でのXML 構文解析方法について説明します。ここではいくつかの構文解析方法の背景を少し紹介し、どのタイプの XML 文書にどの方法が最適なのか、もし選択肢がある場合、選択の基準は何か、などについて説明します。
SimpleXML
第 1 回では、XML に関する基本的な情報を説明し、クイック・スタート API (Application Programming Interface) に焦点を当てました。そして、単純かつ予測可能で比較的基本的な XML 文書を扱う場合に、SimpleXML が (必要な場合には DOM (Document Object Model) と組み合わせることで) いかに理想的な選択肢であるかを説明しました。
XML と PHP5
XML (Extensible Markup Language) は、マークアップ言語であると同時にテキスト・ベースのデータ・ストレージ・フォーマットであると言われます。また、情報にツリー構造を適用し、また情報をツリー構造で記述するためのテキスト・ベースの手段でもあります。
PHP5 には、XML を構文解析するための、まったく新規に書き直された拡張モジュールがあります。XML 文書全体をメモリーにロードするパーサーとしては、SimpleXML と DOM、そして XSLT プロセッサーがあります。XML 文書を一部分ずつ構文解析するパーサーとしては、SAX (Simple API for XML ) と XMLReader があります。SAX の動作は PHP4 の場合と同じですが、SAX のベースは expat ライブラリーではなくなり、PHP5 では libxml2 ライブラリーがベースとなっています。他の言語での経験から DOM に慣れている人は、これまでのバージョンよりも PHP5 の方が DOM を使ったコーディングが楽だと感じるはずです。
XML 構文解析の基本
XML を構文解析するための基本的な方法には、ツリー方式とストリーム方式があります。ツリー方式の構文解析では、XML 文書全体をメモリーの中にロードします。ツリー方式のファイル構造では、文書の要素にランダム・アクセスすることができ、また XML を編集することができます。ツリー方式の構文解析の例としては、DOM と SimpleXML があります。この 2 つは、メモリー内に、異なるフォーマットではあるけれども相互運用可能なフォーマットで、ツリーのような構造を共有しています。
ストリーム方式の構文解析は、ツリー方式の構文解析とは異なり、XML 文書全体をメモリーにロードすることはありません。この場合のストリームという言葉の使い方は、ストリーミング・オーディオの場合のストリームと非常によく似ています。どちらも、そこで行っている内容、そのように行っている理由は、まったく同じです。つまり、一度に少しずつデータを提供することによって、帯域幅とメモリーの両方を節約するのです。ストリーム方式の構文解析では、現在構文解析中のノードにしかアクセスできません。また、文書として XML を構文解析することはできません。ストリーム・パーサーの例としては、XMLReader と SAX があります。
ツリー・ベースのパーサー
ツリー・ベースのパーサーという名前は、XML 文書全体をメモリーにロードし、ドキュメント・ルートを幹として、またすべての子や孫、それに続く世代、そして属性を枝として処理することに由来しています。最もよく知られているツリー・ベースのパーサーは、DOM です。そして最もコーディングが容易なツリー・ベースのパーサーは SimpleXML です。ではこの両方を見てみましょう。
DOM による構文解析
W3C によると、DOM 標準は「プラットフォームや言語に中立なインターフェースであり、これを利用することで、プログラムやスクリプトが文書の内容や構造、スタイルに対して動的にアクセスし、それらを更新することができます。」GNOME プロジェクトによる libxml2 ライブラリーは、DOM とそのすべてのメソッドを、C で実装しています。PHP5 の XML 拡張モジュールはどれも libxml2 をベースにしているため、すべての拡張モジュール間に完全な相互運用性があります。この相互運用性によって、拡張モジュールの機能が大幅に強化されます。例えば、ストリーム・パーサーである XMLReader を使って要素を取得し、それを DOM にインポートし、そして XPath を使ってデータを抽出することができます。これは素晴らしい柔軟性です。リスト 5 を見ると、これが理解できるでしょう。
DOM はツリー・ベースのパーサーです。メモリー上での DOM の構造は元の XML 文書と似ているため、DOM は理解しやすく、使い方も容易です。DOM は、XML ファイルの要素ツリーをそのまま複製したオブジェクト・ツリー (各 XML 要素がツリーのノードです) を作成することで、アプリケーションに情報を渡します。DOM は W3C 標準であるため、他のプログラミング言語と一貫性があり、開発者にとっては非常に信頼できます。DOM は文書全体のツリーを構築するため、大量のメモリーとプロセッサーの時間を使用します。
DOM の実際
設計上の制約、あるいは他の制約から、パーサーとして 1 つを選ぶとしたら、柔軟性という 1 つの理由だけでも DOM を選ぶ価値があります。DOM を利用することで、XML 文書を作成したり、修正したりすることができ、また同文書に対して、クエリーを実行したり、妥当性検証を行ったり、そして変換を行ったりすることができ、DOM のすべてのメソッドとプロパティーを利用することができます。DOM のレベル 2 メソッドの大部分は、さまざまなプロパティーが適切にサポートされて実装されています。DOM は非常に柔軟性が高いため、DOM で構文解析する文書はどんなに複雑でも構いません。ただし、大きな XML 文書全体を一度にメモリーにロードすると、柔軟性のコストが高くつくことを忘れないでください。
リスト 1 に示す例は、DOM を使って文書を構文解析し、getElementById を持つ要素を取得します。この ID を参照する前に、validateOnParse=true を設定することで文書の妥当性検証をする必要があります。DOM 標準によると、そのためには属性 ID が ID 型の値を持つように定義する DTD が必要です。
リスト 1. 基本的な文書に DOM を使う
<?php
$doc = new DomDocument;
// We must validate the document before referring to the id
$doc->validateOnParse = true;
$doc->Load('basic.xml');
echo "The element whose id is myelement is: " .
$doc->getElementById('myelement')->tagName . "\n";
?>
|
getElementsByTagName() 関数は、与えられたタグ名を持つ要素を含んだクラス DOMNodeList の新しいインスタンスを返します。当然ですが、このリストをウォークスルーする必要があります。getElementsByTagName() が返す NodeList に対して繰り返しを行う間に文書の構造を変更すると、その繰り返し対象の NodeList に影響を与えます (リスト 2)。妥当性検証は必要ありません。
リスト 2. DOM の getElementsByTagName メソッド
DOMDocument {
DOMNodeList getElementsByTagName(string name);
}
|
リスト 3 に示す例は、DOM を XPath と共に使っています。
リスト 3. DOM を XPath と共に使って構文解析する
<?php
$doc = new DOMDocument;
// We don't want to bother with white spaces
$doc->preserveWhiteSpace = false;
$doc->Load('book.xml');
$xpath = new DOMXPath($doc);
// We start from the root element
$query = '//book/chapter/para/informaltable/tgroup/tbody/row/entry[. = "en"]';
$entries = $xpath->query($query);
foreach ($entries as $entry) {
echo "Found {$entry->previousSibling->previousSibling->nodeValue}," .
" by {$entry->previousSibling->nodeValue}\n";
}
?>
|
DOM に関して素晴らしいことを説明してきましたが、今度は DOM のまとめとして、DOM を使うべきではない場合の例を、できるだけ要点が明確にわかるように示すことにします。そしてその次の例では、その問題の解決方法を示します。リスト 4 は、単に 1 つの属性から DomXpath を使ってデータを抽出するために、大きなファイルを DOM にロードする場合を示しています。
リスト 4. 大きな XML 文書に対して DOM を XPath と共に使う悪い例
<?php
// Parsing a Large Document with DOM and DomXpath
// First create a new DOM document to parse
$dom = new DomDocument();
// This document is huge and we don't really need anything from the tree
// This huge document uses a huge amount of memory
$dom->load("tooBig.xml");
$xp = new DomXPath($dom);
$result = $xp->query("/blog/entries/entry[@ID = 5225]/title") ;
print $result->item(0)->nodeValue ."\n";
?>
|
最後に、リスト 4 のフォロー・アップ例として、リスト 5 をあげておきます。リスト 4 と同じように DOM を XPath と一緒に使っていますが、XMLReader で expand() を使うことによって、一度に 1 つの要素ずつのデータが渡されている点が異なります。この方法を使えば、XMLReader が渡すノードを DOMElement に変換することができます。
リスト 5. 大きな XML 文書に対して DOM を XPath と共に使う良い例
<?php
// Parsing a large document with XMLReader with Expand - DOM/DOMXpath
$reader = new XMLReader();
$reader->open("tooBig.xml");
while ($reader->read()) {
switch ($reader->nodeType) {
case (XMLREADER::ELEMENT):
if ($reader->localName == "entry") {
if ($reader->getAttribute("ID") == 5225) {
$node = $reader->expand();
$dom = new DomDocument();
$n = $dom->importNode($node,true);
$dom->appendChild($n);
$xp = new DomXpath($dom);
$res = $xp->query("/entry/title");
echo $res->item(0)->nodeValue;
}
}
}
}
?>
|
SimpleXML による構文解析
SimpleXML 拡張モジュールは、XML 文書を構文解析するための、もう 1 つの選択肢です。SimpleXML 拡張モジュールは PHP5 を必要とし、また XPath のサポートが組み込まれています。SimpleXML は、複雑ではない、基本的な XML データに最適です。XML 文書があまり複雑ではなく、あまり深くなく、複合コンテンツを含まなければ、SimpleXML の使い方は (名前のとおり) DOM よりも簡単です。また、既知の文書構造を扱う場合には DOM よりも直感的です。
SimpleXML の実際
SimpleXML は DOM と同じ利点を数多く備えており、DOM よりもコーディングが容易です。SimpleXML は XML ツリーに容易にアクセスでき、妥当性検証と XPath サポートが組み込まれており、また DOM と相互運用性があるため XML 文書を読み書きすることができます。SimpleXML で構文解析した文書は、簡単に、そして素早くコーディングすることができます。ただし、SimpleXML もDOM と同じように、大きな XML 文書をメモリーにロードすると、使いやすさと柔軟性のコストが高くつくことを忘れないでください。
下記のリスト 6 のコードは、サンプルの XML から <plot> を抽出します。
リスト 6. plot テキストを抽出する
<?php
$xmlstr = <<<XML
<?xml version='1.0' standalone='yes'?>
<books>
<book>
<title>Great American Novel<title>
<plot>
Cliff meets Lovely Woman. Loyal Dog sleeps, but
wakes up to bark at mailman.
</plot>
<success type="bestseller">4<success>
<success type="bookclubs">9<success>
</book>
<books>
XML;
?>
<?php
$xml = new SimpleXMLElement($xmlstr);
echo $xml->book[0]->plot; // "Cliff meets Lovely Woman. ..."
?>
|
その一方、複数行にまたがる住所を抽出したいことがあるかもしれません。ある 1 つの親要素の子として、ある要素の複数インスタンスが存在する場合には、通常の繰り返し手法を適用することができます。下記のリスト 7 のコードは、この機能を示したものです。
リスト 7. ある要素の複数インスタンスを抽出する
<?php
$xmlstr = <<<XML
<xml version='1.0' standalone='yes'?>
<books>
<book>
<title>Great American Novel<title>
<plot>
Cliff meets Lovely Woman.
<plot>
<success type="bestseller">4<success>
<success type="bookclubs">9</success>
<book>
<book>
<title>Man Bites Dog</title>
<plot>
Reporter invents a prize-winning story.
</plot>
<success type="bestseller">22<success>
<success type="bookclubs">3<success>
<book>
</books>
XML;
?>
<php
$xml = new SimpleXMLElement($xmlstr);
foreach ($xml->book as $book) {
echo $book->plot, '<br />';
}
?
|
SimpleXML は、要素の名前と値を読み取ることの他に、要素の属性にアクセスすることもできます。リスト 8 に示すコードは、配列の要素にアクセスする場合と同じように、要素の属性にアクセスしています。
リスト 8. SimpleXML が要素の属性にアクセする
<?php
$xmlstr = <<<XML
<?xml version='1.0' standalone='yes'?>
<books>
<book>
<title>Great American Novel</title>
<plot>
Cliff meets Lovely Woman.
</plot>
<success type="bestseller">4</success>
<success type="bookclubs">9</success>
</book>
<book>
<title>Man Bites Dog</title>
<plot>
Reporter invents a prize-winning story.
<plot>
<success type="bestseller">22<success>
<success type="bookclubs">3</success>
</book>
<books>
XML;
?>
<?php
$xml = new SimpleXMLElement($xmlstr);
foreach ($xml->book[0]->success as $success) {
switch((string) $success['type']) {
case 'bestseller':
echo $success, ' months on bestseller list<br />';
break;
case 'bookclubs':
echo $success, ' bookclub listings<br />';
break;
}
}
|
最後の例 (リスト 9) では、SimpleXML と、XMLReader と一緒の DOM を使っています。XMLReader の場合、データは一度に 1 要素ずつ、expand() を使って渡されます。この方法を使うと、XMLReader が渡すノードを DOMElement に変換し、それをさらに SimpleXML に変換することができます。
リスト 9. SimpleXML と DOM、XMLReader を使って、大きな XML 文書を構文解析する
<?php
// Parsing a large document with Expand and SimpleXML
$reader = new XMLReader();
$reader->open("tooBig.xml");
while ($reader->read()) {
switch ($reader->nodeType) {
case (XMLREADER::ELEMENT):
if ($reader->localName == "entry") {
if ($reader->getAttribute("ID") == 5225) {
$node = $reader->expand();
$dom = new DomDocument();
$n = $dom->importNode($node,true);
$dom->appendChild($n);
$sxe = simplexml_import_dom($n);
echo $sxe->title;
}
}
}
}
?>
|
ストリーム・ベースのパーサー
ストリーム・ベースのパーサーという名前は、ストリーミング・オーディオとほとんど同じ原理で、ストリームとして XML を構文解析することに由来しています。つまり、ある特定ノードを処理し、そのノードの処理が終わると、そのノードが存在することを完全に忘れてしまいます。XMLReader はプル・パーサーであり、そのコーディング方法は、データベース・クエリーの結果のテーブルをカーソルで処理する方法とほとんど同じです。この方法では、未知の、あるいは予測できない XML ファイルを容易に処理することができます。
XMLReader による構文解析
XMLReader 拡張モジュールは、ストリーム・ベースのパーサーであり、よくカーソル型のパーサー、あるいはプル・パーサーと言われます。XMLReader は、C# XmlTextReader から派生した API をベースにしており、要求に応じて XML 文書から情報を引き出します。また XMLReader は、 PHP 5.1 には含まれていて、デフォルトで有効になっており、libxml2 をベースとしています。PHP 5.1 の前までは、XMLReader 拡張モジュールはデフォルトで有効ではなく、PECL (「参考文献」にリンクがあります) で入手していました。XMLReader は、名前空間と、DTD と Relaxed NG を含む妥当性検証をサポートしています。
XMLReader の実際
XMLReader はストリーム・パーサーであるため、大きな XML 文書の構文解析に適しています。XMLReader のコーディングは SAX よりもはるかに容易であり、通常は SAX よりも高速です。ストリーム・パーサーを選択するのであれば、XMLReader が最適です。
リスト 10 に示す例は、大きな XML 文書を XMLReader で構文解析しています。
リスト 10. 大きな XML 文書に XMLReader を使う
<?php
$reader = new XMLReader();
$reader->open("tooBig.xml");
while ($reader->read()) {
switch ($reader->nodeType) {
case (XMLREADER::ELEMENT):
if ($reader->localName == "entry") {
if ($reader->getAttribute("ID") == 5225) {
while ($reader->read()) {
if ($reader->nodeType == XMLREADER::ELEMENT) {
if ($reader->localName == "title") {
$reader->read();
echo $reader->value;
break;
}
if ($reader->localName == "entry") {
break;
}
}
}
}
}
}
}
?>
|
SAX による構文解析
SAX (Simple API for XML) はストリーム・パーサーです。イベントは読み取り対象の XML 文書に関連付けられるため、SAX はコールバックの中にコーディングされます。タグを開いたり閉じたりする要素に対するイベントがあり、要素の内容に対するイベントがあり、またエンティティー、構文解析エラーに対するイベントがあります。XMLReader を使わずに SAX を使う主な理由は、SAX パーサーの方が効率的な場合があり、通常は SAX の方がよく知られているためです。SAX パーサーの主な欠点は、コードが複雑であり、XMLReader のコードよりも作成するのが難しいことです。
SAX の実際
PHP4 で XML を扱ったことのある人には SAX はおそらくおなじみであり、また PHP5 の SAX 拡張モジュールは、彼らが慣れているバージョンと互換性があります。SAX はストリーム・パーサーであるため、大きなファイルには適していますが、XMLReader ほど適切な選択肢ではありません。
リスト 11 に示す例は、大きな XML 文書を SAX を使って構文解析しています。
リスト 11. 大きな XML 文書を SAX を使って構文解析する
<?php
//This class contains all the callback methods that will actually
//handle the XML data.
class SaxClass {
private $hit = false;
private $titleHit = false;
//callback for the start of each element
function startElement($parser_object, $elementname, $attribute) {
if ($elementname == "entry") {
if ( $attribute['ID'] == 5225) {
$this->hit = true;
} else {
$this->hit = false;
}
}
if ($this->hit && $elementname == "title") {
$this->titleHit = true;
} else {
$this->titleHit =false;
}
}
//callback for the end of each element
function endElement($parser_object, $elementname) {
}
//callback for the content within an element
function contentHandler($parser_object,$data)
{
if ($this->titleHit) {
echo trim($data)."<br />";
}
}
}
//Function to start the parsing once all values are set and
//the file has been opened
function doParse($parser_object) {
if (!($fp = fopen("tooBig.xml", "r")));
//loop through data
while ($data = fread($fp, 4096)) {
//parse the fragment
xml_parse($parser_object, $data, feof($fp));
}
}
$SaxObject = new SaxClass();
$parser_object = xml_parser_create();
xml_set_object ($parser_object, $SaxObject);
//Don't alter the case of the data
xml_parser_set_option($parser_object, XML_OPTION_CASE_FOLDING, false);
xml_set_element_handler($parser_object,"startElement","endElement");
xml_set_character_data_handler($parser_object, "contentHandler");
doParse($parser_object);
?> |

 |
まとめ
PHP5 では、改善されたさまざまな方式の構文解析が提供されています。DOM を使った構文解析は、完全に W3C 標準準拠となり、よく知られた選択肢として、複雑でも比較的小さな文書の構文解析に適しています。基本的であまり大きくない XML 文書には SimpleXML が最適であり、また XMLReader は SAX よりも容易で高速であり、大きな文書に対するストリーム・パーサーとして最適です。
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | |  | Cliff Morgan は独立コンサルタントとして Web アプリケーションや Web サイトの設計や実装を行っています。 |
記事の評価
|