Module:Navbox/sandbox

require('strict')

local p =

local cfg = mw.loadData('Module:Navbox/configuration')

local getArgs -- lazily initialized

local args

local format = string.format

local function striped(wikitext, border)

	-- 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 = cfg.category.orphan

	if border == cfg.keyword.bordersubgroup and argscfg.arg.orphan ~= cfg.keyword.orphanyes then

		-- No change; striping occurs in outermost navbox.

		return wikitext .. orphanCat

	end

	local first, second = cfg.class.navboxoddpart, cfg.class.navboxevenpart

	if argscfg.arg.evenodd then

		if argscfg.arg.evenodd == cfg.keyword.evenodd_swap then

			first, second = second, first

		else

			first = argscfg.arg.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(cfg.marker.regex, changer)) -- omits gsub count

end

local function processItem(item, nowrapitems)

	if item:sub(1, 2) == '

		-- Applying nowrap to lines in a table does not make sense.

		-- Add newlines to compensate for trim of x in |parm=x in a template.

		return '\n' .. item ..'\n'

	end

	if nowrapitems == cfg.keyword.nowrapitems_yes then

		local lines =

		for line in (item .. '\n'):gmatch('(^\n*)\n') do

			local prefix, content = line:match('^(:;#+)%s(.*)')

			if prefix and not content:match(cfg.pattern.nowrap) then

				line = format(cfg.nowrap_item, prefix, content)

			end

			table.insert(lines, line)

		end

		item = table.concat(lines, '\n')

	end

	if item:match('^*:;#') then

		return '\n' .. item ..'\n'

	end

	return item

end

local function has_navbar

	return argscfg.arg.navbar ~= cfg.keyword.navbar_off

		and argscfg.arg.navbar ~= cfg.keyword.navbar_plain

		and (

			argscfg.arg.name

			or mw.getCurrentFrame:getParent:getTitle:gsub(cfg.pattern.sandbox, )

				~= cfg.pattern.navbox

		)

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{

			cfg.navbar.name = argscfg.arg.name,

			cfg.navbar.mini = 1,

			cfg.navbar.fontstyle = extract_color(

				(argscfg.arg.basestyle or ) .. ';' .. (argscfg.arg.titlestyle or )

			)

		})

	end

end

local function renderTitleRow(tbl)

	if not argscfg.arg.title then return end

	local titleRow = tbl:tag('tr')

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

	local titleColspan = 2

	if argscfg.arg.imageleft then titleColspan = titleColspan + 1 end

	if argscfg.arg.image then titleColspan = titleColspan + 1 end

	titleCell

		:cssText(argscfg.arg.basestyle)

		:cssText(argscfg.arg.titlestyle)

		:addClass(cfg.class.navbox_title)

		:attr('colspan', titleColspan)

	renderNavBar(titleCell)

	titleCell

		:tag('div')

			-- id for aria-labelledby attribute

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

			:addClass(argscfg.arg.titleclass)

			:css('font-size', '114%')

			:css('margin', '0 4em')

			:wikitext(processItem(argscfg.arg.title))

end

local function getAboveBelowColspan

	local ret = 2

	if argscfg.arg.imageleft then ret = ret + 1 end

	if argscfg.arg.image then ret = ret + 1 end

	return ret

end

local function renderAboveRow(tbl)

	if not argscfg.arg.above then return end

	tbl:tag('tr')

		:tag('td')

			:addClass(cfg.class.navbox_abovebelow)

			:addClass(argscfg.arg.aboveclass)

			:cssText(argscfg.arg.basestyle)

			:cssText(argscfg.arg.abovestyle)

			:attr('colspan', getAboveBelowColspan)

			:tag('div')

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

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

				:wikitext(processItem(argscfg.arg.above, argscfg.arg.nowrapitems))

end

local function renderBelowRow(tbl)

	if not argscfg.arg.below then return end

	tbl:tag('tr')

		:tag('td')

			:addClass(cfg.class.navbox_abovebelow)

			:addClass(argscfg.arg.belowclass)

			:cssText(argscfg.arg.basestyle)

			:cssText(argscfg.arg.belowstyle)

			:attr('colspan', getAboveBelowColspan)

			:tag('div')

				:wikitext(processItem(argscfg.arg.below, argscfg.arg.nowrapitems))

end

local function renderListRow(tbl, index, listnum, listnums_size)

	local row = tbl:tag('tr')

	if index == 1 and argscfg.arg.imageleft then

		row

			:tag('td')

				:addClass(cfg.class.noviewer)

				:addClass(cfg.class.navbox_image)

				:addClass(argscfg.arg.imageclass)

				:css('width', '1px') -- Minimize width

				:css('padding', '0 2px 0 0')

				:cssText(argscfg.arg.imageleftstyle)

				:attr('rowspan', listnums_size)

				:tag('div')

					:wikitext(processItem(argscfg.arg.imageleft))

	end

	local groupandnum = format(cfg.arg.groupandnum, listnum)

	local groupstyleandnum = format(cfg.arg.groupstyleandnum, listnum)

	if argsgroupandnum then

		local groupCell = row:tag('th')

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

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

			groupCell

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

		end

		groupCell

			:attr('scope', 'row')

			:addClass(cfg.class.navbox_group)

			:addClass(argscfg.arg.groupclass)

			:cssText(argscfg.arg.basestyle)

			-- If groupwidth not specified, minimize width

			:css('width', argscfg.arg.groupwidth or '1%')

		groupCell

			:cssText(argscfg.arg.groupstyle)

			:cssText(argsgroupstyleandnum)

			:wikitext(argsgroupandnum)

	end

	local listCell = row:tag('td')

	if argsgroupandnum then

		listCell

			:addClass(cfg.class.navboxlistwith_group)

	else

		listCell:attr('colspan', 2)

	end

	if not argscfg.arg.groupwidth then

		listCell:css('width', '100%')

	end

	local rowstyle -- usually nil so cssText(rowstyle) usually adds nothing

	if index % 2 == 1 then

		rowstyle = argscfg.arg.oddstyle

	else

		rowstyle = argscfg.arg.evenstyle

	end

	local listandnum = format(cfg.arg.listandnum, listnum)

	local listText = argslistandnum

	local oddEven = cfg.marker.oddeven

	if listText:sub(1, 12) == ' plainlist is best-effort to preserve old Common.css ordering.

	-- this ordering is not a guarantee because most navboxes will emit only

	-- one of these classes hlist_note

	return hliststyles .. plainliststyles

end

local function needsHorizontalLists(border)

	if border == cfg.keyword.bordersubgroup or argscfg.arg.tracking == cfg.keyword.trackingno then

		return false

	end

	return not haslistclass(cfg.pattern.hlist) and not haslistclass(cfg.pattern.plainlist)

end

local function hasBackgroundColors

	for _, key in ipairs({cfg.arg.titlestyle, cfg.arg.groupstyle,

		cfg.arg.basestyle, cfg.arg.abovestyle, cfg.arg.belowstyle}) do

		if tostring(argskey):find('background', 1, true) then

			return true

		end

	end

	return false

end

local function hasBorders

	for _, key in ipairs({cfg.arg.groupstyle, cfg.arg.basestyle,

		cfg.arg.abovestyle, cfg.arg.belowstyle}) do

		if tostring(argskey):find('border', 1, true) then

			return true

		end

	end

	return false

end

local function isIllegible

	local styleratio = require('Module:Color contrast')._styleratio

	for key, style in pairs(args) do

		if tostring(key):match(cfg.pattern.style) then

			if styleratio{mw.text.unstripNoWiki(style)} to avoid the padding being applied, and at the

		-- end add a to balance out the parent's

		res

			:wikitext

			:node(tbl)

			:wikitext

	else

		res:node(addnavboxstyles(hiding_templatestyles))

		local nav = res:tag('div')

			:attr('role', 'navigation')

			:addClass(cfg.class.navbox)

			:addClass(argscfg.arg.navboxclass)

			:cssText(argscfg.arg.bodystyle)

			:cssText(argscfg.arg.style)

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

			:node(tbl)

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

		if argscfg.arg.title or argscfg.arg.above

			or (argscfg.arg.group1 and not argscfg.arg.group2) then

			nav:attr(

				'aria-labelledby',

				mw.uri.anchorEncode(argscfg.arg.title or argscfg.arg.above or argscfg.arg.group1)

			)

		else

			nav:attr('aria-label', cfg.aria_label)

		end

	end

	if (argscfg.arg.nocat or cfg.keyword.nocatfalse):lower == cfg.keyword.nocatfalse then

		renderTrackingCategories(res, border)

	end

	return striped(tostring(res), border)

end

function p.navbox(frame)

	if not getArgs then

		getArgs = require('Module:Arguments').getArgs

	end

	args = getArgs(frame, {wrappers = {cfg.pattern.navbox}})

	-- Read the arguments in the order they'll be output in, to make references

	-- number in the right order.

	local _

	_ = argscfg.arg.title

	_ = argscfg.arg.above

	-- Limit this to 35 as covering 'most' cases (that's a SWAG) and because

	-- iterator approach won't work here

	for i = 1, 35 do

		 = argsformat(cfg.arg.groupand_num, i)

		 = argsformat(cfg.arg.listand_num, i)

	end

	_ = argscfg.arg.below

	return p._navbox(args)

end

return p