XML とスクリプト言語の間には、XML の登場以来きわめて自然な関係が存在しています。XML の設計グループの当初の目標の 1 つは、1 人の Perl マニアが XML パーサーを 2 週間で仕上げられるようにすることでした。XML の処理や操作は、XML でスクリプト処理を行うための肥沃な土壌にもなってきました。XML は人間にとっても読みやすく、基本的にテキスト・ベースの設計になっているからです。歴史的に見ても、テキストの処理は、スクリプト言語の得意分野でした。柔軟性とパワーを備えたスクリプト処理は、XML の記述能力にとって、まさに完璧な補完機能となるのです。
XML は元来、情報の表現、保管、交換のための媒体です。これにスクリプト処理を追加すれば、その情報がアクティブになります。つまり、アクションに影響を与えたり、変換処理の対象になったり、既存システムと接続したりといった具合に、情報を表現することにとどまらず、実際にアクションを起こすことになるのです。
本稿では、Perl を使って XML を処理したり変換したりします。もちろん、ここで紹介するテクニックは、Tcl や Python といった他のスクリプト言語でも活用できます。まず、XML を HTML に変換するためのテクニックを示してから、簡単な株取引用アプリケーションの話に進みましょう。このアプリケーションでは、Perl と XML とデータベースの併用により、株取引のルールを評価します。
本稿の目的に合わせて、入力ファイルとしては、XML で表現した株価情報を使いましょう。
<stock_quote>
<symbol>IBM</symbol>
<when>
<date>12/16/1999</date>
<time>4:40PM</time>
</when>
<price type="ask" value="109.1875"/>
<price type="open" value="108"/>
<price type="dayhigh" value="109.6875"/>
<price type="daylow" value="105.75"/>
<change>+2.1875</change>
<volume>7050200</volume>
</stock_quote>
|
このシンプルなコードは、株価に関する典型的な情報を取り込んだものです。ここには、属性や空のタグといった XML の特色が見受けられます。本稿で使用する 実際の XML ファイル には、いくつかのstock_quote 要素が含まれており、これによって持ち株のポートフォリオ (明細一覧表) が表現されています。
この XML ファイルは、finance.yahoo.com の Web サイトにあるスプレッドシート形式の株価情報をスクリプトで XML に変換したものです。変換のために使用したスクリプトとライブの XML 株価サーバーはいずれも、XML Today という Web サイト (「参考文献」 を参照) にあります。
XML ソースを HTML に変換するための簡単な方法は、それぞれの XML タグを HTML の各部分に置換するための定義を示すということです。Perl 用のポピュラーな XML::Parser モジュール (James Clark 氏の Expat パーサーをベースにしたモジュール:「参考文献」 を参照) を活用すれば、XML 文書を解析して、置換を実行するためのコールバック・ルーチンを定義することができます。
以下は、XML::Parser の簡単な呼び出しです。
use XML::Parser;
my $parser = new XML::Parser(ErrorContext => 2);
$parser->setHandlers(Start => \&start_handler,
End => \&end_handler,
Char => \&char_handler);
$parser->parsefile($file);
|
このパーサーで指定のファイルを解析すると、タグの開始地点でstart_handler 関数、タグの終了地点でend_handler 関数がそれぞれ呼び出されます。タグの内容を処理するのは、char_handler 関数です。
これらのコールバック関数を前提として、シンプルな置換アルゴリズムをインプリメントしてみましょう。まず、いくつかの置換内容を定義します。
%startsub = (
"stock_quote" => "<hr><p>",
"symbol" => "<h2>",
"price" => "<br><b>Price:</b>"
);
%endsub = (
"stock_quote" => ",
"symbol" => "</h2>",
"price" => "
);
|
に、上記の置換内容を実行するためのハンドラーを作成します。
sub start_handler
{
my $expat = shift; my $element = shift;
# element is the name of the tag
print $startsub{$element};
}
sub char_handler
{
my ($p, $data) = @_;
print $data;
}
|
start_handler 関数は、指定のタグの代わりに使用する値を出力します。char_handler は、受け取ったデータ、つまりタグの内容を出力します。(属性を処理するための幾つかの追加を含んだ完全なプログラム は、別にリストされています。) このプログラムを上記の XML ファイル で実行すると、以下の出力が得られます。
<hr><p>
<h2>IBM</h2>
<br><b>Date:</b><i>12/16/1999</i>
<br><b>Time:</b><i>4:40PM</i>
<br><b>Price:</b>type=ask value=109.1875
<br><b>Price:</b>type=open value=108
<br><b>Price:</b>type=dayhigh value=109.6875
<br><b>Price:</b>type=daylow value=105.75
<br><b>Change:</b>+2.1875
<br><b>Volume:</b>7050200 |
完全な出力 も用意されています。この方法の場合では、置換定義を行うことによって XML から HTML への簡単な変換が行えます。
置換ベースの変換はたいへん分かりやすく、インプリメントするのも簡単ですが、ロジックをインプリメントできないという問題があります。タグの内容や属性に合わせてそれぞれ違った処理をしたり、データベースに接続してタグの内容と保管済みの値を比較したりすることができれば、たいへん便利でしょう。そのためには、1 対 1 の単純な置換以上のもの、つまりそれぞれのタグに合わせて関数を実行するための機能が必要です。
XML::Parser には、XML 文書のそれぞれのタグに合わせて関数を呼び出すための方法が用意されています。それぞれのタグごとに、構文解析モジュールがタグ名の付いた関数を呼び出すのです。したがって、変換を実行するための関数セットを定義したり、データベースに接続したり、ビジネス・ロジックをインプリメントしたりすることが可能になっています。
タグ名に基づく関数コールバックを実行するには、"Subs" スタイルでこのパーサーを呼び出す必要があります。さらに、関数コールバックが入っている名前空間を指定しなければなりません。この指定は、"Pkg" オプションで行います。
my $parser = new XML::Parser(Style=>'Subs', Pkg=>'SubHandlers', ErrorContext => 2);
$parser->setHandlers(Char => \&char_handler);
|
これによって、XML ファイルのタグと内容に基づく一連の関数コールバックが実現します。まず、タグの開始地点では、そのタグと同じ名前の付いた関数がSubHandlers 名前空間から呼び出されます。次に、タグの内容がchar_handler 関数によって処理され、タグの終了地点になると、そのタグと同じ名前の付いた関数が "_" の付いた形で呼び出されます。たとえば、symbol というタグの終了地点では、SubHandlers::symbol_() 関数が呼び出されるわけです。
上記の XML ファイル の場合は、関数呼び出しが以下のような順序で実行されます。
SubHandlers::stock_quotes();
SubHandlers::stock_quote();
SubHandlers::symbol();
char_handler();
SubHandlers::symbol_();
SubHandlers::when();
.... |
もちろん、変換用の関数を作成するのは簡単なことですが、単純な置換ももちろん実行できます。
sub symbol {
print "<img src=images/";
}
sub symbol_ {
print ".gif>\n";
}
|
ここでは、symbol タグの内容として入っている銘柄のシンボルを使って、結果として生成される HTML ページに、同じ名前のイメージを挿入しています。もちろん、さらに複雑なロジックをインプリメントすることも可能です。
sub price {
my $expat = shift; my $element = shift;
# Read the attributes
while (@_) {
my $att = shift;
my $val = shift;
$attr{$att} = $val;
}
my $type = $attr{'type'};
my $price = $attr{'value'};
if ($type eq 'ask') {
$label="Ask Price";
} elsif ($type eq 'open') {
$label="Opening Price";
}
print "<td align="left" >\n<b>$label</b></td>\n";
print "<td align=right>$price</td>\n";
}
|
この関数に最初に渡されるパラメーターは、パーサーそのものへのハンドルです。その後にタグの名前 (element) が続き、オプションとしてタグの属性が attribute_name と attribute_value の組み合わせという形で続くこともあります。上記のコードでは、price タグのtype 属性に合わせてそれぞれ違ったラベルが出力されることになります。
完全なプログラム が別のリストとして用意されています。このプログラムを上記の XML ファイル で実行すると、さらに魅力的な出力 が得られます。以下は、1 つのサンプルです。
<table width="100%">
<tr>
<td>
<img src=images/IBM.gif><br><br> 12/16/1999 4:40PM
</td>
<td>
<table width="100%">
<tr>
<td align="left" ><b>Ask Price</b></td>
<td align=right>109.1875</td>
<td align="left" ><b>Opening Price</b></td>
<td align=right>108</td>
</tr>
<tr>
<td align="left" ><b>Today's High</b></td>
<td align=right>109.6875</td>
</tr>
</table>
</td>
</tr>
</table>
|
これまで説明してきた方法は、XML 文書をストリームとして処理するという考え方に基づいていました。つまり、ファイルの構文解析という流れの中で、タグが出てくるたびにハンドラーを呼び出すという方法です。確かに、この方法による XML の処理は、メモリーの使用量という観点からも、処理時間という観点からも、効率的ではあります。しかしながら、この方法では実現しにくい作業もあります。たとえば、文書内の一部のセグメントを動かしたり、並べ替えたり、項目をソートしたりすることが必要な場合はどうでしょうか。文書をストリームとして受け取るのであれば、項目をソートしたり並べ替えたりする前に、いったん保管しておくことが必要になるでしょう。自動的に項目を保管するためのメカニズムがあれば、そのような作業が非常に簡単になります。
XML 文書は、開始タグと終了タグの対応が、入れ子構造も含めきちんと取られている ことが要件になっているため、ツリーとして保管しやすくなっています。それで、XML 文書を処理するためのポピュラーなテクニックの 1 つとして、まず文書をツリー形式のデータ構造に解析してから、そのツリーを操作するというものがあります。DOM (Document Object Model) や Grove や Twig (「参考文献」 を参照) などは、このモデルを利用しています。そのようにするならば、文書処理の柔軟性が大いに高まり、文書内の項目に対するランダム・アクセス、項目の並べ替え、追加、削除などが可能になるのです。
とはいえ、ツリー・ベースの方法にもいくらかの欠点があります。実際に処理を行って、ビジネスのロジックを活用するには、XML 文書全体を構文解析することやツリー形式のデータ構造を作成することが必要になります。ツリー・データ構造はメモリーに保管されるのが普通なので、ストリーム・ベースの方法に比べてはるかに多くのメモリーを消費します。しかも、さらに悪いことに、文書をツリーとしてメモリーに保管する場合は、元の XML 文書の場合よりも何倍も多くの記憶域を占有してしまいます。大きな文書であれば、この両方の欠点がかなりの影響を及ぼし、構文解析とツリー作成にかかる時間が大幅に増えて、メモリー要件が使用可能なリソースの許容範囲を超えてしまうかもしれません。
XML 文書のツリー・ベースの処理については、今後の記事で取り上げることにして、本稿の後半では、前述のストリーム・ベースの処理を活用してみることにしましょう。
XML 文書を表示用に変換することは、XML 処理の最初の作業として典型的なものであり、いろいろな仕組みを知るための手頃な手段でもあります。しかし、XML の本当の威力は、情報を伝達する能力だけではなく、むしろその伝達された情報に基づいてアクションを起こす能力にあります。ここでは、その種のアクティブな文書を使って、簡単な株取引用のルールをインプリメントするためのサンプル・アプリケーションを取り上げましょう。
基本的なシナリオは、次のようになります。つまり、株価提供サービスのようなものが、あらかじめ選んでおいた銘柄の最新の株価と出来高を示した XML 文書を周期的に送信します。これは、本稿で使っているような形式の XML ファイル です。そしてこのアプリケーションが、データベースに保管してある一連のルールと実際の株価に基づいて、売りか買いかの判断を下すというわけです。
この簡単なアプリケーションの場合は、売り呼値と出来高を基準として、売りか買いかの判断だけをします。売り呼値と出来高は XML ファイルから、ルールは MySQL データベース (「参考文献」 を参照) から取り込みます。ルールを調べた上で、売りか買いのいずれかのアクションが必要であれば、それに対応するコマンドを発行するのです。
表示を主な目的とした以前のアプリケーションでは、タグの内容を出力するだけで事が足りました。しかし、今回の株価アプリケーションの場合は、売り/買いの基準と比較するために、特定のタグ (株価や出来高など) の内容にアクセスし、それを保管しなければなりません。
ストリーム・ベースの処理モデルを使ってタグの内容を保管する場合は、まず、タグの開始地点に来た時に、内容を保管するための場所を用意してから、char_handler 関数によってタグの内容を実際に保管するという手順になります。文書はストリームとして処理されるので、最初に必ず開始タグが出てきます。したがって、その時点で、内容を保管するためのステージを設定できるわけです。次に、タグの内容が出てくるので、用意してある場所にその内容を保管します。最後に、終了タグが出てきたら、保管場所に関して、必要な終結処置とクローズ処理を行うことになるでしょう。
保管場所は、タグの開始関数でセットアップされ、タグの終了関数でクローズされます。
sub volume {
$::state::store_contents = "volume";
}
sub volume_ {
undef $::state::store_contents;
}
|
ここでは、volume がstore_contents 変数を定義することによって、タグの内容を保管する場所を設定します。次にvolume_ がstore_contents の定義を解除して、他のタグの内容が同じ場所に保管されることがないようにします。
内容を保管するためには、char_handler を次のように修正しなければなりません。
sub char_handler {
my ($p, $data) = @_;
if ($::state::store_contents) {
$::state::storage{$::state::store_contents} .= $data;
}
}
|
この関数は、store_contents が定義されているかどうかを調べ、もし定義されていれば、保管ハッシュにデータを保管します。さらに、::state 名前空間によって、保管と状態の変数がパーサーとハンドラーの名前空間から分離されます。
このテクニックを活用すれば、どれでも好きなタグの内容を保管することができます。株価タグの場合であれば、問題となっている株価の値がそのタグの属性として表現されます。つまり、以下のようにして、タグが出てきた時点で、その内容を保管してゆくというわけです。
sub price {
my $expat = shift; my $element = shift;
# Read the attributes
while (@_) {
my $att = shift;
my $val = shift;
$attr{$att} = $val;
}
if ($attr{'type'} eq "ask") {
$::state::storage{'price'} = $attr{'value'};
}
}
|
売り/買いに関するルールは以下のテーブルに保管されています。
CREATE TABLE rules (
symbol CHAR(5),
field CHAR(8),
value CHAR(16),
action CHAR(5)
); |
この中で、symbol は銘柄のシンボルです。field では、基準の中のどのフィールドを使うかを指定します (この場合は、株価か出来高のいずれか)。value はアクションを起こすフィールドの値です。action では、アクションのタイプを記述します (この場合は、売りか買いのいずれか)。
たとえば、ルール・テーブルに以下の行があるとします。
INSERT INTO rules VALUES ("IBM", "price", "120.0", "buy"); |
この行は、IBM 株の価格が 120 を超えたら買い注文を出す、という意味です。次の行はどうでしょうか。
INSERT INTO rules VALUES ("MSFT", "volume", "65000000", "sell"); |
この行は、Microsoft 株の出来高が 65000000 を超えたら売り注文を出す、という意味になります。
データベースからこれらのルールを取り出すのは、Perl DBI/DBD 拡張機能によって簡単に行えます。データベースへの接続は、処理の開始時に作成し、処理が終了するまでオープンしておけばよいでしょう。それぞれの株に該当するルールを取り出すには、銘柄のシンボルに基づいてルール・テーブルの中から行を選べばよいのです。
stock_quotes タグは、最外部タグです。最外部タグというのはつまり、このタグの開始によって最初のハンドラー・コールバックが起動され、このタグの終了によって最後のハンドラー・コールバックが起動されるという意味です。したがって、このタグこそが、データベースへの接続を確立してクローズするための最適な場所を提供していると言えるでしょう。
sub stock_quotes {
use DBI;
$dsn = "DBI:mysql:database=test;";
$::state::dbh = DBI->connect($dsn);
}
sub stock_quotes_ {
$::state::dbh->disconnect();
}
|
ルールの取り出しは、銘柄のシンボルに基づいて選択的に行われます。
my $sth = $::state::dbh->prepare("select * from rules where symbol='$symbol'");
$sth->execute();
while (my $ref = $sth->fetchrow_hashref()) {
# Act on the retrieved rules
}
$sth->finish();
|
それぞれの株価の情報は stock_quote タグに入っています。stock_quote タグの終了地点では、すでに必要な情報 (銘柄のシンボル、株価、出来高) がすべて保管されているはずです。したがって、ルールに基づいてアクションを起こすのは、stock_quote_ 関数の中ということになります。
sub stock_quote_ {
my $symbol = $::state::storage{'symbol'};
# Grab the rules for the given stock from the rules table
my $sth = $::state::dbh->prepare("select * from rules where symbol='$symbol'");
$sth->execute();
while (my $ref = $sth->fetchrow_hashref()) {
my $field = $ref->{'field'};
my $value = $ref->{'value'};
if ($::state::storage{$field} > $::state::storage{$value}) {
# This rule applies
print "Rule \"$field > $value\" applies for $symbol\n";
take_action($symbol, $ref->{'action'});
}
}
$sth->finish();
}
|
指定の銘柄に該当するルールが取り出されると、比較処理が実行されます。そのルールが当てはまっている場合は、take_action 関数が呼び出されます (この場合は、単なる終了アクション)。
完全なプログラム は、別のリストとして用意されています。また、ルール・テーブルを作成するためのスキーマ もあります。元の XML ファイル でこのプログラムを実行すると、以下のような出力が得られます。
Rule "price > 120.0" applies for IBM
Taking action "buy" on stock "IBM" .
Rule "volume > 65000000" applies for MSFT
Taking action "sell" on stock "MSFT" .
|
本稿で説明したようなテクニックをもっと大きなプロジェクトに応用し、高速で柔軟性の高い XML ベースのシステムを構築することも可能でしょう。スクリプト言語を変換言語やコマンド言語として活用したソリューションと、XML 文書の構文解析を実行するハイパフォーマンスな C/C++ ベースのパーサーは、まさに最高の組み合わせと言えます。この方法によって、低レベル言語のスピードと、スクリプト処理の簡便さの両方を手に入れることができるのです。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| XML file | stocks.xml.txt | 1.95KB | HTTP |
| Simple substitution | simplesub.pl.txt | 1.44KB | HTTP |
| Function-based substitution | procsub.pl.txt | 1.78KNB | HTTP |
| Take action (buy/sell) | active.pl.txt | 2.31KB | HTTP |
| Schema for rules table | stocks.sql.txt | 276Byte | HTTP |
ダウンロード形式について Adobe® Reader® が必要
- finance.yahoo.com では、スプレッドシート形式の株価情報が得られます。
- 変換のために使用したスクリプトとライブの XML 株価サーバー はいずれも、XML Today という Web サイトにあります。
- Expat は C で作成された完全準拠の非検証 XML パーサーです。
- XML::Twig は XML 文書用のツリー・インターフェースであり、巨大な文書の部分ごとの処理が可能です。
- XML::DOM は XML::Parser 用の Perl 拡張機能であり、DOM レベル 1 に準拠したインターフェースによってオブジェクト指向のデータ構造を構築できます。
- XML::Grove は、Perl ハッシュのツリーを使って、XML、HTML、SGML の構文解析済みインスタンスの情報セットに対して、シンプルなアクセスを提供します。
- DOM (Document Object Model) は、HTML 文書と XML 文書を表すための標準的なオブジェクト・セットと、それらの文書にアクセスしたり、処理を行ったりするための標準的なインターフェースを提供します。
- MySQL Database は、UNIX ベースの大半のシステム、Windows、OS/2 をはじめとするほとんどのオペレーティング・システムに対応するフリーの SQL データベースです。
- High Performance Web Applications using Perl, XML, and Databases
では、Perl、XML、データベースの併用により、ハイパフォーマンスな Web アプリケーションを構築するためのテクニックや問題点を解説しています。

Binary Evolution Inc. の社長であり共同設立者でもある Parand Tony Darugar 氏は、1992 以来、インターネット・ベースのベンチャー・ビジネスにかかわってきました。興味の対象としては、インターネット商取引、データベースやレガシー・システムからの Web 接続、XML、人工知能などがあります。連絡先はtdarugar@binev.com です。