class HTMLParser def initialize str @s = StringScanner.new(str) end def run while not @s.eos? if decl = @s.scan(/]+>/) next elsif tag = @s.scan(/<(\w+)((?: [-\w]+=(?:"[^"]+"|\S+))*)>/) as = {} a = StringScanner.new(@s[2]) while not a.eos? if a.scan(/\s+/) then next elsif a.scan(/([-\w]+)=(?:"([^"]+)")/) as[a[1].downcase] = a[2] elsif a.scan(/([-\w]+)=(\S+)/) as[a[1].downcase] = a[2] end end yield :opentag, @s[1].downcase, as elsif tag = @s.scan(/<\/(\w+)>/) yield :closetag, @s[1].downcase elsif @s.scan(//) next elsif text = @s.scan(/[^<]+/) a = StringScanner.new(text) buf = [] while not a.eos? if str = a.scan(/[^&]+/) buf.push str elsif a.scan(/ ?/) buf.push "\xC2\xA0" else buf.push a.scan(/./) end end yield :text, buf.join else text = @s.scan(/<[^>]*>/) yield :text, text end end end end class WebTabParser def initialize host, port, path @host, @port, @path = host, port, path @cond = @row_post = nil end def newtab_cond(&cond) @cond = cond end def row_post(&sub) @row_post = sub end def run tab = [] require 'net/http' require 'strscan' Net::HTTP.version_1_2 Net::HTTP.start(@host, @port) {|http| resp = http.get(@path) stack = [] row = nil colspan = nil rowspan = {} text = [] HTMLParser.new(resp.body).run {|ttype, tval, as| $deferr.puts "[#{ttype}, #{tval}]" if $DEBUG case ttype when :opentag case tval when 'td', 'th' then if stack.size == 2 stack.push :cell text = [] $deferr.puts "#cell" if $DEBUG colspan = as['colspan'].to_i if as['colspan'] rowspan[row.size] = -as['rowspan'].to_i if as['rowspan'] end when 'tr' then if stack.size >= 2 for icol in rowspan.keys.sort.reverse if rowspan[icol] < 0 then rowspan[icol] = -rowspan[icol] else row[icol,0] = ['||'] rowspan[icol] -= 1 $deferr.puts "#rowspan #{icol}" if $DEBUG rowspan.delete(icol) if rowspan[icol] <= 1 end end @row_post.call(row) if @row_post tab.push row row = [] $deferr.puts "#endrow-#row" if $DEBUG elsif stack.size == 1 stack.push :row row = [] $deferr.puts "#row" if $DEBUG end when 'table' then if @cond ? @cond.call(ttype, tval, as) : true stack.push :table $deferr.puts "#tab" if $DEBUG end when 'br' then text.push " " end when :closetag case tval when 'td', 'th' then if stack.size >= 3 stack = stack[0,2] row.push text.join if colspan then row << ([row.last] * (colspan - 1)) colspan = nil end $deferr.puts "#endcell" if $DEBUG end when 'tr' if stack.size >= 2 stack = stack[0,1] for icol in rowspan.keys.sort if rowspan[icol] < 0 then rowspan[icol] = -rowspan[icol] else row[icol,0] = ['||'] rowspan[icol] -= 1 $deferr.puts "#rowspan #{icol}" if $DEBUG rowspan.delete(icol) if rowspan[icol] <= 1 end end @row_post.call(row) if @row_post tab.push row $deferr.puts "#endrow" if $DEBUG end stack.pop if stack.last == :row when 'table' unless stack.empty? stack = [] $deferr.puts "#endtab" if $DEBUG end end when :text text.push tval if stack.size == 3 end } } tab end end class Amd def initialize parent, relpath @parent = parent @path = relpath end def stnlist tab = [] File.open(File.join(App::DATADIR, 'stnhack.csv'), 'r') { |fp| for line in fp id, wid, name = line.chomp.split(/,/) next if /^#id/ === id row = [id, name] tab.push row end } @parent.list tab, :show_any => true end def resolve_station stn File.open(File.join(App::DATADIR, 'stnhack.csv'), 'r') { |fp| for line in fp id, wid, name = line.chomp.split(/,/) next unless stn == id return wid.split(/-/, 2) end } end def menu1 stn prec, wid = resolve_station(stn) if stn == 'any' then lst = [ ['stnmeta', '地点情報'] ] elsif /^\d\d\d\d\d$/ === wid then lst = [ ['stnmeta', '地点情報'], ['monthly', '月毎の値'], ['normal-monthly', '月毎の平年値'], ] else lst = [ ['stnmeta', '地点情報'], ['normal-monthly', '月毎の平年値'], ] end @parent.list lst end def monthly stn @parent.list [ ['a1', '日平均気温'], ['a2', '日最高気温'], ['a3', '日最低気温'], ['a4', '平均風速'], ['a5', '海面気圧'], ['a6', '現地気圧'], ['a7', '相対湿度'], ['a8', '蒸気圧'], ['a9', '雲量'], ['a10', '日照率'], ['a11', '全天日射量'], ['a12', '日照時間'], ['a13', '降水量'], ['a14', '降雪の深さ'], ], :linkto => 'data' end def monthly_data stn, elem prec, wstn = resolve_station(stn) path = "/obd/stats/etrn/view/monthly_s3.php?prec_no=#{prec}&block_no=#{wstn}&year=&month=&day=&view=#{elem}" wtp = WebTabParser.new('www.data.jma.go.jp', 80, path) wtp.newtab_cond {|ttype, tval, as| as['class']} tab = wtp.run @parent.table tab end def stnmeta stn @parent.list [ ['history', '履歴'], ['latest', '最新のみ'], ], :linkto => 'data' end def stnmeta_data stn, squeeze File.open(File.join(App::DATADIR, 'amdmaster.csv'), 'r') { |fp| head = nil tab = (squeeze == 'latest') ? Hash.new : Array.new for line in fp row = line.chomp.split(/,/) if not head then head = row else id = row[0] if stn == 'any' or stn == id then namej = row[1] namee = row[3] lat = row[4].to_f + row[5].to_f / 60.0 lon = row[6].to_f + row[7].to_f / 60.0 hgt = row[8].to_f begdate = sprintf('%04s-%02s-%02s', row[15], row[16], row[17]) enddate = sprintf('%04s-%02s-%02s', row[18], row[19], row[20]) row = [id, namej, namee, lat, lon, hgt, begdate, enddate] case tab when Hash next unless /^9999-/ === enddate tab[id] = row when Array tab.push row end end end end if Hash === tab tab = tab.keys.map{|id| tab[id]} end @parent.table tab } end def normal_monthly stn prec, wid = resolve_station(stn) case wid when /^\d\d\d\d$/ then @parent.list [ ['p1', '主な要素'], ['a1', '詳細(降水量)'], ], :linkto => 'data' when /^\d\d\d\d\d$/ then @parent.list [ ['p1', '主な要素'], ['a1', '詳細(気圧・降水量) ... 予定'], ], :linkto => 'data' end end def normal_monthly_data_amd(stn, type, prec, wid) path = "/obd/stats/etrn/view/nml_amd_ym.php?prec_no=#{prec}&block_no=#{wid}&year=&month=&day=&view=" wtp = WebTabParser.new('www.data.jma.go.jp', 80, path) wtp.newtab_cond {|ttype, tval, as| as['class']} wtp.row_post {|row| case [type, row.first] when ['a1', '合計'] then row.unshift('') when ['a1', '≧1.0mm'] then row.unshift('', '') end } tab = wtp.run @parent.table tab end def normal_monthly_data_sfc(stn, type, prec, wid) path = "/obd/stats/etrn/view/nml_sfc_ym.php?prec_no=#{prec}&block_no=#{wid}&year=&month=&day=&view=#{type}" wtp = WebTabParser.new('www.data.jma.go.jp', 80, path) wtp.newtab_cond {|ttype, tval, as| as['class']} wtp.row_post {|row| case [type, row.first] when ['a1', '合計'] then row.unshift('') end } tab = wtp.run @parent.table tab end def normal_monthly_data stn, type prec, wid = resolve_station(stn) case wid when /^\d\d\d\d$/ then normal_monthly_data_amd(stn, type, prec, wid) when /^\d\d\d\d\d$/ then normal_monthly_data_sfc(stn, type, prec, wid) end end def run case @path when %r{^/*(?:index(?:\.\w+)?)?$} stnlist when %r{^/(\w+)/*(?:index(?:\.\w+)?)?$} menu1 $1 when %r{^/(\w+)/stnmeta/*(?:index(?:\.\w+)?)?$} stnmeta $1 when %r{^/(\w+)/stnmeta/(\w+)/*(?:data(?:\.\w+)?)?$} stnmeta_data $1, $2 when %r{^/(\w+)/monthly/*(?:index(?:\.\w+)?)?$} monthly $1 when %r{^/(\w+)/monthly/(\w+)/*(?:data(?:\.\w+)?)?$} monthly_data $1, $2 when %r{^/(\w+)/normal-monthly/*(?:index(?:\.\w+)?)?$} normal_monthly $1 when %r{^/(\w+)/normal-monthly/(\w+)/*(?:data(?:\.\w+)?)?$} normal_monthly_data $1, $2 else raise HTTP404 end end end