2012年1月10日火曜日

Rubyでコーパス言語学(4)

複数のファイルをまとめて処理

複数のXMLファイルをまとめて処理したい場合は、次のようにする。

files = Dir.glob('C:¥KyotoCorpus4.0¥xml¥syn¥*.xml')
files.each do |file|
  STDERR.puts file
  doc = open(file){|f| Nokogiri::XML(f){|config| config.noblanks}}
  (ここに処理を記述)
end

Dir.globはワイルドカードを含むファイル名を展開してファイル名のリスト(Array)で返す。STDERR.putsは標準エラー出力(普通はコマンドプロンプト)に文字列を出力する命令で、現在処理中のファイルの名前を画面に表示する。複数のファイルをまとめて処理する場合、処理が完了するまでに時間がかかって、ちゃんと動いているのか不安になることがあるので動作確認のために入れてある。上記のコードを関数として定義しておいて次のように使うこともできる。

def xml_enum(path)
  files = Dir.glob(path)
  files.each do |file|
    STDERR.puts file
    doc = open(file){|f| Nokogiri::XML(f){|config| config.noblanks}}
    yield doc
  end
end

path = 'C:¥KyotoCorpus4.0¥xml¥syn¥*.xml'
xml_enum(path) do |doc|
  (ここに処理を記述)
end

さらに応用してクラスとして定義する場合。

class XMLFiles
  include enumerable
  def initialize(path)
    @path = path
    @files = Dir.glob(path)
  end
  def each
    @files.each do |file|
      doc = open(file){|f| Nokogiri::XML(f){|config| config.noblanks}}
      yield doc
    end
  end
end

path = 'C:¥KyotoCorpus4.0¥xml¥syn¥*.xml'
corpora = XMLFiles.new(path)
corpora.each do |doc|
  (ここに処理を記述)
end

このクラスはeachメソッドが定義されており、enumerableモジュールをインクルードしてあるが、to_aとかすると全てのXMLファイルを一度にメモリに読み込むことになるのでエラーに注意。mapとかで必要な情報だけを拾い集める分には問題ないと思われる。さらにファイルが複数あることを意識せずにxpath検索するメソッドを追加してみる。

class XMLFiles
  def xpath(xpath)
    self.each do |doc|
      doc.xpath(xpath).each{|node| yield node}
    end
  end
end

corpora.xpath(xpath) do |node|
  (ここに処理を記述)
end

Nokogiri::XML::Documentなどに対するxpathメソッドがNodeSetを返すのに対して、上記のxpathメソッドはeachのように動作する。複数のXML文書から集めたノードを集めて、それらのリストを返すようなメソッドとして定義してしまうと、メモリエラーになる可能性があるのでこのような方法を取っている。ノードセットに似たオブジェクトを返すようにしたいのであれば、次のようにEnumeratorを使う方法が考えられる。

class XMLFiles
  def xpath(xpath)
    enum = Enumerator.new do |y|
      self.each do |doc|
        doc.xpath(xpath).each{|node| y << node}
      end
    end
    return enum
  end
end

corpora.xpath(xpath).each do |node|
  (ここに処理を記述)
end

この方法であれば、each以外のmapやcountなどのメソッドも利用することが可能になる。to_aするとメモリエラーになる可能性があるのは同じなので注意すること。

0 件のコメント:

コメントを投稿