Bước tới nội dung

Mô đun:Category series navigation/sandbox

Bách khoa toàn thư mở Wikipedia
require('strict')local p = {}local horizontal = require('Module:List').horizontallocal rtarget = require('Module:Resolve category redirect').rtarget--[[==========================================================================]]--[[                                Globals                                   ]]--[[==========================================================================]]local currtitle = mw.title.getCurrentTitle()local nexistingcats = 0local errors = ''local testcasecolon = ''local testcases = string.match(currtitle.subpageText, '^testcases')if    testcases then testcasecolon = ':' endlocal navborder = truelocal followRs = truelocal skipgaps = falselocal skipgaps_limit = 50local term_limit = 10local hgap_limit = 6local ygap_limit = 5local listall = falselocal tlistall = {}local tlistallbwd = {}local tlistallfwd = {}local ttrackingcats = { --when reindexing, Ctrl+H 'trackcat(13,' & 'ttrackingcats[16]'	'', -- [1] placeholder for [[Thể loại:Category series navigation using cat parameter]]	'', -- [2] placeholder for [[Thể loại:Category series navigation using testcase parameter]]	'', -- [3] placeholder for [[Thể loại:Category series navigation using unknown parameter]]	'', -- [4] placeholder for [[Thể loại:Category series navigation range not using en dash]]	'', -- [5] placeholder for [[Thể loại:Category series navigation range abbreviated (CNBS)]]	'', -- [6] placeholder for [[Thể loại:Category series navigation range redirected (base change)]]	'', -- [7] placeholder for [[Thể loại:Category series navigation range redirected (var change)]]	'', -- [8] placeholder for [[Thể loại:Category series navigation range redirected (end)]]	'', -- [9] placeholder for [[Thể loại:Category series navigation range redirected (CNBS)]]	'', --[10] placeholder for [[Thể loại:Category series navigation range redirected (other)]]	'', --[11] placeholder for [[Thể loại:Category series navigation range gaps]]	'', --[12] placeholder for [[Thể loại:Category series navigation range irregular]]	'', --[13] placeholder for [[Thể loại:Category series navigation range irregular, 0-length]]	'', --[14] placeholder for [[Thể loại:Category series navigation range ends (present)]]	'', --[15] placeholder for [[Thể loại:Category series navigation range ends (blank, CNBS)]]	'', --[16] placeholder for [[Thể loại:Category series navigation isolated]]	'', --[17] placeholder for [[Thể loại:Category series navigation default season gap size]]	'', --[18] placeholder for [[Thể loại:Category series navigation decade redirected]]	'', --[19] placeholder for [[Thể loại:Category series navigation year redirected (base change)]]	'', --[20] placeholder for [[Thể loại:Category series navigation year redirected (var change)]]	'', --[21] placeholder for [[Thể loại:Category series navigation year redirected (other)]]	'', --[22] placeholder for [[Thể loại:Category series navigation roman numeral redirected]]	'', --[23] placeholder for [[Thể loại:Category series navigation nordinal redirected]]	'', --[24] placeholder for [[Thể loại:Category series navigation wordinal redirected]]	'', --[25] placeholder for [[Thể loại:Category series navigation TV season redirected]]	'', --[26] placeholder for [[Thể loại:Category series navigation using skip-gaps parameter]]	'', --[27] placeholder for [[Thể loại:Category series navigation year and range]]	'', --[28] placeholder for [[Thể loại:Category series navigation year and decade]]	'', --[29] placeholder for [[Thể loại:Category series navigation decade and century]]	'', --[30] placeholder for [[Thể loại:Category series navigation in mainspace]]	'', --[31] placeholder for [[Thể loại:Category series navigation redirection error]]}local avoidself =  (not string.match(currtitle.text, 'Category series navigation with') and					not string.match(currtitle.text, 'Category series navigation.*/doc') and					not string.match(currtitle.text, 'Category series navigation.*/sandbox') and					currtitle.text ~= 'Category series navigation' and					currtitle.nsText:gsub('_', ' ') ~= 'Thảo luận Thành viên' and -- [[phab:T369784]]					currtitle.nsText:gsub('_', ' ') ~= 'Thảo luận Bản mẫu' and					(currtitle.nsText ~= 'Bản_mẫu' or testcases)) --avoid nested transclusion errors (i.e. {{Infilmdecade}})--[[==========================================================================]]--[[                      Utility & category functions                        ]]--[[==========================================================================]]--Determine if a category exists (in a function for easier localization).local function catexists( title )	return mw.title.new(title, 'Thể_loại' ).existsend--Error message handling.function p.errorclass( msg )	return mw.text.tag( 'span', {class='error mw-ext-cite-error'}, '<b>Lỗi!</b> '..string.gsub(msg, '&#', '&amp;#') )end--Failure handling.function p.failedcat( errors, sortkey )	if avoidself then		return (errors or '')..'&#42;&#42;&#42;Category series navigation tạo navbox không thành công***'..			   '[['..testcasecolon..'Thể loại:Category series navigation tạo navbox không thành công|'..(sortkey or 'O')..']]\n'	end	return ''end--Tracking cat handling.--	key: 15 (when reindexing ttrackingcats{}, Ctrl+H 'trackcat(13,' & 'ttrackingcats[16]')--	cat: 'Category series navigation isolated'; '' to remove--Used by main, all nav_*(), & several utility functions.local function trackcat( key, cat )	if avoidself and key and cat then		if cat ~= '' then			ttrackingcats[key] = '[['..testcasecolon..'Thể loại:'..cat..']]'		else			ttrackingcats[key] = ''		end	end	returnend--Check for unknown parameters.--Used by main only.local function checkforunknownparams( tbl )	local knownparams = { --parameter whitelist		['min'] = 'min',		['max'] = 'max',		['cat'] = 'cat',		['show'] = 'show',		['testcase'] = 'testcase',		['testcasegap'] = 'testcasegap',		['skip-gaps'] = 'skip-gaps',		['list-all-links'] = 'list-all-links',		['follow-redirects'] = 'follow-redirects',	}	for k, _ in pairs (tbl) do		if knownparams[k] == nil then			trackcat(3, 'Category series navigation sử dụng tham số không rõ')			break		end	endend--Check for nav_*() navigational isolation (not necessarily an error).--Used by all nav_*().local function isolatedcat()	if nexistingcats == 0 then		trackcat(16, 'Category series navigation cô lập')	endend--Similar to {{LinkCatIfExists2}}: make a piped link to a category, if it exists;--if it doesn't exist, just display the greyed link title without linking.--Follows {{Đổi hướng thể loại}}s.--Returns {--			['cat'] = cat,--			['catexists'] = true,--			['rtarget'] = <#R target>,--			['navelement'] = <#R target navelement>,--			['displaytext'] = displaytext,--		  }--		  if #R followed;--returns {--			['cat'] = cat,--			['catexists'] = <true|false>,--			['rtarget'] = nil,--			['navelement'] = <cat navelement>,--			['displaytext'] = displaytext,--		  }--		  otherwise.--Used by all nav_*().local function catlinkfollowr( frame, cat, displaytext, displayend, listoverride )	cat         = mw.text.trim(cat or '')	displaytext = mw.text.trim(displaytext or '')	displayend  = displayend or false --bool flag to override displaytext IIF the cat/target is terminal (e.g. "2021–present" or "2021–")		local disp = cat	if displaytext ~= '' then --use 'displaytext' parameter if present		disp = mw.ustring.gsub(displaytext, '%s+%(.+$', ''); --strip any trailing disambiguator	end		local link, nilorR	local exists = catexists(cat)	if exists then		nexistingcats = nexistingcats + 1		if followRs then			local R = rtarget(cat, frame) --find & follow #R			if R ~= cat then --#R followed				nilorR = R			end						if displayend then				local y, hyph, ending = mw.ustring.match(R, '^.-(%d+)([–-])(.*)$')				if ending == 'nay' then					disp = y..hyph..ending				elseif ending == '' then					disp = y..hyph..'<span style="visibility:hidden">'..y..'</span>' --hidden y to match spacing				end			end						link = '[[:Thể loại:'..R..'|'..disp..']]'		else			link = '[[:Thể loại:'..cat..'|'..disp..']]'		end	else		link = '<span class="categorySeriesNavigation-item-inactive">'..disp..'</span>'	end		if listall and listoverride == nil then		if nilorR then --#R followed			table.insert( tlistall, '[[:Thể loại:'..cat..']] → '..'[[:Thể loại:'..nilorR..']] ('..link..')' )		else --no #R			table.insert( tlistall, '[[:Thể loại:'..cat..']] ('..link..')' )		end	end		return {		['cat'] = cat,		['catexists'] = exists,		['rtarget'] = nilorR,		['navelement'] = link,		['displaytext'] = disp,	}end--Returns a numbered list of all {{Đổi hướng thể loại}}s followed by catlinkfollowr() -> rtarget().--For a nav_hyphen() cat, also returns a formatted list of all cats searched for & found, & all loop indices.--Used by all nav_*().local function listalllinks()	local nl = '\n# '	local out = ''	if currtitle.nsText == 'Thể_loại' then		errors = p.errorclass('Tham số/công cụ <b><code>|list-all-links=yes</code></b> '..							'không nên được lưu trong không gian thể loại, mà chỉ có thể xem trước.')		out = p.failedcat(errors, 'Z')	end		local bwd, fwd = '', ''	if tlistallbwd[1] then		bwd = '\n\nbackward search:'..nl..table.concat(tlistallbwd, nl)	end	if tlistallfwd[1] then		fwd = '\n\nforward search:'..nl..table.concat(tlistallfwd, nl)	end		if tlistall[1] then		return out..nl..table.concat(tlistall, nl)..bwd..fwd	else		return out..nl..'Không tìm thấy liên kết!?'..bwd..fwd	endend--Returns the difference b/w 2 ints separated by endash|hyphen, nil if error.--Used by nav_hyphen() only.local function find_duration( cat )	local from, to = mw.ustring.match(cat, '(%d+)[–-](%d+)')	if from and to then		if to == '00' then return nil end --doesn't follow CNBS:DATERANGE		if (#from == 4) and (#to == 2) then             --1900-01			to = string.match(from, '(%d%d)%d%d')..to   --1900-1901		elseif (#from == 2) and (#to == 4) then         --  01-1902			from = string.match(to, '(%d%d)%d%d')..from --1901-1902		end		return (tonumber(to) - tonumber(from))	end	return 0end--Returns the ending of a terminal cat, and sets the appropriate tracking cat, else nil.--Used by nav_hyphen() only.local function find_terminaltxt( cat )	local terminaltxt = nil	if mw.ustring.match(cat, '%d+[–-]nay$') then		terminaltxt = 'nay'		trackcat(14, 'Category series navigation có khoảng kết thúc (hiện tại)')	elseif mw.ustring.match(cat, '%d+[–-]$') then		terminaltxt = ''		trackcat(15, 'Category series navigation có khoảng kết thúc (trắng, CNBS)')	end	return terminaltxtend--Returns an unsigned string of the 1-4 digit decade ending in "0", else nil.--Used by nav_decade() only.local function sterilizedec( decade )	if decade == nil or decade == '' then		return nil	end		local dec = string.match(decade, '^[-%+]?(%d?%d?%d?0)$') or				string.match(decade, '^[-%+]?(%d?%d?%d?0)%D')	if dec then		return dec	else		--fix 2-4 digit decade		local decade_fixed234 = string.match(decade, '^[-%+]?(%d%d?%d?)%d$') or								string.match(decade, '^[-%+]?(%d%d?%d?)%d%D')		if decade_fixed234 then			return decade_fixed234..'0'		end				--fix 1-digit decade		local decade_fixed1   = string.match(decade, '^[-%+]?(%d)$') or								string.match(decade, '^[-%+]?(%d)%D')		if decade_fixed1 then			return '0'		end				--unfixable		return nil	endend--Check for nav_hyphen default gap size + isolatedcat() (not necessarily an error).--Used by nav_hyphen() only.local function defaultgapcat( bool )	if bool and nexistingcats == 0 then		--using "nexistingcats > 0" isn't as useful, since the default gap size obviously worked		trackcat(17, 'Category series navigation có khoảng cách thời gian mặc định')	endend--12 -> 12th, etc.--Used by nav_nordinal() & nav_wordinal().function p.addord( i )	if tonumber(i) then		local s = tostring(i)				local tens = string.match(s, '1%d$')		if    tens then return s end				local  ones = string.match(s, '%d$')		if     ones == '1' then return s		elseif ones == '2' then return s		elseif ones == '3' then return s end				return s	end	return iend--Returns the properly formatted central nav element.--Expects an integer i, and a catlinkfollowr() table.--Used by nav_decade() & nav_ordinal() only.local function navcenter( i, catlink )	if i == 0 then --center nav element		if navborder == true then			return '<b>'..catlink.displaytext..'</b>'		else			return '<b>'..catlink.navelement..'</b>'		end	else		return catlink.navelement	endend--Wrap one or two navs in a <div> with ARIA attributes; add TemplateStyles--before it. This also aligns the navs in case some floating element (like a--portal box) breaks their alignment.--Used by main only.local function wrap( nav1, nav2 )	local templatestyles = require("Module:TemplateStyles")(		"Module:Category series navigation/styles.css"	)	local prepare = function (nav)		if nav then			nav = '\n'..nav		else			nav = ''		end		return nav	end	return templatestyles..		'<div class="categorySeriesNavigation" role="navigation" aria-label="Range">'..		prepare(nav1)..prepare(nav2)..		'\n</div>'end--[[==========================================================================]]--[[                  Formerly separated templates/modules                    ]]--[[==========================================================================]]--[[==========================={{  nav_hyphen  }}=============================]]local function nav_hyphen( frame, start, hyph, finish, firstpart, lastpart, minseas, maxseas, testgap )	--Expects a PAGENAME of the form "Some sequential 2015–16 example cat", where	--	start     = 2015	--	hyph      = –	--	finish    = 16 (sequential years can be abbreviated, but others should be full year, e.g. "2001–2005")	--	firstpart = Some sequential	--	lastpart  = example cat	--	minseas   = 1800 ('min' starting season shown; optional; defaults to -9999)	--	maxseas   = 2000 ('max' starting season shown; optional; defaults to 9999; 2000 will show 2000-01)	--	testgap   = 0 (testcasegap parameter for easier testing; optional)		--sterilize start	if string.match(start or '', '^%d%d?%d?%d?$') == nil then --1-4 digits, AD only		local start_fixed = mw.ustring.match(start or '', '^%s*(%d%d?%d?%d?)%D')		if start_fixed then			start = start_fixed		else			errors = p.errorclass('Hàm nav_hyphen không thể nhận ra số "'..(start or '')..'" '..								  'trong phần đầu của tham số "season" được đưa vào nó. '..								  'V.d. "2015–16", "2015" lẽ ra phải được nhận ra bằng "|2015|–|16|".')			return p.failedcat(errors, 'H')		end	end	local nstart = tonumber(start)		--en dash check	if hyph ~= '–' then		trackcat(4, 'Category series navigation có khoảng không sử dụng en dash') --nav still processable, but track	end		--sterilize finish & check for weird parents	local tgaps   = {} --table of gap sizes found b/w terms    { [<gap size found>]    = 1 } for -3 <= j <= 3	local tgapsj4 = {} --table of gap sizes found b/w terms    { [<gap size found>]    = 1 } for j = { -4, 4 }	local ttlens  = {} --table of term lengths found w/i terms { [<term length found>] = 1 }	local tirregs = {} --table of ir/regular-term-length cats' "from"s & "to"s found	local regularparent = true	if (finish == -1) or --"Members of the Scottish Parliament 2021–present"	   (finish == 0)	 --"Members of the Scottish Parliament 2021–"	then		regularparent = false		if maxseas == nil or maxseas == '' then			maxseas = start --hide subsequent ranges		end		if finish == -1 then trackcat(14, 'Category series navigation có khoảng kết thúc (hiện tại)')		else				 trackcat(15, 'Category series navigation có khoảng kết thúc (trắng, CNBS)') end	elseif (start == finish) and		   (ttrackingcats[16] ~= '') --nav_year found isolated; check for surrounding hyphenated terms (e.g. UK MPs 1974)	then		trackcat(16, '') --reset for another check later		trackcat(13, 'Category series navigation có khoảng bất thường, độ dài 0')		ttlens[0] = 1 --calc ttlens for std cases below		regularparent = 'isolated'	end	if (string.match(finish or '', '^%d+$') == nil) and	   (string.match(finish or '', '^%-%d+$') == nil)	then		local finish_fixed = mw.ustring.match(finish or '', '^%s*(%d%d?%d?%d?)%D')		if finish_fixed then			finish = finish_fixed		else			errors = p.errorclass('Hàm nav_hyphen không thể nhận ra số "'..(finish or '')..'" '..								  'trong phần thứ hai của tham số "season" được đưa vào nó. '..								  'V.d. "2015–16", "16" lẽ ra phải được nhận ra bằng "|2015|–|16|".')			return p.failedcat(errors, 'I')		end	else		if string.len(finish) >= 5 then			errors = p.errorclass('Phần thứ hai của tham số season đưa vào hàm nav_hyphen chỉ nên có bốn số hoặc ít hơn, không phải "'..(finish or '')..'". '..								  'Xem [[CNBS:DATERANGE]] để biết thêm thông tin.')			return p.failedcat(errors, 'J')		end	end	local nfinish = tonumber(finish)		--save sterilized parent range for easier lookup later	tirregs['from0'] = nstart	tirregs['to0']   = nfinish		--sterilize min/max	local nminseas_default = -9999	local nmaxseas_default =  9999	local nminseas = tonumber(minseas) or nminseas_default --same behavior as nav_year	local nmaxseas = tonumber(maxseas) or nmaxseas_default --same behavior as nav_year	if nminseas > nstart then nminseas = nstart end	if nmaxseas < nstart then nmaxseas = nstart end		local lspace = ' ' --assume a leading space (most common)	local tspace = ' ' --assume a trailing space (most common)	if string.match(firstpart, '%($') then lspace = '' end --DNE for "Madrid city councillors (2007–2011)"-type cats	if string.match(lastpart,  '^%)') then tspace = '' end --DNE for "Madrid city councillors (2007–2011)"-type cats		--calculate term length/intRAseason size & finishing year	local t = 1	while t <= term_limit and regularparent == true do		local nish = nstart + t --use switchADBC to flip this sign to work for years BC, if/when the time comes		if (nish == nfinish) or (string.match(nish, '%d?%d$') == finish) then			ttlens[t] = 1			break		end		if t == term_limit then			errors = p.errorclass('Hàm nav_hyphen không thể xác định khoảng định nghĩa phù hợp cho "'..start..hyph..finish..'".')			return p.failedcat(errors, 'K')		end		t = t + 1	end		--apply CNBS:DATERANGE to parent	local lenstart = string.len(start)	local lenfinish = string.len(finish)	if lenstart == 4 and regularparent == true then --"2001–..."		if t == 1 then --"2001–02" & "2001–2002" both allowed			if lenfinish ~= 2 and lenfinish ~= 4 then				errors = p.errorclass('Phần thứ hai của tham số season đưa vào hàm nav_hyphen nên có hai hoặc bốn số, không phải "'..finish..'".')				return p.failedcat(errors, 'L')			end		else --"2001–2005" is required for t > 1; track "2001–05"; anything else = error			if lenfinish == 2 then				trackcat(5, 'Category series navigation có khoảng viết tắt (CNBS)')			elseif lenfinish ~= 4 then				errors = p.errorclass('Phần thứ hai của tham số season đưa vào hàm nav_hyphen chỉ nên có bốn số, không phải "'..finish..'".')				return p.failedcat(errors, 'M')			end		end		if finish == '00' then --full year required regardless of term length			trackcat(5, 'Category series navigation có khoảng viết tắt (CNBS)')		end	end		--calculate intERseason gap size	local hgap_default     = 0 --assume & start at the most common case: 2001–02 -> 2002–03, etc.	local hgap_limit_reg   = hgap_limit --less expensive per-increment (inc x 4)	local hgap_limit_irreg = hgap_limit --more expensive per-increment (inc x 23 = inc x (k_bwd + k_fwd) = inc x (12 + 11))	local hgap_success = false	local hgap = hgap_default	while hgap <= hgap_limit_reg and regularparent == true do --verify		local prevseason2 = firstpart..lspace..(nstart-t-hgap)..hyph..string.match(nstart-hgap, '%d?%d$')    ..tspace..lastpart		local nextseason2 = firstpart..lspace..(nstart+t+hgap)..hyph..string.match(nstart+2*t+hgap, '%d?%d$')..tspace..lastpart		local prevseason4 = firstpart..lspace..(nstart-t-hgap)..hyph..(nstart-hgap)    ..tspace..lastpart		local nextseason4 = firstpart..lspace..(nstart+t+hgap)..hyph..(nstart+2*t+hgap)..tspace..lastpart		if t == 1 then --test abbreviated range first, then full range, to be frugal with expensive functions			if catexists(prevseason2) or --use 'or', in case we're at the edge of the cat structure,			   catexists(nextseason2) or --or we hit a "–00"/"–2000" situation on one side			   catexists(prevseason4) or			   catexists(nextseason4)			then				hgap_success = true				break			end		elseif t > 1 then --test full range first, then abbreviated range, to be frugal with expensive functions			if catexists(prevseason4) or --use 'or', in case we're at the edge of the cat structure,			   catexists(nextseason4) or --or we hit a "–00"/"–2000" situation on one side			   catexists(prevseason2) or			   catexists(nextseason2)			then				hgap_success = true				break			end		end		hgap = hgap + 1	end	if hgap_success == false then		hgap = tonumber(testgap) or hgap_default --tracked via defaultgapcat()	end		--preliminary scan to determine ir/regular spacing of nearby cats;	--to limit expensive function calls, CNBS:DATERANGE-violating cats are ignored;	--an irregular-term-length series should follow "YYYY..hyph..YYYY" throughout	local jlimit = 4  --4-a-side if all YYYY-YY, 3-a-side if all YYYY-YYYY, with some threshold in between	if hgap <= hgap_limit_reg then --also to isolate temp vars		--find # of nav-visible ir/regular-term-length cats		local bwanchor = nstart       --backward anchor/common year		local fwanchor = bwanchor + t --forward anchor/common year		if regularparent == 'isolated' then			fwanchor = bwanchor		end		local spangreen = '[<span style="color:green">j, g, k = ' --used for/when debugging via list-all-links=yes		local spanblue = '<span style="color:blue">'		local spanred = ' (<span style="color:red">'		local span = '</span>'		local lastg = nil --to check for run-on searches		local lastk = nil --to check for run-on searches		local endfound = false --switch used to stop searching forward		local iirregs = 0 --index of tirregs[] for j < 0, since search starts from parent		local j = -jlimit --index of tirregs[] for j > 0 & pseudo navh position		while j <= jlimit do						if j < 0 then --search backward from parent				local gbreak = false --switch used to break out of g-loop				local g = 0 --gap size				while g <= hgap_limit_irreg do					local k = 0 --term length: 0 = "0-length", 1+ = normal					while k <= term_limit do						local from = bwanchor - k - g						local to   = bwanchor - g						local full = mw.text.trim( firstpart..lspace..from..hyph..to..tspace..lastpart )						if k == 0 then							if regularparent ~= 'isolated' then --+restrict to g == 0 if repeating year problems arise								to = '0-length'								full = mw.text.trim( firstpart..lspace..from..tspace..lastpart )								if catlinkfollowr( frame, full ).rtarget ~= nil then --#R followed									table.insert( tlistallbwd, spangreen..j..', '..g..', '..k..span..'] '..full..spanred..'#R ignored'..span..')' )									full, to = '', '' --don't use/follow 0-length cat #Rs from nav_hyphen(); otherwise gets messy								end							end						end						if (k >= 1) or		  --the normal case; only continue k = 0 if 0-length found						   (to == '0-length') --ghetto "continue" (thx Lua) to avoid expensive searches for "UK MPs 1974-1974", etc.						then							table.insert( tlistallbwd, spangreen..j..', '..g..', '..k..span..'] '..full )							if (k == 1) and--							   (g == 0 or g == 1) and --commented to match j>0 case ("1995–96 in Federal Republic of Yugoslavia basketball")							   (catexists(full) == false)							then --allow bare-bones CNBS:DATERANGE alternation, in case we're on a 0|1-gap, 1-year term series								local to2 = string.match(to, '%d%d$')								if to2 and to2 ~= '00' then --and not at a century transition (i.e. 1999–2000)									to = to2									full = mw.text.trim( firstpart..lspace..from..hyph..to..tspace..lastpart )									table.insert( tlistallbwd, spangreen..j..', '..g..', '..k..span..'] '..full )								end							end							if catexists(full) then								if to == '0-length' then									trackcat(13, 'Category series navigation có khoảng bất thường, độ dài 0')								end								tlistallbwd[#tlistallbwd] = spanblue..tlistallbwd[#tlistallbwd]..span..' (found)'								ttlens[ find_duration(full) ] = 1								if j == -1 then tgapsj4[g] = 1 -- -1 since bwd search starts from parent @ -4 and ends at -1								else tgaps[g] = 1 end								iirregs = iirregs + 1								tirregs['from-'..iirregs] = from								tirregs['to-'..iirregs] = to								bwanchor = from --ratchet down								if to ~= '0-length' then									gbreak = true									break								else									g = 0 --soft-reset g, to keep stepping thru k									j = j + 1 --save, but keep searching thru k									if j > 0 then --(restore "> 3" if acts up) lest we keep searching bwd & finding 0-length cats ("MEPs for the Republic of Ireland 1973" & down)										j = -1 --allow a normal, full search fwd after break										gbreak = true										break									end								end							elseif (j >= 0) and								   (lastg and lastk) and								   ((lastg >= hgap_limit_irreg) or									(lastk >= term_limit))							then --bwd search exhausted and/or done (runaway bwd search on "2018–19 FIA World Endurance Championship season")								j = -1 --allow a normal, full search fwd after break								gbreak = true								break							end						end --ghetto "continue"						k = k + 1						lastk = k					end --while k <= term_limit do					if gbreak == true then break end					g = g + 1					lastg = g				end --while g <= hgap_limit_irreg do			end --if j < 0						if j > 0 and endfound == false then --search forward from parent				local gbreak = false --switch used to break out of g-loop				local g = 0 --gap size				while g <= hgap_limit_irreg do					local k = -2 --term length: -2 = "0-length", -1 = "2020–present", 0 = "2020–", 1+ = normal					while k <= term_limit do						local from = fwanchor + g						local to4  = fwanchor + k + g	--override carefully						local to2  = nil				--last 2 digits of to4, IIF exists						if k == -1 then to4 = 'present'	--see if end-cat exists (present)						elseif k == 0 then to4 = '' end	--see if end-cat exists (blank)						local full = mw.text.trim( firstpart..lspace..from..hyph..to4..tspace..lastpart )						if k == -2 then							if regularparent ~= 'isolated' then --+restrict to g == 0 if repeating year problems arise								to4 = '0-length' --see if 0-length cat exists								full = mw.text.trim( firstpart..lspace..from..tspace..lastpart )								if catlinkfollowr( frame, full ).rtarget ~= nil then --#R followed									table.insert( tlistallfwd, spangreen..j..', '..g..', '..k..span..'] '..full..spanred..'#R ignored'..span..')' )									full, to4 = '', '' --don't use/follow 0-length cat #Rs from nav_hyphen(); otherwise gets messy								end							end						end						if (k >= -1) or		   --only continue k = -2 if 0-length found						   (to4 == '0-length') --ghetto "continue" (thx Lua) to avoid expensive searches for "UK MPs 1974-1974", etc.						then							table.insert( tlistallfwd, spangreen..j..', '..g..', '..k..span..'] '..full )							if (k == 1) and--							   (g == 0 or g == 1) and --commented to let "2002–03 in Scottish women's football" find "2008–09 in Scottish women's football"							   (catexists(full) == false)							then --allow bare-bones CNBS:DATERANGE alternation, in case we're on a 0|1-gap, 1-year term series								to2 = string.match(to4, '%d%d$')								if to2 and to2 ~= '00' then --and not at a century transition (i.e. 1999–2000)									full = mw.text.trim( firstpart..lspace..from..hyph..to2..tspace..lastpart )									table.insert( tlistallfwd, spangreen..j..', '..g..', '..k..span..'] '..full )								end							end							if catexists(full) then								if to4 == '0-length' then									if rtarget(full, frame) == full then --only use 0-length cats that don't #R										trackcat(13, 'Category series navigation có khoảng bất thường, độ dài 0')									end								end								tirregs['from'..j] = from								tirregs['to'..j] = (to2 or to4)								if (k == -1) or (k == 0) then									endfound = true --tentative								else --k == { -2, > 0 }									tlistallfwd[#tlistallfwd] = spanblue..tlistallfwd[#tlistallfwd]..span..' (found)'									ttlens[ find_duration(full) ] = 1									if j == 4 then tgapsj4[g] = 1									else tgaps[g] = 1 end									endfound = false									if to4 ~= '0-length' then --k > 0										fwanchor = to4 --ratchet up										gbreak = true										break --only break on k > 0 b/c old end-cat #Rs still exist like "Members of the Scottish Parliament 2011–"									else --k == -2										j = j + 1 --save, but keep searching k's, in case "1974" → "1974-1979"										if j > jlimit then --lest we keep searching & finding 0-length cats ("2018 CONCACAF Champions League" & up)											gbreak = true											break										elseif g == hgap_limit_irreg then											--keep searching, since not a runaway, just far away ("American soccer clubs 1958–59 season")											hgap_limit_irreg = hgap_limit_irreg + 1										end									end								end							end						end --ghetto "continue"						k = k + 1						lastk = k					end --while k <= term_limit do					if gbreak == true then break end					g = g + 1					lastg = g				end --while g <= hgap_limit_irreg do			end --if j > 0 and endfound == false then						if (lastg and lastk) and			   (lastg > hgap_limit_irreg) and			   (lastk > term_limit)			then --search exhausted				if j < 0 then j = 0 --bwd search exhausted; continue fwd				elseif j > 0 then break end --fwd search exhausted			end						j = j + 1		end --while j <= jlimit	end --if hgap <= hgap_limit_reg		--determine # of displayed navh elements based on "YYYY-YY" vs. "YYYY-YYYY" counts	local Ythreshold = 3.3 --((YYYY-YY x 7) + (YYYY-YYYY x 2))/18 = 3.222; ((YYYY-YY x 6) + (YYYY-YYYY x 3))/18 = 3.333	local Ycount = 0 --"Y" count	local ycount = 0 --tirregs counter; # of contiguous #s	for k, v in pairs (tirregs) do		local dummy, dunce = mw.ustring.gsub(tostring(v), '%d', '') --why can't gsub just return a table??		Ycount = Ycount + dunce		ycount = ycount + 1	end	local ycount_limit = ((jlimit * 2) + 1) * 2 --i.e. ((4 * 2) + 1) * 2 = 18	if ycount < ycount_limit then --fill in the blanks with Ycount_parent, since hidden/dne cats aren't in tirregs		local dummy_finish = finish		if not regularparent then dummy_finish = start end		local dummy, dunce_from = mw.ustring.gsub(start,  '%d', '')		local dummy, dunce_to   = mw.ustring.gsub(dummy_finish, '%d', '')		local Ycount_parent_avg = (dunce_from + dunce_to)/2 --"YYYY-YYYY" = 4; "YYYY-YY" = 3		Ycount = Ycount + (Ycount_parent_avg * (ycount_limit - ycount))		ycount = ycount_limit	end	local iwidth = 3 --default to 3-a-side, 7 total	local Y_per_y = Ycount / ycount --normalized range: [3-4]	if Y_per_y < Ythreshold then		iwidth = 4 --extend to 4-a-side, 9 total	end		--begin navhyphen	local navh = '<div class="toccolours categorySeriesNavigation-range">\n'		local navlist = {}	local terminalcat = false --switch used to hide future cats	local terminaltxt = nil	local i = -iwidth --nav position	while i <= iwidth do		local from = nstart + i*(t+hgap) --the logical, but not necessarily correct, 'from'		if tirregs['from'..i] then --prefer the irregular term table			from = tonumber(tirregs['from'..i])		else --fallback to lazy/naive 'from'			if i > 0 and			   tirregs['from'..(i-1)] and			   tirregs['from'..(i-1)] >= from			then --end of the line: avoid dups/past, and create reasonable grey'd ranges				local greyto   = tonumber(tirregs['to' .. (i-1)]) or -9999				local greyfrom = tonumber(tirregs['from'..(i-1)]) or -9999				local grey = greyto --prefer 'to'				if greyfrom > greyto then grey = greyfrom end --'from' fallback, in case "1995–96", "1995-present", etc.				if grey > -9999 then					if grey ~= greyto then						from = grey + t + hgap --account for missing/incomplete 'to'					else						from = grey + hgap					end					tirregs['from'..i] = from --remember					tirregs['to' .. i] = from + t				end			elseif i < 0 then				local greyfrom				local ii = 0				while ii < 3 do					ii = ii + 1					greyfrom = tonumber(tirregs['from'..(i+ii)])					if greyfrom then break end				end				from = (greyfrom or nstart) - ii*(t+hgap)				tirregs['from'..i] = from --remember				tirregs['to' .. i] = from + t			end		end		local from2 = string.match(from, '%d?%d$')				local to = tostring(from+t)	--the logical, naive range, but		if tirregs['to'..i] then	--prefer irregular term table			to = tirregs['to'..i]		elseif regularparent == false and tirregs and i > 0 then			to = tirregs['to-1']	--special treatment for parent terminal cats, since they have no natural 'to'		end		local to2 = string.match(to, '%d?%d$')		local tofinal = (to2 or '')    --assume t=1 and abbreviated 'to' (the most common case)		if t > 1 or                    --per CNBS:DATERANGE (e.g. 1999-2004)		  (from2 - (to2 or from2)) > 0 --century transition exception (e.g. 1999–2000)		then			tofinal = (to or '')       --default to the CNBS-correct format, in case no fallbacks found		end		if to == '0-length' then			tofinal = to		end				--check existance of 4-digit, CNBS-correct range, with abbreviation fallback		if tofinal ~= '0-length' then			if t > 1 and string.len(from) == 4 then --e.g. 1999-2004				--determine which link exists (full or abbr)				local full = firstpart..lspace..from..hyph..tofinal..tspace..lastpart				if not catexists(full) then					local abbr = firstpart..lspace..from..hyph..to2..tspace..lastpart					if catexists(abbr) then						tofinal = (to2 or '') --rv to CNBS-incorrect format; if full AND abbr DNE, then tofinal is still in its CNBS-correct format					end				end			elseif t == 1 then --full-year consecutive ranges are also allowed				local abbr = firstpart..lspace..from..hyph..tofinal..tspace..lastpart --assume tofinal is in abbr format				if not catexists(abbr) and tofinal ~= to then					local full = firstpart..lspace..from..hyph..to..tspace..lastpart					if catexists(full) then						tofinal = (to or '') --if abbr AND full DNE, then tofinal is still in its abbr format (unless it's a century transition)		end	end	end	end				--populate navh		if i ~= 0 then --left/right navh			local orig = firstpart..lspace..from..hyph..tofinal..tspace..lastpart			local disp = from..hyph..tofinal			if tofinal == '0-length' then				orig = firstpart..lspace..from..tspace..lastpart				disp = from			end			local catlink = catlinkfollowr(frame, orig, disp, true) --force terminal cat display						if terminalcat == false then				terminaltxt = find_terminaltxt( disp ) --also sets tracking cats				terminalcat = (terminaltxt ~= nil)			end			if catlink.rtarget and avoidself then --a {{Đổi hướng thể loại}} was followed, figure out why				--determine new term length & gap size				ttlens[ find_duration( catlink.rtarget ) ] = 1				if i > -iwidth then					local lastto = tirregs['to'..(i-1)]					if lastto == nil then						local lastfrom = nstart + (i-1)*(t+hgap)						lastto = lastfrom+t --use last logical 'from' to calc lastto					end					if lastto then						local gapcat = lastto..'-'..from --dummy cat to calc with						local gap = find_duration(gapcat) or -1	--in case of nil,						if iwidth == 4 then							tgapsj4[ gap ] = 1 --tgapsj4[-1] are ignored later						else							tgaps[ gap ] = 1 --tgaps[-1] are ignored later						end					end				end								--display/tracking handling				local base_regex = '%d+[–-]%d+'				local origbase = mw.ustring.gsub(orig, base_regex, '')				local rtarbase, rtarbase_success = mw.ustring.gsub(catlink.rtarget, base_regex, '')				if rtarbase_success == 0 then					local base_regex_lax = '%d%d%d%d' --in case rtarget is a year cat					rtarbase, rtarbase_success = mw.ustring.gsub(catlink.rtarget, base_regex_lax, '')				end				local terminal_regex = '%d+[–-]'..(terminaltxt or '')..'$' --more manual ORs bc Lua regex sux				if mw.ustring.match(orig, terminal_regex) then					origbase = mw.ustring.gsub(orig, terminal_regex, '')				end				if mw.ustring.match(catlink.rtarget, terminal_regex) then					--finagle/overload terminalcat type to set nmaxseas on 1st occurence only					if terminalcat == false then terminalcat = 1 end					local dummy = find_terminaltxt( catlink.rtarget ) --also sets tracking cats					rtarbase = mw.ustring.gsub(catlink.rtarget, terminal_regex, '')				end				origbase = mw.text.trim(origbase)				rtarbase = mw.text.trim(rtarbase)				if origbase ~= rtarbase then					trackcat(6, 'Category series navigation có khoảng là đổi hướng (thay đổi gốc)')				elseif terminalcat == 1 then					trackcat(8, 'Category series navigation có khoảng là đổi hướng (kết thúc)')				else --origbase == rtarbase					local all4s_regex = '%d%d%d%d[–-]%d%d%d%d'					local orig_all4s = mw.ustring.match(orig, all4s_regex)					local rtar_all4s = mw.ustring.match(catlink.rtarget, all4s_regex)					if orig_all4s and rtar_all4s then						trackcat(10, 'Category series navigation có khoảng là đổi hướng (khác)')					else						local year_regex1 = '%d%d%d%d$'						local year_regex2 = '%d%d%d%d[%s%)]'						local year_rtar = mw.ustring.match(catlink.rtarget, year_regex1) or										  mw.ustring.match(catlink.rtarget, year_regex2)						if orig_all4s and year_rtar then							trackcat(7, 'Category series navigation có khoảng là đổi hướng (thay đổi var)')						else							trackcat(9, 'Category series navigation có khoảng là đổi hướng (CNBS)')						end					end				end			end						if terminalcat then --true or 1				if type(terminalcat) ~= 'boolean' then nmaxseas = from end --only want to do this once				terminalcat = true --done finagling/overloading			end			if (from >= 0) and (nminseas <= from) and (from <= nmaxseas) then				table.insert(navlist, catlink.navelement)				if terminalcat then nmaxseas = nminseas_default end --prevent display of future ranges			else				local hidden = '<span style="visibility:hidden">'..disp..'</span>'				table.insert(navlist, hidden)				if listall then					tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'				end			end		else --center navh			if finish == -1 then finish = 'nay'			elseif finish == 0 then finish = '<span style="visibility:hidden">'..start..'</span>' end			local disp = start..hyph..finish			if regularparent == 'isolated' then disp = start end			table.insert(navlist, '<b>'..disp..'</b>')		end				i = i + 1	end		-- add the list	navh = navh..horizontal(navlist)..'\n'		--tracking cats & finalize	if avoidself then		local igaps  = 0 --# of diff gap sizes > 0 found		local itlens = 0 --# of diff term lengths found		for s = 1, hgap_limit_reg do --must loop; #tgaps, #ttlens unreliable			igaps = igaps + (tgaps[s] or 0)		end		if iwidth == 4 then --only count gaps if they were displayed ("Karnataka MLAs 1957–1962")			for s = 1, hgap_limit_reg do				igaps = igaps + (tgapsj4[s] or 0)			end		end		for s = 0, term_limit do			itlens = itlens + (ttlens[s] or 0)		end		if igaps  > 0 then trackcat(11, 'Category series navigation có khoảng với chu kỳ') end		if itlens > 1 and ttrackingcats[13] == '' then --avoid duplication in "Category series navigation range irregular, 0-length"			trackcat(12, 'Category series navigation có khoảng bất thường')		end	end	isolatedcat()	defaultgapcat(not hgap_success)	if listall then		return listalllinks()	else		return navh..'</div>'	endend--[[=========================={{  nav_tvseason  }}============================]]local function nav_tvseason( frame, firstpart, tv, lastpart, maximumtv )	--Expects a PAGENAME of the form "Futurama season 1 episodes", where	--	firstpart = Futurama season	--	tv        = 1	--	lastpart  = episodes	--	maximumtv = 7 ('max' tv season parameter; optional; defaults to 9999)	tv = tonumber(tv)	if tv == nil then		errors = p.errorclass('Hàm nav_tvseason không thể nhận ra số mùa truyền hình được đưa vào tham số thứ 3 của nó.')		return p.failedcat(errors, 'T')	end		--"(season 1) episodes" -> "season 1 episodes" following March 2024 RfC:	--[[Wikipedia talk:Naming conventions (television)#Follow-up RfC on TV season article titles]]	--                  [[Special:Permalink/1216885280#Follow-up RfC on TV season article titles]]	local tspace = ' ' --"season 1 episodes"	local parenth_check = string.match(lastpart, '^%)')	if parenth_check then tspace = '' end --accommodate old style "(season 1) episodes" just in case		local maxtv_default = 9999	local maxtv = tonumber(maximumtv) or maxtv_default --allow +/- qualifier	if maxtv < tv then maxtv = tv end --input error; maxtv should be >= parent		--begin navtvseason	local navt = '<div class="toccolours categorySeriesNavigation-range">\n'		local navlist = {}	local prepad = ''	local i = -5 --nav position	while i <= 5 do		local t = tv + i		if i ~= 0 then --left/right navt			local catlink = catlinkfollowr( frame, firstpart..' '..t..tspace..lastpart, t )			if t >= 1 and t <= maxtv then --hardcode mintv				if catlink.rtarget then --a {{Đổi hướng thể loại}} was followed					trackcat(25, 'Category series navigation về mùa truyền hình đã đổi hướng')				end				if catlink.catexists or				   (maxtv ~= maxtv_default and t <= maxtv)				then					table.insert(navlist, prepad..catlink.navelement) --display normally					prepad = ''				else					local postpad = '<span style="visibility:hidden"> • '..t..'</span>'					navlist[#navlist] = (navlist[#navlist] or '')..postpad					if listall then tlistall[#tlistall] = tlistall[#tlistall]..' ('..postpad..')' end				end				elseif t < 1 then				prepad = prepad..'<span style="visibility:hidden"> • '..'0'..'</span>'				if listall then tlistall[#tlistall] = (tlistall[#tlistall] or '')..' (x)' end			else --t > maxtv				local postpad = '<span style="visibility:hidden"> • '..t..'</span>'				navlist[#navlist] = (navlist[#navlist] or '')..postpad				if listall then tlistall[#tlistall] = tlistall[#tlistall]..' ('..postpad..')' end			end		else --center navt			table.insert(navlist, prepad..'<b>'..tv..'</b>')			prepad = ''		end				i = i + 1	end	-- add the list	navt = navt..horizontal(navlist)..'\n'	isolatedcat()	if listall then		return listalllinks()	else		return navt..'</div>'	endend--[[==========================={{  nav_decade  }}=============================]]local function nav_decade( frame, firstpart, decade, lastpart, mindecade, maxdecade )	--Expects a PAGENAME of the form "Some sequential 2000 example cat", where	--	firstpart = Some sequential	--	decade    = 2000	--	lastpart  = example cat	--	mindecade = 1800 ('min' decade parameter; optional; defaults to -9999)	--	maxdecade = 2020 ('max' decade parameter; optional; defaults to 9999)		--sterilize dec	local dec = sterilizedec(decade)	if dec == nil then		errors = p.errorclass('Hàm nav_decade đã được gửi "'..(decade or '')..'" như là tham số thứ 2, '..							'nhưng nó cần năm từ 1 đến 4 số kết thúc bằng số "0".')		return p.failedcat(errors, 'D')	end	local ndec = tonumber(dec)		--sterilize mindecade & determine AD/BC	local mindefault = '-9999'	local mindec = sterilizedec(mindecade) --returns a tostring(unsigned int), or nil	if mindec then		if string.match(mindecade, '-%d') or		   string.match(mindecade, 'TCN')		then			mindec = '-'..mindec --better +/-0 behavior with strings (0-initialized int == "-0" string...)		end	elseif mindec == nil and mindecade and mindecade ~= '' then		errors = p.errorclass('Hàm nav_decade đã được gửi "'..(mindecade or '')..'" như là tham số thứ 4, '..							'nhưng nó cần năm từ 1 đến 4 số kết thúc bằng số "0", chỉ thập niên đầu tiên cần được hiển thị.')		return p.failedcat(errors, 'E')	else --mindec == nil		mindec = mindefault --tonumber() later, after error checks	end		--sterilize maxdecade & determine AD/BC	local maxdefault = '9999'	local maxdec = sterilizedec(maxdecade) --returns a tostring(unsigned int), or nil + error	if maxdec then		if string.match(maxdecade, '-%d') or		   string.match(maxdecade, 'TCN')		then                     --better +/-0 behavior with strings (0-initialized int == "-0" string...),			maxdec = '-'..maxdec --but a "-0" string -> tonumber() -> tostring() = "-0",		end                      --and a  "0" string -> tonumber() -> tostring() =  "0"	elseif maxdec == nil and maxdecade and maxdecade ~= '' then		errors = p.errorclass('Hàm nav_decade đã được gửi "'..(maxdecade or '')..'" như là tham số thứ 5, '..							'nhưng nó cần năm từ 1 đến 4 số kết thúc bằng số "0", chỉ thập niên cuối cùng cần được hiển thị.')		return p.failedcat(errors, 'F')	else --maxdec == nil		maxdec = maxdefault	end		local tspace = ' ' --assume trailing space for "1950s in X"-type cats	if string.match(lastpart, '^-') then tspace = '' end --DNE for "1970s-related"-type cats		--AD/BC switches & vars		local parentBC = string.match(lastpart, '^TCN') --following the "0s BC" convention for all years BC	lastpart = mw.ustring.gsub(lastpart, '^TCN%s*', '') --handle BC separately; AD never used	--TODO?: handle BCE, but only if it exists in the wild		local dec0to40AD = (ndec >= 0 and ndec <= 40 and not parentBC) --special behavior in this range	local switchADBC = 1                 --  1=AD parent	if parentBC then switchADBC = -1 end -- -1=BC parent; possibly adjusted later	local BCdisp = ''	local D = -math.huge --secondary switch & iterator for AD/BC transition		--check non-default min/max more carefully	if mindec ~= mindefault then		if tonumber(mindec) > ndec*switchADBC then			mindec = tostring(ndec*switchADBC) --input error; mindec should be <= parent		end	end	if maxdec ~= maxdefault then		if tonumber(maxdec) < ndec*switchADBC then			maxdec = tostring(ndec*switchADBC) --input error; maxdec should be >= parent		end	end	local nmindec = tonumber(mindec) --similar behavior to nav_year & nav_nordinal	local nmaxdec = tonumber(maxdec) --similar behavior to nav_nordinal		--begin navdecade	local bnb = '' --border/no border	if navborder == false then --for Category series navigation year and decade		bnb = 'categorySeriesNavigation-range-transparent'	end	local navd = '<div class="toccolours categorySeriesNavigation-range '..bnb..'">\n'		local navlist = {}	local i = -50 --nav position x 10	while i <= 50 do		local d = ndec + i*switchADBC				local BC = ''		BCdisp = ''		if dec0to40AD then			if D < -10 then				d = math.abs(d + 10) --b/c 2 "0s" decades exist: "0s BC" & "0s" (AD)				BC = 'TCN '				if d == 0 then					D = -10 --track 1st d = 0 use (BC)				end			elseif D >= -10 then				D = D + 10 --now iterate from 0s AD				d = D      --2nd d = 0 use			end		elseif parentBC then			if switchADBC == -1 then --parentBC looking at the BC side (the common case)				BC = 'TCN '				if d == 0 then     --prepare to switch to the AD side on the next iteration					switchADBC = 1 --1st d = 0 use (BC)					D = -10        --prep				end			elseif switchADBC == 1 then --switched to the AD side				D = D + 10 --now iterate from 0s AD				d = D      --2nd d = 0 use (on first use)			end		end		if BC ~= '' and ndec <= 50 then			BCdisp = ' TCN' --show BC for all BC decades whenever a "0s" is displayed on the nav		end				--determine target cat		local disp = 'TN&nbsp;'..d..BCdisp		local catlink = catlinkfollowr( frame, firstpart..' '..d..tspace..BC..lastpart, disp )		if catlink.rtarget then --a {{Đổi hướng thể loại}} was followed			trackcat(18, 'Category series navigation có thập niên đã đổi hướng')		end				--populate left/right navd		local shown = navcenter(i, catlink)		local hidden = '<span style="visibility:hidden">'..disp..'</span>'		local dsign = d --use d for display & dsign for logic		if BC ~= '' then dsign = -dsign end		if (nmindec <= dsign) and (dsign <= nmaxdec) then			if dsign == 0 and (nmindec == 0 or nmaxdec == 0) then --distinguish b/w -0 (BC) & 0 (AD)				--"zoom in" on +/- 0 and turn dsign/min/max temporarily into +/- 1 for easier processing				local zsign, zmin, zmax = 1, nmindec, nmaxdec				if BC ~= '' then zsign = -1 end				if     mindec == '-0' then zmin = -1				elseif mindec ==  '0' then zmin =  1 end				if     maxdec == '-0' then zmax = -1				elseif maxdec ==  '0' then zmax =  1 end								if (zmin <= zsign) and (zsign <= zmax) then					table.insert(navlist, shown)					hidden = nil				else					table.insert(navlist, hidden)				end			else				table.insert(navlist, shown)--the common case				hidden = nil			end		else			table.insert(navlist, hidden)		end		if listall and hidden then			tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'		end				i = i + 10	end	-- add the list	navd = navd..horizontal(navlist)..'\n'	isolatedcat()	if listall then		return listalllinks()	else		return navd..'</div>'	endend--[[============================{{  nav_year  }}==============================]]local function nav_year( frame, firstpart, year, lastpart, minimumyear, maximumyear )	--Expects a PAGENAME of the form "Some sequential 1760 example cat", where	--	firstpart   = Some sequential	--	year        = 1760	--	lastpart    = example cat	--	minimumyear = 1758 ('min' year parameter; optional)	--	maximumyear = 1800 ('max' year parameter; optional)	local minyear_default = -9999	local maxyear_default =  9999	year = tonumber(year) or tonumber(mw.ustring.match(year or '', '^%s*(%d*)'))	local minyear = tonumber(string.match(minimumyear or '', '-?%d+')) or minyear_default --allow +/- qualifier	local maxyear = tonumber(string.match(maximumyear or '', '-?%d+')) or maxyear_default --allow +/- qualifier	if string.match(minimumyear or '', 'TCN') then minyear = -math.abs(minyear) end --allow BC qualifier (AD otherwise assumed)	if string.match(maximumyear or '', 'TCN') then maxyear = -math.abs(maxyear) end --allow BC qualifier (AD otherwise assumed)		if year == nil then		errors = p.errorclass('Hàm nav_year không thể nhận ra năm được đưa vào tham số thứ 2 của nó.')		return p.failedcat(errors, 'Y')	end		--AD/BC switches & vars		local yearBCElastparts = { --needed for parent = AD 1-5, when the BC/E format is unknown		--"BCE" removed to match both AD & BCE cats; easier & faster than multiple string.match()s		['example_Hebrew people_example'] = 'BCE', --example entry format; add to & adjust as needed	}	local parentAD = nil  --following the "AD 1" convention from AD 1 to AD 10	local parentBC = string.match(lastpart, '^TCN?') --following the "1 BC" convention for all years BC	firstpart = mw.ustring.gsub(firstpart, '%s*SCN$', '') --handle AD/BC separately for easier & faster accounting	lastpart  = mw.ustring.gsub(lastpart,  '^TCN?%s*', '')	local BCe = parentBC or yearBCElastparts[lastpart] or 'TCN' --"TCN" default		local year1to10 = (year >= 1 and year <= 10)	local year1to10ADBC = year1to10 and (parentBC or parentAD) --special behavior 1-10 for low-# non-year series	local year1to15AD = (year >= 1 and year <= 15 and not parentBC) --special behavior 1-15 for AD/BC display	local switchADBC = 1                 --  1=AD parent	if parentBC then switchADBC = -1 end -- -1=BC parent; possibly adjusted later	local Y = 0 --secondary iterator for AD-on-a-BC-parent		if minyear > year*switchADBC then minyear = year*switchADBC end --input error; minyear should be <= parent	if maxyear < year*switchADBC then maxyear = year*switchADBC end --input error; maxyear should be >= parent		local lspace = ' ' --leading space before year, after firstpart	if string.match(firstpart, '[%-VW]$') then		lspace = '' --e.g. "Straight-8 engines"	end		local tspace = ' ' --trailing space after year, before lastpart	if string.match(lastpart, '^-') then		tspace = '' --e.g. "2018-related timelines"	elseif lastpart == '' then		tspace = ''	end		--determine interyear gap size to condense special category types, if possible	local ygapdefault = 1 --assume/start at the most common case: 2001, 2002, etc.	local ygap = ygapdefault	if string.match(lastpart, 'presidential') then		local ygap1, ygap2 = ygapdefault, ygapdefault --need to determine previous & next year gaps indepedently		local ygap1_success, ygap2_success = false, false				local prevseason = nil		while ygap1 <= ygap_limit do --Czech Republic, Poland, Sri Lanka, etc. have 5-year terms			prevseason = firstpart..lspace..(year-ygap1)..tspace..lastpart			if catexists(prevseason) then				ygap1_success = true				break			end			ygap1 = ygap1 + 1		end				local nextseason = nil		while ygap2 <= ygap_limit do --Czech Republic, Poland, Sri Lanka, etc. have 5-year terms			nextseason = firstpart..lspace..(year+ygap2)..tspace..lastpart			if catexists(nextseason) then				ygap2_success = true				break			end			ygap2 = ygap2 + 1		end				if ygap1_success and ygap2_success then			if ygap1 == ygap2 then ygap = ygap1 end		elseif ygap1_success then  ygap = ygap1		elseif ygap2_success then  ygap = ygap2		end	end		--skip non-existing years, if requested	local ynogaps = {} --populate with existing years in the range, at most, [year - (skipgaps_limit * 5), year + (skipgaps_limit * 5)]	if skipgaps then		mw.log("skipgaps enabled")		if minyear == minyear_default then			minyear = 0 --automatically set minyear to 0, as AD/BC not supported anyway		end		if (year > 70) or --add support for AD/BC (<= AD 10) if/when needed		   (minyear >= 0 and --must be a non-year series like "AC with 0 elements"		   	not parentAD and not parentBC)		then			local yskipped = {} --track skipped y's to avoid double-checking			local cat, found, Yeary						 --populate nav element queue outwards positively from the parent			local Year = year --to save/ratchet progression			local i = 1			while i <= 5 do				local y = 1				while y <= skipgaps_limit do					found = false					Yeary = Year + y					if yskipped[Yeary] == nil then						yskipped[Yeary] = Yeary						cat = firstpart..lspace..Yeary..tspace..lastpart						mw.log("Checking cat: '" .. cat .. "'...")						found = catexists(cat)						mw.log(found)						if found then break end					end					y = y + 1				end				if found then Year = Yeary				else          Year = Year + 1 end				ynogaps[i] =  Year				i = i + 1			end						ynogaps[0] = year --the parent						--populate nav element queue outwards negatively from the parent			Year = year --reset ratchet			i = -1			while i >= -5 do				local y = -1				while y >= -skipgaps_limit do					found = false					Yeary = Year + y					if yskipped[Yeary] == nil then						yskipped[Yeary] = Yeary						cat = firstpart..lspace..Yeary..tspace..lastpart						mw.log("Checking cat: '" .. cat .. "'...")						found = catexists(cat)						mw.log(found)						if found then break end					end					y = y - 1				end				if found then Year = Yeary				else          Year = Year - 1 end				ynogaps[i] =  Year				i = i - 1			end		else			skipgaps = false --TODO: AD/BC support, then lift BC restrictions @ [[Template:Establishment category BC]] & [[Template:Year category header/core]]		end	end		--begin navyears	local navy = '<div class="toccolours categorySeriesNavigation-range">\n'		local navlist = {}	local y	local j = 0 --decrementor for special cases "2021 World Rugby Sevens Series" -> "2021–2022"	local i = -5 --nav position	while i <= 5 do		if skipgaps then			y = ynogaps[i]		else			y = year + i*ygap*switchADBC - j		end		local BCdisp = ''		if i ~= 0 then --left/right navy						local AD = ''			local BC = ''			if year1to15AD and not			   (year1to10 and not year1to10ADBC) --don't AD/BC 1-10's if parents don't contain AD/BC			then				if year >= 11 then --parent = AD 11-15					if y <= 10 then --prepend AD on y = 1-10 cats only, per existing cats						AD = ''					end									elseif year >= 1 then --parent = AD 1-10					if y <= 0 then						BC = BCe..' '						y = math.abs(y - 1) --skip y = 0 (DNE)					elseif y >= 1 and y <= 10 then --prepend AD on y = 1-10 cats only, per existing cats						AD = ''					end				end							elseif parentBC then				if switchADBC == -1 then --displayed y is in the BC regime					if y >= 1 then     --the common case						BC = BCe..' '					elseif y == 0 then --switch from BC to AD regime						switchADBC = 1					end				end				if switchADBC == 1 then --displayed y is now in the AD regime					Y = Y + 1 --skip y = 0 (DNE)					y = Y     --easiest solution: start another iterator for these AD y's displayed on a BC year parent					AD = ''				end			end			if BC ~= '' and year <= 5 then --only show 'TCN' for parent years <= 5: saves room, easier to read,				BCdisp = ' '..BCe          --and 6 is the first/last nav year that doesn't need a disambiguator;			end                            --the center/parent year will always show BC, so no need to show it another 10x						--populate left/right navy			local ysign = y --use y for display & ysign for logic			local disp = y..BCdisp			if BC ~= '' then ysign = -ysign end			local firsttry = firstpart..lspace..AD..y..tspace..BC..lastpart			if (minyear <= ysign) and (ysign <= maxyear) then				local catlinkAD = catlinkfollowr( frame, firsttry, disp ) --try AD				local catlink = catlinkAD --tentative winner				if AD ~= '' then --for "ACArt with 5 suppressed elements"-type cats					local catlinkNoAD = catlinkfollowr( frame, firstpart..lspace..y..tspace..BC..lastpart, disp ) --try !AD					if catlinkNoAD.catexists == true then						catlink = catlinkNoAD --usurp					elseif listall then						tlistall[#tlistall] = tlistall[#tlistall]..' (tried; not displayed)<sup>1</sup>'					end				end				if (AD..BC == '') and (catlink.catexists == false) and (y >= 1000) then --!ADBC & DNE; 4-digit only, to be frugal					--try basic hyphenated cats: 1-year, endash, CNBS-correct only, no #Rs					local yHyph_4 = y..'–'..(y+1) --try 2010–2011 type cats					local catlinkHyph_4 = catlinkfollowr( frame, firstpart..lspace..yHyph_4..tspace..BC..lastpart, yHyph_4 )					if catlinkHyph_4.catexists and catlinkHyph_4.rtarget == nil then --exists & no #Rs						catlink = catlinkHyph_4 --usurp						trackcat(27, 'Category series navigation năm và khoảng')					else						if listall then							tlistall[#tlistall] = tlistall[#tlistall]..' (tried; not displayed)<sup>2</sup>'						end						local yHyph_2 = y..'–'..string.match(y+1, '%d%d$') --try 2010–11 type cats						if i == 1 then							local yHyph_2_special = (y-1)..'–'..string.match(y, '%d%d$') --try special case 2021 -> 2021–22							local catlinkHyph_2_special = catlinkfollowr( frame, firstpart..lspace..yHyph_2_special..tspace..BC..lastpart, yHyph_2_special )							if catlinkHyph_2_special.catexists and catlinkHyph_2_special.rtarget == nil then --exists & no #Rs								catlink = catlinkHyph_2_special --usurp								trackcat(27, 'Category series navigation năm và khoảng')								j = 1							elseif listall then								tlistall[#tlistall] = tlistall[#tlistall]..' (tried; not displayed)<sup>3</sup>'							end						end						if not (i == 1 and j == 1) then							local catlinkHyph_2 = catlinkfollowr( frame, firstpart..lspace..yHyph_2..tspace..BC..lastpart, yHyph_2 )							if catlinkHyph_2.catexists and catlinkHyph_2.rtarget == nil then --exists & no #Rs								catlink = catlinkHyph_2 --usurp								trackcat(27, 'Category series navigation năm và khoảng')							elseif listall then								tlistall[#tlistall] = tlistall[#tlistall]..' (tried; not displayed)<sup>4</sup>'							end						end					end				end				if catlink.rtarget then --#R followed; determine why					local r = catlink.rtarget					local c = catlink.cat					local year_regex  = '%d%d%d%d[–-]?%d?%d?%d?%d?' --prioritize year/range stripping, e.g. for "2006 Super 14 season"					local hyph_regex  = '%d%d%d%d[–-]%d+' --stricter					local num_regex   = '%d+' --strip any number otherwise					local final_regex = nil   --best choice goes here					if mw.ustring.match(r, year_regex) and mw.ustring.match(c, year_regex) then						final_regex = year_regex					elseif mw.ustring.match(r, num_regex) and mw.ustring.match(c, num_regex) then						final_regex = num_regex					end					if final_regex then						local r_base = mw.ustring.gsub(r, final_regex, '')						local c_base = mw.ustring.gsub(c, final_regex, '')						if r_base ~= c_base then							trackcat(19, 'Category series navigation có năm đã đổi hướng (thay đổi gốc)') --acceptable #R target						elseif mw.ustring.match(r, hyph_regex) then							trackcat(20, 'Category series navigation có năm đã đổi hướng (thay đổi var)') --e.g. "2008 in Scottish women's football" to "2008–09"						else							trackcat(21, 'Category series navigation có năm đã đổi hướng (khác)') --exceptions go here						end					else						trackcat(20, 'Category series navigation có năm đã đổi hướng (thay đổi var)') --e.g. "V2 engines" to "V-twin engines"					end				end				table.insert(navlist, catlink.navelement)			else --OOB vs min/max				local hidden = '<span style="visibility:hidden">'..disp..'</span>'				table.insert(navlist, hidden)				if listall then					local dummy = catlinkfollowr( frame, firsttry, disp )					tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'				end			end		else --center navy			if parentBC then BCdisp = ' '..BCe end			table.insert(navlist, '<b>'..year..BCdisp..'</b>')		end				i = i + 1	end		--add the list	navy = navy..horizontal(navlist)..'\n'		isolatedcat()	if listall then		return listalllinks()	else		return navy..'</div>'	endend--[[==========================={{  nav_roman  }}==============================]]local function nav_roman( frame, firstpart, roman, lastpart, minimumrom, maximumrom )	local toarabic = require('Module:ConvertNumeric').roman_to_numeral	local toroman  = require('Module:Roman').main		--sterilize/convert rom/num	local num = tonumber(toarabic(roman))	local rom = toroman({ [1] = num })	if num == nil or rom == nil then --out of range or some other error		errors = p.errorclass('Hàm nav_roman không thể nhận ra một hoặc nhiều "'..(num or 'nil')..'" & "'..							(rom or 'nil')..'" trong thể loại "'..firstpart..' '..roman..' '..lastpart..'".')		return p.failedcat(errors, 'R')	end		--sterilize min/max	local minrom = tonumber(minimumrom or '') or tonumber(toarabic(minimumrom or ''))	local maxrom = tonumber(maximumrom or '') or tonumber(toarabic(maximumrom or ''))	if minrom < 1 then minrom = 1 end    --toarabic() returns -1 on error	if maxrom < 1 then maxrom = 9999 end --toarabic() returns -1 on error	if minrom > num then minrom = num end	if maxrom < num then maxrom = num end		--begin navroman	local navr = '<div class="toccolours categorySeriesNavigation-range">\n'		local navlist = {}	local i = -5 --nav position	while i <= 5 do		local n = num + i				if n >= 1 then			local r = toroman({ [1] = n })			if i ~= 0 then --left/right navr				local catlink = catlinkfollowr( frame, firstpart..' '..r..' '..lastpart, r )				if minrom <= n and n <= maxrom then					if catlink.rtarget then --a {{Đổi hướng thể loại}} was followed						trackcat(22, 'Category series navigation có số La Mã đã đổi hướng')					end					table.insert(navlist, catlink.navelement)				else					local hidden = '<span style="visibility:hidden">'..r..'</span>'					table.insert(navlist, hidden)					if listall then						tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'					end				end			else --center navr				table.insert(navlist, '<b>'..r..'</b>')			end		else			table.insert(navlist, '<span style="visibility:hidden">I</span>')		end				i = i + 1	end		-- add the list	navr = navr..horizontal(navlist)..'\n'	isolatedcat()	if listall then		return listalllinks()	else		return navr..'</div>'	endend--[[=========================={{  nav_nordinal  }}============================]]local function nav_nordinal( frame, firstpart, ord, lastpart, minimumord, maximumord )	local nord = tonumber(ord)	local minord = tonumber(string.match(minimumord or '', '(-?%d+)')) or -9999 --allow full ord & +/- qualifier	local maxord = tonumber(string.match(maximumord or '', '(-?%d+)')) or  9999 --allow full ord & +/- qualifier	if string.match(minimumord or '', 'TCN') then minord = -math.abs(minord) end --allow BC qualifier (AD otherwise assumed)	if string.match(maximumord or '', 'TCN') then maxord = -math.abs(maxord) end --allow BC qualifier (AD otherwise assumed)		local temporal = string.match(lastpart, 'thế kỷ') or					 string.match(lastpart, 'thiên niên kỷ')		local tspace = ' ' --assume a trailing space after ordinal	if string.match(lastpart, '^-') then tspace = '' end --DNE for "19th-century"-type cats		--AD/BC switches & vars		local ordBCElastparts = { --needed for parent = AD 1-5, when the BC/E format is unknown		--lists the lastpart of valid BCE cats		--"BCE" removed to match both AD & BCE cats; easier & faster than multiple string.match()s		['-century Hebrew people'] = 'TCN', --WP:CFD/Log/2016 June 21#Category:11th-century BC Hebrew people		['-century Jews']          = 'TCN', --co-nominated		['-century Judaism']       = 'TCN', --co-nominated		['-century rabbis']        = 'TCN', --co-nominated		['-century High Priests of Israel'] = 'TCN',	}	local parentBC = mw.ustring.match(lastpart, '%s(TCN?)')       --"1st-century BC" format	local lastpartNoBC = mw.ustring.gsub(lastpart, '%sTCN?', '')  --easier than splitting lastpart up in 2; AD never used	local BCe = parentBC or ordBCElastparts[lastpartNoBC] or 'TCN' --"BC" default		local switchADBC = 1                 --  1=AD parent	if parentBC then switchADBC = -1 end -- -1=BC parent; possibly adjusted later	local O = 0 --secondary iterator for AD-on-a-BC-parent		if not temporal and minord < 1 then minord = 1 end --nothing before "1st parliament", etc.	if minord > nord*switchADBC then minord = nord*switchADBC end --input error; minord should be <= parent	if maxord < nord*switchADBC then maxord = nord*switchADBC end --input error; maxord should be >= parent		--begin navnordinal	local bnb = '' --border/no border	if navborder == false then --for Category series navigation decade and century		bnb = 'categorySeriesNavigation-range-transparent'	end	local navo = '<div class="toccolours categorySeriesNavigation-range '..bnb..'">\n'		local navlist = {}	local i = -5 --nav position	while i <= 5 do		local o = nord + i*switchADBC		local BC = ''		local BCdisp = ''		if parentBC then			if switchADBC == -1 then --parentBC looking at the BC side				if o >= 1 then     --the common case					BC = ' '..BCe				elseif o == 0 then --switch to the AD side					BC = ''					switchADBC = 1				end			end			if switchADBC == 1 then --displayed o is now in the AD regime				O = O + 1 --skip o = 0 (DNE)				o = O     --easiest solution: start another iterator for these AD o's displayed on a BC year parent			end		elseif o <= 0 then --parentAD looking at BC side			BC = ' '..BCe			o = math.abs(o - 1) --skip o = 0 (DNE)		end		if BC ~= '' and nord <= 5 then --only show 'TCN' for parent ords <= 5: saves room, easier to read,			BCdisp = ' '..BCe          --and 6 is the first/last nav ord that doesn't need a disambiguator;		end                            --the center/parent ord will always show BC, so no need to show it another 10x				--populate left/right navo		local oth = p.addord(o)		local osign = o --use o for display & osign for logic		if BC ~= '' then osign = -osign end		local hidden = '<span style="visibility:hidden">'..oth..'</span>'		if temporal then --e.g. "3rd-century BC"			local lastpart = lastpartNoBC --lest we recursively add multiple "BC"s			if BC ~= '' then				lastpart = string.gsub(lastpart, temporal, temporal..BC) --replace BC if needed			end			local catlink = catlinkfollowr( frame, firstpart..' '..oth..tspace..lastpart, oth..BCdisp )			if (minord <= osign) and (osign <= maxord) then				if catlink.rtarget then --a {{Đổi hướng thể loại}} was followed					trackcat(23, 'Category series navigation có số thứ tự đã đổi hướng')				end				table.insert(navlist, navcenter(i, catlink))			else				table.insert(navlist, hidden)				if listall then					tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'				end			end		elseif BC == '' and minord <= osign and osign <= maxord then --e.g. >= "1st parliament"			local catlink = catlinkfollowr( frame, firstpart..' '..oth..tspace..lastpart, oth )			if catlink.rtarget then --a {{Đổi hướng thể loại}} was followed				trackcat(23, 'Category series navigation có số thứ tự đã đổi hướng')			end			table.insert(navlist, navcenter(i, catlink))		else --either out-of-range (hide), or non-temporal + BC = something might be wrong (2nd X parliament BC?); handle exceptions if/as they arise			table.insert(navlist, hidden)		end				i = i + 1	end		navo = navo..horizontal(navlist)..'\n'		isolatedcat()	if listall then		return listalllinks()	else		return navo..'</div>'	endend--[[========================={{  nav_wordinal  }}=============================]]local function nav_wordinal( frame, firstpart, word, lastpart, minimumword, maximumword, ordinal, frame )	--Module:ConvertNumeric.spell_number2() args:	--   ordinal == true : 'second' is output instead of 'two'	--   ordinal == false: 'two' is output instead of 'second'	local ord2viet = require('Module:ConvertNumeric').spell_number2	local viet2ord = require('Module:ConvertNumeric').vietnamese_to_numeral	local th = 'th'	if not ordinal then		th = ''		viet2ord = require('Module:ConvertNumeric').vietnamese_to_numeral	end	local capitalize = nil ~= string.match(word, '^%u') --determine capitalization	local nord = viet2ord(string.lower(word)) --operate on/with lowercase, and restore any capitalization later		local lspace = ' ' --assume a leading space (most common)	local tspace = ' ' --assume a trailing space (most common)	if string.match(firstpart, '[%-%(]$') then lspace = '' end --DNE for "Straight-eight engines"-type cats	if string.match(lastpart, '^[%-%)]' ) then tspace = '' end --DNE for "Nine-cylinder engines"-type cats		--sterilize min/max	local maxword_default = 99	local maxword = maxword_default	local minword = 1	if minimumword then		local num = tonumber(minimumword)		if num and 0 < num and num < maxword then			minword = num		else			local ord = viet2ord(minimumword)			if 0 < ord and ord < maxword then				minword = ord			end		end	end	if maximumword then		local num = tonumber(maximumword)		if num and 0 < num and num < maxword then			maxword = num		else			local ord = viet2ord(maximumword)			if 0 < ord and ord < maxword then				maxword = ord			end		end	end	if minword > nord then minword = nord end	if maxword < nord then maxword = nord end		--determine max existing cat	local listoverride = true	local n_max = nord	local m = 1	while m <= 5 do		local n = nord + m		local nth = p.addord(n)		if not ordinal then nth = n end		local w = ord2viet{ num = n, ordinal = ordinal, capitalize = capitalize }		local catlink = catlinkfollowr( frame, firstpart..lspace..w..tspace..lastpart, nth, nil, listoverride )		if catlink.catexists then n_max = n end		m = m + 1	end		--begin navwordinal	local navw = '<div class="toccolours categorySeriesNavigation-range">\n'		local navlist = {}	local prepad = ''	local i = -5 --nav position	while i <= 5 do		local n = nord + i				if n >= 1 then			local nth = p.addord(n)			if not ordinal then nth = n end			if i ~= 0 then --left/right navw				local w = ord2viet{ num = n, ordinal = ordinal, capitalize = capitalize }				local catlink = catlinkfollowr( frame, firstpart..lspace..w..tspace..lastpart, nth )				if minword <= n and n <= maxword then					if catlink.rtarget then --a {{Đổi hướng thể loại}} was followed						trackcat(24, 'Category series navigation có từ thứ tự đã đổi hướng')					end					if n <= n_max or					   maxword ~= maxword_default					then						table.insert(navlist, prepad..catlink.navelement) --display normally						prepad = ''					else						local postpad = '<span style="visibility:hidden"> • '..nth..'</span>'						navlist[#navlist] = (navlist[#navlist] or '')..postpad						if listall then tlistall[#tlistall] = tlistall[#tlistall]..' ('..postpad..')' end					end				else					local postpad = '<span style="visibility:hidden"> • '..nth..'</span>'					navlist[#navlist] = (navlist[#navlist] or '')..postpad					if listall then tlistall[#tlistall] = tlistall[#tlistall]..' ('..postpad..')' end				end			else --center navw				table.insert(navlist, prepad..'<b>'..nth..'</b>')				prepad = ''			end		else --n < 1			prepad = prepad..'<span style="visibility:hidden"> • '..'0'..th..'</span>'			if listall then tlistall[#tlistall] = (tlistall[#tlistall] or '')..' (x)' end		end				i = i + 1	end	-- Add the list	navw = navw..horizontal(navlist)..'\n'		isolatedcat()	if listall then		return listalllinks()	else		return navw..'</div>'	endend--[[==========================={{  find_var  }}===============================]]local function find_var( pn )	--Extracts the variable text (e.g. 2015, 2015–16, 2000s, 3rd, III, etc.) from a string,	--and returns { ['vtype'] = <'year'|'season'|etc.>, <v> = <2015|2015–16|etc.> }	local pagename = currtitle.text	if pn and pn ~= '' then		pagename = pn	end		local cpagename = 'Thể loại:'..pagename --limited-Lua-regex workaround		local d_season = mw.ustring.match(cpagename, ':(%d+s).+%(%d+[–-]%d+%)') --i.e. "1760s in the Province of Quebec (1763–1791)"		local y_season = mw.ustring.match(cpagename, ':(%d+) .+%(%d+[–-]%d+%)') --i.e. "1763 establishments in the Province of Quebec (1763–1791)"		local e_season = mw.ustring.match(cpagename, '%s(%d+[–-])$') or --irreg; ending unknown, e.g. "Members of the Scottish Parliament 2021–"					 mw.ustring.match(cpagename, '%s(%d+[–-]nay)$') --e.g. "UK MPs 2019–present"		local season   = mw.ustring.match(cpagename, '[:%s%(](%d+[–-]%d+)[%)%s]') or --split in 2 b/c you can't frontier '$'/eos?					 mw.ustring.match(cpagename, '[:%s](%d+[–-]%d+)$')		local tvseason = mw.ustring.match(cpagename, 'mùa (%d+)') or					 mw.ustring.match(cpagename, 'series (%d+)')		local nordinal = mw.ustring.match(cpagename, '[:%s](%d+)[-%s]') or					 mw.ustring.match(cpagename, '[:%s](%d+)$')		local decade   = mw.ustring.match(cpagename, '[:%s][Tt]hập niên (%d+)[%s-]') or					 mw.ustring.match(cpagename, '[:%s][Tt]hập niên (%d+)$')		local year     = mw.ustring.match(cpagename, '[:%s][Nn]ăm (%d%d%d%d)%s') or --prioritize 4-digit years					 mw.ustring.match(cpagename, '[:%s][Nn]ăm (%d%d%d%d)$') or					 mw.ustring.match(cpagename, '[:%s][Nn]ăm (%d+)%s') or					 mw.ustring.match(cpagename, '[:%s][Nn]ăm (%d+)$') or					 --expand/combine exceptions below as needed					--  mw.ustring.match(cpagename, '[:%s](%d+)-related') or					--  mw.ustring.match(cpagename, '[:%s](%d+)-cylinder') or					 mw.ustring.match(cpagename, '[:%-VW](%d+)%s') --e.g. "Straight-8 engines"		local roman    = mw.ustring.match(cpagename, '%s([IVXLCDM]+)%s')		local found    = d_season or y_season or e_season or season or tvseason or					 nordinal or decade or year or roman		if found then		if string.match(found, '%d%d%d%d%d') == nil then			--return in order of decreasing complexity/chance for duplication			if nordinal and season --i.e. "18th-century establishments in the Province of Quebec (1763–1791)"						then return { ['vtype'] = 'nordinal', ['v'] = nordinal } end			if d_season then return { ['vtype'] = 'decade',   ['v'] = d_season } end			if y_season then return { ['vtype'] = 'year',     ['v'] = y_season } end			if e_season then return { ['vtype'] = 'ending',   ['v'] = e_season } end			if season   then return { ['vtype'] = 'season',   ['v'] = season   } end			if tvseason then return { ['vtype'] = 'tvseason', ['v'] = tvseason } end			--giữ nguyên thứ tự decade, year, nordinal			if decade   then return { ['vtype'] = 'decade',   ['v'] = decade   } end			if year     then return { ['vtype'] = 'year',     ['v'] = year     } end			if nordinal then return { ['vtype'] = 'nordinal', ['v'] = nordinal } end			if roman    then return { ['vtype'] = 'roman',    ['v'] = roman    } end		end	else		--try wordinals ('zeroth' to 'ninety-ninth' only)		--TODO: sửa hàm vietnamese_to_numeral và viết thêm hàm vietnamese_to_ordinal		local viet2ord = require('Module:ConvertNumeric').vietnamese_to_numeral		local split = mw.text.split(pagename, ' ')		for i=1, #split do			local compound			--'hai mươi mốt đến chín mươi chín'			if i < #split - 1 then				compound = split[i] .. ' ' .. split[i+1] .. ' ' .. split[i+2]				if viet2ord(compound:lower()) > -1 then					return { ['vtype'] = 'wordinal', ['v'] = compound }				end			end			--'mười một' đến 'mười chín'			if i < #split then				compound = split[i] .. ' ' .. split[i+1]				if viet2ord(compound) > -1 then					return { ['vtype'] = 'wordinal', ['v'] = compound }				end		    end			if viet2ord(split[i]) > -1 then				return { ['vtype'] = 'wordinal', ['v'] = split[i] }			end		end				--try Vietnamese numerics ('one'/'single' to 'ninety-nine' only)		local viet2num = require('Module:ConvertNumeric').vietnamese_to_numeral		local split = mw.text.split(pagename, '[%s%-]') --e.g. "Nine-cylinder engines"		for i=1, #split do			if viet2num(split[i]) > -1 then				return { ['vtype'] = 'enumeric', ['v'] = split[i] }			end		end	end		errors = p.errorclass('Hàm find_var không thể tìm văn bản biến trong thể loại "'..pagename..'".')	return { ['vtype'] = 'error', ['v'] = p.failedcat(errors, 'V') }end--[[==========================================================================]]--[[                                  Main                                    ]]--[[==========================================================================]]function p.csn( frame )	--arg checks & handling	local args = frame:getParent().args	checkforunknownparams(args)       --for template args	checkforunknownparams(frame.args) --for #invoke'd args	local cat  = args['cat']                --'testcase' alias for catspace	local list = args['list-all-links']     --debugging utility to output all links & followed #Rs	local follow = args['follow-redirects'] --default 'yes'	local testcase    = args['testcase']	local testcasegap = args['testcasegap']	local minimum = args['min']	local maximum = args['max']	local skip_gaps = args['skip-gaps']	local show = args['show']		if show and show ~= '' then		if show == 'skip-gaps'  then return skipgaps_limit		elseif show == 'term-limit' then return term_limit		elseif show == 'hgap-limit' then return hgap_limit		elseif show == 'ygap-limit' then return ygap_limit end	end		--apply args	local pagename = testcase or cat or currtitle.text	local testcaseindent = ''	if testcasecolon == ':' then testcaseindent = '\n::' end	if follow and follow == 'no' then followRs = false end	if list and list == 'yes' then listall = true end	if skip_gaps and skip_gaps == 'yes' then		skipgaps = true		trackcat(26, 'Category series navigation sử dụng tham số skip-gaps')	end		--ns checks	if currtitle.nsText == 'Thể_loại' then		if cat and cat ~= '' then			trackcat(1, 'Category series navigation sử dụng tham số cat')		end		if testcase and testcase ~= '' then			trackcat(2, 'Category series navigation sử dụng tham số testcase')		end	elseif currtitle.nsText == '' then		trackcat(30, 'Category series navigation trong không gian tên chính')	end		--find the variable parts of pagename	local findvar = find_var(pagename)	if skipgaps then		findvar.vtype = 'year' -- skip-gaps chỉ được dùng cho đơn vị là năm	end	mw.log("findvar.vtype: " .. findvar.vtype)	if findvar.vtype == 'error' then --basic format error checking in find_var()		return findvar.v..table.concat(ttrackingcats)	end	local start = string.match(findvar.v, '^%d+')		--the rest is static	local findvar_escaped = string.gsub( findvar.v, '%-', '%%%-')	local firstpart, lastpart = string.match(pagename, '^(.-)'..findvar_escaped..'(.*)$')	if findvar.vtype == 'tvseason' then --double check for cases like "30 Rock (season 3) episodes"		firstpart, lastpart = string.match(pagename, '^(.-season )'..findvar_escaped..'(.*)$')		if firstpart == nil then			firstpart, lastpart = string.match(pagename, '^(.-series )'..findvar_escaped..'(.*)$')		end	end	firstpart = mw.text.trim(firstpart or '')	lastpart  = mw.text.trim(lastpart or '')	--call the appropriate nav function, in order of decreasing popularity	if findvar.vtype == 'year' then     --e.g. "500", "2001"; nav_year..nav_decade; ~75% of cats		local nav1 = nav_year( frame, firstpart, start, lastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)				local dec = math.floor(findvar.v/10)		local decadecat = nil		local firstpart_dec = firstpart:gsub('[Nn]ăm', 'thập niên')		if firstpart_dec == 'SCN' and dec <= 1 then			firstpart_dec = ''			if dec == 0 then dec = '' end		end		local decade = dec..'0 '		decadecat = mw.text.trim( firstpart_dec..' '..decade..lastpart )		local exists = catexists(decadecat)		if exists then			navborder = false			trackcat(28, 'Category series navigation năm và thập niên')			local nav2 = nav_decade( frame, firstpart_dec, decade, lastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)			return wrap( nav1, nav2 )		elseif ttrackingcats[16] ~= '' then --nav_year isolated; check nav_hyphen (e.g. UK MPs 1974, Moldovan MPs 2009, etc.)			local hyphen = '–'			local finish = start			local nav2 = nav_hyphen( frame, start, hyphen, finish, firstpart, lastpart, minimum, maximum, testcasegap )..testcaseindent..table.concat(ttrackingcats)			if ttrackingcats[16] ~= '' then return wrap( nav1 ) --still isolated; rv to nav_year			else return wrap( nav2 ) end		else --regular nav_year			return wrap( nav1 )		end			elseif findvar.vtype == 'decade' then   --e.g. "0s", "2010s"; nav_decade..nav_nordinal; ~12% of cats		local nav1 = nav_decade( frame, firstpart, start, lastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)				local decade = tonumber(string.match(findvar.v, '^(%d+)'))		local century = math.floor( ((decade-1)/100) + 1 ) --from {{CENTURY}}		if century == 0 then century = 1 end --no 0th century		if string.match(decade, '00$') then			century = century + 1 --'2000' is in the 20th, but the rest of the 2000s is in the 21st		end		firstpart = firstpart:gsub('([Tt])hập niên', '%1hế kỷ')		local clastpart = lastpart		local centurycat = mw.text.trim( firstpart..' '..p.addord(century)..' '..clastpart )		local exists = catexists(centurycat)		if exists then			navborder = false			trackcat(29, 'Category series navigation thập niên và thế kỷ')			local nav2 = nav_nordinal( frame, firstpart, century, clastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)			return wrap( nav1, nav2 )		else			return wrap( nav1 )		end			elseif findvar.vtype == 'nordinal' then --e.g. "1st", "99th"; ~7.5% of cats		return wrap( nav_nordinal( frame, firstpart, start, lastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats) )			elseif findvar.vtype == 'season' then   --e.g. "1–4", "1999–2000", "2001–02", "2001–2002", "2005–2010", etc.; ~5.25%		local hyphen, finish = mw.ustring.match(findvar.v, '%d([–-])(%d+)') --ascii 150 & 45 (ndash & keyboard hyphen); mw req'd		return wrap( nav_hyphen( frame, start, hyphen, finish, firstpart, lastpart, minimum, maximum, testcasegap )..testcaseindent..table.concat(ttrackingcats) )			elseif findvar.vtype == 'tvseason' then --e.g. "1", "15" but preceded with "season" or "series"; <1% of cats		return wrap( nav_tvseason( frame, firstpart, start, lastpart, maximum )..testcaseindent..table.concat(ttrackingcats) ) --"minimum" defaults to 1			elseif findvar.vtype == 'wordinal' then --e.g. "first", "ninety-ninth"; <<1% of cats		local ordinal = true		return wrap( nav_wordinal( frame, firstpart, findvar.v, lastpart, minimum, maximum, ordinal, frame )..testcaseindent..table.concat(ttrackingcats) )			elseif findvar.vtype == 'enumeric' then --e.g. "one", "ninety-nine"; <<1% of cats		local ordinal = false		return wrap( nav_wordinal( frame, firstpart, findvar.v, lastpart, minimum, maximum, ordinal, frame )..testcaseindent..table.concat(ttrackingcats) )			elseif findvar.vtype == 'roman' then    --e.g. "I", "XXVIII"; <<1% of cats		return wrap( nav_roman( frame, firstpart, findvar.v, lastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats) )			elseif findvar.vtype == 'ending' then   --e.g. "2021–" (irregular; ending unknown); <<<1% of cats		local hyphen, finish = mw.ustring.match(findvar.v, '%d([–-])nay$'), -1 --ascii 150 & 45 (ndash & keyboard hyphen); mw req'd		if hyphen == nil then			hyphen, finish = mw.ustring.match(findvar.v, '%d([–-])$'), 0 --0/-1 are hardcoded switches for nav_hyphen()		end		return wrap( nav_hyphen( frame, start, hyphen, finish, firstpart, lastpart, minimum, maximum, testcasegap )..testcaseindent..table.concat(ttrackingcats) )			else                                 --malformed		errors = p.errorclass('Không thể xác định hàm nav phù hợp từ khoảng thời gian không đúng định dạng "'..findvar.v..'". ')		return p.failedcat(errors, 'N')..table.concat(ttrackingcats)	endendreturn p