Output Lua Table To Html File

lua-users home
wiki

Showing revision 8
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 ids = {}
local current_id = 0
local count = {}
local from = {}

local format = string.format

local function htmlize(s)
  --FIX: improve?
  s = s:gsub('&', '&amp;')
  s = s:gsub('<', '&lt;')
  s = s:gsub('>', '&gt;')
  return s
end

local function output(s)
  io.stdout:write(s)
end

local function analyze_tree(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(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
    end
  end
end

local function output_html(o)
  if type(o) == 'table' then
    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>')
    end

    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

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

      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

        output('=')
        if vnew then output(vnew) else
          output_html(v)
        end

        output('</div>\n')
      end
    end
    output('</div></div>\n')
  else
    output('<div>' .. htmlize(tostring(o)) .. '</div>\n')
  end
end

local function dump(o)
  analyze_tree(o)
  for k,v in pairs(count) do
    if v == 1 then count[k] = nil end
  end

  output [[
<!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:1em; border: 1px solid black}
.table_row { margin-left:1em; border: 1px solid black}
</style>
<script>
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(ele.parentNode);
  }
}
function show(id) {
  if (document.getElementById) {
    var ele = document.getElementById(id);
    if (ele) show_node(ele);
  }
}
</script>
</head>
<body>
    ]]
  output_html(o)
  for oo in pairs(count) do
    output_html(oo)
  end

  output [[</body></html>]]
end

dump(_G)

-- metalua example
--[=[
package.path = package.path .. ';/u/work/luawork/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 · current revision
Edited December 23, 2008 6:32 am GMT (diff)