Changes

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