スクリプト言語 Python, Perl, Ruby を使って XML を処理するプログラムを書いてみて, それぞれの言語や XML パーサの機能を比較してみました。
それぞれ,強力な文字列処理機能などを持っていて, XML 処理を行なうのに問題ありません。 どの言語を選択するかは完全に趣味の問題と言えるでしょう (個人的には Ruby をお勧めします)。
また,パーサの機能も James Clark 氏の expat を組み込んでいるので 大差ありません (Perl だけは拡張された expat を使っているようです)。
Python, Perl, Ruby にそれぞれ James Clark 氏の XML Parser "expat" を呼び出すモジュールが用意されています。
以下のサイトで入手可能です。Python 用モジュールは既に Python-1.5.1 に取り込まれているようです。 Perl 用モジュールはそろそろ CPAN に登録されるでしょう。 Ruby 用モジュールは Debian-JP に登録されているらしいです。
XML ファイル内のエレメントの名前と出現回数を調べるプログラムを書 いてみてみました。
Python プログラミングは一夜漬けなのであまり Python らしくないかも 知れません。
import sys
import pyexpat
class XMLHandler:
def __init__(self):
self.elemNames = {}
def startElement(self, name, attr):
if self.elemNames.has_key(name):
self.elemNames[name] = self.elemNames[name] + 1
else:
self.elemNames[name] = 1
handler = XMLHandler()
p = pyexpat.ParserCreate()
p.StartElementHandler = handler.startElement
if p.Parse(open(sys.argv[1]).read()) == 0:
print "Error:", pyexpat.ErrorString(p.ErrorCode),
"in line", p.ErrorLineNumber
## print result
print handler.elemNames
Python の場合,pyexpat クラスの ParserCreate メソッドでパーサオブ ジェクトを作成し,StartElementHandler のようなインスタンス変数に イベントハンドラを関数オブジェクトとして登録します。 その後,Parse メソッドでパースを開始します。 パースエラーは Parse メソッドの戻値で判断します。
use XML::Parser;
sub initHandler ($) {
my $self = shift;
$self->{'elemNames'} = {};
}
sub elemStartHandler ($$) {
my $self = shift;
my $name = shift;
$self->{'elemNames'}->{$name}++;
}
## print result
sub finalHandler ($) {
my $self = shift;
while (my ($key, $value) = each % {$self->{'elemNames'}}) {
print "$key=>$value ";
}
print "\n";
}
my $parser = new XML::Parser(ErrorContext=>3);
$parser->setHandlers(Init=>\&initHandler,
Start=>\&elemStartHandler,
Final=>\&finalHandler);
$parser->parsefile($ARGV[0]);
Perl の場合,まず XML::Parser クラスのパーサオブジェクトを作成します。 次に setHandlers メソッドでイベントハンドラを関数のリファレンスと して登録します (コンストラクタでも設定できます)。 その後,parsefile メソッドでパースを開始します。 パースエラーは die が呼ばれます。
require 'xmlparser'
class NewParser < XMLParser
def initialize
@elemNames = {}
end
def startElement(name, attrs)
if @elemNames[name].nil?
@elemNames[name] = 1
else
@elemNames[name] += 1
end
end
def elemNames
@elemNames
end
end
parser = NewParser.new
begin
parser.parse($<.read)
rescue XMLParserError
print "Error: #{$!} in line #{parser.line}\n"
end
## print result
p parser.elemNames
Ruby の場合は,イベントハンドラを定義する方法と, イテレータとして使う 2 つの方法を用意しています。 こちらはイベントハンドラを定義する方法です。 まず,XMLParser クラスを継承して, startElement などのイベントハンドラを再定義します。 次に,そのクラスのオブジェクトを生成し, parse メソッドでパースを開始します。 パースエラーは XMLParserError 例外で報告されます。
require 'xmlparser'
parser = XMLParser.new
elemNames = {}
begin
parser.parse($<.read) do |type, name, data|
case (type)
when XMLParser::START_ELEM
if elemNames[name].nil?
elemNames[name] = 1
else
elemNames[name] += 1
end
end
end
## print result
p elemNames
rescue XMLParserError
print "Error: #{$!} in line #{parser.line}\n"
end
こちらはイテレータとして使う方法です。 まず,XMLParser クラスのオブジェクトを生成し, parse メソッドをイテレータとして呼び出し, イベントを処理します。 パースを単なるループのように処理できるので, イベント駆動プログラムが苦手な人にも理解しやすいでしょう。 パースエラーは XMLParserError 例外で報告されます。
XML 仕様書 (REC-xml-19980210.xml,159,357 バイト) と Jon Bosak 氏が XML 化した旧約聖書 (ot.xml,3,438,602 バイト) を使って実行したときにかかった大まかな時間です。 マシンは Pentium 133MHz,メモリ 72Mバイトで,OS は Linux 2.0.35 + libc5 です。
非常に大雑把な数字なので参考程度にとどめて下さい。
| プログラム | REC-xml-19980210.xml | ot.xml | 備考 | ||||
|---|---|---|---|---|---|---|---|
| user [s] | system [s] | 実時間 [s] | user [s] | system [s] | 実時間 [s] | ||
| Python サンプル | 0.82 | 0.03 | 0.923 | 7.41 | 0.32 | 8.450 | Python-1.4 + pyexpat-1.1 |
| Perl サンプル | 1.56 | 0.08 | 1.648 | 8.72 | 0.21 | 9.174 | Perl-5.005_02 + XML::Parser-2.16 |
| Ruby サンプル 1 | 0.68 | 0.05 | 0.798 | 5.21 | 0.33 | 6.008 | Ruby-1.1c6 + XMLParser-0.4.14 |
| Ruby サンプル 2 | 1.79 | 0.05 | 1.934 | 14.02 | 0.21 | 14.838 | |
Ruby サンプル 2 を除くと,どれもそれほど速度に差はありませんが, Ruby サンプル 1 が少し速いです。 Ruby サンプル 2 は START_ELEM 以外のイベントもイテレータが発生させて いるので,かなり遅くなります。
Python の XML パッケージには XML パーサのほか,SAX や DOM モジュールも含まれています。
Perl にも XML 関係のモジュールが次第に揃って来つつあるようです。
Ruby も DOM や XPointer などが一応用意されています。