Python, Perl, Ruby を使った XML プログラミング

スクリプト言語 Python, Perl, Ruby を使って XML を処理するプログラムを書いてみて, それぞれの言語や XML パーサの機能を比較してみました。

それぞれ,強力な文字列処理機能などを持っていて, XML 処理を行なうのに問題ありません。 どの言語を選択するかは完全に趣味の問題と言えるでしょう (個人的には Ruby をお勧めします)。

また,パーサの機能も James Clark 氏の expat を組み込んでいるので 大差ありません (Perl だけは拡張された expat を使っているようです)。


[Ruby]

XML パーサについて

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

以下のサイトで入手可能です。Python 用モジュールは既に Python-1.5.1 に取り込まれているようです。 Perl 用モジュールはそろそろ CPAN に登録されるでしょう。 Ruby 用モジュールは Debian-JP に登録されているらしいです。

Python
http://www.python.org/sigs/xml-sig/status.html
Perl
http://www.netheaven.com/~coopercc/xmlparser/intro.html
Ruby
http://www.bekkoame.ne.jp/~yoshidam/Ruby_ja.html

サンプルプログラム

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 メソッドの戻値で判断します。

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-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 などが一応用意されています。


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