Module:Television episode short description: Difference between revisions

From IxWiki
Jump to navigation Jump to search
>Gonnym
added support for |special= and |single_season parameters; cleaned up code
 
display ordinals as numbers instead of words in every description, for consistency. See Template talk:Infobox television episode.
Line 1: Line 1:
-- This module requires the use of Module:ConvertNumeric.
--- @module
local convertNumeric = require('Module:ConvertNumeric')
local television = {}


-- Unique suffix list.
-- Unique suffix list.
local uniqueSuffix = {
local uniqueSuffix = {
[1] = 'st',
[1] = "st",
[2] = 'nd',
[2] = "nd",
[3] = 'rd'
[3] = "rd",
}
}


Line 15: Line 15:
local test = false
local test = false


-- Description list.
local descriptions = {
local descriptionList = {
no_series = {
["NO_SERIES"] = "A television episode",
type = 1,
["ONLY_SERIES_NAME"] = "An episode of ''%s''",
text = "Television episode",
["EPISODE_AND_SERIES_NAME"] = "An episode of the %s season of ''%s''",
category = "[[Category:Television episode articles with short description with no series name|%s]]",
["ALL_VALUES"] = "%s episode%s of the %s season of ''%s''",
},
["SINGLE_SEASON"] = "%s episode%s of ''%s''",
only_series_name = {
["SPECIAL_EPISODE"] = "A %s episode of ''%s''"
type = 2,
text = "Episode of %s",
category = "[[Category:Television episode articles with short description with no season number|%s]]",
},
season_and_series_name = {
type = 3,
text = "Episode of the %s %s of %s",
category = "[[Category:Television episode articles with short description with no episode number|%s]]",
},
single_episode = {
type = 4,
text = "%s episode of the %s %s of %s",
category = "[[Category:Television episode articles with short description for single episodes|%s]]",
},
multi_episodes = {
type = 5,
text = "%s episodes of the %s %s of %s",
category = "[[Category:Television episode articles with short description for multi-part episodes|%s]]",
},
limited_series = {
type = 6,
text = {
single_episode = "%s episode of %s",
multi_episodes = "%s episodes of %s",
},
category = "", -- None
},
special_episode = {
type = 7,
text = "%s episode of %s",
category = "", -- None
},
}
}


-- Tracking category list.
-- Tracking category list.
local trackingCategoryList = {
local trackingCategories = {
["NO_SERIES"] = '[[Category:Television episode articles with short description with no series name|%s]]',
disambiguated = "[[Category:Television episode articles with short description and disambiguated page names|%s]]"
["NO_SEASON_NUMBER"] = '[[Category:Television episode articles with short description with no season number|%s]]',
["NO_EPISODE_NUMBER"] = '[[Category:Television episode articles with short description with no episode number|%s]]',
["SINGLE_EPISODE"] = '[[Category:Television episode articles with short description for single episodes|%s]]',
["MULTI_EPISODE"] = '[[Category:Television episode articles with short description for multi-part episodes|%s]]',
["DISAMBIGUATED_TITLE"] = '[[Category:Television episode articles with short description and disambiguated page names|%s]]'
}
}


local p = {}
--- Returns a tracking category from a list by its name and adds a sort key.
--- @param typeName string The name of the category type.
--- @param useTrackingList boolean Whether to return a category from the trackingCategories list.
--- @param sortKey string The key by which to sort the page in the category.
local function getTrackingCategoryFromList(typeName, useTrackingList, sortKey)
local category
if useTrackingList then
category = trackingCategories[typeName]
else
category = descriptions[typeName].category
end
return string.format(category, sortKey)
end
 
--- Returns true if the article name is disambiguated.
---
--- This is usually in the format of "Episode name (<TV series name>)" or "Episode name (<TV series name> episode)".
--- @param articleTitle string The name of the page.
--- @param tvSeriesName string The TV series name.
local function isDisambiguated(articleTitle, tvSeriesName)
local disambiguation = string.match(tostring(articleTitle), "%s%((.-)%)")


--[[
if not (disambiguation and tvSeriesName) then
Local function which is used to retrieve the ordinal indicator for an integer between 0 and 100.
return false
--]]
local function getOrdinalIndicatorLessThan100(number)
local suffix -- Variable to save the ordinal indicator suffix.
while (not suffix) do -- Initiate a loop that goes on until a suffix has been found.
if (number == 0) then -- Check if the number equals 0; This should never be a valid entry.
suffix = "" -- Assign suffix as an empty string.
elseif (number < 4) then -- Check if the number is less than 4; Numbers "1", "2" and "3" have unique suffixes.
suffix = uniqueSuffix[number] -- It is; Get the unique suffix for that number and assign it.
elseif (number < 20) then -- Check if the number is more than 4 AND less than 20; These numbers all have the same common suffix.
suffix = commonSuffix -- It is; Assign suffix as the common suffix - "th".
elseif (number % 10 == 0) then -- Check if the remainder after division of the number by 10 equals 0.
suffix = commonSuffix -- It is; Assign suffix as the common suffix - "th".
else -- Anything else - numbers that are above 20 and which their remainder doesn't equal 0 (such as 45).
number = number % 10 -- Save the new number to the remainder after division of the number by 100; So if the current number is 45, the new number be 5.
end
end
end
return suffix -- Return the suffix.
end


--[[
-- Search for the TV series name in the article name disambiguation.
Local function which is used to retrieve the ordinal indicator for an integer between 0 and 1000.
if (string.find(disambiguation, tvSeriesName)) then
--]]
return true
local function getOrdinalIndicatorLessThan1000(number)
if (number < 100) then -- Check if the number is less than 100.
return getOrdinalIndicatorLessThan100(number) -- The number is less than 100; Call getOrdinalIndicatorLessThan100() to get the ordinal indicator and return it.
elseif (number % 100 == 0) then -- Check if the remainder after division of the number by 100 equals 0.
return commonSuffix -- It does; Return the common suffix - "th".
else -- Anything else - numbers that are above 100 and which their remainder doesn't equal 0 (such as 345).
return getOrdinalIndicatorLessThan100(number % 100) -- Call getOrdinalIndicatorLessThan100() to get the ordinal indicator and return it;
-- Pass the remainder after division of the number by 100 (So for 345, it would pass 45) as the parameter.
end
end
end


--[[
return false
Local function which is used to create an ordinal number.
--]]
local function getEpisodeOrdinalNumber(number)
local ordinalIndicator = getOrdinalIndicatorLessThan1000(number) -- Call getOrdinalIndicatorLessThan1000() to get the number's ordinal indicator.
return number .. ordinalIndicator -- Create an ordinal number and return it.
end
end


--[[
--- Returns the sort key for the current page.
Local function which retrieves the correct category with a sort key.
--]]
local function getCategory(categoryKey, sortKey)
local category = trackingCategoryList[categoryKey]
return string.format(category, sortKey)
end
--[[
Local function which "Module:Sort title" to retrieve a sortkey.
--]]
local function getSortKey()
local function getSortKey()
local sortkeyModule = require('Module:Sort title')
local sortTitleModule = require("Module:Sort title")
return sortkeyModule._getSortKey()
return sortTitleModule._getSortKey()
end
end


--[[
--- Returns a tracking category depending on the type of short description created.
Local function which is used to check if the article name is disambiguated.
--- @param tvSeriesName string The TV series name.
This is usually in the format of "Episode name (<TV series name>)" or "Episode name (<TV series name> episode)".
--- @param descriptionName string
--]]
local function getTrackingCategory(tvSeriesName, descriptionName)
local function isDisambiguated(articleTitle, tvSeriesName)
local articleTitle = mw.title.getCurrentTitle()
local disambiguation = string.match(tostring(articleTitle), "%s%((.-)%)") -- Get the text inside the disambiguation parentheses.
local namespace = articleTitle.nsText


if (disambiguation and tvSeriesName) then -- Check if the article has parentheses and that the TV series name is not nil.
-- Check if the invoking page is from the allowed namespace.
if (string.find(disambiguation, tvSeriesName)) then -- Article has parentheses; Search for the TV series name in the article name disambiguation.
if (not (namespace == "" or namespace == "Draft" or test)) then
return true -- Article is disambiguated; Return true.
return ""
else
return false -- Article is not disambiguated; Return false.
end
else
return false -- Article does not have parentheses; Return false.
end
end
end


--[[
local sortKey = getSortKey()
Local function which is used to return a relevant tracking category.
if (isDisambiguated(articleTitle, tvSeriesName) == true) then
--]]
local category1 = getTrackingCategoryFromList(descriptionName, false, sortKey)
local function createTrackingCategory(tvSeriesName, categoryKey)
local category2 = getTrackingCategoryFromList("disambiguated", true, sortKey)
local articleTitle = mw.title.getCurrentTitle() -- Get the current page's title.
return category1 .. category2
local namespace = articleTitle.nsText -- Get the invoking namespace.
local sortKey = getSortKey() -- Get sort key.
if (namespace == '' or namespace == 'Draft' or test) then -- Check if the invoking page is from the allowed namespace.
if (isDisambiguated(articleTitle, tvSeriesName) == true) then -- Invoking page is from the allowed namespace; Call isDisambiguated() to check if page is disambiguated.
return getCategory(categoryKey, sortKey) .. getCategory("DISAMBIGUATED_TITLE", sortKey) -- Article is disambiguated; Call getCategory() to retrieve the correct tracking categories and return them.
else
return getCategory(categoryKey, sortKey) -- Article is not disambiguated; Retrieve the correct tracking category and return it.
end
else
return '' -- Invoking page is not from the allowed namespace; Return empty string.
end
end
return getTrackingCategoryFromList(descriptionName, false, sortKey)
end
end


--[[
 
Local function which is used to create a short description in the style of: "A television episode".
--- Returns a short description in the style of: "Television episode" and a maintenance category:
Adds article to the maintenance category: "Category:Television episode articles with short description with no series name".
--- "Category:Television episode articles with short description with no series name".
--]]
local function getShortDescriptionNoSeries()
local function getShortDescriptionNoValues()
local shortDescription = descriptions.no_series.text
return descriptionList["NO_SERIES"], createTrackingCategory(nil, "NO_SERIES")
local category = getTrackingCategory(nil, "no_series")
return shortDescription, category
end
end


--[[
--- Returns a short description in the style of: "Episode of Lost" and a maintenance category:
Local function which is used to create a short description in the style of: "An episode of ''Lost''".
--- "Category:Television episode articles with short description with no season number".
Adds article to the maintenance category: "Category:Television episode articles with short description with no season number".
--- @param tvSeriesName string The TV series name.
--]]
local function getShortDescriptionOnlySeriesName(tvSeriesName)
local function getShortDescriptionNoEpisodeNoSeasonsValues(tvSeriesName)
local text = descriptions.only_series_name.text
return  string.format(descriptionList["ONLY_SERIES_NAME"], tvSeriesName), createTrackingCategory(tvSeriesName, "NO_SEASON_NUMBER")
local shortDescription = string.format(text, tvSeriesName)
local category = getTrackingCategory(tvSeriesName, "only_series_name")
return shortDescription, category
end
end


--[[
--- Returns a short description in the style of: "Episode of the first season of Lost" and a maintenance category:
Local function which is used to create a short description in the style of: "An episode of the first season of ''Lost''".
--- "Category:Television episode articles with short description with no episode number".
Adds article to the maintenance category: "Category:Television episode articles with short description with no episode number".
--- @param tvSeriesName string The TV series name.
--]]
--- @param seasonOrdinalNumber string The season's ordinal number.
local function getShortDescriptionNoEpisodeValue(seasonOrdinalNumber, tvSeriesName)
--- @param seasonTextStyle string The text to use for seasons - either "season" or "series".
return string.format(descriptionList["EPISODE_AND_SERIES_NAME"], seasonOrdinalNumber, tvSeriesName), createTrackingCategory(tvSeriesName, "NO_EPISODE_NUMBER")
local function getShortDescriptionSeasonAndSeriesName(tvSeriesName, seasonOrdinalNumber, seasonTextStyle)
local text = descriptions.season_and_series_name.text
local shortDescription = string.format(text, seasonOrdinalNumber, seasonTextStyle, tvSeriesName)
local category = getTrackingCategory(tvSeriesName, "season_and_series_name")
return shortDescription, category
end
end


--[[
--- Returns a short description for a limited series in the style of: "1st episode of WandaVision" and a tracking category
Local function which is used to create a short description for single season episodes in the style of: "1st episode of ''Lost''".
--- based on the categoryKey value.
Adds article to the tracking category: "Category:Television episode articles with short description for single episodes".
--- @param tvSeriesName string The TV series name.
--]]
--- @param episodeOrdinalNumber string The episode's ordinal number.
local function getShortDescriptionSingleSeason(episodeOrdinalNumber, plural, tvSeriesName, category)
--- @param descriptionName string A key from the descriptions table.
return string.format(descriptionList["SINGLE_SEASON"], episodeOrdinalNumber, plural, tvSeriesName), createTrackingCategory(tvSeriesName, category)
local function getShortDescriptionLimitedSeries(tvSeriesName, episodeOrdinalNumber, descriptionName)
local text = descriptions.limited_series.text[descriptionName]
local shortDescription = string.format(text, episodeOrdinalNumber, tvSeriesName)
local category = getTrackingCategory(tvSeriesName, descriptionName)
return shortDescription, category
end
end


--[[
--- Returns a short description in the style of: "5th episode of the fourth season of Lost" and a tracking category:
Local function which is used to create a short description in the style of: "5th episode of the fourth season of ''Lost''".
--- "Category:Television episode articles with short description for single episodes".
Adds article to the tracking category: "Category:Television episode articles with short description for single episodes".
--- @param tvSeriesName string The TV series name.
--]]
--- @param seasonOrdinalNumber string The season's ordinal number.
local function getShortDescriptionSingleEpisode(episodeOrdinalNumber, seasonOrdinalNumber, tvSeriesName, singleSeason)
--- @param seasonTextStyle string The text to use for seasons - either "season" or "series".
if (singleSeason) then
--- @param episodeOrdinalNumber string The episode's ordinal number.
return getShortDescriptionSingleSeason(episodeOrdinalNumber, "", tvSeriesName, "SINGLE_EPISODE")
--- @param limitedSeries boolean Whether the episode belongs to a limited series.
else
local function getShortDescriptionSingleEpisode(tvSeriesName, seasonOrdinalNumber, seasonTextStyle, episodeOrdinalNumber, limitedSeries)
return string.format(descriptionList["ALL_VALUES"], episodeOrdinalNumber, "", seasonOrdinalNumber, tvSeriesName), createTrackingCategory(tvSeriesName, "SINGLE_EPISODE")
if (limitedSeries) then
return getShortDescriptionLimitedSeries(tvSeriesName, episodeOrdinalNumber,"single_episode")
end
end
local text = descriptions.single_episode.text
local shortDescription =  string.format(text, episodeOrdinalNumber, seasonOrdinalNumber, seasonTextStyle, tvSeriesName)
local category = getTrackingCategory(tvSeriesName, "single_episode")
return shortDescription, category
end
end


--[[
--- Returns a short description for a multi-part episode in the style of:
Local function which is used to create a short description for a multi-part episode in the style of: "23rd and 24th episodes of the third season of ''Lost''".
--- "23rd and 24th episodes of the third season of Lost" and a tracking category:
Adds article to the tracking category: "Category:Television episode articles with short description for multi-part episodes".
--- "Category:Television episode articles with short description for multi-part episodes".
--]]
--- @param tvSeriesName string The TV series name.
local function getShortDescriptionMultiEpisode(episodeOrdinalNumber, episodeNumber, seasonOrdinalNumber, tvSeriesName, multiEpisodes, singleSeason)
--- @param seasonOrdinalNumber string The season's ordinal number.
local episodeOrdinalList = {episodeOrdinalNumber}
--- @param seasonTextStyle string The text to use for seasons - either "season" or "series".
-- Check if the |multi_episodes value was a number or a "yes" string.
--- @param episodeOrdinalNumbers table A list of episode ordinal numbers.
if (tonumber(multiEpisodes)) then
--- @param limitedSeries boolean Whether the episode belongs to a limited series.
multiEpisodes = tonumber(multiEpisodes)
local function getShortDescriptionMultiEpisode(tvSeriesName, seasonOrdinalNumber, seasonTextStyle, episodeOrdinalNumbers, limitedSeries)
-- If the value was entered as 1, this isn't a multi-episode.
local episodeText = mw.text.listToText(episodeOrdinalNumbers)
if (multiEpisodes == 1) then
 
return getShortDescriptionSingleEpisode(episodeOrdinalNumber, seasonOrdinalNumber, tvSeriesName)
if (limitedSeries) then
end
return getShortDescriptionLimitedSeries(tvSeriesName, episodeText, "multi_episodes")
-- Go over the amount entered minus 1 (as the first episode ordinal is already known).
for i = 1, multiEpisodes - 1 do
table.insert(episodeOrdinalList, getEpisodeOrdinalNumber(episodeNumber + i))
end
else
-- The value entered was "yes", use as default 2 episodes.
table.insert(episodeOrdinalList, getEpisodeOrdinalNumber(episodeNumber + 1))
end
local episodeText = mw.text.listToText(episodeOrdinalList)
if (singleSeason) then
return getShortDescriptionSingleSeason(episodeText, "s", tvSeriesName, "MULTI_EPISODE")
else
return string.format(descriptionList["ALL_VALUES"], episodeText, "s", seasonOrdinalNumber, tvSeriesName), createTrackingCategory(tvSeriesName, "MULTI_EPISODE")
end
end
local text = descriptions.multi_episodes.text
local shortDescription = string.format(text, episodeText, seasonOrdinalNumber, seasonTextStyle, tvSeriesName)
local category = getTrackingCategory(tvSeriesName, "multi_episodes")
return shortDescription, category
end
end


--[[
--- Returns a short description for a special episode in the style of:
Local function which is used to create a short description for a special episode in the style of: "A special episode of ''Lost''" or "A <value used for |special=> episode of ''Lost''"
--- "Special episode of Lost" or "<value> episode of Lost" and a tracking category:
Adds article to the tracking category: "Category:Television episode articles with short description for single episodes".
--- "Category:Television episode articles with short description for single episodes".
--]]
--- @param tvSeriesName string The TV series name.
local function getShortDescriptionSpecialEpisode(special, tvSeriesName)
--- @param special string The type of special episode. A "yes" value defaults to "Special".
local function getShortDescriptionSpecialEpisode(tvSeriesName, special)
if (special == "yes" or special == "y") then
if (special == "yes" or special == "y") then
special = "special"
special = "Special"
end
end
return string.format(descriptionList["SPECIAL_EPISODE"], special, tvSeriesName), createTrackingCategory(tvSeriesName, "SINGLE_EPISODE")
local text = descriptions.special_episode.text
local shortDescription = string.format(text, special, tvSeriesName)
local category = getTrackingCategory(tvSeriesName, "single_episode")
return shortDescription, category
end
end


--[[
--- Returns a short description based on the description type passed.
Local function which is used to validate if data was entered into a parameter of type number.
--- @param descriptionType number A description type number.
--]]
--- @param tvSeriesName string The TV series name.
local function validateNumberParam(number)
--- @param seasonOrdinalNumber string The season's ordinal number.
if (tonumber(number)) then -- Convert the string into a number and check if the value equals nil (conversion failed).
--- @param seasonTextStyle string The text to use for seasons - either "season" or "series".
return true -- Param is a number; Return true.
--- @param episodeOrdinalNumbers table A list of episode ordinal numbers.
--- @param specialEpisode string The type of special episode.
--- @param limitedSeries boolean Whether the episode belongs to a limited series.
local function getShortDescriptionByType(
descriptionType, tvSeriesName, seasonOrdinalNumber, seasonTextStyle, episodeOrdinalNumbers, specialEpisode, limitedSeries)
if descriptionType == descriptions.no_series.type then
return getShortDescriptionNoSeries()
elseif descriptionType == descriptions.only_series_name.type then
return getShortDescriptionOnlySeriesName(tvSeriesName)
elseif descriptionType == descriptions.season_and_series_name.type then
return getShortDescriptionSeasonAndSeriesName(tvSeriesName, seasonOrdinalNumber, seasonTextStyle)
elseif descriptionType == descriptions.single_episode.type then
return getShortDescriptionSingleEpisode(
                tvSeriesName, seasonOrdinalNumber, seasonTextStyle, episodeOrdinalNumbers[1], limitedSeries)
elseif descriptionType == descriptions.multi_episodes.type then
return getShortDescriptionMultiEpisode(
tvSeriesName, seasonOrdinalNumber, seasonTextStyle, episodeOrdinalNumbers, limitedSeries)
elseif descriptionType == descriptions.special_episode.type then
return getShortDescriptionSpecialEpisode(tvSeriesName, specialEpisode)
else
else
return false -- Param is either empty or not a number; Return false.
return ""
end
end
end
end


--[[
--- Returns the type of the description to use.
Local function which is used to return a clean version of the number.
--- @param tvSeriesName string The TV series name.
This is done to make sure that no malformed episode or season values
--- @param seasonOrdinalNumber string The season's ordinal number.
have been entered. The function will remove all text which is not part
--- @param episodeOrdinalNumbers table A list of episode ordinal numbers.
of the first number in the string.
--- @param specialEpisode string The type of special episode.
--- @param limitedSeries boolean Whether the episode belongs to a limited series.
local function getDescriptionType(tvSeriesName, seasonOrdinalNumber, episodeOrdinalNumbers, specialEpisode, limitedSeries)
if (not tvSeriesName) then
return descriptions.no_series.type
end
 
if (specialEpisode) then
return descriptions.special_episode.type
end
 
if (not seasonOrdinalNumber and not limitedSeries) then
return descriptions.only_series_name.type
end
 
if (#episodeOrdinalNumbers < 1) then
return descriptions.season_and_series_name.type
end
 
if (#episodeOrdinalNumbers == 1) then
return descriptions.single_episode.type
end


The function converts entries such as:
if (#episodeOrdinalNumbers > 1) then
-- "1.2" -> "1"
return descriptions.multi_episodes.type
-- "12.2" -> "12"
-- "1<ref name="number" />" -> "1"
--]]
local function getCleanNumber(number)
if (number) then -- Check if the number is not nil (some kind of value was entered).
return string.match(number, '%d+') -- The value is not null; Clean the number, if needed.
else
return nil -- The number is nil; Return nil.
end
end
end
end


--[[
--- Returns true if the TV series is a limited series.
Local function which is used to create a short description
--- @param limitedSeries string Any value will be considered as true.
by validating if a "multi_episodes" value was entered.
local function isLimitedSeries(limitedSeries)
--]]
if (limitedSeries) then
local function createDescriptionValidateEpisodeValue(args, tvSeriesName, seasonOrdinalNumber)
return true
local episodeNumber = getCleanNumber(args['episode_num']) -- Call getCleanNumber() to return a cleaned version of the number.
end
episodeNumber = tonumber(episodeNumber) -- Convert the value into a number.
return false
end


if (validateNumberParam(episodeNumber)) then -- Call validateNumberParam() to check if an episode number was entered.
--- Returns the ordinal indicator for an integer between 0 and 100.
local episodeOrdinalNumber = getEpisodeOrdinalNumber(episodeNumber) -- A number was entered; Call getEpisodeOrdinalNumber() to get the episode ordinal number.
---
local multiEpisodes = args['multi_episodes']
--- Numbers "1", "2" and "3" have unique suffixes.
local singleSeason = false
--- Numbers between 4 and 20 have the same common suffix - "th".
if (seasonOrdinalNumber == -1) then
--- Numbers ending with 0 have the same common suffix - "th".
singleSeason = true
--- @param number number A number value.
end
local function getOrdinalIndicatorLessThan100(number)
if (multiEpisodes) then -- Check if a |multi_episodes= value was entered.
local suffix
return getShortDescriptionMultiEpisode(episodeOrdinalNumber, episodeNumber, seasonOrdinalNumber, tvSeriesName, multiEpisodes, singleSeason) -- A |multi_episodes= value was entered; Call getShortDescriptionMultiEpisode().
while (not suffix) do
-- Check if the number equals 0; This should never be a valid entry. Assign suffix as an empty string.
if (number == 0) then
suffix = ""
-- Check if the number is less than 4; Numbers "1", "2" and "3" have unique suffixes.
elseif (number < 4) then
suffix = uniqueSuffix[number]
-- Check if the number is more than 4 AND less than 20; These numbers all have the same common suffix.
elseif (number < 20) then
suffix = commonSuffix
-- Check if the remainder after division of the number by 10 equals 0.
elseif (number % 10 == 0) then
suffix = commonSuffix
else
else
return getShortDescriptionSingleEpisode(episodeOrdinalNumber, seasonOrdinalNumber, tvSeriesName, singleSeason) -- A |multi_episodes= value was not entered; Call getShortDescriptionSingleEpisode().
-- Numbers that are above 20 and which their remainder doesn't equal 0 (such as 45).
-- Remainder after division of the number by 10; So if the current number is 45, the new number is 5.
number = number % 10
end
end
end
return suffix
end
--- Returns the ordinal indicator for an integer between 0 and 1000.
--- @param number number A number value.
local function getOrdinalIndicatorLessThan1000(number)
if (number < 100) then
return getOrdinalIndicatorLessThan100(number)
elseif (number % 100 == 0) then
return commonSuffix
else
else
return getShortDescriptionNoEpisodeValue(seasonOrdinalNumber, tvSeriesName) -- A an episode number was not entered; Call getShortDescriptionNoEpisodeValue().
-- Numbers that are above 100 and which their remainder doesn't equal 0 (such as 345).
-- Pass the remainder after division of the number by 100 (So for 345, it would pass 45) as the parameter.
return getOrdinalIndicatorLessThan100(number % 100)
end
end
 
--- Returns a table of episode numbers.
---
--- Episode values may be of multipart episodes, in such situations, an episode may be seperated by one of the following:
--- ",", "/", "&", "-", "–", "and".
--- Decimal values and episode overall values sometimes erroneously used are removed.
--- @param number string A number value in string format.
local function cleanEpisodeNumber(number)
if (not number) then
return {}
end
 
number = string.gsub(number, "%(.*%)", " ")
number = string.gsub(number, "%.%d+", " ")
 
local numbers = {}
for digits in string.gmatch(number, "%d+") do
table.insert(numbers, tonumber(digits))
end
return numbers
end
 
--- Returns a table of episode ordinal numbers.
---
--- In most situations there will be only one episode, but this can support more.
--- @param episodeNumber number The episode's number.
local function getEpisodeOrdinalNumbers(episodeNumber)
local episodeNumbers = cleanEpisodeNumber(episodeNumber)
 
if (#episodeNumbers < 1) then
return episodeNumbers
end
end
local episodeOrdinals = {}
for _, cleanedEpisodeNumber in pairs(episodeNumbers) do
local ordinalIndicator = getOrdinalIndicatorLessThan1000(cleanedEpisodeNumber)
table.insert(episodeOrdinals, cleanedEpisodeNumber .. ordinalIndicator)
end
return episodeOrdinals
end
end


--[[
--- Returns true if the season number value is a number.
Local function which is used to retrieve the season number, since it can be entered in
--- @param seasonNumber string The season number value in string format.
either the "season" or "series_no" params.
local function validateSeasonNumber(seasonNumber)
--]]
if (tonumber(seasonNumber)) then
local function getSeasonNumber(seasonNumber, seasonNumberUK)
return true
seasonNumber = getCleanNumber(seasonNumber) -- Call getCleanNumber() to return a cleaned version of the number.
seasonNumberUK = getCleanNumber(seasonNumberUK) -- Call getCleanNumber() to return a cleaned version of the number.
if (validateNumberParam(seasonNumber)) then -- Call validateNumberParam() to check if the value in the "|season_num" ("season") param is a number.
return seasonNumber -- It is; Return value.
elseif (validateNumberParam(seasonNumberUK)) then -- Call validateNumberParam() to check if the value in the "|season_num_uk" ("series_no") param is a number.
return seasonNumberUK -- It is; Return value.
else
else
return "" -- Anything else - value not entered. Return empty string.
return false
end
end
 
--- Returns the season's ordinal number, or nil if no season number was set.
--- @param seasonNumber string The season number.
local function getSeasonOrdinalNumber(seasonNumber)
if (seasonNumber) then
local convertOrdinal = require("Module:Ordinal")
return convertOrdinal._ordinal(seasonNumber)
end
end
return nil
end
end


--[[
--- Returns a season number after removing from it unwanted characters.
Local function which is used to create a short description by validating if a season number was entered.
---
--]]
--- This is done to make sure that no malformed season values have been entered.
local function createDescriptionValidateSeasonValue(args, tvSeriesName)
--- The function will remove all text which is not part of the first number in the string.
local seasonNumber = getSeasonNumber(args['season_num'], args['season_num_uk']) -- Call getSeasonNumber() to get the season number, as it can be in one of two fields.
---
if (validateNumberParam(seasonNumber)) then -- Call validateNumberParam() to check if a season number was entered.
--- The function converts entries such as:
local seasonOrdinalNumber = convertNumeric.spell_number2({num = seasonNumber, ordinal = true}) -- A season number was entered; Call spell_number2() from Module:ConvertNumeric to get the season ordinal number.
--- "1.2" -> "1"
return createDescriptionValidateEpisodeValue(args, tvSeriesName, seasonOrdinalNumber) -- Call createDescriptionValidateEpisodeValue() to continue validation process.
--- "12.2" -> "12"
elseif (args['single_season']) then -- A season number was not entered; Check if a |single_season= value was entered.
--- @param seasonNumber string The season number value in string format.
return createDescriptionValidateEpisodeValue(args, tvSeriesName, -1) -- |single_season= was entered; Call createDescriptionValidateEpisodeValue().
local function cleanSeasonNumber(seasonNumber)
elseif (args['special']) then -- Check if a |special= value was entered.
if (seasonNumber) then
return getShortDescriptionSpecialEpisode(args['special'], tvSeriesName) -- Call getShortDescriptionSpecialEpisode().
return string.match(seasonNumber, "%d+")
else
return getShortDescriptionNoEpisodeNoSeasonsValues(tvSeriesName) -- A special value was not entered; Call getShortDescriptionNoEpisodeNoSeasonsValues().
end
end
return nil
end
end


--[[
--- Returns the season number after or cleaning it from unwanted values and validating value is a number.
Local function which is used to create a short description.
--- Also returns the text style to use - either "season" or "series".
This creates a description by a process of validating which values have values.
--- If no value was entered or if value was not a number, return nil.
These are the following options:
--- @param seasonNumber string The season number.
-- If no |series_name= was entered, it calls getShortDescriptionNoValues().
--- @param seasonNumberUK string The season number, if UK style was used.
-- If only |series_name= and |season_num= or |season_num_uk= were entered, it calls getShortDescriptionNoEpisodeValue().
local function getSeasonNumberAndTextStyle(seasonNumber, seasonNumberUK)
-- If all information was entered and |multi_episodes= was not entered, it calls getShortDescriptionSingleEpisode().
    for _, v in ipairs({{seasonNumber, "season"}, {seasonNumberUK, "series"}}) do
-- If all information and |multi_episodes= was entered, it calls getShortDescriptionDoubleEpisode().
        local cleanedSeasonNumber = cleanSeasonNumber(v[1])
-- If |series_name= and |special= was entered, it calls getShortDescriptionSpecialEpisode().
        if (validateSeasonNumber(cleanedSeasonNumber)) then
-- If |series_name=, |episode_num= and |no_season= were entered, it calls getShortDescriptionNoSeason().
            return cleanedSeasonNumber, v[2]
--]]
        end
local function getDescription(args)
    end
local tvSeriesName = args['series_name']
return nil
if (tvSeriesName) then -- Check if a TV series name was entered.
end
if (not args['not_dab']) then -- A TV series name was entered; Check if a not_dab value was entered.
 
tvSeriesName = string.gsub(tvSeriesName, "%s+%b()$", "", 1, false) -- A |not_dab= value was not entered; Get the article title without the disambiguation.
--- Returns the TV series title without disambiguation, or nil if no TV series name was set.
--- @param tvSeriesName string The TV series name.
--- @param notDab string If set, the parenthesis in the title is not disambiguation.
local function getTVSeriesName(tvSeriesName, notDab)
if (tvSeriesName) then
if (not notDab) then
return string.gsub(tvSeriesName, "%s+%b()$", "", 1, false)
end
end
return createDescriptionValidateSeasonValue(args, tvSeriesName) -- Call createDescriptionValidateSeasonValue() to continue validation process.
return tvSeriesName
else
return getShortDescriptionNoValues() -- A TV series name was not entered; Call getShortDescriptionNoValues().
end
end
return nil
end
end


--[[
--- Returns the initial values after removing unwanted characters.
Local function which is used to clean the values from unwanted characters.
--- @param args table The values that should be processed.
--]]
local function cleanValues(args)
local function getCleanValues(args)
for _, v in ipairs({"episode_num", "season_num", "season_num_uk", "series_name"}) do
for _, v in ipairs({'episode_num', 'season_num', 'season_num_uk', 'series_name'}) do
if (args[v]) then
if (args[v]) then
args[v] = args[v]:gsub('\127[^\127]*UNIQ%-%-(%a+)%-%x+%-QINU[^\127]*\127', '') -- Remove all strip-markers.
args[v] = args[v]:gsub("\127[^\127]*UNIQ%-%-(%a+)%-%x+%-QINU[^\127]*\127", "") -- Remove all strip-markers.
args[v] = args[v]:gsub('</? *br */?>', ' ') -- Replace <br /> (and variants) with space character.
args[v] = args[v]:gsub("</? *br */?>", " ") -- Replace <br /> (and variants) with space character.
args[v] = args[v]:gsub('%b<>[^<]+%b<>', '') -- Remove html markup.
args[v] = args[v]:gsub("%b<>[^<]+%b<>", "") -- Remove html markup.
args[v] = args[v]:gsub('%b<>', '') -- Remove self-closed html tags.
args[v] = args[v]:gsub("%b<>", "") -- Remove self-closed html tags.
args[v] = args[v]:gsub('%[%[[^|]+|([^%]]+)%]%]', '%1') -- Remove wiki-link retain label.
args[v] = args[v]:gsub("%[%[[^|]+|([^%]]+)%]%]", "%1") -- Remove wiki-link retain label.
args[v] = args[v]:gsub('%[%[([^%]]+)%]%]', '%1') -- Remove wiki-link retain article.
args[v] = args[v]:gsub("%[%[([^%]]+)%]%]", "%1") -- Remove wiki-link retain article.
args[v] = args[v]:gsub('%[%S+ +([^%]]-)%]', '%1') -- Remove URLs retain label.
args[v] = args[v]:gsub("%[%S+ +([^%]]-)%]", "%1") -- Remove URLs retain label.
args[v] = args[v]:gsub('%[[^%]]-%]', '') -- Remove all remaining URLs.
args[v] = args[v]:gsub("%[[^%]]-%]", "") -- Remove all remaining URLs.


if (args[v] == '') then -- Check if the value is an empty string.
if (args[v] == "") then -- Check if the value is an empty string.
args[v] = nil -- The value is an empty string; Set it to nil.
args[v] = nil -- The value is an empty string; Set it to nil.
end
end
end
end
end
end
return args -- Return args.
return args
end
end


--[[
--- Public function - main process.
Public function which does the actual main process.
--- @param frame table The frame invoking the module.
--]]
--- @param args table The key-value parameters passed to the module.
function p._getShortDescription(frame, args)
function television._getShortDescription(frame, args)
args = getCleanValues(args) -- Call getCleanValues() to remove all unwanted characters.
args = cleanValues(args)
local shortDescription, trackingCat = getDescription(args) -- Call getDescription() and return two values: the episode's short description and tracking category.
local tvSeriesName = getTVSeriesName(args.series_name, args.not_dab)
    local seasonNumber, seasonTextStyle = getSeasonNumberAndTextStyle(args.season_num, args.season_num_uk)
local seasonOrdinalNumber = getSeasonOrdinalNumber(seasonNumber)
local episodeOrdinalNumbers = getEpisodeOrdinalNumbers(args.episode_num)
local limitedSeries = isLimitedSeries(args.limited)
local descriptionType = getDescriptionType(
tvSeriesName,
seasonOrdinalNumber,
episodeOrdinalNumbers,
args.special,
limitedSeries
)
 
local shortDescription, trackingCat = getShortDescriptionByType(
descriptionType,
tvSeriesName,
seasonOrdinalNumber,
            seasonTextStyle,
episodeOrdinalNumbers,
args.special,
limitedSeries
)


-- Check if the invoking page is from /testcases or /doc pages.
-- Check if the invoking page is from /testcases or /doc pages.
if (args['test']) then
if (args.test) then
return shortDescription, trackingCat
return shortDescription, trackingCat
elseif (args['doc']) then
elseif (args.doc) then
return shortDescription
return shortDescription
else
else
local tableData = {shortDescription, 'noreplace'} -- Invoking page isn't a test or doc; Create a table for the short description parameter.
local tableData = {shortDescription, "noreplace"}
return frame:expandTemplate({title = 'short description', args = tableData}) .. trackingCat -- Return expanded short description with tracking category.
return frame:expandTemplate({title = "short description", args = tableData}) .. trackingCat
end
end
end
end


--[[
--- Public function which is used to create a television episode's short description
Public function which is used to create a television episode's short description
--- from the data available in [Template:Infobox television episode].
from the data available in [Template:Infobox television episode].
--- A suitable description will be generated depending on the values of the various parameters.
A suitable description will be generated depending on the values
--- See documentation for examples.
of the various parameters. See documentation for examples.
---
--- Parameters:
--- |episode_num= — optional; The episode's number.
--- |season_num= — optional; The season's number.
--- |season_num_uk= — optional; The season's number if using the British "series" term.
--- |series_name= — optional; The TV series name.
--- |not_dab= — optional; Set if the TV series name has parentheses as part of its name.
--- |special= — optional; Setting to "yes" will set the description as a "special episode".
--- Any other value will replace the word "special" with the one entered.
--- For example "special=recap" will create "recap episode".
--- |limited= — optional; Set if the series is a single season series, such as miniseries or limited series
--- and does not need a season number as part of the description.
--- @param frame table The frame invoking the module.
function television.getShortDescription(frame)
local getArgs = require("Module:Arguments").getArgs
local args = getArgs(frame)
return television._getShortDescription(frame, args)
end


Parameters:
-- |episode_num= — optional; The episode's number.
-- |season_num= — optional; The season's number.
-- |season_num_uk= — optional; The season's number if using the British "series" term.
-- |series_name= — optional; The TV series name.
-- |multi_episodes= — optional; Setting "yes" will default to a two-part episode.
If there are more than 2 parts, set the value to the number of parts.
-- |not_dab= — optional; Set if the TV series name has parentheses as part of its name.
-- |special= — optional; Setting to "yes" will set the description as a "special episode".
Any other value will replace the word "special" with the one entered. For example "special=recap" will create "recap episode".
-- |single_season= — optional; Set if the series is a single season series, such as miniseries or limited series and does not need "1st season" as part of the description.
--]]
function p.getShortDescription(frame)
local getArgs = require('Module:Arguments').getArgs -- Use Module:Arguments to access module arguments.
local args = getArgs(frame) -- Get the arguments sent via the template.


return p._getShortDescription(frame, args) -- Call _getShortDescription() to perform the actual process.
--- Public function which is used for testing output only.
end
--- @param frame table The frame invoking the module.
function television.test(frame)
local getArgs = require("Module:Arguments").getArgs
local args = getArgs(frame)
 
test = args.test
local shortDescription, categories = television._getShortDescription(frame, args)


--[[
Public function which is used for testing only.
--]]
function p.test(frame)
local getArgs = require('Module:Arguments').getArgs
local args = getArgs(frame)
test = args['test'] -- This param should only be used by tests runned through /testcases.
local shortDescription, categories = p._getShortDescription(frame, args)
if (test == "cat") then
if (test == "cat") then
return categories
return categories
Line 414: Line 540:
end
end


return p
return television

Revision as of 09:29, 29 August 2022

Module:Television episode short description extracts data from a television episode article's Template:Infobox television episode and creates a relevant short description based on the data available.

This module implements the {{Television episode short description}} template.

Usage

Parameter list

<section begin=Parameters/> The parameter names use a corresponding parameter from Template:Infobox television episode to fill in the data. They are listed below.

Parameter Corresponding infobox parameter Description
series_name series The TV series name.
episode_num episode The episode's number.
season_num season The season's number.
season_num_uk series_no The season's number if using the British "series" term.
not_dab same name Set if the TV series name has parentheses as part of its name.
special same name Setting to "yes" will set the description as a "special episode". Any other value will replace the word "special" with the one entered. For example |special=recap will set the text to "recap episode".
limited same name Set if the series is a single season series, such as miniseries or limited series and does not need a season number as part of the description.

<section end=Parameters/>

Examples

Issues

<section begin=Issues/>

  1. If an article does not show the short description with the data from the infobox, make sure you entered the parameter names correctly in the infobox.<section end=Issues/>

Tracking categories

<section begin=Categories/>

See also


--- @module
local television = {}

-- Unique suffix list.
local uniqueSuffix = {
	[1] = "st",
	[2] = "nd",
	[3] = "rd",
}

-- Common suffix.
local commonSuffix = "th"

-- Test validation.
local test = false

local descriptions = {
	no_series = {
		type = 1,
		text = "Television episode",
		category = "[[Category:Television episode articles with short description with no series name|%s]]",
	},
	only_series_name = {
		type = 2,
		text = "Episode of %s",
		category = "[[Category:Television episode articles with short description with no season number|%s]]",
	},
	season_and_series_name = {
		type = 3,
		text = "Episode of the %s %s of %s",
		category = "[[Category:Television episode articles with short description with no episode number|%s]]",
	},
	single_episode = {
		type = 4,
		text = "%s episode of the %s %s of %s",
		category = "[[Category:Television episode articles with short description for single episodes|%s]]",
	},
	multi_episodes = {
		type = 5,
		text = "%s episodes of the %s %s of %s",
		category = "[[Category:Television episode articles with short description for multi-part episodes|%s]]",
	},
	limited_series = {
		type = 6,
		text = {
			single_episode = "%s episode of %s",
			multi_episodes = "%s episodes of %s",
		},
		category = "", -- None
	},
	special_episode = {
		type = 7,
		text = "%s episode of %s",
		category = "", -- None
	},
}

-- Tracking category list.
local trackingCategories = {
	disambiguated = "[[Category:Television episode articles with short description and disambiguated page names|%s]]"
}

--- Returns a tracking category from a list by its name and adds a sort key.
--- @param typeName string The name of the category type.
--- @param useTrackingList boolean Whether to return a category from the trackingCategories list.
--- @param sortKey string The key by which to sort the page in the category.
local function getTrackingCategoryFromList(typeName, useTrackingList, sortKey)
	local category
	if useTrackingList then
		category = trackingCategories[typeName]
	else
		category = descriptions[typeName].category
	end
	return string.format(category, sortKey)
end

--- Returns true if the article name is disambiguated.
---
--- This is usually in the format of "Episode name (<TV series name>)" or "Episode name (<TV series name> episode)".
--- @param articleTitle string The name of the page.
--- @param tvSeriesName string The TV series name.
local function isDisambiguated(articleTitle, tvSeriesName)
	local disambiguation = string.match(tostring(articleTitle), "%s%((.-)%)")

	if not (disambiguation and tvSeriesName) then
		return false
	end

	-- Search for the TV series name in the article name disambiguation.
	if (string.find(disambiguation, tvSeriesName)) then
		return true
	end

	return false
end

--- Returns the sort key for the current page.
local function getSortKey()
	local sortTitleModule = require("Module:Sort title")
	return sortTitleModule._getSortKey()
end

--- Returns a tracking category depending on the type of short description created.
--- @param tvSeriesName string The TV series name.
--- @param descriptionName string
local function getTrackingCategory(tvSeriesName, descriptionName)
	local articleTitle = mw.title.getCurrentTitle()
	local namespace = articleTitle.nsText

	-- Check if the invoking page is from the allowed namespace.
	if (not (namespace == "" or namespace == "Draft" or test)) then
		return ""
	end

	local sortKey = getSortKey()
	if (isDisambiguated(articleTitle, tvSeriesName) == true) then
		local category1 = getTrackingCategoryFromList(descriptionName, false, sortKey)
		local category2 = getTrackingCategoryFromList("disambiguated", true, sortKey)
		return category1 .. category2
	end

	return getTrackingCategoryFromList(descriptionName, false, sortKey)
end


--- Returns a short description in the style of: "Television episode" and a maintenance category:
--- "Category:Television episode articles with short description with no series name".
local function getShortDescriptionNoSeries()
	local shortDescription = descriptions.no_series.text
	local category = getTrackingCategory(nil, "no_series")
	return shortDescription, category
end

--- Returns a short description in the style of: "Episode of Lost" and a maintenance category:
--- "Category:Television episode articles with short description with no season number".
--- @param tvSeriesName string The TV series name.
local function getShortDescriptionOnlySeriesName(tvSeriesName)
	local text = descriptions.only_series_name.text
	local shortDescription = string.format(text, tvSeriesName)
	local category = getTrackingCategory(tvSeriesName, "only_series_name")
	return shortDescription, category
end

--- Returns a short description in the style of: "Episode of the first season of Lost" and a maintenance category:
--- "Category:Television episode articles with short description with no episode number".
--- @param tvSeriesName string The TV series name.
--- @param seasonOrdinalNumber string The season's ordinal number.
--- @param seasonTextStyle string The text to use for seasons - either "season" or "series".
local function getShortDescriptionSeasonAndSeriesName(tvSeriesName, seasonOrdinalNumber, seasonTextStyle)
	local text = descriptions.season_and_series_name.text
	local shortDescription = string.format(text, seasonOrdinalNumber, seasonTextStyle, tvSeriesName)
	local category = getTrackingCategory(tvSeriesName, "season_and_series_name")
	return shortDescription, category
end

--- Returns a short description for a limited series in the style of: "1st episode of WandaVision" and a tracking category
--- based on the categoryKey value.
--- @param tvSeriesName string The TV series name.
--- @param episodeOrdinalNumber string The episode's ordinal number.
--- @param descriptionName string A key from the descriptions table.
local function getShortDescriptionLimitedSeries(tvSeriesName, episodeOrdinalNumber, descriptionName)
	local text = descriptions.limited_series.text[descriptionName]
	local shortDescription = string.format(text, episodeOrdinalNumber, tvSeriesName)
	local category = getTrackingCategory(tvSeriesName, descriptionName)
	return shortDescription, category
end

--- Returns a short description in the style of: "5th episode of the fourth season of Lost" and a tracking category:
--- "Category:Television episode articles with short description for single episodes".
--- @param tvSeriesName string The TV series name.
--- @param seasonOrdinalNumber string The season's ordinal number.
--- @param seasonTextStyle string The text to use for seasons - either "season" or "series".
--- @param episodeOrdinalNumber string The episode's ordinal number.
--- @param limitedSeries boolean Whether the episode belongs to a limited series.
local function getShortDescriptionSingleEpisode(tvSeriesName, seasonOrdinalNumber, seasonTextStyle, episodeOrdinalNumber, limitedSeries)
	if (limitedSeries) then
		return getShortDescriptionLimitedSeries(tvSeriesName, episodeOrdinalNumber,"single_episode")
	end

	local text = descriptions.single_episode.text
	local shortDescription =  string.format(text, episodeOrdinalNumber, seasonOrdinalNumber, seasonTextStyle, tvSeriesName)
	local category = getTrackingCategory(tvSeriesName, "single_episode")
	return shortDescription, category
end

--- Returns a short description for a multi-part episode in the style of:
--- "23rd and 24th episodes of the third season of Lost" and a tracking category:
--- "Category:Television episode articles with short description for multi-part episodes".
--- @param tvSeriesName string The TV series name.
--- @param seasonOrdinalNumber string The season's ordinal number.
--- @param seasonTextStyle string The text to use for seasons - either "season" or "series".
--- @param episodeOrdinalNumbers table A list of episode ordinal numbers.
--- @param limitedSeries boolean Whether the episode belongs to a limited series.
local function getShortDescriptionMultiEpisode(tvSeriesName, seasonOrdinalNumber, seasonTextStyle, episodeOrdinalNumbers, limitedSeries)
	local episodeText = mw.text.listToText(episodeOrdinalNumbers)

	if (limitedSeries) then
		return getShortDescriptionLimitedSeries(tvSeriesName, episodeText, "multi_episodes")
	end

	local text = descriptions.multi_episodes.text
	local shortDescription = string.format(text, episodeText, seasonOrdinalNumber, seasonTextStyle, tvSeriesName)
	local category = getTrackingCategory(tvSeriesName, "multi_episodes")
	return shortDescription, category
end

--- Returns a short description for a special episode in the style of:
--- "Special episode of Lost" or "<value> episode of Lost" and a tracking category:
--- "Category:Television episode articles with short description for single episodes".
--- @param tvSeriesName string The TV series name.
--- @param special string The type of special episode. A "yes" value defaults to "Special".
local function getShortDescriptionSpecialEpisode(tvSeriesName, special)
	if (special == "yes" or special == "y") then
		special = "Special"
	end
	local text = descriptions.special_episode.text
	local shortDescription = string.format(text, special, tvSeriesName)
	local category = getTrackingCategory(tvSeriesName, "single_episode")
	return shortDescription, category
end

--- Returns a short description based on the description type passed.
--- @param descriptionType number A description type number.
--- @param tvSeriesName string The TV series name.
--- @param seasonOrdinalNumber string The season's ordinal number.
--- @param seasonTextStyle string The text to use for seasons - either "season" or "series".
--- @param episodeOrdinalNumbers table A list of episode ordinal numbers.
--- @param specialEpisode string The type of special episode.
--- @param limitedSeries boolean Whether the episode belongs to a limited series.
local function getShortDescriptionByType(
		descriptionType, tvSeriesName, seasonOrdinalNumber, seasonTextStyle, episodeOrdinalNumbers, specialEpisode, limitedSeries)
	if descriptionType == descriptions.no_series.type then
		return getShortDescriptionNoSeries()
	elseif descriptionType == descriptions.only_series_name.type then
		return getShortDescriptionOnlySeriesName(tvSeriesName)
	elseif descriptionType == descriptions.season_and_series_name.type then
		return getShortDescriptionSeasonAndSeriesName(tvSeriesName, seasonOrdinalNumber, seasonTextStyle)
	elseif descriptionType == descriptions.single_episode.type then
		return getShortDescriptionSingleEpisode(
                tvSeriesName, seasonOrdinalNumber, seasonTextStyle, episodeOrdinalNumbers[1], limitedSeries)
	elseif descriptionType == descriptions.multi_episodes.type then
		return getShortDescriptionMultiEpisode(
				tvSeriesName, seasonOrdinalNumber, seasonTextStyle, episodeOrdinalNumbers, limitedSeries)
	elseif descriptionType == descriptions.special_episode.type then
		return getShortDescriptionSpecialEpisode(tvSeriesName, specialEpisode)
	else
		return ""
	end
end

--- Returns the type of the description to use.
--- @param tvSeriesName string The TV series name.
--- @param seasonOrdinalNumber string The season's ordinal number.
--- @param episodeOrdinalNumbers table A list of episode ordinal numbers.
--- @param specialEpisode string The type of special episode.
--- @param limitedSeries boolean Whether the episode belongs to a limited series.
local function getDescriptionType(tvSeriesName, seasonOrdinalNumber, episodeOrdinalNumbers, specialEpisode, limitedSeries)
	if (not tvSeriesName) then
		return descriptions.no_series.type
	end

	if (specialEpisode) then
		return descriptions.special_episode.type
	end

	if (not seasonOrdinalNumber and not limitedSeries) then
		return descriptions.only_series_name.type
	end

	if (#episodeOrdinalNumbers < 1) then
		return descriptions.season_and_series_name.type
	end

	if (#episodeOrdinalNumbers == 1) then
		return descriptions.single_episode.type
	end

	if (#episodeOrdinalNumbers > 1) then
		return descriptions.multi_episodes.type
	end
end

--- Returns true if the TV series is a limited series.
--- @param limitedSeries string Any value will be considered as true.
local function isLimitedSeries(limitedSeries)
	if (limitedSeries) then
		return true
	end
	return false
end

--- Returns the ordinal indicator for an integer between 0 and 100.
---
--- Numbers "1", "2" and "3" have unique suffixes.
--- Numbers between 4 and 20 have the same common suffix - "th".
--- Numbers ending with 0 have the same common suffix - "th".
--- @param number number A number value.
local function getOrdinalIndicatorLessThan100(number)
	local suffix
	while (not suffix) do
		-- Check if the number equals 0; This should never be a valid entry. Assign suffix as an empty string.
		if (number == 0) then
			suffix = ""
		-- Check if the number is less than 4; Numbers "1", "2" and "3" have unique suffixes.
		elseif (number < 4) then
			suffix = uniqueSuffix[number]
		-- Check if the number is more than 4 AND less than 20; These numbers all have the same common suffix.
		elseif (number < 20) then
			suffix = commonSuffix
		-- Check if the remainder after division of the number by 10 equals 0.
		elseif (number % 10 == 0) then
			suffix = commonSuffix
		else
			-- Numbers that are above 20 and which their remainder doesn't equal 0 (such as 45).
			-- Remainder after division of the number by 10; So if the current number is 45, the new number is 5.
			number = number % 10
		end
	end
	return suffix
end

--- Returns the ordinal indicator for an integer between 0 and 1000.
--- @param number number A number value.
local function getOrdinalIndicatorLessThan1000(number)
	if (number < 100) then
		return getOrdinalIndicatorLessThan100(number)
	elseif (number % 100 == 0) then
		return commonSuffix
	else
		-- Numbers that are above 100 and which their remainder doesn't equal 0 (such as 345).
		-- Pass the remainder after division of the number by 100 (So for 345, it would pass 45) as the parameter.
		return getOrdinalIndicatorLessThan100(number % 100)
	end
end

--- Returns a table of episode numbers.
---
--- Episode values may be of multipart episodes, in such situations, an episode may be seperated by one of the following:
--- ",", "/", "&", "-", "–", "and".
--- Decimal values and episode overall values sometimes erroneously used are removed.
--- @param number string A number value in string format.
local function cleanEpisodeNumber(number)
	if (not number) then
		return {}
	end

	number = string.gsub(number, "%(.*%)", " ")
	number = string.gsub(number, "%.%d+", " ")

	local numbers = {}
	for digits in string.gmatch(number, "%d+") do
		table.insert(numbers, tonumber(digits))
	end
	return numbers
end

--- Returns a table of episode ordinal numbers.
---
--- In most situations there will be only one episode, but this can support more.
--- @param episodeNumber number The episode's number.
local function getEpisodeOrdinalNumbers(episodeNumber)
	local episodeNumbers = cleanEpisodeNumber(episodeNumber)

	if (#episodeNumbers < 1) then
		return episodeNumbers
	end

	local episodeOrdinals = {}
	for _, cleanedEpisodeNumber in pairs(episodeNumbers) do
		local ordinalIndicator = getOrdinalIndicatorLessThan1000(cleanedEpisodeNumber)
		table.insert(episodeOrdinals, cleanedEpisodeNumber .. ordinalIndicator)
	end

	return episodeOrdinals
end

--- Returns true if the season number value is a number.
--- @param seasonNumber string The season number value in string format.
local function validateSeasonNumber(seasonNumber)
	if (tonumber(seasonNumber)) then
		return true
	else
		return false
	end
end

--- Returns the season's ordinal number, or nil if no season number was set.
--- @param seasonNumber string The season number.
local function getSeasonOrdinalNumber(seasonNumber)
	if (seasonNumber) then
		local convertOrdinal = require("Module:Ordinal")
		return convertOrdinal._ordinal(seasonNumber)
	end
	return nil
end

--- Returns a season number after removing from it unwanted characters.
---
--- This is done to make sure that no malformed season values have been entered.
--- The function will remove all text which is not part of the first number in the string.
---
--- The function converts entries such as:
--- "1.2" -> "1"
--- "12.2" -> "12"
--- @param seasonNumber string The season number value in string format.
local function cleanSeasonNumber(seasonNumber)
	if (seasonNumber) then
		return string.match(seasonNumber, "%d+")
	end
	return nil
end

--- Returns the season number after or cleaning it from unwanted values and validating value is a number.
--- Also returns the text style to use - either "season" or "series".
--- If no value was entered or if value was not a number, return nil.
--- @param seasonNumber string The season number.
--- @param seasonNumberUK string The season number, if UK style was used.
local function getSeasonNumberAndTextStyle(seasonNumber, seasonNumberUK)
    for _, v in ipairs({{seasonNumber, "season"}, {seasonNumberUK, "series"}}) do
        local cleanedSeasonNumber = cleanSeasonNumber(v[1])
        if (validateSeasonNumber(cleanedSeasonNumber)) then
            return cleanedSeasonNumber, v[2]
        end
    end
	return nil
end

--- Returns the TV series title without disambiguation, or nil if no TV series name was set.
--- @param tvSeriesName string The TV series name.
--- @param notDab string If set, the parenthesis in the title is not disambiguation.
local function getTVSeriesName(tvSeriesName, notDab)
	if (tvSeriesName) then
		if (not notDab) then
			return string.gsub(tvSeriesName, "%s+%b()$", "", 1, false)
		end
		return tvSeriesName
	end
	return nil
end

--- Returns the initial values after removing unwanted characters.
--- @param args table The values that should be processed.
local function cleanValues(args)
	for _, v in ipairs({"episode_num", "season_num", "season_num_uk", "series_name"}) do
		if (args[v]) then
			args[v] = args[v]:gsub("\127[^\127]*UNIQ%-%-(%a+)%-%x+%-QINU[^\127]*\127", "")	-- Remove all strip-markers.
			args[v] = args[v]:gsub("</? *br */?>", " ")					-- Replace <br /> (and variants) with space character.
			args[v] = args[v]:gsub("%b<>[^<]+%b<>", "")					-- Remove html markup.
			args[v] = args[v]:gsub("%b<>", "")							-- Remove self-closed html tags.
			args[v] = args[v]:gsub("%[%[[^|]+|([^%]]+)%]%]", "%1")		-- Remove wiki-link retain label.
			args[v] = args[v]:gsub("%[%[([^%]]+)%]%]", "%1")			-- Remove wiki-link retain article.
			args[v] = args[v]:gsub("%[%S+ +([^%]]-)%]", "%1")			-- Remove URLs retain label.
			args[v] = args[v]:gsub("%[[^%]]-%]", "")					-- Remove all remaining URLs.

			if (args[v] == "") then										-- Check if the value is an empty string.
				args[v] = nil											-- The value is an empty string; Set it to nil.
			end
		end
	end
	return args
end

--- Public function - main process.
--- @param frame table The frame invoking the module.
--- @param args table The key-value parameters passed to the module.
function television._getShortDescription(frame, args)
	args = cleanValues(args)
	local tvSeriesName = getTVSeriesName(args.series_name, args.not_dab)
    local seasonNumber, seasonTextStyle = getSeasonNumberAndTextStyle(args.season_num, args.season_num_uk)
	local seasonOrdinalNumber = getSeasonOrdinalNumber(seasonNumber)
	local episodeOrdinalNumbers = getEpisodeOrdinalNumbers(args.episode_num)
	local limitedSeries = isLimitedSeries(args.limited)
	local descriptionType = getDescriptionType(
			tvSeriesName,
			seasonOrdinalNumber,
			episodeOrdinalNumbers,
			args.special,
			limitedSeries
	)

	local shortDescription, trackingCat = getShortDescriptionByType(
			descriptionType,
			tvSeriesName,
			seasonOrdinalNumber,
            seasonTextStyle,
			episodeOrdinalNumbers,
			args.special,
			limitedSeries
	)

	-- Check if the invoking page is from /testcases or /doc pages.
	if (args.test) then
		return shortDescription, trackingCat
	elseif (args.doc) then
		return shortDescription
	else
		local tableData = {shortDescription, "noreplace"}
		return frame:expandTemplate({title = "short description", args = tableData}) .. trackingCat
	end
end

--- Public function which is used to create a television episode's short description
--- from the data available in [Template:Infobox television episode].
--- A suitable description will be generated depending on the values of the various parameters.
--- See documentation for examples.
---
--- Parameters:
--- |episode_num=		— optional; The episode's number.
--- |season_num=		— optional; The season's number.
--- |season_num_uk=		— optional; The season's number if using the British "series" term.
--- |series_name=		— optional; The TV series name.
--- |not_dab=			— optional; Set if the TV series name has parentheses as part of its name.
--- |special=			— optional; Setting to "yes" will set the description as a "special episode".
---							Any other value will replace the word "special" with the one entered.
---							For example "special=recap" will create "recap episode".
---	|limited=			— optional; Set if the series is a single season series, such as miniseries or limited series
---							and does not need a season number as part of the description.
--- @param frame table The frame invoking the module.
function television.getShortDescription(frame)
	local getArgs = require("Module:Arguments").getArgs
	local args = getArgs(frame)
	return television._getShortDescription(frame, args)
end


--- Public function which is used for testing output only.
--- @param frame table The frame invoking the module.
function television.test(frame)
	local getArgs = require("Module:Arguments").getArgs
	local args = getArgs(frame)

	test = args.test
	local shortDescription, categories = television._getShortDescription(frame, args)

	if (test == "cat") then
		return categories
	else
		return shortDescription
	end
end

return television