Python, Perl, Ruby を使った XML プログラミング (改訂版)

データが古すぎてほとんど意味の無いものになっていたので, 最新の環境でデータをとり直しました。

以前のデータはこちら


[Ruby]

XML パーサについて

Python, Perl, Ruby にそれぞれ James Clark 氏の XML Parser "expat" を呼び出すモジュールが用意されています。

以下のサイトで入手可能です。Python 用モジュールは Python-2.1 には標準で含まれています。 Perl 用モジュールは CPAN に登録されています。

Expat
http://sourceforge.net/projects/expat/
Python
http://sourceforge.net/projects/pyxml/
Perl
http://www.netheaven.com/~coopercc/xmlparser/intro.html
Ruby
http://www.yoshidam.net/Ruby_ja.html#xmlparser

サンプルプログラム

XML ファイル内のエレメントの名前と出現回数を調べるプログラムを書 いてみてみました。

Python サンプル

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 としてみてください。

Perl サンプル

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 が呼ばれます。

Ruby サンプル 1

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 例外で報告されます。

Ruby サンプル 2

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 だけ開発版を使うのは反則なので, 今回は各処理系の現時点での最新安定版を使って比較しました。


Last modified: Fri Jan 28 22:14:30 JST 2011