require 'html-parser'
require 'formatter'
require 'uri'
# 2003/02/11
# * authorが拾えてなかった(assocの使い方を間違えてた……)ので
# 修正。仕様についてはまだ要検討。
#
# 2003/02/09
# * detecter -> detector (汗
# * @anchor_name -> @last_anchor_name
# * handle_comment を追加。
# RHG
# とかいうタグがあった場合、foo.html#t_01をISBN4-8443-1721-0
# の書評と見なすようにする。
# HTML中の書評(ISBN)を自動検出するクラス
#
class ReviewDetector < HTMLParser
# 書評の入れ物クラス
#
class Review
attr_accessor :isbn, :reviewer, :url
def initialize(isbn, url, reviewer)
@isbn = isbn
@url = url
@reviewer = reviewer
end
# ISBNを数値のみにする。識別用に使うためのもの。
#
def s_isbn
@isbn.gsub(/[^0-9]+/,"")
end
end
attr_reader :review_list
# 初期化する。
# 第3引数は書評を書いた人を指定する場合。nilの場合はHTML中の
# から検出する。
#
def initialize(writer, url, reviewer=nil)
super(writer)
@url = url
@parsed_url = URI.parse(url)
@reviewer = reviewer
@review_list = {}
@anchor = nil
@last_anchor_name = nil
end
# というタグ
# があったらその人をデフォルトの書評者とする。
#
def do_meta(attrs)
if !@reviewer
if attrs.assoc("name") &&
attrs.assoc("name")[1].to_s.downcase == "author"
reviewer_list = attrs.assoc("content")
if reviewer_list
@reviewer = reviewer_list[1]
end
end
end
end
# コメント部の処理。
# コメント中にISBNがあって、それが...で囲まれていれば
# そのアンカーのリンク先を書評ページとする。
# アンカーになっていない場合はテキスト中のIBSNと同じく、その
# ページ内に書評が書かれているとする。
#
def handle_comment(data)
isbn = lookup_isbn_by_text(data)
if isbn
if @anchor
url = @parsed_url.merge(URI.parse(@anchor)).to_s
regist_review(isbn, url)
else
regist_review(isbn)
end
end
end
# の処理
#
def anchor_bgn(href, name, type)
isbn = lookup_isbn_by_href(href)
if isbn
regist_review(isbn)
end
@anchor = href
if name
@last_anchor_name = name
end
end
# の処理
#
def anchor_end()
@anchor = nil
end
# HTML内のテキスト部分の処理。
# テキスト中にISBNがあって、それが...で囲まれていれば
# そのアンカーのリンク先を書評ページとする。
# アンカーになっていない場合は、そのページに書評が
# 書かれているとして、直前のに書かれているname
# 属性のところを書評として登録する。
# その場合、name属性が指定してあるAタグがないときにはページ
# そのものを登録する。
#
def handle_data(data)
isbn = lookup_isbn_by_text(data)
if isbn
if @anchor
url = @parsed_url.merge(URI.parse(@anchor)).to_s
regist_review(isbn, url)
else
regist_review(isbn)
end
end
end
# 書評をリストに格納する
#
def regist_review(isbn, url=nil)
if url
review = Review.new(isbn, url, @reviewer)
else
if @last_anchor_name
review = Review.new(isbn, @url+"#"+@last_anchor_name, @reviewer)
else
review = Review.new(isbn, @url, @reviewer)
end
end
@review_list[review.s_isbn] = review
end
# href属性値の中からISBNを探す
#
def lookup_isbn_by_href(href)
if %r|www.amazon.co.jp/exec/obidos/ASIN/([0-9][-0-9X]+)/|i =~ href
return $1
elsif %r|www.bk1.co.jp/[^'"]*isbn=([0-9][-0-9X]+)|i =~ href
return $1
end
return nil
end
# 文字列の中からISBNを探す
#
def lookup_isbn_by_text(data)
if %r|isbn[\s=:]?([0-9][-0-9X]+)|i =~ data
return $1
end
end
## for simple use
class << self
def detect(data, url, reviewer=nil)
f = NullFormatter.new()
parser = ReviewDetector.new(f, url, reviewer)
parser.feed(data)
list = parser.review_list
parser.close()
return list
end
end
end
if __FILE__ == $0
# コマンドラインでの使い方
# URLは必須。reviewerの指定はオプションで、指定しない
# 場合は自動検出を行う。
#
def usage()
print "ruby reviewdetector.rb []\n"
exit 1
end
require 'net/http'
Net::HTTP.version_1_2
usage() if ARGV.size < 1 or ARGV.size > 2
data = nil
url = URI.parse(ARGV[0])
if ARGV[1]
reviewer = ARGV[1]
else
reviewer = nil
end
if url.scheme == "http"
http = Net::HTTP.new(url.host, url.port)
http.start{
res, = http.get(url.path)
data = res.body
}
list = ReviewDetector.detect(data, ARGV[0], reviewer)
else
data = File.open(ARGV[0]){|f| f.read()}
list = ReviewDetector.detect(data, "file:///"+ARGV[0], reviewer)
end
# ISBN、URL、書評者をtab区切りで出力
#
list.each{|key, item|
printf("%s\t%s\t%s\n", item.isbn, item.url, item.reviewer)
}
end