From d20de7785febc6015d9976744cafe5d5793e8736 Mon Sep 17 00:00:00 2001 From: Tphamtranba <119475427+Bapham12@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:46:18 +0700 Subject: [PATCH] =?UTF-8?q?Th=C3=AAm=20c=C3=A1c=20m=C3=B4=20=C4=91un=20sor?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Other/Sort title.lua | 137 +++++++++++++++++++++++++++++++++++++ Other/SortName.lua | 88 ++++++++++++++++++++++++ Testcases/Sort title.lua | 143 +++++++++++++++++++++++++++++++++++++++ Testcases/SortName.lua | 20 ++++++ 4 files changed, 388 insertions(+) create mode 100644 Other/Sort title.lua create mode 100644 Other/SortName.lua create mode 100644 Testcases/Sort title.lua create mode 100644 Testcases/SortName.lua diff --git a/Other/Sort title.lua b/Other/Sort title.lua new file mode 100644 index 0000000..628774d --- /dev/null +++ b/Other/Sort title.lua @@ -0,0 +1,137 @@ +local match = require("Module:String")._match + +local p = {} + +--[[ +Local function which is used to return a number without commas. +For example: 4,722 will become 4722. +--]] +local function cleanNumberFromCommas(title) + return string.gsub(title, "(%d+)(,+)", "%1") +end + +--[[ +Local function which is used to check if the word is an article. +Returns true if it is, false if it isn't. +--]] +local function isFirstWordAnArticle(word) + word = string.lower(word) + if (word == "a" or word == "an" or word == "the") then + return true + else + return false + end +end + +--[[ +Local function which is used to return the title without its disambiguation. +--]] +local function getTitleWithoutDisambiguation(title, disambiguation) + local newTitle = string.gsub(title, "%(".. disambiguation .. "%)", "") + return mw.text.trim(newTitle) +end + +--[[ +Local function which is used to return a title without its first word. +--]] +local function getTitleWithoutFirstWord(title) + return mw.ustring.gsub(title, "^[^%s]*%s*", "") +end + +--[[ +Local function which is used to return the first word from a title. +--]] +local function getFirstWord(title) + return match(title, "^[^%s]*", 1, 1, false, "") +end + +--[[ +Local function which is used to return a sort key for a specific part. +--]] +local function getSortKey(title, firstWord) + local sortKey = title + if (isFirstWordAnArticle(firstWord) and firstWord ~= title) then + title = getTitleWithoutFirstWord(title) + sortKey = title .. ", " .. firstWord + end + + return sortKey +end + +--[[ +Local function which is used to return the disambiguation sort key. +--]] +local function getDisambiguationSortKey(disambiguation) + if (disambiguation == "") then + return "" + end + + local firstWord = getFirstWord(disambiguation) + local disambiguationSortKey = getSortKey(disambiguation, firstWord) + return "(" .. disambiguationSortKey .. ")" +end + +--[[ +Local function which is used to return the disambiguation from a title. +--]] +local function getDisambiguation(title) + local disambiguation = match(title, "%s%((.-)%)", 1, -1, false, "") + if (disambiguation == "") then + return "" + else + return disambiguation + end +end + +--[[ +The main function. +--]] +local function _main(title) + if (not title) then + title = mw.title.getCurrentTitle().text + end + + local firstWord = getFirstWord(title) + local disambiguation = getDisambiguation(title) + local disambiguationSortKey = getDisambiguationSortKey(disambiguation) + + title = getTitleWithoutDisambiguation(title, disambiguation) + title = cleanNumberFromCommas(title) + title = getSortKey(title, firstWord) + + local sortKey = title .. " " .. disambiguationSortKey + return mw.text.trim(sortKey) +end + +--[[ +Public function which allows modules to retrieve a sort key. +--]] +function p._getSortKey() + return _main(nil) +end + +--[[ +Public function which allows templates to retrieve a sort key. +--]] +function p.getSortKey(frame) + return _main(nil) +end + +--[[ +Public function which allows templates to retrieve the sort key inside a DEFAULTSORT. +--]] +function p.getDefaultSort(frame) + local sortKey = _main(nil) + return frame:preprocess{text = "{{DEFAULTSORT:" .. sortKey .. "}}"} +end + +--[[ +Public function which is used for testing various names and not the current page name. +--]] +function p.testcases(frame) + local getArgs = require('Module:Arguments').getArgs + local args = getArgs(frame) + return _main(args[1]) +end + +return p \ No newline at end of file diff --git a/Other/SortName.lua b/Other/SortName.lua new file mode 100644 index 0000000..f3afff5 --- /dev/null +++ b/Other/SortName.lua @@ -0,0 +1,88 @@ +local p = {} + +function p.sortname(frame) + local currentpage = mw.title.getCurrentTitle() + local pagetitle = frame.args[1] or currentpage.text + local langvar = mw.language.getContentLanguage() + local text1 = '' + local text2 = '' + local parts = { 'de','De','von','Von','du','Du','del','Del','zu','Zu','di','Di','van','Van','na','Na','le','Le','de\'','De\'' } + local partmatch = false + if string.find( pagetitle, ' ' ) ~= nil then + pagetitle = string.gsub( string.gsub( string.gsub( pagetitle, '%b()', '' ), ' +', ' '), ' $', '' ) + if string.find( pagetitle, '^List of ' ) ~= nil then + pagetitle = langvar:ucfirst( string.gsub( pagetitle, '^List of ', '', 1 ) ) + elseif string.find( pagetitle, '^The ' ) ~= nil then + pagetitle = string.gsub( pagetitle, '^The ', '' ) .. ', The' + else + pagetitle = string.gsub( pagetitle, ',.*$', '' ) + pagetitle = string.gsub( pagetitle, ' of .*$', '' ) + for i in ipairs( parts ) do + if string.find( pagetitle, ' ' .. parts[i] .. ' ' ) ~= nil then + text1 = string.sub( pagetitle, string.find( pagetitle, ' ' .. parts[i] .. ' ' ) + 1, #pagetitle ) + text2 = string.sub( pagetitle, 0, string.find( pagetitle, ' ' .. parts[i] .. ' ' ) - 1 ) + pagetitle = text1 .. ', ' .. text2 + partmatch = true + break + end + end + if not partmatch and string.find( pagetitle, ' ' ) ~= nil then + text1 = string.sub( pagetitle, string.find( pagetitle, ' [^ ]*$' ) + 1, #pagetitle ) + text2 = string.sub( pagetitle, 0, string.find( pagetitle, ' [^ ]*$' ) - 1 ) + local romannumeral = roman_to_numeral(text1) + if romannumeral == -1 then + pagetitle = text1 .. ', ' .. text2 + else + if string.find( text2, ' ' ) == nil then + pagetitle = text2 .. ' ' .. romannumeral + else + text1 = string.sub( text2, string.find( text2, ' [^ ]*$' ) + 1, #text2 ) + text2 = string.sub( text2, 0, string.find( text2, ' [^ ]*$' ) - 1 ) + pagetitle = text1 .. ' ' .. romannumeral .. ', ' .. text2 + end + end + end + end + end + return pagetitle +end + +-- the following table and roman_to_numeral function came from Module:ConvertNumeric, created by User:Dcoetzee +roman_numerals = { + I = 1, + V = 5, + X = 10, + L = 50, + C = 100, + D = 500, + M = 1000 +} + +-- Converts a given valid roman numeral (and some invalid roman numerals) to a number. Returns -1, errorstring on error +function roman_to_numeral(roman) + if type(roman) ~= "string" then return -1, "roman numeral not a string" end + local rev = roman:reverse() + local raising = true + local last = 0 + local result = 0 + for i = 1, #rev do + local c = rev:sub(i, i) + local next = roman_numerals[c] + if next == nil then return -1, "roman numeral contains illegal character " .. c end + if next > last then + result = result + next + raising = true + elseif next < last then + result = result - next + raising = false + elseif raising then + result = result + next + else + result = result - next + end + last = next + end + return result +end + +return p \ No newline at end of file diff --git a/Testcases/Sort title.lua b/Testcases/Sort title.lua new file mode 100644 index 0000000..fa222eb --- /dev/null +++ b/Testcases/Sort title.lua @@ -0,0 +1,143 @@ +-- Unit tests for [[Module:Sort title]]. Click talk page to run tests. +local p = require('Module:UnitTests') + +function p:test_1_1_titles_live() + self:preprocess_equals_many('{{#invoke:Sort title|testcases|', '}}', { + {'Lost', "Lost"}, + {'Lost (TV series)', "Lost (TV series)"}, + {'Lost (2004 TV series)', "Lost (2004 TV series)"}, + {'Lost (American TV series)', "Lost (American TV series)"}, + {'The Flash', "Flash, The"}, + {'The Flash (TV series)', "Flash, The (TV series)"}, + {'Pilot (The Flash)', "Pilot (Flash, The)"}, + {'The Trap (The Flash)', "Trap, The (Flash, The)"}, + {'4,722 Hours', "4722 Hours"}, + {'4,722 Hours (Agents of S.H.I.E.L.D.)', "4722 Hours (Agents of S.H.I.E.L.D.)"}, + }) +end + +function p:test_1_2_titles_sandbox() + self:preprocess_equals_many('{{#invoke:Sort title/sandbox|testcases|', '}}', { + {'Lost', "Lost"}, + {'Lost (TV series)', "Lost (TV series)"}, + {'Lost (2004 TV series)', "Lost (2004 TV series)"}, + {'Lost (American TV series)', "Lost (American TV series)"}, + {'The Flash', "Flash, The"}, + {'The Flash (TV series)', "Flash, The (TV series)"}, + {'Pilot (The Flash)', "Pilot (Flash, The)"}, + {'The Trap (The Flash)', "Trap, The (Flash, The)"}, + {'4,722 Hours', "4722 Hours"}, + {'4,722 Hours (Agents of S.H.I.E.L.D.)', "4722 Hours (Agents of S.H.I.E.L.D.)"}, + }) +end + +function p:test_2_1_special_characters_live() + self:preprocess_equals_many('{{#invoke:Sort title|testcases|', '}}', { + {'A', "A"}, + {'An', "An"}, + {'The', "The"}, + {'A (Dab)', "A (Dab)"}, + {'A (A)', "A (A)"}, + {'A Title', "Title, A"}, + {'A Title (Dab)', "Title, A (Dab)"}, + {'A Title (A Dab)', "Title, A (Dab, A)"}, + {'Title (Dab)', "Title (Dab)"}, + {'Title (A Dab)', "Title (Dab, A)"}, + {'The Title', "Title, The"}, + {'An Egg', "Egg, An"}, + {'A. B. and C.', "A. B. and C."}, + {'\'A\' Title', "\'A\' Title"}, + {'A, My Name', "A, My Name"}, + {'...Title', "...Title"}, + {'...A Title', "...A Title"}, + {'4,722 Hours', "4722 Hours"} + }) +end + +function p:test_2_2_special_characters_sandbox() + self:preprocess_equals_many('{{#invoke:Sort title/sandbox|testcases|', '}}', { + {'A', "A"}, + {'An', "An"}, + {'The', "The"}, + {'A (Dab)', "A (Dab)"}, + {'A (A)', "A (A)"}, + {'A Title', "Title, A"}, + {'A Title (Dab)', "Title, A (Dab)"}, + {'A Title (A Dab)', "Title, A (Dab, A)"}, + {'Title (Dab)', "Title (Dab)"}, + {'Title (A Dab)', "Title (Dab, A)"}, + {'The Title', "Title, The"}, + {'An Egg', "Egg, An"}, + {'A. B. and C.', "A. B. and C."}, + {'\'A\' Title', "\'A\' Title"}, + {'A, My Name', "A, My Name"}, + {'...Title', "...Title"}, + {'...A Title', "...A Title"}, + {'4,722 Hours', "4722 Hours"} + }) +end + +--[[ +function p:test_3_1_roman_live() + self:preprocess_equals_many('{{#invoke:Sort title|testcases|', '}}', { + {'Treehouse of Horror', "Treehouse of Horror"}, + {'Treehouse of Horror 6', "Treehouse of Horror 6"}, + {'The Treehouse of Horror VI', "Treehouse of Horror 6, The"}, + {'Words with abbreviation I.V.Not.roman.', "Words with abbreviation I.V.Not.roman."}, + {'Words with abbreviation S.H.I.E.L', "Words with abbreviation S.H.I.E.L"}, + {'Treehouse of Horror I', "Treehouse of Horror 1"}, + {'Treehouse of Horror II', "Treehouse of Horror 2"}, + {'Treehouse of Horror III', "Treehouse of Horror 3"}, + {'Treehouse of Horror IV', "Treehouse of Horror 4"}, + {'Treehouse of Horror V', "Treehouse of Horror 5"}, + {'Treehouse of Horror VI', "Treehouse of Horror 6"}, + {'Treehouse of Horror VII', "Treehouse of Horror 7"}, + {'Treehouse of Horror VIII', "Treehouse of Horror 8"}, + {'Treehouse of Horror IX', "Treehouse of Horror 9"}, + {'Treehouse of Horror X', "Treehouse of Horror 10"}, + {'Treehouse of Horror XI', "Treehouse of Horror 11"}, + {'Treehouse of Horror XII', "Treehouse of Horror 12"}, + {'Treehouse of Horror XIII', "Treehouse of Horror 13"}, + {'Treehouse of Horror XIV', "Treehouse of Horror 14"}, + {'Treehouse of Horror XV', "Treehouse of Horror 15"}, + {'Treehouse of Horror XVI', "Treehouse of Horror 16"}, + {'Treehouse of Horror XVII', "Treehouse of Horror 17"}, + {'Treehouse of Horror XVIII', "Treehouse of Horror 18"}, + {'Treehouse of Horror XIX', "Treehouse of Horror 19"}, + {'Treehouse of Horror XX', "Treehouse of Horror 20"} + }) +end--]] + +function p:test_3_2_roman_sandbox() + self:preprocess_equals_many('{{#invoke:Sort title/sandbox|testcases|', '}}', { + {'Treehouse of Horror', "Treehouse of Horror"}, + {'Treehouse of Horror 6', "Treehouse of Horror 6"}, + {'The Treehouse of Horror VI', "Treehouse of Horror 6, The"}, + {'Words with abbreviation I.V.Not.roman.', "Words with abbreviation I.V.Not.roman."}, + {'Words with abbreviation S.H.I.E.L', "Words with abbreviation S.H.I.E.L"}, + {'Treehouse of Horror I', "Treehouse of Horror 1"}, + {'Treehouse of Horror II', "Treehouse of Horror 2"}, + {'Treehouse of Horror III', "Treehouse of Horror 3"}, + {'Treehouse of Horror IV', "Treehouse of Horror 4"}, + {'Treehouse of Horror V', "Treehouse of Horror 5"}, + {'Treehouse of Horror VI', "Treehouse of Horror 6"}, + {'Treehouse of Horror VII', "Treehouse of Horror 7"}, + {'Treehouse of Horror VIII', "Treehouse of Horror 8"}, + {'Treehouse of Horror IX', "Treehouse of Horror 9"}, + {'Treehouse of Horror X', "Treehouse of Horror 10"}, + {'Treehouse of Horror XI', "Treehouse of Horror 11"}, + {'Treehouse of Horror XII', "Treehouse of Horror 12"}, + {'Treehouse of Horror XIII', "Treehouse of Horror 13"}, + {'Treehouse of Horror XIV', "Treehouse of Horror 14"}, + {'Treehouse of Horror XV', "Treehouse of Horror 15"}, + {'Treehouse of Horror XVI', "Treehouse of Horror 16"}, + {'Treehouse of Horror XVII', "Treehouse of Horror 17"}, + {'Treehouse of Horror XVIII', "Treehouse of Horror 18"}, + {'Treehouse of Horror XIX', "Treehouse of Horror 19"}, + {'Treehouse of Horror XX', "Treehouse of Horror 20"}, + {'7:15 A.M.', "7:15 A.M."}, + {'The House of L', "House of L, The"}, + }) +end + +return p \ No newline at end of file diff --git a/Testcases/SortName.lua b/Testcases/SortName.lua new file mode 100644 index 0000000..bfe705d --- /dev/null +++ b/Testcases/SortName.lua @@ -0,0 +1,20 @@ +local p = require('Module:UnitTests') + +function p:test_sortname() + self:preprocess_equals_many('{{#invoke:SortName|sortname|', '}}', { + { 'Jim Excelsior Tyler', 'Tyler, Jim Excelsior' }, + { 'Jim', 'Jim' }, + { 'List of people named Jim', 'People named Jim' }, + { 'List of jims', 'Jims' }, + { 'Jim Tyler (musician)', 'Tyler, Jim' }, + { 'Jim Tyler, man and myth', 'Tyler, Jim' }, + { 'Jim Tyler of Clan Tyler', 'Tyler, Jim' }, + { 'Jim von Tyler', 'von Tyler, Jim' }, + { 'Jim Von Tyler', 'Von Tyler, Jim' }, + { 'Jim Tyler III', 'Tyler 3, Jim' }, + { 'Jim III', 'Jim 3' }, + { 'Jim Tyler III, Emperor of New Brunswick', 'Tyler 3, Jim' } + }) +end + +return p \ No newline at end of file