Jump to content

Module:Autoname

Permanently protected module
From Wikipedia, the free encyclopedia

local utilities = require ('Module:Citation/CS1/Utilities');
local cfg = mw.loadData ('Module:Citation/CS1/Configuration');
local data = mw.loadData ('Module:Autoname/data');


--[[--------------------------< N A M E _ H A S _ E T A L >----------------------------------------------------

Evaluates the content of name parameters (contributor, author, editor) for variations on the theme of et al.  If
found, the et al. is removed.

Adapted from same-named function in Module:Citation/CS1

]]

local function name_has_etal (name)
	if utilities.is_set (name) then												-- name can be nil in which case just return
		local patterns = cfg.et_al_patterns; 									-- get patterns from configuration
		
		for _, pattern in ipairs (patterns) do									-- loop through all of the patterns
			if name:match (pattern) then										-- if this 'et al' pattern is found in name
				name = name:gsub (pattern, '');									-- remove the offending text
				return name;													-- and done;
			end
		end
	end

	return name;																-- no etal found, return <name>
end


--[=[-------------------------< G E T _ V _ N A M E _ T A B L E >----------------------------------------------

split apart a |vauthors= or |veditors= parameter.  This function allows for corporate names, wrapped in accept-
as-written markup to also have commas.  Individual author names may be wikilinked.

	|vauthors=Jones AB, [[E. B. White|White EB]], ((Black, Brown, and Co.))

Adapted from same-named function in Module:Citation/CS1

]=]

local function get_v_name_table (vparam, out_t)
	local names_t = mw.text.split (vparam, "%s*,%s*");							-- names in vparam are separated by commas
	local label;																-- label from a wikilinked name
		
	local i = 1;																-- initialize indexer
	
	while names_t[i] and i < 5 do												-- no more than four names
		if names_t[i]:match ('^%(%(.*[^%)][^%)]$') then							-- first segment of corporate with one or more commas; this segment has the opening doubled parentheses
			local name = names_t[i];
			i = i + 1;															-- bump indexer to next segment
			while names_t[i] do
				name = name .. ', ' .. names_t[i];								-- concatenate with previous segments
				if names_t[i]:match ('^.*%)%)$') then							-- if this table member has the closing doubled parentheses
					break;														-- and done reassembling so
				end
				i = i + 1;														-- bump indexer
			end
			table.insert (out_t, name);											-- and add corporate name to the output table
		else
			local _;
			_, label, _ = utilities.is_wikilink (names_t[i]);					-- wl_type is: 0, no wl (text in <label>); 1, [[D]]; 2, [[L|D]]
			table.insert (out_t, label);										-- add this name
		end
		i = i + 1;
	end	
	return out_t;
end


--[[--------------------------< P A R S E _ V A U T H O R S _ V E D I T O R S >--------------------------------

This function extracts author / editor surnames from |vauthors= or |veditors=.  Initials and generational suffixes
are discarded.  Accept-as-written markup around an individual name is stripped.

Adapted from same-named function in Module:Citation/CS1

]]

local function parse_vauthors_veditors (vparam)
	local names_t = {};															-- table of names assembled from |vauthors=, |author-maskn=, |author-linkn=
	local v_name_table = {};
	local last, first, suffix;

	vparam = name_has_etal (vparam);											-- find and remove variations on et al. do not categorize (do it here because et al. might have a period)
	v_name_table = get_v_name_table (vparam, v_name_table);						-- names are separated by commas

	for i, v_name in ipairs (v_name_table) do
		first = '';																-- set to empty string for concatenation and because it may have been set for previous author/editor
		local accept_name;
		v_name, accept_name = utilities.has_accept_as_written (v_name);			-- remove accept-this-as-written markup when it wraps all of <v_name>

		if accept_name then
			last = v_name;
		elseif string.find (v_name, "%s") then
			local lastfirst_t = mw.text.split (v_name, "%s+");
			first = table.remove (lastfirst_t);									-- removes and returns value of last element in table which should be initials or generational suffix

			if not mw.ustring.match (first, '^%u+$') then						-- mw.ustring here so that later we will catch non-Latin characters
				suffix = first;													-- not initials so assume that whatever we got is a generational suffix
				first = table.remove (lastfirst_t);								-- get what should be the initials from the table
			end
			last = table.concat (lastfirst_t, ' ');								-- returns a string that is the concatenation of all other names that are not initials and generational suffix
			if not utilities.is_set (last) then
				first = '';														-- unset
				last = v_name;													-- last empty because something wrong with first
			end
		else
			last = v_name;														-- last name or single corporate name?  Doesn't support multiword corporate names? do we need this?
		end
		
		if utilities.is_set (first) then
			if utilities.is_set (suffix) then
				suffix = '';													-- unset so we don't add this suffix to all subsequent names
			end
		end

		names_t[i] = last;														-- add name to names list
	end
	return names_t;																-- all done, return list of names
end


--[[--------------------------< Y E A R _ G E T >--------------------------------------------------------------

because |date= and |publication-date= will be full aliases ofeach other at the next cs1|2 suite update, accept
only one of them; assume that |date= is the canonical form

]]

local function year_get (args_t)
	local date, year;
	local d_dab, y_dab;															-- used to identify which to returm when both 'date' and year are set
	date, d_dab = (args_t['date'] or args_t['publication-date'] or ''):match ('([1-9]%d%d%d?(%l?))');	-- if both are present prefer |date=
	year, y_dab = (args_t['year'] or ''):match ('([1-9]%d%d%d?(%l?))');

	if date and year then
		if '' ~= d_dab then														-- when no CITEREF disambiguator, <d_dab> is empty string
			return date;														-- prefer |date= and |publication-date= over year when 'date' has a CITEREF disambiguator
		elseif '' ~= y_dab then													-- when no CITEREF disambiguator, <y_dab> is empty string
			return year;														-- prefer |year= over 'date' when |year= has a CITEREF disambiguator
		end
	end

	return date or year;														-- when no dabs or only one of |date=, |publication-date=, or |year= set
end

--[[--------------------------< D O M A I N _ G E T >----------------------------------------------------------

extract domain name from a url.

]]

local function domain_get (url)
	url = mw.text.trim (url or '')
    if url == '' then return nil end

    local domain = mw.ustring.match(url, "^%a[%w+.-]*://([^/?#]+)")				-- Extract the domain (with or without protocol)
              or mw.ustring.match(url, "^//([^/?#]+)")
              or mw.ustring.match(url, "^([^/?#]+)")
    if not domain then return nil end

    domain = domain:gsub ("^.*@", ""):gsub (":%d+$", "")						-- Remove credentials (user:pass@) and port (:8080)
	
	local _, segments = domain:gsub ("[^.]+", "")									
	if segments < 2 or domain:find ('%.%.') then								-- Reject values that don't look like a domain name
		return nil
	elseif segments > 2 then 													-- Remove common prefixes
		domain = domain:gsub ("^ww[w%d]%d*%.", ""):gsub ("^m%.", ""):gsub ("^ftp%.", "")
	end

    return domain
end

--[[--------------------------< A U T O N A M E _ M A K E >----------------------------------------------------

Common function to create autoname from whichever of contributor / author / editor name list is chosen.
Attempt to prevent the autoname from being over 78 characters (80 total including a 2-digit index).

]]

local function autoname_make (names_t, year)
	local suffix = (year or '') .. '-';											-- add year to name list followed by a hyphen to separate year from ve-added index digit(s)
	local length = mw.ustring.len (suffix);
	local output = '';

	for i, name in ipairs(names_t) do
		local name_length = mw.ustring.len (name);
		if length + name_length > 78 then										-- check if the next item would push the string over 78 characters
			if i == 1 then														-- first item in the list, try to truncate first on punctuation, then on spaces
				local prefix = mw.ustring.sub (name, 1, 78 - length);
				name = mw.ustring.match (prefix, "^(.*)%p") or mw.ustring.match (prefix, "^(.*)%s") or name;
			else																-- stop concatenation if it would push the string over 78 characters
				break;
			end
		end
		output = output .. name;
		length = length + name_length;
	end

	return mw.uri.anchorEncode (output .. suffix);								-- concatenate, anchor encode, and done					
end


--[[--------------------------< _ A U T O N A M E >------------------------------------------------------------

entry point from another module or from the debug console

]]

local function _autoname (args_t)
	local params_order_t = data.params_order_t;
	local enum_params_order_t = data.enum_params_order_t;
	local enum_params_t = data.enum_params_t;
	local params_t = data.params_t;
	
	local names_t = {};															-- sequence to hold values to potentially pass to autoname_make
	for _, ntype in ipairs (params_order_t) do
		names_t[ntype] = {};
	end

	for param, value in pairs (args_t) do
		local enum = tonumber (param:match ('%d+'));							-- get the enumerator if this parameter has one; nil else
		local index = param:gsub ('%d+', '#');
		local accept;
		value, accept = utilities.has_accept_as_written (value);				-- remove accept-as-written markup if present
		if not accept then
			value = name_has_etal (value);
		end
		if enum and (5 > enum) then												-- sort enumerated name parameter values into the various sequences
			for _, ntype in ipairs (enum_params_order_t) do
				if enum_params_t[ntype][index] then
					names_t[ntype][enum] = value;
					break;
				end
			end
		else																	-- sort non-enumerated name parameter values into the various sequences
			for _, ntype in ipairs (params_order_t) do
				if params_t[ntype][index] then
					if ntype == 'url' then
						names_t[ntype][1] = domain_get (value)
					else
						names_t[ntype][1] = value;
					end
					break;
				end
			end
		end	
	end

	if args_t['vauthors'] and #names_t.author == 0 then
		names_t.author = parse_vauthors_veditors (args_t['vauthors']);			-- fetch a sequence of vauthor surnames
	end
	if args_t['veditors'] and #names_t.editor == 0 then
		names_t.editor = parse_vauthors_veditors (args_t['veditors']);			-- fetch a sequence of veditor surnames
	end
	
	local year = year_get (args_t);												-- get year portion of date from |date=, |publication-date=, or |year=
	
	for _, ntype in ipairs(params_order_t) do									-- in params_order_t order, make a CITEREF-like anchor for autonaming
		if names_t[ntype][1] then
			return autoname_make (names_t[ntype], year);
		end
	end
end


--[[--------------------------< A U T O N A M E >--------------------------------------------------------------

entry point from a template

]]

local function autoname (frame)
	local args_t = require ('Module:Arguments').getArgs (frame);				-- get the template's parameters
	return _autoname (args_t);													-- and render the reference name
end


--[[--------------------------E X P O R T S >------------------------------------------------------------------
]]

return {
	autoname = autoname,														-- entry point from a template

	_autoname = _autoname,														-- entry point from another module or from the debug console
	}