Aller au contenu

Module:Taille des sections

Une page de Wikipédia, l'encyclopédie libre.

 Documentation[voir] [modifier] [historique] [purger]


Ce module peut être utilisé sur la page de discussion d'un article, pour générer un tableau récapitulatif indiquant la taille des sections et des sous-sections dans l'article.

Utilisation

[modifier le code]

Fonctions exportables :

  • aucun

Voir aussi : Modèle:Taille des sections

require('strict');

--[[--------------------------< I 1 8 N _ T >------------------------------------------------------------------

An associative table of static text used in this module for use when translating this module to other languages.

The values $1 and $2 are replaced with values as stated in the associated message comment

]]

local i18n_t = {
																				-- non-fatal error messaging
	['markup removed'] = 'balisage supprimé ; lien impossible',					-- error message

																				-- fatal error messaging
	['fatal_no_article'] = 'Erreur : pas d&apos;article : $1',					-- $1 is page name
	['fatal_no_sections'] = 'Erreur : pas de sections trouvées dans : $1',		-- $1 is page name
	['fatal_redirect'] = 'Erreur : $1 est une redirection',						-- $1 is page name

	['help link'] = '([[$1|aide]])',											-- help text wikilink for all error messages; $1 is calling template's name
	['error category'] = nil,													-- $1 is calling template's name; comment out this line, to suppress error category

	['table caption'] = 'Taille des sections pour [[$1]] ($2 sections)',		-- caption; $1 is page name; $2 is number of sections

	['section_name'] = 'Nom de la section',										-- column headings; left to right
	['byte_count'] = 'Nombre<br />d&apos;octets',
	['section_total'] = 'Totale<br />section',

	['top'] = nil,																-- for the unnamed lede section; use this only when this module does not get correct string from MediaWiki:Vector-toc-beginning
	
	['total'] = 'Totales',				 										-- footer
	}


--[[--------------------------< E R R O R _ M S G _ M A K E >--------------------------------------------------

common funtion to emit both fatal and non-fatal error messages

template_name used in error message help text link and error category link fetched from MediaWiki

error category emitted only for transclusions in the Talk namespace; only when i18n_t['error category'] has a
value; only when we have not already emitted the category link.

]]

local err_cat_added;															-- page-scope boolean flag; true when error cat link has been emmitted; nil else
local function error_msg_make (template_name, msg, args_t, fatal)
	local err_cat = '';															-- empty string for concatenation

	if not err_cat_added and i18n_t['error category'] and 1 == mw.title.getCurrentTitle().namespace then
		err_cat = mw.message.newRawMessage (i18n_t['error category'], {template_name}):plain();	-- create error category wikilink
		err_cat_added = true;													-- we will do this only once
	end

	local err_msg = table.concat ({
		fatal and '' or ' ',													-- no space before fatal error messages
		fatal and '<span style="font-size:100%;" class="error">' or '<span style="color:#d33">',	-- select fatal/non-fatal styling
		mw.message.newRawMessage (msg, args_t):plain(),							-- compose the message
		' ',																	-- insert a space between the message and the help link
		mw.message.newRawMessage (i18n_t['help link'], {template_name}):plain(),	-- add the help link
		'</span>',																-- and done with styling
		err_cat																	-- if not yet emitted, append error cat link
	});
	
	return err_msg;
end


--[[--------------------------< A N C H O R S _ R E M O V E >--------------------------------------------------

remove html markup that looks like an anchor.  There appear to be two general forms:
	<span class="anchor" id="Foo"></span>
	<span id="Foo"></span>
multiple anchor spans are allowed

Because anchor markup is allowed in section headings, does not set the modified flag on return

]]

local function anchors_remove (section_name)
	local patterns = {
		'<span +[^>]*class *= *"anchor"[^>]*></span>',							-- don't care about the id= or any other attribute here if we have the anchor class
		'<span +%f[%a]id *= *".-" *></span>',									-- here the span must have only the id= attribute
		}	

	for _, pattern in ipairs (patterns) do
		section_name = section_name:gsub (pattern, '');							-- remove all anchor spans
	end
	
	return section_name;
end


--[[--------------------------< R E F S _ R E M O V E >--------------------------------------------------------

remove wikitext reference markup.  done this way because we later preprocess the section name to render any templates
that are present in the section name (there shouldn't be but that doesn't stop editors from including them).
preprocessing a section name with reference markup causes MediaWiki to create a reflist; a side effect that we
don't want.

returns modified section name and boolean true when references have been removed; unmodified section name and false else.

]]

local function refs_remove (section_name)
	local name;																	-- modified (or unmodified) section name
	local markup_removed;														-- boolean true when reference markup has been removed
	local count;
	
	name, count = section_name:gsub ('<ref.-/>', '');							-- remove self-closed <ref with attributes /> tags
	markup_removed = 0 < count;													-- count not zero, set <markup_removed> true
	name, count =  name:gsub ('<ref.->.-</ref>', '');							-- remove remaining ref tags and content (if any)

	return name, markup_removed or (0 < count)
end


--[[--------------------------< S T R I P M A R K E R S _ R E M O V E >----------------------------------------

remove stripmarkers from preprocessed section names.  it may be best to preserve <nowiki/> tags before section name
is preprocessed to prevent '<nowiki/>'' from being interpreted as ''' bold markup.  It is not possible to do that
here because all nowiki strip markers are only identifiable by the numbers.

returns modified section name and boolean true when stripmarkers have been removed; unmodified section name and false else.

]]

local function stripmarkers_remove (section_name)
	local count;
	
	section_name, count = section_name:gsub ('\127[^\127]*UNIQ%-%-%a+%-[%x]+%-QINU[^\127]*\127', '');
	return section_name, (0 < count);
end


--[=[-------------------------< R E M O V E _ W I K I _ L I N K >----------------------------------------------

Gets the display text from a wikilink like [[A|B]] or [[B]] gives B

The str:gsub() returns either A|B from a [[A|B]] or B from [[B]] or B from B (no wikilink markup).

In l(), l:gsub() removes the link and pipe (if they exist); the second :gsub() trims white space from the label
if str was wrapped in wikilink markup.  Presumably, this is because without wikimarkup in str, there is no match
in the initial gsub, the replacement function l() doesn't get called.

]=]

local function remove_wiki_link (str)
	return (str:gsub( "%[%[([^%[%]]*)%]%]", function(l) 
		return l:gsub( "^[^|]*|(.*)$", "%1" ):gsub("^%s*(.-)%s*$", "%1");
	end));
end


--[[--------------------------< R E M O V E _ C O N T A I N E R >----------------------------------------------

Inspired from above, removes everything between < & >
Used to remove html containers from headings to fix breaking section links, but legitimate text within < & > are removed too

returns text and boolean true if modified; text and boolean false else

]]

local function remove_container (str)
	local count;
	str, count = str:gsub( "<([^>]*)>", function(l)
		return l:gsub("^%s*(.-)%s*$", "");
	end);
	
	return str,  0 < count
end


--[=[-------------------------< M A K E _ W I K I L I N K >----------------------------------------------------

Makes a wikilink; when both link and display text is provided, returns a wikilink in the form [[L|D]]; if only
link is provided, returns a wikilink in the form [[L]]; if neither are provided or link is omitted, returns an
empty string.

]=]

local function make_wikilink (link, display)
	if link and ('' ~= link) then
		if display and ('' ~= display) then
			return table.concat ({'[[', link, '|', display, ']]'});
		else
			return table.concat ({'[[', link, ']]'});
		end
	end
	return display or '';														-- link not set so return the display text
end


--[[--------------------------< S I Z E >----------------------------------------------------------------------

module entry point

créer une liste liée au wikilien des sections de <nom de l'article> et de leur taille en octets dans un wikitable triable.

{{#invoke:Taille des sections|taille|<nom d'article>}}

]]

local function size (frame)
	local template_name = frame:getParent():getTitle();							-- get template name for use in error messaging and category name

	local section_info_t = {};													-- table to hold section names and sizes
	local section_name_list = {}												-- an interim list that holds just the section names
	local section_content;														-- section content used for counting
	local section = i18n_t.top or												-- i18n_t.top rarely needed 
		mw.message.new ('vector-toc-beginning')									-- lead section doesn't have a heading, get the interface message for 'top'
			:inLanguage (mw.language.getContentLanguage())						-- in the current wiki's content language
			:plain();															-- and make sure we have a string
	local count = {};															-- number of bytes in a section including the header text
	local totcount = {};
	local lastlevel;
	local maxlevels;
	local levelcounts = {};
	local upperlevel;
	local highlight;
	local highlighttot;
	local total;																-- sum of all byte counts
	local max;																	-- largest section so far encountered
	local totmax;																-- largest section so far encountered (section total)
	local _;																	-- dummy for using gsub to count bytes
	local lang = mw.language.getContentLanguage();								-- language object for number formatting appropriate to local language
	local s;																	-- start position of found heading (returned from string.find())
	local e = 1;																-- end position of found heading (returned from string.find())
	local section_name;															-- captured heading name (returned from string.find())
	local level = {};															-- number of leading '=' in heading markup; used for indenting subsections in the rendered list
	local wl_name;																-- anchor and display portion for wikilinks in rendered list

	local title_obj = mw.title.new (frame.args[1])
	local content = title_obj:getContent();										-- get unparsed wikitext from the article
	if not content then
		return error_msg_make (template_name, i18n_t['fatal_no_article'], {frame.args[1]}, true);	-- emit fatal error message and abandon
	end

	if title_obj.isRedirect then												-- redirects don't have sections
		return error_msg_make (template_name, i18n_t['fatal_redirect'], {frame.args[1]}, true);	-- emit fatal error message and abandon
	end

	section_content = content:match ('(.-)===*');								-- get the lead section
	if section_content then
		count[0] = #section_content;											-- get a count the bytes in the lead section
	else
		return error_msg_make (template_name, i18n_t['fatal_no_sections'], {frame.args[1]}, true);	-- emit fatal error message and abandon
	end

	total = count[0];
	max = count[0];
	
	table.insert (section_info_t, make_wikilink (frame.args[1], section) .. '|| style="text-align:right"|' .. lang:formatNum (count[0]) .. '|| style="text-align:right"|' .. lang:formatNum (count[0]));

	while (1) do																-- done this way because some articles reuse section names
		s, e, section_name = string.find (content, '\n==+ *(.-) *==+', e);		-- get start, end, and section name beginning a end of last find; newline must precede '==' heading markup
		if s then
			table.insert (section_name_list, {section_name, s});				-- save section name and start location of this find
		else
			break;
		end
	end
	
	for i, section_name in ipairs (section_name_list) do
		local escaped_section_name = string.gsub (section_name[1], '([%(%)%.%%%+%-%*%?%[%^%$%]])', '%%%1');		-- escape lua patterns in section name
		local pattern = '(==+ *' .. escaped_section_name .. ' *==+.-)==+';		-- make a pattern to get the content of a section
		section_content = string.match (content, pattern, section_name[2]);		-- get the content beginning at the string.find() start location
		if section_content then
			count[i] = #section_content;										-- get a count of the bytes in the section
			total = total + count[i];
			max = max < count[i] and count[i] or max;							-- keep track of largest count
		else																	-- probably the last section (no proper header follows this section name)
			pattern = '(==+ *' .. escaped_section_name .. ' *==+.+)';			-- make a new pattern
			section_content = string.match (content, pattern, section_name[2]);	-- try to get content
			if section_content then
				count[i] = #section_content;									-- get a count the bytes in the section
				total = total + count[i];
				max = max < count[i] and count[i] or max;						-- keep track of largest count
			else
				count[i] = '—';													-- no content so show that
			end
		end

		_, level[i] = section_content:find ('^=+');								-- should always be the first n characters of section content
		
	end
	
	totmax=0;
	lastlevel=0;
	maxlevels=7;
	for j=1,maxlevels do
		levelcounts[j]=0;
	end
    for i=#count,1,-1 do
    	if level[i]<lastlevel then	-- reset all
    		totcount[i]=levelcounts[level[i]]+count[i];
    		for j=level[i],maxlevels do
				levelcounts[j]=0;
			end
    	end
    	if level[i]>=lastlevel then
    		totcount[i]=count[i];
    	end
		if level[i]>0 then
	    	upperlevel=level[i]-1;
	    	levelcounts[upperlevel]=levelcounts[upperlevel]+totcount[i];
    	end
		lastlevel=level[i];
		if totcount[i]>totmax then
			totmax=totcount[i];
		end
	end

	for i, section_name in ipairs (section_name_list) do
		if count[i]==max then
			highlight='background:red;"|';
		else
			local proportion = count[i] / max									-- get value of "how much of the max" the count is
			local gb = 250 - math.floor(250 * proportion)						-- approach #f8f9fa [r=248,g=249,b=250] (default wikitable cell color) for small bytecounts
			highlight = string.format('background:#F8%02X%02X;"|', gb, gb)		-- shade the bg as r: 248, g: gb, and b: gb
		end
		
		highlighttot='';														-- start the style declaration
		if totcount[i]==totmax then
			highlighttot=highlighttot .. 'background:red;';
		else
			local proportion = totcount[i] / totmax								-- get value of "how much of the max" the count is
			local gb = 250 - math.floor(250 * proportion)						-- approach #f8f9fa [r=248,g=249,b=250] (default wikitable cell color) for small bytecounts
			highlighttot=highlighttot .. string.format('background:#F8%02X%02X;', gb, gb)	-- shade the bg as r: 248, g: gb, and b: gb
		end
		if level[i]==2 then
			highlighttot=highlighttot .. 'font-weight:bold;';					-- if main section, make it bold
		elseif totcount[i]==count[i] then
			highlighttot='color:transparent;';									-- hide totals for subsections with no subsubsections, values required for proper sorting
		end
		highlighttot=highlighttot .. '"|';										-- close the style declaration
		
		level[i] = (2 < level[i]) and ((level[i]-2) * 1.6) or nil;				-- remove offset and mult by 1.6em (same indent as ':' markup which doesn't work in a table)

		local markup_removed;													-- temp flag to note that the section heading has been modified (references and html-like markup stripped)
		local modified = false;													-- flag to select section heading styling; false: wikilink; true: plain text with error message
		wl_name, modified = refs_remove (section_name[1]);						-- remove all references
		wl_name = remove_wiki_link (wl_name);									-- remove all wikilinks
		wl_name = wl_name:gsub ('<nowiki/>', '__sss_nowiki/__');				-- replace <nowiki/> tag with special secret symbol
		wl_name = frame:preprocess (wl_name);									-- render to html
		wl_name = anchors_remove (wl_name);										-- remove known anchor markup; these allowed in section heading so do not bump <modified>
		wl_name = wl_name:gsub ('__sss_nowiki/__', '<nowiki/>');				-- replace special secret symbol with <nowiki/> tag
		wl_name, markup_removed = stripmarkers_remove (wl_name);				-- remove any strip markers resulting from preprocessing
		modified = modified or markup_removed;									-- update <modified>
		wl_name = wl_name:gsub ('</?i>', '\'\'');								-- italic markup has been converted to html; unconvert so remove_container() doesn't remove inappropriately
		wl_name = wl_name:gsub ('</?b>', '\'\'\'');								-- bold markup has been converted to html; unconvert so remove_container() doesn't remove inappropriately
		wl_name, markup_removed = remove_container (wl_name);					-- remove html containers from section headings so that we can link to the section
		modified = modified or markup_removed;									-- update <modified>

		wl_name = wl_name:gsub ('[%[%]]', {['[']='&#91;', [']']='&#93;'});		-- replace '[' and ']' characters with html entities so that wikilinked section names work
		wl_name = mw.text.trim (wl_name);										-- trim leading/trailing white space if any because white space buggers up url anchor links
		
		local heading_text;
		if modified then
			heading_text = table.concat ({										-- modified headings are rendered in plain text with an error message
				wl_name,														-- plain text section name
				error_msg_make (template_name, i18n_t['markup removed'], {}),	-- error message with help link
				});													-- close help link
		else
			heading_text = make_wikilink (frame.args[1] .. '#' .. wl_name:gsub ("''+", ''), wl_name);	-- unmodified rendered as is
		end


		table.insert (section_info_t, table.concat ({									-- build most of a table row here because here we have heading information that we won't have later
			level[i] and '<span style="margin-left:' .. level[i] .. 'em">' or '';		-- indent per heading level (number of '=' in heading markup)
			heading_text,														-- section link remove any bold/italic markup from the link part of the wikilink; leave the markup in the display
			level[i] and '</span>' or '',										-- close the span if opened
			'||',																-- table column separator
			'style="text-align:right;',											-- the byte count column is right aligned
			highlight ,
			lang:formatNum (count[i]),											-- commafied byte count for section
			'||',
			'style="text-align:right;',											-- the section total column is right aligned
			highlighttot ,
			lang:formatNum (totcount[i]),										-- section total count!!
		}));																
	end

	local out = {};																-- make a sortable wikitable for output
	table.insert (out, string.format ('{| class="wikitable sortable" style="%s"\n|+%s',		-- output caption and column headings
		frame.args.style or '',													-- value for style= attribute
		mw.message.newRawMessage (i18n_t['table caption'], {frame.args[1], #section_info_t}):plain()
		));

	table.insert (out, table.concat ({											-- column headings
		'\n!',
		i18n_t.section_name,
		'!!',
		i18n_t.byte_count,
		'!!',
		i18n_t.section_total,
		'\n|-\n|'																-- first row pipe
		})); 

	table.insert (out, table.concat (section_info_t, '\n|-\n|'));				-- section rows with leading pipes (except first row already done)
	table.insert (out, table.concat ({											-- total number of bytes; heading markup so that sorting doesn't move this row from the bottom to top
		'\n|-\n!',
		i18n_t.total,															-- footer label
		'!!style="text-align:right"|',
		lang:formatNum (total),													-- byte count sum; TODO: to columns, should be two separate sums; test for equality?
		'!!style="text-align:right"|',
		lang:formatNum (total)													-- section size sum; TODO: to columns, should be two separate sums; test for equality?
		}));
	table.insert (out, '\n|}');													-- close the wikitable
	
	local result = table.concat (out, '');
	return result;																-- because gsub returns string and number of replacements
end


--[[--------------------------< E X P O R T E D   F U N C T I O N S >------------------------------------------
]]

return
	{
	size = size,
	}