モジュール:サンドボックス/Ef3/mw.html.lua

--
	A module for building complex HTML from Lua using a
	fluent interface.
	Originally written on the English Wikipedia by
	Toohool and Mr. Stradivarius.
	Code released under the GPL v2+ as per:
	https://en.wikipedia.org/w/index.php?diff=next&oldid=581399786
	https://en.wikipedia.org/w/index.php?diff=next&oldid=581403025
	@license GNU GPL v2+
	@author Marius Hoch 
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 == expectType then
			return
		end
	end
	local n = #expectTypes
	local typeList
	if n > 1 then
		typeList = table.concat( expectTypes, ', ', 1, n - 1 ) .. ' or ' .. expectTypesn
	else
		typeList = expectTypes1
	end
	local msg = string.format( "bad argument #%d to '%s' (%s expected, got %s)",
		argIdx,
		name,
		typeList,
		type( arg )
	)
	error( msg, 3 )
end
local metatable = {}
local methodtable = {}
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 = {
	'>' = '>',
	'&"', 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 args
local 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 = selfClosingTagstagName 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 )
	if t.tagName then
		table.insert( ret, ' 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
				node:_build( ret )
			else
				table.insert( ret, tostring( node ) )
			end
		end
	end
	if t.tagName then
		table.insert( ret,  )
	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, ... )
	local vals = {...}
	for i = 1, #vals do
		checkTypeMulti( 'wikitext', i, valsi, { 'string', 'number' } )
		appendBuilder( t, valsi )
	end
	return t
end
-- Appends a newline character to the node.
methodtable.newline = function( t )
	t:wikitext( '\n' )
	return t
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: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 )
	if attr then
		return attr.val
	end
	return nil
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' )
	checkTypeMulti( 'attr', 2, val, { 'string', 'number', 'nil' } )
	-- 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 )
	checkTypeMulti( 'addClass', 1, class, { 'string', 'number', 'nil' } )
	if class == nil then
		return t
	end
	local attr = getAttr( t, 'class' )
	if attr then
		attr.val = attr.val .. ' ' .. class
	else
		t:attr( 'class', class )
	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
	checkTypeMulti( 'css', 1, name, { 'string', 'number' } )
	checkTypeMulti( 'css', 2, val, { 'string', 'number', 'nil' } )
	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 )
	checkTypeMulti( 'cssText', 1, css, { 'string', 'number', 'nil' } )
	if css ~= nil then
		table.insert( t.styles, css )
	end
	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 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" global
mw = mw or {}
mw.html = HtmlBuilder
package.loaded'mw.html' = HtmlBuilder
return HtmlBuilder