データが古すぎてほとんど意味の無いものになっていたので, 最新の環境でデータをとり直しました。
以前のデータはこちら。
Python, Perl, Ruby にそれぞれ James Clark 氏の XML Parser "expat" を呼び出すモジュールが用意されています。
以下のサイトで入手可能です。Python 用モジュールは Python-2.1 には標準で含まれています。 Perl 用モジュールは CPAN に登録されています。
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 メソッドの戻値で判断します。
import pyexpat が失敗する場合は,
from xml.parsers import pyexpat
としてみてください。
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-20001006.xml,201,918 バイト) と Jon Bosak 氏が XML 化した旧約聖書 (ot.xml,3,487,157 バイト) を使って実行したときにかかった大まかな時間です。 マシンは Pentium III 600MHz,メモリ 320Mバイトで,OS は Linux 2.4.4 です。
以前よりマシン性能が大幅に向上してしまっているので, 10 回繰り返した結果です。 非常に大雑把な数字なので参考程度にとどめて下さい。
| プログラム | REC-xml-20001006.xml | ot.xml | 備考 | ||||
|---|---|---|---|---|---|---|---|
| user [s] | system [s] | 実時間 [s] | user [s] | system [s] | 実時間 [s] | ||
| Python サンプル | 1.03 | 0.00 | 0.995 | 7.38 | 0.42 | 7.781 | Python-2.1.1 + expat-1.95.1 |
| Perl サンプル | 0.76 | 0.03 | 0.756 | 4.95 | 0.09 | 5.023 | Perl-5.6.1 + XML::Parser-2.30 |
| Ruby サンプル 1 | 0.86 | 0.05 | 0.872 | 8.33 | 0.43 | 8.736 | Ruby-1.6.4 + xmlparser-0.6.2 + expat-1.95.1 |
| Ruby サンプル 2 | 1.84 | 0.02 | 1.832 | 16.10 | 0.40 | 16.490 | |
以前に比べると Perl がかなりよい結果になっています。 もはや Ruby が最速ではなくなりました。
実は Ruby-1.7 を使うと Ruby が最速になります。 Ruby だけ開発版を使うのは反則なので, 今回は各処理系の現時点での最新安定版を使って比較しました。