Module:Navseasoncats
This Lua module is used on approximately 242,000 pages. To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them. Transclusion count updated automatically (see documentation). |
This module depends on the following other modules: |
Related pages |
---|
About
Type | Example category | BC(E)? | Example output | |
---|---|---|---|---|
Season | 2001–02 FA Cup | No |
| |
TV season | Futurama (season 1) episodes | – |
| |
Office term | MEPs 2004–2009 | No | Lua error in mw.title.lua at line 209: too many expensive function calls. | |
Numerical range | Taxonbars with 30–34 taxon IDs | – | Lua error in mw.title.lua at line 209: too many expensive function calls. | |
Decade | 2020s awards | BC | Lua error in mw.title.lua at line 209: too many expensive function calls. | |
Year | 1999 in Scotland | BC(E) | Lua error in mw.title.lua at line 209: too many expensive function calls. | |
Year (auto-condensed) | Candidates in the 2000 US presidential election | – | Lua error in mw.title.lua at line 209: too many expensive function calls. | |
Year (|skip-gaps=yes) | Nations at the 1980 World Championships in Athletics | – | Lua error in mw.title.lua at line 209: too many expensive function calls. | |
Ordinal (temporal) | 2nd-century rabbis | BC(E) | Lua error in mw.title.lua at line 209: too many expensive function calls. | |
Ordinal (numeric) | 1st Lok Sabha members | – | Lua error in mw.title.lua at line 209: too many expensive function calls. | |
Ordinal (word) | First Dynasty of Egypt | – | Lua error in mw.title.lua at line 209: too many expensive function calls. | |
Roman numeral | Deputies of Legislature X of the Kingdom of Italy | – | Lua error in mw.title.lua at line 209: too many expensive function calls. | |
Mixed decade | 1760s in the Province of Quebec (1763–1791) | – | Lua error in mw.title.lua at line 209: too many expensive function calls. | |
Mixed year | 1778 establishments in the Province of Quebec (1763–1791) | – | Lua error in mw.title.lua at line 209: too many expensive function calls. |
Searching behavior
Multi-year seasons/office terms/numerical ranges are acceptable as long as the duration/range size remains constant, and no years/numbers are irregularly skipped. The length of each duration/range is automatically determined from the originating category name, up to and including 10 years. MOS:DATERANGE compliance is preferred, but some deviation is allowed and tracked. {{Category redirect}}s are followed, and tracked for either MOS contravention (to be corrected) or for navigational aid (no error). The gap size between successive durations/ranges is also automatically determined, up to and including 5 years if a surrounding category is found, and defaults to 0 (e.g. 1995–96 → 1996–97).
Automatically condensed year display is supported for presidential
categories only (but can be easily expanded as needed), for gaps up to and including 5 years, and defaults to 1. To skip gaps up to 10–15 years (position-dependent) in any year categories, use |skip-gaps=yes
.
Limitations
- Season/office term categories do not work for any years BC, which will be hidden, because no working examples were found.
- Decade categories recognize BC, but not BCE, because no working examples were found.
- Automatically condensed year display is supported for
presidential
categories only, due to their consistency. Use|skip-gaps=yes
as desired elsewhere. - Automatically condensed Olympics display is not supported due to peculiarities; use {{Winter Olympics by year category navigation}}, etc., instead.
- Ordinal words do not work above the ninety-ninth, because no higher working examples were found.
- General: for large, permanent gaps between successive categories, or when the base category name changes, use {{Category pair}} in addition to {{Navseasoncats}} on both sides of the gap/name change. Even if {{Navseasoncats}} is isolated, it has the benefit of confirming the absence of nearby categories to the reader or maintainer.
Related CfDs
- Wikipedia:Categories for discussion/Log/2019 June 8#Category:Northern Ireland MLAs 2016–17
- Wikipedia:Categories for discussion/Log/2019 May 29#Category:MEPs 1952–58
- Wikipedia:Categories for discussion/Log/2019 April 19#Category:Aircraft piston engines 1900–1909
Usage
- Typical usage
{{Navseasoncats}}
- Specify a minimum and/or maximum year to display
{{Navseasoncats|min=-100}}
{{Navseasoncats|min=100 BC}}
{{Navseasoncats|min=1753|max=1810}}
{{Navseasoncats|max=2025}}
- To skip gaps in year categories
{{Navseasoncats|skip-gaps=yes}}
- To not automatically follow {{Category redirect}}s
{{Navseasoncats|follow-redirects=no}}
- Exceptional cases
{{Navseasoncats|cat=2010s albums}}
— to behave as if placed on|cat=
; consider using {{Category pair}} instead of|cat=
Testing & debugging
To test the output of the template on a particular category name, use the |testcase=
parameter, and |testcasegap=
if necessary:
{{Navseasoncats|testcase=Birds described in 1761 |min=1758}}
Lua error in mw.title.lua at line 209: too many expensive function calls.
{{Navseasoncats|testcase=2024 elections |max=2026}}
Lua error in mw.title.lua at line 209: too many expensive function calls.
{{Navseasoncats|testcase=Nations at the 2013 World Athletics Championships |min=2008 |skip-gaps=yes |list-all-links=yes}}
Lua error in mw.title.lua at line 209: too many expensive function calls.
- Which is technically an inappropriate category for {{Navseasoncats}} placement, but it best shows the
|list-all-links=yes
behavior for all element variants (blue, red/grey, hidden, and redirect), and would otherwise display as:
- Which is technically an inappropriate category for {{Navseasoncats}} placement, but it best shows the
Lua error in mw.title.lua at line 209: too many expensive function calls.
Tracking categories
If the template encounters an issue, it displays an error message and/or places the category into one or more of the following tracking categories:
Maintenance required
- Category:Navseasoncats failed to generate navbox (0)
- Category:Navseasoncats range abbreviated (0)
- Category:Navseasoncats range redirected (MOS) (0)
- Category:Navseasoncats range not using en dash (0)
Maintenance possible
- Category:Navseasoncats default season gap size (0)
- Category:Navseasoncats using cat parameter (0)
- Category:Navseasoncats using testcase parameter (0)
- Category:Navseasoncats isolated (0)
Tracking only
- Category:Navseasoncats range redirected (base change) (0)
- Category:Navseasoncats TV season redirected (0)
- Category:Navseasoncats decade redirected (0)
- Category:Navseasoncats year redirected (0)
- Category:Navseasoncats roman numeral redirected (0)
- Category:Navseasoncats nordinal redirected (0)
- Category:Navseasoncats wordinal redirected (0)
- Category:Navseasoncats using skip-gaps parameter (0)
See also
- {{Navseasoncats with centuries below decade}}
- {{Navseasoncats with decades below year}}
- {{Year by category}}
- {{R from category navigation}}
local p = {} local errors = '' local nexistingcats = 0 local currtitle = mw.title.getCurrentTitle() local testcasecolon = '' local testcases = string.match(currtitle.subpageText, '^testcases') if testcases then testcasecolon = ':' end local navborder = true local followRs = true local listall = false local listofalllinks = {} local skipgaps = false local misctrackingcats = { '', -- [1] placeholder for [[Category:Navseasoncats using cat parameter]] '', -- [2] placeholder for [[Category:Navseasoncats using testcase parameter]] '', -- [3] placeholder for [[Category:Navseasoncats range not using en dash]] '', -- [4] placeholder for [[Category:Navseasoncats range abbreviated]] '', -- [5] placeholder for [[Category:Navseasoncats range redirected (base change)]] '', -- [6] placeholder for [[Category:Navseasoncats range redirected (MOS)]] '', -- [7] placeholder for [[Category:Navseasoncats range irregular, no gaps]] '', -- [8] placeholder for [[Category:Navseasoncats range redirected (end)]] '', -- [9] placeholder for [[Category:Navseasoncats range unredirected (end)]] '', --[10] placeholder for [[Category:Navseasoncats range redirected (other)]] '', --[11] placeholder for [[Category:Navseasoncats isolated]] '', --[12] placeholder for [[Category:Navseasoncats default season gap size]] '', --[13] placeholder for [[Category:Navseasoncats decade redirected]] '', --[14] placeholder for [[Category:Navseasoncats year redirected]] '', --[15] placeholder for [[Category:Navseasoncats roman numeral redirected]] '', --[16] placeholder for [[Category:Navseasoncats nordinal redirected]] '', --[17] placeholder for [[Category:Navseasoncats wordinal redirected]] '', --[18] placeholder for [[Category:Navseasoncats TV season redirected]] '', --[19] placeholder for [[Category:Navseasoncats using skip-gaps parameter]] } local avoidself = (currtitle.text ~= 'Navseasoncats' and --avoid self currtitle.text ~= 'Navseasoncats/doc' and --avoid self currtitle.text ~= 'Navseasoncats/sandbox' and --avoid self (currtitle.nsText ~= 'Template' or testcases)) --avoid nested transclusion errors (i.e. {{Infilmdecade}}) --[[==========================================================================]] --[[ Utility & category functions ]] --[[==========================================================================]] --Error message handling --Also used by {{Navseasoncats with centuries below decade}}. function p.errorclass( msg ) return mw.text.tag( 'span', {class='error mw-ext-cite-error'}, '<b>Error!</b> '..string.gsub(msg, '&#', '&#') ) end --Failure handling --Also used by {{Navseasoncats with centuries below decade}}. function p.failedcat( errors, sortkey ) if avoidself then return (errors or '')..'***Navseasoncats failed to generate navbox***'.. '[['..testcasecolon..'Category:Navseasoncats failed to generate navbox|'..(sortkey or 'O')..']]' end return '' end --Check for nav_*() navigational isolation (not necessarily an error). --Used by all nav_*(). function isolatedcat() if nexistingcats == 0 and avoidself then misctrackingcats[11] = '[['..testcasecolon..'Category:Navseasoncats isolated]]' end end --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 {{Category redirect}}s. --Returns { <#R target navelement>, <basetext of #R target> } if {{Category redirect}} followed; --returns { <original navelement>, nil } otherwise. --Used by all nav_*(). function catlinkfollowr( catname, displaytext, displayend ) catname = mw.text.trim(catname 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–") local grey = '#888' local disp = catname if displaytext ~= '' then --use 'displaytext' parameter if present disp = mw.ustring.gsub(displaytext, '%s+%(.+$', ''); --strip any trailing disambiguator end local link, nilorR local exists = mw.title.new( catname, 'Category' ).exists if exists then nexistingcats = nexistingcats + 1 if followRs then local R = rtarget(catname) local y, hyph = mw.ustring.match(R, '(%d+)([–-])$') if displayend and y and hyph then disp = y..hyph..'<span style="visibility:hidden">'..y..'</span>' --hidden y to match spacing end if R ~= catname then --#R followed nilorR = R end link = '[[:Category:'..R..'|'..disp..']]' else link = '[[:Category:'..catname..'|'..disp..']]' end else link = '<span style="color:'..grey..'">'..disp..'</span>' --return { '[[:Category:'..catname..'|'..disp..']]', nil } --for debugging end if listall then if nilorR then --#R followed table.insert( listofalllinks, '[[:Category:'..catname..']] → '..'[[:Category:'..nilorR..']] ('..link..')' ) else --no #R table.insert( listofalllinks, '[[:Category:'..catname..']] ('..link..')' ) end end return { link, nilorR } end --Returns the target of {{Category redirect}}, if it exists, else returns the original cat. --Used by catlinkfollowr(), and so indirectly by all nav_*(). function rtarget( cat ) local catcontent = mw.title.new( cat or '', 'Category' ):getContent() if string.match( catcontent or '', '{{ *[Cc]at' ) then local regex = { --the following 11 pages (7 condensed) redirect to [[Template:Category redirect]] (as of 6/2019): { '1', '{{ *[Cc]ategory *[Rr]edirect' }, --most likely match 1st { '2', '{{ *[Cc]at *redirect' }, --444+240 transclusions { '3', '{{ *[Cc]at *redir' }, --8+3 { '4', '{{ *[Cc]ategory *move' }, --6 { '5', '{{ *[Cc]at *red' }, --6 { '6', '{{ *[Cc]atr' }, --4 { '7', '{{ *[Cc]at *move' }, --0 } for k, v in pairs (regex) do local rtarget = mw.ustring.match( catcontent, v[2]..'%s*|%s*([^|}]+)' ) if rtarget then rtarget = mw.ustring.gsub(rtarget, '^1%s*=%s*', '') rtarget = string.gsub(rtarget, '^[Cc]ategory:', '') return rtarget end end end return cat end --Returns a numbered list of all {{Category redirect}}s followed by catlinkfollowr() -> rtarget(). --Used by all nav_*(). function listalllinks() local nl = '\n# ' local out = '' if currtitle.nsText == 'Category' then errors = p.errorclass('The <b><code>|list-all-links=yes</code></b> parameter/utility '.. 'should not be saved in category space, only previewed.') out = p.failedcat(errors, 'Z') end if listofalllinks[1] then return out..nl..table.concat(listofalllinks, nl) else return out..nl..'No links found!?' end end --Returns an unsigned string of the 1-4 digit decade ending in "0", else error. --Used by nav_decade() only. 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 errors = 'sterilizedec() error' return nil end end --Check for nav_hyphen default gap size + isolatedcat() (not necessarily an error). --Used by nav_hyphen() only. function defaultgapcat( bool ) if bool and nexistingcats == 0 and avoidself then --using "nexistingcats > 0" isn't as useful, since the default gap size obviously worked misctrackingcats[12] = '[['..testcasecolon..'Category:Navseasoncats default season gap size]]' end end --12 -> 12th, etc. --Used by nav_nordinal(), nav_wordinal(), and {{Navseasoncats with centuries below decade}}. function p.addord( i ) if tonumber(i) then local s = tostring(i) local tens = string.match(s, '1%d$') if tens then return s..'th' end local ones = string.match(s, '%d$') if ones == '1' then return s..'st' elseif ones == '2' then return s..'nd' elseif ones == '3' then return s..'rd' end return s..'th' end return i end --[[==========================================================================]] --[[ Formerly separated templates/modules ]] --[[==========================================================================]] --[[==========================={{ nav_hyphen }}=============================]] function nav_hyphen( 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) -- maxseas = 2000 ('max' starting season shown; optional; 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('Function nav_hyphen can\'t recognize the number "'..(start or '')..'" '.. 'in the first part of the "season" that was passed to it. '.. 'For e.g. "2015–16", "2015" is expected via "|2015|–|16|".') return p.failedcat(errors, 'H') end end local nstart = tonumber(start) --en dash check if hyph ~= '–' then misctrackingcats[3] = '[['..testcasecolon..'Category:Navseasoncats range not using en dash]]' --nav still processable, but track end --sterilize finish local regularparent = true if finish == 0 then regularparent = false --"Members of the Scottish Parliament 2021–" if maxseas == nil or maxseas == '' then maxseas = start --hide subsequent ranges end end if 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('Function nav_hyphen can\'t recognize "'..(finish or '')..'" '.. 'in the second part of the "season" that was passed to it. '.. 'For e.g. "2015–16", "16" is expected via "|2015|–|16|".') return p.failedcat(errors, 'I') end else if string.len(finish) >= 5 then errors = p.errorclass('The second part of the season passed to function nav_hyphen should only be four or fewer digits, not "'..(finish or '')..'". '.. 'See [[MOS:DATERANGE]] for details.') return p.failedcat(errors, 'J') end end local nfinish = tonumber(finish) --sterilize min/max local nminseas = tonumber(minseas) or -9999 --same behavior as nav_year local nmaxseas = tonumber(maxseas) or 9999 --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 <= 10 and regularparent do local nish = nstart + t --use switchADBC to flip this sign to work for years BC if nish == nfinish or string.match(nish, '%d?%d$') == finish then break end if t == 10 then errors = p.errorclass('Function nav_hyphen can\'t determine a reasonable term length for "'..start..hyph..finish..'".') return p.failedcat(errors, 'K') end t = t + 1 end --apply MOS:DATERANGE to parent local lenstart = string.len(start) local lenfinish = string.len(finish) if lenstart == 4 and regularparent then --"2001–..." if t == 1 then --"2001–02" & "2001–2002" both allowed if lenfinish ~= 2 and lenfinish ~= 4 then errors = p.errorclass('The second part of the season passed to function nav_hyphen should be two or four digits, not "'..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 if avoidself then misctrackingcats[4] = '[['..testcasecolon..'Category:Navseasoncats range abbreviated]]' end elseif lenfinish ~= 4 then errors = p.errorclass('The second part of the season passed to function nav_hyphen should be four digits, not "'..finish..'".') return p.failedcat(errors, 'M') end end if finish == '00' and avoidself then --full year required regardless of term length misctrackingcats[4] = '[['..testcasecolon..'Category:Navseasoncats range abbreviated]]' 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 = 5 local hgap_success = false local hgap = hgap_default while hgap <= hgap_limit and regularparent 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 if mw.title.new(prevseason2, 'Category').exists or --use 'or', in case we're at the edge of the cat structure, mw.title.new(nextseason2, 'Category').exists or --or we hit a "–00"/"–2000" situation on one side mw.title.new(prevseason4, 'Category').exists or mw.title.new(nextseason4, 'Category').exists then hgap_success = true break end elseif t > 1 then --test full range first, then abbreviated range if mw.title.new(prevseason4, 'Category').exists or --use 'or', in case we're at the edge of the cat structure, mw.title.new(nextseason4, 'Category').exists or --or we hit a "–00"/"–2000" situation on one side mw.title.new(prevseason2, 'Category').exists or mw.title.new(nextseason2, 'Category').exists 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 hgap=0 cats --to limit expensive function usage, MOS:DATERANGE alternation is not used --an irregular-term-length series should follow "YYYY..hyph..YYYY" throughout local iregs = 0 local iirregs = 0 local tirregs = {} if hgap <= hgap_limit then --keep this now-unnecessary if-block to isolate temp vars --find # of nav-visible regular-term-length cats local i = -3 while i <= 3 do local from = nstart + i*(t+hgap) local to = tostring(from+t) local full = firstpart..lspace..from..hyph..to..tspace..lastpart if i ~= 0 and mw.title.new( full, 'Category' ).exists then iregs = iregs + 1 end i = i + 1 end --find # of nav-visible irregular-term-length cats local bwanchor = nstart --backwards anchor/common year local fwanchor = bwanchor + t --forwards anchor/common year local endfound = false local j = -3 while j <= 3 do if j < 0 then --search backwards local k = 1 while k <= 10 do local from = bwanchor - k local to = bwanchor local full = firstpart..lspace..from..hyph..to..tspace..lastpart if mw.title.new( full, 'Category' ).exists then iirregs = iirregs + 1 tirregs['from-'..iirregs] = from tirregs['to-'..iirregs] = to bwanchor = from --ratchet down break end k = k + 1 end end if j > 0 and endfound == false then --search forwards local k = 0 while k <= 10 do local from = fwanchor local to = fwanchor + k if k == 0 then to = '' end --see if end-cat exists local full = firstpart..lspace..from..hyph..to..tspace..lastpart if mw.title.new( full, 'Category' ).exists then tirregs['from'..j] = from tirregs['to'..j] = to iirregs = iirregs + 1 if k == 0 then endfound = true --tentative else endfound = false fwanchor = to --ratchet up break --only break on k > 0; old end-cat #Rs still exist like "Members of the Scottish Parliament 2011–" end end k = k + 1 end end j = j + 1 end --rm irreg table if not useful if iregs >= iirregs then tirregs = {} elseif avoidself then misctrackingcats[7] = '[['..testcasecolon..'Category:Navseasoncats range irregular, no gaps]]' end end --begin navhyphen local navh = '{| class="toccolours hlist" style="text-align: center; margin: auto;"\n'..'|\n' local terminalcat = false local i = -3 while i <= 3 do local from = nstart + i*(t+hgap) if tirregs['from'..i] then from = tonumber(tirregs['from'..i]) end local from2 = string.match(from, '%d?%d$') local to = tostring(from+t) if tirregs['to'..i] then 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 --assume t=1 and abbreviated 'to' (the most common case) if t > 1 or --per MOS:DATERANGE (e.g. 1999-2004) (from2 - to2) > 0 --century transition exception (e.g. 1999–2000) then tofinal = to --default to the MOS-correct format, in case no fallbacks found end --check existance of 4-digit, MOS-correct range, with abbreviation fallback 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 mw.title.new( full, 'Category' ).exists then local abbr = firstpart..lspace..from..hyph..to2..tspace..lastpart if mw.title.new( abbr, 'Category' ).exists then tofinal = to2 --rv to MOS-incorrect format; if full AND abbr DNE, then tofinal is still in its MOS-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 mw.title.new( abbr, 'Category' ).exists and tofinal ~= to then local full = firstpart..lspace..from..hyph..to..tspace..lastpart if mw.title.new( full, 'Category' ).exists then tofinal = to --if abbr AND full DNE, then tofinal is still in its abbr format (unless it's a century transition) 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 local catlink = catlinkfollowr(orig, disp, true) --force terminal cat display if terminalcat == false then terminalcat = (mw.ustring.match(disp, '%d+[–-]$') or false) end if catlink[2] and avoidself then --a {{Category redirect}} was followed, figure out why local origbase = mw.ustring.gsub(orig, '%d+[–-]%d+', '') local rtarbase = mw.ustring.gsub(catlink[2], '%d+[–-]%d+', '') if mw.ustring.match(orig, '%d+[–-]$') then origbase = mw.ustring.gsub(orig, '%d+[–-]$', '') end if mw.ustring.match(catlink[2], '%d+[–-]$') then --finagle/overload terminalcat type to set tracking on 1st occurence only if terminalcat == false then terminalcat = 1 end rtarbase = mw.ustring.gsub(catlink[2], '%d+[–-]$', '') end origbase = mw.text.trim(origbase) rtarbase = mw.text.trim(rtarbase) if avoidself then if origbase ~= rtarbase then misctrackingcats[5] = '[['..testcasecolon..'Category:Navseasoncats range redirected (base change)]]' elseif terminalcat == 1 then misctrackingcats[8] = '[['..testcasecolon..'Category:Navseasoncats range redirected (end)]]' else local all4s = (mw.ustring.match(orig, '%d%d%d%d[–-]%d%d%d%d') and mw.ustring.match(catlink[2], '%d%d%d%d[–-]%d%d%d%d')) if all4s then misctrackingcats[10] = '[['..testcasecolon..'Category:Navseasoncats range redirected (other)]]' else misctrackingcats[6] = '[['..testcasecolon..'Category:Navseasoncats range redirected (MOS)]]' end end end end if terminalcat then if type(terminalcat) ~= 'boolean' then nmaxseas = from end --only want to do this once if type(terminalcat) == 'string' and catlink[2] == nil and avoidself then misctrackingcats[9] = '[['..testcasecolon..'Category:Navseasoncats range unredirected (end)]]' end terminalcat = true --done finagling/overloading end if from >= 0 and nminseas <= from and from <= nmaxseas then navh = navh..'*'..catlink[1]..'\n' --in case irreg froms outpace reg froms, and are visible/grey after terminalcat instead of hidden if terminalcat then nmaxseas = nmaxseas - 50 end else local hidden = '<span style="visibility:hidden">'..disp..'</span>' navh = navh..'*'..hidden..'\n' if listall then listofalllinks[#listofalllinks] = listofalllinks[#listofalllinks]..' ('..hidden..')' end end else --center navh if finish == 0 then finish = '<span style="visibility:hidden">'..start..'</span>' end navh = navh..'*<b>'..start..hyph..finish..'</b>\n' end i = i + 1 end isolatedcat() defaultgapcat(not hgap_success) if listall then return listalllinks() else return navh..'|}' end end --[[=========================={{ nav_tvseason }}============================]] function nav_tvseason( 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) tv = tonumber(tv) if tv == nil then errors = p.errorclass('Function nav_tvseason can\'t recognize the TV season number sent to its 2nd parameter.') return p.failedcat(errors, 'T') end local maxtv = tonumber(maximumtv) or 9999 --allow +/- qualifier if maxtv < tv then maxtv = tv end --input error; maxtv should be >= parent local loffset = 0 --left-offset --if tv <= 5 then loffset = 6 - tv end --commented to behave similarly to the other nav_* functions --begin navtvseason local navt = '{| class="toccolours hlist" style="text-align: center; margin: auto;"\n'..'|\n' local i = (-5 + loffset) while i <= (5 + loffset) do local t = tv + i if i ~= 0 then --left/right navt local catlink = catlinkfollowr( firstpart..' '..t..lastpart, t ) if (t >= 1 and t <= maxtv) then --hardcode mintv if catlink[2] and avoidself then --a {{Category redirect}} was followed misctrackingcats[18] = '[['..testcasecolon..'Category:Navseasoncats TV season redirected]]' end navt = navt..'*'..catlink[1]..'\n' else local hidden = '<span style="visibility:hidden">'..'0'..'</span>' --'0' to maintain dot spacing navt = navt..'*'..hidden..'\n' if listall then listofalllinks[#listofalllinks] = listofalllinks[#listofalllinks]..' ('..hidden..')' end end else --center navt navt = navt..'*<b>'..tv..'</b>\n' end i = i + 1 end isolatedcat() if listall then return listalllinks() else return navt..'|}' end end --[[==========================={{ nav_decade }}=============================]] function nav_decade( 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) -- maxdecade = 2020 ('max' decade parameter; optional; defaults to next decade) --sterilize dec local dec = sterilizedec(decade) if errors ~= '' then errors = p.errorclass('Function nav_decade was sent "'..(decade or '')..'" as its 2nd parameter, '.. 'but expects a 1 to 4-digit year ending in "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 + errors if mindec then if string.match(mindecade, '-%d') or string.match(mindecade, 'BC') then mindec = '-'..mindec --better +/-0 behavior with strings (0-initialized int == "-0" string...) end elseif errors ~= '' then errors = p.errorclass('Function nav_decade was sent "'..(mindecade or '')..'" as its 4th parameter, '.. 'but expects a 1 to 4-digit year ending in "0", the earliest decade to be shown.') return p.failedcat(errors, 'E') else 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, 'BC') 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 errors ~= '' then errors = p.errorclass('Function nav_decade was sent "'..(maxdecade or '')..'" as its 5th parameter, '.. 'but expects a 1 to 4-digit year ending in "0", the highest decade to be shown.') return p.failedcat(errors, 'F') else 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, '^BC') --following the "0s BC" convention for all years BC lastpart = mw.ustring.gsub(lastpart, '^BC%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; determine right-offset local roffset = 0 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 --a non-default max will override offsetting behavior if tonumber(maxdec) < ndec*switchADBC then maxdec = tostring(ndec*switchADBC) --input error; maxdec should be >= parent end else --offset only if 1) max == maxdefault, --[[ ** BHG commented out default offsets unless and until there is an expicit consensus for this ** perverse behaviour, which disrupts navigation by making the poistion of the page's decade ** jump around erratically as we approcha and then pass the current decade local thisyear = mw.getContentLanguage():formatDate( 'Y' ) local nthisdecade = tonumber(string.match(thisyear, '^%d%d%d')..'0') if ndec <= nthisdecade then --and 2) we're not on a future-decade cat (e.g. Works set in the 2100s) local diff = nthisdecade - ndec*switchADBC --in 2019: diff=30 for 1980, 0 for 2010, -20 for 2030 if diff < 0 then diff = 0 end --always show at least 1 decade ahead for present-decade+ cats if diff >= 0 and diff <= 30 then roffset = 40 - diff --in 2019: roffset=10 for 1980, 40 for 2010, 40 for 2030 end end ** End section commented by BHG --]] 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 embedding in {{Navseasoncats with decades below year}} bnb = ' border-style: none; background-color: transparent;' end local navd = '{| class="toccolours hlist" style="text-align: center; margin: auto;'..bnb..'"\n'..'|\n' local i = (-50 - roffset) while i <= (50 - roffset) do local d = ndec + i*switchADBC if true then --left/right navd -- Hack by BHG to ensure that decade is linked. -- if i ~= 0 then --left/right navd 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 = 'BC ' 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 = 'BC ' 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 = ' BC' --show BC for all BC decades whenever a "0s" is displayed on the nav end --determine target cat local disp = d..'s'..BCdisp local catlink = catlinkfollowr( firstpart..' '..d..'s'..tspace..BC..lastpart, disp ) if catlink[2] and avoidself then --a {{Category redirect}} was followed misctrackingcats[13] = '[['..testcasecolon..'Category:Navseasoncats decade redirected]]' end --populate left/right navd local shown = '*'..catlink[1]..'\n' 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 navd = navd..shown hidden = nil else navd = navd..'*'..hidden..'\n' end else navd = navd..shown --the common case hidden = nil end else navd = navd..'*'..hidden..'\n' end if listall and hidden then listofalllinks[#listofalllinks] = listofalllinks[#listofalllinks]..' ('..hidden..')' end else --center navd if D >= -10 then D = D + 10 --housekeeping b/w left/right sides end if parentBC then BCdisp = ' BC' if ndec == 0 then switchADBC = 1 --next element will be 0s AD D = -10 --for this special case, D is still -inf end else BCdisp = '' end navd = navd..'*<b>'..dec..'s'..BCdisp..'</b>\n' end i = i + 10 end isolatedcat() if listall then return listalllinks() else return navd..'|}' end end --[[============================{{ nav_year }}==============================]] function nav_year( 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) year = tonumber(year) or tonumber(mw.ustring.match(year or '', '^%s*(%d*)')) local minyear = tonumber(string.match(minimumyear or '', '-?%d+')) or -9999 --allow +/- qualifier local maxyear = tonumber(string.match(maximumyear or '', '-?%d+')) or 9999 --allow +/- qualifier if string.match(minimumyear or '', 'BC') then minyear = -math.abs(minyear) end --allow BC qualifier (AD otherwise assumed) if string.match(maximumyear or '', 'BC') then maxyear = -math.abs(maxyear) end --allow BC qualifier (AD otherwise assumed) if year == nil then errors = p.errorclass('Function nav_year can\'t recognize the year sent to its 2nd parameter.') 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 = string.match(firstpart, 'AD$') --following the "AD 1" convention from AD 1 to AD 10 local parentBC = string.match(lastpart, '^BCE?') --following the "1 BC" convention for all years BC firstpart = mw.ustring.gsub(firstpart, '%s*AD$', '') --handle AD/BC separately for easier & faster accounting lastpart = mw.ustring.gsub(lastpart, '^BCE?%s*', '') local BCe = parentBC or yearBCElastparts[lastpart] or 'BC' --"BC" default local year1to15AD = (year >= 1 and year <= 15 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 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 tspace = ' ' --trailing space after year, before lastpart if string.match(lastpart, '^-') then tspace = '' --e.g. "2018-related timelines" 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 <= 5 do --Czech Republic, Poland, Sri Lanka, etc. have 5-year terms prevseason = firstpart..' '..(year-ygap1)..tspace..lastpart if mw.title.new(prevseason, 'Category').exists then ygap1_success = true break end ygap1 = ygap1 + 1 end local nextseason = nil while ygap2 <= 5 do --Czech Republic, Poland, Sri Lanka, etc. have 5-year terms nextseason = firstpart..' '..(year+ygap2)..tspace..lastpart if mw.title.new(nextseason, 'Category').exists 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-60, year+60] if skipgaps then if year > 70 then --add support for AD/BC (<= AD 10) if/when needed 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 <= (15 - i) do --'15' in case we get to i=5 and find nothing 10 y out found = false Yeary = Year + y if yskipped[Yeary] == nil then yskipped[Yeary] = Yeary cat = firstpart..' '..Yeary..tspace..lastpart found = mw.title.new( cat, 'Category' ).exists 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 >= (-15 - i) do --'-15' in case we get to i=-5 and find nothing -10 y out found = false Yeary = Year + y if yskipped[Yeary] == nil then yskipped[Yeary] = Yeary cat = firstpart..' '..Yeary..tspace..lastpart found = mw.title.new( cat, 'Category' ).exists 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 --AD/BC not supported end end --begin navyears local navy = '{| class="toccolours hlist" style="text-align: center; margin: auto;"\n'..'|\n' local y local i = -5 while i <= 5 do if skipgaps then y = ynogaps[i] else y = year + i*ygap*switchADBC end local BCdisp = '' if i ~= 0 then --left/right navy local AD = '' local BC = '' if year1to15AD 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 = '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 = '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 = 'AD ' end end if BC ~= '' and year <= 5 then --only show 'BC' 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 catlink = catlinkfollowr( firstpart..' '..AD..y..tspace..BC..lastpart, disp ) if (minyear <= ysign) and (ysign <= maxyear) then --ex: 1758, 1759, 1761, 1762, 1763, 1764, 1765 if catlink[2] and avoidself then --a {{Category redirect}} was followed misctrackingcats[14] = '[['..testcasecolon..'Category:Navseasoncats year redirected]]' end navy = navy..'*'..catlink[1]..'\n' else --ex: 1755, 1756, 1757 local hidden = '<span style="visibility:hidden">'..disp..'</span>' navy = navy..'*'..hidden..'\n' if listall then listofalllinks[#listofalllinks] = listofalllinks[#listofalllinks]..' ('..hidden..')' end end else --center navy; ex: 1760 if parentBC then BCdisp = ' '..BCe end navy = navy..'*<b>'..year..BCdisp..'</b>\n' end i = i + 1 end isolatedcat() if listall then return listalllinks() else return navy..'|}' end end --[[==========================={{ nav_roman }}==============================]] function nav_roman( 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('Function nav_roman can\'t recognize one or more of "'..(num or 'nil')..'" & "'.. (rom or 'nil')..'" in category "'..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 = '{| class="toccolours hlist" style="text-align: center; margin: auto;"\n'..'|\n' local i = -5 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( firstpart..' '..r..' '..lastpart, r ) if minrom <= n and n <= maxrom then if catlink[2] and avoidself then --a {{Category redirect}} was followed misctrackingcats[15] = '[['..testcasecolon..'Category:Navseasoncats roman numeral redirected]]' end navr = navr..'*'..catlink[1]..'\n' else local hidden = '<span style="visibility:hidden">'..r..'</span>' navr = navr..'*'..hidden..'\n' if listall then listofalllinks[#listofalllinks] = listofalllinks[#listofalllinks]..' ('..hidden..')' end end else --center navr navr = navr..'*<b>'..r..'</b>\n' end else navr = navr..'*<span style="visibility:hidden">'..'I'..'</span>\n' end i = i + 1 end isolatedcat() if listall then return listalllinks() else return navr..'|}' end end --[[=========================={{ nav_nordinal }}============================]] function nav_nordinal( firstpart, ord, lastpart, minimumord, maximumord ) local nord = tonumber(ord) local minord = tonumber(string.match(minimumord or '', '(-?%d+)[snrt]?[tdh]?')) or -9999 --allow full ord & +/- qualifier local maxord = tonumber(string.match(maximumord or '', '(-?%d+)[snrt]?[tdh]?')) or 9999 --allow full ord & +/- qualifier if string.match(minimumord or '', 'BC') then minord = -math.abs(minord) end --allow BC qualifier (AD otherwise assumed) if string.match(maximumord or '', 'BC') then maxord = -math.abs(maxord) end --allow BC qualifier (AD otherwise assumed) local temporal = string.match(lastpart, 'century') or string.match(lastpart, 'millennium') 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'] = 'BCE', --WP:CFD/Log/2016 June 21#Category:11th-century BC Hebrew people ['-century Jews'] = 'BCE', --co-nominated ['-century Judaism'] = 'BCE', --co-nominated ['-century rabbis'] = 'BCE', --co-nominated } local parentBC = mw.ustring.match(lastpart, '%s(BCE?)') --"1st-century BC" format local lastpartNoBC = mw.ustring.gsub(lastpart, '%sBCE?', '') --easier than splitting lastpart up in 2; AD never used local BCe = parentBC or ordBCElastparts[lastpartNoBC] or 'BC' --"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 --determine right-offset, to not show unnecessary future millenia local roffset = 0 if temporal and nord <= 3 then if string.match(lastpartNoBC, 'millennium ') and --only offset "1st millennium BC in Egypt" to "3rd-millennium people"-type cats string.match(lastpartNoBC, 'millennium in fiction') == nil and --except these, which extend > 4th millennium maxord == 9999 --only apply if max unspecified then if not parentBC and nord <= 3 then --1st, 2nd, & 3rd millennium parents roffset = nord + 1 elseif parentBC and nord == 1 then --1st millennium BC only roffset = 1 end end end --begin navnordinal local bnb = '' --border/no border if navborder == false then --for embedding in {{Navseasoncats with centuries below decade}} bnb = ' border-style: none; background-color: transparent;' end local navo = '{| class="toccolours hlist" style="text-align: center; margin: auto;'..bnb..'"\n'..'|\n' local i = (-5 - roffset) while i <= (5 - roffset) do local o = nord + i*switchADBC local BC = '' local BCdisp = '' if true then --left/right navo -- hack by BHG to ensure that century is linked -- if i ~= 0 then --left/right navo 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 'BC' 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( firstpart..' '..oth..tspace..lastpart, oth..BCdisp ) if (minord <= osign) and (osign <= maxord) then if catlink[2] and avoidself then --a {{Category redirect}} was followed misctrackingcats[16] = '[['..testcasecolon..'Category:Navseasoncats nordinal redirected]]' end navo = navo..'*'..catlink[1]..'\n' else navo = navo..'*'..hidden..'\n' if listall then listofalllinks[#listofalllinks] = listofalllinks[#listofalllinks]..' ('..hidden..')' end end elseif BC == '' and minord <= osign and osign <= maxord then --e.g. >= "1st parliament" local catlink = catlinkfollowr( firstpart..' '..oth..tspace..lastpart, oth ) if catlink[2] and avoidself then --a {{Category redirect}} was followed misctrackingcats[16] = '[['..testcasecolon..'Category:Navseasoncats nordinal redirected]]' end navo = navo..'*'..catlink[1]..'\n' else --either out-of-range (hide), or non-temporal + BC = something might be wrong (2nd X parliament BC?); handle exceptions if/as they arise navo = navo..'*'..hidden..'\n' end else --center navo if parentBC then BC = ' '..BCe end navo = navo..'*<b>'..p.addord(o)..BC..'</b>\n' end i = i + 1 end isolatedcat() if listall then return listalllinks() else return navo..'|}' end end --[[========================={{ nav_wordinal }}=============================]] function nav_wordinal( firstpart, word, lastpart, minimumword, maximumword, frame ) local eng2ord = require('Module:ConvertNumeric').english_to_ordinal local ord2eng = require('Module:ConvertNumeric').numeral_to_english local sc = string.match(word, '^%u') --sentence-case check local lc = string.lower(word) --operate on/with lc, and restore any sc later local nword = eng2ord(lc) local case = nil if sc then case = 'U' end --sterilize min/max local minword = 1 local maxword = 99 if minimumword then local num = tonumber(minimumword) if num and 0 < num and num < maxword then minword = num else local ord = eng2ord(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 = eng2ord(maximumword) if 0 < ord and ord < maxword then maxword = ord end end end if minword > nword then minword = nword end if maxword < nword then maxword = nword end --begin navwordinal local navw = '{| class="toccolours hlist" style="text-align: center; margin: auto;"\n'..'|\n' local i = -5 while i <= 5 do local n = nword + i if n >= 1 then local nth = p.addord(n) if i ~= 0 then --left/right navw local frame_args = frame:newChild{ args = { n, ord = 'on', case = case } } --easier to do this than modify Module:ConvertNumeric local w = ord2eng( frame_args ) local catlink = catlinkfollowr( firstpart..' '..w..' '..lastpart, nth ) if minword <= n and n <= maxword then if catlink[2] and avoidself then --a {{Category redirect}} was followed misctrackingcats[17] = '[['..testcasecolon..'Category:Navseasoncats wordinal redirected]]' end navw = navw..'*'..catlink[1]..'\n' else local hidden = '<span style="visibility:hidden">'..nth..'</span>' navw = navw..'*'..hidden..'\n' if listall then listofalllinks[#listofalllinks] = listofalllinks[#listofalllinks]..' ('..hidden..')' end end else --center navw navw = navw..'*<b>'..nth..'</b>\n' end else navw = navw..'*<span style="visibility:hidden">'..'0th'..'</span>\n' end i = i + 1 end isolatedcat() if listall then return listalllinks() else return navw..'|}' end end --[[==========================={{ find_var }}===============================]] --Also used by {{Navseasoncats with centuries below decade}}. function p.find_var( pn ) --Extracts the variable text (e.g. 2015–16, 3rd, 2000s, III, etc.) from a string local pagename = currtitle.text if pn and pn ~= '' then pagename = pn end local cpagename = 'Category:'..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+[–-])$') --irreg; ending unknown, e.g. "Members of the Scottish Parliament 2021–" 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, 'season (%d+)') or mw.ustring.match(cpagename, 'series (%d+)') local nordinal = mw.ustring.match(cpagename, '[:%s](%d+[snrt][tdh])[-%s]') or mw.ustring.match(cpagename, '[:%s](%d+[snrt][tdh])$') local decade = mw.ustring.match(cpagename, '[:%s](%d+s)[%s-]') or mw.ustring.match(cpagename, '[:%s](%d+s)$') local year = mw.ustring.match(cpagename, '[:%s](%d%d%d%d)%s') or --prioritize 4-digit years first mw.ustring.match(cpagename, '[:%s](%d%d%d%d)$') or mw.ustring.match(cpagename, '[:%s](%d+)%s') or mw.ustring.match(cpagename, '[:%s](%d+)$') or mw.ustring.match(cpagename, '[:%s](%d+)-related') --expand as needed 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/least chance for duplication if nordinal and season --i.e. "18th-century establishments in the Province of Quebec (1763–1791)" then return { 'nordinal', nordinal } end if d_season then return { 'decade', d_season } end if y_season then return { 'year', y_season } end if e_season then return { 'ending', e_season } end if season then return { 'season', season } end if tvseason then return { 'tvseason', tvseason } end if nordinal then return { 'nordinal', nordinal } end if decade then return { 'decade', decade } end if year then return { 'year', year } end if roman then return { 'roman', roman } end end else --try word ('zeroth' to 'ninety-ninth' only) local eng2ord = require('Module:ConvertNumeric').english_to_ordinal local split = mw.text.split(pagename, ' ') for i=1, #split do if eng2ord(split[i]) > -1 then return { 'wordinal', split[i] } end end end errors = p.errorclass('Function find_var can\'t find the variable text in category "'..pagename..'".') return { 'error', p.failedcat(errors, 'V') } end --[[==========================================================================]] --[[ Main ]] --[[==========================================================================]] function p.navseasoncats( frame ) local args = frame:getParent().args local dby = args['decade-below-year'] --used by {{Navseasoncats with decades below year}} local cbd = args['century-below-decade'] --used by {{Navseasoncats with centuries below decade}} local cat = args['cat'] --'testcase' alias for mainspace local list = args['list-all-links'] --utility to output all links & followed #Rs instead of a navbar 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'] if dby then navborder = false dby = string.gsub(dby, ' ', ' ') --unicodify forced whitespace end if cbd then navborder = false cbd = string.gsub(cbd, ' ', ' ') --unicodify forced whitespace 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 if avoidself then misctrackingcats[19] = '[['..testcasecolon..'Category:Navseasoncats using skip-gaps parameter]]' end end if currtitle.nsText == 'Category' then if cat then misctrackingcats[1] = '[['..testcasecolon..'Category:Navseasoncats using cat parameter]]' end if testcase then misctrackingcats[2] = '[['..testcasecolon..'Category:Navseasoncats using testcase parameter]]' end end local pagename = testcase or cat or dby or cbd or currtitle.text --find the variable parts of pagename local findvar = p.find_var(pagename) if findvar[1] == 'error' then return findvar[2]..table.concat(misctrackingcats) end --basic format error checking in find_var() local start = string.match(findvar[2], '^%d+') --the rest is static local findvar_escaped = string.gsub( findvar[2], '%-', '%%%-') local firstpart, lastpart = string.match(pagename, '^(.-)'..findvar_escaped..'(.*)$') if findvar[1] == '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 if findvar[1] == 'season' then --e.g. "1–4", "1999–2000", "2001–02", "2001–2002", "2005–2010", etc. local hyphen, finish = mw.ustring.match(findvar[2], '%d([–-])(%d+)') --ascii 150 & 45 (ndash & keyboard hyphen); mw req'd return nav_hyphen( start, hyphen, finish, firstpart, lastpart, minimum, maximum, testcasegap )..table.concat(misctrackingcats) elseif findvar[1] == 'ending' then --e.g. "2021–" (irregular; ending unknown) local hyphen, finish = mw.ustring.match(findvar[2], '%d([–-])$'), 0 --ascii 150 & 45 (ndash & keyboard hyphen); mw req'd return nav_hyphen( start, hyphen, finish, firstpart, lastpart, minimum, maximum, testcasegap )..table.concat(misctrackingcats) elseif findvar[1] == 'tvseason' then --e.g. "1", "15" but preceded with "season" or "series" return nav_tvseason( firstpart, start, lastpart, maximum )..table.concat(misctrackingcats) --"minimum" defaults to 1 elseif findvar[1] == 'decade' then --e.g. "0s", "2010s" return nav_decade( firstpart, start, lastpart, minimum, maximum )..table.concat(misctrackingcats) elseif findvar[1] == 'year' then --e.g. "500", "2001" return nav_year( firstpart, start, lastpart, minimum, maximum )..table.concat(misctrackingcats) elseif findvar[1] == 'roman' then --e.g. "I", "XXVIII" return nav_roman( firstpart, findvar[2], lastpart, minimum, maximum )..table.concat(misctrackingcats) elseif findvar[1] == 'nordinal' then --e.g. "4th" return nav_nordinal( firstpart, start, lastpart, minimum, maximum )..table.concat(misctrackingcats) elseif findvar[1] == 'wordinal' then --e.g. "first", "ninety-ninth" return nav_wordinal( firstpart, findvar[2], lastpart, minimum, maximum, frame )..table.concat(misctrackingcats) else --malformed errors = p.errorclass('Failed to determine the appropriate nav function from malformed season "'..findvar[2]..'". ') return p.failedcat(errors, 'N')..table.concat(misctrackingcats) end end return p