Aller au contenu

Module:Debate

De Wikidébats, l'encyclopédie des débats et des arguments « pour » et « contre »

La documentation pour ce module peut être créée à Module:Debate/doc

--	Module:Debate (i18n FR/EN) — Optimisé
--	Objectif : rendu du modèle « Débat » (MediaWiki 1.43) fidèle au rendu existant

local p = {}

----------------------------------------------------------------------
--	Accès global au frame + VariablesLua (accélération sans changer le rendu)
----------------------------------------------------------------------

local mw			= mw
local F				= mw.getCurrentFrame()
local V				= ( mw.ext and mw.ext.VariablesLua ) or nil
local WD_I18N		= require( 'Module:WD/I18N' )
local uri_encode	= mw.uri.encode
local string_format	= string.format


local title_getCurrentTitle	= mw.title.getCurrentTitle
local uri_fullUrl			= mw.uri.fullUrl
local uri_localUrl			= mw.uri.localUrl

--	Locaux (micro-optis)
local tostring		= tostring
local type			= type
local ipairs		= ipairs
local pairs			= pairs
local table_concat	= table.concat
local function table_insert( t, a, b )
	if b == nil then
		return table.insert( t, a )
	end

	if type( a ) ~= "number" then
		if type( b ) == "number" then
			return table.insert( t, a )
		end
		return table.insert( t, b )
	end

	return table.insert( t, a, b )
end
local t_trim		= mw.text.trim
local t_gsplit		= mw.text.gsplit
local t_nowiki		= mw.text.nowiki
local t_jsonEncode	= mw.text.jsonEncode
local t_jsonDecode	= mw.text.jsonDecode
local html_create	= mw.html.create
local gsub			= string.gsub

--	Forward decl (utilisé avant définition)
local escapeAttr

local ITEM_SEP	= "⟭"
local FIELD_SEP	= "⟬"

--	wrappers VariablesLua (no-op si extension absente)
local function vset( name, val )
	if V and type( V.vardefine ) == "function" then
		pcall( V.vardefine, name, val ~= nil and tostring( val ) or "" )
	end
end
local function vget( name )
	if V and type( V.var ) == "function" then
		local ok, res = pcall( V.var, name )
		if ok then return res end
	end
	return nil
end

local function detectLang( args )
	return WD_I18N.getLangFromArgs( args or {} )
end

local function L( lang, section, key, ... )
	return WD_I18N.msg( "Debate." .. section, lang, key, ... )
end

local function smwProp( lang, key )
	return WD_I18N.msg( "Debate.props", lang, key )
end

local function isProgress( lang, val, key )
	if not val or val == "" then return false end
	local label = WD_I18N.msg( "Debate.progress", lang, key )
	return val == label
end

----------------------------------------------------------------------
--	Anchor intégré (remplace Module:Anchor)
----------------------------------------------------------------------

local function anchorId( s )
	s = tostring( s or "" )

	--	Normalisation des apostrophes HTML éventuelles
	s = gsub( s, "'", "'" )
	s = gsub( s, "'", "'" )
	s = gsub( s, "'", "'" )

	--	Nettoyage minimal
	s = gsub( s, "%s+", " " )
	s = t_trim( s )

	--	Espaces → underscore
	s = gsub( s, " ", "_" )

	--	Suppression caractères cassants (HTML / wiki)
	s = gsub( s, '[%[%]{}|#<>"]', "" )

	return s
end

----------------------------------------------------------------------
--	Helpers
----------------------------------------------------------------------

local function getArgs( frame )
	local parent = frame:getParent()
	local A = {}

	local function add( k, v )
		if v == nil then return end
		v = t_trim( tostring( v ) )
		if v ~= "" then
			A[ k ] = v
		end
	end

	if parent and parent.args then
		for k, v in pairs( parent.args ) do
			add( k, v )
		end
	end

	if frame.args then
		for k, v in pairs( frame.args ) do
			add( k, v )
		end
	end

	return A
end

local function splitCSV( s, sep )
	if not s or s == "" then return {} end
	sep = sep or ","
	local out = {}
	for part in t_gsplit( s, sep, true ) do
		part = t_trim( part )
		if part ~= "" then table_insert( out, part ) end
	end
	return out
end

local function wkParseWikipediaMarkers( raw )
	raw = tostring( raw or "" )
	raw = t_trim( raw )
	if raw == "" then
		return {}
	end

	--	On attend maintenant une série de "Titre⟭"
	if not raw:find( ITEM_SEP, 1, true ) then
		return {}
	end

	local pages = {}

	for part in t_gsplit( raw, ITEM_SEP, true ) do
		part = t_trim( part )
		if part ~= "" then
			pages[ #pages + 1 ] = part
		end
	end

	return pages
end

local function expand( frame, title, params )
	local ok, res = pcall( function()
		return frame:expandTemplate{ title = title, args = params or {} }
	end )
	return ok and ( res or "" ) or ""
end

local function hasSMW()
	return type( mw.smw ) == "table" and type( mw.smw.set ) == "function"
end

local function smwSetSafe( props )
	if not mw.smw or type( mw.smw.set ) ~= "function" then
		return
	end

	pcall( function()
		mw.smw.set( props )
	end )
end

local function smwAskSafe( q )
	if not hasSMW() then return nil end
	local ok, res = pcall( function() return mw.smw.ask( q ) end )
	return ok and res or nil
end

----------------------------------------------------------------------
--	SEO : cache “par rendu”
----------------------------------------------------------------------

local SEO_CACHE = {}

local function seoCacheKey( args, pv, lang )
	return table_concat( {
		tostring( lang or "" ),
		pv.pageId or "",
		tostring( pv.rawTitle or "" ),
		tostring( args[ "complete-topic" ] or "" ),
		tostring( args[ "keywords" ] or "" ),
		tostring( args[ "sections" ] or "" ),
	}, "\n" )
end

----------------------------------------------------------------------
--	Wikiseo helpers
----------------------------------------------------------------------

local function wkSeoHas()
	return mw.ext
		and mw.ext.seo
		and type( mw.ext.seo.set ) == "function"
end

local function wkSeoSet( data )
	if not wkSeoHas() then
		return
	end
	pcall( mw.ext.seo.set, data )
end

local function seoL( lang, key, ... )
	return WD_I18N.msg( "Debate.seo", lang, key, ... ) or ""
end

local function seoJoinList( items )
	local out = {}
	local seen = {}

	for _, v in ipairs( items or {} ) do
		v = t_trim( tostring( v or "" ) )
		if v ~= "" and not seen[ v ] then
			seen[ v ] = true
			out[ #out + 1 ] = v
		end
	end

	return table_concat( out, ", " )
end

local function buildSeoKeywords( args, lang )
	local items = {}

	for _, k in ipairs( splitCSV( args[ "keywords" ], "," ) ) do
		items[ #items + 1 ] = k
	end

	for _, s in ipairs( splitCSV( args[ "sections" ], "," ) ) do
		items[ #items + 1 ] = s
	end

	return seoJoinList( items )
end

local function getPageCreation(pv, args)
	--	1) Priorité : paramètre PageForms / modèle
	local v = args and args[ "creation-date" ]
	v = (type(v) == "string") and t_trim(v) or ""
	if v ~= "" then
		return v
	end

	local title = pv and pv.title
	local page = title and title.prefixedText or ""
	if page == "" then
		return nil
	end

	--	2) Sinon : SMW ?Date de création
	if hasSMW() and type(mw.smw.ask) == "function" then
		local res = smwAskSafe({
			"[[" .. page .. "]]",
			"?Creation date#-F[Y-m-d\\TH:i:s\\Z]=cd",
			"limit=1",
			"link=none"
		})

		if type(res) == "table" and res[1] then
			local cd = res[1].cd
			return cd
		end
	end
end

----------------------------------------------------------------------
--	SEO principal (avec cache)
----------------------------------------------------------------------

local function renderFullSEO( args, pv, lang )
	--	cache par rendu
	local ck = seoCacheKey( args, pv, lang )
	if SEO_CACHE[ ck ] then
		return
	end
	SEO_CACHE[ ck ] = true

	local pageTitle	= pv.rawTitle or ""

	local topic = t_trim( tostring( args[ "complete-topic" ] or "" ) )
	if topic == "" then
		topic = pageTitle
	end

	local kw = buildSeoKeywords( args, lang )
	local published = getPageCreation(pv, args)

	wkSeoSet{
		title				= string_format( seoL( lang, "title" ), pageTitle ),
		title_mode			= string_format( seoL( lang, "title_mode" ), pageTitle ),
		title_separator		= string_format( seoL( lang, "title_separator" ), pageTitle ),
		description			= string_format( seoL( lang, "description" ), topic ),
		keywords			= kw,
		image				= seoL( lang, "image" ),
		image_alt			= seoL( lang, "image_alt" ),
		type				= "article",
		section				= seoL( lang, "section" ),
		site_name			= seoL( lang, "site_name" ),
		twitter_site		= seoL( lang, "twitter_site" ),
		locale				= seoL( lang, "locale" ),
		author				= seoL( lang, "author" ),
		published_time		= published,
		robots				= "index,follow",
		googlebot			= "index,follow",
	}
end

----------------------------------------------------------------------
--	Helpers PERF : preprocess conditionnel + cache Bandeau
----------------------------------------------------------------------

local function getBanner( x, bannerCache )
	x = t_trim( tostring( x or "" ) )
	if x == "" then
		return ""
	end

	local v = bannerCache[ x ]
	if v ~= nil then
		return v
	end

	--	Le rendu historique faisait {{Bandeau x}} (et non un appel Lua)
	v = expand( F, "Bandeau " .. x, {} ) or ""
	bannerCache[ x ] = v
	return v
end

----------------------------------------------------------------------
--	SMW batch : on accumule et on flush une fois à la fin
----------------------------------------------------------------------

local function smwQueueAdd( pending, key, val, multi )
	if not key or key == "" or val == nil then
		return
	end

	if type( val ) == "string" then
		val = t_trim( val )
		if val == "" then
			return
		end
	end

	local cur = pending[ key ]

	if multi then
		--	Multi-valeurs : toujours en table côté pending
		if cur == nil then
			pending[ key ] = ( type( val ) == "table" ) and val or { val }
			return
		end

		if type( cur ) ~= "table" then
			cur = { cur }
			pending[ key ] = cur
		end

		if type( val ) == "table" then
			for _, x in ipairs( val ) do
				if x ~= nil then
					cur[ #cur + 1 ] = x
				end
			end
		else
			cur[ #cur + 1 ] = val
		end

		return
	end

	--	Scalar : dernière valeur gagne (comportement le plus sûr)
	pending[ key ] = val
end

local function smwQueueAddFromSepString( pending, key, raw, sep )
	raw = ( type( raw ) == "string" ) and t_trim( raw ) or ""
	if raw == "" then
		return
	end

	sep = sep or ITEM_SEP

	local vals = {}

	if raw:find( sep, 1, true ) then
		for part in t_gsplit( raw, sep, true ) do
			part = t_trim( part )
			if part ~= "" then
				vals[ #vals + 1 ] = part
			end
		end
	else
		vals[ 1 ] = raw
	end

	if #vals > 0 then
		smwQueueAdd( pending, key, vals, true )
	end
end

local function smwQueueAddFromCSV( pending, key, raw )
	smwQueueAddFromSepString( pending, key, raw, "," )
end

local function smwQueueMerge( dst, src )
	for k, v in pairs( src or {} ) do
		if type( v ) == "table" then
			smwQueueAdd( dst, k, v, true )
		else
			smwQueueAdd( dst, k, v, false )
		end
	end
end

----------------------------------------------------------------------
--	PageForms : utilitaires HTML / attributs
----------------------------------------------------------------------

escapeAttr = function( s )
	s = tostring( s or "" )
	return s
		:gsub( "[\r\n\t]", " " )
		:gsub( "&", "&amp;" )
		:gsub( "<", "&lt;" )
		:gsub( ">", "&gt;" )
		:gsub( '"', "&quot;" )
	-- pas besoin d'échapper ' si on entoure l'attribut avec des guillemets doubles
end

--	{{#tag}} utilitaire
local function tag( name, content, attrs )
	local parts = {}
	if attrs then
		for k, v in pairs( attrs ) do
			table_insert( parts, string_format( "%s=%s", k, v ) )
		end
	end
	local src
	if #parts > 0 then
		src = string_format( "{{#tag:%s|%s|%s}}", name, content or "", table_concat( parts, "|" ) )
	else
		src = string_format( "{{#tag:%s|%s}}", name, content or "" )
	end
	return F:preprocess( src )
end

local function pf_escape( v )
	if not v then return "" end
	v = tostring( v )
	v = v:gsub( "|", "&#124;" ):gsub( "=", "&#61;" )
	return v
end

----------------------------------------------------------------------
--	PageForms : remplacement des #formlink (link) par Special:AddData
----------------------------------------------------------------------

local function wkDbKey( page )
	local t = mw.title.new( page )
	if not t then
		return tostring( page or "" ):gsub( "%s", "_" )
	end
	return t.prefixedText:gsub( " ", "_" )
end

local function wkEncodeAddDataSegment( s )
	s = tostring( s or "" )
	s = s:gsub( "%%", "%%25" )
	s = s:gsub( "%?", "%%3F" )
	s = s:gsub( "#", "%%23" )
	s = s:gsub( "/", "%%2F" )
	return s
end

local function wkWikipediaUrl( page, lang )
	if not page or page == "" then
		return ""
	end

	local base = L( lang, "urls", "wikipedia" )
	if not base or base == "" then
		return ""
	end

	return base .. uri_encode( page, "WIKI" )
end

local function wkAddDataPath( formName, pageTitle )
	local form = wkEncodeAddDataSegment( wkDbKey( formName ) )
	local page = wkEncodeAddDataSegment( wkDbKey( pageTitle ) )
	return "Special:AddData/" .. form .. "/" .. page
end

local function addDataLink( formName, pageTitle, linktext, tooltip )
	formName = tostring( formName or "" )
	pageTitle = tostring( pageTitle or "" )
	if formName == "" or pageTitle == "" then
		return ""
	end

	local target = wkAddDataPath( formName, pageTitle )

	local lt = "&nbsp;"
	local tt = tostring( tooltip or "" )

	if tt ~= "" then
		return string_format(
			'<span class="wk-adddata-link" data-wk-tooltip="%s">[[%s|%s]]</span>',
			escapeAttr( tt ),
			target,
			lt
		)
	end

	return string_format( '[[%s|%s]]', target, lt )
end

local function wkRunQueryPath( formName )
	local form = wkEncodeAddDataSegment( wkDbKey( formName ) )
	return "Special:RunQuery/" .. form
end

local function normalizeTooltipText( s )
	s = tostring( s or "" )

	--	Apostrophes
	s = s:gsub( "'", "’" )

	--	Guillemets droits → typographiques
	s = s:gsub( '"', '“' )

	return s
end

local function wkRunQueryHtmlTagButton( formName, label, tooltip, query )
	formName	= tostring( formName or "" )
	label		= tostring( label or "" )
	query		= ( type( query ) == "table" ) and query or {}
	tooltip		= tostring( tooltip or "" )

	tooltip = normalizeTooltipText( tooltip )

	if formName == "" then
		return ""
	end

	local title	= wkRunQueryPath( formName )
	local href	= tostring( uri_localUrl( title, query ) )

	local content = "+ " .. label

	return F:preprocess(
		'<htmltag tagname="a"'
		.. ' href="#"'
		.. ' data-href="' .. escapeAttr( href ) .. '"'
		.. ' class="wk-btn__a wk-js-nav"'
		.. ( tooltip ~= "" and ( ' title="' .. escapeAttr( tooltip ) .. '"' ) or "" )
		.. '>'
		.. content
		.. '</htmltag>'
	)
end

local function wkAddDataGetButton( formName, pageTitle, label, tooltip, query )
	formName	= tostring( formName or "" )
	pageTitle	= tostring( pageTitle or "" )
	label		= tostring( label or "" )
	tooltip		= tostring( tooltip or "" )
	query		= ( type( query ) == "table" ) and query or {}

	if formName == "" or pageTitle == "" then
		return ""
	end

	local title	= wkAddDataPath( formName, pageTitle )
	local url	= tostring( uri_fullUrl( title, query ) )	--	URL absolue obligatoire

	local linkInner =
		'<span class="wk-btn__icon" aria-hidden="true">➜</span>'
		.. '<span class="wk-btn__label">' .. label .. '</span>'

	local link = '[' .. url .. ' ' .. linkInner .. ']'

	return
		'<div class="wk-btn wk-auto-id mw-ui-button navigation-not-searchable"'
		.. ( tooltip ~= "" and ( ' title="' .. escapeAttr( tooltip ) .. '"' ) or "" )
		.. '>'
		.. link
		.. '</div>'
end

local function runQueryLink( formName, fieldLabel, fieldName, value, linktext, tooltip, extraParams )
	formName = tostring( formName or "" )
	fieldLabel = tostring( fieldLabel or "" )
	fieldName = tostring( fieldName or "" )
	value = tostring( value or "" )
	if formName == "" or fieldLabel == "" or fieldName == "" or value == "" then
		return ""
	end

	local title = wkRunQueryPath( formName )

	local q = {}
	q[ fieldLabel .. "[" .. fieldName .. "]" ] = value

	if type( extraParams ) == "table" then
		for k, v in pairs( extraParams ) do
			if k and k ~= "" and v ~= nil and tostring( v ) ~= "" then
				q[ k ] = tostring( v )
			end
		end
	end

	q[ "_run" ] = ""

	local url = tostring( uri_fullUrl( title, q ) )
	local label = linktext or value
	local tt = tostring( tooltip or "" )

	local link = "[" .. url .. " " .. label .. "]"

	if tt ~= "" then
		return string_format(
			'<span class="wk-adddata-link masquer-externe" data-wk-tooltip="%s">%s</span>',
			escapeAttr( tt ),
			link
		)
	end

	return string_format( '<span class="wk-adddata-link">%s</span>', link )
end

local function joinArticles( items, lang )
	local n = #items
	if n == 0 then return "" end
	if n == 1 then return items[ 1 ] end

	local conj = WD_I18N.msg( "Common.text", lang, "list_conjunction_text" )

	if n == 2 then
		return items[ 1 ] .. conj .. items[ 2 ]
	end

	local last = items[ n ]
	local first = {}
	for i = 1, n - 1 do first[ i ] = items[ i ] end
	return table_concat( first, ", " ) .. conj .. last
end

local function listBreaker()
	return "\n\n<!--__WD_LIST_BREAK__-->"
end

local function push( t, s )
	t[ #t + 1 ] = s
end

----------------------------------------------------------------------
--	Arguments : extraction depuis chaînes
----------------------------------------------------------------------

local function wkSplit2( s, sep )
	s = tostring( s or "" )
	sep = tostring( sep or "" )
	if sep == "" then
		return s, ""
	end

	local i = s:find( sep, 1, true )
	if not i then
		return s, ""
	end

	return s:sub( 1, i - 1 ), s:sub( i + #sep )
end

local function wkExtractArgumentItemsFromData( raw )
	raw = tostring( raw or "" )
	raw = t_trim( raw )
	if raw == "" then
		return {}
	end

	if not raw:find( ITEM_SEP, 1, true ) and not raw:find( FIELD_SEP, 1, true ) then
		return {}
	end

	local items = {}

	for block in t_gsplit( raw, ITEM_SEP, true ) do
		block = t_trim( block )
		if block ~= "" then
			local page, rest = wkSplit2( block, FIELD_SEP )
			local title, warnings = wkSplit2( rest, FIELD_SEP )

			page = t_trim( page )
			if page ~= "" then
				title = t_trim( title )
				warnings = t_trim( warnings )

				if title == "" then
					title = page
				end

				items[ #items + 1 ] = {
					page = page,
					title = title,
					warnings = warnings
				}
			end
		end
	end

	return items
end

local function wkComputeArgumentItems( raw )
	local items = wkExtractArgumentItemsFromData( raw )
	if #items > 0 then
		return items, "data"
	end
	return {}, "none"
end

local function wkBuildArgumentData( items, lang, kind )
	items = items or {}

	local idPrefix = ( kind == "pro" ) and "Pro_argument_" or "Con_argument_"

	local listLines = {}
	for _, it in ipairs( items ) do
		table_insert( listLines, "* " .. it.title )
	end
	local listText = table_concat( listLines, "\n" )

	local argsVarParts = {}
	for _, it in ipairs( items ) do
		if it.page == it.title then
			table_insert( argsVarParts, it.page )
		else
			table_insert( argsVarParts, it.page .. "⟬" .. it.title )
		end
	end
	local argsVar = table_concat( argsVarParts, " -;- " )

	local mapLines = {}
	for i, it in ipairs( items ) do
		table_insert( mapLines,
			'<div  id="' .. idPrefix .. i .. '_map" class="argument-title--map wk-icon argument-icon">' .. it.title .. '</div>'
		)
	end
	local mapHtml = table_concat( mapLines, "\n" )

	local hoverLines = {}
	for _, it in ipairs( items ) do
		table_insert( hoverLines,
			'<div class="argument-title--map wk-icon argument-icon">' .. '[[' .. it.page .. '|' .. it.title .. ']]</div>'
		)
	end
	local mapHoverHtml = table_concat( hoverLines, "\n" )

	return {
		items = items,
		list = listText,
		argsVar = argsVar,
		map = mapHtml,
		mapHover = mapHoverHtml
	}
end

local function computeArgumentMapsFromArgs( args, lang )
	local rawPro = args[ "pro-arguments" ] or ""
	local rawCon = args[ "con-arguments" ] or ""

	if rawPro ~= "" then
		rawPro = F:preprocess( rawPro )
	end
	if rawCon ~= "" then
		rawCon = F:preprocess( rawCon )
	end

	local pro, proMode = wkComputeArgumentItems( rawPro )
	local con, conMode = wkComputeArgumentItems( rawCon )

	return {
		pro = wkBuildArgumentData( pro, lang, "pro" ),
		con = wkBuildArgumentData( con, lang, "con" ),
		_mode = { pro = proMode, con = conMode }
	}
end

local function wkRenderArgumentLi( pv, lang, it, kind, idx )
	local isPro = ( kind == "pro" )

	local idPrefix = isPro and "Pro_argument_" or "Con_argument_"

	local page = tostring( it.page or "" )
	local titleShown = tostring( it.title or "" )
	if titleShown == "" then
		titleShown = page
	end

	local aId = anchorId(titleShown)

	local warnings = tostring( it.warnings or "" )

	return
		'<li id="'
		.. idPrefix .. tostring( idx )
		.. '" class="argument level-1">'
		.. '<div id="'
		.. escapeAttr( aId )
		.. '" class="argument-title wk-icon argument-icon wk-carret"'
		.. ( warnings ~= "" and ( ' data-warnings="' .. escapeAttr( warnings ) .. '"' ) or "" ) .. '>'
		.. '[[' .. page .. '|' .. titleShown .. ']]'
		.. '</div>'
		.. '</li>'
end

----------------------------------------------------------------------
--	Bloc d’en-tête / variables de page
----------------------------------------------------------------------

local function computePageVars()
	local title = title_getCurrentTitle()
	local rawTitle = title.prefixedText
	local encoded = uri_encode( rawTitle, "WIKI" )
	local pageUrl = tostring( uri_fullUrl( title.prefixedText ) )
	local pageId = tostring( title.id or "" )

	return {
		title = title,
		rawTitle = rawTitle,
		encoded = encoded,
		pageUrl = pageUrl,
		pageId = pageId
	}
end

----------------------------------------------------------------------
--	Bandeau principal + JSON-LD breadcrumb (i18n)
----------------------------------------------------------------------

local function renderMetaWarningHtml( params )
	local color	= tostring( params.color or "" )
	local icon	= tostring( params.icon or "" )
	local size	= tostring( params.size or "40px" )
	local alt	= tostring( params.alt or "" )
	local title	= tostring( params.title or "" )
	local text	= tostring( params.text or "" )

	return
		'<div class="bandeau bandeau-' .. color .. ' navigation-not-searchable">'
		.. '<table style="background-color:transparent">'
		.. '<tr>'
		.. '<td class="bandeau-icone">'
		.. '<div style="width:60px; text-align:center">[[File: ' .. icon .. '|' .. size .. '|alt=' .. escapeAttr( alt ) .. '|link=]]</div>'
		.. '</td>'
		.. '<td style="width: 100%;">'
		.. '<div class="bandeau-titre"><strong>' .. title .. '</strong></div><!--'
		.. '--><div class="bandeau-texte">' .. text .. '</div>'
		.. '</td>'
		.. '</tr>'
		.. '</table><!--'
		.. '--></div>'
end

local function renderMainBanner( frame, pv, lang )
	local titre = string_format( L( lang, "text", "main_banner_title" ), t_nowiki( pv.rawTitle ) )

	return renderMetaWarningHtml{
		color	= L( lang, "text", "main_banner_color" ),
		icon	= L( lang, "files", "icon_wikidebats" ),
		size	= "48px",
		alt		= L( lang, "text", "main_banner_alt" ),
		title	= titre,
		text		=
			'<div class="bandeau-section" style="margin: 0.5em 0 0.35em 0;">'
			.. '<table style="background-color:transparent"><tr>'
			.. '<td><div style="text-align:center; margin-right: 0.5em;">[[File: ' .. L( lang, "files", "search" ) .. ' | 13px | link= | class=mw-no-invert]]</div></td>'
			.. '<td>' .. L( lang, "text", "learn_more_label" ) .. L( lang, "text", "founding_principles_link" ) .. '</td>'
			.. '</tr></table></div>'
	}
end

----------------------------------------------------------------------
--	JSON-LD breadcrumb + FAQPage (i18n complet, listes en puces texte "•")
----------------------------------------------------------------------

local function wkBulletsFromArgListText( listText )
	listText = t_trim( tostring( listText or "" ) )
	if listText == "" then
		return ""
	end

	local lines = {}
	for line in t_gsplit( listText, "\n", true ) do
		line = t_trim( tostring( line or "" ) )
		if line ~= "" then
			line = line:gsub( "^%*%s*", "" )
			line = t_trim( line )
			if line ~= "" then
				table_insert( lines, "• " .. line )
			end
		end
	end

	if #lines == 0 then
		return ""
	end

	return table_concat( lines, "\n" )
end

local function renderBreadcrumbJSONLD( pv, lang, args, maps )
	local rootName = L( lang, "text", "breadcrumb_debates_label" )
	local rootUrl = L( lang, "urls", "breadcrumb_debates" )

	local breadcrumb = {
		[ "@type" ] = "BreadcrumbList",
		itemListElement = {
			{
				[ "@type" ] = "ListItem",
				position = 1,
				item = { [ "@id" ] = rootUrl, name = rootName }
			},
			{
				[ "@type" ] = "ListItem",
				position = 2,
				item = { [ "@id" ] = pv.pageUrl, name = pv.rawTitle }
			}
		}
	}

	local graph = { breadcrumb }

	local includeFaq = not isProgress( lang, ( args or {} )[ "progress" ], "stub" )

	if includeFaq then
		local topic = t_trim( tostring( ( args or {} )[ "complete-topic" ] or "" ) )
		if topic == "" then
			topic = pv.rawTitle
		end

		local proListText = ( maps and maps.pro and maps.pro.list ) and tostring( maps.pro.list ) or ""
		local conListText = ( maps and maps.con and maps.con.list ) and tostring( maps.con.list ) or ""

		local proList = wkBulletsFromArgListText( proListText )
		local conList = wkBulletsFromArgListText( conListText )

		local titlePro = L( lang, "text", "args_pro_title" )
		local titleCon = L( lang, "text", "args_con_title" )

		local noPro = L( lang, "text", "none_pro_args_msg_short" )
		local noCon = L( lang, "text", "none_con_args_msg_short" )

		local learnMore = L( lang, "text", "learn_more_label" ) .. L( lang, "text", "site_title_text" )

		local anchorPro = L( lang, "text", "faq_anchor_pro" )
		local anchorCon = L( lang, "text", "faq_anchor_con" )

		local function mainAnswerText()
			local parts = {}
			table_insert( parts, titlePro )
			table_insert( parts, ( proList ~= "" and proList or noPro ) )
			table_insert( parts, titleCon )
			table_insert( parts, ( conList ~= "" and conList or noCon ) )
			table_insert( parts, learnMore )
			return table_concat( parts, "\n" )
		end

		local mainEntity = {}

		table_insert( mainEntity, {
			[ "@type" ] = "Question",
			name = pv.rawTitle,
			acceptedAnswer = {
				[ "@type" ] = "Answer",
				text = mainAnswerText(),
				url = pv.pageUrl
			}
		} )

		if proList ~= "" then
			local qFor = string_format( L( lang, "text", "faq_q_pro" ), " " .. topic )
			table_insert( mainEntity, {
				[ "@type" ] = "Question",
				name = qFor,
				acceptedAnswer = {
					[ "@type" ] = "Answer",
					text = proList .. "\n" .. learnMore,
					url = pv.pageUrl .. anchorPro
				}
			} )
		end

		if conList ~= "" then
			local qCon = string_format( L( lang, "text", "faq_q_con" ), " " .. topic )
			table_insert( mainEntity, {
				[ "@type" ] = "Question",
				name = qCon,
				acceptedAnswer = {
					[ "@type" ] = "Answer",
					text = conList .. "\n" .. learnMore,
					url = pv.pageUrl .. anchorCon
				}
			} )
		end

		table_insert( graph, {
			[ "@type" ] = "FAQPage",
			mainEntity = mainEntity
		} )
	end

	local data = {
		[ "@context" ] = "https://schema.org",
		[ "@graph" ] = graph
	}

	local json = t_jsonEncode( data )

	return tag(
		"htmltag",
		json,
		{
			tagname = "script",
			type = "application/ld+json",
			[ "class" ] = "navigation-not-searchable"
		}
	)
end

----------------------------------------------------------------------
--	Bandeaux : rendu natif + résolution via label i18n (C optimisée)
----------------------------------------------------------------------

local PROGRESS_BANNER_KEYS = {
	"progress_under_construction",
	"progress_draft",
}

local TITLE_WARNING_BANNER_KEYS = {
	"title_non_standard",
	"title_to_explain",
	"title_to_simplify",
}

local DEBATE_WARNING_BANNER_KEYS = {
	"debate_unbalanced",
	"debate_redundant",
	"debate_fanciful",
	"debate_sensitive",
	"outline_to_improve",
	"debate_generated_by_chatgpt",
}

local function bannerMsg( lang, key, ... )
	return WD_I18N.msg( "Debate.banners", lang, key, ... ) or ""
end

local function renderBannerByKey( pv, lang, key, cats )
	key = tostring( key or "" )
	if key == "" then
		return ""
	end

	local color	= bannerMsg( lang, key .. "_color" )
	local icon	= bannerMsg( lang, key .. "_icon" )
	local size	= bannerMsg( lang, key .. "_size" )
	local alt	= bannerMsg( lang, key .. "_alt" )
	local title	= bannerMsg( lang, key .. "_title", pv.rawTitle )
	local text	= bannerMsg( lang, key .. "_text", pv.rawTitle )

	if color == "" and icon == "" and title == "" and text == "" then
		return ""
	end

	local html = renderMetaWarningHtml{
		color	= color,
		icon	= icon,
		size	= size ~= "" and size or "40px",
		alt		= alt,
		title	= title,
		text		= text
	}

	local cat = bannerMsg( lang, key .. "_category" )
	if cats and cat and cat ~= "" then
		table_insert( cats, "[[Category:" .. cat .. "]]" )
	end

	return html
end

local function buildBannerLabelLookup( lang, keys )
	local lookup = {}
	for _, key in ipairs( keys or {} ) do
		local lab = t_trim( bannerMsg( lang, key .. "_label" ) )
		if lab ~= "" then
			lookup[ lab ] = key
		end
	end
	return lookup
end

local function resolveBannerKeyFromLookup( raw, lookup )
	raw = t_trim( tostring( raw or "" ) )
	if raw == "" then
		return ""
	end
	return ( lookup and lookup[ raw ] ) or ""
end

local function renderWarningBanners( args, pv, cats, lang )
	local av = args[ "progress" ]
	local out = {}

	local titleLookup	= buildBannerLabelLookup( lang, TITLE_WARNING_BANNER_KEYS )
	local debateLookup	= buildBannerLabelLookup( lang, DEBATE_WARNING_BANNER_KEYS )
	local progLookup	= buildBannerLabelLookup( lang, PROGRESS_BANNER_KEYS )

	local function renderBannersFromList( list, lookup )
		local html = {}
		for _, x in ipairs( splitCSV( list, "," ) ) do
			local key = resolveBannerKeyFromLookup( x, lookup )
			if key ~= "" then
				table_insert( html, renderBannerByKey( pv, lang, key, cats ) )
			end
		end
		return table_concat( html )
	end

	if args[ "title-warnings" ] then
		table_insert( out, renderBannersFromList( args[ "title-warnings" ], titleLookup ) )
	end

	if av and t_trim( av ) ~= "" then
		local key = resolveBannerKeyFromLookup( av, progLookup )
		if key ~= "" then
			table_insert( out, renderBannerByKey( pv, lang, key, cats ) )
		end
	end

	if isProgress( lang, av, "constructed" ) then
		table_insert( cats, "[[Category:" .. L( lang, "categories", "constructed" ) .. "]]" )
	elseif not isProgress( lang, av, "stub" ) and not isProgress( lang, av, "building" ) and not isProgress( lang, av, "constructed" ) then
		table_insert( cats, "[[Category:" .. L( lang, "categories", "progress_missing" ) .. "]]" )
	end

	if args[ "debate-warnings" ] then
		table_insert( out, renderBannersFromList( args[ "debate-warnings" ], debateLookup ) )
	end

	return table_concat( out )
end

----------------------------------------------------------------------
--	Carte des arguments (en-tête)
----------------------------------------------------------------------

local function renderArgumentMapTop( lang, maps )
	maps = maps or {}
	local pro = ( maps.pro and maps.pro.map ) or ""
	local con = ( maps.con and maps.con.map ) or ""

	local labelPour		= L( lang, "text", "arg_map_pro_label" )
	local labelContre	= L( lang, "text", "arg_map_con_label" )
	local msgPour		= L( lang, "text", "none_pro_args_msg_short" )
	local msgContre		= L( lang, "text", "none_con_args_msg_short" )

	local tbl = html_create( "table" )
		:attr( "id", "Argument_map" )
		:css( "color", "var(--color-emphasized,#101418)" )
		:css( "width", "100%" )
		:attr( "align", "center" )

	local tr  = html_create( "tr" )
	local tdP = html_create( "td" ):addClass( "is-pro" ):css( "width", "50%" ):css( "vertical-align", "top" ):css( "padding", "5px 1.5% 0" )
	local tdC = html_create( "td" ):addClass( "is-con" ):css( "width", "50%" ):css( "vertical-align", "top" ):css( "padding", "5px 0 0" )

	tdP:wikitext( '<div style="font-size:101%; font-weight:bold; margin-bottom: 0.5em;">' .. labelPour .. '</div>' )
	tdP:wikitext( pro ~= "" and pro or '<div class="carte-vide navigation-not-searchable">' .. msgPour .. '</div>' )

	tdC:wikitext( '<div style="font-size:101%; font-weight:bold; margin-bottom: 0.5em;">' .. labelContre .. '</div>' )
	tdC:wikitext( con ~= "" and con or '<div class="carte-vide navigation-not-searchable">' .. msgContre .. '</div>' )

	tr:node( tdP ):node( tdC )
	tbl:node( tr )
	return tostring( tbl )
end

----------------------------------------------------------------------
--	Sections
----------------------------------------------------------------------

local function renderIntroduction( args, pv, lang )
	local h2 =
		'<h2 class="section-modifiable">'
		.. '<span style="margin-right: 0.5em;">[[File: ' .. L( lang, "files", "intro" ) .. ' | 22px | link= | alt=' .. L( lang, "text", "understand_debate_title" ) .. ']]</span>'
		.. L( lang, "text", "understand_debate_title" )
		.. '<span class="modifier-section navigation-not-searchable noprint">'
		.. addDataLink(
			L( lang, "forms", "intro_edit_title" ),
			pv.rawTitle,
			L( lang, "text", "edit_label" ),
			L( lang, "text", "intro_edit_tt" )
		)
		.. '</span></h2>'

	local body
	if args[ "introduction" ] and t_trim( args[ "introduction" ] ) ~= "" then
		body = args[ "introduction" ]
	else
		local cat = "[[Category:" .. L( lang, "categories", "intro_missing" ) .. "]]"
		body = '<div class="aucun-contenu navigation-not-searchable" style="margin-bottom: 1em;">' .. L( lang, "text", "none_intro_msg" ) .. '</div>' .. cat
	end
	return ( h2 .. body ):gsub( "%s*$", "" )
end

local function renderWikipediaLinks( args, pv, lang )
	local raw = args[ "wikipedia-articles" ]
	raw = ( type( raw ) == "string" ) and t_trim( raw ) or ""

	local wrap = html_create( "div" ):addClass( "bandeau-section hover-top navigation-not-searchable" )
	local tbl  = html_create( "table" ):css( "background-color", "transparent" )
	local tr   = html_create( "tr" )
	local tdIcon = html_create( "td" )
		:wikitext( '<div style="text-align:center; margin-right: 0.5em;">[[File: ' .. L( lang, "files", "wikipedia" ) .. ' | 18px | link=]]</div>' )

	tr:node( tdIcon )

	local tdMain = html_create( "td" ):addClass( "width-100" ):css( "line-height", "1.5" )
	local hasPages = false

	if raw ~= "" then
		local expanded = F:preprocess( raw )
		local pages = wkParseWikipediaMarkers( expanded )

		if #pages > 0 then
			hasPages = true
			local items = {}

			for _, page in ipairs( pages ) do
				local url = wkWikipediaUrl( page, lang )
				items[ #items + 1 ] = '<span class="hover-wikipedia">[' .. url .. ' ' .. page .. ']</span>'
			end

			tdMain:wikitext(
				string_format(
					L( lang, "text", "wikipedia_seealso_msg" ),
					joinArticles( items, lang )
				)
			)
		else
			tdMain:addClass( "aucun-contenu" )
				:cssText( "line-height: 1.5; font-style: italic;" )
				:wikitext( L( lang, "text", "none_wikipedia_msg" ) )
		end
	else
		tdMain:addClass( "aucun-contenu" )
			:cssText( "line-height: 1.5; font-style: italic;" )
			:wikitext( L( lang, "text", "none_wikipedia_msg" ) )
	end

	local tdBtn = html_create( "td" )
	tdBtn:wikitext(
		'<span class="modifier-section navigation-not-searchable noprint">'
		.. addDataLink(
			L( lang, "forms", "wiki_seealso_edit_title" ),
			pv.rawTitle,
			L( lang, "text", "edit_label" ),
			L( lang, "text", "wikipedia_edit_tt" )
		)
		.. '</span>'
	)

	tr:node( tdMain ):node( tdBtn )
	tbl:node( tr )
	wrap:node( tbl )

	if raw == "" or not hasPages then
		return tostring( wrap ) .. "[[Category:" .. L( lang, "categories", "wikipedia_missing" ) .. "]]"
	end

	return tostring( wrap )
end

local function renderQuestionsFAQ( sujetComplet, sens, lang )
	local topic = ( sujetComplet and sujetComplet ~= "" ) and sujetComplet or ""
	local isCon = ( sens == "con" )
	local q
	if isCon then
		q = string_format( L( lang, "text", "faq_q_con" ), topic )
	else
		q = string_format( L( lang, "text", "faq_q_pro" ), topic )
	end
	return string_format( L( lang, "text", "faq_wrapper_text" ), q )
end

local function renderArgsList( args, pv, lang, sens, maps, cats )
	local isCon = ( sens == "con" )

	local hIconCon = '[[File: ' .. L( lang, "files", "arg_con" ) .. ' | 22px | link= | alt=' .. L( lang, "text", "args_con_title" ) .. ' | class=mw-no-invert]]'
	local hIconPro = '[[File: ' .. L( lang, "files", "arg_pro" ) .. ' | 22px | link= | alt=' .. L( lang, "text", "args_pro_title" ) .. ']]'
	local title = isCon and L( lang, "text", "args_con_title" ) or L( lang, "text", "args_pro_title" )

	local btnForm = addDataLink(
		isCon and L( lang, "forms", "args_con_edit_title" ) or L( lang, "forms", "args_pro_edit_title" ),
		pv.rawTitle,
		L( lang, "text", "edit_label" ),
		isCon and L( lang, "text", "args_con_edit_tt" ) or L( lang, "text", "args_pro_edit_tt" )
	)

	local out = {}
	push( out, listBreaker() )
	push( out,
		'<h2 class="section-modifiable"><span style="margin-right: 0.5em;">'
		.. ( isCon and hIconCon or hIconPro )
		.. '</span>'
		.. title
		.. '<span class="modifier-section navigation-not-searchable noprint">'
		.. btnForm
		.. '</span></h2>'
	)

	push( out, renderQuestionsFAQ( args[ "complete-topic" ], sens, lang ) )

	local ul = html_create( "ul" )
		:addClass( "argument-list" )
		:addClass( isCon and "is-con" or "is-pro" )

	local mode = maps and maps._mode and ( isCon and maps._mode.con or maps._mode.pro ) or "none"
	local data = maps and ( isCon and maps.con or maps.pro ) or nil
	local items = data and data.items or {}

	if ( mode == "data" ) and #items > 0 then
		local lis = {}
		local anyTitleWarnings = false

		for i, it in ipairs( items ) do
			if it.warnings and t_trim( it.warnings ) ~= "" then
				anyTitleWarnings = true
			end
			table_insert( lis, wkRenderArgumentLi( pv, lang, it, isCon and "con" or "pro", i ) )
		end

		ul:wikitext( table_concat( lis, "\n" ) )

		if anyTitleWarnings then
			table_insert( cats, "[[Category:" .. L( lang, "categories", "refs_to_review" ) .. "]]" )
		end
	else
		local cat = isCon and ( "[[Category:" .. L( lang, "categories", "con_missing" ) .. "]]" ) or ( "[[Category:" .. L( lang, "categories", "pro_missing" ) .. "]]" )
		local msg = isCon and L( lang, "text", "none_con_args_msg" ) or L( lang, "text", "none_pro_args_msg" )
		table_insert( out, '<div class="aucun-argument navigation-not-searchable">' .. msg .. '</div>' .. cat )
	end

	push( out, tostring( ul ) )

	local sideLabel = isCon and L( lang, "text", "side_con_word" ) or L( lang, "text", "side_pro_word" )
	local label = string_format( L( lang, "text", "args_add_label" ), sideLabel )

	local tooltip = string_format( L( lang, "text", "arg_add_tt" ), sideLabel, pv.rawTitle )

	local baseField = L( lang, "text", "arg_new_title_field_base" )

	local rqForm		= L( lang, "forms", "new_arg_title" )
	local rqTypeValue	= isCon and L( lang, "forms", "new_arg_con_type_label" ) or L( lang, "forms", "new_arg_pro_type_label" )

	local rqQuery = {}
	rqQuery[ baseField .. "[type]" ]	= rqTypeValue
	rqQuery[ baseField .. "[ID]" ]		= pv.pageId
	rqQuery[ "_run" ]					= "1"

	push( out,
		'<div class="bouton-ajouter wk-btn mw-ui-button navigation-not-searchable noprint">'
		.. wkRunQueryHtmlTagButton( rqForm, label, tooltip, rqQuery )
		.. '</div>'
	)

	return table_concat( out )
end

----------------------------------------------------------------------
--	Sections de références
----------------------------------------------------------------------

local REF_SEP		= "⟭"
local REF_FIELD_SEP	= "⟬"

local function wkParseBiblioMarkers( raw )
	raw = tostring( raw or "" )
	raw = t_trim( raw )
	if raw == "" then
		return {}
	end

	if not raw:find( REF_FIELD_SEP, 1, true ) and not raw:find( REF_SEP, 1, true ) then
		return {}
	end

	local items = {}

	for block in t_gsplit( raw, REF_SEP, true ) do
		block = t_trim( block )
		if block ~= "" then
			local f = {}
			for part in t_gsplit( block, REF_FIELD_SEP, true ) do
				f[ #f + 1 ] = t_trim( tostring( part or "" ) )
			end

			for i = #f + 1, 12 do
				f[ i ] = ""
			end

			items[ #items + 1 ] = {
				authors		= f[ 1 ],
				article		= f[ 2 ],
				work		= f[ 3 ],
				volume		= f[ 4 ],
				number		= f[ 5 ],
				location	= f[ 6 ],
				page		= f[ 7 ],
				publisher	= f[ 8 ],
				place		= f[ 9 ],
				date		= f[ 10 ],
				link		= f[ 11 ],
				warnings	= f[ 12 ]
			}
		end
	end

	return items
end

local function wkRenderOneBiblioLi( item, lang, categories, smwPending, bannerCache )
	local out = {}

	local authors = t_trim( item.authors or "" )
	if authors == "" then
		authors = "Auteur non renseigné"
		table_insert( categories, "[[Catégorie:Références à revoir]]" )
	end
	out[ #out + 1 ] = authors

	local article = t_trim( item.article or "" )
	local work = t_trim( item.work or "" )
	local link = t_trim( item.link or "" )

	if article ~= "" then
		smwQueueAdd( smwPending, smwProp( lang, "article_name" ), article, true )
	end
	if work ~= "" then
		smwQueueAdd( smwPending, smwProp( lang, "work_name" ), work, true )
	end

	if article ~= "" then
		if link ~= "" then
			out[ #out + 1 ] = ", « [" .. link .. " " .. article .. "] »"
		else
			out[ #out + 1 ] = ", « " .. article .. " »"
		end
	end

	if work ~= "" then
		if article ~= "" then
			out[ #out + 1 ] = ", ''" .. work .. "''"
		else
			if link ~= "" then
				out[ #out + 1 ] = ", ''[" .. link .. " " .. work .. "]''"
			else
				out[ #out + 1 ] = ", ''" .. work .. "''"
			end
		end
	end

	if work ~= "" then
		local volume = t_trim( item.volume or "" )
		if volume ~= "" then
			out[ #out + 1 ] = ", " .. volume
		end

		local number = t_trim( item.number or "" )
		if number ~= "" then
			out[ #out + 1 ] = ", n°" .. number
		end
	end

	local location = t_trim( item.location or "" )
	if location ~= "" then
		out[ #out + 1 ] = ", " .. location
	end

	local page = t_trim( item.page or "" )
	if page ~= "" then
		out[ #out + 1 ] = ", p." .. page
	end

	local publisher = t_trim( item.publisher or "" )
	if publisher ~= "" then
		smwQueueAdd( smwPending, smwProp( lang, "publishing_house" ), publisher, true )
		out[ #out + 1 ] = ", " .. publisher
	end

	local place = t_trim( item.place or "" )
	if place ~= "" then
		smwQueueAdd( smwPending, smwProp( lang, "place_of_publication" ), place, true )
		out[ #out + 1 ] = ", " .. place
	end

	local date = t_trim( item.date or "" )
	if date ~= "" then
		out[ #out + 1 ] = ", " .. date
	end

	local warnings = t_trim( item.warnings or "" )
	if warnings ~= "" then
		for x in t_gsplit( warnings, ",", true ) do
			x = t_trim( x )
			if x ~= "" then
				out[ #out + 1 ] = " " .. getBanner( x, bannerCache )
			end
		end
	end

	return "<li>" .. table_concat( out ) .. "</li>"
end

local function wkRenderBiblioListFromArg( raw, lang, categories, smwPending, bannerCache )
	local items = wkParseBiblioMarkers( raw )
	if #items == 0 then
		return ""
	end

	local lis = {}
	for _, item in ipairs( items ) do
		lis[ #lis + 1 ] = wkRenderOneBiblioLi( item, lang, categories, smwPending, bannerCache )
	end
	return table_concat( lis, "\n" )
end

local function wkParseWeblioMarkers( raw )
	raw = tostring( raw or "" )
	raw = t_trim( raw )
	if raw == "" then
		return {}
	end

	if not raw:find( "⟭", 1, true ) and not raw:find( "⟬", 1, true ) then
		return {}
	end

	local items = {}

	for block in t_gsplit( raw, "⟭", true ) do
		block = t_trim( block )
		if block ~= "" then
			local f = {}
			for part in t_gsplit( block, "⟬", true ) do
				f[ #f + 1 ] = t_trim( tostring( part or "" ) )
			end

			for i = #f + 1, 6 do
				f[ i ] = ""
			end

			items[ #items + 1 ] = {
				page		= f[ 1 ],
				site		= f[ 2 ],
				link		= f[ 3 ],
				authors		= f[ 4 ],
				date		= f[ 5 ],
				warnings	= f[ 6 ]
			}
		end
	end

	return items
end

local function wkRenderOneWeblioLi( item, lang, categories, smwPending, bannerCache, side )
	local out = {}

	local page		= t_trim( item.page or "" )
	local site		= t_trim( item.site or "" )
	local link		= t_trim( item.link or "" )
	local authors	= t_trim( item.authors or "" )
	local date		= t_trim( item.date or "" )

	if page ~= "" then
		smwQueueAdd( smwPending, smwProp( lang, "article_name" ), page, true )
	end
	if site ~= "" then
		smwQueueAdd( smwPending, smwProp( lang, "website_name" ), site, true )
	end

	do
		local label = ""
		if page ~= "" then
			label = page
		elseif site ~= "" then
			label = site
		end

		if label == "" then
			return ""
		end

		if link ~= "" then
			out[ #out + 1 ] = "[" .. link .. " " .. label .. "]"
		else
			out[ #out + 1 ] = label
		end
	end

	if site ~= "" and page ~= "" then
		out[ #out + 1 ] = ", ''" .. site .. "''"
	end

	if authors ~= "" then
		out[ #out + 1 ] = ", " .. authors
	end

	if date ~= "" then
		out[ #out + 1 ] = ", " .. date
	end

	--	Bandeaux d’avertissement
	local warnings = t_trim( item.warnings or "" )
	if warnings ~= "" then
		for x in t_gsplit( warnings, ",", true ) do
			x = t_trim( x )
			if x ~= "" then
				out[ #out + 1 ] = " " .. getBanner( x, bannerCache )
			end
		end
	end

	--	SMW auteurs (par côté)
	if authors ~= "" then
		local propKey = "author"
		if side == "pro" then
			propKey = "author_pro"
		elseif side == "con" then
			propKey = "author_con"
		end

		for a in t_gsplit( authors, ",", true ) do
			a = t_trim( a )
			if a ~= "" then
				smwQueueAdd( smwPending, smwProp( lang, propKey ), a, true )
			end
		end
	end

	return "<li>" .. table_concat( out ) .. "</li>"
end

local function wkRenderWeblioListFromArg( raw, lang, categories, smwPending, bannerCache, side )
	local items = wkParseWeblioMarkers( raw )
	if #items == 0 then
		return ""
	end

	local lis = {}
	for _, item in ipairs( items ) do
		local li = wkRenderOneWeblioLi( item, lang, categories, smwPending, bannerCache, side )
		if li ~= "" then
			lis[ #lis + 1 ] = li
		end
	end

	return table_concat( lis, "\n" )
end

local function wkParseVideoMarkers( raw )
	raw = tostring( raw or "" )
	raw = t_trim( raw )
	if raw == "" then
		return {}
	end

	if not raw:find( "⟭", 1, true ) and not raw:find( "⟬", 1, true ) then
		return {}
	end

	local items = {}

	for block in t_gsplit( raw, "⟭", true ) do
		block = t_trim( block )
		if block ~= "" then
			local f = {}
			for part in t_gsplit( block, "⟬", true ) do
				f[ #f + 1 ] = t_trim( tostring( part or "" ) )
			end

			for i = #f + 1, 4 do
				f[ i ] = ""
			end

			items[ #items + 1 ] = {
				title		= f[ 1 ],
				link		= f[ 2 ],
				authors		= f[ 3 ],
				warnings	= f[ 4 ]
			}
		end
	end

	return items
end

local function wkRenderOneVideoLi( item, lang, categories, smwPending, bannerCache )
	local title = t_trim( item.title or "" )
	if title == "" then
		return ""
	end

	local link = t_trim( item.link or "" )
	local authors = t_trim( item.authors or "" )

	local out = {}

	if link ~= "" then
		out[ #out + 1 ] = "[" .. link .. " " .. title .. "]"
	else
		out[ #out + 1 ] = title
	end

	if authors ~= "" then
		out[ #out + 1 ] = ", " .. authors
	end

	local warnings = t_trim( item.warnings or "" )
	if warnings ~= "" then
		for x in t_gsplit( warnings, ",", true ) do
			x = t_trim( x )
			if x ~= "" then
				out[ #out + 1 ] = " " .. getBanner( x, bannerCache )
			end
		end
	end

	return "<li>" .. table_concat( out ) .. "</li>"
end

local function wkRenderVideoListFromArg( raw, lang, categories, smwPending, bannerCache, side )
	local items = wkParseVideoMarkers( raw )
	if #items == 0 then
		return ""
	end

	local lis = {}
	for _, item in ipairs( items ) do
		local li = wkRenderOneVideoLi( item, lang, categories, smwPending, bannerCache )
		if li ~= "" then
			lis[ #lis + 1 ] = li
		end
	end

	return table_concat( lis, "\n" )
end

local function renderRefBlock( pageVars, lang, titleIcon, titleText, formKey, value )
	local out = {}
	table_insert( out,
		'<div class="titre-references navigation-not-searchable">'
		.. titleIcon .. titleText
		.. '<span class="modifier-rubrique flottant-droite navigation-not-searchable noprint">'
		.. addDataLink(
			L( lang, "forms", formKey ),
			pageVars.rawTitle,
			L( lang, "text", "edit_label" ),
			L( lang, "text", "section_edit_tt" )
		)
		.. '</span></div>'
	)
	if value and t_trim( value ) ~= "" then
		table_insert( out, "<ul>" .. value .. "</ul>" )
	else
		table_insert( out, '<div class="aucune-reference navigation-not-searchable">' .. L( lang, "text", "none_references_msg" ) .. '</div>' )
	end
	return table_concat( out )
end

local function renderReferences( args, pageVars, lang, categories, smwPending )
	local wrap = {}
	local bannerCache = {}

	table_insert( wrap, '<h2><span style="margin-right: 0.5em;">[[File: ' .. L( lang, "files", "further" ) .. ' | 20px | link= | alt=' .. L( lang, "text", "further_reading_title" ) .. ']]</span>' .. L( lang, "text", "further_reading_title" ) .. '</h2>' )

	--	Bibliography
	do
		local h3 = '<h3 id="Bibliography" class="fr-collapsible-toggle wk-icon wk-carret">'
			.. '[[File: ' .. L( lang, "files", "biblio" ) .. ' | 13px | middle | link= | alt=' .. L( lang, "text", "bibliography_title" ) .. ' | class=sub pictogramme-h3]]'
			.. L( lang, "text", "bibliography_title" ) .. '</h3>'

		local content = {}

		local proRaw = args[ "pro-bibliography" ] or ""
		if proRaw ~= "" then
			proRaw = F:preprocess( proRaw )
			proRaw = wkRenderBiblioListFromArg( proRaw, lang, categories, smwPending, bannerCache )
		end

		local conRaw = args[ "con-bibliography" ] or ""
		if conRaw ~= "" then
			conRaw = F:preprocess( conRaw )
			conRaw = wkRenderBiblioListFromArg( conRaw, lang, categories, smwPending, bannerCache )
		end

		local neuRaw = args[ "bibliography" ] or ""
		if neuRaw ~= "" then
			neuRaw = F:preprocess( neuRaw )
			neuRaw = wkRenderBiblioListFromArg( neuRaw, lang, categories, smwPending, bannerCache )
		end

		table_insert( content, renderRefBlock( pageVars, lang,
			'[[File: ' .. L( lang, "files", "arg_pro" ) .. ' | 17px | link= | alt=' .. escapeAttr( L( lang, "text", "side_pro_alt" ) ) .. ' | class=pictogramme-h3 mw-no-invert]]',
			L( lang, "text", "side_pro_title" ),
			"biblio_pro_title",
			proRaw
		) )

		table_insert( content, renderRefBlock( pageVars, lang,
			'[[File: ' .. L( lang, "files", "arg_con" ) .. ' | 17px | link= | alt=' .. escapeAttr( L( lang, "text", "side_con_alt" ) ) .. ' | class=pictogramme-h3 mw-no-invert]]',
			L( lang, "text", "side_con_title" ),
			"biblio_con_title",
			conRaw
		) )

		table_insert( content, renderRefBlock( pageVars, lang,
			'[[File: ' .. L( lang, "files", "arg_neutral" ) .. ' | 17px | text-bottom | link= | alt=' .. escapeAttr( L( lang, "text", "side_neutral_alt" ) ) .. ' | class=pictogramme-h3 mw-no-invert]]',
			L( lang, "text", "side_neutral_title" ),
			"biblio_neutral_title",
			neuRaw
		) )

		table_insert( wrap, '<div class="fr-collapsible fr-collapsed">' .. h3 .. '<div class="fr-collapsible-content contenu-references">' .. table_concat( content ) .. '</div></div>' )
	end

	--	Webliography
	do
		local h3 = '<h3 id="Webliography" class="fr-collapsible-toggle wk-icon wk-carret">'
			.. '[[File: ' .. L( lang, "files", "webliography" ) .. ' | 15px | link= | alt=' .. L( lang, "text", "webliography_title" ) .. ' | class=pictogramme-h3]]'
			.. L( lang, "text", "webliography_title" ) .. '</h3>'

		local content = {}

		local proRaw = args[ "pro-webliography" ] or ""
		if proRaw ~= "" then
			proRaw = F:preprocess( proRaw )
			proRaw = wkRenderWeblioListFromArg( proRaw, lang, categories, smwPending, bannerCache, "pro" )
		end

		local conRaw = args[ "con-webliography" ] or ""
		if conRaw ~= "" then
			conRaw = F:preprocess( conRaw )
			conRaw = wkRenderWeblioListFromArg( conRaw, lang, categories, smwPending, bannerCache, "con" )
		end

		local neuRaw = args[ "webliography" ] or ""
		if neuRaw ~= "" then
			neuRaw = F:preprocess( neuRaw )
			neuRaw = wkRenderWeblioListFromArg( neuRaw, lang, categories, smwPending, bannerCache, "neutral" )
		end

		table_insert( content, renderRefBlock( pageVars, lang,
			'[[File: ' .. L( lang, "files", "arg_pro" ) .. ' | 17px | link= | alt=' .. escapeAttr( L( lang, "text", "side_pro_alt" ) ) .. ' | class=pictogramme-h3 mw-no-invert]]',
			L( lang, "text", "side_pro_title" ),
			"webliography_pro_title",
			proRaw
		) )

		table_insert( content, renderRefBlock( pageVars, lang,
			'[[File: ' .. L( lang, "files", "arg_con" ) .. ' | 17px | link= | alt=' .. escapeAttr( L( lang, "text", "side_con_alt" ) ) .. ' | class=pictogramme-h3 mw-no-invert]]',
			L( lang, "text", "side_con_title" ),
			"webliography_con_title",
			conRaw
		) )

		table_insert( content, renderRefBlock( pageVars, lang,
			'[[File: ' .. L( lang, "files", "arg_neutral" ) .. ' | 17px | text-bottom | link= | alt=' .. escapeAttr( L( lang, "text", "side_neutral_alt" ) ) .. ' | class=pictogramme-h3 mw-no-invert]]',
			L( lang, "text", "side_neutral_title" ),
			"webliography_neutral_title",
			neuRaw
		) )

		table_insert( wrap, '<div class="fr-collapsible fr-collapsed">' .. h3 .. '<div class="fr-collapsible-content contenu-references">' .. table_concat( content ) .. '</div></div>' )
	end

	--	Videography
	do
		local h3 = '<h3 id="Videography" class="fr-collapsible-toggle wk-icon wk-carret">'
			.. '[[File: ' .. L( lang, "files", "videography" ) .. ' | 17px | link= | alt=' .. L( lang, "text", "videography_title" ) .. ' | class=pictogramme-h3]]'
			.. L( lang, "text", "videography_title" ) .. '</h3>'

		local content = {}

		local proRaw = args[ "pro-videography" ] or ""
		if proRaw ~= "" then
			proRaw = F:preprocess( proRaw )
			proRaw = wkRenderVideoListFromArg( proRaw, lang, categories, smwPending, bannerCache, "pro" )
		end

		local conRaw = args[ "con-videography" ] or ""
		if conRaw ~= "" then
			conRaw = F:preprocess( conRaw )
			conRaw = wkRenderVideoListFromArg( conRaw, lang, categories, smwPending, bannerCache, "con" )
		end

		local neuRaw = args[ "videography" ] or ""
		if neuRaw ~= "" then
			neuRaw = F:preprocess( neuRaw )
			neuRaw = wkRenderVideoListFromArg( neuRaw, lang, categories, smwPending, bannerCache, "neutral" )
		end

		table_insert( content, renderRefBlock( pageVars, lang,
			'[[File: ' .. L( lang, "files", "arg_pro" ) .. ' | 17px | link= | alt=' .. escapeAttr( L( lang, "text", "side_pro_alt" ) ) .. ' | class=pictogramme-h3 mw-no-invert]]',
			L( lang, "text", "side_pro_title" ),
			"videography_pro_title",
			proRaw
		) )

		table_insert( content, renderRefBlock( pageVars, lang,
			'[[File: ' .. L( lang, "files", "arg_con" ) .. ' | 17px | link= | alt=' .. escapeAttr( L( lang, "text", "side_con_alt" ) ) .. ' | class=pictogramme-h3 mw-no-invert]]',
			L( lang, "text", "side_con_title" ),
			"videography_con_title",
			conRaw
		) )

		table_insert( content, renderRefBlock( pageVars, lang,
			'[[File: ' .. L( lang, "files", "arg_neutral" ) .. ' | 17px | text-bottom | link= | alt=' .. escapeAttr( L( lang, "text", "side_neutral_alt" ) ) .. ' | class=pictogramme-h3 mw-no-invert]]',
			L( lang, "text", "side_neutral_title" ),
			"videography_neutral_title",
			neuRaw
		) )

		table_insert( wrap, '<div class="fr-collapsible fr-collapsed">' .. h3 .. '<div class="fr-collapsible-content contenu-references">' .. table_concat( content ) .. '</div></div>' )
	end

	return table_concat( wrap )
end

----------------------------------------------------------------------
--	Champs divers (mots-clés, rubriques, connexes, interlangue)
----------------------------------------------------------------------

local function renderKeywords( args, pv, cats, lang )
	local out = {}
	table_insert( out, '<div style="font-size: 95%; margin-top: 1em;">' .. L( lang, "text", "keywords_label_text" ) )

	if args[ "keywords" ] and t_trim( args[ "keywords" ] ) ~= "" then
		local rendered = {}

		local formName = L( lang, "forms", "search_by_keywords_title" )
		local fieldLabel = formName
		local fieldName = L( lang, "params", "keywords_field" )
		local tt = L( lang, "text", "search_by_keyword_tt" )

		local typeField = L( lang, "params", "search_type_field" )
		local typeValue = L( lang, "params", "search_type_value" )

		local extra = {
			[ fieldLabel .. "[" .. typeField .. "]" ] = typeValue
		}

		for _, k in ipairs( splitCSV( args[ "keywords" ], "," ) ) do
			k = t_trim( tostring( k or "" ) )
			if k ~= "" then
				local chip = runQueryLink( formName, fieldLabel, fieldName, k, k, tt, extra )
				if not chip or chip == "" then
					chip = k
				end
				if chip ~= "" then
					table_insert( rendered, chip )
				end
			end
		end

		if #rendered > 0 then
			table_insert( out, table_concat( rendered, ", " ) )
		else
			table_insert( out, L( lang, "text", "none_label" ) )
			table_insert( cats, "[[Category:" .. L( lang, "categories", "keywords_missing" ) .. "]]" )
		end
	else
		table_insert( out, L( lang, "text", "none_label" ) )
		table_insert( cats, "[[Category:" .. L( lang, "categories", "keywords_missing" ) .. "]]" )
	end

	table_insert( out,
		'<span class="modifier-rubrique navigation-not-searchable noprint">'
		.. addDataLink(
			L( lang, "forms", "keywords_edit_title" ),
			pv.rawTitle,
			L( lang, "text", "edit_label" ),
			L( lang, "text", "keywords_edit_tt" )
		)
		.. '</span><span style="display: none;">.</span></div>'
	)

	return table_concat( out )
end

local function renderConnexes( args, pv, cats, lang )
	local h2 =
		'<h2 class="section-modifiable">'
		.. '<span style="margin-right: 0.5em;">[[File: ' .. L( lang, "files", "related" ) .. ' | 20px | link= | alt=' .. L( lang, "text", "related_debates_title" ) .. ']]</span>'
		.. L( lang, "text", "related_debates_title" )
		.. '<span class="modifier-section navigation-not-searchable noprint">'
		.. addDataLink(
			L( lang, "forms", "related_edit_title" ),
			pv.rawTitle,
			L( lang, "text", "edit_label" ),
			L( lang, "text", "related_edit_tt" )
		)
		.. '</span></h2>'

	if args[ "related-debates" ] and t_trim( args[ "related-debates" ] ) ~= "" then
		return h2 .. '<ul class="hover-top searchaux">' .. args[ "related-debates" ] .. '</ul>'
	end

	table_insert( cats, "[[Category:" .. L( lang, "categories", "related_missing" ) .. "]]" )
	return h2 .. '<div class="aucun-contenu navigation-not-searchable">' .. L( lang, "text", "none_related_msg" ) .. '</div>'
end

local function renderRubriques( args, cats, lang )
	if args[ "sections" ] and t_trim( args[ "sections" ] ) ~= "" then
		local out = {}
		for _, r in ipairs( splitCSV( args[ "sections" ], "," ) ) do
			table_insert( out, "[[Category:" .. r .. "]]" )
		end
		return table_concat( out )
	end
	return "[[Category:" .. L( lang, "categories", "sections_missing" ) .. "]]"
end

----------------------------------------------------------------------
--	SMW (sans legacy)
----------------------------------------------------------------------

local function setSemanticData( args, pv, lang, maps, smwPending )
	maps = maps or {}
	local pro = maps.pro or {}
	local con = maps.con or {}

	local pourPages = {}
	local contrePages = {}

	for _, it in ipairs( pro.items or {} ) do
		if it.page and it.page ~= "" then
			table_insert( pourPages, it.page )
		end
	end

	for _, it in ipairs( con.items or {} ) do
		if it.page and it.page ~= "" then
			table_insert( contrePages, it.page )
		end
	end

	local props = {}

	local pourSurvol = t_trim( pro.mapHover or "" )
	local contreSurvol = t_trim( con.mapHover or "" )

	if pourSurvol == "" then
		pourSurvol = '<div class="carte-vide">' .. L( lang, "text", "none_pro_args_msg_short" ) .. '</div>'
	end
	if contreSurvol == "" then
		contreSurvol = '<div class="carte-vide">' .. L( lang, "text", "none_con_args_msg_short" ) .. '</div>'
	end

	local listePour = t_trim( pro.argsVar or "" )
	local listeContre = t_trim( con.argsVar or "" )

	props[ smwProp( lang, "debate_name" ) ]		= pv.rawTitle
	props[ smwProp( lang, "debate_number" ) ]	= pv.pageId
	props[ smwProp( lang, "breadcrumb" ) ]		= pv.rawTitle
	props[ smwProp( lang, "pro_list" ) ]		= listePour
	props[ smwProp( lang, "con_list" ) ]		= listeContre

	props[ smwProp( lang, "arg_map" ) ] =
		'<table style="background-color:transparent; width: 100%; margin: 0em 0.5em 0.15em 0;" class="navigation-not-searchable">'
		.. '<tr><th style="text-align:left;">' .. L( lang, "text", "arg_map_pro_label" ) .. '</th>'
		.. '<th style="text-align:left; padding-left: 1em;">' .. L( lang, "text", "arg_map_con_label" ) .. '</th></tr>'
		.. '<tr class="is-pro" style="vertical-align:top;"><td>' .. pourSurvol .. '</td>'
		.. '<td class="is-con" style="padding-left: 1em;">' .. contreSurvol .. '</td></tr></table>'

	if #pourPages > 0 then
		props[ smwProp( lang, "arg_pro" ) ] = pourPages
	end

	if #contrePages > 0 then
		props[ smwProp( lang, "arg_con" ) ] = contrePages
	end

	if smwPending then
		smwQueueMerge( props, smwPending )
	end

	smwSetSafe( props )
end

----------------------------------------------------------------------
--	Rendu principal
----------------------------------------------------------------------

function p.render( frame )
	for k in pairs( SEO_CACHE ) do
		SEO_CACHE[ k ] = nil
	end

	local args	= getArgs( frame )
	local lang	= detectLang( args )
	local pv	= computePageVars()
	local pieces, cats = {}, {}
	local smwPending = {}

	smwQueueAddFromCSV(
		smwPending,
		smwProp( lang, "keyword" ),
		args[ "keywords" ]
	)

	local maps = computeArgumentMapsFromArgs( args, lang )

	table_insert( cats, "[[Category:" .. L( lang, "categories", "debates" ) .. "]]" )

	if args[ "topic" ] and t_trim( args[ "topic" ] ) ~= "" then
		smwQueueAdd( smwPending, smwProp( lang, "debate_subject" ), args[ "topic" ], false )
	else
		table_insert( cats, "[[Category:" .. L( lang, "categories", "topic_missing" ) .. "]]" )
	end

	if args[ "complete-topic" ] and t_trim( args[ "complete-topic" ] ) ~= "" then
		table_insert( pieces, F:preprocess( "{{SHORTDESC: " .. string_format( L( lang, "text", "shortdesc_topic" ), args[ "complete-topic" ] ) .. "}}" ) )
	else
		table_insert( cats, "[[Category:" .. L( lang, "categories", "topic_complete_missing" ) .. "]]" )
		table_insert( pieces, F:preprocess( "{{SHORTDESC: " .. L( lang, "text", "shortdesc_plain" ) .. "}}" ) )
	end

	table_insert( pieces, renderBreadcrumbJSONLD( pv, lang, args, maps ) )

	table_insert( pieces, renderArgumentMapTop( lang, maps ) )
	table_insert( pieces, renderKeywords( args, pv, cats, lang ) )

	local btnForm = addDataLink(
		L( lang, "forms", "banners_edit_title" ),
		pv.rawTitle,
		L( lang, "text", "edit_label" ),
		L( lang, "text", "debate_banners_edit_tt" )
	)
	table_insert( pieces, '<h2 class="section-modifiable"><span style="margin-right: 0.5em; position: relative;">[[File: ' .. L( lang, "files", "banner" ) .. ' | 14px | link= | alt=' .. L( lang, "text", "warnings_title" ) .. ']]</span>' .. L( lang, "text", "warnings_title" ) ..  '<span class="modifier-section navigation-not-searchable noprint">' .. btnForm .. '</span></h2>' )
	table_insert( pieces, renderMainBanner( F, pv, lang ) )
	table_insert( pieces, renderWarningBanners( args, pv, cats, lang ) )

	if not isProgress( lang, args[ "progress" ], "stub" ) then
		local target = L( lang, "text", "self_eval_ns_label" ) .. ":" .. pv.rawTitle .. " (ID)"

		local ns			= L( lang, "text", "self_eval_ns_label" )
		local f_debat	= L( lang, "text", "self_eval_debate_field" )

		local box = {}
		table_insert( box, '<h2 class="navigation-not-searchable noprint" style="margin-bottom: 0;"><span style="margin-right: 0.5em; position: relative; bottom: 2px;">[[File: ' .. L( lang, "files", "selftest" ) .. ' | 17px | link= | alt=' .. L( lang, "text", "self_eval_title" ) .. ']]</span>' .. L( lang, "text", "self_eval_title" ) .. '</h2>' )

		local startBtn = wkAddDataGetButton(
			L( lang, "forms", "self_eval_home_title" ),
			target,
			L( lang, "text", "self_eval_start_label" ),
			L( lang, "text", "self_eval_start_tt" ),
			{
				[ ns .. "[" .. f_debat .. "]" ]	= pv.rawTitle
			}
		)
		table_insert(
			box,
			'<div class="boite-info navigation-not-searchable noprint">'
			.. L( lang, "text", "self_eval_blurb" )
			.. startBtn
			.. '</div>'
		)

		table_insert( pieces, table_concat( box ) )
	end

	table_insert( pieces, renderIntroduction( args, pv, lang ) )
	table_insert( pieces, renderWikipediaLinks( args, pv, lang ) )
	table_insert( pieces, renderArgsList( args, pv, lang, "pro", maps, cats ) )
	table_insert( pieces, renderArgsList( args, pv, lang, "con", maps, cats ) )

	table_insert( pieces, renderReferences( args, pv, lang, cats, smwPending ) )
	table_insert( pieces, renderConnexes( args, pv, cats, lang ) )

	table_insert( pieces,
		'<h2 class="latest-changes-button navigation-not-searchable noprint">'
		.. '<span style="margin-right: 0.5em;">[[File: '
		.. L( lang, "files", "changes" )
		.. ' | 16px | link= | alt='
		.. L( lang, "text", "latest_changes_title" )
		.. ']]</span>'
		.. L( lang, "text", "latest_changes_title" )
		.. '</h2>'
		.. '<div class="latest-changes navigation-not-searchable noprint">'
		.. '<div class="latest-changes-button wk-btn mw-ui-button"'
		.. ' title="' .. L( lang, "text", "latest_changes_show_tt" ) .. '"'
		.. ' data-page="' .. t_nowiki( pv.rawTitle ) .. '">'
		.. L( lang, "text", "latest_changes_show_label" )
		.. '</div>'
		.. '<div class="latest-changes-wrapper"></div>'
		.. '</div>'
	)

	if args[ "interlanguage" ] and t_trim( args[ "interlanguage" ] ) ~= "" then
		table_insert( pieces, args[ "interlanguage" ] )
	end

	table_insert( pieces, renderRubriques( args, cats, lang ) )

	setSemanticData( args, pv, lang, maps, smwPending )

	renderFullSEO( args, pv, lang )
	table_insert( pieces, table_concat( cats ) )

	local renameLink = string_format( L( lang, "text", "rename_special_page_text" ), pv.encoded )
	table_insert( pieces,
		'<span id="bouton-renommer" class="modifier-rubrique navigation-not-searchable noprint" style="display: none;">[['
		.. renameLink .. '|' .. L( lang, "text", "rename_link_label" ) .. ']]</span>'
	)

	table_insert( pieces, '<span id="bouton-modifier-sujet" class="modifier-rubrique navigation-not-searchable noprint" style="display: none;">'
		.. addDataLink( L( lang, "forms", "subject_edit_title" ), pv.rawTitle, L( lang, "text", "edit_label" ), L( lang, "text", "subject_edit_tt" ) )
		.. '</span>' )
	table_insert( pieces, '<span id="bouton-modifier-categories" class="modifier-rubrique navigation-not-searchable noprint" style="display: none;">'
		.. addDataLink( L( lang, "forms", "sections_edit_title" ), pv.rawTitle, L( lang, "text", "edit_label" ), L( lang, "text", "sections_edit_tt" ) )
		.. '</span>' )
	if args["interlanguage"] and t_trim( tostring( args["interlanguage"] or "" ) ) ~= "" then
		table_insert( pieces,
			'<span id="bouton-modifier-interlangue" class="modifier-rubrique navigation-not-searchable noprint" style="display: none;"'
			.. ' data-wk-tooltip="' .. escapeAttr( L( lang, "text", "interlanguage_edit_tt" ) ) .. '">'
			.. addDataLink(
				L( lang, "forms", "interlanguage_edit_title" ),
				pv.rawTitle,
				L( lang, "text", "edit_label" ),
				L( lang, "text", "interlanguage_edit_tt" )
			)
			.. '</span>'
		)
	end

	return table_concat( pieces, "" )
end

return p