引き続き、 言語処理100本ノック を敢えてRubyで解いている。
第3章は正規表現がテーマだが、正規表現はそこまで得意ではないので辛い。マッチの効率とか深く考えていない。
20. JSONデータの読み込み
require 'json' uk = File.foreach("jawiki-country.json"). map(&JSON.method(:parse)). find {|article| article['title'] == 'イギリス' } raise "no article about イギリス" unless uk puts uk['text']
感覚的なものだけど、この場合は &obj.method(:mid)
形式の簡潔さが見慣れない分のデメリットを上回る気がした。
21. カテゴリ名を含む行を抽出
File.foreach('uk.txt').grep(/\[\[Category:/) do |line| puts line end
この辺はRubyのほうがPythonよりsedやawkの影響が色濃く残っていて簡潔に書けるかなぁ。
22. カテゴリ名の抽出
pattern = / \[\[ Category: ([^|\]]+) (?:\|[^\]]*)? \]\] /x File.read("uk.txt").scan(pattern) do |cat,| puts cat end
正規表現がとても読みづらくなったのでせめて x
オプションで複数行に分けてみた。
23. セクション構造
File.foreach("uk.txt") do |line| next unless m = /^(={2,})\s*([^=]+)\s*\1/o.match(line) puts "level=#{m[1].size - 1}, name=#{m[2]}" end
24. ファイル参照の抽出
pattern = / \[\[ File: ([^|\]]+) (?:\|[^\]]*)* \]\] /x File.read("uk.txt").scan(pattern) do |cat,| puts cat end
Categoryの場合とほとんど変わらないと思うんだが、出題意図を読み違えてないよね?
25. テンプレートの抽出
def params templatePattern = / {{基礎情報\s+国\s+ (?<params> (?<markup> {{\g<markup>}} | [^{}]* )* ) }} /x article = File.read("uk.txt") raise "no 基礎情報 found" unless m = templatePattern.match(article) base = m[:params] Hash.new.tap {|result| base.split(/^\|/).each do |entry| next if entry.empty? name, value = entry.split(/\s*=\s*/, 2) result[name] = value.chomp end } end if $0 == __FILE__ p params end
String#scanで切り出すのとnamed captureはあまり相性が良くないことが判明。 最初に基礎情報を取り出す部分は田中哲スペシャルのおかげで簡潔に再帰構造をサポートできている。
一方、テンプレートパラメータの抽出はMediaWikiの文法を正確に表してはいない。だが、今回はパラメータ区切りは常に行頭から始まるのでとりあえず十分である。
26. 強調マークアップの除去
require_relative '25.rb' def params2 Hash.new.tap {|result| params.each do |name, value| result[name] = value.gsub(/('{2,3}|'{5})([^']+)\1/, '\2') end } end if $0 == __FILE__ p params2 end
27. 内部リンクの除去
require_relative '26.rb' def params3 Hash.new.tap {|result| params2.each do |name, value| result[name] = value.gsub(/\[\[ ([^\|\]:]+) (?:\|([^\]]+))? \]\]/x) do $2 || $1 end end } end if $0 == __FILE__ p params3 end
28. MediaWikiマークアップの除去
require_relative '27.rb' def params4 Hash.new.tap {|result| params3.each do |name, value| value = value.gsub(/{{([^\|}]+)(?:\|([^\|}]+)?)*}}/) { $2 || $1 } value = value.gsub(%r!<ref(?:\s[^/>]+)?>.*?</ref>!m, '') value = value.gsub(%r!<ref(?:\s[^/>]+)?/>!, '') value = value.gsub(%r!<[[:alpha:]]+\s*/>!, '') value = value.gsub(%r!£!, '£') result[name] = value end } end if $0 == __FILE__ p params4 end
MediaWiki記法は結構いろいろあるのでやり出すとキリが無い。とりあえずこんな感じだろうか。 遊びでやるにはそろそろ辛くなってきた。
実務でやるなら、目的を満たす程度のminimalなMediaWiki parserを書いてちゃんとunit testを書くべきだろう。
29. 国旗画像のURLを取得する
require 'json' require 'open-uri' require_relative '28.rb' name = params4['国旗画像'] url = "http://ja.wikipedia.org/w/api.php?format=json&action=query&continue=&titles=Image:%s&prop=imageinfo&iiprop=url" % URI.escape(name) result = JSON.parse(open(url) {|f| f.read }) puts result['query']['pages']['-1']['imageinfo'].first['url']