มอดูล:Excerpt/sandbox

local p = {}
local mRedirect = require('Module:Redirect')
-- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect
local function getRedirectTarget(titleObject)
	local content = titleObject:getContent()
	if not content then return nil end
	return mRedirect.getTargetFromText(content)
end
local errors
-- Return blank text, or an error message if requested
local function err(text)
	if errors then error(text, 2) end
	return ""
end
-- In text, match pre..list1..post or pre..list2..post or ...
local function matchany(text, pre, list, post)
	local match
	for i = 1, #list do
		match = mw.ustring.match(text, pre .. listi .. post)
		if match then return match end
	end
	return nil
end
-- Get a page's content, following redirects, and processing file description pages for files.
-- Also returns the page name, or the target page name if a redirect was followed, or false if no page found
local function getContent(page, frame)
	local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo)
	if not title then return false, false end
	local redir = getRedirectTarget(title)
	if redir then title = mw.title.new(redir) end
	if title.namespace == 6 then
		frame = frame or mw.getCurrentFrame()
		return frame:preprocess(""), redir or title.prefixedText
	else
		return title:getContent(), redir or title.prefixedText
	end
end
-- Check image for suitability
local function checkimage(image)
	local page = matchany(image, "", {"Ffile", "Iimage"}, "%s*:%*") -- match File:(name) or Image:(name)
	if not page then return false end
	-- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.)
	if not matchany(page, "%.", {"GgIiFf", "JjPpEe?Gg", "PpNnGg", "SsVvGg", "TtIiFfFf", "XxCcFf"}, "%s*$") then
		return false
	end
	local desc = getContent(page)
	return ( desc and desc ~= "" and not mw.ustring.match(desc, "Nnon%-free") ) and true or false -- hide non-free image
end
-- Attempt to parse File:... or Image:..., either anywhere (start=false) or at the start only (start=true)
local function parseimage(text, start)
	local startre = ""
	if start then startre = "^" end -- a true flag restricts search to start of string
	local image = matchany(text, startre .. "%%%s*", {"Ffile", "Iimage"}, "%s*:.*") -- File: or Image: ...
	if image then
		image = mw.ustring.match(image, "%b%s*") -- matching ... to handle wikilinks nested in caption
	end
	return image
end
-- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested .. and {..}
local function parsecaption(caption)
	if not caption then return nil end
	local len = mw.ustring.len(caption)
	local pos = 1
	while pos ', '|imagemap=')
	-- find all images
	local hasImages = false
	local images = {}
	local capture_from = 1
	while capture_from  0 and not imagesi do
				i = i - 1
				if imagesi then
					if not captionsi then
						captionsi = parsecaption(caption)
					end
				end
			end
			capture_from = position
		else
			capture_from = mw.ustring.len(text)
		end
	end
	-- find all alt text
	local altTexts = {}
	for position, altText in mw.ustring.gmatch(text, "|%s*AaLlTt%s*=%s*()(^\n*)") do
		if altText then
			altText = mw.text.trim(altText)
			local altTextStart = mw.ustring.sub(altText, 1, 1)
			if altTextStart == '|' or altTextStart == '}' then altText = nil end
		end
		if altText then
			-- find nearest image, and use same index for altTexts table
			local i = position
			while i > 0 and not imagesi do
				i = i - 1
				if imagesi then
					if not altTextsi then
						altTextsi = altText
					end
				end
			end
		end
	end
	-- find all image sizes
	local imageSizes = {}
	for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*IiMmAaGgEe _?SsIiZzEe%s*=%s*()(\n*)") do
		local imageSize = mw.ustring.match(imageSizeMatch, "=%s*(\n*)")
		if imageSize then
			imageSize = mw.text.trim(imageSize )
			local imageSizeStart = mw.ustring.sub(imageSize, 1, 1)
			if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end
		end
		if imageSize then
			-- find nearest image, and use same index for imageSizes table
			local i = position
			while i > 0 and not imagesi do
				i = i - 1
				if imagesi then
					if not imageSizesi then
						imageSizesi = imageSize
					end
				end
			end
		end
	end
	-- sort the keys of the images table (in a table sequence), so that images can be iterated over in order
	local keys = {}
	for key, val in pairs(images) do
		table.insert(keys, key)
	end
	table.sort(keys)
	 -- add in relevant optional parameters for each image: caption, alt text and image size
	local imageTokens = {}
	for _, index in ipairs(keys) do
		local image = imagesindex
		local token = parseimage(image, true) -- look for image=File:... etc.
		if not token then
			image = mw.ustring.match(image, "^\n*") -- remove later arguments
			token = "" -- Add File: unless name already begins File: or Image:
			if not matchany(image, "^", {"Ffile", "Iimage"}, "%s*:") then
				token = token .. "File:"
			end
			token = token .. image
			local caption = captionsindex
			if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end
			local alt = altTextsindex
			if alt then token = token .. "|alt=" .. alt end
			local image_size = imageSizesindex
			if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end
			token = token .. ""
		end
		token = mw.ustring.gsub(token, "\n","") .. "\n"
		table.insert(imageTokens, token)
	end
	return imageTokens
end
-- Help gsub convert imagemaps into standard images
local function convertImagemap(imagemap)
	local image = matchany(imagemap, ">\n%s*", {"Iimage:", "Ffile:"}, "^\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
-- Help gsub to remove unwanted templates
local function striptemplate(t)
	-- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string)
	local unwanted = {"Eefn", "Eefn%-luarg", "Eelmn", "Rrp?", "Ssfnbmp", "Ssfbn", "NoteTag", "#Ttag:%s*Rref", "Rrefn?",
	 "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",
	 -- 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",
	 -- 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%??"}
	if matchany(t, "^", "")
	noref = mw.ustring.gsub(noref, "|%s*ref%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)
	if noref ~= t then return noref end
	return nil -- not an unwanted template: keep
end
-- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true}
local function numberflags(str)
	local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"}
	local flags = {}
	for _, r in pairs(ranges) do
		local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5
		if not max then	min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1
		if max then
			for p = min, max do flagsp = true end
		end
	end
	return flags
end
-- a basic parser to trim down extracted wikitext
--   @param text : Wikitext to be processed
--   @param options : A table of options...
--          options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept.
--          options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`
--          options.fileargs : args for the File: syntax, such as `left`
--   @param filesOnly : If set, only return the files and not the prose
local function parse(text, options, filesOnly)
	local allparas = true -- keep all paragraphs?
	if options.paraflags then
		if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end
		for _, v in pairs(options.paraflags) do
			if v then allparas = false end -- if any para specifically requested, don't keep all
		end
	end
	if filesOnly then
		allparas = false
		options.paraflags = {}
	end
	local maxfile = 0 -- for efficiency, stop checking images after this many have been found
	if options.fileflags then
		if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end
		for k, v in pairs(options.fileflags) do
			if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags
		end
	end
	
	local fileargs = options.fileargs and mw.text.trim(options.fileargs)
	if fileargs ==  then fileargs = nil end
	local leadstart = nil -- have we found some text yet?
	local t = "" -- the stripped down output text
	local filetext = "" -- output text with concatenated ...\n entries
	local files = 0 -- how many images so far
	local paras = 0 -- how many paragraphs so far
	local startLine = true -- at the start of a line (no non-spaces found since last \n)?
	text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space
	repeat -- loop around parsing a template, image or paragraph
		local token = mw.ustring.match(text, "^%b{}%s*") or false -- 
		local line = mw.ustring.match(text, "^\n*")
		if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates)
			line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line
			-- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line
			if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "