English Japanese Kawa.netxp [Perl] XML::TreePP/Pure Perl実装によるXMLファイル展開モジュール

XML::TreePP モジュールは、 XML ファイルを解析してツリー構造の多次元変数(ハッシュ/配列)に展開します。
XML テキストと Perl オブジェクト(ハッシュ&配列)間の相互変換が可能です。
コンパイル不要の Pure Perl 実装モジュールなので、 プロバイダのレンタルサーバ環境でも .pm ファイル単体のコピーのみで手軽に利用できます。
(Ajax で利用する JavaScript の XMLHttpRequest オブジェクトのように) サーバからの XML ファイルのダウンロード処理にも対応しています。

安定版アーカイブ: XML-TreePP-0.41.tar.gz TARGZ CPAN ←通常パッケージ

最新版ソースファイル: lib/XML/TreePP.pm PM ←.pmファイルのみ必要な場合

Subversion レポジトリ: http://xml-treepp.googlecode.com/svn/trunk/XML-TreePP/ SVN

XML::TreePP モジュールは、Perl 5.005/5.6.x/5.8.x/5.10.x で利用できます。

Perl でなくて JavaScript をご利用の場合は、 XML.ObjTree クラスで同様の機能をサポートしています。

XML::TreePP モジュールの特長

サンプルプログラム

XML ファイルの内容をツリー構造のハッシュ変数に展開する処理の例。
RDF ファイルを受信して、最初のページのURLを表示しています。
面倒なDOMインターフェースの操作は不要です。 つまり JKL.ParseXML の Perl 版として利用できます。

    use XML::TreePP;
    my $treepp = XML::TreePP->new();
    my $hash = $treepp->parsefile( "index.rdf" );
    print "URL: ", $hash->{"rdf:RDF"}->{item}->[0]->{link}, "\n";

ツリー構造を作成したハッシュ変数から、XML ファイルを生成する処理の例。
2つのページを含む RSS ファイルを作成しています。(完全ではない)
もちろん、XML 生成処理でも面倒なDOMインターフェースの操作は不要です。

    use XML::TreePP;
    my $treepp = XML::TreePP->new();
    my $tree = { rss => { channel => { item => [ {
        title   => "The Perl Directory",
        link    => "http://www.perl.org/",
    }, {
        title   => "The Comprehensive Perl Archive Network",
        link    => "http://cpan.perl.org/",
    } ] } } };
    my $xml = $treepp->write( $tree );
    print $xml;

サーバ上からXML形式のデータを受信する場合

XML::TreePP では、 JavaScript の XMLHttpRequest(ajax)のように Web サーバ上にある XML ファイルを受信したり、 Web サービスで生成された XML データを受信することができます。

HTTP(GETメソッド)で他のウェブサーバ上のXMLファイルを取得して、ハッシュ変数に展開する例:

    use XML::TreePP;
    my $tpp = XML::TreePP->new();
    my $tree = $tpp->parsehttp( GET => "http://use.perl.org/index.rss" );
    print "Title: ", $tree->{"rdf:RDF"}->{channel}->{title}, "\n";
    print "URL: ", $tree->{"rdf:RDF"}->{channel}->{link}, "\n";

parsehttp() メソッドの第1引数は、GET・POST といった HTTP のメソッドを指定します。
第2引数は、アクセス先URLを指定します。

HTTP(POSTメソッド)はてなの検索結果を取り出して、ハッシュ変数に展開する例:

    use XML::TreePP;
    my $tpp = XML::TreePP->new( force_array => [qw( item )] );
    my $cgiurl = "http://search.hatena.ne.jp/keyword";
    my $keyword = "ajax";
    my $cgiquery = "mode=rss2&word=".$keyword;
    my $tree = $tpp->parsehttp( POST => $cgiurl, $cgiquery );
    print "Link: ", $tree->{rss}->{channel}->{item}->[0]->{link}, "\n";
    print "Desc: ", $tree->{rss}->{channel}->{item}->[0]->{description}, "\n";

new コンストラクタの array_element(⇒正:force_array)オプションは、 結果が1件だったとしても、確実に配列としてツリー展開させるための指定です。
デフォルト動作では、1回のみ登場する要素はスカラーとして、 2回以上登場する要素は配列として展開されます。

parsehttp() メソッドの第3引数は、POST 送信のリクエストボディ(本文データ)が入ります。

XML-RPC クライアントの実装については、 こちら を参照してください。

インストール手順

アーカイブの中にはいくつかのファイルが含まれていますが、 実際に必要となるファイルは XML/TreePP.pm のみです。

CPAN 経由でインストールする場合

普通に install XML::TreePP でインストールできます。
サーバの管理者権限が利用できる場合は、こちらになります。

[root]# perl -MCPAN -e shell
cpan shell -- CPAN exploration and modules installation (v1.86)
ReadLine support enabled

cpan> install XML::TreePP
Running install for module XML::TreePP
Running make for K/KA/KAWASAKI/XML-TreePP-0.03.tar.gz
Signature for /var/root/.cpan/sources/authors/id/K/KA/KAWASAKI/CHECKSUMS ok
Checksum for /var/root/.cpan/sources/authors/id/K/KA/KAWASAKI/XML-TreePP-0.03.tar.gz ok
        (中略)
  /usr/bin/make  -- OK
Running make test
PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/force_array....ok
t/parse..........ok
t/parsefile......ok
t/parsehttp......ok
t/write..........ok
All tests successful.
Files=5, Tests=19,  3 wallclock secs ( 0.57 cusr +  0.11 csys =  0.68 CPU)
  /usr/bin/make test -- OK
Running make install
Writing /Library/Perl/5.8.6/darwin-thread-multi-2level/auto/XML-TreePP/.packlist
Appending installation info to //System/Library/Perl/5.8.6/darwin-thread-multi-2level/perllocal.pod
  /usr/bin/make install  -- OK

cpan> quit

インストール時には、安全のためテストスクリプトを実行します。
この make test 用として Test::More モジュールが必要になります。
(テスト時のみ必要です。XML::TreePP モジュール利用時には必要ありません)

.pm ソースファイル単体で利用する場合

プロバイダのレンタルサーバなどで、サーバの管理者権限はないけど オリジナル CGI を設置できる環境などでは、 XML/Tree.pm ファイル のみをサーバにコピーするだけでも利用できます。

hogehoge.cgi
XML/TreePP.pm

CGI ファイル(↑ではhogehoge.cgi)と同じ階層に XML ディレクトリを作成して、 その中に TreePP.pm ファイルをアップロードしてください。
通常、TreePP.pm のパーミションはデフォルト(644)のままでOKです。
XML ディレクトリのパーミションはデフォルト(755)のままでも構いませんし、 少し厳しく(711)としてもOKです。

メソッド一覧・使い方

XML::TreePP モジュールを利用する際は、use してください。
エクスポートされる関数などはありません。

use XML::TreePP;

もしご不明な点がありましたら、 1行コメント欄 にお寄せ下さい。

new() コンストラクタ・set()・get() メソッド

new

$tpp = XML::TreePP->new()

new() コンストラクタは、XML::TreePP オブジェクトを返します。
以下のように引数でオプションを指定できます。

$tpp = XML::TreePP->new(
    output_encoding => "UTF-8",
    force_array => [ "rdf:li", "item", "-xmlns" ],
    first_out => [ "link", "title", "-type" ],
    last_out => [ "items", "item", "entry" ],
    cdata_scalar_ref => 1,
);

詳しいオプションについては、 こちら を参照してください。

set

$tpp->set( option_name => $value )

set() メソッドでもオプションを指定できます。

get

$tpp->get( 'option_name' )

get() メソッドは、現在のオプション指定値を返します。

parse() 系読み込み処理メソッド

parse() 系メソッドでは、 XML の各要素は、ハッシュ変数にその要素名をキーとして格納されます。
各属性は、属性名の先頭に - を付けたものをキーとして格納されます。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<children name="山田">
    <girl>花子</girl>
    <boy>太郎</boy>
    <boy>次郎</boy>
</children>

このXMLファイルは、下記のようなツリー構造のハッシュに格納されます。

{
    'children' => {
        '-name' => '山田'
        'girl' => '花子',
        'boy' => [
            '太郎',
            '次郎'
        ],
    }
};

ドキュメントルートの要素名 children から始まります。 XML::Simple モジュールの KeepRootと同様です。
入れ子構造のハッシュ変数を構成しています。
name="" 属性は、属性名 name の先頭に - がついて『-name』をキーとして ハッシュに格納されます。
girl 要素は、そのままハッシュに格納されます。
boy 要素は children 要素内に2回以上出現するため、 自動的に配列化されて、出現順に配列の値として格納されます。

parse

$tree = $tpp->parse( $source );

parse() メソッドは、XMLソースコード(文字列)の内容を ハッシュ変数にツリー構造で格納します。
引数にXMLソースコードを文字列で指定します。

parsefile

$tree = $tpp->parsefile( $file );

parsefile() メソッドは、XMLファイルの内容を ハッシュ変数にツリー構造で格納します。
引数にファイル名で指定します。

parsehttp

$tree = $tpp->parsehttp( $method, $url, $body, $head );

parsehttp() メソッドは、HTTP プロトコルでウェブサーバに接続して、 結果の XML コードの内容をハッシュ変数にツリー構造で格納します。
第1引数は、GET/POST などの HTTP メソッドを指定します。
第2引数は、アクセス先 URL を指定します。
第3引数は、POST メソッド時のリクエストボディを指定します。
第4引数は、HTTP リクエストヘッダのヘッダ名・値の組をハッシュ変数で指定します。
LWP::UserAgent モジュールまたは HTTP::Lite モジュールが必要です。

write() 系書き出し処理メソッド

write() 系メソッドは、parse() 系メソッドの逆方向の処理を行います。
すなわち、ツリー構造のハッシュ変数から XML に展開します。

write

$source = $tpp->write( $tree, $encode );

write() メソッドは、ツリー構造のハッシュ変数 $tree の内容を XML形式の文字列として返します。

writefile

$tpp->writefile( $file, $tree, $encode );

writefile() メソッドは、ツリー構造のハッシュ変数 $tree の内容を XMLファイル $file に書き出します。

parse 時に有効なオプション・プロパティ

XML 解析処理 parse 時に有効なオプション・プロパティは以下の通りです。

force_array

$tpp->set( force_array => [ "rdf:li", "item", "-xmlns" ] )

force_array プロパティは、 parse() 系メソッドで XML を読み込む際に 必ず配列化する要素を指定します。
デフォルトでは、要素が登場する回数に応じて自動的に配列化されます。
子要素として1回しか登場しない要素はハッシュの値として格納されますが、 2回以上登場する要素の値は配列として格納されます。
force_array プロパティを指定した場合、指定した要素は常に配列化されます。
force_array プロパティで指定しなかった要素は、通常通り自動判別されます。

$tpp->set( force_array => '*' )

force_array プロパティにワイルドカード '*' を指定した場合は、 全ての要素が配列化されます。 データは冗長になりますが、アプリ側の判定処理は簡単になります。

force_hash

$tpp->set( force_hash => [ 'item', 'image' ] )

parse 時のデフォルト動作では、 子要素または属性のある要素がテキストノードも持つ場合、 自動的にテキストノードは {'#text'} というハッシュキーに格納されます。 force_hash プロパティは、 常に子要素や属性のない要素でも常にテキストノードを {'#text'} に格納したい要素名を指定します。 テキストノードが存在するが、子要素や属性の存在が不安定な要素などで利用できます。 テキストノードのキー名 {'#text'} を変更する場合は、 text_node_key プロパティを参照して下さい。

$tpp->set( force_hash => '*' )

force_hash プロパティにワイルドカード '*' を指定した場合は、 全てのテキストノードがハッシュ化されます。 データは冗長になりますが、アプリ側の判定処理は簡単になります。

cdata_scalar_ref

$tpp->set( cdata_scalar_ref => 1 )

cdata_scalar_ref プロパティを真(1)にした場合は、 parse() 系メソッド使用時に XML コードの CDATA セクションの内容を スカラーへのリファレンスとして格納されます。 CDATA セクションの往復変換のためにも利用できます。
デフォルトでは、CDATA セクションの内容はテキストノードと同様に 通常のスカラーとして格納されます。

user_agent

  $tpp->set( user_agent => 'Mozilla/4.0 (compatible; ...)' );

parsehttp() メソッド利用時の User-Agent: ヘッダを指定します。
デフォルトでは、「XML-TreePP/#.##」形式で「#.##」にはバージョン番号が入ります。
LWP::UserAgent を利用している時は、さらに「libwww-perl/#.##」が続きます。
user_agent の末尾を半角空白で指定した場合は、 末尾にこれらのデフォルト形式が追加されます。

http_lite

  $http = HTTP::Lite->new();
  $tpp->set( http_lite => $http );

parsehttp() メソッドの通信で常に HTTP::Lite モジュールを利用します。

lwp_useragent

  $ua = LWP::UserAgent->new();
  $tpp->set( lwp_useragent => $ua );

parsehttp() メソッドの通信で常に LWP::UserAgent モジュールを利用します。
タイムアウトを指定したり、 LWP::UserAgent::WithCache モジュール も利用可能です。

base_class

  $tpp->set( base_class => 'MyElement' );

parse() 時に各ハッシュを 'MyElement' クラスからの子クラスとして bless します。例えば、
<one><two><three four="5">6</three></two></one>
という XML なら、$tree->{one}->{two}->{three} のハッシュは MyElement::one::two::three クラスになります。
bless されるのはハッシュ変数のみで、配列・文字列は bless されません。
アクセサ・メソッドは設定されません。 Class::Accessor モジュールなどを利用して、自前で設定して下さい。

elem_class

  $tpp->set( elem_class => 'MyElement' );

parse() 時に各ハッシュを 'MyElement' クラス直下の子クラスとして bless します。例えば、
<one><two><three four="5">6</three></two></one>
という XML なら、$tree->{one}->{two}->{three} のハッシュは MyElement::three クラスになります。

base_class プロパティが XML の階層構造をクラス名で再現するのに対し、 elem_class プロパティでは要素名自身を用いた並列的なクラス名となります。 用途に合わせて使い分け下さい。

write 時に有効なオプション・プロパティ

XML 出力処理 write 時に有効なオプション・プロパティは以下の通りです。

output_encoding

$tpp->set( output_encoding => "UTF-8" )

output_encoding プロパティは、 write() 系メソッドで XML を出力する際の 文字コードを指定できます。
Shift_JIS、EUC-JP などを指定できます。 (Perl 5.005/5.6.x では、Jcode.pm が必須)
Perl 5.8.x では標準の Encode.pm を利用するので、 それ以外の任意の文字コードを指定できます。
デフォルトの文字コードは、UTF-8 になります。
utf8 フラグを ON にする場合は、 utf8_flag オプション を指定して下さい。

first_out、last_out

$tpp->set( first_out => [ "link", "title", "-type" ] )
$tpp->set( last_out => [ "items", "item", "entry" ] )

first_out プロパティおよび last_out プロパティは、 write() 系メソッドで XML を出力する際の 要素・属性の並び順を指定します。
他の要素よりも優先して先に出力したい要素は、first_out プロパティで指定します。
他の要素よりも後・末尾に出力したい要素は、last_out プロパティで指定します。
指定しなかった要素は、アルファベット順で出力されます。
ただし、first_out/last_out プロパティで指定した要素については、 そのグループ内でアルファベット順で出力されます。 first_out/last_out プロパティで指定した順序のままではありません。
first_out/last_out プロパティで指定した要素については、 その指定順序のまま出力されます。(Version 0.20 以降)

xml_decl

  $tpp->set( xml_decl => '' );

write() 系メソッドで XML を出力する際の 冒頭の XML 宣言を指定します。
xml_decl の値が空文字列(undefではなく'')の場合は、XML 宣言を出力しません。
デフォルトでは、XML::TreePP が自動的に XML 宣言の行を生成します。

indent

  $tpp->set( indent => 2 );

write() 系メソッドで XML を出力する際の各行頭にスペースを挿入し、 読みすいようにインデントします。
数字は、インデントの桁数(スペース文字数)を指定します。(デフォルト:undef=インデントしない)

parse/write 時どちらも有効なオプション・プロパティ

parse 処理時・write 処理時どちらも有効なオプション・プロパティは以下の通りです。

utf8_flag

  $tpp->set( utf8_flag => 1 );

utf8_flag の値が真の場合、parse/write 結果文字列の utf8 フラグを ON にします。
デフォルトでは、utf8 フラグは操作しません。

attr_prefix

  $tpp->set( attr_prefix => '@' );

属性名の前に付加されるプリフィックスを指定します。
デフォルトでは、半角ハイフン『-』になります。
例えば、半角アットマーク『@』を指定した場合は ECMAScript for XML (E4X) 風味になります。

また、空文字列 '' を指定した場合は、parse() 時に接頭辞が付きません。(バージョン0.19以降)
ただし、この場合は write() 時に属性値の出力ができなくなるため、全て要素扱いになります。

text_node_key

  $tpp->set( text_node_key => '#text' );

属性付き要素のテキストノードのキー名を指定します。
デフォルトは『#text』です。

ignore_error

  $tpp->set( ignore_error => 1 );

XML フォーマットエラーや、ファイル入出力時のエラーを無視します。
ignore_error の値が偽の場合(デフォルト)では、エラー(Carp::croak)が発生します。

use_ixhash

  $tpp->set( use_ixhash => 1 );

parse() 系メソッドで XML を解析する際に、 Tie::IxHash モジュール により XML 中での要素名の出現順を保存します。
パースしたハッシュに対して keys すると、XML 中での出現順のままキーを取り出せます。
また、write() 系メソッドで XML を出力する際に、 各要素をアルファベット順でソートし直さずに、 keys で取得したそのままの順序で生成します。

この use_ixhash => 1 を利用すると、parse() したデータを write() で再出力する際に、 XML の要素名の出現順を保った往復変換を実現しやすくなります。
ただし、XHTML のように異なる要素が入り混じった XML は再現できません。
例: <a>1</a><b>2</b><a>3</a><b>4</b> という XML は個々の要素でなくて a→b の要素名の出現順のみ保存されるので <a>1</a><a>3</a><b>2</b><b>4</b> の順序で出力されます。
また、要素前後の空白文字は XML::TreePP 独自方式で正規化されるので、完全な往復変換ではありません。

なお、use_ixhash を利用すると parse() 系メソッドの XML 解析処理速度が半分になりますので、 ご注意ください(倍の処理時間がかかります)。 write() 系メソッドの XML 生成処理については、ほとんど影響ありません。

XML-RPC クライアントの実装方法(参考)

XML-RPC リクエストの処理では、 リクエスト用のツリーを作成して write() メソッドで生成した XML ソースを、 parsehttp() メソッドの第3引数でウェブサーバに渡すことで実現できます。

my $reqtree = {
  methodCall => {
    methodName => 'weblogUpdates.ping',
    params => {
      param => [
        { value => $WEBLOGNAME },
        { value => $WEBLOGURL },
      ]
    }
  }
};
my $xmlopt = { force_array => [qw( member )] };
my $treepp = XML::TreePP->new( %$xmlopt );
my $reqxml = $treepp->write( $reqtree );
my $restree = $treepp->parsehttp( POST => $PINGURL, $reqxml );

上の例では、ブログの更新ピングを送信しています。

parsehttp() メソッドの戻り値もハッシュ変数になっているので、 XML ファイルを全く操作せずに、XML-RPC クライアントを実現できます。

my $reshash = &parse_xmlrpc_response( $restree );
foreach my $key ( keys %$reshash ) {
    print $key, "=", $reshash->{$key}, "\n";
}

sub parse_xmlrpc_response {
    my $tree = shift;
    return unless ref $tree;
    return unless ref $tree->{methodResponse};
    return unless ref $tree->{methodResponse}->{params};
    return unless ref $tree->{methodResponse}->{params}->{param};
    return unless ref $tree->{methodResponse}->{params}->{param}->{value};
    return unless ref $tree->{methodResponse}->{params}->{param}->{value}->{struct};
    my $array = $tree->{methodResponse}->{params}->{param}->{value}->{struct}->{member};
    return unless ref $array;
    my $hash = {};
    foreach my $member ( @$array ) {
        my $val = $member->{value};
        if ( ref $val ) {
            my $type = ( keys %$val )[0];
            $val = $val->{$type};
        }
        $hash->{$member->{name}} = $val;
    }
    $hash;
}

上記の parse_xmlrpc_response() 関数を使えば、 XML-RPC のレスポンス内容の深いツリー構造のハッシュ変数を、 さらに簡単な構造のハッシュに変換できます。

類似モジュールとの処理速度比較

XML ファイルをハッシュに変換するためのモジュールは、いくつか公開されています。
大小 2 つの XML ファイルをハッシュ変数に変換する処理の時間を計測しました。
Pure Perl 実装であるため、やはり大きなファイルでは時間がかかってしまいますが、
RSS/RDF 程度のファイルについては遜色ない実用的な処理速度が実現できています。

モジュール名XML 30KBXML 200KBXML 30KB×10回 Pure Perl
XML::Parser::Lite::Tree0.08 秒0.36 秒0.40 秒 ○ 要SOAP::Lite
XML::Parser::EasyTree0.14 秒0.49 秒0.53 秒 × 要XML::Parser
XML::TreePP0.09 秒0.52 秒0.54 秒 ◎ 単体のみでOK
XML::Simple0.32 秒1.04 秒0.99 秒 × 要XML::Parser
XML::Mini7.17 秒N/A71.08 秒 ◎ 単体のみでOK

結論としては、XML::TreePP は高速で、かつ展開されたオブジェクト構造も分かりやすいため、オススメできます。

このモジュールの応用例

更新履歴

コメントはこちらへ by AjaxCom

その他のページへのリンク

Kawa.netxp © Copyright 2006-2010 Yusuke Kawasaki