まず最初に、私はコンピューターの標準が大好きであることを認めなければなりません。もし誰もが業界の標準に従えば、インターネットはより良いメディアになるでしょう。標準化されたデータ交換フォーマットの使用により、オープンでプラットフォームに依存しないコンピューティング モデルが実現可能になります。それが私が XML 愛好家である理由です。
幸いなことに、私のお気に入りのスクリプト言語は XML をサポートするだけでなく、ますますサポートするようになっています。 PHP を使用すると、XML ドキュメントをインターネットにすばやく公開したり、XML ドキュメントに関する統計情報を収集したり、XML ドキュメントを他の形式に変換したりすることができます。たとえば、私は XML で書いた記事や書籍を管理するために、PHP の XML 処理機能をよく使用します。
この記事では、PHP の組み込み Expat パーサーを使用して XML ドキュメントを処理する方法について説明します。例を通して、Expat の処理方法を説明します。同時に、この例では次の方法を示すことができます。
独自の処理関数を作成する
XML ドキュメントを独自の PHP データ構造に変換する
はじめに Expat
XML のパーサーは XML プロセッサとも呼ばれ、プログラムが XML ドキュメントの構造とコンテンツにアクセスできるようにします。 Expat は、PHP スクリプト言語用の XML パーサーです。 Mozilla、Apache、Perl などの他のプロジェクトでも使用されます。
イベントベースのパーサーとは何ですか?
XML パーサーには 2 つの基本的なタイプがあります。
ツリーベースのパーサー: XML ドキュメントをツリー構造に変換します。このタイプのパーサーは、結果のツリーの各要素にアクセスするための API を提供しながら、記事全体を解析します。その共通規格は DOM (Document Object Model) です。
イベントベースのパーサー: XML ドキュメントを一連のイベントとして扱います。特別なイベントが発生すると、パーサーは開発者が提供する関数を呼び出してそれを処理します。
イベントベースのパーサーは、XML 文書のデータ中心のビューを備えています。これは、XML 文書の構造ではなく、データ部分に焦点を当てていることを意味します。これらのパーサーはドキュメントを最初から最後まで処理し、コールバック関数を通じて要素の開始、要素の終了、特徴データの開始などのイベントをアプリケーションに報告します。以下は、「Hello-World」の XML ドキュメントの例です
。
こんにちは世界
イベントベースのパーサーは、
開始要素: 挨拶の
3 つのイベントとして報告します。
CDATA 項目の始まり、値は次のとおりです: Hello World
終了要素: 挨拶
ツリーベースのパーサーとは異なり、イベントベースのパーサーはドキュメントを記述する構造を生成しません。 CDATA アイテムでは、イベントベースのパーサーでは親要素の挨拶情報を取得できません。
ただし、下位レベルのアクセスが提供されるため、リソースをより有効に活用し、アクセスを高速化できます。この方法では、ドキュメント全体をメモリに収める必要がなく、実際にはドキュメント全体が実際のメモリ値よりも大きくなる場合もあります。
Expat は、このようなイベントベースのパーサーです。もちろん、Expat を使用する場合は、必要に応じて PHP で完全なネイティブ ツリー構造を生成することもできます。
上記の Hello-World の例には、完全な XML 形式が含まれています。ただし、関連付けられた DTD (Document Type Definition) も埋め込まれた DTD も存在しないため、これは無効です。
Expat の場合、これは何の違いもありません。Expat は有効性をチェックしないパーサーであるため、ドキュメントに関連付けられた DTD を無視します。ただし、ドキュメントを完全にフォーマットする必要があることに注意してください。そうでないと、Expat (他の XML 準拠パーサーと同様) がエラー メッセージを表示して停止します。
有効性をチェックしないパーサーである Exapt は、その速度と軽量性により、インターネット アプリケーションに適しています。
Expat のコンパイル
Expat は PHP3.0.6 バージョン (またはそれ以降) にコンパイルできます。 Apache 1.3.9 以降、Expat は Apache の一部として含まれるようになりました。 Unix システムでは、-with-xml オプションを使用して PHP を構成することで、PHP にコンパイルできます。
PHP を Apache モジュールとしてコンパイルすると、デフォルトで Expat が Apache の一部として組み込まれます。 Windows では、XML ダイナミック リンク ライブラリをロードする必要があります。
XML の例: XMLstats
Expat の関数について学ぶ 1 つの方法は、例を通して見ることです。これから説明する例では、Expat を使用して XML ドキュメントの統計を収集します。
ドキュメント内の各要素について、次の情報が出力されます:
ドキュメント内で要素が使用された回数
この要素内の文字データの量
要素の親要素
要素の子要素
注: デモのために、PHP を使用して要素の親要素と子要素を保存する構造体を生成します。XML
パーサー インスタンスを生成するために
用意された
関数は xml_parser_create() です。このインスタンスは、今後のすべての関数に使用されます。この考え方は、PHP の MySQL 関数の接続タグに非常に似ています。通常、イベントベースのパーサーでは、ドキュメントを解析する前に、特定のイベントが発生したときに呼び出されるコールバック関数を登録する必要があります。 Expat には、次の 7 つの例外イベントが定義されています。
オブジェクト XML 解析関数の説明
要素 xml_set_element_handler() 要素の開始および終了
文字データ xml_set_character_data_handler() 文字データの開始
外部エンティティ xml_set_external_entity_ref_handler() 外部エンティティ 未解析の
外部エンティティ xml_set_unparsed_entity_decl_handler ( ) 未解決の外部エンティティの処理命令の発生
xml_set_processing_instruction_handler() 処理命令の表記法宣言の発生
xml_set_notation_decl_handler() 表記法宣言の発生
default xml_set_default_handler() ハンドラ関数が指定されていないその他のイベント
すべてのコールバック関数は、 parser を最初のパラメータとして使用します (他にもパラメータがあります)。
この記事の最後にあるサンプル スクリプトについては、注意が必要なのは、要素処理関数と文字データ処理関数の両方を使用していることです。要素のコールバック ハンドラー関数は、xml_set_element_handler() を通じて登録されます。
この関数は 3 つのパラメータを取ります:
パーサーのインスタンス
開始要素を処理するコールバック関数の名前
終了要素を処理するコールバック関数の名前
XML ドキュメントの解析を開始するときに、コールバック関数が存在している必要があります。これらは、PHP マニュアルに記載されているプロトタイプと一致して定義する必要があります。
たとえば、Expat は開始要素のハンドラー関数に 3 つの引数を渡します。スクリプト例では、次のように定義されています。
function start_element($parser, $name, $attrs)
最初のパラメーターはパーサー識別子、2 番目のパラメーターは開始要素の名前、3 番目のパラメーターにはすべての属性と要素配列の値。
XML ドキュメントの解析を開始すると、Expat は開始要素に遭遇するたびに start_element() 関数を呼び出してパラメータを渡します。
XML の大文字と小文字の折り畳みオプションは、
xml_parser_set_option () 関数を使用して大文字と小文字の折り畳みオプションをオフにします。このオプションはデフォルトでオンになっており、ハンドラー関数に渡される要素名が自動的に大文字に変換されます。ただし、XML では大文字と小文字が区別されます (したがって、統計 XML ドキュメントでは大文字と小文字が非常に重要です)。この例では、ケース折りたたみオプションをオフにする必要があります。
ドキュメントの解析
すべての準備が完了したら、スクリプトは最終的に XML ドキュメントを解析できます。カスタム
関数である Xml_parse_from_file() は、パラメーターで指定されたファイルを開き、4kb サイズで解析します。
xml_parse() は、xml_parse_from_file() と同様、エラーが発生した場合、つまり XML ドキュメントが完全にフォーマットされていない場合に false を返します。
xml_get_error_code() 関数を使用して、最後のエラーの数値コードを取得できます。この数値コードを xml_error_string() 関数に渡して、エラー テキスト情報を取得します。
XML の現在の行番号を出力し、デバッグを容易にします。
解析プロセス中に、コールバック関数が呼び出されます。
文書構造の記述
文書を解析するときに、Expat で対処する必要がある問題は、文書構造の基本的な記述をどのように維持するかということです。
前述したように、イベントベースのパーサー自体は構造情報を生成しません。
ただし、タグ構造は XML の重要な機能です。たとえば、要素シーケンス <book><title> は、<figure><title> とは異なる意味を持ちます。そうは言っても、たとえどちらも「タイトル」という用語を使用していても、本のタイトルと絵のタイトルは互いに何の関係もないと、著者なら誰でも言うでしょう。したがって、イベントベースのパーサーで XML を効率的に処理するには、独自のスタックまたはリストを使用してドキュメントに関する構造情報を維持する必要があります。
ドキュメント構造をミラーリングするには、スクリプトは少なくとも現在の要素の親要素を認識している必要があります。これは、Exapt の API では不可能であり、コンテキスト情報なしで現在の要素のイベントのみを報告します。したがって、独自のスタック構造を構築する必要があります。
スクリプトの例では、先入れ後出し (FILO) スタック構造を使用します。配列を通じて、スタックはすべての開始要素を保存します。開始要素処理関数の場合、現在の要素は array_push() 関数によってスタックの先頭にプッシュされます。同様に、終了要素処理関数は、array_pop() を通じて先頭要素を削除します。
シーケンス <book><title></title></book> の場合、スタックは次のように設定されます:
要素の開始 book: スタックの最初の要素 ($stack[0]) に「book」を割り当てます。
開始要素 title: スタックの先頭 ($stack[1]) に「title」を割り当てます。
要素タイトルの終了: スタック ($stack[1]) から最上位要素を削除します。
要素タイトルの終了: スタック ($stack[0]) から最上位要素を削除します。
PHP3.0 は、$ Depth 変数を通じて要素のネストを手動で制御することにより、この例を実装します。これにより、スクリプトがより複雑に見えます。 PHP4.0 では、array_pop() 関数と array_push() 関数を使用して、スクリプトをより簡潔に見せます。
データの収集
各要素に関する情報を収集するには、スクリプトは各要素のイベントを記憶する必要があります。グローバル配列変数 $elements を使用して、ドキュメント内のさまざまな要素をすべて保存します。配列の項目は要素クラスのインスタンスであり、4 つのプロパティ (クラスの変数) があります。
$count - ドキュメント内で要素が見つかった回数
$chars - 要素内の文字イベントのバイト数
$parents - 親要素
$childs - 子要素
ご覧のとおり、クラス インスタンスを配列に保存するのは簡単です。
注: PHP の特徴は、対応する配列全体を走査するのと同じように、while(list() = each()) ループを通じてクラス構造全体を走査できることです。すべてのクラス変数 (および PHP3.0 を使用する場合はメソッド名) は文字列として出力されます。
要素が見つかったら、対応するカウンタをインクリメントして、ドキュメント内でその要素が何回出現したかを追跡する必要があります。対応する $elements 項目の count 要素も 1 つ増加します。
また、現在の要素がその子要素であることを親要素に知らせる必要があります。したがって、現在の要素の名前は、親要素の $childs 配列内の項目に追加されます。最後に、現在の要素はその親が誰であるかを記憶する必要があります。したがって、親要素は、現在の要素の $parents 配列内の項目に追加されます。
統計の表示
残りのコードは、$elements 配列とそのサブ配列をループして、その統計を表示します。これは最も単純なネストされたループですが、正しい結果が出力されますが、コードは簡潔でも特別なスキルもありません。これは、作業を完了するために毎日使用できる単なるループです。
スクリプトの例は、PHP の CGI アプローチを介してコマンド ラインから呼び出せるように設計されています。そのため、統計結果の出力形式はテキスト形式となります。スクリプトをインターネット上で使用する場合は、出力関数を変更して HTML 形式を生成する必要があります。
概要
Exapt は、PHP 用の XML パーサーです。イベントベースのパーサーとして、文書の構造的な記述は生成されません。ただし、低レベルのアクセスを提供することにより、リソースの利用効率が向上し、アクセスが高速化されます。
Expat は有効性をチェックしないパーサーとして、XML ドキュメントに添付された DTD を無視しますが、ドキュメントが整形式でない場合はエラー メッセージを表示して停止します。
ドキュメントを処理するためのイベント ハンドラーを提供する
スタックやツリーなどの独自のイベント構造を構築して、XML 構造化情報マークアップを活用します。
新しい XML プログラムは毎日登場し、PHP の XML サポートは常に強化されています (たとえば、DOM ベースの XML パーサー LibXML のサポートが追加されました)。
PHP と Expat を使用すると、有効かつオープンでプラットフォームに依存しない今後の標準に備えることができます。
例
<?
/************************************************ ***** *****************************
* 名前: XML 解析例: XML 文書情報統計
* 説明する
* この例では、PHP の Expat パーサーを使用して、XML ドキュメント情報 (各要素、親要素、子要素の出現数など) を収集およびカウントします。
* パラメータとしての XML ファイル。/xmlstats_PHP4.php3 test.xml
* $Requires: Expat 要件: Expat PHP4.0 は CGI モードにコンパイルされます
************************************************* * ***************************/
// 最初のパラメータは XML ファイルです
$file = $argv[1]
// 変数の初期化
$elements = $stack = array();
$total_elements = $total_chars = 0;
//要素の基本クラス
クラス要素
{
var $count = 0;
var $chars = 0;
var $parents = array();
var $childs = array();
}
// XMLファイルを解析する関数
関数 xml_parse_from_file($parser, $file)
{
if(!file_exists($file))
{
die("ファイル "$file" が見つかりません。");
if(!($fp = @fopen($file, "r"))
)
{
die("ファイル "$file" を開けません。");
while($data = fread($fp, 4096)
)
{
if(!xml_parse($parser, $data, feof($fp)))
{
戻り値(偽);
}
fclose
($fp)
;
}
// 出力結果関数(ボックス形式)
関数 print_box($title, $value)
{
printf("n+%'-60s+n", "");
printf("|%20s", "$title:");
printf("%14s", $value);
printf("%26s|n", "");
printf("+%'-60s+n", "");
}
// 結果関数の出力(行形式)
関数 print_line($title, $value)
{
printf("%20s", "$title:");
printf("%15sn", $value);
}
// ソート関数
関数 my_sort($a, $b)
{
return(is_object($a) && is_object($b) ? $b->count - $a->count: 0);
関数
start_element($parser, $name, $attrs)
{
global $elements, $stack;
// 要素はすでにグローバル $elements 配列にありますか?
if(!isset($elements[$name]))
{
// いいえ - 要素のクラス インスタンスを追加します
$element = 新しい要素;
$elements[$name] = $element;
}
// この要素のカウンタを 1 つインクリメントします
$elements[$name]->count++;
// 親要素はありますか?
if(isset($stack[count($stack)-1]))
{
// はい - 親要素を $last_element に割り当てます
$last_element = $stack[count($stack)-1];
// 現在の要素の親要素配列が空の場合は、0 に初期化します。
if(!isset($elements[$name]->parents[$last_element]))
{
$elements[$name]->parents[$last_element] = 0;
}
// この要素の親要素カウンタを 1 つインクリメントします
$elements[$name]->parents[$last_element]++;
// 現在の要素の親要素の子要素配列が空の場合、0 に初期化されます
if(!isset($elements[$last_element]->子供[$名前]))
{
$elements[$last_element]->childs[$name] = 0;
}
// 要素の親要素の子要素カウンタに 1 を加算します。
$elements[$last_element]->childs[$name]++;
}
//現在の要素をスタックに追加します
array_push($stack, $name);
関数
stop_element($parser, $name)
{
global $stack;
// スタックから最上位の要素を削除します。
array_pop($stack);
関数
char_data($parser, $data)
{
global $elements, $stack, $ Depth;
// 現在の要素の文字数を増やします。
$elements[$stack][count($stack)-1]]->chars += strlen(trim($data));
}
// パーサーインスタンスを生成します
$parser = xml_parser_create();
// 処理関数を設定します
xml_set_element_handler($parser, "start_element", "stop_element");
xml_set_character_data_handler($parser, "char_data");
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
// ファイルを解析します。
$ret = xml_parse_from_file($parser, $file);
if(!$ret)
{
die(sprintf("XML エラー: %d 行目 %s",
xml_error_string(xml_get_error_code($parser)),
xml_get_current_line_number($parser)));
}
// パーサーを解放します
xml_parser_free($parser);
// ヘルパー要素を解放します。
unset($elements["current_element"]);
unset($elements["last_element"]);
// 要素の数に従って並べ替えます。
uasort($elements, "my_sort");
// $elements をループして要素情報を収集します
while(リスト($name, $element) = each($elements))
{
print_box("要素名", $name);
print_line("要素数", $element->count);
print_line("Character count", $element->chars);
printf("n%20sn", "* Parent elements");
// 要素の親をループして結果を出力します。
while(list($key, $value) = each($element->parents))
{
print_line($key, $value);
}
if(カウント($element->parents) == 0)
{
printf("%35sn", "[ルート要素]");
}
// この要素の子をループし、結果を出力します
printf("n%20sn", "* 子要素");
while(list($key, $value) = each($element->childs))
{
print_line($key, $value);
}
if(カウント($element->childs) == 0)
{
printf("%35sn", "[子なし]");
$
total_elements += $element->count;
$total_chars += $element->chars;
}
// 最終結果
print_box("総要素数", $total_elements);
print_box("合計文字数", $total_chars);
?>