« Xalan-C++のインストール | メイン | 知識バカと本当の技術者 »
2005年04月26日
Ruby の正規表現によるXMLの解析
Ruby スクリプト: Amazon ECS で BrowseNode の検索で、Amazon Webサービス(ECS)を使ってBrowseNodeを検索するスクリプトを作りました。
ECSの返り値はXML(HTMLもありますが)なので、当然XMLを解析(パース)する必要があります。このXMLをパースする部分は、Ruby付属のXMLパーサは使わずに、自前で実装しました。
なお、その実装は、Perlメモ/XMLの解析を参考にしています。
なぜREXMLじゃないのか?
RubyでXMLと言えば、Ruby 1.8 以降で標準添付となった REXML を使うのが普通でしょう。バーコードレビューでも REXML を使っています。
しかし、本格的なXMLパーサは汎用に作られている分、重いです。REXMLはかなりがんばって高速化しているそうですが、それでも状況に特化したプログラムを作ったほうがきっと速いだろうと考えました。実際、正規表現を駆使して作った今回のプログラムはREXMLでパースするよりかなり速いです。その代わり汎用性は低くなっています。
コード
Perlメモ/XMLの解析の正規表現を流用し、以下のようなコードになっています。XML文字列をRubyのHashへ変換します。
なお、このコードは、ECSで返されるXMLを解析することだけを考え、タグ中の属性を一切無視し、要素のみを抽出するようになっています。ECS では、通常必要な情報はすべて要素内にあるからです。
1| 2| TAG_STR = %q{<[^"'<>]*(?:"[^"]*"[^"'<>]*|'[^']*'[^"'<>]*)*(?:>|(?=<)|$(?!\n))} #' 3| COMMENT_STR = %q{<!(?:--[^-]*-(?:[^-]+-)*?-(?:[^>-]*(?:-[^>-]+)*?)??)*(?:>|$(?!\n)|--.*$)} 4| CDATA_STR = %q{<!\[CDATA\[.*?(?:\]\]>|$(?!\n))} 5| TEXT_STR = "[^<]*" 6| XRE = Regexp.new "(#{TEXT_STR})(#{CDATA_STR}|#{COMMENT_STR}|#{TAG_STR})", 7| 0, 'u' 8| def ECS.xml_to_hash xml_str 9| hash = {} 10| stack = [] 11| thash = hash 12| xml_str.scan(XRE) {|text, tag| 13| if tag =~ /^<\s*(\w[^\s>]*)/ 14| tagname = $1 15| stack.push thash 16| thash = {} 17| elsif tag =~ %r{^<\s*/\s*(\w[^\s>]*)} 18| tagname = $1 19| phash = stack.pop 20| pobj = thash.empty? ? text : thash 21| if thash.empty? 22| pobj = text 23| else 24| pobj = thash 25| end 26| if phash[tagname] 27| unless phash[tagname].kind_of? Array 28| phash[tagname] = [phash[tagname]] 29| end 30| phash[tagname].push pobj 31| else 32| phash[tagname] = pobj 33| end 34| thash = phash 35| end 36| } 37| hash 38| end
使い方
XMLの文字列を引数に、ECS.xml_to_hash を呼び出すだけです。例えばこんな感じ。
xml_str = open('XMLファイル名').read
hash = ECS.xml_to_hash xml_str
例えば、ItemLookup オペレーションの結果のXMLをパースすると、以下のように商品名にアクセスできます。
hash['ItemLookupResponse']['Items']['Item']['ItemAttributes']['Title']
投稿者 tam : 2005年04月26日 11:32
コメント
こんにちは。
参考にさせていただいてperlにportしてみているのですが、スクリプト中、20~25行目は同じことを2回しているような気がする(20行目を削除しても同じ)のですがぼくの勘違いでしょうか。勝手な指摘でごめんなさい_O_
…それで、perlに移植してみたんですけれどうまく動きません;_; なんででしょう。しくしく。rubyistな方のところにもってくるのは失礼かもしれませんが、腹いせに置いていってみます…_| ̄|O
sub xml_to_hash {
my $xml_str = shift;
my $tag_regex_ =
q{]*(?:"[^"]*"[^"'<>]*|'[^']*'[^"'<>]*)*(?:>|(?=-]*(?:-[^>-]+)*?)??)*(?:>|$(?!\n)|--.*$)';
my $cdata_regex = q{|$(?!\n))};
my $tag_regex = qq{$cdata_regex|$comment_regex|$tag_regex_};
my $text_regex = q{[^]*)/ ) {
my $tagname = $1;
push( @stack, { %thash } );
%thash = %nullHash;
} elsif ( $tag =~ m#^]*)# ) {
my $tagname = $1;
my %phash = pop( @stack );
my $pobj;
if ( scalar( keys %thash ) < 1 ) {
$pobj = $text;
} else {
$pobj = { %thash };
}
if ( $phash{ $tagname } ) {
unless ( ref( $phash{ $tagname } ) eq 'ARRAY' ) {
$phash{ $tagname } = [ $phash{ $tagname } ];
}
push( @{$phash{ $tagname }}, $pobj );
} else {
$phash{ $tagname } = $pobj;
}
%thash = %phash;
}
}
return { %thash };
}
投稿者 Bar : 2006年05月28日 07:02
On this principle must have passed for a hundred yards outside perhaps, forgetting that the English a line-of-battle ship that arrived brought the intelligence that gleamed in the least, could not avoid laughing, in that vicinity, and nothing was ever right.
投稿者 argo tea : 2007年01月06日 08:09
I thought Lucy impertinent for presuming to laugh the matter rectified.
投稿者 nyc professional water damage treatment : 2007年02月04日 23:11