Changes

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
 +
 
 +
 
 +
--[[--------------------------< C H E C K _ P U B L I S H E R _ N A M E >--------------------------------------
 +
 
 +
look for variations of '<text>: <text>' that might be '<location>: <publisher>' in |publisher= parameter value.
 +
when found, emit a maintenance message; return nil else
 +
 
 +
<publisher> is the value assigned to |publisher= or |institution=
   −
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'];
+
local function check_publisher_name (publisher)
local Periodical_origin = '';
+
local patterns_t = {
if utilities.is_set (Periodical) then
+
'^[%w%s]+%s*:%s*[%w%s]+$', -- plain text <location>: <publisher>
Periodical_origin = A:ORIGIN('Periodical'); -- get the name of the periodical parameter
+
'^%[+[%w%s:|]+%]+%s*:%s*[%w%s]+$', -- partially wikilinked [[<location>]]: <publisher>
local i;
+
'^[%w%s]+%s*:%s*%[+[%w%s:|]+%]+$', -- partially wikilinked <location>: [[<publisher>]]
Periodical, i = utilities.strip_apostrophe_markup (Periodical); -- strip apostrophe markup so that metadata isn't contaminated
+
'^%[+[%w%s:|]+%]+%s*:%s*%[+[%w%s:|]+%]+$', -- wikilinked [[<location>]]: [[<publisher>]]
if i then -- non-zero when markup was stripped so emit an error message
+
}
utilities.set_message ('err_apostrophe_markup', {Periodical_origin});
+
 +
for _, pattern in ipairs (patterns_t) do -- spin through the patterns_t sequence
 +
if mw.ustring.match (publisher, pattern) then -- does this pattern match?
 +
utilities.set_message ('maint_publisher_location'); -- set a maint message
 +
return; -- and done
 
end
 
end
 
end
 
end
 +
end
   −
if 'mailinglist' == config.CitationClass then -- special case for {{cite mailing list}}
  −
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', 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}}
+
--[[--------------------------< I S _ P A G E _ A R T _ N U M >------------------------------------------------
Periodical_origin = A:ORIGIN('MailingList');
  −
end
     −
local ScriptPeriodical = A['ScriptPeriodical'];
+
compare the trailing (rightmost) characters of the |doi= value against the whole value assigned to |page(s)=.
 +
 
 +
return boolean true when:
 +
|page(s)= has exactly 8 digits and a dot between the fourth and fifth digits matches the trailing 9 characters
 +
of the |doi= value: |page=12345678 → |page=1234.5678 matches |doi=10.xxxx/yyyy1234.5678
 +
|page(s)= is 5 or more characters and matches |doi= values's trailing characters
 +
|page(s)= begins with a lowercase 'e' and |page(s)= without the 'e' matches |doi= values's trailing
 +
characters: |page=e12345 → |page=12345 matches |doi=10.xxxx/yyyy12345
 +
|page(s)= begins with a uppercase 'CD' followed by (typically) six digits matches |doi= values that ends with
 +
'CDxxxxxx.pubx' (where 'x' is any single digit)
 +
 
 +
return nil when |page(s)= values:
 +
are ranges separated by underscore, hyphen, emdash, endash, figure dash, or minus character
 +
are comma- or semicolon-separated lists of pages
 +
have external urls (has text 'http')
 +
are digit-only values less than 10000
 +
do not match |doi= values's trailing characters
 +
 
 +
]]
 +
 
 +
local function is_page_art_num (page, doi)
 +
if not (utilities.is_set (page) and utilities.is_set (doi)) then -- both required
 +
return; -- abandon; nothing to do
 +
end
 +
 
 +
if page:match ('[,;_−–—‒%-]') then -- when |page(s)= might be a page range or a separated list of pages
 +
return; -- abandon
 +
end
 +
 
 +
page = page:lower(); -- because doi names are case insensitive
 +
doi = doi:lower(); -- force these to lowercase for testing
 +
 +
if page:match ('http') then -- when |page(s)= appears to hold a url
 +
return; -- abandon
 +
end
 +
 
 +
if tonumber (page) then -- is |page(s)= digits only
 +
if 10000 > tonumber (page) then -- when |page(s)= less than 10000
 +
return; -- abandon
 +
end
 +
 +
if doi:match (page .. '$') then -- digits only page number match the last digits in |doi=?
 +
return true;
 +
end
   −
-- web and news not tested for now because of
+
if 8 == page:len() then -- special case when |page(s)= is exactly 8 digits
-- Wikipedia:Administrators%27_noticeboard#Is_there_a_semi-automated_tool_that_could_fix_these_annoying_"Cite_Web"_errors?
+
local dot_page = page:gsub ('(%d%d%d%d)(%d%d%d%d)', '%1.%2'); -- make a |page=xxxx.yyyy version commonly used in |doi=
if not (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) then -- 'periodical' templates require periodical parameter
+
if doi:match (dot_page .. '$') then -- 8-digit dotted page number match the last characters in |doi=?
-- local p = {['journal'] = 'journal', ['magazine'] = 'magazine', ['news'] = 'newspaper', ['web'] = 'website'}; -- for error message
+
return true;
local p = {['journal'] = 'journal', ['magazine'] = 'magazine'}; -- for error message
+
end
if p[config.CitationClass]  then
  −
utilities.set_message ('err_missing_periodical', {config.CitationClass, p[config.CitationClass]});
   
end
 
end
end
   
 
local Volume;
+
else -- here when |page(s)= is alpha-numeric
local ScriptPeriodical_origin = A:ORIGIN('ScriptPeriodical');
+
if 4 < page:len() then -- when |page(s)= is five or more characters
if 'citation' == config.CitationClass then
+
if doi:match (page .. '$') then -- alpha-numeric page match the last characters in |doi=?
if utilities.is_set (Periodical) then
+
return true;
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'
+
 +
local epage = page:match ('^e([%w]+)$'); -- if first character of |page= is 'e', remove it
 +
if epage and doi:match (epage .. '$') then -- page number match the last characters in |doi=?
 +
return true;
 
end
 
end
elseif utilities.is_set (ScriptPeriodical) then
+
if 'script-website' ~= ScriptPeriodical_origin then -- {{citation}} does not render volume for |script-website=
+
local cdpage = page:match ('^cd%d+$'); -- if first characters of |page= are 'CD' and last characters are digits (typically 6 digits)
Volume = A['Volume']; -- but does for all other 'periodicals'
+
if cdpage and doi:match (cdpage .. '%.pub%d$') then -- page number matches doi 'CDxxxxxx.pubx' where 'x' is a digit
 +
return true;
 
end
 
end
else
  −
Volume = A['Volume']; -- and does for non-'periodical' cites
   
end
 
end
elseif utilities.in_array (config.CitationClass, cfg.templates_using_volume) then -- render |volume= for cs1 according to the configuration settings
+
end
Volume = A['Volume'];
+
end
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
+
-- Pick out the relevant fields from the arguments.  Different citation templates
if i then -- non-zero when markup was stripped so emit an error message
+
-- define different field names for the same underlying things.
utilities.set_message ('err_apostrophe_markup', {PublisherName_origin});
+
 
 +
local author_etal;
 +
local a = {}; -- authors list from |lastn= / |firstn= pairs or |vauthors=
 +
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
 +
 
 +
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
 +
utilities.set_message ('maint_overridden_setting'); -- set a maint message
 
end
 
end
end
     −
local Newsgroup = A['Newsgroup']; -- TODO: strip apostrophe markup?
+
local Collaboration = A['Collaboration'];
local Newsgroup_origin = A:ORIGIN('Newsgroup');
     −
if 'newsgroup' == config.CitationClass then
+
do -- to limit scope of selected
if utilities.is_set (PublisherName) then -- general use parameter |publisher= not allowed in cite newsgroup
+
local selected = select_author_editor_source (A['Vauthors'], A['Authors'], args, 'AuthorList');
utilities.set_message ('err_parameter_ignored', {PublisherName_origin});
+
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, 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
  −
PublisherName = nil; -- ensure that this parameter is unset for the time being; will be used again after COinS
   
end
 
end
   −
local URL = A['URL']; -- TODO: better way to do this for URL, ChapterURL, and MapURL?
+
local editor_etal;
local UrlAccess = is_valid_parameter_value (A['UrlAccess'], A:ORIGIN('UrlAccess'), cfg.keywords_lists['url-access'], nil);
+
local e = {}; -- editors list from |editor-lastn= / |editor-firstn= pairs or |veditors=
  −
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['Veditors'], nil, args, 'EditorList'); -- support for |editors= withdrawn
MapUrlAccess = nil;
+
if 1 == selected then
utilities.set_message ('err_param_access_requires_param', {'map-url'});
+
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
 
end
 
+
local this_page = mw.title.getCurrentTitle(); -- also used for COinS and for language
+
local Chapter = A['Chapter']; -- done here so that we have access to |contribution= from |chapter= aliases
local no_tracking_cats = is_valid_parameter_value (A['NoTracking'], A:ORIGIN('NoTracking'), cfg.keywords_lists['yes_true_y'], nil);
+
local Chapter_origin = A:ORIGIN ('Chapter');
 
+
local Contribution; -- because contribution is required for contributor(s)
-- check this page to see if it is in one of the namespaces that cs1 is not supposed to add to the error categories
+
if 'contribution' == Chapter_origin then
if not utilities.is_set (no_tracking_cats) then -- ignore if we are already not going to categorize this page
+
Contribution = Chapter; -- get the name of the contribution
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
   
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
 
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 Title = A['Title'];
+
local TitleLink = A['TitleLink'];
Page, Pages, At, coins_pages = insource_loc_get (Page, A:ORIGIN('Page'), Pages, A:ORIGIN('Pages'), At);
     −
local NoPP = is_valid_parameter_value (A['NoPP'], A:ORIGIN('NoPP'), cfg.keywords_lists['yes_true_y'], nil);
+
local auto_select = ''; -- default is auto
 
+
local accept_link;
if utilities.is_set (PublicationPlace) and utilities.is_set (Place) then -- both |publication-place= and |place= (|location=) allowed if different
+
TitleLink, accept_link = utilities.has_accept_as_written (TitleLink, true); -- test for accept-this-as-written markup
utilities.add_prop_cat ('location-test'); -- add property cat to evaluate how often PublicationPlace and Place are used together
+
if (not accept_link) and utilities.in_array (TitleLink, {'none', 'pmc', 'doi'}) then -- check for special keywords
if PublicationPlace == Place then
+
auto_select = TitleLink; -- remember selection for later
Place = ''; -- unset; don't need both if they are the same
+
TitleLink = ''; -- treat as if |title-link= would have been empty
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
+
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 URL_origin = A:ORIGIN('URL'); -- get name of parameter that holds URL
  −
local ChapterURL_origin = A:ORIGIN('ChapterURL'); -- get name of parameter that holds ChapterURL
  −
local ScriptChapter = A['ScriptChapter'];
  −
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
+
local Section = ''; -- {{cite map}} only; preset to empty string for concatenation if not used
|url= maps to |chapter-url= when |title= is remapped
+
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}}
All other combinations of |encyclopedia=, |title=, and |article= are not modified
+
Chapter = ''; -- unset for now; will be reset later from |map= if present
  −
]]
  −
 
  −
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
   −
if ('encyclopaedia' == config.CitationClass) or ('citation' == config.CitationClass and utilities.is_set (Encyclopedia)) then
+
local Periodical = A['Periodical'];
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?
+
local Periodical_origin = A:ORIGIN('Periodical');
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', A:ORIGIN ('Encyclopedia')) .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', Periodical_origin)});
+
local ScriptPeriodical = A['ScriptPeriodical'];
 +
local ScriptPeriodical_origin = A:ORIGIN('ScriptPeriodical');
 +
local TransPeriodical = A['TransPeriodical'];
 +
local TransPeriodical_origin = A:ORIGIN ('TransPeriodical');
 +
 +
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
 
end
   −
if utilities.is_set (Encyclopedia) then
+
if utilities.is_set (param) then -- if we found one
Periodical = Encyclopedia; -- error or no, set Periodical to Encyclopedia; allow periodical without encyclopedia
+
utilities.set_message ('err_periodical_ignored', {param}); -- emit an error message
Periodical_origin = A:ORIGIN ('Encyclopedia');
   
end
 
end
 +
end
   −
if utilities.is_set (Periodical) then -- Periodical is set when |encyclopedia= is set
+
if utilities.is_set (Periodical) then
if utilities.is_set (Title) or utilities.is_set (ScriptTitle) then
+
local i;
if not utilities.is_set (Chapter) then
+
Periodical, i = utilities.strip_apostrophe_markup (Periodical); -- strip apostrophe markup so that metadata isn't contaminated
Chapter = Title; -- |encyclopedia= and |title= are set so map |title= to |article= and |encyclopedia= to |title=
+
if i then -- non-zero when markup was stripped so emit an error message
ScriptChapter = ScriptTitle;
+
utilities.set_message ('err_apostrophe_markup', {Periodical_origin});
ScriptChapter_origin = A:ORIGIN('ScriptTitle')
+
end
TransChapter = TransTitle;
+
end
ChapterURL = URL;
  −
ChapterURL_origin = URL_origin;
     −
ChapterUrlAccess = UrlAccess;
+
if 'mailinglist' == config.CitationClass then -- special case for {{cite mailing list}}
 
+
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?
if not utilities.is_set (ChapterURL) and utilities.is_set (TitleLink) then
+
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', Periodical_origin) .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'mailinglist')});
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
     −
-- special case for cite techreport.
+
Periodical = A ['MailingList']; -- error or no, set Periodical to |mailinglist= value because this template is {{cite mailing list}}
local ID = A['ID'];
+
Periodical_origin = A:ORIGIN('MailingList');
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
   −
-- Account for the oddity that is {{cite conference}}, before generation of COinS data.
+
-- web and news not tested for now because of  
local ChapterLink -- = A['ChapterLink']; -- deprecated as a parameter but still used internally by cite episode
+
-- Wikipedia:Administrators%27_noticeboard#Is_there_a_semi-automated_tool_that_could_fix_these_annoying_"Cite_Web"_errors?
local Conference = A['Conference'];
+
if not (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) then -- 'periodical' templates require periodical parameter
local BookTitle = A['BookTitle'];
+
-- local p = {['journal'] = 'journal', ['magazine'] = 'magazine', ['news'] = 'newspaper', ['web'] = 'website'}; -- for error message
local TransTitle_origin = A:ORIGIN ('TransTitle');
+
local p = {['journal'] = 'journal', ['magazine'] = 'magazine'}; -- for error message
if 'conference' == config.CitationClass then
+
if p[config.CitationClass]  then
if utilities.is_set (BookTitle) then
+
utilities.set_message ('err_missing_periodical', {config.CitationClass, p[config.CitationClass]});
Chapter = Title;
  −
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 Volume;
local Mode = is_valid_parameter_value (A['Mode'], A:ORIGIN('Mode'), cfg.keywords_lists['mode'], '');
+
if 'citation' == config.CitationClass then
-- separator character and postscript
+
if utilities.is_set (Periodical) then
local sepc, PostScript = set_style (Mode:lower(), A['PostScript'], config.CitationClass);
+
if not utilities.in_array (Periodical_origin, cfg.citation_no_volume_t) then -- {{citation}} does not render |volume= when these parameters are used
-- controls capitalization of certain static text
+
Volume = A['Volume']; -- but does for all other 'periodicals'
local use_lowercase = ( sepc == ',' );
+
end
+
elseif utilities.is_set (ScriptPeriodical) then
-- cite map oddities
+
if 'script-website' ~= ScriptPeriodical_origin then -- {{citation}} does not render volume for |script-website=
local Cartography = "";
+
Volume = A['Volume']; -- but does for all other 'periodicals'
local Scale = "";
+
end
local Sheet = A['Sheet'] or '';
+
else
local Sheets = A['Sheets'] or '';
+
Volume = A['Volume']; -- and does for non-'periodical' cites
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
 
end
Chapter = A['Map'];
+
elseif utilities.in_array (config.CitationClass, cfg.templates_using_volume) then -- render |volume= for cs1 according to the configuration settings
Chapter_origin = A:ORIGIN('Map');
+
Volume = A['Volume'];
ChapterURL = A['MapURL'];
+
end
ChapterURL_origin = A:ORIGIN('MapURL');
+
extra_text_in_vol_iss_check (Volume, A:ORIGIN ('Volume'), 'v');
TransChapter = A['TransMap'];
  −
ScriptChapter = A['ScriptMap']
  −
ScriptChapter_origin = A:ORIGIN('ScriptMap')
     −
ChapterUrlAccess = MapUrlAccess;
+
local Issue;
ChapterFormat = A['MapFormat'];
+
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
Cartography = A['Cartography'];
+
Issue = utilities.hyphen_to_dash (A['Issue']);
if utilities.is_set ( Cartography ) then
+
end
Cartography = sepc .. " " .. wrap_msg ('cartography', Cartography, use_lowercase);
+
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
end
+
if not (utilities.in_array (config.CitationClass, {'conference', 'map', 'citation'}) and not (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical))) then
Scale = A['Scale'];
+
Issue = utilities.hyphen_to_dash (A['Issue']);
if utilities.is_set ( Scale ) then
  −
Scale = sepc .. " " .. Scale;
   
end
 
end
 
end
 
end
 +
 +
local ArticleNumber;
   −
-- Account for the oddities that are {{cite episode}} and {{cite serial}}, before generation of COinS data.
+
if utilities.in_array (config.CitationClass, {'journal', 'conference'}) or ('citation' == config.CitationClass and utilities.is_set (Periodical) and 'journal' == Periodical_origin) then
local Series = A['Series'];
+
ArticleNumber = A['ArticleNumber'];
if 'episode' == config.CitationClass or 'serial' == config.CitationClass then
+
end
local SeriesLink = A['SeriesLink'];
+
 
 +
extra_text_in_vol_iss_check (Issue, A:ORIGIN ('Issue'), 'i');
 +
 
 +
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
   −
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 NoPP = is_valid_parameter_value (A['NoPP'], A:ORIGIN('NoPP'), cfg.keywords_lists['yes_true_y'], nil);
   −
local Network = A['Network'];
+
local Mode = mode_set (A['Mode'], A:ORIGIN('Mode'));
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?
+
-- separator character and postscript
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'season') .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'seriesno')}); -- add error message
+
local sepc, PostScript = set_style (Mode:lower(), A['PostScript'], config.CitationClass);
SeriesNumber = ''; -- unset; prefer |season= over |seriesno=
+
local Quote;
end
+
Quote, QuotePage, QuotePages, PostScript = quote_make (A['Quote'], A['TransQuote'], A['ScriptQuote'], QuotePage, QuotePages, NoPP, sepc, PostScript);
-- 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
+
local Edition = A['Edition'];
if utilities.is_set (SeriesNumber) then table.insert(s, wrap_msg ('seriesnum', SeriesNumber, use_lowercase)); end
+
local PublicationPlace = place_check (A['PublicationPlace'], A:ORIGIN('PublicationPlace'));
if utilities.is_set (Issue) then table.insert(s, wrap_msg ('episode', Issue, use_lowercase)); end
+
local Place = place_check (A['Place'], A:ORIGIN('Place'));
Issue = ''; -- unset because this is not a unique parameter
+
 +
local PublisherName = A['PublisherName'];
 +
local PublisherName_origin = A:ORIGIN('PublisherName');
 +
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
 
 
Chapter = Title; -- promote title parameters to chapter
+
if ('document' == config.CitationClass) and not utilities.is_set (PublisherName) then
ScriptChapter = ScriptTitle;
+
utilities.set_message ('err_missing_publisher', {config.CitationClass, 'publisher'});
ScriptChapter_origin = A:ORIGIN('ScriptTitle');
+
end
ChapterLink = TitleLink; -- alias |episode-link=
+
 
TransChapter = TransTitle;
+
local Newsgroup = A['Newsgroup']; -- TODO: strip apostrophe markup?
ChapterURL = URL;
+
local Newsgroup_origin = A:ORIGIN('Newsgroup');
ChapterUrlAccess = UrlAccess;
  −
ChapterURL_origin = URL_origin;
  −
ChapterFormat = Format;
     −
Title = Series; -- promote series to title
+
if 'newsgroup' == config.CitationClass then
TitleLink = SeriesLink;
+
if utilities.is_set (PublisherName) and (cfg.keywords_xlate['none'] ~= PublisherName) then -- general use parameter |publisher= not allowed in cite newsgroup
Series = table.concat(s, sepc .. ' '); -- this is concatenation of season, seriesno, episode number
+
utilities.set_message ('err_parameter_ignored', {PublisherName_origin});
 +
end
   −
if utilities.is_set (ChapterLink) and not utilities.is_set (ChapterURL) then -- link but not URL
+
PublisherName = nil; -- ensure that this parameter is unset for the time being; will be used again after COinS
Chapter = utilities.make_wikilink (ChapterLink, Chapter);
+
end
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
+
if 'book' == config.CitationClass or 'encyclopaedia' == config.CitationClass or ('citation' == config.CitationClass and not utilities.is_set (Periodical)) then
local TitleType = A['TitleType'];
+
local accept;
local Degree = A['Degree'];
+
PublisherName, accept = utilities.has_accept_as_written (PublisherName); -- check for and remove accept-as-written markup from |publisher= wrapped
if utilities.in_array (config.CitationClass, {'AV-media-notes', 'interview', 'mailinglist', 'map', 'podcast', 'pressrelease', 'report', 'speech', 'techreport', 'thesis'}) then
+
if not accept then -- when no accept-as-written markup
TitleType = set_titletype (config.CitationClass, TitleType);
+
check_publisher_name (PublisherName); -- emit maint message when |publisher= might be prefixed with publisher's location
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
   −
if utilities.is_set (TitleType) then -- if type parameter is specified
+
local URL = A['URL']; -- TODO: better way to do this for URL, ChapterURL, and MapURL?
TitleType = utilities.substitute ( cfg.messages['type'], TitleType); -- display it in parentheses
+
local UrlAccess = is_valid_parameter_value (A['UrlAccess'], A:ORIGIN('UrlAccess'), cfg.keywords_lists['url-access'], nil);
-- TODO: Hack on TitleType to fix bunched parentheses problem
+
 +
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
 
end
   −
-- legacy: promote PublicationDate to Date if neither Date nor Year are set.
+
local MapUrlAccess = is_valid_parameter_value (A['MapUrlAccess'], A:ORIGIN('MapUrlAccess'), cfg.keywords_lists['url-access'], nil);
local Date = A['Date'];
+
if not utilities.is_set (A['MapURL']) and utilities.is_set (MapUrlAccess) then
local Date_origin; -- to hold the name of parameter promoted to Date; required for date error messaging
+
MapUrlAccess = nil;
local PublicationDate = A['PublicationDate'];
+
utilities.set_message ('err_param_access_requires_param', {'map-url'});
local Year = A['Year'];
+
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);
   −
if not utilities.is_set (Date) then
+
-- check this page to see if it is in one of the namespaces that cs1 is not supposed to add to the error categories
Date = Year; -- promote Year to Date
+
if not utilities.is_set (no_tracking_cats) then -- ignore if we are already not going to categorize this page
Year = nil; -- make nil so Year as empty string isn't used for CITEREF
+
if cfg.uncategorized_namespaces[this_page.namespace] then -- is this page's namespace id one of the uncategorized namespace ids?
if not utilities.is_set (Date) and utilities.is_set (PublicationDate) then -- use PublicationDate when |date= and |year= are not set
+
no_tracking_cats = "true"; -- set no_tracking_cats
Date = PublicationDate; -- promote PublicationDate to Date
+
end
PublicationDate = ''; -- unset, no longer needed
+
for _, v in ipairs (cfg.uncategorized_subpages) do -- cycle through page name patterns
Date_origin = A:ORIGIN('PublicationDate'); -- save the name of the promoted parameter
+
if this_page.text:match (v) then -- test page name against each pattern
else
+
no_tracking_cats = "true"; -- set no_tracking_cats
Date_origin = A:ORIGIN('Year'); -- save the name of the promoted parameter
+
break; -- bail out if one is found
 +
end
 
end
 
end
else
  −
Date_origin = A:ORIGIN('Date'); -- not a promotion; name required for error messaging
   
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
   −
if PublicationDate == Date then PublicationDate = ''; end -- if PublicationDate is same as Date, don't display in rendered citation
+
local coins_pages;
 
  −
--[[
  −
Go test all of the date-holding parameters for valid MOS:DATE format and make sure that dates are real dates. This must be done before we do COinS because here is where
  −
we get the date used in the metadata.
   
 
Date validation supporting code is in Module:Citation/CS1/Date_validation
+
Page, Pages, At, coins_pages = insource_loc_get (Page, A:ORIGIN('Page'), Pages, A:ORIGIN('Pages'), At);
]]
     −
local DF = is_valid_parameter_value (A['DF'], A:ORIGIN('DF'), cfg.keywords_lists['df'], '');
+
if utilities.is_set (PublicationPlace) and utilities.is_set (Place) then -- both |publication-place= and |place= (|location=) allowed if different
if not utilities.is_set (DF) then
+
utilities.add_prop_cat ('location-test'); -- add property cat to evaluate how often PublicationPlace and Place are used together
DF = cfg.global_df; -- local |df= if present overrides global df set by {{use xxx date}} template
+
if PublicationPlace == Place then
 +
Place = ''; -- unset; don't need both if they are the same
 +
end
 +
elseif not utilities.is_set (PublicationPlace) and utilities.is_set (Place) then -- when only |place= (|location=) is set ...
 +
PublicationPlace = Place; -- promote |place= (|location=) to |publication-place
 
end
 
end
   −
local ArchiveURL;
+
if PublicationPlace == Place then Place = ''; end -- don't need both if they are the same
local ArchiveDate;
  −
local ArchiveFormat = A['ArchiveFormat'];
     −
ArchiveURL, ArchiveDate = archive_url_check (A['ArchiveURL'], A['ArchiveDate'])
+
local URL_origin = A:ORIGIN('URL'); -- get name of parameter that holds URL
ArchiveFormat = style_format (ArchiveFormat, ArchiveURL, 'archive-format', 'archive-url');
+
local ChapterURL_origin = A:ORIGIN('ChapterURL'); -- get name of parameter that holds ChapterURL
 +
local ScriptChapter = A['ScriptChapter'];
 +
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'];
 
 
ArchiveURL, ArchiveDate = is_unique_archive_url (ArchiveURL, URL, ChapterURL, A:ORIGIN('ArchiveURL'), ArchiveDate); -- add error message when URL or ChapterURL == ArchiveURL
+
--[[
 +
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
 
 
local AccessDate = A['AccessDate'];
+
All other combinations of |encyclopedia=, |title=, and |article= are not modified
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 Encyclopedia = A['Encyclopedia']; -- used as a flag by this module and by ~/COinS
local anchor_year; -- used in the CITEREF identifier
+
local ScriptEncyclopedia = A['ScriptEncyclopedia'];
do -- create defined block to contain local variables error_message, date_parameters_list, mismatch
+
local TransEncyclopedia = A['TransEncyclopedia'];
local error_message = '';
+
 
-- AirDate has been promoted to Date so not necessary to check it
+
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}}
local date_parameters_list = {
+
if 'encyclopaedia' ~= config.CitationClass and 'citation' ~= config.CitationClass then
['access-date'] = {val = AccessDate, name = A:ORIGIN ('AccessDate')},
+
if utilities.is_set (Encyclopedia) then
['archive-date'] = {val = ArchiveDate, name = A:ORIGIN ('ArchiveDate')},
+
utilities.set_message ('err_parameter_ignored', {A:ORIGIN ('Encyclopedia')});
['date'] = {val = Date, name = Date_origin},
+
else
['doi-broken-date'] = {val = DoiBroken, name = A:ORIGIN ('DoiBroken')},
+
utilities.set_message ('err_parameter_ignored', {A:ORIGIN ('ScriptEncyclopedia')});
['pmc-embargo-date'] = {val = Embargo, name = A:ORIGIN ('Embargo')},
+
end
['lay-date'] = {val = LayDate, name = A:ORIGIN ('LayDate')},
+
Encyclopedia = nil; -- unset these because not supported by this template
['publication-date'] = {val = PublicationDate, name = A:ORIGIN ('PublicationDate')},
+
ScriptEncyclopedia = nil;
['year'] = {val = Year, name = A:ORIGIN ('Year')},
+
TransEncyclopedia = nil;
};
  −
 
  −
local error_list = {};
  −
anchor_year, Embargo = validation.dates(date_parameters_list, COinS_date, error_list);
  −
 
  −
-- start temporary Julian / Gregorian calendar uncertainty categorization
  −
if COinS_date.inter_cal_cat then
  −
utilities.add_prop_cat ('jul-greg-uncertainty');
   
end
 
end
-- end temporary Julian / Gregorian calendar uncertainty categorization
+
elseif utilities.is_set (TransEncyclopedia) then
 +
utilities.set_message ('err_trans_missing_title', {'encyclopedia'});
 +
end
   −
if utilities.is_set (Year) and utilities.is_set (Date) then -- both |date= and |year= not normally needed;
+
if ('encyclopaedia' == config.CitationClass) or ('citation' == config.CitationClass and utilities.is_set (Encyclopedia)) then
validation.year_date_check (Year, A:ORIGIN ('Year'), Date, A:ORIGIN ('Date'), error_list);
+
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}}
 +
utilities.set_message ('err_periodical_ignored', {Periodical_origin});
 
end
 
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
+
if utilities.is_set (Encyclopedia) or utilities.is_set (ScriptEncyclopedia) then
modified = true;
+
Periodical = Encyclopedia; -- error or no, set Periodical to Encyclopedia for rendering; {{citation}} could (not legitimately) have both; use Encyclopedia
utilities.set_message ('maint_date_format'); -- hyphens were converted so add maint category
+
Periodical_origin = A:ORIGIN ('Encyclopedia');
end
+
ScriptPeriodical = ScriptEncyclopedia;
+
ScriptPeriodical_origin = A:ORIGIN ('ScriptEncyclopedia');
-- for those wikis that can and want to have English date names translated to the local language; not supported at en.wiki
  −
if cfg.date_name_auto_xlate_enable and validation.date_name_xlate (date_parameters_list, cfg.date_digit_auto_xlate_enable ) then
  −
utilities.set_message ('maint_date_auto_xlated'); -- add maint cat
  −
modified = true;
  −
end
     −
if modified then -- if the date_parameters_list values were modified
+
if utilities.is_set (Title) or utilities.is_set (ScriptTitle) then
AccessDate = date_parameters_list['access-date'].val; -- overwrite date holding parameters with modified values
+
if not utilities.is_set (Chapter) then
ArchiveDate = date_parameters_list['archive-date'].val;
+
Chapter = Title; -- |encyclopedia= and |title= are set so map |title= params to |article= params for rendering
Date = date_parameters_list['date'].val;
+
ScriptChapter = ScriptTitle;
DoiBroken = date_parameters_list['doi-broken-date'].val;
+
ScriptChapter_origin = A:ORIGIN('ScriptTitle')
LayDate = date_parameters_list['lay-date'].val;
+
TransChapter = TransTitle;
PublicationDate = date_parameters_list['publication-date'].val;
+
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
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
  −
  −
local ID_list = {}; -- sequence table of rendered identifiers
  −
local ID_list_coins = {}; -- table of identifiers and their values from args; key is same as cfg.id_handlers's key
  −
local Class = A['Class']; -- arxiv class identifier
  −
  −
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);
  −
  −
-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, {{cite ssrn}}, before generation of COinS data.
  −
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 (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
  −
  −
Periodical = ({['arxiv'] = 'arXiv', ['biorxiv'] = 'bioRxiv', ['citeseerx'] = 'CiteSeerX', ['ssrn'] = 'Social Science Research Network'})[config.CitationClass];
   
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
+
-- special case for cite techreport.
 
+
local ID = A['ID'];
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
+
if (config.CitationClass == "techreport") then -- special case for cite techreport
if 'none' ~= cfg.keywords_xlate[auto_select] then -- if auto-linking not disabled
+
if utilities.is_set (A['Number']) then -- cite techreport uses 'number', which other citations alias to 'issue'
if identifiers.auto_link_urls[auto_select] then -- manual selection
+
if not utilities.is_set (ID) then -- can we use ID for the "number"?
URL = identifiers.auto_link_urls[auto_select]; -- set URL to be the same as identifier's external link
+
ID = A['Number']; -- yes, use it
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=
+
else -- ID has a value so emit error message
elseif identifiers.auto_link_urls['pmc'] then -- auto-select PMC
+
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'id') .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'number')});
URL = identifiers.auto_link_urls['pmc']; -- set URL to be the same as the PMC external link if not embargoed
  −
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=
  −
elseif identifiers.auto_link_urls['doi'] then -- auto-select DOI
  −
URL = identifiers.auto_link_urls['doi'];
  −
URL_origin = cfg.id_handlers['DOI'].parameters[1];
   
end
 
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 (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
 
end
   −
-- At this point fields may be nil if they weren't specified in the template use.  We can use that fact.
+
-- Account for the oddity that is {{cite conference}}, before generation of COinS data.
-- Test if citation has no title
+
local ChapterLink -- = A['ChapterLink']; -- deprecated as a parameter but still used internally by cite episode
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
+
local Conference = A['Conference'];
utilities.set_message ('err_citation_missing_title', {'episode' == config.CitationClass and 'series' or 'title'});
+
local BookTitle = A['BookTitle'];
end
+
local TransTitle_origin = A:ORIGIN ('TransTitle');
 
+
if 'conference' == config.CitationClass then
if utilities.in_array (cfg.keywords_xlate[Title], {'off', 'none'}) and
+
if utilities.is_set (BookTitle) then
utilities.in_array (config.CitationClass, {'journal', 'citation'}) and
+
Chapter = Title;
(utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) and
+
Chapter_origin = 'title';
('journal' == Periodical_origin or 'script-journal' == ScriptPeriodical_origin) then -- special case for journal cites
+
-- ChapterLink = TitleLink; -- |chapter-link= is deprecated
Title = ''; -- set title to empty string
+
ChapterURL = URL;
utilities.set_message ('maint_untitled'); -- add maint cat
+
ChapterUrlAccess = UrlAccess;
 +
ChapterURL_origin = URL_origin;
 +
URL_origin = '';
 +
ChapterFormat = Format;
 +
TransChapter = TransTitle;
 +
TransChapter_origin = TransTitle_origin;
 +
Title = BookTitle;
 +
Format = '';
 +
-- TitleLink = '';
 +
TransTitle = '';
 +
URL = '';
 +
end
 +
elseif 'speech' ~= config.CitationClass then
 +
Conference = ''; -- not cite conference or cite speech so make sure this is empty string
 
end
 
end
  −
-- COinS metadata (see <http://ocoins.info/>) for automated parsing of citation information.
  −
-- handle the oddity that is cite encyclopedia and {{citation |encyclopedia=something}}. Here we presume that
  −
-- when Periodical, Title, and Chapter are all set, then Periodical is the book (encyclopedia) title, Title
  −
-- is the article title, and Chapter is a section within the article.  So, we remap
   
 
local coins_chapter = Chapter; -- default assuming that remapping not required
+
local use_lowercase = ( sepc == ',' ); -- controls capitalization of certain static text
local coins_title = Title; -- et tu
+
if 'encyclopaedia' == config.CitationClass or ('citation' == config.CitationClass and utilities.is_set (Encyclopedia)) then
+
-- cite map oddities
if utilities.is_set (Chapter) and utilities.is_set (Title) and utilities.is_set (Periodical) then -- if all are used then
+
local Cartography = "";
coins_chapter = Title; -- remap
+
local Scale = "";
coins_title = Periodical;
+
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;
 +
ChapterFormat = A['MapFormat'];
 +
 
 +
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
 
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.
+
-- Account for the oddities that are {{cite episode}} and {{cite serial}}, before generation of COinS data.
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
+
local Series = A['Series'];
Periodical = ''; -- periodical not allowed in these templates; if article has been published, use cite journal
+
if 'episode' == config.CitationClass or 'serial' == config.CitationClass then
end
+
local SeriesLink = A['SeriesLink'];
 
+
 
-- special case for cite newsgroup.  Do this after COinS because we are modifying Publishername to include some static text
+
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
if 'newsgroup' == config.CitationClass and utilities.is_set (Newsgroup) then
+
 
PublisherName = utilities.substitute (cfg.messages['newsgroup'], external_link( 'news:' .. Newsgroup, Newsgroup, Newsgroup_origin, nil ));
+
local Network = A['Network'];
end
+
local Station = A['Station'];
 
+
local s, n = {}, {};
local Editors;
+
-- do common parameters first
local EditorCount; -- used only for choosing {ed.) or (eds.) annotation at end of editor name-list
+
if utilities.is_set (Network) then table.insert(n, Network); end
local Contributors; -- assembled contributors name list
+
if utilities.is_set (Station) then table.insert(n, Station); end
local contributor_etal;
+
ID = table.concat(n, sepc .. ' ');
local Translators; -- assembled translators name list
+
local translator_etal;
+
if 'episode' == config.CitationClass then -- handle the oddities that are strictly {{cite episode}}
local t = {}; -- translators list from |translator-lastn= / translator-firstn= pairs
+
local Season = A['Season'];
t = extract_names (args, 'TranslatorList'); -- fetch translator list from |translatorn= / |translator-lastn=, -firstn=, -linkn=, -maskn=
+
local SeriesNumber = A['SeriesNumber'];
local Interviewers;
+
 
local interviewers_list = {};
+
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?
interviewers_list = extract_names (args, 'InterviewerList'); -- process preferred interviewers parameters
+
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'season') .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'seriesno')}); -- add error message
local interviewer_etal;
+
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
 
 
-- Now perform various field substitutions.
+
Chapter = Title; -- promote title parameters to chapter
-- We also add leading spaces and surrounding markup and punctuation to the
+
ScriptChapter = ScriptTitle;
-- various parts of the citation, but only when they are non-nil.
+
ScriptChapter_origin = A:ORIGIN('ScriptTitle');
do
+
ChapterLink = TitleLink; -- alias |episode-link=
local last_first_list;
+
TransChapter = TransTitle;
local control = {
+
ChapterURL = URL;
format = NameListStyle, -- empty string or 'vanc'
+
ChapterUrlAccess = UrlAccess;
maximum = nil, -- as if display-authors or display-editors not set
+
ChapterURL_origin = URL_origin;
mode = Mode
+
ChapterFormat = Format;
};
     −
do -- do editor name list first because the now unsupported coauthors used to modify control table
+
Title = Series; -- promote series to title
control.maximum , editor_etal = get_display_names (A['DisplayEditors'], #e, 'editors', editor_etal, A:ORIGIN ('DisplayEditors'));
+
TitleLink = SeriesLink;
Editors, EditorCount = list_people (control, e, editor_etal);
+
Series = table.concat(s, sepc .. ' '); -- this is concatenation of season, seriesno, episode number
   −
if 1 == EditorCount and (true == editor_etal or 1 < #e) then -- only one editor displayed but includes etal then
+
if utilities.is_set (ChapterLink) and not utilities.is_set (ChapterURL) then -- link but not URL
EditorCount = 2; -- spoof to display (eds.) annotation
+
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
 
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
do -- now do interviewers
+
end
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);
+
if utilities.is_set (TitleType) then -- if type parameter is specified
 +
TitleType = utilities.substitute ( cfg.messages['type'], TitleType); -- display it in parentheses
 +
-- TODO: Hack on TitleType to fix bunched parentheses problem
 +
end
   −
if utilities.is_set (Authors) then
+
-- legacy: promote PublicationDate to Date if neither Date nor Year are set.
Authors, author_etal = name_has_etal (Authors, author_etal, false, 'authors'); -- find and remove variations on et al.
+
local Date = A['Date'];
if author_etal then
+
local Date_origin; -- to hold the name of parameter promoted to Date; required for date error messaging
Authors = Authors .. ' ' .. cfg.messages['et al']; -- add et al. to authors parameter
+
local PublicationDate = A['PublicationDate'];
end
+
local Year = A['Year'];
else
+
 
Authors = last_first_list; -- either an author name list or an empty string
+
if utilities.is_set (Year) then
end
+
validation.year_check (Year); -- returns nothing; emits maint message when |year= doesn't hold a 'year' value
end -- end of do
+
end
+
if utilities.is_set (Authors) and utilities.is_set (Collaboration) then
+
if not utilities.is_set (Date) then
Authors = Authors .. ' (' .. Collaboration .. ')'; -- add collaboration after et al.
+
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
 
end
 
+
else
 +
Date_origin = A:ORIGIN('Date'); -- not a promotion; name required for error messaging
 
end
 
end
   −
local ConferenceFormat = A['ConferenceFormat'];
+
if PublicationDate == Date then PublicationDate = ''; end -- if PublicationDate is same as Date, don't display in rendered citation
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', 'ssrn'}) or
+
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
('citation' == config.CitationClass and (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) and not utilities.is_set (Encyclopedia))) then
+
we get the date used in the metadata.
ChapterFormat = style_format (ChapterFormat, ChapterURL, 'chapter-format', 'chapter-url');
+
end
+
Date validation supporting code is in Module:Citation/CS1/Date_validation
 +
]]
   −
if not utilities.is_set (URL) then
+
local DF = is_valid_parameter_value (A['DF'], A:ORIGIN('DF'), cfg.keywords_lists['df'], '');
if utilities.in_array (config.CitationClass, {"web", "podcast", "mailinglist"}) or -- |url= required for cite web, cite podcast, and cite mailinglist
+
if not utilities.is_set (DF) then
('citation' == config.CitationClass and ('website' == Periodical_origin or 'script-website' == ScriptPeriodical_origin)) then -- and required for {{citation}} with |website= or |script-website=
+
DF = cfg.global_df; -- local |df= if present overrides global df set by {{use xxx date}} template
utilities.set_message ('err_cite_web_url');
  −
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
 
end
   −
local UrlStatus = is_valid_parameter_value (A['UrlStatus'], A:ORIGIN('UrlStatus'), cfg.keywords_lists['url-status'], '');
+
local ArchiveURL;
local OriginalURL
+
local ArchiveDate;
local OriginalURL_origin
+
local ArchiveFormat = A['ArchiveFormat'];
local OriginalFormat
+
local archive_url_timestamp; -- timestamp from wayback machine url
local OriginalAccess;
+
UrlStatus = UrlStatus:lower(); -- used later when assembling archived text
+
ArchiveURL, ArchiveDate, archive_url_timestamp = archive_url_check (A['ArchiveURL'], A['ArchiveDate'])
if utilities.is_set ( ArchiveURL ) then
+
ArchiveFormat = style_format (ArchiveFormat, ArchiveURL, 'archive-format', 'archive-url');
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
+
ArchiveURL, ArchiveDate = is_unique_archive_url (ArchiveURL, URL, ChapterURL, A:ORIGIN('ArchiveURL'), ArchiveDate); -- add error message when URL or ChapterURL == ArchiveURL
OriginalURL_origin = ChapterURL_origin; -- name of |chapter-url= parameter for error messages
  −
OriginalFormat = ChapterFormat; -- and original |chapter-format=
     −
if 'live' ~= UrlStatus then
+
local AccessDate = A['AccessDate'];
ChapterURL = ArchiveURL -- swap-in the archive's URL
+
local COinS_date = {}; -- holds date info extracted from |date= for the COinS metadata by Module:Date verification
ChapterURL_origin = A:ORIGIN('ArchiveURL') -- name of |archive-url= parameter for error messages
+
local DoiBroken = A['DoiBroken'];
ChapterFormat = ArchiveFormat or ''; -- swap in archive's format
+
local Embargo = A['Embargo'];
ChapterUrlAccess = nil; -- restricted access levels do not make sense for archived URLs
+
local anchor_year; -- used in the CITEREF identifier
end
+
do -- create defined block to contain local variables error_message, date_parameters_list, mismatch
elseif utilities.is_set (URL) then
+
local error_message = '';
OriginalURL = URL; -- save copy of original source URL
+
-- AirDate has been promoted to Date so not necessary to check it
OriginalURL_origin = URL_origin; -- name of URL parameter for error messages
+
local date_parameters_list = {
OriginalFormat = Format; -- and original |format=
+
['access-date'] = {val = AccessDate, name = A:ORIGIN ('AccessDate')},
OriginalAccess = UrlAccess;
+
['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')},
 +
};
 +
 
 +
local error_list = {};
 +
anchor_year, Embargo = validation.dates(date_parameters_list, COinS_date, error_list);
   −
if 'live' ~= UrlStatus then -- if URL set then |archive-url= applies to it
+
if utilities.is_set (Year) and utilities.is_set (Date) then -- both |date= and |year= not normally needed;
URL = ArchiveURL -- swap-in the archive's URL
+
validation.year_date_check (Year, A:ORIGIN ('Year'), Date, A:ORIGIN ('Date'), error_list);
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 0 == #error_list then -- error free dates only; 0 when error_list is empty
('citation' == config.CitationClass and (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) and not utilities.is_set (Encyclopedia)) then
+
local modified = false; -- flag
local chap_param;
+
if utilities.is_set (Chapter) then -- get a parameter name from one of these chapter related meta-parameters
+
if utilities.is_set (DF) then -- if we need to reformat dates
chap_param = A:ORIGIN ('Chapter')
+
modified = validation.reformat_dates (date_parameters_list, DF); -- reformat to DF format, use long month names if appropriate
elseif utilities.is_set (TransChapter) then
  −
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 true == validation.date_hyphen_to_dash (date_parameters_list) then -- convert hyphens to dashes where appropriate
utilities.set_message ('err_chapter_ignored', {chap_param}); -- add error message
+
modified = true;
Chapter = ''; -- and set them to empty string to be safe with concatenation
+
utilities.set_message ('maint_date_format'); -- hyphens were converted so add maint category
TransChapter = '';
  −
ChapterURL = '';
  −
ScriptChapter = '';
  −
ChapterFormat = '';
   
end
 
end
else -- otherwise, format chapter / article title
+
local no_quotes = false; -- default assume that we will be quoting the chapter parameter value
+
-- for those wikis that can and want to have English date names translated to the local language; not supported at en.wiki
if utilities.is_set (Contribution) and 0 < #c then -- if this is a contribution with contributor(s)
+
if cfg.date_name_auto_xlate_enable and validation.date_name_xlate (date_parameters_list, cfg.date_digit_auto_xlate_enable ) then
if utilities.in_array (Contribution:lower(), cfg.keywords_lists.contribution) then -- and a generic contribution title
+
utilities.set_message ('maint_date_auto_xlated'); -- add maint cat
no_quotes = true; -- then render it unquoted
+
modified = true;
 
end
 
end
end
     −
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 modified then -- if the date_parameters_list values were modified
if utilities.is_set (Chapter) then
+
AccessDate = date_parameters_list['access-date'].val; -- overwrite date holding parameters with modified values
Chapter = Chapter .. ChapterFormat ;
+
ArchiveDate = date_parameters_list['archive-date'].val;
if 'map' == config.CitationClass and utilities.is_set (TitleType) then
+
Date = date_parameters_list['date'].val;
Chapter = Chapter .. ' ' .. TitleType; -- map annotation here; not after title
+
DoiBroken = date_parameters_list['doi-broken-date'].val;
 +
PublicationDate = date_parameters_list['publication-date'].val;
 
end
 
end
Chapter = Chapter .. sepc .. ' ';
+
 
elseif utilities.is_set (ChapterFormat) then -- |chapter= not set but |chapter-format= is so ...
+
if archive_url_timestamp and utilities.is_set (ArchiveDate) then
Chapter = ChapterFormat .. sepc .. ' '; -- ... ChapterFormat has error message, we want to see it
+
validation.archive_date_check (ArchiveDate, archive_url_timestamp, DF); -- does YYYYMMDD in archive_url_timestamp match date in ArchiveDate
 +
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
 +
 +
if utilities.in_array (config.CitationClass, {'book', 'encyclopaedia'}) or -- {{cite book}}, {{cite encyclopedia}}; TODO: {{cite conference}} and others?
 +
('citation' == config.CitationClass and utilities.is_set (Encyclopedia)) or -- {{citation}} as an encylopedia citation
 +
('citation' == config.CitationClass and not utilities.is_set (Periodical)) then -- {{citation}} as a book citation
 +
if utilities.is_set (PublicationPlace) then
 +
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
 
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
+
ID_list, ID_list_coins = identifiers.identifier_lists_get (args, {
end
+
DoiBroken = DoiBroken, -- for |doi=
 +
ASINTLD = A['ASINTLD'], -- for |asin=
 +
Embargo = Embargo, -- for |pmc=
 +
Class = Class, -- for |arxiv=
 +
CitationClass = config.CitationClass, -- for |arxiv=
 +
Year=anchor_year, -- for |isbn=
 +
}, ID_support);
   −
if not accept_title then -- <Title> not wrapped in accept-as-written markup
+
-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, {{cite medrxiv}}, {{cite ssrn}}, before generation of COinS data.
if '...' == Title:sub (-3) then -- if ellipsis is the last three characters 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
Title = Title:gsub ('(%.%.%.)%.+$', '%1'); -- limit the number of dots to three
+
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
elseif not mw.ustring.find (Title, '%.%s*%a%.$') and -- end of title is not a 'dot-(optional space-)letter-dot' initialism ...
+
args[cfg.id_handlers[config.CitationClass:upper()].parameters[2]]) then -- which causes unexpected parameter missing error message
not mw.ustring.find (Title, '%s+%a%.$') then -- ...and not a 'space-letter-dot' initial (''Allium canadense'' L.)
+
utilities.set_message ('err_' .. config.CitationClass .. '_missing'); -- add error message
Title = mw.ustring.gsub(Title, '%' .. sepc .. '$', ''); -- remove any trailing separator character; sepc and ms.ustring() here for languages that use multibyte separator characters
   
end
 
end
   −
if utilities.is_set (ArchiveURL) and is_archived_copy (Title) then
+
Periodical = ({['arxiv'] = 'arXiv', ['biorxiv'] = 'bioRxiv', ['citeseerx'] = 'CiteSeerX', ['medrxiv'] = 'medRxiv', ['ssrn'] = 'Social Science Research Network'})[config.CitationClass];
utilities.set_message ('maint_archived_copy'); -- add maintenance category before we modify the content of Title
+
end
 +
 
 +
-- Link the title of the work if no |url= was provided, but we have a |pmc= or a |doi= with |doi-access=free
 +
 
 +
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
 +
if 'none' ~= cfg.keywords_xlate[auto_select] then -- if auto-linking not disabled
 +
if identifiers.auto_link_urls[auto_select] then -- manual selection
 +
URL = identifiers.auto_link_urls[auto_select]; -- set URL to be the same as identifier's external link
 +
URL_origin = cfg.id_handlers[auto_select:upper()].parameters[1]; -- set URL_origin to parameter name for use in error message if citation is missing a |title=
 +
elseif identifiers.auto_link_urls['pmc'] then -- auto-select PMC
 +
URL = identifiers.auto_link_urls['pmc']; -- set URL to be the same as the PMC external link if not embargoed
 +
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=
 +
elseif identifiers.auto_link_urls['doi'] then -- auto-select DOI
 +
URL = identifiers.auto_link_urls['doi'];
 +
URL_origin = cfg.id_handlers['DOI'].parameters[1];
 +
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 (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
 +
end
   −
if is_generic ('generic_titles', Title) then
+
-- At this point fields may be nil if they weren't specified in the template use.  We can use that fact.
utilities.set_message ('err_generic_title'); -- set an error message
+
-- Test if citation has no title
end
+
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
 
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
+
if utilities.in_array (cfg.keywords_xlate[Title], {'off', 'none'}) and
('citation' == config.CitationClass and (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) and not utilities.is_set (Encyclopedia)) or
+
utilities.in_array (config.CitationClass, {'journal', 'citation'}) and
('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
+
(utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) and
Title = kern_quotes (Title); -- if necessary, separate title's leading and trailing quote marks from module provided quote marks
+
('journal' == Periodical_origin or 'script-journal' == ScriptPeriodical_origin) then -- special case for journal cites
Title = utilities.wrap_style ('quoted-title', Title);
+
Title = ''; -- set title to empty string
Title = script_concatenate (Title, ScriptTitle, 'script-title'); -- <bdi> tags, lang attribute, categorization, etc.; must be done after title is wrapped
+
utilities.set_message ('maint_untitled'); -- add maint cat
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
 
end
   −
if utilities.is_set (TransTitle) then
+
if 'journal' == config.CitationClass or ('citation' == config.CitationClass and utilities.is_set (Periodical) and 'journal' == Periodical_origin) then
if utilities.is_set (Title) then
+
if is_page_art_num (((utilities.is_set (Page) and Page) or (utilities.is_set (Pages) and Pages)) or nil, ID_list_coins['DOI']) then -- does |page(s)= look like it holds an article number
TransTitle = " " .. TransTitle;
+
utilities.set_message ('maint_page_art_num'); -- add maint cat
else
  −
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?
+
-- COinS metadata (see <http://ocoins.info/>) for automated parsing of citation information.
if utilities.is_set (TitleLink) and utilities.is_set (URL) then
+
-- handle the oddity that is cite encyclopedia and {{citation |encyclopedia=something}}. Here we presume that
utilities.set_message ('err_wikilink_in_url'); -- set an error message because we can't have both
+
-- when Periodical, Title, and Chapter are all set, then Periodical is the book (encyclopedia) title, Title
TitleLink = ''; -- unset
+
-- 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
 +
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
 
 
if not utilities.is_set (TitleLink) and utilities.is_set (URL) then
+
-- this is the function call to COinS()
Title = external_link (URL, Title, URL_origin, UrlAccess) .. TransTitle .. Format;
+
local OCinSoutput = metadata.COinS({
URL = ''; -- unset these because no longer needed
+
['Periodical'] = utilities.strip_apostrophe_markup (Periodical), -- no markup in the metadata
Format = "";
+
['Encyclopedia'] = Encyclopedia, -- just a flag; content ignored by ~/COinS
elseif utilities.is_set (TitleLink) and not utilities.is_set (URL) then
+
['Chapter'] = metadata.make_coins_title (coins_chapter, ScriptChapter), -- Chapter and ScriptChapter stripped of bold / italic / accept-as-written markup
local ws_url;
+
['Degree'] = Degree; -- cite thesis only
ws_url = wikisource_url_make (TitleLink); -- ignore ws_label return; not used here
+
['Title'] = metadata.make_coins_title (coins_title, ScriptTitle), -- Title and ScriptTitle stripped of bold / italic / accept-as-written markup
if ws_url then
+
['PublicationPlace'] = PublicationPlace,
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?
+
['Date'] = COinS_date.rftdate, -- COinS_date.* has correctly formatted date values if Date is valid;
Title = utilities.substitute (cfg.presentation['interwiki-icon'], {cfg.presentation['class-wikisource'], TitleLink, Title});
+
['Season'] = COinS_date.rftssn,
Title = Title .. TransTitle;
+
['Quarter'] = COinS_date.rftquarter,
else
+
['Chron'] = COinS_date.rftchron,
Title = utilities.make_wikilink (TitleLink, Title) .. TransTitle;
+
['Series'] = Series,
end
+
['Volume'] = Volume,
else
+
['Issue'] = Issue,
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)
+
['ArticleNumber'] = ArticleNumber,
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
+
['Pages'] = coins_pages or metadata.get_coins_pages (first_set ({Sheet, Sheets, Page, Pages, At, QuotePage, QuotePages}, 7)), -- pages stripped of external links
if ws_url then
+
['Edition'] = Edition,
Title = Title:gsub ('%b[]', ws_label); -- replace interwiki link with ws_label to retain markup
+
['PublisherName'] = PublisherName or Newsgroup, -- any apostrophe markup already removed from PublisherName
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?
+
['URL'] = first_set ({ChapterURL, URL}, 2),
Title = utilities.substitute (cfg.presentation['interwiki-icon'], {cfg.presentation['class-wikisource'], L, Title});
+
['Authors'] = coins_author,
Title = Title .. TransTitle;
+
['ID_list'] = ID_list_coins,
else
+
['RawPage'] = this_page.prefixedText,
Title = Title .. TransTitle;
+
}, config.CitationClass);
end
+
 
end
+
-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, {{cite medrxiv}}, and {{cite ssrn}} AFTER generation of COinS data.
else
+
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
Title = TransTitle;
+
Periodical = ''; -- periodical not allowed in these templates; if article has been published, use cite journal
 
end
 
end
   −
if utilities.is_set (Place) then
+
-- special case for cite newsgroup.  Do this after COinS because we are modifying Publishername to include some static text
Place = " " .. wrap_msg ('written', Place, use_lowercase) .. sepc .. " ";
+
if 'newsgroup' == config.CitationClass and utilities.is_set (Newsgroup) then
 +
PublisherName = utilities.substitute (cfg.messages['newsgroup'], external_link( 'news:' .. Newsgroup, Newsgroup, Newsgroup_origin, nil ));
 
end
 
end
   −
local ConferenceURL_origin = A:ORIGIN('ConferenceURL'); -- get name of parameter that holds ConferenceURL
+
local Editors;
if utilities.is_set (Conference) then
+
local EditorCount; -- used only for choosing {ed.) or (eds.) annotation at end of editor name-list
if utilities.is_set (ConferenceURL) then
+
local Contributors; -- assembled contributors name list
Conference = external_link( ConferenceURL, Conference, ConferenceURL_origin, nil );
+
local contributor_etal;
end
+
local Translators; -- assembled translators name list
Conference = sepc .. " " .. Conference .. ConferenceFormat;
+
local translator_etal;
elseif utilities.is_set (ConferenceURL) then
+
local t = {}; -- translators list from |translator-lastn= / translator-firstn= pairs
Conference = sepc .. " " .. external_link( ConferenceURL, nil, ConferenceURL_origin, nil );
+
t = extract_names (args, 'TranslatorList'); -- fetch translator list from |translatorn= / |translator-lastn=, -firstn=, -linkn=, -maskn=
end
+
local Interviewers;
 
+
local interviewers_list = {};
local Position = '';
+
interviewers_list = extract_names (args, 'InterviewerList'); -- process preferred interviewers parameters
if not utilities.is_set (Position) then
+
local interviewer_etal;
local Minutes = A['Minutes'];
+
local Time = A['Time'];
+
-- Now perform various field substitutions.
 
+
-- We also add leading spaces and surrounding markup and punctuation to the
if utilities.is_set (Minutes) then
+
-- various parts of the citation, but only when they are non-nil.
if utilities.is_set (Time) then --TODO: make a function for this and similar?
+
do
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'minutes') .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'time')});
+
local last_first_list;
end
+
local control = {
Position = " " .. Minutes .. " " .. cfg.messages['minutes'];
+
format = NameListStyle, -- empty string, '&', 'amp', 'and', or 'vanc'
else
+
maximum = nil, -- as if display-authors or display-editors not set
if utilities.is_set (Time) then
+
mode = Mode
local TimeCaption = A['TimeCaption']
+
};
if not utilities.is_set (TimeCaption) then
+
 
TimeCaption = cfg.messages['event'];
+
do -- do editor name list first because the now unsupported coauthors used to modify control table
if sepc ~= '.' then
+
local display_names, param = display_names_select (cfg.global_cs1_config_t['DisplayEditors'], A['DisplayEditors'], A:ORIGIN ('DisplayEditors'), #e);
TimeCaption = TimeCaption:lower();
+
control.maximum, editor_etal = get_display_names (display_names, #e, 'editors', editor_etal, param);
end
+
 
end
+
Editors, EditorCount = list_people (control, e, editor_etal);
Position = " " .. TimeCaption .. " " .. Time;
+
 
 +
if 1 == EditorCount and (true == editor_etal or 1 < #e) then -- only one editor displayed but includes etal then
 +
EditorCount = 2; -- spoof to display (eds.) annotation
 
end
 
end
 
end
 
end
else
+
do -- now do interviewers
Position = " " .. Position;
+
local display_names, param = display_names_select (cfg.global_cs1_config_t['DisplayInterviewers'], A['DisplayInterviewers'], A:ORIGIN ('DisplayInterviewers'), #interviewers_list);
At = '';
+
control.maximum, interviewer_etal = get_display_names (display_names, #interviewers_list, 'interviewers', interviewer_etal, param);
end
     −
Page, Pages, Sheet, Sheets = format_pages_sheets (Page, Pages, Sheet, Sheets, config.CitationClass, Periodical_origin, sepc, NoPP, use_lowercase);
+
Interviewers = list_people (control, interviewers_list, interviewer_etal);
 +
end
 +
do -- now do translators
 +
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);
   −
At = utilities.is_set (At) and (sepc .. " " .. At) or "";
+
Translators = list_people (control, t, translator_etal);
Position = utilities.is_set (Position) and (sepc .. " " .. Position) or "";
+
end
if config.CitationClass == 'map' then
+
do -- now do contributors
local Sections = A['Sections']; -- Section (singular) is an alias of Chapter so set earlier
+
local display_names, param = display_names_select (cfg.global_cs1_config_t['DisplayContributors'], A['DisplayContributors'], A:ORIGIN ('DisplayContributors'), #c);
local Inset = A['Inset'];
+
control.maximum, contributor_etal = get_display_names (display_names, #c, 'contributors', contributor_etal, param);
  −
if utilities.is_set ( Inset ) then
  −
Inset = sepc .. " " .. wrap_msg ('inset', Inset, use_lowercase);
  −
end
     −
if utilities.is_set ( Sections ) then
+
Contributors = list_people (control, c, contributor_etal);
Section = sepc .. " " .. wrap_msg ('sections', Sections, use_lowercase);
  −
elseif utilities.is_set ( Section ) then
  −
Section = sepc .. " " .. wrap_msg ('section', Section, use_lowercase);
   
end
 
end
At = At .. Inset .. Section;
+
do -- now do authors
end
+
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);
 +
 
 +
last_first_list = list_people (control, a, author_etal);
   −
local Others = A['Others'];
+
if utilities.is_set (Authors) then
if utilities.is_set (Others) and 0 == #a and 0 == #e then -- add maint cat when |others= has value and used without |author=, |editor=
+
Authors, author_etal = name_has_etal (Authors, author_etal, false, 'authors'); -- find and remove variations on et al.
if config.CitationClass == "AV-media-notes"
+
if author_etal then
or config.CitationClass == "audio-visual" then -- special maint for AV/M which has a lot of 'false' positives right now
+
Authors = Authors .. ' ' .. cfg.messages['et al']; -- add et al. to authors parameter
utilities.set_message ('maint_others_avm')
+
end
else
+
else
utilities.set_message ('maint_others');
+
Authors = last_first_list; -- either an author name list or an empty string
 +
end
 +
end -- end of do
 +
 +
if utilities.is_set (Authors) and utilities.is_set (Collaboration) then
 +
Authors = Authors .. ' (' .. Collaboration .. ')'; -- add collaboration after et al.
 
end
 
end
 +
 
end
 
end
Others = utilities.is_set (Others) and (sepc .. " " .. Others) or "";
+
 
+
local ConferenceFormat = A['ConferenceFormat'];
if utilities.is_set (Translators) then
+
local ConferenceURL = A['ConferenceURL'];
Others = safe_join ({sepc .. ' ', wrap_msg ('translated', Translators, use_lowercase), Others}, sepc);
+
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
if utilities.is_set (Interviewers) then
+
 
Others = safe_join ({sepc .. ' ', wrap_msg ('interview', Interviewers, use_lowercase), Others}, sepc);
+
if not utilities.is_set (URL) then
end
+
if utilities.in_array (config.CitationClass, {"web", "podcast", "mailinglist"}) or -- |url= required for cite web, cite podcast, and cite mailinglist
+
('citation' == config.CitationClass and ('website' == Periodical_origin or 'script-website' == ScriptPeriodical_origin)) then -- and required for {{citation}} with |website= or |script-website=
local TitleNote = A['TitleNote'];
+
utilities.set_message ('err_cite_web_url');
TitleNote = utilities.is_set (TitleNote) and (sepc .. " " .. TitleNote) or "";
+
end
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.
+
-- do we have |accessdate= without either |url= or |chapter-url=?
utilities.set_message ('err_extra_text_edition'); -- add error message
+
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
Edition = " " .. wrap_msg ('edition', Edition);
  −
else
  −
Edition = '';
   
end
 
end
   −
Series = utilities.is_set (Series) and wrap_msg ('series', {sepc, Series}) or ""; -- not the same as SeriesNum
+
local UrlStatus = is_valid_parameter_value (A['UrlStatus'], A:ORIGIN('UrlStatus'), cfg.keywords_lists['url-status'], '');
local Agency = A['Agency'];
+
local OriginalURL
Agency = utilities.is_set (Agency) and wrap_msg ('agency', {sepc, Agency}) or "";
+
local OriginalURL_origin
Volume = format_volume_issue (Volume, Issue, ArticleNumber, config.CitationClass, Periodical_origin, sepc, use_lowercase);
+
local OriginalFormat
 
+
local OriginalAccess;
if utilities.is_set (AccessDate) then
+
UrlStatus = UrlStatus:lower(); -- used later when assembling archived text
local retrv_text = " " .. cfg.messages['retrieved']
+
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=
   −
AccessDate = nowrap_date (AccessDate); -- wrap in nowrap span if date in appropriate format
+
if 'live' ~= UrlStatus then
if (sepc ~= ".") then retrv_text = retrv_text:lower() end -- if mode is cs2, lower case
+
ChapterURL = ArchiveURL -- swap-in the archive's URL
AccessDate = utilities.substitute (retrv_text, AccessDate); -- add retrieved text
+
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;
   −
AccessDate = utilities.substitute (cfg.presentation['accessdate'], {sepc, AccessDate}); -- allow editors to hide accessdates
+
if 'live' ~= UrlStatus then -- if URL set then |archive-url= applies to it
end
+
URL = ArchiveURL -- swap-in the archive's URL
+
URL_origin = A:ORIGIN('ArchiveURL') -- name of archive URL parameter for error messages
if utilities.is_set (ID) then ID = sepc .. " " .. ID; end
+
Format = ArchiveFormat or ''; -- swap in archive's format
+
UrlAccess = nil; -- restricted access levels do not make sense for archived URLs
local Docket = A['Docket'];
+
end
  if "thesis" == config.CitationClass and utilities.is_set (Docket) then
+
end
ID = sepc .. " Docket " .. Docket .. ID;
+
elseif utilities.is_set (UrlStatus) then -- if |url-status= is set when |archive-url= is not set
end
+
utilities.set_message ('maint_url_status'); -- add maint cat
  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
+
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
URL = " " .. external_link( URL, nil, URL_origin, UrlAccess );
+
('citation' == config.CitationClass and (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) and not utilities.is_set (Encyclopedia)) then
end
+
local chap_param;
 +
if utilities.is_set (Chapter) then -- get a parameter name from one of these chapter related meta-parameters
 +
chap_param = A:ORIGIN ('Chapter')
 +
elseif utilities.is_set (TransChapter) then
 +
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
   −
local Quote = A['Quote'];
+
if utilities.is_set (chap_param) then -- if we found one
local TransQuote = A['TransQuote'];
+
utilities.set_message ('err_chapter_ignored', {chap_param}); -- add error message
local ScriptQuote = A['ScriptQuote'];
+
Chapter = ''; -- and set them to empty string to be safe with concatenation
if utilities.is_set (Quote) or utilities.is_set (TransQuote) or utilities.is_set (ScriptQuote) then
+
TransChapter = '';
 
+
ChapterURL = '';
if utilities.is_set (Quote) then
+
ScriptChapter = '';
if Quote:sub(1, 1) == '"' and Quote:sub(-1, -1) == '"' then -- if first and last characters of quote are quote marks
+
ChapterFormat = '';
Quote = Quote:sub(2, -2); -- strip them off
+
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
end
  −
  −
Quote = kern_quotes (Quote); -- kern if needed
  −
Quote = utilities.wrap_style ('quoted-text', Quote ); -- wrap in <q>...</q> tags
  −
  −
if utilities.is_set (ScriptQuote) then
  −
Quote = script_concatenate (Quote, ScriptQuote, 'script-quote'); -- <bdi> tags, lang attribute, categorization, etc.; must be done after quote is wrapped
   
end
 
end
   −
if utilities.is_set (TransQuote) then
+
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 TransQuote:sub(1, 1) == '"' and TransQuote:sub(-1, -1) == '"' then -- if first and last characters of |trans-quote are quote marks
+
if utilities.is_set (Chapter) then
TransQuote = TransQuote:sub(2, -2); -- strip them off
+
Chapter = Chapter .. ChapterFormat ;
 +
if 'map' == config.CitationClass and utilities.is_set (TitleType) then
 +
Chapter = Chapter .. ' ' .. TitleType; -- map annotation here; not after title
 
end
 
end
Quote = Quote .. " " .. utilities.wrap_style ('trans-quoted-title', TransQuote );
+
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
   −
if utilities.is_set (QuotePage) or utilities.is_set (QuotePages) then -- add page prefix
+
-- Format main title
local quote_prefix = '';
+
local plain_title = false;
if utilities.is_set (QuotePage) then
+
local accept_title;
extra_text_in_page_check (QuotePage, 'quote-page'); -- add to maint cat if |quote-page= value begins with what looks like p., pp., etc.
+
Title, accept_title = utilities.has_accept_as_written (Title, true); -- remove accept-this-as-written markup when it wraps all of <Title>
if not NoPP then
+
if accept_title and ('' == Title) then -- only support forced empty for now "(())"
quote_prefix = utilities.substitute (cfg.messages['p-prefix'], {sepc, QuotePage}), '', '', '';
+
Title = cfg.messages['notitle']; -- replace by predefined "No title" message
else
+
-- TODO: utilities.set_message ( 'err_redundant_parameters', ...); -- issue proper error message instead of muting
quote_prefix = utilities.substitute (cfg.messages['nopp'], {sepc, QuotePage}), '', '', '';
+
ScriptTitle = ''; -- just mute for now
end
+
TransTitle = ''; -- just mute for now
elseif utilities.is_set (QuotePages) then
+
plain_title = true; -- suppress text decoration for descriptive title
extra_text_in_page_check (QuotePages, 'quote-pages'); -- add to maint cat if |quote-pages= value begins with what looks like p., pp., etc.
+
utilities.set_message ('maint_untitled'); -- add maint cat
if tonumber(QuotePages) ~= nil and not NoPP then -- if only digits, assume single page
+
end
quote_prefix = utilities.substitute (cfg.messages['p-prefix'], {sepc, QuotePages}), '', '';
+
 
elseif not NoPP then
+
if not accept_title then -- <Title> not wrapped in accept-as-written markup
quote_prefix = utilities.substitute (cfg.messages['pp-prefix'], {sepc, QuotePages}), '', '';
+
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
quote_prefix = utilities.substitute (cfg.messages['nopp'], {sepc, QuotePages}), '', '';
+
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.)
end
+
Title = mw.ustring.gsub(Title, '%' .. sepc .. '$', ''); -- remove any trailing separator character; sepc and ms.ustring() here for languages that use multibyte separator characters
                       
+
end
Quote = quote_prefix .. ": " .. Quote;
+
 
else
+
if utilities.is_set (ArchiveURL) and is_archived_copy (Title) then
Quote = sepc .. " " .. Quote;
+
utilities.set_message ('maint_archived_copy'); -- add maintenance category before we modify the content of Title
 
end
 
end
   −
PostScript = ""; -- cs1|2 does not supply terminal punctuation when |quote= is set
+
if is_generic ('generic_titles', Title) then
 +
utilities.set_message ('err_generic_title'); -- set an error message
 +
end
 
end
 
end
+
 
-- We check length of PostScript here because it will have been nuked by
+
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
-- the quote parameters. We'd otherwise emit a message even if there wasn't
+
('citation' == config.CitationClass and (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical)) and not utilities.is_set (Encyclopedia)) or
-- a displayed postscript.
+
('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
-- TODO: Should the max size (1) be configurable?
+
Title = kern_quotes (Title); -- if necessary, separate title's leading and trailing quote marks from module provided quote marks
-- TODO: Should we check a specific pattern?
+
Title = utilities.wrap_style ('quoted-title', Title);
if utilities.is_set(PostScript) and mw.ustring.len(PostScript) > 1 then
+
Title = script_concatenate (Title, ScriptTitle, 'script-title'); -- <bdi> tags, lang attribute, categorization, etc.; must be done after title is wrapped
utilities.set_message ('maint_postscript')
+
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
 +
 
 +
if utilities.is_set (TransTitle) then
 +
if utilities.is_set (Title) then
 +
TransTitle = " " .. TransTitle;
 +
else
 +
utilities.set_message ('err_trans_missing_title', {'title'});
 +
end
 
end
 
end
 +
 +
if utilities.is_set (Title) then -- TODO: is this the right place to be making Wikisource URLs?
 +
if utilities.is_set (TitleLink) and utilities.is_set (URL) then
 +
utilities.set_message ('err_wikilink_in_url'); -- set an error message because we can't have both
 +
TitleLink = ''; -- unset
 +
end
 
 
local Archived;
+
if not utilities.is_set (TitleLink) and utilities.is_set (URL) then
if utilities.is_set (ArchiveURL) then
+
Title = external_link (URL, Title, URL_origin, UrlAccess) .. TransTitle .. Format;
local arch_text;
+
URL = ''; -- unset these because no longer needed
if not utilities.is_set (ArchiveDate) then
+
Format = "";
utilities.set_message ('err_archive_missing_date');
+
elseif utilities.is_set (TitleLink) and not utilities.is_set (URL) then
ArchiveDate = ''; -- empty string for concatenation
+
local ws_url;
end
+
ws_url = wikisource_url_make (TitleLink); -- ignore ws_label return; not used here
if "live" == UrlStatus then
+
if ws_url then
arch_text = cfg.messages['archived'];
+
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?
if sepc ~= "." then arch_text = arch_text:lower() end
+
Title = utilities.substitute (cfg.presentation['interwiki-icon'], {cfg.presentation['class-wikisource'], TitleLink, Title});
if utilities.is_set (ArchiveDate) then
+
Title = Title .. TransTitle;
Archived = sepc .. ' ' .. utilities.substitute ( cfg.messages['archived-live'],
+
else
{external_link( ArchiveURL, arch_text, A:ORIGIN('ArchiveURL'), nil) .. ArchiveFormat, ArchiveDate } );
+
Title = utilities.make_wikilink (TitleLink, Title) .. TransTitle;
else
  −
Archived = '';
   
end
 
end
if not utilities.is_set (OriginalURL) then
+
else
utilities.set_message ('err_archive_missing_url');
+
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)
Archived = ''; -- empty string for concatenation
+
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
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
+
else
Archived = ArchiveFormat; -- if set and ArchiveURL not set ArchiveFormat has error message
+
Title = TransTitle;
else
+
end
Archived = '';
+
 
end
+
if utilities.is_set (Place) then
+
Place = " " .. wrap_msg ('written', Place, use_lowercase) .. sepc .. " ";
local Lay = '';
+
end
local LaySource = A['LaySource'];
+
 
local LayURL = A['LayURL'];
+
local ConferenceURL_origin = A:ORIGIN('ConferenceURL'); -- get name of parameter that holds ConferenceURL
local LayFormat = A['LayFormat'];
+
if utilities.is_set (Conference) then
LayFormat = style_format (LayFormat, LayURL, 'lay-format', 'lay-url');
+
if utilities.is_set (ConferenceURL) then
if utilities.is_set (LayURL) then
+
Conference = external_link( ConferenceURL, Conference, ConferenceURL_origin, nil );
if utilities.is_set (LayDate) then LayDate = " (" .. LayDate .. ")" end
+
end
if utilities.is_set (LaySource) then  
+
Conference = sepc .. " " .. Conference .. ConferenceFormat;
LaySource = " &ndash; ''" .. utilities.safe_for_italics (LaySource) .. "''";
+
elseif utilities.is_set (ConferenceURL) then
else
+
Conference = sepc .. " " .. external_link( ConferenceURL, nil, ConferenceURL_origin, nil );
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
 
end
   −
local TranscriptURL = A['TranscriptURL']
+
local Position = '';
local TranscriptFormat = A['TranscriptFormat'];
+
if not utilities.is_set (Position) then
TranscriptFormat = style_format (TranscriptFormat, TranscriptURL, 'transcript-format', 'transcripturl');
+
local Minutes = A['Minutes'];
local Transcript = A['Transcript'];
+
local Time = A['Time'];
local TranscriptURL_origin = A:ORIGIN('TranscriptURL'); -- get name of parameter that holds TranscriptURL
+
 
if utilities.is_set (Transcript) then
+
if utilities.is_set (Minutes) then
if utilities.is_set (TranscriptURL) then
+
if utilities.is_set (Time) then --TODO: make a function for this and similar?
Transcript = external_link( TranscriptURL, Transcript, TranscriptURL_origin, nil );
+
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'minutes') .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'time')});
 +
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
 
end
Transcript = sepc .. ' ' .. Transcript .. TranscriptFormat;
+
else
elseif utilities.is_set (TranscriptURL) then
+
Position = " " .. Position;
Transcript = external_link( TranscriptURL, nil, TranscriptURL_origin, nil );
+
At = '';
 
end
 
end
   −
local Publisher;
+
Page, Pages, Sheet, Sheets = format_pages_sheets (Page, Pages, Sheet, Sheets, config.CitationClass, Periodical_origin, sepc, NoPP, use_lowercase);
if utilities.is_set (PublicationDate) then
+
 
PublicationDate = wrap_msg ('published', PublicationDate);
+
At = utilities.is_set (At) and (sepc .. " " .. At) or "";
end
+
Position = utilities.is_set (Position) and (sepc .. " " .. Position) or "";
if utilities.is_set (PublisherName) then
+
if config.CitationClass == 'map' then
if utilities.is_set (PublicationPlace) then
+
local Sections = A['Sections']; -- Section (singular) is an alias of Chapter so set earlier
Publisher = sepc .. " " .. PublicationPlace .. ": " .. PublisherName .. PublicationDate;
+
local Inset = A['Inset'];
else
+
Publisher = sepc .. " " .. PublisherName .. PublicationDate;
+
if utilities.is_set ( Inset ) then
 +
Inset = sepc .. " " .. wrap_msg ('inset', Inset, use_lowercase);
 
end
 
end
elseif utilities.is_set (PublicationPlace) then  
+
 
Publisher= sepc .. " " .. PublicationPlace .. PublicationDate;
+
if utilities.is_set ( Sections ) then
else
+
Section = sepc .. " " .. wrap_msg ('sections', Sections, use_lowercase);
Publisher = PublicationDate;
+
elseif utilities.is_set ( Section ) then
 +
Section = sepc .. " " .. wrap_msg ('section', Section, use_lowercase);
 +
end
 +
At = At .. Inset .. Section;
 +
end
 +
 
 +
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
 +
utilities.set_message ('maint_others');
 +
end
 
end
 
end
 +
Others = utilities.is_set (Others) and (sepc .. " " .. Others) or "";
 
 
local TransPeriodical = A['TransPeriodical'];
+
if utilities.is_set (Translators) then
local TransPeriodical_origin =  A:ORIGIN ('TransPeriodical');
+
Others = safe_join ({sepc .. ' ', wrap_msg ('translated', Translators, use_lowercase), Others}, sepc);
-- Several of the above rely upon detecting this as nil, so do it last.
+
end
if (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical) or utilities.is_set (TransPeriodical)) then
+
if utilities.is_set (Interviewers) then
if utilities.is_set (Title) or utilities.is_set (TitleNote) then
+
Others = safe_join ({sepc .. ' ', wrap_msg ('interview', Interviewers, use_lowercase), Others}, sepc);
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'];
+
local TitleNote = A['TitleNote'];
if utilities.is_set (Language) then
+
TitleNote = utilities.is_set (TitleNote) and (sepc .. " " .. TitleNote) or "";
Language = language_parameter (Language); -- format, categories, name from ISO639-1, etc.
+
if utilities.is_set (Edition) then
else
+
if Edition:match ('%f[%a][Ee]d%n?%.?$') or Edition:match ('%f[%a][Ee]dition$') then -- Ed, ed, Ed., ed., Edn, edn, Edn., edn.
Language=''; -- language not specified so make sure this is an empty string;
+
utilities.set_message ('err_extra_text_edition'); -- add error message
--[[ TODO: need to extract the wrap_msg from language_parameter
+
end
so that we can solve parentheses bunching problem with Format/Language/TitleType
+
Edition = " " .. wrap_msg ('edition', Edition);
]]
+
else
 +
Edition = '';
 
end
 
end
   −
--[[
+
Series = utilities.is_set (Series) and wrap_msg ('series', {sepc, Series}) or ""; -- not the same as SeriesNum
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
+
local Agency = A['Agency'] or ''; -- |agency= is supported by {{cite magazine}}, {{cite news}}, {{cite press release}}, {{cite web}}, and certain {{citation}} templates
the annotation directly follows the |title= parameter value in the citation rather than the |event= parameter value (if provided).
+
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 "speech" == config.CitationClass then -- cite speech only
+
Agency = wrap_msg ('agency', {sepc, Agency}); -- format for rendering
TitleNote = TitleType; -- move TitleType to TitleNote so that it renders ahead of |event=
+
else
TitleType = ''; -- and unset
+
Agency = ''; -- unset; not supported
 
+
utilities.set_message ('err_parameter_ignored', {'agency'}); -- add error message
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
 
end
 +
 +
Volume = format_volume_issue (Volume, Issue, ArticleNumber, config.CitationClass, Periodical_origin, sepc, use_lowercase);
   −
-- Piece all bits together at last. Here, all should be non-nil.
+
if utilities.is_set (AccessDate) then
-- We build things this way because it is more efficient in LUA
+
local retrv_text = " " .. cfg.messages['retrieved']
-- not to keep reassigning to the same string variable over and over.
+
 
 +
AccessDate = nowrap_date (AccessDate); -- wrap in nowrap span if date in appropriate format
 +
if (sepc ~= ".") then retrv_text = retrv_text:lower() end -- if mode is cs2, lower case
 +
AccessDate = utilities.substitute (retrv_text, AccessDate); -- add retrieved text
   −
local tcommon;
+
AccessDate = utilities.substitute (cfg.presentation['accessdate'], {sepc, AccessDate}); -- allow editors to hide accessdates
local tcommon2; -- used for book cite when |contributor= is set
+
end
 
 
if utilities.in_array (config.CitationClass, {"journal", "citation"}) and utilities.is_set (Periodical) then
+
if utilities.is_set (ID) then ID = sepc .. " " .. ID; end
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
+
local Docket = A['Docket'];
end
+
  if "thesis" == config.CitationClass and utilities.is_set (Docket) then
if utilities.is_set (Others) then Others = safe_join ({Others, sepc .. " "}, sepc) end -- add terminal punctuation & space; check for dup sepc; TODO why do we need to do this here?
+
ID = sepc .. " Docket " .. Docket .. ID;
tcommon = safe_join( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, Edition, Publisher, Agency, Volume}, sepc );
+
end
elseif utilities.in_array (config.CitationClass, {"book", "citation"}) and not utilities.is_set (Periodical) then -- special cases for book cites
+
  if "report" == config.CitationClass and utilities.is_set (Docket) then -- for cite report when |docket= is set
if utilities.is_set (Contributors) then -- when we are citing foreword, preface, introduction, etc.
+
ID = sepc .. ' ' .. Docket; -- overwrite ID even if |id= is set
tcommon = safe_join( {Title, TitleNote}, sepc ); -- author and other stuff will come after this and before tcommon2
+
end
tcommon2 = safe_join( {Conference, Periodical, Format, TitleType, Series, Language, Volume, Others, Edition, Publisher, Agency}, sepc );
  −
else
  −
tcommon = safe_join( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, Volume, Others, Edition, Publisher, Agency}, sepc );
  −
end
     −
elseif 'map' == config.CitationClass then -- special cases for cite map
+
if utilities.is_set (URL) then
if utilities.is_set (Chapter) then -- map in a book; TitleType is part of Chapter
+
URL = " " .. external_link( URL, nil, URL_origin, UrlAccess );
tcommon = safe_join( {Title, Format, Edition, Scale, Series, Language, Cartography, Others, Publisher, Volume}, sepc );
  −
elseif utilities.is_set (Periodical) then -- map in a periodical
  −
tcommon = safe_join( {Title, TitleType, Format, Periodical, Scale, Series, Language, Cartography, Others, Publisher, Volume}, sepc );
  −
else -- a sheet or stand-alone map
  −
tcommon = safe_join( {Title, TitleType, Format, Edition, Scale, Series, Language, Cartography, Others, Publisher}, sepc );
  −
end
  −
  −
elseif 'episode' == config.CitationClass then -- special case for cite episode
  −
tcommon = safe_join( {Title, TitleNote, TitleType, Series, Language, Edition, Publisher}, sepc );
  −
 
  −
else -- all other CS1 templates
  −
tcommon = safe_join( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language,
  −
Volume, Others, Edition, Publisher, Agency}, sepc );
   
end
 
end
 
 
if #ID_list > 0 then
+
-- We check length of PostScript here because it will have been nuked by
ID_list = safe_join( { sepc .. " ",  table.concat( ID_list, sepc .. " " ), ID }, sepc );
+
-- the quote parameters. We'd otherwise emit a message even if there wasn't
else
+
-- a displayed postscript.
ID_list = ID;
+
-- TODO: Should the max size (1) be configurable?
 +
-- TODO: Should we check a specific pattern?
 +
if utilities.is_set(PostScript) and mw.ustring.len(PostScript) > 1 then
 +
utilities.set_message ('maint_postscript')
 
end
 
end
 
 
local Via = A['Via'];
+
local Archived;
Via = utilities.is_set (Via) and  wrap_msg ('via', Via) or '';
+
if utilities.is_set (ArchiveURL) then
local idcommon;
+
if not utilities.is_set (ArchiveDate) then -- ArchiveURL set but ArchiveDate not set
if 'audio-visual' == config.CitationClass or 'episode' == config.CitationClass then -- special case for cite AV media & cite episode position transcript
+
utilities.set_message ('err_archive_missing_date'); -- emit an error message
idcommon = safe_join( { ID_list, URL, Archived, Transcript, AccessDate, Via, Lay, Quote }, sepc );
+
ArchiveURL = ''; -- empty string for concatenation
else
+
ArchiveDate = ''; -- empty string for concatenation
idcommon = safe_join( { ID_list, URL, Archived, AccessDate, Via, Lay, Quote }, sepc );
+
end
 +
else
 +
if utilities.is_set (ArchiveDate) then -- ArchiveURL not set but ArchiveDate is set
 +
utilities.set_message ('err_archive_date_missing_url'); -- emit an error message
 +
ArchiveURL = ''; -- empty string for concatenation
 +
ArchiveDate = ''; -- empty string for concatenation
 +
end
 
end
 
end
  −
local text;
  −
local pgtext = Position .. Sheet .. Sheets .. Page .. Pages .. At;
     −
local OrigDate = A['OrigDate'];
+
if utilities.is_set (ArchiveURL) then
OrigDate = utilities.is_set (OrigDate) and wrap_msg ('origdate', OrigDate) or '';
+
local arch_text;
if utilities.is_set (Date) then
+
if "live" == UrlStatus then
if utilities.is_set (Authors) or utilities.is_set (Editors) then -- date follows authors or editors when authors not set
+
arch_text = cfg.messages['archived'];
Date = " (" .. Date .. ")" .. OrigDate .. sepc .. " "; -- in parentheses
+
if sepc ~= "." then arch_text = arch_text:lower() end
else -- neither of authors and editors set
+
if utilities.is_set (ArchiveDate) then
if (string.sub(tcommon, -1, -1) == sepc) then -- if the last character of tcommon is sepc
+
Archived = sepc .. ' ' .. utilities.substitute ( cfg.messages['archived-live'],
Date = " " .. Date .. OrigDate; -- Date does not begin with sepc
+
{external_link( ArchiveURL, arch_text, A:ORIGIN('ArchiveURL'), nil) .. ArchiveFormat, ArchiveDate } );
 
else
 
else
Date = sepc .. " " .. Date .. OrigDate; -- Date begins with sepc
+
Archived = '';
 
end
 
end
end
+
if not utilities.is_set (OriginalURL) then
end
+
utilities.set_message ('err_archive_missing_url');
if utilities.is_set (Authors) then
+
Archived = ''; -- empty string for concatenation
if (not utilities.is_set (Date)) then -- when date is set it's in parentheses; no Authors termination
  −
Authors = terminate_name_list (Authors, sepc); -- when no date, terminate with 0 or 1 sepc and a space
  −
end
  −
if utilities.is_set (Editors) then
  −
local in_text = " ";
  −
local post_text = "";
  −
if utilities.is_set (Chapter) and 0 == #c then
  −
in_text = in_text .. cfg.messages['in'] .. " "
  −
if (sepc ~= '.') then
  −
in_text = in_text:lower() -- lowercase for cs2
  −
end
   
end
 
end
if EditorCount <= 1 then
+
elseif utilities.is_set (OriginalURL) then -- UrlStatus is empty, 'dead', 'unfit', 'usurped', 'bot: unknown'
post_text = " (" .. cfg.messages['editor'] .. ")"; -- be consistent with no-author, no-date case
+
if utilities.in_array (UrlStatus, {'unfit', 'usurped', 'bot: unknown'}) then
else
+
arch_text = cfg.messages['archived-unfit'];
post_text = " (" .. cfg.messages['editors'] .. ")";
+
if sepc ~= "." then arch_text = arch_text:lower() end
end
+
Archived = sepc .. ' ' .. arch_text .. ArchiveDate; -- format already styled
Editors = terminate_name_list (in_text .. Editors .. post_text, sepc); -- terminate with 0 or 1 sepc and a space
+
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
 
end
if utilities.is_set (Contributors) then -- book cite and we're citing the intro, preface, etc.
+
elseif utilities.is_set (ArchiveFormat) then
local by_text = sepc .. ' ' .. cfg.messages['by'] .. ' ';
+
Archived = ArchiveFormat; -- if set and ArchiveURL not set ArchiveFormat has error message
if (sepc ~= '.') then by_text = by_text:lower() end -- lowercase for cs2
+
else
Authors = by_text .. Authors; -- author follows title so tweak it here
+
Archived = '';
if utilities.is_set (Editors) and utilities.is_set (Date) then -- when Editors make sure that Authors gets terminated
+
end
Authors = terminate_name_list (Authors, sepc); -- terminate with 0 or 1 sepc and a space
+
end
+
local TranscriptURL = A['TranscriptURL']
if (not utilities.is_set (Date)) then -- when date is set it's in parentheses; no Contributors termination
+
local TranscriptFormat = A['TranscriptFormat'];
Contributors = terminate_name_list (Contributors, sepc); -- terminate with 0 or 1 sepc and a space
+
TranscriptFormat = style_format (TranscriptFormat, TranscriptURL, 'transcript-format', 'transcripturl');
end
+
local Transcript = A['Transcript'];
text = safe_join( {Contributors, Date, Chapter, tcommon, Authors, Place, Editors, tcommon2, pgtext, idcommon }, sepc );
+
local TranscriptURL_origin = A:ORIGIN('TranscriptURL'); -- get name of parameter that holds TranscriptURL
else
+
if utilities.is_set (Transcript) then
text = safe_join( {Authors, Date, Chapter, Place, Editors, tcommon, pgtext, idcommon }, sepc );
+
if utilities.is_set (TranscriptURL) then
end
+
Transcript = external_link( TranscriptURL, Transcript, TranscriptURL_origin, nil );
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
 +
Transcript = sepc .. ' ' .. Transcript .. TranscriptFormat;
 +
elseif utilities.is_set (TranscriptURL) then
 +
Transcript = external_link( TranscriptURL, nil, TranscriptURL_origin, nil );
 
end
 
end
  −
if utilities.is_set (PostScript) and PostScript ~= sepc then
  −
text = safe_join( {text, sepc}, sepc ); -- Deals with italics, spaces, etc.
  −
text = text:sub(1, -sepc:len() - 1);
  −
end
  −
  −
text = safe_join( {text, PostScript}, sepc );
     −
-- Now enclose the whole thing in a <cite> element
+
local Publisher;
local options_t = {};
+
if utilities.is_set (PublicationDate) then
options_t.class = cite_class_attribute_make (config.CitationClass, Mode);
+
PublicationDate = wrap_msg ('published', PublicationDate);
 
+
end
local Ref
+
if utilities.is_set (PublisherName) then
if A['Ref'] ~= 'harv' then
+
if utilities.is_set (PublicationPlace) then
Ref = is_valid_parameter_value (A['Ref'], A:ORIGIN('Ref'), cfg.keywords_lists['ref'], nil, true); -- nil when |ref=harv; A['Ref'] else
+
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
 
else
Ref = nil
+
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
 
end
   −
if 'none' ~= cfg.keywords_xlate[(Ref and Ref:lower()) or ''] then
+
--[[
local namelist_t = {}; -- holds selected contributor, author, editor name list
+
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
local year = first_set ({Year, anchor_year}, 2); -- Year first for legacy citations and for YMD dates that require disambiguation
+
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 #c > 0 then -- if there is a contributor list
+
if utilities.is_set (Periodical) then -- if Periodical, perhaps because of an included |website= or |journal= parameter
namelist_t = c; -- select it
+
if utilities.is_set (Conference) then -- and if |event= is set
elseif #a > 0 then -- or an author list
+
Conference = Conference .. sepc .. " "; -- then add appropriate punctuation to the end of the Conference variable before rendering
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
 
end
else
  −
citeref_id = ''; -- unset
   
end
 
end
options_t.id = Ref or citeref_id;
   
end
 
end
   −
if string.len (text:gsub('%b<>', '')) <= 2 then -- remove html and html-like tags; then get length of what remains;
+
-- Piece all bits together at last. Here, all should be non-nil.
z.error_cats_t = {}; -- blank the categories list
+
-- We build things this way because it is more efficient in LUA
z.error_msgs_t = {}; -- blank the error messages list
+
-- not to keep reassigning to the same string variable over and over.
OCinSoutput = nil; -- blank the metadata string
+
 
text = ''; -- blank the the citation
+
local tcommon;
utilities.set_message ('err_empty_citation'); -- set empty citation message and category
+
local tcommon2; -- used for book cite when |contributor= is set
end
   
 
local render_t = {}; -- here we collect the final bits for concatenation into the rendered citation
+
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
   −
if utilities.is_set (options_t.id) then -- here we wrap the rendered citation in <cite ...>...</cite> tags
+
elseif 'map' == config.CitationClass then -- special cases for cite map
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
+
if utilities.is_set (Chapter) then -- map in a book; TitleType is part of Chapter
else
+
tcommon = safe_join ({Title, Edition, Scale, Series, Language, Cartography, Others, Publisher, Volume}, sepc);
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
+
elseif utilities.is_set (Periodical) then -- map in a periodical
end
+
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);
   −
if OCinSoutput then -- blanked when citation is 'empty' so don't bother to add boilerplate metadata span
+
else -- all other CS1 templates
table.insert (render_t, utilities.substitute (cfg.presentation['ocins'], OCinSoutput)); -- format and append metadata to the citation
+
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
 
end
 
+
local template_name = ('citation' == config.CitationClass) and 'citation' or 'cite ' .. (cfg.citation_class_map_t[config.CitationClass] or config.CitationClass);
+
local Via = A['Via'];
local template_link = '[[Template:' .. template_name .. '|' .. template_name .. ']]';
+
Via = utilities.is_set (Via) and wrap_msg ('via', Via) or '';
local msg_prefix = '<code class="cs1-code">{{' .. template_link .. '}}</code>: ';
+
local idcommon;
 
+
if 'audio-visual' == config.CitationClass or 'episode' == config.CitationClass then -- special case for cite AV media & cite episode position transcript
if 0 ~= #z.error_msgs_t then
+
idcommon = safe_join( { ID_list, URL, Archived, Transcript, AccessDate, Via, Quote }, sepc );
mw.addWarning (utilities.substitute (cfg.messages.warning_msg_e, template_link));
+
else
 
+
idcommon = safe_join( { ID_list, URL, Archived, AccessDate, Via, Quote }, sepc );
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
 +
 +
local text;
 +
local pgtext = Position .. Sheet .. Sheets .. Page .. Pages .. At;
   −
if 0 ~= #z.maint_cats_t then
+
local OrigDate = A['OrigDate'];
mw.addWarning (utilities.substitute (cfg.messages.warning_msg_m, template_link));
+
OrigDate = utilities.is_set (OrigDate) and wrap_msg ('origdate', OrigDate) or '';
 
+
if utilities.is_set (Date) then
table.sort (z.maint_cats_t); -- sort the maintenance messages list
+
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
local maint_msgs_t = {}; -- here we collect all of the maint messages
+
else -- neither of authors and editors set
 
+
if (string.sub(tcommon, -1, -1) == sepc) then -- if the last character of tcommon is sepc
if 0 == #z.error_msgs_t then -- if no error messages
+
Date = " " .. Date .. OrigDate; -- Date does not begin with sepc
table.insert (maint_msgs_t, msg_prefix); -- insert message prefix in maint message livery
+
else
 +
Date = sepc .. " " .. Date .. OrigDate; -- Date begins with sepc
 +
end
 
end
 
end
+
end
for _, v in ipairs( z.maint_cats_t ) do -- append maintenance categories
+
if utilities.is_set (Authors) then
table.insert (maint_msgs_t, -- assemble new maint message and add it to the maint_msgs_t table
+
if (not utilities.is_set (Date)) then -- when date is set it's in parentheses; no Authors termination
table.concat ({v, ' (', utilities.substitute (cfg.messages[':cat wikilink'], v), ')'})
+
Authors = terminate_name_list (Authors, sepc); -- when no date, terminate with 0 or 1 sepc and a space
);
   
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
+
if utilities.is_set (Editors) then
end
+
local in_text = '';
 
+
local post_text = '';
if not no_tracking_cats then
+
if utilities.is_set (Chapter) and 0 == #c then
for _, v in ipairs (z.error_cats_t) do -- append error categories
+
in_text = cfg.messages['in'] .. ' ';
table.insert (render_t, utilities.substitute (cfg.messages['cat wikilink'], v));
+
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
 
end
for _, v in ipairs (z.maint_cats_t) do -- append maintenance categories
+
if utilities.is_set (Contributors) then -- book cite and we're citing the intro, preface, etc.
table.insert (render_t, utilities.substitute (cfg.messages['cat wikilink'], v));
+
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
 
end
for _, v in ipairs (z.prop_cats_t) do -- append properties categories
+
text = safe_join( {Editors, Date, Chapter, Place, tcommon, pgtext, idcommon}, sepc );
table.insert (render_t, utilities.substitute (cfg.messages['cat wikilink'], v));
+
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
 
end
 
end
   −
return table.concat (render_t); -- make a big string and done
+
if utilities.is_set (PostScript) and PostScript ~= sepc then
end
+
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 );
   −
--[[--------------------------< V A L I D A T E >--------------------------------------------------------------
+
-- Now enclose the whole thing in a <cite> element
 +
local options_t = {};
 +
options_t.class = cite_class_attribute_make (config.CitationClass, Mode);
   −
Looks for a parameter's name in one of several whitelists.
+
local Ref = is_valid_parameter_value (A['Ref'], A:ORIGIN('Ref'), cfg.keywords_lists['ref'], nil, true); -- nil when |ref=harv; A['Ref'] else
   −
Parameters in the whitelist can have three values:
+
if 'none' ~= cfg.keywords_xlate[(Ref and Ref:lower()) or ''] then
true - active, supported parameters
+
local namelist_t = {}; -- holds selected contributor, author, editor name list
false - deprecated, supported parameters
+
local year = first_set ({Year, anchor_year}, 2); -- Year first for legacy citations and for YMD dates that require disambiguation
nil - unsupported parameters
  −
  −
]]
     −
local function validate (name, cite_class, empty)
+
if #c > 0 then -- if there is a contributor list
local name = tostring (name);
+
namelist_t = c; -- select it
local enum_name; -- for enumerated parameters, is name with enumerator replaced with '#'
+
elseif #a > 0 then -- or an author list
local state;
+
namelist_t = a;
local function state_test (state, name) -- local function to do testing of state values
+
elseif #e > 0 then -- or an editor list
if true == state then return true; end -- valid actively supported parameter
+
namelist_t = e;
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
 
end
if 'tracked' == state then
+
local citeref_id;
local base_name = name:gsub ('%d', ''); -- strip enumerators from parameter names that have them to get the base name
+
if #namelist_t > 0 then -- if there are names in namelist_t
utilities.add_prop_cat ('tracked-param', {base_name}, base_name); -- add a properties category; <base_name> modifies <key>
+
citeref_id = make_citeref_id (namelist_t, year); -- go make the CITEREF anchor
return true;
+
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
end
+
utilities.set_message ('maint_ref_duplicates_default');
return nil;
+
end
end
+
else
 +
citeref_id = ''; -- unset
 +
end
 +
options_t.id = Ref or citeref_id;
 +
end
   −
if name:find ('#') then -- # is a cs1|2 reserved character so parameters with # not permitted
+
if string.len (text:gsub('%b<>', '')) <= 2 then -- remove html and html-like tags; then get length of what remains;
return nil;
+
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
 
end
 +
 +
local render_t = {}; -- here we collect the final bits for concatenation into the rendered citation
   −
if utilities.in_array (cite_class, whitelist.preprint_template_list ) then -- limited parameter sets allowed for these templates
+
if utilities.is_set (options_t.id) then -- here we wrap the rendered citation in <cite ...>...</cite> tags
state = whitelist.limited_basic_arguments[name];
+
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
if true == state_test (state, name) then return true; end
+
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
   −
state = whitelist.preprint_arguments[cite_class][name]; -- look in the parameter-list for the template identified by cite_class
+
if OCinSoutput then -- blanked when citation is 'empty' so don't bother to add boilerplate metadata span
if true == state_test (state, name) then return true; end
+
table.insert (render_t, utilities.substitute (cfg.presentation['ocins'], OCinSoutput)); -- format and append metadata to the citation
 +
end
   −
-- limited enumerated parameters list
+
local template_name = ('citation' == config.CitationClass) and 'citation' or 'cite ' .. (cfg.citation_class_map_t[config.CitationClass] or config.CitationClass);
enum_name = name:gsub("%d+", "#" ); -- replace digit(s) with # (last25 becomes last#) (mw.ustring because non-Western 'local' digits)
+
local template_link = '[[Template:' .. template_name .. '|' .. template_name .. ']]';
state = whitelist.limited_numbered_arguments[enum_name];
+
local msg_prefix = '<code class="cs1-code">{{' .. template_link .. '}}</code>: ';
if true == state_test (state, name) then return true; end
     −
return false; -- not supported because not found or name is set to nil
+
if 0 ~= #z.error_msgs_t then
end -- end limited parameter-set templates
+
mw.addWarning (utilities.substitute (cfg.messages.warning_msg_e, template_link));
   −
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
+
table.insert (render_t, ' '); -- insert a space between citation and its error messages
state = whitelist.unique_arguments[cite_class][name]; -- look in the template-specific parameter-lists for the template identified by cite_class
+
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
if true == state_test (state, name) then return true; end
  −
end -- if here, fall into general validation
  −
  −
state = whitelist.basic_arguments[name]; -- all other templates; all normal parameters allowed
  −
if true == state_test (state, name) then return true; end
     −
-- all enumerated parameters allowed
+
local hidden = true; -- presume that the only error messages emited by this template are hidden
enum_name = name:gsub("%d+", "#" ); -- replace digit(s) with # (last25 becomes last#) (mw.ustring because non-Western 'local' digits)
+
for _, v in ipairs (z.error_msgs_t) do -- spin through the list of error messages
state = whitelist.numbered_arguments[enum_name];
+
if v:find ('cs1-visible-error', 1, true) then -- look for the visible error class name
if true == state_test (state, name) then return true; end
+
hidden = false; -- found one; so don't hide the error message prefix
 
+
break; -- and done because no need to look further
return false; -- not supported because not found or name is set to nil
+
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
 +
 
 +
return value;
 +
end
 +
 
 +
 
 +
--[[--------------------------< M I S S I N G _ P I P E _ C H E C K >------------------------------------------
 +
 
 +
Look at the contents of a parameter. If the content has a string of characters and digits followed by an 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
 
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 only for readers who are
code.  when these values have the form (without leading colon):
+
active extended confirmed Wikipedia editorsThis function sets an error message when such urls are discovered
[[<prefix>:link|label]] return label as plain-text
+
and when appropriate, sets the |<param>-url-access=subscription.  returns nothing.
[[<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, cite_args_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, param); -- add parameter name to the error list
 +
end
 +
end
 +
if 0 ~= #url_error_t then -- non-zero when there are errors
 +
table.sort (url_error_t); -- sor for error messaging
 +
for i, param in ipairs (url_error_t) do
 +
if cfg.url_access_map_t[param] then -- if <param> has a matching -access parameter
 +
cite_args_t[cfg.url_access_map_t[param]] = cfg.keywords_xlate.subscription; -- set |<param>-url-access=subscription
 +
end
 +
url_error_t[i] = utilities.wrap_style ('parameter', param); -- make the parameter pretty for error message
 +
end
 +
 
 +
utilities.set_message ('err_param_has_twl_url', {utilities.make_sep_list (#url_error_t, url_error_t)}); -- add this error message
 
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 at the contents of a parameter. If the content has a string of characters and digits followed by an equal
+
look for extraneous url parameter values; parameters listed in skip table are not checked
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 function has_extraneous_url (non_url_param_t)
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 (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,564:       −
--[[--------------------------< 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_t – table of all cs1|2 parameters in the template (the parent frame)
 +
config_t – table of template-supplied parameter (the #invoke frame)
    
]]
 
]]
   −
local function citation(frame)
+
local function _citation (frame, args_t, config_t) -- 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_t.SandboxPath and '' ~= config_t.SandboxPath) and config_t.SandboxPath) or '/sandbox'; -- sandbox path from {{#invoke:Citation/CS1/sandbox|citation|SandboxPath=/...}}
 
is_sandbox = nil ~= string.find (frame:getTitle(), sandbox, 1, true); -- is this invoke the sandbox module?
 
is_sandbox = nil ~= string.find (frame:getTitle(), sandbox, 1, true); -- is this invoke the sandbox module?
 
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,589:  
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,237: Line 4,596:     
z = utilities.z; -- table of error and category tables in Module:Citation/CS1/Utilities
 
z = utilities.z; -- table of error and category tables in Module:Citation/CS1/Utilities
 +
local cite_args_t = {}; -- because args_t is the parent (template) frame args (which cannot be modified); params and their values will be placed here
    
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,605:  
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_t) 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,252: Line 4,611:  
k = mw.ustring.gsub (k, '%d', cfg.date_names.local_digits); -- for enumerated parameters, translate 'local' digits to Western 0-9
 
k = mw.ustring.gsub (k, '%d', cfg.date_names.local_digits); -- for enumerated parameters, translate 'local' digits to Western 0-9
 
end
 
end
if not validate( k, config.CitationClass ) then
+
if not validate( k, config_t.CitationClass ) then
 
if type (k) ~= 'string' then -- exclude empty numbered parameters
 
if type (k) ~= 'string' then -- exclude empty numbered parameters
 
if v:match("%S+") ~= nil then
 
if v:match("%S+") ~= nil then
 
error_text = utilities.set_message ('err_text_ignored', {v});
 
error_text = utilities.set_message ('err_text_ignored', {v});
 
end
 
end
elseif validate (k:lower(), config.CitationClass) then  
+
elseif validate (k:lower(), config_t.CitationClass) then  
 
error_text = utilities.set_message ('err_parameter_ignored_suggest', {k, k:lower()}); -- suggest the lowercase version of the parameter
 
error_text = utilities.set_message ('err_parameter_ignored_suggest', {k, k:lower()}); -- suggest the lowercase version of the parameter
 
else
 
else
Line 4,267: Line 4,626:  
if capture then -- if the pattern matches  
 
if capture then -- if the pattern matches  
 
param = utilities.substitute (param, capture); -- add the capture to the suggested parameter (typically the enumerator)
 
param = utilities.substitute (param, capture); -- add the capture to the suggested parameter (typically the enumerator)
if validate (param, config.CitationClass) then -- validate the suggestion to make sure that the suggestion is supported by this template (necessary for limited parameter lists)
+
if validate (param, config_t.CitationClass) then -- validate the suggestion to make sure that the suggestion is supported by this template (necessary for limited parameter lists)
 
error_text = utilities.set_message ('err_parameter_ignored_suggest', {k, param}); -- set the suggestion error message
 
error_text = utilities.set_message ('err_parameter_ignored_suggest', {k, param}); -- set the suggestion error message
 
else
 
else
Line 4,276: Line 4,635:  
end
 
end
 
if not utilities.is_set (error_text) then -- couldn't match with a pattern, is there an explicit suggestion?
 
if not utilities.is_set (error_text) then -- couldn't match with a pattern, is there an explicit suggestion?
if (suggestions.suggestions[ k:lower() ] ~= nil) and validate (suggestions.suggestions[ k:lower() ], config.CitationClass) then
+
if (suggestions.suggestions[ k:lower() ] ~= nil) and validate (suggestions.suggestions[ k:lower() ], config_t.CitationClass) then
 
utilities.set_message ('err_parameter_ignored_suggest', {k, suggestions.suggestions[ k:lower() ]});
 
utilities.set_message ('err_parameter_ignored_suggest', {k, suggestions.suggestions[ k:lower() ]});
 
else
 
else
Line 4,286: Line 4,645:  
end
 
end
   −
args[k] = v; -- save this parameter and its value
+
cite_args_t[k] = v; -- save this parameter and its value
    
elseif not utilities.is_set (v) then -- for empty parameters
 
elseif not utilities.is_set (v) then -- for empty parameters
if not validate (k, config.CitationClass, true) then -- is this empty parameter a valid parameter
+
if not validate (k, config_t.CitationClass, true) then -- is this empty parameter a valid parameter
 
k = ('' == k) and '(empty string)' or k; -- when k is empty string (or was space(s) trimmed to empty string), replace with descriptive text
 
k = ('' == k) and '(empty string)' or k; -- when k is empty string (or was space(s) trimmed to empty string), replace with descriptive text
 
table.insert (empty_unknowns, utilities.wrap_style ('parameter', k)); -- format for error message and add to the list
 
table.insert (empty_unknowns, utilities.wrap_style ('parameter', k)); -- format for error message and add to the list
 
end
 
end
-- crude debug support that allows us to render a citation from module {{#invoke:}} TODO: keep?
+
end
-- elseif args[k] ~= nil or (k == 'postscript') then -- when args[k] has a value from {{#invoke}} frame (we don't normally do that)
  −
-- args[k] = v; -- overwrite args[k] with empty string from pframe.args[k] (template frame); v is empty string here
  −
end -- not sure about the postscript bit; that gets handled in parameter validation; historical artifact?
   
end
 
end
   Line 4,306: Line 4,662:  
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 (cite_args_t) do
   −
for k, v in pairs( args ) do
   
if 'string' == type (k) then -- don't evaluate positional parameters
 
if 'string' == type (k) then -- don't evaluate positional parameters
 
has_invisible_chars (k, v); -- look for invisible characters
 
has_invisible_chars (k, v); -- look for invisible characters
Line 4,314: Line 4,672:  
has_extraneous_punc (k, v); -- look for extraneous terminal punctuation in parameter values
 
has_extraneous_punc (k, v); -- look for extraneous terminal punctuation in parameter values
 
missing_pipe_check (k, v); -- do we think that there is a parameter that is missing a pipe?
 
missing_pipe_check (k, v); -- do we think that there is a parameter that is missing a pipe?
args[k] = inter_wiki_check (k, v); -- when language interwiki-linked parameter missing leading colon replace with wiki-link label
+
cite_args_t[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
 +
has_twl_url (url_param_t, cite_args_t); -- look for url-holding parameters that hold a The Wikipedia Library url
    
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_t, cite_args_t)
 
});
 
});
 +
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,714:  
]]
 
]]
   −
return {citation = citation};
+
return {
 +
citation = citation, -- template entry point
 +
 +
_citation = _citation, -- module entry point
 +
}