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 August 5, 2012 6:54 am GMT (diff)