모듈:Excerpt/portals

-- ATTENTION !
-- This version of Excerpt is designed specifically for the portal namespace and its associated templates
-- Prefer Module:Excerpt whenever possible
-- Name of the category to track content pages with errors
local errorCategory = "Articles with broken excerpts"
-- Error messages
local errorMessages = {
	prefix = "Excerpt error: ",
	noPage = "No page given",
	pageNotFound = "Page '%s' not found",
	leadEmpty = "Lead section is empty",
	sectionEmpty = "Section '%s' is empty",
	sectionNotFound = "Section '%s' not found",
	fragmentEmpty = "Fragment '%s' is empty",
	fragmentNotFound = "Fragment '%s' not found"
}
-- Regular expressions to match all aliases of the file namespace
local fileNamespaces = {
	"Ffile",
	"Iimage"
}
-- Regular expressions to match all image parameters
local imageParams = {
	{"thumb", "thumbnail", "frame", "framed", "frameless"},
	{"right", "left", "center", "centre", "none"},
	{"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"}
}
-- Regular expressions to match all infobox parameters for image captions
local captionParams = {
	"*Ccaption*",
	"*Llegend*"
}
-- List of file types that are allowed to be transcluded
local fileTypes = {"GgIiFf", "JjPpEe?Gg", "PpNnGg", "SsVvGg", "TtIiFfFf", "XxCcFf"}
-- Regular expressions to match all inline templates that are undesirable in excerpts
local unwantedInlineTemplates = {
	"Eefn", "Eefn%-luarg", "Eefn %a -", "Eelmn", "Rrp?", "Ssfnbmp", "Ssfbn", "NnoteTtag", "#Ttag:%s*Rref", "Rrefn?", "Ffamily _name _footnote",
	"CcDdn", "Ccitation%- _needed", "Ddisambiguation needed", "Ffeatured article", "Ggood article",
	"DdISPLAYTITLE", "Sshort _+description", "Ccitation", "Ccite%- _+%w_%s-", "Ccoor%w_%s-",
	"Uu?n?Rreliable source%?%w_%s-", "Rrs%??", "Vvc", "Vverify credibility", "Bby _*Wwhom*%??", "Wwikisource -_*multi", "Iinflation _/-*Ffn",
	"Bbiblesource",
	"Ddecadebox",
	"Eevents by year for decade",
	-- aliases for Clarification needed
	"Ccfny", "Cclarification _+inline", "Cclarification%- _*needed", "Cclarification", "Cclarify%-inline", "Cclarify%-?me",
	"Cclarify _+inline", "Cclarify", "CcLARIFY", "Cconfusing%-inline", "Cconfusing%-short", "Eexplainme", "Hhuh _*%??", "Wwhat%?",
	"Iinline _+Uunclear", "Iin _+what _+sense", "Oobscure", "Pplease _+clarify", "Uunclear _+inline", "Wwhat's _+this%?",
	"GgeoQuelle", "Nneeds+%- _+IiPpAa", "IiPA needed",
	-- aliases for Clarification needed lead
	"Ccitation needed %(?lea?de?%)?", "Ccnl", "Ffact %(?lea?de?%)?", "Llead citation needed", "Nnot in body", "Nnot verified in body",
	-- Primary source etc.
	"Ppsci", "Nnpsn", "Nnon%-primary _+source _+needed", "Sself%-published%w_%s-", "Uuser%-generated%w_%s-",
	"Pprimary source%w_%s-", "Ssecondary source%w_%s-", "Ttertiary source%w_%s-", "Tthird%-party%w_%s-",
	-- aliases for Disambiguation (page) and similar
	"Bbegriffsklärung", "DdAaBb", "Ddbig", "%w_%s-%f%wDdisam%w_%s-", "HhNnDdIiSs",
	-- aliases for Failed verification
	"Bbadref", "Ffaile?ds ?rv%w_%s-", "FfVv", "NnIi?CcGg", "Nnot ?in ?crs%w_%s-", "Nnot specifically in source",
	"Vverification%- _failed",
	-- aliases for When
	"Aas _+of _+when%??", "Aas _%-+of%?", "Cclarify date", "Ddate _*needed", "Nneeds? _+date", "Rrecently", "Ssince _+when%??",
	"WwHEN", "Wwhen%??",
	-- aliases for Update
	"Nnot _*up _*to _*date","Oou?TtDd","Oout%- _*o?f?%- _*dated?", "Uupdate",  "Uupdate _+sect", "Uupdate _+Watch",
	-- aliases for Pronunciation needed
	"Ppronunciation%??%- _*n?e?e?d?e?d?", "Ppronounce", "Rrequested%- _*pronunciation", "Rre?q?pron", "Nneeds%- _*pronunciation",
	-- Chart, including Chart/start etc.
	"Cchart", "Cchart/%w_%s-",
	-- Cref and others
	"Ccref2?", "Ccnote",
	-- Explain and others
	"Eexplain", "Ffurther *explanation *needed", "Eelaboration *needed", "Eexplanation *needed",
	-- TOC templates
	"CcOoMmPpAaCcTt _*TtOoCc8*5*", "TtOoCc", "09AaZz", "TtOoCc *CcOoMmPpAaCcTt", "TtOoCc *SsMmAaLlLl", "CcOoMmPpAaCcTt _*AaLlPpHhAaBbEeTtIiCc _*TtOoCc",
	"DEFAULTSORT:.-",
	"Oone _+source",
	"Ccontains _+special _+characters",
	"Iinfobox _+Cchinese"
}
-- Regular expressions to match all block templates that are desirable in excerpts
local wantedBlockTemplates = {
	"Bbasketball _roster _header",
	"Ccabinet _table}*",
	"Cchart}*",
	"Cclear",
	"Ccol%- es}*", -- all abbreviated column templates without excessively matching (, , etc.)
	"Ccolumn}*", -- all other column templates
	"COVID-19 _pandemic _data}*",
	"Ccycling _squad}*",
	"Ddynamic _list",
	"Eelection _box}*",
	"Ggallery",
	"Ggraph}*",
	"Hhidden",
	"Hhistorical _populations",
	"Llegend _inline",
	"Pplainlist",
	"Pplayer}*",
	"Sseries _overview",
	"Sside _box",
	"Sswitcher",
	"Ttree _chart}*",
	"Ttelevision _ratings _graph"
}
-- Regular expressions to match non-free file templates
local nonFreeFileTemplates = {
	"Nnon%-free",
}
local Transcluder = require("Module:Transcluder")
local escapeString = require("Module:String")._escapePattern
local yesno = require('Module:Yesno')
local p = {}
-- Helper function to test for truthy and falsy values
local function is(value)
	if not value or value == "" or value == "0" or value == "false" or value == "no" then
		return false
	end
	return true
end
-- Error handling function
-- Throws a Lua error or returns an empty string if error reporting is disabled
local errors = true -- show errors by default
local function luaError(message, value)
	if not is(errors) then return  end -- error reporting is disabled
	message = errorMessagesmessage or message or 
	message = mw.ustring.format(message, value)
	error(message, 2)
end
-- Error handling function
-- Returns a wiki friendly error or an empty string if error reporting is disabled
local function wikiError(message, value)
	if not is(errors) then return  end -- error reporting is disabled
	message = errorMessagesmessage or message or 
	message = mw.ustring.format(message, value)
	message = errorMessages.prefix .. message
	if mw.title.getCurrentTitle().isContentPage then
		local errorCategory = mw.title.new(errorCategory, 'Category')
		if errorCategory then message = message ..  .. errorCategory.prefixedText ..  end
	end
	message = mw.html.create('div'):addClass('error'):wikitext(message)
	return message
end
-- Helper function to match from a list regular expressions
-- Like so: match pre..list1..post or pre..list2..post or ...
local function matchAny(text, pre, list, post, init)
	local match = {}
	for i = 1, #list do
		match = { mw.ustring.match(text, pre .. listi .. post, init) }
		if match1 then return unpack(match) end
	end
	return nil
end
-- Helper function to convert imagemaps into standard images
local function convertImageMap(imagemap)
	local image = matchAny(imagemap, ">\n%s*", fileNamespaces, "^\n*")
	if image then
		return "" .. mw.ustring.gsub(image, ">\n%s*", "", 1) .. ""
	else
		return "" -- remove entire block if image can't be extracted
	end
end
-- Helper function to convert a comma-separated list of numbers or min-max ranges into a list of booleans
-- For example: "1,3-5" to {1=true,2=false,3=true,4=true,5=true}
local function numberFlags(str)
	if not str then return {} end
	local flags = {}
	local ranges = mw.text.split(str, ",") -- parse ranges: "1,3-5" to {"1","3-5"}
	for _, r in pairs(ranges) do
		local min, max = mw.ustring.match(r, "^%s*(%d+)%s*-–—%s*(%d+)%s*$") -- "3-5" to min=3 max=5
		if not max then	min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" to min=1 max=1
		if max then
			for p = min, max do flagsp = true end
		end
	end
	return flags
end
-- Helper function to convert template arguments into an array of arguments fit for get()
local function parseArgs(frame)
	local args = {}
	for key, value in pairs(frame:getParent().args) do argskey = value end
	for key, value in pairs(frame.args) do argskey = value end -- args from a Lua call have priority over parent args from template
	args.paraflags = numberFlags(args"paragraphs" or "") -- parse paragraphs: "1,3-5" to {"1","3-5"}
	args.fileflags = numberFlags(args"files" or "") -- parse file numbers
	return args
end
-- simulate  without the footnote, given "N|485US|," or similar
local function airreg(p)
	local s = mw.text.split(p, "%s*|%s*")
	if s1 ~= "N" and s1 ~= "HL" and s1 ~= "JA" then s1=s1 .. "-" end
	return table.concat(s, "")
end
-- Helper function to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT
local function stripTemplate(t)
	-- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string)
	if matchAny(t, "^", "")
	noRef = mw.ustring.gsub(noRef, "|%s*ref%s*%f}", "")
	noRef = mw.ustring.gsub(noRef, "|%s*shortref%s*%f}", "")
	-- If a wanted template has unwanted nested templates, purge them too
	noRef = mw.ustring.sub(noRef, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noRef, 3), "%b{}", stripTemplate)
	-- Hide pipes in wikilinks for the next bit of processing
	noRef = mw.ustring.gsub(noRef, "%(%b)%", function(s)
		return "", "\27_PIPE_\27") .. ""
	end)
	-- Replace  by its text parameter: Bar → Bar
	noRef = mw.ustring.gsub(noRef, "^\27%2") --  → 
	until text == t
	text = text.gsub(text, "({})%1^\27.*", "") -- remove unmatched  and everything thereafter, avoiding }E}E etc.
	text = text.gsub(text, "({})%1$", "") -- remove unmatched  at end of text
	text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → ")
							if isStub then text = nil end
						end
						if section and section ~= "" then
							page = page .. '#' .. section -- restore the section
						end
						text = get(page, options)
					end
				end
			end
			table.remove(candidates, candidateKey) -- candidate processed
			candidateCount = candidateCount - 1 -- ensure that we exit the loop after all candidates are done
		end
	end
	if not text or text == "" then return wikiError("No valid pages found") end
	-- Store all candidates before processing
	local allCandidates = {page}  -- Include current page
	for _, candidate in pairs(candidates) do
		table.insert(allCandidates, candidate)
	end
	if args.showall then
		local separator = args.showall
		if separator == "" then separator = "" end
		-- Add list at the top for showall if list parameter is not present
		if not args.list then
			local listText = "\n\n"
			text = listText .. text
		end
		for _, candidate in pairs(candidates) do
			local t = get(candidate, options)
			if t ~= "" then
				text = text .. separator .. t
			end
		end
	end
	-- Add a collapsed list of pages which might appear
	if args.list then
		local list = args.list
		if list == "" then list = "Other articles" end
		text = text .. "\n"
	end
	return frame:preprocess(text)
end
-- Old invocation function used by 
local function excerpt(frame)
	local args = parseArgs(frame)
	-- Make sure the requested page exists
	local page = args1 or args.article or args.source or args.page
	if not page then return wikiError("noPage") end
	local title = mw.title.new(page)
	if not title then return wikiError("noPage") end
	if title.isRedirect then title = title.redirectTarget end
	if not title.exists then return wikiError("pageNotFound", page) end
	page = title.prefixedText
	-- Define some useful variables
	local section = args2 or args.section or mw.ustring.match(args1, "^#+#(^#+)")
	local tag = args.tag or 'div'
	-- Define the HTML elements
	local block = mw.html.create(tag):addClass('excerpt-block')
	if is(args.indicator) then block:addClass('excerpt-indicator') end
	local style = frame:extensionTag{ name = 'templatestyles', args = { src = 'Excerpt/styles.css' } }
	local hatnote
	if not args.nohat then
		if args.this then
			hatnote = args.this
		elseif args.indicator then
			hatnote = 'This is'
		elseif args.only == 'file' then
			hatnote = 'This file is'
		elseif args.only == 'file' then
			hatnote = 'These files are'
		elseif args.only == 'list' then
			hatnote = 'This list is'
		elseif args.only == 'lists' then
			hatnote = 'These lists are'
		elseif args.only == 'table' then
			hatnote = 'This table is'
		elseif args.only == 'tables' then
			hatnote = 'These tables are'
		else
			hatnote = 'This section is'
		end
		hatnote = hatnote .. ' an excerpt from '
		if section then
			hatnote = hatnote ..  .. page .. ' § ' .. section .. 
		else
			hatnote = hatnote ..  .. page .. 
		end
		hatnote = hatnote .. "" .. 
		hatnote = hatnote .. title:fullUrl('action=edit') .. ' edit'
		hatnote = hatnote ..  .. ""
		hatnote = require('Module:Hatnote')._hatnote(hatnote, {selfref=true}) or wikiError('Error generating hatnote')
	end
	-- Build the module options out of the template arguments and the desired defaults
	local options = {
		fileflags = numberFlags( args'files' or 1 ),
		paraflags = numberFlags( args'paragraphs' ),
		filesOnly = is( args'only' == 'file' or args'only' == 'files' ),
		listsOnly = is( args'only' == 'list' or args'only' == 'lists'),
		tablesOnly = is( args'only' == 'table' or args'only' == 'tables' ),
		keepTables = is( args'tables' or true ),
		keepRefs = is( args'references'  or true ),
		keepSubsections = is( args'subsections' ),
		nobold = not is( args'bold' ),
		fragment = args'fragment'
	}
	-- Get the excerpt itself
	if section then page = page .. '#' .. section end
	local ok, excerpt = pcall(e.get, page, options)
	if not ok then return wikiError(excerpt) end
	excerpt = "\n" .. excerpt -- line break is necessary to prevent broken tables and lists
	if mw.title.getCurrentTitle().isContentPage then excerpt = excerpt ..  end
	excerpt = frame:preprocess(excerpt)
	excerpt = mw.html.create(tag):addClass('excerpt'):wikitext(excerpt)
	-- Combine and return the elements
	return block:node(style):node(hatnote):node(excerpt)
end
-- Entry points for templates
function p.main(frame) return main(frame) end
function p.lead(frame) return portal(frame, "lead") end --  reads a randomly selected article linked from the given page
function p.linked(frame) return portal(frame, "linked") end --  reads a randomly selected article linked from the given page
function p.listitem(frame) return portal(frame, "listitem") end --  reads a randomly selected article listed on the given page
function p.random(frame) return portal(frame, "random") end --  reads any article (default for invoke with one argument)
function p.selected(frame) return portal(frame, "selected") end --  reads the article whose key is in the selected= parameter
function p.excerpt(frame) return excerpt(frame) end --  transcludes part of an article into another article
-- Entry points for other Lua modules
function p.get(page, options) return get(page, options) end
function p.getContent(page) return getContent(page) end
function p.getSection(text, section) return getSection(text, section) end
function p.getTables(text, options) return getTables(text, options) end
function p.getLists(text, options) return getLists(text, options) end
function p.parse(text, options) return parse(text, options) end
function p.parseImage(text, start) return parseImage(text, start) end
function p.parseArgs(frame) return parseArgs(frame) end
function p.getTemplateImages(text) return getTemplateImages(text) end
function p.checkImage(image) return checkImage(image) end
function p.cleanupText(text, options) return cleanupText(text, options) end
function p.numberFlags(str) return numberFlags(str) end
function p.getMoreLinkText(more) return getMoreLinkText(more) end
return p