表格序列化 |
|
設計決策包括
loadstring
(如果是,則採用哪種表格建構樣式?)、二進制 I/O、XML、JSON、YAML、HTML、協議緩衝區等來完成?table.foreach(t, print)
)?loadstring
,則可以使用自訂環境,雖然這無法防止無限迴圈。)由於這些不同的需求,已經有很多實作了。
table.tostring
和 table.print
[1] 函數(可在 MetaLua 外部使用)。這些函數著重於自訂的美印,但不會保留循環或序列化函數。(請參閱以下範例。)load
/loadstring
的安全替代方案。目的類似於 JSON.parse()
與 JavaScript eval()
。
此範例不注重序列化後的表格可讀性。與此頁的其他範例相反,它會保留共用子結構。以下是金屬語言序列化會通過,但精緻列印器無法通過的測試範例
> x={ 1 } > x[2] = x > x[x] = 3 > x[3]={ 'indirect recursion', [x]=x } > y = { x, x } > x.y = y > assert (y[1] == y[2]) > s = serialize (x) > z = loadstring (s)() > assert (z.y[1] == z.y[2]) > =s local _={ } _[1]={ "indirect recursion" } _[2]={ false, false } _[3]={ 1, false, _[1], ["y"] = _[2] } _[3][2] = _[3] _[1][_[3]] = _[3] _[3][_[3]] = 3 _[2][1] = _[3] _[2][2] = _[3] return _[3] >
這些函式專為美化列印設計,而非序列化:它們不會保留恆等性。不過,它們會終止:如果表格自我參照,會將內部出現的內容列印為「[table: 0x12345678]」,避免無限遞迴。
require "table2" require "string2" local u = {9} local t = {2, "3\"4", {5, 6}, x=function() end, [u]=u} table.print(t) --> { [{ 9 }] = { 9 }, x = function: 0x6a2870, 2, "3\"4", { 5, 6 } } table.print(t, 'nohash') --> { 2, "3\"4", { 5, 6 } } table.print(t, 'nohash', 10) --> { 2, -- "3\"4", -- { 5, 6 } } -- The `tag' field is particularly important in metalua, to represent tree-like structures. -- As such, it has got a special syntax, introduced by a back-quote "`", -- which is rendered by default by metalua's pretty printers. local t = {tag='Sum', 1, {tag='Product', 2, 3}, lines={10,11}} table.print(t) --> `Sum{ lines = { 10, 11 }, 1, `Product{ 2, 3 } } -- metalua tag syntax table.print(t, 'nohash') --> `Sum{ 1, `Product{ 2, 3 } } table.print(t, 'nohash', 10) -- metalua tag syntax --> `Sum{ 1, -- `Product{ 2, -- 3 } } -- tags syntax can be disabled: table.print(t, 'nohash', 'notag') --> { tag = "Sum", 1, { tag = "Product", 2, 3 } } -- tag syntax disabled -- Note: table.print(t, ...) is equivalent to print(table.tostring(t, ...)).
ISSUE:此函式應該回傳字串,而不是預設使用者如何輸出文字。
-- Print anything - including nested tables function table_print (tt, indent, done) done = done or {} indent = indent or 0 if type(tt) == "table" then for key, value in pairs (tt) do io.write(string.rep (" ", indent)) -- indent it if type (value) == "table" and not done [value] then done [value] = true io.write(string.format("[%s] => table\n", tostring (key))); io.write(string.rep (" ", indent+4)) -- indent it io.write("(\n"); table_print (value, indent + 7, done) io.write(string.rep (" ", indent+4)) -- indent it io.write(")\n"); else io.write(string.format("[%s] => %s\n", tostring (key), tostring(value))) end end else io.write(tt .. "\n") end end
通用 tostring
function table_print (tt, indent, done) done = done or {} indent = indent or 0 if type(tt) == "table" then local sb = {} for key, value in pairs (tt) do table.insert(sb, string.rep (" ", indent)) -- indent it if type (value) == "table" and not done [value] then done [value] = true table.insert(sb, key .. " = {\n"); table.insert(sb, table_print (value, indent + 2, done)) table.insert(sb, string.rep (" ", indent)) -- indent it table.insert(sb, "}\n"); elseif "number" == type(key) then table.insert(sb, string.format("\"%s\"\n", tostring(value))) else table.insert(sb, string.format( "%s = \"%s\"\n", tostring (key), tostring(value))) end end return table.concat(sb) else return tt .. "\n" end end function to_string( tbl ) if "nil" == type( tbl ) then return tostring(nil) elseif "table" == type( tbl ) then return table_print(tbl) elseif "string" == type( tbl ) then return tbl else return tostring(tbl) end end
範例
print(to_string{ "Lua",user="Mariacher", {{co=coroutine.create(function() end),{number=12345.6789}}, func=function() end}, boolt=true} )
此函式會列印
"Lua" { { { number = "12345.6789" } co = "thread: 0212B848" } func = "function: 01FC7C70" } boolt = "true" user = "Mariacher"
(上述程式碼最初來自 TableUtils)
根據 [PHP print_r]。程式碼基於 DracoBlue 根據 Nick Gammon 編寫的程式碼修改,以符合 [PHP print_r] 的格式。
範例:print_r{ 5,3,{5,3} } -->
[1] => 5 [2] => 3 [3] => Table { [1] => 5 [2] => 3 }
相容性:Lua 5.0 和 5.1
function print_r (t, indent, done) done = done or {} indent = indent or '' local nextIndent -- Storage for next indentation value for key, value in pairs (t) do if type (value) == "table" and not done [value] then nextIndent = nextIndent or (indent .. string.rep(' ',string.len(tostring (key))+2)) -- Shortcut conditional allocation done [value] = true print (indent .. "[" .. tostring (key) .. "] => Table {"); print (nextIndent .. "{"); print_r (value, nextIndent .. string.rep(' ',2), done) print (nextIndent .. "}"); else print (indent .. "[" .. tostring (key) .. "] => " .. tostring (value).."") end end end function print_r (t, indent) -- alt version, abuse to http://richard.warburton.it local indent=indent or '' for key,value in pairs(t) do io.write(indent,'[',tostring(key),']') if type(value)=="table" then io.write(':\n') print_r(value,indent..'\t') else io.write(' = ',tostring(value),'\n') end end end -- alt version2, handles cycles, functions, booleans, etc -- - abuse to http://richard.warburton.it -- output almost identical to print(table.show(t)) below. function print_r (t, name, indent) local tableList = {} function table_r (t, name, indent, full) local serial=string.len(full) == 0 and name or type(name)~="number" and '["'..tostring(name)..'"]' or '['..name..']' io.write(indent,serial,' = ') if type(t) == "table" then if tableList[t] ~= nil then io.write('{}; -- ',tableList[t],' (self reference)\n') else tableList[t]=full..serial if next(t) then -- Table not empty io.write('{\n') for key,value in pairs(t) do table_r(value,key,indent..'\t',full..serial) end io.write(indent,'};\n') else io.write('{};\n') end end else io.write(type(t)~="number" and type(t)~="boolean" and '"'..tostring(t)..'"' or tostring(t),';\n') end end table_r(t,name or '__unnamed__',indent or '','') end
以下是 print_r 更完整的版本
很抱歉這麼長!
--[[ Author: Julio Manuel Fernandez-Diaz Date: January 12, 2007 (For Lua 5.1) Modified slightly by RiciLake to avoid the unnecessary table traversal in tablecount() Formats tables with cycles recursively to any depth. The output is returned as a string. References to other tables are shown as values. Self references are indicated. The string returned is "Lua code", which can be procesed (in the case in which indent is composed by spaces or "--"). Userdata and function keys and values are shown as strings, which logically are exactly not equivalent to the original code. This routine can serve for pretty formating tables with proper indentations, apart from printing them: print(table.show(t, "t")) -- a typical use Heavily based on "Saving tables with cycles", PIL2, p. 113. Arguments: t is the table. name is the name of the table (optional) indent is a first indentation (optional). --]] function table.show(t, name, indent) local cart -- a container local autoref -- for self references --[[ counts the number of elements in a table local function tablecount(t) local n = 0 for _, _ in pairs(t) do n = n+1 end return n end ]] -- (RiciLake) returns true if the table is empty local function isemptytable(t) return next(t) == nil end local function basicSerialize (o) local so = tostring(o) if type(o) == "function" then local info = debug.getinfo(o, "S") -- info.name is nil because o is not a calling level if info.what == "C" then return string.format("%q", so .. ", C function") else -- the information is defined through lines return string.format("%q", so .. ", defined in (" .. info.linedefined .. "-" .. info.lastlinedefined .. ")" .. info.source) end elseif type(o) == "number" or type(o) == "boolean" then return so else return string.format("%q", so) end end local function addtocart (value, name, indent, saved, field) indent = indent or "" saved = saved or {} field = field or name cart = cart .. indent .. field if type(value) ~= "table" then cart = cart .. " = " .. basicSerialize(value) .. ";\n" else if saved[value] then cart = cart .. " = {}; -- " .. saved[value] .. " (self reference)\n" autoref = autoref .. name .. " = " .. saved[value] .. ";\n" else saved[value] = name --if tablecount(value) == 0 then if isemptytable(value) then cart = cart .. " = {};\n" else cart = cart .. " = {\n" for k, v in pairs(value) do k = basicSerialize(k) local fname = string.format("%s[%s]", name, k) field = string.format("[%s]", k) -- three spaces between levels addtocart(v, fname, indent .. " ", saved, field) end cart = cart .. indent .. "};\n" end end end end name = name or "__unnamed__" if type(t) ~= "table" then return name .. " = " .. basicSerialize(t) end cart, autoref = "", "" addtocart(t, name, indent) return cart .. autoref end
一個測試
----------------------------------------------------------- --- testing table.show t = {1, {2, 3, 4}, default = {"a", "b", d = {12, "w"}, e = 14}} t.g = t.default print("-----------------------------------") print(table.show(t)) -- shows __unnamed__ table tt = {1, h = {["p-q"] = "a", b = "e", c = {color = 3, name = "abc"}}, 2} f = table.show tt[f] = "OK" print("-----------------------------------") print(table.show(tt, "tt", "--oo-- ")) -- shows some initial 'indent' t.m = {} t.g.a = {} t.g.a.c = t t.tt = tt.new t.show = table.show print("-----------------------------------") print(table.show(t, "t")) -- most typical use print("-----------------------------------") print(table.show(math.tan, "tan")) -- not a table is OK print("-----------------------------------") s = "a string" print(table.show(s, "s")) -- not a table is OK
輸出
----------------------------------- __unnamed__ = { [1] = 1; [2] = { [1] = 2; [2] = 3; [3] = 4; }; ["default"] = { [1] = "a"; [2] = "b"; ["e"] = 14; ["d"] = { [1] = 12; [2] = "w"; }; }; ["g"] = {}; -- __unnamed__["default"] (self reference) }; __unnamed__["g"] = __unnamed__["default"]; ----------------------------------- --oo-- tt = { --oo-- [1] = 1; --oo-- [2] = 2; --oo-- ["function: 0x8070e20, defined in (28-99)@newprint_r.lua"] = "OK"; --oo-- ["h"] = { --oo-- ["b"] = "e"; --oo-- ["c"] = { --oo-- ["color"] = 3; --oo-- ["name"] = "abc"; --oo-- }; --oo-- ["p-q"] = "a"; --oo-- }; --oo-- }; ----------------------------------- t = { [1] = 1; [2] = { [1] = 2; [2] = 3; [3] = 4; }; ["m"] = {}; ["show"] = "function: 0x8070e20, defined in (28-99)@newprint_r.lua"; ["g"] = { [1] = "a"; [2] = "b"; ["e"] = 14; ["d"] = { [1] = 12; [2] = "w"; }; ["a"] = { ["c"] = {}; -- t (self reference) }; }; ["default"] = {}; -- t["g"] (self reference) }; t["g"]["a"]["c"] = t; t["default"] = t["g"]; ----------------------------------- tan = "function: 0x806f758, C function" ----------------------------------- s = "a string"
(上述程式碼最初存在於 MakingLuaLikePhp)
x = {1, 2, 3} x[x]=x print(table.show(x)) --[[output: __unnamed__ = { [1] = 1; [2] = 2; [3] = 3; ["table: 0x695f08"] = {}; -- __unnamed__ (self reference) }; __unnamed__["table: 0x695f08"] = __unnamed__; --]]
local val_to_str; do -- Cached function references (for performance). local byte = string.byte local find = string.find local match = string.match local gsub = string.gsub local format = string.format local insert = table.insert local sort = table.sort local concat = table.concat -- For escaping string values. local str_escape_map = { ['\a'] = '\\a', ['\b'] = '\\b', ['\t'] = '\\t', ['\n'] = '\\n', ['\v'] = '\\v', ['\f'] = '\\f', ['\r'] = '\\r', ['\\'] = '\\\\' } local str_escape_replace = function(c) return str_escape_map[c] or format('\\%03d', byte(c)) end -- Keys are comparable only if the same type, otherwise just sort them by type. local types_order, ref_types_order = { ['number'] = 0, ['boolean'] = 1, ['string'] = 2, ['table'] = 3, ['function'] = 4 }, 5 end local function compare_keys(k1, k2) local t1, t2 = type(k1), type(k2) if t1 ~= t2 then -- not the same type return (types_order[t1] or ref_types_order) < (types_order[t2] or ref_types_order) elseif t1 == 'boolean' then -- comparing booleans return not k1 -- Sort false before true. elseif t1 == 'number' or t1 == 'string' then -- comparing numbers (including NaNs or infinites) or strings return k1 < k2 -- Keys with the same comparable type. else -- comparing references (including tables, functions, userdata, threads...) return tostring(k1) < tostring(k2) -- may be the Lua engine adds some comparable info end end -- String keys matching valid identifiers that are reserved by Lua. local reserved_keys = { ['and'] = 1, ['break'] = 1, ['do'] = 1, ['else'] = 1, ['elseif'] = 1, ['end'] = 1, ['false'] = 1, ['for'] = 1, ['function'] = 1, ['if'] = 1, ['in'] = 1, ['local'] = 1, ['nil'] = 1, ['not'] = 1, ['or'] = 1, ['repeat'] = 1, ['return'] = 1, ['then'] = 1, ['true'] = 1, ['until'] = 1, ['while'] = 1 } -- Main function. val_to_str = function(val, options) -- Decode and cache the options. local include_mt = options and options.include_mt local prettyprint = options and options.prettyprint local asciionly = options and options.asciionly -- Precompute the output formats depending on options. local open = prettyprint and '{ ' or '{' local equals = prettyprint and ' = ' or '=' local comma = prettyprint and ', ' or ',' local close = prettyprint and ' }' or '}' -- What to escape: C0 controls, the backslash, and optionally non-ASCII bytes. local str_escape_pattern = asciionly and '[%z\001-\031\\\127-\255]' or '[%z\001-\031\\\127]' -- Indexed references (mapped to ids), and counters per ref type. local ref_ids, ref_counts = {}, {} -- Helper needed to detect recursive tables and avoid infinite loops. local function visit(ref) local typ = type(ref) if typ == 'number' or typ == 'boolean' then return tostring(ref) elseif typ == 'string' then if find(ref, "'") then str_escape_map['"'] = '\\"' return '"' .. gsub(ref, str_escape_pattern, str_escape_replace) .. '"' else str_escape_map['"'] = '"' return "'" .. gsub(ref, str_escape_pattern, str_escape_replace) .. "'" end elseif typ == 'table' then local id = ref_ids[ref] if id then return ':' .. typ .. '#' .. id .. ':' end id = (ref_counts[typ] or 0) + 1; ref_ids[ref], ref_counts[typ] = id, id -- First dump keys that are in sequence. local result, sequenced, keys = {}, {}, {} for i, val in ipairs(ref) do insert(result, visit(val)) sequenced[i] = true end -- Then dump other keys out of sequence, in a stable order. for key, _ in pairs(ref) do if not sequenced[key] then insert(keys, key) end end sequenced = nil -- Free the temp table no longer needed. -- Sorting keys (of any type) is needed for stable comparison of results. sort(keys, compare_keys) for _, key in ipairs(keys) do insert(result, (type(key) == 'string' and not reserved_keys[key] and match(key, '^[%a_][%d%a_]*$') and key or '[' .. visit(key) .. ']') .. equals .. visit(ref[key])) end keys = nil -- Free the temp table no longer needed. -- Finally dump the metatable (with pseudo-key '[]'), if there's one. if include_mt then ref = getmetatable(ref) if ref then insert(result, '[]' .. equals .. visit(ref)) end end -- Pack the result string. -- TODO: improve pretty-printing with newlines/indentation return open .. concat(result, comma) .. close elseif typ ~= 'nil' then -- other reference types (function, userdata, etc.) local id = ref_ids[ref] if not id then id = (ref_counts[typ] or 0) + 1; ref_ids[ref], ref_counts[ref] = id, id end return ':' .. typ .. '#' .. id .. ':' else return 'nil' end end return visit(val) end end
範例用法
local s = val_to_str(anyvalue) local s = val_to_str(anyvalue, {include_mt=1, prettyprint=1}) -- you can specify optional flags
筆記
{ }
對、鍵/值對的等號 =
以及表格建構符中的逗號 ,
後面增加空白區隔,且完全沒有換行符號。 [] =
列為表格成員,而且允許循環參照(這適用於表格附加自身作為其元資料表的情況)。 :function#1:
,其中序號是該類別物件在回傳字串中出現的次數。除非有「debug.info()」函式且它提供追蹤識別碼(它不在安全的 Lua 安裝中,例如 Scribunto,其中僅有的可用除錯功能是已修改的「info.traceback()」),否則沒有可靠的方式來識別它們,如果可用 getfenv(),你可以轉儲函式環境(它不在 Scribunto 中)。