Module:Navbox

--

-- This module implements

--

local p =

local getArgs -- lazily initialized

local args

local border

local listnums

local ODDEVENMARKER = '\127ODDEVEN\127'

local RESTARTMARKER = '\127ODDEVEN0_\127'

local REGEXMARKER = '\127ODDEVEN (% 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 haslistclass (argstocheck)

	for _, list in pairs (lists) do

		if not list.found then

			for , arg in pairs (argsto_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 =

	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 ('^

		return '\n' .. s ..'\n'

	else

		return s

	end

end

local function hascollapsibletoggle

	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 extractcolor (cssstr)

	-- 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(CcOoLlOoRr% 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

	haslistclass ({args.titleclass})

	local titleRow = tbl:tag ('tr')

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

	if hascollapsibletoggle 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

	titleCell

		:cssText (args.basestyle)

		:cssText (args.titlestyle)

		:addClass ('navbox-title')

		:attr ('colspan', titleColspan)

	renderNavBar (titleCell)

	titleCell

		:tag ('div')

			-- id for aria-labelledby attribute

			:attr ('id', mw.uri.anchorEncode (args.title))

			: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

	haslistclass ({args.aboveclass})

	tbl:tag ('tr')

		:tag ('td')

			:addClass ('navbox-abovebelow')

			:addClass (args.aboveclass)

			:cssText (args.basestyle)

			:cssText (args.abovestyle)

			:attr ('colspan', getAboveBelowColspan )

			:tag ('div')

				-- id for aria-labelledby attribute, if no title

				:attr ('id', (not args.title) and mw.uri.anchorEncode (args.above) or nil)

				:wikitext (addNewline (args.above))

end

local function renderBelowRow (tbl)

	if not args.below then return end

	haslistclass ({args.belowclass})

	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

		haslistclass ({args.imageclass})

		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

		haslistclass ({args.groupclass})

		local groupCell = row:tag ('th')

		-- id for aria-labelledby attribute, if lone group with no title or above

		if listnum == 1 and not (args.title or args.above or args.group2) then

			groupCell

				:attr ('id', mw.uri.anchorEncode (args.group1))

		end

		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

	haslistclass ({args.listclass, args'list' .. listnum .. 'class'})

	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 = ODDEVENMARKER

	if listText:sub (1, 12) == '*"? ?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

		haslistclass ({args.imageclass})

		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.hlistt.found and not lists.plainlistt.found

end

local function hasBackgroundColors

	for _, key in ipairs ({'titlestyle', 'groupstyle', 'basestyle', 'abovestyle', 'belowstyle'}) do

		if tostring (argskey):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

	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

		hlisttemplatestyles = frame:extensionTag {name = 'templatestyles', args = { src = lists.hlistt.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 hasnavbar and hlisttemplatestyles == then

		hlisttemplatestyles = frame:extensionTag {name = 'templatestyles', args = { src = lists.hlistt.styles}

		}

	end

	local plainlist_templatestyles =

	if lists.plainlist_t.found then

		plainlisttemplatestyles = frame:extensionTag {name = 'templatestyles', args = { src = lists.plainlistt.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 movehidingtemplatestyles (args)

	local gfind = string.gfind

	local gsub = string.gsub

	local templatestyles_markers =

	local stripmarkerpattern = '(\127^\127UNIQ%-%-templatestyles%-% x+%-QINU^\127\127)'

	for k, arg in pairs (args) do

		for marker in gfind (arg, stripmarkerpattern) do

			table.insert (templatestyles_markers, marker)

		end

		argsk = gsub (arg, stripmarkerpattern,)

	end

	return templatestyles_markers

end

--

-- Main navbox tables

--

local function renderMainTable

	local tbl = mw.html.create ('table')

		:addClass ('nowraplinks')

		:addClass (args.bodyclass)

		haslistclass ({args.bodyclass})

	if args.title and hascollapsibletoggle 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 hidingtemplatestyles = movehiding_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 args1 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:tag ('div')

			:attr ('role', 'navigation')

			:node (tbl)

		-- aria-labelledby title, otherwise above, otherwise lone group

		if args.title or args.above or (args.group1 and not args.group2) then

			nav:attr ('aria-labelledby', mw.uri.anchorEncode (args.title or args.above or args.group1))

		else

			nav:attr ('aria-label', 'Navbox')

		end

	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 to avoid the

		-- padding being applied, and at the end add a to balance out the parent's

		res

			:wikitext -- mw.html 未支援 unclosed

			:node (tbl)

			:wikitext -- mw.html 未支援 unclosed

	else

		res:node (loadTemplateStyles (hiding_templatestyles))

		haslistclass ({args.navboxclass})

		local nav = res:tag ('div')

			:attr ('role', 'navigation')

			:addClass ('navbox')

			:addClass (args.navboxclass)

			:cssText (args.bodystyle)

			:cssText (args.style)

			:css ('padding', '3px')

			:node (tbl)

		-- aria-labelledby title, otherwise above, otherwise lone group

		if args.title or args.above or (args.group1 and not args.group2) then

			nav:attr ('aria-labelledby', mw.uri.anchorEncode (args.title or args.above or args.group1))

		else

			nav:attr ('aria-label', 'Navbox')

		end

	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 .

		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