┌─────────┐
│PerlでXMLを使う  │
│(2)XMLパーサの基本│
│2005/03/21    │
└─────────┘


●XMLパーサの基本

 ◆パーサとは
 
  ・これからXMLデータをPerlで扱うわけですが、「XMLデータをPerlプログラムで読
   み書きする」とは何を意味しているでしょうか。
  
  ・例えば前回[(1)まずは使ってみる]使用したXMLデータをもう一度見てみましょう。
  
   [リスト1. XMLデータ例]
   ┌───────────────────────────────────┐
    <NameList>
     <Member>
      <Mail>itiro@hoge.co.jp</Mail>
      <Name>Ichiro Suzuki</Name>
      <Tel>090-1111-1111</Tel>
     </Member>
     <Member>
      <Mail>jiro@hoge.co.jp</Mail>
      <Name>Jiro Sato</Name>
      <Tel>090-2222-2222</Tel>
     </Member>
     <Member>
      <Mail>sabro@hoge.co.jp</Mail>
      <Name>Sabro Yamada</Name>
      <Tel>090-3333-3333</Tel>
     </Member>
    </NameList>
   └───────────────────────────────────┘

  ・このデータをXML::Simpleパッケージが持つ「XMLin()」関数で読み込みました。
   戻り値は構造化されたハッシュデータのリファレンスでした。つまり上記のデー
   タを文法的に解釈(parse)し、結果をPerlのデータ構造に変換したのです。
  
  ・このように、何らかのデータをプログラムで利用するために、対象データを文法
   的/構造的に解釈するプログラムをパーサ(parser)と呼びます。

 ◆データを渡す方法
 
  ・一般にパーサがプログラムにデータを渡す方法は、大きく分けて2種類あります。
    (1)イベント方式
    (2)オブジェクト方式
  
  ・イベント方式とは、「ストリーム」としてデータを読み込んでいる最中、ある構
   造/形式毎にイベントを駆動する方式です。
  
  ・XMLパーサにおけるイベントは
     + 要素開始タグに出会った
     + 要素終了タグに出会った
     + 通常の文字列データ
   等があります。これらのイベントを検知するたびに、それぞれのイベントを処理
   する関数(ハンドラ)がパーサから呼び出されます。
  
  ・オブジェクト方式の場合、最初にXMLデータ全体を読み込み、構造化されたデー
   タとしてプログラムに渡します。
  
  ・渡されたデータ構造体は永続的なオブジェクトとして扱うことができます。前回
   紹介した XML::Simple の XMLin() はオブジェクト方式に相当します。

 ◆イベント方式とオブジェクト方式の比較
 
  ・どちらのタイプのパーサを使用すべきかについては、扱うXMLデータの構造や大
   きさによって適宜選ぶことになります。

  ・データが比較的小さいのであれば、オブジェクト方式を採用する方が便利です。
   繰り返し参照や、最初のデータを見ながらの処理といった融通が利きます。
  
  ・しかしデータが大きい場合、オブジェクト方式ではメモリ資源を大量に消費し、
   動作速度も目に見えて落ちてきます。このような場合にはイベント方式で、必要
   なデータだけをスタックしながら処理する方が効率的です。
  
  ・またデータの前後関係が重要な場合にもイベント方式が適しています。


●XML::Parser(イベント方式)

 ◆XML::Parserについて
 
  ・XML::ParserはPerlで初期に開発されたXML用のパーサです。XML::ParserのI/Fは
   標準化(*1)されたものではありませんが、ほぼ全てのOS/プラットフォーム用
   Perl環境において、使用できる可能性の高いmoduleです。
  
  ・XML::Parserは複数のデータ処理スタイルを持っており、イベント方式とオブジ
   ェクト方式の両方に対応していますが、今回はイベント方式でXML::Parser利用
   してみたいと思います。
 
 ◆XML::Parserを使ってみる
 
  ・詳細はPerlのDocumentに書いてあるので、ここでは必要な事柄だけ説明します。
   使用にあたり意識することは下記2点です。
    + XML::Parserオブジェクトの生成とハンドラの指定
    + 読み込みファイルの指定
  
  ・リスト2に具体例を示します。この例では、リスト1のXMLデータをハッシュデー
   タへ取り込みます。

   [リスト2. XML::Parser使用例]
   ┌───────────────────────────────────┐
    #! perl -w
   
    use XML::Parser;
   
    my %ghData;  # データ全体を保持するハッシュ
    my $gString; # 文字データ蓄積用
    my %ghEle;  # 要素毎のハッシュデータ蓄積用
   
    #====================================================================
    {
      my $obj_parse = XML::Parser -> new (
        Handlers => {
          Start => \&HandleStart, # タグ開始
          End  => \&HandleEnd,  # タグ終了
          Char => \&HandleChar  # 通常データ
        }
      );
   
      $obj_parse -> parsefile($ARGV[0]);
   
      for (my $i=0; $i<=$#{$ghData{Member}}; $i++) {
        print "Member : $i\n";
        foreach my $ele (keys(%{$ghData{Member}->[$i]})) {
          print " $ele $ghData{Member}->[$i]{$ele}\n";
        }
      }
     
      exit(0);
    }
   
    #====================================================================
    sub HandleStart {
      my ($expat, $element) = @_;
     
      if ($element eq 'Member') { # Memberタグなら要素ハッシュ初期化
        %ghEle = (Mail=>'', Name=>'', Tel=>'');
      }
      $gString = '';       # 文字データも初期化
    }
   
    #====================================================================
    sub HandleEnd {
      my ($expat, $element) = @_;
     
      if ($element eq 'Member') {
        my %Ele = %ghEle;  # 要素データをローカルハッシュにコピー
        push(@{$ghData{Member}}, \%Ele); # リファレンスをプッシュ
      }
      elsif ($element eq 'Mail') {
        $ghEle{Mail} = $gString;
      }
      elsif ($element eq 'Name') {
        $ghEle{Name} = $gString;
      }
      elsif ($element eq 'Tel') {
        $ghEle{Tel} = $gString;
      }
    }
   
    #====================================================================
    sub HandleChar {
      my ($expat, $string) = @_;
     
      $gString .= $string;
    }
   
    # EOF
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
    Member : 0
     Tel 090-1111-1111
     Mail itiro@hoge.co.jp
     Name Ichiro Suzuki
    Member : 1
     Tel 090-2222-2222
     Mail jiro@hoge.co.jp
     Name Jiro Sato
    Member : 2
     Tel 090-3333-3333
     Mail sabro@hoge.co.jp
     Name Sabro Yamada
   └───────────────────────────────────┘
  
  ・まずXML::Parserの使用宣言を記述します。
    use XML::Parser;
  
  ・次にオブジェクトの生成と、ハンドラの指定を行います。

    my $obj_parse = XML::Parser -> new (
      Handlers => {
        Start => \&HandleStart, # タグ開始
        End  => \&HandleEnd,  # タグ終了
        Char => \&HandleChar  # 通常データ
      }
    );
   
    + Startはタグ開始時のハンドラ用関数指定です。関数名の参照を渡します。
    + Endはタグ終了時のハンドラ指定です。
    + Charは通常の記述データを読込んだときのハンドラ指定です。
  
  ・XML::Parserの通常データ用ハンドラ呼び出しは独特の癖があります。それは
   
    <data>
      あいうえお  <--- Charハンドル呼び出し
      かきくけこ  <--- Charハンドル呼び出し
      さしすせそ  <--- Charハンドル呼び出し
    </data>
   
   のように、文字データ内の改行を読む度にCharハンドラ関数が呼び出されてしま
   う点です。
これは要注意です。
  
  ・各ハンドラでは引数指定が
    my ($expat, $element) = @_;
   のようになっています。第一引数の $expat は Expatのオブジェクト参照を受け
   ています。
  
  ・ExpatはXML::Parserの裏で動いている低レベルのパーサです。Expatのオブジェ
   クトを受けているということは、Expatが持つメソッドを使用できることを意味
   しています。例えば
    
    $expat->current_line(); # XMLデータ内の現在行を表示
   
   のように使用します。Expat各メソッド詳細はPerlのDocumentを参照下さい。
  
  ・残念ながらXML::ParserにはXMLを書き出すメソッドがありません。ただしデータ
   が構造化されていれば、書き出しは難しくないので、自分でルーチンを書いても
   良いし、他のパッケージ/クラスを使うのもありです。


●次は

 ・XML::ParserはPerlで最もよく利用されているパーサですが、そのI/Fが標準とは異
  なります。
 
 ・そこで、次回はSAX/DOMを利用した環境を検討してみたいと思います。


(*1)XMLで標準化されたI/FにはSAXとDOMがあります。SAXはイベント方式、DOMはオブジ
  ェクト方式です。
   SAX : Simple Api for XML
   DOM : Document Object Model
  PerlからSAX/DOMでXMLにアクセスする方法もいずれ紹介したいと思います。


[▲Perl Home▲]

Copyright(c)2005 Monpe
All Rights Reserved