--
local HtmlBuilder =
local util = require 'libraryUtil'local checkType = util.checkType
local function checkTypeMulti(name, argIdx, arg, expectTypes) local argType = type(arg) for _, expectType in ipairs(expectTypes) do if argType
local metatable = local methodtable =
local selfClosingTags =
local htmlencodeMap =
metatable.__index = methodtable
metatable.__tostring = function(t) local ret = t:_build(ret) return table.concat(ret)end
-- Get an attribute table (name, value) and its index---- @param namelocal function getAttr(t, name) for i, attr in ipairs(t.attributes) do if attr.name
-- Is this a valid attribute name?---- @param slocal 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 slocal function isValidTag(s) return s:match('^[a-zA-Z0-9]+$')end
-- Escape a value, for use in HTML---- @param slocal function htmlEncode(s) -- The parentheses ensure that there is only one return value return (string.gsub(s, '[<>&"]', htmlencodeMap))end
local function cssEncode(s) -- XXX: I'm not sure this character set is complete. -- bug #68011: allow delete character (\127) return (s:find('[^%z\1-\127]') and mw.ustring or string) .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 argslocal function createBuilder(tagName, args) if tagName ~= nil and tagName ~= and not isValidTag(tagName) then error(string.format("invalid tag name '%s'", tagName), 3) end
args = args or local builder = setmetatable(builder, metatable) builder.nodes = builder.attributes = builder.styles =
if tagName ~= then builder.tagName = tagName end
builder.parent = args.parent builder.selfClosing = selfClosingTags[tagName] or args.selfClosing or false return builderend
-- 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 builderlocal 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 tend
methodtable._build = function(t, ret) if t.tagName then table.insert(ret, '<' .. t.tagName) for i, attr in ipairs(t.attributes) do table.insert(ret, -- Note: Attribute names have already been validated ' ' .. 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)
-- Append a builder to the current node---- @param buildermethodtable.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, ...) local vals = for i = 1, #vals do checkTypeMulti('wikitext', i, vals[i],) appendBuilder(t, vals[i]) end return tend
-- Appends a newline character to the node.methodtable.newline = function(t) t:wikitext('\n') return tend
-- Appends a new child node to the builder, and returns an HtmlBuilder instance-- representing that new node.---- @param tagName-- @param argsmethodtable.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:node(builder) return builderend
-- Get the value of an html attribute---- @param namemethodtable.getAttr = function(t, name) checkType('getAttr', 1, name, 'string')
local attr = getAttr(t, name) if attr then return attr.val end return nilend
-- 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 unsetmethodtable.attr = function(t, name, val) if type(name)
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') checkTypeMulti('attr', 2, val,)
-- if caller sets the style attribute explicitly, then replace all styles -- previously added with css and cssText if name
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,) end
return tend
-- Adds a class name to the node's class attribute. Spaces will be-- automatically added to delimit each added class name.---- @param classmethodtable.addClass = function(t, class) checkTypeMulti('addClass', 1, class,)
if class
local attr = getAttr(t, 'class') if attr then attr.val = attr.val .. ' ' .. class else t:attr('class', class) end
return tend
-- 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 unsetmethodtable.css = function(t, name, val) if type(name)
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
checkTypeMulti('css', 1, name,) checkTypeMulti('css', 2, val,)
for i, prop in ipairs(t.styles) do if prop.name
if val ~= nil then table.insert(t.styles,) end
return tend
-- 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 cssmethodtable.cssText = function(t, css) checkTypeMulti('cssText', 1, css,) if css ~= nil then table.insert(t.styles, css) end return tend
-- 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 tend
-- 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 tend
-- Create a new instance---- @param tagName-- @param argsfunction HtmlBuilder.create(tagName, args) checkType('mw.html.create', 1, tagName, 'string', true) checkType('mw.html.create', 2, args, 'table', true) return createBuilder(tagName, args)end
mw_interface = nil
-- Register this library in the "mw" globalmw = mw or mw.html = HtmlBuilder
package.loaded['mw.html'] = HtmlBuilder
return HtmlBuilder