Module:Television episode short description

Jump to navigation Jump to search

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[edit source]

  • {{#invoke:Television episode short description|getShortDescription|episode_num= |season_num= |season_num_uk= |series_name= |multi_episodes= |not_dab= }}

Parameter list[edit source]

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 Explanation Status
episode_num episode The episode's number optional
season_num season The season's number optional
season_num_uk series_no The season's number if using the British "series" term optional
series_name series The TV series name optional
multi_episodes multi_episodes Set if the episode is a double episode optional
not_dab not_dab Set if the TV series name has parentheses as part of its name optional

Examples[edit source]

An empty infobox[edit source]

  • Code: {{#invoke:Television episode short description|getShortDescription}}
  • Produces: A television episode

An infobox with only the TV series name[edit source]

  • Code: {{#invoke:Television episode short description|getShortDescription|series_name=Lost}}
  • Produces: An episode of Lost

An infobox with the TV series name and season number[edit source]

  • Code: {{#invoke:Television episode short description|getShortDescription|series_name=Lost|season_num=1}}
  • Produces: An episode of the first season of Lost

An infobox with the TV series name, episode and season number[edit source]

  • Code: {{#invoke:Television episode short description|getShortDescription|series_name=Lost|season_num=5|episode_num=4}}
  • Produces: 4th episode of the fifth season of Lost

An infobox with the TV series name with disambiguation, episode and season number[edit source]

  • Code: {{#invoke:Television episode short description|getShortDescription|series_name=[[Firefly (TV series)|Firefly]]|season_num=1|episode_num=1}}
  • Produces: 1st episode of the first season of Firefly

An infobox with a multi-episode[edit source]

  • Code: {{#invoke:Television episode short description|getShortDescription|series_name=Lost|season_num=3|episode_num=22 and 23|multi_episodes=yes}}
  • Produces: 22nd and 23rd episodes of the third season of Lost

An infobox with a TV series name that has parentheses as part of its name[edit source]

  • Code: {{#invoke:Television episode short description|getShortDescription|series_name=Randall and Hopkirk (Deceased)|season_num=1|episode_num=1|not_dab=yes}}
  • Produces: 1st episode of the first season of Randall and Hopkirk (Deceased)

Issues[edit source]

  1. If "multi_episodes" is set, make sure there are 2 episodes listed in the episode list, or it won't work. Supported styles are: x and y, x, y, x / y and x & y.
  2. 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.

Tracking categories[edit source]

See also[edit source]

External links[edit source]

 

-- This module requires the use of Module:ConvertNumeric.
local convertNumeric = require('Module:ConvertNumeric')

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

-- Common suffix.
local commonSuffix = "th"

-- Test validation.
local test = false

-- Description list.
local descriptionList = {
	[1] = "A television episode",
	[2] = "An episode of ''%s''",
	[3] = "An episode of the %s season of ''%s''",
	[4] = "%s episode of the %s season of ''%s''",
	[5] = "%s and %s episodes of the %s season of ''%s''"
}

-- Tracking category list.
local trackingCategoryList = {
	[1] = '[[Category:Television episode articles with short description with no series name|{}]]',
	[2] = '[[Category:Television episode articles with short description with no season number|{}]]',
	[3] = '[[Category:Television episode articles with short description with no episode number|{}]]',
	[4] = '[[Category:Television episode articles with short description for single episodes|{}]]',
	[5] = '[[Category:Television episode articles with short description for multi-part episodes|{}]]',
	[6] = '[[Category:Television episode articles with short description and disambiguated page names|{}]]'
}

local p = {}

--[[
Local function which retrieves the correct category with a sort key.
--]]
local function getCategory(categoryNumber, sortKey)
	local category = trackingCategoryList[categoryNumber]
	return category:gsub("{}", sortKey)	
end
	
--[[
Local function which "Module:Sort title" to retrieve a sortkey.
--]]
local function getSortKey()
	local sortkeyModule = require('Module:Sort title')
	return sortkeyModule._getSortKey()
end

--[[
Local function which is used to check 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)".
--]]
local function isDisambiguated(articleTitle, tvSeriesName)
	local disambiguation = string.match(tostring(articleTitle), "%s%((.-)%)")								-- Get the text inside the disambiguation parentheses.

	if (disambiguation and tvSeriesName) then																-- Check if the article has parentheses and that the TV series name is not nil.
		if (string.find(disambiguation, tvSeriesName)) then 												-- Article has parentheses; Search for the TV series name in the article name disambiguation.
			return true																						-- Article is disambiguated; Return true.
		else
			return false																					-- Article is not disambiguated; Return false.
		end
	else
		return false																						-- Article does not have parentheses; Return false.
	end
end

--[[
Local function which is used to return a relevant tracking category.
--]]
local function createTrackingCategory(tvSeriesName, categoryNumber)
	local articleTitle = mw.title.getCurrentTitle()															-- Get the current page's title.
	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(categoryNumber, sortKey) .. getCategory(6, sortKey)							-- Article is disambiguated; Call getCategory() to retrieve the correct tracking categories and return them.
		else
			return getCategory(categoryNumber, 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

--[[
Local function which is used to create a short description in the style of: "A television episode".
Add article to the maintenance category: "Category:Television episode articles with short description with no series name".
--]]
local function getShortDescriptionNoValues()
	return descriptionList[1], createTrackingCategory(nil, 1)
end

--[[
-- Local function which is used to create a short description in the style of: "An episode of ''Lost''".
-- Add article to the maintenance category: "Category:Television episode articles with short description with no season number".
--]]
local function getShortDescriptionNoEpisodeNoSeasonsValues(tvSeriesName)
	return  string.format(descriptionList[2], tvSeriesName), createTrackingCategory(tvSeriesName, 2)
end

--[[
Local function which is used to create a short description in the style of: "An episode of the first season of ''Lost''".
Add article to the maintenance category: "Category:Television episode articles with short description with no episode number".
--]]
local function getShortDescriptionNoEpisodeValue(seasonOrdinalNumber, tvSeriesName)
	return string.format(descriptionList[3], seasonOrdinalNumber, tvSeriesName), createTrackingCategory(tvSeriesName, 3)
end

--[[
Local function which is used to create a short description in the style of: "5th episode of the fourth season of ''Lost''".
Add article to the tracking category: "Category:Television episode articles with short description for single episodes".
--]]
local function getShortDescriptionSingleEpisode(episodeOrdinalNumber, seasonOrdinalNumber, tvSeriesName)
	return string.format(descriptionList[4], episodeOrdinalNumber, seasonOrdinalNumber, tvSeriesName), createTrackingCategory(tvSeriesName, 4)
end

--[[
Local function which is used to create a short description for double episodes in the style of: "23rd and 24th episodes of the third season of ''Lost''".
Add article to the tracking category: "Category:Television episode articles with short description for multi-part episodes".
--]]
local function getShortDescriptionDoubleEpisode(episodeOrdinalNumber, secondEpisodeOrdinalNumber, seasonOrdinalNumber, tvSeriesName)
	return string.format(descriptionList[5], episodeOrdinalNumber, secondEpisodeOrdinalNumber, seasonOrdinalNumber, tvSeriesName), createTrackingCategory(tvSeriesName, 5)
end

--[[
Local function which is used to retrieve the ordinal indicator for an integer between 0 and 100.
--]]
local function getOrdinalIndicatorLessThan100(number)
	local suffix																							-- Variable to save the ordinal indicator suffix.

	while (suffix == nil) 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
	return suffix																							-- Return the suffix.
end

--[[
Local function which is used to retrieve the ordinal indicator for an integer between 0 and 1000.
--]]
local function getOrdinalIndicatorLessThan1000(number)
	if (number < 100) then																					-- Check if the number is less than 100.
																											-- The number is less than 100;
		return getOrdinalIndicatorLessThan100(number)														-- 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

--[[
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

--[[
Local function which is used to validate if data was entered into a parameter of type number.
--]]
local function validateNumberParam(number)
	if (tonumber(number) == nil) then																		-- Convert the string into a number and check if the value equals nil (conversion failed).
		return false																						-- Param is either empty or not a number; Return false.
	else																									-- Param is a number;
		return true																							-- Return true.
	end
end

--[[
Local function which is used to return a clean version of the number.
This is done to make sure that no malformed episode or 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"
	-- "1<ref name="number" />" -> "1"
--]]
local function getCleanNumber(number)
	if (number ~= nil) then																					-- Check if the number is not null (some kind of value was entered).
		local cleanNumber = string.match(number, '%d+')														-- The value is not null; Clean the number, if needed.
		return cleanNumber																					-- Return the number.
	else
		return nil																							-- The number is nil; Return nil.
	end
end

--[[
Local function which is used to create a short description
by validating if a "multi_episodes" value was entered.
--]]
local function createDescriptionValidateEpisodeValue(episodeNumberText, seasonOrdinalNumber, tvSeriesName, isDoubleEpisode)
	local episodeNumber = getCleanNumber(episodeNumberText)													-- Call getCleanNumber() to return a cleaned version of the number.
	episodeNumber = tonumber(episodeNumber)																	-- Convert the value into a number.

	if (validateNumberParam(episodeNumber)) then															-- Call validateNumberParam() to check if an episode number was entered.
		local episodeOrdinalNumber = getEpisodeOrdinalNumber(episodeNumber)									-- A number was entered; Call getEpisodeOrdinalNumber() to get the episode ordinal number.

		if (isDoubleEpisode == nil) then																	-- Check if a multi_episodes value was entered.
																											-- A multi_episodes value was not entered;
			return getShortDescriptionSingleEpisode(
				episodeOrdinalNumber, seasonOrdinalNumber, tvSeriesName)									-- Call getShortDescriptionSingleEpisode() to get the episode's short description and return it.
		else																								-- A multi_episodes value was entered;
			local secondEpisodeOrdinalNumber = getEpisodeOrdinalNumber(episodeNumber + 1)					-- Call getEpisodeOrdinalNumber() and increment, to get the second episode ordinal number.
			return getShortDescriptionDoubleEpisode(
				episodeOrdinalNumber, secondEpisodeOrdinalNumber, seasonOrdinalNumber, tvSeriesName)		-- Call getShortDescriptionDoubleEpisode() to get the double episode's short description and return it.
		end
	else																									-- A an episode number was not entered;
		return getShortDescriptionNoEpisodeValue(seasonOrdinalNumber, tvSeriesName)							-- Call getShortDescriptionNoEpisodeValue() to get the short description and return it.
	end
end

--[[
Local function which is used to retrieve the season number, since it can be entered in
either the "season" or "series_no" params.
--]]
local function getSeasonNumber(seasonNumber, seasonNumberUK)
	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																									-- Anything else - value not entered.
		return ""																							-- Return empty string.
	end
end

--[[
Local function which is used to create a short description by validating if a season number was entered.
--]]
local function createDescriptionValidateSeasonValue(episodeNumberText, seasonNumber, seasonNumberUK, tvSeriesName, isDoubleEpisode)
	seasonNumber = getSeasonNumber(seasonNumber, seasonNumberUK)											-- 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.
																											-- A season number was entered;
		local seasonOrdinalNumber = convertNumeric.spell_number(											-- Call spell_number() from Module:ConvertNumeric to get the season ordinal number.
			seasonNumber,																					-- Pass it the season number.
			nil,																							-- Not used here.
			nil,																							-- Not used here.
			false,																							-- Not used here.
			false,																							-- Not used here.
			false,																							-- Not used here.
			true,																							-- Get the season ordinal number from the season number.
			false,																							-- Not used here.
			nil,																							-- Not used here.
			nil,																							-- Not used here.
			nil,																							-- Not used here.
			nil,																							-- Not used here.
			false)																							-- Not used here.

		return createDescriptionValidateEpisodeValue(episodeNumberText, 									-- Call createDescriptionValidateEpisodeValue() to continue validation process.
			seasonOrdinalNumber, tvSeriesName, isDoubleEpisode)
	else																									-- A season number was not entered;
		return getShortDescriptionNoEpisodeNoSeasonsValues(tvSeriesName)									-- Call getShortDescriptionNoEpisodeNoSeasonsValues() to get the short description and return it.
	end
end

--[[
Local function which is used to create a short description.
This creates a description by a process of validating which values where entered
into the Infobox and has the following options:
	-- If no TV series name was entered, it calls getShortDescriptionNoValues().
	-- If only the TV series name and season number were entered, it calls getShortDescriptionNoEpisodeValue().
	-- If all information was entered and it is a single episode, it calls getShortDescriptionSingleEpisode().
	-- If all information was entered and it is a double episode, it calls getShortDescriptionDoubleEpisode().
--]]
local function createDescription(episodeNumberText, seasonNumber, seasonNumberUK, tvSeriesName, isDoubleEpisode, notDisambiguated)
	if (tvSeriesName ~= nil) then																			-- Check if a TV series name was entered.

		if (notDisambiguated == nil) 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.
		end

		return createDescriptionValidateSeasonValue(episodeNumberText,										-- Call createDescriptionValidateSeasonValue() to continue validation process.
			seasonNumber, seasonNumberUK, tvSeriesName, isDoubleEpisode)
	else																									-- A TV series name was not entered;
		return getShortDescriptionNoValues()																-- Call getShortDescriptionNoValues() to get the short description and return it.
	end
end

--[[
Local function which is used to clean the values from unwanted characters.
--]]
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																								-- Return args.
end

--[[
Public function which does the actual main process.
--]]
function p._getShortDescription(frame, args)
	args = cleanValues(args)																				-- Call cleanValues() to remove all unwanted characters.

	local episodeNumberText = args['episode_num']															-- Get the episode number in text.
	local seasonNumber = args['season_num']																	-- Get the season number from the {{{season}}} param.
	local seasonNumberUK = args['season_num_uk']															-- Get the season number if it's written in the "series_no" param.
	local tvSeriesName = args['series_name']																-- Get the TV series name.
	local isDoubleEpisode = args['multi_episodes']															-- Get the value of "multi_episodes" (used for episode consisting of two episodes).
	local notDisambiguated = args['not_dab']																-- Get the value of "not_dab" (used if the TV series name has parentheses as part of its name).

	local shortDescription, trackingCat = createDescription(
		episodeNumberText, seasonNumber, seasonNumberUK, tvSeriesName, isDoubleEpisode, notDisambiguated)	-- Call createDescription() and return two values: the episode's short description and tracking category.

	-- 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'}													-- Invoking page isn't a test or doc; Create a table for the short description parameter.
		return frame:expandTemplate({title = 'short description', args = tableData}) .. trackingCat			-- Return expanded short description with tracking category.
	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.
	-- |multi_episodes=		— optional; Set if the episode is a double episode.
	-- |not_dab=			— optional; Set if the TV series name has parentheses as part of its name.
--]]
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.
end

--[[
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
		return categories
	else
		return shortDescription
	end
end

return p