Module:Excerpt and Module:Excerpt/sandbox: Difference between pages
Appearance
(Difference between pages)
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 |
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 |
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 |
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() |
|||
-- |
-- Get the template params and process them |
||
local params = { |
|||
local section = getArg( 2, mw.ustring.match( getArg( 1 ), '[^#]+#(.+)' ) ) |
|||
hat = yesno( Excerpt.getArg( 'hat', true ) ), |
|||
this = Excerpt.getArg( 'this' ), |
|||
only = Excerpt.getArg( 'only' ), |
|||
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 ), |
|||
} |
|||
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 |
|||
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 ) |
|||
-- |
-- Leave only the requested elements |
||
if |
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 |
|||
if not section then |
|||
excerpt = Excerpt.linkBold( excerpt, page ) |
|||
end |
|||
if not ok then return getError( excerpt ) end |
|||
if |
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 |
|||
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 |
|||
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 |
if file and Excerpt.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then |
||
file = |
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] |
|||
for _, p in pairs( |
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 ] |
|||
break |
break |
||
end |
end |
||
end |
end |
||
end |
end |
||
local class = cssClass and ( '|class=' .. cssClass ) or '' |
|||
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' |
|||
-- |
-- 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' ) |
|||
-- |
-- Build the link |
||
if |
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 |
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 |
return Excerpt |