Module:Citation/CS1: Difference between revisions

don't evaluate positional parameters for invisible chars;
m (1 revision imported)
(don't evaluate positional parameters for invisible chars;)
Line 1:
 
local cs1 ={};
 
--[[--------------------------< F O R W A R D D E C L A R A T I O N S >--------------------------------------
Line 9 ⟶ 7:
 
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;,
strip_apostrophe_markup;
 
local z ={}; -- tables in Module:Citation/CS1/Utilities
Line 23 ⟶ 22:
--[[--------------------------< P A G E S C O P E V A R I A B L E S >--------------------------------------
 
delaredeclare variables here that have page-wide scope that are not brought in from other modules; thatarethat are created here and used here
and used here
 
]]
Line 71 ⟶ 69:
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?[%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
Line 133 ⟶ 131:
the first character of the whole domain name including subdomains must be a letter or a digit
internationalized domain name (ascii characters with .xn-- ASCII Compatible Encoding (ACE) prefix xn-- in the tld) see https://tools.ietf.org/html/rfc3490
single-letter/digit second-level domains in the .org, .cash, and .cashtoday TLDs
q, x, and z SL domains in the .com TLD
i and q SL domains in the .net TLD
Line 152 ⟶ 150:
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
Line 159 ⟶ 157:
return false;
end
 
-- Do most common case first
local patterns = { -- patterns that look like urls
if domain:match ('%f[%a%d][%a%d][%a%d%-]+[%a%d]%.%a%a+$') then -- three or more character hostname.hostname or hostname.tld
'%f[%w][%w][%w%-]+[%w]%.%a%a+$', -- three or more character hostname.hostname or hostname.tld
return true;
elseif domain:match ( '%f[%a%dw][%a%dw][%a%dw%-]+[%a%dw]%.xn%-%-[%a%dw]+$') then, -- internationalized domain name with ACE prefix
'%f[%a][qxz]%.com$', -- assigned one character .com hostname (x.com times out 2015-12-10)
return true;
elseif domain:match ( '%f[%a%d][%a%diq]%.cashnet$') then, -- assigned one character/digit .cashnet hostname (q.net registered but not active 2015-12-10)
'%f[%w][%w]%.%a%a$', -- one character hostname and cctld (2 chars)
return true;
elseif domain:match ( '%f[%aw][%dw][%a%dw]%.org%a%a+$') then, -- onetwo character/digit .org hostname and tld
'^%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?', -- IPv4 address
return true;
}
elseif domain:match ('%f[%a][qxz]%.com$') then -- assigned one character .com hostname (x.com times out 2015-12-10)
 
return true;
for _, pattern in ipairs (patterns) do -- loop through the patterns list
elseif domain:match ('%f[%a][iq]%.net$') then -- assigned one character .net hostname (q.net registered but not active 2015-12-10)
if domain:match (pattern) then
return true;
return true; -- if a match then we think that this thing that purports to be a url is a url
elseif domain:match ('%f[%a%d][%a%d]%.%a%a$') then -- one character hostname and cctld (2 chars)
end
return true;
end
elseif domain:match ('%f[%a%d][%a%d][%a%d]%.%a%a+$') then -- two character hostname and tld
 
return true;
for _, d in ipairs ({'cash', 'company', 'today', 'org'}) do -- look for single letter second level domain names for these top level domains
elseif domain:match ('^%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?') then -- IPv4 address
if domain:match ('%f[%w][%w]%.' .. d) then
return true;
return true
else
end
return false;
end
return false; -- no matches, we don't know what this thing is
end
 
Line 445 ⟶ 444:
if not added_deprecated_cat then
added_deprecated_cat = true; -- note that we've added this category
table.insert( z.message_tail, { set_error( 'deprecated_params', {name}, true ) } ); -- add error message
end
end
Line 527 ⟶ 526:
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 is_set (lang) then
table.insert( z.message_tail, { set_error( '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 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 in_array (lang, cfg.script_lang_codes) then
add_prop_cat ('script_with_name', {name, lang})
else
table.insert( z.message_tail, { set_error( 'script_parameter', {script_param, 'unknown language code'}, true ) } ); -- unknown script-language; add error message
add_prop_cat ('script')
end
lang = ' lang="' .. lang .. '" '; -- convert prefix into a lang attribute
else
table.insert( z.message_tail, { set_error( '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, { set_error( 'script_parameter', {script_param, 'missing prefix'}, true ) } ); -- no language code prefix; add error message
end
script_value = substitute (cfg.presentation['bdi'], {lang, script_value}); -- isolate in case script is rtl
Line 567 ⟶ 569:
]]
 
local function script_concatenate (title, script, script_param)
if is_set (script) then
script = format_script_value (script, script_param); -- <bdi> tags, lang atribute, categorization, etc; returns empty string on error
if is_set (script) then
title = title .. ' ' .. script; -- concatenate title and script title
Line 593 ⟶ 595:
local msg;
msg = cfg.messages[key]:lower(); -- set the message to lower case before
return substitute( msg, str ); -- including template text
else
return substitute( cfg.messages[key], str );
Line 611 ⟶ 613:
local wl_type, D, L;
local ws_url, ws_label;
local wikisource_prefix = table.concat ({'https://', cfg.this_wiki_code, '.wikisource.org/wiki/'});
 
wl_type, D, L = is_wikilink (str); -- wl_type is 0 (not a wikilink), 1 (simple wikilink), 2 (complex wikilink)
Line 618 ⟶ 621:
if is_set (str) then
ws_url = table.concat ({ -- build a wikisource url
'https://en.wikisource.org/wiki/'wikisource_prefix, -- prefix
str, -- 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]:(.+)'); -- article title from interwiki link with long-form or short-form namespace
if is_set (str) then
ws_url = table.concat ({ -- build a wikisource url
'https://en.wikisource.org/wiki/'wikisource_prefix, -- prefix
str, -- article title
});
Line 637 ⟶ 640:
ws_label = D; -- get ws article name from display portion of interwiki link
ws_url = table.concat ({ -- build a wikisource url
'https://en.wikisource.org/wiki/'wikisource_prefix, -- prefix
str, -- article title without namespace from link portion of wikilink
});
Line 645 ⟶ 648:
if ws_url then
ws_url = mw.uri.encode (ws_url, 'WIKI'); -- make a usable url
ws_url = ws_url:gsub ('%%23', '#'); -- undo percent encoding of anchorfragment marker
end
 
return ws_url, ws_label, L or D; -- return proper url or nil and a label or nil
end
 
 
--[[--------------------------< F O R M A T _ P E R I O D I C A L >--------------------------------------------
 
Format the three periodical parameters: |script-<periodical>=, |<periodical>=, and |trans-<periodical>= into a single Periodical meta-
parameter.
 
]]
 
local function format_periodical (script_periodical, script_periodical_source, periodical, trans_periodical)
local periodical_error = '';
 
if not is_set (periodical) then
periodical = ''; -- to be safe for concatenation
else
periodical = wrap_style ('italic-title', periodical); -- style
end
 
periodical = script_concatenate (periodical, script_periodical, script_periodical_source); -- <bdi> tags, lang atribute, categorization, etc; must be done after title is wrapped
 
if is_set (trans_periodical) then
trans_periodical = wrap_style ('trans-italic-title', trans_periodical);
if is_set (periodical) then
periodical = periodical .. ' ' .. trans_periodical;
else -- here when trans-periodical without periodical or script-periodical
periodical = trans_periodical;
periodical_error = ' ' .. set_error ('trans_missing_title', {'periodical'});
end
end
 
return periodical .. periodical_error;
end
 
Line 659 ⟶ 694:
]]
 
local function format_chapter_title (scriptchapterscript_chapter, script_chapter_source, chapter, transchapterchapter_source, trans_chapter, trans_chapter_source, chapterurlchapter_url, chapter_url_source, no_quotes, access)
local 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
Line 677 ⟶ 712:
end
 
chapter = script_concatenate (chapter, scriptchapterscript_chapter, script_chapter_source) ; -- <bdi> tags, lang atribute, categorization, etc; must be done after title is wrapped
 
if is_set (chapterurlchapter_url) then
chapter = external_link (chapterurlchapter_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 = substitute (cfg.presentation['interwiki-icon'], {cfg.presentation['class-wikisource'], L, chapter});
end
 
if is_set (transchaptertrans_chapter) then
transchaptertrans_chapter = wrap_style ('trans-quoted-title', transchaptertrans_chapter);
if is_set (chapter) then
chapter = chapter .. ' ' .. transchaptertrans_chapter;
else -- here when transchaptertrans_chapter without chapter or script-chapter
chapter = transchaptertrans_chapter; --
chapter_source = trans_chapter_source:match ('trans%-?(.+)'); -- when no chapter, get matching name from trans-<param>
chapter_error = ' ' .. set_error ('trans_missing_title', {'chapter'});
chapter_error = ' ' .. set_error ('trans_missing_title', {chapter_source});
end
end
 
-- if is_set (chapterurl) then
-- chapter = external_link (chapterurl, chapter, chapter_url_source, access); -- adds bare_url_missing_title error if appropriate
-- end
 
return chapter .. chapter_error;
Line 713 ⟶ 745:
Detects but ignores nowiki and math stripmarkers. Also detects other named stripmarkers (gallery, math, pre, ref)
and identifies them with a slightly different error message. See also coins_cleanup().
 
Detects but ignores the character pattern that results from the transclusion of {{'}} templates.
 
Output of this function is an error message that identifies the character or the Unicode group, or the stripmarker
Line 802 ⟶ 832:
-- maybe let through instead of raising an error?
-- v, origin[k] = args[k], k;
error( cfg.messages['unknown_argument_map'] .. ': ' .. k);
end
-- Empty strings, not nil;
if v == nil then
-- v = cfg.defaults[k] or '';
v = '';
origin[k] = '';
end
Line 851 ⟶ 882:
 
local function set_titletype (cite_class, title_type)
if is_set (title_type) then
if "'none"' == cfg.keywords_xlate[title_type] then
title_type = ""''; -- if |type=none then type parameter not displayed
end
return title_type; -- if |type= has been set to any other value use that value
Line 889 ⟶ 920:
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 = {};
Line 895 ⟶ 928:
for _, item in ipairs (list) do -- for each item in the list
if mw.ustring.match (item, '^%w*[%.%-]?%w+%s*[%-–—]%s*%w*[%.%-]?%w+$') then -- if a hyphenated range or has endash or emdash separators
if item:match ('^%a+[%.%-]?%d+%s*%-%s*%a+[%.%-]?%d+$') or -- letterdigit hyphen letterdigit (optional separator between letter and digit)
item:match ('^%d+[%.%-]?%a+%s*%-%s*%d+[%.%-]?%a+$') or -- digitletter hyphen digitletter (optional separator between digit and letter)
item:match ('^%d+[%.%-]%d+%s*%-%s*%d+[%.%-]%d+$') or -- 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, remove extraneous space characters
else
Line 1,040 ⟶ 1,073:
]]
 
local function is_good_vanc_name (last, first, suffix)
if not suffix then
local first, suffix = first:match ('(.-),?%s*([%dJS][%drndth]+)%.?$') or first; -- if first has something that looks like a generational suffix, get it
if first:find ('[,%s]') then -- when there is a space or comma, might be first name/initials + generational suffix
 
first = first:match ('(.-)[,%s]+'); -- get name/initials
suffix = first:match ('[,%s]+(.+)$'); -- get generational suffix
end
end
if is_set (suffix) then
if not is_suffix (suffix) then
add_vanc_error ('cfg.err_msg_supl.suffix');
return false; -- not a name with an appropriate suffix
end
Line 1,051 ⟶ 1,088:
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 characterchar']);
return false; -- not a string of latin characters; Vancouver requires Romanization
end;
Line 1,084 ⟶ 1,121:
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
Line 1,092 ⟶ 1,129:
end
end -- if here then name has 3 or more uppercase letters so treat them as a word
 
 
local initials, names = {}, {}; -- tables to hold name parts and initials
Line 1,134 ⟶ 1,170:
sep = cfg.presentation['sep_nl_vanc']; -- name-list separator between authors is a comma
namesep = cfg.presentation['sep_name_vanc']; -- last/first separator is a space
lastauthoramp = nil; -- unset because isn't used by Vancouver style
else
sep = cfg.presentation['sep_nl']; -- name-list separator between authors is a semicolon
Line 1,170 ⟶ 1,207:
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
if is_set (person.link) then
table.insert( text, one )
one = make_wikilink (person.link, one); -- link author/editor
table.insert( text, sep_one )
end
table.insert (text, one)
table.insert (text, sep_one)
end
end
Line 1,203 ⟶ 1,240:
 
]]
 
local function anchor_id (namelist, year)
local names={}; -- a table for the one to four names and year
Line 1,221 ⟶ 1,259:
--[[--------------------------< N A M E _ H A S _ E T A L >----------------------------------------------------
 
Evaluates the content of authorname andparameters (author, editor, name parametersetc) for variations on the theme of et al. If found,
the et al. is removed, a flag is set to true and the function returns the modified name and the flag.
 
This function never sets the flag to false but returns it's previous state because it may have been set by
previous passes through this function or by the parametersassociated |display-authors<names>=etal or |display-editors=etalparameter
 
]]
 
local function name_has_etal (name, etal, nocat, param)
 
if is_set (name) then -- name can be nil in which case just return
local patterns = cfg.et_al_patterns; --get patterns from configuration
local etal_pattern = "[;,]? *[\"']*%f[%a][Ee][Tt] *[Aa][Ll][%.\"']*$" -- variations on the 'et al' theme
local others_pattern = "[;,]? *%f[%a]and [Oo]thers"; -- and alternate to et al.
iffor name:match_, pattern in ipairs (etal_patternpatterns) then do -- variantsloop through all onof etthe al.patterns
name =if name:gsubmatch (etal_pattern, ''pattern); then -- if this 'et al' pattern is found, removein name
name = name:gsub (pattern, ''); -- remove the offending text
etal = true; -- set flag (may have been set previously here or by |display-authors=etal)
if etal not nocat= thentrue; -- noset categorizationflag for(may have been set previously here or by |vauthorsdisplay-<names>=etal)
add_maint_catif ('etal');not nocat then -- andno add a category if notcategorization alreadyfor added|vauthors=
table.insert( z.message_tail, {set_error ('etal', {param})}); -- and set an error if not added
end
end
elseif name:match (others_pattern) then -- if not 'et al.', then 'and others'?
name = name:gsub (others_pattern, ''); -- if found, remove
etal = true; -- set flag (may have been set previously here or by |display-authors=etal)
if not nocat then -- no categorization for |vauthors=
add_maint_cat ('etal'); -- and add a category if not already added
end
end
end
 
return name, etal; --
end
 
 
--[[--------------------------< N A M E _ I S _ N U M E R I C >------------------------------------------------
 
Add maint cat when name parameter value does not contain letters. Does not catch mixed alphanumeric names so
|last=A. Green (1922-1987) does not get caught in the current version of this test but |first=(1888) is caught.
 
returns nothing
 
]]
 
local function name_is_numeric (name, list_name)
if is_set (name) then
if mw.ustring.match (name, '^[%A]+$') then -- when name does not contain any letters
add_maint_cat ('numeric_names', cfg.special_case_translation [list_name]); -- add a maint cat for this template
end
end
end
 
Line 1,258 ⟶ 1,310:
These annotation do not belong in author parameters and are redundant in editor parameters. If found, the function
adds the editor markup maintenance category.
 
returns nothing
 
]]
 
local function name_has_ed_markup (name, list_name)
local patterns = cfg.editor_markup_patterns; -- get patterns from configuration
local _, pattern;
local patterns = { -- these patterns match annotations at end of name
'%f[%(%[][%(%[]%s*[Ee][Dd][Ss]?%.?%s*[%)%]]?$', -- (ed) or (eds): leading '(', case insensitive 'ed', optional 's', '.' and/or ')'
'[,%.%s]%f[e]eds?%.?$', -- ed or eds: without '('or ')'; case sensitive (ED could be initials Ed could be name)
'%f[%(%[][%(%[]%s*[Ee][Dd][Ii][Tt][Oo][Rr][Ss]?%.?%s*[%)%]]?$', -- (editor) or (editors): leading '(', case insensitive, optional '.' and/or ')'
'[,%.%s]%f[Ee][Ee][Dd][Ii][Tt][Oo][Rr][Ss]?%.?$', -- editor or editors: without '('or ')'; case insensitive
-- these patterns match annotations at beginning of name
'^eds?[%.,;]', -- ed. or eds.: lower case only, optional 's', requires '.'
'^[%(%[]%s*[Ee][Dd][Ss]?%.?%s*[%)%]]', -- (ed) or (eds): also sqare brackets, case insensitive, optional 's', '.'
'^[%(%[]?%s*[Ee][Dd][Ii][Tt][Oo][Rr][Ss]?%A', -- (editor or (editors: also sq brackets, case insensitive, optional brackets, 's'
'^[%(%[]?%s*[Ee][Dd][Ii][Tt][Ee][Dd]%A', -- (edited: also sq brackets, case insensitive, optional brackets
}
 
if is_set (name) then
Line 1,284 ⟶ 1,326:
end
end
return name; -- and done
end
 
Line 1,293 ⟶ 1,334:
indicated if there is more than one comma and or semicolon. If found, the function adds the multiple name
(author or editor) maintenance category.
 
returns nothing
 
]]
 
local function name_has_mult_names (name, list_name)
local count_, _count;
if is_set (name) then
_, count = name:gsub ('[;,]', ''); -- count the number of separator-like characters
Line 1,305 ⟶ 1,348:
end
end
return name; -- and done
end
 
 
--[[--------------------------< N A M E _ C H E C K S >--------------------------------------------------------
 
This function calls various name checking functions used to validate the content of the various name-holding
parameters.
Line 1,320 ⟶ 1,363:
last = last:match ('^%(%((.*)%)%)$'); -- strip parens
else
last = name_has_mult_names (last, list_name); -- check for multiple names in the parameter (last only)
last = name_has_ed_markup (last, list_name); -- check for extraneous 'editor' annotation
name_is_numeric (last, list_name); -- check for names that are compsed of digits and punctuation
end
end
Line 1,328 ⟶ 1,372:
first = first:match ('^%(%((.*)%)%)$'); -- strip parens
else
first = name_has_ed_markup (first, list_name); -- check for extraneous 'editor' annotation
name_is_numeric (first, list_name); -- check for names that are compsed of digits and punctuation
end
end
Line 1,362 ⟶ 1,407:
local etal=false; -- return value set to true when we find some form of et al. in an author parameter
 
local last_alias, first_alias, link_alias; -- selected parameter aliases used in error messaging
local err_msg_list_name = list_name:match ("(%w+)List") .. 's list'; -- modify AuthorList or EditorList for use in error messages if necessary
while true do
last, last_alias = select_one( args, cfg.aliases[list_name .. '-Last'], 'redundant_parameters', i ); -- search through args for name components beginning at 1
first, first_alias = select_one( args, cfg.aliases[list_name .. '-First'], 'redundant_parameters', i );
link, link_alias = select_one( args, cfg.aliases[list_name .. '-Link'], 'redundant_parameters', i );
mask = select_one( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i );
 
last, etal = name_has_etal (last, etal, false, 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
table.insert( z.message_tail, { set_error( 'first_missing_last', {err_msg_list_namefirst_alias, ifirst_alias:gsub('first', 'last')}, 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
Line 1,381 ⟶ 1,426:
end
else -- we have last with or without a first
link_title_ok (link, list_name:match ("(%w+)List"):lower() .. '-link' .. ilink_alias, last, list_name:match ("(%w+)List"):lower() .. '-last' .. ilast_alias); -- check for improper wikimarkup
if first then
link_title_ok (link, link_alias, first, first_alias); -- check for improper wikimarkup
end
 
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_namelist_name:match ("(%w+)List"):lower(), i-1}, true ) } ); -- add this error message
end
count = 0; -- reset the counter, we're looking for two consecutive missing names
Line 1,460 ⟶ 1,508:
 
Languages that are the same as the local wiki are not categorized. MediaWiki does not recognize three-character
equivalents of two-character codes: code 'ar' is recognized bitbut code 'ara' is not.
 
This function supports multiple languages in the form |language=nb, French, th where the language names or codes are
separated from each other by commas with optional space characters.
 
]]
Line 1,473 ⟶ 1,521:
local names_table = {}; -- table made from the value assigned to |language=
 
local this_wikithis_wiki_name = mw.getContentLanguagelanguage.fetchLanguageName(cfg.this_wiki_code, cfg.this_wiki_code); -- get athis wiki's language object for this wikiname
local this_wiki_code = this_wiki:getCode() -- 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
 
names_table = mw.text.split (lang, '%s*,%s*'); -- names should be a comma separated list
Line 1,483 ⟶ 1,529:
 
if name then -- there was a remapped code so
langif =not lang:gsubmatch ('^(%a%a%a?)%-.*', 'x%-%1a+$'); then -- stripif ietfnot tagsa fromprivate ietf codetag
lang = lang:gsub ('^(%a%a%a?)%-.*', '%1'); -- strip ietf tags from code
else
if lang:match ('^%a%a%-') then -- strip ietf tags from code; TODO: is there a need to support 3-char with tag?
lang = lang:match ('(%a%a)%-') -- keep only 639-1 code portion to lang; TODO: do something with 3166 alpha 2 country code?
end
else
lang = lang:gsub ('^(%a%a%a?)%-.*', '%1'); -- strip any ietf-like tags from code
if 2 == lang:len() or 3 == lang:len() then -- if two-or three-character code
name = mw.language.fetchLanguageName (lang:lower(), cfg.this_wiki_code); -- get language name if |language= is a proper code
end
end
Line 1,496 ⟶ 1,542:
code = lang:lower(); -- save it
else
name, code = get_iso639_code (lang, cfg.this_wiki_code); -- attempt to get code from name (assign name here so that we are sure of proper capitalization)
end
Line 1,502 ⟶ 1,548:
name = cfg.lang_code_remap[code] or name; -- 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
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)
add_prop_cat ('foreign_lang_source_2' .. code, {code}); -- categorize it differently TODO: support mutliplemultiple 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
add_prop_cat ('local_lang_source', {name, code}); -- categorize it
end
else
Line 1,521 ⟶ 1,569:
name = table.concat (language_list, cfg.messages['parameter-pair-separator']) -- insert '<space>and<space>' between two language names
elseif 2 < code then
name = table.concat (language_list, cfg.messages['parameter-separator'], '1, code-1); -- and concatenate withall '<comma><space>'but separatorslast
name = name:gsubtable.concat ('{name, (language_list[^,code]+)$'}, cfg.messages['parameter-final-separator'] .. '%1'); -- replaceconcatenate last '<comma><space>' separator with '<comma><space>and<space>'final separator
end
if this_wiki_name == name then
Line 1,544 ⟶ 1,592:
 
local function set_cs1_style (ps)
if not is_set (ps) then -- unless explicitelyexplicitly set to something
ps = cfg.presentation['ps_cs1']; -- terminate the rendered citation
end
Line 1,606 ⟶ 1,654:
sep, ps, ref = get_settings_from_cite_class (ps, ref, cite_class); -- get settings based on the template's CitationClass
end
 
if 'none' == ps:lower() then -- if assigned value is 'none' then
if cfg.keywords_xlate[ps:lower()] == 'none' then -- if assigned value is 'none' then
ps = ''; -- set to empty string
end
Line 1,624 ⟶ 1,673:
 
local function is_pdf (url)
return url:match ('%.pdf$') or url:match ('%.PDF$') or url:match ('%.pdf[%?#]') or url:match ('%.PDF[%?#]');
url:match ('%.pdf[%?#]') or url:match ('%.PDF[%?#]') or
url:match ('%.PDF&#035') or url:match ('%.pdf&#035');
end
 
Line 1,652 ⟶ 1,703:
 
 
--[[--------------------------< G E T _ D I S P L A Y _ N A U T H O R S _M E D I T O R S >--------------------------------------------
 
Returns a number that defines the number of names displayed for author and editor name lists and a boolean flag
Line 1,675 ⟶ 1,726:
]]
 
local function get_display_authors_editorsget_display_names (max, count, list_name, etal)
if is_set (max) then
if 'etal' == max:lower():gsub("[ '%.]", '') then -- the :gsub() portion makes 'etal' from a variety of 'et al.' spellings and stylings
Line 1,683 ⟶ 1,734:
max = tonumber (max); -- make it a number
if max >= count then -- if |display-xxxxors= value greater than or equal to number of authors/editors
add_maint_cat ('disp_auth_eddisp_name', cfg.special_case_translation [list_name]);
end
else -- not a valid keyword or number
Line 1,784 ⟶ 1,835:
 
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 ipairs(v_name_table) do
first = ''; -- set to empty string for concatenation and because it may have been set for previous author/editor
if v_name:match ('^%(%(.+%)%)$') 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_name:match ('^%(%((.+)%)%)$') -- remove doubled parntheses
corporate = true; -- flag used in list_people()
elseif string.find(v_name, "%s") then
if v_name:find('[;%.]') then -- look for commonly occurring punctuation characters;
add_vanc_error ('cfg.err_msg_supl.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 or generational suffix
 
if is_suffix (first) then -- if a valid suffix
if not mw.ustring.match (first, '^%u+$') then -- mw.ustring here so that later we will catch non-latin characters
suffix = first -- save it as a suffix and
suffix = first; -- not initials so assume that whatever we got is a generational suffix
first = table.remove(lastfirstTable); -- get what should be the initials from the table
end
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 and generational suffix
if not is_set (last) then
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+') then
add_vanc_error (cfg.err_msg_supl['missing comma']); -- matches last II last; the case when a comma is missing
end
if mw.ustring.match (v_name, ' %u %u$') then -- this test is in the wrong place TODO: move or replace with a more appropriate test
add_vanc_error ('cfg.err_msg_supl.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
Line 1,816 ⟶ 1,872:
if is_set (first) then
if not mw.ustring.match (first, "^%u?%u$") then -- first shall contain one or two upper-case letters, nothing else
add_vanc_error ('cfg.err_msg_supl.initials'); -- too many initials; mixed case initials (which may be ok Romanization); hyphenated initials
end
is_good_vanc_name (last, first, suffix); -- 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
Line 1,856 ⟶ 1,912:
 
local function select_author_editor_source (vxxxxors, xxxxors, args, list_name)
local lastfirst = false;
if select_one( args, cfg.aliases[list_name .. '-Last'], 'none', 1 ) or -- do this twice incase we have a |first1= without a |last1=; this ...
select_one( args, cfg.aliases[list_name .. '-First'], 'none', 1 ) or -- ... also catches the case where |first= is used with |vauthors=
Line 1,887 ⟶ 1,943:
 
This function is used to validate a parameter's assigned value for those parameters that have only a limited number
of allowable values (yes, y, true, nolive, dead, etc). When the parameter value has not been assigned a value (missing or empty
or empty in the source template) the function returns truethe value specified by ret_val. If the parameter value is one of the list of allowed values returns
trueof the list of allowed values returns the translated value; else, emits an error message and returns false.the value
specified by ret_val.
 
]]
 
local function is_valid_parameter_value (value, name, possible, ret_val)
if not is_set (value) then
return trueret_val; -- an empty parameter is ok
elseif in_array (value:lower(), possible) then
return cfg.keywords_xlate[value]; -- return translation of parameter keyword
return true;
else
table.insert( z.message_tail, { set_error( 'invalid_param_val', {name, value}, true ) } ); -- not an allowed value so add error message
return falseret_val;
end
end
Line 1,945 ⟶ 2,002:
return wrap_msg ('issue', {sepc, issue}, lower);
end
end
 
if 'podcast' == cite_class and is_set (issue) then
return wrap_msg ('issue', {sepc, issue}, lower);
end
 
Line 2,006 ⟶ 2,067:
if is_journal then
return substitute (cfg.messages['j-page(s)'], pages), '', '', '';
elseif tonumber(pages) ~= nil and not nopp then -- if pages is only digits, assume a single page number
return '', substitute (cfg.messages['p-prefix'], {sepc, pages}), '', '';
elseif not nopp then
Line 2,073 ⟶ 2,134:
return page, pages, at, coins_pages;
end
 
 
 
Line 2,118 ⟶ 2,178:
 
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 command';
url = url:gsub ('(//web%.archive%.org)/save/', '%1/*/', 1); -- for preview mode: modify ArchiveURL
elseif url:match('//liveweb%.archive%.org/') then
err_msg = 'cfg.err_msg_supl.liveweb';
else
path, timestamp, flag = url:match('//web%.archive%.org/([^%d]*)(%d+)([^/]*)/'); -- split out some of the url parts for evaluation
if not is_set(timestamp) or 14 ~= timestamp:len() then -- path and flag optional, must have 14-digit timestamp here
err_msg = 'cfg.err_msg_supl.timestamp';
if '*' ~= flag then
url=url:gsub ('(//web%.archive%.org/[^%d]*%d?%d?%d?%d?%d?%d?)[^/]*', '%1*', 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 = 'cfg.err_msg_supl.path';
elseif is_set (flag) and not is_set (path) then -- flag not allowed with the old form url (without the 'web/' path element)
err_msg = 'cfg.err_msg_supl.flag';
elseif is_set (flag) and not flag:match ('%a%a_') then -- flag if present must be two alpha characters and underscore (requires 'web/' path element)
err_msg = 'cfg.err_msg_supl.flag';
else
return url, date; -- return archiveURLArchiveURL and ArchiveDate
end
end
Line 2,143 ⟶ 2,203:
table.insert( z.message_tail, { set_error( 'archive_url', {err_msg}, true ) } ); -- add error message and
if is_set (Frame:preprocess('{{REVISIONID}}')) then
return '', ''; -- return empty strings for archiveURLArchiveURL and ArchiveDate
else
return url, date; -- preview mode so return archiveURLArchiveURL and ArchiveDate
end
end
 
 
--[[--------------------------< P L A C E _ C H E C K >--------------------------------------------------------
 
check |place=, |publication-place=, |location= to see if these params include digits. This function added because
many editors mis-use location to specify the in-source location (|page(s)= and |at= are supposed to do that)
 
returns the original parameter value without modification; added maint cat when parameter value contains digits
 
]]
 
local function place_check (param_val)
if not is_set (param_val) then -- parameter empty or omitted
return param_val; -- return that empty state
end
if mw.ustring.find (param_val, '%d') then -- not empty, are there digits in the parameter value
add_maint_cat ('location'); -- yep, add maint cat
end
return param_val; -- and done
end
 
Line 2,167 ⟶ 2,249:
-- define different field names for the same underlying things.
 
local Mode = is_valid_parameter_value (A['Mode'], A:ORIGIN('Mode'), cfg.keywords_lists['mode'], '');
-- 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 = A['NameListFormat'];
local NameListFormat = is_valid_parameter_value (A['NameListFormat'], A:ORIGIN('NameListFormat'), cfg.keywords_lists['name-list-format'], '');
local Collaboration = A['Collaboration'];
 
Line 2,216 ⟶ 2,295:
end
 
local translator_etal;
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 interviewer_etal;
local interviewers_list = {};
local Interviewers; -- used later
interviewers_list = extract_names (args, 'InterviewerList'); -- process preferred interviewers parameters
 
local contributor_etal;
local c = {}; -- contributors list from |contributor-lastn= / contributor-firstn= pairs
local Contributors; -- assembled contributors name list
 
local Contribution = A['Contribution'];
local Chapter = A['Chapter']; -- done here so that we have access to |contribution= from |chapter= aliases
local Chapter_origin = A:ORIGIN ('Chapter');
local Contribution; -- because contribution is required for contributor(s)
if 'contribution' == A:ORIGIN ('Chapter') then
Contribution = A['Chapter']; -- get the name of the contribution
end
 
if in_array(config.CitationClass, {"book","citation"}) and not is_set(A['Periodical']) then -- |contributor= and |contribution= only supported in book cites
c = extract_names (args, 'ContributorList'); -- fetch contributor list from |contributorn= / |contributor-lastn=, -firstn=, -linkn=, -maskn=
Line 2,247 ⟶ 2,336:
end
 
if is_set (Others) then
if not is_valid_parameter_value (NameListFormat, 'name-list-format', cfg.keywords['name-list-format']) then -- only accepted value for this parameter is 'vanc'
if 0 == #a and 0 == #e then -- add maint cat when |others= has value and used without |author=, |editor=
NameListFormat = ''; -- anything else, set to empty string
add_maint_cat ('others');
end
end
 
Line 2,262 ⟶ 2,353:
local Conference = A['Conference'];
local TransTitle = A['TransTitle'];
local TransTitle_origin = A:ORIGIN ('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 Section = ''; -- {{cite map}} only; preset to empty string for concatnation if not used
local Chapter = A['Chapter'];
if 'map' == config.CitationClass and 'section' == A:ORIGIN ('Chapter') then
Section = A['Chapter']; -- get |section= from |chapter= alias list; |chapter= and the other aliases not supported in {{cite map}}
Chapter = ''; -- unset for now; will be reset later from |map= if present
end
 
local ScriptChapter = A['ScriptChapter'];
local ScriptChapter_origin = A:ORIGIN ('ScriptChapter');
local ChapterLink -- = A['ChapterLink']; -- 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'];
Line 2,280 ⟶ 2,379:
ArchiveURL, ArchiveDate = archive_url_check (A['ArchiveURL'], A['ArchiveDate'])
local UrlStatus = is_valid_parameter_value (A['UrlStatus'], A:ORIGIN('UrlStatus'), cfg.keywords_lists['url-status'], '');
local DeadURL = A['DeadURL']
 
if not is_valid_parameter_value (DeadURL, 'dead-url', cfg.keywords ['deadurl']) then -- set in config.defaults to 'yes'
DeadURL = ''; -- anything else, set to empty string
end
 
local URL = A['URL']
local URLoriginURL_origin = A:ORIGIN('URL'); -- get name of parameter that holds URL
local ChapterURL = A['ChapterURL'];
local ChapterURLoriginChapterURL_origin = A:ORIGIN('ChapterURL'); -- get name of parameter that holds ChapterURL
local ConferenceFormat = A['ConferenceFormat'];
local ConferenceURL = A['ConferenceURL'];
local ConferenceURLoriginConferenceURL_origin = A:ORIGIN('ConferenceURL'); -- get name of parameter that holds ConferenceURL
 
local Periodical = A['Periodical'];
local Periodical_origin = A:ORIGIN('Periodical'); -- get the name of the periodical parameter
if is_set (Periodical) then
Periodical_origin = A:ORIGIN('Periodical'); -- get the name of the periodical parameter
local i;
Periodical, i = strip_apostrophe_markup (Periodical); -- strip appostrophe markup so that metadata isn't contaminated
if i then -- non-zero when markup was stripped so emit an error message
table.insert( z.message_tail, {set_error ('apostrophe_markup', {Periodical_origin}, true)});
end
end
 
if 'mailinglist' == config.CitationClass then -- special case for {{cite mailing list}}
if is_set (Periodical) and is_set (A ['MailingList']) then -- both set emit an error
table.insert( z.message_tail, { set_error('redundant_parameters', {wrap_style ('parameter', Periodical_origin) .. ' and ' .. wrap_style ('parameter', 'mailinglist')}, true )});
end
 
Periodical = A ['MailingList']; -- error or no, set Periodical to |mailinglist= value because this template is {{cite mailing list}}
Periodical_origin = A:ORIGIN('MailingList');
end
 
 
 
local ScriptPeriodical = A['ScriptPeriodical'];
local ScriptPeriodical_origin = A:ORIGIN('ScriptPeriodical');
-- web and news not tested for now because of
-- Wikipedia:Administrators%27_noticeboard#Is_there_a_semi-automated_tool_that_could_fix_these_annoying_"Cite_Web"_errors?
if not (is_set (Periodical) or is_set (ScriptPeriodical)) then -- 'periodical' templates require periodical parameter
-- local p = {['journal'] = 'journal', ['magazine'] = 'magazine', ['news'] = 'newspaper', ['web'] = 'website'}; -- for error message
local p = {['journal'] = 'journal', ['magazine'] = 'magazine'}; -- for error message
if p[config.CitationClass] then
table.insert( z.message_tail, {set_error ('missing_periodical', {config.CitationClass, p[config.CitationClass]}, true)});
end
end
 
local TransPeriodical = A['TransPeriodical'];
local TransPeriodical_origin = A:ORIGIN ('TransPeriodical');
 
local Series = A['Series'];
Line 2,303 ⟶ 2,436:
local At;
 
if in_array'citation' == (config.CitationClass, cfg.templates_using_volume) then
if is_set (Periodical) then
if not in_array (Periodical_origin, {'website', 'mailinglist'}) then -- {{citation}} does not render volume for these 'periodicals'
Volume = A['Volume']; -- but does for all other 'periodicals'
end
elseif is_set (ScriptPeriodical) then
if 'script-website' ~= ScriptPeriodical_origin then -- {{citation}} does not render volume for |script-website=
Volume = A['Volume']; -- but does for all other 'periodicals'
end
else
Volume = A['Volume']; -- and does for non-'periodical' cites
end
elseif in_array (config.CitationClass, cfg.templates_using_volume) then -- render |volume= for cs1 according to the configuration settings
Volume = A['Volume'];
end
 
if 'citation' == config.CitationClass then
if is_set (Periodical) and in_array (Periodical_origin, {'journal', 'magazine', 'newspaper', 'periodical', 'work'}) or -- {{citation}} renders issue for these 'periodicals'
is_set (ScriptPeriodical) and 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 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 (in_array (config.CitationClass, {'conference', 'map', 'citation'}) and not (is_set (Periodical) or is_set (ScriptPeriodical))) then
Issue = hyphen_to_dash (A['Issue']);
end
end
 
-- conference & map books do not support issue
if in_array (config.CitationClass, cfg.templates_using_issue) and not (in_array (config.CitationClass, {'conference', 'map'}) and not is_set (Periodical))then
Issue = hyphen_to_dash (A['Issue']);
end
local Position = '';
if not in_array (config.CitationClass, cfg.templates_not_using_page) then
Line 2,318 ⟶ 2,471:
 
local Edition = A['Edition'];
local PublicationPlace = place_check (A['PublicationPlace'], A:ORIGIN('PublicationPlace'));
local Place = place_check (A['Place'], A:ORIGIN('Place'));
local PublisherName = A['PublisherName'];
local RegistrationRequiredPublisherName_origin = A[:ORIGIN('RegistrationRequiredPublisherName']);
if is_set (PublisherName) then
if not is_valid_parameter_value (RegistrationRequired, 'registration', cfg.keywords ['yes_true_y']) then
local i=0;
RegistrationRequired=nil;
PublisherName, i = strip_apostrophe_markup (PublisherName); -- strip appostrophe markup so that metadata isn't contaminated; publisher is never italicized
end
 
if i then -- non-zero when markup was stripped so emit an error message
local SubscriptionRequired = A['SubscriptionRequired'];
table.insert( z.message_tail, {set_error ('apostrophe_markup', {PublisherName_origin}, true)});
if not is_valid_parameter_value (SubscriptionRequired, 'subscription', cfg.keywords ['yes_true_y']) then
end
SubscriptionRequired=nil;
end
 
local UrlAccessNewsgroup = A['UrlAccessNewsgroup']; -- TODO: strip apostrophe markup?
local Newsgroup_origin = A:ORIGIN('Newsgroup');
if not is_valid_parameter_value (UrlAccess, 'url-access', cfg.keywords ['url-access']) then
 
UrlAccess = nil;
if 'newsgroup' == config.CitationClass then
if is_set (PublisherName) then -- general use parmeter |publisher= not allowed in cite newsgroup
local error_text = set_error ('parameter_ignored', {PublisherName_origin}, true);
if is_set (error_text) then
table.insert( z.message_tail, {error_text, error_state} );
end
end
 
PublisherName = nil; -- ensure that this parameter is unset for the time being; will be used again after COinS
end
 
local UrlAccess = is_valid_parameter_value (A['UrlAccess'], A:ORIGIN('UrlAccess'), cfg.keywords_lists['url-access'], nil);
if not is_set(URL) and is_set(UrlAccess) then
UrlAccess = nil;
table.insert( z.message_tail, { set_error( 'param_access_requires_param', {'url'}, true ) } );
end
 
local ChapterUrlAccess = is_valid_parameter_value (A['ChapterUrlAccess'], A:ORIGIN('ChapterUrlAccess'), cfg.keywords_lists['url-access'], nil);
if is_set (UrlAccess) and is_set (SubscriptionRequired) then -- while not aliases, these are much the same so if both are set
table.insert( z.message_tail, { set_error( 'redundant_parameters', {wrap_style ('parameter', 'url-access') .. ' and ' .. wrap_style ('parameter', 'subscription')}, true ) } ); -- add error message
SubscriptionRequired = nil; -- unset; 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 ('parameter', 'registration')}, true ) } ); -- add error message
RegistrationRequired = nil; -- unset; prefer |access= over |registration=
end
 
local ChapterUrlAccess = A['ChapterUrlAccess'];
if not is_valid_parameter_value (ChapterUrlAccess, 'chapter-url-access', cfg.keywords ['url-access']) then -- same as url-access
ChapterUrlAccess = nil;
end
if not is_set(ChapterURL) and is_set(ChapterUrlAccess) then
ChapterUrlAccess = nil;
table.insert( z.message_tail, { set_error( 'param_access_requires_param', {A:ORIGIN('chapterChapterUrlAccess'):gsub ('%-urlaccess', '')}, true ) } );
end
 
local MapUrlAccess = is_valid_parameter_value (A['MapUrlAccess'], A:ORIGIN('MapUrlAccess'), cfg.keywords_lists['url-access'], nil);
if not is_set(A['MapURL']) and is_set(MapUrlAccess) then
MapUrlAccess = nil;
table.insert( z.message_tail, { set_error( 'param_access_requires_param', {'map-url'}, true ) } );
end
 
Line 2,369 ⟶ 2,527:
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);
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
 
local ID_list = extract_ids( args );
if is_set (DoiBroken) and not ID_list['DOI'] then
table.insert( z.message_tail, { set_error( 'doibroken_missing_doi', A:ORIGIN('DoiBroken'))});
end
local ID_access_levels = extract_id_access_levels( args, ID_list );
 
Line 2,387 ⟶ 2,545:
local TranscriptFormat = A['TranscriptFormat'];
local TranscriptURL = A['TranscriptURL']
local TranscriptURLoriginTranscriptURL_origin = A:ORIGIN('TranscriptURL'); -- get name of parameter that holds TranscriptURL
 
local LastAuthorAmp = is_valid_parameter_value (A['LastAuthorAmp'], A:ORIGIN('LastAuthorAmp'), cfg.keywords_lists['yes_true_y'], nil);
if not is_valid_parameter_value (LastAuthorAmp, 'last-author-amp', cfg.keywords ['yes_true_y']) then
LastAuthorAmp = nil; -- set to empty string
end
 
local no_tracking_cats = is_valid_parameter_value (A['NoTracking'], A:ORIGIN('NoTracking'), cfg.keywords_lists['yes_true_y'], nil);
if not is_valid_parameter_value (no_tracking_cats, 'no-tracking', cfg.keywords ['yes_true_y']) then
no_tracking_cats = nil; -- set to empty string
end
 
--local variables that are not cs1 parameters
Line 2,405 ⟶ 2,557:
local COinS_date = {}; -- holds 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'], '');
local DF = A['DF']; -- date format set in cs1|2 template
if not is_set (DF) then
if not is_valid_parameter_value (DF, 'df', cfg.keywords['date-format']) then -- validate reformatting keyword
DF = ''cfg.global_df; -- notlocal valid,df if present overrides global df set toby {{use xxx emptydate}} stringtemplate
end
 
local sepc; -- separator between citation elements for CS1 a period, for CS2, a comma
local PostScript;
local Ref = A['Ref'];
if 'harv' == Ref then
sepc, PostScript, Ref = set_style (Mode:lower(), A['PostScript'], A['Ref'], config.CitationClass);
add_maint_cat ('ref_harv'); -- add maint cat to identify templates that have this now-extraneous param value
use_lowercase = ( sepc == ',' ); -- used to control capitalization for certain static text
elseif not is_set (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 == ',' ); -- used to control capitalization for certain static text
 
--check this page to see if it is in one of the namespaces that cs1 is not supposed to add to the error categories
Line 2,428 ⟶ 2,586:
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'); -- this is a dummy call simply to get the error message and category
-- 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' ); -- this is a dummy call simply to get the error message and category
 
local coins_pages;
Line 2,436 ⟶ 2,593:
Page, Pages, At, coins_pages = insource_loc_get (Page, Pages, At);
 
local NoPP = is_valid_parameter_value (A['NoPP'], A:ORIGIN('NoPP'), cfg.keywords_lists['yes_true_y'], nil);
if is_set (NoPP) and is_valid_parameter_value (NoPP, 'nopp', cfg.keywords ['yes_true_y']) then
NoPP = true;
else
NoPP = nil; -- unset, used as a flag later
end
 
if is_set (PublicationPlace) and is_set (Place) then -- both |publication-place= and |place= (|location=) allowed if different
add_prop_cat ('location test'); -- add property cat to evaluate how often PublicationPlace and Place are used together
if not is_set(PublicationPlace) and is_set(Place) then
if PublicationPlace == Place; -- promote |place= (|location=) to |publication-placethen
Place = ''; -- unset; don't need both if they are the same
end
elseif not is_set (PublicationPlace) and 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 -- don't need both if they are the same
--[[
Line 2,455 ⟶ 2,611:
|encyclopedia and |title then map |title to |article and |encyclopedia to |title
|encyclopedia and |article then map |encyclopedia to |title
 
|encyclopedia then map |encyclopedia to |title
|trans-title maps to |trans-chapter when |title is re-mapped
|url maps to |chapterurl when |title is remapped
Line 2,464 ⟶ 2,619:
]]
 
local Encyclopedia = A['Encyclopedia']; -- used as a flag by this module and by ~/COinS
 
if is_set (Encyclopedia) then -- emit error message when Encyclopedia set but template is other than {{cite encyclopedia}} or {{citation}}
if ( config.CitationClass == "encyclopaedia" ) or ( config.CitationClass == "citation" and is_set (Encyclopedia)) then -- test code for citation
if 'encyclopaedia' ~= config.CitationClass and 'citation' ~= config.CitationClass then
if is_set(Periodical) then -- Periodical is set when |encyclopedia is set
table.insert (z.message_tail, {set_error ('parameter_ignored', {A:ORIGIN ('Encyclopedia')}, true)});
Encyclopedia = nil; -- unset because not supported by this template
end
end
 
if ('encyclopaedia' == config.CitationClass) or ('citation' == config.CitationClass and is_set (Encyclopedia)) then
if is_set (Periodical) and is_set (Encyclopedia) then -- when both set emit an error
table.insert (z.message_tail, {set_error('redundant_parameters', {wrap_style ('parameter', A:ORIGIN ('Encyclopedia')) .. ' and ' .. wrap_style ('parameter', Periodical_origin)}, true )});
end
 
if is_set (Encyclopedia) then
Periodical = Encyclopedia; -- error or no, set Periodical to Encyclopedia; allow periodical without encyclopedia
Periodical_origin = A:ORIGIN ('Encyclopedia');
end
 
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) then
Chapter = Title; -- |encyclopedia and |title are set so map |title to |article and |encyclopedia to |title
ScriptChapter = ScriptTitle;
ScriptChapter_origin = A:ORIGIN('ScriptTitle')
TransChapter = TransTitle;
ChapterURL = URL;
ChapterURL_origin = A:ORIGIN('URL')
 
ChapterUrlAccess = UrlAccess;
 
Line 2,488 ⟶ 2,662:
ScriptTitle = '';
end
else elseif is_set (Chapter) then -- |title not set
Title = Periodical; -- |encyclopedia set and |article set or not set so map |encyclopedia to |title
Periodical = ''; -- redundant so unset
end
Line 2,504 ⟶ 2,678:
end
end
end
 
-- special case for cite mailing list
if (config.CitationClass == "mailinglist") then
Periodical = A ['MailingList'];
elseif 'mailinglist' == A:ORIGIN('Periodical') then
Periodical = ''; -- unset because mailing list is only used for cite mailing list
end
 
Line 2,517 ⟶ 2,684:
if is_set(BookTitle) then
Chapter = Title;
Chapter_origin = 'title';
-- ChapterLink = TitleLink; -- |chapterlink= is deprecated
-- ChapterLink = TitleLink; -- |chapterlink= is deprecated
ChapterURL = URL;
ChapterUrlAccess = UrlAccess;
ChapterURLoriginChapterURL_origin = URLoriginURL_origin;
URLoriginURL_origin = '';
ChapterFormat = Format;
TransChapter = TransTitle;
TransChapter_origin = TransTitle_origin;
Title = BookTitle;
Format = '';
-- TitleLink = '';
TransTitle = '';
URL = '';
Line 2,540 ⟶ 2,709:
local Sheets = A['Sheets'] or '';
if config.CitationClass == "map" then
if is_set (Chapter) then
table.insert( z.message_tail, { set_error( 'redundant_parameters', {wrap_style ('parameter', 'map') .. ' and ' .. wrap_style ('parameter', Chapter_origin)}, true ) } ); -- add error message
end
Chapter = A['Map'];
Chapter_origin = A:ORIGIN('Map');
ChapterURL = A['MapURL'];
ChapterURL_origin = A:ORIGIN('MapURL');
ChapterUrlAccess = UrlAccess;
TransChapter = A['TransMap'];
ScriptChapter = A['ScriptMap']
ChapterURLorigin = A:ORIGIN('MapURL');
ScriptChapter_origin = A:ORIGIN('ScriptMap')
 
ChapterUrlAccess = MapUrlAccess;
ChapterFormat = A['MapFormat'];
 
Cartography = A['Cartography'];
if is_set( Cartography ) then
Line 2,559 ⟶ 2,735:
-- Account for the oddities that are {{cite episode}} and {{cite serial}}, before generation of COinS data.
if 'episode' == config.CitationClass or 'serial' == config.CitationClass then
local AirDate = A['AirDate'];
local SeriesLink = A['SeriesLink'];
 
Line 2,572 ⟶ 2,747:
ID = table.concat(n, sepc .. ' ');
if not is_set (Date) and is_set (AirDate) then -- promote airdate to date
Date = AirDate;
end
 
if 'episode' == config.CitationClass then -- handle the oddities that are strictly {{cite episode}}
local Season = A['Season'];
Line 2,586 ⟶ 2,757:
-- assemble a table of parts concatenated later into Series
if is_set(Season) then table.insert(s, wrap_msg ('season', Season, use_lowercase)); end
if is_set(SeriesNumber) then table.insert(s, wrap_msg ('seriesseriesnum', 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
Line 2,592 ⟶ 2,763:
Chapter = Title; -- promote title parameters to chapter
ScriptChapter = ScriptTitle;
ScriptChapter_origin = A:ORIGIN('ScriptTitle');
ChapterLink = TitleLink; -- alias episodelink
TransChapter = TransTitle;
ChapterURL = URL;
ChapterUrlAccess = UrlAccess;
ChapterURLoriginChapterURL_origin = A:ORIGIN('URL');
Title = Series; -- promote series to title
Line 2,622 ⟶ 2,794:
-- end of {{cite episode}} stuff
 
-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, {{cite ssrn}}, before generation of COinS data.
do
if in_array (config.CitationClass, {'arxiv', 'biorxiv', 'citeseerx'}whitelist.preprint_template_list) then
if not is_set (ID_list[config.CitationClass:upper()]) then -- |arxiv= or |eprint= required for cite arxiv; |biorxiv= & |citeseerx= required for their templates
table.insert( z.message_tail, { set_error( config.CitationClass .. '_missing', {}, true ) } ); -- add error message
end
if 'arxiv' == config.CitationClass then
Periodical = 'arXiv'; -- set to arXiv for COinS; after that, must be set to empty string
end
 
ifPeriodical = ({['arxiv'] = 'arXiv', ['biorxiv'] = 'bioRxiv', ['citeseerx'] = 'CiteSeerX', ['ssrn'] = 'Social Science Research Network'})[config.CitationClass then];
Periodical = 'bioRxiv'; -- set to bioRxiv for COinS; after that, must be set to empty string
end
 
if 'citeseerx' == config.CitationClass then
Periodical = 'CiteSeerX'; -- set to CiteSeerX for COinS; after that, must be set to empty string
end
end
end
Line 2,729 ⟶ 2,891:
-- uncomment these three lines. Not supported by en.wiki (for obvious reasons)
-- set date_name_xlate() second argument to true to translate English digits to local digits (will translate ymd dates)
-- if date_name_xlate (date_parameters_list, false) then
-- modified = true;
-- end
 
if modified then -- if the date_parameters_list values were modified
Line 2,746 ⟶ 2,908:
end -- end of do
 
-- Account forLink the odditytitle thatof isthe {{citework journal}}if with |pmc= set andno |url= notwas set.provided, but Dowe thishave aftera date|pmc= checkor buta before|doi= COInS.with |doi-access=free
-- Here we unset Embargo if PMC not embargoed (|embargo= not set in the citation) or if the embargo time has expired. Otherwise, holds embargo date
Embargo = is_embargoed (Embargo);
 
if config.CitationClass == "journal" and not is_set(URL) and not is_set(ID_list['PMC']TitleLink) then
if is_set(ID_list['PMC']) and not is_set (Embargo) then -- if not embargoed or embargo has expired
URL=cfg.id_handlers['PMC'].prefix .. ID_list['PMC']; -- set url to be the same as the PMC external link if not embargoed
URLoriginURL_origin = cfg.id_handlers['PMC'].parameters[1]; -- set URLoriginURL_origin to parameter name for use in error message if citation is missing a |title=
elseif is_set(ID_list['DOI']) and ID_access_levels['DOI'] == "free" then
if is_set(AccessDate) then -- access date requires |url=; pmc created url is not |url=
URL=cfg.id_handlers['DOI'].prefix .. ID_list['DOI'];
table.insert( z.message_tail, { set_error( 'accessdate_missing_url', {}, true ) } );
URL_origin = cfg.id_handlers['DOI'].parameters[1];
AccessDate = ''; -- unset
end
if is_set(URL) and is_set(AccessDate) then -- access date requires |url=; pmc or doi created url is not |url=
 
table.insert( z.message_tail, { set_error( 'accessdate_missing_url', {}, true ) } );
AccessDate = ''; -- unset
end
end
Line 2,764 ⟶ 2,928:
-- At this point fields may be nil if they weren't specified in the template use. We can use that fact.
-- Test if citation has no title
if not is_set(Title) and not is_set(TransTitle) and not is_set(ScriptTitle) then -- has special case for cite episode
table.insert( z.message_tail, { set_error( 'citation_missing_title', {'episode' == config.CitationClass and 'series' or 'title'}, true ) } );
not is_set(TransTitle) and
not is_set(ScriptTitle) then
if 'episode' == config.CitationClass then -- special case for cite episode; TODO: is there a better way to do this?
table.insert( z.message_tail, { set_error( 'citation_missing_title', {'series'}, true ) } );
else
table.insert( z.message_tail, { set_error( 'citation_missing_title', {'title'}, true ) } );
end
end
 
if cfg.keywords_xlate[Title] == 'none' and
if 'none' == Title and in_array (config.CitationClass, {'journal', 'citation'}) and is_set (Periodical) and 'journal' == A:ORIGIN('Periodical') then -- special case for journal cites
in_array (config.CitationClass, {'journal', 'citation'}) and
Title = ''; -- set title to empty string
(is_set (Periodical) or is_set (ScriptPeriodical)) and
add_maint_cat ('untitled');
('journal' == Periodical_origin or 'script-journal' == ScriptPeriodical_origin) then -- special case for journal cites
Title = ''; -- set title to empty string
add_maint_cat ('untitled');
end
 
check_for_url ({ -- add error message when any of these parameters containshold a URL
['title']=Title,
[A:ORIGIN('Chapter')]=Chapter,
[A:ORIGIN('Periodical')Periodical_origin] = Periodical,
[A:ORIGIN('PublisherName')PublisherName_origin] = PublisherName
});
 
Line 2,803 ⟶ 2,964:
coins_author = c; -- use that instead
end
 
-- this is the function call to COinS()
local OCinSoutput = COinS({
['Periodical'] = strip_apostrophe_markup (Periodical), -- no markup in the metadata
['Encyclopedia'] = Encyclopedia, -- just a flag; content ignored by ~/COinS
['Chapter'] = make_coins_title (coins_chapter, ScriptChapter), -- Chapter and ScriptChapter stripped of bold / italic wikimarkup
['Degree'] = Degree; -- cite thesis only
Line 2,814 ⟶ 2,975:
['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 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,
Line 2,827 ⟶ 2,989:
}, config.CitationClass);
 
-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, and {{cite citeseerxssrn}} AFTER generation of COinS data.
if in_array (config.CitationClass, {'arxiv', 'biorxiv', 'citeseerx'}whitelist.preprint_template_list) then -- we have set rft.jtitle in COinS to arXiv, bioRxiv, CiteSeerX, or CiteSeerXssrn 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 'newsgroup' == config.CitationClass and is_set (Newsgroup) then
PublisherName = substitute (cfg.messages['newsgroup'], external_link( 'news:' .. Newsgroup, Newsgroup, Newsgroup_origin, nil ));
if is_set (PublisherName) then
PublisherName = substitute (cfg.messages['newsgroup'], external_link( 'news:' .. PublisherName, PublisherName, A:ORIGIN('PublisherName'), nil ));
end
end
 
 
 
-- Now perform various field substitutions.
Line 2,851 ⟶ 3,009:
maximum = nil, -- as if display-authors or display-editors not set
lastauthoramp = LastAuthorAmp,
page_name = this_page.text, -- get current page name so that we don't wikilink to it via editorlinkn
mode = Mode
};
 
do -- do editor name list first because the now unsupported coauthors used to modify control table
control.maximum , editor_etal = get_display_authors_editorsget_display_names (A['DisplayEditors'], #e, 'editors', editor_etal);
last_first_list, EditorCount = list_people(control, e, editor_etal);
 
if is_set (Editors) then
Editors, editor_etal = name_has_etal (Editors, editor_etal, false, 'editors'); -- find and remove variations on et al.
if editor_etal then
Editors = Editors .. ' ' .. cfg.messages['et al']; -- add et al. to editors parameter beause |display-editors=etal
EditorCount = 2; -- with et al., |editors= is multiple names; spoof to display (eds.) annotation
else
EditorCount = 2; -- we don't know but assume |editors= is multiple names; spoof to display (eds.) annotation
end
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
Line 2,875 ⟶ 3,031:
end
do -- now do interviewers
control.maximum , interviewer_etal = get_display_names (A['DisplayInterviewers'], #interviewers_list; --, number'interviewers', of interviewerssinterviewer_etal);
Interviewers = list_people (control, interviewers_list, falseinterviewer_etal); -- et al not currently supported
end
do -- now do translators
control.maximum , translator_etal = get_display_names (A['DisplayTranslators'], #t; -- number of, 'translators', translator_etal);
Translators = list_people (control, t, falsetranslator_etal); -- et al not currently supported
end
do -- now do contributors
control.maximum , contributor_etal = get_display_names (A['DisplayContributors'], #c; -- number of, 'contributors', contributor_etal);
Contributors = list_people (control, c, falsecontributor_etal); -- et al not currently supported
end
do -- now do authors
control.maximum , author_etal = get_display_authors_editorsget_display_names (A['DisplayAuthors'], #a, 'authors', author_etal);
 
last_first_list = list_people(control, a, author_etal);
 
if is_set (Authors) then
Authors, author_etal = name_has_etal (Authors, author_etal, false, 'authors'); -- find and remove variations on et al.
if author_etal then
Authors = Authors .. ' ' .. cfg.messages['et al']; -- add et al. to authors parameter
Line 2,916 ⟶ 3,072:
 
-- special case for chapter format so no error message or cat when chapter not supported
if not (in_array(config.CitationClass, {'web', 'news', 'journal', 'magazine', 'pressrelease', 'podcast', 'newsgroup', 'arxiv', 'biorxiv', 'citeseerx', 'ssrn'}) or
('citation' == config.CitationClass and (is_set (Periodical) or is_set (ScriptPeriodical)) and not is_set (Encyclopedia))) then
ChapterFormat = style_format (ChapterFormat, ChapterURL, 'chapter-format', 'chapter-url');
end
 
if not is_set(URL) then
if in_array(config.CitationClass, {"web","podcast", "mailinglist"}) thenor -- |url= required for cite web, cite podcast, and cite mailinglist
('citation' == config.CitationClass and ('website' == Periodical_origin or 'script-website' == ScriptPeriodical_origin)) then -- and required for {{citation}} with |website= or |script-website=
table.insert( z.message_tail, { set_error( 'cite_web_url', {}, true ) } );
table.insert( z.message_tail, { set_error( 'cite_web_url', {}, true ) } );
end
Line 2,933 ⟶ 3,090:
end
 
local OriginalURL, OriginalURLoriginOriginalURL_origin, OriginalFormat, OriginalAccess;
DeadURLUrlStatus = DeadURLUrlStatus:lower(); -- used later when assembling archived text
if is_set( ArchiveURL ) then
if is_set (ChapterURL) then -- if chapter-url is set apply archive url to it
OriginalURL = ChapterURL; -- save copy of source chapter's url for archive text
OriginalURLoriginOriginalURL_origin = ChapterURLoriginChapterURL_origin; -- name of chapter-url parameter for error messages
OriginalFormat = ChapterFormat; -- and original |chapter-format=
 
if 'no' ~= DeadURL then
if 'live' ~= UrlStatus then
ChapterURL = ArchiveURL -- swap-in the archive's url
ChapterURLoriginChapterURL_origin = A:ORIGIN('ArchiveURL') -- name of archive-url parameter for error messages
ChapterFormat = ArchiveFormat or ''; -- swap in archive's format
ChapterUrlAccess = nil; -- restricted access levels do not make sense for archived urls
Line 2,948 ⟶ 3,106:
elseif is_set (URL) then
OriginalURL = URL; -- save copy of original source URL
OriginalURLoriginOriginalURL_origin = URLoriginURL_origin; -- 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
if 'live' ~= UrlStatus then -- if URL set then archive-url applies to it
URL = ArchiveURL -- swap-in the archive's url
URLoriginURL_origin = A:ORIGIN('ArchiveURL') -- 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
Line 2,960 ⟶ 3,119:
end
 
if in_array(config.CitationClass, {'web','news','journal', 'magazine', 'pressrelease', 'podcast', 'newsgroup', 'arxiv', 'biorxiv', 'citeseerx', 'ssrn'}) or -- if any of the 'periodical' cites except encyclopedia
('citation' == config.CitationClass and (is_set (Periodical) or is_set (ScriptPeriodical)) and not is_set (Encyclopedia)) then
local chap_param;
if is_set (Chapter) then -- get a parameter name from one of these chapter related meta-parameters
Line 2,970 ⟶ 3,129:
chap_param = A:ORIGIN ('ChapterURL')
elseif is_set (ScriptChapter) then
chap_param = A:ORIGIN ('ScriptChapter')ScriptChapter_origin;
else is_set (ChapterFormat)
chap_param = A:ORIGIN ('ChapterFormat')
Line 2,986 ⟶ 3,145:
local no_quotes = false; -- default assume that we will be quoting the chapter parameter value
if is_set (Contribution) and 0 < #c then -- if this is a contribution with contributor(s)
if in_array (Contribution:lower(), cfg.keywordskeywords_lists.contribution) then -- and a generic contribution title
no_quotes = true; -- then render it unquoted
end
end
 
Chapter = format_chapter_title (ScriptChapter, ScriptChapter_origin, Chapter, Chapter_origin, TransChapter, TransChapter_origin, ChapterURL, ChapterURLoriginChapterURL_origin, no_quotes, ChapterUrlAccess); -- Contribution is also in Chapter
if is_set (Chapter) then
Chapter = Chapter .. ChapterFormat ;
Line 3,003 ⟶ 3,162:
end
 
-- Format main title.
if is_set (ArchiveURL) and
if is_set (ArchiveURL) and mw.ustring.match (mw.ustring.lower(Title), cfg.special_case_translation['archived_copy']) then -- if title is 'Archived copy' (place holder added by bots that can't find proper title)
(mw.ustring.match (mw.ustring.lower(Title), cfg.special_case_translation.archived_copy.en) or -- if title is 'Archived copy' (place holder added by bots that can't find proper title)
add_maint_cat ('archived_copy'); -- add maintenance category before we modify the content of Title
mw.ustring.match (mw.ustring.lower(Title), cfg.special_case_translation.archived_copy['local'])) then -- local-wiki's form
add_maint_cat ('archived_copy'); -- add maintenance category before we modify the content of Title
end
 
Line 3,018 ⟶ 3,179:
end
end
 
if in_array(config.CitationClass, {'web', 'news', 'journal', 'magazine', 'pressrelease', 'podcast', 'newsgroup', 'mailinglist', 'interview', 'arxiv', 'biorxiv', 'citeseerx', 'ssrn'}) or
('citation' == config.CitationClass and (is_set (Periodical) or is_set (ScriptPeriodical)) and not is_set (Encyclopedia)) or
('map' == config.CitationClass and (is_set (Periodical) or is_set (ScriptPeriodical))) then -- special case for cite 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 = wrap_style ('quoted-title', Title);
Title = script_concatenate (Title, ScriptTitle, 'script-title'); -- <bdi> tags, lang atribute, categorization, etc; must be done after title is wrapped
TransTitle= wrap_style ('trans-quoted-title', TransTitle );
elseif 'report' == config.CitationClass then -- no styling for cite report
Title = script_concatenate (Title, ScriptTitle, 'script-title'); -- <bdi> tags, lang atribute, categorization, etc; must be done after title is wrapped
TransTitle= wrap_style ('trans-quoted-title', TransTitle ); -- for cite report, use this form for trans-title
else
Title = wrap_style ('italic-title', Title);
Title = script_concatenate (Title, ScriptTitle, 'script-title'); -- <bdi> tags, lang atribute, categorization, etc; must be done after title is wrapped
TransTitle = wrap_style ('trans-italic-title', TransTitle);
end
Line 3,044 ⟶ 3,205:
end
 
if is_set (Title) then -- TODO: is this the right place to be making wikisource urls?
if is_set (TitleLink) and is_set (URL) then
table.insert( z.message_tail, { set_error( 'wikilink_in_url', {}, true ) } ); -- set an error message because we can't have both
TitleLink = ''; -- unset
end
if not is_set (TitleLink) and is_set (URL) then
Title = external_link (URL, Title, URLoriginURL_origin, UrlAccess) .. TransTitle .. TransError .. Format;
URL = ''; -- unset these because no longer needed
Format = "";
Line 3,060 ⟶ 3,226:
end
else
local ws_url, ws_label; -- Title has italic or quote markup by the time we get here which causes is_wikilink() to return 0 (not a wikilink)
local ws_url, ws_label;
ws_url, ws_label, L = wikisource_url_make (Title:gsub('[\'"](.-)[\'"]', '%1')); -- make ws url from |title= interwiki link (strip italic or quote markup); link portion L becomes tool tip label
if ws_url then
Title = Title:gsub ('%b[]', ws_label); -- replace interwiki link with ws_label to retain markup
Line 3,081 ⟶ 3,247:
if is_set (Conference) then
if is_set (ConferenceURL) then
Conference = external_link( ConferenceURL, Conference, ConferenceURLoriginConferenceURL_origin, nil );
end
Conference = sepc .. " " .. Conference .. ConferenceFormat;
elseif is_set(ConferenceURL) then
Conference = sepc .. " " .. external_link( ConferenceURL, nil, ConferenceURLoriginConferenceURL_origin, nil );
end
 
Line 3,119 ⟶ 3,285:
Position = is_set(Position) and (sepc .. " " .. Position) or "";
if config.CitationClass == 'map' then
local Sections = A['Sections']; -- Section (singular) is an alias of Chapter so set earlier
local Section = A['Section'];
local Sections = A['Sections'];
local Inset = A['Inset'];
Line 3,163 ⟶ 3,328:
end
 
Series = is_set (Series) and wrap_msg ('series', {sepc .. " " .., Series}) or ""; -- not the same as SeriesNum
OrigYear = is_set (OrigYear) and wrap_msg (" [" ..'origyear', OrigYear .. "]") or ""''; -- TODO: presentation
Agency = is_set (Agency) and wrap_msg ('agency', {sepc, Agency}) or "";
 
Agency = is_set(Agency) and (sepc .. " " .. Agency) or "";
 
Volume = format_volume_issue (Volume, Issue, config.CitationClass, Periodical_origin, sepc, use_lowercase);
 
------------------------------------ totally unrelated data
ifVia = is_set (Via) thenand wrap_msg ('via', Via) or '';
Via = " " .. wrap_msg ('via', Via);
end
 
--[[
Subscription implies paywall; Registration does not. If both are used in a citation, the subscription required link
note is displayed. There are no error messages for this condition.
]]
if is_set (SubscriptionRequired) then
SubscriptionRequired = sepc .. " " .. cfg.messages['subscription']; -- subscription required message
elseif is_set (RegistrationRequired) then
SubscriptionRequired = sepc .. " " .. cfg.messages['registration']; -- registration required message
else
SubscriptionRequired = ''; -- either or both might be set to something other than yes true y
end
 
if is_set(AccessDate) then
Line 3,209 ⟶ 3,357:
 
if is_set(URL) then
URL = " " .. external_link( URL, nil, URLoriginURL_origin, UrlAccess );
end
 
Line 3,222 ⟶ 3,370:
local Archived
if is_set(ArchiveURL) then
local arch_text;
if not is_set(ArchiveDate) then
ArchiveDate = set_error('archive_missing_date');
end
if "nolive" == DeadURLUrlStatus then
local arch_text = cfg.messages['archived'];
if sepc ~= "." then arch_text = arch_text:lower() end
Archived = sepc .. " " .. substitute( cfg.messages['archived-not-deadlive'],
{ external_link( ArchiveURL, arch_text, A:ORIGIN('ArchiveURL'), nil ) .. ArchiveFormat, ArchiveDate } );
if not is_set (OriginalURL) then
Archived = Archived .. " " .. set_error('archive_missing_url');
end
elseif is_set(OriginalURL) then -- DeadURLUrlStatus is empty, 'yesdead', 'trueunfit', 'yusurped', 'unfit',bot: 'usurpedunknown'
if in_array (UrlStatus, {'unfit', 'usurped', 'bot: unknown'}) then
local arch_text = cfg.messages['archived-dead'];
arch_text = cfg.messages['archived-unfit'];
if sepc ~= "." then arch_text = arch_text:lower() end
if sepc ~= "." then arch_text = arch_text:lower() end
if in_array (DeadURL, {'unfit', 'usurped', 'bot: unknown'}) then
Archived = sepc .. " " .. 'Archived from the original on 'arch_text .. ArchiveDate; -- format already styled
if 'bot: unknown' == DeadURLUrlStatus then
add_maint_cat ('bot:_unknown'); -- and add a category if not already added
else
add_maint_cat ('unfit'); -- and add a category if not already added
end
else -- DeadURLUrlStatus is empty, 'yes', 'true', or 'ydead'
arch_text = cfg.messages['archived-dead'];
if sepc ~= "." then arch_text = arch_text:lower() end
Archived = sepc .. " " .. substitute( arch_text,
{ external_link( OriginalURL, cfg.messages['original'], OriginalURLoriginOriginalURL_origin, OriginalAccess ) .. OriginalFormat, ArchiveDate } ); -- format already styled
end
else -- OriginalUrl not set
else
local arch_text = cfg.messages['archived-missing'];
if sepc ~= "." then arch_text = arch_text:lower() end
Archived = sepc .. " " .. substitute( arch_text,
Line 3,278 ⟶ 3,429:
if is_set(Transcript) then
if is_set(TranscriptURL) then
Transcript = external_link( TranscriptURL, Transcript, TranscriptURLoriginTranscriptURL_origin, nil );
end
Transcript = sepc .. ' ' .. Transcript .. TranscriptFormat;
elseif is_set(TranscriptURL) then
Transcript = external_link( TranscriptURL, nil, TranscriptURLoriginTranscriptURL_origin, nil );
end
 
Line 3,302 ⟶ 3,453:
-- Several of the above rely upon detecting this as nil, so do it last.
if (is_set (Periodical) or is_set (ScriptPeriodical) or is_set (TransPeriodical)) then
if is_set(Title) or is_set(TitleNote) then
Periodical = sepc .. " " .. wrap_styleformat_periodical ('italic-title'ScriptPeriodical, ScriptPeriodical_origin, Periodical), TransPeriodical, TransPeriodical_origin);
else
Periodical = wrap_styleformat_periodical ('italic-title'ScriptPeriodical, ScriptPeriodical_origin, Periodical, TransPeriodical, TransPeriodical_origin);
end
end
Line 3,314 ⟶ 3,465:
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 is_set (Periodical) then -- if Periodical, perhaps because of an included |website= or |journal= parameter
if 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
Line 3,351 ⟶ 3,502:
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
Line 3,364 ⟶ 3,515:
end
local idcommon;
local idcommon = safe_join( { ID_list, URL, Archived, AccessDate, Via, SubscriptionRequired, Lay, Quote }, sepc );
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;
Line 3,390 ⟶ 3,547:
if (sepc ~= '.') then
in_text = in_text:lower() -- lowercase for cs2
end
else
if EditorCount <= 1 then
post_text = ", " .. cfg.messages['editor'];
else
post_text = ", " .. cfg.messages['editors'];
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
Line 3,438 ⟶ 3,594:
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
Line 3,448 ⟶ 3,604:
if is_set(config.CitationClass) and config.CitationClass ~= "citation" then
options.class = string.format ('%s %s %s', 'citation', config.CitationClass, is_set (Mode) and Mode or 'cs1'); -- class=citation required for blue highlight when used with |ref=
options.class = "citation " .. config.CitationClass; -- class=citation required for blue highlight when used with |ref=
else
options.class = "string.format ('%s %s', 'citation"', is_set (Mode) and Mode or 'cs2');
end
if is_set(Ref) and 'none' ~= cfg.keywords_xlate[Ref:lower() ~= "none"] then -- set reference anchor if appropriate
local id = Ref
if ('harv' == Ref ) then
Line 3,490 ⟶ 3,645:
end
 
table.insert (render, substitute (cfg.presentation['ocins'], {OCinSoutput})); -- append metadata to the citation
 
if 0 ~= #z.message_tail then
Line 3,518 ⟶ 3,673:
end
no_tracking_catsif =not no_tracking_cats:lower(); then
if in_array(no_tracking_cats, {"", "no", "false", "n"}) then
for _, v in ipairs( z.error_categories ) do
table.insert (render, make_wikilink ('Category:' .. v));
Line 3,549 ⟶ 3,703:
local name = tostring (name);
local state;
local function state_test (state, name) -- local function to do testing of state values
if in_array (cite_class, {'arxiv', 'biorxiv', 'citeseerx'}) then -- limited parameter sets allowed for these templates
state = whitelist.limited_basic_arguments[name];
if true == state then return true; end -- valid actively supported parameter
if false == state then
Line 3,557 ⟶ 3,709:
return true;
end
return nil;
end
 
if name:find ('#') then -- # is a cs1|2 reserved character so parameters with # not permitted
state = whitelist[cite_class .. '_basic_arguments'][name]; -- look in the parameter-list for the template identified by cite_class
return nil;
end
 
if 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
 
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
-- limited enumerated parameters list
name = name:gsub("%d+", "#" ); -- replace digit(s) with # (last25 becomes last#) (mw.ustring because non-Western 'local' digits)
state = whitelist.limited_numbered_arguments[name];
if true == state_test (state, name) then return true; end -- valid actively supported parameter
if false == state then
deprecated_parameter (name); -- parameter is deprecated but still supported
return true;
end
 
return false; -- not supported because not found or name is set to nil
end -- end limited parameter-set templates
 
if 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
 
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
-- all enumerated parameters allowed
name = name:gsub("%d+", "#" ); -- replace digit(s) with # (last25 becomes last#) (mw.ustring because non-Western 'local' digits)
state = whitelist.numbered_arguments[name];
if true == state_test (state, name) then return true; end
 
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
return false; -- not supported because not found or name is set to nil
end
Line 3,602 ⟶ 3,752:
Look at the contents of a parameter. If the content has a string of characters and digits followed by an equal
sign, compare the alphanumeric string to the list of cs1|2 parameters. If found, then the string is possibly a
parameter 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 atributes: class=, title=, etc. To prevent false positives xml/html
tags are removed before the search.
Line 3,612 ⟶ 3,762:
]]
 
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[%aw%d-]+)%s*=') or value:match ('^(%a[%aw%d-]+)%s*='); -- find and categorize parameters with possible missing pipes
if capture and validate (capture) then -- if the capture is a valid parameter name
add_maint_cattable.insert( z.message_tail, {set_error ('missing_pipe', parameter)});
end
end
 
 
--[[--------------------------< CH A S 1_ .E C IX T R A TN IE 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
add_maint_cat ('extra_punct'); -- has extraneous punctuation; add maint cat
end
end
 
 
--[[--------------------------< C I T A T I O N >--------------------------------------------------------------
 
This is used by templates such as {{cite book}} to create the actual citation text.
Line 3,629 ⟶ 3,801:
]]
 
local function cs1.citation(frame)
Frame = frame; -- save a copy incase we need to display an error message in preview mode
local pframe = frame:getParent()
Line 3,676 ⟶ 3,848:
is_wikilink = utilities.is_wikilink;
make_wikilink = utilities.make_wikilink;
strip_apostrophe_markup = utilities.strip_apostrophe_markup;
 
z = utilities.z; -- table of error and category tables in Module:Citation/CS1/Utilities
Line 3,693 ⟶ 3,866:
 
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 toolsupport 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
for k, v in pairs( pframe.args ) do -- get parameters from the parent (template) frame
if v ~= '' then
if ('string' == type (k)) then
Line 3,729 ⟶ 3,902:
else
error_text, error_state = set_error( 'parameter_ignored', {param}, true ); -- suggested param not supported by this template
v = ''; -- unset
end
end
Line 3,745 ⟶ 3,919:
end
end
 
missing_pipe_check (v); -- do we think that there is a parameter that is missing a pipe?
args[k] = v; -- TODO: issave this theparameter bestand place for thisits translation?value
 
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 -- here when v is empty string
-- elseif args[k] ~= v; nil or (k == 'postscript') then -- whywhen doargs[k] wehas doa this?value from {{#invoke}} frame (we don't supportnormally 'empty'do parametersthat)
-- args[k] = v; -- overwrite args[k] with empty string from pframe.args[k] (template frame); v is empty string here
end
end -- not sure about the postscript bit; that gets handled in parameter validation; historical artifact?
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?
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;
]]
 
return {citation = citation};