Module:Html

- -- From MediaWiki source file -

local loadLib = require('Module:LoadLib')

loadLib(_G)

local exports, methodtable, metatable = {}, {}, {} local selfClosingTags = { area = true, base = true, br = true, col = true, command = true, embed = true, hr = true, img = true, input = true, keygen = true, link = true, meta = true, param = true, source = true, track = true, wbr = true, }

local htmlencodeMap = { ['>'] = '&gt;', ['<'] = '&lt;', ['&'] = '&amp;', ['"'] = '&quot;', }

metatable.__index = methodtable

metatable.__tostring = function(t) local ret = {} t:_build(ret) return table.concat(ret) :gsub('<%/(%w-)> ?\n( *)\n', '\n') :gsub('<(%w-)> ?\n( *)\n', '\n') :gsub('>\n\n', '>\n') end

metatable.__concat = function(a, b)	return tostring(a)..tostring(b) end

-- Get an attribute table (name, value) and its index -- -- @param name local function getAttr(t, name) for i, attr in ipairs(t.attributes) do		if attr.name == name then return attr, i		end end end

-- Is this a valid attribute name? -- -- @param s local function isValidAttributeName(s) -- Good estimate: http://www.w3.org/TR/2000/REC-xml-20001006#NT-Name return s:match('^[a-zA-Z_:][a-zA-Z0-9_.:-]*$') end

-- Is this a valid tag name? -- -- @param s local function isValidTag(s) return s:match('^[a-zA-Z0-9%-_]+$') end

-- Escape a value, for use in HTML -- -- @param s local function htmlEncode(s) -- The parentheses ensure that there is only one return value local tmp = string.gsub(s, '[<>&"]', htmlencodeMap);

return tmp end

local function cssEncode(s) -- mw.ustring is so slow that it's worth searching the whole string -- for non-ASCII characters to avoid it if possible return ( string.find(s, '[^%z\1-\127]') and mw.ustring or string ) -- XXX: I'm not sure this character set is complete. -- bug #68011: allow delete character (\127) .gsub(s, '[^\32-\57\60-\127]', function ( m)			return string.format('\\%X ', mw.ustring.codepoint( m) )		end ) end

-- Create a builder object. This is a separate function so that we can show the -- correct error levels in both HtmlBuilder.create and metatable.tag. -- -- @param tagName -- @param args local function createBuilder(tagName, args, indent) if tagName ~= nil and tagName ~= '' and not isValidTag(tagName) then error(string.format( "invalid tag name %q", tagName), 3 ) end

args = args or {} local builder = {} setmetatable(builder, metatable) builder.nodes = {} builder.attributes = {} builder.styles = {} builder.indent = indent + 2

if tagName ~= '' then builder.tagName = tagName end

builder.parent = args.parent builder.selfClosing = selfClosingTags[tagName] or args.selfClosing or false return builder end

-- Append a builder to the current node. This is separate from methodtable.node -- so that we can show the correct error level in both methodtable.node and -- methodtable.wikitext. -- -- @param builder local function appendBuilder(t, builder) if t.selfClosing then error("self-closing tags can't have child nodes", 3) end

if builder then table.insert(t.nodes, builder) end return t end

methodtable._build = function(t, ret) local hasChildren = table.some(t.nodes, function(k, v)		return type(v) == "table"		end) if t.tagName then local insert = '<' .. t.tagName if #((t.parent or {}).nodes or {}) >= 1 then insert = table.concat{ '\n', (' '):rep(t.indent), insert } end table.insert(ret, insert) for i, attr in ipairs(t.attributes) do			table.insert(				ret,				-- Note: Attribute names have already been validated				table.concat{ ' ', attr.name, '="', htmlEncode(attr.val), '"' }			) end if #t.styles > 0 then table.insert(ret, ' style="')			local css = {}			for i, prop in ipairs(t.styles) do				if type(prop) ~= 'table' then -- added with cssText					table.insert(css, htmlEncode( prop) )				else -- added with css					table.insert(						css,						htmlEncode(cssEncode( prop.name) .. ': ' .. cssEncode(prop.val) )					)				end			end			table.insert(ret, table.concat( css, '; ') .. ';' )			table.insert(ret, '"') end if t.selfClosing then table.insert(ret, ' />\n') return end table.insert(ret, '>'..(hasChildren and '\n' or '')) end for i, node in ipairs(t.nodes) do		if node then if type(node) == 'table' then node:_build(ret) else local insert = tostring(node) if hasChildren and i == 1 then insert = (' '):rep(t.indent+2)..insert end table.insert(ret, insert) end end end if t.tagName then local insert = table.concat{ '' } table.insert(ret, 			(hasChildren and '\n'..(' '):rep(t.indent) or '') 			.. '' 			.. ( #((t.parent or {}).nodes or {}) > 2 and '\n' .. (' '):rep(t.indent) or '' )		)	end end

-- Append a builder to the current node -- -- @param builder methodtable.node = function(t, builder) return appendBuilder(t, builder) end

-- Appends some markup to the node. This will be treated as wikitext. methodtable.wikitext = function(t, ...) for k,v in ipairs{ ... } do		checkType('wikitext', k, v, { 'string', 'number' }) appendBuilder(t, string.parseDualArg(v)) end return t end

-- Appends a newline character to the node. methodtable.newline = function(t) return t:wikitext('\n') end

-- Appends a new child node to the builder, and returns an HtmlBuilder instance -- representing that new node. -- -- @param tagName -- @param args methodtable.tag = function(t, tagName, args) checkType('tag', 1, tagName, 'string') checkType('tag', 2, args, 'table', true) args = args or {}

args.parent = t	local builder = createBuilder(tagName, args, t.indent) t:node(builder) return builder end

-- Get the value of an html attribute -- -- @param name methodtable.getAttr = function(t, name) checkType('getAttr', 1, name, 'string')

local attr = getAttr(t, name) return attr and attr.val end

-- Set an HTML attribute on the node. -- -- @param name Attribute to set, alternative table of name-value pairs -- @param val Value of the attribute. Nil causes the attribute to be unset methodtable.attr = function(t, name, val) if type(name) == 'table' then if val ~= nil then error(				"bad argument #2 to 'attr' " ..				'(if argument #1 is a table, argument #2 must be left empty)',				2			) end

local callForTable = function for attrName, attrValue in pairs(name) do				t:attr(attrName, attrValue) end end

if not pcall(callForTable) then error(				"bad argument #1 to 'attr' " ..				'(table keys must be strings, and values must be strings or numbers)',				2			) end

return t	end

checkType('attr', 1, name, 'string') checkType('attr', 2, val, { 'string', 'number'}, true)

-- 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

if not isValidAttributeName(name) then error( string.format( "bad argument #1 to 'attr' (invalid attribute name '%s')", name ), 2)	end

local attr, i = getAttr(t, name) if attr then if val ~= nil then attr.val = val else table.remove(t.attributes, i)		end elseif val ~= nil then table.insert(t.attributes, { name = name, val = val }) end

return t end

-- Adds a class name to the node's class attribute. Spaces will be -- automatically added to delimit each added class name. -- -- @param class methodtable.addClass = function(t, class) checkType('addClass', 1, class, { 'string', 'number', 'nil' })

if class ~= nil then local attr = getAttr(t, 'class') if attr then attr.val = attr.val .. ' ' .. class else t:attr('class', class) end end return t end

-- Set a CSS property to be added to the node's style attribute. -- -- @param name CSS attribute to set, alternative table of name-value pairs -- @param val The value to set. Nil causes it to be unset methodtable.css = function(t, name, val) if type(name) == 'table' then if val ~= nil then error(				"bad argument #2 to 'css' " ..				'(if argument #1 is a table, argument #2 must be left empty)',				2			) end

local callForTable = function for attrName, attrValue in pairs(name) do				t:css(attrName, attrValue) end end

if not pcall(callForTable) then error(				"bad argument #1 to 'css' " ..				'(table keys and values must be strings or numbers)',				2			) end

return t	end

checkType('css', 1, name, { 'string', 'number' }) checkType('css', 2, val, { 'string', 'number' }, true)

for i, prop in ipairs(t.styles) do		if prop.name == name then if val ~= nil then prop.val = val else table.remove(t.styles, i)			end return t		end end

if val ~= nil then table.insert(t.styles, { name = name, val = val }) end

return t end

-- Add some raw CSS to the node's style attribute. This is typically used -- when a template allows some CSS to be passed in as a parameter -- -- @param css methodtable.cssText = function(t, css) checkType( 'cssText', 1, css, { 'string', 'number' }, true) table.insert(t.styles, css) return t end

-- Returns the parent node under which the current node was created. Like -- jQuery.end, this is a convenience function to allow the construction of -- several child nodes to be chained together into a single statement. methodtable.done = function(t) return t.parent or t end

-- Like .done, but traverses all the way to the root node of the tree and -- returns it. methodtable.allDone = function(t) while t.parent do		t = t.parent end return t end

-- Create a new instance -- -- @param tagName -- @param args function exports.create(tagName, args) checkType('create', 1, tagName, 'string') checkType('create', 2, args, 'table', true) return createBuilder(tagName, args, -2) end

function exports.test return exports.create('test') :tag("div") :wikitext("Test") :tag("div") :css("e", "34px") :wikitext("Test2") :done :wikitext("Test2") :wikitext("Test2") :tag("div") :wikitext("Test2") :done :wikitext("Test2") :done end

return exports