Module:Russia by federal subject category navigation
Jump to navigation
Jump to search
--[[
v01.18: fix handling of definite article (e.g. for 'the Northwest Territories')
2.0 Resolve category redirects
each title consists of three parts
* prefix
* federal subject name
* suffix
e.g. "Foo in Adygea"
* prefix = "Foo in "
* federal subject name = "Adygea"
* suffix = ""
e.g. "Bashkortostan-related lists"
* prefix = ""
* federal subject name = "Bashkortostan"
* suffix = "-related lists"
]]
-- config
local textSize = '90%'
local tableClass="toc nomobile mw-collapsible mw-collapsed"
local evenRowStyle = "vertical-align:top; background-color:#f3f3f3;"
local oddRowStyle = "vertical-align:top;"
local labelStyle = "text-align:right; font-weight: bold; padding: 0.25em 0.5em 0.25em 0.5em;"
local listStyle = "text-align:left; font-weight: normal; padding: 0.25em 0.5em 0.25em 0.5em;"
local greyLinkColor = "#888"
--[[ Note that the table styles are designed to ensure that the navbox is as wide as possible, while still leaving
enough enough space on the right for portal boxes, commons links, and GeoGroup templates.
A lot of fiddling was needed to make it work, so please test any changes very carfully in the sandbox.
]]
local tableFallbackMaxWidth="auto"
local tableMaxWidth="calc(100% - 25em)" -- Template:GeoGroup has width: 23em<
-- local tableStyle="margin-left:0; margin-right:auto; clear:left !important; margin-top:0 !important; float:left; width:auto;"
local tableStyle="margin-left:0; margin-right:auto; clear:left !important; margin-top:0 !important; width:auto;"
-- Templates which are allowed to call this module
local callingTemplates = {
'Template:Russia by federal subject category navigation',
}
-- globals for this module
local debugging = false
local debugmsg = ""
local tableRowNum = 0
local title_prefix = ""
local title_suffix = ""
local title_prefix
local title_suffix
local thisPageFederalSubject
local greyLinkCount = 0
local blueLinkCount = 0
local parentname = ""
local templateName
local horizontal = require('Module:List').horizontal
local getArgs = require('Module:Arguments').getArgs
local yesno = require('Module:Yesno')
local ResolveCategoryRedirect = require('Module:Resolve category redirect').rtarget
local p = {}
--[[
Plain text list of federal subjects
* Each entry exactly as it appears in running text in category titles, with any prefix (e.g. "the")
* Be sure to avoid hidden characters and duplicate spaces. They break the pattern-matching on which this module relies
]]
local Republics = {
'Adygea',
'the Altai Republic',
'Bashkortostan',
'Buryatia',
'Chechnya',
'Chuvashia',
'the Republic of Crimea',
'Dagestan',
'Ingushetia',
'Kabardino-Balkaria',
'Kalmykia',
'Karachay-Cherkessia',
'the Republic of Karelia',
'Khakassia',
'the Komi Republic',
'Mari El',
'Mordovia',
'North Ossetia–Alania',
'the Sakha Republic',
'Tatarstan',
'Tuva',
'Udmurtia'
}
local Krais = {
'Altai Krai',
'Kamchatka Krai',
'Khabarovsk Krai',
'Krasnodar Krai',
'Krasnoyarsk Krai',
'Perm Krai',
'Primorsky Krai',
'Stavropol Krai',
'Zabaykalsky Krai'
}
local Oblasts = {
'Amur Oblast',
'Arkhangelsk Oblast',
'Astrakhan Oblast',
'Belgorod Oblast',
'Bryansk Oblast',
'Chelyabinsk Oblast',
'Donetsk Oblast (Russia)',
'Irkutsk Oblast',
'Ivanovo Oblast',
'Kaliningrad Oblast',
'Kaluga Oblast',
'Kemerovo Oblast',
'Kherson Oblast (Russia)',
'Kirov Oblast',
'Kostroma Oblast',
'Kurgan Oblast',
'Kursk Oblast',
'Leningrad Oblast',
'Lipetsk Oblast',
'Luhansk Oblast (Russia)',
'Magadan Oblast',
'Moscow Oblast',
'Murmansk Oblast',
'Nizhny Novgorod Oblast',
'Novgorod Oblast',
'Novosibirsk Oblast',
'Omsk Oblast',
'Orenburg Oblast',
'Oryol Oblast',
'Penza Oblast',
'Pskov Oblast',
'Rostov Oblast',
'Ryazan Oblast',
'Sakhalin Oblast',
'Samara Oblast',
'Saratov Oblast',
'Smolensk Oblast',
'Sverdlovsk Oblast',
'Tambov Oblast',
'Tomsk Oblast',
'Tula Oblast',
'Tver Oblast',
'Tyumen Oblast',
'Ulyanovsk Oblast',
'Vladimir Oblast',
'Volgograd Oblast',
'Vologda Oblast',
'Voronezh Oblast',
'Yaroslavl Oblast',
'Zaporizhzhia Oblast (Russia)'
}
local FederalCities = {
'Moscow',
'Saint Petersburg',
'Sevastopol'
}
local AutonomousOblasts = {
'the Jewish Autonomous Oblast'
}
local AutonomousOkrugs = {
'Chukotka Autonomous Okrug',
'Khanty-Mansi Autonomous Okrug',
'Nenets Autonomous Okrug',
'Yamalo-Nenets Autonomous Okrug'
}
-- sort order for checks to fix errors coming when one entry includes the full name of another entry
local AutonomousOkrugsCheck = {
'Chukotka Autonomous Okrug',
'Khanty-Mansi Autonomous Okrug',
'Yamalo-Nenets Autonomous Okrug',
'Nenets Autonomous Okrug'
}
-- If the page title matches any of these Lua patterns, treat it as a false positive
local falsePositiveChecks = {
}
local function publishDebugLog()
if not debugging then
return ""
end
return "==Debugging ==\n\n" .. debugmsg .. "\n== Output ==\n"
end
-- debugLog builds a log which can be output if debuging is enabled
-- each log entry is given a level, so that the output is not simply a flat list
-- a debug msg may be appended to the previous msg by setting the level to nil
local function debugLog(level, msg)
if (debugmsg == nil) then
debugmsg = ""
end
if (level ~= nil) then
-- not appending, so make a new line
debugmsg = debugmsg .. "\n"
-- then add the level
local i
for i = 1, level do
if (i % 2) == 1 then
debugmsg = debugmsg .. "#"
else
debugmsg = debugmsg .. "*"
end
end
end
debugmsg = debugmsg .. " " .. msg
return true
end
local function makeTrackingCategory()
-- discount the current page, which will always be coded as a blue link, but rendered as bold un-navigable
blueLinkCount = blueLinkCount - 1
if greyLinkCount == 0 then
return "[[Category:" .. templateName .. " with no grey links|" .. mw.title.getCurrentTitle().text .. "]]"
end
if blueLinkCount == 0 then
return "[[Category:" .. templateName .. " with all grey links|" .. mw.title.getCurrentTitle().text .. "]]"
end
if greyLinkCount <= 20 then
return "[[Category:" .. templateName .. " with 1–20 grey links|" .. mw.title.getCurrentTitle().text .. "]]"
end
if greyLinkCount > 70 then
return "[[Category:" .. templateName .. " with 71 or more grey links|" .. mw.title.getCurrentTitle().text .. "]]"
end
if greyLinkCount > 20 then
return "[[Category:" .. templateName .. " with 21–70 grey links|" .. mw.title.getCurrentTitle().text .. "]]"
end
return ""
end
-- Make a piped link to a category, if it exists
-- If it doesn't exist, just display the greyed the link title without linking
local function makeCatLink(catname, disp)
local displaytext
if (disp ~= "") and (disp ~= nil) then
-- use 'disp' parameter, but strip any trailing disambiguator
displaytext = mw.ustring.gsub(disp, "%s+%(.+$", "");
-- also strip any leading word "the"
displaytext = mw.ustring.gsub(displaytext, "^[tT]he%s+", "");
-- also shorten any "People's Republic"
displaytext = mw.ustring.gsub(displaytext, "People\'s Republic", "PR");
-- also strip any "Republic of"
displaytext = mw.ustring.gsub(displaytext, "Republic%sof%s", "");
-- also strip any trailing "Autonomous"
displaytext = mw.ustring.gsub(displaytext, "%sAutonomous", "");
-- also strip any trailing "Krai"
displaytext = mw.ustring.gsub(displaytext, "%sKrai", "");
-- also strip any trailing "Oblast"
displaytext = mw.ustring.gsub(displaytext, "%sOblast", "");
-- also strip any trailing "Okrug"
displaytext = mw.ustring.gsub(displaytext, "%sOkrug", "");
-- also strip any trailing "Republic"
displaytext = mw.ustring.gsub(displaytext, "%sRepublic", "");
-- restore "Jewish Autonomous Oblast"
displaytext = mw.ustring.gsub(displaytext, "Jewish", "Jewish Autonomous Oblast");
else
displaytext = catname
end
local fmtlink
local catPage = mw.title.new( catname, "Category" )
if (catPage.exists) then
fmtlink = "[[:Category:" .. catname .. "|" .. displaytext .. "]]"
blueLinkCount = blueLinkCount + 1
else
fmtlink = '<span style="color:' .. greyLinkColor .. '">' .. displaytext .. "</span>"
greyLinkCount = greyLinkCount + 1
end
if (disp == "the Republic of Crimea" or disp == "Donetsk Oblast (Russia)" or disp == "Kherson Oblast (Russia)" or disp == "Luhansk Oblast (Russia)" or disp == "Sevastopol" or disp == "Zaporizhzhia Oblast (Russia)") then
fmtlink = fmtlink .. "<sup>1</sup>;"
end
return fmtlink
end
local function makeCatName(federalSubjectName, prefix, suffix)
local thisCatName = prefix .. federalSubjectName .. suffix
debugLog(5, "thisCatName = [" .. thisCatName .. "]")
--[[
Now check whether the all following conditions are true
1/ the federalSubjectName begins with "the"
2/ the category does NOT exist if we use "the "
3/ the category does exist if we strip "the "
If those conditions are all true, them strip "the"
]]
if (mw.ustring.match(federalSubjectName, "^[tT]he ") ~= nil) then
debugLog(6, "FederalSubjectName begins with 'the'")
local federalSubjectNameStripped = mw.ustring.gsub(federalSubjectName, "^[tT]he ", "", 1)
local thisCatNameStripped = prefix .. federalSubjectNameStripped .. suffix
debugLog(6, "thisCatNameStripped = [" .. thisCatNameStripped .. "]")
local testCatPage = mw.title.new(thisCatName, "Category" )
local testCatPageStripped = mw.title.new(thisCatNameStripped, "Category" )
if not testCatPage.exists then
debugLog(7, "[" .. testCatPage.fullText .. "] .. does not exist")
if (testCatPageStripped.exists) then
debugLog(7, "[" .. testCatPageStripped.fullText .. "] .. DOES exist, so use that")
return thisCatNameStripped
end
end
end
return thisCatName
end
local function makeTableRow(rowLabel, federalSubjectList)
debugLog(2, "makeTableRow, label: ")
if (rowLabel == nil) then
rowLabel = "By federalSubject"
debugLog(nil, rowLabel)
else
rowLabel = mw.text.trim(rowLabel)
debugLog(nil, " [" .. rowLabel .. "]")
end
tableRowNum = tableRowNum + 1
local thisRow
if (tableRowNum % 2) == 0 then
debugLog(3, "Even-numbered")
thisRow = '<tr style="' .. evenRowStyle .. '">\n'
else
debugLog(3, "Odd-numbered")
thisRow = '<tr style="' .. oddRowStyle .. '">\n'
end
if not ((rowLabel == nil) or (rowLabel =="")) then
thisRow = thisRow .. '<td style="' .. labelStyle .. '">' .. rowLabel .. '</td>\n'
end
-- now begin making the row contents
debugLog(3, "Process federalSubjectList")
local list_args = {}
for i, aFederalSubject in ipairs(federalSubjectList) do
debugLog(4, "No. " .. tostring(i) .. ": [" .. aFederalSubject .. "]")
myCatName = ResolveCategoryRedirect(makeCatName(aFederalSubject, title_prefix, title_suffix))
table.insert(list_args, makeCatLink(myCatName, aFederalSubject))
end
return thisRow .. '<td style="' .. listStyle .. ';">' .. horizontal(list_args) .. '</td>\n</tr>'
end
local function makeTable()
debugLog(1, "makeTable")
tableRowNum = 0
local myTable = '<table class="' .. tableClass .. '"'
myTable = myTable .. ' style="' .. tableStyle .. '; font-size:' .. textSize .. '; max-width:' .. tableFallbackMaxWidth .. '; max-width:' .. tableMaxWidth ..'">\n'
myTable = myTable .. "<th colspan=2>By federal subject</th>"
myTable = myTable .. makeTableRow("Republics", Republics)
myTable = myTable .. makeTableRow("Krais", Krais)
myTable = myTable .. makeTableRow("Oblasts", Oblasts)
myTable = myTable .. makeTableRow("Federal cities", FederalCities)
myTable = myTable .. makeTableRow("Autonomous oblasts", AutonomousOblasts)
myTable = myTable .. makeTableRow("Autonomous okrugs", AutonomousOkrugs)
myTable = myTable .. "<td colspan=2><sup>1</sup>Considered by most of the international community to be part of [[Ukraine]]</td>"
myTable = myTable .. "</table>\n"
return myTable
end
local function patternSearchEncode(s)
return mw.ustring.gsub(s, "([%W])", "%%%1")
end
-- Does the pagename include a federal subject name?
local function findfederalSubjectNameInPagename(pn, federalSubjectList, description)
local i, aFederalSubject, testFederalSubject
debugLog(2, "checking [" .. pn .."] for a federal subject name in federal subject set: " .. description)
for i, aFederalSubject in ipairs(federalSubjectList) do
testFederalSubject = aFederalSubject
debugLog(3, "testing: [" .. testFederalSubject .. "]")
local testFederalSubjectEncoded = patternSearchEncode(testFederalSubject)
-- For efficiency, the first test is a simple match as a a screening test
-- If the bare federal subject name is nowhere in the pagename, then no need for
-- more precise checks
-- This check would be one line in regex, but Lua pattern matching is cruder,
--so we need several passes to ensure that any match is of a complete word
debugLog(4, "simple match? ")
if (not mw.ustring.match(pn, testFederalSubjectEncoded)) then
debugLog(nil, "Fail")
else
debugLog(nil, "Success")
-- test for false positives
local j, aFalsePositiveTest
for j, aFalsePositiveTest in ipairs(falsePositiveChecks) do
debugLog(5, "false positive test pattern '" .. aFalsePositiveTest .. "' ? ")
if (mw.ustring.match(pn, aFalsePositiveTest)) then
debugLog(nil, "Match, so fail")
return nil
end
debugLog(nil, "No match, so OK")
end
debugLog(4, "match whole name? ")
if (pn == testFederalSubject) then
debugLog(nil, "Yes")
return testFederalSubject
end
debugLog(nil, "No")
debugLog(4, "match at start, followed by separator? ")
if mw.ustring.match(pn, "^" .. testFederalSubjectEncoded .. "[^%w]") then
debugLog(nil, "Yes")
return testFederalSubject
end
debugLog(nil, "No")
debugLog(4, "match at end, preceded by separator? ")
if mw.ustring.match(pn, "[^%w]" .. testFederalSubjectEncoded .. "$") then
debugLog(nil, "Yes")
return testFederalSubject
end
debugLog(nil, "No")
debugLog(4, "match anywhere, preceded and followed by separator? ")
if mw.ustring.match(pn, "[^%w]" .. testFederalSubjectEncoded .. "[^%w]") then
debugLog(nil, "Yes")
return testFederalSubject
end
debugLog(nil, "No")
end
-- Special case: if the federal subject name we are testing begins with a prefixed "the"
debugLog(4, "does testFederalSubject begin with 'the' ? ")
if (mw.ustring.match(testFederalSubject, "^[tT]he ") == nil) then
debugLog(nil, "No")
else
debugLog(nil, "Yes")
end
if (mw.ustring.match(testFederalSubject, "^[tT]he ") ~= nil) then
local testFederalSubjectStripped = mw.ustring.gsub(testFederalSubject, "^[tT]he ", "", 1)
local testFederalSubjectStrippedEncoded = patternSearchEncode(testFederalSubjectStripped)
debugLog(4, "test pattern without leading definite article, i.e. '" .. testFederalSubjectStrippedEncoded .. "' ? ")
if (mw.ustring.match(pn, "[^%w]" .. testFederalSubjectStrippedEncoded .. "[^%w]") ~= nil)
or (mw.ustring.match(pn, "^" .. testFederalSubjectStrippedEncoded .. "[^%w]") ~= nil)
or (mw.ustring.match(pn, "[^%w]" .. testFederalSubjectStrippedEncoded .. "$") ~= nil)
or (mw.ustring.match(pn, "^" .. testFederalSubjectStrippedEncoded .. "$") ~= nil) then
debugLog(nil, "Yes")
return testFederalSubjectStripped
end
debugLog(nil, "No")
end
end
return nil
end
-- parse the pagename to find three parts: prefix, federal subject name, suffix
local function parsePagename(pn)
debugLog(1, "parsePagename: [" .. pn .. "]")
local validfederalSubjectName
validfederalSubjectName = findfederalSubjectNameInPagename(pn, Republics, "federalSubjectlst")
if validfederalSubjectName == nil then
validfederalSubjectName = findfederalSubjectNameInPagename(pn, Krais, "federalSubjectlst")
if validfederalSubjectName == nil then
validfederalSubjectName = findfederalSubjectNameInPagename(pn, Oblasts, "federalSubjectlst")
if validfederalSubjectName == nil then
validfederalSubjectName = findfederalSubjectNameInPagename(pn, FederalCities, "federalSubjectlst")
if validfederalSubjectName == nil then
validfederalSubjectName = findfederalSubjectNameInPagename(pn, AutonomousOblasts, "federalSubjectlst")
if validfederalSubjectName == nil then
validfederalSubjectName = findfederalSubjectNameInPagename(pn, AutonomousOkrugsCheck, "federalSubjectlst")
if validfederalSubjectName == nil then
return false
end
end
end
end
end
end
-- if we get here, the page name "pn" includes a validfederalSubjectName
-- so now we need to split the string
debugLog(2, "split pagename around [" .. validfederalSubjectName .. "]")
local validFederalSubjectEncoded = mw.ustring.gsub(validfederalSubjectName, "([%W])", "%%%1")
match_prefix, match_federalSubject, match_suffix = mw.ustring.match(pn, "^(.*)(" .. validFederalSubjectEncoded .. ")(.*)$")
title_prefix = match_prefix
title_suffix = match_suffix
debugLog(2, "parse successful")
debugLog(3, "title_prefix = [" .. title_prefix .. "]")
debugLog(3, "thisPageFederalSubject = [" .. match_federalSubject .. "]")
debugLog(3, "title_suffix = [" .. title_suffix .. "]")
return true
end
local function getYesNoParam(args, thisParamName, defaultVal)
local paramVal = args[thisParamName]
if paramVal == nil then
paramVal = ""
end
debugLog(2, "Evaluate yes/no parameter: [" .. thisParamName .. "] = [" .. paramVal .. "]")
debugLog(3, "default = " .. ((defaultVal and "Yes") or "No"))
debugLog(3, "Evaluate as: ")
local returnValue
if paramVal == "" then
returnValue = defaultVal
else
returnValue = yesno(args[thisParamName], defaultVal)
end
if (returnValue) then
debugLog(nil, "Yes")
else
debugLog(nil, "No")
end
return returnValue
end
local function makeErrorMsg(s)
return '<p class="error">[[' .. parentname .. ']] Error: ' .. s .. '</p>\n'
end
local function isValidParent(p)
for i, aParent in ipairs(callingTemplates) do
if p == aParent then
return true
end
end
return false
end
function p.main(frame)
local parent = frame:getParent()
if parent then
parentname = parent:getTitle():gsub('/sandbox$', '')
end
if (parentname == nil) or not isValidParent(parentname) then
local errormsg = '<p class="error"> Error: ' .. parentname .. ' is not a valid wrapper for [[' .. frame:getTitle() .. ']]\n'
errormsg = errormsg .. '<br><br>Valid wrappers: '
local i, aParent
for i, aParent in ipairs(callingTemplates) do
errormsg = errormsg .. '[[' .. aParent .. ']]'
end
errormsg = errormsg .. '</p>'
return errormsg
end
templateName = mw.ustring.gsub(parentname, "^Template:", "")
debugLog(1, "Check parameters")
debugging = getYesNoParam(frame.args, "debug", false)
-- get the page title
thispage = mw.title.getCurrentTitle()
thispagename = thispage.text;
debugLog(1, "mw.title.getCurrentTitle()")
debugLog(2, "thispage.text = [" .. thispage.text .."]")
debugLog(2, "thispage.namespace = [" .. thispage.namespace .."]")
debugLog(2, "thispage.nsText = [" .. thispage.nsText .."]")
debugLog(2, "is it a cat? using (thispage:inNamespace(14)): ")
if not (thispage:inNamespace(14)) then
debugLog(nil, "No, this is not a category")
debugLog(1, "Not a category, so no output")
return makeErrorMsg("only for use on a category page") .. publishDebugLog()
end
debugLog(nil, "Yes, this is a category")
if not parsePagename(thispagename) then
-- some error parsing the title, so don't proceed to output
local trackingCatInvalid = "[[Category:" .. templateName .. " on invalid category|" .. mw.title.getCurrentTitle().text .. "]]"
return makeErrorMsg('the name of this category does not include a valid Russian federal subject') .. publishDebugLog() .. trackingCatInvalid
end
debugLog(1, "all parse done")
debugLog(2, "title_prefix = [" .. title_prefix .. "]")
debugLog(2, "title_suffix = [" .. title_suffix .. "]")
local myNavTable = makeTable()
debugLog(2, "blueLinkCount = [" .. blueLinkCount .. "]. (NB The current page is always counted as a bluelink, but will not be navigable)")
debugLog(2, "greyLinkCount = [" .. greyLinkCount .. "]")
if (blueLinkCount <= 1) then
-- This is a navbar to nowhere, so suppress display
myNavTable = ""
debugLog(1, "Zero bluelinks (other than the current page) makes this a navbox to nowhere, so do not display the navbox")
end
local myTrackingCat = makeTrackingCategory()
return publishDebugLog() .. myNavTable .. myTrackingCat
end
return p