CLXVIII. XML パーサ関数

導入

XML (eXtensible Markup Language) は、Web における構造化された ドキュメント交換用のデータフォーマットです。XML は、World Wide Web consortium (W3C) で規定された規格です。XML に関する情報およ び関連する技術は、http://www.w3.org/XML/ で参照することができます。

このPHPエクステンションは、James Clark氏の expatのサポートをPHPに付加します。 このツールキットは、XML ドキュメントの構文解析をしますが、 検証は行いません。3種類のソース 文字エンコーディングUS-ASCII, ISO-8859-1 ,UTF-8 がPHPでサポートされます。UTF-16 はサポートさ れません。

この拡張モジュールは、XML パーサの作成 を行い、異なった XML イベントに関してハンドラ を定義します。各XMLパーサーには、設定可能な小数の パラメータ もあります。

要件

このエクステンションは、expat を使用します。 これは、http://www.jclark.com/xml/expat.htmlにあります。 expatに付属のMakefileは、デフォルトでライブラリを構築しません。こ れを行うmakeルールを次のように指定できます。
libexpat.a: $(OBJS)
    ar -rc $@ $(OBJS)
    ranlib $@
expat のソース RPM パッケージが http://sourceforge.net/projects/expat/ にあります。

インストール手順

付属しているexpatライブラリを用いて以下の関数はデフォルトで有効となっ ています。 --disable-xmlを指定してXMLサポート を無効にすることができます。Apache 1.3.9以降でモジュールとしてPHPを コンパイルする場合、PHPは、Apacheから自動的に付属する expatライブラリを使用します。 付属するexpatライブラリを使用したくない場合には、 --with-expat-dir=DIRを指定してPHP のconfigureを実行してください。ただし、DIRは、expatをインストールした ベースディレクトリです。

Windows 版の PHP には この拡張モジュールのサポートが組み込まれています。これらの関数を使用 するために拡張モジュールを追加でロードする必要はありません。

実行時設定

設定ディレクティブは定義されていません。

リソース型

xml

xml_parser_create()および xml_parser_create_ns() により返された xmlリソースは、 このエクステンションにより提供された関数で使用される XMLパーサのインスタンスを参照します。

定義済み定数

以下の定数が定義されています。 この関数の拡張モジュールが PHP 組み込みでコンパイルされているか、 実行時に動的にロードされている場合のみ使用可能です。

XML_ERROR_NONE (integer)

XML_ERROR_NO_MEMORY (integer)

XML_ERROR_SYNTAX (integer)

XML_ERROR_NO_ELEMENTS (integer)

XML_ERROR_INVALID_TOKEN (integer)

XML_ERROR_UNCLOSED_TOKEN (integer)

XML_ERROR_PARTIAL_CHAR (integer)

XML_ERROR_TAG_MISMATCH (integer)

XML_ERROR_DUPLICATE_ATTRIBUTE (integer)

XML_ERROR_JUNK_AFTER_DOC_ELEMENT (integer)

XML_ERROR_PARAM_ENTITY_REF (integer)

XML_ERROR_UNDEFINED_ENTITY (integer)

XML_ERROR_RECURSIVE_ENTITY_REF (integer)

XML_ERROR_ASYNC_ENTITY (integer)

XML_ERROR_BAD_CHAR_REF (integer)

XML_ERROR_BINARY_ENTITY_REF (integer)

XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF (integer)

XML_ERROR_MISPLACED_XML_PI (integer)

XML_ERROR_UNKNOWN_ENCODING (integer)

XML_ERROR_INCORRECT_ENCODING (integer)

XML_ERROR_UNCLOSED_CDATA_SECTION (integer)

XML_ERROR_EXTERNAL_ENTITY_HANDLING (integer)

XML_OPTION_CASE_FOLDING (integer)

XML_OPTION_TARGET_ENCODING (integer)

XML_OPTION_SKIP_TAGSTART (integer)

XML_OPTION_SKIP_WHITE (integer)

イベントハンドラ

XML イベントハンドラは次のように定義されます。

表 1. サポートされる XML ハンドラ

ハンドラ設定用の PHP 関数イベントの説明
xml_set_element_handler() 要素イベントは、XML パーサーが開始または終了タグに出会うたび に発行されます。開始タグと終了タグについて別のハンドラがあり ます。
xml_set_character_data_handler() 文字データは、タグの間の空白を含めて XML ドキュメントにおけ るほぼ全ての非マークアップ部分の内容です。XML パーサーは、 空白を加えたり削除したりしないことに注意してください。空白が 意味を有するかどうかを決めるのは、アプリケーション側の責任 です。
xml_set_processing_instruction_handler() PHP プログラマは、既に処理用命令 (PI) に既に慣れているに違 いありません。<?php ?> は処理用命令であり、この場合、 php は "PI ターゲット"と呼ばれます。 これらの処理はアプリケーション依存ですが、全ての PI ターゲッ トが "XML" から始まることだけは、規定されています。
xml_set_default_handler() 別のハンドラでしないことをデフォルトのハンドラで行います。 XML およびドキュメント型の宣言のようなことをデフォルトハンドラで 行います。
xml_set_unparsed_entity_decl_handler() このハンドラは、処理されない (NDATA) エンティティの宣言用に コールされます。
xml_set_notation_decl_handler() このハンドラは、表記の宣言用にコールされます。
xml_set_external_entity_ref_handler() このハンドラは、XML パーサーが外部処理された通常のエンティティ への参照を見つけた際にコールされます。これは、例えば、ファ イルまたは URL への参照とすることが可能です。例としては、 外部エンティティ の例 を参照ください。

大文字変換(Case Folding)

要素ハンドラ関数は、その要素に大文字小文字を変換する (case-folded)の名前をつけることができます。 大文字変換(case-folding) は、XML標準により "大文字でないものは等 価な大文字に置換される一連の文字に適用されるプロセス" として定義 されています。言い替えると、XML に関しては単に大文字変換は大文字 にすることを意味します。

デフォルトで、ハンドラ関数に渡される全ての要素名は、大文字変換さ れます。この動作は、xml_parser_get_option() およびxml_parser_set_option() 関数でXMLパーサー 毎にそれぞれ問い合わせ、制御することが可能です。

エラーコード

(xml_parse() により返されるものとして) XMLエラーコードとして次のような定数が定義されています。:

XML_ERROR_NONE
XML_ERROR_NO_MEMORY
XML_ERROR_SYNTAX
XML_ERROR_NO_ELEMENTS
XML_ERROR_INVALID_TOKEN
XML_ERROR_UNCLOSED_TOKEN
XML_ERROR_PARTIAL_CHAR
XML_ERROR_TAG_MISMATCH
XML_ERROR_DUPLICATE_ATTRIBUTE
XML_ERROR_JUNK_AFTER_DOC_ELEMENT
XML_ERROR_PARAM_ENTITY_REF
XML_ERROR_UNDEFINED_ENTITY
XML_ERROR_RECURSIVE_ENTITY_REF
XML_ERROR_ASYNC_ENTITY
XML_ERROR_BAD_CHAR_REF
XML_ERROR_BINARY_ENTITY_REF
XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF
XML_ERROR_MISPLACED_XML_PI
XML_ERROR_UNKNOWN_ENCODING
XML_ERROR_INCORRECT_ENCODING
XML_ERROR_UNCLOSED_CDATA_SECTION
XML_ERROR_EXTERNAL_ENTITY_HANDLING

文字エンコーディング

PHPのXML拡張機能は、異なった文字エンコーディング を通じてUnicode 文字セットをサポートします。ソースエンコーディング およびターゲットエンコーディング という2種類の文字エンコーディングがあります。 PHP におけるドキュメントの内部表現は、常に UTF-8でエンコードされます。

ソースエンコーディングは、XMLドキュメントが 構文解析された際に行わ れます。XML パーサの 作成を行う際に、ソースエンコードを指定することができます。 (このエンコーディングは、その XML パーサーが存在する間、後で変更す ることはできません)サポートされるソースエンコーディングは、 ISO-8859-1, US-ASCII , UTF-8 です。前の二つは、シングルバイトエンコー ディングです。これは、各文字がシングルバイトで表現されることを意 味します。UTF-8 は、1から4バイトの可変ビット 数(最大21ビット)で構成された文字をエンコードすることが可能です。 PHP で用いられるデフォルトのソースエンコーディングは、 ISO-8859-1です。

ターゲットエンコーディングは、PHPがデータをXMLハンドラ関数に 渡す時に行われます。あるXMLパーサが作成された際、ターゲットエン コーディングは、ソースエンコーディングと同様に設定されます。 しかし、これは、いつでも変更可能です。ターゲットエンコーディング は、タグ名と同様に文字データに作用し、命令を処理します。

XML パーサがソースエンコーディングが表現できる範囲の外側の文字に 出会った場合、エラーが返されます。

解釈するXMLドキュメントにおいてPHPが文字に出会った際に、選択した ターゲットエンコーディングで表現できない文字に出会った場合、問題 の文字は "降格" されます。現在、このことはこのような文字が疑問符 で置換されることを意味します。

以下にXMLドキュメントを処理するPHPスクリプトの例をいくつか示します。

XML エレメント構造の例

この最初の例は、あるドキュメント中のstart エレメントの構造をイン デントを付けて表示します。

例 1. XML エレメント構造を表示

<?php
$file
= "data.xml";
$depth = array();

function
startElement($parser, $name, $attrs)
{
    global
$depth;
    for (
$i = 0; $i < $depth[$parser]; $i++) {
        print
"  ";
    }
    print
"$name\n";
    
$depth[$parser]++;
}

function
endElement($parser, $name)
{
    global
$depth;
    
$depth[$parser]--;
}

$xml_parser = xml_parser_create();
xml_set_element_handler($xml_parser, "startElement", "endElement");
if (!(
$fp = fopen($file, "r"))) {
    die(
"XML 入力をオープンできませんでした");
}
while (
$data = fread($fp, 4096)) {
    if (!
xml_parse($xml_parser, $data, feof($fp))) {
        die(
sprintf("XML エラー: %s が %d 行目で発生しました",
                    
xml_error_string(xml_get_error_code($xml_parser)),
                    
xml_get_current_line_number($xml_parser)));
    }
}
xml_parser_free($xml_parser);
?>

XMLタグのマッピングの例

例 2. XMLをHTMLにマップする

この例は、XMLドキュメントのタグを直接HTMLタグにマップします。 "map array" にないエレメントは無視されます。もちろん、この例は、 特定の XML ドキュメント型を有する場合のみ動作します。

<?php
$file
= "data.xml";
$map_array = array(
    
"BOLD"     => "B",
    
"EMPHASIS" => "I",
    
"LITERAL"  => "TT"
);

function
startElement($parser, $name, $attrs)
{
    global
$map_array;
    if (isset(
$map_array[$name])) {
        echo
"<$map_array[$name]>";
    }
}

function
endElement($parser, $name)
{
    global
$map_array;
    if (isset(
$map_array[$name])) {
        echo
"</$map_array[$name]>";
    }
}

function
characterData($parser, $data)
{
    echo
$data;
}

$xml_parser = xml_parser_create();
// case-folding を用いることで、$map_array から確実にタグを見つけられるようにします
xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true);
xml_set_element_handler($xml_parser, "startElement", "endElement");
xml_set_character_data_handler($xml_parser, "characterData");
if (!(
$fp = fopen($file, "r"))) {
    die(
"XML 入力をオープンできませんでした");
}

while (
$data = fread($fp, 4096)) {
    if (!
xml_parse($xml_parser, $data, feof($fp))) {
        die(
sprintf("XML エラー: %s が %d 行目で発生しました",
                    
xml_error_string(xml_get_error_code($xml_parser)),
                    
xml_get_current_line_number($xml_parser)));
    }
}
xml_parser_free($xml_parser);
?>

XML 外部エンティティの例

この例は、XML コードに焦点を当てます。この例は、他のドキュメント をインクルードし処理するための外部エンティティリファレンスのハン ドラの使用法およびPIの処理方法、PIが含むコードに関する"信頼度" を定義する手段を説明します。

この例で使用される XML ドキュメントは、例題ファイル (xmltest.xml および xmltest2.xml) にあります。

例 3. 外部エンティティの例

<?php
$file
= "xmltest.xml";

function
trustedFile($file)
{
    
// 信頼できるのは、自分自身が所有しているローカルファイルのみです
    
if (!eregi("^([a-z]+)://", $file)
        &&
fileowner($file) == getmyuid()) {
            return
true;
    }
    return
false;
}

function
startElement($parser, $name, $attribs)
{
    echo
"&lt;<font color=\"#0000cc\">$name</font>";
    if (
count($attribs)) {
        foreach (
$attribs as $k => $v) {
            echo
" <font color=\"#009900\">$k</font>=\"<font
                   color=
\"#990000\">$v</font>\"";
        }
    }
    echo
"&gt;";
}

function
endElement($parser, $name)
{
    echo
"&lt;/<font color=\"#0000cc\">$name</font>&gt;";
}

function
characterData($parser, $data)
{
    echo
"<b>$data</b>";
}

function
PIHandler($parser, $target, $data)
{
    switch (
strtolower($target)) {
        case
"php":
            global
$parser_file;
            
// もし「信頼できる」ドキュメントだった場合、その中に書かれている
            // PHP コードを実行しても安全だと考えます。そうでない場合、
            // コードを実行するかわりにコードそのものを表示します。
            
if (trustedFile($parser_file[$parser])) {
                eval(
$data);
            } else {
                
printf("信頼できない PHP コード: <i>%s</i>",
                        
htmlspecialchars($data));
            }
            break;
    }
}

function
defaultHandler($parser, $data)
{
    if (
substr($data, 0, 1) == "&" && substr($data, -1, 1) == ";") {
        
printf('<font color="#aa00aa">%s</font>',
                
htmlspecialchars($data));
    } else {
        
printf('<font size="-1">%s</font>',
                
htmlspecialchars($data));
    }
}

function
externalEntityRefHandler($parser, $openEntityNames, $base, $systemId,
                                  
$publicId) {
    if (
$systemId) {
        if (!list(
$parser, $fp) = new_xml_parser($systemId)) {
            
printf("エンティティ %s (%s にある) をオープンできませんでした\n", $openEntityNames,
                   
$systemId);
            return
false;
        }
        while (
$data = fread($fp, 4096)) {
            if (!
xml_parse($parser, $data, feof($fp))) {
                
printf("XML エラー: %s が、%d 行目でエンティティ %s のパース中に発生しました\n",
                       
xml_error_string(xml_get_error_code($parser)),
                       
xml_get_current_line_number($parser), $openEntityNames);
                
xml_parser_free($parser);
                return
false;
            }
        }
        
xml_parser_free($parser);
        return
true;
    }
    return
false;
}

function
new_xml_parser($file)
{
    global
$parser_file;

    
$xml_parser = xml_parser_create();
    
xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 1);
    
xml_set_element_handler($xml_parser, "startElement", "endElement");
    
xml_set_character_data_handler($xml_parser, "characterData");
    
xml_set_processing_instruction_handler($xml_parser, "PIHandler");
    
xml_set_default_handler($xml_parser, "defaultHandler");
    
xml_set_external_entity_ref_handler($xml_parser, "externalEntityRefHandler");

    if (!(
$fp = @fopen($file, "r"))) {
        return
false;
    }
    if (!
is_array($parser_file)) {
        
settype($parser_file, "array");
    }
    
$parser_file[$xml_parser] = $file;
    return array(
$xml_parser, $fp);
}

if (!(list(
$xml_parser, $fp) = new_xml_parser($file))) {
    die(
"XML 入力をオープンできませんでした");
}

echo
"<pre>";
while (
$data = fread($fp, 4096)) {
    if (!
xml_parse($xml_parser, $data, feof($fp))) {
        die(
sprintf("XML エラー: %s が %d 行目で発生しました\n",
                    
xml_error_string(xml_get_error_code($xml_parser)),
                    
xml_get_current_line_number($xml_parser)));
    }
}
echo
"</pre>";
echo
"パースが完了しました\n";
xml_parser_free($xml_parser);

?>

例 4. xmltest.xml

<?xml version='1.0'?>
<!DOCTYPE chapter SYSTEM "/just/a/test.dtd" [
<!ENTITY plainEntity "FOO entity">
<!ENTITY systemEntity SYSTEM "xmltest2.xml">
]>
<chapter>
 <TITLE>Title &plainEntity;</TITLE>
 <para>
  <informaltable>
   <tgroup cols="3">
    <tbody>
     <row><entry>a1</entry><entry morerows="1">b1</entry><entry>c1</entry></row>
     <row><entry>a2</entry><entry>c2</entry></row>
     <row><entry>a3</entry><entry>b3</entry><entry>c3</entry></row>
    </tbody>
   </tgroup>
  </informaltable>
 </para>
 &systemEntity;
 <section id="about">
  <title>About this Document</title>
  <para>
   <!-- this is a comment -->
   <?php print 'Hi!  This is PHP version '.phpversion(); ?>
  </para>
 </section>
</chapter>

このファイルは、xmltest.xml からインクルードされます。

例 5. xmltest2.xml

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY testEnt "test entity">
]>
<foo>
   <element attrib="value"/>
   &testEnt;
   <?php print "This is some more PHP code being executed."; ?>
</foo>

目次
utf8_decode --  UTF-8 エンコードされたISO-8859-1 文字列をシングルバイトの ISO-8859-1 に変換する
utf8_encode -- ISO-8859-1 文字列を UTF-8 にエンコードする
xml_error_string -- XML パーサのエラー文字列を得る
xml_get_current_byte_index -- XML パーサのカレントのバイトインデックスを得る
xml_get_current_column_number -- XML パーサのカレントのカラム番号を得る
xml_get_current_line_number -- XML パーサのカレントの行番号を得る
xml_get_error_code -- XML パーサのエラーコードを得る
xml_parse_into_struct -- 配列構造体に XML データを処理する
xml_parse -- XML ドキュメントの処理を開始する
xml_parser_create_ns -- 名前空間をサポートした XML パーサを生成する
xml_parser_create -- XML パーサを作成する
xml_parser_free -- XML パーサを解放する
xml_parser_get_option -- XML パーサからオプションを得る
xml_parser_set_option -- XML パーサのオプションを設定する
xml_set_character_data_handler -- 文字データハンドラを設定する
xml_set_default_handler -- デフォルトのハンドラを設定する
xml_set_element_handler -- 開始要素および終了要素のハンドラを設定する
xml_set_end_namespace_decl_handler -- 名前空間終了ハンドラを設定する
xml_set_external_entity_ref_handler -- 外部エンティティリファレンスハンドラを設定する
xml_set_notation_decl_handler -- 表記法宣言ハンドラを設定する
xml_set_object -- オブジェクト内部で XML パーサを使用する
xml_set_processing_instruction_handler --  処理命令 (PI) 用ハンドラを設定する
xml_set_start_namespace_decl_handler -- 名前空間開始ハンドラを設定する
xml_set_unparsed_entity_decl_handler --  処理されないエンティティ宣言用ハンドラを設定する