Open main menu

Changes

Module:Citation/CS1

39,745 bytes added, 05:11, 17 December 2020
m
1 revision imported
local cs1 ={}require('Module:No globals');
--[[--------------------------< F O R W A R D D E C L A R A T I O N S >--------------------------------------
]]
local dates, year_date_check, reformat_dates, date_hyphen_to_dash, -- functions in Module:Citation/CS1/Date_validation date_name_xlateeach of these counts against the Lua upvalue limit
local is_set, in_array, substitute, error_comment, set_error, select_one, -- functions in Module:Citation/CS1/Utilities add_maint_cat, wrap_style, safe_for_italics, is_wikilink, make_wikilink;]]
local z ={}validation; -- tables functions in Module:Citation/CS1/UtilitiesDate_validation
local extract_ids, extract_id_access_levels, build_id_list, is_embargoedutilities; -- functions in Module:Citation/CS1/IdentifiersUtilitieslocal make_coins_title, get_coins_pages, COinSz ={}; -- functions table of tables in Module:Citation/CS1/COinSUtilities
local identifiers; -- functions and tables in Module:Citation/CS1/Identifiers
local metadata; -- functions in Module:Citation/CS1/COinS
local cfg = {}; -- table of configuration tables that are defined in Module:Citation/CS1/Configuration
local whitelist = {}; -- table of tables listing valid template parameter names; defined in Module:Citation/CS1/Whitelist
--[[--------------------------< P A G E S C O P E V A R I A B L E S >--------------------------------------
delare declare variables here that have page-wide scope that are not brought in from other modules; thatare that are created hereand used here
]]
local added_deprecated_cat; -- boolean Boolean flag so that the category is added only oncelocal added_prop_cats = {}; -- list of property categories that have been added to z.properties_catslocal added_vanc_errs; -- boolean Boolean flag so we only emit one Vancouver error / category
local Frame; -- holds the module's frame table
local i = 1;
while i <= count do -- loop through all items in list
if utilities.is_set( list[i] ) then
return list[i]; -- return the first set list member
end
i = i + 1; -- point to next
end
end
 
 
--[[--------------------------< A D D _ P R O P _ C A T >--------------------------------------------------------
 
Adds a category to z.properties_cats using names from the configuration file with additional text if any.
 
foreign_lang_source and foreign_lang_source_2 keys have a language code appended to them so that multiple languages
may be categorized but multiples of the same language are not categorized.
 
added_prop_cats is a table declared in page scope variables above
 
]]
 
local function add_prop_cat (key, arguments)
if not added_prop_cats [key] then
added_prop_cats [key] = true; -- note that we've added this category
key = key:gsub ('(foreign_lang_source_?2?)%a%a%a?', '%1'); -- strip lang code from keyname
table.insert( z.properties_cats, substitute (cfg.prop_cats [key], arguments)); -- make name then add to table
end
end
To prevent duplication, added_vanc_errs is nil until an error message is emitted.
added_vanc_errs is a boolean Boolean declared in page scope variables above
]]
if not added_vanc_errs then
added_vanc_errs = true; -- note that we've added this category
table.insert( z.message_tail, { set_errorutilities.set_message ( 'vancouvererr_vancouver', {source}, true ) } );
end
end
--[[--------------------------< I S _ S C H E M E >------------------------------------------------------------
does this thing that purports to be a uri URI scheme seem to be a valid scheme? The scheme is checked to see if it
is in agreement with http://tools.ietf.org/html/std66#section-3.1 which says:
Scheme names consist of a sequence of characters beginning with a
Single character names are generally reserved; see https://tools.ietf.org/html/draft-ietf-dnsind-iana-dns-01#page-15;
see also [[Single-letter second-level domain]]
list of tldsTLDs: https://www.iana.org/domains/root/db
rfc952 RFC 952 (modified by rfc RFC 1123) requires the first and last character of a hostname to be a letter or a digit. Between
the first and last characters the name may use letters, digits, and the hyphen.
Also allowed are IPv4 addresses. IPv6 not supported
domain is expected to be stripped of any path so that the last character in the last character of the tldTLD. tldis two or more alpha characters. Any preceding '//' (from splitting a url URL with a scheme) will be strippedhere. Perhaps not necessary but retained incase in case it is necessary for IPv4 dot decimal.
There are several tests:
the first character of the whole domain name including subdomains must be a letter or a digit
internationalized domain name (ascii ASCII characters with .xn-- ASCII Compatible Encoding (ACE) prefix xn-- in the tldTLD) see https://tools.ietf.org/html/rfc3490 single-letter/digit second-level domains in the .org , .cash, and .cash today TLDs
q, x, and z SL domains in the .com TLD
i and q SL domains in the .net TLD
IPv4 dot-decimal address format; TLD not allowed
returns true if domain appears to be a proper name and tld TLD or IPv4 address, else false
]=]
domain = domain:gsub ('^//', ''); -- strip '//' from domain name if present; done here so we only have to do it once
if not domain:match ('^[%a%dw]') then -- first character must be letter or digit
return false;
end
-- Do most common case first if domain:match ('%f[%a%d][%a%d][%a%d%-]+[%a%d]%.%a%a+$') then -- three or more character hostname.hostname or hostname.tld return true; elseif domain:match ('%f[^%a%d][%a%d][%a%d%-]+[%a%d]%.xn%-%-[%a%d]+$') then -- internationalized domain name with ACE prefix return true; elseif domain:match ('%f[%a%d][%a%d]%.cash$') then -- one character/digit .cash hostname return true; elseif domain:match ('%f[%a%d][%a%d]%.org$') then -- one character/digit .org hostname return true; elseif domain:match ('%f[%a][qxz]%.com$') then -- assigned one character .com hostname (x.com times out 2015-12-10) return true; elseif domain:match ('%f[%a][iq]%.net$') then -- assigned one character .net hostname (q.net registered but not active 2015-12-10) return true; elseif domainhack to detect things that look like s:match ('%f[%a%d][%a%d]%.%a%a$') then -- one character hostname and cctld (2 chars) return true; elseif domainPage:match ('%f[%a%d][%a%d][%a%d]%.%a%a+$') then -- two character hostname and tld return true; elseif domainTitle where Page:match ('^%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?') then -- IPv4 address return true; elseis namespace at Wikisource
return false;
end
 
local patterns = { -- patterns that look like URLs
'%f[%w][%w][%w%-]+[%w]%.%a%a+$', -- three or more character hostname.hostname or hostname.tld
'%f[%w][%w][%w%-]+[%w]%.xn%-%-[%w]+$', -- internationalized domain name with ACE prefix
'%f[%a][qxz]%.com$', -- assigned one character .com hostname (x.com times out 2015-12-10)
'%f[%a][iq]%.net$', -- assigned one character .net hostname (q.net registered but not active 2015-12-10)
'%f[%w][%w]%.%a%a$', -- one character hostname and ccTLD (2 chars)
'%f[%w][%w][%w]%.%a%a+$', -- two character hostname and TLD
'^%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?', -- IPv4 address
}
 
for _, pattern in ipairs (patterns) do -- loop through the patterns list
if domain:match (pattern) then
return true; -- if a match then we think that this thing that purports to be a URL is a URL
end
end
 
for _, d in ipairs ({'cash', 'company', 'today', 'org'}) do -- look for single letter second level domain names for these top level domains
if domain:match ('%f[%w][%w]%.' .. d) then
return true
end
end
return false; -- no matches, we don't know what this thing is
end
--[[--------------------------< I S _ U R L >------------------------------------------------------------------
returns true if the scheme and domain parts of a url URL appear to be a valid urlURL; else false.
This function is the last step in the validation process. This function is separate because there are cases that
local function is_url (scheme, domain)
if utilities.is_set (scheme) then -- if scheme is set check it and domain
return is_scheme (scheme) and is_domain_name (domain);
else
return is_domain_name (domain); -- scheme not set when url URL is protocol -relative
end
end
--[[--------------------------< S P L I T _ U R L >------------------------------------------------------------
Split a url URL into a scheme, authority indicator, and domain.
First remove Fully Qualified Domain Name terminator (a dot following tldTLD) (if any) and any path(/), query(?) or fragment(#).
If protocol -relative urlURL, return nil scheme and domain else return nil for both scheme and domain.
When not protocol -relative, get scheme, authority indicator, and domain. If there is an authority indicator (one
or more '/' characters immediately following the scheme's colon), make sure that there are only 2.
 
Any URL that does not have news: scheme must have authority indicator (//). TODO: are there other common schemes
like news: that don't use authority indicator?
Strip off any port and path;
url_str = url_str:gsub ('([%a%d])%.?[/%?#].*$', '%1'); -- strip FQDN terminator and path(/), query(?), fragment (#) (the capture prevents false replacement of '//')
if url_str:match ('^//%S*') then -- if there is what appears to be a protocol -relative urlURL
domain = url_str:match ('^//(%S*)')
elseif url_str:match ('%S-:/*%S+') then -- if there is what appears to be a scheme, optional authority indicator, and domain name
scheme, authority, domain = url_str:match ('(%S-:)(/*)(%S+)'); -- extract the scheme, authority indicator, and domain portions
if utilities.is_set (authority) then authority = authority:gsub ('//', '', 1); -- replace place 1 pair of '/' with nothing; if utilities.is_set(authority) then -- if anything left (1 or 3+ '/' where authority should be) then return scheme; -- return scheme only making domain nil which will cause an error message end else if not scheme:match ('^news:') then -- except for news:..., MediaWiki won't link URLs that do not have authority indicator; TODO: a better way to do this test? return scheme; -- return scheme only making domain nil which will cause an error message end
end
domain = domain:gsub ('(%a):%d+', '%1'); -- strip port number if present
--[[--------------------------< L I N K _ P A R A M _ O K >---------------------------------------------------
checks the content of |title-link=, |series-link=, |author-link= , etc . for properly formatted content: no wikilinks, no urlsURLs
Link parameters are to hold the title of a wikipedia Wikipedia article , so none of the WP:TITLESPECIALCHARACTERS are allowed:
# < > [ ] | { } _
except the underscore which is used as a space in wiki urls URLs and # which is used for section links
returns false when the value contains any of these characters.
When there are no illegal characters, this function returns TRUE if value DOES NOT appear to be a valid url URL (the|<param>-link= parameter is ok); else false when value appears to be a valid url URL (the |<param>-link= parameter is NOT ok).
]]
end
scheme, domain = split_url (value); -- get scheme or nil and domain or nil from urlURL; return not is_url (scheme, domain); -- return true if value DOES NOT appear to be a valid urlURL
end
Use link_param_ok() to validate |<param>-link= value and its matching |<title>= value.
|<title>= may be wikilinked wiki-linked but not when |<param>-link= has a value. This function emits an error message when
that condition exists
 
check <link> for inter-language interwiki-link prefix. prefix must be a MediaWiki-recognized language
code and must begin with a colon.
]]
local function link_title_ok (link, lorig, title, torig)
local orig;
  if utilities.is_set (link) then -- don't bother if <param>-link doesn't have a value
if not link_param_ok (link) then -- check |<param>-link= markup
orig = lorig; -- identify the failing link parameter
elseif title:find ('%[%[') then -- check |title= for wikilink markup
orig = torig; -- identify the failing |title= parameter
elseif link:match ('^%a+:') then -- if the link is what looks like an interwiki
local prefix = link:match ('^(%a+):'):lower(); -- get the interwiki prefix
 
if cfg.inter_wiki_map[prefix] then -- if prefix is in the map, must have preceding colon
orig = lorig; -- flag as error
end
end
end
if utilities.is_set (orig) then link = ''; -- unset table.insert( z.message_tail, { set_errorutilities.set_message ( 'bad_paramlinkerr_bad_paramlink', orig)}); -- url URL or wikilink in |title= with |title-link=;
end
return link; -- link if ok, empty string else
end
Determines whether a URL string appears to be valid.
First we test for space characters. If any are found, return false. Then split the url URL into scheme and domainportions, or for protocol -relative (//example.com) urlsURLs, just the domain. Use is_url() to validate the twoportions of the urlURL. If both are valid, or for protocol -relative if domain is valid, return true, else false.
Because it is different from a standard urlURL, and because this module used external_link() to make external links
that work for standard and news: links, we validate newsgroup names here. The specification for a newsgroup name
is at https://tools.ietf.org/html/rfc5536#section-3.1.4
local function check_url( url_str )
if nil == url_str:match ("^%S+$") then -- if there are any spaces in |url=value it can't be a proper urlURL
return false;
end
local scheme, domain;
scheme, domain = split_url (url_str); -- get scheme or nil and domain or nil from urlURL;
if 'news:' == scheme then -- special case for newsgroups
end
return is_url (scheme, domain); -- return true if value appears to be a valid urlURL
end
Return true if a parameter value has a string that begins and ends with square brackets [ and ] and the first
non-space characters following the opening bracket appear to be a urlURL. The test will also find external wikilinksthat use protocol -relative urlsURLs. Also finds bare urlsURLs.
The frontier pattern prevents a match on interwiki -links which are similar to scheme:path urlsURLs. The tests thatfind bracketed urls URLs are required because the parameters that call this test (currently |title=, |chapter=, |work=,
and |publisher=) may have wikilinks and there are articles or redirects like '//Hus' so, while uncommon, |title=[[//Hus]]
is possible as might be [[en://Hus]].
local scheme, domain;
if value:match ('%f[%[]%[%a%S*:%S+.*%]') then -- if ext . wikilink with scheme and domain: [xxxx://yyyyy.zzz]
scheme, domain = split_url (value:match ('%f[%[]%[(%a%S*:%S+).*%]'));
elseif value:match ('%f[%[]%[//%S+.*%]') then -- if protocol -relative ext . wikilink: [//yyyyy.zzz]
scheme, domain = split_url (value:match ('%f[%[]%[(//%S+).*%]'));
elseif value:match ('%a%S*:%S+') then -- if bare url URL with scheme; may have leading or trailing plain text
scheme, domain = split_url (value:match ('(%a%S*:%S+)'));
elseif value:match ('//%S+') then -- if protocol -relative bare urlURL: //yyyyy.zzz; may have leading or trailing plain text
scheme, domain = split_url (value:match ('(//%S+)')); -- what is left should be the domain
else
return false; -- didn't find anything that is obviously a urlURL
end
return is_url (scheme, domain); -- return true if value appears to be a valid urlURL
end
local error_message = '';
for k, v in pairs (parameter_list) do -- for each parameter in the list
if is_parameter_ext_wikilink (v) then -- look at the value; if there is a url URL add an error message if utilities.is_set(error_message) then -- once we've added the first portion of the error message ... error_message=error_message .. ", "; -- ... add a comma space separator
end
error_message=error_message .. "&#124;" .. k .. "="; -- add the failed parameter
end
end
if utilities.is_set (error_message) then -- done looping, if there is an error message, display it table.insert( z.message_tail, { set_errorutilities.set_message ( 'param_has_ext_linkerr_param_has_ext_link', {error_message}, true ) } );
end
end
local function safe_for_url( str )
if str:match( "%[%[.-%]%]" ) ~= nil then
table.insert( z.message_tail, { set_errorutilities.set_message ( 'wikilink_in_urlerr_wikilink_in_url', {}, true ) } );
end
local path;
local base_url;
if not utilities.is_set( label ) then
label = URL;
if utilities.is_set( source ) then error_str = set_errorutilities.set_message ( 'bare_url_missing_titleerr_bare_url_missing_title', { utilities.wrap_style ('parameter', source) }, false, " " );
else
error( cfg.messages["bare_url_no_origin"] );
end
if not check_url( URL ) then
error_str = set_errorutilities.set_message ( 'bad_urlerr_bad_url', {utilities.wrap_style ('parameter', source)}, false, " " ) .. error_str;
end
domain, path = URL:match ('^([/%.%-%+:%a%d]+)([/%?#].*)$'); -- split the url URL into scheme plus domain and path
if path then -- if there is a path portion
path = path:gsub ('[%[%]]', {['[']='%5b',[']']='%5d'}); -- replace '[' and ']' with their percent -encoded values URL=table.concat ({domain.., path}); -- and reassemble
end
if is_set (access) then -- access level (subscription, registration, limited)
label = safe_for_url (label); -- replace square brackets and newlines
base_url = table.concat ( -- assemble external link with access signal { '<span class="plainlinks[">[', -- opening css and url markup URL, -- the url ' '" ", -- the required space safe_for_url (label), '<span style="padding]" }); -left:0.15em">', -assemble a wiki- signal spacing cssmarkup URL  cfg if utilities.presentation[is_set (access], ) then -- the appropriate icon '</span>'access level (subscription, registration, -- close signal spacing span ']</span>' -- close url markup and plain links span }limited); else base_url = tableutilities.concatsubstitute (cfg.presentation['ext-link-access-signal'], { "cfg.presentation["access].class, URLcfg.presentation[access].title, " ", safe_for_url( label ), "]" base_url}); -- no signal markupadd the appropriate icon
end
return table.concat({ base_url, error_str });
end
parameters in the citation.
added_deprecated_cat is a boolean Boolean declared in page scope variables above
]]
if not added_deprecated_cat then
added_deprecated_cat = true; -- note that we've added this category
table.insert( z.message_tail, { set_errorutilities.set_message ( 'deprecated_paramserr_deprecated_params', {name}, true ) } ); -- add error message
end
end
--[=[-------------------------< K E R N _ Q U O T E S >--------------------------------------------------------
Apply kerning to open the space between the quote mark provided by the Module module and a leading or trailing quote
mark contained in a |title= or |chapter= parameter's value.
"'Unkerned title with leading and trailing single quote marks'"
" 'Kerned title with leading and trailing single quote marks' " (in real life the kerning isn't as wide as this example)
Double single quotes (italic or bold wikimarkupwiki-markup) are not kerned.
Replaces unicode quotemarks Unicode quote marks in plain text or in the label portion of a [[L|D]] style wikilink with typewriter
quote marks regardless of the need for kerning. Unicode quote marks are not replaced in simple [[D]] wikilinks.
Call this function for chapter titles, for website titles, etc.; not for book titles.
]=]
local function kern_quotes (str)
local cap=''; local cap2='';
local wl_type, label, link;
wl_type, label, link = utilities.is_wikilink (str); -- wl_type is: 0, no wl (text in label variable); 1, [[D]]; 2, [[L|D]]
if 1 == wl_type then -- [[D]] simple wikilink with or without quote marks
if mw.ustring.match (str, '%[%[[\"“”\'‘’].+[\"“”\'‘’]%]%]') then -- leading and trailing quote marks
str = utilities.substitute (cfg.presentation['kern-wl-both'], str);
elseif mw.ustring.match (str, '%[%[[\"“”\'‘’].+%]%]') then -- leading quote marks
str = utilities.substitute (cfg.presentation['kern-wl-left'], str);
elseif mw.ustring.match (str, '%[%[.+[\"“”\'‘’]%]%]') then -- trailing quote marks
str = utilities.substitute (cfg.presentation['kern-wl-right'], str);
end
else -- plain text or [[L|D]]; text in label variable
label= mw.ustring.gsub (label, '[“”]', '\"'); -- replace “” (U+201C & U+201D) with " (typewriter double quote mark) label= mw.ustring.gsub (label, '[‘’]', '\''); -- replace ‘’ (U+2018 & U+2019) with ' (typewriter single quote mark)
cap, cap2 = mw.ustring.match (label, "^([\"\'])([^\'].+)"); -- match leading double or single quote but not doubled single quotes (italic markup)
if utilities.is_set (cap) then label = utilities.substitute (cfg.presentation['kern-left'], {cap, cap2});
end
cap, cap2 = mw.ustring.match (label, "^(.+[^\'])([\"\'])$") -- match trailing double or single quote but not doubled single quotes (italic markup)
if utilities.is_set (cap) then label = utilities.substitute (cfg.presentation['kern-right'], {cap, cap2});
end
if 2 == wl_type then
str = utilities.make_wikilink (link, label); -- reassemble the wikilink
else
str = label;
--[[--------------------------< F O R M A T _ S C R I P T _ V A L U E >----------------------------------------
|script-title= holds title parameters that are not written in Latin -based scripts: Chinese, Japanese, Arabic, Hebrew, etc. These scripts should
not be italicized and may be written right-to-left. The value supplied by |script-title= is concatenated onto Title after Title has been wrapped
in italic markup.
Regardless of language, all values provided by |script-title= are wrapped in <bdi>...</bdi> tags to isolate rtl RTL languages from the English left to right.
|script-title= provides a unique feature. The value in |script-title= may be prefixed with a two-character ISO639ISO 639-1 language code and a colon:
|script-title=ja:*** *** (where * represents a Japanese character)
Spaces between the two-character code and the colon and the colon and the first script character are allowed:
Spaces preceding the prefix are allowed: |script-title = ja:*** ***
The prefix is checked for validity. If it is a valid ISO639ISO 639-1 language code, the lang attribute (lang="ja") is added to the <bdi> tag so that browsers can
know the language the tag contains. This may help the browser render the script more correctly. If the prefix is invalid, the lang attribute
is not added. At this time there is no error message for this condition.
Supports |script-title= and , |script-chapter=, |script-<periodical>=
TODO: error messages when prefix is invalid ISO639-1 code; when script_value has prefix but no script;
]]
local function format_script_value (script_value, script_param)
local lang=''; -- initialize to empty string
local name;
if script_value:match('^%l%l%l?%s*:') then -- if first 3 or 4 non-space characters are script language prefix lang = script_value:match('^(%l%l%l?)%s*:%s*%S.*'); -- get the language prefix or nil if there is no script if not utilities.is_set (lang) then table.insert( z.message_tail, { utilities.set_message ( 'err_script_parameter', {script_param, 'missing title part'}, true ) } ); -- prefix without 'title'; add error message
return ''; -- script_value was just the prefix so return empty string
end
-- if we get this far we have prefix and script
name = cfg.lang_code_remap[lang] or mw.language.fetchLanguageName( lang, "en" cfg.this_wiki_code ); -- get language name so that we can use it to categorize if utilities.is_set (name) then -- is prefix a proper ISO 639-1 language code? script_value = script_value:gsub ('^%l%l+%s*:%s*', ''); -- strip prefix from script
-- is prefix one of these language codes?
if utilities.in_array (lang, cfg.script_lang_codes) then utilities.add_prop_cat ('script_with_name', {name, lang})
else
add_prop_cat table.insert(z.message_tail, { utilities.set_message ( 'err_script_parameter', {script_param, 'scriptunknown language code'}, true )} ); -- unknown script-language; add error message
end
lang = ' lang="' .. lang .. '" '; -- convert prefix into a lang attribute
else
table.insert( z.message_tail, { utilities.set_message ( 'err_script_parameter', {script_param, 'invalid language code'}, true ) } ); -- invalid language code; add error message
lang = ''; -- invalid so set lang to empty string
end
else
table.insert( z.message_tail, { utilities.set_message ( 'err_script_parameter', {script_param, 'missing prefix'}, true ) } ); -- no language code prefix; add error message
end
script_value = utilities.substitute (cfg.presentation['bdi'], {lang, script_value}); -- isolate in case script is rtlRTL
return script_value;
]]
local function script_concatenate (title, script, script_param) if utilities.is_set (script) then script = format_script_value (script, script_param); -- <bdi> tags, lang atributeattribute, categorization, etc.; returns empty string on error if utilities.is_set (script) then
title = title .. ' ' .. script; -- concatenate title and script title
end
local function wrap_msg (key, str, lower)
if not utilities.is_set( str ) then
return "";
end
local msg;
msg = cfg.messages[key]:lower(); -- set the message to lower case before
return utilities.substitute( msg, str ); -- including template text
else
return utilities.substitute( cfg.messages[key], str );
end
end
--[[--------------------------< F W I K I S O U R M A T _ C H A P T E _ U R L _ T I T L M A K E >---------------------------------------- Makes a Wikisource URL from Wikisource interwiki-link. Returns the URL and appropriate label; nil else.
Format str is the four chapter parameters: value assigned to |script-chapter=, (or aliases) or |chaptertitle=, or |transtitle-chapterlink=, and |chapter-url= into a single Chapter meta-parameter (chapter_url_source used for error messages).
]]
local function format_chapter_title wikisource_url_make (scriptchapter, chapter, transchapter, chapterurl, chapter_url_source, no_quotes, accessstr) local chapter_error = ''wl_type, D, L; local ws_url, ws_label; if not is_set local wikisource_prefix = table.concat (chapter) then chapter = {'https://'; -- to be safe for concatenation else if false == no_quotes then chapter = kern_quotes (chapter); -- if necessary, separate chapter titlecfg.this_wiki_code, 's leading and trailing quote marks from Module provided quote marks chapter = wrap_style (.wikisource.org/wiki/'quoted-title', chapter}); end end
chapter wl_type, D, L = script_concatenate utilities.is_wikilink (chapter, scriptchapterstr) ; -- <bdi> tagswl_type is 0 (not a wikilink), lang atribute1 (simple wikilink), categorization, etc; must be done after title is wrapped2 (complex wikilink)
if 0 == wl_type then -- not a wikilink; might be from |title-link= str = D:match ('^[Ww]ikisource:(.+)') or D:match ('^[Ss]:(.+)'); -- article title from interwiki link with long-form or short-form namespace if utilities.is_set (transchapterstr) then transchapter ws_url = wrap_style table.concat ('trans{ -- build a Wikisource URL wikisource_prefix, -- prefix str, -quoted-article title }); ws_label = str; -- label for the URL end elseif 1 == wl_type then -- simple wikilink: [[Wikisource:ws article]] str = D:match ('^[Ww]ikisource:(.+)') or D:match ('^[Ss]:(.+)', transchapter); -- article title from interwiki link with long-form or short-form namespace if utilities.is_set (chapterstr) then chapter ws_url = chapter table.concat ({ -- build a Wikisource URL wikisource_prefix, -- prefix str, -- article title }); ws_label = str; -- label for the URL end elseif 2 == wl_type then -- non-so-simple wikilink: [[Wikisource:ws article|displayed text]] ([[L|D]]) str = L:match ('^[Ww]ikisource:(. +)' ) or L:match (' ^[Ss]:(.. transchapter+)'); else --article title from interwiki link with long- here when transchapter without chapter form or scriptshort-chapterform namespace if utilities.is_set (str) then chapter ws_label = transchapterD; -- get ws article name from display portion of interwiki link chapter_error ws_url = ' ' .table. set_error concat ('trans_missing_title'{ -- build a Wikisource URL wikisource_prefix, -- prefix str, {'chapter' -- article title without namespace from link portion of wikilink });
end
end
if is_set ws_url then ws_url = mw.uri.encode (chapterurlws_url, 'WIKI') then; -- make a usable URL chapter ws_url = external_link ws_url:gsub (chapterurl'%%23', chapter, chapter_url_source, access'#'); -- adds bare_url_missing_title error if appropriateundo percent encoding of fragment marker
end
return chapter .. chapter_errorws_url, ws_label, L or D; -- return proper URL or nil and a label or nil
end
--[[--------------------------< H F O R M A S T _ P E R I N V O D I S I B L E _ C H A R S L >--------------------------------------------
This function searches Format the three periodical parameters: |script-<periodical>=, |<periodical>=, and |trans-<periodical>= into a single Periodical meta-parameter's value for nonprintable or invisible characters. The search stops at thefirst match.
This function will detect the visible replacement character when it is part of the wikisource.]]
Detects but ignores nowiki and math stripmarkers. Also detects other named stripmarkers local function format_periodical (galleryscript_periodical, mathscript_periodical_source, preperiodical, reftrans_periodical)and identifies them with a slightly different error message. See also coins_cleanup(). local periodical_error = '';
Detects but ignores the character pattern that results from the transclusion of {{'}} templates if not utilitiesOutput of this function is an error message that identifies the character or the Unicode group, or the stripmarkerthat was detected along with its position is_set (or, for multi-byte characters, the position of its first byteperiodical) in thethenparameter value. ]] local function has_invisible_chars (param, v) local position periodical = ''; -- position of invisible char or starting position of stripmarker local dummy; -- end of matching string; not used but required to hold end position when a capture is returnedbe safe for concatenation local capture; -- used by stripmarker detection to hold name of the stripmarkerelse local i periodical =1; local stripmarker, apostrophe; capture = stringutilities.match wrap_style (v, '[%w%p ]*italic-title', periodical); -- Test for values that are simple ASCII text and bypass other tests if true if capture == v then -- if same there are no unicode characters return;style
end
while cfg.invisible_chars[i] do local char=cfg.invisible_chars[i][1] -- the character or group name local pattern=cfg.invisible_chars[i][2] -- the pattern used to find it position, dummy, capture periodical = mw.ustring.find script_concatenate (vperiodical, pattern) -- see if the parameter value contains characters that match the pattern if position and (char == 'zero width joiner') then -- if we found a zero width joiner character if mw.ustring.find (vscript_periodical, cfg.indic_scriptscript_periodical_source) then -- its ok if one of the indic scripts position = nil; -- unset position end end if position then if 'nowiki' == capture or 'math' == capture then -- nowiki<bdi> tags, lang attribute, math stripmarker (not an error condition) stripmarker = true; -- set a flag elseif true == stripmarker and 'delete' == char then -- because stripmakers begin and end with the delete charcategorization, assume that we've found one end of a stripmarker position = nil; -- unset else local err_msg; if capture then err_msg = capture .. ' ' .. char; else err_msg = char .. ' ' .etc. 'character'; endmust be done after title is wrapped
table if utilities.insertis_set ( ztrans_periodical) then trans_periodical = utilities.message_tail, { set_errorwrap_style ( 'invisible_chartrans-italic-title', {err_msg, wrap_style trans_periodical); if utilities.is_set (periodical) then periodical = periodical .. 'parameter', param), position}, true ) } ).. trans_periodical; else -- add error messagehere when trans-periodical without periodical or script-periodical return periodical = trans_periodical; -- and done with this parameter endperiodical_error = ' ' .. utilities.set_message ('err_trans_missing_title', {'periodical'});
end
i=i+1; -- bump our index
end
 
return periodical .. periodical_error;
end
--[[--------------------------< A F O R G U M E N A T _ W R C H A P P T E R _ T I T L E >----------------------------------------------
Argument wrapper. This function provides support for argument mapping defined in Format the configuration file so thatfour chapter parameters: |script-chapter=, |chapter=, |trans-chapter=, and |chapter-url= into a single chapter meta-multiple names can be transparently aliased to single internal variableparameter (chapter_url_source used for error messages).
]]
local function argument_wrapperformat_chapter_title ( args script_chapter, script_chapter_source, chapter, chapter_source, trans_chapter, trans_chapter_source, chapter_url, chapter_url_source, no_quotes, access) local origin chapter_error = '';  local ws_url, ws_label, L = wikisource_url_make (chapter); -- make a wikisource URL and label from a wikisource interwiki link if ws_url then ws_label = ws_label:gsub ('_', ''); -- replace underscore separaters with space characters chapter = ws_label; end  if not utilities.is_set (chapter) then chapter = ''; -- to be safe for concatenation else if false == no_quotes then chapter = kern_quotes (chapter); -- if necessary, separate chapter title's leading and trailing quote marks from module provided quote marks chapter = utilities.wrap_style ('quoted-title', chapter); end end  chapter = script_concatenate (chapter, script_chapter, script_chapter_source); -- <bdi> tags, lang attribute, categorization, etc.; must be done after title is wrapped  if utilities.is_set (chapter_url) then chapter = external_link (chapter_url, chapter, chapter_url_source, access); -- adds bare_url_missing_title error if appropriate elseif ws_url then chapter = external_link (ws_url, chapter .. '&nbsp;', 'ws link in chapter'); -- adds bare_url_missing_title error if appropriate; space char to move icon away from chap text; TODO: better way to do this? chapter = utilities.substitute (cfg.presentation['interwiki-icon'], {cfg.presentation['class-wikisource'], L, chapter}); end  return setmetatableif utilities.is_set ({trans_chapter) then ORIGIN trans_chapter = functionutilities.wrap_style ( self'trans-quoted-title', k trans_chapter); if utilities.is_set (chapter) then local dummy chapter = self[k]chapter .. ' ' .. trans_chapter; else --force the variable to be loadedhere when trans_chapter without chapter or script-chapter chapter = trans_chapter; chapter_source = trans_chapter_source:match ('trans%-?(.+)'); -- when no chapter, get matching name from trans-<param> return origin[k]chapter_error = ' ' .. utilities.set_message ('err_trans_missing_title', {chapter_source});
end
},end  { __index = function ( tbl, k ) if origin[k] ~= nil then return nil; end local args, list, v = args, cfg.aliases[k]; if type( list ) == 'table' then v, origin[k] = select_one( args, list, 'redundant_parameters' ); if origin[k] == nil then origin[k] = ''; -- Empty string, not nil end elseif list ~= nil then v, origin[k] = args[list], list; else -- maybe let through instead of raising an error? -- v, origin[k] = args[k], k; error( cfgchapter .messages['unknown_argument_map'] ); end -- Empty strings, not nil; if v == nil then v = cfg.defaults[k] or ''; origin[k] = ''; end tbl = rawset( tbl, k, v ); return v; end, })chapter_error;
end
--[[--------------------------< H A S _ I N V A I S I B L I D E _ C H A T E R S >--------------------------------------------------------------
Looks for This function searches a parameter's name in one of several whitelistsvalue for non-printable or invisible characters. The search stops at thefirst match.
Parameters in This function will detect the whitelist can have three values: true - active, supported parameters false - deprecated, supported parameters nil - unsupported parameters ]]visible replacement character when it is part of the Wikisource.
local function validateDetects but ignores nowiki and math stripmarkers. Also detects other named stripmarkers ( namegallery, cite_class math, pre, ref) local name = tostringand identifies them with a slightly different error message. See also coins_cleanup( name );. local state; Output of this function is an error message that identifies the character or the Unicode group, or the stripmarker if in_array that was detected along with its position (cite_classor, {'arxiv', 'biorxiv'for multi-byte characters, 'citeseerx'}the position of its first byte) then -- limited in theparameter sets allowed for these templates state = whitelistvalue.limited_basic_arguments[ name ]; if true == state then return true; end -- valid actively supported parameter if false == state then deprecated_parameter (name); -- parameter is deprecated but still supported return true; end
if 'arxiv' == cite_class then -- basic parameters unique to these templates state = whitelist.arxiv_basic_arguments[name]; end if 'biorxiv' == cite_class then state = whitelist.biorxiv_basic_arguments[name]; end if 'citeseerx' == cite_class then state = whitelist.citeseerx_basic_arguments[name]; end
if true local function has_invisible_chars (param, v) local position == state then return true''; end -- valid actively supported parameter if false == state thenposition of invisible char or starting position of stripmarker deprecated_parameter (name) local dummy; -- parameter end of matching string; not used but required to hold end position when a capture is deprecated but still supportedreturned return true local capture; end -- limited enumerated parameters listused by stripmarker detection to hold name of the stripmarker name local i = name:gsub( "%d+"1; local stripmarker, "#" )apostrophe; -- replace digit(s) with # (last25 becomes last#) state capture = whiteliststring.limited_numbered_argumentsmatch (v, '[ name %w%p ]*'); -- test for values that are simple ASCII text and bypass other tests if true == state then return true; end -- valid actively supported parameter if false capture == state v then deprecated_parameter (name); -- parameter is deprecated but still supportedif same there are no Unicode characters return true; end
while cfg.invisible_chars[i] do return false; local char = cfg.invisible_chars[i][1] -- not supported because not found the character or group name is set local pattern = cfg.invisible_chars[i][2] -- the pattern used to nilfind it end position, dummy, capture = mw.ustring.find (v, pattern) -- end limited see if the parametervalue contains characters that match the pattern if position and (char == 'zero width joiner') then -set templates- if we found a zero-width joiner character if mw.ustring.find (v, cfg.indic_script) then -- it's ok if one of the Indic scripts position = nil; -- unset position end end
state if position then if 'nowiki' == capture or 'math' == capture or -- nowiki and math stripmarkers (not an error condition) ('templatestyles' == whitelistcapture and utilities.basic_arguments[ name ]; in_array (param, {'id', 'quote'})) then -- all other templates; all normal templatestyles stripmarker allowed in these parameters allowed if true stripmarker == state then return true; end -- valid actively supported parameterset a flag if false elseif true == stripmarker and 'delete' == state char then -- because stripmakers begin and end with the delete char, assume that we've found one end of a stripmarker deprecated_parameter (name) position = nil; -- parameter is deprecated but still supportedunset else return true local err_msg; end if capture then -- all enumerated parameters allowed name err_msg = name:gsub( "%d+", "#" )capture .. ' ' .. char; -- replace digit(s) with # (last25 becomes last# state else err_msg = whitelistchar .numbered_arguments[ name ]. ' ' .. 'character'; end
if table.insert( z.message_tail, { utilities.set_message ( 'err_invisible_char', {err_msg, utilities.wrap_style ('parameter', param), position}, true == state then ) } ); -- add error message return true; end -- valid actively supported and done with this parameter if false == state then end end deprecated_parameter (name)i = i+1; -- parameter is deprecated but still supported return true;bump our index
end
return false; -- not supported because not found or name is set to nil
end
--[[--------------------------< A R G U M E N O T _ W R A P _ D A T P E R >--------------------------------------------------------
When date is YYYY-MM-DD format wrap in nowrap span: <span ...>YYYY-MM-DD</span>Argument wrapper. When date is DD MMMM YYYY or isThis function provides support for argument mapping defined in the configuration file so thatMMMM DD, YYYY then wrap in nowrap span: <span ...>DD MMMM</span> YYYY or <span ..multiple names can be transparently aliased to single internal variable.>MMMM DD,</span> YYYY
DOES NOT yet support MMMM YYYY or any of the date ranges.]]
]] local function nowrap_date argument_wrapper (dateargs ) local cap=''; local cap2origin ='';  if date:match("^%d%d%d%d%-%d%d%-%d%d$") then date = substitute (cfg.presentation['nowrap1'], date){};
elseif date:matchreturn setmetatable("^%a+%s*%d%d?,%s+%d%d%d%d$") or date:match ("^%d%d?%s*%a+%s+%d%d%d%d$") then{ cap, cap2 ORIGIN = string.match function (dateself, "^(.*k )%s+(%d%d%d%d)$"); date local dummy = substitute (cfg.presentationself['nowrap2'k], {cap, cap2}); end return date;end  --[[--------------------------< S E T _ T I T L E T Y P E >---------------------------------------------------- This function sets default title types (equivalent to the citation including |type=<default value>) for those templates that have defaults.Also handles force the special case where it is desirable variable to omit the title type from the rendered citation (|type=none)be loaded] return origin[klocal function set_titletype (cite_class, title_type) if is_set(title_type) then if "none" == title_type then title_type = ""; -- if |type=none then type parameter not displayed
end
}, { __index = function ( tbl, k ) if origin[k] ~= nil then return title_typenil; end local args, list, v = args, cfg.aliases[k]; -- if |type( list ) == 'table' then v, origin[k] = utilities.select_one ( args, list, 'err_redundant_parameters' ); if origin[k] == nil then origin[k] = has been set to any other value use that value''; -- Empty string, not nil end elseif list ~= nil then v, origin[k] = args[list], list; else -- maybe let through instead of raising an error? -- v, origin[k] = args[k], k; return error( cfg.title_types messages[cite_class'unknown_argument_map'] or .. ': '.. k); end -- set templateEmpty strings, not nil; if v == nil then v = ''s default title type; else empty string for concatenation origin[k] = ''; end tbl = rawset( tbl, k, v ); return v; end, });
end
--[[--------------------------< H Y P H E N _ T O W R A P _ D A S H T E >-------------------------------------------------------- When date is YYYY-MM-DD format wrap in nowrap span: <span ...>YYYY-MM-DD</span>. When date is DD MMMM YYYY or isMMMM DD, YYYY then wrap in nowrap span: <span ...>DD MMMM</span> YYYY or <span ...>MMMM DD,</span> YYYY
Converts a hyphen to a dashDOES NOT yet support MMMM YYYY or any of the date ranges.
]]
local function hyphen_to_dashnowrap_date ( str date) local cap = ''; local cap2 = '';  if not is_set(str) or strdate:match( "[^%d%d%d%d%-%d%d%-%[d%]{}<>]d$" ) ~= nil then return strdate = utilities.substitute (cfg.presentation['nowrap1'], date); end return strelseif date:match("^%a+%s*%d%d?,%s+%d%d%d%d$") or date:gsubmatch ("^%d%d?%s*%a+%s+%d%d%d%d$") then cap, cap2 = string.match (date, "^(.*)%s+( %d%d%d%d)$"); date = utilities.substitute (cfg.presentation['-nowrap2'], '–' {cap, cap2}); end return date;
end
--[[--------------------------< S A F E T _ J O T I N T L E T Y P E >------------------------------------------------------------
Joins a sequence of strings together while checking This function sets default title types (equivalent to the citation including |type=<default value>) for duplicate separation charactersthose templates that have defaults.Also handles the special case where it is desirable to omit the title type from the rendered citation (|type=none).
]]
local function safe_joinset_titletype ( tblcite_class, duplicate_char title_type) --[[ Note: we use string functions here, rather than ustring functionsif utilities.is_set (title_type) then This has considerably faster performance and should work correctly as long as the duplicate_char is strict ASCII if 'none' == cfg. The strings in tbl may be ASCII or UTF8. ]keywords_xlate[title_type]then local str title_type = ''; -- the output string local comp = ''; -- what does 'comp' mean? local end_chr = ''; local trim; for _, value in ipairs( tbl ) do if value =|type= nil none then value = ''; endtype parameter not displayed end if str == '' then return title_type; -- if output string is empty str |type= has been set to any other value; -- assign use that value to it (first time through the loop) elseif value ~= '' then end  if value:sub(1,1) == return cfg.title_types [cite_class] or '<' then ; -- Special case of values enclosed in spans and other markup. comp = value:gsub( "%b<>", "" )set template's default title type; -- remove html markup (<span>else empty string</span> -> string)for concatenation elseend comp = value; end -- typically duplicate_char is sepc if comp:sub(1,1) == duplicate_char then [[----------- is first charactier same as duplicate_char? why test first character? -- Because individual string segments often (always?) begin with terminal punct for th -- preceding segment: 'First element' .. 'sepc next element' .. etc? trim = false; end_chr = str:sub(-1,-1); -- get the last character of the output string -- str = str .. "<HERE(enchr=" .. end_chr.. ")" -- debug stuff? if end_chr == duplicate_char then -- if same as separator str = str:sub(1,-2); < H Y P H E N _ T O _ D A S H >-- remove it elseif end_chr == "'" then -- if it might be wikimarkup if str:sub(-3,-1) == duplicate_char .. "''" then -- if last three chars of str are sepc'' str = str:sub(1, -4) .. "''"; -- remove them and add back '' elseif str:sub(-5,-1) == duplicate_char .. "]]''" then -- if last five chars of str are sepc]]'' trim = true; -- why? why do this and next differently from previous? elseif str:sub(-4,-1) == duplicate_char .. "]''" then -- if last four chars of str are sepc]'' trim = true; -- same question end elseif end_chr == "]" then -- if it might be wikimarkup if str:sub(-3,-1) == duplicate_char .. "]]" then -- if last three chars of str are sepc]] wikilink trim = true; elseif str:sub(-3,-1) == duplicate_char .. '"]' then -- if last three chars of str are sepc"] quoted external link trim = true; elseif str:sub(-2,-1) == duplicate_char .. "]" then -- if last two chars of str are sepc] external link trim = true; elseif str:sub(-4,-1) == duplicate_char .. "'']" then -- normal case when |url=something & |title=Title. trim = true; end elseif end_chr == " " then -- if last char of output string is a space if str:sub(-2,-1) == duplicate_char .. " " then -- if last two chars of str are <sepc><space> str = str:sub(1,-3); -- remove them both end end
if trim then if value ~= comp then -- value does not equal comp when value contains html markup local dup2 = duplicate_charConverts a hyphen to a dash under certain conditions. The hyphen must separate like items;unlike items are if dup2returned unmodified. These forms are modified:match letter - letter ( "%A" - B) then dup2 = "%" .. dup2; end digit -digit (4- if duplicate_char not a letter then escape it5) value = value:gsub digit separator digit - digit separator digit ( "(%b<>)" 4.1-4. dup2, "%1", 5 or 4-1 -4-5) letterdigit -letterdigit (A1- remove duplicate_char if it follows html markup else value = value:subA5) ( 2, an optional separator between letter and digit is supported – a.1-a.5 or a-1 -a-5); digitletter -digitletter (5a - remove duplicate_char when it 5d) (an optional separator between letter and digit is first character end end end str = str supported – 5.a-5. value; d or 5-a-5-add it to the output string end end return str;end d)
any other forms are returned unmodified.
str may be a comma-or semicolon-[[--------------------------< I S _ S U F F I X >------------------------------------------------------------ returns true is suffix is properly formed Jr, Sr, or ordinal in the range 2–9. Puncutation not allowed.separated list
]]
local function is_suffix hyphen_to_dash(suffixstr ) if in_array not utilities.is_set (suffix, {'Jr', 'Sr', '2nd', '3rd'}) or suffix:match ('^%dth$'str) then return truestr;
end
return false;
end
local accept; -- Boolean
str, accept = utilities.has_accept_as_written (str); -- remove accept-this-as-written markup when it wraps all of str
if accept then
return str; -- when markup removed, nothing to do, we're done
end
-- str = str:gsub ('&[nm]dash;', {['&ndash;'] = '–', ['&mdash;'] = '—'}); --replace &mdash; and &ndash; entities with their characters; semicolon mucks up the text.split str = str:gsub ('&#45;', '-'); --replace HTML numeric entity with hyphen character str = str:gsub ('&nbsp;', ' '); --replace &nbsp; entity with generic keyboard space character local out = {}; local list = mw.text.split (str, '%s*[,;]%s*'); -------------------< I S _ G O O D _ V A N C _ N A M E >--------------------------------------------split str at comma or semicolon separators if there are any
For Vancouver Style for _, author/editor names are supposed to be rendered item in Latin ipairs (read ASCIIlist) charactersdo -- for each item in the list if mw.ustring.match (item, '^%w*[%.%-]?%w+%s*[%-–—]%s*%w*[%. When %-]?%w+$') then -- if a namehyphenated range or has endash or emdash separatorsuses characters that contain diacritical marks, those characters are to converted to the corresponding Latin character if item:match ('^%a+[%.%-]?%d+%s*%-%s*%a+[%.%-]?%d+$') or -- letterdigit hyphen letterdigit (optional separator between letter and digit)When item:match ('^%d+[%.%-]?%a name is written using +%s*%-%s*%d+[%.%-]?%a non+$') or -Latin alphabet - digitletter hyphen digitletter (optional separator between digit and letter) item:match ('^%d+[%.%-]%d+%s*%-%s*%d+[%.%-]%d+$') or logogram -- digit separator digit hyphen digit separator digit item:match ('^%d+%s*%-%s*%d+$') or -- digit hyphen digit item:match ('^%a+%s*%-%s*%a+$') then -- letter hyphen letter item = item:gsub ('(%w*[%.%-]?%w+)%s*%-%s*(%w*[%.%-]?%w+)', '%1–%2'); -- replace hyphen, that name is to be transliterated into Latin remove extraneous space characters else item = mw.ustring.gsub (item, '%s*[–—]%s*', '–'); -- for endash or emdash separated ranges, replace em with en, remove extraneous whitespace end endThese things are not currently possible in item = utilities.has_accept_as_written (item); -- remove accept-this module so are left -as-written markup when it wraps all of str table.insert (out, item); -- add the (possibly modified) item to the editor to do.output table end
This test allows |first= and |last= names to contain any of the letters defined in the four Unicode Latin character sets [http://wwwreturn table.unicode.org/charts/PDF/U0000.pdf C0 Controls and Basic Latin] 0041–005Aconcat (out, ', 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–017Fconcatenate the output table into a comma separated string [http://www.unicode.org/charts/PDF/U0180.pdf Latin Extended-B] 0180–01BF, 01C4–024Fend
|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
This original test: if nil == mw.ustring.find (last, "^--[[--------------------------< S AF E _ J O I N >----------------------------------------------Za--ÖØ-öø-ƿDŽ-ɏ%-%s%']*$") or nil == mw.ustring.find (first, "^[A-Za--ÖØ-öø-ƿDŽ-ɏ%-%s%'%.]+[2-6%a]*$") thenwas written ouside of the code editor and pasted here because the code editor gets confused between character insertion point and cursor position.The test has been rewritten to use decimal character escape sequence for the individual bytes of the unicode characters so that it is not necessaryto use an external editor to maintain this code.
\195\128-\195\150 – À-Ö (U+00C0–U+00D6 – C0 controls) \195\152-\195\182 – Ø-ö (U+00D8-U+00F6 – C0 controls) \195\184-\198\191 – ø-ƿ (U+00F8-U+01BF – C0 controls, Latin extended A & B) \199\132-\201\143 – DŽ-ɏ (U+01C4-U+024F – Latin extended B)Joins a sequence of strings together while checking for duplicate separation characters.
]]
local function is_good_vanc_name safe_join(lasttbl, firstduplicate_char ) local first, suffix f = first:match ('(.-),?%s*([%dJS][%drndth]+)%.?$') or first{}; -- if first has something that looks like create a generational suffix, get itfunction table appropriate to type of 'duplicate character'  if is_set (suffix) 1 == #duplicate_char then -- for single byte ASCII characters use the string library functions if not is_suffix (suffix) then f.gsub = string.gsub add_vanc_error ('suffix');f.match = string.match return false; f.sub = string.sub else --for multi- not a name with an appropriate suffixbyte characters use the ustring library functions f.gsub = mw.ustring.gsub f.match = mw.ustring.match f.sub = mw.ustring.sub
end
end
if nil == mw.ustring.find (last, "^[A-Za-z\195\128-\195\150\195\152-\195\182\195\184-\198\191\199\132-\201\143%-%s%']*$") or
nil == mw.ustring.find (first, "^[A-Za-z\195\128-\195\150\195\152-\195\182\195\184-\198\191\199\132-\201\143%-%s%'%.]*$") then
add_vanc_error ('non-Latin character');
return false; -- not a string of latin characters; Vancouver requires Romanization
end;
return true;
end
local str = ''; -- the output string
local comp = ''; -- what does 'comp' mean?
local end_chr = '';
local trim;
for _, value in ipairs( tbl ) do
if value == nil then value = ''; end
if str == '' then -- if output string is empty
str = value; -- assign value to it (first time through the loop)
elseif value ~= '' then
if value:sub(1, 1) == '<' then -- special case of values enclosed in spans and other markup.
comp = value:gsub( "%b<>", "" ); -- remove HTML markup (<span>string</span> -> string)
else
comp = value;
end
-- typically duplicate_char is sepc
if f.sub(comp, 1, 1) == duplicate_char then -- is first character same as duplicate_char? why test first character?
-- Because individual string segments often (always?) begin with terminal punct for the
-- preceding segment: 'First element' .. 'sepc next element' .. etc.?
trim = false;
end_chr = f.sub(str, -1, -1); -- get the last character of the output string
-- str = str .. "<HERE(enchr=" .. end_chr .. ")" -- debug stuff?
if end_chr == duplicate_char then -- if same as separator
str = f.sub(str, 1, -2); -- remove it
elseif end_chr == "'" then -- if it might be wiki-markup
if f.sub(str, -3, -1) == duplicate_char .. "''" then -- if last three chars of str are sepc''
str = f.sub(str, 1, -4) .. "''"; -- remove them and add back ''
elseif f.sub(str, -5, -1) == duplicate_char .. "]]''" then -- if last five chars of str are sepc]]''
trim = true; -- why? why do this and next differently from previous?
elseif f.sub(str, -4, -1) == duplicate_char .. "]''" then -- if last four chars of str are sepc]''
trim = true; -- same question
end
elseif end_chr == "]" then -- if it might be wiki-markup
if f.sub(str, -3, -1) == duplicate_char .. "]]" then -- if last three chars of str are sepc]] wikilink
trim = true;
elseif f.sub(str, -3, -1) == duplicate_char .. '"]' then -- if last three chars of str are sepc"] quoted external link
trim = true;
elseif f.sub(str, -2, -1) == duplicate_char .. "]" then -- if last two chars of str are sepc] external link
trim = true;
elseif f.sub(str, -4, -1) == duplicate_char .. "'']" then -- normal case when |url=something & |title=Title.
trim = true;
end
elseif end_chr == " " then -- if last char of output string is a space
if f.sub(str, -2, -1) == duplicate_char .. " " then -- if last two chars of str are <sepc><space>
str = f.sub(str, 1, -3); -- remove them both
end
end
--[[--------------------------< R E D U C E _ T O _ I N I T I A L S >------------------------------------------ if trim then Attempts to convert names to initials in support of |name if value ~= comp then -list-format=vanc. value does not equal comp when value contains HTML markup Names in |firstn local dup2 = may be separated by spaces or hyphens, or for initials, a period. See http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35062/.duplicate_char; Vancouver style requires family rank designations (Jr, II, III, etc) to be rendered as Jr, 2nd, 3rd, etc. See http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35085/.This code only accepts and understands generational suffix in the Vancouver format because Roman numerals look like, and can be mistaken for, initials. 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) local name, suffix = mw.ustring if f.match(firstdup2, "^(%u+A" ) ([then dup2 = "%dJS][%drndth]+)$").. dup2 if not name then end -- if duplicate_char not initials and a suffixletter then escape it name value = mwf.ustring.matchgsub(firstvalue, "^(%u+b<>)$"); -- is it just intials? end  if name then -- if first is initials with or without suffix if 3 > mw.ustring.len (namedup2, "%1", 1 ) then -- if one or two initials if suffix then -- remove duplicate_char if there is a suffix if is_suffix (suffix) then -- is it legitimate?follows HTML markup return first; -- one or two initials and a valid suffix so nothing to do else add_vanc_error value = f.sub('suffix'value, 2, -1 ); -- one or two initials with invalid suffix so error messageremove duplicate_char when it is first character return first; -- and return first unmolestedend
end
else
return first; -- one or two initials without suffix; nothing to do
end
str = str .. value; --add it to the output string
end
end -- if here then name has 3 or more uppercase letters so treat them as a word return str;end
local initials, names = {}, {}; -- tables to hold name parts and initials local i = 1; [[--------------------------< I S _ S U F F I X >------------------------------------------------------------ counter for number of initials returns true is suffix is properly formed Jr, Sr, or ordinal in the range 1–9. Puncutation not allowed.
names = mw.text.split (first, '[%s,]+'); -- split into a table of names and possible suffix]
local function is_suffix (suffix) while names[i] do -- loop through the table if 1 < i and names[i]:match utilities.in_array (suffix, {'[%dJS][%drndth]+%.?$Jr') then -- if not the first name, and looks like a suffix (may have trailing dot) names[i] = names[i]:gsub ('%.Sr', 'Jnr'); -- remove terminal dot if present if is_suffix (names[i]) then -- if a legitimate suffix table.insert (initials, ' Snr' .. names[i]); -- add a separator space, insert at end of initials table break; -- and done because suffix must fall at the end of a name end -- no error message if not a suffix; possibly because of Romanization end if 3 > i then table.insert (initials'1st', mw.ustring.sub(names[i]'2nd',1,1'3rd'})or suffix:match ('^%dth$'); -- insert the intial at end of initials table endthen i = i+1return true; -- bump the counter
end
return table.concat(initials) -- Vancouver format does not include spaces.false;
end
--[[--------------------------< L I S T _ P E G O O P L D _ V A N C _ N A M E >-------------------------------------------------------
Formats For Vancouver style, author/editor names are supposed to be rendered in Latin (read ASCII) characters. When a nameuses characters that contain diacritical marks, those characters are to converted to the corresponding Latin character.When a list of people (ename is written using a non-Latin alphabet or logogram, that name is to be transliterated into Latin characters.gThese things are not currently possible in this module so are left to the editor to do. authors / editors)
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–017Flocal function list_people [http://www.unicode.org/charts/PDF/U0180.pdf Latin Extended-B] 0180–01BF, 01C4–024F |lastn= also allowed to contain hyphens, spaces, and apostrophes. (controlhttp://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35029/)|firstn= also allowed to contain hyphens, peoplespaces, etal)apostrophes, and periods local sep; local namesep;This original test: local format if nil = control= mw.ustring.find (last, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%']*$") or nil == mw.ustring.find (first, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%'%.]+[2-6%a]*$") thenwas written outside of the code editor and pasted here because the code editor gets confused between character insertion point and cursor position.The test has been rewritten to use decimal character escape sequence for the individual bytes of the Unicode characters so that it is not necessaryto use an external editor to maintain this code.format  \195\128-\195\150 – À-Ö (U+00C0–U+00D6 – C0 controls) local maximum = control.maximum\195\152-\195\182 – Ø-ö (U+00D8-U+00F6 – C0 controls) local lastauthoramp = control.lastauthoramp;\195\184-\198\191 – ø-ƿ (U+00F8-U+01BF – C0 controls, Latin extended A & B) local text = {}\199\132-\201\143 – DŽ-ɏ (U+01C4-U+024F – Latin extended B) ]]
local function is_good_vanc_name (last, first, suffix) if 'vanc' == format not suffix then -- Vancouver-like author/editor name styling? sep = if first:find ('[,%s]'; -) then - name-list separator between authors when there is a space or comma, might be first name/initials + generational suffix namesep first = first:match (' (.-)[,%s]+'); -- lastget name/first separator is a spaceinitials else sep suffix = first:match ('[,%s]+(.+)$');' - - name-list separator between authors is a semicolonget generational suffix namesep = ', ' -- last/first separator is <comma><space>end
end
if sep:sub(-1,-1) ~= " " then sep = sep utilities.. " " end if is_set (maximumsuffix) 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_setnot is_suffix (person.lastsuffix) then local mask = personadd_vanc_error (cfg.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 = stringerr_msg_supl.rep("&mdash;",nsuffix) 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 ('%.', '')return false; -- 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 a 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_wikilink (person.link, one); -- 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 )with an appropriate suffix end
end
if nil == mw.ustring.find (last, "^[A-Za-z\195\128-\195\150\195\152-\195\182\195\184-\198\191\199\132-\201\143%-%s%']*$") or
nil == mw.ustring.find (first, "^[A-Za-z\195\128-\195\150\195\152-\195\182\195\184-\198\191\199\132-\201\143%-%s%'%.]*$") then
add_vanc_error (cfg.err_msg_supl['non-Latin char']);
return false; -- not a string of Latin characters; Vancouver requires Romanization
end;
return true;
end
 
local count = #text / 2; -- (number of names + number of separators) divided by 2 if count [[--------------------------< R E D U C E _ T O _ I N I T I A L S > 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, countend----------------------------
Attempts to convert names to initials in support of |name-list-style=vanc.
--[[--------------------------< A N C H O R _ I D >------------------------------------------------------------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/.
Generates a CITEREF anchor ID if we have at least one name or a dateVancouver style requires family rank designations (Jr, II, III, etc.) to be rendered as Jr, 2nd, 3rd, etc. Otherwise returns an empty stringSee http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35085/.This code only accepts and understands generational suffix in the Vancouver format because Roman numerals look like, and can be mistaken for, initials.
namelist is one This function uses ustring functions because firstname initials may be any of the contributor-, author-, or editor-name lists chosen in that order. year is Year or anchor_yearUnicode Latin characters accepted by is_good_vanc_name ().
]]
 local function anchor_id reduce_to_initials(namelist, yearfirst) local namesname, suffix ={}mw.ustring.match(first, "^(%u+) ([%dJS][%drndth]+)$"); -- a table for the one to four names and year  for i,v in ipairs (namelist) do if not name then -- loop through the list if not initials and take up to the first four last namesa suffix names[i] name = vmw.ustring.last if i == 4 then break end match(first, "^(%u+)$"); -- if four then doneis it just initials?
end
table.insert (names, year); -- add the year at the end
local id = table.concat(names); -- concatenate names and year for CITEREF id
if is_set (id) then -- if concatenation is not an empty string
return "CITEREF" .. id; -- add the CITEREF portion
else
return ''; -- return an empty string; no reason to include CITEREF id in this citation
end
end
if name then -- if first is initials with or without suffix
if 3 > mw.ustring.len (name) then -- if one or two initials
if suffix then -- if there is a suffix
if is_suffix (suffix) then -- is it legitimate?
return first; -- one or two initials and a valid suffix so nothing to do
else
add_vanc_error (cfg.err_msg_supl.suffix); -- one or two initials with invalid suffix so error message
return first; -- and return first unmolested
end
else
return first; -- one or two initials without suffix; nothing to do
end
end
end -- if here then name has 3 or more uppercase letters so treat them as a word
local initials, names = {}, {}; --[[--------------------------< N A M E _ H A S _ E T A L >--------------------------------------------------tables to hold name parts and initials local i = 1; --counter for number of initials
Evaluates the content of author and editor name parameters for variations on the theme of et al names = mw.text. If foundsplit (first,the et al. is removed'[%s, ]+'); -- split into a flag is set to true and the function returns the modified name table of names and the flag.possible suffix
This function never sets the flag to false but returns it's previous state because it may have been set byprevious passes through this function or by the parameters |display-authors=etal or |display-editors=etal ] while names[ilocal function name_has_etal (name, etal, nocat)  if is_set (name) then do -- name can be nil in which case just returnloop through the table local etal_pattern = "if 1 < i and names[;,i]? *[\":match (']*%f[%adJS][Ee][Tt] *[Aa][Ll%drndth][+%.\"?$']*$" ) then -- variations on if not the 'et al' theme local others_pattern = "[;first name,]? *%f[%and looks like a]and [Oo]thers"; -- and alternate to et al. if name:match suffix (etal_patternmay have trailing dot) then -- variants on et al. name names[i] = namenames[i]:gsub (etal_pattern'%.', ''); -- remove terminal dot if found, removepresent etal = true; -- set flag if is_suffix (may have been set previously here or by |display-authors=etalnames[i]) if not nocat then -- no categorization for |vauthors=if a legitimate suffix add_maint_cat table.insert (initials, 'etal'.. names[i]); -- and add a category if not already added separator space, insert at endof initials table elseif name:match (others_pattern) then break; -- if not 'et al.', then 'and others'?done because suffix must fall at the end of a name name = name:gsub (others_pattern, ''); end -- no error message if found, removenot a suffix; possibly because of Romanization etal = true; -- set flag (may have been set previously here or by |display-authors=etal) end if not nocat 3 > i then -- no categorization for |vauthors= add_maint_cat table.insert (initials, mw.ustring.sub('etal'names[i], 1, 1)); -- and add a category if not already added insert the initial at endof initials table
end
i = i+1; -- bump the counter
end
return name, etal; table.concat(initials) -- Vancouver format does not include spaces.
end
--[[--------------------------< N A M E _ H A L I S T _ P E D _ M A R K U O P L E >------------------------------------------------------- Formats a list of people (e.g. authors, contributors, editors, interviewers, translators)  names in the list will be linked when |<name>-link= has a value |<name>-mask- does NOT have a value; masked names are presumed to have been rendered previously so should have been linked there
Evaluates when |<name>-mask=0, the content of author and editor parameters for extranious editor annotations: ed, ed., eds, (Ed.), etc.These annotation do associated name is not belong in author parameters and are redundant in editor parameters. If found, the functionadds the editor markup maintenance category.rendered
]]
local function name_has_ed_markup list_people (namecontrol, people, list_nameetal) local _, patternsep; local namesep; local format = control.format; local patterns maximum = { -- these patterns match annotations at end of namecontrol.maximum; '%f[%(%[][%(%[]%s*[Ee][Dd][Ss]?% local lastauthoramp = control.?%s*[%)%]]?$', lastauthoramp; -- (ed) or (eds)TODO: leading '(', case insensitive 'ed', optional 's', '.' and/or ')'delete after deprecation local name_list = {};  if '[,%.%s]%f[e]eds?%.?$vanc', == format then -- ed or eds: without '('or ')'; case sensitive (ED could be initials Ed could be Vancouver-like name)styling? '%f[%(%[][%(%[]%s*[Ee][Dd][Ii][Tt][Oo][Rr][Ss]?%sep = cfg.?%s*presentation[%)%'sep_nl_vanc']]?$', ; --name- (editor) or (editors): leading '(', case insensitive, optional '.' and/or ')'list separator between names is a comma '[,%namesep = cfg.%s]%f[Ee][Ee][Dd][Ii]presentation[Tt][Oo][Rr][Ss]?%.?$', -- editor or editors: without '('or ')sep_name_vanc']; case insensitive -- these patterns match annotations at beginning of namelast/first separator is a space '^eds?[%.,lastauthoramp = nil;]', -- ed. or eds.TODO: lower case only, optional delete after deprecation -- unset because isn'st used by Vancouver style else sep = cfg.presentation[', requires '.sep_nl']; -- name-list separator between names is a semicolon '^[%(%[]%s*[Ee][Dd][Ss]?%namesep = cfg.?%s*presentation[%)%'sep_name']]', ; -- last/first separator is <comma><space> end if sep:sub (ed-1, -1) or (eds): also sqare brackets, case insensitive, optional 's', '~= " " then sep = sep ..'" " end '^[% if utilities.is_set (%[]?%s*[Ee][Dd][Ii][Tt][Oo][Rr][Ss]?%A'maximum) and maximum < 1 then return "", 0; end -- returned 0 is for EditorCount; not used for other names for i, person in ipairs (editor or (editors: also sq brackets, case insensitive, optional brackets, 's'people) do '^[%if utilities.is_set (%[]?%s*[Ee][Dd][Ii][Tt][Ee][Dd]%A', -- (edited: also sq brackets, case insensitive, optional bracketsperson.last) then local mask = person.mask; local one; } local sep_one = sep;
if utilities.is_set (namemaximum) then for _, pattern in ipairs (patterns) do -- spin through patterns table and if name:match (pattern) i > maximum then add_maint_cat ('extra_text_names', cfg.special_case_translation [list_name])etal = true; -- add a maint cat for this template
break;
end
if mask then local n = tonumber (mask); -- convert to a number if it can be converted; nil else if n then one = 0 ~= n and string.rep("&mdash;",n) or nil; -- make a string of (n > 0) mdashes, nil else, to replace name person.link = nil; -- don't create link to name if name is replaces with mdash string or has been set nil else one = mask; -- replace name with mask text (must include name-list separator) sep_one = " "; -- modify name-list separator end else one = person.last; -- get surname local first = person.first -- get given name if utilities.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 end if utilities.is_set (person.link) then one = utilities.make_wikilink (person.link, one); -- link author/editor end return if one then -- if <one> has a value (name, mdash replacement, or mask text replacement) table.insert (name_list, one); -- add it to the list of names table.insert (name_list, sep_one); --add the proper name- and donelist separator end end end
local count = #name_list / 2; -- (number of names + number of separators) divided by 2
if 0 < count then
if 1 < count and not etal then
if 'amp' == format or utilities.is_set (lastauthoramp) then -- TODO: delete lastauthoramp after deprecation
name_list[#name_list-2] = " & "; -- replace last separator with ampersand text
elseif 'and' == format then
if 2 == count then
name_list[#name_list-2] = cfg.presentation.sep_nl_and; -- replace last separator with 'and' text
else
name_list[#name_list-2] = cfg.presentation.sep_nl_end; -- replace last separator with '(sep) and' text
end
end
end
name_list[#name_list] = nil; -- erase the last separator
end
local result = table.concat (name_list); --[[--------------------------< N A M E _ H A S _ M U L T _ N A M E S >------------------------------construct list if etal and utilities.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 got a last-first list and etal so add et al. end return result, count; --return name-list string and count of number of names (count used for editor names only)end
Evaluates the content of author and editor (surnames only) parameters for multiple names. Multiple names are
indicated if there is more than one comma and or semicolon. If found, the function adds the multiple name
(author or editor) maintenance category.
]]--[[--------------------------< A N C H O R _ I D >------------------------------------------------------------
local function name_has_mult_names (name, list_name)local count, _; Generates a CITEREF anchor ID if is_set (name) then _, count = we have at least one name:gsub ('[;,]', ''); -- count the number of separator-like characters if 1 < count then -- param could be |author= or |editor= so one separator character is acceptable add_maint_cat ('mult_names', cfga date. Otherwise returns an empty string.special_case_translation [list_name]); -- more than one separator indicates multiple names so add a maint cat for this template end end return name; -- and doneend
 --[[--------------------------< N A M E _ C H E C K S >---------------------------------------------------namelist is one of the contributor-, author-, or editor---This function calls various name checking functions used to validate the content of the various name-holdingparameterslists chosen in that order. year is Year or anchor_year.
]]
local function name_checks anchor_id (lastnamelist, first, list_nameyear) if is_set (last) then if last:match ('^%(%(.*%)%)$') then -- if wrapped in doubled parentheses, accept as written last local names= last:match ('^%(%((.*)%)%)$'){}; -- strip parensa table for the one to four names and year else last = name_has_mult_names for i,v in ipairs (last, list_namenamelist); do -- check for multiple names in loop through the list and take up to the parameter (first four last only)names last names[i] = name_has_ed_markup (v.last, list_name); -- check for extraneous 'editor' annotation if i == 4 then break end -- if four then done
end
if is_set table.insert (firstnames, year) then if first:match ('^%(%(.*%)%)$') then ; -- if wrapped in doubled parentheses, accept as written add the year at the end first local id = first:match table.concat('^%(%((.*)%)%)$'names); -- strip parensconcatenate names and year for CITEREF id else first = name_has_ed_markup if utilities.is_set (first, list_nameid); then -- check for extraneous 'editor' annotationif concatenation is not an empty string endreturn "CITEREF" .. id; -- add the CITEREF portion endelse return last, first''; -- donereturn an empty string; no reason to include CITEREF id in this citation
end
end
--[[--------------------------< E X T R A C T _ N A M E _ H A S _ E T A L >----------------------------------------------------Gets Evaluates the content of name parameters (author, editor, etc.) 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 list from and the input argumentsflag.
Searches through args in sequential order This function never sets the flag 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= false but doesnreturns it'ts previous state because it may have been set byfind |last4= and previous passes through this function or by the associated |last5display-<names>= then the search is done.etal parameter
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.'local function name_has_etal (name, the 'et al.' is stripped from the parameter and a flag (etal, nocat, param) returnedthat 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 alif utilities. in an author parameter  local err_msg_list_name = list_name:match is_set ("(%w+)List"name) .. 's list'; then -- modify AuthorList or EditorList for use name can be nil in error messages if necessary while true dowhich case just return 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 local patterns = 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. last, first= name_checks (last, first, list_name)et_al_patterns; -- multiple names, extraneous annotation, etc checksget 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 etal = true; -- set flag (may have been set previously here or by |display-<names>=etal) if first and not last nocat then -- if there is a firstn without a matching lastnno categorization for |vauthors= table.insert( z.message_tail, { set_errorutilities.set_message ( 'first_missing_lasterr_etal', {err_msg_list_name, iparam}, true ) } ); -- add this and set an error message elseif if 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 upadded break; -- normal exit or there is a two-name hole in the list; can't tell whichend
end
else -- we have last with or without a first
link_title_ok (link, list_name:match ("(%w+)List"):lower() .. '-link' .. i, last, list_name:match ("(%w+)List"):lower() .. '-last' .. i); -- check for improper wikimarkup
 
names[n] = {last = last, first = first, link = link, mask = mask, 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
table.insert( z.message_tail, { set_error( 'missing_name', {err_msg_list_name, i-1}, true ) } ); -- 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 namesname, etal; -- all done, return our list of names
end
--[[--------------------------< G N A M E T _ I S O 6 3 9 _ N U M E R I C O D E >------------------------------------------------
Validates language Add maint cat when name parameter value does not contain letters. Does not catch mixed alphanumeric names provided so|last=A. Green (1922-1987) does not get caught in the current version of this test but |languagefirst= parameter if not an ISO639-1 or 639-2 code(1888) is caught.
Returns the language name and associated two- or three-character code. Because case of the source may be incorrector different from the case that WikiMedia uses, the name comparisons are done in lower case and when a match isfound, the Wikimedia version (assumed to be correct) is returned along with the code. When there is no match, wereturn the original language name string.returns nothing
]] local function name_is_numeric (name, list_name) if utilities.is_set (name) then if mw.languageustring.fetchLanguageNamesmatch (<local wiki language>name, 'all^[%A]+$') returns a list of languages that in some cases may includeextensions. For example, code 'cbkthen --zam' and its associated when name 'Chavacano de Zamboanga' (MediaWiki does not supportcontain any letterscode 'cbk' or name 'Chavacano' utilities. Most set_message (all?) of these languages are not used a 'languagemaint_numeric_names' codes per se, rather theyare used as subcfg.special_case_translation [list_name]); -domain names: cbk-zam.wikipedia.org. These names can be found (add a maint cat for the time being) atthis template end endend  https://phabricator.wikimedia.org/diffusion/ECLD/browse/master/LocalNames/LocalNamesEn.php--[[--------------------------< N A M E _ H A S _ E D _ M A R K U P >------------------------------------------
Names but that are included in Evaluates the list will be found if that name is provided in the |language= parametercontent of author and editor parameters for extraneous editor annotations: ed, ed., eds, (Ed. For example),etc.if |language=Chavacano de Zamboanga, that name will be found with the associated code 'cbk-zam'These annotation do not belong in author parameters and are redundant in editor parameters. When names are If foundand , the associated code is not two or three characters, this function returns only adds the Wikimedia language nameeditor markup maintenance category.
Adapted from code taken from Module:Check ISO 639-1.returns nothing
]]
local function get_iso639_code name_has_ed_markup (langname, this_wiki_codelist_name) local remap = { ['bangla'] = {'Bengali', 'bn'}, -- MediaWiki returns Bangla (the endonym) but we want Bengali (the exonym); here we remap ['bengali'] patterns = {'Bengali', 'bn'}, -- MediaWiki doesn't use exonym so here we provide correct language name and 639-1 code ['bihari'] = {'Bihari', 'bh'}, -- MediaWiki replace 'Bihari' with 'Bhojpuri' so 'Bihari' cannot be found ['bhojpuri'] = {'Bhojpuri', 'bho'}, -- MediaWiki uses 'bh' as a subdomain name for Bhojpuri wWikipedia: bhcfg.wikipedia.org } if remap[lang:lower()] then return remap[lang:lower()][1], remap[lang:lower()][2]editor_markup_patterns; -- for this language 'name', return a possibly new name and appropriate code endget patterns from configuration
local languages = mwif utilities.language.fetchLanguageNamesis_set (this_wiki_code, 'all'name) -- get a list of language names known to Wikimediathen -- ('all' is required for North Ndebele_, South Ndebele, and Ojibwa) local langlc = mw.ustring.lower(lang); -- lower case version for comparisons for code, name pattern in pairsipairs (languagespatterns) do -- scan the list to see if we can find our languagespin through patterns table and if langlc == mw.ustring.lowername:match (namepattern) then if 2 ~= code:len utilities.set_message ('maint_extra_text_names', cfg.special_case_translation [list_name]) and 3 ~= code:len() then ; -- two- or three-character codes only; extensions not supportedadd a maint cat for this template return namebreak; -- so return the name but not the code
end
return name, code; -- found it, return name to ensure proper capitalization and the the code
end
end
return lang; -- not valid language; return language in original case and nil for the code
end
--[[--------------------------< L A N G U A G M E _ P H A R S _ M U L T _ N A M E T E R S >------------------------------------------
Gets language name from a provided two- Evaluates the content of last/surname (authors etc.) parameters for multiple names. Multiple names are indicatedif there is more than one comma or three-character ISO 639 codeany semicolons. If a code is recognized by MediaWikifound,use the returned function adds the multiple name; if not, then use the value that was provided with the language parametermaintenance category.
When |language= contains a recognized language (either code or name), the page is assigned to the category forthat code: Category:Norwegian-language sources (no). For valid three-character code languages, the page is assignedto the single category for '639-2' codes: Category:CS1 ISO 639-2 language sources. Languages that are the same as the local wiki are not categorized. MediaWiki does not recognize three-characterequivalents of two-character codes: code 'ar' is recognized bit code 'ara' is not. This function supports multiple languages in the form |language=nb, French, th where the language names or codes areseparated from each other by commas.returns nothing
]]
local function language_parameter name_has_mult_names (langname, list_name) local code_, commas, semicolons; -- the two- or three-character language code local if utilities.is_set (name) then _, commas = name:gsub (',', ''); -- count the language namenumber of commas local language_list _, semicolons = {}name:gsub ('; ', ''); -- table count the number of language names to be renderedsemicolons if 1 < commas or 0 < semicolons then local names_table = {} utilities.set_message ('maint_mult_names', cfg.special_case_translation [list_name]); -- table made from the value assigned to |language=add a maint message end endend 
local this_wiki = mw.getContentLanguage(); -- get a language object for this wiki local this_wiki_code = this_wiki:getCode() [[--------------------------< N A M E _ C H E C K S >------------------------------------------------------ get this wiki's language code local this_wiki_name = mw.language.fetchLanguageName(this_wiki_code, this_wiki_code); -- get this wiki's language name
local remap = { ['bh'] = 'Bihari', -- MediaWiki uses 'bh' as a subdomain This function calls various name checking functions used to validate the content of the various name for Bhojpuri wWikipedia: bh.wikipedia.org ['bn'] = 'Bengali', -- MediaWiki returns Banglaholding }parameters.
names_table = mw.text.split (lang, '%s*,%s*'); -- names should be a comma separated list]]
for _local function name_checks (last, first, lang in ipairs (names_tablelist_name) do -- reuse lang local accept_name;
if utilities.is_set (last) then if lang:match last, accept_name = utilities.has_accept_as_written ('^%a%a%last); -- remove accept-this-as-') written markup when it wraps all of <last> if not accept_name then -- strip ietf language tags from code<last> not wrapped in accept-as-written markup name_has_mult_names (last, list_name); TODO: is there a need to support 3 -char with tag?- check for multiple names in the parameter (last only) lang = lang:match ('name_has_ed_markup (%a%alast, list_name)%; --check for extraneous 'editor'annotation name_is_numeric (last, list_name) ; -- keep only 639-1 code portion to lang; TODO: do something with 3166 alpha 2 country code?check for names that are compsed of digits and punctuation
end
if 2 == lang:len() or 3 == lang:len() then -- if two-or three-character code end name = mw.language.fetchLanguageName( lang:lower(), this_wiki_code); -- get language name if |language= is a proper code end if utilities.is_set (namefirst) then -- if |language= specified a valid code code = lang:lower(); -- save it else namefirst, code accept_name = get_iso639_code utilities.has_accept_as_written (lang, this_wiki_codefirst); -- attempt to get code from name (assign name here so that we are sure of proper capitalization) end if is_set (code) then -- only 2remove accept- or 3-character codes name = remap[code] or name; this-as- override wikimedia written markup when they misuse language codes/namesit wraps all of <first>
if this_wiki_code ~= code not accept_name then -- when the language is <first> not the same wrapped in accept-as this wiki's language if 2 == code:len() then -- and is a two-character codewritten markup add_prop_cat name_has_ed_markup ('foreign_lang_source' .. codefirst, {name, code}list_name) -- categorize it else -; - or is a recognized language (but has a three-character code) add_prop_cat (check for extraneous 'foreign_lang_source_2editor' .. code, {code}) -- categorize it differently TODO: support mutliple three-character code categories per cs1|2 template end end elseannotation add_maint_cat name_is_numeric ('unknown_lang'first, list_name); -- add maint category if not already addedcheck for names that are compsed of digits and punctuation
end
table.insert (language_list, name);
name = ''; -- so we can reuse it
end
code = #language_list -- reuse code as number of languages in the list if 2 >= code then name = table.concat (language_list, ' and ') -- insert '<space>and<space>' between two language names elseif 2 < code then language_list[code] = 'and ' .. language_list[code]; -- prepend return last name with 'and<space>' name = table.concat (language_list, ', ') -- and concatenate with '<comma><space>' separators end if this_wiki_name == name then return ''; -- if one language and that language is this wiki's return an empty string (no annotation) end return (" " .. wrap_msg ('language', name))first; -- otherwise wrap with '(in ...)' --[[ TODO: should only return blank or name rather than full list so we can clean up the bunched parenthetical elements Language, Type, Format ]]done
end
--[[--------------------------< S E X T _ R A C S 1 T _ N A M E S T Y L E >----------------------------------------------------Gets name list from the input arguments
Set style settings for CS1 citation templatesSearches through args in sequential order to find |lastn= and |firstn= parameters (or their aliases), and their matching link and mask parameters. Returns separator Stops searching when both |lastn= and |firstn= are not found in args after two sequential attempts: found |last1=, |last2=, and postscript settings|last3= but doesn'tfind |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=.
local function set_cs1_style When an author or editor parameter contains some form of 'et al.', the 'et al.' is stripped from the parameter and a flag (psetal)returned if not is_set that will cause list_people(ps) then -- unless explicitely set to something ps = add the static 'et al.' text from Module:Citation/CS1/Configuration. This keeps 'et al.'; -- terminate out of the rendered citation with a period end return template's metadata.' When this occurs, ps; -- separator the page is added to a full stopendmaintenance category.
]]
local function extract_names(args, list_name) local names = {}; --[[--------------------------< S E T _ C S 2 _ S T Y L E >-----------------------------------------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
Set style settings local last_alias, first_alias, link_alias; -- selected parameter aliases used in error messaging while true do last, last_alias = utilities.select_one ( args, cfg.aliases[list_name .. '-Last'], 'err_redundant_parameters', i ); -- search through args for CS2 citation templatesname components beginning at 1 first, first_alias = utilities.select_one ( args, cfg.aliases[list_name .. '-First'], 'err_redundant_parameters', i ); link, link_alias = utilities.select_one ( args, cfg.aliases[list_name .. '-Link'], 'err_redundant_parameters', i ); mask = utilities.select_one ( args, cfg.aliases[list_name .. Returns separator'-Mask'], postscript'err_redundant_parameters', ref settingsi );
last, etal = name_has_etal (last, etal, false, last_alias); -- find and remove variations on et al. first, etal = name_has_etal (first, etal, false, first_alias); -- find and remove variations on et al. last, first = name_checks (last, first, list_name); -- multiple names, extraneous annotation, etc. checks if first and not last then -- if there is a firstn without a matching lastn local alias = first_alias:find ('given', 1, true) and 'given' or 'first'; -- get first or given form of the alias table.insert (z.message_tail, { utilities.set_message ( 'err_first_missing_last', { first_alias, -- param name of alias missing its mate first_alias:gsub (alias, {['first']= 'last', ['given']= 'surname'}), -- make param name appropriate to the alias form }, true ) } ); -- 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 local result; link = link_title_ok (link, link_alias, last, last_alias); -- check for improper wiki-markup if first then link = link_title_ok (link, link_alias, first, first_alias); -- check for improper wiki-markup end
local function set_cs2_style names[n] = {last = last, first = first, link = link, mask = mask, corporate = false}; -- add this name to our names list (ps, refcorporate for |vauthors= only) n = n + 1; -- point to next location in the names table if 1 == count then -- if not is_set the previous name was missing table.insert( z.message_tail, { utilities.set_message ( 'err_missing_name', {list_name:match ("(ps%w+)List") then :lower(), i - 1}, true ) } ); -- if |postscriptadd this error message end count = has not been set0; -- reset the counter, set cs2 defaultwe're looking for two consecutive missing names end ps i = ''i + 1; -- make sure it isn't nilpoint to next args location
end
if not is_set (ref) then -- if |ref= is not set ref = "harv"; -- set default |ref=harv end return ','names, ps, refetal; -- separator is a commaall done, return our list of names and the etal flag
end
--[[--------------------------< G E T _ S E T T I N G S _ F R O M 6 3 9 _ C I T O D E _ C L A S S >------------------------------------------------
When Validates language names provided in |modelanguage= is parameter if not set an ISO639-1 or when its value is invalid, use config.CitationClass and parameter values to establishrendered style639-2 code.
]]Returns the language name and associated two- or three-character code. Because case of the source may be incorrector different from the case that WikiMedia uses, the name comparisons are done in lower case and when a match isfound, the Wikimedia version (assumed to be correct) is returned along with the code. When there is no match, wereturn the original language name string.
mw.language.fetchLanguageNames(<local function get_settings_from_cite_class (ps, refwiki language>, cite_class'all')returns a list of languages that in some cases may include local sep; if (cite_class == "citation") then extensions. For example, code 'cbk-- for citation templates zam' and its associated name 'Chavacano de Zamboanga' (CS2)MediaWiki does not support sep, ps, ref = set_cs2_style code 'cbk' or name 'Chavacano'. Most (psall?) of these languages are not used a 'language' codes per se, ref);rather they else are used as sub-domain names: cbk- not a citation template so CS1 sep, ps = set_cs1_style zam.wikipedia.org. A list of language names and codes supported by fetchLanguageNames(ps); endcan be found at Template:Citation Style documentation/language/doc
return sepNames that are included in the list will be found if that name is provided in the |language= parameter. For example, psif |language=Chavacano de Zamboanga, ref that name will be found with the associated code 'cbk-- return them allzam'. When names are foundendand the associated code is not two or three characters, this function returns only the WikiMedia language name.
Some language names have multiple entries under different codes:
Aromanian has code rup and code roa-rup
When this occurs, this function returns the language name and the 2- or 3-character code
--[[--------------------------< S E T _ S T Y L E >------------------------------------------------------------ Establish basic style settings to be used when rendering the citation. Uses |mode= if set and valid or usesconfig.CitationClass Adapted from code taken from the template's #invokeModule: to establish styleCheck ISO 639-1.
]]
local function set_style get_iso639_code (modelang, ps, ref, cite_classthis_wiki_code) local sep; if 'cs2' == mode cfg.lang_name_remap[lang:lower()] then -- if this template there is to be rendered in CS2 (citation) style sep, ps, ref = set_cs2_style a remapped name (ps, ref); elseif because MediaWiki uses something that we don'cs1' == mode then -- if this template t think is to be rendered in CS1 (cite xxxcorrect) style sep, ps = set_cs1_style return cfg.lang_name_remap[lang:lower(ps); else -- anything but cs1 or cs2 sep][1], ps, ref = get_settings_from_cite_class cfg.lang_name_remap[lang:lower(ps, ref, cite_class)][2]; -- get settings based on the templatefor this language 's CitationClassname', return a possibly new name and appropriate code
end
  local ietf_code; -- because some languages have both IETF-like codes and ISO 639-like codes local ietf_name; local langlc = mw.ustring.lower (lang); -- lower-case version for comparisons  for code, name in pairs (cfg.languages) do -- scan the list to see if we can find our language if 'none' langlc == ps:mw.ustring.lower(name) then if 2 == #code or 3 == #code then -- two- or three-character codes only; IETF extensions not supported return name, code; -- if assigned value is 'none' thenso return the name and the code end ietf_code = code; -- remember that we found an IETF-like code and save its name ps ietf_name = ''name; --but keep looking for a 2- set to empty stringor 3-char code end
end
-- didn't find name with 2- or 3-char code; if IETF-like code found return return sep, ps, refietf_code and ietf_name or lang; -- associated name; return original language text else
end
--[=[--------------------------< I S L A N G U A G E _ P D F A R A M E T E R >------------------------------------------------------------------
Determines if Gets language name from a provided two- or three-character ISO 639 code. If a url has the file extension that code is one of the pdf file extensions used recognized by [[MediaWiki:Common.css]] when,applying use the returned name; if not, then use the value that was provided with the pdf icon to external linkslanguage parameter.
returns true if file extension When |language= contains a recognized language (either code or name), the page is one of assigned to the recognized extensionscategory forthat code: Category:Norwegian-language sources (no). For valid three-character code languages, else falsethe page is assignedto the single category for '639-2' codes: Category:CS1 ISO 639-2 language sources.
]=]Languages that are the same as the local wiki are not categorized. MediaWiki does not recognize three-characterequivalents of two-character codes: code 'ar' is recognized but code 'ara' is not.
local This function is_pdf (url) return url:match ('%.pdf$') or url:match ('%.PDF$') or url:match ('%.pdf[%?#]') or url:match ('%.PDF[%?#]');end  --[[--------------------------< S T Y L E _ F O R M A T >------------------------------------------------------ Applies css style to supports multiple languages in the form |formatlanguage=nb, |chapter-format=French, etc. Also emits an error message if th where the format parameter doesnot have a matching url parameter. If the format parameter is not set and the url contains a file extension thatlanguage names or codes areis recognized as a pdf document separated from each other by MediaWiki's commons.css, this code will set the format parameter to (PDF) commas withthe appropriate stylingoptional space characters.
]]
local function style_format language_parameter (format, url, fmt_param, url_paramlang) if is_set (format) then format = wrap_style ('format', format)local code; -- add leading space, parentheses, resize if not is_set (url) then format = format .. set_error( 'format_missing_url', {fmt_param, url_param} ); the two-or three- add an error message endcharacter language code elseif is_pdf (url) then local name; -- format is not set so if url is a pdf file thenthe language name format local language_list = wrap_style ('format', 'PDF'){}; -- set format table of language names to pdfbe rendered else format local names_table = ''{}; -- empty string for concatenation end return format;endtable made from the value assigned to |language=
local this_wiki_name = mw.language.fetchLanguageName (cfg.this_wiki_code, cfg.this_wiki_code); -- get this wiki's language name
names_table = mw.text.split (lang, '%s*,%s*'); --[[--------------------------< G E T _ D I S P L A Y _ A U T H O R S _ E D I T O R S >------------------------names should be a comma separated list
Returns a number that defines the number of names displayed for author and editor name lists and a boolean flag_, lang in ipairs (names_table) do -- reuse langto indicate when et al. should be appended to the name list= cfg.lang_code_remap[lang:lower()]; -- first see if this is a code that is not supported by MediaWiki but is in remap
When the value assigned to |display if name then -- there was a remapped code so if not lang:match ('^%a%a%a?%-x%-%a+$') then --xxxxorsif not a private IETF tag lang = is lang:gsub ('^(%a%a%a number greater than or equal to zero?)%-.*', return the number and'%1'); -- strip IETF tags from code endthe previous state of the else lang = lang:gsub ('etal^(%a%a%a?)%-.*', '%1' flag ); -- strip any IETF-like tags from code if 2 == lang:len(false by default but may have been set to true ) or 3 == lang:len() then -- if the two-or three-character code name list containssome variant of the text 'et al= mw.language.'fetchLanguageName (lang:lower(), cfg.this_wiki_code); -- get language name if |language= is a proper code end end
When the value assigned to |display-xxxxors= is the keyword 'etal', return a number that is one greater than thenumber of authors in the list and set the 'etal' flag true. This will cause the list_people() to display all ofthe names in the name list followed by 'et al.' In all other cases, returns nil and the previous state of the 'etal' flag. inputs: max: A['DisplayAuthors'] or A['DisplayEditors']; a number or some flavor of etal count: #a or #e list_name: 'authors' or 'editors' etal: author_etal or editor_etal ]] local function get_display_authors_editors (max, count, list_name, etal) if is_set (max) then if 'etal' == max:lower():gsub("[ '%utilities.]", '') then -- the :gsubis_set () 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 |language= specified a string of numbersvalid code max code = tonumber lang:lower(max); -- make save it a number else if max >= count then -- if |display-xxxxorsname, code = value greater than or equal to number of authors/editors add_maint_cat get_iso639_code ('disp_auth_ed'lang, cfg.special_case_translation [list_name]this_wiki_code); end else -- not a valid keyword or number table.insertattempt to get code from name ( z.message_tail, { set_error( 'invalid_param_val', {'display-' .. list_name, max}, true ) } assign name here so that we are sure of proper capitalization); -- add error message max = nil; -- unset; as if |display-xxxxors= had not been set
end
end
return max, etal if utilities.is_set (code) then -- only 2- or 3-character codes name = cfg.lang_code_remap[code] or name;end -- override wikimedia when they misuse language codes/names
if cfg.this_wiki_code ~= code then -- when the language is not the same as this wiki's language
if 2 == code:len() then -- and is a two-character code
utilities.add_prop_cat ('foreign_lang_source' .. code, {name, code}); -- categorize it; code appended to allow for multiple language categorization
else -- or is a recognized language (but has a three-character code)
utilities.add_prop_cat ('foreign_lang_source_2' .. code, {code}); -- categorize it differently TODO: support multiple three-character code categories per cs1|2 template
end
elseif cfg.local_lang_cat_enable then -- when the language and this wiki's language are the same and categorization is enabled
utilities.add_prop_cat ('local_lang_source', {name, code}); -- categorize it
end
else
utilities.set_message ('maint_unknown_lang'); -- add maint category if not already added
end
table.insert (language_list, name);
name = ''; -- so we can reuse it
end
name = utilities.make_sep_list (#language_list, language_list);
 
if this_wiki_name == name then
return ''; -- if one language and that language is this wiki's return an empty string (no annotation)
end
return (" " .. wrap_msg ('language', name)); -- otherwise wrap with '(in ...)'
--[[ TODO: should only return blank or name rather than full list
so we can clean up the bunched parenthetical elements Language, Type, Format
]]
end
--[[--------------------------< E X T R A _ T E X T _ I N _ P A G E _ C H E C K >------------------------------
Adds page to Category:CS1 maint: extra text if |page= or |pages= has what appears to be some form of p. or pp. abbreviation in the first characters of the parameter content.--[[--------------------------< S E T _ C S 1 _ S T Y L E >----------------------------------------------------
check Page and Pages Set style settings for extraneous p, pCS1 citation templates., pp, Returns separator and pppostscript settingsAt en. at start of parameter valuewiki, for cs1: good patternps gets: '^P[^%.P%l]' matches when |page(s)= begins PX or P# but not Px where x and X are letters and # is a dgiit bad patternsep gets: '^[Pp][Pp].' matches matches when |page(s)= begins pp or pP or Pp or PP
]]
local function extra_text_in_page_check set_cs1_style (pageps) local good_pattern = '^P[^%if not utilities.Pp]'; is_set (ps) then -- ok unless explicitly set to begin with uppercase P: P7 (pg 7 of section P) but not p123 (page 123) TODO: add Gg for PG or Pg?something local bad_pattern ps = '^[Pp]?[Pp]%cfg.?presentation[ %d]';  if not page:match (good_pattern) and (page:match (bad_pattern) or page:match (ps_cs1'^[Pp]ages?')) then add_maint_cat ('extra_text'); -- terminate the rendered citation
end
return cfg.presentation['sep_cs1'], ps; -- element separator
end
--[=[--------------------------< G S E T _ V _ N A M E C S 2 _ S T A B Y L E >----------------------------------------------------
split apart a Set style settings for CS2 citation templates. Returns separator, postscript, ref settingsAt en.wiki, for cs2: ps gets: '' (empty string - no terminal punctuation) sep gets: ',' ]] local function set_cs2_style (ps, ref) if not utilities.is_set (ps) then -- if |vauthorspostscript= or |veditorshas not been set, set cs2 default ps = parametercfg. This function allows for corporate names, wrapped in doubledparentheses to also have commaspresentation['ps_cs2']; in the old version of the code, -- terminate the doubled parnetheses were included in therendered citation and in the metadata end if not utilities.is_set (ref) then -- if |ref= is not set ref = "harv"; -- set default |ref=harv end return cfg. Individual author names may be wikilinkedpresentation['sep_cs2'], ps, ref; -- element separatorend  --[[--------------------------< G E T _ S E T T I N G S _ F R O M _ C I T E _ C L A S S >----------------------
When |vauthorsmode=Jones ABis not set or when its value is invalid, [[Euse config. B. White|White EB]], ((Black, Brown, CitationClass and Coparameter values to establishrendered style.))
]=]
local function get_v_name_table get_settings_from_cite_class (vparamps, output_tableref, output_link_tablecite_class) local name_table = mw.text.split(vparam, "%s*,%s*")sep; -- names are separated by commas local wl_type, label, link; -- wl_type not used here; just a place holder local i = 1; while name_table[i] do if name_table[i]:match ('^%(%(.*[^%)][^%)]$') then -- first segment of corporate with one or more commas; this segment has the opening doubled parens local name cite_class = name_table[i]; i=i+1; -- bump indexer to next segment while name_table[i] do name = name .. ', ' .. name_table[i]; -- concatenate with previous segments if name_table[i]:match ('^.*%)%)$'"citation") then -- if this table member has the closing doubled parens break; -- and done reassembling so end i=i+1; -- bump indexer end table.insert for citation templates (output_table, nameCS2); -- and add corporate name to the output table table.insert (output_link_table, ''); -- no wikilink else wl_typesep, labelps, link ref = is_wikilink set_cs2_style (name_table[i]); -- wl_type is: 0ps, no wl (text in label variableref); 1, [[D]]; 2, [[L|D]] table.insert (output_table, label); else -- add this namenot a citation template so CS1 if 1 sep, ps == wl_type then table.insert set_cs1_style (output_link_table, labelps); -- simple wikilink [[D]] else end  table.insert (output_link_table return sep, ps, link); ref -- no wikilink or [[L|D]]; add this link if there is one, else empty string end end i = i+1; end return output_table;them all
end
--[[--------------------------< P A R S E T _ V A U S T H O R S _ V Y L 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 argsEstablish basic style settings to be used when rendering the citation. It then returns a table of assembled names just as extract_names() does. Author / editor names in Uses |vauthorsmode= if set and valid or |veditors= must be in Vancouver system style. Corporate or institutional namesmay sometimes be required and because such names will often fail the is_good_vanc_name() and other format complianceusestests, are wrapped in doubled paranethese ((corporate name)) to suppress the format testsconfigSupports generational suffixes Jr, 2nd, 3rd, 4th–6th. This function sets CitationClass from the vancouver error when a reqired comma is missing and when there is a space between an authortemplate's initials#invoke: to establish style.
]]
local function parse_vauthors_veditors set_style (argsmode, vparamps, list_nameref, cite_class) local names sep; if 'cs2' = {}; = mode then -- table of names assembled from |vauthorsif this template is to be rendered in CS2 (citation) style sep, ps, ref =set_cs2_style (ps, |authorref); elseif 'cs1' == mode then --maskn=if this template is to be rendered in CS1 (cite xxx) style sep, |author-linknps =set_cs1_style (ps); local v_name_table else -- anything but cs1 or cs2 sep, ps, ref = {}get_settings_from_cite_class (ps, ref, cite_class); -- get settings based on the template's CitationClass local v_link_table end  if cfg.keywords_xlate[ps:lower()] == {}; 'none' then -- when name if assigned value is wikilinked, targets go in this table'none' then local etal ps = false''; -- return value set to true when we find some form of et al. vauthors parameterempty string end local lastreturn sep, firstps, link, mask, suffix;refend   local corporate --[= [-------------------------< I S _ P D F >------------------------------------------------------------------ Determines if a URL has the file extension that is one of the PDF file extensions used by [[MediaWiki:Common.css]] whenapplying the PDF icon to external links. returns true if file extension is one of the recognized extensions, else 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) v_name_table = get_v_name_table (vparam, v_name_table, v_link_table); -- names are separated by commas]
for i, v_name in ipairslocal function is_pdf (v_name_tableurl) do if v_name return url:match ('^%(%(.+%)%)pdf$') then -- corporate authors are wrapped in doubled parentheses 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_nameor url:match ('^%(%((.+)%)%)PDF$') -- remove doubled parntheses corporate = true; -- flag used in list_people()or elseif string.find(v_name, "%s") then if v_nameurl:findmatch ('%.pdf[;%.?#]') then -- look for commonly occurring punctuation characters; add_vanc_error ('punctuation'); end local 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 if is_suffix (first) then -- if a valid suffix suffix = first -- save it as a suffix and first = table.remove(lastfirstTable); -- get what should be the initials from the table end -- no suffix error message here because letter combination may be result of Romanization; check for digits? last = table.concat(lastfirstTable, " ") -- returns a string that is the concatenation of all other names that are not initials if mw.ustring.or url:match (last, '%a+%s+%u+%s+%a+') then add_vanc_error ('missing comma'); -- matches last II last; the case when a comma is missing end if mw.ustring.match (v_name, ' PDF[%u %u$?#]') then -- this test is in the wrong place TODO: move or replace with a more appropriate test add_vanc_error ('name'); -- matches 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) then if not mw.ustring.url:match (first, "^'%u?%u$") then -- first shall contain one or two upper-case letters, nothing else add_vanc_error ('initials'); -- too many initials; mixed case initials (which may be ok Romanization); hyphenated initials end is_good_vanc_name (last, first); -- check first and last before restoring the suffix which may have a non-Latin digit if is_set (suffix) then first = first .. ' ' .. suffix; -- if there was a suffix concatenate with the initials suffix = ''; -- unset so we don't add this suffix to all subsequent names end else if not corporate then is_good_vanc_name (last, ''); end end  link = select_one( args, cfg.aliases[list_name .. PDF&#035'-Link'], 'redundant_parameters', i ) or v_link_table[i]; mask = select_oneurl:match ( args, cfg.aliases[list_name '%.. '-Mask'], 'redundant_parameterspdf&#035', 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 T Y L E C T _ A U T H F O R _ E D I M A T O R _ S O U R C E >------------------------------------------------------
Select one of Applies CSS style to |authorsformat=, |authorn= / |lastn / firstnchapter-format=, or |vauthors= as etc. Also emits an error message if the format parameter doesnot have a matching URL parameter. If the source of format parameter is not set and the author name list orURL contains a file extension thatselect one of |editors=is recognized as a PDF document by MediaWiki's commons.css, |editorn= / editor-lastn= / |editor-firstn= or |veditors= as this code will set the source of format parameter to (PDF) withthe editor name listappropriate styling.
Only one of these appropriate three will be used. The hierarchy is: |authorn= (and aliases) highest and |authors= lowest andsimilarly, |editorn= (and aliases) highest and |editors= lowest]]
When looking for |authornlocal function style_format (format, url, fmt_param, url_param) if utilities.is_set (format) then format = / |editorn= parametersutilities.wrap_style ('format', format); -- add leading space, parentheses, test |xxxxor1= and |xxxxor2resize if not utilities.is_set (url) then format = format .. ' ' .. utilities.set_message (and all of their aliases'err_format_missing_url', {fmt_param, url_param} ); stops after the second-- add an error messagetest which mimicks the test used in extract_names end elseif is_pdf (url) when looking for then -- format is not set so if URL is a hole in the author name listPDF file then format = utilities. There may be a betterwrap_style ('format', 'PDF'); -- set format to PDF elseway to do this, I just haven format = ''t discovered what that way is.; -- empty string for concatenation end return format;end
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.--[[--------------------------< G E T _ D I S P L A Y _ N A M E S >--------------------------------------------
]]Returns a number that defines the number of names displayed for author and editor name lists and a Boolean flagto indicate when et al. should be appended to the name list.
local function select_author_editor_source (vxxxxors, When the value assigned to |display-xxxxors= is a number greater than or equal to zero, args, list_name)return the number andlocal lastfirst = false; if select_one( args, cfg.aliases[list_name .. the previous state of the '-Lastetal'], 'none', 1 ) or -- do this twice incase we flag (false by default but may have a |first1= without a |last1=; this ...been set to true if the name list contains select_one( args, cfg.aliases[list_name .. '-Firstsome variant of the text '], 'none', 1 ) or -- ... also catches the case where |first= is used with |vauthors= select_one( args, cfg.aliases[list_name .et al. '-Last'], 'none', 2 ) or select_one( args, cfg.aliases[list_name .. '-First'], 'none', 2 ) then lastfirst=true; end
if (is_set (vxxxxors) and true == lastfirst) or -When the value assigned to |display- these are the three error conditions (is_set (vxxxxors) and is_set (xxxxors)) or (true == lastfirst and is_set (xxxxors)) then local err_name; if is the keyword 'AuthorListetal' == list_name then -- figure out which name should be used , return a number that is one greater than thenumber of authors in error message err_name = the list and set the 'authoretal'; else err_name = 'editor'; end tableflag true.insert This will cause the list_people( z.message_tail, { set_error( 'redundant_parameters',) to display all of {err_name .. '-the names in the name-list parametersfollowed by 'et al.'}, true ) } ); -- add error message end
if true == lastfirst then return 1 end; -- return a number indicating which author name source to use if is_set (vxxxxors) then return 2 end; if is_set (xxxxors) then return 3 end; return 1; -- no authors so return 1; this allows missing author name test to run in case there is a first without last endIn all other cases, returns nil and the previous state of the 'etal' flag.
inputs:
max: A['DisplayAuthors'] or A['DisplayEditors']; a number or some flavor of etal
count: #a or #e
list_name: 'authors' or 'editors'
etal: author_etal or editor_etal
--[[--------------------------< I S _ V A L I D _ P A R A M E T E R _ V A L U E >------------------------------]]
This local function is used to validate a parameter's assigned value for those parameters that have only a limited numberof allowable values get_display_names (yesmax, y, truecount, nolist_name, etcetal) if utilities. When the parameter value has not been assigned a value is_set (missing or emptymax) thenin the source template if 'etal' == max:lower() the function returns true:gsub("[ '%. If ]", '') then -- the parameter value is one :gsub() portion makes 'etal' from a variety of the list 'et al.' spellings and stylings max = count + 1; -- number of allowed values returnsauthors + 1 so display all author name plus et al. etal = true; else, emits an error message and returns false. ]] local function is_valid_parameter_value -- overrides value set by extract_names(value, name, possible) if not is_set elseif max:match (value'^%d+$') then -- if is a string of numbers return true max = tonumber (max); -- make it a number if max >= count then -- an empty parameter is okif |display-xxxxors= value greater than or equal to number of authors/editors elseif in_array table.insert(value:lowerz.message_tail, {utilities.set_message ('err_disp_name', {cfg.special_case_translation [list_name], max}, true), possible}) then; -- add error message return true max = nil; end else -- not a valid keyword or number table.insert( z.message_tail, { set_errorutilities.set_message ( 'invalid_param_valerr_disp_name', {namecfg.special_case_translation [list_name], valuemax}, true ) } ); -- not an allowed value so add error message max = nil; -- unset; as if |display-xxxxors= had not been set return falseend
end
return max, etal;
end
--[[--------------------------< E X T E R M I N A _ T E X T _ I N _ P A M G E _ L I S T C H E C K >---------------------------------------- Adds page to Category:CS1 maint: extra text if |page= or |pages= has what appears to be some form of p. or pp. abbreviation in the first characters of the parameter content.
This function terminates a name list (authorcheck Page and Pages for extraneous p, p., contributorpp, editor) with a separator character and pp. at start of parameter value: good pattern: '^P[^%.P%l]' matches when |page(sepcs) = begins PX or P# but not Px where x and X are letters and # is a spacedgiit bad pattern: '^[Pp][Pp]' matches matches when the last character is not a sepc character or when the last three characters are not sepc followed by twoclosing square brackets |page(close of a wikilinks). When either of these is true, the name_list is terminated with asingle space character.= begins pp or pP or Pp or PP
]]
local function terminate_name_list extra_text_in_page_check (name_list, sepcpage) if (stringlocal good_pattern = '^P[^%.sub (name_list,Pp]'; -3,-1ok to begin with uppercase P: P7 (pg 7 of section P) but not p123 (page 123) TODO: add Gg for PG or Pg? local bad_pattern == sepc .. '^[Pp]?[Pp]%. ?[ %d]') then -- if already properly terminated; return name_list; -- just return the name list elseif if not page:match (string.sub good_pattern) and (page:match (name_list,-1,-1) == sepcbad_pattern) or page:match (string.sub (name_list,-3,-1) == sepc .. '^[Pp]]ages?')) then -- if last name in list ends with sepc char return name_list utilities.. " "set_message ('maint_extra_text'); -- don't add another else return name_list .. sepc .. ' 'maint cat; -- otherwise terninate the name list
end
end
--[=[-------------------------< F O R M A G E T _ V O L U _ N A M E _ I S S U T A B L E >---------------------------------------------- split apart a |vauthors= or |veditors= parameter. This function allows for corporate names, wrapped in doubledparentheses to also have commas; in the old version of the code, the doubled parentheses were included in therendered citation and in the metadata. Individual author names may be wikilinked  |vauthors=Jones AB, [[E. B. White|White EB]], ((Black, Brown, and Co.))
returns the concatenation of the formatted volume and issue parameters as a single string; or formatted volumeor formatted issue, or an empty string if neither are set.]=]
]]local function get_v_name_table (vparam, output_table, output_link_table) local name_table = mw.text.split(vparam, "%s*,%s*"); -- names are separated by commas local wl_type, label, link; -- wl_type not used here; just a place holder
local function format_volume_issue (volume, issue, cite_class, origin, sepc, lower) if not is_set (volume) and not is_set (issue) then return ''i = 1; end
while name_table[i] do if name_table[i]:match ('magazine' == cite_class or ^%(in_array %(cite_class, {'citation', 'map'}.*[^%)][^%) and 'magazine]$' == origin) then -- first segment of corporate with one or more commas; this segment has the opening doubled parentheses if is_set (volume) and is_set (issue) then local name = name_table[i]; return wrap_msg ('voli = i + 1; --no', {sepc, volume, issue}, lower);bump indexer to next segment elseif is_set (volume) then while name_table[i] do return wrap_msg ( name = name .. 'vol, ', {sepc, volume}, lower).. name_table[i]; -- concatenate with previous segments else return wrap_msg if name_table[i]:match ('issue^.*%)%)$', {sepc, issue}, lower)then -- if this table member has the closing doubled parentheses break; -- and done reassembling so end end  local vol i = ''i + 1; -- bump indexer end if is_set (volume) then if (4 < mw.ustring table.leninsert (volumeoutput_table, name)) then; -- and add corporate name to the output table vol = substitute table.insert (cfg.messages[output_link_table, 'j-vol'], {sepc, volume}); -- no wikilink
else
vol wl_type, label, link = substitute utilities.is_wikilink (cfg.presentationname_table['voli]); -bold'- wl_type is: 0, no wl (text in label variable); 1, [[D]]; 2, {sepc[[L|D]] table.insert (output_table, hyphen_to_dashlabel); -- add this name if 1 == wl_type then table.insert (volumeoutput_link_table, label)}; -- simple wikilink [[D]] else table.insert (output_link_table, link); -- no wikilink or [[L|D]]; add this link if there is one, else empty string end
end
end if is_set (issue) then return vol .. substitute (cfg.messages['j-issue'], issue)i = i + 1; end return voloutput_table;
end
--[[--------------------------< F O P A R M S E _ V A U T _ P A G E H O R S _ S H E 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 namesmay sometimes be required and because such names will often fail the is_good_vanc_name() and other format compliancetests, are wrapped in doubled parentheses ((corporate name)) to suppress the format tests.
adds static text to one of |page(s)= or |sheet(s)= values and returns it with all of the others set to empty strings.The return order is: pageSupports generational suffixes Jr, pages2nd, sheet3rd, sheets4th–6th.
Singular has priority over plural This function sets the Vancouver error when both are provideda required comma is missing and when there is a space between an author's initials.
]]
local function format_pages_sheets parse_vauthors_veditors (pageargs, pagesvparam, sheetlist_name) local names = {}; -- table of names assembled from |vauthors=, sheets|author-maskn=, cite_class|author-linkn= local v_name_table = {}; local v_link_table = {}; -- when name is wikilinked, origintargets go in this table local etal = false; -- return value set to true when we find some form of et al. vauthors parameter local last, sepcfirst, link, mask, suffix; local corporate = false;  vparam, etal = name_has_etal (vparam, noppetal, lowertrue); -- 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, v_link_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 v_name:match ('^%(%(.+%)%)$') then -- corporate authors are wrapped in doubled parentheses to suppress vanc formatting and error detection-- last = v_name:match ('map^%(%((.+)%)%)$' ) -- remove doubled parentheses if accept_name then last =v_name; corporate = cite_class then true; -- only cite map supports sheetflag used in list_people(s) as in-source locators if is_set elseif string.find(sheetv_name, "%s") then if v_name:find('journal[;%.]' ) then -- look for commonly occurring punctuation characters; add_vanc_error (cfg.err_msg_supl.punctuation); end local lastfirstTable ={} lastfirstTable = origin thenmw.text.split(v_name, "%s+") first = table.remove(lastfirstTable); -- removes and returns value of last element in table which should be initials or generational suffix  return '' if not mw.ustring.match (first, '^%u+$', wrap_msg ) 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('jlastfirstTable); --sheet', sheet, lower)get what should be the initials from the table end last = table.concat(lastfirstTable, '';) -- returns a string that is the concatenation of all other names that are not initials and generational suffix elseif not utilities.is_set (last) then return first = ''; -- unset last = v_name; -- last empty because something wrong with first add_vanc_error (cfg.err_msg_supl.name); end if mw.ustring.match (last, '%a+%s+%u+%s+%a+', wrap_msg ) then add_vanc_error (cfg.err_msg_supl['sheetmissing comma', {sepc, sheet}, lower]), ''; -- matches last II last; the case when a comma is missing
end
elseif is_set (sheets) then if mw.ustring.match (v_name, 'journal%u %u$' == origin ) then return '', '', '', wrap_msg ('j --sheets', sheets, lower); elsethis test is in the wrong place TODO: move or replace with a more appropriate test return '', '', '', wrap_msg add_vanc_error ('sheets', {sepc, sheets}, lowercfg.err_msg_supl.name); -- matches a space between two initials
end
else
last = v_name; -- last name or single corporate name? Doesn't support multiword corporate names? do we need this?
end
end   local is_journal = 'journal' == cite_class or (in_array (cite_class, {'citation', 'map'}) and 'journal' == origin); if utilities.is_set (pagefirst) then if is_journal not mw.ustring.match (first, "^%u?%u$") then -- first shall contain one or two upper-case letters, nothing else return substitute add_vanc_error (cfg.messages['jerr_msg_supl.initials); --pagetoo many initials; mixed case initials (s)'], pagewhich may be ok Romanization), '', '', '';hyphenated initials elseif not nopp then end return substitute is_good_vanc_name (cfg.messages['p-prefix']last, {sepcfirst, page}suffix), ; -- check first and last before restoring the suffix which may have a non-Latin digit if utilities.is_set (suffix) then first = first .. '', .. suffix; -- if there was a suffix concatenate with the initials suffix = '', ; -- unset so we don'';t add this suffix to all subsequent names end
else
return substitute (cfg.messages['nopp'], {sepc, page}), '', '', ''; end elseif is_set(pages) then if is_journal then return substitute (cfg.messages['j-page(s)'], pages), '', '', ''; elseif tonumber(pages) ~= nil and not nopp corporate then -- if pages is only digits, assume a single page number return '', substitute is_good_vanc_name (cfg.messages['p-prefix'], {sepc, pages}), ''last, ''; elseif not nopp then return '', substitute (cfg.messages['pp-prefix'], {sepc, pages}), '', ''; else return '', substitute (cfg.messages['nopp'], {sepc, pages}), '', '';end
end
 
link = utilities.select_one ( args, cfg.aliases[list_name .. '-Link'], 'err_redundant_parameters', i ) or v_link_table[i];
mask = utilities.select_one ( args, cfg.aliases[list_name .. '-Mask'], 'err_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 empty stringsour list of names
end
--[=[--------------------------< S E L E C T _ A U T H O R C H _ E D I V E T O R _ S O U R L _ C H E C K >--------------------------------------------
Check archive.org urls to make sure they at least look like they are pointing at valid archives and not to Select one of |authors=, |authorn= / |lastn / firstn=, or |vauthors= as the source of the author name list orsave snapshot url or to calendar pages. When the archive url is 'https:select one of |editors=, |editorn= /editor-lastn= /web.archive.org/save/' (|editor-firstn= or http://...)archive.org saves a snapshot |veditors= as the source of the target page in the url. That is something that Wikipedia should not allowunwitting readers to doeditor name list.
When the archive.org url does not have a complete timestamp, archive.org chooses a snapshot according to its ownalgorithm or provides a calendar 'search' resultOnly one of these appropriate three will be used. [[WPThe hierarchy is:ELNO]] discourages links to search results.|authorn= (and aliases) highest and |authors= lowest andsimilarly, |editorn= (and aliases) highest and |editors= lowest
This function looks at the value assigned to When looking for |archive-urlauthorn= and returns empty strings for / |archive-urleditorn= andparameters, test |archive-datexxxxor1= and an error message when: |archive-urlxxxxor2= holds an archive.org save command url(and all of their aliases); stops after the second |archive-url= is an archive.org url that does not have a complete timestamp test which mimicks the test used in extract_names(YYYYMMDDhhmmss 14 digits) when looking for a hole in theauthor name list. There may be a better correct placeotherwise returns |archive-url= and |archive-date=way to do this, I just haven't discovered what that way is.
There are two mostly compatible archiveEmits an error message when more than one xxxxor name source is provided.org urls: //web.archive.org/<timestamp>... -- the old form //web.archive.org/web/<timestamp>... -- the new form
The old form does not support In this function, vxxxxors = vauthors or map to the new form when it contains a display flag. There are four identified flags('id_', 'js_', 'cs_', 'im_') but since archive.org ignores others following the same form (two letters and an underscore)we don't check for these specific flags but we do check the formveditors; xxxxors = authors or editors as appropriate.
This function supports a preview mode. When the article is rendered in preview mode, this funct may return a modifiedarchive url: for save command errors, return undated wildcard (/*/) for timestamp errors when the timestamp has a wildcard, return the url unmodified for timestamp errors when the timestamp does not have a wildcard, return with timestamp limited to six digits plus wildcard (/yyyymm*/)]]
]=] local function archive_url_check select_author_editor_source (urlvxxxxors, datexxxxors, args, list_name) local err_msg lastfirst = ''false; -- start with the error message empty local pathif utilities.select_one ( args, cfg.aliases[list_name .. '-Last'], timestamp'none', flag; 1 ) or -- portions of the archivedo this twice in case we have a |first1= without a |last1=; this ...or url if utilities.select_one (not url:match('//web%args, cfg.aliases[list_name .archive%.org/')) and (not url:match(-First'], '//liveweb%.archive%.org/none', 1 )) then or -- ... also deprecated liveweb Wayback machine urlcatches the case where |first= is used with |vauthors= return urlutilities.select_one ( args, date; cfg.aliases[list_name .. '-Last'], 'none', 2 ) or utilities.select_one ( args, cfg.aliases[list_name .. '- not an archive.org archiveFirst'], 'none', return ArchiveURL and ArchiveDate2 ) then lastfirst = true;
end
if url:match('//web%utilities.archive%.org/save/'is_set (vxxxxors) then -- if a save command url, we don't want to allow saving of the target page err_msg and true = 'save command'; url = url:gsub ('(//web%.archive%.orglastfirst)/save/', '%1/*/', 1); or -- for preview mode: modify ArchiveURL elseif url:match('//liveweb%.archive%.org/') thenthese are the three error conditions err_msg = 'liveweb'; else path, timestamp, flag = url:match('//web%utilities.archive%.org/is_set ([^%d]*vxxxxors)and utilities.is_set (%d+xxxxors)([^/]*)/'); -- split out some of the url parts for evaluationor if not (true == lastfirst and utilities.is_set(timestampxxxxors) or 14 ~= timestamp:len() then -- path and flag optional, must have 14-digit timestamp here err_msg = 'timestamp'local err_name; if '*AuthorList' ~= flag = list_name then -- figure out which name should be used in error message urlerr_name =url:gsub ('(//web%.archive%.org/[^%d]*%d?%d?%d?%d?%d?%d?)[^/]*author', ; else err_name = '%1*editor', 1) -- for preview, modify ts to be yearmo* max (0-6 digits plus splat);
end
elseif is_set(path) and 'web/' ~= path then -- older archive urls do not have the extra 'web/' path element err_msg = 'path'; elseif is_set table.insert(flag) and not is_set z.message_tail, { utilities.set_message (path) then -- flag not allowed with the old form url (without the 'web/err_redundant_parameters' path element), err_msg = 'flag'; elseif is_set (flag) and not flag:match ( {err_name .. '%a%a_') then -name- flag if present must be two alpha characters and underscore (requires list parameters'web/' path element}, true ) } ) err_msg = 'flag'; else return url, date; -- return archiveURL and ArchiveDate endadd error message
end
if true == lastfirst then return 1 end; -- if here, something not right soreturn a number indicating which author name source to use tableif utilities.insertis_set ( zvxxxxors) then return 2 end; if utilities.message_tail, { set_erroris_set ( xxxxors) then return 3 end; return 1; -- no authors so return 1; this allows missing author name test to run in case there is a first without last end  --[[--------------------------< I S _ V A L I D _ P A R A M E T E R _ V A L U E >------------------------------ This function is used to validate a parameter'archive_url's assigned value for those parameters that have only a limited numberof allowable values (yes, {err_msg}y, true , live, dead, etc.) } . When the parameter value has not been assigned a value (missingor empty in the source template)the function returns the value specified by ret_val. If the parameter value is oneof the list of allowed values returns the translated value; -- add else, emits an error message andreturns the valuespecified by ret_val. ]] local function is_valid_parameter_value (value, name, possible, ret_val) if not utilities.is_set (Frame:preprocessvalue) then return ret_val; -- an empty parameter is ok elseif utilities.in_array ('{{REVISIONID}}')value, possible) then return '', ''cfg.keywords_xlate[value]; -- return empty strings for archiveURL and ArchiveDatetranslation of parameter keyword
else
return urltable.insert( z.message_tail, { utilities.set_message ( 'err_invalid_param_val', {name, value}, datetrue ) } ); -- preview mode not an allowed value so add error message return archiveURL and ArchiveDateret_val;
end
end
--[[--------------------------< T E R M I S S I N G A T E _ P I P N A M E _ C H E C K L I S T >------------------------------------------
Look at This function terminates a name list (author, contributor, editor) with a separator character (sepc) and a spacewhen the contents of last character is not a parameter. If sepc character or when the content has a string of last three characters and digits are not sepc followed by an equaltwosign, compare the alphanumeric string to the list closing square brackets (close of cs1|2 parametersa wikilink). If foundWhen either of these is true, then the string name_list is possibly terminated with aparameter that is missing its pipe: {{cite ... |title=Title access-date=2016-03-17}} cs1|2 shares some parameter names with xml/html atributes: class=, title=, etc. To prevent false positives xml/htmltags are removed before the search. If a missing pipe is detected, this function adds the missing pipe maintenance categorysingle space character.
]]
local function missing_pipe_check terminate_name_list (valuename_list, sepc) local capture; value = value:gsub if (string.sub ('%b<>'name_list, -3, -1) == sepc .. '. ')then -- if already properly terminated return name_list; -- remove xml/html tags because attributes: class=, title=, etc just return the name list capture = value:match elseif ('%s+string.sub (%a[%a%d]+name_list, -1, -1)%s*='= sepc) or value:match ('^string.sub (%a[%a%d]+name_list, -3, -1)%s*== sepc .. ']]'); then -- find and categorize parameters if last name in list ends with possible missing pipessepc char if capture and validate (capture) then return name_list .. " "; -- if the capture is a valid parameter namedon't add another else add_maint_cat (return name_list .. sepc .. 'missing_pipe'); -- otherwise terminate the name list
end
end
--[[--------------------------< C I T F O R M A T _ V O L U M E _ I O N 0 S S U E >------------------------------------------------------------
This is returns the main function doing the majority concatenation of the citation formattingformatted volume and issue parameters as a single string; or formatted volumeor formatted issue, or an empty string if neither are set.
]]
local function format_volume_issue (volume, issue, cite_class, origin, sepc, lower)
if not utilities.is_set (volume) and not utilities.is_set (issue) then
return '';
end
if 'magazine' == cite_class or (utilities.in_array (cite_class, {'citation', 'map'}) and 'magazine' == origin) then
if utilities.is_set (volume) and utilities.is_set (issue) then
return wrap_msg ('vol-no', {sepc, volume, issue}, lower);
elseif utilities.is_set (volume) then
return wrap_msg ('vol', {sepc, volume}, lower);
else
return wrap_msg ('issue', {sepc, issue}, lower);
end
end
local function citation0 if 'podcast' == cite_class and utilities.is_set ( config, argsissue)then --[[ Load Input Parameters The argument_wrapper facilitates the mapping of multiple aliases to single internal variable. ]] local A = argument_wrapper return wrap_msg ( args 'issue', {sepc, issue}, lower); local i end
local vol = ''; -- Pick out the relevant fields from the argumentshere for all cites except magazine if utilities.is_set (volume) then if volume:match ('^[MDCLXVI]+$') or volume:match ('^%d+$') then -- volume value is all digits or all uppercase Roman numerals vol = utilities.substitute (cfg.presentation['vol-bold'], {sepc, hyphen_to_dash(volume)}); -- render in bold face elseif (4 < mw.ustring.len(volume)) then -- not all digits or Roman numerals and longer than 4 characters vol = utilities.substitute (cfg.messages['j-vol'], {sepc, volume}); -- not bold utilities. Different citation templatesadd_prop_cat ('long_vol'); else -- four or less characters vol = utilities.substitute (cfg.presentation['vol-bold'], {sepc, hyphen_to_dash(volume)}); -- define different field names for the same underlying thingsbold end end if utilities.is_set (issue) then return vol .. utilities.substitute (cfg. messages['j-issue'], issue); end return vol;end
-- set default parameter values defined by |mode= parameter.
local Mode = A['Mode'];
if not is_valid_parameter_value (Mode, 'mode', cfg.keywords['mode']) then
Mode = '';
end
local author_etal; local a = {}; -- authors list from |lastn= / |firstn= pairs or |vauthors= local Authors; local NameListFormat = [[-------------------------< F O R M A['NameListFormat']; local Collaboration = T _ P A['Collaboration'];G E S _ S H E E T S >-----------------------------------------
do -- adds static text to limit scope one of |page(s)= or |sheet(s)= values and returns it with all of selectedthe others set to empty strings.The return order is: page, pages, sheet, sheets Singular has priority over plural when both are provided. ]]  local selected = select_author_editor_source function format_pages_sheets (A['Vauthors']page, pages, sheet, sheets, cite_class, origin, A['Authors']sepc, argsnopp, lower) if 'AuthorListmap'== cite_class then -- only cite map supports sheet(s);as in-source locators if 1 utilities.is_set (sheet) then if 'journal' == selected origin then a return '', '', author_etal = extract_names wrap_msg (args, 'AuthorListj-sheet', sheet, lower); -- fetch author list from |authorn= / |lastn= / |firstn=, |author-linkn=, and |author-maskn= elseif 2 == selected then NameListFormat = 'vanc'; -- override whatever |name-list-format= might be aelse return '', '', author_etal = parse_vauthors_veditors wrap_msg (args'sheet', args.vauthors{sepc, sheet}, lower), 'AuthorList'); -- fetch author list from |vauthors=, |author-linkn=, and |author-maskn= end elseif 3 == selected utilities.is_set (sheets) then Authors = A[if 'Authorsjournal']; -- use content of |authors== origin then if return '', '', 'authors' == A:ORIGIN, wrap_msg ('Authorsj-sheets', sheets, lower) then -- but add a maint cat if the parameter is |authors=; else add_maint_cat return '', '', '', wrap_msg ('authorssheets', {sepc, sheets}, lower); -- because use of this parameter is discouraged; what to do about the aliases is a TODO:
end
end
if is_set (Collaboration) then
author_etal = true; -- so that |display-authors=etal not required
end
end
local Others is_journal = A['Othersjournal'];  local editor_etal; local e = = cite_class or (utilities.in_array (cite_class, {'citation', 'map', 'interview'}; -- editors list from |editor-lastn) and 'journal' = / |editor-firstn= pairs or |veditors=origin); local Editors;  do -- to limit scope of selectedif utilities.is_set (page) then local selected = select_author_editor_source if is_journal then return utilities.substitute (Acfg.messages['Veditorsj-page(s)'], A[page), 'Editors'], args'', 'EditorList'); if 1 == selected elseif not nopp then ereturn utilities.substitute (cfg.messages['p-prefix'], {sepc, page}), '', editor_etal = extract_names (args'', 'EditorList'); else -- fetch editor list from |editorn= / |editor-lastn= / |editor-firstn=return utilities.substitute (cfg.messages['nopp'], {sepc, page}), '', |editor-linkn='', and |editor-maskn=''; end elseif 2 == selected utilities.is_set (pages) then if is_journal then NameListFormat = return utilities.substitute (cfg.messages['j-page(s)'], pages), '', '', 'vanc'; elseif tonumber(pages) ~= nil and not nopp then -- override whatever |name-list-format= might beif pages is only digits, assume a single page number ereturn '', editor_etal = parse_vauthors_veditors utilities.substitute (args, argscfg.veditors, messages['EditorListp-prefix'], {sepc, pages}); -- fetch editor list from |veditors=, |editor-linkn='', and |editor-maskn=''; elseif 3 == selected not nopp then Editors = Areturn '', utilities.substitute (cfg.messages['Editorspp-prefix'], {sepc, pages}), '', ''; -- use content of |editors= else add_maint_cat return '', utilities.substitute (cfg.messages['editorsnopp'], {sepc, pages}), '', ''; -- but add a maint cat because use of this parameter is discouraged
end
end
return '', '', '', ''; -- return empty strings
end
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 interviewers_list = {}; local Interviewers = A--[['Interviewers'] if is_set (Interviewers) then -- add a maint cat if ------------------------< I N S O U R C E _ L O C _ G E T >---------------------------------------------- returns one of the |interviewers= is usedin-source locators: page, pages, or at.   add_maint_cat ('interviewers'); If any of these are interwiki links to Wikisource, returns the label portion of the interwiki-- because link as plain textfor use of this parameter in COinS. This COinS thing is discourageddone because here we convert an interwiki-link to and external link and else interviewers_list = extract_names add an icon span around that; get_coins_pages(args, ) doesn'InterviewerList')t know about the span. TODO: should it?  TODO: add support for sheet and sheets?; streamline;  TODO: make it so that this function returns only one of the three as the single in-- else, process preferred interviewers parameterssource (the return value assigned endto a new name)?
]] local function insource_loc_get (page, pages, at) local c = {}ws_url, ws_label, coins_pages, L; -- contributors list from |contributorfor Wikisource interwiki-lastn= / contributor-firstn= pairslinks; TODO: this corrupts page metadata (span remains in place after cleanup; fix there?)  local Contributorsif utilities.is_set (page) then if utilities.is_set (pages) or utilities.is_set (at) then pages = ''; -- assembled contributors name listunset the others local Contribution at = A['Contribution']; end extra_text_in_page_check (page); -- add this page to maint cat if in_array(config|page= value begins with what looks like p. or pp.CitationClass  ws_url, {"book"ws_label,"citation"}) and not is_setL = wikisource_url_make (A['Periodical']page) then ; -- make ws URL from |contributorpage= and |contribution= only supported in book citesinterwiki link; link portion L becomes tooltip label c if ws_url then page = extract_names external_link (argsws_url, ws_label .. '&nbsp;', 'ContributorListws link in page'); -- fetch contributor list space char after label to move icon away from |contributornin-source text; TODO: a better way to do this? page = / |contributorutilities.substitute (cfg.presentation['interwiki-lastn=icon'], {cfg.presentation['class-firstn=wikisource'], -linkn=L, -masknpage}); coins_pages =ws_label; end elseif utilities.is_set (pages) then if 0 < #c utilities.is_set (at) then if not is_set at = ''; -- unset end extra_text_in_page_check (Contributionpages) then ; -- add this page to maint cat if |contributorpages= value begins with what looks like p. or pp.  ws_url, ws_label, L = requires wikisource_url_make (pages); -- make ws URL from |contributionpages=interwiki link; link portion L becomes tooltip label table if ws_url then pages = external_link (ws_url, ws_label .insert( z.message_tail, { set_error( 'contributor_missing_required_param&nbsp;', 'contributionws link in pages')}); -- add missing contribution error message c = {}; space char after label to move icon away from in-- blank the contributors' tablesource text; it is used as TODO: a flag later endbetter way to do this? if 0 == #a then -- |contributor= requires |authorpages = tableutilities.insertsubstitute ( zcfg.message_tailpresentation['interwiki-icon'], { set_error( cfg.presentation['contributor_missing_required_paramclass-wikisource'], L, 'author')pages}); -- add missing author error message c coins_pages = {}ws_label; -- blank the contributors' table; it is used as a flag later end
end
else elseif utilities.is_set (at) then ws_url, ws_label, L = wikisource_url_make (at); -- if not a book citemake ws URL from |at= interwiki link; link portion L becomes tooltip label if select_one ws_url then at = external_link (argsws_url, cfgws_label ..aliases['ContributorList-Last&nbsp;'], 'redundant_parametersws link in at', 1 ) then ; --space char after label to move icon away from in- are there contributor name list parameterssource text; TODO: a better way to do this? tableat = utilities.insertsubstitute ( zcfg.message_tailpresentation['interwiki-icon'], { set_error( cfg.presentation['contributor_ignoredclass-wikisource')], L, at}); -- add contributor ignored error message coins_pages = ws_label;
end
Contribution = nil; -- unset
end
return page, pages, at, coins_pages;
end
if not is_valid_parameter_value (NameListFormat, 'name-list-format', cfg.keywords['name-list-format']) then -- only accepted value for this parameter is 'vanc'
NameListFormat = ''; -- anything else, set to empty string
end
local Year = A--['Year']; local PublicationDate = A['PublicationDate']; local OrigYear = A['OrigYear']; local Date = A['Date']; local LayDate = A['LayDate']; -------------------------< A R C H I V E _ U R L _ C H E C K >---------------------------------------- Get title data local Title = A['Title']; local ScriptTitle = A['ScriptTitle']; local BookTitle = A['BookTitle']; local Conference = A['Conference']; local TransTitle = A['TransTitle']; local TitleNote = A['TitleNote']; local TitleLink = A['TitleLink']; link_title_ok (TitleLink, A:ORIGIN ('TitleLink'), Title, 'title'); -- check for wikimarkup in |title-link= or wikimarkup in |title= when |title-link= is set
local Chapter = A['Chapter'];Check archive.org URLs to make sure they at least look like they are pointing at valid archives and not to the local ScriptChapter = A[save snapshot URL or to calendar pages. When the archive URL is 'ScriptChapterhttps://web.archive.org/save/'];(or http://...) local ChapterLink -- = A['ChapterLink']; -- deprecated as archive.org saves a parameter but still used internally by cite episodesnapshot of the target page in the URL. That is something that Wikipedia should not allowunwitting readers to do.  local TransChapter = A['TransChapter'];When the archive.org URL does not have a complete timestamp, archive.org chooses a snapshot according to its own local TitleType = A[algorithm or provides a calendar 'TitleTypesearch']; local Degree = Aresult. ['Degree']; local Docket = A['Docket'WP:ELNO]; local ArchiveFormat = A['ArchiveFormat'];discourages links to search results.
This function looks at the value assigned to |archive-url= and returns empty strings for |archive-url= and|archive-date= and an error message when: local ArchiveDate;|archive-url= holds an archive.org save command URL local ArchiveURL;|archive-url= is an archive.org URL that does not have a complete timestamp (YYYYMMDDhhmmss 14 digits) in the correct placeotherwise returns |archive-url= and |archive-date=
There are two mostly compatible archive.org URLs: ArchiveURL, ArchiveDate = archive_url_check (A['ArchiveURL'], A['ArchiveDate'])//web.archive.org/<timestamp>... -- the old form local DeadURL = A['DeadURL'] if not is_valid_parameter_value (DeadURL, 'dead-url', cfg//web.archive.org/web/<timestamp>..keywords ['deadurl']) then -- set in config.defaults to 'yes' DeadURL = ''; -- anything else, set to empty string endthe new form
local URL = A['URL']The old form does not support or map to the new form when it contains a display flag. There are four identified flags local URLorigin = A:ORIGIN('URLid_'); -- get name of parameter that holds URL local ChapterURL = A[, 'ChapterURLjs_']; local ChapterURLorigin = A:ORIGIN(, 'ChapterURLcs_'); -- get name of parameter that holds ChapterURL local ConferenceFormat = A[, 'ConferenceFormatim_']; local ConferenceURL = A['ConferenceURL']; local ConferenceURLorigin = A:ORIGIN) but since archive.org ignores others following the same form ('ConferenceURL'two letters and an underscore); -- get name of parameter that holds ConferenceURL local Periodical = A['Periodical']; local Periodical_origin = A:ORIGIN('Periodicalwe don'); -- get the name of t check for these specific flags but we do check the periodical parameterform.
local Series = A['Series']; local Volume;This function supports a preview mode. When the article is rendered in preview mode, this function may return a modified local Issue;archive URL: local Page;for save command errors, return undated wildcard (/*/) local Pages;for timestamp errors when the timestamp has a wildcard, return the URL unmodified local At;for timestamp errors when the timestamp does not have a wildcard, return with timestamp limited to six digits plus wildcard (/yyyymm*/)
]=] local function archive_url_check (url, date) local err_msg = ''; -- start with the error message empty local path, timestamp, flag; -- portions of the archive.org URL if in_array (confignot url:match('//web%.archive%.org/')) and (not url:match('//liveweb%.CitationClass, cfgarchive%.templates_using_volumeorg/')) then -- also deprecated liveweb Wayback machine URL Volume = A['Volume']return url, date; -- not an archive.org archive, return ArchiveURL and ArchiveDate
end
if url:match('//web%.archive%.org/save/') then -- if a save command URL, we don't want to allow saving of the target page err_msg = cfg.err_msg_supl.save; url = url:gsub ('(//web%.archive%.org)/save/', '%1/*/', 1); -- conference & map books do not support issuefor preview mode: modify ArchiveURL if in_array elseif url:match(config'//liveweb%.CitationClassarchive%.org/') then err_msg = cfg.err_msg_supl.liveweb; else path, cfgtimestamp, flag = url:match('//web%.archive%.org/([^%d]*)(%d+)([^/]*)/'); -- split out some of the URL parts for evaluation if not utilities.templates_using_issueis_set (timestamp) or 14 ~= timestamp:len() then -- path and not flag optional, must have 14-digit timestamp here err_msg = cfg.err_msg_supl.timestamp; if '*' ~= flag then url=url:gsub (in_array '(config//web%.archive%.CitationClassorg/[^%d]*%d?%d?%d?%d?%d?%d?)[^/]*', {'conference%1*', 1) -- for preview, modify ts to be yearmo* max (0-6 digits plus splat) end elseif utilities.is_set (path) and 'mapweb/'}~= path then -- older archive URLs do not have the extra 'web/' path element err_msg = cfg.err_msg_supl.path; elseif utilities.is_set (flag) and not utilities.is_set (Periodicalpath)then -- flag not allowed with the old form URL (without the 'web/' path element)then err_msg = cfg.err_msg_supl.flag; Issue = A[elseif utilities.is_set (flag) and not flag:match ('%a%a_') then -- flag if present must be two alpha characters and underscore (requires 'Issueweb/']path element) err_msg = cfg.err_msg_supl.flag; else return url, date; -- return ArchiveURL and ArchiveDate end
end
-- if here, something not right so local Position = table.insert( z.message_tail, { utilities.set_message ( 'err_archive_url', {err_msg}, true ) } );-- add error message and if not in_array utilities.is_set (Frame:preprocess(config.CitationClass, cfg.templates_not_using_page'{{REVISIONID}}')) then Page = A[return 'Page']; Pages = hyphen_to_dash( A[, 'Pages'] ); -- return empty strings for ArchiveURL and ArchiveDate else At = A['At']return url, date; -- preview mode so return ArchiveURL and ArchiveDate
end
end
local Edition = A['Edition'];
local PublicationPlace = A['PublicationPlace']
local Place = A['Place'];
local PublisherName = A['PublisherName'];
local RegistrationRequired = A['RegistrationRequired'];
if not is_valid_parameter_value (RegistrationRequired, 'registration', cfg.keywords ['yes_true_y']) then
RegistrationRequired=nil;
end
local SubscriptionRequired = A--['SubscriptionRequired']; if not is_valid_parameter_value (SubscriptionRequired, 'subscription', cfg.keywords ['yes_true_y']) then SubscriptionRequired=nil; end--------------------------< P L A C E _ C H E C K >--------------------------------------------------------
local UrlAccess check |place= A['UrlAccess']; if not is_valid_parameter_value (UrlAccess, 'url|publication-access'place=, cfg|location= to see if these params include digits.keywords ['url This function added becausemany editors misuse location to specify the in-access']source location (|page(s) then UrlAccess = nil; end if not is_set(URL) and is_set(UrlAccess) then UrlAccess |at= nil; table.insert( z.message_tail, { set_error( 'param_access_requires_param', {'url'}, true are supposed to do that) } ); end
if is_set (UrlAccess) and is_set (SubscriptionRequired) then -- while not aliases, these are much returns the same so if both are set table.insert( z.message_tail, { set_error( 'redundant_parameters', {wrap_style ('original parameter', 'url-access') .. ' and ' .. wrap_style ('parameter', 'subscription')}, true ) } ); -- add error message SubscriptionRequired = nil; -- unsetvalue without modification; prefer |access= over |subscription= end if is_set (UrlAccess) and is_set (RegistrationRequired) then -- these are not the same but contradictory so if both are set table.insert( z.message_tail, { set_error( 'redundant_parameters', {wrap_style ('parameter', 'url-access') .. ' and ' .. wrap_style ('added maint cat when parameter', 'registration')}, true ) } ); -- add error message RegistrationRequired = nil; -- unset; prefer |access= over |registration= endvalue contains digits
]] local ChapterUrlAccess = A['ChapterUrlAccess'];function place_check (param_val) if not is_valid_parameter_value utilities.is_set (ChapterUrlAccess, 'chapter-url-access', cfg.keywords ['url-access']param_val) then -- same as url-accessparameter empty or omitted ChapterUrlAccess = nil return param_val; -- return that empty state end if not is_setmw.ustring.find (ChapterURL) and is_set(ChapterUrlAccessparam_val, '%d') then -- not empty, are there digits in the parameter value ChapterUrlAccess = nil; table.insert( z utilities.message_tail, { set_errorset_message ( 'param_access_requires_parammaint_location', {'chapter); --url'}yep, true ) } )add maint cat end return param_val; -- and done end
local Via = A['Via'];
local AccessDate = A['AccessDate'];
local Agency = A['Agency'];
local Language = A--['Language']; local Format = A['Format']; local ChapterFormat = A['ChapterFormat']; local DoiBroken = A['DoiBroken']; local ID = A['ID']; local ASINTLD = A['ASINTLD']; local IgnoreISBN = A['IgnoreISBN']; if not is_valid_parameter_value (IgnoreISBN, 'ignore-isbn-error', cfg.keywords ['yes_true_y']) then IgnoreISBN = nil; -- anything else, set to empty string end local Embargo = A['Embargo']; local Class = A['Class']; -- arxiv class identifier--------------------< I S _ G E N E R I C _ T I T L E >----------------------------------------------
local ID_list compares |title= extract_ids( args ); local ID_access_levels = extract_id_access_levels( args, ID_list )value against list of known generic title patterns. Returns true when pattern matches;nil else
the k/v pairs in 'generic_titles' each contain two tables, one for English and one for another 'local Quote = A' languageEach of those tables contain another table that holds the string or pattern (whole title or title fragment) inindex [1]. index [2] is a Boolean that tells string.find() or mw.ustring.find() to do plain-text search (true)or a pattern search (false). The intent of all this complexity is to make these searches as fast as possible sothat we don'Quote'];t run out of processing time on very large articles.
local LayFormat = A['LayFormat']; local LayURL = A['LayURL']; local LaySource = A['LaySource']; local Transcript = A['Transcript']; local TranscriptFormat = A['TranscriptFormat']; local TranscriptURL = A['TranscriptURL'] local TranscriptURLorigin = A:ORIGIN('TranscriptURL'); -- get name of parameter that holds TranscriptURL
local function is_generic_title (title) local LastAuthorAmp title = Amw.ustring.lower(title); -- switch title to lower case for _, generic_title in ipairs (cfg.special_case_translation['LastAuthorAmpgeneric_titles'];) do --spin through the list of known generic title fragments if not is_valid_parameter_value title:find (LastAuthorAmpgeneric_title['en'][1], 1, generic_title['lasten'][2]) then return true; -author-ampfound English generic title so done elseif generic_title['local'] then -- to keep work load down, cfggeneric_title['local'] should be nil except when there is a local version of the generic title if mw.ustring.keywords find (title, generic_title['yes_true_ylocal'][1], 1, generic_title['local'][2]) then-- mw.ustring() because might not be Latin script LastAuthorAmp = nil return true; -- set to empty stringfound local generic title so done end
end
end
end
 
local no_tracking_cats = --[[--------------------------< I S _ A['NoTracking']; if not is_valid_parameter_value (no_tracking_cats, 'noR C H I V E D _ C O P Y >--------------------------------------------tracking', cfg.keywords ['yes_true_y']) then no_tracking_cats = nil; -- set to empty string end
--local variables compares |title= to 'Archived copy' (place holder added by bots that are not cs1 parameters local use_lowercase; -- controls capitalization of certain static text local this_page = mw.can't find proper title.getCurrentTitle(); -- also used for COinS and for language local anchor_year; -- used in the CITEREF identifier local COinS_date = {}if matches, return true; -- holds date info extracted from |date= for the COinS metadata by Module:Date verificationnil else
local DF = A['DF']; -- date format set in cs1|2 template if not is_valid_parameter_value (DF, 'df', cfg.keywords['date-format']) then -- validate reformatting keyword DF = ''; -- not valid, set to empty string end
local sepc; -- separator between citation elements for CS1 a period, for CS2, a comma local PostScript; local Ref; sepc, PostScript, Ref = set_style (Mode:lowerfunction is_archived_copy (title), A['PostScript'], A['Ref'], config.CitationClass); use_lowercase title = mw.ustring.lower( sepc == ',' title); -- used switch title to control capitalization for certain static textlower case --check this page to see if it is in one of the namespaces that cs1 is not supposed to add to the error categories if not is_set title:find (no_tracking_catscfg.special_case_translation.archived_copy.en) then -- ignore if we are already not going to categorize this pagetitle is 'Archived copy' if in_array (this_pagereturn true; elseif cfg.nsText, cfgspecial_case_translation.uncategorized_namespaces) archived_copy['local'] then no_tracking_cats = "true"; -- set no_tracking_cats end for _if mw.ustring.find (title,v in ipairs (cfg.uncategorized_subpagesspecial_case_translation.archived_copy['local']) do then -- cycle through page name patterns if this_pagemw.text:match ustring(v) then -- test page name against each patternbecause might not be Latin script no_tracking_cats = " return true"; -- set no_tracking_cats break; -- bail out if one is found end
end
end
end
 
-- check for extra |page=, |pages= or |at= parameters. (also sheet and sheets while we're at it) select_one( args, {'page', 'p', 'pp', 'pages', 'at', 'sheet', 'sheets'}, 'redundant_parameters' ); [[--------------------------< C I T A T I O N 0 >------------------------------------------------------------ this is a dummy call simply to get the error message and category
local NoPP = A['NoPP'] if is_set (NoPP) and is_valid_parameter_value (NoPP, 'nopp', cfgThis is the main function doing the majority of the citation formatting.keywords ['yes_true_y']) then NoPP = true; else NoPP = nil; -- unset, used as a flag later end
if is_set(Page) then if is_set(Pages) or is_set(At) then Pages = ''; -- unset the others At = ''; end extra_text_in_page_check (Page); -- add this page to maint cat if |page= value begins with what looks like p. or pp. elseif is_set(Pages) then if is_set(At) then At = ''; -- unset end extra_text_in_page_check (Pages); -- add this page to maint cat if |pages= value begins with what looks like p. or pp. end ]]
-- both |publication-place= and |place= local function citation0(|location=) allowed if different if not is_set(PublicationPlace) and is_set(Place) then PublicationPlace = Place; -- promote |place= (|location=config, args) to |publication-place end if PublicationPlace == Place then Place = ''; end -- don't need both if they are the same --[[ Parameter remapping for cite encyclopedia:Load Input Parameters When The argument_wrapper facilitates the citation has these parameters: |encyclopedia and |title then map |title to |article and |encyclopedia to |title |encyclopedia and |article then map |encyclopedia to |title |encyclopedia then map |encyclopedia mapping of multiple aliases to |title |trans-title maps to |trans-chapter when |title is re-mapped |url maps to |chapterurl when |title is remapped All other combinations of |encyclopedia, |title, and |article are not modified single internal variable.
]]
local A = argument_wrapper ( args );
local i
 
-- Pick out the relevant fields from the arguments. Different citation templates
-- define different field names for the same underlying things.
local Encyclopedia Mode = is_valid_parameter_value (A['EncyclopediaMode'], A:ORIGIN('Mode'), cfg.keywords_lists['mode'], '');
if ( config.CitationClass == "encyclopaedia" ) or ( config.CitationClass == "citation" and is_set (Encyclopedia)) then -- test code for citation if is_set(Periodical) then -- Periodical is set when |encyclopedia is set if is_set(Title) or is_set (ScriptTitle) then if not is_set(Chapter) thenlocal author_etal; Chapter local a = Title{}; -- authors list from |encyclopedia and lastn= / |title are set so map |title to |article and |encyclopedia to firstn= pairs or |title ScriptChapter vauthors= ScriptTitle; TransChapter = TransTitle; ChapterURL = URL; ChapterUrlAccess = UrlAccess local Authors;
if not is_set local NameListStyle = is_valid_parameter_value (ChapterURL) and is_set A['NameListStyle'], A:ORIGIN(TitleLink'NameListStyle') then Chapter = make_wikilink (TitleLink, Chapter); end Title = Periodical; ChapterFormat = Format; Periodical = 'cfg.keywords_lists['; name-list- redundant so unset TransTitle = style''; URL = ], ''); Format = ''; TitleLink = ''; ScriptTitle local Collaboration = A['Collaboration']; end else -- |title not set Title = Periodical; -- |encyclopedia set and |article set or not set so map |encyclopedia to |title Periodical = ''; -- redundant so unset end end end
do -- Special case for cite techreport.to limit scope of selected local selected = select_author_editor_source (A['Vauthors'], A['Authors'], args, 'AuthorList'); if (config.CitationClass 1 == "techreport") selected then -- special case for cite techreport if is_set a, author_etal = extract_names (A[args, 'NumberAuthorList']) then ; --fetch author list from |authorn= / |lastn= / |firstn=, |author- cite techreport uses 'number'linkn=, which other citations alias to and |author-maskn= elseif 2 == selected then NameListStyle = 'issuevanc'; -- override whatever |name-list-style= might be if not is_seta, author_etal = parse_vauthors_veditors (IDargs, args.vauthors, 'AuthorList') then ; --fetch author list from |vauthors=, |author- can we use ID for the "number"?linkn=, and |author-maskn= elseif 3 == selected then ID Authors = A['NumberAuthors']; -- yes, use itcontent of |authors= else if 'authors' == A:ORIGIN('Authors') then -- ID has but add a value so emit error messagemaint cat if the parameter is |authors= tableutilities.insert( z.message_tail, { set_errorset_message ('redundant_parameters', {wrap_style ('parameter', 'idmaint_authors') .. ' and ' .. wrap_style ('; -- because use of this parameter', 'number')}, true )})is discouraged;what to do about the aliases is a TODO:
end
end if utilities.is_set (Collaboration) then author_etal = true; -- so that |display-authors=etal not required end
end
local Others = A['Others'];  local editor_etal; local e = {}; -- special case for cite mailing editors listfrom |editor-lastn= / |editor-firstn= pairs or |veditors= local Editors;  do -- to limit scope of selected local selected = select_author_editor_source (A['Veditors'], A['Editors'], args, 'EditorList'); if (config.CitationClass 1 == "mailinglist") selected then Periodical e, editor_etal = A [extract_names (args, 'MailingListEditorList']); -- fetch editor list from |editorn= / |editor-lastn= / |editor-firstn=, |editor-linkn=, and |editor-maskn= elseif 2 == selected then NameListStyle = 'mailinglistvanc' ; -- override whatever |name-list-style=might be e, editor_etal = A:ORIGINparse_vauthors_veditors (args, args.veditors, 'PeriodicalEditorList') ; -- fetch editor list from |veditors=, |editor-linkn=, and |editor-maskn= elseif 3 == selected then Periodical Editors = A['Editors']; -- unset because mailing list is only used for cite mailing listuse content of |editors= end
end
local translator_etal; local t = {}; -- translators list from |translator-lastn= / translator- Account for the oddity that is {{cite conference}}, before generation of COinS data.firstn= pairs local Translators; -- assembled translators name list if t = extract_names (args, 'conferenceTranslatorList' == config.CitationClass then if is_set(BookTitle) then Chapter = Title; --fetch translator list from |translatorn= / |translator- ChapterLink lastn= TitleLink; , -firstn=, - |chapterlinklinkn=, -maskn= is deprecated ChapterURL = URL local interviewer_etal; ChapterUrlAccess local interviewers_list = UrlAccess{}; ChapterURLorigin = URLorigin local Interviewers; -- used later URLorigin interviewers_list = extract_names (args, 'InterviewerList'); -- process preferred interviewers parameters  local contributor_etal; ChapterFormat local c = Format{}; -- contributors list from |contributor-lastn= / contributor-firstn= pairs TransChapter = TransTitle local Contributors; -- assembled contributors name list Title local Chapter = BookTitleA['Chapter']; -- done here so that we have access to |contribution= from |chapter= aliases Format local Chapter_origin = A:ORIGIN ('Chapter'); local Contribution; -- because contribution is required for contributor(s) TitleLink = if 'contribution'; TransTitle = = A:ORIGIN ('Chapter';) then URL Contribution = A['Chapter']; -- get the name of the contribution
end
elseif 'speech' ~= config.CitationClass then
Conference = ''; -- not cite conference or cite speech so make sure this is empty string
end
-- cite map oddities local Cartography = if utilities.in_array (config.CitationClass, {"book"; local Scale = , "citation"; local Sheet = }) and not utilities.is_set (A['SheetPeriodical'] or ''; local Sheets = A['Sheets'] or ''; if config.CitationClass ) then -- |contributor=and |contribution= "map" thenonly supported in book cites Chapter c = A[extract_names (args, 'MapContributorList']); ChapterURL -- fetch contributor list from |contributorn= A['MapURL']; ChapterUrlAccess / |contributor-lastn= UrlAccess; TransChapter , -firstn= A['TransMap']; ChapterURLorigin , -linkn= A:ORIGIN('MapURL'); ChapterFormat , -maskn= A['MapFormat'];
Cartography = A['Cartography'];if 0 < #c then if not utilities.is_set( Cartography Contribution) then -- |contributor= requires |contribution= table.insert( z.message_tail, { utilities.set_message ( 'err_contributor_missing_required_param', 'contribution')}); -- add missing contribution error message c = {}; -- blank the contributors' table; it is used as a flag later end Cartography if 0 = sepc = #a then -- |contributor= requires |author= table.insert( z. " " message_tail, { utilities.. wrap_msg set_message ('cartographyerr_contributor_missing_required_param', Cartography, use_lowercase'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 Scale = Aif utilities.select_one (args, cfg.aliases['ScaleContributorList-Last'], 'err_redundant_parameters', 1 ) then -- are there contributor name list parameters? table.insert( z.message_tail, { utilities.set_message ( 'err_contributor_ignored')});-- add contributor ignored error message end Contribution = nil; -- unset end  if utilities.is_set( Scale Others) then if 0 == #a and 0 == #e then -- add maint cat when |others= has value and used without |author=, |editor= Scale = sepc utilities.. " " .. Scaleset_message ('maint_others');
end
end
local Year = A['Year']; local PublicationDate = A['PublicationDate']; local OrigDate = A['OrigDate']; local Date = A['Date']; local LayDate = A['LayDate']; ---------------------------------------------- Account for the oddities that are {{cite episode}} and {{cite serial}}, before generation of COinS --- Get title data. if local Title = A['Title']; local ScriptTitle = A['episodeScriptTitle' ]; local BookTitle =A['BookTitle']; local Conference = config.CitationClass or A['serialConference' ]; local TransTitle =A['TransTitle']; local TransTitle_origin = config.CitationClass thenA:ORIGIN ('TransTitle'); local AirDate TitleNote = A['AirDateTitleNote']; local SeriesLink TitleLink = A['SeriesLinkTitleLink'];  local auto_select = ''; -- default is auto local accept_link; TitleLink, accept_link = utilities.has_accept_as_written(TitleLink, true); -- test for accept-this-as-written markup if (not accept_link) and utilities.in_array (TitleLink, {'none', 'pmc', 'doi'}) then -- check for special keywords auto_select = TitleLink; -- remember selection for later TitleLink = ''; -- treat as if |title-link= would have been empty end
TitleLink = link_title_ok (SeriesLinkTitleLink, A:ORIGIN ('SeriesLinkTitleLink'), SeriesTitle, 'seriestitle'); -- check for wikimarkup wiki-markup in |seriestitle-link= or wikimarkup wiki-markup in |seriestitle= when |seriestitle-link= is set
local Network Section = A['Network']; local Station = A['Station']; local s, n = -- {{cite map}, {}only; -- do common parameters firstpreset to empty string for concatenation if not used if is_set(Network) then table'map' == config.insertCitationClass and 'section' == A:ORIGIN (n, Network); end if is_set(Station'Chapter') then table.insert(n, Station); end ID Section = table.concat(n, sepc .. A[' Chapter')]; if not is_set (Date) and is_set (AirDate) then -- promote airdate to dateget |section= from |chapter= alias list; |chapter= and the other aliases not supported in {{cite map}} Date Chapter = AirDate''; -- unset for now; will be reset later from |map= if present
end
if local ScriptChapter = A['episodeScriptChapter' ]; local ScriptChapter_origin =A:ORIGIN ('ScriptChapter'); local ChapterLink -- = config.CitationClass then A['ChapterLink']; -- handle the oddities that are strictly {{deprecated as a parameter but still used internally by cite episode}} local TransChapter = A['TransChapter']; local TransChapter_origin = A:ORIGIN ('TransChapter'); local TitleType = A['TitleType']; local Degree = A['Degree']; local Season Docket = A['SeasonDocket']; local SeriesNumber ArchiveFormat = A['SeriesNumberArchiveFormat'];  local ArchiveDate; local ArchiveURL;
if is_set (Season) and is_set (SeriesNumber) then -- these are mutually exclusive so if both are set table.insert( z.message_tail, { set_error( 'redundant_parameters', {wrap_style ('parameter', 'season') .. ' and ' .. wrap_style ('parameter', 'seriesno')} ArchiveURL, true ) } ); -- add error message SeriesNumber ArchiveDate = ''; -- unset; prefer |season= over |seriesno= end -- assemble a table of parts concatenated later into Series if is_set(Season) then table.insert(s, wrap_msg archive_url_check (A['seasonArchiveURL'], Season, use_lowercase)); end if is_set(SeriesNumber) then table.insert(s, wrap_msg (A['seriesArchiveDate', SeriesNumber, use_lowercase])); end if is_set(Issue) then table.insert(s, wrap_msg ('episode', Issue, use_lowercase)); end Issue = ''; -- unset because this is not a unique parameter
Chapter local UrlStatus = Titleis_valid_parameter_value (A['UrlStatus'], A:ORIGIN('UrlStatus'), cfg.keywords_lists['url-status'], ''); -- promote title parameters to chapter ScriptChapter local URL = ScriptTitle;A['URL'] ChapterLink local URL_origin = TitleLinkA:ORIGIN('URL'); -- alias episodelinkget name of parameter that holds URL TransChapter local ChapterURL = TransTitle; A['ChapterURL = URL']; ChapterUrlAccess = UrlAccess; ChapterURLorigin local ChapterURL_origin = A:ORIGIN('URLChapterURL'); -- get name of parameter that holds ChapterURL Title local ConferenceFormat = SeriesA['ConferenceFormat']; -- promote series to title TitleLink local ConferenceURL = SeriesLinkA['ConferenceURL']; Series local ConferenceURL_origin = table.concatA:ORIGIN(s, sepc .. ' ConferenceURL'); -- this is concatenation get name of season, seriesno, episode numberparameter that holds ConferenceURL
if is_set (ChapterLink) and not is_set (ChapterURL) then -- link but not URL local Periodical = A['Periodical']; Chapter local Periodical_origin = make_wikilink (ChapterLink, Chapter)''; elseif is_set (ChapterLink) and if utilities.is_set (ChapterURLPeriodical) then -- if both are set, URL links episode; Series Periodical_origin = make_wikilink A:ORIGIN(ChapterLink, Series); end URL = 'Periodical'); -- unsetget the name of the periodical parameter TransTitle = '' local i; ScriptTitle Periodical, i = ''utilities.strip_apostrophe_markup (Periodical); else -- now oddities strip apostrophe markup so that are cite serialmetadata isn't contaminated Issue = ''; if i then -- unset because this parameter no longer supported by the citation/core version of cite serial Chapter = A['Episode']; -non- TODO: make |episode= available to cite episode someday?zero when markup was stripped so emit an error message if is_set (Series) and is_set (SeriesLink) then Series = make_wikilink table.insert(SeriesLinkz.message_tail, Series); end Series = wrap_style {utilities.set_message ('italic-titleerr_apostrophe_markup', Series{Periodical_origin}, true)}); -- series is italicized end
end
-- end of {{cite episode}} stuff
if 'mailinglist' == config.CitationClass then -- Account special case for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerxmailing list}}, before generation of COinS data. do if in_array utilities.is_set (configPeriodical) and utilities.CitationClass, {'arxiv', 'biorxiv', 'citeseerx'}) then if not is_set (ID_listA [config.CitationClass:upper()'MailingList']) then -- |arxiv= or |eprint= required for cite arxiv; |biorxiv= & |citeseerx= required for their templatesboth set emit an error table.insert( z.message_tail, { set_errorutilities.set_message ( config.CitationClass .. '_missingerr_redundant_parameters', {}, true ) } ); -- add error message end if 'arxiv' == configutilities.CitationClass then Periodical = wrap_style ('arXivparameter'; -- set to arXiv for COinS; after that, must be set to empty string end  if Periodical_origin) .. 'biorxivand ' == config.CitationClass then Periodical = . utilities.wrap_style ('bioRxivparameter'; -- set to bioRxiv for COinS; after that, must be set to empty string end  if 'citeseerxmailinglist' == config.CitationClass then Periodical = 'CiteSeerX'; -- set to CiteSeerX for COinS)}, true )}); after that, must be set to empty string end
end
end
Periodical = A ['MailingList']; -- handle type parameter for those CS1 citations that have default values if in_array(config.CitationClasserror or no, set Periodical to |mailinglist= value because this template is {"AV-media-notes", "interview", "mailinglist", "map", "podcast", "pressrelease", "report", "techreport", "thesis"{cite mailing list}}) then TitleType Periodical_origin = set_titletype (config.CitationClass, TitleType); if is_setA:ORIGIN(Degree) and "Thesis" == TitleType then -- special case for cite thesis TitleType = Degree .. ' ' .. cfg.title_types ['thesisMailingList']:lower(); end
end
if is_set(TitleType) then -- if type parameter is specified TitleType local ScriptPeriodical = substitute( cfg.messagesA['typeScriptPeriodical'], TitleType); -- display it in parentheses -- TODOlocal ScriptPeriodical_origin = A: Hack on TitleType to fix bunched parentheses problem endORIGIN('ScriptPeriodical');
-- legacy: promote PublicationDate to Date if neither Date nor Year are set.web and news not tested for now because of if not is_set (Date) then Date = Year; -- promote Year to Date Year = nil; -Wikipedia:Administrators%27_noticeboard#Is_there_a_semi- make nil so Year as empty string isn't used for CITEREFautomated_tool_that_could_fix_these_annoying_"Cite_Web"_errors? if not (utilities.is_set (DatePeriodical) and or utilities.is_set(PublicationDateScriptPeriodical)) then -- use PublicationDate when |date'periodical' templates require periodical parameter -- local p = {['journal'] = 'journal', ['magazine'] = and |year'magazine', ['news'] = are not set Date 'newspaper', ['web'] = PublicationDate'website'}; -- promote PublicationDate to Datefor error message PublicationDate local p = {['journal'] = 'journal', ['magazine'] = 'magazine'}; -- unsetfor error message if p[config.CitationClass] then table.insert( z.message_tail, {utilities.set_message ('err_missing_periodical', {config.CitationClass, p[config.CitationClass]}, no longer neededtrue)});
end
end
if PublicationDate local TransPeriodical = A['TransPeriodical']; local TransPeriodical_origin = Date then PublicationDate = A:ORIGIN ('TransPeriodical'); end -- if PublicationDate is same as Date, don't display in rendered citation
--local Series = A[[ Go test all of the date-holding parameters for valid MOS:DATE format and make sure that dates are real dates. This must be done before we do COinS because here is where we get the date used in the metadata.'Series'];
Date validation supporting code is in Module:Citation/CS1/Date_validation ]]local Volume; do -- create defined block to contain local variables error_message, date_parameters_list, mismatch local error_message = ''Issue; -- AirDate has been promoted to Date so not necessary to check it local date_parameters_list = {['access-date']=AccessDate, ['archive-date']=ArchiveDate, ['date']=Date, ['doi-broken-date']=DoiBroken, ['embargo']=Embargo, ['lay-date']=LayDate, ['publication-date']=PublicationDate, ['year']=Year}Page anchor_year, Embargo, error_message = dates(date_parameters_list, COinS_date);-- start temporary Julian / Gregorian calendar uncertainty categorization if COinS_date.inter_cal_cat then add_prop_cat ('jul_greg_uncertainty'); end-- end temporary Julian / Gregorian calendar uncertainty categorization  if is_set (Year) and is_set (Date) then -- both |date= and |year= not normally needed; local mismatch = year_date_check (Year, Date) if 0 == mismatch then -- |year= does not match a year-value in |date= if is_set (error_message) then -- if there is already an error message error_message = error_message .. ', 'Pages; -- tack on this additional message end error_message = error_message .. '&#124;year= / &#124;date= mismatch'; elseif 1 == mismatch then -- |year= matches year-value in |date= add_maint_cat ('date_year'); end end if not is_set(error_message) then -- error free dates only local modified = falseAt; -- flag if is_set (DF) then -- if we need to reformat dates modified = reformat_dates (date_parameters_list, DF, false); -- reformat to DF format, use long month names if appropriate end
if true 'citation' == date_hyphen_to_dash config.CitationClass then if utilities.is_set (date_parameters_listPeriodical) then if not utilities.in_array (Periodical_origin, {'website', 'mailinglist'}) then -- convert hyphens to dashes where appropriate{{citation}} does not render volume for these 'periodicals' modified Volume = true; add_maint_cat (A['date_formatVolume')]; -- hyphens were converted so add maint categorybut does for all other 'periodicals'
end
-- for those wikis that can and want to have English date names translated to the local language, -- uncomment these three lines. Not supported by en elseif utilities.wiki is_set (for obvious reasonsScriptPeriodical)then-- if date_name_xlate (date_parameters_list) then'script-- modified website' ~= true;-- end  if modified ScriptPeriodical_origin then -- if the date_parameters_list values were modified AccessDate = date_parameters_list['access-date']; -{{citation}} does not render volume for |script- overwrite date holding parameters with modified values ArchiveDate website= date_parameters_list['archive-date']; Date Volume = date_parameters_listA['dateVolume']; DoiBroken = date_parameters_list['doi -broken-datebut does for all other ']; LayDate = date_parameters_list[periodicals'lay-date']; PublicationDate = date_parameters_list['publication-date'];
end
else
table.insert( z.message_tail, { set_error( Volume = A['bad_dateVolume', {error_message}, true ) } )]; -- add this error messageand does for non-'periodical' cites
end
end -- end of do  -- Account for the oddity that is {{cite journal}} with |pmc= set and |url= not setelseif utilities. Do this after date check but before COInS. -- Here we unset Embargo if PMC not embargoed in_array (|embargo= not set in the citation) or if the embargo time has expiredconfig. OtherwiseCitationClass, holds embargo date Embargo = is_embargoed (Embargo);  if configcfg.CitationClass == "journal" and not is_set(URL) and is_set(ID_list['PMC']) then if not is_set (Embargotemplates_using_volume) then -- if not embargoed or embargo has expired URLrender |volume=cfg.id_handlers['PMC'].prefix .. ID_list['PMC']; -- set url for cs1 according to be the same as the PMC external link if not embargoedconfiguration settings URLorigin Volume = cfg.id_handlersA['PMCVolume'].parameters[1]; -- set URLorigin to parameter name for use in error message if citation is missing a |title= if is_set(AccessDate) then -- access date requires |url=; pmc created url is not |url= table.insert( z.message_tail, { set_error( 'accessdate_missing_url', {}, true ) } ); AccessDate = ''; -- unset end
if 'citation' == config.CitationClass then
if utilities.is_set (Periodical) and utilities.in_array (Periodical_origin, {'journal', 'magazine', 'newspaper', 'periodical', 'work'}) or -- {{citation}} renders issue for these 'periodicals'
utilities.is_set (ScriptPeriodical) and utilities.in_array (ScriptPeriodical_origin, {'script-journal', 'script-magazine', 'script-newspaper', 'script-periodical', 'script-work'}) then -- and these 'script-periodicals'
Issue = hyphen_to_dash (A['Issue']);
end
elseif utilities.in_array (config.CitationClass, cfg.templates_using_issue) then -- conference & map books do not support issue; {{citation}} listed here because included in settings table
if not (utilities.in_array (config.CitationClass, {'conference', 'map', 'citation'}) and not (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical))) then
Issue = hyphen_to_dash (A['Issue']);
end
end
-- At this point fields may be nil if they werenlocal Position = ''t specified in the template use. We can use that fact. -- Test if citation has no title; if not is_setutilities.in_array (Titleconfig.CitationClass, cfg.templates_not_using_page) andthen not is_set(TransTitle) and not is_set(ScriptTitle) then if Page = A['episodePage' == config.CitationClass then -- special case for cite episode]; TODO: is there a better way to do this? table.insert( z.message_tail, { set_error Pages = hyphen_to_dash ( A['citation_missing_title', {'seriesPages'}, true ) } ]); else table.insert( z.message_tail, { set_error( At = A['citation_missing_titleAt', {'title'}, true ) } )]; end
end
 
local Edition = A['Edition'];
local PublicationPlace = place_check (A['PublicationPlace'], A:ORIGIN('PublicationPlace'));
local Place = place_check (A['Place'], A:ORIGIN('Place'));
if local PublisherName = A['nonePublisherName' ]; local PublisherName_origin == Title and in_array A:ORIGIN(config.CitationClass, {'journal', 'citationPublisherName'}) and ; if utilities.is_set (PeriodicalPublisherName) and 'journal' then local i =0; PublisherName, i = A:ORIGINutilities.strip_apostrophe_markup ('Periodical'PublisherName) then ; -- special case for journal citesstrip apostrophe markup so that metadata isn't contaminated; publisher is never italicized Title = ''; if i then -- set title to empty stringnon-zero when markup was stripped so emit an error message add_maint_cat table.insert( z.message_tail, {utilities.set_message ('untitlederr_apostrophe_markup', {PublisherName_origin}, true)}); end
end
check_for_url ({ -- add error message when any of these parameters contains a URL local Newsgroup = A['titleNewsgroup']=Title, [A; -- TODO:ORIGIN('Chapter')]=Chapter,strip apostrophe markup? [A:ORIGIN('Periodical')] local Newsgroup_origin =Periodical, [A:ORIGIN('PublisherNameNewsgroup')] = PublisherName });
-- COinS metadata (see <http://ocoins.info/>) for automated parsing of citation informationif 'newsgroup' == config.CitationClass then -- handle the oddity that is cite encyclopedia and {{citation |encyclopedia=something}} if utilities. Here we presume that -- when Periodical, Title, and Chapter are all set, then Periodical is the book is_set (encyclopediaPublisherName) title, Title then -- is the article title, and Chapter is a section within the article. So, we remap local coins_chapter general use parameter |publisher= Chapter; -- default assuming that remapping not requiredallowed in cite newsgroup local coins_title error_text, error_state = Title; -- et tu if 'encyclopaedia' == configutilities.CitationClass or set_message ('citationerr_parameter_ignored' == config, {PublisherName_origin}, true); if utilities.CitationClass and is_set (Encyclopedia)error_text) then if is_set table.insert(Chapterz.message_tail, {error_text, error_state} ) and is_set (Title) and is_set (Periodical) then -- if all are used then; coins_chapter = Title; -- remapend coins_title = Periodical;
end
end local coins_author PublisherName = anil; -- default ensure that this parameter is unset for coins rft.au if 0 < #c then -- but if contributor list coins_author = cthe time being; -- use that insteadwill be used again after COinS
end
-- this is the function call to COinS() local OCinSoutput UrlAccess = COinSis_valid_parameter_value ({ A['PeriodicalUrlAccess'] = Periodical, [A:ORIGIN('EncyclopediaUrlAccess'] = Encyclopedia), cfg.keywords_lists['Chapterurl-access'] = make_coins_title (coins_chapter, ScriptChapternil), ; -- Chapter if not utilities.is_set (URL) and ScriptChapter stripped of bold / italic wikimarkuputilities.is_set (UrlAccess) then ['Degree'] UrlAccess = Degreenil; -- cite thesis only ['Title'] = make_coins_title table.insert(coins_titlez.message_tail, ScriptTitle), -- Title and ScriptTitle stripped of bold / italic wikimarkup [{ utilities.set_message ( 'PublicationPlaceerr_param_access_requires_param'] = PublicationPlace, [{'Dateurl'] = COinS_date.rftdate}, -- COinS_date has correctly formatted date if Date is validtrue ) } ); ['Season'] = COinS_date.rftssn,end ['Chron'] local ChapterUrlAccess = COinS_date.rftchron or is_valid_parameter_value (not COinS_date.rftdate and Date) or '', -- chron but if not set and invalid date format use Date; keep this last bit? A['SeriesChapterUrlAccess'] = Series, [A:ORIGIN('VolumeChapterUrlAccess'] = Volume), cfg.keywords_lists['Issueurl-access'] = Issue,nil); ['Pages'] = get_coins_pages if not utilities.is_set (first_set ChapterURL) and utilities.is_set ({Sheet, Sheets, Page, Pages, At}, 5ChapterUrlAccess)), -- pages stripped of external linksthen ['Edition'] ChapterUrlAccess = Edition,nil; [table.insert( z.message_tail, { utilities.set_message ( 'PublisherNameerr_param_access_requires_param'] = PublisherName, [{A:ORIGIN('URLChapterUrlAccess'] = first_set ):gsub ({ChapterURL, URL}, 2), ['Authors%-access'] = coins_author, ['ID_list'] = ID_list)}, ['RawPage'] = this_page.prefixedText, true ) }, config.CitationClass); end
-- Account for the oddities that are {{cite arxiv}}local MapUrlAccess = is_valid_parameter_value (A['MapUrlAccess'], {{cite biorxiv}}A:ORIGIN('MapUrlAccess'), and {{cite citeseerx}} AFTER generation of COinS datacfg.keywords_lists['url-access'], nil); if in_array not utilities.is_set (configA['MapURL']) and utilities.CitationClassis_set (MapUrlAccess) then MapUrlAccess = nil; table.insert( z.message_tail, {utilities.set_message ( 'arxiverr_param_access_requires_param', {'biorxivmap-url'}, 'citeseerx'true ) }) then -- we have set rft.jtitle in COinS to arXiv, bioRxiv, or CiteSeerX now unset so it isn't displayed Periodical = ''; -- periodical not allowed in these templates; if article has been published, use cite journal
end
-- special case for cite newsgroup. Do this after COinS because we are modifying Publishername to include some static text if local Via = A['newsgroupVia' == config.CitationClass then]; if is_set (PublisherName) then PublisherName local AccessDate = substitute (cfg.messagesA['newsgroupAccessDate'], external_link( 'news:' .. PublisherName, PublisherName, ; local Agency = A:ORIGIN(['PublisherNameAgency'), nil ))]; end end
local Language = A['Language'];
local Format = A['Format'];
local ChapterFormat = A['ChapterFormat'];
local DoiBroken = A['DoiBroken'];
local ID = A['ID'];
local ASINTLD = A['ASINTLD'];
local IgnoreISBN = is_valid_parameter_value (A['IgnoreISBN'], A:ORIGIN('IgnoreISBN'), cfg.keywords_lists['yes_true_y'], nil);
local Embargo = A['Embargo'];
local Class = A['Class']; -- arxiv class identifier
local Quote = A['Quote'];
local QuotePage = A['QuotePage'];
local QuotePages = A['QuotePages'];
local ScriptQuote = A['ScriptQuote'];
local TransQuote = A['TransQuote'];
-- Now perform various field substitutions.local LayFormat = A['LayFormat']; -- We also add leading spaces and surrounding markup and punctuation to thelocal LayURL = A['LayURL']; -- various parts of the citation, but only when they are non-nil.local LaySource = A['LaySource']; local EditorCountTranscript = A['Transcript']; -- used only for choosing {ed.) or (eds.) annotation at end of editor name-list do local last_first_listTranscriptFormat = A['TranscriptFormat']; local control TranscriptURL = { A['TranscriptURL'] format local TranscriptURL_origin = NameListFormat, -- empty string or A:ORIGIN('vancTranscriptURL' maximum = nil, -- as if display-authors or display-editors not set lastauthoramp = LastAuthorAmp, page_name = this_page.text, ); -- get current page name so of parameter that we don't wikilink to it via editorlinkn mode = Mode };holds TranscriptURL
do -- do editor name list first because the now unsupported coauthors used to modify control table control.maximum , editor_etal local LastAuthorAmp = get_display_authors_editors is_valid_parameter_value (A['DisplayEditorsLastAuthorAmp'], #e, A:ORIGIN('editorsLastAuthorAmp', editor_etal); last_first_list, EditorCount = list_people(control, ecfg.keywords_lists['yes_true_y'], editor_etalnil);
if is_set local no_tracking_cats = is_valid_parameter_value (A['NoTracking'], A:ORIGIN(Editors) then if editor_etal then Editors = Editors .. ' NoTracking' .. ), cfg.messageskeywords_lists['et alyes_true_y'], nil); if 'nocat' == A:ORIGIN('NoTracking') then -- add et alutilities. to editors parameter beause |display-editors=etal EditorCount = 2set_message ('maint_nocat'); -- with et al., |editors= is multiple namesthis one so that we get the message; spoof to display see main categorization at end of citation0(eds.) annotation else end --local variables that are not cs1 parameters EditorCount = 2 local use_lowercase; -- we don't know but assume |editorscontrols capitalization of certain static text local this_page = is multiple names; spoof to display mw.title.getCurrentTitle(eds.) annotation; -- also used for COinS and for language end else local anchor_year; -- used in the CITEREF identifier Editors local COinS_date = last_first_list{}; -- either an author name list or an empty string endholds date info extracted from |date= for the COinS metadata by Module:Date verification
local DF = is_valid_parameter_value (A['DF'], A:ORIGIN('DF'), cfg.keywords_lists['df'], ''); if not utilities.is_set (DF) then DF = cfg.global_df; -- local |df= if present overrides global df set by {{use xxx date}} template end  local sepc; -- separator between citation elements for CS1 a period, for CS2, a comma local PostScript; local Ref = A['Ref']; if 1 'harv' == EditorCount and Ref then utilities.set_message ('maint_ref_harv'); -- add maint cat to identify templates that have this now-extraneous param value elseif not utilities.is_set (true Ref) then Ref = 'harv'; -- set as default when not set externally end sepc, PostScript, Ref = set_style (Mode:lower(), A['PostScript'], Ref, config.CitationClass); use_lowercase = ( sepc == editor_etal or 1 < #e',' ) then ; -- used to control capitalization for certain static text  -- only check this page to see if it is in one editor displayed but includes etal of the namespaces that cs1 is not supposed to add to the error categories if not utilities.is_set (no_tracking_cats) then -- ignore if we are already not going to categorize this page if utilities.in_array (this_page.nsText, cfg.uncategorized_namespaces) then EditorCount no_tracking_cats = 2"true"; -- spoof to display set no_tracking_cats end for _, v in ipairs (edscfg.uncategorized_subpages) annotationdo -- cycle through page name patterns if this_page.text:match (v) then -- test page name against each pattern no_tracking_cats = "true"; -- set no_tracking_cats break; -- bail out if one is found
end
end
do end -- now do interviewers control.maximum check for extra |page=, |pages= #interviewers_list; -- number of interviewerss Interviewers or |at= list_peopleparameters. (control, interviewers_list, falsealso sheet and sheets while we're at it); -- et al not currently supported end do -- now do translators control utilities.maximum = #t; -- number of translators Translators = list_peopleselect_one (controlargs, {'page', 'p', 'pp', t'pages', false'at', 'sheet', 'sheets'}, 'err_redundant_parameters'); -- et al not currently supportedthis is a dummy call simply to get the error message and category end do -- now do contributors local coins_pages; control.maximum = #c; -- number of contributors Contributors = list_people(control Page, cPages, false); -- et al not currently supported end do -- now do authors control.maximum At, author_etal coins_pages = get_display_authors_editors insource_loc_get (A['DisplayAuthors']Page, #a, 'authors'Pages, author_etalAt);
last_first_list local NoPP = list_peopleis_valid_parameter_value (controlA['NoPP'], aA:ORIGIN('NoPP'), cfg.keywords_lists['yes_true_y'], author_etalnil);
if utilities.is_set (AuthorsPublicationPlace) then Authors, author_etal = name_has_etal and utilities.is_set (Authors, author_etal, falsePlace); then --both |publication- find place= and remove variations on et al. |place= (|location=) allowed if author_etal thendifferent Authors = Authors . utilities. add_prop_cat (' location test' .. cfg.messages['et al']); -- add et al. property cat to authors parameter end else Authors = last_first_list; -- either an author name list or an empty string end end -- end of do evaluate how often PublicationPlace and Place are used together if is_set (Authors) and is_set (Collaboration) PublicationPlace == Place then Authors Place = Authors .. ' (' .. Collaboration .. ')'; -- add collaboration after et al.unset; don't need both if they are the same
end
elseif not utilities.is_set (PublicationPlace) and utilities.is_set (Place) then -- when only |place= (|location=) is set ... PublicationPlace = Place; -- promote |place= (|location=) to |publication-place
end
if PublicationPlace == Place then Place = ''; end -- apply |[xx-]format= styling; at don't need both if they are the end, these parameters hold correctly styled format annotation,same -- an error message if the associated url is not set, or an empty string [[ Parameter remapping for concatenationcite encyclopedia: ArchiveFormat When the citation has these parameters: |encyclopedia= and |title= then map |title= to |article= and |encyclopedia= to |title= |encyclopedia= and |article= style_format (ArchiveFormat, ArchiveURL, 'archivethen map |encyclopedia= to |title=  |trans-title= maps to |trans-format', 'archivechapter= when |title= is re-mapped |url'); ConferenceFormat = style_format (ConferenceFormat, ConferenceURL, 'conference-format', 'conferencemaps to |chapter-url');= when |title= is remapped Format All other combinations of |encyclopedia= style_format (Format, URL|title=, 'format', 'url');and |article= are not modified LayFormat = style_format (LayFormat, LayURL, 'lay-format', 'lay-url');]]  TranscriptFormat local Encyclopedia = style_format (TranscriptFormat, TranscriptURL, A['transcript-formatEncyclopedia', 'transcripturl')]; -- used as a flag by this module and by ~/COinS
if utilities.is_set (Encyclopedia) then -- special case for chapter format so no emit error message when Encyclopedia set but template is other than {{cite encyclopedia}} or cat when chapter not supported{{citation}} if not (in_array('encyclopaedia' ~= config.CitationClass, {'web', 'news', 'journal', 'magazine', 'pressrelease', 'podcast', 'newsgroup', 'arxiv', 'biorxiv', 'citeseerx'}) or (and 'citation' =~= config.CitationClass and is_set (Periodical) and not is_set (Encyclopedia))) then ChapterFormat = style_format table.insert (ChapterFormat, ChapterURLz.message_tail, {utilities.set_message ('chapter-formaterr_parameter_ignored', {A:ORIGIN ('chapter-urlEncyclopedia')}, true)}); Encyclopedia = nil; -- unset because not supported by this template end
end
if not ('encyclopaedia' == config.CitationClass) or ('citation' == config.CitationClass and utilities.is_set(URLEncyclopedia)) then if in_arrayutilities.is_set (configPeriodical) and utilities.CitationClass, {"web","podcast", "mailinglist"}is_set (Encyclopedia) then -- |url= required for cite web, cite podcast, and cite mailinglistwhen both set emit an error table.insert( z.message_tail, { set_errorutilities.set_message ( 'cite_web_urlerr_redundant_parameters', {utilities.wrap_style ('parameter', A:ORIGIN ('Encyclopedia')) .. ' and ' .. utilities.wrap_style ('parameter', Periodical_origin)}, true ) } );
end
-- do we have |accessdate= without either |url= or |chapter-url=? if utilities.is_set(AccessDate) and not is_set(ChapterURLEncyclopedia)then Periodical = Encyclopedia; -- ChapterURL may be set when URL is not error or no, setPeriodical to Encyclopedia;allow periodical without encyclopedia table.insert( z.message_tail, { set_errorPeriodical_origin = A:ORIGIN ( 'accessdate_missing_urlEncyclopedia', {}, true ) } ); AccessDate = '';
end
end
local OriginalURL, OriginalURLorigin, OriginalFormat, OriginalAccess; DeadURL = DeadURL:lower if utilities.is_set (Periodical); then -- used later Periodical is set when assembling archived text|encyclopedia= is set if utilities.is_set( ArchiveURL Title) or utilities.is_set (ScriptTitle) then if not utilities.is_set (ChapterURLChapter) then -- URL not set so if chapter-url is set apply archive url to it OriginalURL Chapter = ChapterURLTitle; -- save copy of source chapter's url for archive text OriginalURLorigin |encyclopedia= and |title= are set so map |title= ChapterURLorigin; -- name of chapter-url parameter for error messages OriginalFormat to |article= ChapterFormat; -- and original |formatencyclopedia= if 'no' ~to |title= DeadURL then ChapterURL ScriptChapter = ArchiveURL -- swap-in the archive's urlScriptTitle; ChapterURLorigin ScriptChapter_origin = A:ORIGIN('ArchiveURLScriptTitle') -- name of archive-url parameter for error messages ChapterFormat TransChapter = ArchiveFormat or ''TransTitle; -- swap in archive's format end elseif is_set (URL) then OriginalURL ChapterURL = URL; -- save copy of original source URL OriginalURLorigin = URLorigin; -- name of url parameter for error messages OriginalFormat = Format; -- and original |format= OriginalAccess = UrlAccess; if 'no' ~= DeadURL then -- if URL set then archive-url applies to it URL = ArchiveURL -- swap-in the archive's url URLorigin ChapterURL_origin = A:ORIGIN('ArchiveURLURL') -- name of archive url parameter for error messages Format = ArchiveFormat or ''; -- swap in archive's format UrlAccess = nil; -- restricted access levels do not make sense for archived urls end end end
if in_array(config.CitationClass, {'web','news','journal', 'magazine', 'pressrelease', 'podcast', 'newsgroup', 'arxiv', 'biorxiv', 'citeseerx'}) or -- if any of the 'periodical' cites except encyclopedia ('citation' ChapterUrlAccess == config.CitationClass and is_set (Periodical) and not is_set (Encyclopedia)) then local chap_paramUrlAccess; if is_set (Chapter) then -- get a parameter name from one of these chapter related meta-parameters chap_param = A:ORIGIN ('Chapter') elseif is_set (TransChapter) then chap_param = A:ORIGIN ('TransChapter') elseif is_set (ChapterURL) then chap_param = A:ORIGIN ('ChapterURL') elseif is_set (ScriptChapter) then chap_param = A:ORIGIN ('ScriptChapter') else is_set (ChapterFormat) chap_param = A:ORIGIN ('ChapterFormat') end
if not utilities.is_set (chap_paramChapterURL) and utilities.is_set (TitleLink) then -- if we found one table Chapter = utilities.insertmake_wikilink ( z.message_tailTitleLink, { set_error( Chapter); end Title = Periodical; ChapterFormat = Format; Periodical = 'chapter_ignored', {chap_param}, true ) } ); -- add error messageredundant so unset Chapter TransTitle = ''; -- and set them to empty string to be safe with concatenation TransChapter URL = ''; ChapterURL Format = ''; ScriptChapter TitleLink = ''; ChapterFormat ScriptTitle = ''; end else -- otherwise, format chapter / article title local no_quotes = false; -- default assume that we will be quoting the chapter parameter value if elseif utilities.is_set (ContributionChapter) and 0 < #c then -- if this is a contribution with contributor(s)|title= not set if in_array (Contribution:lower(), cfg.keywords.contribution) then Title = Periodical; -- |encyclopedia= set and a generic contribution |article= set so map |encyclopedia= to |title= no_quotes Periodical = true''; -- then render it unquotedredundant so unset
end
end
end
Chapter -- special case for cite techreport. if (config.CitationClass == format_chapter_title (ScriptChapter, Chapter, TransChapter, ChapterURL, ChapterURLorigin, no_quotes, ChapterUrlAccess"techreport"); then -- Contribution is also in Chapterspecial case for cite techreport if utilities.is_set (ChapterA['Number']) then -- cite techreport uses 'number', which other citations alias to 'issue' Chapter if not utilities.is_set (ID) then -- can we use ID for the "number"? ID = Chapter .. ChapterFormat A['Number']; -- yes, use it if else -- ID has a value so emit error message table.insert( z.message_tail, { utilities.set_message ('maperr_redundant_parameters' == config, {utilities.CitationClass and is_set wrap_style (TitleType'parameter', 'id') then Chapter = Chapter .. ' and ' .. TitleTypeutilities.wrap_style ('parameter', 'number')}, true )}); -- map annotation here; not after title
end
Chapter = Chapter.. sepc .. ' '; elseif is_set (ChapterFormat) then -- |chapter= not set but |chapter-format= is so ... Chapter = ChapterFormat .. sepc .. ' '; -- ... ChapterFormat has error message, we want to see it end
end
-- Format main titleAccount for the oddity that is {{cite conference}}, before generation of COinS data. if '...conference' == Title:sub config.CitationClass then if utilities.is_set (-3BookTitle) then Chapter = Title; Chapter_origin = 'title'; -- ChapterLink = TitleLink; --|chapter- if elipsis link= is the last three characters of |titledeprecated ChapterURL = URL; ChapterUrlAccess = UrlAccess; ChapterURL_origin =URL_origin; Title URL_origin = mw.ustring.gsub (Title, '(%.%.%.)%.+$', ; ChapterFormat = Format; TransChapter = TransTitle; TransChapter_origin = TransTitle_origin; Title = BookTitle; Format = '%1'); -- limit the number of dots to three elseif not mw.ustring.find (Title, '%.%s*%a%.') then -- end of title is not a TitleLink = 'dot-(optional space-)letter-dot' initialism; Title TransTitle = mw.ustring.gsub(Title, '%'..sepc..; URL = '$', ''); -- remove any trailing separator character end if is_set(TitleLink) and is_set(Title) elseif 'speech' ~= config.CitationClass then Title Conference = make_wikilink (TitleLink, Title)''; -- not cite conference or cite speech so make sure this is empty string
end
if in_array(config.CitationClass, {-- cite map oddities local Cartography = ""; local Scale = ""; local Sheet = A['webSheet', ] or 'news', ; local Sheets = A['journalSheets', ] or 'magazine', 'pressrelease', 'podcast', 'newsgroup', 'mailinglist', 'interview', 'arxiv', 'biorxiv', 'citeseerx'}) or; ('citation' if config.CitationClass == config"map" then if utilities.CitationClass and is_set (PeriodicalChapter) and not is_set then table.insert(Encyclopedia)) or z.message_tail, { utilities.set_message ('maperr_redundant_parameters' == config, {utilities.CitationClass and is_set wrap_style (Periodical)) then -- special case for cite 'parameter', 'map when the map is in a periodical treat as an article Title = kern_quotes (Title'); -- if necessary, separate title.. 's leading and trailing quote marks from Module provided quote marks Title = ' .. utilities.wrap_style ('quoted-titleparameter', TitleChapter_origin); Title = script_concatenate (Title}, ScriptTitletrue ) } ); -- <bdi> tags, lang atribute, categorization, etcadd error message end Chapter = A['Map']; must be done after title is wrapped TransTitle Chapter_origin = wrap_style A:ORIGIN('trans-quoted-titleMap', TransTitle ); elseif ChapterURL = A['reportMapURL' == config.CitationClass then -- no styling for cite report Title = script_concatenate (Title, ScriptTitle)]; -- <bdi> tags, lang atribute, categorization, etc; must be done after title is wrapped TransTitleChapterURL_origin = wrap_style A:ORIGIN('trans-quoted-titleMapURL', TransTitle ); -- for cite report, use this form for trans-title else Title TransChapter = wrap_style (A['italic-titleTransMap', Title)]; Title ScriptChapter = script_concatenate (Title, ScriptTitle); -- <bdi> tags, lang atribute, categorization, etc; must be done after title is wrappedA['ScriptMap'] TransTitle ScriptChapter_origin = wrap_style A:ORIGIN('trans-italic-titleScriptMap', TransTitle); end
local TransError ChapterUrlAccess = MapUrlAccess; ChapterFormat = ""A['MapFormat']; if is_set(TransTitle) then Cartography = A['Cartography']; if utilities.is_set(TitleCartography ) then TransTitle Cartography = " " sepc .. TransTitle; else TransError = " " .. set_errorwrap_msg ( 'trans_missing_titlecartography', {'title'} Cartography, use_lowercase); end end if is_set(Title) then Scale = A['Scale']; if not utilities.is_set(TitleLink) and is_set(URLScale ) then Title Scale = external_link( URL, Title, URLorigin, UrlAccess ) sepc .. TransTitle .. TransError .. Format; URL = ''; -- unset these because no longer needed Format = ""; else Title = Title .. TransTitle .. TransErrorScale;
end
else
Title = TransTitle .. TransError;
end
-- Account for the oddities that are {{cite episode}} and {{cite serial}}, before generation of COinS data. if is_set(Place) 'episode' == config.CitationClass or 'serial' == config.CitationClass then Place local SeriesLink = " " .. wrap_msg (A['writtenSeriesLink', Place, use_lowercase) .. sepc .. " "]; end
if is_set (Conference) then if is_set (ConferenceURL) then Conference SeriesLink = external_linklink_title_ok ( ConferenceURLSeriesLink, ConferenceA:ORIGIN ('SeriesLink'), ConferenceURLoriginSeries, nil 'series'); end Conference -- check for wiki-markup in |series-link= or wiki-markup in |series= sepc .. " " .. Conference .. ConferenceFormat; elseif is_set(ConferenceURL) then Conference when |series-link= sepc .. " " .. external_link( ConferenceURL, nil, ConferenceURLorigin, nil ); endis set
local Network = A['Network']; local Station = A['Station']; local s, n = {}, {}; -- do common parameters first if utilities.is_set (Network) then table.insert(n, Network); end if not utilities.is_set(PositionStation) thentable.insert(n, Station); end ID = table.concat(n, sepc .. ' '); if 'episode' == config.CitationClass then -- handle the oddities that are strictly {{cite episode}} local Minutes Season = A['MinutesSeason']; local Time SeriesNumber = A['TimeSeriesNumber'];
if utilities.is_set(MinutesSeason) then if and utilities.is_set (TimeSeriesNumber) then-- these are mutually exclusive so if both are set table.insert( z.message_tail, { set_errorutilities.set_message ( 'redundant_parameterserr_redundant_parameters', {utilities.wrap_style ('parameter', 'minutesseason') .. ' and ' .. utilities. wrap_style ('parameter', 'timeseriesno')}, true ) } ); -- add error message SeriesNumber = ''; -- unset; prefer |season= over |seriesno=
end
-- assemble a table of parts concatenated later into Series Position = " " if utilities.is_set (Season) then table. Minutes .. " " .. cfg.messages[insert(s, wrap_msg ('minutesseason'], Season, use_lowercase)); elseend if utilities.is_set(TimeSeriesNumber) then local TimeCaption = A[table.insert(s, wrap_msg ('TimeCaptionseriesnum'], SeriesNumber, use_lowercase)); end if not utilities.is_set(TimeCaptionIssue) thentable.insert(s, wrap_msg ('episode', Issue, use_lowercase)); end TimeCaption Issue = cfg.messages['event']; -- unset because this is not a unique parameter Chapter = Title; -- promote title parameters to chapter ScriptChapter = ScriptTitle; if sepc ~ ScriptChapter_origin = A:ORIGIN('.ScriptTitle' then); ChapterLink = TitleLink; -- alias |episode-link= TransChapter = TransTitle; ChapterURL = URL; ChapterUrlAccess = UrlAccess; TimeCaption ChapterURL_origin = TimeCaptionA:lowerORIGIN('URL'); end Title = Series; -- promote series to title end TitleLink = SeriesLink; Position Series = " " table.concat(s, sepc . TimeCaption .' '); -- this is concatenation of season, seriesno, episode number  if utilities. " " is_set (ChapterLink) and not utilities.is_set (ChapterURL) then -- link but not URL Chapter = utilities. Timemake_wikilink (ChapterLink, Chapter); elseif utilities.is_set (ChapterLink) and utilities.is_set (ChapterURL) then -- if both are set, URL links episode; Series = utilities.make_wikilink (ChapterLink, Series);
end
end URL = ''; -- unset else TransTitle = ''; Position ScriptTitle = " " .. Position''; At else -- now oddities that are cite serial Issue = ''; -- unset because this parameter no longer supported by the citation/core version of cite serial end Chapter = A['Episode']; -- TODO: make |episode= available to cite episode someday? if utilities.is_set (Series) and utilities.is_set (SeriesLink) then Page, Pages, Sheet, Sheets Series = format_pages_sheets utilities.make_wikilink (PageSeriesLink, Pages, Sheet, Sheets, configSeries); end Series = utilities.CitationClass, Periodical_origin, sepc, NoPPwrap_style ('italic-title', use_lowercaseSeries); -- series is italicized end end -- end of {{cite episode}} stuff
At = is_set-- handle type parameter for those CS1 citations that have default values if utilities.in_array (At) and (sepc .config. CitationClass, {"AV-media-notes", "interview", "mailinglist", "map", "podcast", " pressrelease" .. At) or , "report"; Position = is_set(Position) and (sepc .. , " techreport" .. Position) or , "thesis"; if config.CitationClass == 'map' }) then local Section = A['Section']; local Sections TitleType = A['Sections']; local Inset = A['Inset']; if is_setset_titletype ( Inset ) then Inset = sepc config.. " " .. wrap_msg ('inset'CitationClass, Inset, use_lowercaseTitleType); end   if utilities.is_set( Sections Degree) and "Thesis" == TitleType then -- special case for cite thesis Section TitleType = sepc .Degree . " " .. wrap_msg ('sections', Sections, use_lowercase); elseif is_set( Section ) then Section = sepc .. " " cfg.. wrap_msg (title_types ['sectionthesis', Section, use_lowercase]:lower();
end
At = At .. Inset .. Section; end
if utilities.is_set (LanguageTitleType) then -- if type parameter is specified Language TitleType = language_parameter utilities.substitute (Languagecfg.messages['type'], TitleType); -- format, categories, name from ISO639-1, etc else Language=""; -- language not specified so make sure this is an empty string;display it in parentheses --[[ TODO: need Hack on TitleType to extract the wrap_msg from language_parameter so that we can solve fix bunched parentheses bunching problem with Format/Language/TitleType ]]
end
Others = is_set(Others) and (sepc -- legacy: promote PublicationDate to Date if neither Date nor Year are set.. " " .. Others) or "" local Date_origin; -- to hold the name of parameter promoted to Date;required for date error messaging if not utilities.is_set (TranslatorsDate) then Others Date = sepc .. ' ' .. wrap_msg ('translated', Translators, use_lowercase) .. OthersYear; end if is_set (Interviewers) then -- promote Year to Date Others Year = sepc .. ' nil; -- make nil so Year as empty string isn' t used for CITEREF if not utilities.. wrap_msg ('interview', Interviewers, use_lowercase) .. Others; end TitleNote = is_set(TitleNoteDate) and (sepc utilities.. " " .. TitleNote) or ""; if is_set (EditionPublicationDate) then-- use PublicationDate when |date= and |year= are not set Date = PublicationDate; -- promote PublicationDate to Date if Edition:match ( PublicationDate = '%f[%a][Ee]d%.?$') or Edition; -- unset, no longer needed Date_origin = A:match ORIGIN('%f[%a][Ee]dition$PublicationDate') then; -- save the name of the promoted parameter else add_maint_cat Date_origin = A:ORIGIN('extra_text', 'editionYear'); -- save the name of the promoted parameter
end
Edition = " " .. wrap_msg ('edition', Edition);
else
Edition Date_origin = A:ORIGIN('Date'); -- not a promotion; name required for error messaging
end
Series if PublicationDate = is_set(Series) and (sepc .. " " .. Series) or ""; OrigYear = is_set(OrigYear) and (" [" .. OrigYear .. "]") or ""; -- TODO: presentation  Agency Date then PublicationDate = is_set(Agency) and (sepc .. " " .. Agency) or ""'' Volume = format_volume_issue (Volume, Issue, config.CitationClass, Periodical_origin, sepc, use_lowercase);  ---------end --------------------------- totally unrelated data if is_set(Via) then Via = " " .. wrap_msg (PublicationDate is same as Date, don'via', Via); endt display in rendered citation
--[[
Subscription implies paywall; Registration does notGo test all of the date-holding parameters for valid MOS:DATE format and make sure that dates are real dates. If both are This must be done before we do COinS because here is where we get the date used in a citation, the subscription required link note is displayed. There are no error messages for this conditionmetadata.
Date validation supporting code is in Module:Citation/CS1/Date_validation
]]
if is_set (SubscriptionRequired) thendo -- create defined block to contain local variables error_message, date_parameters_list, mismatch SubscriptionRequired local error_message = sepc .. " " .. cfg.messages['subscription']; -- subscription required messageAirDate has been promoted to Date so not necessary to check it local date_parameters_list = { elseif is_set ['access-date'] = {val = AccessDate, name = A:ORIGIN (RegistrationRequired'AccessDate') then}, SubscriptionRequired ['archive-date'] = {val = ArchiveDate, name = sepc .. " " .. cfg.messagesA:ORIGIN ('ArchiveDate')}, ['registrationdate']; = {val = Date, name = Date_origin}, ['doi-broken- registration required messagedate'] = {val = DoiBroken, name = A:ORIGIN ('DoiBroken')}, else ['pmc-embargo-date'] = {val = Embargo, name = A:ORIGIN ('Embargo')}, SubscriptionRequired ['lay-date'] = {val = LayDate, name = A:ORIGIN ('LayDate')}, ['; publication-- either or both might be set to something other than yes true ydate'] = {val = PublicationDate, name = A:ORIGIN ('PublicationDate')}, ['year'] = {val = Year, name = A:ORIGIN ('Year')}, end };
if is_set(AccessDate) then local error_list = {}; local retrv_text anchor_year, Embargo = " " validation.. cfg.messages['retrieved']dates(date_parameters_list, COinS_date, error_list);
AccessDate = nowrap_date (AccessDate); -- wrap in nowrap span if date in appropriate formatstart temporary Julian / Gregorian calendar uncertainty categorization if (sepc ~= "COinS_date.") inter_cal_cat then retrv_text = retrv_text:lower utilities.add_prop_cat ('jul_greg_uncertainty') ; end -- if mode is cs2, lower case AccessDate = substitute (retrv_text, AccessDate); -- add retrieved textend temporary Julian / Gregorian calendar uncertainty categorization
AccessDate = substitute if utilities.is_set (cfgYear) and utilities.presentation['accessdate'], {sepc, AccessDate}is_set (Date); then -- allow editors to hide accessdatesboth |date= and |year= not normally needed; end local mismatch = validation.year_date_check (Year, Date); if is_set(ID) 0 == mismatch then ID -- |year= sepc .does not match a year-value in |date= table.insert (error_list, '<code class=" cs1-code">&#124;year= / &#124;date= mismatch</code>'); elseif 1 == mismatch then -- |year= matches year-value in |date= utilities.. IDset_message ('maint_date_year'); -- add a maint cat end end if "thesis" 0 == config.CitationClass and is_set(Docket) #error_list then -- error free dates only; 0 when error_list is empty ID local modified = sepc .." Docket ".. Docket .. IDfalse; -- flag end if "report" == configutilities.CitationClass and is_set(DocketDF) then -- for cite report when |docket= is setif we need to reformat dates ID modified = sepc validation.. ' ' .. Docketreformat_dates (date_parameters_list, DF); -- overwrite ID even reformat to DF format, use long month names if |id= is setappropriate end
ID_list if true == build_id_listvalidation.date_hyphen_to_dash ( ID_list, {IdAccessLevelsdate_parameters_list) then -- convert hyphens to dashes where appropriate modified =ID_access_levelstrue; utilities.set_message ('maint_date_format'); -- hyphens were converted so add maint category end -- for those wikis that can and want to have English date names translated to the local language, DoiBroken = DoiBroken -- uncomment the next three lines. Not supported by en.wiki (for obvious reasons) -- set validation.date_name_xlate() second argument to true to translate English digits to local digits (will translate ymd dates) -- if validation.date_name_xlate (date_parameters_list, ASINTLD = ASINTLD, IgnoreISBN = IgnoreISBN, Embargo=Embargo, Class false) then -- modified = Class} )true; -- end
if is_set(URL) modified then -- if the date_parameters_list values were modified AccessDate = date_parameters_list['access-date'].val; URL -- overwrite date holding parameters with modified values ArchiveDate = date_parameters_list['archive-date'].val; Date = date_parameters_list['date'].val; DoiBroken = date_parameters_list['doi-broken-date'].val; LayDate = date_parameters_list['lay-date'].val; PublicationDate = " " date_parameters_list['publication-date'].val; end else table. external_linkinsert ( URLz.message_tail, nil{utilities.set_message ('err_bad_date', URLorigin{utilities.make_sep_list (#error_list, error_list)}, UrlAccess true)});-- add this error message end end-- end of do
if is_setlocal ID_list_coins = identifiers.extract_ids (args); -- gets identifiers and their values from args; this list used for COinS and source for build_id_list(Quote) then if Quote:subutilities.is_set (1,1DoiBroken) == and not ID_list_coins['"DOI' and Quote:sub] then table.insert (-1z.message_tail,-1) == {utilities.set_message ('"err_doibroken_missing_doi' then -- if first and last characters of quote are quote marks Quote = Quote, A:subORIGIN(2,-2'DoiBroken'))}); -- strip them off
end
Quote local ID_access_levels = sepc .." " .identifiers. wrap_style extract_id_access_levels ('quoted-text'args, Quote ID_list_coins); -- wrap in <q> local ID_list = identifiers...</q> tags PostScript build_id_list (ID_list_coins, {IdAccessLevels = ID_access_levels, DoiBroken = DoiBroken, ASINTLD = ASINTLD, IgnoreISBN = IgnoreISBN, Embargo = Embargo, Class = ""Class}); -- cs1|2 does not supply terminal punctuation when |quote= is set endrender identifiers
local Archived-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, {{cite ssrn}}, before generation of COinS data. if is_setutilities.in_array (ArchiveURLconfig.CitationClass, whitelist.preprint_template_list) then if not utilities.is_set(ArchiveDateID_list_coins[config.CitationClass:upper()]) then -- |arxiv= or |eprint= required for cite arxiv; |biorxiv= & |citeseerx= required for their templates ArchiveDate = set_errortable.insert (z.message_tail, {utilities.set_message ('archive_missing_dateerr_'.. config.CitationClass .. '_missing', {}, true)});-- add error message
end
  if "no" Periodical = ({['arxiv'] ='arXiv', ['biorxiv'] = DeadURL then local arch_text 'bioRxiv', ['citeseerx'] = cfg.messages'CiteSeerX', ['archivedssrn'] = 'Social Science Research Network'})[config.CitationClass]; end  -- Link the title of the work if sepc ~no |url= was provided, but we have a |pmc= or a |doi= with |doi-access= "free  if config." then arch_text CitationClass = arch_text:lower() end Archived = sepc .. " journal" and not utilities.is_set (URL) and not utilities.is_set (TitleLink) and not utilities. substitutein_array ( cfg.messageskeywords_xlate[Title], {'off', 'none'archived}) then -not-deadTODO: remove 'none' once existing citations have been switched to 'off'],so 'none' can be used as token for "no title" instead { external_link( ArchiveURL, arch_text, A:ORIGIN( if 'ArchiveURLnone'), nil ) ~= cfg.keywords_xlate[auto_select] then -- if auto-linking not disabled if identifiers.auto_link_urls[auto_select] then -- manual selection URL = identifiers.auto_link_urls[auto_select]; -- set URL to be the same as identifier's external link URL_origin = cfg. ArchiveFormat, ArchiveDate } id_handlers[auto_select:upper()].parameters[1];-- set URL_origin to parameter name for use in error message if citation is missing a |title= elseif identifiers.auto_link_urls['pmc'] then -- auto-select PMC URL = identifiers.auto_link_urls['pmc']; -- set URL to be the same as the PMC external link if not is_set(OriginalURL) thenembargoed Archived URL_origin = Archived cfg.id_handlers['PMC'].parameters[1]; -- set URL_origin to parameter name for use in error message if citation is missing a |title= elseif identifiers. " " auto_link_urls['doi'] then -- auto-select DOI URL = identifiers.auto_link_urls['doi']; URL_origin = cfg. set_error(id_handlers['archive_missing_urlDOI')].parameters[1];
end
elseif is_set(OriginalURL) then -- DeadURL is empty, 'yes', 'true', 'y', 'unfit', 'usurped' end local arch_text = cfg.messages['archived-dead']; if sepc ~= "utilities." then arch_text = arch_text:loweris_set (URL) end if in_array and utilities.is_set (DeadURL, {'unfit', 'usurped', 'bot: unknown'}AccessDate) then Archived = sepc .. " " .. 'Archived from the original on ' .. ArchiveDate; -- format already styled if 'bot: unknown' access date requires |url== DeadURL then add_maint_cat ('bot:_unknown'); identifier-- and add a category if created URL is not already added else add_maint_cat ('unfit'); -- and add a category if not already added end|url= else -- DeadURL is empty, 'yes', 'true', or 'y' Archived = sepc table.insert( z. " " .. substitute( arch_textmessage_tail, { external_linkutilities.set_message ( OriginalURL, cfg.messages['originalerr_accessdate_missing_url'], OriginalURLorigin{}, OriginalAccess true ) .. OriginalFormat, ArchiveDate } ); -- format already styledadd an error message end else local arch_text AccessDate = cfg.messages['archived-missing']; if sepc ~= "." then arch_text = arch_text:lower() end Archived = sepc .. " " .. substitute( arch_text, { set_error('archive_missing_url'), ArchiveDate } ); -- unset
end
elseif is_set (ArchiveFormat) then
Archived = ArchiveFormat; -- if set and ArchiveURL not set ArchiveFormat has error message
else
Archived = ""
end
  -- At this point fields may be nil if they weren't specified in the template use. We can use that fact. local Lay = '';-- Test if citation has no title if not utilities.is_set(LayURLTitle) then if and not utilities.is_set(LayDateTransTitle) then LayDate = " (" .. LayDate .and not utilities. ")" end if is_set(LaySourceScriptTitle) then -- has special case for cite episode LaySource = " &ndash; ''" table.. safe_for_italicsinsert(LaySource) z.message_tail, { utilities. "set_message ( 'err_citation_missing_title', {'episode'"; else LaySource = ""= config.CitationClass and 'series' or 'title'}, true ) } ); end if sepc == 'utilities.' then Lay = sepc .. " " .. external_linkin_array ( LayURL, cfg.messageskeywords_xlate[Title], {'lay summaryoff'], A:ORIGIN('LayURLnone'}), nil ) .. LayFormat .. LaySource .. LayDate elseand Lay = sepc utilities.in_array (config. " " .. external_link( LayURLCitationClass, cfg.messages[{'lay summaryjournal']:lower(), A:ORIGIN('LayURLcitation'}), nil and (utilities.is_set (Periodical) or utilities.. LayFormat .. LaySource .. LayDateis_set (ScriptPeriodical)) and end elseif is_set (LayFormat'journal' == Periodical_origin or 'script-journal' == ScriptPeriodical_origin) then -- Test if |layspecial case for journal cites Title = ''; -format= is given without giving a |lay-url=set title to empty string Lay = sepc utilities.. LayFormatset_message ('maint_untitled'); -- if set and LayURL not set, then LayFormat has error messageadd maint cat
end
check_for_url ({ -- add error message when any of these parameters hold a URL ['title']=Title, [A:ORIGIN('Chapter')]=Chapter, [Periodical_origin] = Periodical, [PublisherName_origin] = PublisherName });  -- COinS metadata (see <http://ocoins.info/>) for automated parsing of citation information. -- handle the oddity that is cite encyclopedia and {{citation |encyclopedia=something}}. Here we presume that -- when Periodical, Title, and Chapter are all set, then Periodical is the book (encyclopedia) title, Title -- is the article title, and Chapter is a section within the article. So, we remap local coins_chapter = Chapter; -- default assuming that remapping not required local coins_title = Title; -- et tu if 'encyclopaedia' == config.CitationClass or ('citation' == config.CitationClass and utilities.is_set(TranscriptEncyclopedia)) then if utilities.is_set(TranscriptURLChapter) and utilities.is_set (Title) and utilities.is_set (Periodical) then -- if all are used then Transcript coins_chapter = Title; -- remap coins_title = external_link( TranscriptURL, Transcript, TranscriptURLorigin, nil )Periodical;
end
Transcript = sepc .. ' ' .. Transcript .. TranscriptFormat;
elseif is_set(TranscriptURL) then
Transcript = external_link( TranscriptURL, nil, TranscriptURLorigin, nil );
end
local coins_author = a; -- default for coins rft.au
if 0 < #c then -- but if contributor list
coins_author = c; -- use that instead
end
 
-- this is the function call to COinS()
local OCinSoutput = metadata.COinS({
['Periodical'] = utilities.strip_apostrophe_markup (Periodical), -- no markup in the metadata
['Encyclopedia'] = Encyclopedia, -- just a flag; content ignored by ~/COinS
['Chapter'] = metadata.make_coins_title (coins_chapter, ScriptChapter), -- Chapter and ScriptChapter stripped of bold / italic wiki-markup
['Degree'] = Degree; -- cite thesis only
['Title'] = metadata.make_coins_title (coins_title, ScriptTitle), -- Title and ScriptTitle stripped of bold / italic wiki-markup
['PublicationPlace'] = PublicationPlace,
['Date'] = COinS_date.rftdate, -- COinS_date has correctly formatted date if Date is valid;
['Season'] = COinS_date.rftssn,
['Quarter'] = COinS_date.rftquarter,
['Chron'] = COinS_date.rftchron or (not COinS_date.rftdate and Date) or '', -- chron but if not set and invalid date format use Date; keep this last bit?
['Series'] = Series,
['Volume'] = Volume,
['Issue'] = Issue,
['Pages'] = coins_pages or metadata.get_coins_pages (first_set ({Sheet, Sheets, Page, Pages, At}, 5)), -- pages stripped of external links
['Edition'] = Edition,
['PublisherName'] = PublisherName or Newsgroup, -- any apostrophe markup already removed from PublisherName
['URL'] = first_set ({ChapterURL, URL}, 2),
['Authors'] = coins_author,
['ID_list'] = ID_list_coins,
['RawPage'] = this_page.prefixedText,
}, config.CitationClass);
local Publisher;-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, and {{cite ssrn}} AFTER generation of COinS data. if is_setutilities.in_array (PublicationDateconfig.CitationClass, whitelist.preprint_template_list) then-- we have set rft.jtitle in COinS to arXiv, bioRxiv, CiteSeerX, or ssrn now unset so it isn't displayed PublicationDate Periodical = wrap_msg (''; -- periodical not allowed in these templates; if article has been published', PublicationDate);use cite journal
end
  if is_set(PublisherName) then-- special case for cite newsgroup. Do this after COinS because we are modifying Publishername to include some static text if is_set(PublicationPlace) then Publisher 'newsgroup' = sepc .. " " .. PublicationPlace .. ": " .. PublisherName .. PublicationDate; else Publisher = sepc .. " " config.CitationClass and utilities. PublisherName .. PublicationDate; end elseif is_set(PublicationPlaceNewsgroup) then PublisherPublisherName = sepc .. " " .utilities. PublicationPlace .. PublicationDate; else Publisher = PublicationDate; end -- Several of the above rely upon detecting this as nil, so do it last. if is_set(Periodical) then if is_set(Title) or is_setsubstitute (TitleNote) then Periodical = sepc .. " " .cfg. wrap_style (messages['italic-titlenewsgroup'], Periodical) else Periodical = wrap_style external_link('italic-titlenews:'.. Newsgroup, Newsgroup, Newsgroup_origin, Periodicalnil ) end);
end
--[[Now perform various field substitutions. Handle the oddity that is cite speech. This code overrides whatever may be the value assigned to TitleNote (through |department=) -- We also add leading spaces and surrounding markup and forces it punctuation to be " (Speech)" so thatthe the annotation directly follows the |title= parameter value in -- various parts of the citation rather than the |event= parameter value , but only when they are non-nil. local EditorCount; -- used only for choosing {ed.) or (if providededs.).annotation at end of editor name-list ]]do local last_first_list; if "speech" local control ={ format = config.CitationClass then NameListStyle, -- empty string or 'vanc' maximum = nil, -- as if display-authors or display- cite speech onlyeditors not set lastauthoramp = LastAuthorAmp, mode = Mode TitleNote = " (Speech)"};   do -- annotate do editor name list first because the citationnow unsupported coauthors used to modify control table if is_set control.maximum , editor_etal = get_display_names (PeriodicalA['DisplayEditors'], #e, 'editors', editor_etal) then -- if Periodical; last_first_list, perhaps because of an included |websiteEditorCount = or |journal= parameter list_people(control, e, editor_etal);  if utilities.is_set (ConferenceEditors) then Editors, editor_etal = name_has_etal (Editors, editor_etal, false, 'editors'); -- find and remove variations on et al. if |event= is seteditor_etal then Conference Editors = Conference Editors ..' ' . sepc .cfg. " "messages['et al']; -- then add appropriate punctuation et al. to the editors parameter beause |display-editors=etal end of the Conference variable before rendering EditorCount = 2; -- we don't know but assume |editors= is multiple names; spoof to display (eds.) annotation else Editors = last_first_list; -- either an author name list or an empty string
end
end
end
if 1 == EditorCount and (true == editor_etal or 1 < #e) then -- Piece all bits together at last. Here, all should be non-nil.only one editor displayed but includes etal then EditorCount = 2; -- We build things this way because it is more efficient in LUA -- not spoof to keep reassigning to the same string variable over and overdisplay (eds.) annotation end local tcommon; end local tcommon2; do -- used for book cite when |contributor= is setnow do interviewers if in_array(config control.CitationClassmaximum , {"journal","citation"}) and is_set(Periodical) then if is_set(Others) then Others = Others .. sepc .. " " end tcommon interviewer_etal = safe_joinget_display_names ( {Others, Title, TitleNote, ConferenceA['DisplayInterviewers'], Periodical#interviewers_list, Format'interviewers', TitleType, Series, Language, Edition, Publisher, Agency, Volume}, sepc interviewer_etal); elseif in_array Interviewers = list_people (config.CitationClasscontrol, {"book"interviewers_list,"citation"}interviewer_etal) and not is_set(Periodical) then ; end do -- special cases for book citesnow do translators if is_set control.maximum , translator_etal = get_display_names (Contributors) then -- when we are citing forewordA['DisplayTranslators'], preface#t, introduction'translators', etctranslator_etal); tcommon Translators = safe_joinlist_people ( {Titlecontrol, TitleNote}t, sepc translator_etal); end do -- author and other stuff will come after this and before tcommon2now do contributors tcommon2 control.maximum , contributor_etal = safe_joinget_display_names ( {Conference, Periodical, Format, TitleType, Series, LanguageA['DisplayContributors'], Volume#c, Others'contributors', Edition, Publisher, Agency}, sepc contributor_etal); else tcommon Contributors = safe_joinlist_people ( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, Volume, Others, Edition, Publishercontrol, Agency}c, sepc contributor_etal);
end
do -- now do authors
control.maximum , author_etal = get_display_names (A['DisplayAuthors'], #a, 'authors', author_etal);
 
last_first_list = list_people(control, a, author_etal);
elseif 'map' == config if utilities.CitationClass then -- special cases for cite map if is_set (ChapterAuthors) then -- map in a book; TitleType is part of Chapter tcommon Authors, author_etal = safe_joinname_has_etal ( {TitleAuthors, Formatauthor_etal, Editionfalse, Scale, Series, Language, Cartography, Others, Publisher, Volume}, sepc 'authors');-- find and remove variations on et al. if author_etal then Authors = Authors .. ' ' .. cfg.messages['et al']; elseif is_set (Periodical) then -- map in a periodicaladd et al. to authors parameter end tcommon else Authors = safe_join( {Title, TitleType, Format, Periodical, Scale, Series, Language, Cartography, Others, Publisher, Volume}, sepc )last_first_list; -- either an author name list or an empty string end else end -- a sheet or stand-alone mapend of do if utilities.is_set (Authors) and utilities.is_set (Collaboration) then tcommon Authors = safe_joinAuthors .. ' ( {Title, TitleType, Format, Edition, Scale, Series, Language, Cartography, Others, Publisher}, sepc ' .. Collaboration .. ')'; -- add collaboration after et al.
end
elseif 'episode' == config.CitationClass then -- special case for cite episode
tcommon = safe_join( {Title, TitleNote, TitleType, Series, Transcript, Language, Edition, Publisher}, sepc );
else -- all other CS1 templates
tcommon = safe_join( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language,
Volume, Others, Edition, Publisher, Agency}, sepc );
end
if #ID_list > 0 then
ID_list = safe_join( { sepc .. " ", table.concat( ID_list, sepc .. " " ), ID }, sepc );
else
ID_list = ID;
end
local idcommon = safe_join( { ID_list, URL, Archived, AccessDate, Via, SubscriptionRequired, Lay, Quote }, sepc );
local text;
local pgtext = Position .. Sheet .. Sheets .. Page .. Pages .. At;
-- apply |[xx-]format= styling; at the end, these parameters hold correctly styled format annotation, -- an error message if is_setthe associated URL is not set, or an empty string for concatenation ArchiveFormat = style_format (ArchiveFormat, ArchiveURL, 'archive-format', 'archive-url'); ConferenceFormat = style_format (ConferenceFormat, ConferenceURL, 'conference-format', 'conference-url'); Format = style_format (DateFormat, URL, 'format', 'url') then; if is_set LayFormat = style_format (AuthorsLayFormat, LayURL, 'lay-format', 'lay-url') or is_set ; TranscriptFormat = style_format (EditorsTranscriptFormat, TranscriptURL, 'transcript-format', 'transcripturl') then ;  -- date follows authors special case for chapter format so no error message or editors cat when authors chapter not setsupported Date = " if not (" utilities.in_array (config. Date CitationClass, {'web', 'news', 'journal', 'magazine', 'pressrelease', 'podcast', 'newsgroup', 'arxiv', 'biorxiv', 'citeseerx', 'ssrn'}) or ('citation' == config.CitationClass and (utilities."is_set (Periodical)" or utilities.is_set (ScriptPeriodical)) and not utilities. OrigYear is_set (Encyclopedia))) then ChapterFormat = style_format (ChapterFormat, ChapterURL, 'chapter-format', 'chapter-url'); end  if not utilities.is_set (URL) then if utilities. sepc .in_array (config. CitationClass, {"web", "podcast", " mailinglist"; }) or -- in paranetheses else -- neither of authors |url= required for cite web, cite podcast, and editors setcite mailinglist if (string'citation' == config.subCitationClass and (tcommon,-1,'website' == Periodical_origin or 'script-1) website' == sepcScriptPeriodical_origin)) then -- if the last character of tcommon is sepc Date and required for {{citation}} with |website= " " .. Date .. OrigYear; or |script-- Date does not begin with sepc elsewebsite= Date = sepc table.insert( z. " " message_tail, { utilities.. Date .. OrigYearset_message ( 'err_cite_web_url', {}, true ) } ); -- Date begins with sepc end
end
end -- do we have |accessdate= without either |url= or |chapter-url=? if utilities.is_set(AuthorsAccessDate) then if (and not utilities.is_set (Date)ChapterURL) then -- ChapterURL may be set when date URL is not set it's in parentheses; no Authors termination Authors = terminate_name_list table.insert(Authorsz.message_tail, sepc{ utilities.set_message ( 'err_accessdate_missing_url', {}, true ) } ); -- when no date, terminate with 0 or 1 sepc and a space AccessDate = '';
end
if is_set(Editors) then end  local in_text = " "OriginalURL, OriginalURL_origin, OriginalFormat, OriginalAccess; local post_text UrlStatus = ""UrlStatus:lower(); -- used later when assembling archived text if utilities.is_set(ChapterArchiveURL ) and 0 == #c then in_text = in_text if utilities.. cfg.messages['in'] .. " " if is_set (sepc ~= '.'ChapterURL) then -- if chapter-url= is set apply archive url to it in_text OriginalURL = in_text:lower() ChapterURL; -- lowercase save copy of source chapter's url for cs2 endarchive text else if EditorCount <OriginalURL_origin = 1 then post_text = ", " .. cfg.messages['editor']ChapterURL_origin; else post_text -- name of |chapter-url= ", " .. cfg.parameter for error messages['editors']; end end Editors OriginalFormat = terminate_name_list (in_text .. Editors .. post_text, sepc)ChapterFormat; -- terminate with 0 or 1 sepc and a spaceoriginal |chapter-format= end if is_set (Contributors) 'live' ~= UrlStatus then ChapterURL = ArchiveURL -- book cite and weswap-in the archive're citing the intro, preface, etcs URL local by_text ChapterURL_origin = sepc .. ' ' .. cfg.messages['by'] .. ' '; if A:ORIGIN(sepc ~= '.ArchiveURL') then by_text = by_text:lower() end --name of |archive- lowercase url= parameter for cs2error messages Authors ChapterFormat = by_text .. AuthorsArchiveFormat or ''; -- author follows title so tweak it here if is_set (Editors) and is_set (Date) then -- when Editors make sure that Authors gets terminatedswap in archive's format Authors ChapterUrlAccess = terminate_name_list (Authors, sepc)nil; -- terminate with 0 or 1 sepc and a spacerestricted access levels do not make sense for archived URLs
end
if (not elseif utilities.is_set (Date)URL) then OriginalURL = URL; -- save copy of original source URL OriginalURL_origin = URL_origin; -- name of URL parameter for error messages OriginalFormat = Format; -- and original |format= OriginalAccess = UrlAccess;  if 'live' ~= UrlStatus then -- when date is if URL set then |archive-url= applies to it URL = ArchiveURL -- swap-in the archive's in parentheses; no Contributors terminationURL Contributors URL_origin = terminate_name_list A:ORIGIN(Contributors, sepc'ArchiveURL') -- name of archive URL parameter for error messages Format = ArchiveFormat or ''; -- swap in archive's format UrlAccess = nil; -- terminate with 0 or 1 sepc and a spacerestricted access levels do not make sense for archived URLs
end
text = safe_join end end  if utilities.in_array ( config.CitationClass, {Contributors'web', Date'news', Chapter'journal', tcommon'magazine', Authors'pressrelease', Place'podcast', 'newsgroup', Editors'arxiv', tcommon2'biorxiv', pgtext'citeseerx', idcommon 'ssrn'}, sepc );or -- if any of the 'periodical' cites except encyclopedia else('citation' == config.CitationClass and (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) and not utilities.is_set (Encyclopedia)) then local chap_param; text if utilities.is_set (Chapter) then -- get a parameter name from one of these chapter related meta-parameters chap_param = safe_joinA:ORIGIN ( {Authors, Date, 'Chapter, Place, Editors, tcommon, pgtext, idcommon }, sepc ') elseif utilities.is_set (TransChapter);then end chap_param = A:ORIGIN ('TransChapter') elseif utilities.is_set(EditorsChapterURL) then if chap_param = A:ORIGIN ('ChapterURL') elseif utilities.is_set(DateScriptChapter) then chap_param = ScriptChapter_origin; else utilities.is_set (ChapterFormat) chap_param = A:ORIGIN ('ChapterFormat') end  if EditorCount <= 1 utilities.is_set (chap_param) then -- if we found one Editors = Editors table.insert( z. "message_tail, " { utilities.. cfg.messages[set_message ( 'err_chapter_ignored', {chap_param}, true ) } ); -- add error message Chapter = ''; -- and set them to empty string to be safe with concatenation TransChapter = ''; ChapterURL = 'editor']; else ScriptChapter = ''; Editors ChapterFormat = Editors .. ", " .. cfg.messages['editors'];
end
else -- otherwise, format chapter / article title elselocal no_quotes = false; -- default assume that we will be quoting the chapter parameter value if EditorCount utilities.is_set (Contribution) and 0 <= 1 #c then -- if this is a contribution with contributor(s) Editors = Editors if utilities.. " in_array (Contribution:lower(" .. ), cfg.messages['editor'] .keywords_lists. "contribution)" .. sepc .. " " elsethen -- and a generic contribution title Editors no_quotes = Editors .. " (" .. cfg.messages['editors'] .. ")" .. sepc .. " "true; -- then render it unquoted
end
end
  text Chapter = safe_joinformat_chapter_title ( {EditorsScriptChapter, DateScriptChapter_origin, Chapter, PlaceChapter_origin, TransChapter, TransChapter_origin, tcommonChapterURL, pgtextChapterURL_origin, idcommon}no_quotes, sepc ChapterUrlAccess); else-- Contribution is also in Chapter if in_arrayutilities.is_set (Chapter) then Chapter = Chapter .. ChapterFormat ; if 'map' == config.CitationClass, {"journal","citation"}) and utilities.is_set(PeriodicalTitleType) then Chapter = Chapter .. ' ' .. TitleType; -- map annotation here; not after title end text Chapter = safe_join( {Chapter, Place, tcommon, pgtext, Date, idcommon}, .. sepc ).. ' '; elseelseif utilities.is_set (ChapterFormat) then -- |chapter= not set but |chapter-format= is so ... text Chapter = safe_join( {Chapter, Place, tcommon, Date, pgtext, idcommon}, ChapterFormat .. sepc ).. ' '; -- ... ChapterFormat has error message, we want to see it
end
end
if is_set(PostScript) and PostScript ~= sepc then
text = safe_join( {text, sepc}, sepc ); --Deals with italics, spaces, etc.
text = text:sub(1,-sepc:len()-1);
end
text = safe_join( {text, PostScript}, sepc );
-- Now enclose the whole thing in a <cite/> elementFormat main title local options plain_title = {}false; local accept_title; if is_setTitle, accept_title = utilities.has_accept_as_written (config.CitationClassTitle, true) ; -- remove accept-this-as-written markup when it wraps all of <Title> if accept_title and config.CitationClass ~('' == Title) then -- only support forced empty for now "citation(())" then options.class Title = configcfg.CitationClassmessages['notitle']; -- replace by predefined "No title" message options -- TODO: utilities.class = "citation " set_message ( 'err_redundant_parameters', .. config.CitationClass); -- classissue proper error message instead of muting ScriptTitle =citation required ''; -- just mute for blue highlight when used with |refnow TransTitle =''; -- just mute for now else plain_title = true; -- suppress text decoration for descriptive title optionsutilities.class = "citation"set_message ('maint_untitled'); -- add maint cat end if is_set(Ref) and Ref:lower() ~= "none" not accept_title then -- set reference anchor if appropriate local id = Ref<Title> not wrapped in accept-as-written markup if ('harv...' == Ref Title:sub (-3) then -- if ellipsis is the last three characters of |title= local namelist Title = {}Title:gsub ('(%.%.%.)%.+$', '%1'); -- holds selected contributorlimit the number of dots to three elseif not mw.ustring.find (Title, author'%.%s*%a%.$') and -- end of title is not a 'dot-(optional space-)letter-dot' initialism ... not mw.ustring.find (Title, editor name list'%s+%a%.$') then -- ...and not a 'space-letter-dot' initial (''Allium canadense'' L.) local year Title = first_set mw.ustring.gsub({YearTitle, anchor_year}'%' .. sepc .. '$', 2''); -- Year first for legacy citations remove any trailing separator character; sepc and ms.ustring() here for YMD dates languages that require disambiguationuse multibyte separator characters end  if utilities.is_set (ArchiveURL) and is_archived_copy (Title) then utilities.set_message ('maint_archived_copy'); -- add maintenance category before we modify the content of Title end
if #c > 0 is_generic_title (Title) then -- if there is a contributor list namelist = c; -- select it elseif #a > 0 then -- or an author list namelist = a; elseif #e > 0 then -- or an editor list namelist = e; end if #namelist > 0 then -- if there are names in namelist id = anchor_id table.insert (namelistz.message_tail, year); -- go make the CITEREF anchor else id = {utilities.set_message ( 'err_generic_title', {}, true ) } ); -- unset endset an error message
end
options.id = id;
end
if string.len(text:gsub("<span[^>/]*>(.-)</span>", "%1"):gsub("%b<>","")) <= 2 then -- remove <span> tags and other html-like markup; then get length of what remains
z.error_categories = {};
text = set_error('empty_citation');
z.message_tail = {};
end
local render = {}; -- here we collect the final bits for concatenation into the rendered citation
if is_set(optionsnot plain_title) and (utilities.in_array (config.idCitationClass, {'web', 'news', 'journal', 'magazine', 'pressrelease', 'podcast', 'newsgroup', 'mailinglist', 'interview', 'arxiv', 'biorxiv', 'citeseerx', 'ssrn'}) then -- here we wrap the rendered or ('citation in <cite ' == config.CitationClass and (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) and not utilities.>is_set (Encyclopedia)) or ('map' == config.CitationClass and (utilities.is_set (Periodical) or utilities.</is_set (ScriptPeriodical)))) then -- special case for cite> tagsmap when the map is in a periodical treat as an article Title = kern_quotes (Title); -- if necessary, separate title's leading and trailing quote marks from module provided quote marks table Title = utilities.insert wrap_style (render'quoted-title', substitute Title); Title = script_concatenate (cfg.presentation[Title, ScriptTitle, 'citescript-idtitle']); -- <bdi> tags, lang attribute, {mwcategorization, etc.uri; must be done after title is wrapped TransTitle = utilities.anchorEncodewrap_style ('trans-quoted-title', TransTitle ); elseif plain_title or (options'report' == config.idCitationClass) then -- no styling for cite report and descriptive titles (otherwise same as above) Title = script_concatenate (Title, ScriptTitle, 'script-title'); -- <bdi> tags, lang attribute, mwcategorization, etc.text; must be done after title is wrapped TransTitle = utilities.nowikiwrap_style (options.class)'trans-quoted-title', text})TransTitle ); --for cite report, use this form for trans- when |ref= is settitle
else
tableTitle = utilities.insert wrap_style (render'italic-title', substitute Title); Title = script_concatenate (cfg.presentation[Title, ScriptTitle, 'citescript-title']); -- <bdi> tags, {mwlang attribute, categorization, etc.text; must be done after title is wrapped TransTitle = utilities.nowikiwrap_style (options.class)'trans-italic-title', text})TransTitle); -- all other cases end
tablelocal TransError = ""; if utilities.insert is_set (render, substitute TransTitle) then if utilities.is_set (cfgTitle) then TransTitle = " " ..presentation[TransTitle; else TransError = " " .. utilities.set_message ( 'ocinserr_trans_missing_title'], {OCinSoutput'title'})); end -- append metadata to the citationend
if #zutilities.message_tail ~= 0 is_set (Title) then -- TODO: is this the right place to be making Wikisource URLs? if utilities.is_set (TitleLink) and utilities.is_set (URL) then table.insert (renderz.message_tail, { utilities.set_message ( ' err_wikilink_in_url', {}, true )} ); -- set an error message because we can't have both TitleLink = ''; -- unset for iend if not utilities.is_set (TitleLink) and utilities.is_set (URL) then Title = external_link (URL, Title, URL_origin,v in ipairsUrlAccess) .. TransTitle .. TransError .. Format; URL = ''; -- unset these because no longer needed Format = ""; elseif utilities.is_set ( zTitleLink) and not utilities.message_tail is_set (URL) dothen local ws_url; if is_setws_url = wikisource_url_make (v[1]TitleLink) ; -- ignore ws_label return; not used here if ws_url then if i Title == #zexternal_link (ws_url, Title ..message_tail then'&nbsp;', 'ws link in title-link'); -- space char after Title to move icon away from italic text; TODO: a better way to do this? table Title = utilities.insert substitute (render, error_comment( vcfg.presentation[1'interwiki-icon'], v{cfg.presentation[2'class-wikisource'] ), TitleLink, Title}); Title = Title .. TransTitle .. TransError; else table Title = utilities.make_wikilink (TitleLink, Title) .. TransTitle ..insert TransError; end else local ws_url, ws_label, L; -- Title has italic or quote markup by the time we get here which causes is_wikilink() to return 0 (rendernot a wikilink) ws_url, error_commentws_label, L = wikisource_url_make (Title:gsub( v'[\'"](.-)[\'"]', '%1')); -- make ws URL from |title= interwiki link (strip italic or quote markup); link portion L becomes tooltip label if ws_url then Title = Title:gsub ('%b[] ', ws_label); -- replace interwiki link with ws_label to retain markup Title = external_link (ws_url, Title .. "'&nbsp;', 'ws link in title'); -- space char after Title to move icon away from italic text; "TODO: a better way to do this? Title = utilities.substitute (cfg.presentation['interwiki-icon'], v{cfg.presentation[2'class-wikisource'] , L, Title})); Title = Title .. TransTitle .. TransError; else endTitle = Title .. TransTitle .. TransError;
end
end
else
Title = TransTitle .. TransError;
end
if #zutilities.maintenance_cats ~= 0 is_set (Place) then table.insert (render, '<span classPlace ="citation-comment" style="display:none; color:#33aa33; margin-left:0.3em">'); for _, v in ipairs( z.maintenance_cats ) do -- append maintenance categories table.insert wrap_msg (render'written', v); table.insert (renderPlace, ' ('use_lowercase); table.insert (render, make_wikilink (':Category:' .sepc . v, 'link')); table.insert (render, ') '); end table.insert (render, '</span>')" ";
end
no_tracking_cats = no_tracking_cats:lower(); if in_arrayutilities.is_set (no_tracking_cats, {"", "no", "false", "n"}Conference) then for _, v in ipairs( zif utilities.error_categories ) do table.insert is_set (render, make_wikilink ('Category:' .. vConferenceURL)); end for _, v in ipairs( z.maintenance_cats ) do -- append maintenance categoriesthen table.insert Conference = external_link(renderConferenceURL, make_wikilink ('Category:' .. v)); end for _Conference, v in ipairs( z.properties_cats ) do -- append properties categories table.insert (renderConferenceURL_origin, make_wikilink ('Category:' .. v)nil );
end
Conference = sepc .. " " .. Conference .. ConferenceFormat;
elseif utilities.is_set (ConferenceURL) then
Conference = sepc .. " " .. external_link( ConferenceURL, nil, ConferenceURL_origin, nil );
end
return tableif not utilities.concat is_set (renderPosition)then local Minutes = A['Minutes']; end local Time = A['Time'];
if utilities.is_set (Minutes) then if utilities.is_set (Time) then--[[--------------------------< C S 1 table.insert( z.message_tail, { utilities.set_message ( 'err_redundant_parameters', {utilities. C I T A T I O N >------------------------------------------------------wrap_style ('parameter', 'minutes') .. ' and ' .. utilities.wrap_style ('parameter', 'time')}, true ) } ); end Position = " " .. Minutes .. " " .. cfg.messages['minutes']; elseThis is used by templates such as {{cite book}} to create the actual citation text. if utilities.is_set (Time) then] local TimeCaption = A['TimeCaption'function cs1 if not utilities.citationis_set (frameTimeCaption)then Frame TimeCaption = framecfg.messages['event']; -- save a copy incase we need to display an error message in preview mode local pframe if sepc ~= '.' then TimeCaption = frameTimeCaption:getParentlower() local validation, utilities, identifiers, metadata; end if nil ~ end Position = " " .. TimeCaption .. " " .. Time; end end else Position = string" " ..find (frame:getTitle(), Position; At = 'sandbox'; end  Page, Pages, Sheet, 1, true) then -- did the {{#invoke:}} use sandbox version? cfg Sheets = mw.loadData format_pages_sheets ('Module:Citation/CS1/Configuration/sandbox'Page, Pages, Sheet, Sheets, config.CitationClass, Periodical_origin, sepc, NoPP, use_lowercase);   -- load sandbox versions of support modules whitelist At = mwutilities.loadData is_set (At) and ('Module:Citation/CS1/Whitelist/sandbox'sepc .. " " .. At)or ""; Position = utilities = require .is_set (Position) and ('Module:Citation/CS1/Utilities/sandbox'sepc .. " " .. Position)or ""; validation if config.CitationClass == require ('Module:Citation/CS1/Date_validation/sandboxmap');then identifiers local Sections = require (A['Module:Citation/CS1/Identifiers/sandboxSections')]; metadata = require -- Section (singular) is an alias of Chapter so set earlier local Inset = A['Module:Citation/CS1/COinS/sandboxInset')]; else -- otherwise cfg = mwif utilities.loadData is_set ('Module:Citation/CS1/Configuration'); Inset ) then -- load live versions of support modules whitelist Inset = mwsepc .. " " ..loadData wrap_msg ('Module:Citation/CS1/Whitelistinset', Inset, use_lowercase); utilities = require end   if utilities.is_set ('Module:Citation/CS1/Utilities'Sections );then validation Section = require sepc .. " " .. wrap_msg ('Module:Citation/CS1/Date_validationsections', Sections, use_lowercase); identifiers = require elseif utilities.is_set ('Module:Citation/CS1/Identifiers'Section );then metadata Section = require sepc .. " " .. wrap_msg ('Module:Citation/CS1/COinSsection', Section, use_lowercase); end At = At .. Inset .. Section; end   if utilities.set_selected_modules is_set (cfgLanguage); -- so that functions in Utilities can see the cfg tablesthen identifiers.set_selected_modules Language = language_parameter (cfg, utilitiesLanguage); --format, categories, name from ISO639- so that functions in Identifiers can see the selected cfg tables and selected Utilities module1, etc. validation.set_selected_modules (cfg, utilities)else Language=""; -- language not specified so that functions in Date validataion can see selected cfg tables and make sure this is an empty string; --[[ TODO: need to extract the selected Utilities modulewrap_msg from language_parameter metadata.set_selected_modules (cfg, utilities); -- so that functions in COinS we can see the selected cfg tables and selected Utilities modulesolve parentheses bunching problem with Format/Language/TitleType ]] end  dates Others = validationutilities.is_set (Others) and (sepc .. " " ..datesOthers) or ""; -- imported functions from Module:Citation/CS1/Date validation year_date_check = validation if utilities.year_date_check;is_set (Translators) then reformat_dates Others = validationsafe_join ({sepc ..reformat_dates' ', wrap_msg ('translated', Translators, use_lowercase), Others}, sepc); date_hyphen_to_dash = validation.date_hyphen_to_dash;end date_name_xlate = validationif utilities.date_name_xlate;  is_set (Interviewers) then Others = utilitiessafe_join ({sepc ..is_set' ', wrap_msg ('interview', Interviewers, use_lowercase), Others}, sepc); -- imported functions from Module:Citation/CS1/Utilities in_array = utilities.in_array;end substitute TitleNote = utilities.substitute; error_comment = utilitiesis_set (TitleNote) and (sepc ..error_comment; set_error = utilities" " .set_error; select_one = utilities.select_oneTitleNote) or ""; add_maint_cat = if utilities.add_maint_cat;is_set (Edition) then wrap_style = utilities if Edition:match ('%f[%a][Ee]d%.wrap_style;?$') or Edition:match ('%f[%a][Ee]dition$') then safe_for_italics = utilities.safe_for_italicsset_message ('maint_extra_text', 'edition'); -- add maint cat end is_wikilink Edition = utilities" " ..is_wikilinkwrap_msg ('edition', Edition); make_wikilink else Edition = utilities.make_wikilink''; end  z Series = utilities.z; -- table of error and category tables in Module:Citation/CS1/Utilities  extract_ids = identifiers.extract_idsis_set (Series) and wrap_msg ('series', {sepc, Series}) or ""; -- imported functions from Module:Citation/CS1/Identifiersnot the same as SeriesNum build_id_list OrigDate = identifiersutilities.build_id_listis_set (OrigDate) and wrap_msg ('origdate', OrigDate) or ''; is_embargoed Agency = identifiersutilities.is_embargoedis_set (Agency) and wrap_msg ('agency', {sepc, Agency}) or ""; extract_id_access_levels Volume = identifiersformat_volume_issue (Volume, Issue, config.extract_id_access_levelsCitationClass, Periodical_origin, sepc, use_lowercase); make_coins_title = metadata.make_coins_title; -- imported functions from Module:Citation/CS1/COinS get_coins_pages = metadata.get_coins_pages; COinS = metadata.COinS;  local args = {}; -- table where we store all of the template's arguments-------------------------------- totally unrelated data local suggestions Via = {}; -- table where we store suggestions if we need to loadData them local error_textutilities.is_set (Via) and wrap_msg ('via', error_stateVia) or ''local config = {if utilities.is_set (AccessDate) then local retrv_text = " " .. cfg.messages['retrieved']  AccessDate = nowrap_date (AccessDate); -- wrap in nowrap span if date in appropriate format if (sepc ~= ".") then retrv_text = retrv_text:lower() end -- if mode is cs2, lower case AccessDate = utilities.substitute (retrv_text, AccessDate); -- add retrieved text  AccessDate = utilities.substitute (cfg.presentation['accessdate'], {sepc, AccessDate}; -- table to store parameters from the module {{#invoke:}} for k, v in pairs( frame.args ) do config[k] = v;-- args); -- allow editors to hide accessdates end if utilities.is_set (ID) then ID = sepc .. " " .. ID; end if "thesis" == config.CitationClass and utilities.is_set (Docket) then ID = sepc .. " Docket " .. Docket .. ID; end if "report" == config.CitationClass and utilities.is_set (Docket) then -- for cite report when |docket= is set ID = sepc .. ' ' .. Docket; -- overwrite ID even if |id= is set end  if utilities.is_set (URL) then URL = " " .. external_link( URL, nil, URL_origin, UrlAccess ); end  if utilities.is_set (Quote) or utilities.is_set (TransQuote) or utilities.is_set (ScriptQuote) then  if utilities.is_set (Quote) then if Quote:sub(1, 1) == '"' and Quote:sub(-1, -1) == '"' then -- if first and last characters of quote are quote marks Quote = Quote:sub(2, -2); -- strip them off end end  Quote = utilities.wrap_style ('quoted-text', Quote ); -- wrap in <q>...</q> tags if utilities.is_set (ScriptQuote) then Quote = script_concatenate (Quote, ScriptQuote, 'script-quote'); -- <bdi> tags, lang attribute, categorization, etc.; must be done after quote is wrapped end  if utilities.is_set (TransQuote) then if TransQuote:sub(1, 1) == '"' and TransQuote:sub(-1, -1) == '"' then -- if first and last characters of |trans-quote are quote marks TransQuote = TransQuote:sub(2, -2); -- strip them off end Quote = Quote .. " " .. utilities.wrap_style ('trans-quoted-title', TransQuote ); end  if utilities.is_set (QuotePage) or utilities.is_set (QuotePages) then -- add page prefix local quote_prefix = ''; if utilities.is_set (QuotePage) then if not NoPP then quote_prefix = utilities.substitute (cfg.messages['p-prefix'], {sepc, QuotePage}), '', '', ''; else quote_prefix = utilities.substitute (cfg.messages['nopp'], {sepc, QuotePage}), '', '', ''; end elseif utilities.is_set (QuotePages) then if tonumber(QuotePages) ~= nil and not NoPP then -- if only digits, assume single page quote_prefix = utilities.substitute (cfg.messages['p-prefix'], {sepc, QuotePages}), '', ''; elseif not NoPP then quote_prefix = utilities.substitute (cfg.messages['pp-prefix'], {sepc, QuotePages}), '', ''; else quote_prefix = utilities.substitute (cfg.messages['nopp'], {sepc, QuotePages}), '', ''; end end Quote = quote_prefix .. ": " .. Quote; else Quote = sepc .. " " .. Quote; end  PostScript = ""; -- cs1|2 does not supply terminal punctuation when |quote= is set end local Archived if utilities.is_set (ArchiveURL) then local arch_text; if not utilities.is_set (ArchiveDate) then ArchiveDate = utilities.set_message ('err_archive_missing_date'); end if "live" == UrlStatus then arch_text = cfg.messages['archived']; if sepc ~= "." then arch_text = arch_text:lower() end Archived = sepc .. " " .. utilities.substitute ( cfg.messages['archived-live'], { external_link( ArchiveURL, arch_text, A:ORIGIN('ArchiveURL'), nil ) .. ArchiveFormat, ArchiveDate } ); if not utilities.is_set (OriginalURL) then Archived = Archived .. " " .. utilities.set_message ('err_archive_missing_url'); end elseif utilities.is_set (OriginalURL) then -- UrlStatus is empty, 'dead', 'unfit', 'usurped', 'bot: unknown' if utilities.in_array (UrlStatus, {'unfit', 'usurped', 'bot: unknown'}) then arch_text = cfg.messages['archived-unfit']; if sepc ~= "." then arch_text = arch_text:lower() end Archived = sepc .. " " .. arch_text .. ArchiveDate; -- format already styled if 'bot: unknown' == UrlStatus then utilities.set_message ('maint_bot_unknown'); -- and add a category if not already added else utilities.set_message ('maint_unfit'); -- and add a category if not already added end else -- UrlStatus is empty, 'dead' arch_text = cfg.messages['archived-dead']; if sepc ~= "." then arch_text = arch_text:lower() end Archived = sepc .. " " .. utilities.substitute ( arch_text, { external_link( OriginalURL, cfg.messages['original'], OriginalURL_origin, OriginalAccess ) .. OriginalFormat, ArchiveDate } ); -- format already styled end else -- OriginalUrl not set arch_text = cfg.messages['archived-missing']; if sepc ~= "." then arch_text = arch_text:lower() end Archived = sepc .. " " .. utilities.substitute ( arch_text, { utilities.set_message ('err_archive_missing_url'), ArchiveDate } ); end elseif utilities.is_set (ArchiveFormat) then Archived = ArchiveFormat; -- if set and ArchiveURL not set ArchiveFormat has error message else Archived = "" end local Lay = ''; if utilities.is_set (LayURL) then if utilities.is_set (LayDate) then LayDate = " (" .. LayDate .. ")" end if utilities.is_set (LaySource) then LaySource = " &ndash; ''" .. utilities.safe_for_italics (LaySource) .. "''"; else LaySource = ""; end if sepc == '.' then Lay = sepc .. " " .. external_link( LayURL, cfg.messages['lay summary'], A:ORIGIN('LayURL'), nil ) .. LayFormat .. LaySource .. LayDate else Lay = sepc .. " " .. external_link( LayURL, cfg.messages['lay summary']:lower(), A:ORIGIN('LayURL'), nil ) .. LayFormat .. LaySource .. LayDate end elseif utilities.is_set (LayFormat) then -- Test if |lay-format= is given without giving a |lay-url= Lay = sepc .. LayFormat; -- if set and LayURL not set, then LayFormat has error message end  if utilities.is_set (Transcript) then if utilities.is_set (TranscriptURL) then Transcript = external_link( TranscriptURL, Transcript, TranscriptURL_origin, nil ); end Transcript = sepc .. ' ' .. Transcript .. TranscriptFormat; elseif utilities.is_set (TranscriptURL) then Transcript = external_link( TranscriptURL, nil, TranscriptURL_origin, nil ); end  local Publisher; if utilities.is_set (PublicationDate) then PublicationDate = wrap_msg ('published', PublicationDate); end if utilities.is_set (PublisherName) then if utilities.is_set (PublicationPlace) then Publisher = sepc .. " " .. PublicationPlace .. ": " .. PublisherName .. PublicationDate; else Publisher = sepc .. " " .. PublisherName .. PublicationDate; end elseif utilities.is_set (PublicationPlace) then Publisher= sepc .. " " .. PublicationPlace .. PublicationDate; else Publisher = PublicationDate; end -- Several of the above rely upon detecting this as nil, so do it last. if (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical) or utilities.is_set (TransPeriodical)) then if utilities.is_set (Title) or utilities.is_set (TitleNote) then Periodical = sepc .. " " .. format_periodical (ScriptPeriodical, ScriptPeriodical_origin, Periodical, TransPeriodical, TransPeriodical_origin); else Periodical = format_periodical (ScriptPeriodical, ScriptPeriodical_origin, Periodical, TransPeriodical, TransPeriodical_origin); end end  --[[ Handle the oddity that is cite speech. This code overrides whatever may be the value assigned to TitleNote (through |department=) and forces it to be " (Speech)" so that the annotation directly follows the |title= parameter value in the citation rather than the |event= parameter value (if provided). ]] if "speech" == config.CitationClass then -- cite speech only TitleNote = " (Speech)"; -- annotate the citation if utilities.is_set (Periodical) then -- if Periodical, perhaps because of an included |website= or |journal= parameter if utilities.is_set (Conference) then -- and if |event= is set Conference = Conference .. sepc .. " "; -- then add appropriate punctuation to the end of the Conference variable before rendering end end end  -- Piece all bits together at last. Here, all should be non-nil. -- We build things this way because it is more efficient in LUA -- not to keep reassigning to the same string variable over and over.  local tcommon; local tcommon2; -- used for book cite when |contributor= is set if utilities.in_array (config.CitationClass, {"journal", "citation"}) and utilities.is_set (Periodical) then if utilities.is_set (Others) then Others = safe_join ({Others, sepc .. " "}, sepc) end -- add terminal punctuation & space; check for dup sepc; TODO why do we need to do this here? tcommon = safe_join( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, Edition, Publisher, Agency, Volume}, sepc ); elseif utilities.in_array (config.CitationClass, {"book", "citation"}) and not utilities.is_set (Periodical) then -- special cases for book cites if utilities.is_set (Contributors) then -- when we are citing foreword, preface, introduction, etc. tcommon = safe_join( {Title, TitleNote}, sepc ); -- author and other stuff will come after this and before tcommon2 tcommon2 = safe_join( {Conference, Periodical, Format, TitleType, Series, Language, Volume, Others, Edition, Publisher, Agency}, sepc ); else tcommon = safe_join( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, Volume, Others, Edition, Publisher, Agency}, sepc ); end  elseif 'map' == config.CitationClass then -- special cases for cite map if utilities.is_set (Chapter) then -- map in a book; TitleType is part of Chapter tcommon = safe_join( {Title, Format, Edition, Scale, Series, Language, Cartography, Others, Publisher, Volume}, sepc ); elseif utilities.is_set (Periodical) then -- map in a periodical tcommon = safe_join( {Title, TitleType, Format, Periodical, Scale, Series, Language, Cartography, Others, Publisher, Volume}, sepc ); else -- a sheet or stand-alone map tcommon = safe_join( {Title, TitleType, Format, Edition, Scale, Series, Language, Cartography, Others, Publisher}, sepc ); end elseif 'episode' == config.CitationClass then -- special case for cite episode tcommon = safe_join( {Title, TitleNote, TitleType, Series, Language, Edition, Publisher}, sepc );  else -- all other CS1 templates tcommon = safe_join( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, Volume, Others, Edition, Publisher, Agency}, sepc ); end if #ID_list > 0 then ID_list = safe_join( { sepc .. " ", table.concat( ID_list, sepc .. " " ), ID }, sepc ); else ID_list = ID; end local idcommon; if 'audio-visual' == config.CitationClass or 'episode' == config.CitationClass then -- special case for cite AV media & cite episode position transcript idcommon = safe_join( { ID_list, URL, Archived, Transcript, AccessDate, Via, Lay, Quote }, sepc ); else idcommon = safe_join( { ID_list, URL, Archived, AccessDate, Via, Lay, Quote }, sepc ); end local text; local pgtext = Position .. Sheet .. Sheets .. Page .. Pages .. At;  if utilities.is_set (Date) then if utilities.is_set (Authors) or utilities.is_set (Editors) then -- date follows authors or editors when authors not set Date = " (" .. Date .. ")" .. OrigDate .. sepc .. " "; -- in parentheses else -- neither of authors and editors set if (string.sub(tcommon, -1, -1) == sepc) then -- if the last character of tcommon is sepc Date = " " .. Date .. OrigDate; -- Date does not begin with sepc else Date = sepc .. " " .. Date .. OrigDate; -- Date begins with sepc end end end if utilities.is_set (Authors) then if (not utilities.is_set (Date)) then -- when date is set it's in parentheses; no Authors termination Authors = terminate_name_list (Authors, sepc); -- when no date, terminate with 0 or 1 sepc and a space end if utilities.is_set (Editors) then local in_text = " "; local post_text = ""; if utilities.is_set (Chapter) and 0 == #c then in_text = in_text .. cfg.messages['in'] .. " " if (sepc ~= '.') then in_text = in_text:lower() -- lowercase for cs2 end end if EditorCount <= 1 then post_text = " (" .. cfg.messages['editor'] .. ")"; -- be consistent with no-author, no-date case else post_text = " (" .. cfg.messages['editors'] .. ")"; end Editors = terminate_name_list (in_text .. Editors .. post_text, sepc); -- terminate with 0 or 1 sepc and a space end if utilities.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 utilities.is_set (Editors) and utilities.is_set (Date) 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 if (not utilities.is_set (Date)) then -- when date is set it's in parentheses; no Contributors termination Contributors = terminate_name_list (Contributors, sepc); -- terminate with 0 or 1 sepc and a space end text = safe_join( {Contributors, Date, Chapter, tcommon, Authors, Place, Editors, tcommon2, pgtext, idcommon }, sepc ); else text = safe_join( {Authors, Date, Chapter, Place, Editors, tcommon, pgtext, idcommon }, sepc ); end elseif utilities.is_set (Editors) then if utilities.is_set (Date) then if EditorCount <= 1 then Editors = Editors .. ", " .. cfg.messages['editor']; else Editors = Editors .. ", " .. cfg.messages['editors']; end else if EditorCount <= 1 then Editors = Editors .. " (" .. cfg.messages['editor'] .. ")" .. sepc .. " " else Editors = Editors .. " (" .. cfg.messages['editors'] .. ")" .. sepc .. " " end end text = safe_join( {Editors, Date, Chapter, Place, tcommon, pgtext, idcommon}, sepc ); else if utilities.in_array (config.CitationClass, {"journal", "citation"}) and utilities.is_set (Periodical) then text = safe_join( {Chapter, Place, tcommon, pgtext, Date, idcommon}, sepc ); else text = safe_join( {Chapter, Place, tcommon, Date, pgtext, idcommon}, sepc ); end end if utilities.is_set (PostScript) and PostScript ~= sepc then text = safe_join( {text, sepc}, sepc ); --Deals with italics, spaces, etc. text = text:sub(1, -sepc:len() - 1); end text = safe_join( {text, PostScript}, sepc );  -- Now enclose the whole thing in a <cite/> element local options = {}; if utilities.is_set (config.CitationClass) and config.CitationClass ~= "citation" then options.class = string.format ('%s %s %s', 'citation', config.CitationClass, utilities.is_set (Mode) and Mode or 'cs1'); -- class=citation required for blue highlight when used with |ref= else options.class = string.format ('%s %s', 'citation', utilities.is_set (Mode) and Mode or 'cs2'); end if utilities.is_set (Ref) and 'none' ~= cfg.keywords_xlate[Ref:lower()] then local id = Ref if ('harv' == Ref ) then local namelist = {}; -- holds selected contributor, author, editor name list local year = first_set ({Year, anchor_year}, 2); -- Year first for legacy citations and for YMD dates that require disambiguation  if #c > 0 then -- if there is a contributor list namelist = c; -- select it elseif #a > 0 then -- or an author list namelist = a; elseif #e > 0 then -- or an editor list namelist = e; end if #namelist > 0 then -- if there are names in namelist id = anchor_id (namelist, year); -- go make the CITEREF anchor else id = ''; -- unset end end options.id = id; end if string.len(text:gsub("<span[^>/]*>(.-)</span>", "%1"):gsub("%b<>", "")) <= 2 then -- remove <span> tags and other HTML-like markup; then get length of what remains z.error_categories = {}; text = utilities.set_message ('err_empty_citation'); z.message_tail = {}; end local render = {}; -- here we collect the final bits for concatenation into the rendered citation  if utilities.is_set (options.id) then -- here we wrap the rendered citation in <cite ...>...</cite> tags table.insert (render, utilities.substitute (cfg.presentation['cite-id'], {mw.uri.anchorEncode(options.id), mw.text.nowiki(options.class), text})); -- when |ref= is set else table.insert (render, utilities.substitute (cfg.presentation['cite'], {mw.text.nowiki(options.class), text})); -- all other cases end   table.insert (render, utilities.substitute (cfg.presentation['ocins'], {OCinSoutput})); -- append metadata to the citation  if 0 ~= #z.message_tail then table.insert (render, ' '); for i,v in ipairs( z.message_tail ) do if utilities.is_set (v[1]) then if i == #z.message_tail then table.insert (render, utilities.error_comment ( v[1], v[2] )); else table.insert (render, utilities.error_comment ( v[1] .. "; ", v[2] )); end end end end  if 0 ~= #z.maintenance_cats then local maint_msgs = {}; -- here we collect all of the maint messages for _, v in ipairs( z.maintenance_cats ) do -- append maintenance categories local maint = {}; -- here we assemble a maintenence message table.insert (maint, v); -- maint msg is the category name table.insert (maint, ' ('); -- open the link text table.insert (maint, utilities.make_wikilink (':Category:' .. v, 'link')); -- add the link table.insert (maint, ')'); -- and close it table.insert (maint_msgs, table.concat (maint)); -- assemble new maint message and add it to the maint_msgs table end table.insert (render, utilities.substitute (cfg.presentation['hidden-maint'], table.concat (maint_msgs, ' '))); -- wrap the group of maint message with proper presentation and save end if not no_tracking_cats then for _, v in ipairs( z.error_categories ) do table.insert (render, utilities.make_wikilink ('Category:' .. v)); end for _, v in ipairs( z.maintenance_cats ) do -- append maintenance categories table.insert (render, utilities.make_wikilink ('Category:' .. v)); end for _, v in ipairs( z.properties_cats ) do -- append properties categories table.insert (render, utilities.make_wikilink ('Category:' .. v)); end elseif 'nocat' == A:ORIGIN('NoTracking') then -- peculiar special case; can't track nocat without tracking nocat table.insert (render, utilities.make_wikilink ('Category:CS1 maint: nocat')); -- hand-set this category because it is supposed to be temporary end  return table.concat (render); end  --[[--------------------------< V A L I D A T E >-------------------------------------------------------------- Looks for a parameter's name in one of several whitelists. Parameters in the whitelist can have three values: true - active, supported parameters false - deprecated, supported parameters nil - unsupported parameters ]] local function validate (name, cite_class, empty) local name = tostring (name); local enum_name; -- for enumerated parameters, is name with enumerator replaced with '#' local state; local function state_test (state, name) -- local function to do testing of state values if true == state then return true; end -- valid actively supported parameter if false == state then if empty then return nil; end -- deprecated empty parameters are treated as unknowns deprecated_parameter (name); -- parameter is deprecated but still supported return true; end return nil; end   if name:find ('#') then -- # is a cs1|2 reserved character so parameters with # not permitted return nil; end  if utilities.in_array (cite_class, whitelist.preprint_template_list ) then -- limited parameter sets allowed for these templates state = whitelist.limited_basic_arguments[name]; if true == state_test (state, name) then return true; end  state = whitelist.preprint_arguments[cite_class][name]; -- look in the parameter-list for the template identified by cite_class if true == state_test (state, name) then return true; end  -- limited enumerated parameters list enum_name = name:gsub("%d+", "#" ); -- replace digit(s) with # (last25 becomes last#) (mw.ustring because non-Western 'local' digits) state = whitelist.limited_numbered_arguments[enum_name]; if true == state_test (state, name) then return true; end  return false; -- not supported because not found or name is set to nil end -- end limited parameter-set templates  if utilities.in_array (cite_class, whitelist.unique_param_template_list) then -- experiment for template-specific parameters for templates that accept parameters from the basic argument list state = whitelist.unique_arguments[cite_class][name]; -- look in the template-specific parameter-lists for the template identified by cite_class if true == state_test (state, name) then return true; end end -- if here, fall into general validation state = whitelist.basic_arguments[name]; -- all other templates; all normal parameters allowed if true == state_test (state, name) then return true; end  -- all enumerated parameters allowed enum_name = name:gsub("%d+", "#" ); -- replace digit(s) with # (last25 becomes last#) (mw.ustring because non-Western 'local' digits) state = whitelist.numbered_arguments[enum_name]; if true == state_test (state, name) then return true; end  return false; -- not supported because not found or name is set to nilend  --[=[-------------------------< I N T E R _ W I K I _ C H E C K >---------------------------------------------- check <value> for inter-language interwiki-link markup. <prefix> must be a MediaWiki-recognized languagecode. when these values have the form (without leading colon): [[<prefix>:link|label]] return label as plain-text [[<prefix>:link]] return <prefix>:link as plain-text return value as is else ]=] local function inter_wiki_check (parameter, value) local prefix = value:match ('%[%[(%a+):'); -- get an interwiki prefix if one exists local _; if prefix and cfg.inter_wiki_map[prefix:lower()] then -- if prefix is in the map, needs preceding colon so table.insert( z.message_tail, {utilities.set_message ('err_bad_paramlink', parameter)}); -- emit an error message _, value, _ = utilities.is_wikilink (value); -- extract label portion from wikilink end return value;end  --[[--------------------------< M I S S I N G _ P I P E _ C H E C K >------------------------------------------ Look at the contents of a parameter. If the content has a string of characters and digits followed by an equalsign, compare the alphanumeric string to the list of cs1|2 parameters. If found, then the string is possibly aparameter that is missing its pipe. There are two tests made: {{cite ... |title=Title access-date=2016-03-17}} -- the first parameter has a value and whitespace separates that value from the missing pipe parameter name {{cite ... |title=access-date=2016-03-17}} -- the first parameter has no value (whitespace after the first = is trimmed by MediaWiki)cs1|2 shares some parameter names with XML/HTML attributes: class=, title=, etc. To prevent false positives XML/HTMLtags are removed before the search. If a missing pipe is detected, this function adds the missing pipe maintenance category. ]] local function missing_pipe_check (parameter, value) local capture; value = value:gsub ('%b<>', ''); -- remove XML/HTML tags because attributes: class=, title=, etc.  capture = value:match ('%s+(%a[%w%-]+)%s*=') or value:match ('^(%a[%w%-]+)%s*='); -- find and categorize parameters with possible missing pipes if capture and validate (capture) then -- if the capture is a valid parameter name table.insert( z.message_tail, {utilities.set_message ('err_missing_pipe', parameter)}); endend  --[[--------------------------< H A S _ E X T R A N E O U S _ P U N C T >-------------------------------------- look for extraneous terminal punctuation in most parameter values; parameters listed in skip table are not checked ]] local function has_extraneous_punc (param, value) if 'number' == type (param) then return; end param = param:gsub ('%d+', '#'); -- enumerated name-list mask params allow terminal punct; normalize if cfg.punct_skip[param] then return; -- parameter name found in the skip table so done end if value:match ('[,;:]$') then utilities.set_message ('maint_extra_punct'); -- has extraneous punctuation; add maint cat endend  --[[--------------------------< C I T A T I O N >-------------------------------------------------------------- This is used by templates such as {{cite book}} to create the actual citation text. ]] local function citation(frame) Frame = frame; -- save a copy in case we need to display an error message in preview mode local pframe = frame:getParent() local styles; if nil ~= string.find (frame:getTitle(), 'sandbox', 1, true) then -- did the {{#invoke:}} use sandbox version? cfg = mw.loadData ('Module:Citation/CS1/Configuration/sandbox'); -- load sandbox versions of support modules whitelist = mw.loadData ('Module:Citation/CS1/Whitelist/sandbox'); utilities = require ('Module:Citation/CS1/Utilities/sandbox'); validation = require ('Module:Citation/CS1/Date_validation/sandbox'); identifiers = require ('Module:Citation/CS1/Identifiers/sandbox'); metadata = require ('Module:Citation/CS1/COinS/sandbox'); styles = 'Module:Citation/CS1/sandbox/styles.css'; else -- otherwise cfg = mw.loadData ('Module:Citation/CS1/Configuration'); -- load live versions of support modules whitelist = mw.loadData ('Module:Citation/CS1/Whitelist'); utilities = require ('Module:Citation/CS1/Utilities'); validation = require ('Module:Citation/CS1/Date_validation'); identifiers = require ('Module:Citation/CS1/Identifiers'); metadata = require ('Module:Citation/CS1/COinS'); styles = 'Module:Citation/CS1/styles.css'; end  utilities.set_selected_modules (cfg); -- so that functions in Utilities can see the selected cfg tables identifiers.set_selected_modules (cfg, utilities); -- so that functions in Identifiers can see the selected cfg tables and selected Utilities module validation.set_selected_modules (cfg, utilities); -- so that functions in Date validataion can see selected cfg tables and the selected Utilities module metadata.set_selected_modules (cfg, utilities); -- so that functions in COinS can see the selected cfg tables and selected Utilities module  z = utilities.z; -- table of error and category tables in Module:Citation/CS1/Utilities  local args = {}; -- table where we store all of the template's arguments local suggestions = {}; -- table where we store suggestions if we need to loadData them local error_text, error_state;  local config = {}; -- table to store parameters from the module {{#invoke:}} for k, v in pairs( frame.args ) do -- get parameters from the {{#invoke}} frame config[k] = v; -- args[k] = v; -- crude debug tool support that allows us to render a citation from module {{#invoke:}}; skips parameter validation; TODO: keep?
end
local capture; -- the single supported capture when matching unknown parameters using patterns
local empty_unknowns = {}; -- sequence table to hold empty unknown params for error message listing for k, v in pairs( pframe.args ) do -- get parameters from the parent (template) frame v = mw.ustring.gsub (v, '^%s*(.-)%s*$', '%1'); -- trim leading/trailing whitespace; when v is only whitespace, becomes empty string
if v ~= '' then
if ('string' == type (k)) then
k = mw.ustring.gsub (k, '%d', cfg.date_names.local_digits); -- for enumerated parameters, translate 'local' digits to Western 0-9
end
if not validate( k, config.CitationClass ) then
error_text = "";
if type( k ) ~= 'string' then
-- Exclude exclude empty numbered parameters
if v:match("%S+") ~= nil then
error_text, error_state = set_errorutilities.set_message ( 'text_ignorederr_text_ignored', {v}, true );
end
elseif validate( k:lower(), config.CitationClass ) then
error_text, error_state = set_errorutilities.set_message ( 'parameter_ignored_suggesterr_parameter_ignored_suggest', {k, k:lower()}, true );-- suggest the lowercase version of the parameter
else
if nil == suggestions.suggestions then -- if this table is nil then we need to load it
end
for pattern, param in pairs (suggestions.patterns) do -- loop through the patterns to see if we can suggest a proper parameter
capture = k:match (pattern); -- the whole match if no caputre capture in pattern else the capture if a match
if capture then -- if the pattern matches
param = utilities.substitute( param, capture ); -- add the capture to the suggested parameter (typically the enumerator) if validate (param, config.CitationClass) then -- validate the suggestion to make sure that the suggestion is supported by this template (necessary for limited parameter lists) error_text, error_state = set_errorutilities.set_message ( 'parameter_ignored_suggesterr_parameter_ignored_suggest', {k, param}, true ); -- set the suggestion error message else error_text, error_state = utilities.set_message ( 'err_parameter_ignored', {param}, true ); -- suggested param not supported by this template v = ''; -- unset end
end
end
if not utilities.is_set (error_text) then -- couldn't match with a pattern, is there an expicit suggestion?
if suggestions.suggestions[ k:lower() ] ~= nil then
error_text, error_state = set_errorutilities.set_message ( 'parameter_ignored_suggesterr_parameter_ignored_suggest', {k, suggestions.suggestions[ k:lower() ]}, true );
else
error_text, error_state = set_errorutilities.set_message ( 'parameter_ignorederr_parameter_ignored', {k}, true );
v = ''; -- unset value assigned to unrecognized parameters (this for the limited parameter lists)
end
end
end
  missing_pipe_check args[k] = v; -- save this parameter and its value  elseif not utilities.is_set (v); then -- for empty parameters if not validate (k, config.CitationClass, true) then -- do we think that there is this empty parameter a valid parameter that k = ('' == k) and '(empty string)' or k; -- when k is missing a pipe?empty string (or was space(s) trimmed to empty string), replace with descriptive text table.insert (empty_unknowns, utilities.wrap_style ('parameter', k)); -- format for error message and add to the list end args[k] = v; -- crude debug support that allows us to render a citation from module {{#invoke:}} TODO: keep? -- elseif args[k] ~= nil or (k == 'postscript') then -- when args[k] has a value from {{#invoke}} frame (we don't normally do that) -- args[k] = v; -- overwrite args[k] with empty string from pframe.args[k] (template frame); v is empty string here end -- not sure about the postscript bit; that gets handled in parameter validation; historical artifact?
end
 
if 0 ~= #empty_unknowns then -- create empty unknown error message
table.insert (z.message_tail, {utilities.set_message ('err_param_unknown_empty', {
1 == #empty_unknowns and '' or 's',
utilities.make_sep_list (#empty_unknowns, empty_unknowns)
}, true )});
end
for k, v in pairs( args ) do
if 'string' == type (k) then -- don't evaluate positional parameters
has_invisible_chars (k, v); -- look for invisible characters
end
has_extraneous_punc (k, v); -- look for extraneous terminal punctuation in parameter values
missing_pipe_check (k, v); -- do we think that there is a parameter that is missing a pipe?
args[k] = inter_wiki_check (k, v); -- when language interwiki-linked parameter missing leading colon replace with wiki-link label
end
  return table.concat ({citation0( config, args), frame:extensionTag ('templatestyles', '', {src=styles})});
end
--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------]] return cs1{citation = citation};