Module:HtmlBuilder

-- Module for building complex HTML (e.g. infoboxes, navboxes) using a fluent interface.
local HtmlBuilder = {}
local metatable = {}
metatable.__index = function(t, key)
 local ret = rawget(t, key)
 if ret then
 return ret
 end
 
 ret = metatable[key]
 if type(ret) == 'function' then
 return function(...) 
 return ret(t, ...) 
 end 
 else
 return ret
 end
end
metatable.__tostring = function(t)
 local ret = {}
 t._build(ret)
 return table.concat(ret)
end
metatable._build = function(t, ret)
 if t.tagName then 
 table.insert(ret, '<' .. t.tagName)
 for i, attr in ipairs(t.attributes) do
 table.insert(ret, ' ' .. attr.name .. '="' .. attr.val .. '"') 
 end
 if #t.styles > 0 then
 table.insert(ret, ' style="')
 for i, prop in ipairs(t.styles) do
 if type(prop) == 'string' then -- added with cssText()
 table.insert(ret, prop .. ';')
 else -- added with css()
 table.insert(ret, prop.name .. ':' .. prop.val .. ';')
 end
 end
 table.insert(ret, '"')
 end
 if t.selfClosing then
 table.insert(ret, ' /')
 end
 table.insert(ret, '>') 
 end
 for i, node in ipairs(t.nodes) do
 if node then
 if type(node) == 'table' then
 node._build(ret)
 else
 table.insert(ret, tostring(node))
 end
 end
 end
 if t.tagName and not t.unclosed and not t.selfClosing then
 table.insert(ret, '</' .. t.tagName .. '>')
 end
end
metatable.node = function(t, builder)
 if builder then
 table.insert(t.nodes, builder)
 end
 return t
end
metatable.wikitext = function(t, ...) 
 local vals = {...}
 for i = 1, #vals do
 if vals[i] then
 table.insert(t.nodes, vals[i])
 end
 end
 return t
end
metatable.newline = function(t)
 table.insert(t.nodes, '\n')
 return t
end
metatable.tag = function(t, tagName, args)
 args = args or {}
 args.parent = t
 local builder = HtmlBuilder.create(tagName, args)
 table.insert(t.nodes, builder)
 return builder
end
local function getAttr(t, name)
 for i, attr in ipairs(t.attributes) do
 if attr.name == name then
 return attr
 end
 end
end
metatable.attr = function(t, name, val)
 if type(val) == 'string' or type(val) == 'number' then
 -- if caller sets the style attribute explicitly, then replace all styles previously added with css() and cssText()
 if name == 'style' then
 t.styles = {val}
 return t
 end
 
 local attr = getAttr(t, name)
 if attr then
 attr.val = val
 else
 table.insert(t.attributes, {name = name, val = val})
 end
 end
 
 return t
end
metatable.addClass = function(t, class)
 if class then
 local attr = getAttr(t, 'class')
 if attr then
 attr.val = attr.val .. ' ' .. class
 else
 t.attr('class', class)
 end
 end
 
 return t
end
metatable.css = function(t, name, val)
 if type(val) == 'string' or type(val) == 'number' then
 for i, prop in ipairs(t.styles) do
 if prop.name == name then
 prop.val = val
 return t
 end
 end
 
 table.insert(t.styles, {name = name, val = val})
 end
 
 return t
end
metatable.cssText = function(t, css)
 if css then
 table.insert(t.styles, css)
 end
 return t
end
metatable.done = function(t)
 return t.parent or t
end
metatable.allDone = function(t)
 while t.parent do
 t = t.parent
 end
 return t
end
function HtmlBuilder.create(tagName, args)
 args = args or {}
 local builder = {}
 setmetatable(builder, metatable)
 builder.nodes = {}
 builder.attributes = {}
 builder.styles = {}
 builder.tagName = tagName
 builder.parent = args.parent
 builder.unclosed = args.unclosed or false
 builder.selfClosing = args.selfClosing or false
 return builder
end
return HtmlBuilder