Changes

18,373 bytes added ,  17:12, 16 August 2025
m
KS update 1.4
Line 1: Line 1: −
require('strict');
+
require ('strict');
    
--[[--------------------------< F O R W A R D  D E C L A R A T I O N S >--------------------------------------
 
--[[--------------------------< F O R W A R D  D E C L A R A T I O N S >--------------------------------------
Line 28: Line 28:  
local added_vanc_errs; -- Boolean flag so we only emit one Vancouver error / category
 
local added_vanc_errs; -- Boolean flag so we only emit one Vancouver error / category
 
local added_generic_name_errs; -- Boolean flag so we only emit one generic name error / category and stop testing names once an error is encountered
 
local added_generic_name_errs; -- Boolean flag so we only emit one generic name error / category and stop testing names once an error is encountered
local Frame; -- holds the module's frame table
+
local added_numeric_name_errs; -- Boolean flag so we only emit one numeric name error / category and stop testing names once an error is encountered
 +
local added_numeric_name_maint; -- Boolean flag so we only emit one numeric name maint category and stop testing names once a category has been emitted
 
local is_preview_mode; -- true when article is in preview mode; false when using 'Preview page with this template' (previewing the module)
 
local is_preview_mode; -- true when article is in preview mode; false when using 'Preview page with this template' (previewing the module)
 
local is_sandbox; -- true when using sandbox modules to render citation
 
local is_sandbox; -- true when using sandbox modules to render citation
Line 146: Line 147:  
'%f[%w][%w][%w]%.%a%a+$', -- two character hostname and TLD
 
'%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
 
'^%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?', -- IPv4 address
 +
'[%a%d]+%:?'                                                            -- IPv6 address
 
}
 
}
   Line 243: Line 245:  
local function link_param_ok (value)
 
local function link_param_ok (value)
 
local scheme, domain;
 
local scheme, domain;
if value:find ('[<>%[%]|{}]') then -- if any prohibited characters
+
if value:find ('[<>%[%]|{}]') then                                         -- if any prohibited characters
 
return false;
 
return false;
 
end
 
end
Line 341: Line 343:  
elseif value:match ('%a%S*:%S+') then -- if bare URL with scheme; may have leading or trailing plain text
 
elseif value:match ('%a%S*:%S+') then -- if bare URL with scheme; may have leading or trailing plain text
 
scheme, domain = split_url (value:match ('(%a%S*:%S+)'));
 
scheme, domain = split_url (value:match ('(%a%S*:%S+)'));
elseif value:match ('//%S+') then -- if protocol-relative bare URL: //yyyyy.zzz; may have leading or trailing plain text
+
elseif value:match ('^//%S+') or value:match ('%s//%S+') then -- if protocol-relative bare URL: //yyyyy.zzz; authority indicator (//) must be be preceded nothing or by whitespace
 
scheme, domain = split_url (value:match ('(//%S+)')); -- what is left should be the domain
 
scheme, domain = split_url (value:match ('(//%S+)')); -- what is left should be the domain
 
else
 
else
Line 533: Line 535:  
end
 
end
 
-- if we get this far we have prefix and script
 
-- if we get this far we have prefix and script
name = cfg.lang_code_remap[lang] or mw.language.fetchLanguageName( lang, cfg.this_wiki_code ); -- get language name so that we can use it to categorize
+
name = cfg.lang_tag_remap[lang] or mw.language.fetchLanguageName( lang, 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?
 
if utilities.is_set (name) then -- is prefix a proper ISO 639-1 language code?
 
script_value = script_value:gsub ('^%l+%s*:%s*', ''); -- strip prefix from script
 
script_value = script_value:gsub ('^%l+%s*:%s*', ''); -- strip prefix from script
Line 1,037: Line 1,039:  
end
 
end
 
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
+
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\225\184\128-\225\187\191%-%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
+
nil == mw.ustring.find (first, "^[A-Za-z\195\128-\195\150\195\152-\195\182\195\184-\198\191\199\132-\201\143\225\184\128-\225\187\191%-%s%'%.]*$") then
 
add_vanc_error (cfg.err_msg_supl['non-Latin char'], position);
 
add_vanc_error (cfg.err_msg_supl['non-Latin char'], position);
 
return false; -- not a string of Latin characters; Vancouver requires Romanization
 
return false; -- not a string of Latin characters; Vancouver requires Romanization
Line 1,063: Line 1,065:  
]]
 
]]
   −
local function reduce_to_initials(first, position)
+
local function reduce_to_initials (first, position)
local name, suffix = mw.ustring.match(first, "^(%u+) ([%dJS][%drndth]+)$");
+
if first:find (',', 1, true) then
 +
return first; -- commas not allowed; abandon
 +
end
 +
 
 +
local name, suffix = mw.ustring.match (first, "^(%u+) ([%dJS][%drndth]+)$");
    
if not name then -- if not initials and a suffix
 
if not name then -- if not initials and a suffix
name = mw.ustring.match(first, "^(%u+)$"); -- is it just initials?
+
name = mw.ustring.match (first, "^(%u+)$"); -- is it just initials?
 
end
 
end
   Line 1,085: Line 1,091:  
end -- if here then name has 3 or more uppercase letters so treat them as a word
 
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
+
local initials_t, names_t = {}, {}; -- tables to hold name parts and initials
 
local i = 1; -- counter for number of initials
 
local i = 1; -- counter for number of initials
   −
names = mw.text.split (first, '[%s,]+'); -- split into a table of names and possible suffix
+
names_t = mw.text.split (first, '[%s%-]+'); -- split into a sequence of names and possible suffix
   −
while names[i] do -- loop through the table
+
while names_t[i] do -- loop through the sequence
if 1 < i and names[i]:match ('[%dJS][%drndth]+%.?$') then -- if not the first name, and looks like a suffix (may have trailing dot)
+
if 1 < i and names_t[i]:match ('[%dJS][%drndth]+%.?$') then -- if not the first name, and looks like a suffix (may have trailing dot)
names[i] = names[i]:gsub ('%.', ''); -- remove terminal dot if present
+
names_t[i] = names_t[i]:gsub ('%.', ''); -- remove terminal dot if present
if is_suffix (names[i]) then -- if a legitimate suffix
+
if is_suffix (names_t[i]) then -- if a legitimate suffix
table.insert (initials, ' ' .. names[i]); -- add a separator space, insert at end of initials table
+
table.insert (initials_t, ' ' .. names_t[i]); -- add a separator space, insert at end of initials sequence
 
break; -- and done because suffix must fall at the end of a name
 
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 -- no error message if not a suffix; possibly because of Romanization
 
end
 
end
 
if 3 > i then
 
if 3 > i then
table.insert (initials, mw.ustring.sub(names[i], 1, 1)); -- insert the initial at end of initials table
+
table.insert (initials_t, mw.ustring.sub (names_t[i], 1, 1)); -- insert the initial at end of initials sequence
 
end
 
end
 
i = i + 1; -- bump the counter
 
i = i + 1; -- bump the counter
 
end
 
end
 
 
return table.concat(initials) -- Vancouver format does not include spaces.
+
return table.concat (initials_t); -- Vancouver format does not include spaces.
 
end
 
end
   Line 1,245: Line 1,251:  
if one then -- if <one> has a value (name, mdash replacement, or mask text replacement)
 
if one then -- if <one> has a value (name, mdash replacement, or mask text replacement)
 
local proj, tag = interwiki_prefixen_get (one, true); -- get the interwiki prefixen if present
 
local proj, tag = interwiki_prefixen_get (one, true); -- get the interwiki prefixen if present
   
if 'w' == proj and ('Wikipedia' == mw.site.namespaces.Project['name']) then
 
if 'w' == proj and ('Wikipedia' == mw.site.namespaces.Project['name']) then
 
proj = nil; -- for stuff like :w:de:<article>, :w is unnecessary TODO: maint cat?
 
proj = nil; -- for stuff like :w:de:<article>, :w is unnecessary TODO: maint cat?
 
end
 
end
 
if proj then
 
if proj then
proj = ({['d'] = 'Wikidata', ['s'] = 'Wikisource', ['w'] = 'Wikipedia'})[proj]; -- :w (wikipedia) for linking from a non-wikipedia project
+
local proj_name = ({['d'] = 'Wikidata', ['s'] = 'Wikisource', ['w'] = 'Wikipedia'})[proj]; -- :w (wikipedia) for linking from a non-wikipedia project
if proj then  
+
if proj_name then  
one = one .. utilities.wrap_style ('interproj', proj); -- add resized leading space, brackets, static text, language name
+
one = one .. utilities.wrap_style ('interproj', proj_name); -- add resized leading space, brackets, static text, language name
 +
utilities.add_prop_cat ('interproj-linked-name', proj); -- categorize it; <proj> is sort key
 
tag = nil; -- unset; don't do both project and language
 
tag = nil; -- unset; don't do both project and language
 
end
 
end
Line 1,260: Line 1,266:  
end
 
end
 
if tag then
 
if tag then
local lang = cfg.lang_code_remap[tag] or cfg.mw_languages_by_tag_t[tag];
+
local lang = cfg.lang_tag_remap[tag] or cfg.mw_languages_by_tag_t[tag];
 
if lang then -- error messaging done in extract_names() where we know parameter names
 
if lang then -- error messaging done in extract_names() where we know parameter names
 
one = one .. utilities.wrap_style ('interwiki', lang); -- add resized leading space, brackets, static text, language name
 
one = one .. utilities.wrap_style ('interwiki', lang); -- add resized leading space, brackets, static text, language name
 +
utilities.add_prop_cat ('interwiki-linked-name', tag); -- categorize it; <tag> is sort key
 
end
 
end
 
end
 
end
Line 1,290: Line 1,297:  
local result = table.concat (name_list); -- construct list
 
local result = table.concat (name_list); -- 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
 
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.
+
result = result .. sep .. cfg.messages['et al']; -- we've got a last-first list and etal so add et al.
 
end
 
end
 
 
Line 1,383: Line 1,390:  
--[[---------------------< N A M E _ I S _ N U M E R I C >----------------------
 
--[[---------------------< 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
+
Add an error message and category when <name> parameter value does not contain letters.   
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.
+
Add a maintenance category when <name> parameter value has numeric characters mixed with characters that are
 +
not numeric characters; could be letters and/or punctuation characters.
 +
 
 +
This function will only emit one error and one maint message for the current template. Does not emit both error
 +
and maint messages/categories for the same parameter value.
    
returns nothing
 
returns nothing
Line 1,391: Line 1,402:  
]]
 
]]
   −
local function name_is_numeric (name, list_name)
+
local function name_is_numeric (name, name_alias, list_name)
if utilities.is_set (name) then
+
local patterns = {
if mw.ustring.match (name, '^[%A]+$') then -- when name does not contain any letters
+
'^%D+%d', -- <name> must have digits preceded by other characters
utilities.set_message ('maint_numeric_names', cfg.special_case_translation [list_name]); -- add a maint cat for this template
+
'^%D*%d+%D+', -- <name> must have digits followed by other characters
 +
}
 +
 
 +
if not added_numeric_name_errs and mw.ustring.match (name, '^[%A]+$') then -- if we have not already set an error message and <name> does not have any alpha characters
 +
utilities.set_message ('err_numeric_names', name_alias); -- add an error message
 +
added_numeric_name_errs = true; -- set the flag so we emit only one error message
 +
return; -- when here no point in further testing; abandon
 +
end
 +
 
 +
if not added_numeric_name_maint then -- if we have already set a maint message
 +
for _, pattern in ipairs (patterns) do -- spin through list of patterns
 +
if mw.ustring.match (name, pattern) then -- digits preceded or followed by anything but digits; %D+ includes punctuation
 +
utilities.set_message ('maint_numeric_names', cfg.special_case_translation [list_name]); -- add a maint cat for this template
 +
added_numeric_name_maint = true; -- set the flag so we emit only one maint message
 +
return; -- when here no point in further testing; abandon
 +
end
 
end
 
end
 
end
 
end
Line 1,406: Line 1,432:  
semicolons. Escaped semicolons are ones used as part of selected HTML entities.
 
semicolons. Escaped semicolons are ones used as part of selected HTML entities.
 
If the condition is met, the function adds the multiple name maintenance category.
 
If the condition is met, the function adds the multiple name maintenance category.
 +
 +
Same test for first except that commas should not appear in given names (MOS:JR says
 +
that the generational suffix does not take a separator character).  Titles, degrees,
 +
postnominals, affiliations, all normally comma separated don't belong in a citation.
 +
 +
<name> – name parameter value
 +
<list_name> – AuthorList, EditorList, etc
 +
<limit> – number of allowed commas; 1 (default) for surnames; 0 for given names
    
returns nothing
 
returns nothing
Line 1,411: Line 1,445:  
]]
 
]]
   −
local function name_has_mult_names (name, list_name)
+
local function name_has_mult_names (name, list_name, limit)
 
local _, commas, semicolons, nbsps;
 
local _, commas, semicolons, nbsps;
 +
limit = limit and limit or 1;
 
if utilities.is_set (name) then
 
if utilities.is_set (name) then
 
_, commas = name:gsub (',', ''); -- count the number of commas
 
_, commas = name:gsub (',', ''); -- count the number of commas
Line 1,426: Line 1,461:  
-- from semicolons to 'escape' them. If additional entities are added,
 
-- from semicolons to 'escape' them. If additional entities are added,
 
-- they also can be subtracted.
 
-- they also can be subtracted.
if 1 < commas or 0 < (semicolons - nbsps) then
+
if limit < commas or 0 < (semicolons - nbsps) then
 
utilities.set_message ('maint_mult_names', cfg.special_case_translation [list_name]); -- add a maint message
 
utilities.set_message ('maint_mult_names', cfg.special_case_translation [list_name]); -- add a maint message
 
end
 
end
Line 1,521: Line 1,556:     
if not accept_name then -- <last> not wrapped in accept-as-written markup
 
if not accept_name then -- <last> not wrapped in accept-as-written markup
name_has_mult_names (last, list_name); -- check for multiple names in the parameter (last only)
+
name_has_mult_names (last, list_name); -- check for multiple names in the parameter
name_is_numeric (last, list_name); -- check for names that are composed of digits and punctuation
+
name_is_numeric (last, last_alias, list_name); -- check for names that have no letters or are a mix of digits and other characters
 
name_is_generic (last, last_alias); -- check for names found in the generic names list
 
name_is_generic (last, last_alias); -- check for names found in the generic names list
 
end
 
end
Line 1,531: Line 1,566:     
if not accept_name then -- <first> not wrapped in accept-as-written markup
 
if not accept_name then -- <first> not wrapped in accept-as-written markup
name_is_numeric (first, list_name); -- check for names that are composed of digits and punctuation
+
name_has_mult_names (first, list_name, 0); -- check for multiple names in the parameter; 0 is number of allowed commas in a given name
 +
name_is_numeric (first, first_alias, list_name); -- check for names that have no letters or are a mix of digits and other characters
 
name_is_generic (first, first_alias); -- check for names found in the generic names list
 
name_is_generic (first, first_alias); -- check for names found in the generic names list
 
end
 
end
Line 1,644: Line 1,680:     
This function looks for:
 
This function looks for:
<lang_param> as a tag in cfg.lang_code_remap{}
+
<lang_param> as a tag in cfg.lang_tag_remap{}
 
<lang_param> as a name in cfg.lang_name_remap{}
 
<lang_param> as a name in cfg.lang_name_remap{}
 
 
Line 1,662: Line 1,698:  
local tag;
 
local tag;
   −
name = cfg.lang_code_remap[lang_param_lc]; -- assume <lang_param_lc> is a tag; attempt to get remapped language name  
+
name = cfg.lang_tag_remap[lang_param_lc]; -- assume <lang_param_lc> is a tag; attempt to get remapped language name  
 
if name then -- when <name>, <lang_param> is a tag for a remapped language name
 
if name then -- when <name>, <lang_param> is a tag for a remapped language name
 +
if cfg.lang_name_remap[name:lower()][2] ~= lang_param_lc then
 +
utilities.set_message ('maint_unknown_lang'); -- add maint category if not already added
 +
return name, cfg.lang_name_remap[name:lower()][2]; -- so return name and tag from lang_name_remap[name]; special case to xlate sr-ec and sr-el to sr-cyrl and sr-latn
 +
end
 
return name, lang_param_lc; -- so return <name> from remap and <lang_param_lc>
 
return name, lang_param_lc; -- so return <name> from remap and <lang_param_lc>
 
end
 
end
    
tag = lang_param_lc:match ('^(%a%a%a?)%-.*'); -- still assuming that <lang_param_lc> is a tag; strip script, region, variant subtags
 
tag = lang_param_lc:match ('^(%a%a%a?)%-.*'); -- still assuming that <lang_param_lc> is a tag; strip script, region, variant subtags
name = cfg.lang_code_remap[tag]; -- attempt to get remapped language name with language subtag only
+
name = cfg.lang_tag_remap[tag]; -- attempt to get remapped language name with language subtag only
 
if name then -- when <name>, <tag> is a tag for a remapped language name
 
if name then -- when <name>, <tag> is a tag for a remapped language name
 
return name, tag; -- so return <name> from remap and <tag>
 
return name, tag; -- so return <name> from remap and <tag>
 
end
 
end
   −
if cfg.lang_name_remap[lang_param_lc] then -- not a tag, assume <lang_param_lc> is a name; attempt to get remapped language tag  
+
if cfg.lang_name_remap[lang_param_lc] then -- not a remapped tag, assume <lang_param_lc> is a name; attempt to get remapped language tag  
 
return cfg.lang_name_remap[lang_param_lc][1], cfg.lang_name_remap[lang_param_lc][2]; -- for this <lang_param_lc>, return a (possibly) new name and appropriate tag
 
return cfg.lang_name_remap[lang_param_lc][1], cfg.lang_name_remap[lang_param_lc][2]; -- for this <lang_param_lc>, return a (possibly) new name and appropriate tag
 
end
 
end
    +
name = cfg.mw_languages_by_tag_t[lang_param_lc]; -- assume that <lang_param_lc> is a tag; attempt to get its matching language name
 +
 +
if name then
 +
return name, lang_param_lc; -- <lang_param_lc> is a tag so return it and <name>
 +
end
 +
 
tag = cfg.mw_languages_by_name_t[lang_param_lc]; -- assume that <lang_param_lc> is a language name; attempt to get its matching tag
 
tag = cfg.mw_languages_by_name_t[lang_param_lc]; -- assume that <lang_param_lc> is a language name; attempt to get its matching tag
 
 
Line 1,683: Line 1,729:  
end
 
end
   −
name = cfg.mw_languages_by_tag_t[lang_param_lc]; -- assume that <lang_param_lc> is a tag; attempt to get its matching language name
  −
  −
if name then
  −
return name, lang_param_lc; -- <lang_param_lc> is a tag so return it and <name>
  −
end
  −
   
tag = lang_param_lc:match ('^(%a%a%a?)%-.*'); -- is <lang_param_lc> an IETF-like tag that MediaWiki doesn't recognize? <tag> gets the language subtag; nil else
 
tag = lang_param_lc:match ('^(%a%a%a?)%-.*'); -- is <lang_param_lc> an IETF-like tag that MediaWiki doesn't recognize? <tag> gets the language subtag; nil else
   Line 1,812: Line 1,852:  
-- emit a maintenance message if user postscript is the default cs2 postscript
 
-- emit a maintenance message if user postscript is the default cs2 postscript
 
-- we catch the opposite case for cs1 in set_cs_style
 
-- we catch the opposite case for cs1 in set_cs_style
if 'cs2' == mode or 'citation' == cite_class then
+
if 'cs2' == mode or ('cs1' ~= mode and 'citation' == cite_class) then -- {{citation |title=Title |mode=cs1 |postscript=none}} should not emit maint message
 
utilities.set_message ('maint_postscript');
 
utilities.set_message ('maint_postscript');
 
end
 
end
Line 1,879: Line 1,919:     
inputs:
 
inputs:
max: A['DisplayAuthors'] or A['DisplayEditors']; a number or some flavor of etal
+
max: A['DisplayAuthors'] or A['DisplayEditors'], etc; a number or some flavor of etal
 
count: #a or #e
 
count: #a or #e
 
list_name: 'authors' or 'editors'
 
list_name: 'authors' or 'editors'
 
etal: author_etal or editor_etal
 
etal: author_etal or editor_etal
 +
 +
This function sets an error message when |display-xxxxors= value greater than or equal to number of names but
 +
not when <max> comes from {{cs1 config}} global settings.  When using global settings, <param> is set to the
 +
keyword 'cs1 config' which is used to supress the normal error.  Error is suppressed because it is to be expected
 +
that some citations in an article will have the same or fewer names that the limit specified in {{cs1 config}}.
    
]]
 
]]
Line 1,893: Line 1,938:  
elseif max:match ('^%d+$') then -- if is a string of numbers
 
elseif max:match ('^%d+$') then -- if is a string of numbers
 
max = tonumber (max); -- make it a number
 
max = tonumber (max); -- make it a number
if max >= count then -- if |display-xxxxors= value greater than or equal to number of authors/editors
+
if (max >= count) and ('cs1 config' ~= param) then -- error when local |display-xxxxors= value greater than or equal to number of names; not an error when using global setting
 
utilities.set_message ('err_disp_name', {param, max}); -- add error message
 
utilities.set_message ('err_disp_name', {param, max}); -- add error message
 
max = nil;
 
max = nil;
 
end
 
end
 
else -- not a valid keyword or number
 
else -- not a valid keyword or number
utilities.set_message ('err_disp_name', {param, max}); -- add error message
+
utilities.set_message ('err_disp_name', {param, max}); -- add error message
 
max = nil; -- unset; as if |display-xxxxors= had not been set
 
max = nil; -- unset; as if |display-xxxxors= had not been set
 
end
 
end
Line 1,933: Line 1,978:  
--[[--------------------------< E X T R A _ T E X T _ I N _ V O L _ I S S _ C H E C K >------------------------
 
--[[--------------------------< E X T R A _ T E X T _ I N _ V O L _ I S S _ C H E C K >------------------------
   −
Adds error if |volume= or |issue= has what appears to be some form of redundant 'type' indicator.
+
Adds error if |volume= or |issue= has what appears to be some form of redundant 'type' indicator.  Applies to
 +
both; this function looks for issue text in both |issue= and |volume= and looks for volume-like text in |voluem=
 +
and |issue=.
    
For |volume=:
 
For |volume=:
Line 1,942: Line 1,989:  
For |issue=:
 
For |issue=:
 
'No.', 'I.', 'Iss.' (with or without the dot) abbreviations, or 'Issue' in the first characters of the
 
'No.', 'I.', 'Iss.' (with or without the dot) abbreviations, or 'Issue' in the first characters of the
parameter content (all case insensitive).
+
parameter content (all case insensitive); numero styling: 'n°' with degree sign U+00B0, and № precomposed
 +
numero sign U+2116.
 
 
 
Single character values ('v', 'i', 'n') allowed when not followed by separator character ('.', ':', '=', or
 
Single character values ('v', 'i', 'n') allowed when not followed by separator character ('.', ':', '=', or
Line 1,960: Line 2,008:  
end
 
end
 
 
local patterns = 'v' == selector and cfg.vol_iss_pg_patterns.vpatterns or cfg.vol_iss_pg_patterns.ipatterns;
  −
   
local handler = 'v' == selector and 'err_extra_text_volume' or 'err_extra_text_issue';
 
local handler = 'v' == selector and 'err_extra_text_volume' or 'err_extra_text_issue';
 
val = val:lower(); -- force parameter value to lower case
 
val = val:lower(); -- force parameter value to lower case
for _, pattern in ipairs (patterns) do -- spin through the selected sequence table of patterns
+
 
 +
for _, pattern in ipairs (cfg.vol_iss_pg_patterns.vi_patterns_t) do -- spin through the sequence table of patterns
 
if val:match (pattern) then -- when a match, error so
 
if val:match (pattern) then -- when a match, error so
 
utilities.set_message (handler, name); -- add error message
 
utilities.set_message (handler, name); -- add error message
Line 1,984: Line 2,031:     
local function get_v_name_table (vparam, output_table, output_link_table)
 
local function get_v_name_table (vparam, output_table, output_link_table)
 +
local _, accept = utilities.has_accept_as_written (vparam);
 +
if accept then
 +
utilities.add_prop_cat ('vanc-accept'); -- add properties category
 +
end
 
local name_table = mw.text.split(vparam, "%s*,%s*"); -- names are separated by commas
 
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 placeholder
 
local wl_type, label, link; -- wl_type not used here; just a placeholder
Line 2,414: Line 2,465:  
for timestamp errors when the timestamp has a wildcard, return the URL unmodified
 
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*/)
 
for timestamp errors when the timestamp does not have a wildcard, return with timestamp limited to six digits plus wildcard (/yyyymm*/)
 +
 +
A secondary function is to return an archive-url timestamp from those urls that have them (archive.org and
 +
archive.today).  The timestamp is used by validation.archive_date_check() to see if the value in |archive-date=
 +
matches the timestamp in the archive url.
    
]=]
 
]=]
Line 2,421: Line 2,476:  
local path, timestamp, flag; -- portions of the archive.org URL
 
local path, timestamp, flag; -- portions of the archive.org URL
 
 
 +
timestamp = url:match ('//archive.today/(%d%d%d%d%d%d%d%d%d%d%d%d%d%d)/') or -- get timestamp from archive.today urls
 +
url:match ('//archive.today/(%d%d%d%d%.%d%d%.%d%d%-%d%d%d%d%d%d)/'); -- this timestamp needs cleanup
 +
if timestamp then -- if this was an archive.today url ...
 +
return url, date, timestamp:gsub ('[%.%-]', ''); -- return ArchiveURL, ArchiveDate, and timestamp (dots and dashes removed) from |archive-url=, and done
 +
end
 +
-- here for archive.org urls
 
if (not url:match('//web%.archive%.org/')) and (not url:match('//liveweb%.archive%.org/')) then -- also deprecated liveweb Wayback machine URL
 
if (not url:match('//web%.archive%.org/')) and (not url:match('//liveweb%.archive%.org/')) then -- also deprecated liveweb Wayback machine URL
 
return url, date; -- not an archive.org archive, return ArchiveURL and ArchiveDate
 
return url, date; -- not an archive.org archive, return ArchiveURL and ArchiveDate
Line 2,450: Line 2,511:  
err_msg = cfg.err_msg_supl.flag;
 
err_msg = cfg.err_msg_supl.flag;
 
else
 
else
return url, date; -- return ArchiveURL and ArchiveDate
+
return url, date, timestamp; -- return ArchiveURL, ArchiveDate, and timestamp from |archive-url=
 
end
 
end
 
end
 
end
Line 2,457: Line 2,518:     
if is_preview_mode then
 
if is_preview_mode then
return url, date; -- preview mode so return ArchiveURL and ArchiveDate
+
return url, date, timestamp; -- preview mode so return ArchiveURL, ArchiveDate, and timestamp from |archive-url=
 
else
 
else
 
return '', ''; -- return empty strings for ArchiveURL and ArchiveDate
 
return '', ''; -- return empty strings for ArchiveURL and ArchiveDate
Line 2,504: Line 2,565:       −
--[[--------------------------< C I T A T I O N 0 >------------------------------------------------------------
+
--[[--------------------------< D I S P L A Y _ N A M E S _ S E L E C T >--------------------------------------
 +
 
 +
for any of the |display-authors=, |display-editors=, etc parameters, select either the local or global setting.
 +
When both are present, look at <local_display_names> value.  When the value is some sort of 'et al.'string,
 +
special handling is required.
 +
 
 +
When {{cs1 config}} has |display-<namelist>= AND this template has |display-<namelist>=etal AND:
 +
the number of names specified by <number_of_names> is:
 +
greater than the number specified in the global |display-<namelist>= parameter (<global_display_names>)
 +
use global |display-<namelist>= parameter value
 +
set overridden maint category
 +
less than or equal to the number specified in the global |display-<namelist>=  parameter
 +
use local |display-<namelist>= parameter value
   −
This is the main function doing the majority of the citation formatting.
+
The purpose of this function is to prevent categorizing a template that has fewer names than the global setting
 +
to keep the etal annotation specified by <local_display_names>.
    
]]
 
]]
   −
local function citation0( config, args )
+
local function display_names_select (global_display_names, local_display_names, param_name, number_of_names, test)
--[[
+
if global_display_names and utilities.is_set (local_display_names) then -- when both
Load Input Parameters
+
if 'etal' == local_display_names:lower():gsub("[ '%.]", '') then -- the :gsub() portion makes 'etal' from a variety of 'et al.' spellings and stylings
The argument_wrapper facilitates the mapping of multiple aliases to single internal variable.
+
number_of_names = tonumber (number_of_names); -- convert these to numbers for comparison
]]
+
local global_display_names_num = tonumber (global_display_names); -- <global_display_names> not set when parameter value is not digits
local A = argument_wrapper ( args );
+
 
local i
+
if number_of_names > global_display_names_num then -- template has more names than global config allows to be displayed?
 +
utilities.set_message ('maint_overridden_setting'); -- set a maint message because global is overriding local |display-<namelist>=etal
 +
return global_display_names, 'cs1 config'; -- return global with spoof parameter name (for get_display_names())
 +
else
 +
return local_display_names, param_name; -- return local because fewer names so let <local_display_names> control
 +
end
 +
end
 +
-- here when <global_display_names> and <local_display_names> both numbers; <global_display_names> controls
 +
utilities.set_message ('maint_overridden_setting'); -- set a maint message
 +
return global_display_names, 'cs1 config'; -- return global with spoof parameter name (for get_display_names())
 +
end
 +
-- here when only one of <global_display_names> or <local_display_names> set
 +
if global_display_names then
 +
return global_display_names, 'cs1 config'; -- return global with spoof parameter name (for get_display_names())
 +
else
 +
return local_display_names, param_name; -- return local
 +
end
 +
end
 +
 
 +
 
 +
--[[--------------------------< M O D E _ S E T >--------------------------------------------------------------
 +
 
 +
fetch global mode setting from {{cs1 config}} (if present) or from |mode= (if present); global setting overrides
 +
local |mode= parameter value.  When both are present, emit maintenance message
   −
-- Pick out the relevant fields from the arguments.  Different citation templates
+
]]
-- define different field names for the same underlying things.
     −
local author_etal;
+
local function mode_set (Mode, Mode_origin)
local a = {}; -- authors list from |lastn= / |firstn= pairs or |vauthors=
+
local mode;
local Authors;
+
if cfg.global_cs1_config_t['Mode'] then -- global setting in {{cs1 config}}; nil when empty or assigned value invalid
local NameListStyle = is_valid_parameter_value (A['NameListStyle'], A:ORIGIN('NameListStyle'), cfg.keywords_lists['name-list-style'], '');
+
mode = is_valid_parameter_value (cfg.global_cs1_config_t['Mode'], 'cs1 config: mode', cfg.keywords_lists['mode'], ''); -- error messaging 'param' here is a hoax
local Collaboration = A['Collaboration'];
+
else
 +
mode = is_valid_parameter_value (Mode, Mode_origin, cfg.keywords_lists['mode'], '');
 +
end
   −
do -- to limit scope of selected
+
if cfg.global_cs1_config_t['Mode'] and utilities.is_set (Mode) then -- when template has |mode=<something> which global setting has overridden
local selected = select_author_editor_source (A['Vauthors'], A['Authors'], args, 'AuthorList');
+
utilities.set_message ('maint_overridden_setting'); -- set a maint message
if 1 == selected then
  −
a, author_etal = extract_names (args, 'AuthorList'); -- fetch author list from |authorn= / |lastn= / |firstn=, |author-linkn=, and |author-maskn=
  −
elseif 2 == selected then
  −
NameListStyle = 'vanc'; -- override whatever |name-list-style= might be
  −
a, author_etal = parse_vauthors_veditors (args, args.vauthors, 'AuthorList'); -- fetch author list from |vauthors=, |author-linkn=, and |author-maskn=
  −
elseif 3 == selected then
  −
Authors = A['Authors']; -- use content of |authors=
  −
if 'authors' == A:ORIGIN('Authors') then -- but add a maint cat if the parameter is |authors=
  −
utilities.set_message ('maint_authors'); -- because use of this parameter 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
 
end
 +
return mode;
 +
end
   −
local editor_etal;
  −
local e = {}; -- editors list from |editor-lastn= / |editor-firstn= pairs or |veditors=
     −
do -- to limit scope of selected
+
--[[--------------------------< Q U O T E _ M A K E >----------------------------------------------------------
local selected = select_author_editor_source (A['Veditors'], nil, args, 'EditorList'); -- support for |editors= withdrawn
+
 
if 1 == selected then
+
create quotation from |quote=, |trans-quote=, and/or script-quote= with or without |quote-page= or |quote-pages=
e, editor_etal = extract_names (args, 'EditorList'); -- fetch editor list from |editorn= / |editor-lastn= / |editor-firstn=, |editor-linkn=, and |editor-maskn=
+
 
elseif 2 == selected then
+
when any of those three quote parameters are set, this function unsets <PostScript>.  When none of those parameters
NameListStyle = 'vanc'; -- override whatever |name-list-style= might be
+
are set, |quote-page= and |quote-pages= are unset to nil so that they are not included in the template's metadata
e, editor_etal = parse_vauthors_veditors (args, args.veditors, 'EditorList'); -- fetch editor list from |veditors=, |editor-linkn=, and |editor-maskn=
+
 
 +
]]
 +
 
 +
local function quote_make (quote, trans_quote, script_quote, quote_page, quote_pages, nopp, sepc, postscript)
 +
if utilities.is_set (quote) or utilities.is_set (trans_quote) or utilities.is_set (script_quote) 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
 
end
end
+
+
quote = kern_quotes (quote); -- kern if needed
local Chapter = A['Chapter']; -- done here so that we have access to |contribution= from |chapter= aliases
+
quote = utilities.wrap_style ('quoted-text', quote ); -- wrap in <q>...</q> tags
local Chapter_origin = A:ORIGIN ('Chapter');
+
local Contribution; -- because contribution is required for contributor(s)
+
if utilities.is_set (script_quote) then
if 'contribution' == Chapter_origin then
+
quote = script_concatenate (quote, script_quote, 'script-quote'); -- <bdi> tags, lang attribute, categorization, etc.; must be done after quote is wrapped
Contribution = Chapter; -- get the name of the contribution
   
end
 
end
local c = {}; -- contributors list from |contributor-lastn= / contributor-firstn= pairs
+
 
+
if utilities.is_set (trans_quote) then
if utilities.in_array (config.CitationClass, {"book", "citation"}) and not utilities.is_set (A['Periodical']) then -- |contributor= and |contribution= only supported in book cites
+
if trans_quote:sub(1, 1) == '"' and trans_quote:sub(-1, -1) == '"' then -- if first and last characters of |trans-quote are quote marks
c = extract_names (args, 'ContributorList'); -- fetch contributor list from |contributorn= / |contributor-lastn=, -firstn=, -linkn=, -maskn=
+
trans_quote = trans_quote:sub(2, -2); -- strip them off
  −
if 0 < #c then
  −
if not utilities.is_set (Contribution) then -- |contributor= requires |contribution=
  −
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
  −
if 0 == #a then -- |contributor= requires |author=
  −
utilities.set_message ('err_contributor_missing_required_param', 'author'); -- add missing author error message
  −
c = {}; -- blank the contributors' table; it is used as a flag later
   
end
 
end
 +
quote = quote .. " " .. utilities.wrap_style ('trans-quoted-title', trans_quote );
 
end
 
end
else -- if not a book cite
  −
if utilities.select_one (args, cfg.aliases['ContributorList-Last'], 'err_redundant_parameters', 1 ) then -- are there contributor name list parameters?
  −
utilities.set_message ('err_contributor_ignored'); -- add contributor ignored error message
  −
end
  −
Contribution = nil; -- unset
  −
end
     −
local Title = A['Title'];
+
if utilities.is_set (quote_page) or utilities.is_set (quote_pages) then -- add page prefix
local TitleLink = A['TitleLink'];
+
local quote_prefix = '';
 +
if utilities.is_set (quote_page) then
 +
extra_text_in_page_check (quote_page, 'quote-page'); -- add to maint cat if |quote-page= value begins with what looks like p., pp., etc.
 +
if not nopp then
 +
quote_prefix = utilities.substitute (cfg.messages['p-prefix'], {sepc, quote_page}), '', '', '';
 +
else
 +
quote_prefix = utilities.substitute (cfg.messages['nopp'], {sepc, quote_page}), '', '', '';
 +
end
 +
elseif utilities.is_set (quote_pages) then
 +
extra_text_in_page_check (quote_pages, 'quote-pages'); -- add to maint cat if |quote-pages= value begins with what looks like p., pp., etc.
 +
if tonumber(quote_pages) ~= nil and not nopp then -- if only digits, assume single page
 +
quote_prefix = utilities.substitute (cfg.messages['p-prefix'], {sepc, quote_pages}), '', '';
 +
elseif not nopp then
 +
quote_prefix = utilities.substitute (cfg.messages['pp-prefix'], {sepc, quote_pages}), '', '';
 +
else
 +
quote_prefix = utilities.substitute (cfg.messages['nopp'], {sepc, quote_pages}), '', '';
 +
end
 +
end
 +
                       
 +
quote = quote_prefix .. ": " .. quote;
 +
else
 +
quote = sepc .. " " .. quote;
 +
end
   −
local auto_select = ''; -- default is auto
+
postscript = ""; -- cs1|2 does not supply terminal punctuation when |quote= is set
local accept_link;
+
TitleLink, accept_link = utilities.has_accept_as_written (TitleLink, true); -- test for accept-this-as-written markup
+
elseif utilities.is_set (quote_page) or utilities.is_set (quote_pages) then
if (not accept_link) and utilities.in_array (TitleLink, {'none', 'pmc', 'doi'}) then -- check for special keywords
+
quote_page = nil; -- unset; these require |quote=; TODO: error message?
auto_select = TitleLink; -- remember selection for later
+
quote_pages = nil;
TitleLink = ''; -- treat as if |title-link= would have been empty
   
end
 
end
   −
TitleLink = link_title_ok (TitleLink, A:ORIGIN ('TitleLink'), Title, 'title'); -- check for wiki-markup in |title-link= or wiki-markup in |title= when |title-link= is set
+
return quote, quote_page, quote_pages, postscript;
 +
end
   −
local Section = ''; -- {{cite map}} only; preset to empty string for concatenation if not used
  −
if 'map' == config.CitationClass and 'section' == Chapter_origin 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 Periodical = A['Periodical'];
+
--[[--------------------------< C H E C K _ P U B L I S H E R _ N A M E >--------------------------------------
local Periodical_origin = '';
  −
if utilities.is_set (Periodical) then
  −
Periodical_origin = A:ORIGIN('Periodical'); -- get the name of the periodical parameter
  −
local i;
  −
Periodical, i = utilities.strip_apostrophe_markup (Periodical); -- strip apostrophe markup so that metadata isn't contaminated
  −
if i then -- non-zero when markup was stripped so emit an error message
  −
utilities.set_message ('err_apostrophe_markup', {Periodical_origin});
  −
end
  −
end
     −
if 'mailinglist' == config.CitationClass then -- special case for {{cite mailing list}}
+
look for variations of '<text>: <text>' that might be '<location>: <publisher>' in |publisher= parameter value.
if utilities.is_set (Periodical) and utilities.is_set (A ['MailingList']) then -- both set emit an error TODO: make a function for this and similar?
+
when found, emit a maintenance message; return nil else
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', Periodical_origin) .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'mailinglist')});
  −
end
     −
Periodical = A ['MailingList']; -- error or no, set Periodical to |mailinglist= value because this template is {{cite mailing list}}
+
<publisher> is the value assigned to |publisher= or |institution=
Periodical_origin = A:ORIGIN('MailingList');
  −
end
     −
local ScriptPeriodical = A['ScriptPeriodical'];
+
]]
   −
-- web and news not tested for now because of
+
local function check_publisher_name (publisher)
-- Wikipedia:Administrators%27_noticeboard#Is_there_a_semi-automated_tool_that_could_fix_these_annoying_"Cite_Web"_errors?
+
local patterns_t = {
if not (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) then -- 'periodical' templates require periodical parameter
+
'^[%w%s]+%s*:%s*[%w%s]+$', -- plain text <location>: <publisher>
-- local p = {['journal'] = 'journal', ['magazine'] = 'magazine', ['news'] = 'newspaper', ['web'] = 'website'}; -- for error message
+
'^%[+[%w%s:|]+%]+%s*:%s*[%w%s]+$', -- partially wikilinked [[<location>]]: <publisher>
local p = {['journal'] = 'journal', ['magazine'] = 'magazine'}; -- for error message
+
'^[%w%s]+%s*:%s*%[+[%w%s:|]+%]+$', -- partially wikilinked <location>: [[<publisher>]]
if p[config.CitationClass] then
+
'^%[+[%w%s:|]+%]+%s*:%s*%[+[%w%s:|]+%]+$', -- wikilinked [[<location>]]: [[<publisher>]]
utilities.set_message ('err_missing_periodical', {config.CitationClass, p[config.CitationClass]});
+
}
end
+
end
+
for _, pattern in ipairs (patterns_t) do -- spin through the patterns_t sequence
+
if mw.ustring.match (publisher, pattern) then -- does this pattern match?
local Volume;
+
utilities.set_message ('maint_publisher_location'); -- set a maint message
local ScriptPeriodical_origin = A:ORIGIN('ScriptPeriodical');
+
return; -- and done
if 'citation' == config.CitationClass then
+
end
if utilities.is_set (Periodical) then
+
end
if not utilities.in_array (Periodical_origin, cfg.citation_no_volume_t) then -- {{citation}} does not render |volume= when these parameters are used
+
end
Volume = A['Volume']; -- but does for all other 'periodicals'
  −
end
  −
elseif utilities.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 utilities.in_array (config.CitationClass, cfg.templates_using_volume) then -- render |volume= for cs1 according to the configuration settings
  −
Volume = A['Volume'];
  −
end
  −
extra_text_in_vol_iss_check (Volume, A:ORIGIN ('Volume'), 'v');
     −
local Issue;
  −
if 'citation' == config.CitationClass then
  −
if utilities.is_set (Periodical) and utilities.in_array (Periodical_origin, cfg.citation_issue_t) then -- {{citation}} may render |issue= when these parameters are used
  −
Issue = utilities.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 = utilities.hyphen_to_dash (A['Issue']);
  −
end
  −
end
  −
  −
local ArticleNumber;
     −
if utilities.in_array (config.CitationClass, {'journal', 'conference'}) or ('citation' == config.CitationClass and utilities.is_set (Periodical) and 'journal' == Periodical_origin) then
+
--[[--------------------------< C I T A T I O N 0 >------------------------------------------------------------
ArticleNumber = A['ArticleNumber'];
  −
end
     −
extra_text_in_vol_iss_check (Issue, A:ORIGIN ('Issue'), 'i');
+
This is the main function doing the majority of the citation formatting.
   −
local Page;
+
]]
local Pages;
  −
local At;
  −
local QuotePage;
  −
local QuotePages;
  −
if not utilities.in_array (config.CitationClass, cfg.templates_not_using_page) then -- TODO: rewrite to emit ignored parameter error message?
  −
Page = A['Page'];
  −
Pages = utilities.hyphen_to_dash (A['Pages']);
  −
At = A['At'];
  −
QuotePage = A['QuotePage'];
  −
QuotePages = utilities.hyphen_to_dash (A['QuotePages']);
  −
end
     −
local Edition = A['Edition'];
+
local function citation0( config, args )
local PublicationPlace = place_check (A['PublicationPlace'], A:ORIGIN('PublicationPlace'));
+
--[[  
local Place = place_check (A['Place'], A:ORIGIN('Place'));
+
Load Input Parameters
+
The argument_wrapper facilitates the mapping of multiple aliases to single internal variable.
local PublisherName = A['PublisherName'];
+
]]
local PublisherName_origin = A:ORIGIN('PublisherName');
+
local A = argument_wrapper ( args );
if utilities.is_set (PublisherName) then
+
local i  
local i = 0;
  −
PublisherName, i = utilities.strip_apostrophe_markup (PublisherName); -- strip apostrophe markup so that metadata isn't contaminated; publisher is never italicized
  −
if i then -- non-zero when markup was stripped so emit an error message
  −
utilities.set_message ('err_apostrophe_markup', {PublisherName_origin});
  −
end
  −
end
     −
local Newsgroup = A['Newsgroup']; -- TODO: strip apostrophe markup?
+
-- Pick out the relevant fields from the arguments.  Different citation templates
local Newsgroup_origin = A:ORIGIN('Newsgroup');
+
-- define different field names for the same underlying things.
   −
if 'newsgroup' == config.CitationClass then
+
local author_etal;
if utilities.is_set (PublisherName) then -- general use parameter |publisher= not allowed in cite newsgroup
+
local a = {}; -- authors list from |lastn= / |firstn= pairs or |vauthors=
utilities.set_message ('err_parameter_ignored', {PublisherName_origin});
+
local Authors;
 +
local NameListStyle;
 +
if cfg.global_cs1_config_t['NameListStyle'] then -- global setting in {{cs1 config}} overrides local |name-list-style= parameter value; nil when empty or assigned value invalid
 +
NameListStyle = is_valid_parameter_value (cfg.global_cs1_config_t['NameListStyle'], 'cs1 config: name-list-style', cfg.keywords_lists['name-list-style'], ''); -- error messaging 'param' here is a hoax
 +
else
 +
NameListStyle = is_valid_parameter_value (A['NameListStyle'], A:ORIGIN('NameListStyle'), cfg.keywords_lists['name-list-style'], '');
 
end
 
end
   −
PublisherName = nil; -- ensure that this parameter is unset for the time being; will be used again after COinS
+
if cfg.global_cs1_config_t['NameListStyle'] and utilities.is_set (A['NameListStyle']) then -- when template has |name-list-style=<something> which global setting has overridden
end
+
utilities.set_message ('maint_overridden_setting'); -- set a maint message
 +
end
   −
local URL = A['URL']; -- TODO: better way to do this for URL, ChapterURL, and MapURL?
+
local Collaboration = A['Collaboration'];
local UrlAccess = is_valid_parameter_value (A['UrlAccess'], A:ORIGIN('UrlAccess'), cfg.keywords_lists['url-access'], nil);
  −
  −
if not utilities.is_set (URL) and utilities.is_set (UrlAccess) then
  −
UrlAccess = nil;
  −
utilities.set_message ('err_param_access_requires_param', 'url');
  −
end
  −
  −
local ChapterURL = A['ChapterURL'];
  −
local ChapterUrlAccess = is_valid_parameter_value (A['ChapterUrlAccess'], A:ORIGIN('ChapterUrlAccess'), cfg.keywords_lists['url-access'], nil);
  −
if not utilities.is_set (ChapterURL) and utilities.is_set (ChapterUrlAccess) then
  −
ChapterUrlAccess = nil;
  −
utilities.set_message ('err_param_access_requires_param', {A:ORIGIN('ChapterUrlAccess'):gsub ('%-access', '')});
  −
end
     −
local MapUrlAccess = is_valid_parameter_value (A['MapUrlAccess'], A:ORIGIN('MapUrlAccess'), cfg.keywords_lists['url-access'], nil);
+
do -- to limit scope of selected
if not utilities.is_set (A['MapURL']) and utilities.is_set (MapUrlAccess) then
+
local selected = select_author_editor_source (A['Vauthors'], A['Authors'], args, 'AuthorList');
MapUrlAccess = nil;
+
if 1 == selected then
utilities.set_message ('err_param_access_requires_param', {'map-url'});
+
a, author_etal = extract_names (args, 'AuthorList'); -- fetch author list from |authorn= / |lastn= / |firstn=, |author-linkn=, and |author-maskn=
 +
elseif 2 == selected then
 +
NameListStyle = 'vanc'; -- override whatever |name-list-style= might be
 +
a, author_etal = parse_vauthors_veditors (args, A['Vauthors'], 'AuthorList'); -- fetch author list from |vauthors=, |author-linkn=, and |author-maskn=
 +
elseif 3 == selected then
 +
Authors = A['Authors']; -- use content of |people= or |credits=; |authors= is deprecated; TODO: constrain |people= and |credits= to cite av media, episode, serial?
 +
end
 +
if utilities.is_set (Collaboration) then
 +
author_etal = true; -- so that |display-authors=etal not required
 +
end
 
end
 
end
   −
local this_page = mw.title.getCurrentTitle(); -- also used for COinS and for language
+
local editor_etal;
local no_tracking_cats = is_valid_parameter_value (A['NoTracking'], A:ORIGIN('NoTracking'), cfg.keywords_lists['yes_true_y'], nil);
+
local e = {}; -- editors list from |editor-lastn= / |editor-firstn= pairs or |veditors=
   −
-- check this page to see if it is in one of the namespaces that cs1 is not supposed to add to the error categories
+
do -- to limit scope of selected
if not utilities.is_set (no_tracking_cats) then -- ignore if we are already not going to categorize this page
+
local selected = select_author_editor_source (A['Veditors'], nil, args, 'EditorList'); -- support for |editors= withdrawn
if cfg.uncategorized_namespaces[this_page.namespace] then -- is this page's namespace id one of the uncategorized namespace ids?
+
if 1 == selected then
no_tracking_cats = "true"; -- set no_tracking_cats
+
e, editor_etal = extract_names (args, 'EditorList'); -- fetch editor list from |editorn= / |editor-lastn= / |editor-firstn=, |editor-linkn=, and |editor-maskn=
 +
elseif 2 == selected then
 +
NameListStyle = 'vanc'; -- override whatever |name-list-style= might be
 +
e, editor_etal = parse_vauthors_veditors (args, args.veditors, 'EditorList'); -- fetch editor list from |veditors=, |editor-linkn=, and |editor-maskn=
 +
end
 +
end
 +
 +
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' == Chapter_origin then
 +
Contribution = Chapter; -- get the name of the contribution
 
end
 
end
for _, v in ipairs (cfg.uncategorized_subpages) do -- cycle through page name patterns
+
local c = {}; -- contributors list from |contributor-lastn= / contributor-firstn= pairs
if this_page.text:match (v) then -- test page name against each pattern
+
no_tracking_cats = "true"; -- set no_tracking_cats
+
if utilities.in_array (config.CitationClass, {"book", "citation"}) and not utilities.is_set (A['Periodical']) then -- |contributor= and |contribution= only supported in book cites
break; -- bail out if one is found
+
c = extract_names (args, 'ContributorList'); -- fetch contributor list from |contributorn= / |contributor-lastn=, -firstn=, -linkn=, -maskn=
 +
 +
if 0 < #c then
 +
if not utilities.is_set (Contribution) then -- |contributor= requires |contribution=
 +
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
 +
if 0 == #a then -- |contributor= requires |author=
 +
utilities.set_message ('err_contributor_missing_required_param', 'author'); -- add missing author error message
 +
c = {}; -- blank the contributors' table; it is used as a flag later
 
end
 
end
 
end
 
end
 +
else -- if not a book cite
 +
if utilities.select_one (args, cfg.aliases['ContributorList-Last'], 'err_redundant_parameters', 1 ) then -- are there contributor name list parameters?
 +
utilities.set_message ('err_contributor_ignored'); -- add contributor ignored error message
 +
end
 +
Contribution = nil; -- unset
 +
end
 +
 +
local Title = A['Title'];
 +
local TitleLink = A['TitleLink'];
 +
 +
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 (TitleLink, A:ORIGIN ('TitleLink'), Title, 'title'); -- check for wiki-markup in |title-link= or wiki-markup in |title= when |title-link= is set
 +
 +
local Section = ''; -- {{cite map}} only; preset to empty string for concatenation if not used
 +
if 'map' == config.CitationClass and 'section' == Chapter_origin 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
 
end
-- check for extra |page=, |pages= or |at= parameters. (also sheet and sheets while we're at it)
  −
utilities.select_one (args, {'page', 'p', 'pp', 'pages', 'at', 'sheet', 'sheets'}, 'err_redundant_parameters'); -- this is a dummy call simply to get the error message and category
     −
local coins_pages;
+
local Periodical = A['Periodical'];
 +
local Periodical_origin = A:ORIGIN('Periodical');
 +
local ScriptPeriodical = A['ScriptPeriodical'];
 +
local ScriptPeriodical_origin = A:ORIGIN('ScriptPeriodical');
 +
local TransPeriodical =  A['TransPeriodical'];
 +
local TransPeriodical_origin =  A:ORIGIN ('TransPeriodical');
 
 
Page, Pages, At, coins_pages = insource_loc_get (Page, A:ORIGIN('Page'), Pages, A:ORIGIN('Pages'), At);
+
if (utilities.in_array (config.CitationClass, {'book', 'encyclopaedia'}) and (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical) or utilities.is_set (TransPeriodical))) then
 +
local param;
 +
if utilities.is_set (Periodical) then -- get a parameter name from one of these periodical related meta-parameters
 +
Periodical = ''; -- unset because not valid {{cite book}} or {{cite encyclopedia}} parameters
 +
param = Periodical_origin -- get parameter name for error messaging
 +
elseif utilities.is_set (TransPeriodical) then
 +
TransPeriodical = ''; -- unset because not valid {{cite book}} or {{cite encyclopedia}} parameters
 +
param = TransPeriodical_origin; -- get parameter name for error messaging
 +
elseif utilities.is_set (ScriptPeriodical) then
 +
ScriptPeriodical = ''; -- unset because not valid {{cite book}} or {{cite encyclopedia}} parameters
 +
param = ScriptPeriodical_origin; -- get parameter name for error messaging
 +
end
   −
local NoPP = is_valid_parameter_value (A['NoPP'], A:ORIGIN('NoPP'), cfg.keywords_lists['yes_true_y'], nil);
+
if utilities.is_set (param) then -- if we found one
 
+
utilities.set_message ('err_periodical_ignored', {param}); -- emit an error message
if utilities.is_set (PublicationPlace) and utilities.is_set (Place) then -- both |publication-place= and |place= (|location=) allowed if different
  −
utilities.add_prop_cat ('location-test'); -- add property cat to evaluate how often PublicationPlace and Place are used together
  −
if PublicationPlace == Place then
  −
Place = ''; -- unset; don't need both if they are the same
   
end
 
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
 
end
   −
if PublicationPlace == Place then Place = ''; end -- don't need both if they are the same
+
if utilities.is_set (Periodical) then
+
local i;
local URL_origin = A:ORIGIN('URL'); -- get name of parameter that holds URL
+
Periodical, i = utilities.strip_apostrophe_markup (Periodical); -- strip apostrophe markup so that metadata isn't contaminated
local ChapterURL_origin = A:ORIGIN('ChapterURL'); -- get name of parameter that holds ChapterURL
+
if i then -- non-zero when markup was stripped so emit an error message
local ScriptChapter = A['ScriptChapter'];
+
utilities.set_message ('err_apostrophe_markup', {Periodical_origin});
local ScriptChapter_origin = A:ORIGIN ('ScriptChapter');
  −
local Format = A['Format'];
  −
local ChapterFormat = A['ChapterFormat'];
  −
local TransChapter = A['TransChapter'];
  −
local TransChapter_origin = A:ORIGIN ('TransChapter');
  −
local TransTitle = A['TransTitle'];
  −
local ScriptTitle = A['ScriptTitle'];
  −
  −
--[[
  −
Parameter remapping for cite encyclopedia:
  −
When 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=
  −
 
  −
|trans-title= maps to |trans-chapter= when |title= is re-mapped
  −
|url= maps to |chapter-url= when |title= is remapped
  −
  −
All other combinations of |encyclopedia=, |title=, and |article= are not modified
  −
  −
]]
  −
 
  −
local Encyclopedia = A['Encyclopedia']; -- used as a flag by this module and by ~/COinS
  −
 
  −
if utilities.is_set (Encyclopedia) then -- emit error message when Encyclopedia set but template is other than {{cite encyclopedia}} or {{citation}}
  −
if 'encyclopaedia' ~= config.CitationClass and 'citation' ~= config.CitationClass then
  −
utilities.set_message ('err_parameter_ignored', {A:ORIGIN ('Encyclopedia')});
  −
Encyclopedia = nil; -- unset because not supported by this template
   
end
 
end
 
end
 
end
   −
if ('encyclopaedia' == config.CitationClass) or ('citation' == config.CitationClass and utilities.is_set (Encyclopedia)) then
+
if 'mailinglist' == config.CitationClass then -- special case for {{cite mailing list}}
if utilities.is_set (Periodical) and utilities.is_set (Encyclopedia) then -- when both set emit an error TODO: make a function for this and similar?
+
if utilities.is_set (Periodical) and utilities.is_set (A ['MailingList']) then -- both set emit an error TODO: make a function for this and similar?
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', A:ORIGIN ('Encyclopedia')) .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', Periodical_origin)});
+
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', Periodical_origin) .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'mailinglist')});
 
end
 
end
   −
if utilities.is_set (Encyclopedia) then
+
Periodical = A ['MailingList']; -- error or no, set Periodical to |mailinglist= value because this template is {{cite mailing list}}
Periodical = Encyclopedia; -- error or no, set Periodical to Encyclopedia; allow periodical without encyclopedia
+
Periodical_origin = A:ORIGIN('MailingList');
Periodical_origin = A:ORIGIN ('Encyclopedia');
+
end
end
     −
if utilities.is_set (Periodical) then -- Periodical is set when |encyclopedia= is set
+
-- web and news not tested for now because of
if utilities.is_set (Title) or utilities.is_set (ScriptTitle) then
+
-- Wikipedia:Administrators%27_noticeboard#Is_there_a_semi-automated_tool_that_could_fix_these_annoying_"Cite_Web"_errors?
if not utilities.is_set (Chapter) then
+
if not (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) then -- 'periodical' templates require periodical parameter
Chapter = Title; -- |encyclopedia= and |title= are set so map |title= to |article= and |encyclopedia= to |title=
+
-- local p = {['journal'] = 'journal', ['magazine'] = 'magazine', ['news'] = 'newspaper', ['web'] = 'website'}; -- for error message
ScriptChapter = ScriptTitle;
+
local p = {['journal'] = 'journal', ['magazine'] = 'magazine'}; -- for error message
ScriptChapter_origin = A:ORIGIN('ScriptTitle')
+
if p[config.CitationClass]  then
TransChapter = TransTitle;
+
utilities.set_message ('err_missing_periodical', {config.CitationClass, p[config.CitationClass]});
ChapterURL = URL;
  −
ChapterURL_origin = URL_origin;
  −
 
  −
ChapterUrlAccess = UrlAccess;
  −
 
  −
if not utilities.is_set (ChapterURL) and utilities.is_set (TitleLink) then
  −
Chapter = utilities.make_wikilink (TitleLink, Chapter);
  −
end
  −
Title = Periodical;
  −
ChapterFormat = Format;
  −
Periodical = ''; -- redundant so unset
  −
TransTitle = '';
  −
URL = '';
  −
Format = '';
  −
TitleLink = '';
  −
ScriptTitle = '';
  −
end
  −
elseif utilities.is_set (Chapter) or utilities.is_set (ScriptChapter) then -- |title= not set
  −
Title = Periodical; -- |encyclopedia= set and |article= set so map |encyclopedia= to |title=
  −
Periodical = ''; -- redundant so unset
  −
end
   
end
 
end
 
end
 
end
 
+
-- special case for cite techreport.
+
local Volume;
local ID = A['ID'];
+
if 'citation' == config.CitationClass then
if (config.CitationClass == "techreport") then -- special case for cite techreport
+
if utilities.is_set (Periodical) then
if utilities.is_set (A['Number']) then -- cite techreport uses 'number', which other citations alias to 'issue'
+
if not utilities.in_array (Periodical_origin, cfg.citation_no_volume_t) then -- {{citation}} does not render |volume= when these parameters are used
if not utilities.is_set (ID) then -- can we use ID for the "number"?
+
Volume = A['Volume']; -- but does for all other 'periodicals'
ID = A['Number']; -- yes, use it
+
end
else -- ID has a value so emit error message
+
elseif utilities.is_set (ScriptPeriodical) then
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'id') .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'number')});
+
if 'script-website' ~= ScriptPeriodical_origin then -- {{citation}} does not render volume for |script-website=
 +
Volume = A['Volume']; -- but does for all other 'periodicals'
 
end
 
end
end
+
else
end
+
Volume = A['Volume']; -- and does for non-'periodical' cites
 +
end
 +
elseif utilities.in_array (config.CitationClass, cfg.templates_using_volume) then -- render |volume= for cs1 according to the configuration settings
 +
Volume = A['Volume'];
 +
end
 +
extra_text_in_vol_iss_check (Volume, A:ORIGIN ('Volume'), 'v');
   −
-- Account for the oddity that is {{cite conference}}, before generation of COinS data.
+
local Issue;
local ChapterLink -- = A['ChapterLink']; -- deprecated as a parameter but still used internally by cite episode
+
if 'citation' == config.CitationClass then
local Conference = A['Conference'];
+
if utilities.is_set (Periodical) and utilities.in_array (Periodical_origin, cfg.citation_issue_t) then -- {{citation}} may render |issue= when these parameters are used
local BookTitle = A['BookTitle'];
+
Issue = utilities.hyphen_to_dash (A['Issue']);
local TransTitle_origin = A:ORIGIN ('TransTitle');
+
end
if 'conference' == config.CitationClass then
+
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 utilities.is_set (BookTitle) then
+
if not (utilities.in_array (config.CitationClass, {'conference', 'map', 'citation'}) and not (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical))) then
Chapter = Title;
+
Issue = utilities.hyphen_to_dash (A['Issue']);
Chapter_origin = 'title';
  −
-- ChapterLink = TitleLink; -- |chapter-link= is deprecated
  −
ChapterURL = URL;
  −
ChapterUrlAccess = UrlAccess;
  −
ChapterURL_origin = URL_origin;
  −
URL_origin = '';
  −
ChapterFormat = Format;
  −
TransChapter = TransTitle;
  −
TransChapter_origin = TransTitle_origin;
  −
Title = BookTitle;
  −
Format = '';
  −
-- TitleLink = '';
  −
TransTitle = '';
  −
URL = '';
   
end
 
end
elseif 'speech' ~= config.CitationClass then
  −
Conference = ''; -- not cite conference or cite speech so make sure this is empty string
   
end
 
end
 
 
-- CS1/2 mode
+
local ArticleNumber;
local Mode = is_valid_parameter_value (A['Mode'], A:ORIGIN('Mode'), cfg.keywords_lists['mode'], '');
  −
-- separator character and postscript
  −
local sepc, PostScript = set_style (Mode:lower(), A['PostScript'], config.CitationClass);
  −
-- controls capitalization of certain static text
  −
local use_lowercase = ( sepc == ',' );
  −
  −
-- cite map oddities
  −
local Cartography = "";
  −
local Scale = "";
  −
local Sheet = A['Sheet'] or '';
  −
local Sheets = A['Sheets'] or '';
  −
if config.CitationClass == "map" then
  −
if utilities.is_set (Chapter) then --TODO: make a function for this and similar?
  −
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'map') .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', Chapter_origin)}); -- add error message
  −
end
  −
Chapter = A['Map'];
  −
Chapter_origin = A:ORIGIN('Map');
  −
ChapterURL = A['MapURL'];
  −
ChapterURL_origin = A:ORIGIN('MapURL');
  −
TransChapter = A['TransMap'];
  −
ScriptChapter = A['ScriptMap']
  −
ScriptChapter_origin = A:ORIGIN('ScriptMap')
     −
ChapterUrlAccess = MapUrlAccess;
+
if utilities.in_array (config.CitationClass, {'journal', 'conference'}) or ('citation' == config.CitationClass and utilities.is_set (Periodical) and 'journal' == Periodical_origin) then
ChapterFormat = A['MapFormat'];
+
ArticleNumber = A['ArticleNumber'];
 
  −
Cartography = A['Cartography'];
  −
if utilities.is_set ( Cartography ) then
  −
Cartography = sepc .. " " .. wrap_msg ('cartography', Cartography, use_lowercase);
  −
end
  −
Scale = A['Scale'];
  −
if utilities.is_set ( Scale ) then
  −
Scale = sepc .. " " .. Scale;
  −
end
   
end
 
end
   −
-- Account for the oddities that are {{cite episode}} and {{cite serial}}, before generation of COinS data.
+
extra_text_in_vol_iss_check (Issue, A:ORIGIN ('Issue'), 'i');
local Series = A['Series'];
  −
if 'episode' == config.CitationClass or 'serial' == config.CitationClass then
  −
local SeriesLink = A['SeriesLink'];
     −
SeriesLink = link_title_ok (SeriesLink, A:ORIGIN ('SeriesLink'), Series, 'series'); -- check for wiki-markup in |series-link= or wiki-markup in |series= when |series-link= is set
+
local Page;
 
+
local Pages;
local Network = A['Network'];
+
local At;
local Station = A['Station'];
+
local QuotePage;
local s, n = {}, {};
+
local QuotePages;
-- do common parameters first
+
if not utilities.in_array (config.CitationClass, cfg.templates_not_using_page) then -- TODO: rewrite to emit ignored parameter error message?
if utilities.is_set (Network) then table.insert(n, Network); end
+
Page = A['Page'];
if utilities.is_set (Station) then table.insert(n, Station); end
+
Pages = utilities.hyphen_to_dash (A['Pages']);
ID = table.concat(n, sepc .. ' ');
+
At = A['At'];
+
QuotePage = A['QuotePage'];
if 'episode' == config.CitationClass then -- handle the oddities that are strictly {{cite episode}}
+
QuotePages = utilities.hyphen_to_dash (A['QuotePages']);
local Season = A['Season'];
+
end
local SeriesNumber = A['SeriesNumber'];
     −
if utilities.is_set (Season) and utilities.is_set (SeriesNumber) then -- these are mutually exclusive so if both are set TODO: make a function for this and similar?
+
local NoPP = is_valid_parameter_value (A['NoPP'], A:ORIGIN('NoPP'), cfg.keywords_lists['yes_true_y'], nil);
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'season') .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'seriesno')}); -- add error message
  −
SeriesNumber = ''; -- unset; prefer |season= over |seriesno=
  −
end
  −
-- assemble a table of parts concatenated later into Series
  −
if utilities.is_set (Season) then table.insert(s, wrap_msg ('season', Season, use_lowercase)); end
  −
if utilities.is_set (SeriesNumber) then table.insert(s, wrap_msg ('seriesnum', SeriesNumber, use_lowercase)); end
  −
if utilities.is_set (Issue) then table.insert(s, wrap_msg ('episode', Issue, use_lowercase)); end
  −
Issue = ''; -- unset because this is not a unique parameter
  −
  −
Chapter = Title; -- promote title parameters to chapter
  −
ScriptChapter = ScriptTitle;
  −
ScriptChapter_origin = A:ORIGIN('ScriptTitle');
  −
ChapterLink = TitleLink; -- alias |episode-link=
  −
TransChapter = TransTitle;
  −
ChapterURL = URL;
  −
ChapterUrlAccess = UrlAccess;
  −
ChapterURL_origin = URL_origin;
  −
ChapterFormat = Format;
     −
Title = Series; -- promote series to title
+
local Mode = mode_set (A['Mode'], A:ORIGIN('Mode'));
TitleLink = SeriesLink;
  −
Series = table.concat(s, sepc .. ' '); -- this is concatenation of season, seriesno, episode number
     −
if utilities.is_set (ChapterLink) and not utilities.is_set (ChapterURL) then -- link but not URL
+
-- separator character and postscript
Chapter = utilities.make_wikilink (ChapterLink, Chapter);
+
local sepc, PostScript = set_style (Mode:lower(), A['PostScript'], config.CitationClass);
elseif utilities.is_set (ChapterLink) and utilities.is_set (ChapterURL) then -- if both are set, URL links episode;
+
local Quote;
Series = utilities.make_wikilink (ChapterLink, Series);
+
Quote, QuotePage, QuotePages, PostScript = quote_make (A['Quote'], A['TransQuote'], A['ScriptQuote'], QuotePage, QuotePages, NoPP, sepc, PostScript);
end
  −
URL = ''; -- unset
  −
TransTitle = '';
  −
ScriptTitle = '';
  −
Format = '';
  −
  −
else -- now oddities that are cite serial
  −
Issue = ''; -- unset because this parameter no longer supported by the citation/core version of cite serial
  −
Chapter = A['Episode']; -- TODO: make |episode= available to cite episode someday?
  −
if utilities.is_set (Series) and utilities.is_set (SeriesLink) then
  −
Series = utilities.make_wikilink (SeriesLink, Series);
  −
end
  −
Series = utilities.wrap_style ('italic-title', Series); -- series is italicized
  −
end
  −
end
  −
-- end of {{cite episode}} stuff
     −
-- handle type parameter for those CS1 citations that have default values
+
local Edition = A['Edition'];
local TitleType = A['TitleType'];
+
local PublicationPlace = place_check (A['PublicationPlace'], A:ORIGIN('PublicationPlace'));
local Degree = A['Degree'];
+
local Place = place_check (A['Place'], A:ORIGIN('Place'));
if utilities.in_array (config.CitationClass, {'AV-media-notes', 'interview', 'mailinglist', 'map', 'podcast', 'pressrelease', 'report', 'speech', 'techreport', 'thesis'}) then
+
TitleType = set_titletype (config.CitationClass, TitleType);
+
local PublisherName = A['PublisherName'];
if utilities.is_set (Degree) and "Thesis" == TitleType then -- special case for cite thesis
+
local PublisherName_origin = A:ORIGIN('PublisherName');
TitleType = Degree .. ' ' .. cfg.title_types ['thesis']:lower();
+
if utilities.is_set (PublisherName) and (cfg.keywords_xlate['none'] ~= PublisherName) then
 +
local i = 0;
 +
PublisherName, i = utilities.strip_apostrophe_markup (PublisherName); -- strip apostrophe markup so that metadata isn't contaminated; publisher is never italicized
 +
if i and (0 < i) then -- non-zero when markup was stripped so emit an error message
 +
utilities.set_message ('err_apostrophe_markup', {PublisherName_origin});
 
end
 
end
 
end
 
end
 
+
if utilities.is_set (TitleType) then -- if type parameter is specified
+
if ('document' == config.CitationClass) and not utilities.is_set (PublisherName) then
TitleType = utilities.substitute ( cfg.messages['type'], TitleType); -- display it in parentheses
+
utilities.set_message ('err_missing_publisher', {config.CitationClass, 'publisher'});
-- TODO: Hack on TitleType to fix bunched parentheses problem
   
end
 
end
   −
-- legacy: promote PublicationDate to Date if neither Date nor Year are set.
+
local Newsgroup = A['Newsgroup']; -- TODO: strip apostrophe markup?
local Date = A['Date'];
+
local Newsgroup_origin = A:ORIGIN('Newsgroup');
local Date_origin; -- to hold the name of parameter promoted to Date; required for date error messaging
  −
local PublicationDate = A['PublicationDate'];
  −
local Year = A['Year'];
     −
if not utilities.is_set (Date) then
+
if 'newsgroup' == config.CitationClass then
Date = Year; -- promote Year to Date
+
if utilities.is_set (PublisherName) and (cfg.keywords_xlate['none'] ~= PublisherName) then -- general use parameter |publisher= not allowed in cite newsgroup
Year = nil; -- make nil so Year as empty string isn't used for CITEREF
+
utilities.set_message ('err_parameter_ignored', {PublisherName_origin});
if not utilities.is_set (Date) and utilities.is_set (PublicationDate) then -- use PublicationDate when |date= and |year= are not set
  −
Date = PublicationDate; -- promote PublicationDate to Date
  −
PublicationDate = ''; -- unset, no longer needed
  −
Date_origin = A:ORIGIN('PublicationDate'); -- save the name of the promoted parameter
  −
else
  −
Date_origin = A:ORIGIN('Year'); -- save the name of the promoted parameter
   
end
 
end
else
+
 
Date_origin = A:ORIGIN('Date'); -- not a promotion; name required for error messaging
+
PublisherName = nil; -- ensure that this parameter is unset for the time being; will be used again after COinS
 
end
 
end
   −
if PublicationDate == Date then PublicationDate = ''; end -- if PublicationDate is same as Date, don't display in rendered citation
+
if 'book' == config.CitationClass or 'encyclopaedia' == config.CitationClass or ('citation' == config.CitationClass and not utilities.is_set (Periodical)) then
 +
local accept;
 +
PublisherName, accept = utilities.has_accept_as_written (PublisherName); -- check for and remove accept-as-written markup from |publisher= wrapped
 +
if not accept then -- when no accept-as-written markup
 +
check_publisher_name (PublisherName); -- emit maint message when |publisher= might be prefixed with publisher's location
 +
end
 +
end
   −
--[[
+
local URL = A['URL']; -- TODO: better way to do this for URL, ChapterURL, and MapURL?
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
+
local UrlAccess = is_valid_parameter_value (A['UrlAccess'], A:ORIGIN('UrlAccess'), cfg.keywords_lists['url-access'], nil);
we get the date used in the metadata.
   
 
Date validation supporting code is in Module:Citation/CS1/Date_validation
+
if not utilities.is_set (URL) and utilities.is_set (UrlAccess) then
]]
+
UrlAccess = nil;
 
+
utilities.set_message ('err_param_access_requires_param', 'url');
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
 
end
 +
 +
local ChapterURL = A['ChapterURL'];
 +
local ChapterUrlAccess = is_valid_parameter_value (A['ChapterUrlAccess'], A:ORIGIN('ChapterUrlAccess'), cfg.keywords_lists['url-access'], nil);
 +
if not utilities.is_set (ChapterURL) and utilities.is_set (ChapterUrlAccess) then
 +
ChapterUrlAccess = nil;
 +
utilities.set_message ('err_param_access_requires_param', {A:ORIGIN('ChapterUrlAccess'):gsub ('%-access', '')});
 +
end
 +
 +
local MapUrlAccess = is_valid_parameter_value (A['MapUrlAccess'], A:ORIGIN('MapUrlAccess'), cfg.keywords_lists['url-access'], nil);
 +
if not utilities.is_set (A['MapURL']) and utilities.is_set (MapUrlAccess) then
 +
MapUrlAccess = nil;
 +
utilities.set_message ('err_param_access_requires_param', {'map-url'});
 +
end
 +
 +
local this_page = mw.title.getCurrentTitle(); -- also used for COinS and for language
 +
local no_tracking_cats = is_valid_parameter_value (A['NoTracking'], A:ORIGIN('NoTracking'), cfg.keywords_lists['yes_true_y'], nil);
   −
local ArchiveURL;
+
-- check this page to see if it is in one of the namespaces that cs1 is not supposed to add to the error categories
local ArchiveDate;
+
if not utilities.is_set (no_tracking_cats) then -- ignore if we are already not going to categorize this page
local ArchiveFormat = A['ArchiveFormat'];
+
if cfg.uncategorized_namespaces[this_page.namespace] then -- is this page's namespace id one of the uncategorized namespace ids?
 
+
no_tracking_cats = "true"; -- set no_tracking_cats
ArchiveURL, ArchiveDate = archive_url_check (A['ArchiveURL'], A['ArchiveDate'])
+
end
ArchiveFormat = style_format (ArchiveFormat, ArchiveURL, 'archive-format', 'archive-url');
+
for _, v in ipairs (cfg.uncategorized_subpages) do -- cycle through page name patterns
+
if this_page.text:match (v) then -- test page name against each pattern
ArchiveURL, ArchiveDate = is_unique_archive_url (ArchiveURL, URL, ChapterURL, A:ORIGIN('ArchiveURL'), ArchiveDate); -- add error message when URL or ChapterURL == ArchiveURL
+
no_tracking_cats = "true"; -- set no_tracking_cats
 +
break; -- bail out if one is found
 +
end
 +
end
 +
end
 +
-- check for extra |page=, |pages= or |at= parameters. (also sheet and sheets while we're at it)
 +
utilities.select_one (args, {'page', 'p', 'pp', 'pages', 'at', 'sheet', 'sheets'}, 'err_redundant_parameters'); -- this is a dummy call simply to get the error message and category
    +
local coins_pages;
 
 
local AccessDate = A['AccessDate'];
+
Page, Pages, At, coins_pages = insource_loc_get (Page, A:ORIGIN('Page'), Pages, A:ORIGIN('Pages'), At);
local LayDate = A['LayDate'];
  −
local COinS_date = {}; -- holds date info extracted from |date= for the COinS metadata by Module:Date verification
  −
local DoiBroken = A['DoiBroken'];
  −
local Embargo = A['Embargo'];
  −
local anchor_year; -- used in the CITEREF identifier
  −
do -- create defined block to contain local variables error_message, date_parameters_list, mismatch
  −
local error_message = '';
  −
-- AirDate has been promoted to Date so not necessary to check it
  −
local date_parameters_list = {
  −
['access-date'] = {val = AccessDate, name = A:ORIGIN ('AccessDate')},
  −
['archive-date'] = {val = ArchiveDate, name = A:ORIGIN ('ArchiveDate')},
  −
['date'] = {val = Date, name = Date_origin},
  −
['doi-broken-date'] = {val = DoiBroken, name = A:ORIGIN ('DoiBroken')},
  −
['pmc-embargo-date'] = {val = Embargo, name = A:ORIGIN ('Embargo')},
  −
['lay-date'] = {val = LayDate, name = A:ORIGIN ('LayDate')},
  −
['publication-date'] = {val = PublicationDate, name = A:ORIGIN ('PublicationDate')},
  −
['year'] = {val = Year, name = A:ORIGIN ('Year')},
  −
};
     −
local error_list = {};
+
if utilities.is_set (PublicationPlace) and utilities.is_set (Place) then -- both |publication-place= and |place= (|location=) allowed if different
anchor_year, Embargo = validation.dates(date_parameters_list, COinS_date, error_list);
+
utilities.add_prop_cat ('location-test'); -- add property cat to evaluate how often PublicationPlace and Place are used together
 
+
if PublicationPlace == Place then
-- start temporary Julian / Gregorian calendar uncertainty categorization
+
Place = ''; -- unset; don't need both if they are the same
if COinS_date.inter_cal_cat then
  −
utilities.add_prop_cat ('jul-greg-uncertainty');
   
end
 
end
-- end temporary Julian / Gregorian calendar uncertainty categorization
+
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 utilities.is_set (Year) and utilities.is_set (Date) then -- both |date= and |year= not normally needed;
+
if PublicationPlace == Place then Place = ''; end -- don't need both if they are the same
validation.year_date_check (Year, A:ORIGIN ('Year'), Date, A:ORIGIN ('Date'), error_list);
  −
end
  −
  −
if 0 == #error_list then -- error free dates only; 0 when error_list is empty
  −
local modified = false; -- flag
  −
  −
if utilities.is_set (DF) then -- if we need to reformat dates
  −
modified = validation.reformat_dates (date_parameters_list, DF); -- reformat to DF format, use long month names if appropriate
  −
end
     −
if true == validation.date_hyphen_to_dash (date_parameters_list) then -- convert hyphens to dashes where appropriate
+
local URL_origin = A:ORIGIN('URL'); -- get name of parameter that holds URL
modified = true;
+
local ChapterURL_origin = A:ORIGIN('ChapterURL'); -- get name of parameter that holds ChapterURL
utilities.set_message ('maint_date_format'); -- hyphens were converted so add maint category
+
local ScriptChapter = A['ScriptChapter'];
end
+
local ScriptChapter_origin = A:ORIGIN ('ScriptChapter');
+
local Format = A['Format'];
-- for those wikis that can and want to have English date names translated to the local language; not supported at en.wiki
+
local ChapterFormat = A['ChapterFormat'];
if cfg.date_name_auto_xlate_enable and validation.date_name_xlate (date_parameters_list, cfg.date_digit_auto_xlate_enable ) then
+
local TransChapter = A['TransChapter'];
utilities.set_message ('maint_date_auto_xlated'); -- add maint cat
+
local TransChapter_origin = A:ORIGIN ('TransChapter');
modified = true;
+
local TransTitle = A['TransTitle'];
end
+
local ScriptTitle = A['ScriptTitle'];
 +
 +
--[[
 +
Parameter remapping for cite encyclopedia:
 +
When the citation has these parameters:
 +
|encyclopedia= and |title= then map |title= to |article= and |encyclopedia= to |title= for rendering
 +
|encyclopedia= and |article= then map |encyclopedia= to |title= for rendering
 +
 
 +
|trans-title= maps to |trans-chapter= when |title= is re-mapped
 +
|url= maps to |chapter-url= when |title= is remapped
 +
 +
All other combinations of |encyclopedia=, |title=, and |article= are not modified
 +
 +
]]
 +
 
 +
local Encyclopedia = A['Encyclopedia']; -- used as a flag by this module and by ~/COinS
 +
local ScriptEncyclopedia = A['ScriptEncyclopedia'];
 +
local TransEncyclopedia = A['TransEncyclopedia'];
   −
if modified then -- if the date_parameters_list values were modified
+
if utilities.is_set (Encyclopedia) or utilities.is_set (ScriptEncyclopedia) then -- emit error message when Encyclopedia set but template is other than {{cite encyclopedia}} or {{citation}}
AccessDate = date_parameters_list['access-date'].val; -- overwrite date holding parameters with modified values
+
if 'encyclopaedia' ~= config.CitationClass and 'citation' ~= config.CitationClass then
ArchiveDate = date_parameters_list['archive-date'].val;
+
if utilities.is_set (Encyclopedia) then
Date = date_parameters_list['date'].val;
+
utilities.set_message ('err_parameter_ignored', {A:ORIGIN ('Encyclopedia')});
DoiBroken = date_parameters_list['doi-broken-date'].val;
+
else
LayDate = date_parameters_list['lay-date'].val;
+
utilities.set_message ('err_parameter_ignored', {A:ORIGIN ('ScriptEncyclopedia')});
PublicationDate = date_parameters_list['publication-date'].val;
   
end
 
end
else
+
Encyclopedia = nil; -- unset these because not supported by this template
utilities.set_message ('err_bad_date', {utilities.make_sep_list (#error_list, error_list)}); -- add this error message
+
ScriptEncyclopedia = nil;
 +
TransEncyclopedia = nil;
 
end
 
end
end -- end of do
+
elseif utilities.is_set (TransEncyclopedia) then
 +
utilities.set_message ('err_trans_missing_title', {'encyclopedia'});
 +
end
   −
local ID_list = {}; -- sequence table of rendered identifiers
+
if ('encyclopaedia' == config.CitationClass) or ('citation' == config.CitationClass and utilities.is_set (Encyclopedia)) then
local ID_list_coins = {}; -- table of identifiers and their values from args; key is same as cfg.id_handlers's key
+
if utilities.is_set (Periodical) and utilities.is_set (Encyclopedia) then -- when both parameters set emit an error message; {{citation}} only; Periodical not allowed in {{cite encyclopedia}}
local Class = A['Class']; -- arxiv class identifier
+
utilities.set_message ('err_periodical_ignored', {Periodical_origin});
+
end
local ID_support = {
  −
{A['ASINTLD'], 'ASIN', 'err_asintld_missing_asin', A:ORIGIN ('ASINTLD')},
  −
{DoiBroken, 'DOI', 'err_doibroken_missing_doi', A:ORIGIN ('DoiBroken')},
  −
{Embargo, 'PMC', 'err_embargo_missing_pmc', A:ORIGIN ('Embargo')},
  −
}
     −
ID_list, ID_list_coins = identifiers.identifier_lists_get (args, {DoiBroken = DoiBroken, ASINTLD = A['ASINTLD'], Embargo = Embargo, Class = Class}, ID_support);
+
if utilities.is_set (Encyclopedia) or utilities.is_set (ScriptEncyclopedia) then
 +
Periodical = Encyclopedia; -- error or no, set Periodical to Encyclopedia for rendering; {{citation}} could (not legitimately) have both; use Encyclopedia
 +
Periodical_origin = A:ORIGIN ('Encyclopedia');
 +
ScriptPeriodical = ScriptEncyclopedia;
 +
ScriptPeriodical_origin = A:ORIGIN ('ScriptEncyclopedia');
   −
-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, {{cite ssrn}}, before generation of COinS data.
+
if utilities.is_set (Title) or utilities.is_set (ScriptTitle) then
if utilities.in_array (config.CitationClass, whitelist.preprint_template_list) then -- |arxiv= or |eprint= required for cite arxiv; |biorxiv=, |citeseerx=, |ssrn= required for their templates
+
if not utilities.is_set (Chapter) then
if not (args[cfg.id_handlers[config.CitationClass:upper()].parameters[1]] or -- can't use ID_list_coins k/v table here because invalid parameters omitted
+
Chapter = Title; -- |encyclopedia= and |title= are set so map |title= params to |article= params for rendering
args[cfg.id_handlers[config.CitationClass:upper()].parameters[2]]) then -- which causes unexpected parameter missing error message
+
ScriptChapter = ScriptTitle;
utilities.set_message ('err_' .. config.CitationClass .. '_missing'); -- add error message
+
ScriptChapter_origin = A:ORIGIN('ScriptTitle')
 +
TransChapter = TransTitle;
 +
ChapterURL = URL;
 +
ChapterURL_origin = URL_origin;
 +
ChapterUrlAccess = UrlAccess;
 +
ChapterFormat = Format;
 +
 
 +
if not utilities.is_set (ChapterURL) and utilities.is_set (TitleLink) then
 +
Chapter = utilities.make_wikilink (TitleLink, Chapter);
 +
end
 +
Title = Periodical; -- now map |encyclopedia= params to |title= params for rendering
 +
ScriptTitle = ScriptPeriodical or '';
 +
TransTitle = TransEncyclopedia or '';
 +
Periodical = ''; -- redundant so unset
 +
ScriptPeriodical = '';
 +
URL = '';
 +
Format = '';
 +
TitleLink = '';
 +
end
 +
elseif utilities.is_set (Chapter) or utilities.is_set (ScriptChapter) then -- |title= not set
 +
Title = Periodical; -- |encyclopedia= set and |article= set so map |encyclopedia= to |title= for rendering
 +
ScriptTitle = ScriptPeriodical or '';
 +
TransTitle = TransEncyclopedia or '';
 +
Periodical = ''; -- redundant so unset
 +
ScriptPeriodical = '';
 +
end
 
end
 
end
 +
end
   −
Periodical = ({['arxiv'] = 'arXiv', ['biorxiv'] = 'bioRxiv', ['citeseerx'] = 'CiteSeerX', ['ssrn'] = 'Social Science Research Network'})[config.CitationClass];
+
-- special case for cite techreport.
 +
local ID = A['ID'];
 +
if (config.CitationClass == "techreport") then -- special case for cite techreport
 +
if utilities.is_set (A['Number']) then -- cite techreport uses 'number', which other citations alias to 'issue'
 +
if not utilities.is_set (ID) then -- can we use ID for the "number"?
 +
ID = A['Number']; -- yes, use it
 +
else -- ID has a value so emit error message
 +
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'id') .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'number')});
 +
end
 +
end
 
end
 
end
   −
-- Link the title of the work if no |url= was provided, but we have a |pmc= or a |doi= with |doi-access=free
+
-- Account for the oddity that is {{cite conference}}, before generation of COinS data.
 
+
local ChapterLink -- = A['ChapterLink']; -- deprecated as a parameter but still used internally by cite episode
if config.CitationClass == "journal" and not utilities.is_set (URL) and not utilities.is_set (TitleLink) and not utilities.in_array (cfg.keywords_xlate[Title], {'off', 'none'}) then -- TODO: remove 'none' once existing citations have been switched to 'off', so 'none' can be used as token for "no title" instead
+
local Conference = A['Conference'];
if 'none' ~= cfg.keywords_xlate[auto_select] then -- if auto-linking not disabled
+
local BookTitle = A['BookTitle'];
if identifiers.auto_link_urls[auto_select] then -- manual selection
+
local TransTitle_origin = A:ORIGIN ('TransTitle');
URL = identifiers.auto_link_urls[auto_select]; -- set URL to be the same as identifier's external link
+
if 'conference' == config.CitationClass then
URL_origin = cfg.id_handlers[auto_select:upper()].parameters[1]; -- set URL_origin to parameter name for use in error message if citation is missing a |title=
+
if utilities.is_set (BookTitle) then
elseif identifiers.auto_link_urls['pmc'] then -- auto-select PMC
+
Chapter = Title;
URL = identifiers.auto_link_urls['pmc']; -- set URL to be the same as the PMC external link if not embargoed
+
Chapter_origin = 'title';
URL_origin = cfg.id_handlers['PMC'].parameters[1]; -- set URL_origin to parameter name for use in error message if citation is missing a |title=
+
-- ChapterLink = TitleLink; -- |chapter-link= is deprecated
elseif identifiers.auto_link_urls['doi'] then -- auto-select DOI
+
ChapterURL = URL;
URL = identifiers.auto_link_urls['doi'];
+
ChapterUrlAccess = UrlAccess;
URL_origin = cfg.id_handlers['DOI'].parameters[1];
+
ChapterURL_origin = URL_origin;
end
+
URL_origin = '';
end
+
ChapterFormat = Format;
 
+
TransChapter = TransTitle;
if utilities.is_set (URL) then -- set when using an identifier-created URL
+
TransChapter_origin = TransTitle_origin;
if utilities.is_set (AccessDate) then -- |access-date= requires |url=; identifier-created URL is not |url=
+
Title = BookTitle;
utilities.set_message ('err_accessdate_missing_url'); -- add an error message
+
Format = '';
AccessDate = ''; -- unset
+
-- TitleLink = '';
end
+
TransTitle = '';
 
+
URL = '';
if utilities.is_set (ArchiveURL) then -- |archive-url= requires |url=; identifier-created URL is not |url=
  −
utilities.set_message ('err_archive_missing_url'); -- add an error message
  −
ArchiveURL = ''; -- unset
  −
end
   
end
 
end
 +
elseif 'speech' ~= config.CitationClass then
 +
Conference = ''; -- not cite conference or cite speech so make sure this is empty string
 
end
 
end
 +
 +
local use_lowercase = ( sepc == ',' ); -- controls capitalization of certain static text
 +
 +
-- cite map oddities
 +
local Cartography = "";
 +
local Scale = "";
 +
local Sheet = A['Sheet'] or '';
 +
local Sheets = A['Sheets'] or '';
 +
if config.CitationClass == "map" then
 +
if utilities.is_set (Chapter) then --TODO: make a function for this and similar?
 +
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'map') .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', Chapter_origin)}); -- add error message
 +
end
 +
Chapter = A['Map'];
 +
Chapter_origin = A:ORIGIN('Map');
 +
ChapterURL = A['MapURL'];
 +
ChapterURL_origin = A:ORIGIN('MapURL');
 +
TransChapter = A['TransMap'];
 +
ScriptChapter = A['ScriptMap']
 +
ScriptChapter_origin = A:ORIGIN('ScriptMap')
   −
-- At this point fields may be nil if they weren't specified in the template use.  We can use that fact.
+
ChapterUrlAccess = MapUrlAccess;
-- Test if citation has no title
+
ChapterFormat = A['MapFormat'];
if not utilities.is_set (Title) and not utilities.is_set (TransTitle) and not utilities.is_set (ScriptTitle) then -- has special case for cite episode
  −
utilities.set_message ('err_citation_missing_title', {'episode' == config.CitationClass and 'series' or 'title'});
  −
end
     −
if utilities.in_array (cfg.keywords_xlate[Title], {'off', 'none'}) and
+
Cartography = A['Cartography'];
utilities.in_array (config.CitationClass, {'journal', 'citation'}) and
+
if utilities.is_set ( Cartography ) then
(utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) and
+
Cartography = sepc .. " " .. wrap_msg ('cartography', Cartography, use_lowercase);
('journal' == Periodical_origin or 'script-journal' == ScriptPeriodical_origin) then -- special case for journal cites
+
end
Title = ''; -- set title to empty string
+
Scale = A['Scale'];
utilities.set_message ('maint_untitled'); -- add maint cat
+
if utilities.is_set ( Scale ) then
 +
Scale = sepc .. " " .. Scale;
 +
end
 
end
 
end
   −
-- COinS metadata (see <http://ocoins.info/>) for automated parsing of citation information.
+
-- Account for the oddities that are {{cite episode}} and {{cite serial}}, before generation of COinS data.
-- handle the oddity that is cite encyclopedia and {{citation |encyclopedia=something}}. Here we presume that
+
local Series = A['Series'];
-- when Periodical, Title, and Chapter are all set, then Periodical is the book (encyclopedia) title, Title
+
if 'episode' == config.CitationClass or 'serial' == config.CitationClass then
-- is the article title, and Chapter is a section within the article.  So, we remap
+
local SeriesLink = A['SeriesLink'];
 +
 
 +
SeriesLink = link_title_ok (SeriesLink, A:ORIGIN ('SeriesLink'), Series, 'series'); -- check for wiki-markup in |series-link= or wiki-markup in |series= when |series-link= is 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 utilities.is_set (Station) then table.insert(n, Station); end
 +
ID = table.concat(n, sepc .. ' ');
 +
 +
if 'episode' == config.CitationClass then -- handle the oddities that are strictly {{cite episode}}
 +
local Season = A['Season'];
 +
local SeriesNumber = A['SeriesNumber'];
 +
 
 +
if utilities.is_set (Season) and utilities.is_set (SeriesNumber) then -- these are mutually exclusive so if both are set TODO: make a function for this and similar?
 +
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'season') .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'seriesno')}); -- add error message
 +
SeriesNumber = ''; -- unset; prefer |season= over |seriesno=
 +
end
 +
-- assemble a table of parts concatenated later into Series
 +
if utilities.is_set (Season) then table.insert(s, wrap_msg ('season', Season, use_lowercase)); end
 +
if utilities.is_set (SeriesNumber) then table.insert(s, wrap_msg ('seriesnum', SeriesNumber, use_lowercase)); end
 +
if utilities.is_set (Issue) then table.insert(s, wrap_msg ('episode', Issue, use_lowercase)); end
 +
Issue = ''; -- unset because this is not a unique parameter
 
 
local coins_chapter = Chapter; -- default assuming that remapping not required
+
Chapter = Title; -- promote title parameters to chapter
local coins_title = Title; -- et tu
+
ScriptChapter = ScriptTitle;
if 'encyclopaedia' == config.CitationClass or ('citation' == config.CitationClass and utilities.is_set (Encyclopedia)) then
+
ScriptChapter_origin = A:ORIGIN('ScriptTitle');
if utilities.is_set (Chapter) and utilities.is_set (Title) and utilities.is_set (Periodical) then -- if all are used then
+
ChapterLink = TitleLink; -- alias |episode-link=
coins_chapter = Title; -- remap
+
TransChapter = TransTitle;
coins_title = Periodical;
+
ChapterURL = URL;
 +
ChapterUrlAccess = UrlAccess;
 +
ChapterURL_origin = URL_origin;
 +
ChapterFormat = Format;
 +
 
 +
Title = Series; -- promote series to title
 +
TitleLink = SeriesLink;
 +
Series = table.concat(s, sepc .. ' '); -- 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.make_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
 +
URL = ''; -- unset
 +
TransTitle = '';
 +
ScriptTitle = '';
 +
Format = '';
 +
 +
else -- now oddities that are cite serial
 +
Issue = ''; -- unset because this parameter no longer supported by the citation/core version of cite serial
 +
Chapter = A['Episode']; -- TODO: make |episode= available to cite episode someday?
 +
if utilities.is_set (Series) and utilities.is_set (SeriesLink) then
 +
Series = utilities.make_wikilink (SeriesLink, Series);
 +
end
 +
Series = utilities.wrap_style ('italic-title', Series); -- series is italicized
 +
end
 +
end
 +
-- end of {{cite episode}} stuff
 +
 
 +
-- handle type parameter for those CS1 citations that have default values
 +
local TitleType = A['TitleType'];
 +
local Degree = A['Degree'];
 +
if utilities.in_array (config.CitationClass, {'AV-media-notes', 'document', 'interview', 'mailinglist', 'map', 'podcast', 'pressrelease', 'report', 'speech', 'techreport', 'thesis'}) then
 +
TitleType = set_titletype (config.CitationClass, TitleType);
 +
if utilities.is_set (Degree) and "Thesis" == TitleType then -- special case for cite thesis
 +
TitleType = Degree .. ' ' .. cfg.title_types ['thesis']:lower();
 
end
 
end
 
end
 
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 / accept-as-written markup
  −
['Degree'] = Degree; -- cite thesis only
  −
['Title'] = metadata.make_coins_title (coins_title, ScriptTitle), -- Title and ScriptTitle stripped of bold / italic / accept-as-written 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,
  −
['ArticleNumber'] = ArticleNumber,
  −
['Pages'] = coins_pages or metadata.get_coins_pages (first_set ({Sheet, Sheets, Page, Pages, At, QuotePage, QuotePages}, 7)), -- 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);
     −
-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, and {{cite ssrn}} AFTER generation of COinS data.
+
if utilities.is_set (TitleType) then -- if type parameter is specified
if utilities.in_array (config.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
+
TitleType = utilities.substitute ( cfg.messages['type'], TitleType); -- display it in parentheses
Periodical = ''; -- periodical not allowed in these templates; if article has been published, use cite journal
+
-- TODO: Hack on TitleType to fix bunched parentheses problem
end
+
end
 +
 
 +
-- legacy: promote PublicationDate to Date if neither Date nor Year are set.
 +
local Date = A['Date'];
 +
local Date_origin; -- to hold the name of parameter promoted to Date; required for date error messaging
 +
local PublicationDate = A['PublicationDate'];
 +
local Year = A['Year'];
   −
-- special case for cite newsgroup.  Do this after COinS because we are modifying Publishername to include some static text
+
if utilities.is_set (Year) then
if 'newsgroup' == config.CitationClass and utilities.is_set (Newsgroup) then
+
validation.year_check (Year); -- returns nothing; emits maint message when |year= doesn't hold a 'year' value
PublisherName = utilities.substitute (cfg.messages['newsgroup'], external_link( 'news:' .. Newsgroup, Newsgroup, Newsgroup_origin, nil ));
+
end
 +
 +
if not utilities.is_set (Date) then
 +
Date = Year; -- promote Year to Date
 +
Year = nil; -- make nil so Year as empty string isn't used for CITEREF
 +
if not utilities.is_set (Date) and utilities.is_set (PublicationDate) then -- use PublicationDate when |date= and |year= are not set
 +
Date = PublicationDate; -- promote PublicationDate to Date
 +
PublicationDate = ''; -- unset, no longer needed
 +
Date_origin = A:ORIGIN('PublicationDate'); -- save the name of the promoted parameter
 +
else
 +
Date_origin = A:ORIGIN('Year'); -- save the name of the promoted parameter
 +
end
 +
else
 +
Date_origin = A:ORIGIN('Date'); -- not a promotion; name required for error messaging
 
end
 
end
   −
local Editors;
+
if PublicationDate == Date then PublicationDate = ''; end -- if PublicationDate is same as Date, don't display in rendered citation
local EditorCount; -- used only for choosing {ed.) or (eds.) annotation at end of editor name-list
  −
local Contributors; -- assembled contributors name list
  −
local contributor_etal;
  −
local Translators; -- assembled translators name list
  −
local translator_etal;
  −
local t = {}; -- translators list from |translator-lastn= / translator-firstn= pairs
  −
t = extract_names (args, 'TranslatorList'); -- fetch translator list from |translatorn= / |translator-lastn=, -firstn=, -linkn=, -maskn=
  −
local Interviewers;
  −
local interviewers_list = {};
  −
interviewers_list = extract_names (args, 'InterviewerList'); -- process preferred interviewers parameters
  −
local interviewer_etal;
  −
  −
-- Now perform various field substitutions.
  −
-- We also add leading spaces and surrounding markup and punctuation to the
  −
-- various parts of the citation, but only when they are non-nil.
  −
do
  −
local last_first_list;
  −
local control = {
  −
format = NameListStyle, -- empty string or 'vanc'
  −
maximum = nil, -- as if display-authors or display-editors not set
  −
mode = Mode
  −
};
  −
 
  −
do -- do editor name list first because the now unsupported coauthors used to modify control table
  −
control.maximum , editor_etal = get_display_names (A['DisplayEditors'], #e, 'editors', editor_etal, A:ORIGIN ('DisplayEditors'));
  −
Editors, EditorCount = list_people (control, e, editor_etal);
     −
if 1 == EditorCount and (true == editor_etal or 1 < #e) then -- only one editor displayed but includes etal then
+
--[[
EditorCount = 2; -- spoof to display (eds.) annotation
+
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
end
+
we get the date used in the metadata.
end
+
do -- now do interviewers
+
Date validation supporting code is in Module:Citation/CS1/Date_validation
control.maximum, interviewer_etal = get_display_names (A['DisplayInterviewers'], #interviewers_list, 'interviewers', interviewer_etal, A:ORIGIN ('DisplayInterviewers'));
+
]]
Interviewers = list_people (control, interviewers_list, interviewer_etal);
  −
end
  −
do -- now do translators
  −
control.maximum, translator_etal = get_display_names (A['DisplayTranslators'], #t, 'translators', translator_etal, A:ORIGIN ('DisplayTranslators'));
  −
Translators = list_people (control, t, translator_etal);
  −
end
  −
do -- now do contributors
  −
control.maximum, contributor_etal = get_display_names (A['DisplayContributors'], #c, 'contributors', contributor_etal, A:ORIGIN ('DisplayContributors'));
  −
Contributors = list_people (control, c, contributor_etal);
  −
end
  −
do -- now do authors
  −
control.maximum, author_etal = get_display_names (A['DisplayAuthors'], #a, 'authors', author_etal, A:ORIGIN ('DisplayAuthors'));
     −
last_first_list = list_people (control, a, author_etal);
+
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
   −
if utilities.is_set (Authors) then
+
local ArchiveURL;
Authors, author_etal = name_has_etal (Authors, author_etal, false, 'authors'); -- find and remove variations on et al.
+
local ArchiveDate;
if author_etal then
+
local ArchiveFormat = A['ArchiveFormat'];
Authors = Authors .. ' ' .. cfg.messages['et al']; -- add et al. to authors parameter
+
local archive_url_timestamp; -- timestamp from wayback machine url
end
+
else
+
ArchiveURL, ArchiveDate, archive_url_timestamp = archive_url_check (A['ArchiveURL'], A['ArchiveDate'])
Authors = last_first_list; -- either an author name list or an empty string
+
ArchiveFormat = style_format (ArchiveFormat, ArchiveURL, 'archive-format', 'archive-url');
end
  −
end -- end of do
   
 
if utilities.is_set (Authors) and utilities.is_set (Collaboration) then
+
ArchiveURL, ArchiveDate = is_unique_archive_url (ArchiveURL, URL, ChapterURL, A:ORIGIN('ArchiveURL'), ArchiveDate); -- add error message when URL or ChapterURL == ArchiveURL
Authors = Authors .. ' (' .. Collaboration .. ')'; -- add collaboration after et al.
  −
end
     −
end
+
local AccessDate = A['AccessDate'];
 
+
local COinS_date = {}; -- holds date info extracted from |date= for the COinS metadata by Module:Date verification
local ConferenceFormat = A['ConferenceFormat'];
+
local DoiBroken = A['DoiBroken'];
local ConferenceURL = A['ConferenceURL'];
+
local Embargo = A['Embargo'];
ConferenceFormat = style_format (ConferenceFormat, ConferenceURL, 'conference-format', 'conference-url');
+
local anchor_year; -- used in the CITEREF identifier
Format = style_format (Format, URL, 'format', 'url');
+
do -- create defined block to contain local variables error_message, date_parameters_list, mismatch
 +
local error_message = '';
 +
-- AirDate has been promoted to Date so not necessary to check it
 +
local date_parameters_list = {
 +
['access-date'] = {val = AccessDate, name = A:ORIGIN ('AccessDate')},
 +
['archive-date'] = {val = ArchiveDate, name = A:ORIGIN ('ArchiveDate')},
 +
['date'] = {val = Date, name = Date_origin},
 +
['doi-broken-date'] = {val = DoiBroken, name = A:ORIGIN ('DoiBroken')},
 +
['pmc-embargo-date'] = {val = Embargo, name = A:ORIGIN ('Embargo')},
 +
['publication-date'] = {val = PublicationDate, name = A:ORIGIN ('PublicationDate')},
 +
['year'] = {val = Year, name = A:ORIGIN ('Year')},
 +
};
   −
-- special case for chapter format so no error message or cat when chapter not supported
+
local error_list = {};
if not (utilities.in_array (config.CitationClass, {'web', 'news', 'journal', 'magazine', 'pressrelease', 'podcast', 'newsgroup', 'arxiv', 'biorxiv', 'citeseerx', 'ssrn'}) or
+
anchor_year, Embargo = validation.dates(date_parameters_list, COinS_date, error_list);
('citation' == config.CitationClass and (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) and not utilities.is_set (Encyclopedia))) then
  −
ChapterFormat = style_format (ChapterFormat, ChapterURL, 'chapter-format', 'chapter-url');
  −
end
     −
if not utilities.is_set (URL) then
+
if utilities.is_set (Year) and utilities.is_set (Date) then -- both |date= and |year= not normally needed;
if utilities.in_array (config.CitationClass, {"web", "podcast", "mailinglist"}) or -- |url= required for cite web, cite podcast, and cite mailinglist
+
validation.year_date_check (Year, A:ORIGIN ('Year'), Date, A:ORIGIN ('Date'), error_list);
('citation' == config.CitationClass and ('website' == Periodical_origin or 'script-website' == ScriptPeriodical_origin)) then -- and required for {{citation}} with |website= or |script-website=
  −
utilities.set_message ('err_cite_web_url');
   
end
 
end
  −
-- do we have |accessdate= without either |url= or |chapter-url=?
  −
if utilities.is_set (AccessDate) and not utilities.is_set (ChapterURL) then -- ChapterURL may be set when URL is not set;
  −
utilities.set_message ('err_accessdate_missing_url');
  −
AccessDate = '';
  −
end
  −
end
     −
local UrlStatus = is_valid_parameter_value (A['UrlStatus'], A:ORIGIN('UrlStatus'), cfg.keywords_lists['url-status'], '');
+
if 0 == #error_list then -- error free dates only; 0 when error_list is empty
local OriginalURL
+
local modified = false; -- flag
local OriginalURL_origin
+
local OriginalFormat
+
if utilities.is_set (DF) then -- if we need to reformat dates
local OriginalAccess;
+
modified = validation.reformat_dates (date_parameters_list, DF); -- reformat to DF format, use long month names if appropriate
UrlStatus = UrlStatus:lower(); -- used later when assembling archived text
+
end
if utilities.is_set ( ArchiveURL ) then
  −
if utilities.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
  −
OriginalURL_origin = ChapterURL_origin; -- name of |chapter-url= parameter for error messages
  −
OriginalFormat = ChapterFormat; -- and original |chapter-format=
     −
if 'live' ~= UrlStatus then
+
if true == validation.date_hyphen_to_dash (date_parameters_list) then -- convert hyphens to dashes where appropriate
ChapterURL = ArchiveURL -- swap-in the archive's URL
+
modified = true;
ChapterURL_origin = A:ORIGIN('ArchiveURL') -- name of |archive-url= parameter for error messages
+
utilities.set_message ('maint_date_format'); -- hyphens were converted so add maint category
ChapterFormat = ArchiveFormat or ''; -- swap in archive's format
  −
ChapterUrlAccess = nil; -- restricted access levels do not make sense for archived URLs
   
end
 
end
elseif utilities.is_set (URL) then
+
OriginalURL = URL; -- save copy of original source URL
+
-- for those wikis that can and want to have English date names translated to the local language; not supported at en.wiki
OriginalURL_origin = URL_origin; -- name of URL parameter for error messages
+
if cfg.date_name_auto_xlate_enable and validation.date_name_xlate (date_parameters_list, cfg.date_digit_auto_xlate_enable ) then
OriginalFormat = Format; -- and original |format=
+
utilities.set_message ('maint_date_auto_xlated'); -- add maint cat
OriginalAccess = UrlAccess;
+
modified = true;
 
  −
if 'live' ~= UrlStatus then -- if URL set then |archive-url= applies to it
  −
URL = ArchiveURL -- swap-in the archive's URL
  −
URL_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
   
end
 
end
end
  −
elseif utilities.is_set (UrlStatus) then -- if |url-status= is set when |archive-url= is not set
  −
utilities.set_message ('maint_url_status'); -- add maint cat
  −
end
     −
if utilities.in_array (config.CitationClass, {'web', 'news', 'journal', 'magazine', 'pressrelease', 'podcast', 'newsgroup', 'arxiv', 'biorxiv', 'citeseerx', 'ssrn'}) or -- if any of the 'periodical' cites except encyclopedia
+
if modified then -- if the date_parameters_list values were modified
('citation' == config.CitationClass and (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) and not utilities.is_set (Encyclopedia)) then
+
AccessDate = date_parameters_list['access-date'].val; -- overwrite date holding parameters with modified values
local chap_param;
+
ArchiveDate = date_parameters_list['archive-date'].val;
if utilities.is_set (Chapter) then -- get a parameter name from one of these chapter related meta-parameters
+
Date = date_parameters_list['date'].val;
chap_param = A:ORIGIN ('Chapter')
+
DoiBroken = date_parameters_list['doi-broken-date'].val;
elseif utilities.is_set (TransChapter) then
+
PublicationDate = date_parameters_list['publication-date'].val;
chap_param = A:ORIGIN ('TransChapter')
  −
elseif utilities.is_set (ChapterURL) then
  −
chap_param = A:ORIGIN ('ChapterURL')
  −
elseif utilities.is_set (ScriptChapter) then
  −
chap_param = ScriptChapter_origin;
  −
else utilities.is_set (ChapterFormat)
  −
chap_param = A:ORIGIN ('ChapterFormat')
   
end
 
end
   −
if utilities.is_set (chap_param) then -- if we found one
+
if archive_url_timestamp and utilities.is_set (ArchiveDate) then
utilities.set_message ('err_chapter_ignored', {chap_param}); -- add error message
+
validation.archive_date_check (ArchiveDate, archive_url_timestamp, DF); -- does YYYYMMDD in archive_url_timestamp match date in ArchiveDate
Chapter = ''; -- and set them to empty string to be safe with concatenation
  −
TransChapter = '';
  −
ChapterURL = '';
  −
ScriptChapter = '';
  −
ChapterFormat = '';
  −
end
  −
else -- otherwise, format chapter / article title
  −
local no_quotes = false; -- default assume that we will be quoting the chapter parameter value
  −
if utilities.is_set (Contribution) and 0 < #c then -- if this is a contribution with contributor(s)
  −
if utilities.in_array (Contribution:lower(), cfg.keywords_lists.contribution) then -- and a generic contribution title
  −
no_quotes = true; -- then render it unquoted
   
end
 
end
 +
else
 +
utilities.set_message ('err_bad_date', {utilities.make_sep_list (#error_list, error_list)}); -- add this error message
 
end
 
end
 +
end -- end of do
   −
Chapter = format_chapter_title (ScriptChapter, ScriptChapter_origin, Chapter, Chapter_origin, TransChapter, TransChapter_origin, ChapterURL, ChapterURL_origin, no_quotes, ChapterUrlAccess); -- Contribution is also in Chapter
+
if utilities.in_array (config.CitationClass, {'book', 'encyclopaedia'}) or -- {{cite book}}, {{cite encyclopedia}}; TODO: {{cite conference}} and others?
if utilities.is_set (Chapter) then
+
('citation' == config.CitationClass and utilities.is_set (Encyclopedia)) or -- {{citation}} as an encylopedia citation
Chapter = Chapter .. ChapterFormat ;
+
('citation' == config.CitationClass and not utilities.is_set (Periodical)) then -- {{citation}} as a book citation
if 'map' == config.CitationClass and utilities.is_set (TitleType) then
+
if utilities.is_set (PublicationPlace) then
Chapter = Chapter .. ' ' .. TitleType; -- map annotation here; not after title
+
if not utilities.is_set (PublisherName) then
 +
local date = COinS_date.rftdate and tonumber (COinS_date.rftdate:match ('%d%d%d%d')); -- get year portion of COinS date (because in Arabic numerals); convert string to number
 +
if date and (1850 <= date) then -- location has no publisher; if date is 1850 or later
 +
utilities.set_message ('maint_location_no_publisher'); -- add maint cat
 +
end
 +
else -- PublisherName has a value
 +
if cfg.keywords_xlate['none'] == PublisherName then -- if that value is 'none' (only for book and encyclopedia citations)
 +
PublisherName = ''; -- unset
 +
end
 +
end
 
end
 
end
Chapter = Chapter .. sepc .. ' ';
  −
elseif utilities.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
 
end
   −
-- Format main title
+
local ID_list = {}; -- sequence table of rendered identifiers
local plain_title = false;
+
local ID_list_coins = {}; -- table of identifiers and their values from args; key is same as cfg.id_handlers's key
local accept_title;
+
local Class = A['Class']; -- arxiv class identifier
Title, accept_title = utilities.has_accept_as_written (Title, true); -- remove accept-this-as-written markup when it wraps all of <Title>
+
if accept_title and ('' == Title) then -- only support forced empty for now "(())"
+
local ID_support = {
Title = cfg.messages['notitle']; -- replace by predefined "No title" message
+
{A['ASINTLD'], 'ASIN', 'err_asintld_missing_asin', A:ORIGIN ('ASINTLD')},
-- TODO: utilities.set_message ( 'err_redundant_parameters', ...); -- issue proper error message instead of muting
+
{DoiBroken, 'DOI', 'err_doibroken_missing_doi', A:ORIGIN ('DoiBroken')},
ScriptTitle = ''; -- just mute for now
+
{Embargo, 'PMC', 'err_embargo_missing_pmc', A:ORIGIN ('Embargo')},
TransTitle = ''; -- just mute for now
+
}
plain_title = true; -- suppress text decoration for descriptive title
  −
utilities.set_message ('maint_untitled'); -- add maint cat
  −
end
     −
if not accept_title then -- <Title> not wrapped in accept-as-written markup
+
ID_list, ID_list_coins = identifiers.identifier_lists_get (args, {DoiBroken = DoiBroken, ASINTLD = A['ASINTLD'], Embargo = Embargo, Class = Class, Year=anchor_year}, ID_support);
if '...' == Title:sub (-3) then -- if ellipsis is the last three characters of |title=
  −
Title = Title:gsub ('(%.%.%.)%.+$', '%1'); -- limit the number of dots to three
  −
elseif not mw.ustring.find (Title, '%.%s*%a%.$') and -- end of title is not a 'dot-(optional space-)letter-dot' initialism ...
  −
not mw.ustring.find (Title, '%s+%a%.$') then -- ...and not a 'space-letter-dot' initial (''Allium canadense'' L.)
  −
Title = mw.ustring.gsub(Title, '%' .. sepc .. '$', ''); -- remove any trailing separator character; sepc and ms.ustring() here for languages that use multibyte separator characters
  −
end
     −
if utilities.is_set (ArchiveURL) and is_archived_copy (Title) then
+
-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, {{cite medrxiv}}, {{cite ssrn}}, before generation of COinS data.
utilities.set_message ('maint_archived_copy'); -- add maintenance category before we modify the content of Title
+
if utilities.in_array (config.CitationClass, whitelist.preprint_template_list_t) then -- |arxiv= or |eprint= required for cite arxiv; |biorxiv=, |citeseerx=, |medrxiv=, |ssrn= required for their templates
 +
if not (args[cfg.id_handlers[config.CitationClass:upper()].parameters[1]] or -- can't use ID_list_coins k/v table here because invalid parameters omitted
 +
args[cfg.id_handlers[config.CitationClass:upper()].parameters[2]]) then -- which causes unexpected parameter missing error message
 +
utilities.set_message ('err_' .. config.CitationClass .. '_missing'); -- add error message
 
end
 
end
   −
if is_generic ('generic_titles', Title) then
+
Periodical = ({['arxiv'] = 'arXiv', ['biorxiv'] = 'bioRxiv', ['citeseerx'] = 'CiteSeerX', ['medrxiv'] = 'medRxiv', ['ssrn'] = 'Social Science Research Network'})[config.CitationClass];
utilities.set_message ('err_generic_title'); -- set an error message
  −
end
   
end
 
end
   −
if (not plain_title) and (utilities.in_array (config.CitationClass, {'web', 'news', 'journal', 'magazine', 'pressrelease', 'podcast', 'newsgroup', 'mailinglist', 'interview', 'arxiv', 'biorxiv', 'citeseerx', 'ssrn'}) or
+
-- Link the title of the work if no |url= was provided, but we have a |pmc= or a |doi= with |doi-access=free
('citation' == 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 map when the map is in a periodical treat as an article
+
if config.CitationClass == "journal" and not utilities.is_set (URL) and not utilities.is_set (TitleLink) and not utilities.in_array (cfg.keywords_xlate[Title], {'off', 'none'}) then -- TODO: remove 'none' once existing citations have been switched to 'off', so 'none' can be used as token for "no title" instead
Title = kern_quotes (Title); -- if necessary, separate title's leading and trailing quote marks from module provided quote marks
+
if 'none' ~= cfg.keywords_xlate[auto_select] then -- if auto-linking not disabled
Title = utilities.wrap_style ('quoted-title', Title);
+
if identifiers.auto_link_urls[auto_select] then -- manual selection
Title = script_concatenate (Title, ScriptTitle, 'script-title'); -- <bdi> tags, lang attribute, categorization, etc.; must be done after title is wrapped
+
URL = identifiers.auto_link_urls[auto_select]; -- set URL to be the same as identifier's external link
TransTitle = utilities.wrap_style ('trans-quoted-title', TransTitle );
+
URL_origin = cfg.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 plain_title or ('report' == config.CitationClass) then -- no styling for cite report and descriptive titles (otherwise same as above)
+
elseif identifiers.auto_link_urls['pmc'] then -- auto-select PMC
Title = script_concatenate (Title, ScriptTitle, 'script-title'); -- <bdi> tags, lang attribute, categorization, etc.; must be done after title is wrapped
+
URL = identifiers.auto_link_urls['pmc']; -- set URL to be the same as the PMC external link if not embargoed
TransTitle = utilities.wrap_style ('trans-quoted-title', TransTitle ); -- for cite report, use this form for trans-title
+
URL_origin = cfg.id_handlers['PMC'].parameters[1]; -- set URL_origin to parameter name for use in error message if citation is missing a |title=
else
+
elseif identifiers.auto_link_urls['doi'] then -- auto-select DOI
Title = utilities.wrap_style ('italic-title', Title);
+
URL = identifiers.auto_link_urls['doi'];
Title = script_concatenate (Title, ScriptTitle, 'script-title'); -- <bdi> tags, lang attribute, categorization, etc.; must be done after title is wrapped
+
URL_origin = cfg.id_handlers['DOI'].parameters[1];
TransTitle = utilities.wrap_style ('trans-italic-title', TransTitle);
+
end
end
+
end
 +
 
 +
if utilities.is_set (URL) then -- set when using an identifier-created URL
 +
if utilities.is_set (AccessDate) then -- |access-date= requires |url=; identifier-created URL is not |url=
 +
utilities.set_message ('err_accessdate_missing_url'); -- add an error message
 +
AccessDate = ''; -- unset
 +
end
   −
if utilities.is_set (TransTitle) then
+
if utilities.is_set (ArchiveURL) then -- |archive-url= requires |url=; identifier-created URL is not |url=
if utilities.is_set (Title) then
+
utilities.set_message ('err_archive_missing_url'); -- add an error message
TransTitle = " " .. TransTitle;
+
ArchiveURL = ''; -- unset
else
+
end
utilities.set_message ('err_trans_missing_title', {'title'});
   
end
 
end
 
end
 
end
   −
if utilities.is_set (Title) then -- TODO: is this the right place to be making Wikisource URLs?
+
-- At this point fields may be nil if they weren't specified in the template use. We can use that fact.
if utilities.is_set (TitleLink) and utilities.is_set (URL) then
+
-- Test if citation has no title
utilities.set_message ('err_wikilink_in_url'); -- set an error message because we can't have both
+
if not utilities.is_set (Title) and not utilities.is_set (TransTitle) and not utilities.is_set (ScriptTitle) then -- has special case for cite episode
TitleLink = ''; -- unset
+
utilities.set_message ('err_citation_missing_title', {'episode' == config.CitationClass and 'series' or 'title'});
end
  −
  −
if not utilities.is_set (TitleLink) and utilities.is_set (URL) then
  −
Title = external_link (URL, Title, URL_origin, UrlAccess) .. TransTitle .. Format;
  −
URL = ''; -- unset these because no longer needed
  −
Format = "";
  −
elseif utilities.is_set (TitleLink) and not utilities.is_set (URL) then
  −
local ws_url;
  −
ws_url = wikisource_url_make (TitleLink); -- ignore ws_label return; not used here
  −
if ws_url then
  −
Title = external_link (ws_url, Title .. '&nbsp;', 'ws link in title-link'); -- 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'], {cfg.presentation['class-wikisource'], TitleLink, Title});
  −
Title = Title .. TransTitle;
  −
else
  −
Title = utilities.make_wikilink (TitleLink, Title) .. TransTitle;
  −
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 (not a wikilink)
  −
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 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'], {cfg.presentation['class-wikisource'], L, Title});
  −
Title = Title .. TransTitle;
  −
else
  −
Title = Title .. TransTitle;
  −
end
  −
end
  −
else
  −
Title = TransTitle;
   
end
 
end
   −
if utilities.is_set (Place) then
+
if utilities.in_array (cfg.keywords_xlate[Title], {'off', 'none'}) and
Place = " " .. wrap_msg ('written', Place, use_lowercase) .. sepc .. " ";
+
utilities.in_array (config.CitationClass, {'journal', 'citation'}) and
 +
(utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) and
 +
('journal' == Periodical_origin or 'script-journal' == ScriptPeriodical_origin) then -- special case for journal cites
 +
Title = ''; -- set title to empty string
 +
utilities.set_message ('maint_untitled'); -- add maint cat
 
end
 
end
   −
local ConferenceURL_origin = A:ORIGIN('ConferenceURL'); -- get name of parameter that holds ConferenceURL
+
-- COinS metadata (see <http://ocoins.info/>) for automated parsing of citation information.
if utilities.is_set (Conference) then
+
-- handle the oddity that is cite encyclopedia and {{citation |encyclopedia=something}}. Here we presume that
if utilities.is_set (ConferenceURL) then
+
-- when Periodical, Title, and Chapter are all set, then Periodical is the book (encyclopedia) title, Title
Conference = external_link( ConferenceURL, Conference, ConferenceURL_origin, nil );
+
-- 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 (Encyclopedia)) then
 +
if utilities.is_set (Chapter) and utilities.is_set (Title) and utilities.is_set (Periodical) then -- if all are used then
 +
coins_chapter = Title; -- remap
 +
coins_title = Periodical;
 
end
 
end
Conference = sepc .. " " .. Conference .. ConferenceFormat;
  −
elseif utilities.is_set (ConferenceURL) then
  −
Conference = sepc .. " " .. external_link( ConferenceURL, nil, ConferenceURL_origin, nil );
   
end
 
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 / accept-as-written markup
 +
['Degree'] = Degree; -- cite thesis only
 +
['Title'] = metadata.make_coins_title (coins_title, ScriptTitle), -- Title and ScriptTitle stripped of bold / italic / accept-as-written markup
 +
['PublicationPlace'] = PublicationPlace,
 +
['Date'] = COinS_date.rftdate, -- COinS_date.* has correctly formatted date values if Date is valid;
 +
['Season'] = COinS_date.rftssn,
 +
['Quarter'] = COinS_date.rftquarter,
 +
['Chron'] =  COinS_date.rftchron,
 +
['Series'] = Series,
 +
['Volume'] = Volume,
 +
['Issue'] = Issue,
 +
['ArticleNumber'] = ArticleNumber,
 +
['Pages'] = coins_pages or metadata.get_coins_pages (first_set ({Sheet, Sheets, Page, Pages, At, QuotePage, QuotePages}, 7)), -- 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 Position = '';
+
-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, {{cite medrxiv}}, and {{cite ssrn}} AFTER generation of COinS data.
if not utilities.is_set (Position) then
+
if utilities.in_array (config.CitationClass, whitelist.preprint_template_list_t) then -- we have set rft.jtitle in COinS to arXiv, bioRxiv, CiteSeerX, medRxiv, or ssrn now unset so it isn't displayed
local Minutes = A['Minutes'];
+
Periodical = ''; -- periodical not allowed in these templates; if article has been published, use cite journal
local Time = A['Time'];
+
end
   −
if utilities.is_set (Minutes) then
+
-- special case for cite newsgroup.  Do this after COinS because we are modifying Publishername to include some static text
if utilities.is_set (Time) then --TODO: make a function for this and similar?
+
if 'newsgroup' == config.CitationClass and utilities.is_set (Newsgroup) then
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'minutes') .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'time')});
+
PublisherName = utilities.substitute (cfg.messages['newsgroup'], external_link( 'news:' .. Newsgroup, Newsgroup, Newsgroup_origin, nil ));
end
  −
Position = " " .. Minutes .. " " .. cfg.messages['minutes'];
  −
else
  −
if utilities.is_set (Time) then
  −
local TimeCaption = A['TimeCaption']
  −
if not utilities.is_set (TimeCaption) then
  −
TimeCaption = cfg.messages['event'];
  −
if sepc ~= '.' then
  −
TimeCaption = TimeCaption:lower();
  −
end
  −
end
  −
Position = " " .. TimeCaption .. " " .. Time;
  −
end
  −
end
  −
else
  −
Position = " " .. Position;
  −
At = '';
   
end
 
end
   −
Page, Pages, Sheet, Sheets = format_pages_sheets (Page, Pages, Sheet, Sheets, config.CitationClass, Periodical_origin, sepc, NoPP, use_lowercase);
+
local Editors;
 
+
local EditorCount; -- used only for choosing {ed.) or (eds.) annotation at end of editor name-list
At = utilities.is_set (At) and (sepc .. " " .. At) or "";
+
local Contributors; -- assembled contributors name list
Position = utilities.is_set (Position) and (sepc .. " " .. Position) or "";
+
local contributor_etal;
if config.CitationClass == 'map' then
+
local Translators; -- assembled translators name list
local Sections = A['Sections']; -- Section (singular) is an alias of Chapter so set earlier
+
local translator_etal;
local Inset = A['Inset'];
+
local t = {}; -- translators list from |translator-lastn= / translator-firstn= pairs
+
t = extract_names (args, 'TranslatorList'); -- fetch translator list from |translatorn= / |translator-lastn=, -firstn=, -linkn=, -maskn=
if utilities.is_set ( Inset ) then
+
local Interviewers;
Inset = sepc .. " " .. wrap_msg ('inset', Inset, use_lowercase);
+
local interviewers_list = {};
end
+
interviewers_list = extract_names (args, 'InterviewerList'); -- process preferred interviewers parameters
 +
local interviewer_etal;
 +
 +
-- Now perform various field substitutions.
 +
-- We also add leading spaces and surrounding markup and punctuation to the
 +
-- various parts of the citation, but only when they are non-nil.
 +
do
 +
local last_first_list;
 +
local control = {
 +
format = NameListStyle, -- empty string, '&', 'amp', 'and', or 'vanc'
 +
maximum = nil, -- as if display-authors or display-editors not set
 +
mode = Mode
 +
};
 +
 
 +
do -- do editor name list first because the now unsupported coauthors used to modify control table
 +
local display_names, param = display_names_select (cfg.global_cs1_config_t['DisplayEditors'], A['DisplayEditors'], A:ORIGIN ('DisplayEditors'), #e);
 +
control.maximum, editor_etal = get_display_names (display_names, #e, 'editors', editor_etal, param);
 +
 
 +
Editors, EditorCount = list_people (control, e, editor_etal);
   −
if utilities.is_set ( Sections ) then
+
if 1 == EditorCount and (true == editor_etal or 1 < #e) then -- only one editor displayed but includes etal then  
Section = sepc .. " " .. wrap_msg ('sections', Sections, use_lowercase);
+
EditorCount = 2; -- spoof to display (eds.) annotation
elseif utilities.is_set ( Section ) then
+
end
Section = sepc .. " " .. wrap_msg ('section', Section, use_lowercase);
   
end
 
end
At = At .. Inset .. Section;
+
do -- now do interviewers
end
+
local display_names, param = display_names_select (cfg.global_cs1_config_t['DisplayInterviewers'], A['DisplayInterviewers'], A:ORIGIN ('DisplayInterviewers'), #interviewers_list);
 +
control.maximum, interviewer_etal = get_display_names (display_names, #interviewers_list, 'interviewers', interviewer_etal, param);
   −
local Others = A['Others'];
+
Interviewers = list_people (control, interviewers_list, interviewer_etal);
if utilities.is_set (Others) and 0 == #a and 0 == #e then -- add maint cat when |others= has value and used without |author=, |editor=
  −
if config.CitationClass == "AV-media-notes"
  −
or config.CitationClass == "audio-visual" then -- special maint for AV/M which has a lot of 'false' positives right now
  −
utilities.set_message ('maint_others_avm')
  −
else
  −
utilities.set_message ('maint_others');
   
end
 
end
end
+
do -- now do translators
Others = utilities.is_set (Others) and (sepc .. " " .. Others) or "";
+
local display_names, param = display_names_select (cfg.global_cs1_config_t['DisplayTranslators'], A['DisplayTranslators'], A:ORIGIN ('DisplayTranslators'), #t);
+
control.maximum, translator_etal = get_display_names (display_names, #t, 'translators', translator_etal, param);
if utilities.is_set (Translators) then
+
 
Others = safe_join ({sepc .. ' ', wrap_msg ('translated', Translators, use_lowercase), Others}, sepc);
+
Translators = list_people (control, t, translator_etal);
end
  −
if utilities.is_set (Interviewers) then
  −
Others = safe_join ({sepc .. ' ', wrap_msg ('interview', Interviewers, use_lowercase), Others}, sepc);
  −
end
  −
  −
local TitleNote = A['TitleNote'];
  −
TitleNote = utilities.is_set (TitleNote) and (sepc .. " " .. TitleNote) or "";
  −
if utilities.is_set (Edition) then
  −
if Edition:match ('%f[%a][Ee]d%n?%.?$') or Edition:match ('%f[%a][Ee]dition$') then -- Ed, ed, Ed., ed., Edn, edn, Edn., edn.
  −
utilities.set_message ('err_extra_text_edition'); -- add error message
   
end
 
end
Edition = " " .. wrap_msg ('edition', Edition);
+
do -- now do contributors
else
+
local display_names, param = display_names_select (cfg.global_cs1_config_t['DisplayContributors'], A['DisplayContributors'], A:ORIGIN ('DisplayContributors'), #c);
Edition = '';
+
control.maximum, contributor_etal = get_display_names (display_names, #c, 'contributors', contributor_etal, param);
end
     −
Series = utilities.is_set (Series) and wrap_msg ('series', {sepc, Series}) or ""; -- not the same as SeriesNum
+
Contributors = list_people (control, c, contributor_etal);
local Agency = A['Agency'];
+
end
Agency = utilities.is_set (Agency) and wrap_msg ('agency', {sepc, Agency}) or "";
+
do -- now do authors
Volume = format_volume_issue (Volume, Issue, ArticleNumber, config.CitationClass, Periodical_origin, sepc, use_lowercase);
+
local display_names, param = display_names_select (cfg.global_cs1_config_t['DisplayAuthors'], A['DisplayAuthors'], A:ORIGIN ('DisplayAuthors'), #a, author_etal);
 +
control.maximum, author_etal = get_display_names (display_names, #a, 'authors', author_etal, param);
   −
if utilities.is_set (AccessDate) then
+
last_first_list = list_people (control, a, author_etal);
local retrv_text = " " .. cfg.messages['retrieved']
     −
AccessDate = nowrap_date (AccessDate); -- wrap in nowrap span if date in appropriate format
+
if utilities.is_set (Authors) then
if (sepc ~= ".") then retrv_text = retrv_text:lower() end -- if mode is cs2, lower case
+
Authors, author_etal = name_has_etal (Authors, author_etal, false, 'authors'); -- find and remove variations on et al.
AccessDate = utilities.substitute (retrv_text, AccessDate); -- add retrieved text
+
if author_etal then
 
+
Authors = Authors .. ' ' .. cfg.messages['et al']; -- add et al. to authors parameter
AccessDate = utilities.substitute (cfg.presentation['accessdate'], {sepc, AccessDate}); -- allow editors to hide accessdates
+
end
end
+
else
 +
Authors = last_first_list; -- either an author name list or an empty string
 +
end
 +
end -- end of do
 
 
if utilities.is_set (ID) then ID = sepc .. " " .. ID; end
+
if utilities.is_set (Authors) and utilities.is_set (Collaboration) then
+
Authors = Authors .. ' (' .. Collaboration .. ')'; -- add collaboration after et al.
local Docket = A['Docket'];
+
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
 
end
   −
if utilities.is_set (URL) then
+
local ConferenceFormat = A['ConferenceFormat'];
URL = " " .. external_link( URL, nil, URL_origin, UrlAccess );
+
local ConferenceURL = A['ConferenceURL'];
 +
ConferenceFormat = style_format (ConferenceFormat, ConferenceURL, 'conference-format', 'conference-url');
 +
Format = style_format (Format, URL, 'format', 'url');
 +
 
 +
-- special case for chapter format so no error message or cat when chapter not supported
 +
if not (utilities.in_array (config.CitationClass, {'web', 'news', 'journal', 'magazine', 'pressrelease', 'podcast', 'newsgroup', 'arxiv', 'biorxiv', 'citeseerx', 'medrxiv', 'ssrn'}) or
 +
('citation' == config.CitationClass and (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) and not utilities.is_set (Encyclopedia))) then
 +
ChapterFormat = style_format (ChapterFormat, ChapterURL, 'chapter-format', 'chapter-url');
 
end
 
end
   −
local Quote = A['Quote'];
+
if not utilities.is_set (URL) then
local TransQuote = A['TransQuote'];
+
if utilities.in_array (config.CitationClass, {"web", "podcast", "mailinglist"}) or -- |url= required for cite web, cite podcast, and cite mailinglist
local ScriptQuote = A['ScriptQuote'];
+
('citation' == config.CitationClass and ('website' == Periodical_origin or 'script-website' == ScriptPeriodical_origin)) then -- and required for {{citation}} with |website= or |script-website=
if utilities.is_set (Quote) or utilities.is_set (TransQuote) or utilities.is_set (ScriptQuote) then
+
utilities.set_message ('err_cite_web_url');
 
  −
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
 
end
 
 
Quote = kern_quotes (Quote); -- kern if needed
+
-- do we have |accessdate= without either |url= or |chapter-url=?
Quote = utilities.wrap_style ('quoted-text', Quote ); -- wrap in <q>...</q> tags
+
if utilities.is_set (AccessDate) and not utilities.is_set (ChapterURL) then -- ChapterURL may be set when URL is not set;
+
utilities.set_message ('err_accessdate_missing_url');
if utilities.is_set (ScriptQuote) then
+
AccessDate = '';
Quote = script_concatenate (Quote, ScriptQuote, 'script-quote'); -- <bdi> tags, lang attribute, categorization, etc.; must be done after quote is wrapped
   
end
 
end
 +
end
   −
if utilities.is_set (TransQuote) then
+
local UrlStatus = is_valid_parameter_value (A['UrlStatus'], A:ORIGIN('UrlStatus'), cfg.keywords_lists['url-status'], '');
if TransQuote:sub(1, 1) == '"' and TransQuote:sub(-1, -1) == '"' then -- if first and last characters of |trans-quote are quote marks
+
local OriginalURL
TransQuote = TransQuote:sub(2, -2); -- strip them off
+
local OriginalURL_origin
end
+
local OriginalFormat
Quote = Quote .. " " .. utilities.wrap_style ('trans-quoted-title', TransQuote );
+
local OriginalAccess;
end
+
UrlStatus = UrlStatus:lower(); -- used later when assembling archived text
 +
if utilities.is_set ( ArchiveURL ) then
 +
if utilities.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
 +
OriginalURL_origin = ChapterURL_origin; -- name of |chapter-url= parameter for error messages
 +
OriginalFormat = ChapterFormat; -- and original |chapter-format=
 +
 
 +
if 'live' ~= UrlStatus then
 +
ChapterURL = ArchiveURL -- swap-in the archive's URL
 +
ChapterURL_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
 +
end
 +
elseif utilities.is_set (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 utilities.is_set (QuotePage) or utilities.is_set (QuotePages) then -- add page prefix
+
if 'live' ~= UrlStatus then -- if URL set then |archive-url= applies to it
local quote_prefix = '';
+
URL = ArchiveURL -- swap-in the archive's URL
if utilities.is_set (QuotePage) then
+
URL_origin = A:ORIGIN('ArchiveURL') -- name of archive URL parameter for error messages
extra_text_in_page_check (QuotePage, 'quote-page'); -- add to maint cat if |quote-page= value begins with what looks like p., pp., etc.
+
Format = ArchiveFormat or ''; -- swap in archive's format
if not NoPP then
+
UrlAccess = nil; -- restricted access levels do not make sense for archived URLs
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
  −
extra_text_in_page_check (QuotePages, 'quote-pages'); -- add to maint cat if |quote-pages= value begins with what looks like p., pp., etc.
  −
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
 
end
                       
  −
Quote = quote_prefix .. ": " .. Quote;
  −
else
  −
Quote = sepc .. " " .. Quote;
   
end
 
end
 +
elseif utilities.is_set (UrlStatus) then -- if |url-status= is set when |archive-url= is not set
 +
utilities.set_message ('maint_url_status'); -- add maint cat
 +
end
   −
PostScript = ""; -- cs1|2 does not supply terminal punctuation when |quote= is set
+
if utilities.in_array (config.CitationClass, {'web', 'news', 'journal', 'magazine', 'pressrelease', 'podcast', 'newsgroup', 'arxiv', 'biorxiv', 'citeseerx', 'medrxiv', 'ssrn'}) or -- if any of the 'periodical' cites except encyclopedia
end
+
('citation' == config.CitationClass and (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) and not utilities.is_set (Encyclopedia)) then
+
local chap_param;
-- We check length of PostScript here because it will have been nuked by
+
if utilities.is_set (Chapter) then -- get a parameter name from one of these chapter related meta-parameters
-- the quote parameters. We'd otherwise emit a message even if there wasn't
+
chap_param = A:ORIGIN ('Chapter')
-- a displayed postscript.
+
elseif utilities.is_set (TransChapter) then
-- TODO: Should the max size (1) be configurable?
+
chap_param = A:ORIGIN ('TransChapter')
-- TODO: Should we check a specific pattern?
+
elseif utilities.is_set (ChapterURL) then
if utilities.is_set(PostScript) and mw.ustring.len(PostScript) > 1 then
+
chap_param = A:ORIGIN ('ChapterURL')
utilities.set_message ('maint_postscript')
+
elseif utilities.is_set (ScriptChapter) then
end
+
chap_param = ScriptChapter_origin;
+
else utilities.is_set (ChapterFormat)
local Archived;
+
chap_param = A:ORIGIN ('ChapterFormat')
if utilities.is_set (ArchiveURL) then
  −
local arch_text;
  −
if not utilities.is_set (ArchiveDate) then
  −
utilities.set_message ('err_archive_missing_date');
  −
ArchiveDate = ''; -- empty string for concatenation
  −
end
  −
if "live" == UrlStatus then
  −
arch_text = cfg.messages['archived'];
  −
if sepc ~= "." then arch_text = arch_text:lower() end
  −
if utilities.is_set (ArchiveDate) then
  −
Archived = sepc .. ' ' .. utilities.substitute ( cfg.messages['archived-live'],
  −
{external_link( ArchiveURL, arch_text, A:ORIGIN('ArchiveURL'), nil) .. ArchiveFormat, ArchiveDate } );
  −
else
  −
Archived = '';
   
end
 
end
if not utilities.is_set (OriginalURL) then
+
 
utilities.set_message ('err_archive_missing_url');
+
if utilities.is_set (chap_param) then -- if we found one
Archived = ''; -- empty string for concatenation
+
utilities.set_message ('err_chapter_ignored', {chap_param}); -- add error message
 +
Chapter = ''; -- and set them to empty string to be safe with concatenation
 +
TransChapter = '';
 +
ChapterURL = '';
 +
ScriptChapter = '';
 +
ChapterFormat = '';
 +
end
 +
else -- otherwise, format chapter / article title
 +
local no_quotes = false; -- default assume that we will be quoting the chapter parameter value
 +
if utilities.is_set (Contribution) and 0 < #c then -- if this is a contribution with contributor(s)
 +
if utilities.in_array (Contribution:lower(), cfg.keywords_lists.contribution) then -- and a generic contribution title
 +
no_quotes = true; -- then render it unquoted
 
end
 
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
  −
if utilities.is_set (ArchiveDate) then
  −
Archived = sepc .. " " .. utilities.substitute ( arch_text,
  −
{ external_link( OriginalURL, cfg.messages['original'], OriginalURL_origin, OriginalAccess ) .. OriginalFormat, ArchiveDate } ); -- format already styled
  −
else
  −
Archived = ''; -- unset for concatenation
  −
end
  −
end
  −
else -- OriginalUrl not set
  −
arch_text = cfg.messages['archived-missing'];
  −
if sepc ~= "." then arch_text = arch_text:lower() end
  −
utilities.set_message ('err_archive_missing_url');
  −
Archived = ''; -- empty string for concatenation
   
end
 
end
elseif utilities.is_set (ArchiveFormat) then
  −
Archived = ArchiveFormat; -- if set and ArchiveURL not set ArchiveFormat has error message
  −
else
  −
Archived = '';
  −
end
  −
  −
local Lay = '';
  −
local LaySource = A['LaySource'];
  −
local LayURL = A['LayURL'];
  −
local LayFormat = A['LayFormat'];
  −
LayFormat = style_format (LayFormat, LayURL, 'lay-format', 'lay-url');
  −
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
     −
local TranscriptURL = A['TranscriptURL']
+
Chapter = format_chapter_title (ScriptChapter, ScriptChapter_origin, Chapter, Chapter_origin, TransChapter, TransChapter_origin, ChapterURL, ChapterURL_origin, no_quotes, ChapterUrlAccess); -- Contribution is also in Chapter
local TranscriptFormat = A['TranscriptFormat'];
+
if utilities.is_set (Chapter) then
TranscriptFormat = style_format (TranscriptFormat, TranscriptURL, 'transcript-format', 'transcripturl');
+
Chapter = Chapter .. ChapterFormat ;
local Transcript = A['Transcript'];
+
if 'map' == config.CitationClass and utilities.is_set (TitleType) then
local TranscriptURL_origin = A:ORIGIN('TranscriptURL'); -- get name of parameter that holds TranscriptURL
+
Chapter = Chapter .. ' ' .. TitleType; -- map annotation here; not after title
if utilities.is_set (Transcript) then
+
end
if utilities.is_set (TranscriptURL) then
+
Chapter = Chapter .. sepc .. ' ';
Transcript = external_link( TranscriptURL, Transcript, TranscriptURL_origin, nil );
+
elseif utilities.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
Transcript = sepc .. ' ' .. Transcript .. TranscriptFormat;
  −
elseif utilities.is_set (TranscriptURL) then
  −
Transcript = external_link( TranscriptURL, nil, TranscriptURL_origin, nil );
   
end
 
end
   −
local Publisher;
+
-- Format main title
if utilities.is_set (PublicationDate) then
+
local plain_title = false;
PublicationDate = wrap_msg ('published', PublicationDate);
+
local accept_title;
 +
Title, accept_title = utilities.has_accept_as_written (Title, true); -- remove accept-this-as-written markup when it wraps all of <Title>
 +
if accept_title and ('' == Title) then -- only support forced empty for now "(())"
 +
Title = cfg.messages['notitle']; -- replace by predefined "No title" message
 +
-- TODO: utilities.set_message ( 'err_redundant_parameters', ...); -- issue proper error message instead of muting
 +
ScriptTitle = ''; -- just mute for now
 +
TransTitle = ''; -- just mute for now
 +
plain_title = true; -- suppress text decoration for descriptive title
 +
utilities.set_message ('maint_untitled'); -- add maint cat
 
end
 
end
if utilities.is_set (PublisherName) then
+
 
if utilities.is_set (PublicationPlace) then
+
if not accept_title then -- <Title> not wrapped in accept-as-written markup
Publisher = sepc .. " " .. PublicationPlace .. ": " .. PublisherName .. PublicationDate;
+
if '...' == Title:sub (-3) then -- if ellipsis is the last three characters of |title=
else
+
Title = Title:gsub ('(%.%.%.)%.+$', '%1'); -- limit the number of dots to three
Publisher = sepc .. " " .. PublisherName .. PublicationDate; 
+
elseif not mw.ustring.find (Title, '%.%s*%a%.$') and -- end of title is not a 'dot-(optional space-)letter-dot' initialism ...
end
+
not mw.ustring.find (Title, '%s+%a%.$') then -- ...and not a 'space-letter-dot' initial (''Allium canadense'' L.)
elseif utilities.is_set (PublicationPlace) then
+
Title = mw.ustring.gsub(Title, '%' .. sepc .. '$', ''); -- remove any trailing separator character; sepc and ms.ustring() here for languages that use multibyte separator characters
Publisher= sepc .. " " .. PublicationPlace .. PublicationDate;
  −
else
  −
Publisher = PublicationDate;
  −
end
  −
  −
local TransPeriodical =  A['TransPeriodical'];
  −
local TransPeriodical_origin =  A:ORIGIN ('TransPeriodical');
  −
-- 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
end
  −
  −
local Language = A['Language'];
  −
if utilities.is_set (Language) then
  −
Language = language_parameter (Language); -- format, categories, name from ISO639-1, etc.
  −
else
  −
Language=''; -- language not specified so make sure this is an empty string;
  −
--[[ TODO: need to extract the wrap_msg from language_parameter
  −
so that we can solve parentheses bunching problem with Format/Language/TitleType
  −
]]
  −
end
     −
--[[
+
if utilities.is_set (ArchiveURL) and is_archived_copy (Title) then
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
+
utilities.set_message ('maint_archived_copy'); -- add maintenance category before we modify the content of Title
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 = TitleType; -- move TitleType to TitleNote so that it renders ahead of |event=
  −
TitleType = ''; -- and unset
  −
 
  −
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
end
     −
-- Piece all bits together at last. Here, all should be non-nil.
+
if is_generic ('generic_titles', Title) then
-- We build things this way because it is more efficient in LUA
+
utilities.set_message ('err_generic_title'); -- set an error message
-- not to keep reassigning to the same string variable over and over.
+
end
 +
end
 +
 
 +
if (not plain_title) and (utilities.in_array (config.CitationClass, {'web', 'news', 'journal', 'magazine', 'document', 'pressrelease', 'podcast', 'newsgroup', 'mailinglist', 'interview', 'arxiv', 'biorxiv', 'citeseerx', 'medrxiv', 'ssrn'}) or
 +
('citation' == 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 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-title', Title);
 +
Title = script_concatenate (Title, ScriptTitle, 'script-title'); -- <bdi> tags, lang attribute, categorization, etc.; must be done after title is wrapped
 +
TransTitle = utilities.wrap_style ('trans-quoted-title', TransTitle );
 +
elseif plain_title or ('report' == config.CitationClass) then -- no styling for cite report and descriptive titles (otherwise same as above)
 +
Title = script_concatenate (Title, ScriptTitle, 'script-title'); -- <bdi> tags, lang attribute, categorization, etc.; must be done after title is wrapped
 +
TransTitle = utilities.wrap_style ('trans-quoted-title', TransTitle ); -- for cite report, use this form for trans-title
 +
else
 +
Title = utilities.wrap_style ('italic-title', Title);
 +
Title = script_concatenate (Title, ScriptTitle, 'script-title'); -- <bdi> tags, lang attribute, categorization, etc.; must be done after title is wrapped
 +
TransTitle = utilities.wrap_style ('trans-italic-title', TransTitle);
 +
end
   −
local tcommon;
+
if utilities.is_set (TransTitle) then
local tcommon2; -- used for book cite when |contributor= is set
+
if utilities.is_set (Title) then
+
TransTitle = " " .. TransTitle;
if utilities.in_array (config.CitationClass, {"journal", "citation"}) and utilities.is_set (Periodical) then
  −
if not (utilities.is_set (Authors) or utilities.is_set (Editors)) then
  −
Others = Others:gsub ('^' .. sepc .. ' ', ''); -- when no authors and no editors, strip leading sepc and space
  −
end
  −
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
 
else
tcommon = safe_join( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, Volume, Others, Edition, Publisher, Agency}, sepc );
+
utilities.set_message ('err_trans_missing_title', {'title'});
 
end
 
end
 +
end
   −
elseif 'map' == config.CitationClass then -- special cases for cite map
+
if utilities.is_set (Title) then -- TODO: is this the right place to be making Wikisource URLs?
if utilities.is_set (Chapter) then -- map in a book; TitleType is part of Chapter
+
if utilities.is_set (TitleLink) and utilities.is_set (URL) then
tcommon = safe_join( {Title, Format, Edition, Scale, Series, Language, Cartography, Others, Publisher, Volume}, sepc );
+
utilities.set_message ('err_wikilink_in_url'); -- set an error message because we can't have both
elseif utilities.is_set (Periodical) then -- map in a periodical
+
TitleLink = ''; -- unset
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
 
end
+
elseif 'episode' == config.CitationClass then -- special case for cite episode
+
if not utilities.is_set (TitleLink) and utilities.is_set (URL) then
tcommon = safe_join( {Title, TitleNote, TitleType, Series, Language, Edition, Publisher}, sepc );
+
Title = external_link (URL, Title, URL_origin, UrlAccess) .. TransTitle .. Format;
 +
URL = ''; -- unset these because no longer needed
 +
Format = "";
 +
elseif utilities.is_set (TitleLink) and not utilities.is_set (URL) then
 +
local ws_url;
 +
ws_url = wikisource_url_make (TitleLink); -- ignore ws_label return; not used here
 +
if ws_url then
 +
Title = external_link (ws_url, Title .. '&nbsp;', 'ws link in title-link'); -- 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'], {cfg.presentation['class-wikisource'], TitleLink, Title});
 +
Title = Title .. TransTitle;
 +
else
 +
Title = utilities.make_wikilink (TitleLink, Title) .. TransTitle;
 +
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 (not a wikilink)
 +
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 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'], {cfg.presentation['class-wikisource'], L, Title});
 +
Title = Title .. TransTitle;
 +
else
 +
Title = Title .. TransTitle;
 +
end
 +
end
 +
else
 +
Title = TransTitle;
 +
end
   −
else -- all other CS1 templates
+
if utilities.is_set (Place) then
tcommon = safe_join( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language,
+
Place = " " .. wrap_msg ('written', Place, use_lowercase) .. sepc .. " ";
Volume, Others, Edition, Publisher, Agency}, sepc );
   
end
 
end
+
 
if #ID_list > 0 then
+
local ConferenceURL_origin = A:ORIGIN('ConferenceURL'); -- get name of parameter that holds ConferenceURL
ID_list = safe_join( { sepc .. " ", table.concat( ID_list, sepc .. " " ), ID }, sepc );
+
if utilities.is_set (Conference) then
else
+
if utilities.is_set (ConferenceURL) then
ID_list = ID;
+
Conference = external_link( ConferenceURL, Conference, ConferenceURL_origin, nil );
end
+
end
+
Conference = sepc .. " " .. Conference .. ConferenceFormat;
local Via = A['Via'];
+
elseif utilities.is_set (ConferenceURL) then
Via = utilities.is_set (Via) and  wrap_msg ('via', Via) or '';
+
Conference = sepc .. " " .. external_link( ConferenceURL, nil, ConferenceURL_origin, nil );
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
 
end
  −
local text;
  −
local pgtext = Position .. Sheet .. Sheets .. Page .. Pages .. At;
     −
local OrigDate = A['OrigDate'];
+
local Position = '';
OrigDate = utilities.is_set (OrigDate) and wrap_msg ('origdate', OrigDate) or '';
+
if not utilities.is_set (Position) then
if utilities.is_set (Date) then
+
local Minutes = A['Minutes'];
if utilities.is_set (Authors) or utilities.is_set (Editors) then -- date follows authors or editors when authors not set
+
local Time = A['Time'];
Date = " (" .. Date .. ")" .. OrigDate .. sepc .. " "; -- in parentheses
+
 
else -- neither of authors and editors set
+
if utilities.is_set (Minutes) then
if (string.sub(tcommon, -1, -1) == sepc) then -- if the last character of tcommon is sepc
+
if utilities.is_set (Time) then --TODO: make a function for this and similar?
Date = " " .. Date .. OrigDate; -- Date does not begin with sepc
+
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'minutes') .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'time')});
else
  −
Date = sepc .. " " .. Date .. OrigDate; -- Date begins with sepc
   
end
 
end
end
+
Position = " " .. Minutes .. " " .. cfg.messages['minutes'];
end
+
else
if utilities.is_set (Authors) then
+
if utilities.is_set (Time) then
if (not utilities.is_set (Date)) then -- when date is set it's in parentheses; no Authors termination
+
local TimeCaption = A['TimeCaption']
Authors = terminate_name_list (Authors, sepc); -- when no date, terminate with 0 or 1 sepc and a space
+
if not utilities.is_set (TimeCaption) then
end
+
TimeCaption = cfg.messages['event'];
if utilities.is_set (Editors) then
+
if sepc ~= '.' then
local in_text = " ";
+
TimeCaption = TimeCaption:lower();
local post_text = "";
+
end
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
 +
Position = " " .. TimeCaption .. " " .. Time;
 
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
 
end
if utilities.is_set (Contributors) then -- book cite and we're citing the intro, preface, etc.
+
else
local by_text = sepc .. ' ' .. cfg.messages['by'] .. ' ';
+
Position = " " .. Position;
if (sepc ~= '.') then by_text = by_text:lower() end -- lowercase for cs2
+
At = '';
Authors = by_text .. Authors; -- author follows title so tweak it here
+
end
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
+
Page, Pages, Sheet, Sheets = format_pages_sheets (Page, Pages, Sheet, Sheets, config.CitationClass, Periodical_origin, sepc, NoPP, use_lowercase);
end
+
 
if (not utilities.is_set (Date)) then -- when date is set it's in parentheses; no Contributors termination
+
At = utilities.is_set (At) and (sepc .. " " .. At) or "";
Contributors = terminate_name_list (Contributors, sepc); -- terminate with 0 or 1 sepc and a space
+
Position = utilities.is_set (Position) and (sepc .. " " .. Position) or "";
end
+
if config.CitationClass == 'map' then
text = safe_join( {Contributors, Date, Chapter, tcommon, Authors, Place, Editors, tcommon2, pgtext, idcommon }, sepc );
+
local Sections = A['Sections']; -- Section (singular) is an alias of Chapter so set earlier
else
+
local Inset = A['Inset'];
text = safe_join( {Authors, Date, Chapter, Place, Editors, tcommon, pgtext, idcommon }, sepc );
+
end
+
if utilities.is_set ( Inset ) then
elseif utilities.is_set (Editors) then
+
Inset = sepc .. " " .. wrap_msg ('inset', Inset, use_lowercase);
if utilities.is_set (Date) then
+
end
if EditorCount <= 1 then
+
 
Editors = Editors .. cfg.presentation['sep_name'] .. cfg.messages['editor'];
+
if utilities.is_set ( Sections ) then
else
+
Section = sepc .. " " .. wrap_msg ('sections', Sections, use_lowercase);
Editors = Editors .. cfg.presentation['sep_name'] .. cfg.messages['editors'];
+
elseif utilities.is_set ( Section ) then
end
+
Section = sepc .. " " .. wrap_msg ('section', Section, use_lowercase);
else
  −
if EditorCount <= 1 then
  −
Editors = Editors .. " (" .. cfg.messages['editor'] .. ")" .. sepc .. " "
  −
else
  −
Editors = Editors .. " (" .. cfg.messages['editors'] .. ")" .. sepc .. " "
  −
end
   
end
 
end
text = safe_join( {Editors, Date, Chapter, Place, tcommon, pgtext, idcommon}, sepc );
+
At = At .. Inset .. Section;
else
+
end
if utilities.in_array (config.CitationClass, {"journal", "citation"}) and utilities.is_set (Periodical) then
+
 
text = safe_join( {Chapter, Place, tcommon, pgtext, Date, idcommon}, sepc );
+
local Others = A['Others'];
 +
if utilities.is_set (Others) and 0 == #a and 0 == #e then -- add maint cat when |others= has value and used without |author=, |editor=
 +
if config.CitationClass == "AV-media-notes"
 +
or config.CitationClass == "audio-visual" then -- special maint for AV/M which has a lot of 'false' positives right now
 +
utilities.set_message ('maint_others_avm')
 
else
 
else
text = safe_join( {Chapter, Place, tcommon, Date, pgtext, idcommon}, sepc );
+
utilities.set_message ('maint_others');
 
end
 
end
 
end
 
end
 +
Others = utilities.is_set (Others) and (sepc .. " " .. Others) or "";
 
 
if utilities.is_set (PostScript) and PostScript ~= sepc then
+
if utilities.is_set (Translators) then
text = safe_join( {text, sepc}, sepc ); -- Deals with italics, spaces, etc.
+
Others = safe_join ({sepc .. ' ', wrap_msg ('translated', Translators, use_lowercase), Others}, sepc);
text = text:sub(1, -sepc:len() - 1);
+
end
end
+
if utilities.is_set (Interviewers) then
 +
Others = safe_join ({sepc .. ' ', wrap_msg ('interview', Interviewers, use_lowercase), Others}, sepc);
 +
end
 
 
text = safe_join( {text, PostScript}, sepc );
+
local TitleNote = A['TitleNote'];
 
+
TitleNote = utilities.is_set (TitleNote) and (sepc .. " " .. TitleNote) or "";
-- Now enclose the whole thing in a <cite> element
+
if utilities.is_set (Edition) then
local options_t = {};
+
if Edition:match ('%f[%a][Ee]d%n?%.?$') or Edition:match ('%f[%a][Ee]dition$') then -- Ed, ed, Ed., ed., Edn, edn, Edn., edn.
options_t.class = cite_class_attribute_make (config.CitationClass, Mode);
+
utilities.set_message ('err_extra_text_edition'); -- add error message
 
+
end
local Ref
+
Edition = " " .. wrap_msg ('edition', Edition);
if A['Ref'] ~= 'harv' then
  −
Ref = is_valid_parameter_value (A['Ref'], A:ORIGIN('Ref'), cfg.keywords_lists['ref'], nil, true); -- nil when |ref=harv; A['Ref'] else
   
else
 
else
Ref = nil
+
Edition = '';
 
end
 
end
   −
if 'none' ~= cfg.keywords_xlate[(Ref and Ref:lower()) or ''] then
+
Series = utilities.is_set (Series) and wrap_msg ('series', {sepc, Series}) or ""; -- not the same as SeriesNum
local namelist_t = {}; -- holds selected contributor, author, editor name list
+
local Agency = A['Agency'] or ''; -- |agency= is supported by {{cite magazine}}, {{cite news}}, {{cite press release}}, {{cite web}}, and certain {{citation}} templates
local year = first_set ({Year, anchor_year}, 2); -- Year first for legacy citations and for YMD dates that require disambiguation
+
if utilities.is_set (Agency) then -- this testing done here because {{citation}} supports 'news' citations
 
+
if utilities.in_array (config.CitationClass, {'magazine', 'news', 'pressrelease', 'web'}) or ('citation' == config.CitationClass and utilities.in_array (Periodical_origin, {"magazine", "newspaper", "work"})) then
if #c > 0 then -- if there is a contributor list
+
Agency = wrap_msg ('agency', {sepc, Agency}); -- format for rendering
namelist_t = c; -- select it
  −
elseif #a > 0 then -- or an author list
  −
namelist_t = a;
  −
elseif #e > 0 then -- or an editor list
  −
namelist_t = e;
  −
end
  −
local citeref_id;
  −
if #namelist_t > 0 then -- if there are names in namelist_t
  −
citeref_id = make_citeref_id (namelist_t, year); -- go make the CITEREF anchor
  −
if mw.uri.anchorEncode (citeref_id) == ((Ref and mw.uri.anchorEncode (Ref)) or '') then -- Ref may already be encoded (by {{sfnref}}) so citeref_id must be encoded before comparison
  −
utilities.set_message ('maint_ref_duplicates_default');
  −
end
   
else
 
else
citeref_id = ''; -- unset
+
Agency = ''; -- unset; not supported
 +
utilities.set_message ('err_parameter_ignored', {'agency'}); -- add error message
 
end
 
end
options_t.id = Ref or citeref_id;
   
end
 
end
 +
 +
Volume = format_volume_issue (Volume, Issue, ArticleNumber, config.CitationClass, Periodical_origin, sepc, use_lowercase);
   −
if string.len (text:gsub('%b<>', '')) <= 2 then -- remove html and html-like tags; then get length of what remains;
+
if utilities.is_set (AccessDate) then
z.error_cats_t = {}; -- blank the categories list
+
local retrv_text = " " .. cfg.messages['retrieved']
z.error_msgs_t = {}; -- blank the error messages list
+
 
OCinSoutput = nil; -- blank the metadata string
+
AccessDate = nowrap_date (AccessDate); -- wrap in nowrap span if date in appropriate format
text = ''; -- blank the the citation
+
if (sepc ~= ".") then retrv_text = retrv_text:lower() end -- if mode is cs2, lower case
utilities.set_message ('err_empty_citation'); -- set empty citation message and category
+
AccessDate = utilities.substitute (retrv_text, AccessDate); -- add retrieved text
 +
 
 +
AccessDate = utilities.substitute (cfg.presentation['accessdate'], {sepc, AccessDate}); -- allow editors to hide accessdates
 
end
 
end
 
 
local render_t = {}; -- here we collect the final bits for concatenation into the rendered citation
+
if utilities.is_set (ID) then ID = sepc .. " " .. ID; end
 +
 +
local Docket = A['Docket'];
 +
  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 (options_t.id) then -- here we wrap the rendered citation in <cite ...>...</cite> tags
+
if utilities.is_set (URL) then
table.insert (render_t, utilities.substitute (cfg.presentation['cite-id'], {mw.uri.anchorEncode(options_t.id), mw.text.nowiki(options_t.class), text})); -- when |ref= is set or when there is a namelist
+
URL = " " .. external_link( URL, nil, URL_origin, UrlAccess );
else
  −
table.insert (render_t, utilities.substitute (cfg.presentation['cite'], {mw.text.nowiki(options_t.class), text})); -- when |ref=none or when namelist_t empty and |ref= is missing or is empty
  −
end
  −
 
  −
if OCinSoutput then -- blanked when citation is 'empty' so don't bother to add boilerplate metadata span
  −
table.insert (render_t, utilities.substitute (cfg.presentation['ocins'], OCinSoutput)); -- format and append metadata to the citation
   
end
 
end
 
+
local template_name = ('citation' == config.CitationClass) and 'citation' or 'cite ' .. (cfg.citation_class_map_t[config.CitationClass] or config.CitationClass);
+
-- We check length of PostScript here because it will have been nuked by
local template_link = '[[Template:' .. template_name .. '|' .. template_name .. ']]';
+
-- the quote parameters. We'd otherwise emit a message even if there wasn't
local msg_prefix = '<code class="cs1-code">{{' .. template_link .. '}}</code>: ';
+
-- a displayed postscript.
 
+
-- TODO: Should the max size (1) be configurable?
if 0 ~= #z.error_msgs_t then
+
-- TODO: Should we check a specific pattern?
mw.addWarning (utilities.substitute (cfg.messages.warning_msg_e, template_link));
+
if utilities.is_set(PostScript) and mw.ustring.len(PostScript) > 1 then
 
+
utilities.set_message ('maint_postscript')
table.insert (render_t, ' '); -- insert a space between citation and its error messages
  −
table.sort (z.error_msgs_t); -- sort the error messages list; sorting includes wrapping <span> and <code> tags; hidden-error sorts ahead of visible-error
  −
 
  −
local hidden = true; -- presume that the only error messages emited by this template are hidden
  −
for _, v in ipairs (z.error_msgs_t) do -- spin through the list of error messages
  −
if v:find ('cs1-visible-error', 1, true) then -- look for the visible error class name
  −
hidden = false; -- found one; so don't hide the error message prefix
  −
break; -- and done because no need to look further
  −
end
  −
end
  −
 
  −
z.error_msgs_t[1] = table.concat ({utilities.error_comment (msg_prefix, hidden), z.error_msgs_t[1]}); -- add error message prefix to first error message to prevent extraneous punctuation
  −
table.insert (render_t, table.concat (z.error_msgs_t, '; ')); -- make a big string of error messages and add it to the rendering
   
end
 
end
 
+
if 0 ~= #z.maint_cats_t then
+
local Archived;
mw.addWarning (utilities.substitute (cfg.messages.warning_msg_m, template_link));
+
if utilities.is_set (ArchiveURL) then
 
+
if not utilities.is_set (ArchiveDate) then -- ArchiveURL set but ArchiveDate not set
table.sort (z.maint_cats_t); -- sort the maintenance messages list
+
utilities.set_message ('err_archive_missing_date'); -- emit an error message
 
+
ArchiveURL = ''; -- empty string for concatenation
local maint_msgs_t = {}; -- here we collect all of the maint messages
+
ArchiveDate = ''; -- empty string for concatenation
 
  −
if 0 == #z.error_msgs_t then -- if no error messages
  −
table.insert (maint_msgs_t, msg_prefix); -- insert message prefix in maint message livery
   
end
 
end
+
else
for _, v in ipairs( z.maint_cats_t ) do -- append maintenance categories
+
if utilities.is_set (ArchiveDate) then -- ArchiveURL not set but ArchiveDate is set
table.insert (maint_msgs_t, -- assemble new maint message and add it to the maint_msgs_t table
+
utilities.set_message ('err_archive_date_missing_url'); -- emit an error message
table.concat ({v, ' (', utilities.substitute (cfg.messages[':cat wikilink'], v), ')'})
+
ArchiveURL = ''; -- empty string for concatenation
);
+
ArchiveDate = ''; -- empty string for concatenation
 
end
 
end
table.insert (render_t, utilities.substitute (cfg.presentation['hidden-maint'], table.concat (maint_msgs_t, ' '))); -- wrap the group of maint messages with proper presentation and save
   
end
 
end
   −
if not no_tracking_cats then
+
if utilities.is_set (ArchiveURL) then
for _, v in ipairs (z.error_cats_t) do -- append error categories
+
local arch_text;
table.insert (render_t, utilities.substitute (cfg.messages['cat wikilink'], v));
+
if "live" == UrlStatus then
end
+
arch_text = cfg.messages['archived'];
for _, v in ipairs (z.maint_cats_t) do -- append maintenance categories
+
if sepc ~= "." then arch_text = arch_text:lower() end
table.insert (render_t, utilities.substitute (cfg.messages['cat wikilink'], v));
+
if utilities.is_set (ArchiveDate) then
end
+
Archived = sepc .. ' ' .. utilities.substitute ( cfg.messages['archived-live'],
for _, v in ipairs (z.prop_cats_t) do -- append properties categories
+
{external_link( ArchiveURL, arch_text, A:ORIGIN('ArchiveURL'), nil) .. ArchiveFormat, ArchiveDate } );
table.insert (render_t, utilities.substitute (cfg.messages['cat wikilink'], v));
+
else
end
+
Archived = '';
 +
end
 +
if not utilities.is_set (OriginalURL) then
 +
utilities.set_message ('err_archive_missing_url');
 +
Archived = ''; -- empty string for concatenation
 +
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.add_prop_cat ('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
 +
if utilities.is_set (ArchiveDate) then
 +
Archived = sepc .. " " .. utilities.substitute ( arch_text,
 +
{ external_link( OriginalURL, cfg.messages['original'], OriginalURL_origin, OriginalAccess ) .. OriginalFormat, ArchiveDate } ); -- format already styled
 +
else
 +
Archived = ''; -- unset for concatenation
 +
end
 +
end
 +
else -- OriginalUrl not set
 +
utilities.set_message ('err_archive_missing_url');
 +
Archived = ''; -- empty string for concatenation
 +
end
 +
elseif utilities.is_set (ArchiveFormat) then
 +
Archived = ArchiveFormat; -- if set and ArchiveURL not set ArchiveFormat has error message
 +
else
 +
Archived = '';
 +
end
 +
 +
local TranscriptURL = A['TranscriptURL']
 +
local TranscriptFormat = A['TranscriptFormat'];
 +
TranscriptFormat = style_format (TranscriptFormat, TranscriptURL, 'transcript-format', 'transcripturl');
 +
local Transcript = A['Transcript'];
 +
local TranscriptURL_origin = A:ORIGIN('TranscriptURL'); -- get name of parameter that holds TranscriptURL
 +
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
 +
 +
local Language = A['Language'];
 +
if utilities.is_set (Language) then
 +
Language = language_parameter (Language); -- format, categories, name from ISO639-1, etc.
 +
else
 +
Language=''; -- language not specified so make sure this is an empty string;
 +
--[[ TODO: need to extract the wrap_msg from language_parameter
 +
so that we can solve parentheses bunching problem with Format/Language/TitleType
 +
]]
 +
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 = TitleType; -- move TitleType to TitleNote so that it renders ahead of |event=
 +
TitleType = ''; -- and unset
 +
 
 +
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, {"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 ({TitleType, Series, Language, Volume, Others, Edition, Publisher}, sepc);
 +
else
 +
tcommon = safe_join ({Title, TitleNote, TitleType, Series, Language, Volume, Others, Edition, Publisher}, 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, Edition, Scale, Series, Language, Cartography, Others, Publisher, Volume}, sepc);
 +
elseif utilities.is_set (Periodical) then -- map in a periodical
 +
tcommon = safe_join ({Title, TitleType, Periodical, Scale, Series, Language, Cartography, Others, Publisher, Volume}, sepc);
 +
else -- a sheet or stand-alone map
 +
tcommon = safe_join ({Title, TitleType, 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, 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 Via = A['Via'];
 +
Via = utilities.is_set (Via) and  wrap_msg ('via', Via) or '';
 +
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, Quote }, sepc );
 +
else
 +
idcommon = safe_join( { ID_list, URL, Archived, AccessDate, Via, Quote }, sepc );
 +
end
 +
 +
local text;
 +
local pgtext = Position .. Sheet .. Sheets .. Page .. Pages .. At;
 +
 
 +
local OrigDate = A['OrigDate'];
 +
OrigDate = utilities.is_set (OrigDate) and wrap_msg ('origdate', OrigDate) or '';
 +
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 = 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.presentation['sep_name'] .. cfg.messages['editor'];
 +
else
 +
Editors = Editors .. cfg.presentation['sep_name'] .. 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.
 +
if '.' == sepc then -- remove final seperator if present
 +
text = text:gsub ('%' .. sepc .. '$', ''); -- dot must be escaped here
 +
else
 +
text = mw.ustring.gsub (text, sepc .. '$', ''); -- using ustring for non-dot sepc (likely a non-Latin character)
 +
end
 +
end
 +
 +
text = safe_join( {text, PostScript}, sepc );
 +
 
 +
-- Now enclose the whole thing in a <cite> element
 +
local options_t = {};
 +
options_t.class = cite_class_attribute_make (config.CitationClass, Mode);
 +
 
 +
local Ref = is_valid_parameter_value (A['Ref'], A:ORIGIN('Ref'), cfg.keywords_lists['ref'], nil, true); -- nil when |ref=harv; A['Ref'] else
 +
 
 +
if 'none' ~= cfg.keywords_xlate[(Ref and Ref:lower()) or ''] then
 +
local namelist_t = {}; -- 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_t = c; -- select it
 +
elseif #a > 0 then -- or an author list
 +
namelist_t = a;
 +
elseif #e > 0 then -- or an editor list
 +
namelist_t = e;
 +
end
 +
local citeref_id;
 +
if #namelist_t > 0 then -- if there are names in namelist_t
 +
citeref_id = make_citeref_id (namelist_t, year); -- go make the CITEREF anchor
 +
if mw.uri.anchorEncode (citeref_id) == ((Ref and mw.uri.anchorEncode (Ref)) or '') then -- Ref may already be encoded (by {{sfnref}}) so citeref_id must be encoded before comparison
 +
utilities.set_message ('maint_ref_duplicates_default');
 +
end
 +
else
 +
citeref_id = ''; -- unset
 +
end
 +
options_t.id = Ref or citeref_id;
 +
end
 +
 
 +
if string.len (text:gsub('%b<>', '')) <= 2 then -- remove html and html-like tags; then get length of what remains;
 +
z.error_cats_t = {}; -- blank the categories list
 +
z.error_msgs_t = {}; -- blank the error messages list
 +
OCinSoutput = nil; -- blank the metadata string
 +
text = ''; -- blank the the citation
 +
utilities.set_message ('err_empty_citation'); -- set empty citation message and category
 +
end
 +
 +
local render_t = {}; -- here we collect the final bits for concatenation into the rendered citation
 +
 
 +
if utilities.is_set (options_t.id) then -- here we wrap the rendered citation in <cite ...>...</cite> tags
 +
table.insert (render_t, utilities.substitute (cfg.presentation['cite-id'], {mw.uri.anchorEncode(options_t.id), mw.text.nowiki(options_t.class), text})); -- when |ref= is set or when there is a namelist
 +
else
 +
table.insert (render_t, utilities.substitute (cfg.presentation['cite'], {mw.text.nowiki(options_t.class), text})); -- when |ref=none or when namelist_t empty and |ref= is missing or is empty
 +
end
 +
 
 +
if OCinSoutput then -- blanked when citation is 'empty' so don't bother to add boilerplate metadata span
 +
table.insert (render_t, utilities.substitute (cfg.presentation['ocins'], OCinSoutput)); -- format and append metadata to the citation
 +
end
 +
 
 +
local template_name = ('citation' == config.CitationClass) and 'citation' or 'cite ' .. (cfg.citation_class_map_t[config.CitationClass] or config.CitationClass);
 +
local template_link = '[[Template:' .. template_name .. '|' .. template_name .. ']]';
 +
local msg_prefix = '<code class="cs1-code">{{' .. template_link .. '}}</code>: ';
 +
 
 +
if 0 ~= #z.error_msgs_t then
 +
mw.addWarning (utilities.substitute (cfg.messages.warning_msg_e, template_link));
 +
 
 +
table.insert (render_t, ' '); -- insert a space between citation and its error messages
 +
table.sort (z.error_msgs_t); -- sort the error messages list; sorting includes wrapping <span> and <code> tags; hidden-error sorts ahead of visible-error
 +
 
 +
local hidden = true; -- presume that the only error messages emited by this template are hidden
 +
for _, v in ipairs (z.error_msgs_t) do -- spin through the list of error messages
 +
if v:find ('cs1-visible-error', 1, true) then -- look for the visible error class name
 +
hidden = false; -- found one; so don't hide the error message prefix
 +
break; -- and done because no need to look further
 +
end
 +
end
 +
 
 +
z.error_msgs_t[1] = table.concat ({utilities.error_comment (msg_prefix, hidden), z.error_msgs_t[1]}); -- add error message prefix to first error message to prevent extraneous punctuation
 +
table.insert (render_t, table.concat (z.error_msgs_t, '; ')); -- make a big string of error messages and add it to the rendering
 +
end
 +
 
 +
if 0 ~= #z.maint_cats_t then
 +
mw.addWarning (utilities.substitute (cfg.messages.warning_msg_m, template_link));
 +
 
 +
table.sort (z.maint_cats_t); -- sort the maintenance messages list
 +
 
 +
local maint_msgs_t = {}; -- here we collect all of the maint messages
 +
 
 +
if 0 == #z.error_msgs_t then -- if no error messages
 +
table.insert (maint_msgs_t, msg_prefix); -- insert message prefix in maint message livery
 +
end
 +
 +
for _, v in ipairs( z.maint_cats_t ) do -- append maintenance categories
 +
table.insert (maint_msgs_t, -- assemble new maint message and add it to the maint_msgs_t table
 +
table.concat ({v, ' (', utilities.substitute (cfg.messages[':cat wikilink'], v), ')'})
 +
);
 +
end
 +
table.insert (render_t, utilities.substitute (cfg.presentation['hidden-maint'], table.concat (maint_msgs_t, ' '))); -- wrap the group of maint messages with proper presentation and save
 +
end
 +
 
 +
if not no_tracking_cats then
 +
local sort_key;
 +
local cat_wikilink = 'cat wikilink';
 +
if cfg.enable_sort_keys then -- when namespace sort keys enabled
 +
local namespace_number = mw.title.getCurrentTitle().namespace; -- get namespace number for this wikitext
 +
sort_key = (0 ~= namespace_number and (cfg.name_space_sort_keys[namespace_number] or cfg.name_space_sort_keys.other)) or nil; -- get sort key character; nil for mainspace
 +
cat_wikilink = (not sort_key and 'cat wikilink') or 'cat wikilink sk'; -- make <cfg.messages> key
 +
end
 +
 
 +
for _, v in ipairs (z.error_cats_t) do -- append error categories
 +
table.insert (render_t, utilities.substitute (cfg.messages[cat_wikilink], {v, sort_key}));
 +
end
 +
if cfg.id_limits_data_load_fail then -- boolean true when load failed
 +
utilities.set_message ('maint_id_limit_load_fail'); -- done here because this maint cat emits no message
 +
end
 +
for _, v in ipairs (z.maint_cats_t) do -- append maintenance categories
 +
table.insert (render_t, utilities.substitute (cfg.messages[cat_wikilink], {v, sort_key}));
 +
end
 +
for _, v in ipairs (z.prop_cats_t) do -- append properties categories
 +
table.insert (render_t, utilities.substitute (cfg.messages['cat wikilink'], v)); -- no sort keys
 +
end
 +
end
 +
 
 +
return table.concat (render_t); -- make a big string and done
 +
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; -- parameter name with enumerator (if any) 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 -- empty deprecated parameters are treated as unknowns
 +
deprecated_parameter (name); -- parameter is deprecated but still supported
 +
return true;
 +
end
 +
if 'tracked' == state then
 +
local base_name = name:gsub ('%d', ''); -- strip enumerators from parameter names that have them to get the base name
 +
utilities.add_prop_cat ('tracked-param', {base_name}, base_name); -- add a properties category; <base_name> modifies <key>
 +
return true;
 +
end
 +
return nil;
 +
end
 +
 
 +
if name:find ('#') then -- # is a cs1|2 reserved character so parameters with # not permitted
 +
return nil;
 +
end
 +
-- replace enumerator digit(s) with # (|last25= becomes |last#=) (mw.ustring because non-Western 'local' digits)
 +
enum_name = mw.ustring.gsub (name, '%d+$', '#'); -- where enumerator is last charaters in parameter name (these to protect |s2cid=)
 +
enum_name = mw.ustring.gsub (enum_name, '%d+([%-l])', '#%1'); -- where enumerator is in the middle of the parameter name; |author#link= is the oddity
 +
 
 +
if 'document' == cite_class then -- special case for {{cite document}}
 +
state = whitelist.document_parameters_t[enum_name]; -- this list holds enumerated and nonenumerated parameters
 +
if true == state_test (state, name) then return true; end
 +
 +
return false;
 +
end
 +
 
 +
if utilities.in_array (cite_class, whitelist.preprint_template_list_t) then -- limited parameter sets allowed for these templates
 +
state = whitelist.limited_parameters_t[enum_name]; -- this list holds enumerated and nonenumerated parameters
 +
if true == state_test (state, name) then return true; end
 +
 
 +
state = whitelist.preprint_arguments_t[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
 +
 
 +
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_t) then -- template-specific parameters for templates that accept parameters from the basic argument list
 +
state = whitelist.unique_arguments_t[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.common_parameters_t[enum_name]; -- all other templates; all normal parameters allowed; this list holds enumerated and nonenumerated parameters
 +
if true == state_test (state, name) then return true; end
 +
 
 +
return false; -- not supported because not found or name is set to nil
 +
end
 +
 
 +
 
 +
--[=[-------------------------< 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 language
 +
code.  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
 +
utilities.set_message ('err_bad_paramlink', parameter); -- emit an error message
 +
_, value, _ = utilities.is_wikilink (value); -- extract label portion from wikilink
 
end
 
end
 +
return value;
 +
end
   −
return table.concat (render_t); -- make a big string and done
  −
end
      +
--[[--------------------------< M I S S I N G _ P I P E _ C H E C K >------------------------------------------
   −
--[[--------------------------< V A L I D A T E >--------------------------------------------------------------
+
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 attributes: class=, title=, etc.  To prevent false positives XML/HTML
 +
tags are removed before the search.
   −
Looks for a parameter's name in one of several whitelists.
+
If a missing pipe is detected, this function adds the missing pipe maintenance category.
   −
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 function missing_pipe_check (parameter, value)
local name = tostring (name);
+
local capture;
local enum_name; -- for enumerated parameters, is name with enumerator replaced with '#'
+
value = value:gsub ('%b<>', ''); -- remove XML/HTML tags because attributes: class=, title=, etc.
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 -- empty deprecated parameters are treated as unknowns
  −
deprecated_parameter (name); -- parameter is deprecated but still supported
  −
return true;
  −
end
  −
if 'tracked' == state then
  −
local base_name = name:gsub ('%d', ''); -- strip enumerators from parameter names that have them to get the base name
  −
utilities.add_prop_cat ('tracked-param', {base_name}, base_name); -- add a properties category; <base_name> modifies <key>
  −
return true;
  −
end
  −
return nil;
  −
end
     −
if name:find ('#') then -- # is a cs1|2 reserved character so parameters with # not permitted
+
capture = value:match ('%s+(%a[%w%-]+)%s*=') or value:match ('^(%a[%w%-]+)%s*='); -- find and categorize parameters with possible missing pipes
return nil;
+
if capture and validate (capture) then -- if the capture is a valid parameter name
 +
utilities.set_message ('err_missing_pipe', parameter);
 
end
 
end
 +
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
+
--[[--------------------------< H A S _ E X T R A N E O U S _ P U N C T >--------------------------------------
if true == state_test (state, name) then return true; end
     −
-- limited enumerated parameters list
+
look for extraneous terminal punctuation in most parameter values; parameters listed in skip table are not checked
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
+
local function has_extraneous_punc (param, value)
state = whitelist.unique_arguments[cite_class][name]; -- look in the template-specific parameter-lists for the template identified by cite_class
+
if 'number' == type (param) then
if true == state_test (state, name) then return true; end
+
return;
end -- if here, fall into general validation
+
end
+
state = whitelist.basic_arguments[name]; -- all other templates; all normal parameters allowed
+
param = param:gsub ('%d+', '#'); -- enumerated name-list mask params allow terminal punct; normalize
if true == state_test (state, name) then return true; end
+
if cfg.punct_skip[param] then
 
+
return; -- parameter name found in the skip table so done
-- all enumerated parameters allowed
+
end
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 value:match ('[,;:]$') then
if true == state_test (state, name) then return true; end
+
utilities.set_message ('maint_extra_punct'); -- has extraneous punctuation; add maint cat
 
+
end
return false; -- not supported because not found or name is set to nil
+
if value:match ('^=') then -- sometimes an extraneous '=' character appears ...
 +
utilities.set_message ('maint_extra_punct'); -- has extraneous punctuation; add maint cat
 +
end
 
end
 
end
      −
--[=[-------------------------< I N T E R _ W I K I _ C H E C K >----------------------------------------------
+
--[[--------------------------< H A S _ T W L _ U R L >--------------------------------------------------------
   −
check <value> for inter-language interwiki-link markup<prefix> must be a MediaWiki-recognized language
+
look for The Wikipedia Library urls in url-holding parametersTWL urls are accessible for readers who are not
code.  when these values have the form (without leading colon):
+
active extended confirmed Wikipedia editorsThis function emits an error message when such urls are discovered.
[[<prefix>:link|label]] return label as plain-text
  −
[[<prefix>:link]] return <prefix>:link as plain-text
     −
return value as is else
+
looks for: '.wikipedialibrary.idm.oclc.org'
   −
]=]
+
]]
   −
local function inter_wiki_check (parameter, value)
+
local function has_twl_url (url_params_t)
local prefix = value:match ('%[%[(%a+):'); -- get an interwiki prefix if one exists
+
local url_error_t = {}; -- sequence of url-holding parameters that have a TWL url
local _;
   
 
if prefix and cfg.inter_wiki_map[prefix:lower()] then -- if prefix is in the map, needs preceding colon so
+
for param, value in pairs (url_params_t) do
utilities.set_message ('err_bad_paramlink', parameter); -- emit an error message
+
if value:find ('%.wikipedialibrary%.idm%.oclc%.org') then -- has the TWL base url?
_, value, _ = utilities.is_wikilink (value); -- extract label portion from wikilink
+
table.insert (url_error_t, utilities.wrap_style ('parameter', param)); -- add parameter name to the list
 +
end
 +
end
 +
if 0 ~= #url_error_t then -- non-zero when there are errors
 +
table.sort (url_error_t);
 +
utilities.set_message ('err_param_has_twl_url', {utilities.make_sep_list (#url_error_t, url_error_t)}); -- add this error message
 +
return true;
 
end
 
end
return value;
   
end
 
end
      −
--[[--------------------------< M I S S I N G _ P I P E _ C H E C K >------------------------------------------
+
--[[--------------------------< H A S _ E X T R A N E O U S _ U R L >------------------------------------------
 +
 
 +
look for extraneous url parameter values; parameters listed in skip table are not checked
   −
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 attributes: class=, title=, etc.  To prevent false positives XML/HTML
  −
tags 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
  −
utilities.set_message ('err_missing_pipe', parameter);
  −
end
  −
end
  −
 
  −
 
  −
--[[--------------------------< 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
  −
end
  −
if value:match ('^=') then -- sometimes an extraneous '=' character appears ...
  −
utilities.set_message ('maint_extra_punct'); -- has extraneous punctuation; add maint cat
  −
end
  −
end
  −
 
  −
 
  −
--[[--------------------------< H A S _ E X T R A N E O U S _ U R L >------------------------------------------
     −
look for extraneous url parameter values; parameters listed in skip table are not checked
+
local function has_extraneous_url (non_url_param_t)
 
  −
]]
  −
 
  −
local function has_extraneous_url (url_param_t)
   
local url_error_t = {};
 
local url_error_t = {};
 
 
check_for_url (url_param_t, url_error_t); -- extraneous url check
+
check_for_url (non_url_param_t, url_error_t); -- extraneous url check
 
if 0 ~= #url_error_t then -- non-zero when there are errors
 
if 0 ~= #url_error_t then -- non-zero when there are errors
 
table.sort (url_error_t);
 
table.sort (url_error_t);
Line 4,201: Line 4,469:       −
--[[--------------------------< C I T A T I O N >--------------------------------------------------------------
+
--[[--------------------------< _ C I T A T I O N >------------------------------------------------------------
 +
 
 +
Module entry point
   −
This is used by templates such as {{cite book}} to create the actual citation text.
+
frame – from template call (citation()); may be nil when called from another module
 +
args – table of all cs1|2 parameters in the template (the template frame)
 +
config – table of template-supplied parameter (the #invoke frame)
    
]]
 
]]
   −
local function citation(frame)
+
local function _citation (frame, args, config) -- save a copy in case we need to display an error message in preview mode
Frame = frame; -- save a copy in case we need to display an error message in preview mode
+
if not frame then
 
+
frame = mw.getCurrentFrame(); -- if called from another module, get a frame for frame-provided functions
local config = {}; -- table to store parameters from the module {{#invoke:}}
+
end
for k, v in pairs( frame.args ) do -- get parameters from the {{#invoke}} frame
  −
config[k] = v;
  −
-- args[k] = v; -- crude debug support that allows us to render a citation from module {{#invoke:}}; skips parameter validation; TODO: keep?
  −
end
   
-- i18n: set the name that your wiki uses to identify sandbox subpages from sandbox template invoke (or can be set here)
 
-- i18n: set the name that your wiki uses to identify sandbox subpages from sandbox template invoke (or can be set here)
 
local sandbox = ((config.SandboxPath and '' ~= config.SandboxPath) and config.SandboxPath) or '/sandbox'; -- sandbox path from {{#invoke:Citation/CS1/sandbox|citation|SandboxPath=/...}}
 
local sandbox = ((config.SandboxPath and '' ~= config.SandboxPath) and config.SandboxPath) or '/sandbox'; -- sandbox path from {{#invoke:Citation/CS1/sandbox|citation|SandboxPath=/...}}
Line 4,220: Line 4,488:  
sandbox = is_sandbox and sandbox or ''; -- use i18n sandbox to load sandbox modules when this module is the sandox; live modules else
 
sandbox = is_sandbox and sandbox or ''; -- use i18n sandbox to load sandbox modules when this module is the sandox; live modules else
   −
local pframe = frame:getParent()
  −
local styles;
  −
   
cfg = mw.loadData ('Module:Citation/CS1/Configuration' .. sandbox); -- load sandbox versions of support modules when {{#invoke:Citation/CS1/sandbox|...}}; live modules else
 
cfg = mw.loadData ('Module:Citation/CS1/Configuration' .. sandbox); -- load sandbox versions of support modules when {{#invoke:Citation/CS1/sandbox|...}}; live modules else
 
whitelist = mw.loadData ('Module:Citation/CS1/Whitelist' .. sandbox);
 
whitelist = mw.loadData ('Module:Citation/CS1/Whitelist' .. sandbox);
Line 4,229: Line 4,494:  
identifiers = require ('Module:Citation/CS1/Identifiers' .. sandbox);
 
identifiers = require ('Module:Citation/CS1/Identifiers' .. sandbox);
 
metadata = require ('Module:Citation/CS1/COinS' .. sandbox);
 
metadata = require ('Module:Citation/CS1/COinS' .. sandbox);
styles = 'Module:Citation/CS1' .. sandbox .. '/styles.css';
      
utilities.set_selected_modules (cfg); -- so that functions in Utilities can see the selected cfg tables
 
utilities.set_selected_modules (cfg); -- so that functions in Utilities can see the selected cfg tables
Line 4,240: Line 4,504:  
is_preview_mode = not utilities.is_set (frame:preprocess ('{{REVISIONID}}'));
 
is_preview_mode = not utilities.is_set (frame:preprocess ('{{REVISIONID}}'));
   −
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 suggestions = {}; -- table where we store suggestions if we need to loadData them
 
local error_text; -- used as a flag
 
local error_text; -- used as a flag
Line 4,246: Line 4,509:  
local capture; -- the single supported capture when matching unknown parameters using patterns
 
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
 
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
+
for k, v in pairs( 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
 
v = mw.ustring.gsub (v, '^%s*(.-)%s*$', '%1'); -- trim leading/trailing whitespace; when v is only whitespace, becomes empty string
 
if v ~= '' then
 
if v ~= '' then
Line 4,306: Line 4,569:  
end
 
end
   −
local url_param_t = {};
+
local non_url_param_t = {}; -- table of parameters and values that are not url-holding parameters
 +
local url_param_t = {}; -- table of url-holding paramters and their values
    
for k, v in pairs( args ) do
 
for k, v in pairs( args ) do
Line 4,316: Line 4,580:  
args[k] = inter_wiki_check (k, v); -- when language interwiki-linked parameter missing leading colon replace with wiki-link label
 
args[k] = inter_wiki_check (k, v); -- when language interwiki-linked parameter missing leading colon replace with wiki-link label
   −
if 'string' == type (k) and not cfg.url_skip[k] then -- when parameter k is not positional and not in url skip table
+
if 'string' == type (k) then -- when parameter k is not positional
url_param_t[k] = v; -- make a parameter/value list for extraneous url check
+
if not cfg.url_skip[k] then -- and not in url skip table
 +
non_url_param_t[k] = v; -- make a parameter/value list for extraneous url check
 +
else -- and is in url skip table (a url-holding parameter)
 +
url_param_t[k] = v; -- make a parameter/value list to check for values that are The Wikipedia Library url
 +
end
 
end
 
end
 
end
 
end
   −
has_extraneous_url (url_param_t); -- look for url in parameter values where a url does not belong
+
has_extraneous_url (non_url_param_t); -- look for url in parameter values where a url does not belong
 
+
if has_twl_url (url_param_t) then -- look for url-holding parameters that hold a The Wikipedia Library url
 +
args['url-access'] = 'subscription';
 +
end
 
return table.concat ({
 
return table.concat ({
frame:extensionTag ('templatestyles', '', {src=styles}),
+
frame:extensionTag ('templatestyles', '', {src='Module:Citation/CS1' .. sandbox .. '/styles.css'}),
 
citation0( config, args)
 
citation0( config, args)
 
});
 
});
 +
end
 +
 +
 +
--[[--------------------------< C I T A T I O N >--------------------------------------------------------------
 +
 +
Template entry point
 +
 +
]]
 +
 +
local function citation (frame)
 +
local config_t = {}; -- table to store parameters from the module {{#invoke:}}
 +
local args_t = frame:getParent().args; -- get template's preset parameters
 +
 +
for k, v in pairs (frame.args) do -- get parameters from the {{#invoke}} frame
 +
config_t[k] = v;
 +
-- args_t[k] = v; -- crude debug support that allows us to render a citation from module {{#invoke:}}; skips parameter validation; TODO: keep?
 +
end
 +
return _citation (frame, args_t, config_t)
 
end
 
end
   Line 4,333: Line 4,621:  
]]
 
]]
   −
return {citation = citation};
+
return {
 +
citation = citation, -- template entry point
 +
 +
_citation = _citation, -- module entry point
 +
}