2024/10/04 追加 Lua 2 -- Metatable
今年の冬休みは Lua で WebDAV のスクリプトを書く事にした。現在は Yuno さんによる Perl によるスクリプトの改造版を使っているものの、実は僕は Perl は吐き気がするほと嫌いだ。あのグロテスクな言語はとてもプログラムに使えたものではない。そこで Lua で書き直すことにしたのだ。
Perl を Lua で置き換えると大きなメリットがある。それらを挙げると:
つまりこれだけのメリットが期待できれば、面倒だけど書き換えたくなるのだ。
なぜ Python ではなく Lua を?
Python は太り過ぎているが Lua は小さい。それに Lua は僕にとって初めての言語だから...
Lua は実行速度において評判の良いプログラミング言語である。言語仕様は欲張ってなく、処理系も小さい。
何故 Lua を? Lua は僕にとって今回が初めての言語である。ついでに Lua の感触を掴もうと言う訳だ。あまり慣れていないプログラミング言語を使って何かをやると行き詰まる事が多い。必要な機能が無い事に気付くのだ。Lua は大丈夫が? 「大丈夫だ」と Lua に関しては自信を持って言える。C とのインターフェースが極めて良いからだ。「不足するものがあれば C を使え」、これが Lua の設計者の思想である。実際 Lua は OS インターフェースを殆どサポートしていない。結局自分で組み込まなくてはならないが、難しくはない。Python の C インターフェースに比べると遥かに良くできている。なんと言っても C インターフェース関数をも含めて Lua の仕様としてドキュメント化されているのが嬉しい。
Lua は初心者向けの言語か? No!
初歩的なプログラムであればどんな言語を使っても大差はない。しかしちょっと高度なプログラム、例えばシステムプログラム、になると Lua の場合には不足するものがすぐに現れる。この場合には Lua では C のライブラリを組み込む必要があるのだ。従って Lua は C のエキスパートが使う言語だと言える。
+
' ではなく、'..
' である。なぜ '+
' にはしなかったかと言えば、Lua では数字と、その文字列表現との区別が曖昧だからだ。"1" + "2"
12
", Lua なら "3
" である。Lua では明確に数字に変換したい時には tonumber()
なる関数が準備されているが、しかし何となく状況に応じてうまく数字に変換されている。しかし僕は厳格な Python 流が好きだ。
Lua の方針: 関数は、処理結果と、ステータスを返すべきである。
nil
と Python の None
を比較する。変数へのこれらの値の代入x = nil # Lua
y = None # Python
y = None
None
が定義されているのである。nil
はもっと意味が強い。x = nil
del x # 名前の削除
Lua では未定義変数は nil
の値をとる。他方 Python では未定義変数を参照するとエラーになる。
Lua のはルーズなやり方である。表面上は Lua の方が便利ではあるが、変数のスペルミスなどの実行時のエラーは Python の方が早く取れる。例えば代入文
x = foo
string.sub(s,2,5)
s[1:5]
2 ≤ i ≤ 5
1 ≤ i < 5
部分文字列はプログラミング時に頻繁に現れるので、Lua の書き方はうんざりする*。
s:sub(2,5)
の書き方が可能である。Lua 5.1 から可能になったらしい。(2012/05/01 追記)
Lua も Python 流を採用した場合に言語設計の他の部分と矛盾するかと言えば、そうでもないらしい。それどころか文字列 s に対して
print(s[2])
Lua を設計した TeCGraf は意外と頑固なんだと思う。「 [ ] の記法は table に対して適用されるのであって、文字列は table ではない」と誘惑を振り切ったんだろうね。君ならどうする?
R"...."
Lua では何故か '|' がサポートされていない。これをサポートした場合には何かネガティブな問題をもたらすので止めたのであろう。どうしても '|' が必要であれば Lua は採用できないことになる。もっとも WebDAV サーバー程度のプログラム(相当複雑な部類に属する)では '|' は必要はなかった。
モジュールの作成は次の例で分かる。
ファイル: foo.lua
print("module foo is being loaded") module("foo",package.seeall) function f() print("OK") end print("module foo is loaded")ここでは関数
f()
をモジュール名 foo
でモジュール化している。最初の print
はモジュールの外での実行、残りの print
はモジュールの中での実行である。
モジュール foo
を呼び出すには:
ファイル: a.lua
#!/usr/local/bin/lua print(package.path) require("foo") foo.f()ここで
package.path
はモジュールが置かれているパスであり、default は以下の実行結果で判明するが、自分で設定してもよい。
実行結果は
-bash$ a.lua ./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua module foo is being loaded module foo is loaded OK
さて分からないのは module
の第二引数(オプション)の package.seeall
である。これによって module foo
の中でグローバル変数が使えるようになる。逆にこれが無ければ、モジュールの中では事実上何も出来ない。そして module
の第二引数で可能なのは(現在のところ)事実上 package.seeall
だけのようである。(こんな事分かんないよねー!)
何故第二引数の default を package.seeall
にしなかったのか?
module
の第二引数がどのように使われるのかを見てみよう。
print("module foo is being loaded") function g() print("g:OK") package.seeall(foo) end module("foo",g) function f() print("f:OK") end print("module foo is loaded")
a.lua
は同じである。実行結果は
-bash$ a.lua ./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua module foo is being loaded g:OK module foo is loaded f:OK
module
の第二引数に package.seeall
以外の関数を与えた場合に、どのように役に立つのだろうか ?package.seeall
以外の関数がサポートされるのかも知れない。
なおモジュールパスの ?
は shell の *
と似ている。例えば lib?.lua
と書けば、ファイル libfoo.lua にジュール foo
を作る事になろう。僕なら ?
ではなく、率直に *
の仕様にするのに...
関数 module
に関して疑問を述べたが、久しぶりに http://www.lua.org/
にアクセスして見ると Lua 5.2 がリリースされていた。マニュアルを読むと、関数 module
は廃止すると言う。(実際には互換性のために残されているが、使わない方が良い)
理由は、関数 module
が無くても問題はないと言う事である。この件に関しては、「モジュールと 関数 require
」で取り上げる。
Lua では長いコメントは
--[[ ... ... --]] </pre> のように書く。"{...}" の部分は何でもよい。この中に "{--]]}" が含まれるなら <pre class="code"> --[=[ ... ... --]=]と書けばよい。"
=
" の個数は実はいくつでもよい。
僕はこのような Lua の仕様に最初は違和感を覚えたが、実に良く出来ている事に気付いた。(気付いたのは今日ではなくもっと以前である。今頃とは言わないで...)
だってえ...
-- [[ ... ... --]]のように "
-
" と "[
" の間に空白を一個入れるだけでコメントが外れるからだ。
最初に Lua に接した時から僕は Lua の local
修飾子に違和感を覚えた。そして今もなお "?" だ。
Lua はなぜ Javascript や他の言語のように var
による変数宣言を採用しなかったのだろうか?
言葉の響きから var
は変数宣言であるが、local
は一般的な修飾子である。修飾子としての local
が意味を持ちそうなものに function
への local
修飾がある。
local function f() print("f") end f()これはエラーになる*。最後の
f()
はグローバルな名前の呼び出しだからである。うーん、local
は C の static
とも違うんだね...
修飾子 local
は module の中では意味を持っている。
module("foo",package.seeall) local function f() print("f") end function g() print("g") end g() f()これを
foo.lua
に保存して-bash$ lua Lua 5.1.2 Copyright (C) 1994-2007 Lua.org, PUC-Rio > require("foo") g f > foo.g() g > foo.f() stdin:1: attempt to call field 'f' (a nil value) stack traceback: stdin:1: in main chunk [C]: ? >つまり
local
の修飾子は module を使った時に合理的な(つまり local
の響き通りの)振る舞いをしている。
関数の中での関数の定義はどうか?
function f() function g() print("g") end print("f") end g() f()これを実行すると
lua: bar.lua:7: attempt to call global 'g' (a nil value) stack traceback: bar.lua:7: in main chunk [C]: ?これは当然。関数
f()
の中で定義された g()
は外からは見えないのだからと思ったら間違いである。function f() function g() print("g") end print("f") end f() g()を実行するとエラーにならない。出力は
f gエラーにならなかったのは、
f()
が実行されることによって g
がグローバルな名前空間(Lua では _G
)に持ち込まれるからだ。
そこで今度は
function f() local function g() print("g") end print("f") end f() g()を実行してみる。すると
f lua: bar.lua:8: attempt to call global 'g' (a nil value) stack traceback: bar.lua:8: in main chunk [C]: ?つまり
local
の役割はグローバルな名前空間に名前が入るのをブロックしているのだ。
しかし、何と言うシンプルな割り切り方だ! このシンプルさによって、Lua の実装は簡単になったであろう。その代償は、直感の犠牲である。宣言の構造が名前空間を決めるのではなく、実行の流れが名前空間を決めているのであるから直ぐには分からない...
import
は Python でモジュールを使う時の宣言子である。モジュール foo
を使う時には
import foo
require()
がある。但し 宣言子ではなく、関数である。require("foo")
from foo import *
foo
から全ての名前を取り込める。また *
の部分にfrom foo import bar,baz
Lua の場合には、もしも欲しいなら、自分で作らなくてはならない。次のコードが参考になるだろう。
function import(t,...) local u = {...} if u[1] == "*" then for k,v in pairs(t) do _G[k] = v end else for k,v in ipairs(u) do _G[v] = t[v] end end end
これは次のように実行する。
t = require("foo") import(t,"*")
モジュール foo
の中の、bar
, baz
を取り込みたいなら
t = require("foo") import(t,"bar","baz")とやればよい。
require()
と import()
の二つを使うのが嫌なら、import()
を一つに纏めた仕様にすることもできるだろう。
しかし、僕は import()
は要らないと思う。代わりに
require("foo") bar = foo.bar baz = foo.bazとやればよいだけだから。(名前を変えて取り込む事もできる。Python でも同じだけどもね...)
module
の廃止が予告されている。この関数は、モジュールの中で定義されている関数が、モジュールを呼び出した際に、そのまま名前空間に入って行くのを防いでいる。例えば
を呼び出すプログラム
require("hoge") foo() bar("This is bar")は、次の出力を出す。
This is foo This is bar
つまり、require("hoge")
は Python の
from hoge import *
hoge
の全ての名前を取り込む。これは、名前の衝突を引き起こす可能性があり、多くのモジュールを持ち出すと、そのリスクが増大する。module
関数は、仕様に変な部分があるが、その問題に対する1つの回答だったのである。
では、どうしたら良いか? Lua 5.2 のマニュアルには、Lua の一般的な方法の中で問題を解決できるのだと言う。(しかし具体的な解決策は示されていない)
次のようなのはどうか?
このように書けば、グローバルな名前空間に取り込まれるのは hoge
だけである。
require("hoge") hoge.foo() hoge.bar("This is bar")
結果は
This is foo This is bar
となる。つまり Python の
import hoge
import hoge as fuga
hoge
だって衝突するかも知れないから、代替え手段が欲しいのである。
理想的なのは、require
が、モジュールの中で定義されている名前テーブルを返してくれることである。どうすれば良いか? 筆者は今日になって、ようやく方法が存在する事が分かった。モジュールの最後の return
で解決するのだ。
その下で
fuga = require("hoge") fuga.foo() fuga.bar("This is bar")のように実行するのである。
require("hoge")
を2度実行しても、二重にはロード(実行)されない。Lua にはモジュールの二重実行を防ぐための仕組みがある。hoge
が汎用的なモジュールであれば、複数のモジュールから hoge
が呼び出されるであろう。hoge
がデータを持つ場合には、そのデータも共有される。それが問題をもたらすようであれば、closure を使って、データを独立させる必要がある。
実行例を示す。手軽に利用できる一番大きなテーブルは _G
である。_G
のテーブルは、参照関係が循環しているので、注意してコードを書かないと、無限ループにハマる。図4のプログラムでは、一度書き出したテーブルは、再度書かないことで、この無限ループを避けている。「# done
」と表示されているテーブルは、既出のテーブルなのである。
ソートした方が良かったかも... (iPad だと全部が見えない...)
Lua は XML の処理に使いやすい言語だと思う。Lua で扱うデータ構造は十分に強力だし、正規表現による文字列の操作も扱いやすい。Python の場合には正規表現を使う前にはコンパイルしなくてはならないので面倒であるが、Lua はその必要はないのはありがたい。(2009/04/04)
http://lua-users.org/wiki/LuaXml
に載っていた。
function parseargs(s) local arg = {} string.gsub(s, "(%w+)=([\"'])(.-)%2", function (w, _, a) arg[w] = a end) return arg end function collect(s) local stack = {} local top = {} table.insert(stack, top) local ni,c,label,xarg, empty local i, j = 1, 1 while true do ni,j,c,label,xarg, empty = string.find(s, "<(%/?)(%w+)(.-)(%/?)>", i) if not ni then break end local text = string.sub(s, i, ni-1) if not string.find(text, "^%s*$") then table.insert(top, text) end if empty == "/" then -- empty element tag table.insert(top, {label=label, xarg=parseargs(xarg), empty=1}) elseif c == "" then -- start tag top = {label=label, xarg=parseargs(xarg)} table.insert(stack, top) -- new level else -- end tag local toclose = table.remove(stack) -- remove top top = stack[#stack] if #stack < 1 then error("nothing to close with "..label) end if toclose.label ~= label then error("trying to close "..toclose.label.." with "..label) end table.insert(top, toclose) end i = j+1 end local text = string.sub(s, i) if not string.find(text, "^%s*$") then table.insert(stack[#stack], text) end if #stack > 1 then error("unclosed "..stack[stack.n].label) end return stack[1] end次にこのコードの実行例を載せる。
content = [[ <?xml version="1.0" encoding="utf-8"?> <D:lockinfo xmlns:D="DAV:"> <D:lockscope><D:exclusive/></D:lockscope> <D:locktype><D:write/></D:locktype> <D:owner> <D:href>http://www.apple.com/webdav_fs/</D:href> </D:owner> </D:lockinfo> ]] s = collect(content) print(s[1]) print(s[2].label, s[2].xarg) t = s[2].xarg for k,v in pairs(t) do print(k,v) endこの結果を次に示すが明らかにおかしい。
<?xml version="1.0" encoding="utf-8"?> D table: 0x6F488 D DAV:どうやら問題のプログラムコードは名前ラベルが存在しない事を前提にしているのだ。それにしても何で "label" を意識したコードが含まれているのだ?
XML のタグは
<NAME ATTRS> <NAME ATTRS/> </NAME>
NAME
はD:lockinfo
ATTRS
はxmlns:D="DAV:"
NAME
のシンタックスは単純である。NAME
の中には空白は許されない。NAME
の中に /<>
は許されない。
また ATTRS
は空白で区切られた ATTR
のリストである。ここでのシンタックス規則は単に
ATTRS
の中に /<>
は許されない。
次のプログラムの関数名は
xml.collect
.
" を含む名前付けを可能にするためにはxml = {}
function xml.collect(s) local ns local stack = {} local top = {} local ni,c,name,attrs, empty local i,j,decl =string.find(s, "<%?xml *(.-)%?>") table.insert(stack, top) j = j + 1 i = j while true do -- tags have the form: "<NAME ATTRS/?>", "<NAME/?>", "</NAME>", ni,j,c,name,attrs, empty = string.find(s, "<(/?)([^%s/<>]+) *(.-)(/?)>", i) -- print("DBG0: ",ni,j,c,"/",name,attrs,"/",empty) if not ni then break end local text = string.sub(s, i, ni-1) if not string.find(text, "^%s*$") then table.insert(top, text) end if empty == "/" then -- empty element tag local e e = {name=name, attrs=attrs} table.insert(top,e) elseif c == "" then -- start tag top = {name=name, attrs=attrs} table.insert(stack, top) -- new level else -- end tag local toclose = table.remove(stack) -- remove top top = stack[#stack] if #stack < 1 then error("nothing to close with "..name) end if toclose.name ~= name then error("trying to close "..toclose.name.." with "..name) end table.insert(top, toclose) end i = j+1 end local text = string.sub(s, i) if not string.find(text, "^%s*$") then table.insert(stack[#stack], text) end if #stack > 1 then error("unclosed "..stack[stack.n].name) end return stack[1] endこのコードに現れる正規表現であるが
string.find(s, "<(/?)([^%s/<>]+) *(.-)(/?)>", i)
(.-)
([^/<>]*)
次のプログラムは xml.collect が集めた結果を分析するのに役に立つ。
function xml.printstack(t, Ntab) -- this function shows the structure of stack returned from xml.collect() -- please call me by: t = xml.collect(content); xml.printstack(t) if t == nil then return end if Ntab == nil then Ntab = 0 end if type(t) == "string" then print(Ntab,'T', t) -- text outside of tags return end Ntab = Ntab + 1 -- Ntab: tag level (namespace level) -- #t: number of element, starting tag (if #t>0) or empty tag (if #t == 0) -- t.name: tag name; t.attrs: tag attributes print(Ntab,#t,t.name, t.attrs) for i=1, #t do xml.printstack(t[i], Ntab) end print(Ntab,"/",t.name) Ntab = Ntab - 1 end実行例は
content = [[ <?xml version="1.0" encoding="utf-8"?> <D:lockinfo xmlns:D="DAV:"> <D:lockscope><D:exclusive/></D:lockscope> <D:locktype><D:write/></D:locktype> <D:owner> <D:href>http://www.apple.com/webdav_fs/</D:href> </D:owner> </D:lockinfo> ]] s = xml.collect(content) print(s[1].name) print(s[1][1].name) print(s[1][1][1].name) xml.printstack(s[1])この結果は
term% lua libxml.lua D:lockinfo D:lockscope D:exclusive 1 3 D:lockinfo xmlns:D="DAV:" 2 1 D:lockscope 3 0 D:exclusive 3 / D:exclusive 2 / D:lockscope 2 1 D:locktype 3 0 D:write 3 / D:write 2 / D:locktype 2 1 D:owner 3 1 D:href 3 T http://www.apple.com/webdav_fs/ 3 / D:href 2 / D:owner 1 / D:lockinfo term%読者は
xml.printstack
のコードを利用して、xml.collect
の結果を必要に応じて様々に加工する関数を書く事が事ができる。
collect()
を使ってみると、分かり難さを感じる。諸悪の根源は collect()
が情報を十分に組織化していないことにある。例えば node
を collect()
から得られたノードの一つとする。そのときに node[1] が表しているものは、type(node[1])
が string
ならそのノードのテキストデータ、table
なら子供のノードである。このような非一貫性が collect()
の使い心地を酷く損なっている。次に示す mktree()
はこの問題を解決している。(2009/04/04)
さて僕は最近、現実に現れるある複雑な XML データ(XBRL のデータ)を処理していて、強力なツールを必要とした。そのために開発したのが次の mktree()
である。この関数は気に入っているので紹介しておく。
XML のノードは、名前(tag name)と属性(tag attributes)とノードに属するテキストデータを持っている。さらに一般的に言えば、親と子供たちと繋がっている。
<definition>The <term>computer</term> is blah blah ... </definition>
ノードを N
とすると、
N.name # node name N.attrs # node attributes N.attr # elements of the attributes N.data # value of the node N.parent # parent node N.no # array index of this node referenced by the parent N[1] # node of the child 1 N[2] # node of the child 2 ... N[n] # node of the child n
n
は #N
に等しい。また<comment id="X0123" name="alice" age="18">Hello everyone, my name is alice.</comment>
N.name
は "comment
" であり、N.attrs
はid="X0123" name="alice" age="18"
N.data
はHello everyone, my name is alice.
data
があれば子供は無い。子供があれば、data
は無い。i
番目の子供のノードを表現するなら N[i]
ではなくN.child[i]
N
に対して形式的に N[1],N[2],...
を使えるけれども、それらは辞書の要素であって、リストではない。あとのデータ処理を考えると好ましい考え方ではない。(実行速度の問題、加工に伴うよけいな複雑さ、子供の個数の管理など) Lua の table は Python で言う辞書とリストを兼ねているのである。
一般的に言えば N.attrs
は要素を持っており、それらを簡単に参照したいとするニーズは高い。そこで N.attr
を導入した。このケースでは
N.attr["id"] # "X0123" N.attr["name"] # "alice" N.attr["age"] # "18"
N.no
は親ノードから見た、そのノードの配列番号である。従って
N.parent[N.no]
N.parent[N.no + 1]
さらに DOM 的な機能を実現しやすい情報を追加する。すなわち、ノードの名前(tag name) と、属性に現れる id
の一覧のデータベースを返す。つまり XML 仕様の文字列 xmldata
をインプットとして
node,tags,ids = mktree(xmldata)
node
の他に、タグ名の一覧 tags
と id
の一覧 ids
を返す。一般的に言えば、同じ名前のタグ名は複数存在するので tags
は Lua の配列であり、for i,v in ipairs(tags["comment"]) do print(v.attr.name,v.attr.age) # alice 18 -- etc print(v.data) # Hello everyone, my name is alice. --- etc end
id
の場合には、一意に決まるはずだから繰り返し文を使わなくてもv = ids["X0123"] print(v.attr.name,v.attr.age) # alice 18 print(v.data) # Hello everyone, my name is alice
match = string.match find = string.find local function deco(a) -- decompose attrs local t = {} local s,b,c,d local q = "'\"" if a == nil then return nil end s = split(a) for i=1,#s do a,b,c,d = match(s[i],"([^=]+)=(["..q.."])([^"..q.."]*)(["..q.."])") if a == nil or b ~= d then print("## invalid xml attr format: ",s[i]) else t[a] = c end end return t end local function putdb(e,names,ids) if names[e.name] == nil then names[e.name] = {} end table.insert(names[e.name], e) if e.attr and e.attr.id then if ids[e.attr.id] then error("same id: "..e.attr.id) end ids[e.attr.id] = e end end function mktree(s) -- modified collect() -- mktree() has a DOM like ability -- a powerful tool to evaluate xml data local ns local stack = {} local top = {} local ni,c,name,attrs, empty local i,j,decl =find(s, "<%?xml *(.-)%?>") local ids = {} -- id table local names = {} -- tag name table if j == nil then i = 1 else i = j + 1 end while true do -- tags have the form: "<NAME ATTRS/?>", "<NAME/?>", "</NAME>", -- we must skip comment! we cant't use "repeat ... until" here ni,j,c,name,attrs, empty = find(s, "<(/?)([^%s/>]+) *(.-)(/?)>", i) while name == "!--" do i = j+1 ni,j,c,name,attrs, empty = find(s, "<(/?)([^%s/>]+) *(.-)(/?)>", i) end -- here attrs == "" if no attr but we make it nil if attrs == "" then attrs = nil end if not ni then break end local text = sub(s, i, ni-1) if not find(text, "^%s*$") then top.data = text end if empty == "/" then -- empty element tag local e,attr attr = deco(attrs) e = {name=name, attrs=attrs, attr=attr, parent=stack[#stack], no=#top+1} table.insert(top,e) putdb(e,names,ids) elseif c == "" then -- start tag local e,attr attr = deco(attrs) e = {name=name, attrs=attrs, attr=attr, parent=stack[#stack], no=#top+1} table.insert(stack, e) -- new level putdb(e,names,ids) top = e else -- end tag close = table.remove(stack) -- remove top if close.name ~= name then error("inconsitent open and close tag: "..close.name.." with "..name) end if #stack < 1 then break end top = stack[#stack] -- stack[0] == nil table.insert(top, close) end i = j+1 end if #stack > 1 then error("unclosed "..stack[#stack].name) end return close,names,ids end
ここに現れる split()
は次のように定義されている。
gsub = string.gsub find = string.find function split(s,sep) local i,j,k local t={} if sep == nil then gsub(s,"([^%s]+)",function(a) table.insert(t,a) end) return t end i = 1 while true do j,k = find(s,sep,i,true) if j == nil then table.insert(t,sub(s,i)) break end table.insert(t,sub(s,i,j-1)) i = k + 1 end return t end
以上のコードは自由に利用しても構わないが、出典だけは明示して欲しい。
You can use the codes above freely under the Lua license 5.0 and later versions (http://www.lua.org/license.html)
mkidf
現実に使われる XML のデータは複雑である。例えばビジネスに使われる会計データの仕様(XBRL)が最近から運用されだしている。公開されている企業別のデータを見てみると、実に複雑で大きくて、一つの企業の処理に関係する XML で書かれたファイルは数十個に及ぶ。その中の一つの冒頭部部だけを示すと
<?xml version="1.0" encoding="utf-8"?> <!-- Edited with XiRUTE .NET Library 01-04 --> <xbrli:xbrl xmlns:iso4217="http://www.xbrl.org/2003/iso4217" xmlns:jpfr-q3r-E02149-000="http://info.edinet-fsa.go.jp/jp/fr/gaap/E02149-000/q3r/2008-12-31/01/2009-02-12" xmlns:jpfr-t-cte="http://info.edinet-fsa.go.jp/jp/fr/gaap/t/cte/2008-02-01" xmlns:link="http://www.xbrl.org/2003/linkbase" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpfr-di="http://info.edinet-fsa.go.jp/jp/fr/gaap/o/di/2008-02-01" xmlns:xbrli="http://www.xbrl.org/2003/instance"> <link:schemaRef xlink:type="simple" xlink:href="jpfr-q3r-E02149-000-2008-12-31-01-2009-02-12.xsd" /> <link:roleRef xlink:type="simple" xlink:href="http://info.edinet-fsa.go.jp/jp/fr/gaap/o/rt/2008-02-01/jpfr-rt-2008-02-01.xsd#RoleNotesNumber" roleURI="http://info.edinet-fsa.go.jp/jp/fr/gaap/role/NotesNumber" /> <link:roleRef xlink:type="simple" xlink:href="http://info.edinet-fsa.go.jp/jp/fr/gaap/o/rt/2008-02-01/jpfr-rt-2008-02-01.xsd#RoleConsolidatedQuarterlyStatementsOfIncomeYTD" roleURI="http://info.edinet-fsa.go.jp/jp/fr/gaap/role/ConsolidatedQuarterlyStatementsOfIncomeYTD" /> <link:roleRef xlink:type="simple" xlink:href="http://info.edinet-fsa.go.jp/jp/fr/gaap/o/rt/2008-02-01/jpfr-rt-2008-02-01.xsd#RoleConsolidatedQuarterlyStatementsOfIncomeQuarter" roleURI="http://info.edinet-fsa.go.jp/jp/fr/gaap/role/ ConsolidatedQuarterlyStatementsOfIncomeQuarter" /> <link:roleRef xlink:type="simple" xlink:href="http://info.edinet-fsa.go.jp/jp/fr/gaap/o/rt/2008-02-01/jpfr-rt-2008-02-01.xsd#RoleNotesNumberPeriodEnd" roleURI="http://info.edinet-fsa.go.jp/jp/fr/gaap/role/NotesNumberPeriodEnd" /> <link:roleRef xlink:type="simple" xlink:href="http://info.edinet-fsa.go.jp/jp/fr/gaap/o/rt/2008-02-01/jpfr-rt-2008-02-01.xsd#RoleConsolidatedQuarterlyBalanceSheets" roleURI="http://info.edinet-fsa.go.jp/jp/fr/gaap/role/ConsolidatedQuarterlyBalanceSheets" /> <link:roleRef xlink:type="simple" xlink:href="http://info.edinet-fsa.go.jp/jp/fr/gaap/o/rt/2008-02-01/jpfr-rt-2008-02-01.xsd#RoleConsolidatedQuarterlyStatementsOfCashFlowsIndirect" roleURI="http://info.edinet-fsa.go.jp/jp/fr/gaap/role/ConsolidatedQuarterlyStatementsOfCashFlowsIndirect" /> <xbrli:context id="CurrentQuarterConsolidatedInstant"> <xbrli:entity> <xbrli:identifier scheme="http://info.edinet-fsa.go.jp">E02149-000</xbrli:identifier> </xbrli:entity> <xbrli:period> <xbrli:instant>2008-12-31</xbrli:instant> </xbrli:period> </xbrli:context> .... ....のようなものである。 実際のこのデータ
a.xbrl
このようなデータを眺めていると XML データというのは人間にとって、そのデータ構造が分かり難いものだと思う。このケースでは特に属性部の記述量が多くて、見難くしている。もっと分かりやすい表現法がないととても全体像が捉えられないであろう。そこで次のような形式で表示するツールを作った。
# file: jp/fr/gaap/E02149-000/q3r/2008-12-31/01/2009-02-12/jpfr-q3r-E02149-000-2008-12-31-01-2009-02-12.xbrl # format: idf-1.0 # Edited with XiRUTE .NET Library 01-04 xbrli:xbrl - xmlns:iso4217="http://www.xbrl.org/2003/iso4217" - xmlns:jpfr-q3r-E02149-000="http://info.edinet-fsa.go.jp/jp/fr/gaap/E02149-000/q3r/2008-12-31/01/2009-02-12" - xmlns:jpfr-t-cte="http://info.edinet-fsa.go.jp/jp/fr/gaap/t/cte/2008-02-01" - xmlns:link="http://www.xbrl.org/2003/linkbase" - xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:jpfr-di="http://info.edinet-fsa.go.jp/jp/fr/gaap/o/di/2008-02-01" - xmlns:xbrli="http://www.xbrl.org/2003/instance" link:schemaRef - xlink:type="simple" - xlink:href="jpfr-q3r-E02149-000-2008-12-31-01-2009-02-12.xsd" link:roleRef - xlink:type="simple" - xlink:href="http://info.edinet-fsa.go.jp/jp/fr/gaap/o/rt/2008-02-01/jpfr-rt-2008-02-01.xsd#RoleNotesNumber" - roleURI="http://info.edinet-fsa.go.jp/jp/fr/gaap/role/NotesNumber" link:roleRef - xlink:type="simple" - xlink:href="http://info.edinet-fsa.go.jp/jp/fr/gaap/o/rt/2008-02-01/jpfr-rt-2008-02-01.xsd#RoleConsolidatedQuarterlyStatementsOfIncomeYTD" - roleURI="http://info.edinet-fsa.go.jp/jp/fr/gaap/role/ConsolidatedQuarterlyStatementsOfIncomeYTD" link:roleRef - xlink:type="simple" - xlink:href="http://info.edinet-fsa.go.jp/jp/fr/gaap/o/rt/2008-02-01/jpfr-rt-2008-02-01.xsd#RoleConsolidatedQuarterlyStatementsOfIncomeQuarter" - roleURI="http://info.edinet-fsa.go.jp/jp/fr/gaap/role/ConsolidatedQuarterlyStatementsOfIncomeQuarter" link:roleRef - xlink:type="simple" - xlink:href="http://info.edinet-fsa.go.jp/jp/fr/gaap/o/rt/2008-02-01/jpfr-rt-2008-02-01.xsd#RoleNotesNumberPeriodEnd" - roleURI="http://info.edinet-fsa.go.jp/jp/fr/gaap/role/NotesNumberPeriodEnd" link:roleRef - xlink:type="simple" - xlink:href="http://info.edinet-fsa.go.jp/jp/fr/gaap/o/rt/2008-02-01/jpfr-rt-2008-02-01.xsd#RoleConsolidatedQuarterlyBalanceSheets" - roleURI="http://info.edinet-fsa.go.jp/jp/fr/gaap/role/ConsolidatedQuarterlyBalanceSheets" link:roleRef - xlink:type="simple" - xlink:href="http://info.edinet-fsa.go.jp/jp/fr/gaap/o/rt/2008-02-01/jpfr-rt-2008-02-01.xsd#RoleConsolidatedQuarterlyStatementsOfCashFlowsIndirect" - roleURI="http://info.edinet-fsa.go.jp/jp/fr/gaap/role/ConsolidatedQuarterlyStatementsOfCashFlowsIndirect" xbrli:context - id="CurrentQuarterConsolidatedInstant" xbrli:entity xbrli:identifier - scheme="http://info.edinet-fsa.go.jp" * E02149-000 xbrli:period xbrli:instant * 2008-12-31 .... ....
ここに
#
で始まる行はコメントである。XML のコメントの他に、あらたにビューアのコメントが追加されている。-
で始まる行は属性である。*
で始まる行はタグに属するデータである。a.xbrl
をこの形式(僕は IDF と読んでいる)に変換したファイルも参考のために見えるようにしておく。a.idf
次に示すのは関数 mkidf()
と mkxml()
である。関数 mkidf()
は XML ファイルを読み取り、IDF 形式で出力する。関数 mkxml()
はその逆を行う。mkxml()
ではインデントが指定できるので、この二つの関数の組み合わせで、XML ファイルをインデントを指定して整形できる。なお mkidf()
のインデントは TAB 固定である。インデントが命であるから、空白とタブの混合はサポートしない。(Python はプログラムのブロックをインデントで表しているが、そのようなやりかたは、データ表現に対しては重大な問題を引き起こすだろう)
local function mksp(n) s = "" for i=1,n do s = s .. "\t" end return s end function mkidf(file) -- file: xml file -- print in indented data format(idf) -- idf is useful to view logical structure of xml file local ni,c,name,attrs, empty local stack = {} local s,t local f print("# format: idf-1.0") f = io.open(file) if f == nil then print("no such file: "..file) return end s = f:read("*a") f:close() local i,j,decl =find(s, "<%?xml *(.-)%?>") if j == nil then i = 1 else i = j + 1 end while true do -- tags have the form: "<NAME ATTRS/?>", "<NAME/?>", "</NAME>", ni,j,c,name,attrs, empty = find(s, "<(/?)([^%s/>]+) *(.-)(/?)>", i) while name == "!--" do -- <!-- comments --> print(mksp(#stack).."# "..sub(s,ni + 5,j - 4)) i = j+1 ni,j,c,name,attrs, empty = find(s, "<(/?)([^%s/>]+) *(.-)(/?)>", i) end -- here attrs == "" if no attr but we make it nil if attrs == "" then attrs = nil end if not ni then break end local text = sub(s, i, ni-1) if not find(text, "^%s*$") then print(mksp(#stack - 1).."* "..text) end if empty == "/" or c == "" then -- empty element tag or starting tag print(mksp(#stack)..name) if attrs then t = split(attrs) for i=1,#t do print(mksp(#stack).."- "..t[i]) end end if empty ~= "/" then -- start tag table.insert(stack,name) end else -- end tag local toclose = table.remove(stack) -- remove top if toclose ~= name then error("trying to close "..toclose.." with "..name) end end i = j+1 end end function mkxml(file,indent) -- make xml file from idf file -- reverse of mkidf() local function sp(n) local s = "" for i=1,n do s = s .. " " end return s end local s,t,i,stack,t0,t00 if indent == nil then indent = 0 end stack = {} io.write('<?xml version="1.0" encoding="utf-8"?>\n') i = -1 for line in io.lines(file) do s = match(line,"^(\t*)") t = sub(line,#s + 1) if sub(t,1,1) == " " then print("\n### use tabs for indent") os.exit() end t0 = sub(t,1,2) if t0 == "- " then -- an attribute/value pair io.write(" "..sub(t,3)) elseif t0 == "* " then -- a data value if t00 and t0 ~= t00 then io.write(">") end io.write(sub(t,3)) elseif sub(t,1,2) == "# " then -- comments io.write(sp(#stack * indent).."<!-- "..sub(t,3).." -->\n") elseif match(t,"^%w") then -- a name -- we have 9 cases: -- t00 = "- "; t00 = "* "; others -- #s > i; #s == i; #s < i if #s > i then -- new level, t must be a name -- note that t00 ~= "* " here if i ~= -1 then io.write(">\n") end table.insert(stack,name) if #s ~= #stack then print("# invalid indent") os.exit() end io.write(sp(#s * indent).."<"..t) else if t00 == "* " then io.write("</"..name..">\n") else io.write("/>\n") end if #s < i then -- back to low level until #s == i repeat name = table.remove(stack) io.write(sp(#stack * indent).."</"..name..">\n") until #s == #stack end io.write(sp(#s * indent).."<"..t) end name = t i = #s else print("# invalid format") os.exit() end t00 = t0 end if t00 == "* " then io.write("</"..name..">\n") else io.write("/>\n") end while #stack > 0 do name = table.remove(stack) io.write(sp(#stack * indent).."</"..name..">\n") end end
関数 mkidf
は mktree()
を使えばもっと簡単に書けるのだが、その場合にはテータを全てメモリーに読み取る事になるので、実行効率の良い mkidf()
を新たに書いた。
エンコードやデコードを行っていないので注意する。
以上のコードは自由に利用しても構わないが、出典だけは明示して欲しい。
You can use the codes above freely under the Lua license 5.0 and later versions (http://www.lua.org/license.html)
性格の違うものを一緒に議論するのは混乱の元である。XML に関してはこの点はどうか? XML は XBRL のようにコンピュータによるデータ処理のためのものなのか? それとも HTML のように人に読ませるためのドキュメント処理のためのものなのか? あるいはその両方を目指しているのか?
この二つは、共通部分もあるが、基本的に正反対の方向である。
前者の場合には、処理の誤りは致命的である。さらに悪い事にその処理の誤りが見え難い。データ形式については組織化できるし、組織化しなくてはならない。
他方後者の場合には自由が求められる。処理の誤りは直ちに人目に曝され、致命的な結果をもたらす事はない。
一例を挙げよう。前者の場合には計算処理に使われるデータは
<foo>.....</foo>
.....
" の中には他のタグを入れるべきではない*。しかし後者の場合にそのような制約を課していたら文書を作れない。
mktree()
も mkidf()
も mkxml()
もこの規則が守られていることを前提にしている。
XML の仕様書は膨大であり、読む気にもなれない。膨大になって行く原因は、スーパーセットを目指しているからだ。しかしスーパーセット主義は失敗する事を歴史が教えている。
ムーアの法則が示しているように、コンピュータの処理能力は指数関数的に発展している。プログラムの複雑さはどうか? Python や Lua を使っていると、随分楽になったなと思う。新しい言語の出現がプログラムの複雑さを時々大きく緩和してくれるのだ。しかし規格書の複雑さはどうか? XML の仕様書を見ると、プログラマが読まなくてはならない文書が指数関数的に増大し複雑になっていると感じる。やがては人間の能力の限界を越えるであろう。何かがまずいのである。
HTTP のように TCP レベルのプログラムを書くプログラマは、その下位のレベルのプロトコル(IP や イーサネットレベル)を詳しく知る必要は無い。HTML レベルの文書を作る場合にも、同様に HTTP レベルの詳しい知識は要らない。そのような階層化された構造を持っているからこそ、発展可能なのである。XML に関しては無理なのか?