Module:Infobox television

require("strict")

--- @module local p = {}

local maintenance_categories = { alt_name = "", dates_incorrectly_formatted = "", dates_missing = "", flag_icon = "", image_values_without_an_image = "", incorrectly_formatted = "", manual_display_title = "", manual_display_title_temp_tracking = "", non_matching_title = "", unnecessary_title_parameter = "", unlinked_values = "", }

local number_of_network_sets = 7

--- Returns a table consisting of the title's title parts. --- --- The return table's properties: --- - title - The 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 local function get_title_parts(text) local title, disambiguation = string.match(text, "^(.+) (%b)$")

if not title or type(title) ~= "string" then title = text end

---@type table local title_parts = {title = -- nil title, 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(-- nil disambiguation, 2, -2) title_parts.disambiguation = -----@not number disambiguation return title_parts 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 start_date value is set to a future date. --- --- Infobox parameters checked: --- - |first_aired[1-number_of_network_sets]= --- - |released[1-number_of_network_sets]= --- --- @param args table --- @return string local function is_start_date_in_the_future(start_date) -- Extract the date from the start_date. local date_pattern = ".*]*>(.-) " local extracted_date = start_date:match(date_pattern)

-- Parse the date components local year, month, day = extracted_date:match("(%d+)-(%d+)-(%d+)")

-- Create a table with the parsed date components local date_table = { year = tonumber(year) or 0, month = tonumber(month) or 0, day = tonumber(day) or 0, hour = 0,	-- Assuming 00:00:00 for simplicity min = 0, sec = 0 }

-- Convert the date table to a Unix timestamp local start_date_timestamp = os.time(date_table)

-- Get the current date components. local current_date = os.date("*t")

-- Set the time components to zero. current_date.hour = 0 current_date.min = 0 current_date.sec = 0

-- Convert the date components into a timestamp. local current_timestamp = os.time(current_date)

--local extracted_date_timestamp = os.time(extracted_date)

-- The infobox does not allow for future dates. mw.log("current_timestamp: " .. current_timestamp) mw.log("start_date_timestamp: " .. extracted_date) if current_timestamp < start_date_timestamp then return maintenance_categories.dates_incorrectly_formatted end return "" end

--- Returns a maintenance category if the dates are not formatted correctly with --- and  templates. --- --- Infobox parameters checked: --- - |first_aired[1-number_of_network_sets]= --- - |released[1-number_of_network_sets]= --- - |last_aired[1-number_of_network_sets]= --- --- Note: all_tests is meant only for /testcases testing. --- --- @param all_tests string Testing conditional value. --- @param released string The start date value. --- @param first_aired string The start date value. --- @param last_aired string The end date value. --- @return string local function are_dates_formatted_correctly(all_tests, released, first_aired, last_aired) -- To keep /testcases clean, this is set so only what is test is shown. if all_tests == "no" then return "" end -- Config parameters local first_aired_future = "Upcoming" local last_aired_current = "present" local start_date_class = "itvstart" local end_date_class = "itvend" local film_date_class = "film%-date"

local start_date = released or first_aired

-- A start date should always be set. if not start_date then return maintenance_categories.dates_missing end

-- Validate the start date is formmated using and not any other template, including, or uses the word "Upcoming". if start_date and (string.find(start_date, film_date_class) or not string.find(start_date, start_date_class) and start_date ~= first_aired_future) then return maintenance_categories.dates_incorrectly_formatted end

-- An end date should always be set if the show or film wasn't released all at once. if first_aired and first_aired ~= first_aired_future and not last_aired then return maintenance_categories.dates_missing end

-- Validate the end date is formmated using and not any other template, or uses the word "present". if last_aired and (not string.find(last_aired, end_date_class) and last_aired ~= last_aired_current) then return maintenance_categories.dates_incorrectly_formatted end

-- Only one date should be used per field. if (start_date and select(2, string.gsub(start_date, start_date_class, "")) > 1) or (last_aired and select(2, string.gsub(last_aired, end_date_class, "")) > 1) then return maintenance_categories.dates_incorrectly_formatted end -- Check if start date is set to a future date. if start_date ~= first_aired_future then return is_start_date_in_the_future(start_date) end return "" end

--- Returns a maintenance category if exclusive parameter sets are used. --- --- Infobox parameters checked: --- - |image_alt= and |alt= --- - |based_on= and |inspired_by= --- - |screenplay= and |teleplay= --- - |presenter= and |host= --- - |narrator=, |narrated= and |announcer= --- - |theme_music_composer= and |music= --- - |open_theme= and |opentheme= --- - |end_theme= and |endtheme= --- - |released[1-number_of_network_sets]= and |first_aired[1-number_of_network_sets]= --- - |released[1-number_of_network_sets]= and |last_aired[1-number_of_network_sets]= --- - |network[1-number_of_network_sets]= and |channel[1-number_of_network_sets]= --- --- The function currently checks if the network and channel parameters both have values. --- --- @param args table --- @param args table --- @return string local function _are_exclusive_parameter_sets_used(args, parameters) for _, set in ipairs(parameters) do		if (args[set[1]] and args[set[2]]) or (args[set[1]] and args[set[3]]) or (args[set[2]] and args[set[3]]) then return string.format(maintenance_categories.incorrectly_formatted, "-duplicate") end end

return "" end

--- Returns a maintenance category if exclusive parameter sets are used. --- Create a set parameters to check. --- Does not include release information related parameters which are sent from --- a different function due to their numbered variations. --- See _are_exclusive_parameter_sets_used for more details. --- --- @param args table --- @return string local function are_exclusive_parameter_sets_used(args) local parameters = { {"image_alt", "alt"}, {"based_on", "inspired_by"}, {"screenplay", "teleplay"}, {"presenter", "host"}, {"narrator", "narrated", "announcer"}, {"theme_music_composer", "music"}, {"open_theme", "opentheme"}, {"end_theme", "endtheme"}, }

return _are_exclusive_parameter_sets_used(args, parameters) end

--- Returns a maintenance category if the values are unlinked. --- --- Infobox parameters checked: --- - |network[1-number_of_network_sets]= --- - |channel[1-number_of_network_sets]= --- --- The function currently checks if a value is unlinked. --- --- @param args table --- @return string local function are_values_unlinked(args) for key, value in pairs(args) do -- Check whether the values are linked. if value and not string.find(value, "%[%[.*%]%]") then return string.format(maintenance_categories.unlinked_values, key) end end

return "" end

--- Returns a maintenance category if the dates are not formatted correctly --- and using "Original", "Revival" or italics to denote a split in the date range. --- --- Infobox parameters checked: --- - |first_aired[1-number_of_network_sets]= --- - |released[1-number_of_network_sets]= --- - |last_aired[1-number_of_network_sets]= --- - |network[1-number_of_network_sets]= --- - |channel[1-number_of_network_sets]= --- --- @param args table --- @return string local function does_release_information_have_extraneous_text(args) for k, v in pairs(args) do		if string.find(string.lower(v), "original") and not string.find(string.lower(v), "aboriginal")	or string.find(string.lower(v), "revival") or 			string.find(v, "''") then return string.format(maintenance_categories.incorrectly_formatted, k)		end end return "" end

--- Returns a maintenance category if the release information: Is not formatted correctly Has extraneous text Dates don't use correct mark up --- --- Infobox parameters checked: --- - |first_aired[1-number_of_network_sets]= --- - |released[1-number_of_network_sets]= --- - |last_aired[1-number_of_network_sets]= --- - |network[1-number_of_network_sets]= --- - |channel[1-number_of_network_sets]= --- --- @param args table --- @return string local function is_release_information_formatted_correctly(args) local release_information = {"first_aired", "released", "last_aired", "network", "channel"} local categories = {} for i = 1, number_of_network_sets do		local num = "" if i > 1 then num = i		end

local numbered_args = {} for _, v in pairs(release_information) do numbered_args[v .. num] = args[v .. num] end

-- If current table is empty, break current cycle. if next(numbered_args) then table.insert(categories, does_release_information_have_extraneous_text(numbered_args)) table.insert(categories, are_values_unlinked({network = args["network" .. num], channel = args["channel" .. num]})) table.insert(categories, _are_exclusive_parameter_sets_used(args, {{"network" .. num, "channel" .. num}, {"released" .. num, "first_aired" .. num}, {"released" .. num, "last_aired" .. num}})) table.insert(categories, are_dates_formatted_correctly(args.all_tests, args["released" .. num], args["first_aired" .. num], args["last_aired" .. num])) end end

return categories end

--- Returns a maintenance category if a or  template is used. --- Checks also for the following redirects: Italic Italics Italictitle Italics title --- --- Testing parameters: --- - |page_test= - a real Wikipedia page to read the content of the page. --- --- Infobox parameters checked: --- - |italic_title= --- --- @param args table --- @return string local function has_display_title(args) --TODO: when testing below is done uncomment code --if args.italic_title then --	return "" --end

local article if args.page_test then article = mw.title.new(args.page_test) else article = mw.title.getCurrentTitle end

local page_text = article:getContent if not page_text then return "" end

if (string.find(page_text, "") or string.find(page_text, "")) and not string.match(page_text, "")then return maintenance_categories.manual_display_title end

local display_title = string.match(page_text, "") local article_title = article.text --TODO: currently does not work --local display_title_no_namespace = string.gsub(display_title, article.nsText .. ":", "") --local display_title_no_italics = string.sub(display_title_no_namespace, 3, string.len(display_title_no_namespace) - 2) if display_title then -- if article_title == display_title or article_title == display_title_no_italics then if article_title == display_title or article_title == string.sub(display_title, 3, string.len(display_title) - 2) then return maintenance_categories.manual_display_title elseif string.find(display_title, " ") or string.find(display_title, " ") then -- TODO: This is valid. Will remove when done with cleanup. return "" else -- TODO: remove when done checking results. return maintenance_categories.manual_display_title_temp_tracking end end

return "" end

--- Returns a maintenance category if a flag icon is used. --- --- All the infobox values are checked. --- --- @param args table --- @return string local function has_flag_icon(args) for _, v in pairs(args) do		if string.find(v, "flagicon") then return maintenance_categories.flag_icon end end return "" end

--- Returns a maintenance category if the producer information entered is from the following list: --- "assistant", associate", "co-", "executive", "line producer", "on-line", "prod%.", "supervising" --- "screenplay", "story", "teleplay" --- --- Infobox parameters checked: --- - |editor= --- - |executive_producer= --- - |producer= --- - |screenplay= --- - |story= --- - |teleplay= --- - |writer= --- --- @param args table --- @return string local function is_credit_used_correctly(args)	local credits_list = {		"editor",		"executive_producer",		"producer",		"screenplay",		"story",		"teleplay",		"writer",	}	local credits = {}	for _, value in pairs(credits_list) do	   credits[value] = args[value]	end

local invalid_credits = { "assistant", "associate", "co%-", "executive", "line producer", "on%-line", "prod%.", "supervising", "screenplay", "story", "teleplay", }

local delink = require("Module:Delink")._delink for key, credit in pairs(credits) do		for _, invalid_credit in pairs(invalid_credits) do local pattern = "%f[%a]" .. invalid_credit if string.find(string.lower(delink{credit}), pattern) then return string.format(maintenance_categories.incorrectly_formatted, key) end end end

return "" end

--- Returns a maintenance category if the country information entered is from the following list: --- U.S.A, USA, U.S., US, UK, U.K. --- --- Infobox parameters checked: --- - |country= --- --- @param country string --- @return string local function is_country_name_valid(country) if not country then return "" end

local args = {"U.S", "US", "UK", "U.K."} for _, v in pairs(args) do		if string.find(country, v) then return string.format(maintenance_categories.incorrectly_formatted, "country") end end return "" end

--- Returns a maintenance category if the values are linked or formatted. --- --- Infobox parameters checked: --- - |language= --- --- The function currently checks if the following values are present: --- - ] - links. --- --- @param args table --- @return string local function are_values_linked_or_formatted(args) local parameters = {language = args.language} for key, value in pairs(parameters) do		for _, bad_value in pairs({"]"}) do			if string.find(value, bad_value, 1, true) then return string.format(maintenance_categories.incorrectly_formatted, key) end end end return "" end

-- Splits a string and returns a table. -- -- @param str string -- @return table local function split(str) local sep = "\n" local result = {} local regex = ("([^%s]+)"):format(sep) for each in str:gmatch(regex) do		table.insert(result, each) end return result end

-- Returns a string value clean from various list syntax. -- -- @param str string -- @return string local function clean_list_syntax(str) str = string.gsub(str, "\127[^\127]*UNIQ%-%-(%a+)%-%x+%-QINU[^\127]*\127", "")				-- Remove all strip-markers. str = string.gsub(string.gsub(str, "%<%/? *div[^%>]*%>", ""), "%<%/? *span[^%>]*%>", "")	-- Removes div and span tags. str = string.gsub(str, "%<%/? *ul[^%>]*%>", "")		-- Remove list tags. str = string.gsub(str, "%<%/? *li[^%>]*%>", "\n")	-- Remove list tags. Replace with new line. str = string.gsub(str, "", "\n")		-- Replace (and variants) with new line. str = string.gsub(str, "\n\n", "\n")				-- Replace double new line with a single new line. str = string.gsub(str, "*", "")						-- Remove asterisks. return str end

--- Returns a maintenance category if: When alt_name= is a list of values, and not all entries are in italics. When alt_name= is a single value and is in italics. - This is because the template automatically handles the italics and when also manually added, - results in 4 apostrophes which produce a bold title instead. --- --- Infobox parameters checked: --- - |alt_name= --- --- @param alt_name string --- @return string local function is_alt_name_in_italics(alt_name) if not alt_name then return "" end local detect_singular = require("Module:Detect singular")._main local args = {alt_name, ["no_and"] = "1", ["no_comma"] = "1"} local is_singular = detect_singular(args) if is_singular > 1 then local alt_names = clean_list_syntax(alt_name) alt_names = split(alt_names) for _, name in ipairs(alt_names) do			if not string.find(name, "''") then return string.format(maintenance_categories.alt_name, "alt_name") end end else if string.find(alt_name, "''") then return string.format(maintenance_categories.alt_name, "alt_name") 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 the display title text used in either the or  templates. --- --- @param page_text string --- @param article_title string --- @return string | nil local function get_display_title_text(page_text, article_title) if not page_text then return nil end

local title_modification = string.match(page_text, "") if title_modification and type(title_modification) == "string" then local title_parts = get_title_parts(title_modification) return string.gsub(title_parts.title, "'", "") end

title_modification = string.match(page_text, "") if title_modification and type(title_modification) == "string" then return article_title end

return nil end

--- Returns the title used in the template and an optional maintenance category. --- --- @param page_text string --- @param args table --- @param article_title string --- @param title_parts table --- @param return_category boolean --- @return string | nil local function get_lowercase_template_status(page_text, args, article_title, title_parts, return_category) if not page_text then return nil end local lowercase_template = string.match(page_text, "")

if not lowercase_template then return nil end

local lowercase_title if string.find(lowercase_template, "|force=") then lowercase_title = string.gsub(article_title,"^%u", string.lower) else lowercase_title = string.gsub(title_parts.title,"^%u", string.lower) end

if return_category and args.name then if args.name == lowercase_title then return maintenance_categories.unnecessary_title_parameter else return maintenance_categories.non_matching_title end return "" end

return lowercase_title end

--- Returns the title used in the 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) if not page_text then return nil end

local correct_title_template_pattern = ""

local correct_title = string.match(page_text, correct_title_template_pattern)

if not correct_title then correct_title_template_pattern = "" 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 return_category and args.name then if args.name == correct_title or args.name == correct_title_title_parts.title then return maintenance_categories.unnecessary_title_parameter else return maintenance_categories.non_matching_title end end

return correct_title end

--- Returns a maintenance category if the infobox title is equal to the article title. --- --- Infobox parameters checked: --- - |name= --- --- The function currently checks if the infobox title is equal to the article title while ignoring styling such as: --- - Nowrap spans. --- - Line breaks. --- --- 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.name isn't used or the args.name uses an allowed modification --- (such as a nowrap template) while the rest of the args.name matches 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.name 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 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

-- Remove disambiguation. local title_parts = get_title_parts(article_title)

-- Check if the article is using a template. local lowercase_title = get_lowercase_template_status(page_text, args, article_title, title_parts, true) if lowercase_title then return lowercase_title end

-- Remove nowrap span. if string.find(args.name, "nowrap") then local title = frame:expandTemplate{title = "Strip tags", args = {args.name}} if title == article_title or title == title_parts.title then return "" end return maintenance_categories.non_matching_title end

-- Remove line breaks and additional spaces as a result. if string.find(args.name, "") then local title, _ = string.gsub(args.name, "", "") title, _ = string.gsub(title, " ", " ") if title == article_title or title == title_parts.title then return "" end return maintenance_categories.non_matching_title end

if args.name == article_title or args.name == title_parts.title 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 values validated. --- --- @param frame table --- @return string function p.validate_values(frame) local getArgs = require("Module:Arguments").getArgs local args = getArgs(frame)

local categories = {} table.insert(categories, is_infobox_title_equal_to_article_title(frame, args)) table.insert(categories, has_display_title(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, is_alt_name_in_italics(args.alt_name)) table.insert(categories, are_values_linked_or_formatted(args)) table.insert(categories, is_country_name_valid(args.country)) table.insert(categories, has_flag_icon(args)) table.insert(categories, is_credit_used_correctly(args)) table.insert(categories, are_exclusive_parameter_sets_used(args)) local release_categories = is_release_information_formatted_correctly(args) for _, v in ipairs(release_categories) do       table.insert(categories, v)	end

table.insert(categories, is_italic_title_valid_value(args))

return table.concat(categories, "") end

--- Returns the text used for the |above= field of the infobox. --- --- Infobox parameters checked: --- - |name= --- --- 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)

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, article_title, title_parts, false) if lowercase_title then return string.format(title_format, lowercase_title) end

if args.italic_title then local title_modification = get_display_title_text(page_text, article_title) if title_modification then return string.format(title_format, title_modification) end end

if args.name then return string.format(title_format, args.name) end

return string.format(title_format, title_parts.title) end

return p