Jump to content

Module:Excerpt and Module:Excerpt/sandbox: Difference between pages

(Difference between pages)
Page 1
Page 2
Content deleted Content added
Use infobox CSS class if it exists (mainly for dark mode compatibility)
 
Add a cache to filters to speed up repeat checks (especially useful when there's a lot of reference templates in the final excerpt)
 
Line 4: Line 4:
-- License: CC-BY-SA-3.0
-- License: CC-BY-SA-3.0


local Transcluder = require( 'Module:Transcluder' )
local parser = require( 'Module:WikitextParser' )

local yesno = require( 'Module:Yesno' )
local yesno = require( 'Module:Yesno' )


Line 11: Line 10:
if not ok then config = {} end
if not ok then config = {} end


local p = {}
local Excerpt = {}

-- Helper function to get arguments
local args
local function getArg( key, default )
local value = args[ key ]
if value and mw.text.trim( value ) ~= '' then
return value
end
return default
end

-- Helper function to handle errors
local function getError( message, value )
if type( message ) == 'string' then
message = Transcluder.getError( message, value )
end
if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then
message:node( '[[Category:' .. config.categories.errors .. ']]' )
end
return message
end

-- Helper function to get localized messages
local function getMessage( key )
local ok, TNT = pcall( require, 'Module:TNT' )
if not ok then return key end
return TNT.format( 'I18n/Module:Excerpt.tab', key )
end


-- Main entry point for templates
-- Main entry point for templates
function p.main( frame )
function Excerpt.main( frame )
args = Transcluder.parseArgs( frame )


-- Make sure the requested page exists
-- Make sure the requested page exists and get the wikitext
local page = getArg( 1 )
local page = Excerpt.getArg( 1 )
if not page or page == '{{{1}}}' then return getError( 'no-page' ) end
if not page or page == '{{{1}}}' then return Excerpt.getError( 'no-page' ) end
local title = mw.title.new(page)
local title = mw.title.new( page )
if not title then return getError( 'invalid-title', page ) 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 end
if title.isRedirect then title = title.redirectTarget end
if not title.exists then return getError( 'page-not-found', page ) end
if not title.exists then return Excerpt.getError( 'page-not-found', page ) end
page = title.prefixedText
page = title.prefixedText
local wikitext = title:getContent()


-- Set variables from the template parameters
-- Get the template params and process them
local params = {
local section = getArg( 2, mw.ustring.match( getArg( 1 ), '[^#]+#(.+)' ) )
local hat = yesno( getArg( 'hat', true ) )
hat = yesno( Excerpt.getArg( 'hat', true ) ),
local edit = yesno( getArg( 'edit', true ) )
this = Excerpt.getArg( 'this' ),
local this = getArg( 'this' )
only = Excerpt.getArg( 'only' ),
local only = getArg( 'only' )
files = Excerpt.getArg( 'files', Excerpt.getArg( 'file' ) ),
local files = getArg( 'files', getArg( 'file', ( only == 'file' and 1 ) ) )
lists = Excerpt.getArg( 'lists', Excerpt.getArg( 'list' ) ),
local lists = getArg( 'lists', getArg( 'list', ( only == 'list' and 1 ) ) )
tables = Excerpt.getArg( 'tables', Excerpt.getArg( 'table' ) ),
local tables = getArg( 'tables', getArg( 'table', ( only == 'table' and 1 ) ) )
templates = Excerpt.getArg( 'templates', Excerpt.getArg( 'template' ) ),
local templates = getArg( 'templates', getArg( 'template', ( only == 'template' and 1 ) ) )
paragraphs = Excerpt.getArg( 'paragraphs', Excerpt.getArg( 'paragraph' ) ),
local paragraphs = getArg( 'paragraphs', getArg( 'paragraph', ( only == 'paragraph' and 1 ) ) )
references = yesno( Excerpt.getArg( 'references', true ) ),
local references = getArg( 'references' )
subsections = yesno( Excerpt.getArg( 'subsections', false ) ),
local subsections = not yesno( getArg( 'subsections' ) )
links = yesno( Excerpt.getArg( 'links', true ) ),
local noLinks = not yesno( getArg( 'links', true ) )
bold = yesno( Excerpt.getArg( 'bold', false ) ),
local noBold = not yesno( getArg( 'bold' ) )
briefDates = yesno( Excerpt.getArg( 'briefdates', false ) ),
local onlyFreeFiles = yesno( getArg( 'onlyfreefiles', true ) )
inline = yesno( Excerpt.getArg( 'inline' ) ),
local briefDates = yesno( getArg( 'briefdates', false ) )
quote = yesno( Excerpt.getArg( 'quote' ) ),
local inline = yesno( getArg( 'inline' ) )
more = yesno( Excerpt.getArg( 'more' ) ),
local quote = yesno( getArg( 'quote' ) )
class = Excerpt.getArg( 'class' ),
local more = yesno( getArg( 'more' ) )
displayTitle = Excerpt.getArg( 'displaytitle', page ),
}
local class = getArg( 'class' )
local displaytitle = getArg( 'displaytitle' ) or page


-- Make sure the requested section exists and get the excerpt
-- Build the hatnote
local excerpt
if hat and not inline then
local section = Excerpt.getArg( 2, fragment )
if this then
section = mw.text.trim( section )
hat = this
if section == '' then section = nil end
elseif quote then
if section then
hat = getMessage( 'this' )
excerpt = parser.getSectionTag( wikitext, section )
elseif only then
if not excerpt then
hat = getMessage( only )
if params.subsections then
else
hat = getMessage( 'section' )
excerpt = parser.getSection( wikitext, section )
else
local sections = parser.getSections( wikitext )
excerpt = sections[ section ]
end
end
end
if not excerpt then return Excerpt.getError( 'section-not-found', section ) end
hat = hat .. ' ' .. getMessage( 'excerpt' ) .. ' '
if excerpt == '' then return Excerpt.getError( 'section-empty', section ) end
if section then
else
hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. displaytitle
excerpt = parser.getLead( wikitext )
.. ' § ' .. mw.ustring.gsub( section, '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links
if excerpt == '' then return Excerpt.getError( 'lead-empty' ) end
end

-- Remove noinclude bits
excerpt = excerpt:gsub( '<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>', '' )

-- Filter various elements from the excerpt
excerpt = Excerpt.filterFiles( excerpt, params.files )
excerpt = Excerpt.filterLists( excerpt, params.lists )
excerpt = Excerpt.filterTables( excerpt, params.tables )
excerpt = Excerpt.filterParagraphs( excerpt, params.paragraphs )

-- If no file is found, try to get one from the infobox
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
else
params.templates = '-' .. blacklist
hat = hat .. '[[:' .. page .. '|' .. displaytitle .. ']].'
end
end
if edit then
hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>['
hat = hat .. title:fullUrl( 'action=edit' ) .. ' ' .. mw.message.new( 'editsection' ):plain()
hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>'
end
if config.hat then
hat = config.hat .. hat .. '}}'
hat = frame:preprocess( hat )
else
hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat )
end
else
hat = nil
end
end
excerpt = Excerpt.filterTemplates( excerpt, params.templates )


-- Build the "Read more" link
-- Leave only the requested elements
if more and not inline then
if params.only == 'file' or params.only == 'files' then
local files = parser.getFiles( excerpt )
more = "'''[[" .. page .. '#' .. ( section or '' ) .. "|" .. getMessage( 'more' ) .. "]]'''"
excerpt = params.only == 'file' and files[1] or table.concat( files, '\n\n' )
more = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( more )
end
if params.only == 'list' or params.only == 'lists' then
local lists = parser.getLists( excerpt )
excerpt = params.only == 'list' and lists[1] or table.concat( lists, '\n\n' )
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
if params.briefDates then
excerpt = Excerpt.fixDates( excerpt )
end

-- Remove unwanted elements
excerpt = Excerpt.removeComments( excerpt )
excerpt = Excerpt.removeSelfLinks( excerpt )
excerpt = Excerpt.removeNonFreeFiles( excerpt )
excerpt = Excerpt.removeBehaviorSwitches( excerpt )

-- Fix or remove the references
if params.references then
excerpt = Excerpt.fixReferences( excerpt, page, wikitext )
else
else
excerpt = Excerpt.removeReferences( excerpt )
more = nil
end
end


-- Remove wikilinks
-- Build the options for Module:Transcluder out of the template parameters and the desired defaults
if not params.links then
local options = {
excerpt = Excerpt.removeLinks( excerpt )
files = files,
end
lists = lists,
tables = tables,
paragraphs = paragraphs,
sections = subsections,
categories = 0,
references = references,
only = only and mw.text.trim( only, 's' ) .. 's',
noLinks = noLinks,
noBold = noBold,
noSelfLinks = true,
noNonFreeFiles = onlyFreeFiles,
noBehaviorSwitches = true,
fixReferences = true,
linkBold = true,
}


-- Link the bold text near the start of most leads and then remove it
-- Get the excerpt itself
local title = page .. '#' .. ( section or '' )
if not section then
local ok, excerpt = pcall( Transcluder.get, title, options )
excerpt = Excerpt.linkBold( excerpt, page )
end
if not ok then return getError( excerpt ) end
if mw.text.trim( excerpt ) == '' and not only then
if not params.bold then
excerpt = Excerpt.removeBold( excerpt )
if section then return getError( 'section-empty', section ) else return getError( 'lead-empty' ) end
end
end


-- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly
-- Fix birth and death dates, but only in the first paragraph
excerpt = excerpt:gsub( '\n\n\n+', '\n\n' )
if briefDates then
excerpt = mw.text.trim( excerpt )
local startpos = 1 -- skip initial templates
excerpt = '\n' .. excerpt .. '\n'
local s

local e = 0
-- Remove nested categories
repeat
excerpt = frame:preprocess( excerpt )
startpos = e + 1
s, e = mw.ustring.find( excerpt, "%s*%b{}%s*", startpos )
excerpt = Excerpt.removeCategories( excerpt )

until not s or s > startpos
-- Add tracking categories
s, e = mw.ustring.find( excerpt, "%b()", startpos ) -- get (...), which may be (year–year)
if config.categories then
if s and s < startpos + 100 then -- look only near the start
excerpt = Excerpt.addTrackingCategories( excerpt )
local year1, conjunction, year2 = mw.ustring.match( mw.ustring.sub( excerpt, s, e ), '(%d%d%d+)(.-)(%d%d%d+)' )
end
if year1 and year2 and (mw.ustring.match( conjunction, '[%-–—]' ) or mw.ustring.match( conjunction, '{{%s*[sS]nd%s*}}' )) then

local y1 = tonumber(year1)
-- Build the final output
local y2 = tonumber(year2)
if params.inline then
if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( "%Y" )) then
return mw.text.trim( excerpt )
excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. "–" .. year2 .. mw.ustring.sub( excerpt, e )
end
end

end
local tag = params.quote and 'blockquote' or 'div'
local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( params.class )

if config.styles then
local styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } )
block:node( styles )
end

if params.hat then
local hat = Excerpt.getHat( page, section, params )
block:node( hat )
end

excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt )
block:node( excerpt )

if params.more then
local more = Excerpt.getReadMore( page, section )
block:node( more )
end

return block
end

-- Filter the files in the given wikitext against the given filter
function Excerpt.filterFiles( wikitext, filter )
if not filter then return wikitext end
local filters, isBlacklist = Excerpt.parseFilter( filter )
local files = parser.getFiles( wikitext )
for index, file in pairs( files ) do
local name = parser.getFileName( file )
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
end
end
return wikitext
end


-- Filter the lists in the given wikitext against the given filter
-- If no file was found, try to get one from the infobox
function Excerpt.filterLists( wikitext, filter )
local fileNamespaces = Transcluder.getNamespaces( 'File' )
if not filter then return wikitext end
if ( ( only == 'file' or only == 'files' ) or ( not only and ( files ~= '0' or not files ) ) ) and -- caller asked for files
local filters, isBlacklist = Excerpt.parseFilter( filter )
not Transcluder.matchAny( excerpt, '%[%[', fileNamespaces, ':' ) and -- and there are no files in Transcluder's output
local lists = parser.getLists( wikitext )
config.captions -- and we have the config option required to try finding files in templates
for index, list in pairs( lists ) do
then
if isBlacklist and Excerpt.matchFilter( index, filters )
-- We cannot distinguish the infobox from the other templates so we search them all
or not isBlacklist and not Excerpt.matchFilter( index, filters ) then
local infobox = Transcluder.getTemplates( excerpt );
wikitext = Excerpt.removeString( wikitext, list )
infobox = table.concat( infobox )
end
local parameters = Transcluder.getParameters( infobox )
end
local file, captions, caption, cssclasses, cssclass
return wikitext
end

-- Filter the tables in the given wikitext against the given filter
function Excerpt.filterTables( wikitext, filter )
if not filter then return wikitext end
local filters, isBlacklist = Excerpt.parseFilter( filter )
local tables = parser.getTables( wikitext )
for index, t in pairs( tables ) do
local id = string.match( t, '{|[^\n]-id%s*=%s*["\']?([^"\'\n]+)["\']?[^\n]*\n' )
if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( id, filters ) )
or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( id, filters ) ) then
wikitext = Excerpt.removeString( wikitext, t )
end
end
return wikitext
end

-- Filter the paragraphs in the given wikitext against the given filter
function Excerpt.filterParagraphs( wikitext, filter )
if not filter then return wikitext end
local filters, isBlacklist = Excerpt.parseFilter( filter )
local paragraphs = parser.getParagraphs( wikitext )
for index, paragraph in pairs( paragraphs ) do
if isBlacklist and Excerpt.matchFilter( index, filters )
or not isBlacklist and not Excerpt.matchFilter( index, filters ) then
wikitext = Excerpt.removeString( wikitext, paragraph )
end
end
return wikitext
end

-- Filter the templates in the given wikitext against the given filter
function Excerpt.filterTemplates( wikitext, filter )
if not filter then return wikitext end
local filters, isBlacklist = Excerpt.parseFilter( filter )
local templates = parser.getTemplates( wikitext )
for index, template in pairs( templates ) do
local name = parser.getTemplateName( template )
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, template )
end
end
return wikitext
end

function Excerpt.addInfoboxFile( excerpt )
-- We cannot distinguish the infobox from the other templates, so we search them all
local templates = parser.getTemplates( excerpt )
for _, template in pairs( templates ) do
local parameters = parser.getTemplateParameters( template )
local file, captions, caption, cssClasses, cssClass
for _, pair in pairs( config.captions ) do
for _, pair in pairs( config.captions ) do
file = pair[1]
file = pair[1]
file = parameters[file]
file = parameters[file]
if file and Transcluder.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then
if file and Excerpt.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then
file = mw.ustring.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg
file = string.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg
captions = pair[2]
captions = pair[2]
for _, p in pairs( captions ) do
for _, p in pairs( captions ) do
if parameters[ p ] then caption = parameters[ p ] break end
if parameters[ p ] then caption = parameters[ p ] break end
end
end
-- Check for CSS classes
-- Check for CSS classes
-- We opt to use skin-invert-image instead of skin-invert
-- We opt to use skin-invert-image instead of skin-invert
-- in all other cases, the CSS provided in the infobox is used
-- in all other cases, the CSS provided in the infobox is used
if pair[3] then
if pair[3] then
cssclasses = pair[3]
cssClasses = pair[3]
for _, p in pairs(cssclasses) do
for _, p in pairs( cssClasses ) do
if parameters[p] then
if parameters[ p ] then
cssclass = ((parameters[p] == 'skin-invert') and 'skin-invert-image' or parameters[p])
cssClass = ( parameters[ p ] == 'skin-invert' ) and 'skin-invert-image' or parameters[ p ]
break
break
end
end
end
end
end
end
local class = cssClass and ( '|class=' .. cssClass ) or ''
excerpt = '[[File:' .. file ..
return '[[File:' .. file .. class .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt
(cssclass and ('|class=' .. cssclass) or '') ..
'|thumb|' .. (caption or '') .. ']]' .. excerpt
if ( onlyFreeFiles ) then
excerpt = Transcluder.removeNonFreeFiles( excerpt )
end
break
end
end
end
end
end
end
return excerpt
end


function Excerpt.removeNonFreeFiles( wikitext )
-- Unlike other elements, templates are filtered here
local files = parser.getFiles( wikitext )
-- because we had to search the infoboxes for files
for _, file in pairs( files ) do
local trash
local fileName = 'File:' .. parser.getFileName( file )
if only and ( only == 'template' or only == 'templates' ) then
local fileTitle = mw.title.new( fileName )
trash, excerpt = Transcluder.getTemplates( excerpt, templates );
if fileTitle then
else -- Remove blacklisted templates
local fileDescription = fileTitle:getContent()
local blacklist = config.blacklist and table.concat( config.blacklist, ',' ) or ''
if not fileDescription or fileDescription == '' then
if templates then
local frame = mw.getCurrentFrame()
if string.sub( templates, 1, 1 ) == '-' then --Unwanted templates. Append to blacklist
fileDescription = frame:preprocess( '{{' .. fileName .. '}}' ) -- try Commons
blacklist = templates .. ',' .. blacklist
end
else --Wanted templates. Replaces blacklist and acts as whitelist
if fileDescription and string.match( fileDescription, '[Nn]on%-free' ) then
blacklist = templates
wikitext = Excerpt.removeString( wikitext, file )
end
end
else
blacklist = '-' .. blacklist
end
end
trash, excerpt = Transcluder.getTemplates( excerpt, blacklist );
end
end
return wikitext
end


function Excerpt.getHat( page, section, params )
-- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly
local hat
excerpt = mw.text.trim( excerpt )
excerpt = string.gsub( excerpt, '\n\n\n+', '\n\n' )
excerpt = '\n' .. excerpt .. '\n'


-- Remove nested categories
-- Build the text
if params.this then
excerpt = frame:preprocess( excerpt )
hat = params.this
local categories, excerpt = Transcluder.getCategories( excerpt, options.categories )
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' )


-- Add tracking categories
-- Build the link
if config.categories then
if section then
hat = hat .. ' [[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. params.displayTitle
local contentCategory = config.categories.content
.. ' § ' .. section:gsub( '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links
if contentCategory and mw.title.getCurrentTitle().isContentPage then
else
excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]'
hat = hat .. ' [[:' .. page .. '|' .. params.displayTitle .. ']].'
end

-- Build the edit link
local title = mw.title.new( page )
local editUrl = title:fullUrl( 'action=edit' )
hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>['
hat = hat .. editUrl .. ' ' .. mw.message.new( 'editsection' ):plain()
hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>'

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

return hat
end

function Excerpt.getReadMore( page, section )
local link = "'''[[" .. page
if section then
link = link .. '#' .. section
end
local text = Excerpt.getMessage( 'more' )
link = link .. '|' .. text .. "]]'''"
link = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( link )
return link
end

-- Fix birth and death dates, but only in the first paragraph
-- @todo Use parser.getParagraphs() to get the first paragraph
function Excerpt.fixDates( excerpt )
local start = 1 -- skip initial templates
local s
local e = 0
repeat
start = e + 1
s, e = mw.ustring.find( excerpt, '%s*%b{}%s*', start )
until not s or s > start
s, e = mw.ustring.find( excerpt, '%b()', start ) -- get (...), which may be (year–year)
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
end
local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ]
return excerpt
if namespaceCategory then
end
excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]'

-- Replace the first call to each reference defined outside of the excerpt for the full reference, to prevent undefined references
-- 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">
-- and also <ref name="Foo" /> for <ref name="Title of the article Foo" />
-- also remove reference groups: <ref name="Foo" group="Bar"> for <ref name="Title of the article Foo">
-- and <ref group="Bar"> for <ref>
-- @todo The current regex may fail in cases with both kinds of quotes, like <ref name="Darwin's book">
function Excerpt.fixReferences( excerpt, page, wikitext )
local references = parser.getReferences( excerpt )
local fixed = {}
for _, reference in pairs( references ) do
local name = parser.getTagAttribute( reference, 'name' )
if not fixed[ name ] then -- fix each reference only once
local content = parser.getTagContent( reference )
if not content then -- reference is self-closing
local full = parser.getReference( excerpt, name )
if not full then -- the reference is not defined in the excerpt
full = parser.getReference( wikitext, name )
if full then
excerpt = excerpt:gsub( Excerpt.escapeString( reference ), Excerpt.escapeString( full ), 1 )
end
table.insert( fixed, name )
end
end
end
end
end
end
-- Prepend the page title to the reference names to prevent conflicts with other references in the transcluding page
excerpt = excerpt:gsub( '< *[Rr][Ee][Ff][^>]*name *= *["\']?([^"\'>/]+)["\']?[^>/]*(/?) *>', '<ref name="' .. page:gsub( '"', '' ) .. ' %1"%2>' )
-- Remove reference groups because they don't apply to the transcluding page
excerpt = excerpt:gsub( '< *[Rr][Ee][Ff] *group *= *["\']?[^"\'>/]+["\'] *>', '<ref>' )
return excerpt
end


function Excerpt.removeReferences( excerpt )
-- Load the styles
local references = parser.getReferences( excerpt )
local styles
for _, reference in pairs( references ) do
if config.styles then
excerpt = Excerpt.removeString( excerpt, reference )
styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } )
end
end
return excerpt
end


function Excerpt.removeCategories( excerpt )
-- Combine and return the elements
local categories = parser.getCategories( excerpt )
if inline then
for _, category in pairs( categories ) do
return mw.text.trim( excerpt )
excerpt = Excerpt.removeString( excerpt, category )
end
end
return excerpt
local tag = 'div'
end
if quote then

tag = 'blockquote'
function Excerpt.removeBehaviorSwitches( excerpt )
return excerpt:gsub( '__[A-Z]+__', '' )
end

function Excerpt.removeComments( excerpt )
return excerpt:gsub( '<!%-%-.-%-%->', '' )
end

function Excerpt.removeBold( excerpt )
return excerpt:gsub( "'''", '' )
end

function Excerpt.removeLinks( excerpt )
local links = parser.getLinks( excerpt )
for _, link in pairs( links ) do
excerpt = Excerpt.removeString( excerpt, link )
end
end
return excerpt
excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt )
local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( class )
return block:node( styles ):node( hat ):node( excerpt ):node( more )
end
end


-- @todo Use parser.getLinks
-- Entry points for backwards compatibility
function p.lead( frame ) return p.main( frame ) end
function Excerpt.removeSelfLinks( excerpt, page )
local lang = mw.language.getContentLanguage()
function p.excerpt( frame ) return p.main( frame ) end
local page = Excerpt.escapeString( mw.title.getCurrentTitle().prefixedText )
local ucpage = lang:ucfirst( page )
local lcpage = lang:lcfirst( page )
excerpt = excerpt
:gsub( '%[%[(' .. ucpage .. ')%]%]', '%1' )
:gsub( '%[%[(' .. lcpage .. ')%]%]', '%1' )
:gsub( '%[%[' .. ucpage .. '|([^]]+)%]%]', '%1' )
:gsub( '%[%[' .. lcpage .. '|([^]]+)%]%]', '%1' )
return excerpt
end

-- Replace the bold title or synonym near the start of the page by a link to the page
function Excerpt.linkBold( excerpt, page )
local lang = mw.language.getContentLanguage()
local position = mw.ustring.find( excerpt, "'''" .. lang:ucfirst( page ) .. "'''", 1, true ) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc)
or mw.ustring.find( excerpt, "'''" .. lang:lcfirst( page ) .. "'''", 1, true ) -- plain search: special characters in page represent themselves
if position then
local length = mw.ustring.len( page )
excerpt = mw.ustring.sub( excerpt, 1, position + 2 ) .. '[[' .. mw.ustring.sub( excerpt, position + 3, position + length + 2 ) .. ']]' .. mw.ustring.sub( 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)
excerpt = mw.ustring.gsub( excerpt, "()'''(.-'*)'''", function ( a, b )
if not mw.ustring.find( b, '%[' ) and not mw.ustring.find( b, '%{' ) then -- if not wikilinked or some weird template
return "'''[[" .. page .. '|' .. b .. "]]'''" -- replace '''Foo''' by '''[[page|Foo]]'''
else
return nil -- instruct gsub to make no change
end
end, 1 ) -- terminates the anonymous replacement function passed to gsub
end
return excerpt
end

function Excerpt.addTrackingCategories( excerpt )
local currentTitle = mw.title.getCurrentTitle()
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
-- 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
-- args from Lua calls have priority over parent args from template
function Excerpt.getArg( key, default )
local frame = mw.getCurrentFrame()
for k, value in pairs( frame:getParent().args ) do
if k == key and mw.text.trim( value ) ~= '' then
return value
end
end
for k, value in pairs( frame.args ) do
if k == key and mw.text.trim( value ) ~= '' then
return value
end
end
return default
end

-- Helper method to get an error message
-- This method also categorizes the current page in one of the configured error categories
function Excerpt.getError( key, value )
local message = Excerpt.getMessage( 'error-' .. key, value )
local markup = mw.html.create( 'div' ):addClass( 'error' ):wikitext( message )
if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then
markup:node( '[[Category:' .. config.categories.errors .. ']]' )
end
return markup
end

-- Helper method to get a localized message
-- 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
function Excerpt.escapeString( str )
return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' )
end

-- Helper method to remove a string from a text
-- @param text Text from where to remove the string
-- @param str String to remove
-- @return The given text with the string removed
function Excerpt.removeString( text, str )
local pattern = Excerpt.escapeString( str )
if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes
pattern = Excerpt.escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. Excerpt.escapeString( mw.ustring.sub( str, -999 ) )
end
return text:gsub( pattern, '' )
end

-- Helper method to convert a comma-separated list of numbers or min-max ranges into a list of booleans
-- @param filter Required. Comma-separated list of numbers or min-max ranges, for example '1,3-5'
-- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true}
-- @return Boolean indicating whether the filters should be treated as a blacklist or not
-- @note Merging this into matchFilter is possible, but way too inefficient
function Excerpt.parseFilter( filter )
local filters = {}
local isBlacklist = false
if string.sub( filter, 1, 1 ) == '-' then
isBlacklist = true
filter = string.sub( filter, 2 )
end
local values = mw.text.split( filter, ',' ) -- split values: '1,3-5' to {'1','3-5'}
for _, value in pairs( values ) do
value = mw.text.trim( value )
local min, max = mw.ustring.match( value, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5
if not max then min, max = string.match( value, '^((%d+))$' ) end -- '1' to min=1 max=1
if max then
for i = min, max do filters[ i ] = true end
else
filters[ value ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3'
end
end
local filter = {cache = {}, terms = filters}
return filter, isBlacklist
end

-- Helper function to see if a value matches any of the given filters
function Excerpt.matchFilter( value, filter )
if type(value) == "number" then
return filter.terms[value]
else
local cached = filter.cache[value]
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 p
return Excerpt