跳转到内容

模組:Navbox/sandbox2

维基百科,自由的百科全书
文档图示 模块文档[创建]
--
-- This module implements {{Navbox}}
--

local p = {}

local getArgs -- lazily initialized

local args
local border
local listnums
local ODD_EVEN_MARKER = '\127_ODDEVEN_\127'
local RESTART_MARKER = '\127_ODDEVEN0_\127'
local REGEX_MARKER = '\127_ODDEVEN(%d?)_\127'

local lists = {
    plainlist_t = {
        patterns = {
            '^plainlist$',
            '%splainlist$',
            '^plainlist%s',
            '%splainlist%s'
        },
        found = false,
        styles = 'Plainlist/styles.css'
    },
    hlist_t = {
        patterns = {
            '^hlist$',
            '%shlist$',
            '^hlist%s',
            '%shlist%s'
        },
        found = false,
        styles = 'Hlist/styles.css'
    }
}

local function has_list_class(args_to_check)
    for _, list in pairs(lists) do
        if not list.found then
            for _, arg in pairs(args_to_check) do
                for _, pattern in ipairs(list.patterns) do
                    if mw.ustring.find(arg or '', pattern) then
                        list.found = true
                        break
                    end
                end
                if list.found then break end
            end
        end
    end
end

local function striped(wikitext)
    -- Return wikitext with markers replaced for odd/even striping.
    -- Child (subgroup) navboxes are flagged with a category that is removed
    -- by parent navboxes. The result is that the category shows all pages
    -- where a child navbox is not contained in a parent navbox.
    local orphanCat = '[[Category:孤立的子导航框]]'
    if border == 'subgroup' and args.orphan ~= 'yes' then
        -- No change; striping occurs in outermost navbox.
        return wikitext .. orphanCat
    end
    local first, second = 'odd', 'even'
    if args.evenodd then
        if args.evenodd == 'swap' then
            first, second = second, first
        else
            first = args.evenodd
            second = first
        end
    end
    local changer
    if first == second then
        changer = first
    else
        local index = 0
        changer = function (code)
            if code == '0' then
                -- Current occurrence is for a group before a nested table.
                -- Set it to first as a valid although pointless class.
                -- The next occurrence will be the first row after a title
                -- in a subgroup and will also be first.
                index = 0
                return first
            end
            index = index + 1
            return index % 2 == 1 and first or second
        end
    end
    local regex = orphanCat:gsub('([%[%]])', '%%%1')
    return (wikitext:gsub(regex, ''):gsub(REGEX_MARKER, changer))  -- () omits gsub count
end

local function addNewline(s)
    if s:match('^[*:;#]') or s:match('^{|') then
        return '\n' .. s ..'\n'
    else
        return s
    end
end

local function has_collapsible_toggle()
    return args.state ~= 'off'
        and args.state ~= 'plain'
end

local function has_navbar()
    return args.navbar ~= 'off'
        and args.navbar ~= 'plain'
        and (
            args.name
            or (
                mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$', '')
                    ~= 'Template:Navbox' and
                mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$', '')
                    ~= 'Template:Navbox subgroup'
            )
        )
end

-- extract text color from css, which is the only permitted inline CSS for the navbar
local function extract_color(css_str)
    -- return nil because navbar takes its argument into mw.html which handles
    -- nil gracefully, removing the associated style attribute
    return mw.ustring.match(';' .. css_str .. ';', '.*;%s*([Cc][Oo][Ll][Oo][Rr]%s*:%s*.-)%s*;') or nil
end

local function renderNavBar(titleCell)

    if has_navbar() then
        local navbar = require('Module:Navbar')._navbar
        titleCell:wikitext(navbar{
            args.name,
            mini = 1,
            fontstyle = extract_color(
                (args.basestyle or '') .. ';' .. (args.titlestyle or '')
            )
        })
    end

end

--
--   Title row
--
local function renderTitleRow(tbl)
    if not args.title then return end

    local titleRow = tbl:tag('tr')

    if args.titlegroup then
        if has_collapsible_toggle() then titleRow:addClass('collapsible-title') end
        titleRow
            :tag('th')
                :attr('scope', 'row')
                :addClass('navbox-group')
                :addClass(args.titlegroupclass)
                :cssText(args.basestyle)
                :cssText(args.groupstyle)
                :cssText(args.titlegroupstyle)
                :wikitext(args.titlegroup)
    end

    local titleCell = titleRow:tag('th'):attr('scope', 'col')

    if args.titlegroup then
        titleCell
            :css('border-left', '2px solid #fdfdfd')
            :css('width', '100%')
    elseif has_collapsible_toggle() then
        titleCell
            :addClass('collapsible-title')
    end

    local titleColspan = 2
    if args.imageleft then titleColspan = titleColspan + 1 end
    if args.image then titleColspan = titleColspan + 1 end
    if args.titlegroup then titleColspan = titleColspan - 1 end

    titleCell
        :cssText(args.basestyle)
        :cssText(args.titlestyle)
        :addClass('navbox-title')
        :attr('colspan', titleColspan)

    renderNavBar(titleCell)

    titleCell
        :tag('div')
            :addClass(args.titleclass)
            :css('font-size', '110%')
            :css('margin', '0 5em')
            :wikitext(addNewline(args.title))
end

--
--   Above/Below rows
--

local function getAboveBelowColspan()
    local ret = 2
    if args.imageleft then ret = ret + 1 end
    if args.image then ret = ret + 1 end
    return ret
end

local function renderAboveRow(tbl)
    if not args.above then return end

    tbl:tag('tr')
        :tag('td')
            :addClass('navbox-abovebelow')
            :addClass(args.aboveclass)
            :cssText(args.basestyle)
            :cssText(args.abovestyle)
            :attr('colspan', getAboveBelowColspan())
            :tag('div')
                :wikitext(addNewline(args.above))
end

local function renderBelowRow(tbl)
    if not args.below then return end

    tbl:tag('tr')
        :tag('td')
            :addClass('navbox-abovebelow')
            :addClass(args.belowclass)
            :cssText(args.basestyle)
            :cssText(args.belowstyle)
            :attr('colspan', getAboveBelowColspan())
            :tag('div')
                :wikitext(addNewline(args.below))
end

--
--   List rows
--
local function renderListRow(tbl, listnum)
    local row = tbl:tag('tr')

    if listnum == 1 and args.imageleft then
        row
            :tag('td')
                :addClass('noviewer')
                :addClass('navbox-image')
                :addClass(args.imageclass)
                :css('width', '1px')               -- Minimize width
                :css('padding', '0px 2px 0px 0px')
                :cssText(args.imageleftstyle)
                :attr('rowspan', #listnums)
                :tag('div')
                    :wikitext(addNewline(args.imageleft))
    end

    if args['group' .. listnum] then
        local groupCell = row:tag('th')

        groupCell
            :attr('scope', 'row')
            :addClass('navbox-group')
            :addClass(args.groupclass)
            :cssText(args.basestyle)
            :css('width', args.groupwidth or '1%') -- If groupwidth not specified, minimize width

        groupCell
            :cssText(args.groupstyle)
            :cssText(args['group' .. listnum .. 'style'])
            :wikitext(args['group' .. listnum])
    end

    has_list_class({ args.listclass })

    local listCell = row:tag('td')

    if args['group' .. listnum] then
        listCell
            :addClass('navbox-list-with-group')
    else
        listCell:attr('colspan', 2)
    end

    if not args.groupwidth then 
        listCell:css('width', '100%')
    end

    local rowstyle  -- usually nil so cssText(rowstyle) usually adds nothing
    if listnum % 2 == 1 then
        rowstyle = args.oddstyle
    else
        rowstyle = args.evenstyle
    end

    local listText = args['list' .. listnum]
    local oddEven = ODD_EVEN_MARKER
    if listText:sub(1, 12) == '</div><table' then
        -- Assume list text is for a subgroup navbox so no automatic striping for this row.
        oddEven = listText:find('<th[^>]*"navbox%-title"') and RESTART_MARKER or 'odd'
    end

    listCell
        :css('padding', '0px')
        :cssText(args.liststyle)
        :cssText(rowstyle)
        :cssText(args['list' .. listnum .. 'style'])
        :addClass('navbox-list')
        :addClass('navbox-' .. oddEven)
        :addClass(args.listclass)
        :addClass(args['list' .. listnum .. 'class'])
        :tag('div')
            :css('padding', (listnum == 1 and args.list1padding) or args.listpadding or '0em 0.25em')
            :wikitext(addNewline(listText))

    if listnum == 1 and args.image then
        row
            :tag('td')
                :addClass('noviewer')
                :addClass('navbox-image')
                :addClass(args.imageclass)
                :css('width', '1px')               -- Minimize width
                :css('padding', '0px 0px 0px 2px')
                :cssText(args.imagestyle)
                :attr('rowspan', #listnums)
                :tag('div')
                    :wikitext(addNewline(args.image))
    end
end

--
--   Tracking categories
--

local function needsHorizontalLists()
    if border == 'subgroup' or args.tracking == 'no' then
        return false
    end

    return not lists.hlist_t.found and not lists.plainlist_t.found
end

local function hasBackgroundColors()
    for _, key in ipairs({'titlestyle', 'groupstyle', 'basestyle', 'abovestyle', 'belowstyle'}) do
        if tostring(args[key]):find('background', 1, true) then
            return true
        end
    end
end

local function argNameAndRealTitleAreDifferent()
    if border == 'subgroup' or args.tracking == 'no' then
        return false
    end

    if has_navbar() and args.name ~= mw.title.getCurrentTitle().text then
        return true
    end

    return false
end

local function getTrackingCategories()
    local cats = {}
    if needsHorizontalLists() then table.insert(cats, '没有使用水平列表的导航框') end
    if hasBackgroundColors() then table.insert(cats, '使用背景颜色的导航框') end
    if argNameAndRealTitleAreDifferent() then table.insert(cats, 'name參數和實際不同的導航框') end
    return cats
end

local function renderTrackingCategories(builder)
    local title = mw.title.getCurrentTitle()
    if title.namespace ~= 10 then return end -- not in template space
    local subpage = title.subpageText
    if subpage == 'doc' or subpage == 'sandbox' or subpage == 'testcases' then return end

    for _, cat in ipairs(getTrackingCategories()) do
        builder:wikitext('[[Category:' .. cat .. ']]') 
    end
end

--
-- Load the templatestyles for the navbox
--
local function loadTemplateStyles(hiding_templatestyles)
    local frame = mw.getCurrentFrame()

    local hlist_templatestyles = ''
    if lists.hlist_t.found then
        hlist_templatestyles = frame:extensionTag{
            name = 'templatestyles', args = { src = lists.hlist_t.styles }
        }
    end

    -- a second workaround for [[phab:T303378]]
    -- when that issue is fixed, we can actually use has_navbar not to emit the
    -- tag here if we want
    if has_navbar() and hlist_templatestyles == '' then
        hlist_templatestyles = frame:extensionTag{
            name = 'templatestyles', args = { src = lists.hlist_t.styles }
        }
    end

    local plainlist_templatestyles = ''
    if lists.plainlist_t.found then
        plainlist_templatestyles = frame:extensionTag{
            name = 'templatestyles', args = { src = lists.plainlist_t.styles }
        }
    end

    local base_templatestyles = frame:extensionTag{
        name = 'templatestyles', args = { src = 'Module:Navbox/styles.css' }
    }

    local templatestyles = ''
    if args['templatestyles'] then
        templatestyles = frame:extensionTag{
            name = 'templatestyles', args = { src = args['templatestyles'] }
        }
    end

    local child_templatestyles = ''
    if args['child templatestyles'] then
        child_templatestyles = frame:extensionTag{
            name = 'templatestyles', args = { src = args['child templatestyles'] }
        }
    end

    return mw.html.create('div')
        :addClass('navbox-styles')
        :wikitext(
            -- hlist -> plainlist -> base is best-effort to preserve old Common.css ordering.
            -- this ordering is not a guarantee because the rows of interest invoking
            -- each class may not be on a specific page
            hlist_templatestyles ..
            plainlist_templatestyles ..
            base_templatestyles ..
            templatestyles ..
            child_templatestyles ..
            table.concat(hiding_templatestyles)
        )
        :done()
end

-- work around [[phab:T303378]]
-- for each arg: find all the templatestyles strip markers, insert them into a
-- table. then remove all templatestyles markers from the arg
local function move_hiding_templatestyles(args)
    local gfind = string.gfind
    local gsub = string.gsub
    local templatestyles_markers = {}
    local strip_marker_pattern = '(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)'
    for k, arg in pairs(args) do
        for marker in gfind(arg, strip_marker_pattern) do
            table.insert(templatestyles_markers, marker)
        end
        args[k] = gsub(arg, strip_marker_pattern, '')
    end
    return templatestyles_markers
end

--
--   Main navbox tables
--
local function renderMainTable()
    local tbl = mw.html.create('table')
        :attr('cellspacing', 0)
        :addClass('nowraplinks')
        :addClass(args.bodyclass)

        has_list_class({ args.bodyclass })

    if args.title and has_collapsible_toggle() then
        if args.state == 'collapsed' then args.state = 'mw-collapsed' end
        tbl
            :addClass('mw-collapsible')
            :addClass(args.state or 'autocollapse')
    end

    tbl:css('border-spacing', 0)
    if border == 'subgroup' or border == 'none' then
        tbl
            :addClass('navbox-subgroup')
            :cssText(args.bodystyle)
            :cssText(args.style)
    else -- regular navbox - bodystyle and style will be applied to the wrapper table
        tbl
            :addClass('navbox-inner')
            :css('background', 'transparent')
            :css('color', 'inherit')
    end
    tbl:cssText(args.innerstyle)

    renderTitleRow(tbl)
    renderAboveRow(tbl)
    for i, listnum in ipairs(listnums) do
        renderListRow(tbl, listnum) 
    end
    renderBelowRow(tbl)

    return tbl
end

function p._navbox(navboxArgs)
    args = navboxArgs
    local hiding_templatestyles = move_hiding_templatestyles(args)
    listnums = {}

    for k, v in pairs(args) do
        local listnum = ('' .. k):match('^list(%d+)$')
        if listnum then table.insert(listnums, tonumber(listnum)) end
    end
    table.sort(listnums)

    border = mw.text.trim(args.border or args[1] or '')
    if border == 'child' then
        border = 'subgroup'
    end

    -- render the main body of the navbox
    local tbl = renderMainTable()

    -- render the appropriate wrapper around the navbox, depending on the border param
    local res = mw.html.create()
    if border == 'none' then
        res:node(loadTemplateStyles(hiding_templatestyles))
        local nav = res:node(tbl)
    elseif border == 'subgroup' then
        -- We assume that this navbox is being rendered in a list cell of a parent navbox, and is
        -- therefore inside a div with padding:0em 0.25em. We start with a </div> to avoid the
        -- padding being applied, and at the end add a <div> to balance out the parent's </div>
        res
            :wikitext('</div>') -- mw.html 未支持 unclosed
            :node(tbl)
            :wikitext('<div>') -- mw.html 未支持 unclosed
    else
        res:node(loadTemplateStyles(hiding_templatestyles))
        local nav = res:tag('table')
            :attr('cellspacing', 0)
            :addClass('navbox')
            :addClass(args.navboxclass)
            :css('border-spacing', 0)
            :cssText(args.bodystyle)
            :cssText(args.style)
            :tag('tr')
                :tag('td')
                    :css('padding', '2px')
                    :node(tbl)
    end

    if (args.nocat or 'false'):lower() == 'false' then
        renderTrackingCategories(res)
    end
    return striped(tostring(res))
end

function p.navbox(frame)
    if not getArgs then
    	getArgs = require('Module:Arguments').getArgs
    end
    args = getArgs(frame, {wrappers = {'Template:Navbox', 'Template:Navbox subgroup'}})
    if frame.args.border then
        -- This allows Template:Navbox_subgroup to use {{#invoke:Navbox|navbox|border=...}}.
        args.border = frame.args.border
    end

    -- Read the arguments in the order they'll be output in, to make references number in the right order.
    local _
    _ = args.title
    _ = args.above
    for i = 1, 35 do
        _ = args["group" .. tostring(i)]
        _ = args["list" .. tostring(i)]
    end    
    _ = args.below

    return p._navbox(args)
end

return p