コンピュータを楽しもう!!

今、自分が面白くていろいろやってみたことを書き綴りたいと思います。連絡先はtarosa.yでgmail.comです。

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を解釈していません。単純に指定したタグ名の後に書かれた返すのみです。例えば、下記のような場合、xmlGetTag( fp, "tag" )としてもデータは""が返ります。

<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=="&nbsp;" )then str="" end
           dtable[j][i] = str
         else
           tbl, str = xmlGetTag( fp, "td" )
           if( str==nil )then break end
           if( str=="&nbsp;" )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()