<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.minetest.org/index.php?action=history&amp;feed=atom&amp;title=Module%3AHtmlBuilder</id>
	<title>Module:HtmlBuilder - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.minetest.org/index.php?action=history&amp;feed=atom&amp;title=Module%3AHtmlBuilder"/>
	<link rel="alternate" type="text/html" href="https://wiki.minetest.org/index.php?title=Module:HtmlBuilder&amp;action=history"/>
	<updated>2026-04-29T04:44:59Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.39.4</generator>
	<entry>
		<id>https://wiki.minetest.org/index.php?title=Module:HtmlBuilder&amp;diff=15066&amp;oldid=prev</id>
		<title>&gt;FnControlOption: FnControlOption changed the content model of the page Module:HtmlBuilder from &quot;plain text&quot; to &quot;Scribunto&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.minetest.org/index.php?title=Module:HtmlBuilder&amp;diff=15066&amp;oldid=prev"/>
		<updated>2022-06-07T17:36:53Z</updated>

		<summary type="html">&lt;p&gt;FnControlOption changed the content model of the page &lt;a href=&quot;/Module:HtmlBuilder&quot; title=&quot;Module:HtmlBuilder&quot;&gt;Module:HtmlBuilder&lt;/a&gt; from &amp;quot;plain text&amp;quot; to &amp;quot;Scribunto&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;--[[&lt;br /&gt;
    A module for building complex HTML from Lua using a&lt;br /&gt;
    fluent interface.&lt;br /&gt;
&lt;br /&gt;
    Originally written on the English Wikipedia by&lt;br /&gt;
    Toohool and Mr. Stradivarius.&lt;br /&gt;
&lt;br /&gt;
    Modified for the Minetest Wiki.&lt;br /&gt;
&lt;br /&gt;
    Code released under the GPL v2+ as per:&lt;br /&gt;
    https://en.wikipedia.org/w/index.php?diff=next&amp;amp;oldid=581399786&lt;br /&gt;
    https://en.wikipedia.org/w/index.php?diff=next&amp;amp;oldid=581403025&lt;br /&gt;
&lt;br /&gt;
    @license GNU GPL v2+&lt;br /&gt;
    @author Marius Hoch &amp;lt; hoo@online.de &amp;gt;&lt;br /&gt;
    @author MrIbby &amp;lt; siribby@outlook.com &amp;gt;&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local HtmlBuilder = {}&lt;br /&gt;
local options = mw.loadData( 'Module:HtmlBuilder/options' )&lt;br /&gt;
&lt;br /&gt;
local util = require( 'Module:LibraryUtil' )&lt;br /&gt;
local checkType = util.checkType&lt;br /&gt;
local checkTypeMulti = util.checkTypeMulti&lt;br /&gt;
&lt;br /&gt;
local metatable = {}&lt;br /&gt;
local methodtable = {}&lt;br /&gt;
&lt;br /&gt;
local selfClosingTags = {&lt;br /&gt;
    area = true,&lt;br /&gt;
    base = true,&lt;br /&gt;
    br = true,&lt;br /&gt;
    col = true,&lt;br /&gt;
    command = true,&lt;br /&gt;
    embed = true,&lt;br /&gt;
    hr = true,&lt;br /&gt;
    img = true,&lt;br /&gt;
    input = true,&lt;br /&gt;
    keygen = true,&lt;br /&gt;
    link = true,&lt;br /&gt;
    meta = true,&lt;br /&gt;
    param = true,&lt;br /&gt;
    source = true,&lt;br /&gt;
    track = true,&lt;br /&gt;
    wbr = true,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
metatable.__index = methodtable&lt;br /&gt;
&lt;br /&gt;
metatable.__tostring = function( t )&lt;br /&gt;
    local ret = {}&lt;br /&gt;
    t:_build( ret )&lt;br /&gt;
    return table.concat( ret )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Get an attribute table (name, value) and its index&lt;br /&gt;
--&lt;br /&gt;
-- @param name&lt;br /&gt;
local function getAttr( t, name )&lt;br /&gt;
    for i, attr in ipairs( t.attributes ) do&lt;br /&gt;
        if attr.name == name then&lt;br /&gt;
            return attr, i&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Is this a valid attribute name?&lt;br /&gt;
--&lt;br /&gt;
-- @param s&lt;br /&gt;
local function isValidAttributeName( s )&lt;br /&gt;
    -- Good estimate: http://www.w3.org/TR/2000/REC-xml-20001006#NT-Name&lt;br /&gt;
    return s:match( '^[a-zA-Z_:][a-zA-Z0-9_.:-]*$' )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Is this a valid tag name?&lt;br /&gt;
--&lt;br /&gt;
-- @param s&lt;br /&gt;
local function isValidTag( s )&lt;br /&gt;
    return s:match( '^[a-zA-Z0-9]+$' )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Escape a value, for use in HTML&lt;br /&gt;
--&lt;br /&gt;
-- @param s&lt;br /&gt;
local function htmlEncode( s )&lt;br /&gt;
    -- The parentheses ensure that there is only one return value&lt;br /&gt;
    local tmp = string.gsub( s, '[&amp;lt;&amp;gt;&amp;amp;&amp;quot;]', options.htmlencodeMap );&lt;br /&gt;
    -- Don't encode strip markers here (T110143)&lt;br /&gt;
    tmp = string.gsub( tmp, options.encodedUniqPrefixPat, options.uniqPrefixRepl )&lt;br /&gt;
    tmp = string.gsub( tmp, options.encodedUniqSuffixPat, options.uniqSuffixRepl )&lt;br /&gt;
    return tmp&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function cssEncode( s )&lt;br /&gt;
    -- mw.ustring is so slow that it's worth searching the whole string&lt;br /&gt;
    -- for non-ASCII characters to avoid it if possible&lt;br /&gt;
    return ( string.find( s, '[^%z\1-\127]' ) and mw.ustring or string )&lt;br /&gt;
        -- XXX: I'm not sure this character set is complete.&lt;br /&gt;
        -- bug #68011: allow delete character (\127)&lt;br /&gt;
        .gsub( s, '[^\32-\57\60-\127]', function ( m )&lt;br /&gt;
            return string.format( '\\%X ', mw.ustring.codepoint( m ) )&lt;br /&gt;
        end )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Create a builder object. This is a separate function so that we can show the&lt;br /&gt;
-- correct error levels in both HtmlBuilder.create and metatable.tag.&lt;br /&gt;
--&lt;br /&gt;
-- @param tagName&lt;br /&gt;
-- @param args&lt;br /&gt;
local function createBuilder( tagName, args )&lt;br /&gt;
    if tagName ~= nil and tagName ~= '' and not isValidTag( tagName ) then&lt;br /&gt;
        error( string.format( &amp;quot;invalid tag name '%s'&amp;quot;, tagName ), 3 )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    args = args or {}&lt;br /&gt;
    local builder = {}&lt;br /&gt;
    setmetatable( builder, metatable )&lt;br /&gt;
    builder.nodes = {}&lt;br /&gt;
    builder.attributes = {}&lt;br /&gt;
    builder.styles = {}&lt;br /&gt;
&lt;br /&gt;
    if tagName ~= '' then&lt;br /&gt;
        builder.tagName = tagName&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    builder.parent = args.parent&lt;br /&gt;
    builder.selfClosing = selfClosingTags[tagName] or args.selfClosing or false&lt;br /&gt;
    return builder&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Append a builder to the current node. This is separate from methodtable.node&lt;br /&gt;
-- so that we can show the correct error level in both methodtable.node and&lt;br /&gt;
-- methodtable.wikitext.&lt;br /&gt;
--&lt;br /&gt;
-- @param builder&lt;br /&gt;
local function appendBuilder( t, builder )&lt;br /&gt;
    if t.selfClosing then&lt;br /&gt;
        error( &amp;quot;self-closing tags can't have child nodes&amp;quot;, 3 )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if builder then&lt;br /&gt;
        table.insert( t.nodes, builder )&lt;br /&gt;
    end&lt;br /&gt;
    return t&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
methodtable._buildAttributes = function( t, ret )&lt;br /&gt;
    for i, attr in ipairs( t.attributes ) do&lt;br /&gt;
        table.insert(&lt;br /&gt;
            ret,&lt;br /&gt;
            -- Note: Attribute names have already been validated&lt;br /&gt;
            ' ' .. attr.name .. '=&amp;quot;' .. htmlEncode( attr.val ) .. '&amp;quot;'&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
methodtable._buildStyles = function( t, ret )&lt;br /&gt;
    if #t.styles &amp;gt; 0 then&lt;br /&gt;
        table.insert( ret, ' style=&amp;quot;' )&lt;br /&gt;
        local css = {}&lt;br /&gt;
        for i, prop in ipairs( t.styles ) do&lt;br /&gt;
            if type( prop ) ~= 'table' then -- added with cssText()&lt;br /&gt;
                table.insert( css, htmlEncode( prop ) )&lt;br /&gt;
            else -- added with css()&lt;br /&gt;
                table.insert(&lt;br /&gt;
                    css,&lt;br /&gt;
                    htmlEncode( cssEncode( prop.name ) .. ':' .. cssEncode( prop.val ) )&lt;br /&gt;
                )&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
        table.insert( ret, table.concat( css, ';' ) )&lt;br /&gt;
        table.insert( ret, '&amp;quot;' )&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
methodtable._buildNodes = function( t, ret )&lt;br /&gt;
    for i, node in ipairs( t.nodes ) do&lt;br /&gt;
        if node then&lt;br /&gt;
            if type( node ) == 'table' then&lt;br /&gt;
                node:_build( ret )&lt;br /&gt;
            else&lt;br /&gt;
                table.insert( ret, tostring( node ) )&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
methodtable._build = function( t, ret )&lt;br /&gt;
    if t.tagName then&lt;br /&gt;
        table.insert( ret, '&amp;lt;' .. t.tagName )&lt;br /&gt;
        t:_buildAttributes( ret )&lt;br /&gt;
        t:_buildStyles( ret )&lt;br /&gt;
        if t.selfClosing then&lt;br /&gt;
            table.insert( ret, ' /&amp;gt;' )&lt;br /&gt;
            return&lt;br /&gt;
        end&lt;br /&gt;
        table.insert( ret, '&amp;gt;' )&lt;br /&gt;
    end&lt;br /&gt;
    t:_buildNodes( ret )&lt;br /&gt;
    if t.tagName then&lt;br /&gt;
        table.insert( ret, '&amp;lt;/' .. t.tagName .. '&amp;gt;' )&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Append a builder to the current node&lt;br /&gt;
--&lt;br /&gt;
-- @param builder&lt;br /&gt;
methodtable.node = function( t, builder )&lt;br /&gt;
    return appendBuilder( t, builder )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Appends some markup to the node. This will be treated as wikitext.&lt;br /&gt;
methodtable.wikitext = function( t, ... )&lt;br /&gt;
    for k,v in ipairs{...} do&lt;br /&gt;
        checkTypeMulti( 'wikitext', k, v, { 'string', 'number' } )&lt;br /&gt;
        appendBuilder( t, v )&lt;br /&gt;
    end&lt;br /&gt;
    return t&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Appends a newline character to the node.&lt;br /&gt;
methodtable.newline = function( t )&lt;br /&gt;
    return t:wikitext( '\n' )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Appends a new child node to the builder, and returns an HtmlBuilder instance&lt;br /&gt;
-- representing that new node.&lt;br /&gt;
--&lt;br /&gt;
-- @param tagName&lt;br /&gt;
-- @param args&lt;br /&gt;
methodtable.tag = function( t, tagName, args )&lt;br /&gt;
    checkType( 'tag', 1, tagName, 'string' )&lt;br /&gt;
    checkType( 'tag', 2, args, 'table', true )&lt;br /&gt;
    args = args or {}&lt;br /&gt;
&lt;br /&gt;
    args.parent = t&lt;br /&gt;
    local builder = createBuilder( tagName, args )&lt;br /&gt;
    t:node( builder )&lt;br /&gt;
    return builder&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Get the value of an html attribute&lt;br /&gt;
--&lt;br /&gt;
-- @param name&lt;br /&gt;
methodtable.getAttr = function( t, name )&lt;br /&gt;
    checkType( 'getAttr', 1, name, 'string' )&lt;br /&gt;
&lt;br /&gt;
    local attr = getAttr( t, name )&lt;br /&gt;
    return attr and attr.val&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Set an HTML attribute on the node.&lt;br /&gt;
--&lt;br /&gt;
-- @param name Attribute to set, alternative table of name-value pairs&lt;br /&gt;
-- @param val Value of the attribute. Nil causes the attribute to be unset&lt;br /&gt;
methodtable.attr = function( t, name, val )&lt;br /&gt;
    if type( name ) == 'table' then&lt;br /&gt;
        if val ~= nil then&lt;br /&gt;
            error(&lt;br /&gt;
                &amp;quot;bad argument #2 to 'attr' &amp;quot; ..&lt;br /&gt;
                '(if argument #1 is a table, argument #2 must be left empty)',&lt;br /&gt;
                2&lt;br /&gt;
            )&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local callForTable = function()&lt;br /&gt;
            for attrName, attrValue in pairs( name ) do&lt;br /&gt;
                t:attr( attrName, attrValue )&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if not pcall( callForTable ) then&lt;br /&gt;
            error(&lt;br /&gt;
                &amp;quot;bad argument #1 to 'attr' &amp;quot; ..&lt;br /&gt;
                '(table keys must be strings, and values must be strings or numbers)',&lt;br /&gt;
                2&lt;br /&gt;
            )&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        return t&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    checkType( 'attr', 1, name, 'string' )&lt;br /&gt;
    checkTypeMulti( 'attr', 2, val, { 'string', 'number', 'nil' } )&lt;br /&gt;
&lt;br /&gt;
    -- if caller sets the style attribute explicitly, then replace all styles&lt;br /&gt;
    -- previously added with css() and cssText()&lt;br /&gt;
    if name == 'style' then&lt;br /&gt;
        t.styles = { val }&lt;br /&gt;
        return t&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if not isValidAttributeName( name ) then&lt;br /&gt;
        error( string.format(&lt;br /&gt;
            &amp;quot;bad argument #1 to 'attr' (invalid attribute name '%s')&amp;quot;,&lt;br /&gt;
            name&lt;br /&gt;
        ), 2 )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local attr, i = getAttr( t, name )&lt;br /&gt;
    if attr then&lt;br /&gt;
        if val ~= nil then&lt;br /&gt;
            attr.val = val&lt;br /&gt;
        else&lt;br /&gt;
            table.remove( t.attributes, i )&lt;br /&gt;
        end&lt;br /&gt;
    elseif val ~= nil then&lt;br /&gt;
        table.insert( t.attributes, { name = name, val = val } )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return t&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Adds a class name to the node's class attribute. Spaces will be&lt;br /&gt;
-- automatically added to delimit each added class name.&lt;br /&gt;
--&lt;br /&gt;
-- @param class&lt;br /&gt;
methodtable.addClass = function( t, class )&lt;br /&gt;
    checkTypeMulti( 'addClass', 1, class, { 'string', 'number', 'nil' } )&lt;br /&gt;
&lt;br /&gt;
    if class ~= nil then&lt;br /&gt;
        local attr = getAttr( t, 'class' )&lt;br /&gt;
        if attr then&lt;br /&gt;
            attr.val = attr.val .. ' ' .. class&lt;br /&gt;
        else&lt;br /&gt;
            t:attr( 'class', class )&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return t&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Set a CSS property to be added to the node's style attribute.&lt;br /&gt;
--&lt;br /&gt;
-- @param name CSS attribute to set, alternative table of name-value pairs&lt;br /&gt;
-- @param val The value to set. Nil causes it to be unset&lt;br /&gt;
methodtable.css = function( t, name, val )&lt;br /&gt;
    if type( name ) == 'table' then&lt;br /&gt;
        if val ~= nil then&lt;br /&gt;
            error(&lt;br /&gt;
                &amp;quot;bad argument #2 to 'css' &amp;quot; ..&lt;br /&gt;
                '(if argument #1 is a table, argument #2 must be left empty)',&lt;br /&gt;
                2&lt;br /&gt;
            )&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local callForTable = function()&lt;br /&gt;
            for attrName, attrValue in pairs( name ) do&lt;br /&gt;
                t:css( attrName, attrValue )&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if not pcall( callForTable ) then&lt;br /&gt;
            error(&lt;br /&gt;
                &amp;quot;bad argument #1 to 'css' &amp;quot; ..&lt;br /&gt;
                '(table keys and values must be strings or numbers)',&lt;br /&gt;
                2&lt;br /&gt;
            )&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        return t&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    checkTypeMulti( 'css', 1, name, { 'string', 'number' } )&lt;br /&gt;
    checkTypeMulti( 'css', 2, val, { 'string', 'number', 'nil' } )&lt;br /&gt;
&lt;br /&gt;
    for i, prop in ipairs( t.styles ) do&lt;br /&gt;
        if prop.name == name then&lt;br /&gt;
            if val ~= nil then&lt;br /&gt;
                prop.val = val&lt;br /&gt;
            else&lt;br /&gt;
                table.remove( t.styles, i )&lt;br /&gt;
            end&lt;br /&gt;
            return t&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if val ~= nil then&lt;br /&gt;
        table.insert( t.styles, { name = name, val = val } )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return t&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Add some raw CSS to the node's style attribute. This is typically used&lt;br /&gt;
-- when a template allows some CSS to be passed in as a parameter&lt;br /&gt;
--&lt;br /&gt;
-- @param css&lt;br /&gt;
methodtable.cssText = function( t, css )&lt;br /&gt;
    checkTypeMulti( 'cssText', 1, css, { 'string', 'number', 'nil' } )&lt;br /&gt;
    table.insert( t.styles, css )&lt;br /&gt;
    return t&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns the parent node under which the current node was created. Like&lt;br /&gt;
-- jQuery.end, this is a convenience function to allow the construction of&lt;br /&gt;
-- several child nodes to be chained together into a single statement.&lt;br /&gt;
methodtable.done = function( t )&lt;br /&gt;
    return t.parent or t&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Like .done(), but traverses all the way to the root node of the tree and&lt;br /&gt;
-- returns it.&lt;br /&gt;
methodtable.allDone = function( t )&lt;br /&gt;
    while t.parent do&lt;br /&gt;
        t = t.parent&lt;br /&gt;
    end&lt;br /&gt;
    return t&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Create a new instance&lt;br /&gt;
--&lt;br /&gt;
-- @param tagName&lt;br /&gt;
-- @param args&lt;br /&gt;
function HtmlBuilder.create( tagName, args )&lt;br /&gt;
    checkType( 'HtmlBuilder.create', 1, tagName, 'string', true )&lt;br /&gt;
    checkType( 'HtmlBuilder.create', 2, args, 'table', true )&lt;br /&gt;
    return createBuilder( tagName, args )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return HtmlBuilder&lt;/div&gt;</summary>
		<author><name>&gt;FnControlOption</name></author>
	</entry>
</feed>