Difference between revisions of "Module:Excerpt"

From Asmbly Wiki
m (1 revision imported)
(Update to 1.5)
Line 1: Line 1:
local Transcluder = require('Module:Transcluder')
+
-- Module:Excerpt implements the Excerpt template
 +
-- Documentation https://www.mediawiki.org/wiki/Module:Excerpt
 +
-- By User:Sophivorus, User:Certes & others
 +
-- Version 1.5
 +
-- License CC BY-SA-3.0
  
local yesno = require('Module:Yesno')
+
local Transcluder = require( 'Module:Transcluder' )
  
local ok, config = pcall(require, 'Module:Excerpt/config')
+
local yesno = require( 'Module:Yesno' )
 +
 
 +
local ok, config = pcall( require, 'Module:Excerpt/config' )
 
if not ok then config = {} end
 
if not ok then config = {} end
  
Line 10: Line 16:
 
-- Helper function to get arguments
 
-- Helper function to get arguments
 
local args
 
local args
function getArg(key, default)
+
function getArg( key, default )
value = args[key]
+
local value = args[ key ]
if value and mw.text.trim(value) ~= '' then
+
if value and mw.text.trim( value ) ~= '' then
 
return value
 
return value
 
end
 
end
Line 19: Line 25:
  
 
-- Helper function to handle errors
 
-- Helper function to handle errors
function getError(message, value)
+
function getError( message, value )
if type(message) == 'string' then
+
if type( message ) == 'string' then
message = Transcluder.getError(message, value)
+
message = Transcluder.getError( message, value )
 
end
 
end
 
if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then
 
if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then
message:node('[[Category:' .. config.categories.errors .. ']]')
+
message:node( '[[Category:' .. config.categories.errors .. ']]' )
 
end
 
end
 
return message
 
return message
Line 30: Line 36:
  
 
-- Helper function to get localized messages
 
-- Helper function to get localized messages
function getMessage(key)
+
function getMessage( key )
local ok, TNT = pcall(require, 'Module:TNT')
+
local ok, TNT = pcall( require, 'Module:TNT' )
 
if not ok then return key end
 
if not ok then return key end
return TNT.format('I18n/Module:Excerpt.tab', key)
+
return TNT.format( 'I18n/Module:Excerpt.tab', key )
 
end
 
end
  
function p.main(frame)
+
function p.main( frame )
args = Transcluder.parseArgs(frame)
+
args = Transcluder.parseArgs( frame )
  
 
-- Make sure the requested page exists
 
-- Make sure the requested page exists
local page = getArg(1, getArg('article') )
+
local page = getArg( 1 )
if not page then return getError('no-page') end
+
if not page then return getError( 'no-page' ) end
 
local title = mw.title.new(page)
 
local title = mw.title.new(page)
if not title then return getError('no-page') end
+
if not title then return getError( 'no-page' ) end
 
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 getError( 'page-not-found', page ) end
 
page = title.prefixedText
 
page = title.prefixedText
  
-- Set variables
+
-- Set variables from the template parameters
local fragment = getArg('fragment')
+
local section = getArg( 2, mw.ustring.match( getArg( 1 ), '[^#]+#(.+)' ) )
local section = fragment or getArg(2, getArg('section', mw.ustring.match( getArg(1, getArg('article') ), '[^#]+#([^#]+)') ) )
+
local hat = yesno( getArg( 'hat', true ) )
local hat = yesno( getArg('hat', true) )
+
local edit = yesno( getArg( 'edit', true ) )
local edit = yesno( getArg('edit', true) )
+
local this = getArg( 'this' )
local this = getArg('this')
+
local only = getArg( 'only' )
local only = getArg('only')
+
local files = getArg( 'files', getArg( 'file', ( only == 'file' and 1 ) ) )
local files = getArg('files', getArg('file', ( only == 'file' and 1 ) ) )
+
local lists = getArg( 'lists', getArg( 'list', ( only == 'list' and 1 ) ) )
local lists = getArg('lists', getArg('list', ( only == 'list' and 1 ) ) )
+
local tables = getArg( 'tables', getArg( 'table', ( only == 'table' and 1 ) ) )
local tables = getArg('tables', getArg('table', ( only == 'table' and 1 ) ) )
+
local templates = getArg( 'templates', getArg( 'template', ( only == 'template' and 1 ) ) )
local templates = getArg('templates', getArg('template', ( only == 'template' and 1 ) ) )
+
local paragraphs = getArg( 'paragraphs', getArg( 'paragraph', ( only == 'paragraph' and 1 ) ) )
local paragraphs = getArg('paragraphs', getArg('paragraph', ( only == 'paragraph' and 1 ) ) )
+
local references = getArg( 'references' )
local references = getArg('references', getArg('reference', ( only == 'reference' and 1 ) ) )
+
local subsections = not yesno( getArg( 'subsections' ) )
local subsections = not yesno( getArg('subsections') )
+
local noLinks = not yesno( getArg( 'links', true ) )
local noBold = not yesno( getArg('bold') )
+
local noBold = not yesno( getArg( 'bold' ) )
local inline = yesno( getArg('inline') )
+
local onlyFreeFiles = yesno( getArg( 'onlyfreefiles', true ) )
local quote = yesno( getArg('quote') )
+
local inline = yesno( getArg( 'inline' ) )
local more = yesno( getArg('more') )
+
local quote = yesno( getArg( 'quote' ) )
local class = getArg('class')
+
local more = yesno( getArg( 'more' ) )
 +
local class = getArg( 'class' )
  
 
-- Build the hatnote
 
-- Build the hatnote
Line 73: Line 80:
 
hat = this
 
hat = this
 
elseif quote then
 
elseif quote then
hat = getMessage('this')
+
hat = getMessage( 'this' )
 
elseif only then
 
elseif only then
hat = getMessage(only)
+
hat = getMessage( only )
 
else
 
else
hat = getMessage('section')
+
hat = getMessage( 'section' )
 
end
 
end
hat = hat .. ' ' .. getMessage('excerpt') .. ' '
+
hat = hat .. ' ' .. getMessage( 'excerpt' ) .. ' '
 
if section and not fragment then
 
if section and not fragment then
hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page
+
hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. page
.. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']].' -- remove nested links
+
.. ' § ' .. mw.ustring.gsub( section, '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links
 
else
 
else
 
hat = hat .. '[[:' .. page .. '|' .. page .. ']].'
 
hat = hat .. '[[:' .. page .. '|' .. page .. ']].'
Line 88: Line 95:
 
if edit then
 
if edit then
 
hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>['
 
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 .. title:fullUrl( 'action=edit' ) .. ' ' .. mw.message.new( 'editsection' ):plain()
 
hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>'
 
hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>'
 
end
 
end
 
if config.hat then
 
if config.hat then
 
hat = config.hat .. hat .. '}}'
 
hat = config.hat .. hat .. '}}'
hat = frame:preprocess(hat)
+
hat = frame:preprocess( hat )
 
else
 
else
hat = mw.html.create('div'):addClass('dablink excerpt-hat'):wikitext(hat)
+
hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat )
 
end
 
end
 
else
 
else
Line 103: Line 110:
 
-- Build the "Read more" link
 
-- Build the "Read more" link
 
if more and not inline then
 
if more and not inline then
more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''"
+
more = "'''[[" .. page .. '#' .. ( section or '' ) .. "|" .. getMessage( 'more' ) .. "]]'''"
more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more)
+
more = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( more )
 
else
 
else
 
more = nil
 
more = nil
 
end
 
end
  
-- Build the templates option
+
-- Build the options for Module:Transcluder out of the template parameters and the desired defaults
local blacklist = table.concat((config.blacklist or config.templates or {}), ',')
 
if templates then
 
if string.sub(templates, 1, 1) == '-' then
 
templates = templates .. ',' .. blacklist
 
end
 
else
 
templates = '-' .. blacklist
 
end
 
 
 
-- Build the options for Module:Transcluder out of the template arguments and the desired defaults
 
 
local options = {
 
local options = {
 
files = files,
 
files = files,
Line 125: Line 122:
 
tables = tables,
 
tables = tables,
 
paragraphs = paragraphs,
 
paragraphs = paragraphs,
templates = templates,
 
 
sections = subsections,
 
sections = subsections,
 
categories = 0,
 
categories = 0,
 
references = references,
 
references = references,
only = only and mw.text.trim(only, 's') .. 's',
+
only = only and mw.text.trim( only, 's' ) .. 's',
 +
noLinks = noLinks,
 
noBold = noBold,
 
noBold = noBold,
 
noSelfLinks = true,
 
noSelfLinks = true,
noNonFreeFiles = true,
+
noNonFreeFiles = onlyFreeFiles,
 
noBehaviorSwitches = true,
 
noBehaviorSwitches = true,
 
fixReferences = true,
 
fixReferences = true,
Line 139: Line 136:
  
 
-- Get the excerpt itself
 
-- Get the excerpt itself
local title = page .. '#' .. (section or '')
+
local title = page .. '#' .. ( section or '' )
local ok, excerpt = pcall(Transcluder.get, title, options)
+
local ok, excerpt = pcall( Transcluder.get, title, options )
if not ok then return getError(excerpt) end
+
if not ok then return getError( excerpt ) end
if mw.text.trim(excerpt) == '' and not only then
+
if mw.text.trim( excerpt ) == '' and not only then
if section then return getError('section-empty', section) else return getError('lead-empty') end
+
if section then return getError( 'section-empty', section ) else return getError( 'lead-empty' ) end
 
end
 
end
  
-- Add a line break in case the excerpt starts with a table or list
+
-- If no file was found, try to get one from the infobox
excerpt = '\n' .. excerpt
+
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
-- If no file was found, try to excerpt one from the removed infoboxes
+
not Transcluder.matchAny( excerpt, '%[%[', fileNamespaces, ':' ) and -- and there are no files in Transcluder's output
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
 
config.captions -- and we have the config option required to try finding files in templates
 
then
 
then
local templates = Transcluder.get(title, { only = 'templates', templates = blacklist, fixReferences = true } )
+
-- We cannot distinguish the infobox from the other templates so we search them all
local parameters = Transcluder.getParameters(templates)
+
local infobox = Transcluder.getTemplates( excerpt );
 +
infobox = table.concat( infobox )
 +
local parameters = Transcluder.getParameters( infobox )
 
local file, captions, caption
 
local file, captions, caption
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 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
+
file = mw.ustring.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
 +
excerpt = '[[File:' .. file .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt
 +
if ( onlyFreeFiles ) then
 +
excerpt = Transcluder.removeNonFreeFiles( excerpt )
 
end
 
end
excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt
 
excerpt = Transcluder.removeNonFreeFiles(excerpt)
 
 
break
 
break
 
end
 
end
 
end
 
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
 
-- Remove nested categories
excerpt = frame:preprocess(excerpt)
+
excerpt = frame:preprocess( excerpt )
local categories, excerpt = Transcluder.getCategories(excerpt, options.categories)
+
local categories, excerpt = Transcluder.getCategories( excerpt, options.categories )
  
 
-- Add tracking categories
 
-- Add tracking categories
Line 197: Line 219:
  
 
-- Combine and return the elements
 
-- Combine and return the elements
local tag1 = 'div'
 
local tag2 = 'div'
 
 
if inline then
 
if inline then
tag1 = 'span'
+
return mw.text.trim( excerpt )
tag2 = 'span'
+
end
elseif quote then
+
local tag = 'div'
tag2 = 'blockquote'
+
if quote then
 +
tag = 'blockquote'
 
end
 
end
excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt)
+
excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt )
local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class)
+
local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( class )
return block:node(styles):node(hat):node(excerpt):node(more)
+
return block:node( styles ):node( hat ):node( excerpt ):node( more )
 
end
 
end
  
 
-- Entry points for backwards compatibility
 
-- Entry points for backwards compatibility
function p.lead(frame) return p.main(frame) end
+
function p.lead( frame ) return p.main( frame ) end
function p.excerpt(frame) return p.main(frame) end
+
function p.excerpt( frame ) return p.main( frame ) end
  
 
return p
 
return p

Revision as of 14:12, 30 September 2022

Page Module:Message box/ombox.css has no content. Page Module:Message box/ombox.css has no content. Page Module:Message box/ombox.css has no content.

This module implements Template:Excerpt.



-- Module:Excerpt implements the Excerpt template
-- Documentation https://www.mediawiki.org/wiki/Module:Excerpt
-- By User:Sophivorus, User:Certes & others
-- Version 1.5
-- 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
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
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
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

function p.main( frame )
	args = Transcluder.parseArgs( frame )

	-- Make sure the requested page exists
	local page = getArg( 1 )
	if not page then return getError( 'no-page' ) end
	local title = mw.title.new(page)
	if not title then return getError( 'no-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 inline = yesno( getArg( 'inline' ) )
	local quote = yesno( getArg( 'quote' ) )
	local more = yesno( getArg( 'more' ) )
	local class = getArg( 'class' )

	-- 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 and not fragment then
			hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. page
				.. ' § ' .. mw.ustring.gsub( section, '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links
		else
			hat = hat .. '[[:' .. page .. '|' .. page .. ']].'
		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

	-- Build the "Read more" link
	if more and not inline then
		more = "'''[[" .. page .. '#' .. ( section or '' ) .. "|" .. getMessage( 'more' ) .. "]]'''"
		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

	-- 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

return p