HTML generation with lua
In my adventures on the web; I discovered an aid for writing HTML called zen-coding. Playing a bit with it, I found it lacking in some departments, and began to think that macros aren’t whats needed, but complete generation.
I remembered seeing an example of some html generating lua code a while back, and for a bit of fun decided to do my own take on it…
local function renderattributes ( attr )
local t , i = { } , 1
for k , v in pairs ( attr ) do
t [ i ] = ’ ’
t [ i + 1 ] = k
t [ i + 2 ] = ’=“’
t [ i + 3 ] = v
t [ i + 4 ] = ’”’
i = i + 5
end
return table.concat ( t )
end
local function tostringtable ( t )
local r = { }
for i , v in ipairs ( t ) do
r [ i ] = tostring ( v )
end
return r
end
local elementmeta = {
__tostring = function ( t )
if #t == 0 then – Self closing tag
return “<” .. t.tag .. renderattributes ( t.attr ) .. “/>”
else
return “<” .. t.tag .. renderattributes ( t.attr ) .. “>” .. table.concat ( tostringtable ( t ) ) .. “</” .. t.tag .. “>”
end
end ;
}
local escapecodes = setmetatable (
{
[“<”] = “<” ;
[“>”] = “>” ;
[“&”] = “&” ;
[’“’] = ”"“ ;
} ,
{ __index = function ( t , k ) return string.format ( ”&#%03d;“ , string.byte ( k ) ) end }
)
local function escapehtml ( str )
return ( str:gsub ( [=[[<>&”]]=] , escapecodes ) )
end
local stringmeta = {
__tostring = function ( t )
return escapehtml ( table.concat ( t ) )
end
}
local function construct ( tag , contents )
local attr = { }
local r = { tag = tag , attr = attr }
for k , v in pairs ( contents ) do
if type ( k ) == “string” then – attribute
assert ( type ( v ) == “string” , “Attributes must be strings” )
attr [ k ] = v
elseif type ( k ) == “number” then – Contents
if type ( v ) == “string” then
r [ k ] = setmetatable ( { v } , stringmeta )
elseif type ( v ) == “number” then
r [ k ] = setmetatable ( { tostring ( v ) } , stringmeta )
else
r [ k ] = v
end
else
error ( “Bad type” )
end
end
return setmetatable ( r , elementmeta )
end
local CDATA = function ( p ) return setmetatable ( p , { __tostring = function ( t ) return “<![CDATA[” .. table.concat ( tostringtable ( p ) ) .. “]]>” end } ) end
local javascriptblock = function ( p ) return construct ( “script” , { type=“text/javascript” ; “//” ; CDATA { ’\n’ ; p ; ’\n//’ } } ) end
tags = setmetatable ( {
CDATA = CDATA ;
javascriptblock = javascriptblock ;
} ,
{ __index = function ( t , k ) return function ( p ) return construct ( k , p ) end end }
)
To be used like so:
setmetatable ( _G , { __index = tags } )
print ( html {
head {
title { “Text” } ;
javascriptblock [[alert(“XHTML compatible!” );]] ;
} ;
body {
div { id=“main” ;
img { src = “blah.jpg” };
}
}
} )
Output:
<html><head><title>Text</title><script type=“text/javascript”>//<![CDATA[
alert(“XHTML compatible!” );
//]]></script></head><body><div id=“main”><img src=“blah.jpg”/></div></body></html>
The code is quite extensible, to create a new tag/block, all you need is a table with a __tostring metamethod.
Contrary to first appearances, it doesn’t just create a document from a table, but creates an internal structure of a document; which can then be generated on demand (via the recursive __tostring metamethods). This means that dynamic content can be used with the same structure:
userloggedin = function () return true end
function loggedinonly ( p ) if userloggedin() then return setmetatable ( p , { __tostring = function ( t ) return table.concat ( tostringtable ( p ) ) end } ) else return “” end end
print ( body {
loggedinonly {
div {
“Logged in!”
}
}
} )
Obviously, to facilitate writing conditional code easier, some simple helper functions can be created:
setpassthrough = function ( t ) return setmetatable ( t , { __tostring = function ( o ) return table.concat ( tostringtable ( o ) ) end } ) end
function ifcond ( cond , t , f )
if cond then
return setpassthrough ( t )
else
if f then
return setpassthrough ( f )
else
return “”
end
end
end
Maybe when I need to write some html I can put this into practice!
If you find this useful, please give me a yell!