Module:Excerpt: Difference between revisions

Content deleted Content added
Do not try to append paragraphs whose numbers which exceed the number actually present
Use infobox CSS class if it exists (mainly for dark mode compatibility)
 
(178 intermediate revisions by 10 users not shown)
Line 1:
-- Module:Excerpt implements the Excerpt template
-- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt
-- Authors: User:Sophivorus, User:Certes, User:Aidan9382 & others
-- License: CC-BY-SA-3.0
 
local Transcluder = require( 'Module:Transcluder' )
 
local yesno = require( 'Module:Yesno' )
 
local ok, config = pcall( require, 'Module:Excerpt/config' )
if not ok then config = {} end
 
local p = {}
 
-- Helper function to get arguments
-- Entry point for Lua callers
local args
-- Returns a string value: text of the lead of a page
local function p._leadgetArg(pagename key, paragraphlistdefault )
local value = args[ key ]
if not pagename then return "" end -- Return blank text rather than an error splurge
if value and mw.text.trim( value ) ~= '' then
local title = mw.title.new(pagename) -- Find the lead section of the named page
if not title then return "" endvalue
end
local text = title:getContent() or ""
return default
end
 
-- Helper function to handle errors
text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it
local function getError( message, value )
text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits
if type( message ) == 'string' then
repeat
message = Transcluder.getError( message, value )
local oldtext=text
end
text = mw.ustring.gsub(text,"^%A-%b{}%s*","") -- remove infoboxes, hatnotes, tags, etc. from the front
if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then
until text == oldtext
message:node( '[[Category:' .. config.categories.errors .. ']]' )
end
return message
end
 
-- Helper function to get localized messages
if #paragraphlist > 0 then -- limit to requested paragraphs e.g. {1, 3, 4, 5}
local function getMessage( key )
local paras = mw.text.split(text, "\n%s*\n") -- %s* may include \n if three or more appear together
local ok, TNT = pcall( require, 'Module:TNT' )
local sep="" -- no separator before first paragraph
if not ok then return key end
local newtext = ""
return TNT.format( 'I18n/Module:Excerpt.tab', key )
for _, p in pairs(paragraphlist) do
end
if paras[p] then newtext = newtext .. sep .. paras[p] end -- else p exceeds number of paragraphs found
 
sep = "\n\n"
-- Main entry point for templates
function p.main( frame )
args = Transcluder.parseArgs( frame )
 
-- Make sure the requested page exists
local page = getArg( 1 )
if not page or page == '{{{1}}}' then return getError( 'no-page' ) end
local title = mw.title.new(page)
if not title then return getError( 'invalid-title', page ) end
if title.isRedirect then title = title.redirectTarget end
if not title.exists then return getError( 'page-not-found', page ) end
page = title.prefixedText
 
-- Set variables from the template parameters
local section = getArg( 2, mw.ustring.match( getArg( 1 ), '[^#]+#(.+)' ) )
local hat = yesno( getArg( 'hat', true ) )
local edit = yesno( getArg( 'edit', true ) )
local this = getArg( 'this' )
local only = getArg( 'only' )
local files = getArg( 'files', getArg( 'file', ( only == 'file' and 1 ) ) )
local lists = getArg( 'lists', getArg( 'list', ( only == 'list' and 1 ) ) )
local tables = getArg( 'tables', getArg( 'table', ( only == 'table' and 1 ) ) )
local templates = getArg( 'templates', getArg( 'template', ( only == 'template' and 1 ) ) )
local paragraphs = getArg( 'paragraphs', getArg( 'paragraph', ( only == 'paragraph' and 1 ) ) )
local references = getArg( 'references' )
local subsections = not yesno( getArg( 'subsections' ) )
local noLinks = not yesno( getArg( 'links', true ) )
local noBold = not yesno( getArg( 'bold' ) )
local onlyFreeFiles = yesno( getArg( 'onlyfreefiles', true ) )
local briefDates = yesno( getArg( 'briefdates', false ) )
local inline = yesno( getArg( 'inline' ) )
local quote = yesno( getArg( 'quote' ) )
local more = yesno( getArg( 'more' ) )
local class = getArg( 'class' )
local displaytitle = getArg( 'displaytitle' ) or page
 
-- Build the hatnote
if hat and not inline then
if this then
hat = this
elseif quote then
hat = getMessage( 'this' )
elseif only then
hat = getMessage( only )
else
hat = getMessage( 'section' )
end
hat = hat .. ' ' .. getMessage( 'excerpt' ) .. ' '
if section then
hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. displaytitle
.. ' § ' .. mw.ustring.gsub( section, '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links
else
hat = hat .. '[[:' .. page .. '|' .. displaytitle .. ']].'
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
text = newtext
hat = nil
end
 
-- Build the "Read more" link
text = mw.ustring.gsub(text, "{{%s*[Ee]fn%s*|.-}}", "") -- remove {{efn|footnote}}
if more and not inline then
text = mw.ustring.gsub(text, "{{%s*[Ee]fn-la%s*|.-}}", "") -- {{efn-la}} alias for {{efn}}
more = "'''[[" .. page .. '#' .. ( section or '' ) .. "|" .. getMessage( 'more' ) .. "]]'''"
text = mw.ustring.gsub(text, "{{%s*[Ee]l[mn]%s*|.-}}", "") -- {{elm}} and {{eln}} alias for {{efn}}
more = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( more )
text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere
else
text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs
more = nil
end
 
-- Build the options for Module:Transcluder out of the template parameters and the desired defaults
text = mw.ustring.match(text, "%C*'''.*") or text -- start at the first line with bold text, if any
local options = {
return text
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,
}
 
-- Get the excerpt itself
-- Entry point for template callers using #invoke:
local title = page .. '#' .. ( section or '' )
function p.lead(frame)
local ok, excerpt = pcall( Transcluder.get, title, options )
-- args = { 1 = page name, paragraphs = list e.g. "1,3-5" }
if not ok then return getError( excerpt ) end
local args = frame.args -- from calling module
if mw.text.trim( excerpt ) == '' and not only then
local pargs = frame:getParent().args -- from template
if section then return getError( 'section-empty', section ) else return getError( 'lead-empty' ) end
end
 
-- Fix birth and death dates, but only in the first paragraph
local pagename = args[1] or pargs[1] or ""
if briefDates then
pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo"
local startpos = 1 -- skip initial templates
local s
local e = 0
repeat
startpos = e + 1
s, e = mw.ustring.find( excerpt, "%s*%b{}%s*", startpos )
until not s or s > startpos
s, e = mw.ustring.find( excerpt, "%b()", startpos ) -- get (...), which may be (year–year)
if s and s < startpos + 100 then -- look only near the start
local year1, conjunction, year2 = mw.ustring.match( mw.ustring.sub( excerpt, s, e ), '(%d%d%d+)(.-)(%d%d%d+)' )
if year1 and year2 and (mw.ustring.match( conjunction, '[%-–—]' ) or mw.ustring.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
 
-- If no file was found, try to get one from the infobox
local parasets = mw.text.split(args["paragraphs"] or pargs["paragraphs"] or "", ",") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"}
local fileNamespaces = Transcluder.getNamespaces( 'File' )
local paralist = {}
if ( ( only == 'file' or only == 'files' ) or ( not only and ( files ~= '0' or not files ) ) ) and -- caller asked for files
for _, ps in pairs(parasets) do
not Transcluder.matchAny( excerpt, '%[%[', fileNamespaces, ':' ) and -- and there are no files in Transcluder's output
local min, max = mw.ustring.match(ps,"^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5
config.captions -- and we have the config option required to try finding files in templates
if not max then min, max = mw.ustring.match(ps,"^%s*((%d+))%s*$") end -- "1" → min=1 max=1
if max then
-- We cannot distinguish the infobox from the other templates so we search them all
for p = min, max do table.insert(paralist, p) end
local infobox = Transcluder.getTemplates( excerpt );
infobox = table.concat( infobox )
local parameters = Transcluder.getParameters( infobox )
local file, captions, caption, cssclasses, cssclass
for _, pair in pairs( config.captions ) do
file = pair[1]
file = parameters[file]
if file and Transcluder.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
captions = pair[2]
for _, p in pairs( captions ) do
if parameters[ p ] then caption = parameters[ p ] break end
end
-- Check for CSS classes
-- We opt to use skin-invert-image instead of skin-invert
-- in all other cases, the CSS provided in the infobox is used
if pair[3] then
cssclasses = pair[3]
for _, p in pairs(cssclasses) do
if parameters[p] then
cssclass = ((parameters[p] == 'skin-invert') and 'skin-invert-image' or parameters[p])
break
end
end
end
excerpt = '[[File:' .. file ..
(cssclass and ('|class=' .. cssclass) or '') ..
'|thumb|' .. (caption or '') .. ']]' .. excerpt
if ( onlyFreeFiles ) then
excerpt = Transcluder.removeNonFreeFiles( excerpt )
end
break
end
end
end
 
-- Unlike other elements, templates are filtered here
return frame:preprocess(p._lead(pagename, paralist))
-- because we had to search the infoboxes for files
local trash
if only and ( only == 'template' or only == 'templates' ) then
trash, excerpt = Transcluder.getTemplates( excerpt, templates );
else -- Remove blacklisted templates
local blacklist = config.blacklist and table.concat( config.blacklist, ',' ) or ''
if templates then
if string.sub( templates, 1, 1 ) == '-' then --Unwanted templates. Append to blacklist
blacklist = templates .. ',' .. blacklist
else --Wanted templates. Replaces blacklist and acts as whitelist
blacklist = templates
end
else
blacklist = '-' .. blacklist
end
trash, excerpt = Transcluder.getTemplates( excerpt, blacklist );
end
 
-- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly
excerpt = mw.text.trim( excerpt )
excerpt = string.gsub( excerpt, '\n\n\n+', '\n\n' )
excerpt = '\n' .. excerpt .. '\n'
 
-- Remove nested categories
excerpt = frame:preprocess( excerpt )
local categories, excerpt = Transcluder.getCategories( excerpt, options.categories )
 
-- Add tracking categories
if config.categories then
local contentCategory = config.categories.content
if contentCategory and mw.title.getCurrentTitle().isContentPage then
excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]'
end
local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ]
if namespaceCategory then
excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]'
end
end
 
-- Load the styles
local styles
if config.styles then
styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } )
end
 
-- Combine and return the elements
if inline then
return mw.text.trim( excerpt )
end
local tag = 'div'
if quote then
tag = 'blockquote'
end
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
 
-- Entry points for backwards compatibility
function p.lead( frame ) return p.main( frame ) end
function p.excerpt( frame ) return p.main( frame ) end
 
return p