Module:Html

- -- From MediaWiki source file -

local loadLib = require('Module:LoadLib')

loadLib(_G)

local exports = {} 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;', }

-- Is this a valid attribute name? -- -- @param s local function isValidAttributeName(s) checkType(1, s, 'string') -- 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) checkType(1, s, 'string') return s:match('^[a-zA-Z_:][a-zA-Z0-9_.:-]*$') end

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

return tmp end

local function cssEncode(s) checkType(1, s, 'string') -- 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

function exports.parseHtml(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 and t.format then insert = table.concat{ 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, ' />') return end table.insert(ret, '>') end for i, node in ipairs(t.nodes) do		if node then if type(node) == 'table' then exports.parseHtml(node, ret) else local insert = tostring(node) if hasChildren and i == 1 and t.format then insert = insert end table.insert(ret, insert) end end end if t.tagName then local insert = table.concat{ '' } table.insert(ret, insert) end end

table.merge(exports, {	isValidAttributeName = isValidAttributeName,	isValidTag = isValidTag,	cssEncode = cssEncode,	htmlEncode = htmlEncode, })

-- Create a new instance -- -- @param tagName -- @param args function exports.create(tagName, args) checkType('create', 1, tagName, 'string', true) checkType('create', 2, args, 'table', true) local methodtable, metatable = {}, {} metatable.__index = methodtable

metatable.__tostring = function(t) local ret = {} t:parse(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 -- 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) assertFalse(tagName ~= nil and tagName ~= '' and not isValidTag(tagName), "invalid tag name %q", 3, tagName) args = args or {} local builder = {} setmetatable(builder, metatable) builder.nodes = {} builder.attributes = {} builder.styles = {} builder.indent = tagName and tagName ~= '' and indent + 2 or indent builder.format = yesno(args.format, true) 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.parse = exports.parseHtml -- 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 methodtable.getCss = function(t) return t.styles end return createBuilder(tagName, args, -2) end

function exports.parseCss(s) local t = mw.text.split(s, '%s*;%s*') local ret = {} for i, v in ipairs(t) do		table.merge(ret, { }) end end

function exports.makeOrderdTable local ret = {} local keys = {} local seenkeys = {}

setmetatable(ret, {		__newindex = function(t, k, v)			if seenkeys[k] and not t[k] then				for i = 1, #keys do					if keys[i] == k then						table.remove(keys, i)						break					end				end			end			seenkeys[k] = 1			keys[#keys+1] = k			rawset(t, k, v)		end,		__pairs = function(t)			local i, l = 0, #keys			return function 				while i < l do					i = i + 1					local k = keys[i]					if t[k] ~= nil then						return k, t[k]					end				end				return nil			end		end,		__metatable = {}	})

return ret end

function exports.test return exports.create('a') :tag('d') :done :done end

return exports