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

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

よく使う関数をモジュール化してみる

Lua言語でiPhoneAndroidの両方のプログラムが作れるCoronaという開発ツールがありまして、今週末、「日本Coronaの会 関西 第一回 初心者向け勉強会」というものが開催されます。
同じLua言語の開発ツールなので、Coronaにも興味がありまして、参加申し込みしました。
せっかく参加するので、何かLuaのプログラムを作ろうかなと思っていたのですが、Luaridaのプログラムで、Luaridaに依存しないプログラムはCoronaでも動くのではないかと思い、じゃ、そういうのをモジュールにしてみようと思って作ってみました。

Luaのモジュール化

Luaのモジュールは、普通にLuaのプログラムを作って、それを別のLuaプログラムがrequireで呼び出すだけでできるのですが、それだと、モジュール内と呼び出しプログラムで関数名が被っているとまずいことになります。そこで、モジュール側にモジュール宣言を行って、関数名の被りを避けられるようにします。

--モジュール宣言-------------------------
module( "xmlt", package.seeall )

moduleの第1引数は、モジュールのテーブル名です。このテーブルに関数が作られるので、関数の呼び出しは、例えば、xmlt.foo()とかになります。
moduleの第2引数は、module宣言をしてしまうと、元々ある関数 math.なども宣言しないと呼び出せなくなってしまうので、package.seeall と引数をつけると、元々ある関数は宣言しなくても呼び出せるようになります。

モジュールサンプル

Luaridaでモジュールを使うサンプルを作ってみたいと思います。
luaridamm.luaというモジュールを作ります。

--モジュール宣言---luaridamm.lua----
module( "mm", package.seeall )
function sqx( x )
  return x*x
end

このモジュールは、sdcard/luaridaフォルダに保存するとします。関数 sqx を呼び出すときには、mm.sqx( 2 )のようになります。
モジュールを呼び出す側のサンプルを書きます。package.pathにモジュールの場所を指定することにより、require でモジュールを呼び出すことができます。?.luaとなっているのは、"何とか.lua"というモジュールファイルを複数置きたいときに使えます。

--モジュール読み込み宣言
package.path =  system.getCardMnt().."/luarida/?.lua"
require( "luaridamm" )

x = mm.sqx( 6 )
dialog( "6*6", "="..x,1 )
system.exit()

これを実行する下のようなダイアログが出て、関数 sqx(x)が呼び出されていることがわかります。

簡単なXMLパーサのモジュールを作ってみました

以前、気象庁のデータを読み込むプログラムを作りましたが、これのXMLパーサ部分はLuaridaに依存しないプログラムなので、これをモジュール化してみたいと思います。そして、Coronaの勉強会に持って行きたいと思いますが、まぁきっと既にあるのでしょうね(^^;。
下記にモジュール化したプログラムを書きます。(追記、モジュール内でローカルに使う関数は、localを付けて外から見えなくしました)

------------------------------------------
--XMLパーサモジュール(xml_t_module.lua)
------------------------------------------
--モジュール宣言-------------------------
module( "xmlt", package.seeall )
--関数宣言--------------------------------
split={}        --文字の分解
commentSkip={}  --コメントをスキップします
skipSpc={}      --スペースやタブを読み飛ばします
tagTopFound={}  --記号(kigou)に指定された文字が来るまで読み込みます
getTag={}       --タグの要素を配列に入れて返し、データも取得して返します
------------------------------------------
--文字の分解
------------------------------------------
local 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
------------------------------------------
--コメントをスキップします
--">"文字を読み込んで終了します
--nilが返れば失敗しています
------------------------------------------
local function commentSkip( 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
------------------------------------------
--スペースやタブを読み飛ばします
--スペースやタブの次に来る一文字が戻ります
--nilが返れば失敗しています
------------------------------------------
local function skipSpc( 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
------------------------------------------
--記号(kigou)に指定された文字が来るまで読み込みます
--nilが返れば失敗しています
------------------------------------------
local function tagTopFound( 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
------------------------------------------
--タグの要素を配列に入れて返し、データも取得して返します
------------------------------------------
function getTag( fp, tagname )
local str
local tbl={}
local closeflg = false
local data = ""
 while(true)do
   --記号(kigou)に指定された文字が来るまで読み込みます
   if( tagTopFound( fp, "<" )==nil )then break end
   --スペースやタブを読み飛ばします
   str = skipSpc( fp )
   if( str==nil )then
     break
   elseif( str=="!" )then
     --コメントをスキップします
     if( commentSkip( 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 = skipSpc( 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

XMLパーサモジュールを呼び出すサンプル

XMLパーサモジュールを呼び出すサンプルを作ってみました。以前ブログに書いたものと同じですが、気象庁のサイトがShift-JISコードだったのものが、知らない間にBOM無しUTF-8に変わっていました。なので、プログラムも変更しました。
修正したプログラムを再掲載します。先に取得結果画像を貼っておきます。

------------------------------------------
--アメダスデータの取得
------------------------------------------
--モジュール読み込み宣言
package.path =  system.getCardMnt().."/luarida/?.lua"
require( "xml_t_module" )  --xmlパーサ(xmlt.***)

--関数宣言--------------------------------
main={}           --mainメソッド
gethtml={}        -- hmtlを取得
getAmedasTable={} --アメダスのデータを取得します

--グローバル変数宣言----------------------
Amedas = {}       --アメダスのデータを取得するテーブル
HttpUrl = "http://www.jma.go.jp/jp/amedas_h"   --アメダスURL
Gwide, Gheight = canvas.getviewSize()          --画面サイズ取得
LuaridaPath = system.getCardMnt().."/luarida"  --luaファイルを保存しているPath
------------------------------------------
--アメダスのデータを取得します
------------------------------------------
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 = xmlt.getTag( 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 = xmlt.getTag( 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 = xmlt.getTag( 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!" )

 --アメダスのデータを取得します
 Amedas = getAmedasTable( hmtlname )

 --取得データを一覧する
 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()

以上です。