Output Lua Table To Html File

lua-users home
wiki

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 3:21 pm GMT (diff)