Module:Nav

From Moegirlpedia
Revision as of 23:47, 5 March 2022 by LiaMinina (talk | contribs)
Jump to: navigation, search
Template-info.svg Module Documentation  [View] [Edit] [History] [Refresh]

Introduction

This module is to optimize the resources consumed in the process of expanding the template of the analyzer. It is only for {{Navbar}}, {{Navbox}} and their sister templates. The author is zhmoe:User:サンムル. Some templates are translated and rewritten according to the user There are some additions and deletions to the parameters due to factors such as habits.

Invokes

nav.bar

One of the export functions of the module, called in the wiki code via {{#invoke:Nav|bar}}, used to build the wiki code for {{Navbar}}.

nav.box

One of the export functions of the module, called in the wiki code via {{#invoke:Nav|box}}, used to build the wiki code for {{Navbox}} and its sister templates.

The second unnamed parameter of the module is to fill in the Navbox auxiliary template {{Navbox subgroup}}, {{Navbox with columns}} and {{Navbox with collapsible groups}} styles, for example: {{#invoke:Nav|box|collapsible groups}}, Navbox subgroup has already used this module, it is recommended not to invoke it separately.

local nav = {}

local notnil = function(value, default) return value or default end
local __nn = notnil
local notnilnorempty = function(value, default)
    if value == nil then return default
    elseif type(value) == "string" and value == "" then return default
    else return value
    end
end
local __nne = notnilnorempty
local notnilnorwhitespace = function(value, default)
    if value == nil then return default
    elseif type(value) == "string" then
        if value == "" then return value
        elseif mw.text.trim(value) == "" then return default
        else return value
        end
    else return value
    end
end
local __nnw = notnilnorwhitespace
local notnilnoremptynorwhitespace = function(value, default)
    if value == nil then return default
    elseif type(value) == "string" and mw.text.trim(value) == "" then return default
    else return value
    end
end
local __nnew = notnilnoremptynorwhitespace

local iif = function(condition, truevalue, falsevalue)
    if condition then return truevalue
    else return falsevalue
    end
end

--[[
    Get the serial number of the current parameter, support multiple serial numbers, and support multiple compatible writing methods.
pattern:
     The format is based on regular expressions, with the addition of the following matchers:
     % - will be automatically replaced with the regular expression "(%d+)" to match the sequence number at this position.
     %% - will be automatically replaced with "%" in regular expressions, used as an escape for "%" characters in regular expressions.
return:
     [All serial number texts] [All serial number values]
     Note: Each value segment above is an independent return value list item;
Example:
    paramname: prefix000infix10postfix08
    pattern: ^prefix%infix%postfix%$
    Returns: 000     10     08     0     10     8
--]]
local paramindexes = function(paramname, pattern)
    local indexes = { mw.ustring.match(paramname, mw.ustring.gsub(pattern, "%%%%?", function(m)
        if m == "%%" then return "%"
        else return "(%d+)"
        end
    end)) }
    local count = #indexes
    if count ~= 0 then
        for i = 1, count do
            indexes[i + count] = tonumber(indexes[i])
        end

        return unpack(indexes)
    end
end

local indexedparamvalue = function(args, pattern, raw, index, rawfunc, indexfunc)
    if args == nil or pattern == nil or raw == nil or index == nil then return nil end
    if rawfunc ~= nil and indexfunc == nil then indexfunc = rawfunc end
    rawfunc = rawfunc or __nnew
    indexfunc = indexfunc or __nnew
    if type(raw) ~= "table" then raw = { raw } end
    if type(index) ~= "table" then index = { index } end

    local i = 0
    rawname = mw.ustring.gsub(pattern, "%%%%?", function(m)
        if m == "%%" then return "%"
        else
            i = i + 1
            return tostring(raw[i] or "")
        end
    end)
    rawvalue = args[rawname]
    local result = rawfunc(rawvalue)
    if result then return result end
    i = 0
    indexname = mw.ustring.gsub(pattern, "%%%%?", function(m)
        if m == "%%" then return "%"
        else
            i = i + 1
            return tostring(index[i] or "")
        end
    end)
    indexvalue = args[indexname]
    result = indexfunc(indexvalue)
    if result then return result end

    local fuzzyMatchingOn = __nnew(args.fuzzyMatchingOn, "no") == "yes" -- fuzzy matching. 
    if not fuzzyMatchingOn then return nil end -- This is the end of the processing flow when fuzzy matching of parameter names is not enabled. 

-- Start fuzzy matching of parameter names.
     -- Since the format of most parameter names is "[name][number serial number]", using the [name] prefix as the first filter will reduce the time-consuming regular matching function running times and greatly optimize the code running efficiency. 
    local prefixpos = 1
    local v1, v2 = 1, 0
    while true do
        local v1, v2 = mw.ustring.find(pattern, "%%+", v1)
        if v1 == nil then
            prefixpos = mw.ustring.len(pattern) + 1
            break
        elseif (v2 - v1) % 2 == 0 then
            prefixpos = v2
            break
        else
            v1 = v2 + 1
        end
    end
    local prefix = mw.ustring.gsub(mw.ustring.sub(pattern, 1, prefixpos - 1), "%%%%", "%") -- Get the plain text prefix.
    local prefixlen = mw.ustring.len(prefix) -- Get the plain text prefix character length.

    for k, v in pairs(args) do
        if k ~= rawname and k ~= indexname and mw.ustring.sub(k, 1, prefixlen) == prefix then -- Exclude processed parameter names and filter the [name] prefix.
            local indexes = { paramindexes(k, "^"..pattern.."$") } -- Get each serial number part.
            local offset = #indexes / 2 -- The offset value of the pure number serial number part is also used as the number of serial numbers.
            if #index == offset then -- The number of serial numbers is the same. 
                local equal = true -- ordinal Serial Consensus Identifier. 
                for _i, _index in ipairs(index) do
                    if _index ~= indexes[_i + offset] then
                        equal = false
                        break
                    end
                end
                if equal then
                    result = rawfunc(v) or indexfunc(v)
                    if result then return result end
                end
            end
        end
    end
end

local xor = function(left, right) return (left == true and right ~= true) or (left ~= true and right == true) end

local detectevenodd = function(list_previous, evenodd_i, evenodd, iseven)
    if evenodd_i == "swap" then
        if evenodd == "even" or evenodd == "odd" then
            return true, evenodd -- Each swap is swapped once, the basic value: global evenodd.
        else
            return true, nil -- Swap every time. 
        end
    end

    if evenodd == "even" or evenodd == "odd" then
        return false, evenodd -- No swap, base value: global evenodd. 
    end

    if list_previous then
        -- Find the class of the last item in the HTML code of the previous list. 
        list_previous = mw.ustring.gsub(list_previous, "<!%-%-.-%-%->", "") -- Remove HTML comments. 
        local evenodd_previous = nil -- The previous navbox-list parity style (grabbed from the class attribute of the td tag). 
        for tagstart, classstr in mw.ustring.gmatch(list_previous, [[<%s*([Tt][Dd][^>]-)%s[Cc][Ll][Aa][Ss][Ss]="([^"]-navbox%-list[^"]-)"]]) do
            if mw.text.trim(mw.ustring.sub(tagstart, 3, 3)) == "" then -- The label is td.
                for _, class in ipairs(mw.text.split(classstr, "%s+")) do
                    class = mw.ustring.match(class, "^navbox%-(.+)$")
                    if class == "even" or class == "odd" then
                        evenodd_previous = class
                    end
                end
            end
        end
        if evenodd_previous then -- Find the class of the last item in the previous list 
            return xor(evenodd_previous == "even", not iseven), nil -- Swap once when the parity of the last item is the same as the parity of the current item. 
        end
    end

    return false, nil -- No style corrections are made. 
end

local function _bar(args, frame)
    local node = mw.html.create()

    local nodiv = __nnew(args.nodiv)
    local style = __nnew(args.style)
    if nodiv then
        node = node:wikitext("&nbsp;"):tag("span")
    else
        node = node:tag("div")
    end
    node:addClass("noprint")
        :addClass("plainlinks")
        :addClass("hlist")
        :addClass("navbar")
        :addClass("nomobile")
        :cssText(style)

    ---------------core code---------------

    --- left square bracket ---
    local brackets = __nnew(args.brackets)
    if brackets then
        node = node:wikitext("&#91;")
    end

    --- preamble ---
    local mini = __nnew(args.mini)
    local miniv = __nnew(args.miniv)
    local plain = __nnew(args.plain) or __nnew(args.viewplain)
    if not (mini or miniv or plain) then
        node:wikitext("This template:&nbsp;")
    end

    local _1 = __nn(args[1], "{{{1}}}")
    local fontstyle = __nnew(args.fontstyle)
    local fontcolor = __nnew(args.fontcolor, "#002bb8")
    local history = __nnew(args.history)
    local purge = __nnew(args.purge)

    local span
    node:wikitext("[[Template:".._1.."|")

    span = node:tag("span")
        :css("background", "transparent!important")
    if fontstyle then
        span:cssText(fontstyle)
    else
        span:css("color", fontcolor)
    end
    span:attr("title", "View this template")
    if miniv then
        span:wikitext("v")
    elseif plain then
        span:wikitext("view")
    elseif mini then
        span:wikitext("V")
    else
        span:wikitext("View")
    end
    node:wikitext("]]")

    if not (miniv or plain) then
        node:wikitext("&nbsp;·&nbsp;[[Template talk:".._1.."|")

        span = node:tag("span")
            :css("background", "transparent!important")
        if fontstyle then
            span:cssText(fontstyle)
        else
            span:css("color", fontcolor)
        end
        span:attr("title", "Discussion page about this template")
        if mini then
            span:wikitext("d")
        else
            span:wikitext("discuss")
        end

        node:wikitext("]]&nbsp;·&nbsp;[")
            :wikitext(frame:preprocess("{{fullurl:Template:".._1.."|action=edit}}"))
            :wikitext(" ")

        span = node:tag("span")
            :css("background", "transparent!important")
        if fontstyle then
            span:cssText(fontstyle)
        else
            span:css("color", fontcolor)
        end
        span:attr("title", "You can edit this template. Please preview changes before saving.")
        if mini then
            span:wikitext("e")
        else
            span:wikitext("edit")
        end
        node:wikitext("]")

        if history then
            node:wikitext("&nbsp;·&nbsp;[")
                :wikitext(frame:preprocess("{{fullurl:Template:".._1.."|action=history}}"))
                :wikitext(" ")

            span = node:tag("span")
                :css("background", "transparent!important")
            if fontstyle then
                span:cssText(fontstyle)
            else
                span:css("color", fontcolor)
            end
            span:attr("title", "Edit history for this template")
            if mini then
                span:wikitext("h")
            else
                span:wikitext("history")
            end
            node:wikitext("]")
        end

        if purge then
            node:wikitext("&nbsp;·&nbsp;[")
                :wikitext(frame:preprocess("{{fullurl:Template:".._1.."|action=purge}}"))
                :wikitext(" ")

            span = node:tag("span")
                :css("background", "transparent!important")
            if fontstyle then
                span:cssText(fontstyle)
            else
                span:css("color", fontcolor)
            end
            span:attr("title", "Clear cache of this template")
            if mini then
                span:wikitext("p")
            else
                span:wikitext("purge")
            end
            node:wikitext("]")
        end
    end

    --- right square bracket ---
    if brackets then
        node = node:wikitext("&#93;"):done()
    end

    --------------------------------------

    if nodiv then
        node:wikitext("&nbsp;")
    end

    return node
end

local function _box(args, frame)
    local node = mw.html.create()

    local border = __nnew(args.border) or __nne(args[1])
    if border ~= nil then border = mw.text.trim(border) end
    -- Remove any extra blank characters that may be present.
    if type(border) == "string" then
        border = mw.text.trim(border)
    end
    -- When the current template is used to generate a sublist, close the div used by the parent template for padding
    if border == "subgroup" or border == "child" then
        node:wikitext("</div>")
    elseif border ~= "none" then
        node = node:tag("table")
                :addClass("navbox")
                :addClass(__nnew(args.class))
                :attr("cellspacing", 0)
                :cssText(__nnew(args.bodystyle))
                :cssText(__nnew(args.style))
                :tag("tr")
                    :tag("td")
                    :css("padding", "2px")
    end

    node = node:tag("table")
            :attr("cellspacing", 0)
            :addClass("nowraplinks")
            :css("display", "table")
            :css("width", "100%")
            :cssText(__nnew(args.innerstyle))
    local title = __nnew(args.title)
    local state = __nnew(args.state)
    if title then
        if state ~= "plain" and state ~= "off" then
            node:addClass("mw-collapsible")
                :addClass(state or "autocollapse")
        end
    end
    if border == "subgroup" or border == "child" or border == "none" then
        node:addClass("navbox-subgroup")
            :cssText(__nnew(args.bodystyle))
            :cssText(__nnew(args.style))
    else
        node:css("background", "transparent")
            :css("color", "inherit")
    end
    ---------------core code---------------

    local imageleft = __nnew(args.imageleft)
    local image = __nnew(args.image)

    --- Title and Navbar ---
    local grouppadding = __nnew(args.grouppadding)
    local groupstyle = __nnew(args.groupstyle)
    local basestyle = __nnew(args.basestyle)
    if title then
        local temp = node
        node = node:tag("tr")
        local titlegroup = __nnew(args.titlegroup)
        if titlegroup then
            node = node:tag("td")
                    :addClass("navbox-group")
                    :cssText(basestyle)
                    :css("padding", grouppadding or ("0 "..iif(border == "subgroup", "0.75em", "1em")))
                    :cssText(groupstyle)
                    :cssText(__nnew(args.titlegroupstyle))
                    :wikitext(titlegroup)
                    :tag("th")
                        :css("border-left", "2px solid #fdfdfd")
                        :css("width", "100%")
        else
            node = node:tag("th")
        end
        local titlestyle = __nnew(args.titlestyle)
        node:cssText(basestyle)
            :cssText(titlestyle)
            :attr("colspan", 2 + iif(imageleft, 1, 0) + iif(image, 1, 0) + iif(titlegroup, -1, 0))
            :addClass("navbox-title")

        local navbar = __nnew(args.navbar)
        local name = __nnew(args.name)
        if (navbar == "plain" or navbar == "off") or
            (not name and (border == "subgroup" or border == "child" or border == "none")) then
            if navbar == "off" then
                if state == "plain" then
                    node:tag("div")
                            :css("float", "right")
                            :css("width", "2.78em")
                            :wikitext("&nbsp;")
                end
            elseif state ~= "plain" then
                node:tag("div")
                        :css("float", "left")
                        :css("width", "2.78em")
                        :css("text-align", 'left')
                        :wikitext("&nbsp;")
            end
        else
            node:tag("div")
                    :css("float", "left")
                    :css("width", "2.78em")
                    :css("text-align", 'left')
                    :tag("span")
                            :addClass("mobileonly")
                            :wikitext("&nbsp;")
                            :done()
                    :node(_bar({
                        [1] = args.name,
                        fontstyle = iif(basestyle, (basestyle or "")..";", "")..iif(titlestyle, (titlestyle or "")..";", "").."border:none",
                        mini = 1
                    }, frame))
            if state == "plain" then
                node:tag("div")
                        :css("float", "right")
                        :css("width", "2.78em")
                        :wikitext("&nbsp;")
            end
        end
        node:tag("span")
                :css("font-size", iif(border == "subgroup" or border == "child" or border == "none", "100%", "110%"))
                :wikitext(args.title or "{{{title}}}")

        node = temp -- restore node location
    end

    --- Above ---
    local above = __nnew(args.above)
    if above then
        if title then
            node:tag("tr"):css("height", "2px"):tag("td")
        end
        node:tag("tr"):tag("td")
                :addClass("navbox-abovebelow")
                :cssText(basestyle)
                :cssText(__nnew(args.abovestyle))
                :attr("colspan", 2 + iif(imageleft, 1, 0) + iif(image, 1, 0))
                :wikitext(args.above or "{{{above}}}")
    end

    --- Body ---
    local lists = {}
    local listmax = 0
    for k, v in pairs(args) do
        if type(k) == "string" and mw.ustring.sub(k, 1, 4) == "list" then
            local raw, index = paramindexes(k, "^list%$")
            if index ~= nil and index > 0 and __nnew(v) then
                table.insert(lists, { raw, index, v }) -- add new item 
                lists[tostring(index)] = #lists -- Map the search key in string format to the corresponding new item index 
                listmax = math.max(listmax, index)
            end
        end
    end

    --- groups/lists ---
    local evenstyle = __nnew(args.evenstyle)
    local oddstyle = __nnew(args.oddstyle)
    local evenodd = __nnew(args.evenodd)
    local liststyle = __nnew(args.liststyle)
    local listpadding = __nnew(args.listpadding)

    local visiblelist = 0 -- Visible list. 
    local imageleftnode, imagenode
    local swap = evenodd == "swap" -- Whether list item parity styles are swapped.
    local autoSwapOn = __nnew(args.autoSwapOn, "yes") == "yes" -- automatic swap switch.
    for index = 1, listmax do
        if lists[tostring(index)] then -- this key exists
            visiblelist = visiblelist + 1 -- Increment the count.

            local raw, _, list_i = unpack(lists[lists[tostring(index)]])
            if visiblelist > 1 or (title or above) then
                node:tag("tr"):css("height", "2px"):tag("td")
            end

            local tr = node:tag("tr")

            if visiblelist == 1 then
                if imageleft then
                    imageleftnode = tr:tag("td")
                            :css("width", "0%")
                            :css("padding", "0px 2px 0px 0px")
                            :cssText(__nnew(args.imageleftstyle))
                            :wikitext(imageleft)
                end
            end

            local td
            local group_i = indexedparamvalue(args, "group%", raw, index)
            if group_i then
                local groupstyle_i = indexedparamvalue(args, "group%style", raw, index)
                td = tr:tag("td")
                        :addClass("navbox-group")
                        :cssText(basestyle)
                        :css("padding", grouppadding or ("0 "..iif(border == "subgroup", "0.75em", "1em")))
                        :cssText(groupstyle)
                        :cssText(groupstyle_i)
                        :wikitext(group_i)
                        :done()
                    :tag("td")
                        :css("text-align", "left")
                        :css("border-left", "2px solid #fdfdfd")
            else
                td = tr:tag("td")
                        :attr("colspan", 2)
            end
            local liststyle_i = indexedparamvalue(args, "list%style", raw, index)
            local list_previous = nil -- Previous visible list content.
            if autoSwapOn then -- Controls whether the automatic swapper is executed by controlling whether list_previous is nil.
                for _index = index - 1, 1, -1 do
                    if lists[tostring(_index)] then -- this key exists
                        _, _, list_previous = unpack(lists[lists[tostring(_index)]])
                        break
                    end
                end
            end
            local evenodd_i = indexedparamvalue(args, "evenodd%", raw, index)
            if evenodd_i ~= "even" and evenodd_i ~= "odd" then
                local swap_i = nil
                swap_i, evenodd_i = detectevenodd(list_previous, evenodd_i, evenodd, xor(visiblelist % 2 == 0, swap)) -- Find the style of the last item in the previous list.
                if swap_i then swap = not swap end -- Swap list item parity styles.
                if evenodd_i then
                    if swap then
                        evenodd_i = iif(evenodd_i == "even", "odd", "even")
                    end
                else
                    evenodd_i = iif(xor(visiblelist % 2 == 0, swap), "even", "odd")
                end
            end
            td:addClass("navbox-list")
                :addClass("navbox-"..evenodd_i)
                :css("width", "100%")
                :css("padding", "0px")
                :cssText(liststyle)
            if evenodd_i == "even" then td:cssText(evenstyle)
            elseif evenodd_i == "odd" then td:cssText(oddstyle)
            else td:cssText(iif(xor(visiblelist % 2 == 0, swap), evenstyle, oddstyle)) -- Use evenstyle for even lines and oddstyle for odd lines. (Swap in special cases.)
            end
            td:cssText(liststyle_i)
                :tag("div")
                    :css("padding", listpadding or "0em 0.25em")
                    :wikitext(list_i)

            if visiblelist == 1 then
                if image then
                    imagenode = tr:tag("td")
                            :css("width", "0%")
                            :css("padding", "0px 0px 0px 2px")
                            :cssText(__nnew(args.imagestyle))
                            :wikitext(image)
                end
            end
        end
    end
    if imageleftnode then
        imageleftnode:attr("rowspan", visiblelist * 2 - 1)
    end
    if imagenode then
        imagenode:attr("rowspan", visiblelist * 2 - 1)
    end

    --- Below ---
    local below = __nnew(args.below)
    if below then
        if title or above or visiblelist ~= 0 then
            node:tag("tr"):css("height", "2px"):tag("td")
        end
        node:tag("tr"):tag("td")
                :addClass("navbox-abovebelow")
                :cssText(basestyle)
                :cssText(__nnew(args.belowstyle))
                :attr("colspan", 2 + iif(imageleft, 1, 0) + iif(image, 1, 0))
                :wikitext(below)
    end
    --------------------------------------

    node = node:allDone() -- Go back to the top-level node.
     -- When the current template is used to generate a sublist, mark the div whose parent template is closed 
    if border == "subgroup" or border == "child" then
        node:wikitext("<div>")
    end

    return node
end

local function _subgroupbox(args, frame)
    args.border = __nnew(args.border, "subgroup")

    args.style = (args.style or "")..iif(args.bodystyle, ";"..(args.bodystyle or ""), "")

    return args
end

-- Front row reminder, don't set elements to nil while traversing the table, especially non-current elements!
local function _collapsiblegroupsbox(args, frame)
	local newargs = {}
    newargs[1] = args[2]

    newargs.style = (args.style or "")..iif(args.bodystyle, ";"..(args.bodystyle or ""), "")

    local selected = __nnew(args.selected)
    local basestyle = __nnew(args.basestyle)
    local groupstyle = __nnew(args.groupstyle)
    local sectstyle = __nnew(args.sectstyle)
    local secttitlestyle = __nnew(args.secttitlestyle)
    local liststyle = __nnew(args.liststyle)
    for k, v in pairs(args) do
        if type(k) == "string" then
            local raw, index = paramindexes(k, "^list%$")
            if index ~= nil and index > 0 and __nnew(v) then
                local group_i = indexedparamvalue(args, "group%", raw, index)
                local sect_i = indexedparamvalue(args, "sect%", raw, index)
                local abbr_i = indexedparamvalue(args, "abbr%", raw, index)
                local state_i = indexedparamvalue(args, "state%", raw, index)
                local groupstyle_i = indexedparamvalue(args, "group%style", raw, index)
                local sectstyle_i = indexedparamvalue(args, "sect%style", raw, index)
                local liststyle_i = indexedparamvalue(args, "list%style", raw, index)
                local image_i = indexedparamvalue(args, "image%", raw, index)
                local imageleft_i = indexedparamvalue(args, "imageleft%", raw, index)
                local _args = {
                    border = "child",
                    state = iif(selected == abbr_i, "mw-uncollapsed", state_i or "mw-collapsed"),
                    style = (sectstyle or "")..iif(sectstyle_i, ";"..(sectstyle_i or ""), ""),
                    titlestyle = (basestyle or "")..iif(groupstyle, ";"..(groupstyle or ""), "")..iif(secttitlestyle, ";"..(secttitlestyle or ""), "")..iif(groupstyle_i, ";"..(groupstyle_i or ""), ""),
                    liststyle = (liststyle or "")..iif(liststyle_i, ";"..(liststyle_i or ""), ""),
                    title = group_i or sect_i,
                    list1 = v,
                    image = image_i,
                    imageleft = imageleft_i
                }

                newargs[k] = tostring(_box(_args, frame))
            end
        end
    end
     -- Whitelist, not guaranteed to be free of omissions or excesses 
    local list_valid = { 'name', 'state', 'navbar', 'border', 'title', 'above', 'image', 'imageleft', 'below', 'selected', 'bodystyle', 'titlestyle',
    	'abovestyle', 'belowstyle', 'basestyle', 'imagestyle', 'imageleftstyle', 'sectstyle', 'groupstyle', 'liststyle', 'listpadding' }
    for _, val in ipairs(list_valid) do
    	if args[val] then newargs[val] = args[val] end
    end

    return newargs
end

local function _columnsbox(args, frame)
    args[1] = args[2]
    args[2] = nil

    local bodystyle = __nnew(args.bodystyle)
    args.style = (args.style or "")..iif(bodystyle, ";"..(bodystyle or ""), "")
    args.tracking = "no"

    local lists = {}
    -- Collect all lists with columns.
    for k, v in pairs(args) do
        if type(k) == "string" then
            local lraw, craw, lindex, cindex -- raw and index of list and column.
            craw, cindex = paramindexes(k, "^col%$") -- For compatibility, first check whether there is an argument in col+number format.
            if cindex ~= nil then
                lraw, lindex = "1", 1
            else -- An argument that matches the ordinal number of the specified list and column.
                lraw, craw, lindex, cindex = paramindexes(k, "^list%col%$")
            end
            if lindex ~= nil and lindex > 0 and cindex > 0 and __nnew(v) then
                local cols
                if lists[tostring(lindex)] then
                    cols = lists[lists[tostring(lindex)]]
                else
                    cols = {}
                    table.insert(lists, cols) -- Add new item
                    lists[tostring(lindex)] = #lists -- Map the search key in string format to the corresponding new item index
                    lists["#"] = math.max(lists["#"] or 0, lindex)
                end

                table.insert(cols, { { lraw, craw }, { lindex, cindex }, v }) -- Add new item
                cols[tostring(cindex)] = #cols -- Map the search key in string format to the corresponding new item index
                cols["#"] = math.max(cols["#"] or 0, cindex)

                args[k] = nil -- Clear the original content so that it can be overwritten to prevent affecting the Navbox construction logic.
            end
        end
    end

    local coltablestyle = __nnew(args.coltablestyle)
    local fullwidth = __nnew(args.fullwidth)
    local colwidth = __nnew(args.colwidth)
    local colheaderstyle = __nnew(args.colheaderstyle)
    local padding = __nnew(args.padding)
    local colstyle = __nnew(args.colstyle)
    local oddcolstyle = __nnew(args.oddcolstyle)
    local evencolstyle = __nnew(args.evencolstyle)
    for lindex = 1, lists["#"] do
        local cols = lists[lists[tostring(lindex)]]
        if cols then
            local node = mw.html.create("table")
                :addClass("navbox-columns-table")
                :css("border-spacing", "0px")
                :css("text-align", "left")
                :cssText(coltablestyle)
            --if fullwidth then
                node = node:css("width", "100%")
            --else
            --    node = node
            --        :css("width", "auto")
            --        :css("margin-left", "auto")
            --        :css("margin-right", "auto")
            --end

            local header = mw.html.create("tr") -- Header row
            local main = mw.html.create("tr") -- Main columns
            local footer = mw.html.create("tr") -- Footer row

            local hasheader = false
            local hasfooter = false
            local visiblecol = 0 -- Count visible columns.
            for cindex = 1, cols["#"] do
                if cols[cols[tostring(cindex)]] then
                    visiblecol = visiblecol + 1 -- Increment the count.

                    local raw, index, col_i = unpack(cols[cols[tostring(cindex)]])
                    local colheader_i = indexedparamvalue(args, "list%col%header", raw, index)
                    local colfooter_i = indexedparamvalue(args, "list%col%footer", raw, index)
                    local colstyle_i = indexedparamvalue(args, "list%col%style", raw, index)
                    local colheadercolspan_i = indexedparamvalue(args, "list%col%headercolspan", raw, index)
                    local colfootercolspan_i = indexedparamvalue(args, "list%col%footercolspan", raw, index)
                    local colheaderstyle_i = indexedparamvalue(args, "list%col%headerstyle", raw, index)
                    local colfooterstyle_i = indexedparamvalue(args, "list%col%footerstyle", raw, index)
                    local colwidth_i = indexedparamvalue(args, "list%col%width", raw, index)
                    if lindex == 1 then -- If it's the first list, you need to consider compatibility, check omitting the parameter name of list1.
                        colheader_i = colheader_i or indexedparamvalue(args, "col%header", raw[2], index[2])
                        colfooter_i = colfooter_i or indexedparamvalue(args, "col%footer", raw[2], index[2])
                        colstyle_i = colstyle_i or indexedparamvalue(args, "col%style", raw[2], index[2])
                        colheadercolspan_i = colheadercolspan_i or indexedparamvalue(args, "col%headercolspan", raw[2], index[2])
                        colfootercolspan_i = colfootercolspan_i or indexedparamvalue(args, "col%footercolspan", raw[2], index[2])
                        colheaderstyle_i = colheaderstyle_i or indexedparamvalue(args, "col%headerstyle", raw[2], index[2])
                        colfooterstyle_i = colfooterstyle_i or indexedparamvalue(args, "col%footerstyle", raw[2], index[2])
                        colwidth_i = colwidth_i or indexedparamvalue(args, "col%width", raw[2], index[2])
                    end

                    --- Header row ---
                    local hcol
                    if colheader_i then
                        hasheader = true

                        hcol = header:tag("td")
                            :addClass("navbox-abovebelow")
                            :attr("colspan", colheadercolspan_i or 1)
                            :css("font-weight", "bold")
                            :cssText(colheaderstyle)
                            :cssText(colheaderstyle_i)
                            :wikitext(colheader_i)
                    end

                    --- Main columns ---
                    main:css("vertical-align", "top")

                    if visiblecol == 1 and
                        not (colheader_i or colfooter_i or fullwidth) and
                        not (padding == "off" or mw.ustring.find(padding, "^;*-?0+%a+;*$"))
                        then
                        main:tag("td"):css("width", padding or "5em"):wikitext("&nbsp;&nbsp;&nbsp;")
                    end

                    mcol = main:tag("td")
                        :css("padding", "0px")
                        :css("width", colwidth_i or colwidth or "10em")
                        :cssText(colstyle)
                        :cssText(iif(visiblecol % 2 == 1, oddcolstyle, evencolstyle))
                        :cssText(colstyle_i)
                        :tag("div")
                            :wikitext(col_i)
                            :done()

                    --- Footer row ---
                    local fcol
                    if colfooter_i then
                        hasfooter = true

                        fcol = footer:tag("td")
                            :addClass("navbox-abovebelow")
                            :att("colspan", colfootercolspan_i or 1)
                            :css("font-weight", "bold")
                            :cssText(colfooterstyle)
                            :cssText(colfooterstyle_i)
                            :wikitext(colfooter_i)
                    end

                    if visiblecol ~= 1 then
                        mcol:css("border-left", "2px solid #fdfdfd")
                        if hcol then
                            hcol:css("border-left", "2px solid #fdfdfd")
                        end
                        if fcol then
                            fcol:css("border-left", "2px solid #fdfdfd")
                        end
                    end
                end
            end

            node:node(header) -- Add Header row.
            if hasheader then
                node:tag("tr"):css("height", "2px"):tag("td") -- Add a divider below the header.
            end
            node:node(main) -- Add Main columns.
            if hasfooter then
                node:tag("tr"):css("height", "2px"):tag("td") -- Add a divider above the footer.
            end
            node:node(footer) -- Add Footer row.

            args["list"..tostring(lindex)] = tostring(node)
            args["list"..tostring(lindex).."style"] = "background:transparent;color:inherit;"
            args["list"..tostring(lindex).."padding"] = "0px"
        end
    end

    return args
end

local checkNamespaces = function(frame)
    local title = mw.title.new(frame:getParent():getTitle())
    if not title:inNamespaces(
        "Template"
    ) and not mw.title.equals(title, mw.title.new("Sandbox", "Help")) and not mw.title.equals(title, mw.title.new("Nav", "Module_talk")) then
        return "[[Category:在非模板名字空间下的页面中调用Nav模块]]"
    end
end

local getArgs = function(frame)
    local _params_ = __nnew(frame.args._params_, "overwrite")

    if _params_ == "self" then
        return frame.args
    elseif _params_ == "overwrite" then
        local parent = frame:getParent()
        local args = {}
        if parent ~= nil then
            for k, v in pairs(parent.args) do
                args[k] = v
            end
        end
        for k, v in pairs(frame.args) do
            if k ~= "_params_" then
                args[k] = v
            end
        end
        return args
    elseif _params_ == "default" then
        local parent = frame:getParent()
        local args = {}
        for k, v in pairs(frame.args) do
            if k ~= "_params_" then
                args[k] = v
            end
        end
        if parent ~= nil then
            for k, v in pairs(parent.args) do
                args[k] = v
            end
        end
        return args
    else
        return nil
    end
end

function nav.box(frame)
    local result = checkNamespaces(frame) or ""
    local args = getArgs(frame)
    if args == nil then
        return require("Module:Error").error({ message = mw.ustring.format("Unrecognized parameter acquisition method: “%s”", frame.args._params_ or "") })
    end

    local version = __nne(args[1])
    if version ~= nil then version = mw.text.trim(version) end
    local border = __nnew(args.border) or version
    if border == "subgroup" then
        args = _subgroupbox(args, frame)
    elseif version == "collapsible groups" then
        args = _collapsiblegroupsbox(args, frame)
    elseif version == "columns" then
        args = _columnsbox(args, frame)
    end

    return result..tostring(_box(args, frame))
end

function nav.bar(frame)
    local result = checkNamespaces(frame) or ""
    local args = getArgs(frame)
    if args == nil then
        return require("Module:Error").error({ message = mw.ustring.format("Unrecognized parameter acquisition method: “%s”", frame.args._params_ or "") })
    end
    return result..tostring(_bar(args, frame))
end

return nav