Module:Excerpt
-- Module:Excerpt implements the Excerpt template
 -- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt
 -- Authors: User:Sophivorus, User:Certes, User:Aidan9382 &amp; 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
 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
 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 .. '&lt;span class=&quot;mw-editsection-like plainlinks&quot;&gt;&lt;span class=&quot;mw-editsection-bracket&quot;&gt;[&lt;/span&gt;['
 			hat = hat .. title:fullUrl( 'action=edit' ) .. ' ' .. mw.message.new( 'editsection' ):plain()
 			hat = hat .. ']&lt;span class=&quot;mw-editsection-bracket&quot;&gt;]&lt;/span&gt;&lt;/span&gt;'
 		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
 
 	-- Build the &quot;Read more&quot; link
 	if more and not inline then
 		more = &quot;'''[[&quot; .. page .. '#' .. ( section or '' ) .. &quot;|&quot; .. getMessage( 'more' ) .. &quot;]]'''&quot;
 		more = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( more )
 	else
 		more = nil
 	end
 
 	-- Build the options for Module:Transcluder out of the template parameters and the desired defaults
 	local options = {
 		files = files,
 		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
 	local title = page .. '#' .. ( section or '' )
 	local ok, excerpt = pcall( Transcluder.get, title, options )
 	if not ok then return getError( excerpt ) end
 	if mw.text.trim( excerpt ) == '' and not only then
 		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
 	if briefDates then
 		local startpos = 1 -- skip initial templates
 		local s
 		local e = 0
 		repeat
 			startpos = e + 1
 			s, e = mw.ustring.find( excerpt, &quot;%s*%b{}%s*&quot;, startpos )
 		until not s or s &gt; startpos
 		s, e = mw.ustring.find( excerpt, &quot;%b()&quot;, startpos ) -- get (...), which may be (year–year)
 		if s and s &lt; 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 &gt; y1 and y2 &lt; y1 + 125 and y1 &lt;= tonumber( os.date( &quot;%Y&quot; )) then
 					excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. &quot;–&quot; .. year2 .. mw.ustring.sub( excerpt, e )
 				end
 			end
 		end
 	end
 
 	-- If no file was found, try to get one from the infobox
 	local fileNamespaces = Transcluder.getNamespaces( 'File' )
 	if ( ( only == 'file' or only == 'files' ) or ( not only and ( files ~= '0' or not files ) ) ) and -- caller asked for files
 		not Transcluder.matchAny( excerpt, '%[%[', fileNamespaces, ':' ) and -- and there are no files in Transcluder's output
 		config.captions -- and we have the config option required to try finding files in templates
 	then
 		-- We cannot distinguish the infobox from the other templates so we search them all
 		local infobox = Transcluder.getTemplates( excerpt );
 		infobox = table.concat( infobox )
 		local parameters = Transcluder.getParameters( infobox )
 		local file, captions, caption
 		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
 				excerpt = '[[File:' .. file .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt
 				if ( onlyFreeFiles ) then
 					excerpt = Transcluder.removeNonFreeFiles( excerpt )
 				end
 				break
 			end
 		end
 	end
 
 	-- Unlike other elements, templates are filtered here
 	-- 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