打开/关闭菜单
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

模块:HtmlBuilder

来自末世录
OnlyOTO留言 | 贡献2026年3月28日 (六) 19:55的版本

此模块的文档可以在模块:HtmlBuilder/doc创建

-- Module:HtmlBuilder - 安全版本,允许 tagName 为 nil(无标签根节点)
local HtmlBuilder = {}

local metatable = {}

-- 辅助函数:确保值为字符串,若非字符串且非 nil 则尝试转换
local function ensureString(v)
    if v == nil then return nil end
    if type(v) == 'string' then return v end
    return tostring(v)
end

metatable.__index = function(t, key)
    local ret = rawget(t, key)
    if ret then
        return ret
    end
    
    ret = metatable[key]
    if type(ret) == 'function' then
        return function(...) 
            return ret(t, ...) 
        end 
    else
        return ret
    end
end

metatable.__tostring = function(t)
    local ret = {}
    t._build(ret)
    return table.concat(ret)
end

metatable._build = function(t, ret)
    -- 如果 tagName 是字符串,输出开始标签
    if t.tagName and type(t.tagName) == 'string' then
        table.insert(ret, '<' .. t.tagName)
        for i, attr in ipairs(t.attributes) do
            table.insert(ret, ' ' .. attr.name .. '="' .. attr.val .. '"') 
        end
        if #t.styles > 0 then
            table.insert(ret, ' style="')
            for i, prop in ipairs(t.styles) do
                if type(prop) == 'string' then
                    table.insert(ret, prop .. ';')
                else
                    table.insert(ret, prop.name .. ':' .. prop.val .. ';')
                end
            end
            table.insert(ret, '"')
        end
        if t.selfClosing then
            table.insert(ret, ' /')
        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
    -- 如果 tagName 是字符串且不是自闭合,输出结束标签
    if t.tagName and type(t.tagName) == 'string' and not t.unclosed and not t.selfClosing then
        table.insert(ret, '</' .. t.tagName .. '>')
    end
end

metatable.node = function(t, builder)
    if builder then
        table.insert(t.nodes, builder)
    end
    return t
end

metatable.wikitext = function(t, ...) 
    local vals = {...}
    for i = 1, #vals do
        if vals[i] then
            table.insert(t.nodes, vals[i])
        end
    end
    return t
end

metatable.newline = function(t)
    table.insert(t.nodes, '\n')
    return t
end

metatable.tag = function(t, tagName, args)
    args = args or {}
    args.parent = t
    local safeTagName = ensureString(tagName)
    if not safeTagName then
        error("HtmlBuilder.tag: tagName 必须是字符串,传入类型为 " .. type(tagName))
    end
    local builder = HtmlBuilder.create(safeTagName, args)
    table.insert(t.nodes, builder)
    return builder
end

local function getAttr(t, name)
    for i, attr in ipairs(t.attributes) do
        if attr.name == name then
            return attr
        end
    end
end

metatable.attr = function(t, name, val)
    if val ~= nil then
        local nameStr = ensureString(name)
        local valStr = ensureString(val)
        if nameStr == 'style' then
            t.styles = {valStr}
            return t
        end
        
        local attr = getAttr(t, nameStr)
        if attr then
            attr.val = valStr
        else
            table.insert(t.attributes, {name = nameStr, val = valStr})
        end
    end
    
    return t
end

metatable.addClass = function(t, class)
    if class then
        local classStr = ensureString(class)
        if classStr then
            local attr = getAttr(t, 'class')
            if attr then
                attr.val = attr.val .. ' ' .. classStr
            else
                t.attr('class', classStr)
            end
        end
    end
    
    return t
end

metatable.css = function(t, name, val)
    if name and val then
        local nameStr = ensureString(name)
        local valStr = ensureString(val)
        for i, prop in ipairs(t.styles) do
            if prop.name == nameStr then
                prop.val = valStr
                return t
            end
        end
        
        table.insert(t.styles, {name = nameStr, val = valStr})
    end
    
    return t
end

metatable.cssText = function(t, css)
    if css then
        table.insert(t.styles, ensureString(css))
    end
    return t
end

metatable.done = function(t)
    return t.parent or t
end

metatable.allDone = function(t)
    while t.parent do
        t = t.parent
    end
    return t
end

function HtmlBuilder.create(tagName, args)
    args = args or {}
    -- 允许 tagName 为 nil,表示无标签的根容器
    if tagName ~= nil and type(tagName) ~= 'string' then
        error("HtmlBuilder.create: tagName 必须是字符串或 nil,传入类型为 " .. type(tagName))
    end
    local builder = {}
    setmetatable(builder, metatable)
    builder.nodes = {}
    builder.attributes = {}
    builder.styles = {}
    builder.tagName = tagName
    builder.parent = args.parent
    builder.unclosed = args.unclosed or false
    builder.selfClosing = args.selfClosing or false
    return builder
end

return HtmlBuilder