Module:Infobox television episode
Documentation for this module may be created at Module:Infobox television episode/doc
--- @module local 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 string local function get_name_with_br_fixes(text) local title, _ = string.gsub(text, "<br%s?/?>", "") title, _ = string.gsub(title, " ", " ") return title end --- 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 string local 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_name end --- Returns the series link. --- --- @param series string --- @return string local function get_series_link(series) local delink = require("Module:Delink")._delink return 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, string local function get_series_name(series) local delink = require("Module:Delink")._delink local 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_name end --- 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" then titleString = text end ---@type table<string, string | nil> local title_parts = {title = --[[---@not number | nil]] titleString, disambiguation = nil} if not disambiguation or type(disambiguation) ~= "string" then return title_parts end -- 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]] disambiguation return title_parts end --- 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 | nil local function get_lowercase_template_status(page_text, args, title_parts) local lowercase_template = string.match(page_text, "{{[Ll]owercase title.-}}") if lowercase_template then local lowercase_title, _ = string.gsub(title_parts.title,"^%u", string.lower) if args.title then if args.title == lowercase_title then return maintenance_categories.unnecessary_title_parameter else return maintenance_categories.non_matching_title end return "" end return lowercase_title end return nil end --- 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 | nil local 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 then correct_title_template_pattern = "{{[Cc]orrect title|(.*)|reason=.-}}" correct_title = string.match(page_text, correct_title_template_pattern) end if not correct_title and type(correct_title) ~= "string" then return nil end local 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 then if args.title == correct_title_title_parts.title then return maintenance_categories.unnecessary_title_parameter else return maintenance_categories.non_matching_title end end return correct_title_title_parts.title end local 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)) then if return_category and args.title then if args.title == correct_title_title_parts.title then return maintenance_categories.unnecessary_title_parameter else return maintenance_categories.non_matching_title end end return correct_title_title_parts.title end -- Can't determine if the text in parentheses is disambiguation or part of the title since |series= isn't used. if return_category then return "" end return correct_title end --- Returns the display title text used in either the {{DISPLAYTITLE}} or {{Italic title}} templates. --- --- @param page_text string --- @param article_title string --- @return string | nil local 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" then return --[[---@not number | nil]] title_modification end title_modification = string.match(page_text, "{{Italic title|string=(.-)}}") if title_modification and type(title_modification) == "string" then local italic_title_text, _ = string.gsub(article_title, --[[---@not number | nil]] title_modification, "''" .. title_modification .. "''") return italic_title_text end return nil end --- Returns a maintenance category if the italic_title value is not "no". --- --- Infobox parameters checked: --- - |italic_title= --- --- @param args table --- @return string local function is_italic_title_valid_value(args) if args.italic_title and args.italic_title ~= "no" then return string.format(maintenance_categories.incorrectly_formatted, "italic_title") end return "" 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 string local function are_dates_formatted_correctly(start_date) if start_date and (not string.find(start_date, "itvstart") and start_date ~= "Unaired") then return maintenance_categories.dates_incorrectly_formatted end return "" 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 string local 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) do for _, list_pattern in pairs(invalid_tags) do local parameter_value = args[parameter_name] if parameter_value and string.find(parameter_value, list_pattern) then return maintenance_categories.list_markup end end end return "" end --- Returns a maintenance category if a flag icon is used. --- --- All of the infobox values are checked. --- --- @param args table --- @return string local function has_flag_icon(args) for _, value in pairs(args) do if string.find(value, "flagicon") then return maintenance_categories.flag_icon end end return "" 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 string local 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) do if string.find(value, "]]", 1, true) then return string.format(maintenance_categories.incorrectly_formatted, key) end end return "" 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 string local 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) do if not ignore_parameters[key] and string.find(value, "''", 1, true) then if key == "season_article" and args.series == "[[American Horror Story]]" then --TODO: This is hardcoded for now. -- Do nothing. else return string.format(maintenance_categories.incorrectly_formatted, key) end end end return "" 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 string local 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) do if string.find(value, "overall") then return string.format(maintenance_categories.incorrectly_formatted, key) end end return "" 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 string local 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, "%[%[.*%]%]") then return string.format(maintenance_categories.unlinked_values, key) end -- Check whether the values have anything before or after link brackets. if string.gsub(value, "(%[%[.*%]%])", "") ~= "" then return string.format(maintenance_categories.incorrectly_formatted, key) end end return "" end --- Returns a maintenance category if the |image= value includes the "File:" or "Image:" prefix. --- --- Infobox parameters checked: --- - |image= --- --- @param image string --- @return string local function is_image_using_incorrect_syntax(image) if not image then return "" end if string.find(image, "[Ff]ile:") or string.find(image, "[Ii]mage:") then return string.format(maintenance_categories.incorrectly_formatted, "image") end return "" end --- Returns a maintenance category if the |image_size= value includes "px". --- --- Infobox parameters checked: --- - |image_size= --- --- @param image_size string --- @return string local function is_image_size_using_px(image_size) if image_size and string.find(image_size, "px") then return string.format(maintenance_categories.incorrectly_formatted, "image_size") end return "" 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 string local function are_image_auxiliary_values_used_for_no_image(args) if args.image then return "" end if args.image_size or args.image_upright or args.image_alt or args.alt or args.caption then return maintenance_categories.image_values_without_an_image end return "" 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 string local function is_infobox_title_equal_to_article_title(frame, args) if not args.title then return "" end local page_text if args.page_test then page_text = mw.title.new(args.page_test):getContent() else page_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 then return correct_title end local article_title = args.page_title_test if not args.page_title_test then article_title = mw.title.getCurrentTitle().text end local 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 then return lowercase_title end if title_parts.disambiguation then local 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.title end end if 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 then if title_modification == args.title then return maintenance_categories.unnecessary_title_parameter else return maintenance_categories.non_matching_title end end end local page_name = get_page_name_with_apostrophe_quotation_fixes(frame, article_title) -- Remove nowrap span. if string.find(args.title, "nowrap") then local title = frame:expandTemplate{title = "Strip tags", args = {args.title}} if title == page_name then return "" end return maintenance_categories.non_matching_title end -- Remove line breaks and additional spaces as a result. if string.find(args.title, "<br%s?/?>") then local title = get_name_with_br_fixes(args.title) if title == page_name then return "" end return maintenance_categories.non_matching_title end if args.title == page_name then return maintenance_categories.unnecessary_title_parameter end -- Article and infobox titles do not match. return maintenance_categories.non_matching_title end --- Returns the relevant maintenance categories based on the {{Infobox television episode}} values validated. --- --- @param frame table --- @return string function p.validate_values(frame) local getArgs = require("Module:Arguments").getArgs local 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, string function p.italic_title(frame) local getArgs = require("Module:Arguments").getArgs local args = getArgs(frame) local page_text if args.page_test then page_text = mw.title.new(args.page_test):getContent() else page_text = mw.title.getCurrentTitle():getContent() end local 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" then return "", maintenance_category end local article_title = args.page_title_test if not args.page_title_test then article_title = mw.title.getCurrentTitle().text end -- 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 then maintenance_category = maintenance_categories.manual_display_title end local 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 then return "", maintenance_category end local 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_dab if title_parts.disambiguation == series_name then italic_dab = frame:expandTemplate{title = "Italic dab2"} elseif string.find(title_parts.disambiguation, series_name_escaped) then italic_dab = frame:expandTemplate{title = "Italic dab2", args = {string = series_name}} else return "", maintenance_category end if args.page_title_test and italic_dab then italic_dab = "italic_dab" end return italic_dab, maintenance_category end --- Returns a formatted title string. --- --- @param rtitle string --- @return string local function create_title_with_rtitle_value(rtitle) local title_pattern = '"(.*)" and "(.*)"' if string.find(rtitle, title_pattern) then local episode1, episode2 = string.match(rtitle, title_pattern) local title_format = "\"'''%s'''\" and \"'''%s'''\"" return string.format(title_format, episode1, episode2) end local title_pattern_br = '"(.*)" and%s?<br%s?/?>%s?"(.*)"' if string.find(rtitle, title_pattern_br) then local episode1, episode2 = string.match(rtitle, title_pattern_br) local title_format = "\"'''%s'''\" and<br/> \"'''%s'''\"" return string.format(title_format, episode1, episode2) end return 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 string function p.above_title(frame) local getArgs = require("Module:Arguments").getArgs local args = getArgs(frame) if args.rtitle then return create_title_with_rtitle_value(args.rtitle) end local page if args.page_test then page = mw.title.new(args.page_test) else page = mw.title.getCurrentTitle() end local page_text = page:getContent() local article_title = args.page_title_test if not args.page_title_test then article_title = page.text end local title_format = "\"'''%s'''\"" local correct_title = get_correct_title_value(page_text, args, false) if correct_title then return string.format(title_format, correct_title) end local title_parts = get_title_parts(article_title) local lowercase_title = get_lowercase_template_status(page_text, args, title_parts) if lowercase_title then return string.format(title_format, lowercase_title) end local series_name_escaped, _ = get_series_name(args.series) if args.italic_title and not args.rtitle then local title_modification = get_display_title_text(page_text, article_title) if title_modification then if title_parts.disambiguation == series_name_escaped then local correct_title_title_parts = get_title_parts(title_modification) title_modification = correct_title_title_parts.title end return string.format(title_format, title_modification) end end if args.title then return string.format(title_format, args.title) end if not title_parts.disambiguation or (series_name_escaped ~= "" and (title_parts.disambiguation == series_name_escaped or string.find(title_parts.disambiguation, series_name_escaped))) then return string.format(title_format, get_page_name_with_apostrophe_quotation_fixes(frame, title_parts.title)) end return 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 string function p.episode_list(frame) local getArgs = require("Module:Arguments").getArgs ---@type table<string, string> local args = getArgs(frame) if args.episode_list then for _, v in pairs({"]]", "''"}) do if string.find(args.episode_list, v) then return args.episode_list end end if string.find(args.episode_list, "[Ss]toryline") then return "[[" .. args.episode_list .. "|Storylines]]" end return "[[" .. args.episode_list .. "|List of episodes]]" end if args.series then local series_name = get_series_link(args.series) local list_of_episodes = "List of " .. series_name .. " episodes" if mw.title.new(list_of_episodes):getContent() then return "[[" .. list_of_episodes .. "|List of episodes]]" end end end --- Returns the relevant maintenance categories based on the {{Infobox television crossover episode}} values validated. --- --- @param frame table --- @return string function p.validate_values_crossover(frame) local getArgs = require("Module:Arguments").getArgs local 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 do if not args["series" .. i] then break end local 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)) end return table.concat(categories, "") end return p