xml(html)からデータを読む
勢いでhtmlファイルからデータを取得するプログラムを作ってみたので、ソースをブログに書いておきます。作ったものは気象庁のサイトからアメダスのデータを取得するプログラムですが、どちらかと言うとxmlからデータを取り出すプログラムがメインです。
xmlGetTag( fp, tagname )
これがメインのプログラムです。指定したファイルから指定したタグを検索し、そのタグの要素とデータを取得します。要素の内容はテーブル形式で取得します。
------------------------------------------ --タグの要素を配列に入れて返し、データも取得して返します ------------------------------------------ function xmlGetTag( fp, tagname ) local str local tbl={} local closeflg = false local data = "" while(true)do --記号(kigou)に指定された文字が来るまで読み込みます if( xmlTagTopFound( fp, "<" )==nil )then break end --スペースやタブを読み飛ばします str = xmlSkipSpc( fp ) if( str==nil )then break elseif( str=="!" )then --コメントをスキップします if( xmlCommentSkip( fp )==nil )then break end else --タグ名をチェックします if( str==string.sub( tagname,1,1) )then local i local fitflg = true for i=1, #tagname do local n = string.sub( tagname,i,i) if( n~=str )then fitflg = false break end str = fp:read(1) if( str==nil )then fitflg = false break end end --タグ名が同じであれば、要素取得に移ります if( fitflg==true )then break end end end end --要素取得 local eldat = string.gsub( str, "^%s*(.-)", "%1") if( eldat=="" )then --スペースやタブを読み飛ばします eldat = xmlSkipSpc( fp ) end if( eldat~=">" )then -- ">"で無ければ、要素があると考える while(true)do -- ">"が来るまで要素を取得する str = fp:read(1) if( str==nil or str==">" )then break end eldat = eldat..str end --データの前後のスペースなどをトリムする eldat = string.gsub(eldat, "^%s*(.-)%s*$", "%1") -- "/>"があればデータが無いのでcloseflgをtrueにする if( eldat=="/" or string.sub( eldat,-1)=="/" )then closeflg = true end -- " で切り出す local g={} local value local i g = split( eldat, '"' ) local mae={} local usiro={} local j=1 for i,value in pairs(g) do if( string.find ( value, "=" )~=nil )then local aa={} aa = split( value, '=' ) mae[j] = string.gsub(aa[1], "^%s*(.-)%s*$", "%1") else usiro[j] = string.gsub(value, "^%s*(.-)%s*$", "%1") j = j + 1 end end for i=1, j-1 do if( mae[i]~=nil )then tbl[mae[i]] = usiro[i] end end end --closeflgがfalseなら、データを読む if( closeflg==false )then -- "<" が来るまで読む data = "" while(true)do str = fp:read( 1 ) if( str==nil or str=="<" )then break end data = data..str end end return tbl, data end
下記のように戻り値を2つ持ちます。strがタグのデータが返ってきます。strがnilの場合は取得に失敗しています。
tableタグが、<table id="tbl_list" >このようなタグであれば、取得した要素内容がtbl.idに入っています。tbl["id"]としても同じです。下記のようにループすれば、tableタグを順次検索し続けます。
--アメダスのテーブル先頭(table.id="tbl_list")を検索する while(true)do tbl, str = xmlGetTag( fp, "table" ) if( str==nil or tbl.id=="tbl_list" )then break end end
検索を先頭から始めたい場合は、下記のようにすればファイルポインタがファイルの先頭に戻ります。
fp:seek("set")
また、 xmlGetTag()がデータを取得するとき、正確にxmlを解釈していません。単純に指定したタグ名
<tag> <taga>ffff</taga> </tag>
付随関数
xmlGetTag()からいくつかの関数が呼ばれます。それらもブログに書いておきます。
- xmlTagTopFound( fp, kigou ) "<"開きカッコを見つける関数です。
------------------------------------------ --記号(kigou)に指定された文字が来るまで読み込みます --nilが返れば失敗しています ------------------------------------------ function xmlTagTopFound( fp, kigou ) local b local str local s = string.byte( kigou ) while( true )do str = fp:read( 1 ) if( str==nil )then break end b = string.byte( str ) if( s==b )then break end end return str end
- xmlSkipSpc( fp ) スペースやタブを読み飛ばしてファイルポインタを進める関数です。
------------------------------------------ --スペースやタブを読み飛ばします --スペースやタブの次に来る一文字が戻ります --nilが返れば失敗しています ------------------------------------------ function xmlSkipSpc( fp ) local str while(true)do str = fp:read( 1 ) if( str==nil )then break end str = string.gsub( str, "^%s*(.-)", "%1") if( str~="" )then break end end return str end
- xmlCommentSkip( fp ) "<!"があればコメントとみなして、コメントが終わるまでスキップする関数です。
------------------------------------------ --コメントをスキップします --">"文字を読み込んで終了します --nilが返れば失敗しています ------------------------------------------ function xmlCommentSkip( fp ) local str = fp:read( 1 ) if( str=="-" )then str = fp:read( 1 ) if( str=="-" )then -- "!--"だったので、"-->"が来るまで進む while(true)do str = fp:read( 1 ) if( str==nil )then break end if( str=="-" )then str = fp:read( 1 ) if( str==nil )then break end if( str=="-" )then str = fp:read( 1 ) if( str==nil )then break end if( str==">" )then break end end end end else -- ">" が来るまで進む while(true)do str = fp:read( 1 ) if( str==nil or str==">" )then break end end end elseif( str~=">" )then -- ">" が来るまで進む while(true)do str = fp:read( 1 ) if( str==nil or str==">" )then break end end end return str end
- split(str, d) 汎用的に使っている文字の分割関数です。
------------------------------------------ --文字の分解 ------------------------------------------ function split(str, d) local s = str local t = {} local p = "%s*(.-)%s*"..d.."%s*" local f = function(v) table.insert(t, v) end if s ~= nil then string.gsub(s, p, f) f(string.gsub(s, p, "")) end return t end
アメダスデータ取得プログラム
最後に、アメダスのデータを取得するプログラムを書いておきます。気象庁のサイトのhtmlの仕様が変ると正常に動作しなくなると思います。そのときは、各自プログラムを修正してください。
------------------------------------------ --アメダスデータの取得 ------------------------------------------ --関数宣言-------------------------------- main={} --mainメソッド gethtml={} -- hmtlを取得 split={} --文字の分解 getAmedasTable={} --アメダスのデータを取得します xmlCommentSkip={} --コメントをスキップします xmlSkipSpc={} --スペースやタブを読み飛ばします xmlTagTopFound={} --記号(kigou)に指定された文字が来るまで読み込みます xmlGetTag={} --タグの要素を配列に入れて返し、データも取得して返します --グローバル変数宣言---------------------- Amedas = {} --アメダスのデータを取得するテーブル HttpUrl = "http://www.jma.go.jp/jp/amedas_h" --アメダスURL Gwide, Gheight = canvas.getviewSize() --画面サイズ取得 LuaridaPath = system.getCardMnt().."/luarida" --luaファイルを保存しているPath sjis2utf = "/data/data/com.momoonga.luarida/files/sjis2utf " --sjis2utf外部コマンド ------------------------------------------ --アメダスのデータを取得します ------------------------------------------ function getAmedasTable( fname ) local dtable = {} local str = nil local tbl={} local fp = io.open( LuaridaPath.."/"..fname, "r") if( not(fp) )then toast(LuaridaPath.."/"..fname.." が読み込めませんでした") return dtable end --アメダスのテーブル先頭(table.id="tbl_list")を検索する while(true)do tbl, str = xmlGetTag( fp, "table" ) if( str==nil or tbl.id=="tbl_list" )then break end end if( str~=nil )then local j = 1 local i = 1 local k -- td.class="time left"が来るまでtdデータを読み込む dtable[j]={} while(true)do tbl, str = xmlGetTag( fp, "td" ) if( str==nil or tbl.class=="time left" )then break end dtable[j][i] = str i = i + 1 end if( str~=nil )then -- gkが列の数 local gk = i - 1 for j=2,26 do dtable[j]={} for i=1,gk do if( j==2 and i==1 )then if( str==" " )then str="" end dtable[j][i] = str else tbl, str = xmlGetTag( fp, "td" ) if( str==nil )then break end if( str==" " )then str="" end dtable[j][i] = str end end end end end io.close( fp ) return dtable end ------------------------------------------ -- hmtlを取得 ------------------------------------------ function gethtml( urldata ) http.get( HttpUrl.."/"..urldata, LuaridaPath.."/"..urldata ) while( http.status()==0 )do end return http.status() end ------------------------------------------ -- メインプログラム ------------------------------------------ function main() local i local j local num local a local hmtlname = "yesterday-" --画面を白にする canvas.drawCls( color(255,255,255) ) editsetText( "65042" ) num, a = editText( "地域番号の入力", 1 ) if( num==nil or num=="" or a~=1 )then return end item.clear() item.add( "今日のデータ" ) item.add( "昨日のデータ" ) a = item.radio( "取得日を選んでください",1 ) if( a==1 )then hmtlname = "today-"..num..".html" else hmtlname = "yesterday-"..num..".html" end -- hmtlを取得 if( gethtml( hmtlname )~=1 )then toast( hmtlname.."に接続できませんでした") return end toast( "Connect Success!" ) --hmtlnameの文字コードをutf8に変換する os.execute( sjis2utf.."-8 "..LuaridaPath.."/"..hmtlname.." "..LuaridaPath.."/get.tmp" ) --アメダスのデータを取得します Amedas = getAmedasTable( "get.tmp" ) --取得データを一覧する canvas.drawCls( color(255,255,255) ) --画面クリア for i=1,#Amedas do for j=1,#Amedas[i] do canvas.putText( Amedas[i][j], (j-1)*11*5, (i-1)*11, 11, color(0,0,0) ) end end canvas.putflush() touch(3) end main() system.exit()