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 (
    {
        [“<”] = “&lt;” ;
        [“>”] = “&gt;” ;
        [“&”] = “&amp;” ;
        [’“’] = ”&quot;“ ;
    } ,
    { __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!

Motorbike annoyances

So I finally got my GSXR-400 back together on friday, after taking it apart three months earlier for a service… And I hear a valve that isn’t correctly adjusted. Now I have to take the tank, fan and engine cover off again to readjust a valve clearance :(

Costumes

Yet again I am leaving a costume to the last minute… For eng ball on Wednesday night the general theme is “Hollywood”; but each table has it’s own sub-theme.

Our Theme: Toy Story.

I expect I’ll end up going as Woody cause its the easiest…