Output Lua Table To Html File

lua-users home
wiki

Difference (from prior major revision) (minor diff, author diff)

Changed: 119c119
== Take #2 ==
=== Take #2 ===

Changed: 132,135c132,138
local ids = {}
local current_id = 0
local count = {}
local from = {}
local coroutine = coroutine
local next = next
local pairs = pairs
local string = string
local tostring = tostring
local type = type
local _G = _G

Added: 138a142,143

-- Escape string to make suitable for embedding in HTML.

Removed: 140d144
--FIX: improve?

Removed: 147,149d150
local function output(s)
io.stdout:write(s)
end

Changed: 151,154c152,162
local function analyze_tree(o)
if type(o) == 'table' then
if count[o] then
count[o] = count[o] + 1
-- iterator function for table pairs.
-- hash part, then array part.
-- used for display.
local function table_pairs(t)
local keys = {}
for k in pairs(t) do keys[#keys+1] = k end
table.sort(keys, function(a,b)
if type(a) == 'number' and type(b) == 'string' then
return false
elseif type(a) == 'string' and type(b) == 'number' then
return true

Changed: 156,171c164
count[o] = 1
current_id = current_id + 1
ids[o] = current_id

local this_id = current_id
for k,v in pairs(o) do
analyze_tree(k)
analyze_tree(v)
if type(k) == 'table' then
from[k] = from[k] or {}; from[k][o] = this_id .. '.' .. ids[k]
end
if type(v) == 'table' then
from[v] = from[v] or {};
from[v][o] = this_id .. '.' .. (ids[k] or tostring(k))
end
end
return a < b

Added: 172a166,171
end)
local i = 0
return function()
i = i + 1
local k = keys[i]
if k then return k, t[k] end

Changed: 176c175,177
local function output_html(o)

-- Serialize object o. Writes one or more substrings to function append.
local function obj_serialize(o, append)

Changed: 178,193c179,185
local is_empty = next(o) == nil
output(
format('<a name="%d"></a>', ids[o])
.. '<div class="table">'
.. (is_empty and '(empty)' or
('<a href="javascript:toggle(' .. ids[o] .. ')">[toggle]</a>'))
.. format([[Table ID %d]], ids[o])
.. (type(o.tag) == 'string' and ('[Tag=' .. o.tag .. ']') or '')
.. format('<div style="display:none" id="%d">\n', ids[o]))
if from[o] and not next(from[o]) then
output('<div>Referenced from: ')
for _,from_id in pairs(from[o]) do
output(format([[ <a onlick="show(%s)" href="#%s">%s</a> ]],
from_id, from_id, from_id))
end
output('</div>')
append('{')
for k,v in table_pairs(o) do
append('[')
obj_serialize(k, append)
append(']=[')
obj_serialize(v, append)
append('];')

Added: 194a187,193
append('}')
elseif type(o) == 'string' then
append(string.format('%q', o))
else
append(tostring(o))
end
end

Removed: 196,209d194
for k,v in pairs(o) do
local knew, vnew
if count[k] then
knew = format([[<a onclick="show(%s)" href="#%s">(see %s)</a>]],
ids[k],ids[k],ids[k])
elseif type(k) ~= 'table' then
knew = htmlize(tostring(k))
end
if count[v] then
vnew = format([[<a onclick="show(%s)" href="#%s">(see %s)</a>]],
ids[v],ids[v],ids[v])
elseif type(v) ~= 'table' then
vnew = htmlize(tostring(v))
end

Changed: 211,212c196,209
local kname = format([[<a name="%s"></a>]],
ids[o] .. '.' .. (ids[k] or tostring(k)))
-- Returns serialization of o, not exceeding maxlen characters.
local function obj_tostring_short(o, maxlen)
local s = ''
local function append(ss)
s = s .. ss
if #s > maxlen then
s = s:sub(1,maxlen) .. '...'
coroutine.yield()
end
end
local f = coroutine.wrap(obj_serialize)
f(o, append)
return s
end

Removed: 214,222d210
if knew and vnew then
output(kname ..
'<div class="table_row">' .. knew .. '=' .. vnew .. '</div>')
else
output('<div class="table_row">\n')
output(kname)
if knew then output(knew) else
output_html(k)
end

Changed: 224,226c212,237
output('=')
if vnew then output(vnew) else
output_html(v)
local function analyze_tree(o)
local ids = {}
local current_id = 0
local count = {}
local from = {}

local function analyze_tree_helper(o)
if type(o) == 'table' then
if count[o] then
count[o] = count[o] + 1
else
count[o] = 1
current_id = current_id + 1
ids[o] = current_id

local this_id = current_id
for k,v in pairs(o) do
analyze_tree_helper(k)
analyze_tree_helper(v)
if type(k) == 'table' then
from[k] = from[k] or {}; from[k][o] = this_id .. '.' .. ids[k]
end
if type(v) == 'table' then
from[v] = from[v] or {};
from[v][o] = this_id .. '.' .. (ids[k] or tostring(k))
end

Removed: 228,229d238

output('</div>\n')

Removed: 232,234d240
output('</div></div>\n')
else
output('<div>' .. htmlize(tostring(o)) .. '</div>\n')

Changed: 236c242
end
analyze_tree_helper(o)

Removed: 238,239d243
local function dump(o)
analyze_tree(o)

Changed: 244c248,252
output [[
return ids, count, from
end


local header = [[

Changed: 251,252c259,260
.table {margin:1em; border: 1px solid black}
.table_row { margin-left:1em; border: 1px solid black}
.table {margin-left:1em; border: 1px solid black}
.table_row {border: 1px solid black}

Changed: 254c262
<script>
<script type="text/javascript"><!--

Changed: 266c274
if (ele.parentNode) show(ele.parentNode);
if (ele.parentNode) show_node(ele.parentNode);

Changed: 275c283
</script>
//--></script>

Changed: 278c286,374
]]
]]
local footer = [[</body></html>]]


-- Writes HTML representations of object o as one or more strings to
-- function output.
local function object_to_html(o, output)
local ids, count, from = analyze_tree(o)

local output_html

local function output_header_html(o)
if type(o) == 'table' then
output(format('<a name="id%s"></a>', ids[o]))

local is_empty = next(o) == nil
output(is_empty and '(empty)' or
'<a href="javascript:toggle(\'id' .. ids[o] .. '\')">[+]</a>')
output(format([[Table ID %s]], ids[o]))
output(type(o.tag) == 'string' and ' [Tag=' .. o.tag .. ']' or '')
output(' ')
output(htmlize(obj_tostring_short(o, 40)))
elseif type(o) == 'string' then
output(htmlize(string.format('%q', o)))
else
output(htmlize(tostring(o)))
end
end

local function output_body_html(o)
if type(o) == 'table' then
output(format('<div class="table" style="display:none" id="id%s">\n', ids[o]))

-- xref
if from[o] and next(from[o]) and next(from[o], next(from[o])) then
output('<div>Referenced from: ')
for _,from_id in pairs(from[o]) do
output(format([[ <a onclick="show('id%s')" href="#id%s">%s</a> ]],
from_id, from_id, from_id))
end
output('</div>')
end

-- key/values
for k,v in table_pairs(o) do
local function prepare_output(oo)
local f, is_long
if count[oo] then
f = function()
output(format([[<a onclick="show('id%s')" href="#id%s">(see %s)</a>]],
ids[oo],ids[oo],ids[oo]))
end
elseif type(oo) ~= 'table' then
f = function() output_header_html(oo) end
else
f = function() output_header_html(oo) end
is_long = true
end
return f, is_long
end
local kf, klong = prepare_output(k)
local vf, vlong = prepare_output(v)

local field_id = ids[o] .. '.' .. (ids[k] or tostring(k))
output(format([[<a name="id%s"></a>]], field_id))

output(format('<div class="table_row" id="id%s">\n', field_id))
kf()
output('=')
vf()
if vlong then output_body_html(v) end
output('</div>')
end

output('</div>\n')

end
end

--local
function output_html(o)
output('<div>')
output_header_html(o)
output('</div>')
output_body_html(o)
end

output(header)


Changed: 281c377,379
output_html(oo)
if oo ~= o then
output_html(oo)
end

Changed: 284c382,389
output [[</body></html>]]
output(footer)
end


-- example usage

local function dump(obj)
object_to_html(obj, function(s) io.stdout:write(s) end)

Changed: 290,291c395,396
--[=[
package.path = package.path .. ';/u/work/luawork/luaanalyze/lib/?.lua'
--[[
package.path = package.path .. ';/luaanalyze/lib/?.lua'

Changed: 323c428
--]=]
--]]

Changed: 326c431
== See Also ==
=== See Also ===

The following sample code converts a Lua table into an HTML file that can be viewed by a web browser. Different levels of the table nesting are rendered with different colors. The maximum level of nesting is set to 10, but this can be easily tweaked for higher levels.

-- dontspamme_samlie@yahoo.com

-- Converts Lua table to HTML output in table.html file
function tohtml(x)
  ret = tohtml_table(x,1)
  writefile("table.html", ret)
  os.execute("table.html")
  return(ret)
end

-- Saves a string to file
function writefile(filename, value)
  if (value) then
    local file = io.open(filename,"w+")
    file:write(value)
    file:close()
  end
end

-- Flattens a table to html output
function tohtml_table(x, table_level)
  local k, s,  tcolor
  local html_colors = {
    "#339900","#33CC00","#669900","#666600","#FF3300",
    "#FFCC00","#FFFF00","#CCFFCC","#CCCCFF","#CC66FF",
    "#339900","#33CC00","#669900","#666600","#FF3300",
    "#FFCC00","#FFFF00","#CCFFCC","#CCCCFF","#CC66FF"
  }
  local lineout = {}
  local tablefound = false
    if type(x) == "table" then
    s = ""
    k = 1
    local i, v = next(x)
    while i do
      if (type(v) == "table") then
        if (table_level<10) then
          lineout[k] =  "<b>" .. flat(i) .. "</b>".. tohtml_table(v, table_level + 1)   
        else
          lineout[k] = "<b>MAXIMUM LEVEL BREACHED</b>"
        end
        tablefound = true
      else
        lineout[k] = flat(i) .. "=" .. tohtml_table(v)
      end
      k = k + 1
      i, v = next(x, i)
    end

    for k,line in ipairs(lineout) do
      if (tablefound) then
        s = s .. "<tr><td>" .. line .. "</td></tr>\n"
      else
        s = s .. "<td>" .. line .. "</td>\n"
      end
    end
    if not (tablefound) then
      s = "<table border='1' bgcolor='#FFFFCC' cellpadding='5' cellspacing='0'>" ..
        "<tr>" .. s .. "</tr></table>\n"
    else
      tcolor = html_colors[table_level]
      s = "<table border='3' bgcolor='"..tcolor.."' cellpadding='10' cellspacing='0'>" ..
          s ..  "</table>\n"
    end

    return s 
  end
  if type(x) == "function" then
    return "FUNC"
  end
  if type(x) == "file" then
    return "FILE"
  end

  return tostring(x) 
end

-- Flattens a table to string
function flat(x)  
  return toflat(x,1)
end

-- Flattens a table to string
function toflat(x, tlevel)
  local s
  tlevel = tlevel + 1

  if type(x) == "table" then
    s = "{"
    local i, v = next(x)
    while i do
      if (tlevel < 15) then
        s = s .. i .. "=" .. toflat(v, tlevel) 
      else
        s = s .. i .. "={#}" 
      end

      i, v = next(x, i)
      if i then
        s = s .. ", " 
      end
    end
    return s .. "}\n"
  end
  if type(x) == "function" then
    return "FUNC"
  end
  if type(x) == "file" then
    return "FILE"
  end

  return tostring(x) 
end

Take #2

Here is a more advanced implementation, with expand/collapsing and handling cycles. (See warning in comments before use.)

-- Example dumping Lua tables to HTML for debugging.
--
-- Warning: not complete or well tested.  This is only intended
-- as an example/starting point.  Clean it up if used in production.
--
-- (c) 2008 David Manura (2008-12)
-- Licensed under the same terms as Lua (MIT license).

local coroutine = coroutine
local next = next
local pairs = pairs
local string = string
local tostring = tostring
local type = type
local _G = _G

local format = string.format


-- Escape string to make suitable for embedding in HTML.
local function htmlize(s)
  s = s:gsub('&', '&amp;')
  s = s:gsub('<', '&lt;')
  s = s:gsub('>', '&gt;')
  return s
end


-- iterator function for table pairs.
-- hash part, then array part.
-- used for display.
local function table_pairs(t)
  local keys = {}
  for k in pairs(t) do keys[#keys+1] = k end
  table.sort(keys, function(a,b)
    if type(a) == 'number' and type(b) == 'string' then
      return false
    elseif type(a) == 'string' and type(b) == 'number' then
      return true
    else
      return a < b
    end
  end)
  local i = 0
  return function()
    i = i + 1
    local k = keys[i]
    if k then return k, t[k] end
  end
end


-- Serialize object o.  Writes one or more substrings to function append.
local function obj_serialize(o, append)
  if type(o) == 'table' then
    append('{')
    for k,v in table_pairs(o) do
      append('[')
      obj_serialize(k, append)
      append(']=[')
      obj_serialize(v, append)
      append('];')
    end
    append('}')
  elseif type(o) == 'string' then
    append(string.format('%q', o))
  else
    append(tostring(o))
  end
end


-- Returns serialization of o, not exceeding maxlen characters.
local function obj_tostring_short(o, maxlen)
  local s = ''
  local function append(ss)
    s = s .. ss
    if #s > maxlen then
      s = s:sub(1,maxlen) .. '...'
      coroutine.yield()
    end
  end
  local f = coroutine.wrap(obj_serialize)
  f(o, append)
  return s
end


local function analyze_tree(o)
  local ids = {}
  local current_id = 0
  local count = {}
  local from = {}

  local function analyze_tree_helper(o)
    if type(o) == 'table' then
      if count[o] then
        count[o] = count[o] + 1
      else
        count[o] = 1
        current_id = current_id + 1
        ids[o] = current_id
  
        local this_id = current_id
        for k,v in pairs(o) do
          analyze_tree_helper(k)
          analyze_tree_helper(v)
          if type(k) == 'table' then
            from[k] = from[k] or {}; from[k][o] = this_id .. '.' .. ids[k]
          end
          if type(v) == 'table' then
            from[v] = from[v] or {};
            from[v][o] = this_id .. '.' .. (ids[k] or tostring(k))
          end
        end
      end
    end
  end
  analyze_tree_helper(o)

  for k,v in pairs(count) do
    if v == 1 then count[k] = nil end
  end

  return ids, count, from
end


local header = [[
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>FIX</title>
<style type="text/css">
.table {margin-left:1em; border: 1px solid black}
.table_row {border: 1px solid black}
</style>
<script type="text/javascript"><!--
function toggle(id) {
  if (document.getElementById) {
    var ele = document.getElementById(id);
    if (ele && ele.style) {
      ele.style.display = ele.style.display == 'none' ? '' : 'none';
    }
  }
}
function show_node(ele) {
  if (ele.style) {
    ele.style.display = '';
    if (ele.parentNode) show_node(ele.parentNode);
  }
}
function show(id) {
  if (document.getElementById) {
    var ele = document.getElementById(id);
    if (ele) show_node(ele);
  }
}
//--></script>
</head>
<body>
]]
local footer = [[</body></html>]]


-- Writes HTML representations of object o as one or more strings to
-- function output.
local function object_to_html(o, output)
  local ids, count, from = analyze_tree(o)

  local output_html

  local function output_header_html(o)
    if type(o) == 'table' then
      output(format('<a name="id%s"></a>', ids[o]))

      local is_empty = next(o) == nil
      output(is_empty and '(empty)' or
             '<a href="javascript:toggle(\'id' .. ids[o] .. '\')">[+]</a>')
      output(format([[Table ID %s]], ids[o]))
      output(type(o.tag) == 'string' and ' [Tag=' .. o.tag .. ']' or '')
      output(' ')
      output(htmlize(obj_tostring_short(o, 40)))
    elseif type(o) == 'string' then
      output(htmlize(string.format('%q', o)))
    else
      output(htmlize(tostring(o)))
    end  
  end

  local function output_body_html(o)
    if type(o) == 'table' then
      output(format('<div class="table" style="display:none" id="id%s">\n', ids[o]))

      -- xref
      if from[o] and next(from[o]) and next(from[o], next(from[o])) then
        output('<div>Referenced from: ')
        for _,from_id in pairs(from[o]) do
          output(format([[ <a onclick="show('id%s')" href="#id%s">%s</a> ]],
                 from_id, from_id, from_id))
        end
        output('</div>')
      end

      -- key/values
      for k,v in table_pairs(o) do
        local function prepare_output(oo)
          local f, is_long
          if count[oo] then
            f = function()
              output(format([[<a onclick="show('id%s')" href="#id%s">(see %s)</a>]],
                            ids[oo],ids[oo],ids[oo]))
            end
          elseif type(oo) ~= 'table' then
            f = function() output_header_html(oo) end
          else
            f = function() output_header_html(oo) end
            is_long = true
          end
          return f, is_long
        end
        local kf, klong = prepare_output(k)
        local vf, vlong = prepare_output(v)

        local field_id = ids[o] .. '.' .. (ids[k] or tostring(k))
        output(format([[<a name="id%s"></a>]], field_id))

        output(format('<div class="table_row" id="id%s">\n', field_id))
        kf()
        output('=')
        vf()
        if vlong then output_body_html(v) end
        output('</div>')
      end

      output('</div>\n')

    end
  end

  --local
  function output_html(o)
    output('<div>')
    output_header_html(o)
    output('</div>')
    output_body_html(o)
  end

  output(header)

  output_html(o)
  for oo in pairs(count) do
    if oo ~= o then
      output_html(oo)
    end
  end

  output(footer)
end


-- example usage

local function dump(obj)
  object_to_html(obj, function(s) io.stdout:write(s) end)
end

dump(_G)

-- metalua example
--[[
package.path = package.path .. ';/luaanalyze/lib/?.lua'
require "lexer"
require "gg"
require "mlp_lexer"
require "mlp_misc"
require "mlp_table"
require "mlp_meta"
require "mlp_expr"
require "mlp_stat"
require "mlp_ext"
mlc = {} -- make gg happy
local mlp = assert(_G.mlp)
local function string_to_ast(src, filename)
  filename = filename or '(string)'
  local  lx  = mlp.lexer:newstream (src, filename)
  local  ast = mlp.chunk(lx)
  return ast
end
local src_file = assert(io.open ('mydump.lua', 'r'))
local src = src_file:read '*a'; src_file:close()
local ast = string_to_ast(src, src_filename)
local function remove_lineinfo(o)
  if type(o) == 'table' then
    o.lineinfo = nil
    for k,v in pairs(o) do
      remove_lineinfo(k)
      remove_lineinfo(v)
    end
  end
end
remove_lineinfo(ast)
dump(ast)
--]]

See Also


RecentChanges · preferences
edit · history
Last edited June 2, 2020 2:21 pm GMT (diff)