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