Mô đun:Navbox/sandbox

require('strict')
local p = {}
local cfg = mw.loadData('Mô đun:Navbox/configuration')
local inArray = require("Mô đun:TableTools").inArray
local getArgs -- lazily initialized
local format = string.format
function p._navbox(args)
	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.border_subgroup and argscfg.arg.orphan ~= cfg.keyword.orphan_yes then
			-- No change; striping occurs in outermost navbox.
			return wikitext .. orphanCat
		end
		local first, second = cfg.class.navbox_odd_part, cfg.class.navbox_even_part
		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 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*(CcOoLlOoRr%s*:%s*.-)%s*;') or nil
	end
	
	local function renderNavBar(titleCell)
		if has_navbar() then
			local navbar = require('Mô đun: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 group_and_num = argsformat(cfg.arg.group_and_num, listnum) and format(cfg.arg.group_and_num, listnum) or format('nhóm%d', listnum)
		local groupstyle_and_num = format(cfg.arg.groupstyle_and_num, listnum)
		if argsgroup_and_num 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 or args"nhóm1"))
			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(argsgroupstyle_and_num)
				:wikitext(argsgroup_and_num)
		end
	
		local listCell = row:tag('td')
	
		if argsgroup_and_num then
			listCell
				:addClass(cfg.class.navbox_list_with_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 list_and_num = argsformat(cfg.arg.list_and_num, listnum) and format(cfg.arg.list_and_num, listnum) or format('dsach%d', listnum)
		local listText = argslist_and_num
		
		if inArray(cfg.keyword.subgroups, listText) then
			local childArgs = {
				cfg.arg.border = cfg.keyword.border_subgroup,
				cfg.arg.navbar = cfg.keyword.navbar_plain
			}
			local hasChildArgs = false
			for k, v in pairs(args) do
				k = tostring(k)
				for _, w in ipairs(cfg.keyword.subgroups) do
					w = w .. listnum .. "_"
					if (#k > #w) and (k:sub(1, #w) == w) then
						childArgsk:sub(#w + 1) = v
						hasChildArgs = true
					end
				end
			end
			listText = hasChildArgs and p._navbox(childArgs) or listText
		end
		
		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 hlist_styles .. plainlist_styles
	end
	
	local function needsHorizontalLists(border)
		if border == cfg.keyword.border_subgroup or argscfg.arg.tracking == cfg.keyword.tracking_no then
			return false
		end
		return not has_list_class(cfg.pattern.hlist) and not has_list_class(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('Mô đun: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(add_navbox_styles(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.nocat_false):lower() == cfg.keyword.nocat_false then
		renderTrackingCategories(res, border)
	end
	return striped(tostring(res), border)
end
function p.navbox(frame)
	local function readArgs(args, prefix)
		-- Read the arguments in the order they'll be output in, to make references
		-- number in the right order.
		local _
		_ = argsprefix .. cfg.arg.title or argsprefix .. "tiêu đề"
		_ = argsprefix .. cfg.arg.above or argsprefix .. "trên"
		-- Limit this to 20 as covering 'most' cases (that's a SWAG) and because
		-- iterator approach won't work here
		for i = 1, 20 do
			_ = argsprefix .. format(cfg.arg.group_and_num, i) or argsprefix .. format('nhóm%d', i)
			if inArray(cfg.keyword.subgroups, argsprefix .. format(cfg.arg.list_and_num, i) or argsprefix .. format('dsach%d', i)) then
				for _, v in ipairs(cfg.keyword.subgroups) do
					readArgs(args, prefix .. v .. i .. "_")
				end
			end
		end
		_ = argsprefix .. cfg.arg.below or argsprefix .. "dưới"
	end
	if not getArgs then
		getArgs = require('Mô đun:Arguments').getArgs
	end
	local args = getArgs(frame, {wrappers = {cfg.pattern.navbox}})
	
	argscfg.arg.above = argscfg.arg.above or args"trên"
	argscfg.arg.aboveclass = argscfg.arg.aboveclass or args"lớp trên"
	argscfg.arg.abovestyle = argscfg.arg.abovestyle or args"kiểu trên"
	argscfg.arg.basestyle = argscfg.arg.basestyle or args"kiểu gốc"
	argscfg.arg.bodystyle = argscfg.arg.bodystyle or args"kiểu thân"
	argscfg.arg.border = argscfg.arg.border or args"khung"
	argscfg.arg.below = argscfg.arg.below or args"dưới"
	argscfg.arg.belowclass = argscfg.arg.belowclass or args"lớp dưới"
	argscfg.arg.belowstyle = argscfg.arg.belowstyle or args"kiểu dưới"
	argscfg.arg.evenodd = argscfg.arg.evenodd or args"chẵn lẻ"
	argscfg.arg.evenstyle = argscfg.arg.evenstyle or args"kiểu chẵn"
	argscfg.arg.group1 = argscfg.arg.group1 or args"nhóm1"
	argscfg.arg.group2 = argscfg.arg.group2 or args"nhóm2"
	argscfg.arg.groupclass = argscfg.arg.groupclass or args"lớp nhóm"
	argscfg.arg.groupstyle = argscfg.arg.groupstyle or args"kiểu nhóm"
	argscfg.arg.groupwidth = argscfg.arg.groupwidth or args"chiều rộng nhóm"
	argscfg.arg.innerstyle = argscfg.arg.innerstyle or args"kiểu trong"
	argscfg.arg.image = argscfg.arg.image or args"hình"
	argscfg.arg.imageleft = argscfg.arg.imageleft or args"hình trái"
	argscfg.arg.imageleftstyle = argscfg.arg.imageleftstyle or args"kiểu hình trái"
	argscfg.arg.imagestyle = argscfg.arg.imagestyle or args"kiểu hình"
	argscfg.arg.listpadding = argscfg.arg.listpadding or args"đệm danh sách"
	argscfg.arg.liststyle = argscfg.arg.liststyle or args"kiểu danh sách"
	argscfg.arg.name = argscfg.arg.name or args"tên"
	argscfg.arg.navbar = argscfg.arg.navbar or args"thanh chuyển hướng"
	argscfg.arg.oddstyle = argscfg.arg.oddstyle or args"kiểu lẻ"
	argscfg.arg.state = argscfg.arg.state or args"trạng thái"
	argscfg.arg.style = argscfg.arg.style or args"kiểu"
	argscfg.arg.title = argscfg.arg.title or args"tiêu đề"
	argscfg.arg.titleclass = argscfg.arg.titleclass or args"lớp tiêu đề"
	argscfg.arg.titlestyle = argscfg.arg.titlestyle or args"kiểu tiêu đề"
	
	readArgs(args, "")
	return p._navbox(args)
end
return p