Module:Infobox television episode

--- @modulelocal p = {}local maintenance_categories = {incorrectly_formatted = "[[Category:Pages using infobox television episode with incorrectly formatted values|%s]]",unlinked_values = "[[Category:Pages using infobox television episode with unlinked values|%s]]",image_values_without_an_image = "[[Category:Pages using infobox television episode with image-related values without an image]]",unnecessary_title_parameter = "[[Category:Pages using infobox television episode with unnecessary title parameter]]",non_matching_title = "[[Category:Pages using infobox television episode with non-matching title]]",flag_icon = "[[Category:Pages using infobox television with flag icon]]",dates_incorrectly_formatted = "[[Category:Pages using infobox television episode with nonstandard dates]]",manual_display_title = "[[Category:Pages using infobox television episode with unnecessary manual displaytitle]]",list_markup = "[[Category:Pages using infobox television episode with unnecessary list markup]]",}--- Returns the text after removing line breaks (<br> tags) and additional spaces as a result.------ @param text string--- @return stringlocal function get_name_with_br_fixes(text)local title, _ = string.gsub(text, "<br%s?/?>", "")title, _ = string.gsub(title, "  ", " ")return titleend--- Returns the page name after replacing quotation marks for single quotes and fixing it to use the--- "Space+single" and "Single+space" templates if a leading or trailing apostrophe or quotation mark is used.------ Note: per [[MOS:QWQ]] an episode title with quotation marks should be replaced with single quotes.------ @param frame table--- @param article_title string--- @return stringlocal function get_page_name_with_apostrophe_quotation_fixes(frame, article_title)local page_name, _ = string.gsub(article_title, '"', "'")local left_side_template = frame:expandTemplate{title = "Space+single"}local right_side_template = frame:expandTemplate{title = "Single+space"}page_name, _ = string.gsub(string.gsub(page_name, "^'", left_side_template), "'$", right_side_template)return page_nameend--- Returns the series link.------ @param series string--- @return stringlocal function get_series_link(series)local delink = require("Module:Delink")._delinkreturn delink({series, wikilinks = "target"})end--- Returns two strings:---- The series name after de-linking it and escaping "-".---- The series name after de-linking it.------ @param series string--- @return string, stringlocal function get_series_name(series)local delink = require("Module:Delink")._delinklocal series_name = delink({series})-- Escape the character "-" as it is needed for string.find() to work.local _local series_name_escaped, _ = string.gsub(series_name, "-", "%%-")return series_name_escaped, series_nameend--- Returns a table consisting of the episode's title parts.------ The return table's properties:--- - title - The episode's title.--- - disambiguation - the disambiguation text without parentheses.------ Note: could potentially be moved to an outside module for other template and module uses.------ @param text string--- @return table<string, string | nil>local function get_title_parts(text)local title, disambiguation = string.match(text, "^(.+) (%b())$")local titleString = title -- TODO: needed until https://github.com/Benjamin-Dobell/IntelliJ-Luanalysis/issues/63 is resolved.if not title or type(title) ~= "string" thentitleString = textend---@type table<string, string | nil>local title_parts = {title = --[[---@not number | nil]] titleString, disambiguation = nil}if not disambiguation or type(disambiguation) ~= "string" thenreturn title_partsend-- Remove outside parentheses from names which use parentheses as part of the name such as "episode (Randall and Hopkirk (Deceased))".disambiguation = string.sub(--[[---@not number | nil]] disambiguation, 2, -2)title_parts.disambiguation = --[[---@not number]] disambiguationreturn title_partsend--- Returns the title used in the {{Lowercase title}} template and an optional maintenance category.------ @param page_text string--- @param args table--- @param title_parts table--- @return string | nillocal function get_lowercase_template_status(page_text, args, title_parts)local lowercase_template =  string.match(page_text, "{{[Ll]owercase title.-}}")if lowercase_template thenlocal lowercase_title, _ = string.gsub(title_parts.title,"^%u", string.lower)if args.title thenif args.title == lowercase_title thenreturn maintenance_categories.unnecessary_title_parameterelsereturn maintenance_categories.non_matching_titleendreturn ""endreturn lowercase_titleendreturn nilend--- Returns the title used in the {{Correct title}} template and an optional maintenance category.------ @param page_text string--- @param args table--- @param return_category boolean--- @return string | nillocal function get_correct_title_value(page_text, args, return_category)local correct_title_template_pattern = "{{[Cc]orrect title|title=(.*)|reason=.-}}"local correct_title = string.match(page_text, correct_title_template_pattern)if not correct_title thencorrect_title_template_pattern = "{{[Cc]orrect title|(.*)|reason=.-}}"correct_title = string.match(page_text, correct_title_template_pattern)endif not correct_title and type(correct_title) ~= "string" thenreturn nilendlocal correct_title_title_parts = get_title_parts(correct_title)if not correct_title_title_parts.disambiguation then-- If the correct title value has no disambiguation, check if the title used in the infobox is the same as the title used for the correct title value.if return_category and args.title thenif args.title == correct_title_title_parts.title thenreturn maintenance_categories.unnecessary_title_parameterelsereturn maintenance_categories.non_matching_titleendendreturn correct_title_title_parts.titleendlocal series_name_escaped, _ = get_series_name(args.series)if series_name_escaped ~= "" and (correct_title_title_parts.disambiguation == series_name_escaped or string.find(correct_title_title_parts.disambiguation, series_name_escaped)) thenif return_category and args.title thenif args.title == correct_title_title_parts.title thenreturn maintenance_categories.unnecessary_title_parameterelsereturn maintenance_categories.non_matching_titleendendreturn correct_title_title_parts.titleend-- Can't determine if the text in parentheses is disambiguation or part of the title since |series= isn't used.if return_category thenreturn ""endreturn correct_titleend--- Returns the display title text used in either the {{DISPLAYTITLE}} or {{Italic title}} templates.------ @param page_text string--- @param article_title string--- @return string | nillocal function get_display_title_text(page_text, article_title)local title_modification = string.match(page_text, "{{DISPLAYTITLE:(.-)}}")if title_modification and type(title_modification) == "string" thenreturn --[[---@not number | nil]] title_modificationendtitle_modification = string.match(page_text, "{{Italic title|string=(.-)}}")if title_modification and type(title_modification) == "string" thenlocal italic_title_text, _ = string.gsub(article_title, --[[---@not number | nil]] title_modification, "''" .. title_modification .. "''")return italic_title_textendreturn nilend--- Returns a maintenance category if the italic_title value is not "no".------ Infobox parameters checked:--- - |italic_title=------ @param args table--- @return stringlocal function is_italic_title_valid_value(args)if args.italic_title and args.italic_title ~= "no" thenreturn string.format(maintenance_categories.incorrectly_formatted, "italic_title")endreturn ""end--- Returns a maintenance category if the date is not formatted correctly with a {{Start date}} template.--- Allow "Unaired" as a valid value for unaired television episodes.------ Infobox parameters checked:--- - |airdate=--- - |released=--- - |airdate_overall=------ @param start_date string--- @return stringlocal function are_dates_formatted_correctly(start_date)if start_date and (string.find(start_date, "film%-date") or not string.find(start_date, "itvstart") and start_date ~= "Unaired") thenreturn maintenance_categories.dates_incorrectly_formattedendreturn ""end--- Returns a maintenance category if list markup is used. The infobox can handle list markup correctly.------ Note: the code here is temporarily checking only the parameters which have been converted--- to use the plainlist class directly. Once current uses will be converted, the function will check all parameters--- for incorrect usage.------ Infobox parameters checked:---- Parameters listed below.------ Currently checks for the following list markup:--- - <br> tags - per [[MOS:NOBR]].--- - <li> tags.--- - "plainlist" class.--- - "hlist" class.------ @param args table--- @return stringlocal function uses_list_markup(args)local invalid_tags = {["br"] = "<[bB][rR]%s?/?>",["li"] = "<li>",["plainlist"] = "plainlist",["hlist"] = "hlist",}---@type table<string, boolean>local parameters = {director = true,writer = true,story = true,teleplay = true,narrator = true,presenter = true,producer = true,music = true,photographer = true,editor = true,production = true,airdate = true,guests = true,commentary = true,}for parameter_name, _ in pairs(parameters) dofor _, list_pattern in pairs(invalid_tags) dolocal parameter_value = args[parameter_name]if parameter_value and string.find(parameter_value, list_pattern) thenreturn maintenance_categories.list_markupendendendreturn ""end--- Returns a maintenance category if a flag icon is used.------ All of the infobox values are checked.------ @param args table--- @return stringlocal function has_flag_icon(args)for _, value in pairs(args) doif string.find(value, "flagicon") thenreturn maintenance_categories.flag_iconendendreturn ""end--- Returns a maintenance category if the values are linked.------ Infobox parameters checked:--- - |episode=--- - |season=--- - |series_no=--- - |episode_list=------ The function currently checks if the following values are present:--- - ]] - links.------ @param args table--- @return stringlocal function are_values_linked(args)local parameters = {episode = args.episode,season = args.season,series_no = args.series_no,episode_list = args.episode_list,}for key, value in pairs(parameters) doif string.find(value, "]]", 1, true) thenreturn string.format(maintenance_categories.incorrectly_formatted, key)endendreturn ""end--- Returns a maintenance category if the values are formatted.------ Most of the infobox values are checked. Not included are:--- - |title= - is handled in is_infobox_title_equal_to_article_title()--- - |series= - is handled in are_values_links_only()--- - |next= - is handled in are_values_links_only()--- - |prev= is handled in are_values_links_only()--- - |rtitle=--- - |rprev=--- - |rnext=--- - |image_alt=--- - |alt=--- - |caption=--- - |based_on=--- - |music=--- - |guests=--- - |module=------ The function currently checks if the following values are present:--- - '' - italics or bold.------ Note:--- If the series is American Horror Story then the season_article value is allowed to be formatted.--- If in the future more series need this exception then the hardcoded value in the function should be taken out into a list.------ @param args table--- @return stringlocal function are_values_formatted(args)---@type table<string, boolean>local ignore_parameters = {title = true,series = true,prev = true,next = true,rtitle = true,rprev = true,rnext = true,image_alt = true,alt = true,caption = true,based_on = true,music = true,guests = true,module = true,}for key, value in pairs(args) doif not ignore_parameters[key] and string.find(value, "''", 1, true) thenif key == "season_article" and args.series == "[[American Horror Story]]" then --TODO: This is hardcoded for now.-- Do nothing.elsereturn string.format(maintenance_categories.incorrectly_formatted, key)endendendreturn ""end--- Returns a maintenance category if the values use additional overall numbering.------ Infobox parameters checked:--- - |episode=--- - |season=--- - |series_no=------ The function currently checks if the following values are present:--- - overall - unsupported series overall numbering.------ @param args table--- @return stringlocal function are_values_using_overall(args)local parameters = {episode = args.episode,season = args.season,series_no = args.series_no,}for key, value in pairs(parameters) doif string.find(value, "overall") thenreturn string.format(maintenance_categories.incorrectly_formatted, key)endendreturn ""end--- Returns a maintenance category if the values are unlinked and if additional characters are found in the text.------ Infobox parameters checked:--- - |series=--- - |prev=--- - |next=------ The function currently checks if a value is unlinked or if there is any additional character--- before or after the linked text.------ @param args table--- @return stringlocal function are_values_links_only(args)local parameters = {series = args.series,prev = args.prev,next = args.next,}for key, value in pairs(parameters) do-- Check whether the values are linked.if not string.find(value, "%[%[.*%]%]") thenreturn string.format(maintenance_categories.unlinked_values, key)end-- Check whether the values have anything before or after link brackets.if string.gsub(value, "(%[%[.*%]%])", "") ~= "" thenreturn string.format(maintenance_categories.incorrectly_formatted, key)endendreturn ""end--- Returns a maintenance category if the |image= value includes the "File:" or "Image:" prefix.------ Infobox parameters checked:--- - |image=------ @param image string--- @return stringlocal function is_image_using_incorrect_syntax(image)if not image thenreturn ""endif string.find(image, "[Ff]ile:") or string.find(image, "[Ii]mage:") thenreturn string.format(maintenance_categories.incorrectly_formatted, "image")endreturn ""end--- Returns a maintenance category if the |image_size= value includes "px".------ Infobox parameters checked:--- - |image_size=------ @param image_size string--- @return stringlocal function is_image_size_using_px(image_size)if image_size and string.find(image_size, "px") thenreturn string.format(maintenance_categories.incorrectly_formatted, "image_size")endreturn ""end--- Returns a maintenance category if there is no image file while image auxiliary values are present.------ Infobox parameters checked:--- - |image=--- - |image_size=--- - |image_upright=--- - |image_alt=--- - |alt=--- - |caption=------ @param args table--- @return stringlocal function are_image_auxiliary_values_used_for_no_image(args)if args.image thenreturn ""endif args.image_size or args.image_upright or args.image_alt or args.alt or args.caption thenreturn maintenance_categories.image_values_without_an_imageendreturn ""end--- Returns a maintenance category if the infobox title is equal to the article title.------ Infobox parameters checked:--- - |title=--- - |series=--- - |italic_title------ The function currently checks if the infobox title is equal to the article title while ignoring styling such as:--- - Nowrap spans.--- - Line breaks.--- - Leading and trailing apostrophe spaces.------ A return value can be one of three options:--- - The value of maintenance_categories.non_matching_title - when the args.title does not match the article title.--- - The value of maintenance_categories.unnecessary_title_parameter - when the args.title matches the article title.--- - An empty string - when args.title isn't used or the args.title uses an allowed modification--- (such as a nowrap template) while the rest of the args.title matchs the article title.------ Testing parameters:--- - |page_test= - a real Wikipedia page to read the content of the page.--- - |page_title_test= - the title of the page being checked.------ @param frame table--- @param args table--- @return stringlocal function is_infobox_title_equal_to_article_title(frame, args)if not args.title thenreturn ""endlocal page_textif args.page_test thenpage_text = mw.title.new(args.page_test):getContent()elsepage_text = mw.title.getCurrentTitle():getContent()end-- Check if the article is using a {{Correct title}} template.local correct_title = get_correct_title_value(page_text, args, true)if correct_title thenreturn correct_titleendlocal article_title = args.page_title_testif not args.page_title_test thenarticle_title = mw.title.getCurrentTitle().textendlocal title_parts = get_title_parts(article_title)-- Check if the article is using a {{Lowercase title}} template.local lowercase_title = get_lowercase_template_status(page_text, args, title_parts)if lowercase_title thenreturn lowercase_titleendif title_parts.disambiguation thenlocal series_name_escaped, _ = get_series_name(args.series)series_name_escaped = get_name_with_br_fixes(series_name_escaped)if series_name_escaped ~= "" and (title_parts.disambiguation == series_name_escaped or string.find(title_parts.disambiguation, series_name_escaped)) then-- Remove disambiguation.article_title = title_parts.titleendendif args.italic_title then-- Check if the article is using a {{DISPLAYTITLE}} or {{Italic title}} template.local title_modification = get_display_title_text(page_text, article_title)if title_modification thenif title_modification == args.title thenreturn maintenance_categories.unnecessary_title_parameterelsereturn maintenance_categories.non_matching_titleendendendlocal page_name = get_page_name_with_apostrophe_quotation_fixes(frame, article_title)-- Remove nowrap span.if string.find(args.title, "nowrap") thenlocal title = frame:expandTemplate{title = "Strip tags", args = {args.title}}if title == page_name thenreturn ""endreturn maintenance_categories.non_matching_titleend-- Remove line breaks and additional spaces as a result.if string.find(args.title, "<br%s?/?>") thenlocal title = get_name_with_br_fixes(args.title)if title == page_name thenreturn ""endreturn maintenance_categories.non_matching_titleendif args.title == page_name thenreturn maintenance_categories.unnecessary_title_parameterend-- Article and infobox titles do not match.return maintenance_categories.non_matching_titleend--- Returns the relevant maintenance categories based on the {{Infobox television episode}} values validated.------ @param frame table--- @return stringfunction p.validate_values(frame)local getArgs = require("Module:Arguments").getArgslocal args = getArgs(frame)---@type string[]local categories = {}table.insert(categories, is_infobox_title_equal_to_article_title(frame, args))table.insert(categories, are_image_auxiliary_values_used_for_no_image(args))table.insert(categories, is_image_using_incorrect_syntax(args.image))table.insert(categories, is_image_size_using_px(args.image_size))table.insert(categories, are_values_links_only(args))table.insert(categories, are_values_using_overall(args))table.insert(categories, are_values_formatted(args))table.insert(categories, are_values_linked(args))table.insert(categories, has_flag_icon(args))table.insert(categories, uses_list_markup(args))table.insert(categories, are_dates_formatted_correctly(args.airdate or args.released))table.insert(categories, is_italic_title_valid_value(args))return table.concat(categories)end--- Returns an {{Italic dab2}} instance if title qualifies. Also returns a maintenance category if conditions are met.------ The article's title is italicized if the series name is included in the article's title disambiguation.--- No italicization happens if one of the following conditions is met:------ - |italic_title= is set to "no".--- - The article's title does not use disambiguation.--- - No |series= value is set.--- - The article's disambiguation is not equal or does not include the series name.------ The page is added to a maintenance category if the title is italicized and there is already an--- {{Italic dab}}, {{Italic title}} or {{DISPLAYTITLE}} template.------ Infobox parameters checked:--- - |series=--- - |italic_title=------ Testing parameters:--- - |page_test= - a real Wikipedia page to read the content of the page.--- - |page_title_test= - the title of the page being checked.------ @param frame table--- @return string, stringfunction p.italic_title(frame)local getArgs = require("Module:Arguments").getArgslocal args = getArgs(frame)local page_textif args.page_test thenpage_text = mw.title.new(args.page_test):getContent()elsepage_text = mw.title.getCurrentTitle():getContent()endlocal maintenance_category = ""-- In case the page does not need to be italicized or can't be automatically done, a "no" value will disable both-- the italicization and the error handling.if args.italic_title == "no" thenreturn "", maintenance_categoryendlocal article_title = args.page_title_testif not args.page_title_test thenarticle_title = mw.title.getCurrentTitle().textend-- Check if the page already has an {{Italic dab}}, {{Italic title}} or {{DISPLAYTITLE}} template.local has_italic_dab, _ = string.find(page_text, "{{[Ii]talic dab")local has_italic_title, _ = string.find(page_text, "{{[Ii]talic title")local has_display_title, _ = string.find(page_text, "{{DISPLAYTITLE")if has_italic_dab or has_italic_title or has_display_title thenmaintenance_category = maintenance_categories.manual_display_titleendlocal title_parts = get_title_parts(article_title)-- The title is not italicized if the title does not use disambiguation or if the series parameter isn't used.if not title_parts.disambiguation or not args.series thenreturn "", maintenance_categoryendlocal series_name_escaped, series_name = get_series_name(args.series)series_name_escaped = get_name_with_br_fixes(series_name_escaped)series_name = get_name_with_br_fixes(series_name)-- Check if the disambiguation equals the series name or if the series name can be found in the disambiguation.local italic_dabif title_parts.disambiguation == series_name thenitalic_dab = frame:expandTemplate{title = "Italic dab2"}elseif string.find(title_parts.disambiguation, series_name_escaped) thenitalic_dab = frame:expandTemplate{title = "Italic dab2", args = {string = series_name}}elsereturn "", maintenance_categoryendif args.page_title_test and italic_dab thenitalic_dab = "italic_dab"endreturn italic_dab, maintenance_categoryend--- Returns a formatted title string.------ @param rtitle string--- @return stringlocal function create_title_with_rtitle_value(rtitle)local title_pattern = '"(.*)" and "(.*)"'if string.find(rtitle, title_pattern) thenlocal episode1, episode2 = string.match(rtitle, title_pattern)local title_format = "\"'''%s'''\" and \"'''%s'''\""return string.format(title_format, episode1, episode2)endlocal title_pattern_br = '"(.*)" and%s?<br%s?/?>%s?"(.*)"'if string.find(rtitle, title_pattern_br) thenlocal episode1, episode2 = string.match(rtitle, title_pattern_br)local title_format = "\"'''%s'''\" and<br/> \"'''%s'''\""return string.format(title_format, episode1, episode2)endreturn string.format("'''%s'''", rtitle)end--- Returns the text used for the |above= field of the infobox.------ Infobox parameters checked:--- - |rtitle=--- - |title=--- - |series=------ Testing parameters:--- - |page_test= - a real Wikipedia page to read the content of the page.--- - |page_title_test= - the title of the page being checked.------ @param frame table--- @return stringfunction p.above_title(frame)local getArgs = require("Module:Arguments").getArgslocal args = getArgs(frame)if args.rtitle thenreturn create_title_with_rtitle_value(args.rtitle)endlocal pageif args.page_test thenpage = mw.title.new(args.page_test)elsepage = mw.title.getCurrentTitle()endlocal page_text = page:getContent()local article_title = args.page_title_testif not args.page_title_test thenarticle_title = page.textendlocal title_format = "\"'''%s'''\""local correct_title = get_correct_title_value(page_text, args, false)if correct_title thenreturn string.format(title_format, correct_title)endlocal title_parts = get_title_parts(article_title)local lowercase_title = get_lowercase_template_status(page_text, args, title_parts)if lowercase_title thenreturn string.format(title_format, lowercase_title)endlocal series_name_escaped, _ = get_series_name(args.series)if args.italic_title and not args.rtitle thenlocal title_modification = get_display_title_text(page_text, article_title)if title_modification thenif title_parts.disambiguation == series_name_escaped thenlocal correct_title_title_parts = get_title_parts(title_modification)title_modification = correct_title_title_parts.titleendreturn string.format(title_format, title_modification)endendif args.title thenreturn string.format(title_format, args.title)endif not title_parts.disambiguation or (series_name_escaped ~= "" and (title_parts.disambiguation == series_name_escaped or string.find(title_parts.disambiguation, series_name_escaped))) thenreturn string.format(title_format, get_page_name_with_apostrophe_quotation_fixes(frame, title_parts.title))endreturn string.format(title_format, get_page_name_with_apostrophe_quotation_fixes(frame, article_title))end--- Returns a list of episodes link if not formatted, otherwise returns the text used for args.episode_list.------ Infobox parameters checked:--- - |episode_list=--- - |series=------ @param frame table--- @return stringfunction p.episode_list(frame)local getArgs = require("Module:Arguments").getArgs---@type table<string, string>local args = getArgs(frame)if args.episode_list thenfor _, v in pairs({"]]", "''"}) doif string.find(args.episode_list, v) thenreturn args.episode_listendendif string.find(args.episode_list, "[Ss]toryline") thenreturn "[[" .. args.episode_list .. "|Storylines]]"endreturn "[[" .. args.episode_list .. "|List of episodes]]"endif args.series thenlocal series_name = get_series_link(args.series)local list_of_episodes = "List of " .. series_name .. " episodes"if mw.title.new(list_of_episodes):getContent() thenreturn "[[" .. list_of_episodes .. "|List of episodes]]"endendend--- Returns the relevant maintenance categories based on the {{Infobox television crossover episode}} values validated.------ @param frame table--- @return stringfunction p.validate_values_crossover(frame)local getArgs = require("Module:Arguments").getArgslocal args = getArgs(frame)---@type string[]local categories = {}table.insert(categories, are_image_auxiliary_values_used_for_no_image(args))table.insert(categories, is_image_using_incorrect_syntax(args.image))table.insert(categories, is_image_size_using_px(args.image_size))table.insert(categories, has_flag_icon(args))table.insert(categories, are_dates_formatted_correctly(args.airdate_overall))for i = 1, 5 doif not args["series" .. i] thenbreakendlocal nested_args = {series = args["series" .. i],episode = args["episode_no" .. i],season = args["season" .. i],airdate = args["airdate" .. i],prev = args["prev" .. i],next = args["next" .. i],episode_list = args["episode_list" .. i],}table.insert(categories, are_values_links_only(nested_args))table.insert(categories, are_values_using_overall(nested_args))table.insert(categories, are_values_formatted(nested_args))table.insert(categories, are_values_linked(nested_args))table.insert(categories, are_dates_formatted_correctly(nested_args.airdate))endreturn table.concat(categories, "")endreturn p