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 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 result = p.printGameCitation(args.quote, args.source, args.plural, args.game, args.version) if err then result = result .. utilsMarkup.categories(err.categories) end return result 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 = args.publisher and p.getPublisherFromShortcut(args.publisher) or (args.book and Franchise.publisher(args.book)) local source = args source.title = bookTitle source.publisher = publisher local citation = p.printCitation(source, args.quote, nil, 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.publisher, "-"} 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.url)..categories end

function p.printGameCitation(quote, speaker, plural, game, version) local gameLink = game and Franchise.link(game) local speakerDisplay = speaker or "" if gameLink and speaker ~= "N/A" then -- if gameLink is not null here, it means `game` should be a valid term context speakerDisplay = Term.printTerm(speaker, game, {			link = true,			plural = plural,		}) end local categories = "" if game and not gameLink and not utilsMarkup.containsLink(game) and mw.title.getCurrentTitle.nsText ~= "MediaWiki" then --MediaWiki is excluded or else this matches MediaWiki:Gadget-EditToolbarButtons.js for some reason utilsError.warn(utilsMarkup.code(mw.dumpObject(game)) .." is neither a valid code nor an interwiki link.") categories = "" end if game and Franchise.hasRemakes(game) then categories = categories .. ""	end local gameDisplay = gameLink or utilsMarkup.italic(game) local sourceDisplay = table.concat({gameDisplay, version}, ", ") local quoteDisplay = quote and p.color(quote, speaker, game) local result = p.printCitation(sourceDisplay, quoteDisplay, nil, speakerDisplay) if mw.title.getCurrentTitle.nsText ~= "User" then result = result .. categories end return result end

function p.printCitation(source, quote, archive, speaker) 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.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.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 _, 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 wikitable = utilsLayout.table({		caption = "Text Colors",		headers = {"Game", "Character/Source", "Color Sample"},		rows = tableRows,		styles = {			["text-align"] = "center"		},		sortable = true,	}) return wikitable 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 = {"type", "quote", "character", "book", "game", "author", "publisher", "edition", "volume", "page"}, boilerplate = { disable = true, },		params = { type = { desc = "", deprecated = true, type = "string", },			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 (and should not be) 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, },			game = { deprecated = true, desc = "Use  instead.", type = "string", trim = true, nilIfEmpty = true, },			author = { deprecated = true, desc = "Use  instead.", type = "string", 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, },			page = { desc = "The cited page(s).", type = "string", required = true, trim = true, nilIfEmpty = true, },		},	},	["Cite Guide"] = { format = "inline", paramOrder = {"quote", "game", "publisher", "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, },			-- This parameter should be named "guide" because one publisher can have multiple guides for a game that are not editions of the same guide publisher = { desc = "The guide abbreviation. See Template:Guide.", 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, },			-- Should this be "date" for consistency with Cite Book and Cite Magazine? year = { desc = "The year of release of the guide.", type = "string", trim = true, nilIfEmpty = true, },		},		examples = { {				quote = "", game = "LA", publisher = "Nintendo", page = "5", },			{				quote = "This is a quote.", game = "TWWHD", publisher = "Prima", page = "5", edition = "Premiere Edition", year = "2011", },			{				desc = "Error handling", args = { quote = "", game = "OoT3D", publisher = "invalid publisher", page = "5", },			},			{				quote = "", game = "OoT", publisher = "", page = "5", },			{				quote = "", game = "", publisher = "", page = "5", }		},	},	["Cite Magazine"] = { purpose = "Citing magazines according to Guidelines:References.", format = "inline", paramOrder = {"quote", "magazine", "publisher", "volume", "issue", "date", "page", "url"}, params = { quote = { desc = "The quote to cite.", type = "content", 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 (and should not be) 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", },			{				desc = "To cite a magazine not supported by Template:Magazine, 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 = "", },		}	}, }

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 = { 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