-- Module:Excerpt implements the Excerpt template
-- Get localized data
-- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt
local d = require("Module:Excerpt/i18n")
-- Authors: User:Sophivorus, User:Certes, User:Aidan9382 & others
-- License: CC-BY-SA-3.0
local parser = require( 'Module:WikitextParser' )
local p = {}
local yesno = require( 'Module:Yesno' )
local ok, config = pcall( require, 'Module:Excerpt/config' )
-- Helper function to debug
if not ok then config = {} end
-- Returns blank text or an error message if requested
local errors
local function err(msg, a, b)
local text = mw.ustring.format(d.error[msg] or msg or "", a, b)
if errors then error(text, 2) end
return ""
end
local Excerpt = {}
-- 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
-- Main entry point for templates
-- Helper function to match from a list regular expressions
function Excerpt.main( frame )
-- Like so: match pre..list[1]..post or pre..list[2]..post or ...
local function matchAny(text, pre, list, post, init)
-- Make sure the requested page exists and get the wikitext
local match = {}
local page = Excerpt.getArg( 1 )
for i = 1, #list do
if not page or page == '{{{1}}}' then return Excerpt.getError( 'no-page' ) end
match = { mw.ustring.match(text, pre .. list[i] .. post, init) }
local title = mw.title.new( page )
if match[1] then return unpack(match) end
if not title then return Excerpt.getError( 'invalid-title', page ) end
local fragment = title.fragment -- save for later
if title.isRedirect then
title = title.redirectTarget
if fragment == "" then
fragment = title.fragment -- page merge potential
end
end
if not title.exists then return Excerpt.getError( 'page-not-found', page ) end
return nil
page = title.prefixedText
end
local wikitext = title:getContent()
-- Get the template params and process them
-- Helper function to convert imagemaps into standard images
local params = {
local function convertImageMap(imagemap)
hat = yesno( Excerpt.getArg( 'hat', true ) ),
local image = matchAny(imagemap, "[>\n]%s*", d.fileNamespaces, "[^\n]*")
this = Excerpt.getArg( 'this' ),
if image then
only = Excerpt.getArg( 'only' ),
return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]"
files = Excerpt.getArg( 'files', Excerpt.getArg( 'file' ) ),
lists = Excerpt.getArg( 'lists', Excerpt.getArg( 'list' ) ),
tables = Excerpt.getArg( 'tables', Excerpt.getArg( 'table' ) ),
templates = Excerpt.getArg( 'templates', Excerpt.getArg( 'template' ) ),
paragraphs = Excerpt.getArg( 'paragraphs', Excerpt.getArg( 'paragraph' ) ),
references = yesno( Excerpt.getArg( 'references', true ) ),
subsections = yesno( Excerpt.getArg( 'subsections', false ) ),
links = yesno( Excerpt.getArg( 'links', true ) ),
bold = yesno( Excerpt.getArg( 'bold', false ) ),
briefDates = yesno( Excerpt.getArg( 'briefdates', false ) ),
inline = yesno( Excerpt.getArg( 'inline' ) ),
quote = yesno( Excerpt.getArg( 'quote' ) ),
more = yesno( Excerpt.getArg( 'more' ) ),
class = Excerpt.getArg( 'class' ),
displayTitle = Excerpt.getArg( 'displaytitle', page ),
}
-- Make sure the requested section exists and get the excerpt
local excerpt
local section = Excerpt.getArg( 2, fragment )
section = mw.text.trim( section )
if section == '' then section = nil end
if section then
excerpt = parser.getSectionTag( wikitext, section )
if not excerpt then
if params.subsections then
excerpt = parser.getSection( wikitext, section )
else
local sections = parser.getSections( wikitext )
excerpt = sections[ section ]
end
end
if not excerpt then return Excerpt.getError( 'section-not-found', section ) end
if excerpt == '' then return Excerpt.getError( 'section-empty', section ) end
else
excerpt = parser.getLead( wikitext )
return "" -- remove entire block if image can't be extracted
if excerpt == '' then return Excerpt.getError( 'lead-empty' ) end
end
end
-- Remove noinclude bits
-- Helper function to convert a comma-separated list of numbers or min-max ranges into a list of booleans
excerpt = excerpt:gsub( '<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>', '' )
-- For example: "1,3-5" to {1=true,2=false,3=true,4=true,5=true}
local function numberFlags(str)
-- Filter various elements from the excerpt
local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"}
excerpt = Excerpt.filterFiles( excerpt, params.files )
local flags = {}
excerpt = Excerpt.filterLists( excerpt, params.lists )
for _, r in pairs(ranges) do
excerpt = Excerpt.filterTables( excerpt, params.tables )
local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5
excerpt = Excerpt.filterParagraphs( excerpt, params.paragraphs )
if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1
if max then
-- If no file is found, try to get one from the infobox
for p = min, max do flags[p] = true end
if ( params.only == 'file' or params.only == 'files' or not params.only and ( not params.files or params.files ~= '0' ) ) -- caller asked for files
and not section -- and we're in the lead section
and config.captions -- and we have the config option required to try finding files in infoboxes
and #parser.getFiles( excerpt ) == 0 -- and there're no files in the excerpt
then
excerpt = Excerpt.addInfoboxFile( excerpt )
end
-- Filter the templates by appending the templates blacklist to the templates filter
if config.blacklist then
local blacklist = table.concat( config.blacklist, ',' )
if params.templates then
if string.sub( params.templates, 1, 1 ) == '-' then
params.templates = params.templates .. ',' .. blacklist
end
else
params.templates = '-' .. blacklist
end
end
excerpt = Excerpt.filterTemplates( excerpt, params.templates )
return flags
end
-- Leave only the requested elements
-- Helper function to convert template arguments into an array of arguments fit for main()
if params.only == 'file' or params.only == 'files' then
local function parseArgs(frame)
local argsfiles = {}parser.getFiles( excerpt )
excerpt = params.only == 'file' and files[1] or table.concat( files, '\n\n' )
for key, value in pairs(frame:getParent().args) do args[key] = value end
end
for key, value in pairs(frame.args) do args[key] = value end -- args from a Lua call have priority over parent args from template
if params.only == 'list' or params.only == 'lists' then
args.paraflags = numberFlags(args["paragraphs"] or "") -- parse paragraphs: "1,3-5" to {"1","3-5"}
local lists = parser.getLists( excerpt )
args.fileflags = numberFlags(args["files"] or "") -- parse file numbers
excerpt = params.only == 'list' and lists[1] or table.concat( lists, '\n\n' )
return args
end
if params.only == 'table' or params.only == 'tables' then
local tables = parser.getTables( excerpt )
excerpt = params.only == 'table' and tables[1] or table.concat( tables, '\n\n' )
end
if params.only == 'paragraph' or params.only == 'paragraphs' then
local paragraphs = parser.getParagraphs( excerpt )
excerpt = params.only == 'paragraph' and paragraphs[1] or table.concat( paragraphs, '\n\n' )
end
if params.only == 'template' or params.only == 'templates' then
local templates = parser.getTemplates( excerpt )
excerpt = params.only == 'template' and templates[1] or table.concat( templates, '\n\n' )
end
-- @todo Make more robust and move downwards
-- Helper function to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT
if params.briefDates then
local function stripTemplate(t)
excerpt = Excerpt.fixDates( excerpt )
-- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string)
end
if matchAny(t, "^{{%s*", d.unwantedInlineTemplates, "%s*%f[|}]") then return "" end
-- Remove unwanted elements
-- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed
excerpt = Excerpt.removeComments( excerpt )
local noRef = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "")
excerpt = Excerpt.removeSelfLinks( excerpt )
noRef = mw.ustring.gsub(noRef, "|%s*ref%s*%f[|}]", "")
excerpt = Excerpt.removeNonFreeFiles( excerpt )
excerpt = Excerpt.removeBehaviorSwitches( excerpt )
-- Fix or remove the references
-- If a wanted template has unwanted nested templates, purge them too
if params.references then
noRef = mw.ustring.sub(noRef, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noRef, 3), "%b{}", stripTemplate)
excerpt = Excerpt.fixReferences( excerpt, page, wikitext )
else
excerpt = Excerpt.removeReferences( excerpt )
end
-- Remove wikilinks
-- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar
if not params.links then
noRef = mw.ustring.gsub(noRef, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1")
excerpt = Excerpt.removeLinks( excerpt )
end
-- Link the bold text near the start of most leads and then remove it
-- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English
if not section then
noRef = mw.ustring.gsub(noRef, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1")
excerpt = Excerpt.linkBold( excerpt, page )
end
if not params.bold then
excerpt = Excerpt.removeBold( excerpt )
end
-- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly
if noRef ~= t then return noRef end
excerpt = excerpt:gsub( '\n\n\n+', '\n\n' )
excerpt = mw.text.trim( excerpt )
excerpt = '\n' .. excerpt .. '\n'
-- Remove nested categories
return nil -- not an unwanted template: keep
excerpt = frame:preprocess( excerpt )
end
excerpt = Excerpt.removeCategories( excerpt )
-- Add tracking categories
-- Get a page's content, following redirects, and processing file description pages for files
if config.categories then
-- Also returns the page name, or the target page name if a redirect was followed, or false if no page found
excerpt = Excerpt.addTrackingCategories( excerpt )
local function getContent(page, frame)
end
local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo)
if not title then return false, false end
-- Build the final output
local target = title.redirectTarget
if targetparams.inline then title = target end
return mw.text.trim( excerpt )
end
local tag = params.quote and 'blockquote' or 'div'
return title:getContent(), title.prefixedText
local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( params.class )
end
if config.styles then
-- Return the target of the redirect,
local styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } )
-- or the same title if it's not a redirect
block:node( styles )
-- or nil if the title was not found
local function getTarget(page)
local title = mw.title.new(page)
if title then
local target = title.redirectTarget
if target then
return target.prefixedText
end
return title.prefixedText
end
end
-- Check image for suitability
local function checkImage(image)
local page = matchAny(image, "", d.fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name)
if not page then return false end
if params.hat then
-- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.)
local hat = Excerpt.getHat( page, section, params )
if not matchAny(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then
block:node( hat )
return false
end
excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt )
local fileDescription, fileTitle = getContent(page) -- get file description and title after following any redirect
block:node( excerpt )
if fileDescription and fileDescription ~= "" then -- found description on local wiki
if mw.ustring.match(fileDescription, "[Nn]on%-free") then return false end
if params.more then
fileDescription = mw.ustring.gsub(fileDescription, "%b{}", stripTemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess
local more = Excerpt.getReadMore( page, section )
elseif not fileTitle then
block:node( more )
return false
else
-- try commons
fileDescription = "{{" .. fileTitle .. "}}"
end
frame = frame or mw.getCurrentFrame()
fileDescription = frame:preprocess(fileDescription)
return block
return ( fileDescription and fileDescription ~= "" and not mw.ustring.match(fileDescription, "[Nn]on%-free") ) and true or false -- hide non-free image
end
-- Filter the files in the given wikitext against the given filter
-- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true)
local function parseImageExcerpt.filterFiles(text wikitext, startfilter )
if not filter then return wikitext end
local startre = ""
local filters, isBlacklist = Excerpt.parseFilter( filter )
if start then startre = "^" end -- a true flag restricts search to start of string
local files = parser.getFiles( wikitext )
local image = matchAny(text, startre .. "%[%[%s*", d.fileNamespaces, "%s*:.*") -- [[File: or [[Image: ...
for index, file in pairs( files ) do
if image then
local name = parser.getFileName( file )
image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption
if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) )
or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then
wikitext = Excerpt.removeString( wikitext, file )
end
end
return imagewikitext
end
-- Filter the lists in the given wikitext against the given filter
-- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..}
local function parseCaptionExcerpt.filterLists(caption wikitext, filter )
if not captionfilter then return nilwikitext end
local lengthfilters, isBlacklist = mwExcerpt.ustring.lenparseFilter(caption filter )
local positionlists = 1parser.getLists( wikitext )
for index, list in pairs( lists ) do
while position <= length do
if isBlacklist and Excerpt.matchFilter( index, filters )
local linkStart, linkEnd = mw.ustring.find(caption, "%b[]", position)
or not isBlacklist and not Excerpt.matchFilter( index, filters ) then
linkStart = linkStart or length + 1 -- avoid comparison with nil when no link
wikitext = Excerpt.removeString( wikitext, list )
local templateStart, templateEnd = mw.ustring.find(caption, "%b{}", position)
templateStart = templateStart or length + 1 -- avoid comparison with nil when no template
local argEnd = mw.ustring.find(caption, "[|}]", position) or length + 1
if linkStart < templateStart and linkStart < argEnd then
position = linkEnd + 1 -- skip wikilink
elseif templateStart < argEnd then
position = templateEnd + 1 -- skip template
else -- argument ends before the next wikilink or template
return mw.ustring.sub(caption, 1, argEnd - 1)
end
end
return wikitext
return caption -- No terminator found: return entire caption
end
-- Filter the tables in the given wikitext against the given filter
-- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}}
function Excerpt.filterTables( wikitext, filter )
local function argImage(text)
if not filter then return wikitext end
local token = nil
local filters, isBlacklist = Excerpt.parseFilter( filter )
local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=")
local tables = parser.getTables( wikitext )
if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image
for index, t in pairs( tables ) do
local id = string.match( t, '{|[^\n]-id%s*=%s*["\']?([^"\'\n]+)["\']?[^\n]*\n' )
-- ensure image map is captured
if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( id, filters ) )
text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=')
or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( id, filters ) ) then
wikitext = Excerpt.removeString( wikitext, t )
-- find all images
local hasImages = false
local images = {}
local captureFrom = 1
while captureFrom < mw.ustring.len(text) do
local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", captureFrom)
if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image
local lcArgName = mw.ustring.lower(argname)
if mw.ustring.find(lcArgName, "caption")
or mw.ustring.find(lcArgName, "size")
or mw.ustring.find(lcArgName, "upright") then
image = nil
end
end
if image then
hasImages = true
images[position] = image
captureFrom = position
else
captureFrom = mw.ustring.len(text)
end
end
return wikitext
captureFrom = 1
end
while captureFrom < mw.ustring.len(text) do
local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", captureFrom)
-- Filter the paragraphs in the given wikitext against the given filter
if image then
function Excerpt.filterParagraphs( wikitext, filter )
hasImages = true
if not filter then return wikitext end
images[position] = image
local filters, isBlacklist = Excerpt.parseFilter( filter )
captureFrom = position
local paragraphs = parser.getParagraphs( wikitext )
else
for index, paragraph in pairs( paragraphs ) do
captureFrom = mw.ustring.len(text)
if isBlacklist and Excerpt.matchFilter( index, filters )
end
or not isBlacklist and not Excerpt.matchFilter( index, filters ) then
end
wikitext = Excerpt.removeString( wikitext, paragraph )
captureFrom = 1
while captureFrom < mw.ustring.len(text) do
local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", captureFrom)
if image then
hasImages = true
if not images[position] then
images[position] = image
end
captureFrom = position
else
captureFrom = mw.ustring.len(text)
end
end
return wikitext
end
-- Filter the templates in the given wikitext against the given filter
if not hasImages then return nil end
function Excerpt.filterTemplates( wikitext, filter )
if not filter then return wikitext end
-- find all captions
local filters, isBlacklist = Excerpt.parseFilter( filter )
local captions = {}
local templates = parser.getTemplates( wikitext )
captureFrom = 1
for index, template in pairs( templates ) do
while captureFrom < mw.ustring.len(text) do
local name = parser.getTemplateName( template )
local position, caption = matchAny(text, "|%s*", d.captionParams, "%s*=%s*()([^\n]+)", captureFrom)
if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) )
if caption then
or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then
-- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n"
wikitext = Excerpt.removeString( wikitext, template )
local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position)
if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end
caption = mw.text.trim(caption)
local captionStart = mw.ustring.sub(caption, 1, 1)
if captionStart == '|' or captionStart == '}' then caption = nil end
end
if caption then
-- find nearest image, and use same index for captions table
local i = position
while i > 0 and not images[i] do
i = i - 1
if images[i] then
if not captions[i] then
captions[i] = parseCaption(caption)
end
end
end
captureFrom = position
else
captureFrom = mw.ustring.len(text)
end
end
return wikitext
end
function Excerpt.addInfoboxFile( excerpt )
-- find all alt text
-- We cannot distinguish the infobox from the other templates, so we search them all
local altTexts = {}
local templates = parser.getTemplates( excerpt )
for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do
for _, template in pairs( templates ) do
if altText then
local parameters = parser.getTemplateParameters( template )
local file, captions, caption, cssClasses, cssClass
-- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}}
for _, pair in pairs( config.captions ) do
local lookFrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }}
file = pair[1]
mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b
file = parameters[file]
mw.ustring.match(altText, ".*%[%b[]%]()") or 1)
if file and Excerpt.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then
file = string.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg
local length = mw.ustring.len(altText)
captions = pair[2]
local afterText = math.min( -- find position after whichever comes first: end of string, }} or |
for _, p in pairs( captions ) do
mw.ustring.match(altText, "()}}", lookFrom) or length+1,
if parameters[ p ] then caption = parameters[ p ] break end
mw.ustring.match(altText, "()|", lookFrom) or length+1)
altText = mw.ustring.sub(altText, 1, afterText-1) -- chop off |... or }}... which is not part of [[...]] or {{...}}
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 images[i] do
i = i - 1
if images[i] then
if not altTexts[i] then
altTexts[i] = altText
end
end
-- Check for CSS classes
end
-- We opt to use skin-invert-image instead of skin-invert
end
-- in all other cases, the CSS provided in the infobox is used
end
if pair[3] then
cssClasses = pair[3]
-- find all image sizes
for _, p in pairs( cssClasses ) do
local imageSizes = {}
if parameters[ p ] then
for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do
cssClass = ( parameters[ p ] == 'skin-invert' ) and 'skin-invert-image' or parameters[ p ]
local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)")
break
if imageSize then
end
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 images[i] do
i = i - 1
if images[i] then
if not imageSizes[i] then
imageSizes[i] = imageSize
end
end
local class = cssClass and ( '|class=' .. cssClass ) or ''
return '[[File:' .. file .. class .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt
end
end
end
return excerpt
end
function Excerpt.removeNonFreeFiles( wikitext )
-- sort the keys of the images table (in a table sequence), so that images can be iterated over in order
local keysfiles = {}parser.getFiles( wikitext )
for key_, valfile in pairs(images files ) do
local fileName = 'File:' .. parser.getFileName( file )
table.insert(keys, key)
local fileTitle = mw.title.new( fileName )
end
if fileTitle then
table.sort(keys)
local fileDescription = fileTitle:getContent()
if not fileDescription or fileDescription == '' then
-- add in relevant optional parameters for each image: caption, alt text and image size
local imageTokensframe = {}mw.getCurrentFrame()
fileDescription = frame:preprocess( '{{' .. fileName .. '}}' ) -- try Commons
for _, index in ipairs(keys) do
end
local image = images[index]
if fileDescription and string.match( fileDescription, '[Nn]on%-free' ) then
local token = parseImage(image, true) -- look for image=[[File:...]] etc.
wikitext = Excerpt.removeString( wikitext, file )
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, "^", d.fileNamespaces, "%s*:") then
token = token .. "File:"
end
token = token .. image
local caption = captions[index]
if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end
local alt = altTexts[index]
if alt then token = token .. "|alt=" .. alt end
local image_size = imageSizes[index]
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 imageTokenswikitext
end
local function modifyImageExcerpt.getHat(image page, fileArgssection, params )
local hat
if fileArgs then
for _, filearg in pairs(mw.text.split(fileArgs, "|")) do -- handle fileArgs=left|border etc.
local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright"
local group = {fa} -- group of "border" is ["border"]...
for _, g in pairs(d.imageParams) do
for _, a in pairs(g) do
if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"]
end
end
for _, a in pairs(group) do
image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc.
image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc.
end
-- Build the text
image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc.
if params.this then
end
hat = params.this
elseif params.quote then
hat = Excerpt.getMessage( 'this' )
elseif params.only then
hat = Excerpt.getMessage( params.only )
else
hat = Excerpt.getMessage( 'section' )
end
hat = hat .. ' ' .. Excerpt.getMessage( 'excerpt' )
return image
end
-- Build the link
-- a basic parser to trim down extracted wikitext
if section then
-- @param text : Wikitext to be processed
hat = hat .. ' [[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. params.displayTitle
-- @param options : A table of options...
.. ' § ' .. section:gsub( '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links
-- 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.
else
-- 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}`
hat = hat .. ' [[:' .. page .. '|' .. params.displayTitle .. ']].'
-- 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 allParagraphs = 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 allParagraphs = false end -- if any para specifically requested, don't keep all
end
end
if filesOnly then
allParagraphs = false
options.paraflags = {}
end
-- Build the edit link
local maxfile = 0 -- for efficiency, stop checking images after this many have been found
local title = mw.title.new( page )
if options.fileflags then
local editUrl = title:fullUrl( 'action=edit' )
if type(options.fileflags) ~= "table" then options.fileflags = numberFlags(options.fileflags) end
hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>['
for k, v in pairs(options.fileflags) do
hat = hat .. editUrl .. ' ' .. mw.message.new( 'editsection' ):plain()
if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags
hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>'
end
if config.hat then
local frame = mw.getCurrentFrame()
hat = config.hat .. hat .. '}}'
hat = frame:preprocess( hat )
else
hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat )
end
local fileArgs = options.fileargs and mw.text.trim(options.fileargs)
if fileArgs == '' then fileArgs = nil end
return hat
local leadStart = nil -- have we found some text yet?
end
local t = "" -- the stripped down output text
local fileText = "" -- output text with concatenated [[File:Foo|...]]\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)?
function Excerpt.getReadMore( page, section )
text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space
local link = "'''[[" .. page
if section then
-- Add named files
link = link .. '#' .. section
local f = options.files
if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list
f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1)
f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1)
f = "[[File:" .. f .. "]]"
f = modifyImage(f, "thumb")
f = modifyImage(f, fileArgs)
if checkImage(f) then fileText = fileText .. f .. "\n" end
end
local text = Excerpt.getMessage( 'more' )
link = link .. '|' .. text .. "]]'''"
link = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( link )
return link
end
repeat -- loopFix aroundbirth parsingand adeath templatedates, imagebut only in the orfirst paragraph
-- @todo Use parser.getParagraphs() to get the first paragraph
local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |}
function Excerpt.fixDates( excerpt )
if not leadStart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started
local start = 1 -- skip initial templates
local s
local line = mw.ustring.match(text, "[^\n]*")
local e = 0
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)
repeat
line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line
start = e + 1
line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line
s, e = mw.ustring.find( excerpt, '%s*%b{}%s*', start )
-- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line
until not s or s > start
if mw.ustring.find(line, "%S") and not matchAny(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then
s, e = mw.ustring.find( excerpt, '%b()', start ) -- get (...), which may be (year–year)
token = nil
if s and s < start + 100 then -- look only near the start
local excerptStart = mw.ustring.sub( excerpt, s, e )
local year1, conjunction, year2 = string.match( excerptStart, '(%d%d%d+)(.-)(%d%d%d+)' )
if year1 and year2 and ( string.match( conjunction, '[%-–—]' ) or string.match( conjunction, '{{%s*[sS]nd%s*}}' ) ) then
local y1 = tonumber( year1 )
local y2 = tonumber( year2 )
if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( '%Y' ) ) then
excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. '–' .. year2 .. mw.ustring.sub( excerpt, e )
end
end
end
return excerpt
end
-- Replace the first call to each reference defined outside of the excerpt for the full reference, to prevent undefined references
if token then -- found a template which is not the prefix to a line of text
-- Then prefix the page title to the reference names to prevent conflicts
-- that is, replace <ref name="Foo"> for <ref name="Title of the article Foo">
if leadStart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.)
-- and also <ref name="Foo" /> for <ref name="Title of the article Foo" />
if not filesOnly and not startLine then t = t .. token end
-- also remove reference groups: <ref name="Foo" group="Bar"> for <ref name="Title of the article Foo">
-- and <ref group="Bar"> for <ref>
elseif matchAny(token, "{{%s*", d.wantedBlockTemplates, "%s*%f[|}]") then
-- @todo The current regex may fail in cases with both kinds of quotes, like <ref name="Darwin's book">
t = t .. token -- keep wanted block templates
function Excerpt.fixReferences( excerpt, page, wikitext )
local references = parser.getReferences( excerpt )
elseif is(options.keepTables) and mw.ustring.sub(token, 1, 2) == '{|' then
local fixed = {}
t = t .. token -- keep tables
for _, reference in pairs( references ) do
local name = parser.getTagAttribute( reference, 'name' )
elseif files < maxfile then -- discard template, but if we are still collecting images...
if not fixed[ name ] then -- fix each reference only once
local images = argImage(token) or {}
local content = parser.getTagContent( reference )
if not images then
if not content then -- reference is self-closing
local image = parseImage(token, false) -- look for embedded [[File:...]], |image=, etc.
local full = parser.getReference( excerpt, name )
if image then table.insert(images, image) end
if not full then -- the reference is not defined in the excerpt
end
full = parser.getReference( wikitext, name )
for _, image in ipairs(images) do
if full then
if files < maxfile and checkImage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.)
excerpt = excerpt:gsub( Excerpt.escapeString( reference ), Excerpt.escapeString( full ), 1 )
files = files + 1 -- count the file, whether displaying it or not
if options.fileflags and options.fileflags[files] then -- if displaying this image
image = modifyImage(image, "thumb")
image = modifyImage(image, fileArgs)
fileText = fileText .. image
end
end
table.insert( fixed, name )
end
end
end
else -- the next token in text is not a template
end
token = parseImage(text, true)
-- Prepend the page title to the reference names to prevent conflicts with other references in the transcluding page
if token then -- the next token in text looks like an image
excerpt = excerpt:gsub( '< *[Rr][Ee][Ff][^>]*name *= *["\']?([^"\'>/]+)["\']?[^>/]*(/?) *>', '<ref name="' .. page:gsub( '"', '' ) .. ' %1"%2>' )
if files < maxfile and checkImage(token) then -- if more images are wanted and this is a wanted image
-- Remove reference groups because they don't apply to the transcluding page
files = files + 1
excerpt = excerpt:gsub( '< *[Rr][Ee][Ff] *group *= *["\']?[^"\'>/]+["\'] *>', '<ref>' )
if options.fileflags and options.fileflags[files] then
return excerpt
local image = token -- copy token for manipulation by adding |right etc. without changing the original
image = modifyImage(image, fileArgs)
fileText = fileText .. image
end
end
else -- got a paragraph, which ends at a file, image, blank line or end of text
local afterEnd = mw.ustring.len(text) + 1
local blankPosition = mw.ustring.find(text, "\n%s*\n") or afterEnd -- position of next paragraph delimiter (or end of text)
local endPosition = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter
mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterEnd,
mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterEnd,
blankPosition)
token = mw.ustring.sub(text, 1, endPosition-1)
if blankPosition < afterEnd and blankPosition == endPosition then -- paragraph ends with a blank line
token = token .. mw.ustring.match(text, "\n%s*\n", blankPosition)
end
local isHatnote = not(leadStart) and mw.ustring.sub(token, 1, 1) == ':'
if not isHatnote then
leadStart = leadStart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section
paras = paras + 1
if allParagraphs or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted
end
end -- of "else got a paragraph"
end -- of "else not a template"
if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text
startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line?
until not text or text == "" or not token or token == "" -- loop until all text parsed
text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line
return fileText, text
end
local function cleanupTextExcerpt.removeReferences(text, optionsexcerpt )
local references = parser.getReferences( excerpt )
text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments
for _, reference in pairs( references ) do
text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits
excerpt = Excerpt.removeString( excerpt, reference )
if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible
text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections
text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section
text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section
end
return excerpt
if not is(options.keepSubsections) then
text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it
text = mw.ustring.gsub(text, "^==.*","") -- ...even if the lead is empty
end
if not is(options.keepRefs) then
text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere
text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs
text = mw.ustring.gsub(text, "%b{}", stripTemplate) -- remove unwanted templates such as references
end
text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores
text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImageMap) -- convert imagemaps into standard images
text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents
text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches
text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates
text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars
text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates
text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories
text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon
return text
end
function Excerpt.removeCategories( excerpt )
-- Parse a ==Section== from a page
local categories = parser.getCategories( excerpt )
local function getSection(text, section, mainOnly)
for _, category in pairs( categories ) do
local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc.
excerpt = Excerpt.removeString( excerpt, category )
local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)")
if not content then return nil end -- no such section
local nextSection
if mainOnly then
nextSection = "\n==.*" -- Main part of section terminates at any level of header
else
nextSection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "===="
end
return excerpt
content = mw.ustring.gsub(content, nextSection, "") -- remove later sections with headings at this level or higher
return content
end
function Excerpt.removeBehaviorSwitches( excerpt )
-- Remove unmatched <tag> or </tag> tags
return excerpt:gsub( '__[A-Z]+__', '' )
local function fixTags(text, tag)
end
local startCount = 0
for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startCount = startCount + 1 end
function Excerpt.removeComments( excerpt )
local endCount = 0
return excerpt:gsub( '<!%-%-.-%-%->', '' )
for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endCount = endCount + 1 end
end
function Excerpt.removeBold( excerpt )
if startCount > endCount then -- more <tag> than </tag>: remove the last few <tag>s
return excerpt:gsub( "'''", '' )
local i = 0
text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t)
i = i + 1
if i > endCount then return "" else return nil end
end) -- "end" here terminates the anonymous replacement function(t) passed to gsub
elseif endCount > startCount then -- more </tag> than <tag>: remove the first few </tag>s
text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endCount - startCount)
end
return text
end
function Excerpt.removeLinks( excerpt )
-- Main function returns a string value: text of the lead of a page
local links = parser.getLinks( excerpt )
local function main(pageNames, options)
for _, link in pairs( links ) do
if not pageNames or #pageNames < 1 then return err("pageNames") end
excerpt = Excerpt.removeString( excerpt, link )
local pageName
local text
local pageCount = #pageNames
local firstPage = pageNames[1] or "(nil)" -- save for error message, as it the name will be deleted
local gotOptions
local pageOptionsString
local section
-- read the page, or a random one if multiple pages were provided
if pageCount > 1 then math.randomseed(os.time()) end
while not text and pageCount > 0 do
local pageNumber = 1
if pageCount > 1 then pageNumber = math.random(pageCount) end -- pick a random title
pageName = pageNames[pageNumber]
if pageName and pageName ~= "" then
-- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2...
local pn
pn, gotOptions, pageOptionsString = mw.ustring.match(pageName, "^%s*(%[%b[]%])%s*(|?)(.*)")
if pn then
pageName = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text
else -- we have page or page|opt...
pageName, gotOptions, pageOptionsString = mw.ustring.match(pageName, "%s*([^|]*[^|%s])%s*(|?)(.*)")
end
if pageName and pageName ~= "" then
local pn
pn, section = mw.ustring.match(pageName, "(.-)#(.*)")
pageName = pn or pageName
text, normalisedPageName = getContent(pageName)
if is(options.fragment) then
local frame = mw.getCurrentFrame()
text = frame:callParserFunction('#lst', normalisedPageName, options.fragment)
end
if not normalisedPageName then
return err("noTitle", pageName)
else
pageName = normalisedPageName
end
if text and options.nostubs then
local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}")
if isStub then text = nil end
end
if not section then
section = mw.ustring.match(pageName, ".-#(.*)") -- parse redirect to Page#Section
end
if text and section and section ~= "" then text = getSection(text, section) end
end
end
if not text then table.remove(pageNames, pageNumber) end -- this one didn't work; try another
pageCount = pageCount - 1 -- ensure that we exit the loop after at most #pageNames iterations
end
return excerpt
if not text then return err("firstPage", firstPage) end
end
-- @todo Use parser.getLinks
text = cleanupText(text, options)
function Excerpt.removeSelfLinks( excerpt, page )
local lang = mw.language.getContentLanguage()
local pageOptions = {} -- pageOptions (even if value is "") have priority over global options
local page = Excerpt.escapeString( mw.title.getCurrentTitle().prefixedText )
for k, v in pairs(options) do pageOptions[k] = v end
local ucpage = lang:ucfirst( page )
if gotOptions and gotOptions ~= "" then
local lcpage = lang:lcfirst( page )
for _, t in pairs(mw.text.split(pageOptionsString, "|")) do
excerpt = excerpt
local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$")
:gsub( '%[%[(' .. ucpage .. ')%]%]', '%1' )
pageOptions[k] = v
:gsub( '%[%[(' .. lcpage .. ')%]%]', '%1' )
end
:gsub( '%[%[' .. ucpage .. '|([^]]+)%]%]', '%1' )
pageOptions.paraflags = numberFlags(pageOptions["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"}
:gsub( '%[%[' .. lcpage .. '|([^]]+)%]%]', '%1' )
pageOptions.fileflags = numberFlags(pageOptions["files"] or "") -- parse file numbers
return excerpt
if pageOptions.more and pageOptions.more == "" then pageOptions.more = "Read more..." end -- more= is short for this default text
end
local fileText
fileText, text = parse(text, pageOptions)
-- replaceReplace the bold title or synonym near the start of the articlepage by a wikilinklink to the articlepage
function Excerpt.linkBold( excerpt, page )
local lang = mw.language.getContentLanguage()
local position = mw.ustring.find(text excerpt, "'''" .. lang:ucfirst(pageName page ) .. "'''", 1, true ) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc)
or mw.ustring.find(text excerpt, "'''" .. lang:lcfirst(pageName page ) .. "'''", 1, true ) -- plain search: special characters in pageNamepage represent themselves
if position then
local length = mw.ustring.len(pageName page )
textexcerpt = mw.ustring.sub(text excerpt, 1, position + 2 ) .. "'[["' .. mw.ustring.sub(text excerpt, position + 3, position + length + 2 ) .. "']]"' .. mw.ustring.sub(text excerpt, position + length + 3, -1 ) -- link it
else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name)
textexcerpt = mw.ustring.gsub(text excerpt, "()'''(.-'*)'''", function ( a, b )
if anot <mw.ustring.find( 100b, '%[' ) and not mw.ustring.find( b, "'%["{' ) then --- if earlynot inwikilinked articleor andsome notweird wikilinkedtemplate
return "'''[[" .. pageNamepage .. "'|"' .. b .. "]]'''" -- replace '''Foo''' by '''[[pageNamepage|Foo]]'''
else
return nil -- instruct gsub to make no change
end
end, 1 ) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub
end
return excerpt
end
function Excerpt.addTrackingCategories( excerpt )
-- remove '''bold text''' if requested
local currentTitle = mw.title.getCurrentTitle()
if is(pageOptions.nobold) then text = mw.ustring.gsub(text, "'''", "") end
local contentCategory = config.categories.content
if contentCategory and currentTitle.isContentPage then
excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]'
end
local namespaceCategory = config.categories[ currentTitle.namespace ]
if namespaceCategory then
excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]'
end
return excerpt
end
-- Helper method to match from a list of regular expressions
text = fileText .. text
-- Like so: match pre..list[1]..post or pre..list[2]..post or ...
function Excerpt.matchAny( text, pre, list, post, init )
local match = {}
for i = 1, #list do
match = { mw.ustring.match( text, pre .. list[ i ] .. post, init ) }
if match[1] then return unpack( match ) end
end
return nil
end
-- Helper function to get arguments
-- Seek and destroy unterminated templates and wikilinks
-- args from Lua calls have priority over parent args from template
repeat -- hide matched {{template}}s including nested templates
function Excerpt.getArg( key, default )
local t = text
local frame = mw.getCurrentFrame()
text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape
for k, value in pairs( frame:getParent().args ) do
text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math>
if k == key and mw.text.trim( value ) ~= '' then
until text == t
return value
repeat -- do similar for [[wikilink]]s
local t = text
text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27")
until text == t
text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc.
text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text
text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc.
-- Ensure div tags match
text = fixTags(text, "div")
if pageOptions.more then text = text .. " '''[[" .. pageName .. "|" .. pageOptions.more .. "]]'''" end -- wikilink to article for more info
if pageOptions.list and not pageOptions.showall then -- add a collapsed list of pages which might appear
local listtext = pageOptions.list
if listtext == "" then listtext = "Other articles" end
text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist"
for _, p in pairs(pageNames) do
if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end
end
text = text .. "}}\n{{collapse bottom}}"
end
for k, value in pairs( frame.args ) do
if k == key and mw.text.trim( value ) ~= '' then
return text
return value
end
end
return default
end
-- Helper method to get an error message
-- Shared invocation function for English Wikipedia templates
-- This method also categorizes the current page in one of the configured error categories
-- May not work properly on other wikis
local function enwikiExcerpt.getError(frame key, templatevalue )
local message = Excerpt.getMessage( 'error-' .. key, value )
local args = parseArgs(frame)
local markup = mw.html.create( 'div' ):addClass( 'error' ):wikitext( message )
errors = args["errors"] -- set the module level boolean used in local function err
if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then
markup:node( '[[Category:' .. config.categories.errors .. ']]' )
local articleCount = #args -- must be 1 except with selected=Foo and Foo=Somepage
if articleCount < 1 and not (template == "selected" and args[template] and args[args[template]]) then
return err("noArticle")
end
return markup
end
-- Helper method to get a localized message
local pageNames = {}
-- This method uses Module:TNT to get localized messages from https://commons.wikimedia.org/wiki/Data:I18n/Module:Excerpt.tab
-- If Module:TNT is not available or the localized message does not exist, the key is returned instead
function Excerpt.getMessage( key, value )
local ok, TNT = pcall( require, 'Module:TNT' )
if not ok then return key end
local ok2, message = pcall( TNT.format, 'I18n/Module:Excerpt.tab', key, value )
if not ok2 then return key end
return message
end
-- Helper method to escape a string for use in regexes
if template == "excerpt" then
function Excerpt.escapeString( str )
local tag = is(args.tag) and args.tag or 'div'
return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' )
local article = is(args.article) and args.article or args[1] or '{{{1}}}'
end
article = getTarget(article) -- solve redirects
local section = is(args.section) and args.section or args[2]
local output = {}
output[1] = frame:extensionTag{ name = 'templatestyles', args = {src='Excerpt/styles.css'} }
output[2] = '<' .. tag .. ' class="excerpt-block">'
output[3] = is(args.indicator) and ('<' .. tag .. ' class="excerpt-indicator">') or ''
if is(args.nohat) then
output[4] = ''
else
local hatnote = {}
hatnote[1] = is(args.indicator) and d.excerpt.hatnote or d.excerpt.hatnoteSection
hatnote[2] = '[['
hatnote[3] = article .. (is(section) and ('#' .. frame:callParserFunction( 'urlencode', section, 'WIKI' )) or '')
hatnote[4] = '|'
hatnote[5] = article .. (is(section) and (frame:callParserFunction( '#tag:nowiki', ' § ' ) .. section) or '')
hatnote[6] = ']]'
hatnote[7] = "''" .. '<span class="mw-editsection-like plainlinks"><span>[ </span>['
local title = mw.title.new(article) or mw.title.getCurrentTitle()
hatnote[8] = title:fullUrl('action=edit') .. ' ' .. d.excerpt.edit
hatnote[9] = ']<span> ]</span></span>' .. "''"
output[4] = require('Module:Hatnote')._hatnote(table.concat(hatnote), {selfref=true}) or err("hatnote")
end
output[5] = '<' .. tag .. ' class="excerpt">\n'
if article ~= '{{{1}}}' then
local options = args -- turn template arguments into module options
options.paraflags = args.paragraphs
options.fileflags = args.files or 1
options.nobold = 1
options.fragment = args.fragment
options.keepTables = args.tables or 1
options.keepRefs = args.references or 1
options.keepSubsections = args.subsections
local pageNames = { (article .. '#' .. (section or '')) }
local text = main(pageNames, options)
if text == "" and d.brokenCategory and d.brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then
output[6] = "[[Category:" .. d.brokenCategory .. "]]"
else
output[6] = frame:preprocess(text) or err("preprocess")
end
else
output[6] = err("noArticle")
end
output[7] = '</' .. tag .. '>'
output[8] = is(args.indicator) and ('</' .. tag .. '>') or ''
output[9] = '</' .. tag .. '>'
output[10] = mw.title.getCurrentTitle().isContentPage and ('[[' .. d.excerpt.category .. ']]') or ''
return table.concat(output)
-- Helper method to remove a string from a text
elseif template == "linked" or template == "listitem" then
-- @param text Text from where to remove the string
-- Read named page and find its wikilinks
-- @param str String to remove
local page = args[1]
-- @return The given text with the string removed
local text, title = getContent(page)
function Excerpt.removeString( text, str )
if not title then
local pattern = Excerpt.escapeString( str )
return err("noTitle", page)
if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes
elseif not text then
pattern = Excerpt.escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. Excerpt.escapeString( mw.ustring.sub( str, -999 ) )
return err("noContent", page)
end
return text:gsub( pattern, '' )
if args["section"] then -- check relevant section only
end
text = getSection(text, args["section"], args["sectiononly"])
if not text then return err("noSection", args["section"], page) end
end
-- replace annotated links with real links
text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]")
if template == "linked" then
for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pageNames, p) end
else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section
text = mw.ustring.gsub(text, "\n== *See also.*", "")
for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pageNames, p) end
end
-- Helper method to convert a comma-separated list of numbers or min-max ranges into a list of booleans
elseif template == "random" then
-- @param filter Required. Comma-separated list of numbers or min-max ranges, for example '1,3-5'
-- accept any number of page names. If more than one, we'll pick one randomly
-- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true}
for i, p in pairs(args) do
-- @return Boolean indicating whether the filters should be treated as a blacklist or not
if p and type(i) == 'number' then table.insert(pageNames, p) end
-- @note Merging this into matchFilter is possible, but way too inefficient
end
function Excerpt.parseFilter( filter )
local filters = {}
elseif template == "selected" then
local articleKeyisBlacklist = args[template]false
if string.sub( filter, 1, 1 ) == '-' then
if tonumber(articleKey) then -- normalise article number into the range 1..#args
isBlacklist = true
articleKey = articleKey % articleCount
filter = string.sub( filter, 2 )
if articleKey == 0 then articleKey = articleCount end
end
pageNames = { args[articleKey] }
end
local values = mw.text.split( filter, ',' ) -- split values: '1,3-5' to {'1','3-5'}
for _, value in pairs( values ) do
if is(args.more) then args.more = "Read more..." end -- more= is short for this default text
value = mw.text.trim( value )
local min, max = mw.ustring.match( value, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5
local text = ""
if not max then min, max = string.match( value, '^((%d+))$' ) end -- '1' to min=1 max=1
if args.showall then
if max then
local separator = ""
for i = min, max do filters[ i ] = true end
for _, p in pairs(pageNames) do
else
local t = main({ p }, args)
filters[ value ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3'
if t ~= "" then
text = text .. separator .. t
separator = args.showall
if separator == "" then separator = "{{clear}}{{hr}}" end
end
end
else
text = main(pageNames, args)
end
local filter = {cache = {}, terms = filters}
return frame:preprocess(text)filter, isBlacklist
end
-- Helper function to see if a value matches any of the given filters
-- Main invocation function for templates
local function leadExcerpt.matchFilter(frame value, filter )
if type(value) == "number" then
local args = parseArgs(frame)
return filter.terms[value]
local pageNames = { args[1] }
local text = main(pageNames, args)
if text == "" and d.brokenCategory and d.brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then
return "[[Category:" .. d.brokenCategory .. "]]"
else
local cached = filter.cache[value]
return frame:preprocess(text)
if cached ~= nil then
return cached
end
local lang = mw.language.getContentLanguage()
local lcvalue = lang:lcfirst(value)
local ucvalue = lang:ucfirst(value)
for term in pairs( filter.terms ) do
if value == tostring(term)
or type(term) == "string" and (
lcvalue == term
or ucvalue == term
or mw.ustring.match( value, term )
) then
filter.cache[value] = true
return true
end
end
filter.cache[value] = false
end
end
return Excerpt
-- Entry points for templates
function p.lead(frame) return lead(frame) end
function p.target(frame) return getTarget(frame.args[1]) end
-- Entry points for English Wikipedia templates
function p.linked(frame) return enwiki(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page
function p.listitem(frame) return enwiki(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page
function p.random(frame) return enwiki(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument)
function p.selected(frame) return enwiki(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter
function p.excerpt(frame) return enwiki(frame, "excerpt") end -- {{Excerpt}} transcludes part of an article into another article
-- Entry points for other Lua modules
function p.getTarget(page) return getTarget(page) end
function p.getContent(page, frame) return getContent(page, frame) end
function p.getsection(text, section) return getSection(text, section) end
function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end
function p.argimage(text) return argImage(text) end
function p.checkimage(image) return checkImage(image) end
function p.parseimage(text, start) return parseImage(text, start) end
function p.cleanupText(text, options) return cleanupText(text, options) end
function p.main(pageNames, options) return main(pageNames, options) end
function p.numberflags(str) return numberFlags(str) end
return p
|