Module:Cite

local p = {}

local Color = require("Module:Color") local Franchise = require("Module:Franchise") local Guide = require("Module:Guide") local Magazine = require("Module:Magazine") local Term = require("Module:Term") local utilsArg = require("Module:UtilsArg") local utilsError = require("Module:UtilsError") local utilsLanguage = require("Module:UtilsLanguage") local utilsMarkup = require("Module:UtilsMarkup") local utilsString = require("Module:UtilsString") local utilsTable = require("Module:UtilsTable")

local Constants = mw.loadData("Module:Constants/Data") local data = mw.loadData("Module:Cite/Data")

local CAT_BOOK_QUOTES = ""

function p.Main(frame) local args, err = utilsArg.parse(frame:getParent.args, p.Templates.Cite) local categories = err and err.categoryText or "" local gameLink = args.game and Franchise.link(args.game) local speakerDisplay = args.source or "" if gameLink and args.source ~= "N/A" then -- if gameLink is not null here, it means `game` should be a valid term context speakerDisplay = Term.printTerm(args.source, args.game, {			link = true,			plural = plural,		}) end

if args.game and not gameLink and not utilsMarkup.containsLink(args.game) then utilsError.warn(utilsMarkup.code(mw.dumpObject(args.game)) .." is neither a valid code nor an interwiki link.") categories = "" end if args.game and Franchise.hasRemakes(args.game) then categories = categories .. ""	end local gameDisplay = gameLink or utilsMarkup.italic(args.game) local sourceDisplay = table.concat({gameDisplay, args.version}, ", ") local quoteDisplay = args.quote and p.color(args.quote, args.source, args.game) local result = p.printCitation(sourceDisplay, quoteDisplay, speakerDisplay) return result end function p.color(quote, source, game) local colorId = data.dialogueColors[game] and data.dialogueColors[game][source or "default"] if colorId then local coloredText, errCategories = Color.color(colorId, quote) return coloredText .. (errCategories or "") else return quote end end

function p.Book(frame) local args, err = utilsArg.parse(frame:getParent.args, p.Templates["Cite Book"]) local categories = err and err.categoryText or "" if args.quote then categories = categories..CAT_BOOK_QUOTES end -- Backcompat for deprecated parameters if args.game and args.author then args.book = args.game .. " ("..args.author..")" end

local bookTitle = args.book if args.book and not p.hasItalics(args.book) then local bookLink = Franchise.link(args.book) if not bookLink then utilsError.warn(string.format(" must be a code from Data:Franchise or else be written with italics.", bookTitle)) categories = categories.."" else bookTitle = bookLink end end local phraseLink = args.book and Franchise.phraseLink(args.book) -- for manga and comics and such if phraseLink then bookTitle = phraseLink end if not bookTitle then bookTitle = utilsError.error("book title required", true) categories = categories.."" end local publisher if args.book and args.lang then local bookData = data.books[args.book] and data.books[args.book][args.lang] if not bookData then utilsEror.warn(string.format("No data exists for book %s in language %s. See Module:Cite/Data.", args.book, args.lang)) categories = categories.."" end bookTitle = string.format("%s", Franchise.article(args.book) or bookData.display, bookData.display) publisher = bookData.publisher end if not publisher then publisher = args.publisher and p.getPublisherFromShortcut(args.publisher) or (args.book and Franchise.publisher(args.book)) end if not publisher then -- if after all that we still didn't manage to get a publisher... publisher = utilsError.error("publisher required", true) categories = categories.."" end local source = args source.title = bookTitle source.publisher = publisher local citation = p.printCitation(source, args.quote, args.character)

return citation, categories end -- Backwards compatibility for deprecated feature function p.getPublisherFromShortcut(publisher) local publishers = { ["enix"] = "Enix Corporation", ["nintendo"] = "Nintendo Co., Ltd.", ["piggyback"] = "Piggyback Interactive Limited", ["prima"] = "Prima Games", ["soleil"] = "Les Éditions Soleil", ["tokuma shoten"] = "Tokuma Shoten Publishing Co., Ltd.", }	local fullName = publishers[string.lower(publisher)] if fullName then utilsError.warn(string.format("Publisher shortcuts are a deprecated feature. Please enter the full publisher name  instead of  ", fullName, publisher)) return fullName.."" else return publisher end end

function p.Guide(frame) local args, err = utilsArg.parse(frame:getParent.args, p.Templates["Cite Guide"]) local categories = err and err.categoryText or "" if args.quote then categories = categories..CAT_BOOK_QUOTES end local guideArgs = {args.game, args.guide, "-"} local guideTitle, guideCategories, guidePublisher = Guide.guide(guideArgs, "Guide", true) categories = categories..guideCategories if args.year or args.edition then local editionYear = p.concat(", ", {args.edition, args.year}) guideTitle = guideTitle.." ("..editionYear..")" end local source = { title = guideTitle, publisher = guidePublisher, page = args.page, }	return p.printCitation(source, args.quote)..categories end

function p.Magazine(frame) local args, err = utilsArg.parse(frame:getParent.args, p.Templates["Cite Magazine"]) local categories = err and err.categoryText or "" if not args.magazine then return utilsError.error("Magazine name required")..categories end -- We use italics as the cue for a custom magazine name - otherwise the magazine must be one supported by Template:Magazine if p.hasItalics(args.magazine) then args.title = args.magazine else local err = utilsArg.enum(Magazine.enum, args.magazine, "magazine") if err then categories = categories..""..err.category.."" end args.title = ""..args.magazine.."" if args.date then args.date = string.format("%s", args.magazine, args.date, args.date) end end return p.printCitation(args, args.quote, args.interviewee, args.url)..categories end

function p.printCitation(source, quote, speaker, archive) if type(source) == "table" then source = p.formatCitationSource(source) end local citation if not utilsString.isBlank(quote) and not utilsString.isBlank(speaker) then citation = string.format("%s" — %s (%s), quote, speaker, source) elseif not utilsString.isBlank(quote) then citation = string.format("%s" (%s), quote, source) elseif not utilsString.isBlank(speaker) then citation = string.format('%s (%s)', speaker, source) else citation = source end if archive then citation = string.format("%s ([%s archive])", citation, archive) end return citation end function p.formatCitationSource(source) local volume, issue, page = source.volume, source.issue, source.page if volume then volume = "vol. "..volume end if issue then issue = "no. "..issue end if page then page = "pg. "..page end local title = mw.getCurrentFrame:preprocess(source.title) local titleVolumeIssue = p.concat(" ", {title, volume, issue}) local sourceText = p.concat(", ", {titleVolumeIssue, source.publisher, source.date, page}) return sourceText end function p.concat(separator, items) items = utilsTable.compact(items) items = utilsTable.filter(items, utilsString.notBlank) return table.concat(items, separator) end

function p.hasItalics(str) return string.find(str, ".*") end

function p.Data -- Performance optimization; importing this at the top with the others adds processing time to Template:Cite -- these dependencies are only needed on Module:Cite/Data/Documentation local utilsLayout = require("Module:UtilsLayout") local utilsTable = require("Module:UtilsTable") local tableRows = {} for bookCode, bookData in pairs(data.books) do		table.insert(tableRows, {			{				content = string.format(" ", Franchise.article(bookCode) or bookCode, bookCode),				rowspan = utilsTable.size(bookData) + 1,				styles = {					["text-align"] = "center",				},			},		}) for i, langCode in ipairs(utilsLanguage.enum) do			local bookLangData = bookData[langCode] if bookLangData then local language, flag = utilsLanguage.printLanguage(langCode) table.insert(tableRows, {					{						content = flag .. " " .. language,						sortValue = language,					},					string.format("%s", Franchise.article(bookCode) or bookLangData.display, bookLangData.display),					bookLangData.publisher				}) end end end local booksTable = utilsLayout.table({		headers = {"Book", "Language", "Displayed Title", "Publisher"},		rows = tableRows,		sortable = true,	}) booksTable = utilsMarkup.heading(3, utilsMarkup.anchor("books", "Books in Other Languages"))..booksTable local tableRows = {} for _, game in ipairs(Franchise.enumGames) do		local gameColors = data.dialogueColors[game] if gameColors then local colorKeys = utilsTable.keys(gameColors) local sortedColorKeys = utilsTable.sortBy(colorKeys, function(key)				if key == "default" then					return "0" -- show default color first				elseif key == "N/A" then					return "1" -- then show color for N/A (i.e. in-game narration)				else					return key -- then show characters in alphabetical order				end 			end) for i, colorKey in ipairs(sortedColorKeys) do				local row = {} if i == 1 then table.insert(row, {						rowspan = #colorKeys,						content = utilsMarkup.code(utilsMarkup.link(Franchise.article(game), game)),					}) end if colorKey == "default" or colorKey == "N/A" then table.insert(row, utilsMarkup.code(colorKey)) else table.insert(row, utilsMarkup.link(colorKey)) end local colorSample = p.color("The quick brown fox jumps over the lazy dog.", colorKey, game) table.insert(row, colorSample) table.insert(tableRows, row) end end end local colorsTable = utilsLayout.table({		headers = {"Game", "Character/Source", "Color Sample"},		rows = tableRows,		styles = {			["text-align"] = "center"		},		sortable = true,	}) colorsTable = utilsMarkup.heading(3, utilsMarkup.anchor("colors", "Text Colors"))..colorsTable

return booksTable .. "\n" .. colorsTable end

p.Templates = { Cite = { purpose = "Citing in-game text. For more information, see Guidelines:References.", format = "inline", paramOrder = {1, 2, "plural", 3, 4}, params = { [1] = {				name = "quote", required = true, type = "content", desc = "The text to cite. Generally speaking, one should quote the latest remake of a game." },			[2] = {				name = "source", required = true, type = "string", desc = "The source of the in-game text. Usually this is a speaking character. For written text, cite the source as being text itself if it has a name, otherwise cite its author. A menu screen can also be a source. If the text corresponds to in-game narration, use . If   is a valid game code, then   is treated as a term subject (unless it's  ).", },			plural = { type = "boolean", desc = "Typing  in this field causes   to be pluralized, if it is a valid term.", canOmit = true, trim = true, nilIfEmpty = true, },			[3] = {				name = "game", required = true, type = "string", desc = "A game code. If no such game exists at Data:Franchise (i.e. any third-party game outside the Zelda franchise), this field will instead be treated as plain wikitext. In this case, an interwiki link must be used." },			[4] = {				name = "version", type = "content", desc = "Can be used to specify a port, game mode, or localization of the game.", canOmit = true, },		},		examples = { {				desc = "A regular citation. Note that  is not to be used when citing American English text, nor when citing a remake.", args = {"Leave these woods and go to the east, where you will find the land protected by the spirit Eldin.", "Faron (Spirit)", "TPHD"}, },			{				desc = "Cited text may be automatically colorized to match the in-game text color, which may change depending on the source. These colors are defined at Module:Cite/Data. Specific words or phrases must be colorized manually using Template:Color.", args = {"An incarnation of my hatred shall ever follow your kind, dooming them to wander a blood-soaked sea of darkness for all time!", "Demise", "SSHD"}, },			{				desc = "Citing a plural term.", args = {"Cryonis Create a pillar of ice from a water surface. Builds ice pillars that are very stable. These pillars can be used as stepping stones or as obstacles. Use Cryonis on an ice pillar to break it.", "Rune", plural = "yes", "BotW"}, },			{				desc = "Invalid terms are marked accordingly.", args = {"Lorem ipsum", "Not a Real Term", "BotW"}, },			{				desc = " is not counted as a term.", args = {"This is but one of the legends of which the people speak...", "N/A", "TWWHD"}, },			{				desc = "Citing menu screens is sometimes necessary.", args = {"Akkala Highlands", "Map", "BotW"}, },			{				desc = "When citing written text which has a valid term, use that term instead of the author's name.", args = {"Today, I told everything to Mikau, the one person whom I didn't want to know about it. At first, I was too embarrassed and too sad to do anything. And with the words that Mikau said at that moment, I felt that all hope had been lost. But please, Mikau, I'm begging you, don't do anything rash.", "Lulu's Diary", "MM3D"}, },			{				desc = "Citing an alternate version/game mode. Citations of outdated game versions are marked with a maintenance category, as they are generally to be avoided.", args = {"Leave these woods and go to the west, where you will find the land protected by the spirit Eldin.", "Faron (Spirit)", "TP", "Wii version"}, },			{				desc = "Citing the Japanese version of a game.", args = {"", "Ganondorf", "TWW", "Japanese version"} },			{				desc = "Citing a non-Zelda game with Zelda references", args = {"Tired of pixies asking you to listen?", "N/A", ""} },			{				desc = "Citing a game that is neither a valid code nor a link is considered an error.", args = {"", "N/A", "notARealGame"}, },		}	},	["Cite Book"] = { format = "inline", paramOrder = {"quote", "character", "book", "publisher", "lang", "edition", "volume", "issue", "page"}, boilerplate = { tabs = { {					label = "Fiction", desc = "For citing manga, comics, novels, and other works of fiction.", params = {"quote", "book", "character", "publisher", "page"}, },				{					label = "Nonfiction", desc = "For citing books such as .", params = {"quote", "book", "publisher", "page"}, },				{					label = "Nonfiction (Alternative Language)", desc = "For citing books such as in other languages.", params = {"quote", "book", "lang", "page"}, },				{					label = "All Parameters", params = {"quote", "character", "book", "publisher", "lang", "edition", "volume", "issue", "page"}, },			},		},		params = { quote = { desc = "The quote to cite.", type = "content", trim = true, nilIfEmpty = true, },			character = { desc = "The name of the character speaking the quote. Use this for works of fiction such as manga, comics, and gamebooks.", type = "string", trim = true, nilIfEmpty = true, },			book = { desc = " The code for a book entry at Data:Franchise. To cite a book that is not licensed by Nintendo and therefore not defined in Data:Franchise, write the name of the book with italics. A book name without italics is considered invalid if it is not in Data:Franchise. ", type = "content", required = true, trim = true, nilIfEmpty = true, },			lang = { type = "string", desc = "A language code - used when citing books such as in other languages. See Module:Cite/Data for supported languages.", trim = true, nilIfEmpty = true, },			publisher = { desc = " The publisher of the book. Leave this empty when citing a book from Data:Franchise - the publisher will be automatically generated. ", type = "content", trim = true, nilIfEmpty = true, },			edition = { desc = "The book's edition.", type = "string", trim = true, nilIfEmpty = true, },			volume = { desc = "The volume number for the book quote, if applicable.", type = "string", trim = true, nilIfEmpty = true, },			issue = { desc = "A comic issue number.", type = "string", trim = true, nilIfEmpty = true, },			page = { desc = "The cited page(s).", type = "string", required = true, trim = true, nilIfEmpty = true, },		},	},	["Cite Guide"] = { format = "inline", paramOrder = {"quote", "game", "guide", "page", "edition", "year"}, boilerplate = { tabs = { {					label = "Guide", params = {"quote", "game", "guide", "page"}, },				{					label = "Guide Edition", params = {"quote", "game", "guide", "page", "edition", "year"}, },			},		},		params = { quote = { desc = "The quote to cite.", type = "content", trim = true, nilIfEmpty = true, },			game = { desc = "The game abbreviation associated with the guide. See Template:Guide.", type = "string", required = true, trim = true, nilIfEmpty = true, },			guide = { aliases = {"publisher"}, desc = "The guide abbreviation. See Template:Guide. This parameter was formerly named .", type = "string", required = true, trim = true, nilIfEmpty = true, },			page = { desc = "The cited page(s).", type = "string", required = true, trim = true, nilIfEmpty = true, },			edition = { desc = "The edition of the guide.", type = "string", trim = true, nilIfEmpty = true, },			year = { desc = "The year of release of the magazine edition.", type = "string", trim = true, nilIfEmpty = true, },		},		examples = { {				quote = "", game = "TLoZ", guide = "Nintendo", page = "5", },			{				quote = "", game = "TLoZ", guide = "NES Game Atlas", page = "5", },			{				quote = "This is a quote.", game = "TWWHD", guide = "Prima", page = "5", edition = "Premiere Edition", year = "2011", },			{				desc = "The  parameter was renamed to   but the former is still supported.", args = { quote = "", game = "ALttP", publisher = "Nintendo", page = "5", },			},			{				desc = "Error handling", args = { quote = "", game = "OoT3D", guide = "invalid guide", page = "5", },			},			{				quote = "", game = "OoT", guide = "", page = "5", },			{				quote = "", game = "", guide = "", page = "5", }		},	},	["Cite Magazine"] = { purpose = "Citing magazines according to Guidelines:References.", format = "inline", paramOrder = {"quote", "interviewee", "magazine", "publisher", "volume", "issue", "date", "page", "url"}, boilerplate = { tabs = { {					label = "Magazine", params = {"quote", "magazine", "publisher", "volume", "issue", "date", "page", "url"}, },				{					label = "Magazine Interview", params = {"quote", "magazine", "interviewee", "publisher", "volume", "issue", "date", "page", "url"}, }			},			},		params = { quote = { desc = "The quote to cite.", type = "content", trim = true, nilIfEmpty = true, },			interviewee = { desc = "If  is an answer from an inteview, enter the interviewee's name.", type = "string", trim = true, nilIfEmpty= true, },			magazine = { desc = " The name of the magazine. Use a magazine name supported by Template:Magazine. To cite a magazine that is not licensed by Nintendo and therefore not supported by Template:Magazine, write the name of the magazine with italics. A magazine name without formatting is considered invalid if it is not supported by Template:Magazine. ", type = "content", required = true, trim = true, nilIfEmpty = true, },			publisher = { desc = " The publisher of the magazine. Leave this empty if citing a magazine supported by Template:Magazine. ", type = "content", trim = true, nilIfEmpty = true, },			date = { desc = "The of the cited magazine issue.", type = "string", required = true, trim = true, nilIfEmpty = true, },			volume = { desc = "The volume number of the cited magazine issue.", type = "string", trim = true, nilIfEmpty = true, },			issue = { desc = "The issue number of the cited magazine issue.", type = "string", trim = true, nilIfEmpty = true, },			url = { desc = "An archive.org URL where the magazine can be read. Use the URL specifically for the cited page.", type = "string", trim = true, nilIfEmpty = true, },			page = { desc = "The cited page(s).", type = "string", required = true, trim = true, nilIfEmpty = true, },		},		examples = { {				quote = "If you distract its attention and sneak up, you might be able to ride it.", magazine = "Nintendo Magazine", publisher = "", date = "Winter 2021", volume = "", issue = "", url = "https://archive.org/details/nintendomagazine-2021winter-english/16.jpg", page = "16", },			{				quote = "", magazine = "Nintendo Power", publisher = "", date = "July/August 1988", volume = "", issue = "1", url = "https://archive.org/details/nintendo_power_issue1/page/n25", page = "26", },			{				quote = "", magazine = "Nintendo Fun Club News", publisher = "", date = "Fall 1987", volume = "1", issue = "3", url = "https://archive.org/details/NintendoFunClubMagazines/Nintendo%20Fun%20Club%20News%20Issue%203%20%28Fall%201987%29/page/n7", page = "8", },			{				quote = "I have already talked to Mr. Miyamoto about this so I am comfortable releasing this information--this title [Skyward Sword] takes place before Ocarina of Time.", magazine = "Official Nintendo Magazine", date = "December 2010", issue = "62", page = "51", interviewee = "Eiji Aonuma", },			{				desc = "To cite a magazine not licensed by Nintendo, write the magazine name with italics.", args = { quote = "Out: 10/05", magazine = "CD-i Magazine", publisher = "Haymarket Publishing", date = "April 1996", volume = " ", issue = "17", url = "https://archive.org/details/cdi-uk-17", page = "12", },			},			{				magazine = "invalid magazine", date = "October 2022", page = "12", },			{				magazine = "", },		}	},	["Cite Manual"] = { format = "inline", paramOrder = {"quote", "game", "product", "version", "page"}, boilerplate = { tabs = { {					label = "Game Manual", params = {"quote", "game", "page"}, },				{					label = "Game Version", params = {"quote", "game", "version", "page"}, },				{					label = "Product Manual", params = {"quote", "product", "page"}, },			},		},		params = { quote = { desc = "The quote to cite.", type = "content", trim = true, nilIfEmpty = true, },			game = { desc = "The game abbreviation associated with the manual. See Data:Franchise for supported games.", type = "string", required = true, trim = true, nilIfEmpty = true, },			product = { desc = "Used instead of  to cite the manual of a product not defined at Data:Franchise - a console manual, for example.", type = "string", trim = true, nilIfEmpty = true, },			version = { desc = "Used to specify a specific port, game mode, or localization of the game if applicable.", type = "string", trim = true, nilIfEmpty = true, },			page = { desc = "The cited page or pages (format: 54 or 34-36).", type = "string", required = true, trim = true, nilIfEmpty = true, },		},	}, }

p.Documentation = { color = { desc = "Given a valid game and character (source), this function applies the main color used for that character's dialogue in-game.", params = {"quote", "source", "game"}, returns = "The colored text", cases = { {				args = {"Only the true ruler of the Twili can destroy the Mirror of Twilight.", "Midna", "TPHD"}, expect = ' Only the true ruler of the Twili can destroy the Mirror of Twilight. ',			},			{				desc = "Not all characters have a unique color.", args = {"Tingle, Tingle! Kooloo-Limpah!", "Tingle", "MM"}, expect = "Tingle, Tingle! Kooloo-Limpah!" }		}	} }

p.Schemas = { Data = { type = "record", required = true, properties = { {				name = "books", required = true, desc = "Associates a book code from Data:Franchise to information about the publication of the book in other languages. Used by Template:Cite Book.", type = "map", keyPlaceholder = "bookCode", keys = { type = "string" }, values = { type = "map", keyPlaceholder = "langCode", keys = { type = "string", enum = utilsLanguage.enum, desc = "A language code from Module:UtilsLanguage/Data.", },					values = { type = "record", properties = { {								name = "display", required = true, type = "string", desc = "The text to display when referring to the book - typically its subtitle.", },							{								name = "publisher", required = true, type = "string", desc = "The book's publisher." },						},					},				},			},			{				name = "dialogueColors", required = true, desc = "Associates game characters to color IDs from Template:Color. Used by Template:Cite to determine the character's default quote text color.", type = "map", keyPlaceholder = "gameCode", keys = { type = "string", desc = "A game code from Data:Franchise.", },				values = { allOf = { {							type = "map", keyPlaceholder = "character", keys = { type = "string", desc = "The name of a character in the game, or the special value . The former should refer to a page on the wiki, using parentheses if necessary. For example, the entry for Wood in  would be  .", },							values = { type = "string" }, },						{							type = "record", properties = { {									name = "default", type = "string", desc = "Sets the default text color for quotes from the given game.", },							},						},					},				},			},		},	},	color = { quote = { required = true, type = "string", desc = "The quote to color", },		game = { required = true, type = "string", desc = "A game code. See Module:Franchise.", },		source = { required = true, type = "string", desc = "The name of the character who speaks the quote.", },	} }

return p