模組:Citation/CS1/People/sandbox

文档图示 模块文档[查看] [编辑] [历史] [清除缓存]


此模块及关联的子页面为引文格式1引文格式2引用模板提供支持。通常来说不应直接调用此模块,而是用CS1与CS2模板调用。

如下文件涉及对CS1与CS2引用模板的支持:

CS1 | CS2模块
线上 沙盒 描述
Gold padlock Module:Citation/CS1 Module:Citation/CS1/sandbox [编辑] 主模块,依输入的模板参数生成相应的引文
Module:Citation/CS1/Configuration Module:Citation/CS1/Configuration/sandbox [编辑] 配置表
Module:Citation/CS1/Whitelist Module:Citation/CS1/Whitelist/sandbox [编辑] 现行与已过时不推荐使用的CS1与CS2参数
Module:Citation/CS1/Date validation Module:Citation/CS1/Date validation/sandbox [编辑] 日期格式验证函数
Module:Citation/CS1/Error Module:Citation/CS1/Error/sandbox [编辑] 错误/维护信息相关函数
Module:Citation/CS1/Identifiers Module:Citation/CS1/Identifiers/sandbox [编辑] 支持命名标识符(ISBN、DOI、PMID等)的函数
Module:Citation/CS1/Language Module:Citation/CS1/Language/sandbox [编辑] 语言相关函数
Module:Citation/CS1/Links Module:Citation/CS1/Links/sandbox [编辑] 维基内、外链相关函数
Module:Citation/CS1/People Module:Citation/CS1/People/sandbox [编辑] 人名列表相关函数
Module:Citation/CS1/Utilities Module:Citation/CS1/Utilities/sandbox [编辑] 简单无副作用的辅助函数(主要为字串/格式相关)
Module:Citation/CS1/COinS Module:Citation/CS1/COinS/sandbox [编辑] 支持CS1与CS2模板元数据渲染的函数
Module:Citation/CS1/styles.css Module:Citation/CS1/sandbox/styles.css [编辑] 为CS1与CS2模板提供CSS样式
Silver padlock Module:Citation/CS1/Suggestions Module:Citation/CS1/Suggestions/sandbox [编辑] 将常见错误参数名映射到有效参数名的列表


其他文档:

测试用例:

调试: 欲调试该模块,可通过在编辑框下方的“调试控制台”中输入以下代码查看模块的返回值:

mw.logObject(p.citation(mw.getCurrentFrame():newChild{args = {--[[此处填写调试用的模板参数,例如['title'] = 'test', ['url'] = 'http://example.com']] }}))
--[[--------------------------< F O R W A R D   D E C L A R A T I O N S >--------------------------------------
]]
local cfg;
local in_array, is_set;
local append_error, add_maint_cat, select_one;
local make_internal_link;

--[[--------------------------< A D D _ V A N C _ E R R O R >----------------------------------------------------

Adds a single Vancouver system error message to the template's output regardless of how many error actually exist.
To prevent duplication, added_vanc_errs is nil until an error message is emitted.

]]

local added_vanc_errs = false;															-- flag so we only emit one Vancouver error / category
local function add_vanc_error ()
	if not added_vanc_errs then
		added_vanc_errs = true;													-- note that we've added this category
		append_error( 'vancouver', {});
	end
end

--[[--------------------------< I S _ G O O D _ V A N C _ N A M E >--------------------------------------------

For Vancouver Style, author/editor names are supposed to be rendered in Latin (read ASCII) characters.  When a name
uses characters that contain diacritical marks, those characters are to converted to the corresponding Latin character.
When a name is written using a non-Latin alphabet or logogram, that name is to be transliterated into Latin characters.
These things are not currently possible in this module so are left to the editor to do.

This test allows |first= and |last= names to contain any of the letters defined in the four Unicode Latin character sets
	[http://www.unicode.org/charts/PDF/U0000.pdf C0 Controls and Basic Latin] 0041–005A, 0061–007A
	[http://www.unicode.org/charts/PDF/U0080.pdf C1 Controls and Latin-1 Supplement] 00C0–00D6, 00D8–00F6, 00F8–00FF
	[http://www.unicode.org/charts/PDF/U0100.pdf Latin Extended-A] 0100–017F
	[http://www.unicode.org/charts/PDF/U0180.pdf Latin Extended-B] 0180–01BF, 01C4–024F

|lastn= also allowed to contain hyphens, spaces, and apostrophes. (http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35029/)
|firstn= also allowed to contain hyphens, spaces, apostrophes, and periods

At the time of this writing, I had to write the 'if nil == mw.ustring.find ...' test ouside of the code editor and paste it here
because the code editor gets confused between character insertion point and cursor position.

]]

local function is_good_vanc_name (last, first)
	if nil == mw.ustring.find (last, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%']*$") or nil == mw.ustring.find (first, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%'%.]*$") then
		add_vanc_error ();
		return false;															-- not a string of latin characters; Vancouver required Romanization
	end;
	return true;
end

--[[--------------------------< R E D U C E _ T O _ I N I T I A L S >------------------------------------------

Attempts to convert names to initials in support of |name-list-format=vanc.  

Names in |firstn= may be separated by spaces or hyphens, or for initials, a period. See http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35062/.

Vancouver style requires family rank designations (Jr, II, III, etc) to be rendered as Jr, 2nd, 3rd, etc.  This form is not
currently supported by this code so correctly formed names like Smith JL 2nd are converted to Smith J2. See http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35085/.

This function uses ustring functions because firstname initials may be any of the unicode Latin characters accepted by is_good_vanc_name ().

]]

local function reduce_to_initials(first)
	if mw.ustring.match(first, "^%u%u$") then return first end;					-- when first contains just two upper-case letters, nothing to do
	local initials = {}
	local i = 0;																-- counter for number of initials
	for word in mw.ustring.gmatch(first, "[^%s%.%-]+") do						-- names separated by spaces, hyphens, or periods
		table.insert(initials, mw.ustring.sub(word,1,1))						-- Vancouver format does not include full stops.
		i = i + 1;																-- bump the counter 
		if 2 <= i then break; end												-- only two initials allowed in Vancouver system; if 2, quit
	end
	return table.concat(initials)												-- Vancouver format does not include spaces.
end

--[[--------------------------< L I S T  _ P E O P L E >-------------------------------------------------------

Formats a list of people (e.g. authors / editors) 

]]

local function list_people(control, people, etal, list_name)					-- TODO: why is list_name here?  not used in this function
	local sep;
	local namesep;
	local format = control.format
	local maximum = control.maximum
	local lastauthoramp = control.lastauthoramp;
	local text = {}

	if 'vanc' == format then													-- Vancouver-like author/editor name styling?
		sep = ',';																-- name-list separator between authors is a comma
		namesep = ' ';															-- last/first separator is a space
	else
		sep = ';'																-- name-list separator between authors is a semicolon
		namesep = ', '															-- last/first separator is <comma><space>
	end
	
	if sep:sub(-1,-1) ~= " " then sep = sep .. " " end
	if is_set (maximum) and maximum < 1 then return "", 0; end					-- returned 0 is for EditorCount; not used for authors
	
	for i,person in ipairs(people) do
		if is_set(person.last) then
			local mask = person.mask
			local one
			local sep_one = sep;
			if is_set (maximum) and i > maximum then
				etal = true;
				break;
			elseif (mask ~= nil) then
				local n = tonumber(mask)
				if (n ~= nil) then
					one = string.rep("&mdash;",n)
				else
					one = mask;
					sep_one = " ";
				end
			else
				one = person.last
				local first = person.first
				if is_set(first) then 
					if ( "vanc" == format ) then								-- if vancouver format
						one = one:gsub ('%.', '');								-- remove periods from surnames (http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35029/)
						if not person.corporate and is_good_vanc_name (one, first) then					-- and name is all Latin characters; corporate authors not tested
							first = reduce_to_initials(first)					-- attempt to convert first name(s) to initials
						end
					end
					one = one .. namesep .. first 
				end
				if is_set(person.link) and person.link ~= control.page_name then
					one = make_internal_link(person.link, one, person.origin);	-- link author/editor if this page is not the author's/editor's page
				end
			end
			table.insert( text, one )
			table.insert( text, sep_one )
		end
	end

	local count = #text / 2;													-- (number of names + number of separators) divided by 2
	if count > 0 then 
		if count > 1 and is_set(lastauthoramp) and not etal then
			text[#text-2] = " & ";												-- replace last separator with ampersand text
		end
		text[#text] = nil;														-- erase the last separator
	end
	
	local result = table.concat(text)											-- construct list
	if etal and is_set (result) then											-- etal may be set by |display-authors=etal but we might not have a last-first list
		result = result .. sep .. ' ' .. cfg.messages['et al'];					-- we've go a last-first list and etal so add et al.
	end
	
	return result, count
end

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

Returns a number that may or may not limit the length of the author or editor name lists.

When the value assigned to |display-authors= is a number greater than or equal to zero, return the number and
the previous state of the 'etal' flag (false by default but may have been set to true if the name list contains
some variant of the text 'et al.').

When the value assigned to |display-authors= is the keyword 'etal', return a number that is one greater than the
number of authors in the list and set the 'etal' flag true.  This will cause the list_people() to display all of
the names in the name list followed by 'et al.'

In all other cases, returns nil and the previous state of the 'etal' flag.

]]

local function get_display_authors_editors (max, count, list_name, etal)
	if is_set (max) then
		if 'etal' == max:lower():gsub("[ '%.]", '') then						-- the :gsub() portion makes 'etal' from a variety of 'et al.' spellings and stylings
			max = count + 1;													-- number of authors + 1 so display all author name plus et al.
			etal = true;														-- overrides value set by extract_names()
		elseif max:match ('^%d+$') then											-- if is a string of numbers
			max = tonumber (max);												-- make it a number
			if max >= count and 'authors' == list_name then	-- AUTHORS ONLY		-- if |display-xxxxors= value greater than or equal to number of authors/editors
				add_maint_cat ('disp_auth_ed', list_name);
			end
		else																	-- not a valid keyword or number
			append_error( 'invalid_param_val', {'display-' .. list_name, max});		
																				-- add error message
			max = nil;															-- unset
		end
	elseif 'authors' == list_name then		-- AUTHORS ONLY	need to clear implicit et al category
		max = count + 1;														-- number of authors + 1
	end
	
	return max, etal;
end


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

Evaluates the content of author and editor name parameters for variations on the theme of et al.  If found,
the et al. is removed, a flag is set to true and the function returns the modified name and the flag.

This function never sets the flag to false but returns it's previous state because it may have been set by
previous passes through this function or by the parameters |display-authors=etal or |display-editors=etal

]]

local function name_has_etal (name, etal, nocat)

	if is_set (name) then														-- name can be nil in which case just return
		local etal_pattern = "[;,]? *[\"']*%f[%a][Ee][Tt] *[Aa][Ll][%.\"']*$"	-- variations on the 'et al' theme
		local others_pattern = "[;,]? *%f[%a]and [Oo]thers";					-- and alternate to et al.
		
		if name:match (etal_pattern) then										-- variants on et al.
			name = name:gsub (etal_pattern, '');								-- if found, remove
			etal = true;														-- set flag (may have been set previously here or by |display-authors=etal)
			if not nocat then													-- no categorization for |vauthors=
				add_maint_cat ('etal');											-- and add a category if not already added
			end
		elseif name:match (others_pattern) then									-- if not 'et al.', then 'and others'?
			name = name:gsub (others_pattern, '');								-- if found, remove
			etal = true;														-- set flag (may have been set previously here or by |display-authors=etal)
			if not nocat then													-- no categorization for |vauthors=
				add_maint_cat ('etal');											-- and add a category if not already added
			end
		end
	end
	return name, etal;															-- 
end

--[[--------------------------< E X T R A C T _ N A M E S >----------------------------------------------------
Gets name list from the input arguments

Searches through args in sequential order to find |lastn= and |firstn= parameters (or their aliases), and their matching link and mask parameters.
Stops searching when both |lastn= and |firstn= are not found in args after two sequential attempts: found |last1=, |last2=, and |last3= but doesn't
find |last4= and |last5= then the search is done.

This function emits an error message when there is a |firstn= without a matching |lastn=.  When there are 'holes' in the list of last names, |last1= and |last3=
are present but |last2= is missing, an error message is emitted. |lastn= is not required to have a matching |firstn=.

When an author or editor parameter contains some form of 'et al.', the 'et al.' is stripped from the parameter and a flag (etal) returned
that will cause list_people() to add the static 'et al.' text from Module:Citation/CS1/Configuration.  This keeps 'et al.' out of the 
template's metadata.  When this occurs, the page is added to a maintenance category.

]]

local function extract_names(args, list_name)
	local names = {};			-- table of names
	local last;					-- individual name components
	local first;
	local link;
	local mask;
	local i = 1;				-- loop counter/indexer
	local n = 1;				-- output table indexer
	local count = 0;			-- used to count the number of times we haven't found a |last= (or alias for authors, |editor-last or alias for editors)
	local etal=false;			-- return value set to true when we find some form of et al. in an author parameter

	local err_msg_list_name = list_name:match ("(%w+)List") .. 's list';		-- modify AuthorList or EditorList for use in error messages if necessary
	while true do
		last = select_one( args, cfg.aliases[list_name .. '-Last'], 'redundant_parameters', i );		-- search through args for name components beginning at 1
		first = select_one( args, cfg.aliases[list_name .. '-First'], 'redundant_parameters', i );
		link = select_one( args, cfg.aliases[list_name .. '-Link'], 'redundant_parameters', i );
		mask = select_one( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i );

		last, etal = name_has_etal (last, etal, false);								-- find and remove variations on et al.
		first, etal = name_has_etal (first, etal, false);								-- find and remove variations on et al.

		if first and not last then												-- if there is a firstn without a matching lastn
			append_error( 'first_missing_last', {err_msg_list_name, i});		-- add this error message
		elseif not first and not last then										-- if both firstn and lastn aren't found, are we done?
			count = count + 1;													-- number of times we haven't found last and first
			if 2 <= count then													-- two missing names and we give up
				break;															-- normal exit or there is a two-name hole in the list; can't tell which
			end
		else																	-- we have last with or without a first
			names[n] = {
				last = last, 
				first = first, 
				link = link, 
				mask = mask, 
				origin = list_name:match ("(%w+)List"):lower() .. '-link' .. i,
				corporate=false
			};																	-- add this name to our names list (corporate for |vauthors= only)
			n = n + 1;															-- point to next location in the names table
			if 1 == count then													-- if the previous name was missing
				append_error( 'missing_name', {err_msg_list_name, i-1});		-- add this error message
			end
			count = 0;															-- reset the counter, we're looking for two consecutive missing names
		end
		i = i + 1;																-- point to next args location
	end
	
	return names, etal;															-- all done, return our list of names
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 names from |vauthors= or |veditors= and finds matching |xxxxor-maskn= and
|xxxxor-linkn= in args.  It then returns a table of assembled names just as extract_names() does.

Author / editor names in |vauthors= or |veditors= must be in Vancouver system style. Corporate or institutional names
may sometimes be required and because such names will often fail the is_good_vanc_name() and other format compliance
tests, are wrapped in doubled paranethese ((corporate name)) to suppress the format tests.

This function sets the vancouver error when a reqired comma is missing and when there is a space between an author's initials.

]]

local function parse_vauthors_veditors (args, vparam, list_name)
	local names = {};															-- table of names assembled from |vauthors=, |author-maskn=, |author-linkn=
	local v_name_table = {};
	local etal = false;															-- return value set to true when we find some form of et al. vauthors parameter
	local last, first, link, mask;
	local corporate = false;

	vparam, etal = name_has_etal (vparam, etal, true);							-- find and remove variations on et al. do not categorize (do it here because et al. might have a period)
	if vparam:find ('%[%[') or vparam:find ('%]%]')	then						-- no wikilinking vauthors names
		add_vanc_error ();
	end
	v_name_table = mw.text.split(vparam, "%s*,%s*")								-- names are separated by commas

	for i, v_name in ipairs(v_name_table) do
		if v_name:match ('^%(%(.+%)%)$') then									-- corporate authors are wrapped in doubled parenthese to supress vanc formatting and error detection
			first = '';															-- set to empty string for concatenation and because it may have been set for previous author/editor
			last = v_name:match ('^%(%((.+)%)%)$')
			corporate = true;
		elseif string.find(v_name, "%s") then
		    lastfirstTable = {}
		    lastfirstTable = mw.text.split(v_name, "%s")
		    first = table.remove(lastfirstTable);								-- removes and returns value of last element in table which should be author intials
		    last  = table.concat(lastfirstTable, " ")							-- returns a string that is the concatenation of all other names that are not initials
		    if mw.ustring.match (last, '%a+%s+%u+%s+%a+') or mw.ustring.match (v_name, ' %u %u$') then
				add_vanc_error ();												-- matches last II last; the case when a comma is missing or a space between two intiials
			end
		else
			first = '';															-- set to empty string for concatenation and because it may have been set for previous author/editor
			last = v_name;														-- last name or single corporate name?  Doesn't support multiword corporate names? do we need this?
		end
																
		if is_set (first) and not mw.ustring.match (first, "^%u?%u$") then		-- first shall contain one or two upper-case letters, nothing else
			add_vanc_error ();
		end
																				-- this from extract_names ()
		link = select_one( args, cfg.aliases[list_name .. '-Link'], 'redundant_parameters', i );
		mask = select_one( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i );
		names[i] = {last = last, first = first, link = link, mask = mask, corporate=corporate};		-- add this assembled name to our names list
	end
	return names, etal;															-- all done, return our list of names
end

--[[--------------------------< S E L E C T _ A U T H O R S _ E D I T O R S _ F R O M _  S O U R C E >------------------

Select one of |authors=, |authorn= / |lastn / firstn=, or |vauthors= as the source of the author name list or
select one of |editors=, |editorn= / editor-lastn= / |editor-firstn= or |veditors= as the source of the editor name list.

Only one of these appropriate three will be used.  The hierarchy is: |authorn= (and aliases) highest and |authors= lowest and
similarly, |editorn= (and aliases) highest and |editors= lowest

When looking for |authorn= / |editorn= parameters, test |xxxxor1= and |xxxxor2= (and all of their aliases); stops after the second
test which mimicks the test used in extract_names() when looking for a hole in the author name list.  There may be a better
way to do this, I just haven't discovered what that way is.

Emits an error message when more than one xxxxor name source is provided.

In this function, vxxxxors = vauthors or veditors; xxxxors = authors or editors as appropriate.

]]

local function select_authors_editors_from_source (vxxxxors, xxxxors, args, list_name, vparam, name_list_format)
	local lastfirst = false;
	if select_one( args, cfg.aliases[list_name .. '-Last'], 'none', 1 ) or		-- do this twice incase we have a first 1 without a last1
		select_one( args, cfg.aliases[list_name .. '-Last'], 'none', 2 ) then
			lastfirst = true;
	end

	if ( is_set ( vxxxxors ) and true == lastfirst ) or								-- these are the three error conditions
		( is_set ( vxxxxors ) and is_set ( xxxxors ) ) or
		( lastfirst and is_set ( xxxxors ) ) then
			local err_name;
			if 'AuthorList' == list_name then									-- figure out which name should be used in error message
				err_name = 'author';
			else
				err_name = 'editor';
			end
			append_error( 'redundant_parameters', {err_name .. '-name-list parameters'});				
																				-- add error message
	end

	local x, xxxxor_etal;
	if not lastfirst then
		if is_set ( vxxxxors ) then
			x, xxxxor_etal = parse_vauthors_veditors ( args, vparam, list_name );
																				-- fetch author list from |vxxxxor=, |xxxxor-linkn=, and |xxxxor-maskn=
			return x, xxxxor_etal, nil , 'vanc';
		elseif is_set ( xxxxors ) then
			return {}, nil, xxxxors, name_list_format;							-- use content of |xxxxor=
		end
	end;
	x, xxxxor_etal = extract_names ( args, list_name );							-- fetch xxxxor list from |xxxxorn= / |lastn= / |firstn=, |xxxxor-linkn=, and |xxxxor-maskn=, or
	return x, xxxxor_etal, nil, name_list_format;								-- no xxxxors at all; this allows missing xxxxor name test to run in case there is a first without last 
end

local function get_people (handle, para, args, page_name)

	local author_etal;
	local a	= {};																-- authors list from |lastn= / |firstn= pairs or |vauthors=
	local Authors;
	
	local NameListFormat = para.namelistformat;
	
	a, author_etal, Authors, NameListFormat = select_authors_editors_from_source(
		handle.vauthors, 
		handle.authors, 
		args, 
		'AuthorList', 
		args.vauthors, 
		NameListFormat
		);

	local Coauthors = handle.coauthors;

	local editor_etal;
	local e	= {};																-- editors list from |editor-lastn= / |editor-firstn= pairs or |veditors=
	local Editors;
	e, editor_etal, Editors, NameListFormat = select_authors_editors_from_source(
		handle.veditors, 
		handle.editors, 
		args, 
		'EditorList', 
		args.veditors, 
		NameListFormat
		);

	local t = {};																-- translators list from |translator-lastn= / translator-firstn= pairs
	local Translators;															-- assembled translators name list
	t = extract_names (args, 'TranslatorList');									-- fetch translator list from |translatorn= / |translator-lastn=, -firstn=, -linkn=, -maskn=
	
	local c = {};																-- contributors list from |contributor-lastn= / contributor-firstn= pairs
	local Contributors;															-- assembled contributors name list
	local Contribution = handle.contribution;
	if para.contributorsvalid then
		c = extract_names (args, 'ContributorList');							-- fetch contributor list from |contributorn= / |contributor-lastn=, -firstn=, -linkn=, -maskn=
		
		if 0 < #c then
			if not is_set (Contribution) then									-- |contributor= requires |contribution=
				append_error( 'contributor_missing_required_param', {'contribution'});
																				-- add missing contribution error message
				c = {};															-- blank the contributors' table; it is used as a flag later
			end
			if 0 == #a then														-- |contributor= requires |author=
				append_error( 'contributor_missing_required_param', {'author'});-- add missing author error message
				c = {};															-- blank the contributors' table; it is used as a flag later
			end
		end
	else																		-- if not a book cite
		if select_one (args, cfg.aliases['ContributorList-Last'], 'redundant_parameters', 1 ) then	-- are there contributor name list parameters?
			append_error( 'contributor_ignored', {});							-- add contributor ignored error message
		end
		Contribution = nil;														-- unset
	end
	
	local LastAuthorAmp = para.lastauthoramp;
	if (in_array (NameListFormat, {'&', 'amp'})) then
		LastAuthorAmp = 'yes';													-- override |lastauthoramp = if |name-list-format = is set to '&' or 'amp'
	end
	
	local EditorCount;															-- used only for choosing {ed.) or (eds.) annotation at end of editor name-list
	do
		local last_first_list;
		local maximum;
		local control = { 
			format = NameListFormat,											-- empty string, 'vanc', or 'amp'
			maximum = nil,														-- as if display-authors or display-editors not set
			lastauthoramp = LastAuthorAmp,
			page_name = page_name											-- get current page name so that we don't wikilink to it via editorlinkn
		};

		do																		-- do editor name list first because coauthors can modify control table
			maximum , editor_etal = get_display_authors_editors (para.displayeditors, #e, 'editors', editor_etal);
			--[[ Preserve old-style implicit et al.
			临时修复"Category:含有旧式缩略标签的引用的页面 in editors"的问题,中文版目前与英文版逻辑不一样,暂时不需要这个分类。等以后更新时再看怎么处理 --2017.6.23 shizhao
			
			if not is_set(maximum) and #e == 4 then 
				maximum = 3;
				append_error('implict_etal_editor', {});
			end
			]]

			control.maximum = maximum;
			
			last_first_list, EditorCount = list_people(control, e, editor_etal, 'editor');

			if is_set (Editors) then
				if editor_etal then
					Editors = Editors .. ' ' .. cfg.messages['et al'];			-- add et al. to editors parameter beause |display-editors=etal
					EditorCount = 2;											-- with et al., |editors= is multiple names; spoof to display (eds.) annotation
				else
					EditorCount = 2;											-- we don't know but assume |editors= is multiple names; spoof to display (eds.) annotation
				end
			else
				Editors = last_first_list;										-- either an author name list or an empty string
			end

			if 1 == EditorCount and (true == editor_etal or 1 < #e) then		-- only one editor displayed but includes etal then 
				EditorCount = 2;												-- spoof to display (eds.) annotation
			end
		end
		do																		-- now do translators
			control.maximum = #t;												-- number of translators
			Translators = list_people(control, t, false, 'translator');			-- et al not currently supported
		end
		do																		-- now do contributors
			control.maximum = #c;												-- number of contributors
			Contributors = list_people(control, c, false, 'contributor');		-- et al not currently supported
		end
		do																		-- now do authors
			control.maximum , author_etal = get_display_authors_editors (para.displayauthors, #a, 'authors', author_etal);

			if is_set(Coauthors) then											-- if the coauthor field is also used, prevent ampersand and et al. formatting.
				para.lastauthoramp = nil;
				control.maximum = #a + 1;
			end
			
			last_first_list = list_people(control, a, author_etal, 'author');

			if is_set (Authors) then
				Authors, author_etal = name_has_etal (Authors, author_etal, false);	-- find and remove variations on et al.
				if author_etal then
					Authors = Authors .. ' ' .. cfg.messages['et al'];			-- add et al. to authors parameter
				end
			else
				Authors = last_first_list;										-- either an author name list or an empty string
			end
		end																		-- end of do
		
		if is_set(Authors) then
			if is_set(Coauthors) then
				if 'vanc' == NameListFormat then								-- separate authors and coauthors with proper name-list-separator
					Authors = Authors .. ', ' .. Coauthors;
				else
					Authors = Authors .. '; ' .. Coauthors;
				end
			end
		elseif is_set(Coauthors) then											-- coauthors aren't displayed if one of authors=, authorn=, or lastn= isn't specified
			append_error('coauthors_missing_author', {});						-- emit error message
		end
	end
	
	local multiple_editors = EditorCount >1;
	
	local has_contributors = false;
	local NameList = {};														-- holds selected contributor, author, editor name list
	
	if #c > 0 then																-- if there is a contributor list
		NameList = c;															-- select it
		has_contributor = true;
	elseif #a > 0 then															-- or an author list
		NameList = a;
	elseif #e > 0 then															-- or an editor list
		NameList = e;
	end
	
	return Authors, Contributors, Editors, Translators, Contribution, NameList, multiple_editors, has_contributors;
end


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

This function terminates a name list (author, contributor, editor) with a separator character (sepc) and a space
when the last character is not a sepc character or when the last three characters are not sepc followed by two
closing square brackets (close of a wikilink).  When either of these is true, the name_list is terminated with a
single space character.

]]

local function terminate_name_list (name_list, sepc)
	if (string.sub (name_list,-1,-1) == sepc) or (string.sub (name_list,-3,-1) == sepc .. ']]') then	-- if last name in list ends with sepc char
		return name_list .. " ";												-- don't add another
	else
		return name_list .. sepc .. ' ';										-- otherwise terninate the name list
	end
end

--[[--------------------------< F O R M A T _ P E O P L E >------------------------------------------------


]]


local function format_people (Authors, Editors, Contributors, multiple_editors, use_in, sepc)
	if is_set (Authors) then
		Authors = terminate_name_list (Authors, sepc);							-- when no date, terminate with 0 or 1 sepc and a space
		if is_set (Editors) then
			local in_text = " ";
			local post_text = "";
			if use_in then
				in_text = in_text .. " " .. cfg.messages['in'];
				if (sepc ~= '.') then in_text = in_text:lower() end				-- lowercase for cs2
			else
				if multiple_editors then
					post_text = ", " .. cfg.messages['editors'];
				else
					post_text = ", " .. cfg.messages['editor'];
				end
			end 
			Editors = terminate_name_list (Editors .. in_text .. post_text, sepc);	
																				-- terminate with 0 or 1 sepc and a space
		end
		if is_set (Contributors) then											-- book cite and we're citing the intro, preface, etc
			local by_text = sepc .. ' ' .. cfg.messages['by'] .. ' ';
			if (sepc ~= '.') then by_text = by_text:lower() end					-- lowercase for cs2
			Authors = by_text .. Authors;										-- author follows title so tweak it here
			if is_set (Editors) then											-- when Editors make sure that Authors gets terminated
				Authors = terminate_name_list (Authors, sepc);					-- terminate with 0 or 1 sepc and a space
			end
			Contributors = terminate_name_list (Contributors, sepc);			-- terminate with 0 or 1 sepc and a space
		end
	else
		if is_set (Editors) then
			if multiple_editors then
				Editors = Editors .. " (" .. cfg.messages['editors'] .. ")" .. sepc .. " "
			else
				Editors = Editors .. " (" .. cfg.messages['editor'] .. ")" .. sepc .. " "
			end
		end
	end

	return Authors, Editors, Contributors;
end

--[[--------------------------< S E T _ S E L E C T E D _ M O D U L E S >--------------------------------------

Sets local cfg table to same (live or sandbox) as that used by the other modules.

]]

local function set_selected_modules (cfg_table_ptr, utilities_page_ptr, error_page_ptr, links_page_ptr)
	cfg = cfg_table_ptr;
	
	is_set = utilities_page_ptr.is_set;
	in_array = utilities_page_ptr.in_array;
	
	add_maint_cat = error_page_ptr.add_maint_cat;
	append_error = error_page_ptr.append_error;
	select_one = error_page_ptr.select_one;
	
	make_internal_link = links_page_ptr.make_internal_link;
end

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

return {
	get_people = get_people,
	format_people = format_people,
	
	set_selected_modules = set_selected_modules
	}