ここまでのまとめ
第2回から第4回までで示したコードを以下にまとめた。単一のXMLファイルに対する処理はXMLというクラスに、複数のXMLファイルに対する処理はXMLFilesというクラスにまとめてある。次回以降、このファイルをxmlc.rbという名前で保存しておき、requireして使うことにする。
# -*- coding: utf-8 -*-
# xmlc.rb
require 'nokogiri'
class XML
attr_accessor :xml
def initialize(path)
@xml = open(path){|f| Nokogiri::XML(f){|config| config.noblanks}}
end
def nodes(xpath = '//*')
nodes = Hash.new(0)
@xml.xpath('//*').each{|node| nodes[node.node_name] += 1}
return nodes
end
def attributes(xpath = '//*')
attributes = Hash.new(0)
@xml.xpath(xpath).each do |node|
node.attributes.keys.each do |attr|
attributes["#{node.node_name}/@#{attr}"] += 1
end
end
return attributes
end
def attr_values(xpath)
values = Hash.new(0)
@xml.xpath(xpath).each{|attr| values[attr.value] += 1}
return values
end
def frequency(xpath, *attrs)
attrs = [:text, :read, :base, :pos, :ctype, :cform] if attrs == []
text = attrs.delete(:text)
words = Hash.new(0)
@xml.xpath(xpath).each do |word|
items = []
items << word.text if text
items += attrs.map{|attr| word.attr(attr)}
words[items] += 1
end
return words
end
def kwic(xpath)
lines = []
words = @xml.xpath(xpath)
words.each do |tok|
sen = tok.at_xpath('ancestor::sentence')
senid = sen.attr('S-ID')
tokid = tok.attr('id')
prev = sen.xpath('.//tok[@id < %i]' % tokid)
foll = sen.xpath('.//tok[@id > %i]' % tokid)
lines << [senid, tokid, prev.text, tok.text, foll.text]
end
return lines
end
def regexp(re)
list = []
@xml.xpath('//sentence').each do |sen|
pos1 = []; pos2 = [0]
sen.text.scan(re){|s| pos1 << [$~.begin(0), $~.end(0)]}
next if pos1 == []
words = sen.xpath('.//tok')
words.each{|tok| pos2 << pos2[-1] += tok.text.size}
pos1.each do |b1, e1|
b2 = pos2.find_index{|n| n > b1}
e2 = pos2.find_index{|n| n >= e1}
list << words[b2..e2]
end
end
return list
end
def regexp_inspect(re)
data = Hash.new(0)
self.regexp(re).each do |words|
key = words.map{|word|
"#{word.text}(#{word.attr('pos').split('-').last})"
}.join(' ')
data[key] += 1
end
return data
end
def method_missing(sym, *args, &block)
@xml.send sym, *args, &block
end
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
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
上記のライブラリは以下のようにして使う。
# -*- coding: utf-8 -*-
require './xmlc.rb'
doc = XML.new('(中略)/950101.xml')
ここで変数docの中身はNokogiri::XML::Documentではなく新たに定義したXMLというクラスのオブジェクトであることに注意。Nokogiri::XML::Documentオブジェクトに直接アクセスしたい場合はdoc.xml
でアクセスすることができる。XMLクラスの役割は、第2回と第3回で示したような各種のメソッドを実装することである。XMLクラスには次のようなメソッドが実装してある。
- nodes
- attributes
- attr_values
- frequency
- kwic
- regexp
- regexp_inspect
nodes、attributes、attr_valuesの各メソッドは、XML文書中のノード名、属性名、属性の値の一覧と頻度(出現数)を取得するためのメソッドである。戻り値はハッシュであるので、ppを使って表示すると見やすい。なおnodesとattributesは引数を省略すると全てのノードや属性をリストアップするが、引数としてxpathを指定することで特定の種類のノードやその属性のみリストアップすることもできる。nodesとattributesの引数となるxpathはノード(".../tok"など)、attr_valueの引数となるxpathは属性(".../@pos"など)でなければならない。
require 'pp'
pp doc.nodes
pp doc.attributes
pp doc.attr_values('//tok/@pos')
[結果]
{"document"=>1, "sentence"=>1134, "chunk"=>10268, "tok"=>26739}
{"sentence/@S-ID"=>1134,
"sentence/@KNP"=>1134,
(中略)
"tok/@cform"=>26739,
"sentence/@MEMO"=>14}
{"名詞-人名"=>623,
"名詞-普通名詞"=>4756,
(中略)
"感動詞"=>11,
"未定義語-その他"=>2}
frequencyメソッドは特定の語の頻度を数えるためのメソッドである。第1引数には検索する語をxpathで指定する。活用語の活用形のリストを取得するためにも使える。
pp doc.frequency('//tok[@pos="判定詞"]')
[結果]
{["な", "な", "だ", "判定詞", "判定詞", "ダ列基本連体形"]=>7,
["で", "で", "だ", "判定詞", "判定詞", "ダ列タ系連用テ形"]=>53,
(中略)
["であろう", "であろう", "だ", "判定詞", "判定詞", "デアル列基本推量形"]=>1,
["だろう", "だろう", "だ", "判定詞", "判定詞", "ダ列基本推量形"]=>1}
frequencyメソッドは、第2引数以降で出力する属性の種類を指定することができる。省略した場合のデフォルトは[:text, :read, :base, :pos, :ctype, :cform]である(:textはtext()関数として解釈される)。第2引数以降を指定した場合は、指定した属性に基づいて集計が行われる。
pp doc.frequency('//tok[@pos="判定詞"]', :pos)
[結果]
{["判定詞"]=>212}
kwicメソッドはkwicもどきの結果を二次元配列で返す。
pp doc.kwic('//tok[@pos="判定詞"]')
[結果]
[["950101008-006",
"11",
"ようやく経済も明るさを取り戻しつつある微妙な段階",
"な",
"ので、今は解散の時期ではないと考えている。"],
["950101008-006",
"19",
"ようやく経済も明るさを取り戻しつつある微妙な段階なので、今は解散の時期",
"で",
"はないと考えている。"],
(以下略)
regexpメソッドとregexp_inspectメソッドは正規表現検索してマッチした部分のノードを返す。regexpメソッドの戻り値はNokogiri::XML::NodeSetのリストである。regexp_inspectは見やすいように文字列化して集計したものを返す。
pp doc.regexp_inspect(/とは/)
[結果]
{"こと(形式名詞) は(副助詞)"=>12,
"えと(普通名詞) は(副助詞)"=>1,
"と(格助詞) は(副助詞)"=>10,
"もともと(副詞) は(副助詞)"=>1,
"あと(副詞) は(副助詞)"=>2}
その他、XMLクラスにはmethod_missingメソッドが仕込んであり、未定義のメソッドは@xmlに丸投げする。例えば、doc.xpath(...)
は実際にはdoc.xml.xpath(...)
として実行される。もう一つのXMLFilesクラスは第4回で示したのと同じものなのでここでは説明しない。XMLFilesクラスは今のところeachとxpathしかメソッドを定義していないが、今後拡張していくことにする。
0 件のコメント:
コメントを投稿