Changes

Jump to navigation Jump to search
2,385 bytes added ,  18:56, 29 September 2018
synch from sandbox;
Line 5: Line 5:  
]]
 
]]
   −
local dates, year_date_check, reformat_dates, date_hyphen_to_dash -- functions in Module:Citation/CS1/Date_validation
+
local dates, year_date_check, reformat_dates, date_hyphen_to_dash, -- functions in Module:Citation/CS1/Date_validation
 +
date_name_xlate
    
local is_set, in_array, substitute, error_comment, set_error, select_one, -- functions in Module:Citation/CS1/Utilities
 
local is_set, in_array, substitute, error_comment, set_error, select_one, -- functions in Module:Citation/CS1/Utilities
add_maint_cat, wrap_style, safe_for_italics, remove_wiki_link;
+
add_maint_cat, wrap_style, safe_for_italics, is_wikilink, make_wikilink;
    
local z ={}; -- tables in Module:Citation/CS1/Utilities
 
local z ={}; -- tables in Module:Citation/CS1/Utilities
Line 54: Line 55:  
end
 
end
 
end
 
end
 +
    
--[[--------------------------< A D D _ P R O P _ C A T >--------------------------------------------------------
 
--[[--------------------------< A D D _ P R O P _ C A T >--------------------------------------------------------
    
Adds a category to z.properties_cats using names from the configuration file with additional text if any.
 
Adds a category to z.properties_cats using names from the configuration file with additional text if any.
 +
 +
foreign_lang_source and foreign_lang_source_2 keys have a language code appended to them so that multiple languages
 +
may be categorized but multiples of the same language are not categorized.
    
added_prop_cats is a table declared in page scope variables above
 
added_prop_cats is a table declared in page scope variables above
Line 66: Line 71:  
if not added_prop_cats [key] then
 
if not added_prop_cats [key] then
 
added_prop_cats [key] = true; -- note that we've added this category
 
added_prop_cats [key] = true; -- note that we've added this category
 +
key = key:gsub ('(foreign_lang_source_?2?)%a%a%a?', '%1'); -- strip lang code from keyname
 
table.insert( z.properties_cats, substitute (cfg.prop_cats [key], arguments)); -- make name then add to table
 
table.insert( z.properties_cats, substitute (cfg.prop_cats [key], arguments)); -- make name then add to table
 
end
 
end
 
end
 
end
 +
    
--[[--------------------------< A D D _ V A N C _ E R R O R >----------------------------------------------------
 
--[[--------------------------< A D D _ V A N C _ E R R O R >----------------------------------------------------
Line 126: Line 133:  
the first character of the whole domain name including subdomains must be a letter or a digit
 
the first character of the whole domain name including subdomains must be a letter or a digit
 
internationalized domain name (ascii characters with .xn-- ASCII Compatible Encoding (ACE) prefix xn-- in the tld) see https://tools.ietf.org/html/rfc3490
 
internationalized domain name (ascii characters with .xn-- ASCII Compatible Encoding (ACE) prefix xn-- in the tld) see https://tools.ietf.org/html/rfc3490
single-letter/digit second-level domains in the .org TLD
+
single-letter/digit second-level domains in the .org and .cash TLDs
 
q, x, and z SL domains in the .com TLD
 
q, x, and z SL domains in the .com TLD
 
i and q SL domains in the .net TLD
 
i and q SL domains in the .net TLD
Line 153: Line 160:  
elseif domain:match ('%f[%a%d][%a%d][%a%d%-]+[%a%d]%.xn%-%-[%a%d]+$') then -- internationalized domain name with ACE prefix
 
elseif domain:match ('%f[%a%d][%a%d][%a%d%-]+[%a%d]%.xn%-%-[%a%d]+$') then -- internationalized domain name with ACE prefix
 
return true;
 
return true;
elseif domain:match ('%f[%a%d][%a%d]%.org$') then -- one character .org hostname
+
elseif domain:match ('%f[%a%d][%a%d]%.cash$') then -- one character/digit .cash hostname
 +
return true;
 +
elseif domain:match ('%f[%a%d][%a%d]%.org$') then -- one character/digit .org hostname
 
return true;
 
return true;
 
elseif domain:match ('%f[%a][qxz]%.com$') then -- assigned one character .com hostname (x.com times out 2015-12-10)
 
elseif domain:match ('%f[%a][qxz]%.com$') then -- assigned one character .com hostname (x.com times out 2015-12-10)
Line 249: Line 258:  
return not is_url (scheme, domain); -- return true if value DOES NOT appear to be a valid url
 
return not is_url (scheme, domain); -- return true if value DOES NOT appear to be a valid url
 
end
 
end
 +
    
--[[--------------------------< L I N K _ T I T L E _ O K >---------------------------------------------------
 
--[[--------------------------< L I N K _ T I T L E _ O K >---------------------------------------------------
Line 376: Line 386:  
['\n'] = ' ' } );
 
['\n'] = ' ' } );
 
end
 
end
 +
    
--[[--------------------------< E X T E R N A L _ L I N K >----------------------------------------------------
 
--[[--------------------------< E X T E R N A L _ L I N K >----------------------------------------------------
Line 404: Line 415:  
if path then -- if there is a path portion
 
if path then -- if there is a path portion
 
path = path:gsub ('[%[%]]', {['[']='%5b',[']']='%5d'}); -- replace '[' and ']' with their percent encoded values
 
path = path:gsub ('[%[%]]', {['[']='%5b',[']']='%5d'}); -- replace '[' and ']' with their percent encoded values
URL=domain..path; -- and reassemble
+
URL = table.concat ({domain, path}); -- and reassemble
 
end
 
end
 +
 +
base_url = table.concat({ "[", URL, " ", safe_for_url (label), "]" }); -- assemble a wikimarkup url
 
 
 
if is_set (access) then -- access level (subscription, registration, limited)
 
if is_set (access) then -- access level (subscription, registration, limited)
local label_head = '';
+
base_url = substitute (cfg.presentation['ext-link-access-signal'], {cfg.presentation[access].class, cfg.presentation[access].title, base_url}); -- add the appropriate icon
local label_tail;
  −
local markup = ''; -- can't start a span inside italic markup and end it outside the italic markup
  −
 
  −
label = safe_for_url (label); -- replace square brackets and newlines (is this necessary? already done above?)
  −
if label:match ("(.*)%s+(.+)('''?)$") then -- for italicized titles (cite book, etc)
  −
label_head, label_tail, markup = label:match ("(.*)%s+(.+)('''?)$"); -- split the label at the right-most space; separate the markup
  −
elseif label:match ("(.*)%s+(.+)$") then -- for upright titles (journal, news, magazine, etc)
  −
label_head, label_tail = label:match ("(.*)%s+(.+)$"); -- split the label at the right-most space; no markup
  −
elseif label:match ("(.+)('''?)$") then -- single word label with markup
  −
label_tail, markup = label:match ("(.+)('''?)$"); -- save label text as label tail; separate the markup
  −
else
  −
label_tail = label;
  −
end
  −
 
  −
base_url = table.concat (
  −
{
  −
'<span class="plainlinks">[', -- opening css
  −
URL, -- the url
  −
' ', -- the required space
  −
label_head, -- all but the last word of the label
  −
' <span class="nowrap">', -- nowrap css for the last word and the signal icon
  −
label_tail, -- last (or only) word of the label inside the span
  −
'<span style="padding-left:0.15em">', -- signal spacing css
  −
cfg.presentation[access], -- the appropriate icon
  −
'</span></span>', -- close signal spacing and nowrap spans
  −
markup, -- insert italic markup if any
  −
']</span>' -- close the plain links span
  −
});
  −
else
  −
base_url = table.concat({ "[", URL, " ", safe_for_url( label ), "]" }); -- no signal markup
   
end
 
end
 
 
return table.concat({ base_url, error_str });
+
return table.concat ({base_url, error_str});
 
end
 
end
   Line 462: Line 445:  
end
 
end
   −
--[[--------------------------< K E R N _ Q U O T E S >--------------------------------------------------------
     −
Apply kerning to open the space between the quote mark provided by the Module and a leading or trailing quote mark contained in a |title= or |chapter= parameter's value.
+
--[=[-------------------------< K E R N _ Q U O T E S >--------------------------------------------------------
 +
 
 +
Apply kerning to open the space between the quote mark provided by the Module and a leading or trailing quote
 +
mark contained in a |title= or |chapter= parameter's value.
 +
 
 
This function will positive kern either single or double quotes:
 
This function will positive kern either single or double quotes:
 
"'Unkerned title with leading and trailing single quote marks'"
 
"'Unkerned title with leading and trailing single quote marks'"
Line 470: Line 456:  
Double single quotes (italic or bold wikimarkup) are not kerned.
 
Double single quotes (italic or bold wikimarkup) are not kerned.
   −
Replaces unicode quotemarks with typewriter quote marks regardless of the need for kerning.
+
Replaces unicode quotemarks in plain text or in the label portion of a [[L|D]] style wikilink with typewriter
 +
quote marks regardless of the need for kerning.  Unicode quote marks are not replaced in simple [[D]] wikilinks.
    
Call this function for chapter titles, for website titles, etc; not for book titles.
 
Call this function for chapter titles, for website titles, etc; not for book titles.
   −
]]
+
]=]
    
local function kern_quotes (str)
 
local function kern_quotes (str)
 
local cap='';
 
local cap='';
 
local cap2='';
 
local cap2='';
-- TODO: move this elswhere so that all title-holding elements get these quote marks replaced?
+
local wl_type, label, link;
-- str= mw.ustring.gsub (str, '[“”]', '\"'); -- replace “” (U+201C & U+201D) with " (typewriter double quote mark)
+
 
-- str= mw.ustring.gsub (str, '[‘’]', '\''); -- replace ‘’ (U+2018 & U+2019) with ' (typewriter single quote mark)
+
wl_type, label, link = is_wikilink (str); -- wl_type is: 0, no wl (text in label variable); 1, [[D]]; 2, [[L|D]]
 
 
cap, cap2 = str:match ("^([\"\'])([^\'].+)"); -- match leading double or single quote but not double single quotes
+
if 1 == wl_type then -- [[D]] simple wikilink with or without quote marks
if is_set (cap) then
+
if mw.ustring.match (str, '%[%[[\"“”\'‘’].+[\"“”\'‘’]%]%]') then -- leading and trailing quote marks
str = substitute (cfg.presentation['kern-left'], {cap, cap2});
+
str = substitute (cfg.presentation['kern-wl-both'], str);
end
+
elseif mw.ustring.match (str, '%[%[[\"“”\'‘’].+%]%]') then -- leading quote marks
 +
str = substitute (cfg.presentation['kern-wl-left'], str);
 +
elseif mw.ustring.match (str, '%[%[.+[\"“”\'‘’]%]%]') then -- trailing quote marks
 +
str = substitute (cfg.presentation['kern-wl-right'], str);
 +
end
 +
 
 +
else -- plain text or [[L|D]]; text in label variable
 +
label= mw.ustring.gsub (label, '[“”]', '\"'); -- replace “” (U+201C & U+201D) with " (typewriter double quote mark)
 +
label= mw.ustring.gsub (label, '[‘’]', '\''); -- replace ‘’ (U+2018 & U+2019) with ' (typewriter single quote mark)
   −
cap, cap2 = str:match ("^(.+[^\'])([\"\'])$")
+
cap, cap2 = mw.ustring.match (label, "^([\"\'])([^\'].+)"); -- match leading double or single quote but not doubled single quotes (italic markup)
if is_set (cap) then
+
if is_set (cap) then
str = substitute (cfg.presentation['kern-right'], {cap, cap2});
+
label = substitute (cfg.presentation['kern-left'], {cap, cap2});
 +
end
 +
 +
cap, cap2 = mw.ustring.match (label, "^(.+[^\'])([\"\'])$") -- match trailing double or single quote but not doubled single quotes (italic markup)
 +
if is_set (cap) then
 +
label = substitute (cfg.presentation['kern-right'], {cap, cap2});
 +
end
 +
 +
if 2 == wl_type then
 +
str = make_wikilink (link, label); -- reassemble the wikilink
 +
else
 +
str = label;
 +
end
 
end
 
end
 
return str;
 
return str;
 
end
 
end
 +
    
--[[--------------------------< F O R M A T _ S C R I P T _ V A L U E >----------------------------------------
 
--[[--------------------------< F O R M A T _ S C R I P T _ V A L U E >----------------------------------------
Line 529: Line 537:  
end
 
end
 
-- if we get this far we have prefix and script
 
-- if we get this far we have prefix and script
name = mw.language.fetchLanguageName( lang, "en" ); -- get language name so that we can use it to categorize
+
name = cfg.lang_code_remap[lang] or mw.language.fetchLanguageName( lang, "en" ); -- get language name so that we can use it to categorize
 
if is_set (name) then -- is prefix a proper ISO 639-1 language code?
 
if is_set (name) then -- is prefix a proper ISO 639-1 language code?
 
script_value = script_value:gsub ('^%l%l%s*:%s*', ''); -- strip prefix from script
 
script_value = script_value:gsub ('^%l%l%s*:%s*', ''); -- strip prefix from script
Line 547: Line 555:  
return script_value;
 
return script_value;
 
end
 
end
 +
    
--[[--------------------------< S C R I P T _ C O N C A T E N A T E >------------------------------------------
 
--[[--------------------------< S C R I P T _ C O N C A T E N A T E >------------------------------------------
Line 594: Line 603:  
]]
 
]]
   −
local function format_chapter_title (scriptchapter, chapter, transchapter, chapterurl, chapter_url_source, no_quotes)
+
local function format_chapter_title (scriptchapter, chapter, transchapter, chapterurl, chapter_url_source, no_quotes, access)
 
local chapter_error = '';
 
local chapter_error = '';
 
 
Line 601: Line 610:  
else
 
else
 
if false == no_quotes then
 
if false == no_quotes then
chapter = kern_quotes (chapter); -- if necessary, separate chapter title's leading and trailing quote marks from Module provided quote marks
+
chapter = kern_quotes (chapter); -- if necessary, separate chapter title's leading and trailing quote marks from Module provided quote marks
 
chapter = wrap_style ('quoted-title', chapter);
 
chapter = wrap_style ('quoted-title', chapter);
 
end
 
end
Line 607: Line 616:     
chapter = script_concatenate (chapter, scriptchapter) -- <bdi> tags, lang atribute, categorization, etc; must be done after title is wrapped
 
chapter = script_concatenate (chapter, scriptchapter) -- <bdi> tags, lang atribute, categorization, etc; must be done after title is wrapped
 +
 +
if is_set (chapterurl) then
 +
chapter = external_link (chapterurl, chapter, chapter_url_source, access); -- adds bare_url_missing_title error if appropriate
 +
end
    
if is_set (transchapter) then
 
if is_set (transchapter) then
Line 618: Line 631:  
end
 
end
   −
if is_set (chapterurl) then
+
-- if is_set (chapterurl) then
chapter = external_link (chapterurl, chapter, chapter_url_source, nil); -- adds bare_url_missing_title error if appropriate
+
-- chapter = external_link (chapterurl, chapter, chapter_url_source, access); -- adds bare_url_missing_title error if appropriate
end
+
-- end
    
return chapter .. chapter_error;
 
return chapter .. chapter_error;
 
end
 
end
 +
    
--[[--------------------------< H A S _ I N V I S I B L E _ C H A R S >----------------------------------------
 
--[[--------------------------< H A S _ I N V I S I B L E _ C H A R S >----------------------------------------
Line 659: Line 673:  
local pattern=cfg.invisible_chars[i][2] -- the pattern used to find it
 
local pattern=cfg.invisible_chars[i][2] -- the pattern used to find it
 
position, dummy, capture = mw.ustring.find (v, pattern) -- see if the parameter value contains characters that match the pattern
 
position, dummy, capture = mw.ustring.find (v, pattern) -- see if the parameter value contains characters that match the pattern
 +
 +
if position and (char == 'zero width joiner') then -- if we found a zero width joiner character
 +
if mw.ustring.find (v, cfg.indic_script) then -- its ok if one of the indic scripts
 +
position = nil; -- unset position
 +
end
 +
end
 
 
 
if position then
 
if position then
if 'nowiki' == capture or 'math' == capture then -- nowiki, math stripmarker (not an error condition)
+
if 'nowiki' == capture or 'math' == capture or -- nowiki and math stripmarkers (not an error condition)
stripmarker = true; -- set a flag
+
('templatestyles' == capture and in_array (param, {'id', 'quote'})) then -- templatestyles stripmarker allowed in these parameters
 +
stripmarker = true; -- set a flag
 
elseif true == stripmarker and 'delete' == char then -- because stripmakers begin and end with the delete char, assume that we've found one end of a stripmarker
 
elseif true == stripmarker and 'delete' == char then -- because stripmakers begin and end with the delete char, assume that we've found one end of a stripmarker
 
position = nil; -- unset
 
position = nil; -- unset
Line 731: Line 752:  
end
 
end
   −
--[[--------------------------< V A L I D A T E >--------------------------------------------------------------
     −
Looks for a parameter's name in one of several whitelists.
+
--[[--------------------------< N O W R A P _ D A T E >--------------------------------------------------------
 +
 
 +
When date is YYYY-MM-DD format wrap in nowrap span: <span ...>YYYY-MM-DD</span>.  When date is DD MMMM YYYY or is
 +
MMMM DD, YYYY then wrap in nowrap span: <span ...>DD MMMM</span> YYYY or <span ...>MMMM DD,</span> YYYY
 +
 
 +
DOES NOT yet support MMMM YYYY or any of the date ranges.
   −
Parameters in the whitelist can have three values:
  −
true - active, supported parameters
  −
false - deprecated, supported parameters
  −
nil - unsupported parameters
  −
   
]]
 
]]
   −
--local function validate( name )
+
local function nowrap_date (date)
local function validate( name, cite_class )
+
local cap='';
local name = tostring( name );
+
local cap2='';
local state;
+
 
 +
if date:match("^%d%d%d%d%-%d%d%-%d%d$") then
 +
date = substitute (cfg.presentation['nowrap1'], date);
 +
 +
elseif date:match("^%a+%s*%d%d?,%s+%d%d%d%d$") or date:match ("^%d%d?%s*%a+%s+%d%d%d%d$") then
 +
cap, cap2 = string.match (date, "^(.*)%s+(%d%d%d%d)$");
 +
date = substitute (cfg.presentation['nowrap2'], {cap, cap2});
 +
end
 
 
if in_array (cite_class, {'arxiv', 'biorxiv', 'citeseerx'}) then -- limited parameter sets allowed for these templates
+
return date;
state = whitelist.limited_basic_arguments[ name ];
+
end
if true == state then return true; end -- valid actively supported parameter
+
 
if false == state then
  −
deprecated_parameter (name); -- parameter is deprecated but still supported
  −
return true;
  −
end
     −
if 'arxiv' == cite_class then -- basic parameters unique to these templates
+
--[[--------------------------< S E T _ T I T L E T Y P E >----------------------------------------------------
state = whitelist.arxiv_basic_arguments[name];
  −
end
  −
if 'biorxiv' == cite_class then
  −
state = whitelist.biorxiv_basic_arguments[name];
  −
end
  −
if 'citeseerx' == cite_class then
  −
state = whitelist.citeseerx_basic_arguments[name];
  −
end
     −
if true == state then return true; end -- valid actively supported parameter
+
This function sets default title types (equivalent to the citation including |type=<default value>) for those templates that have defaults.
if false == state then
+
Also handles the special case where it is desirable to omit the title type from the rendered citation (|type=none).
deprecated_parameter (name); -- parameter is deprecated but still supported
+
 
return true;
+
]]
 +
 
 +
local function set_titletype (cite_class, title_type)
 +
if is_set(title_type) then
 +
if "none" == title_type then
 +
title_type = ""; -- if |type=none then type parameter not displayed
 
end
 
end
-- limited enumerated parameters list
+
return title_type; -- if |type= has been set to any other value use that value
name = name:gsub( "%d+", "#" ); -- replace digit(s) with # (last25 becomes last#)
  −
state = whitelist.limited_numbered_arguments[ name ];
  −
if true == state then return true; end -- valid actively supported parameter
  −
if false == state then
  −
deprecated_parameter (name); -- parameter is deprecated but still supported
  −
return true;
  −
end
  −
 
  −
return false; -- not supported because not found or name is set to nil
  −
end -- end limited parameter-set templates
  −
  −
state = whitelist.basic_arguments[ name ]; -- all other templates; all normal parameters allowed
  −
  −
if true == state then return true; end -- valid actively supported parameter
  −
if false == state then
  −
deprecated_parameter (name); -- parameter is deprecated but still supported
  −
return true;
   
end
 
end
-- all enumerated parameters allowed
  −
name = name:gsub( "%d+", "#" ); -- replace digit(s) with # (last25 becomes last#
  −
state = whitelist.numbered_arguments[ name ];
     −
if true == state then return true; end -- valid actively supported parameter
+
return cfg.title_types [cite_class] or ''; -- set template's default title type; else empty string for concatenation
if false == state then
  −
deprecated_parameter (name); -- parameter is deprecated but still supported
  −
return true;
  −
end
  −
  −
return false; -- not supported because not found or name is set to nil
   
end
 
end
      −
--[[--------------------------< N O W R A P _ D A T E >--------------------------------------------------------
+
--[[--------------------------< H Y P H E N _ T O _ D A S H >--------------------------------------------------
   −
When date is YYYY-MM-DD format wrap in nowrap span: <span ...>YYYY-MM-DD</span>. When date is DD MMMM YYYY or is
+
Converts a hyphen to a dash under certain conditions.  The hyphen must separate like items; unlike items are
MMMM DD, YYYY then wrap in nowrap span: <span ...>DD MMMM</span> YYYY or <span ...>MMMM DD,</span> YYYY
+
returned unmodified.  These forms are modified:
 +
letter - letter (A - B)
 +
digit - digit (4-5)
 +
digit separator digit - digit separator digit (4.1-4.5 or 4-1-4-5)
 +
letterdigit - letterdigit (A1-A5) (an optional separator between letter and digit is supported – a.1-a.5 or a-1-a-5)
 +
digitletter - digitletter (5a - 5d) (an optional separator between letter and digit is supported – 5.a-5.d or 5-a-5-d)
 +
 
 +
any other forms are returned unmodified.
   −
DOES NOT yet support MMMM YYYY or any of the date ranges.
+
str may be a comma- or semicolon-separated list
    
]]
 
]]
   −
local function nowrap_date (date)
+
local function hyphen_to_dash( str )
local cap='';
+
if not is_set (str) then
local cap2='';
+
return str;
 
+
end
if date:match("^%d%d%d%d%-%d%d%-%d%d$") then
  −
date = substitute (cfg.presentation['nowrap1'], date);
   
 
elseif date:match("^%a+%s*%d%d?,%s+%d%d%d%d$") or date:match ("^%d%d?%s*%a+%s+%d%d%d%d$") then
+
str, count = str:gsub ('^%(%((.+)%)%)$', '%1'); -- remove accept-this-as-written markup when it wraps all of str
cap, cap2 = string.match (date, "^(.*)%s+(%d%d%d%d)$");
+
if 0 ~= count then -- non-zero when markup removed; zero else
date = substitute (cfg.presentation['nowrap2'], {cap, cap2});
+
return str; -- nothing to do, we're done
 
end
 
end
 
 
return date;
+
local out = {};
end
+
local list = mw.text.split (str, '%s*[,;]%s*'); -- split str at comma or semicolon separators if there are any
   −
--[[--------------------------< S E T _ T I T L E T Y P E >----------------------------------------------------
+
for _, item in ipairs (list) do -- for each item in the list
 
+
if mw.ustring.match (item, '^%w*[%.%-]?%w+%s*[%-–—]%s*%w*[%.%-]?%w+$') then -- if a hyphenated range or has endash or emdash separators
This function sets default title types (equivalent to the citation including |type=<default value>) for those templates that have defaults.
+
if item:match ('%a+[%.%-]?%d+%s*%-%s*%a+[%.%-]?%d+') or -- letterdigit hyphen letterdigit (optional separator between letter and digit)
Also handles the special case where it is desirable to omit the title type from the rendered citation (|type=none).
+
item:match ('%d+[%.%-]?%a+%s*%-%s*%d+[%.%-]?%a+') or -- digitletter hyphen digitletter (optional separator between digit and letter)
 
+
item:match ('%d+[%.%-]%d+%s*%-%s*%d+[%.%-]%d+') or -- digit separator digit hyphen digit separator digit
]]
+
item:match ('%d+%s*%-%s*%d+') or -- digit hyphen digit
 
+
item:match ('%a+%s*%-%s*%a+') then -- letter hyphen letter
local function set_titletype (cite_class, title_type)
+
item = item:gsub ('(%w*[%.%-]?%w+)%s*%-%s*(%w*[%.%-]?%w+)', '%1–%2'); -- replace hyphen, remove extraneous space characters
if is_set(title_type) then
+
else
if "none" == title_type then
+
item = mw.ustring.gsub (item, '%s*[–—]%s*', '–'); -- for endash or emdash separated ranges, replace em with en, remove extraneous white space
title_type = ""; -- if |type=none then type parameter not displayed
+
end
 
end
 
end
return title_type; -- if |type= has been set to any other value use that value
+
item = item:gsub ('^%(%((.+)%)%)$', '%1'); -- remove the accept-this-as-written markup
 +
table.insert (out, item); -- add the (possibly modified) item to the output table
 
end
 
end
   −
return cfg.title_types [cite_class] or ''; -- set template's default title type; else empty string for concatenation
+
return table.concat (out, ', '); -- concatenate the output table into a comma separated string
 
end
 
end
      −
--[[--------------------------< H Y P H E N _ T O _ D A S H >--------------------------------------------------
+
--[[--------------------------< S A F E _ J O I N >------------------------------------------------------------
   −
Converts a hyphen to a dash
+
Joins a sequence of strings together while checking for duplicate separation characters.
    
]]
 
]]
   −
local function hyphen_to_dash( str )
+
local function safe_join( tbl, duplicate_char )
if not is_set(str) or str:match( "[%[%]{}<>]" ) ~= nil then
+
local f = {}; -- create a function table appropriate to type of 'dupicate character'
return str;
+
if 1 == #duplicate_char then -- for single byte ascii characters use the string library functions
end
+
f.gsub=string.gsub
return str:gsub( '-', '–' );
+
f.match=string.match
end
+
f.sub=string.sub
 
+
else -- for multi-byte characters use the ustring library functions
 
+
f.gsub=mw.ustring.gsub
--[[--------------------------< S A F E _ J O I N >------------------------------------------------------------
+
f.match=mw.ustring.match
 +
f.sub=mw.ustring.sub
 +
end
   −
Joins a sequence of strings together while checking for duplicate separation characters.
  −
  −
]]
  −
  −
local function safe_join( tbl, duplicate_char )
  −
--[[
  −
Note: we use string functions here, rather than ustring functions.
  −
  −
This has considerably faster performance and should work correctly as
  −
long as the duplicate_char is strict ASCII.  The strings
  −
in tbl may be ASCII or UTF8.
  −
]]
  −
   
local str = ''; -- the output string
 
local str = ''; -- the output string
 
local comp = ''; -- what does 'comp' mean?
 
local comp = ''; -- what does 'comp' mean?
Line 891: Line 880:  
end
 
end
 
-- typically duplicate_char is sepc
 
-- typically duplicate_char is sepc
if comp:sub(1,1) == duplicate_char then -- is first charactier same as duplicate_char? why test first character?
+
if f.sub(comp, 1,1) == duplicate_char then -- is first character same as duplicate_char? why test first character?
--  Because individual string segments often (always?) begin with terminal punct for th
+
--  Because individual string segments often (always?) begin with terminal punct for the
 
--  preceding segment: 'First element' .. 'sepc next element' .. etc?
 
--  preceding segment: 'First element' .. 'sepc next element' .. etc?
 
trim = false;
 
trim = false;
end_chr = str:sub(-1,-1); -- get the last character of the output string
+
end_chr = f.sub(str, -1,-1); -- get the last character of the output string
 
-- str = str .. "<HERE(enchr=" .. end_chr.. ")" -- debug stuff?
 
-- str = str .. "<HERE(enchr=" .. end_chr.. ")" -- debug stuff?
 
if end_chr == duplicate_char then -- if same as separator
 
if end_chr == duplicate_char then -- if same as separator
str = str:sub(1,-2); -- remove it
+
str = f.sub(str, 1,-2); -- remove it
 
elseif end_chr == "'" then -- if it might be wikimarkup
 
elseif end_chr == "'" then -- if it might be wikimarkup
if str:sub(-3,-1) == duplicate_char .. "''" then -- if last three chars of str are sepc''  
+
if f.sub(str, -3,-1) == duplicate_char .. "''" then -- if last three chars of str are sepc''  
str = str:sub(1, -4) .. "''"; -- remove them and add back ''
+
str = f.sub(str, 1, -4) .. "''"; -- remove them and add back ''
elseif str:sub(-5,-1) == duplicate_char .. "]]''" then -- if last five chars of str are sepc]]''  
+
elseif f.sub(str, -5,-1) == duplicate_char .. "]]''" then -- if last five chars of str are sepc]]''  
 
trim = true; -- why? why do this and next differently from previous?
 
trim = true; -- why? why do this and next differently from previous?
elseif str:sub(-4,-1) == duplicate_char .. "]''" then -- if last four chars of str are sepc]''  
+
elseif f.sub(str, -4,-1) == duplicate_char .. "]''" then -- if last four chars of str are sepc]''  
 
trim = true; -- same question
 
trim = true; -- same question
 
end
 
end
 
elseif end_chr == "]" then -- if it might be wikimarkup
 
elseif end_chr == "]" then -- if it might be wikimarkup
if str:sub(-3,-1) == duplicate_char .. "]]" then -- if last three chars of str are sepc]] wikilink  
+
if f.sub(str, -3,-1) == duplicate_char .. "]]" then -- if last three chars of str are sepc]] wikilink  
 +
trim = true;
 +
elseif f.sub(str, -3,-1) == duplicate_char .. '"]' then -- if last three chars of str are sepc"] quoted external link
 
trim = true;
 
trim = true;
elseif str:sub(-2,-1) == duplicate_char .. "]" then -- if last two chars of str are sepc] external link
+
elseif f.sub(str, -2,-1) == duplicate_char .. "]" then -- if last two chars of str are sepc] external link
 
trim = true;
 
trim = true;
elseif str:sub(-4,-1) == duplicate_char .. "'']" then -- normal case when |url=something & |title=Title.
+
elseif f.sub(str, -4,-1) == duplicate_char .. "'']" then -- normal case when |url=something & |title=Title.
 
trim = true;
 
trim = true;
 
end
 
end
 
elseif end_chr == " " then -- if last char of output string is a space
 
elseif end_chr == " " then -- if last char of output string is a space
if str:sub(-2,-1) == duplicate_char .. " " then -- if last two chars of str are <sepc><space>
+
if f.sub(str, -2,-1) == duplicate_char .. " " then -- if last two chars of str are <sepc><space>
str = str:sub(1,-3); -- remove them both
+
str = f.sub(str, 1,-3); -- remove them both
 
end
 
end
 
end
 
end
Line 924: Line 915:  
if value ~= comp then -- value does not equal comp when value contains html markup
 
if value ~= comp then -- value does not equal comp when value contains html markup
 
local dup2 = duplicate_char;
 
local dup2 = duplicate_char;
if dup2:match( "%A" ) then dup2 = "%" .. dup2; end -- if duplicate_char not a letter then escape it
+
if f.match(dup2, "%A" ) then dup2 = "%" .. dup2; end -- if duplicate_char not a letter then escape it
 
 
value = value:gsub( "(%b<>)" .. dup2, "%1", 1 ) -- remove duplicate_char if it follows html markup
+
value = f.gsub(value, "(%b<>)" .. dup2, "%1", 1 ) -- remove duplicate_char if it follows html markup
 
else
 
else
value = value:sub( 2, -1 ); -- remove duplicate_char when it is first character
+
value = f.sub(value, 2, -1 ); -- remove duplicate_char when it is first character
 
end
 
end
 
end
 
end
Line 936: Line 927:  
end
 
end
 
return str;
 
return str;
end
+
end
      Line 951: Line 942:  
return false;
 
return false;
 
end
 
end
 +
    
--[[--------------------------< I S _ G O O D _ V A N C _ N A M E >--------------------------------------------
 
--[[--------------------------< I S _ G O O D _ V A N C _ N A M E >--------------------------------------------
Line 997: Line 989:  
return true;
 
return true;
 
end
 
end
 +
    
--[[--------------------------< R E D U C E _ T O _ I N I T I A L S >------------------------------------------
 
--[[--------------------------< R E D U C E _ T O _ I N I T I A L S >------------------------------------------
Line 1,055: Line 1,048:  
return table.concat(initials) -- Vancouver format does not include spaces.
 
return table.concat(initials) -- Vancouver format does not include spaces.
 
end
 
end
 +
    
--[[--------------------------< L I S T  _ P E O P L E >-------------------------------------------------------
 
--[[--------------------------< L I S T  _ P E O P L E >-------------------------------------------------------
Line 1,071: Line 1,065:     
if 'vanc' == format then -- Vancouver-like author/editor name styling?
 
if 'vanc' == format then -- Vancouver-like author/editor name styling?
sep = ','; -- name-list separator between authors is a comma
+
sep = cfg.presentation['sep_nl_vanc']; -- name-list separator between authors is a comma
namesep = ' '; -- last/first separator is a space
+
namesep = cfg.presentation['sep_name_vanc']; -- last/first separator is a space
elseif 'mla' == control.mode then
  −
sep = ','; -- name-list separator between authors is a comma
  −
namesep = ', ' -- last/first separator is <comma><space>
   
else
 
else
sep = ';' -- name-list separator between authors is a semicolon
+
sep = cfg.presentation['sep_nl']; -- name-list separator between authors is a semicolon
namesep = ', ' -- last/first separator is <comma><space>
+
namesep = cfg.presentation['sep_name']; -- last/first separator is <comma><space>
 
end
 
end
 
 
Line 1,104: Line 1,095:  
local first = person.first
 
local first = person.first
 
if is_set(first) then
 
if is_set(first) then
if 'mla' == control.mode then
+
if ( "vanc" == format ) then -- if vancouver format
if i == 1 then -- for mla
+
one = one:gsub ('%.', ''); -- remove periods from surnames (http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35029/)
one = one .. namesep .. first; -- first name last, first
+
if not person.corporate and is_good_vanc_name (one, first) then -- and name is all Latin characters; corporate authors not tested
else -- all other names
+
first = reduce_to_initials(first) -- attempt to convert first name(s) to initials
one = first .. ' ' .. one; -- first last
  −
end
  −
else
  −
if ( "vanc" == format ) then -- if vancouver format
  −
one = one:gsub ('%.', ''); -- remove periods from surnames (http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35029/)
  −
if not person.corporate and is_good_vanc_name (one, first) then -- and name is all Latin characters; corporate authors not tested
  −
first = reduce_to_initials(first) -- attempt to convert first name(s) to initials
  −
end
   
end
 
end
one = one .. namesep .. first;
   
end
 
end
 +
one = one .. namesep .. first;
 
end
 
end
 
if is_set(person.link) and person.link ~= control.page_name then
 
if is_set(person.link) and person.link ~= control.page_name then
one = "[[" .. person.link .. "|" .. one .. "]]" -- link author/editor if this page is not the author's/editor's page
+
one = make_wikilink (person.link, one); -- link author/editor if this page is not the author's/editor's page
 
end
 
end
 
end
 
end
Line 1,132: Line 1,115:  
if count > 0 then  
 
if count > 0 then  
 
if count > 1 and is_set(lastauthoramp) and not etal then
 
if count > 1 and is_set(lastauthoramp) and not etal then
if 'mla' == control.mode then
+
text[#text-2] = " & "; -- replace last separator with ampersand text
text[#text-2] = ", and "; -- replace last separator with ', and ' text
  −
else
  −
text[#text-2] = " & "; -- replace last separator with ampersand text
  −
end
   
end
 
end
 
text[#text] = nil; -- erase the last separator
 
text[#text] = nil; -- erase the last separator
Line 1,148: Line 1,127:  
return result, count
 
return result, count
 
end
 
end
 +
    
--[[--------------------------< A N C H O R _ I D >------------------------------------------------------------
 
--[[--------------------------< A N C H O R _ I D >------------------------------------------------------------
Line 1,227: Line 1,207:  
'^[%(%[]?%s*[Ee][Dd][Ii][Tt][Oo][Rr][Ss]?%A', -- (editor or (editors: also sq brackets, case insensitive, optional brackets, 's'
 
'^[%(%[]?%s*[Ee][Dd][Ii][Tt][Oo][Rr][Ss]?%A', -- (editor or (editors: also sq brackets, case insensitive, optional brackets, 's'
 
'^[%(%[]?%s*[Ee][Dd][Ii][Tt][Ee][Dd]%A', -- (edited: also sq brackets, case insensitive, optional brackets
 
'^[%(%[]?%s*[Ee][Dd][Ii][Tt][Ee][Dd]%A', -- (edited: also sq brackets, case insensitive, optional brackets
   
}
 
}
   Line 1,349: Line 1,328:  
return names, etal; -- all done, return our list of names
 
return names, etal; -- all done, return our list of names
 
end
 
end
 +
    
--[[--------------------------< G E T _ I S O 6 3 9 _ C O D E >------------------------------------------------
 
--[[--------------------------< G E T _ I S O 6 3 9 _ C O D E >------------------------------------------------
Line 1,359: Line 1,339:  
return the original language name string.
 
return the original language name string.
   −
mw.language.fetchLanguageNames(<local wiki language>, 'all') return a list of languages that in some cases may include
+
mw.language.fetchLanguageNames(<local wiki language>, 'all') returns a list of languages that in some cases may include
 
extensions. For example, code 'cbk-zam' and its associated name 'Chavacano de Zamboanga' (MediaWiki does not support
 
extensions. For example, code 'cbk-zam' and its associated name 'Chavacano de Zamboanga' (MediaWiki does not support
code 'cbk' or name 'Chavacano'.
+
code 'cbk' or name 'Chavacano'. Most (all?) of these languages are not used a 'language' codes per se, rather they
 +
are used as sub-domain names: cbk-zam.wikipedia.org.  These names can be found (for the time being) at
 +
https://phabricator.wikimedia.org/diffusion/ECLD/browse/master/LocalNames/LocalNamesEn.php
    
Names but that are included in the list will be found if that name is provided in the |language= parameter.  For example,
 
Names but that are included in the list will be found if that name is provided in the |language= parameter.  For example,
Line 1,372: Line 1,354:     
local function get_iso639_code (lang, this_wiki_code)
 
local function get_iso639_code (lang, this_wiki_code)
if 'bangla' == lang:lower() then -- special case related to Wikimedia remap of code 'bn' at mw:Extension:CLDR
+
if cfg.lang_name_remap[lang:lower()] then -- if there is a remapped name (because MediaWiki uses something that we don't think is correct)
return 'Bengali', 'bn'; -- make sure rendered version is properly capitalized
+
return cfg.lang_name_remap[lang:lower()][1], cfg.lang_name_remap[lang:lower()][2]; -- for this language 'name', return a possibly new name and appropriate code
 
end
 
end
   Line 1,428: Line 1,410:  
if 2 == lang:len() or 3 == lang:len() then -- if two-or three-character code
 
if 2 == lang:len() or 3 == lang:len() then -- if two-or three-character code
 
name = mw.language.fetchLanguageName( lang:lower(), this_wiki_code); -- get language name if |language= is a proper code
 
name = mw.language.fetchLanguageName( lang:lower(), this_wiki_code); -- get language name if |language= is a proper code
 +
if not is_set (name) then
 +
name = cfg.lang_code_remap[lang]; -- not supported by MediaWiki; is it in remap?
 +
end
 
end
 
end
 
 
Line 1,437: Line 1,422:  
 
 
if is_set (code) then -- only 2- or 3-character codes
 
if is_set (code) then -- only 2- or 3-character codes
if 'bn' == code then name = 'Bengali' end; -- override wikimedia when code is 'bn'
+
name = cfg.lang_code_remap[code] or name; -- override wikimedia when they misuse language codes/names
 +
 
 
if this_wiki_code ~= code then -- when the language is not the same as this wiki's language
 
if this_wiki_code ~= code then -- when the language is not the same as this wiki's language
 
if 2 == code:len() then -- and is a two-character code
 
if 2 == code:len() then -- and is a two-character code
add_prop_cat ('foreign_lang_source', {name, code}) -- categorize it
+
add_prop_cat ('foreign_lang_source' .. code, {name, code}) -- categorize it
 
else -- or is a recognized language (but has a three-character code)
 
else -- or is a recognized language (but has a three-character code)
add_prop_cat ('foreign_lang_source_2', {code}) -- categorize it differently TODO: support mutliple three-character code categories per cs1|2 template
+
add_prop_cat ('foreign_lang_source_2' .. code, {code}) -- categorize it differently TODO: support mutliple three-character code categories per cs1|2 template
 
end
 
end
 
end
 
end
Line 1,468: Line 1,454:  
]]
 
]]
 
end
 
end
 +
    
--[[--------------------------< S E T _ C S 1 _ S T Y L E >----------------------------------------------------
 
--[[--------------------------< S E T _ C S 1 _ S T Y L E >----------------------------------------------------
    
Set style settings for CS1 citation templates. Returns separator and postscript settings
 
Set style settings for CS1 citation templates. Returns separator and postscript settings
 +
At en.wiki, for cs1:
 +
ps gets: '.'
 +
sep gets: '.'
    
]]
 
]]
Line 1,477: Line 1,467:  
local function set_cs1_style (ps)
 
local function set_cs1_style (ps)
 
if not is_set (ps) then -- unless explicitely set to something
 
if not is_set (ps) then -- unless explicitely set to something
ps = '.'; -- terminate the rendered citation with a period
+
ps = cfg.presentation['ps_cs1']; -- terminate the rendered citation
 
end
 
end
return '.', ps; -- separator is a full stop
+
return cfg.presentation['sep_cs1'], ps; -- element separator
 
end
 
end
 +
    
--[[--------------------------< S E T _ C S 2 _ S T Y L E >----------------------------------------------------
 
--[[--------------------------< S E T _ C S 2 _ S T Y L E >----------------------------------------------------
    
Set style settings for CS2 citation templates. Returns separator, postscript, ref settings
 
Set style settings for CS2 citation templates. Returns separator, postscript, ref settings
 +
At en.wiki, for cs2:
 +
ps gets: '' (empty string - no terminal punctuation)
 +
sep gets: ','
    
]]
 
]]
Line 1,490: Line 1,484:  
local function set_cs2_style (ps, ref)
 
local function set_cs2_style (ps, ref)
 
if not is_set (ps) then -- if |postscript= has not been set, set cs2 default
 
if not is_set (ps) then -- if |postscript= has not been set, set cs2 default
ps = ''; -- make sure it isn't nil
+
ps = cfg.presentation['ps_cs2']; -- terminate the rendered citation
 
end
 
end
 
if not is_set (ref) then -- if |ref= is not set
 
if not is_set (ref) then -- if |ref= is not set
 
ref = "harv"; -- set default |ref=harv
 
ref = "harv"; -- set default |ref=harv
 
end
 
end
return ',', ps, ref; -- separator is a comma
+
return cfg.presentation['sep_cs2'], ps, ref; -- element separator
 
end
 
end
 +
    
--[[--------------------------< G E T _ S E T T I N G S _ F R O M _ C I T E _ C L A S S >----------------------
 
--[[--------------------------< G E T _ S E T T I N G S _ F R O M _ C I T E _ C L A S S >----------------------
Line 1,515: Line 1,510:  
return sep, ps, ref -- return them all
 
return sep, ps, ref -- return them all
 
end
 
end
 +
    
--[[--------------------------< S E T _ S T Y L E >------------------------------------------------------------
 
--[[--------------------------< S E T _ S T Y L E >------------------------------------------------------------
Line 1,528: Line 1,524:  
sep, ps, ref = set_cs2_style (ps, ref);
 
sep, ps, ref = set_cs2_style (ps, ref);
 
elseif 'cs1' == mode then -- if this template is to be rendered in CS1 (cite xxx) style
 
elseif 'cs1' == mode then -- if this template is to be rendered in CS1 (cite xxx) style
sep, ps = set_cs1_style (ps);
  −
elseif 'mla' == mode then -- if this template is to be rendered in mla style use cs1 for bot cs1 & cs2 templates
   
sep, ps = set_cs1_style (ps);
 
sep, ps = set_cs1_style (ps);
 
else -- anything but cs1 or cs2
 
else -- anything but cs1 or cs2
Line 1,540: Line 1,534:  
return sep, ps, ref
 
return sep, ps, ref
 
end
 
end
 +
    
--[=[-------------------------< I S _ P D F >------------------------------------------------------------------
 
--[=[-------------------------< I S _ P D F >------------------------------------------------------------------
Line 1,546: Line 1,541:  
applying the pdf icon to external links.
 
applying the pdf icon to external links.
   −
returns true if file extension is one of the recognized extension, else false
+
returns true if file extension is one of the recognized extensions, else false
    
]=]
 
]=]
    
local function is_pdf (url)
 
local function is_pdf (url)
return url:match ('%.pdf[%?#]?') or url:match ('%.PDF[%?#]?');
+
return url:match ('%.pdf$') or url:match ('%.PDF$') or url:match ('%.pdf[%?#]') or url:match ('%.PDF[%?#]');
 
end
 
end
 +
    
--[[--------------------------< S T Y L E _ F O R M A T >------------------------------------------------------
 
--[[--------------------------< S T Y L E _ F O R M A T >------------------------------------------------------
Line 1,565: Line 1,561:  
local function style_format (format, url, fmt_param, url_param)
 
local function style_format (format, url, fmt_param, url_param)
 
if is_set (format) then
 
if is_set (format) then
format = wrap_style ('format', format); -- add leading space, parenthases, resize
+
format = wrap_style ('format', format); -- add leading space, parentheses, resize
 
if not is_set (url) then
 
if not is_set (url) then
 
format = format .. set_error( 'format_missing_url', {fmt_param, url_param} ); -- add an error message
 
format = format .. set_error( 'format_missing_url', {fmt_param, url_param} ); -- add an error message
Line 1,576: Line 1,572:  
return format;
 
return format;
 
end
 
end
 +
    
--[[--------------------------< G E T _ D I S P L A Y _ A U T H O R S _ E D I T O R S >------------------------
 
--[[--------------------------< G E T _ D I S P L A Y _ A U T H O R S _ E D I T O R S >------------------------
Line 1,618: Line 1,615:  
return max, etal;
 
return max, etal;
 
end
 
end
 +
    
--[[--------------------------< E X T R A _ T E X T _ I N _ P A G E _ C H E C K >------------------------------
 
--[[--------------------------< E X T R A _ T E X T _ I N _ P A G E _ C H E C K >------------------------------
Line 1,631: Line 1,629:     
local function extra_text_in_page_check (page)
 
local function extra_text_in_page_check (page)
-- local good_pattern = '^P[^%.P%l]';
   
local good_pattern = '^P[^%.Pp]'; -- ok to begin with uppercase P: P7 (pg 7 of section P) but not p123 (page 123) TODO: add Gg for PG or Pg?
 
local good_pattern = '^P[^%.Pp]'; -- ok to begin with uppercase P: P7 (pg 7 of section P) but not p123 (page 123) TODO: add Gg for PG or Pg?
-- local bad_pattern = '^[Pp][Pp]';
   
local bad_pattern = '^[Pp]?[Pp]%.?[ %d]';
 
local bad_pattern = '^[Pp]?[Pp]%.?[ %d]';
   Line 1,639: Line 1,635:  
add_maint_cat ('extra_text');
 
add_maint_cat ('extra_text');
 
end
 
end
-- if Page:match ('^[Pp]?[Pp]%.?[ %d]') or  Page:match ('^[Pp]ages?[ %d]') or
  −
-- Pages:match ('^[Pp]?[Pp]%.?[ %d]') or  Pages:match ('^[Pp]ages?[ %d]') then
  −
-- add_maint_cat ('extra_text');
  −
-- end
   
end
 
end
   −
--[[--------------------------< G E T _ V _ N A M E _ T A B L E >----------------------------------------------
     −
split apart a |vautthors= or |veditors= parameter.  This function allows for corporate names, wrapped in doubled
+
--[=[-------------------------< G E T _ V _ N A M E _ T A B L E >----------------------------------------------
 +
 
 +
split apart a |vauthors= or |veditors= parameter.  This function allows for corporate names, wrapped in doubled
 
parentheses to also have commas; in the old version of the code, the doubled parnetheses were included in the
 
parentheses to also have commas; in the old version of the code, the doubled parnetheses were included in the
rendered citation and in the metadata.
+
rendered citation and in the metadata. Individual author names may be wikilinked
   −
|vauthors=Jones AB, White EB, ((Black, Brown, and Co.))
+
|vauthors=Jones AB, [[E. B. White|White EB]], ((Black, Brown, and Co.))
   −
This code is experimental and may not be retained.
+
]=]
   −
]]
+
local function get_v_name_table (vparam, output_table, output_link_table)
local function get_v_name_table (vparam, output_table)
   
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 place holder
 
 
 
local i = 1;
 
local i = 1;
Line 1,673: Line 1,666:  
end
 
end
 
table.insert (output_table, name); -- and add corporate name to the output table
 
table.insert (output_table, name); -- and add corporate name to the output table
 +
table.insert (output_link_table, ''); -- no wikilink
 
else
 
else
table.insert (output_table, name_table[i]); -- add this name
+
wl_type, label, link = is_wikilink (name_table[i]); -- wl_type is: 0, no wl (text in label variable); 1, [[D]]; 2, [[L|D]]
 +
table.insert (output_table, label); -- add this name
 +
if 1 == wl_type then
 +
table.insert (output_link_table, label); -- simple wikilink [[D]]
 +
else
 +
table.insert (output_link_table, link); -- no wikilink or [[L|D]]; add this link if there is one, else empty string
 +
end
 
end
 
end
 
i = i+1;
 
i = i+1;
Line 1,680: Line 1,680:  
return output_table;
 
return output_table;
 
end
 
end
 +
    
--[[--------------------------< P A R S E _ V A U T H O R S _ V E D I T O R S >--------------------------------
 
--[[--------------------------< P A R S E _ V A U T H O R S _ V E D I T O R S >--------------------------------
Line 1,699: Line 1,700:  
local names = {}; -- table of names assembled from |vauthors=, |author-maskn=, |author-linkn=
 
local names = {}; -- table of names assembled from |vauthors=, |author-maskn=, |author-linkn=
 
local v_name_table = {};
 
local v_name_table = {};
 +
local v_link_table = {}; -- when name is wikilinked, targets go in this table
 
local etal = false; -- return value set to true when we find some form of et al. vauthors parameter
 
local etal = false; -- return value set to true when we find some form of et al. vauthors parameter
 
local last, first, link, mask, suffix;
 
local last, first, link, mask, suffix;
Line 1,704: Line 1,706:     
vparam, etal = name_has_etal (vparam, etal, true); -- find and remove variations on et al. do not categorize (do it here because et al. might have a period)
 
vparam, etal = name_has_etal (vparam, etal, true); -- find and remove variations on et al. do not categorize (do it here because et al. might have a period)
if vparam:find ('%[%[') or vparam:find ('%]%]') then -- no wikilinking vauthors names
+
v_name_table = get_v_name_table (vparam, v_name_table, v_link_table); -- names are separated by commas
add_vanc_error ('wikilink');
  −
end
  −
v_name_table = get_v_name_table (vparam, v_name_table); -- names are separated by commas
      
for i, v_name in ipairs(v_name_table) do
 
for i, v_name in ipairs(v_name_table) do
Line 1,751: Line 1,750:  
end
 
end
 
end
 
end
-- this from extract_names ()
+
 
link = select_one( args, cfg.aliases[list_name .. '-Link'], 'redundant_parameters', i );
+
link = select_one( args, cfg.aliases[list_name .. '-Link'], 'redundant_parameters', i ) or v_link_table[i];
 
mask = select_one( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i );
 
mask = select_one( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i );
 
names[i] = {last = last, first = first, link = link, mask = mask, corporate=corporate}; -- add this assembled name to our names list
 
names[i] = {last = last, first = first, link = link, mask = mask, corporate=corporate}; -- add this assembled name to our names list
Line 1,811: Line 1,810:  
This function is used to validate a parameter's assigned value for those parameters that have only a limited number
 
This function is used to validate a parameter's assigned value for those parameters that have only a limited number
 
of allowable values (yes, y, true, no, etc).  When the parameter value has not been assigned a value (missing or empty
 
of allowable values (yes, y, true, no, etc).  When the parameter value has not been assigned a value (missing or empty
in the source template) the function refurns true.  If the parameter value is one of the list of allowed values returns
+
in the source template) the function returns true.  If the parameter value is one of the list of allowed values returns
 
true; else, emits an error message and returns false.
 
true; else, emits an error message and returns false.
    
]]
 
]]
   −
local function is_valid_parameter_value (value, name, possible, cite_class)
+
local function is_valid_parameter_value (value, name, possible)
-- begin hack to limit |mode=mla to a specific set of templates
  −
if ('mode' == name) and ('mla' == value) and not in_array (cite_class, {'book', 'journal', 'news'}) then
  −
table.insert( z.message_tail, { set_error( 'invalid_param_val', {name, value}, true ) } ); -- not an allowed value so add error message
  −
return false
  −
end
  −
-- end hack
  −
 
   
if not is_set (value) then
 
if not is_set (value) then
 
return true; -- an empty parameter is ok
 
return true; -- an empty parameter is ok
Line 1,862: Line 1,854:  
]]
 
]]
 
 
local function format_volume_issue (volume, issue, cite_class, origin, sepc, lower, mode)
+
local function format_volume_issue (volume, issue, cite_class, origin, sepc, lower)
 
if not is_set (volume) and not is_set (issue) then
 
if not is_set (volume) and not is_set (issue) then
 
return '';
 
return '';
 
end
 
end
 
 
if ('mla' == mode) and ('journal' == cite_class) then -- same as cs1 for magazines
  −
lower = true; -- mla 8th edition; force these to lower case
  −
if is_set (volume) and is_set (issue) then
  −
return wrap_msg ('vol-no', {sepc, volume, issue}, lower);
  −
elseif is_set (volume) then
  −
return wrap_msg ('vol', {sepc, volume}, lower);
  −
else
  −
return '';
  −
end
  −
end
  −
   
if 'magazine' == cite_class or (in_array (cite_class, {'citation', 'map'}) and 'magazine' == origin) then
 
if 'magazine' == cite_class or (in_array (cite_class, {'citation', 'map'}) and 'magazine' == origin) then
 
if is_set (volume) and is_set (issue) then
 
if is_set (volume) and is_set (issue) then
Line 1,902: Line 1,883:  
return vol;
 
return vol;
 
end
 
end
  −
  −
--[[-------------------------< N O R M A L I Z E _ P A G E _ L I S T >-----------------------------------------
  −
  −
not currently used
  −
  −
normalizes a comma, ampersand, and/or space separated list to be '<value>, <value>, ..., <value>'
  −
returns list unchanged if there are no commas else strips whitespace and then reformats the list
  −
  −
]]
  −
--[[
  −
local function normalize_page_list (list)
  −
if not list:find ('[,& ]') then return list end -- if list is not delimited with commas, ampersands, or spaces; done
  −
  −
list = mw.text.split (list, '[,&%s]+'); -- make a table of values
  −
list = table.concat (list, ', '); -- and now make a normalized list
  −
return list;
  −
end
  −
]]
        Line 1,933: Line 1,895:  
]]
 
]]
   −
local function format_pages_sheets (page, pages, sheet, sheets, cite_class, origin, sepc, nopp, lower, mode)
+
local function format_pages_sheets (page, pages, sheet, sheets, cite_class, origin, sepc, nopp, lower)
 
if 'map' == cite_class then -- only cite map supports sheet(s) as in-source locators
 
if 'map' == cite_class then -- only cite map supports sheet(s) as in-source locators
 
if is_set (sheet) then
 
if is_set (sheet) then
Line 1,950: Line 1,912:  
end
 
end
   −
local is_journal = 'journal' == cite_class or (in_array (cite_class, {'citation', 'map'}) and 'journal' == origin);
+
local is_journal = 'journal' == cite_class or (in_array (cite_class, {'citation', 'map', 'interview'}) and 'journal' == origin);
 
 
if is_journal and 'mla' == mode then
  −
is_journal = false; -- mla always uses p & pp
  −
end
  −
   
if is_set (page) then
 
if is_set (page) then
 
if is_journal then
 
if is_journal then
Line 2,054: Line 2,012:       −
--[[--------------------------< M I S S I N G _ P I P E _ C H E C K >------------------------------------------
+
--[[--------------------------< C I T A T I O N 0 >------------------------------------------------------------
   −
Look at the contents of a parameter. If the content has a string of characters and digits followed by an equal
+
This is the main function doing the majority of the citation formatting.
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:
  −
{{cite ... |title=Title access-date=2016-03-17}}
  −
 
  −
cs1|2 shares some parameter names with xml/html atributes: class=, title=, etc.  To prevent false positives xml/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 (value)
  −
local capture;
  −
value = value:gsub ('%b<>', ''); -- remove xml/html tags because attributes: class=, title=, etc
  −
 
  −
capture = value:match ('%s+(%a[%a%d]+)%s*=') or value:match ('^(%a[%a%d]+)%s*='); -- find and categorize parameters with possible missing pipes
  −
if capture and validate (capture) then -- if the capture is a valid parameter name
  −
add_maint_cat ('missing_pipe');
  −
end
  −
end
  −
 
  −
 
  −
--[[--------------------------< C I T A T I O N 0 >------------------------------------------------------------
  −
 
  −
This is the main function doing the majority of the citation formatting.
      
]]
 
]]
Line 2,096: Line 2,029:  
-- define different field names for the same underlying things.
 
-- define different field names for the same underlying things.
   −
-- set default parameter values defined by |mode= parameter. If |mode= is empty or omitted, use CitationClass to set these values
+
-- set default parameter values defined by |mode= parameter.
 
local Mode = A['Mode'];
 
local Mode = A['Mode'];
if not is_valid_parameter_value (Mode, 'mode', cfg.keywords['mode'], config.CitationClass) then
+
if not is_valid_parameter_value (Mode, 'mode', cfg.keywords['mode']) then
 
Mode = '';
 
Mode = '';
 
end
 
end
Line 2,148: Line 2,081:  
local Translators; -- assembled translators name list
 
local Translators; -- assembled translators name list
 
t = extract_names (args, 'TranslatorList'); -- fetch translator list from |translatorn= / |translator-lastn=, -firstn=, -linkn=, -maskn=
 
t = extract_names (args, 'TranslatorList'); -- fetch translator list from |translatorn= / |translator-lastn=, -firstn=, -linkn=, -maskn=
      
local interviewers_list = {};
 
local interviewers_list = {};
Line 2,236: Line 2,168:  
local Pages;
 
local Pages;
 
local At;
 
local At;
-- previously conference books did not support volume
+
 
-- if in_array (config.CitationClass, cfg.templates_using_volume) and not ('conference' == config.CitationClass and not is_set (Periodical)) then
   
if in_array (config.CitationClass, cfg.templates_using_volume) then
 
if in_array (config.CitationClass, cfg.templates_using_volume) then
 
Volume = A['Volume'];
 
Volume = A['Volume'];
Line 2,243: Line 2,174:  
-- conference & map books do not support issue
 
-- conference & map books do not support issue
 
if in_array (config.CitationClass, cfg.templates_using_issue) and not (in_array (config.CitationClass, {'conference', 'map'}) and not is_set (Periodical))then
 
if in_array (config.CitationClass, cfg.templates_using_issue) and not (in_array (config.CitationClass, {'conference', 'map'}) and not is_set (Periodical))then
Issue = A['Issue'];
+
Issue = hyphen_to_dash (A['Issue']);
 
end
 
end
 
local Position = '';
 
local Position = '';
 
if not in_array (config.CitationClass, cfg.templates_not_using_page) then
 
if not in_array (config.CitationClass, cfg.templates_not_using_page) then
 
Page = A['Page'];
 
Page = A['Page'];
Pages = hyphen_to_dash( A['Pages'] );
+
Pages = hyphen_to_dash (A['Pages']);
 
At = A['At'];
 
At = A['At'];
 
end
 
end
Line 2,261: Line 2,192:  
RegistrationRequired=nil;
 
RegistrationRequired=nil;
 
end
 
end
 +
 
local SubscriptionRequired = A['SubscriptionRequired'];
 
local SubscriptionRequired = A['SubscriptionRequired'];
 
if not is_valid_parameter_value (SubscriptionRequired, 'subscription', cfg.keywords ['yes_true_y']) then
 
if not is_valid_parameter_value (SubscriptionRequired, 'subscription', cfg.keywords ['yes_true_y']) then
 
SubscriptionRequired=nil;
 
SubscriptionRequired=nil;
 
end
 
end
 +
 
local UrlAccess = A['UrlAccess'];
 
local UrlAccess = A['UrlAccess'];
 
if not is_valid_parameter_value (UrlAccess, 'url-access', cfg.keywords ['url-access']) then
 
if not is_valid_parameter_value (UrlAccess, 'url-access', cfg.keywords ['url-access']) then
Line 2,283: Line 2,216:  
end
 
end
    +
local ChapterUrlAccess = A['ChapterUrlAccess'];
 +
if not is_valid_parameter_value (ChapterUrlAccess, 'chapter-url-access', cfg.keywords ['url-access']) then -- same as url-access
 +
ChapterUrlAccess = nil;
 +
end
 +
if not is_set(ChapterURL) and is_set(ChapterUrlAccess) then
 +
ChapterUrlAccess = nil;
 +
table.insert( z.message_tail, { set_error( 'param_access_requires_param', {'chapter-url'}, true ) } );
 +
end
    
local Via = A['Via'];
 
local Via = A['Via'];
Line 2,318: Line 2,259:  
LastAuthorAmp = nil; -- set to empty string
 
LastAuthorAmp = nil; -- set to empty string
 
end
 
end
if 'mla' == Mode then
+
 
LastAuthorAmp = 'yes'; -- replaces last author/editor separator with ' and ' text
  −
end
   
local no_tracking_cats = A['NoTracking'];
 
local no_tracking_cats = A['NoTracking'];
 
if not is_valid_parameter_value (no_tracking_cats, 'no-tracking', cfg.keywords ['yes_true_y']) then
 
if not is_valid_parameter_value (no_tracking_cats, 'no-tracking', cfg.keywords ['yes_true_y']) then
Line 2,326: Line 2,265:  
end
 
end
   −
--local variables that are not cs1 parameters
+
--local variables that are not cs1 parameters
 
local use_lowercase; -- controls capitalization of certain static text
 
local use_lowercase; -- controls capitalization of certain static text
 
local this_page = mw.title.getCurrentTitle(); -- also used for COinS and for language
 
local this_page = mw.title.getCurrentTitle(); -- also used for COinS and for language
Line 2,343: Line 2,282:  
use_lowercase = ( sepc == ',' ); -- used to control capitalization for certain static text
 
use_lowercase = ( sepc == ',' ); -- used to control capitalization for certain static text
   −
--check this page to see if it is in one of the namespaces that cs1 is not supposed to add to the error categories
+
--check this page to see if it is in one of the namespaces that cs1 is not supposed to add to the error categories
 
if not is_set (no_tracking_cats) then -- ignore if we are already not going to categorize this page
 
if not is_set (no_tracking_cats) then -- ignore if we are already not going to categorize this page
 
if in_array (this_page.nsText, cfg.uncategorized_namespaces) then
 
if in_array (this_page.nsText, cfg.uncategorized_namespaces) then
Line 2,356: Line 2,295:  
end
 
end
   −
-- check for extra |page=, |pages= or |at= parameters. (also sheet and sheets while we're at it)
+
-- check for extra |page=, |pages= or |at= parameters. (also sheet and sheets while we're at it)
 
select_one( args, {'page', 'p', 'pp', 'pages', 'at', 'sheet', 'sheets'}, 'redundant_parameters' ); -- this is a dummy call simply to get the error message and category
 
select_one( args, {'page', 'p', 'pp', 'pages', 'at', 'sheet', 'sheets'}, 'redundant_parameters' ); -- this is a dummy call simply to get the error message and category
   Line 2,379: Line 2,318:  
end
 
end
   −
-- both |publication-place= and |place= (|location=) allowed if different
+
-- both |publication-place= and |place= (|location=) allowed if different
 
if not is_set(PublicationPlace) and is_set(Place) then
 
if not is_set(PublicationPlace) and is_set(Place) then
 
PublicationPlace = Place; -- promote |place= (|location=) to |publication-place
 
PublicationPlace = Place; -- promote |place= (|location=) to |publication-place
Line 2,386: Line 2,325:  
if PublicationPlace == Place then Place = ''; end -- don't need both if they are the same
 
if PublicationPlace == Place then Place = ''; end -- don't need both if they are the same
 
 
--[[
+
--[[
Parameter remapping for cite encyclopedia:
+
Parameter remapping for cite encyclopedia:
When the citation has these parameters:
+
When the citation has these parameters:
|encyclopedia and |title then map |title to |article and |encyclopedia to |title
+
|encyclopedia and |title then map |title to |article and |encyclopedia to |title
|encyclopedia and |article then map |encyclopedia to |title
+
|encyclopedia and |article then map |encyclopedia to |title
|encyclopedia then map |encyclopedia to |title
+
|encyclopedia then map |encyclopedia to |title
 
+
|trans_title maps to |trans_chapter when |title is re-mapped
+
|trans-title maps to |trans-chapter when |title is re-mapped
|url maps to |chapterurl when |title is remapped
+
|url maps to |chapterurl when |title is remapped
 
+
All other combinations of |encyclopedia, |title, and |article are not modified
+
All other combinations of |encyclopedia, |title, and |article are not modified
 
+
]]
+
]]
    
local Encyclopedia = A['Encyclopedia'];
 
local Encyclopedia = A['Encyclopedia'];
Line 2,410: Line 2,349:  
TransChapter = TransTitle;
 
TransChapter = TransTitle;
 
ChapterURL = URL;
 
ChapterURL = URL;
 +
ChapterUrlAccess = UrlAccess;
 +
 
if not is_set (ChapterURL) and is_set (TitleLink) then
 
if not is_set (ChapterURL) and is_set (TitleLink) then
Chapter= '[[' .. TitleLink .. '|' .. Chapter .. ']]';
+
Chapter = make_wikilink (TitleLink, Chapter);
 
end
 
end
 
Title = Periodical;
 
Title = Periodical;
Line 2,429: Line 2,370:  
end
 
end
   −
-- Special case for cite techreport.
+
-- Special case for cite techreport.
 
if (config.CitationClass == "techreport") then -- special case for cite techreport
 
if (config.CitationClass == "techreport") then -- special case for cite techreport
 
if is_set(A['Number']) then -- cite techreport uses 'number', which other citations alias to 'issue'
 
if is_set(A['Number']) then -- cite techreport uses 'number', which other citations alias to 'issue'
Line 2,440: Line 2,381:  
end
 
end
   −
-- special case for cite mailing list
+
-- special case for cite mailing list
 
if (config.CitationClass == "mailinglist") then
 
if (config.CitationClass == "mailinglist") then
 
Periodical = A ['MailingList'];
 
Periodical = A ['MailingList'];
Line 2,447: Line 2,388:  
end
 
end
   −
-- Account for the oddity that is {{cite conference}}, before generation of COinS data.
+
-- Account for the oddity that is {{cite conference}}, before generation of COinS data.
 
if 'conference' == config.CitationClass then
 
if 'conference' == config.CitationClass then
 
if is_set(BookTitle) then
 
if is_set(BookTitle) then
Line 2,453: Line 2,394:  
-- ChapterLink = TitleLink; -- |chapterlink= is deprecated
 
-- ChapterLink = TitleLink; -- |chapterlink= is deprecated
 
ChapterURL = URL;
 
ChapterURL = URL;
 +
ChapterUrlAccess = UrlAccess;
 
ChapterURLorigin = URLorigin;
 
ChapterURLorigin = URLorigin;
 
URLorigin = '';
 
URLorigin = '';
Line 2,467: Line 2,409:  
end
 
end
   −
-- cite map oddities
+
-- cite map oddities
 
local Cartography = "";
 
local Cartography = "";
 
local Scale = "";
 
local Scale = "";
Line 2,475: Line 2,417:  
Chapter = A['Map'];
 
Chapter = A['Map'];
 
ChapterURL = A['MapURL'];
 
ChapterURL = A['MapURL'];
 +
ChapterUrlAccess = UrlAccess;
 
TransChapter = A['TransMap'];
 
TransChapter = A['TransMap'];
 
ChapterURLorigin = A:ORIGIN('MapURL');
 
ChapterURLorigin = A:ORIGIN('MapURL');
Line 2,489: Line 2,432:  
end
 
end
   −
-- Account for the oddities that are {{cite episode}} and {{cite serial}}, before generation of COinS data.
+
-- Account for the oddities that are {{cite episode}} and {{cite serial}}, before generation of COinS data.
 
if 'episode' == config.CitationClass or 'serial' == config.CitationClass then
 
if 'episode' == config.CitationClass or 'serial' == config.CitationClass then
 
local AirDate = A['AirDate'];
 
local AirDate = A['AirDate'];
Line 2,527: Line 2,470:  
TransChapter = TransTitle;
 
TransChapter = TransTitle;
 
ChapterURL = URL;
 
ChapterURL = URL;
 +
ChapterUrlAccess = UrlAccess;
 
ChapterURLorigin = A:ORIGIN('URL');
 
ChapterURLorigin = A:ORIGIN('URL');
 
 
Line 2,534: Line 2,478:     
if is_set (ChapterLink) and not is_set (ChapterURL) then -- link but not URL
 
if is_set (ChapterLink) and not is_set (ChapterURL) then -- link but not URL
Chapter = '[[' .. ChapterLink .. '|' .. Chapter .. ']]'; -- ok to wikilink
+
Chapter = make_wikilink (ChapterLink, Chapter);
 
elseif is_set (ChapterLink) and is_set (ChapterURL) then -- if both are set, URL links episode;
 
elseif is_set (ChapterLink) and is_set (ChapterURL) then -- if both are set, URL links episode;
Series = '[[' .. ChapterLink .. '|' .. Series .. ']]'; -- series links with ChapterLink (episodelink -> TitleLink -> ChapterLink) ugly
+
Series = make_wikilink (ChapterLink, Series);
 
end
 
end
 
URL = ''; -- unset
 
URL = ''; -- unset
Line 2,546: Line 2,490:  
Chapter = A['Episode']; -- TODO: make |episode= available to cite episode someday?
 
Chapter = A['Episode']; -- TODO: make |episode= available to cite episode someday?
 
if is_set (Series) and is_set (SeriesLink) then
 
if is_set (Series) and is_set (SeriesLink) then
Series = '[[' .. SeriesLink .. '|' .. Series .. ']]';
+
Series = make_wikilink (SeriesLink, Series);
 
end
 
end
 
Series = wrap_style ('italic-title', Series); -- series is italicized
 
Series = wrap_style ('italic-title', Series); -- series is italicized
 
end
 
end
 
end
 
end
-- end of {{cite episode}} stuff
+
-- end of {{cite episode}} stuff
   −
-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, before generation of COinS data.
+
-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, before generation of COinS data.
 
do
 
do
 
if in_array (config.CitationClass, {'arxiv', 'biorxiv', 'citeseerx'}) then
 
if in_array (config.CitationClass, {'arxiv', 'biorxiv', 'citeseerx'}) then
Line 2,563: Line 2,507:  
Periodical = 'arXiv'; -- set to arXiv for COinS; after that, must be set to empty string
 
Periodical = 'arXiv'; -- set to arXiv for COinS; after that, must be set to empty string
 
end
 
end
 +
 
if 'biorxiv' == config.CitationClass then
 
if 'biorxiv' == config.CitationClass then
 
Periodical = 'bioRxiv'; -- set to bioRxiv for COinS; after that, must be set to empty string
 
Periodical = 'bioRxiv'; -- set to bioRxiv for COinS; after that, must be set to empty string
 
end
 
end
 +
 
if 'citeseerx' == config.CitationClass then
 
if 'citeseerx' == config.CitationClass then
 
Periodical = 'CiteSeerX'; -- set to CiteSeerX for COinS; after that, must be set to empty string
 
Periodical = 'CiteSeerX'; -- set to CiteSeerX for COinS; after that, must be set to empty string
Line 2,572: Line 2,518:  
end
 
end
   −
-- handle type parameter for those CS1 citations that have default values
+
-- handle type parameter for those CS1 citations that have default values
 
if in_array(config.CitationClass, {"AV-media-notes", "interview", "mailinglist", "map", "podcast", "pressrelease", "report", "techreport", "thesis"}) then
 
if in_array(config.CitationClass, {"AV-media-notes", "interview", "mailinglist", "map", "podcast", "pressrelease", "report", "techreport", "thesis"}) then
 
TitleType = set_titletype (config.CitationClass, TitleType);
 
TitleType = set_titletype (config.CitationClass, TitleType);
Line 2,585: Line 2,531:  
end
 
end
   −
-- legacy: promote PublicationDate to Date if neither Date nor Year are set.
+
-- legacy: promote PublicationDate to Date if neither Date nor Year are set.
 +
local Date_origin; -- to hold the name of parameter promoted to Date; required for date error messaging
 +
 
 
if not is_set (Date) then
 
if not is_set (Date) then
 
Date = Year; -- promote Year to Date
 
Date = Year; -- promote Year to Date
Line 2,592: Line 2,540:  
Date = PublicationDate; -- promote PublicationDate to Date
 
Date = PublicationDate; -- promote PublicationDate to Date
 
PublicationDate = ''; -- unset, no longer needed
 
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
 
if PublicationDate == Date then PublicationDate = ''; end -- if PublicationDate is same as Date, don't display in rendered citation
   −
--[[
+
--[[
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
+
Go test all of the date-holding parameters for valid MOS:DATE format and make sure that dates are real dates. This must be done before we do COinS because here is where
we get the date used in the metadata.
+
we get the date used in the metadata.
 
+
Date validation supporting code is in Module:Citation/CS1/Date_validation
+
Date validation supporting code is in Module:Citation/CS1/Date_validation
]]
+
]]
 
do -- create defined block to contain local variables error_message, date_parameters_list, mismatch
 
do -- create defined block to contain local variables error_message, date_parameters_list, mismatch
 
local error_message = '';
 
local error_message = '';
 
-- AirDate has been promoted to Date so not necessary to check it
 
-- AirDate has been promoted to Date so not necessary to check it
local date_parameters_list = {['access-date']=AccessDate, ['archive-date']=ArchiveDate, ['date']=Date, ['doi-broken-date']=DoiBroken,
+
local date_parameters_list = {
['embargo']=Embargo, ['lay-date']=LayDate, ['publication-date']=PublicationDate, ['year']=Year};
+
['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')},
 +
['embargo'] = {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')},
 +
};
 +
anchor_year, Embargo, error_message = dates(date_parameters_list, COinS_date);
   −
anchor_year, Embargo, error_message = dates(date_parameters_list, COinS_date);
+
-- start temporary Julian / Gregorian calendar uncertainty categorization
 +
if COinS_date.inter_cal_cat then
 +
add_prop_cat ('jul_greg_uncertainty');
 +
end
 +
-- end temporary Julian / Gregorian calendar uncertainty categorization
    
if is_set (Year) and is_set (Date) then -- both |date= and |year= not normally needed;  
 
if is_set (Year) and is_set (Date) then -- both |date= and |year= not normally needed;  
Line 2,625: Line 2,591:  
if not is_set(error_message) then -- error free dates only
 
if not is_set(error_message) then -- error free dates only
 
local modified = false; -- flag
 
local modified = false; -- flag
 +
 
if is_set (DF) then -- if we need to reformat dates
 
if is_set (DF) then -- if we need to reformat dates
 
modified = reformat_dates (date_parameters_list, DF, false); -- reformat to DF format, use long month names if appropriate
 
modified = reformat_dates (date_parameters_list, DF, false); -- reformat to DF format, use long month names if appropriate
 
end
 
end
   −
if true == date_hyphen_to_dash (date_parameters_list) then -- convert hyphens to dashes where appropriate
+
if true == date_hyphen_to_dash (date_parameters_list) then -- convert hyphens to dashes where appropriate
 
modified = true;
 
modified = true;
 
add_maint_cat ('date_format'); -- hyphens were converted so add maint category
 
add_maint_cat ('date_format'); -- hyphens were converted so add maint category
 
end
 
end
 
 
 +
-- for those wikis that can and want to have English date names translated to the local language,
 +
-- uncomment these three lines.  Not supported by en.wiki (for obvious reasons)
 +
-- set date_name_xlate() second argument to true to translate English digits to local digits (will translate ymd dates)
 +
-- if date_name_xlate (date_parameters_list, false) then
 +
-- modified = true;
 +
-- end
 +
 
if modified then -- if the date_parameters_list values were modified
 
if modified then -- if the date_parameters_list values were modified
AccessDate = date_parameters_list['access-date']; -- overwrite date holding parameters with modified values
+
AccessDate = date_parameters_list['access-date'].val; -- overwrite date holding parameters with modified values
ArchiveDate = date_parameters_list['archive-date'];
+
ArchiveDate = date_parameters_list['archive-date'].val;
Date = date_parameters_list['date'];
+
Date = date_parameters_list['date'].val;
DoiBroken = date_parameters_list['doi-broken-date'];
+
DoiBroken = date_parameters_list['doi-broken-date'].val;
LayDate = date_parameters_list['lay-date'];
+
LayDate = date_parameters_list['lay-date'].val;
PublicationDate = date_parameters_list['publication-date'];
+
PublicationDate = date_parameters_list['publication-date'].val;
 
end
 
end
 
else
 
else
Line 2,647: Line 2,621:  
end -- end of do
 
end -- end of do
   −
-- Account for the oddity that is {{cite journal}} with |pmc= set and |url= not set.  Do this after date check but before COInS.
+
-- Account for the oddity that is {{cite journal}} with |pmc= set and |url= not set.  Do this after date check but before COInS.
-- Here we unset Embargo if PMC not embargoed (|embargo= not set in the citation) or if the embargo time has expired. Otherwise, holds embargo date
+
-- Here we unset Embargo if PMC not embargoed (|embargo= not set in the citation) or if the embargo time has expired. Otherwise, holds embargo date
Embargo = is_embargoed (Embargo); --
+
Embargo = is_embargoed (Embargo);
    
if config.CitationClass == "journal" and not is_set(URL) and is_set(ID_list['PMC']) then
 
if config.CitationClass == "journal" and not is_set(URL) and is_set(ID_list['PMC']) then
Line 2,663: Line 2,637:  
end
 
end
   −
-- At this point fields may be nil if they weren't specified in the template use.  We can use that fact.
+
-- At this point fields may be nil if they weren't specified in the template use.  We can use that fact.
 
-- Test if citation has no title
 
-- Test if citation has no title
 
if not is_set(Title) and
 
if not is_set(Title) and
Line 2,728: Line 2,702:  
}, config.CitationClass);
 
}, config.CitationClass);
   −
-- Account for the oddities that are {{cite arxiv}}, AFTER generation of COinS data.
+
-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, and {{cite citeseerx}} AFTER generation of COinS data.
-- if 'arxiv' == config.CitationClass then -- we have set rft.jtitle in COinS to arXiv, now unset so it isn't displayed
   
if in_array (config.CitationClass, {'arxiv', 'biorxiv', 'citeseerx'}) then -- we have set rft.jtitle in COinS to arXiv, bioRxiv, or CiteSeerX now unset so it isn't displayed
 
if in_array (config.CitationClass, {'arxiv', 'biorxiv', 'citeseerx'}) then -- we have set rft.jtitle in COinS to arXiv, bioRxiv, or CiteSeerX now unset so it isn't displayed
 
Periodical = ''; -- periodical not allowed in these templates; if article has been published, use cite journal
 
Periodical = ''; -- periodical not allowed in these templates; if article has been published, use cite journal
 
end
 
end
   −
-- special case for cite newsgroup.  Do this after COinS because we are modifying Publishername to include some static text
+
-- special case for cite newsgroup.  Do this after COinS because we are modifying Publishername to include some static text
 
if 'newsgroup' == config.CitationClass then
 
if 'newsgroup' == config.CitationClass then
 
if is_set (PublisherName) then
 
if is_set (PublisherName) then
Line 2,809: Line 2,782:  
end
 
end
   −
-- apply |[xx-]format= styling; at the end, these parameters hold correctly styled format annotation,
+
-- apply |[xx-]format= styling; at the end, these parameters hold correctly styled format annotation,
-- an error message if the associated url is not set, or an empty string for concatenation
+
-- an error message if the associated url is not set, or an empty string for concatenation
 
ArchiveFormat = style_format (ArchiveFormat, ArchiveURL, 'archive-format', 'archive-url');
 
ArchiveFormat = style_format (ArchiveFormat, ArchiveURL, 'archive-format', 'archive-url');
 
ConferenceFormat = style_format (ConferenceFormat, ConferenceURL, 'conference-format', 'conference-url');
 
ConferenceFormat = style_format (ConferenceFormat, ConferenceURL, 'conference-format', 'conference-url');
Line 2,817: Line 2,790:  
TranscriptFormat = style_format (TranscriptFormat, TranscriptURL, 'transcript-format', 'transcripturl');
 
TranscriptFormat = style_format (TranscriptFormat, TranscriptURL, 'transcript-format', 'transcripturl');
   −
-- special case for chapter format so no error message or cat when chapter not supported
+
-- special case for chapter format so no error message or cat when chapter not supported
 
if not (in_array(config.CitationClass, {'web', 'news', 'journal', 'magazine', 'pressrelease', 'podcast', 'newsgroup', 'arxiv', 'biorxiv', 'citeseerx'}) or
 
if not (in_array(config.CitationClass, {'web', 'news', 'journal', 'magazine', 'pressrelease', 'podcast', 'newsgroup', 'arxiv', 'biorxiv', 'citeseerx'}) or
 
('citation' == config.CitationClass and is_set (Periodical) and not is_set (Encyclopedia))) then
 
('citation' == config.CitationClass and is_set (Periodical) and not is_set (Encyclopedia))) then
Line 2,838: Line 2,811:  
DeadURL = DeadURL:lower(); -- used later when assembling archived text
 
DeadURL = DeadURL:lower(); -- used later when assembling archived text
 
if is_set( ArchiveURL ) then
 
if is_set( ArchiveURL ) then
if is_set (ChapterURL) then -- URL not set so if chapter-url is set apply archive url to it
+
if is_set (ChapterURL) then -- if chapter-url is set apply archive url to it
 
OriginalURL = ChapterURL; -- save copy of source chapter's url for archive text
 
OriginalURL = ChapterURL; -- save copy of source chapter's url for archive text
 
OriginalURLorigin = ChapterURLorigin; -- name of chapter-url parameter for error messages
 
OriginalURLorigin = ChapterURLorigin; -- name of chapter-url parameter for error messages
OriginalFormat = ChapterFormat; -- and original |format=
+
OriginalFormat = ChapterFormat; -- and original |chapter-format=
 
if 'no' ~= DeadURL then
 
if 'no' ~= DeadURL then
 
ChapterURL = ArchiveURL -- swap-in the archive's url
 
ChapterURL = ArchiveURL -- swap-in the archive's url
 
ChapterURLorigin = A:ORIGIN('ArchiveURL') -- name of archive-url parameter for error messages
 
ChapterURLorigin = A:ORIGIN('ArchiveURL') -- name of archive-url parameter for error messages
 
ChapterFormat = ArchiveFormat or ''; -- swap in archive's format
 
ChapterFormat = ArchiveFormat or ''; -- swap in archive's format
 +
ChapterUrlAccess = nil; -- restricted access levels do not make sense for archived urls
 
end
 
end
 
elseif is_set (URL) then
 
elseif is_set (URL) then
Line 2,892: Line 2,866:  
end
 
end
   −
Chapter = format_chapter_title (ScriptChapter, Chapter, TransChapter, ChapterURL, ChapterURLorigin, no_quotes); -- Contribution is also in Chapter
+
Chapter = format_chapter_title (ScriptChapter, Chapter, TransChapter, ChapterURL, ChapterURLorigin, no_quotes, ChapterUrlAccess); -- Contribution is also in Chapter
 
if is_set (Chapter) then
 
if is_set (Chapter) then
 
Chapter = Chapter .. ChapterFormat ;
 
Chapter = Chapter .. ChapterFormat ;
Line 2,905: Line 2,879:     
-- Format main title.
 
-- Format main title.
 +
if is_set (ArchiveURL) and mw.ustring.match (mw.ustring.lower(Title), cfg.special_case_translation['archived_copy']) then -- if title is 'Archived copy' (place holder added by bots that can't find proper title)
 +
add_maint_cat ('archived_copy'); -- add maintenance category before we modify the content of Title
 +
end
 +
 +
if Title:match ('^%(%(.*%)%)$') then -- if keep as written markup:
 +
Title= Title:gsub ('^%(%((.*)%)%)$', '%1') -- remove the markup
 +
else
 +
if '...' == Title:sub (-3) then -- if elipsis 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
 +
end
 +
 
if is_set(TitleLink) and is_set(Title) then
 
if is_set(TitleLink) and is_set(Title) then
Title = "[[" .. TitleLink .. "|" .. Title .. "]]"
+
Title = make_wikilink (TitleLink, Title);
 
end
 
end
   Line 2,938: Line 2,927:  
 
 
Title = external_link( URL, Title, URLorigin, UrlAccess ) .. TransTitle .. TransError .. Format;
 
Title = external_link( URL, Title, URLorigin, UrlAccess ) .. TransTitle .. TransError .. Format;
-- this experiment hidden 2016-04-10; see Help_talk:Citation_Style_1#Recycled_urls
  −
-- local temp_title = external_link( URL, Title, URLorigin ) .. TransError .. Format; -- do this so we get error message even if url is usurped no archive
  −
-- if in_array (DeadURL, {'unfit no archive', 'usurped no archive'}) then -- when url links to inappropriate location and there is no archive of original source available
  −
-- local err_msg
  −
-- if temp_title:match ('%[%S+%s+(.+)%](<.+)') then -- if there is an error message
  −
-- Title, err_msg = temp_title:match ('%[%S+%s+(.+)%](<.+)'); -- strip off external link; TODO: find a better to do this
  −
-- Title = Title .. (err_msg or '');
  −
-- end
  −
-- else
  −
-- Title = temp_title;
  −
-- end
  −
   
URL = ''; -- unset these because no longer needed
 
URL = ''; -- unset these because no longer needed
 
Format = "";
 
Format = "";
Line 2,955: Line 2,932:  
Title = Title .. TransTitle .. TransError;
 
Title = Title .. TransTitle .. TransError;
 
end
 
end
 +
else
 +
Title = TransTitle .. TransError;
 
end
 
end
   Line 2,996: Line 2,975:  
end
 
end
   −
Page, Pages, Sheet, Sheets = format_pages_sheets (Page, Pages, Sheet, Sheets, config.CitationClass, Periodical_origin, sepc, NoPP, use_lowercase, Mode);
+
Page, Pages, Sheet, Sheets = format_pages_sheets (Page, Pages, Sheet, Sheets, config.CitationClass, Periodical_origin, sepc, NoPP, use_lowercase);
    
At = is_set(At) and (sepc .. " " .. At) or "";
 
At = is_set(At) and (sepc .. " " .. At) or "";
Line 3,029: Line 3,008:  
 
 
if is_set (Translators) then
 
if is_set (Translators) then
if 'mla' == Mode then
+
Others = safe_join ({sepc .. ' ', wrap_msg ('translated', Translators, use_lowercase), Others}, sepc);
Others = sepc .. ' Trans. ' .. Translators .. Others;
  −
else
  −
Others = sepc .. ' ' .. wrap_msg ('translated', Translators, use_lowercase) .. Others;
  −
end
   
end
 
end
 
if is_set (Interviewers) then
 
if is_set (Interviewers) then
Others = sepc .. ' ' .. wrap_msg ('interview', Interviewers, use_lowercase) .. Others;
+
Others = safe_join ({sepc .. ' ', wrap_msg ('interview', Interviewers, use_lowercase), Others}, sepc);
 
end
 
end
 
 
Line 3,044: Line 3,019:  
add_maint_cat ('extra_text', 'edition');
 
add_maint_cat ('extra_text', 'edition');
 
end
 
end
if 'mla' == Mode then
+
Edition = " " .. wrap_msg ('edition', Edition);
Edition = '. ' .. Edition .. ' ed.';
  −
else
  −
Edition = " " .. wrap_msg ('edition', Edition);
  −
end
   
else
 
else
 
Edition = '';
 
Edition = '';
Line 3,054: Line 3,025:     
Series = is_set(Series) and (sepc .. " " .. Series) or "";
 
Series = is_set(Series) and (sepc .. " " .. Series) or "";
if 'mla' == Mode then -- not in brackets for mla
+
OrigYear = is_set(OrigYear) and (" [" .. OrigYear .. "]") or ""; -- TODO: presentation
OrigYear = is_set(OrigYear) and (". " .. OrigYear) or "";
+
 
else
  −
OrigYear = is_set(OrigYear) and (" [" .. OrigYear .. "]") or "";
  −
end
   
Agency = is_set(Agency) and (sepc .. " " .. Agency) or "";
 
Agency = is_set(Agency) and (sepc .. " " .. Agency) or "";
   −
Volume = format_volume_issue (Volume, Issue, config.CitationClass, Periodical_origin, sepc, use_lowercase, Mode);
+
Volume = format_volume_issue (Volume, Issue, config.CitationClass, Periodical_origin, sepc, use_lowercase);
    
------------------------------------ totally unrelated data
 
------------------------------------ totally unrelated data
Line 3,068: Line 3,036:  
end
 
end
   −
--[[
+
--[[
Subscription implies paywall; Registration does not.  If both are used in a citation, the subscription required link
+
Subscription implies paywall; Registration does not.  If both are used in a citation, the subscription required link
note is displayed. There are no error messages for this condition.
+
note is displayed. There are no error messages for this condition.
 
+
]]
+
]]
 
if is_set (SubscriptionRequired) then
 
if is_set (SubscriptionRequired) then
 
SubscriptionRequired = sepc .. " " .. cfg.messages['subscription']; -- subscription required message
 
SubscriptionRequired = sepc .. " " .. cfg.messages['subscription']; -- subscription required message
Line 3,085: Line 3,053:     
AccessDate = nowrap_date (AccessDate); -- wrap in nowrap span if date in appropriate format
 
AccessDate = nowrap_date (AccessDate); -- wrap in nowrap span if date in appropriate format
if 'mla' == Mode then -- retrieved text not used in mla
+
if (sepc ~= ".") then retrv_text = retrv_text:lower() end -- if mode is cs2, lower case
AccessDate = ' ' .. AccessDate;
+
AccessDate = substitute (retrv_text, AccessDate); -- add retrieved text
else
+
 
if (sepc ~= ".") then retrv_text = retrv_text:lower() end -- if mode is cs2, lower case
  −
AccessDate = substitute (retrv_text, AccessDate); -- add retrieved text
  −
end
   
AccessDate = substitute (cfg.presentation['accessdate'], {sepc, AccessDate}); -- allow editors to hide accessdates
 
AccessDate = substitute (cfg.presentation['accessdate'], {sepc, AccessDate}); -- allow editors to hide accessdates
 
end
 
end
Line 3,206: Line 3,171:  
end
 
end
   −
--[[
+
--[[
Handle the oddity that is cite speech.  This code overrides whatever may be the value assigned to TitleNote (through |department=) and forces it to be " (Speech)" so that
+
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).
+
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
 
if "speech" == config.CitationClass then -- cite speech only
 
TitleNote = " (Speech)"; -- annotate the citation
 
TitleNote = " (Speech)"; -- annotate the citation
Line 3,227: Line 3,192:  
 
 
if in_array(config.CitationClass, {"journal","citation"}) and is_set(Periodical) then
 
if in_array(config.CitationClass, {"journal","citation"}) and is_set(Periodical) then
if is_set(Others) then Others = Others .. sepc .. " " end
+
if 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?
if 'mla' == Mode then
+
tcommon = safe_join( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, Edition, Publisher, Agency, Volume}, sepc );
tcommon = safe_join( {Conference, Periodical, Format, TitleType, Series, Language, Edition, Publisher, Agency, Volume}, sepc );
  −
else
  −
tcommon = safe_join( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series,  
  −
Language, Edition, Publisher, Agency, Volume}, sepc );
  −
end
   
elseif in_array(config.CitationClass, {"book","citation"}) and not is_set(Periodical) then -- special cases for book cites
 
elseif in_array(config.CitationClass, {"book","citation"}) and not is_set(Periodical) then -- special cases for book cites
 
if is_set (Contributors) then -- when we are citing foreword, preface, introduction, etc
 
if 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
 
tcommon = safe_join( {Title, TitleNote}, sepc ); -- author and other stuff will come after this and before tcommon2
if 'mla' == Mode then
+
tcommon2 = safe_join( {Conference, Periodical, Format, TitleType, Series, Language, Volume, Others, Edition, Publisher, Agency}, sepc );
tcommon2 = safe_join( {Conference, Periodical, Format, TitleType, Series, Language, Volume, Edition, Publisher, Agency}, sepc );
  −
else
  −
tcommon2 = safe_join( {Conference, Periodical, Format, TitleType, Series, Language, Volume, Others, Edition, Publisher, Agency}, sepc );
  −
end
  −
elseif 'mla' == Mode then
  −
tcommon = safe_join( {TitleNote, Conference, Periodical, Format, TitleType, Series, Language, Volume, Publisher, Agency}, sepc );
   
else
 
else
 
tcommon = safe_join( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, Volume, Others, Edition, Publisher, Agency}, sepc );
 
tcommon = safe_join( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, Volume, Others, Edition, Publisher, Agency}, sepc );
Line 3,259: Line 3,213:  
elseif 'episode' == config.CitationClass then -- special case for cite episode
 
elseif 'episode' == config.CitationClass then -- special case for cite episode
 
tcommon = safe_join( {Title, TitleNote, TitleType, Series, Transcript, Language, Edition, Publisher}, sepc );
 
tcommon = safe_join( {Title, TitleNote, TitleType, Series, Transcript, Language, Edition, Publisher}, sepc );
  −
elseif ('news' == config.CitationClass) and ('mla' == Mode) then -- special case for cite news in MLA mode
  −
tcommon = safe_join( {Periodical, Format, TitleType, Series, Language, Edition, Agency}, sepc );
  −
  −
elseif ('web' == config.CitationClass) and ('mla' == Mode) then -- special case for cite web in MLA mode
  −
tcommon = safe_join( {Periodical, Format, TitleType, Series, Language,
  −
Edition, Publisher, Agency}, sepc );
      
else -- all other CS1 templates
 
else -- all other CS1 templates
Line 3,283: Line 3,230:     
if is_set(Date) then
 
if is_set(Date) then
if ('mla' == Mode) then
+
if is_set (Authors) or is_set (Editors) then -- date follows authors or editors when authors not set
if in_array (config.CitationClass, {'book', 'news', 'web'}) then
  −
Date = ', ' .. Date; -- origyear follows title in mla
  −
elseif 'journal' == config.CitationClass then
  −
Date = ', (' .. Date .. ')';
  −
end
  −
elseif is_set (Authors) or is_set (Editors) then -- date follows authors or editors when authors not set
   
Date = " (" .. Date ..")" .. OrigYear .. sepc .. " "; -- in paranetheses
 
Date = " (" .. Date ..")" .. OrigYear .. sepc .. " "; -- in paranetheses
 
else -- neither of authors and editors set
 
else -- neither of authors and editors set
Line 3,300: Line 3,241:  
end
 
end
 
if is_set(Authors) then
 
if is_set(Authors) then
if (not is_set (Date)) or ('mla' == Mode) then -- when date is set it's in parentheses; no Authors termination
+
if (not is_set (Date)) then -- when date is set it's in parentheses; no Authors termination
 
Authors = terminate_name_list (Authors, sepc); -- when no date, terminate with 0 or 1 sepc and a space
 
Authors = terminate_name_list (Authors, sepc); -- when no date, terminate with 0 or 1 sepc and a space
 
end
 
end
Line 3,306: Line 3,247:  
local in_text = " ";
 
local in_text = " ";
 
local post_text = "";
 
local post_text = "";
if is_set(Chapter) and 0 == #c and 'mla' ~= Mode then
+
if is_set(Chapter) and 0 == #c then
 
in_text = in_text .. cfg.messages['in'] .. " "
 
in_text = in_text .. cfg.messages['in'] .. " "
if (sepc ~= '.') then in_text = in_text:lower() end -- lowercase for cs2
+
if (sepc ~= '.') then
elseif is_set(Chapter) and 'mla' == Mode then
+
in_text = in_text:lower() -- lowercase for cs2
if EditorCount <= 1 then
+
end
in_text = '. Ed. ';
  −
else
  −
in_text = '. Eds. ';
  −
end
   
else
 
else
 
if EditorCount <= 1 then
 
if EditorCount <= 1 then
Line 3,328: Line 3,265:  
if (sepc ~= '.') then by_text = by_text:lower() end -- lowercase for cs2
 
if (sepc ~= '.') then by_text = by_text:lower() end -- lowercase for cs2
 
Authors = by_text .. Authors; -- author follows title so tweak it here
 
Authors = by_text .. Authors; -- author follows title so tweak it here
if is_set (Editors) and is_set (Date) and ('mla' ~= Mode) then -- when Editors make sure that Authors gets terminated
+
if is_set (Editors) and 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
 
Authors = terminate_name_list (Authors, sepc); -- terminate with 0 or 1 sepc and a space
 
end
 
end
if (not is_set (Date)) or ('mla' == Mode) then -- when date is set it's in parentheses; no Contributors termination
+
if (not 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
 
Contributors = terminate_name_list (Contributors, sepc); -- terminate with 0 or 1 sepc and a space
 
end
 
end
if 'mla' == Mode then
+
text = safe_join( {Contributors, Date, Chapter, tcommon, Authors, Place, Editors, tcommon2, pgtext, idcommon }, sepc );
text = safe_join( {Contributors, Chapter, tcommon, OrigYear, Authors, Place, Others, Editors, tcommon2, Date, pgtext, idcommon }, sepc );
  −
else
  −
text = safe_join( {Contributors, Date, Chapter, tcommon, Authors, Place, Editors, tcommon2, pgtext, idcommon }, sepc );
  −
end
  −
elseif 'mla' == Mode then
  −
tcommon = tcommon .. Date; -- hack to avoid duplicate separators
  −
text = safe_join( {Authors, Chapter, Title, OrigYear, Others, Editors, Edition, Place, tcommon, pgtext, idcommon }, sepc );
   
else
 
else
 
text = safe_join( {Authors, Date, Chapter, Place, Editors, tcommon, pgtext, idcommon }, sepc );
 
text = safe_join( {Authors, Date, Chapter, Place, Editors, tcommon, pgtext, idcommon }, sepc );
Line 3,359: Line 3,289:  
end
 
end
 
end
 
end
if 'mla' == Mode then
+
text = safe_join( {Editors, Date, Chapter, Place, tcommon, pgtext, idcommon}, sepc );
if in_array(config.CitationClass, {'journal', 'news', 'web'}) and is_set(Periodical) then
  −
text = safe_join( {Editors, Title, Place, tcommon, pgtext, Date, idcommon}, sepc );
  −
else
  −
text = safe_join( {Editors, Chapter, Title, Place, tcommon, Date, pgtext, idcommon}, sepc );
  −
end
  −
else
  −
text = safe_join( {Editors, Date, Chapter, Place, tcommon, pgtext, idcommon}, sepc );
  −
end
  −
elseif 'mla' == Mode then
  −
if in_array(config.CitationClass, {'journal', 'news', 'web'}) and is_set(Periodical) then
  −
text = safe_join( {Title, Place, tcommon, pgtext, Date, idcommon}, sepc );
  −
else
  −
text = safe_join( {Chapter, Title, Place, tcommon, Date, pgtext, idcommon}, sepc );
  −
end
   
else
 
else
 
if in_array(config.CitationClass, {"journal","citation"}) and is_set(Periodical) then
 
if in_array(config.CitationClass, {"journal","citation"}) and is_set(Periodical) then
Line 3,427: Line 3,343:  
end
 
end
 
 
 +
local render = {}; -- here we collect the final bits for concatenation into the rendered citation
 +
 
if is_set(options.id) then -- here we wrap the rendered citation in <cite ...>...</cite> tags
 
if is_set(options.id) then -- here we wrap the rendered citation in <cite ...>...</cite> tags
text = substitute (cfg.presentation['cite-id'], {mw.uri.anchorEncode(options.id), mw.text.nowiki(options.class), text}); -- when |ref= is set
+
table.insert (render, substitute (cfg.presentation['cite-id'], {mw.uri.anchorEncode(options.id), mw.text.nowiki(options.class), text})); -- when |ref= is set
 
else
 
else
text = substitute (cfg.presentation['cite'], {mw.text.nowiki(options.class), text}); -- all other cases
+
table.insert (render, substitute (cfg.presentation['cite'], {mw.text.nowiki(options.class), text})); -- all other cases
 
end
 
end
   −
text = text .. substitute (cfg.presentation['ocins'], {OCinSoutput}); -- append metadata to the citation
+
table.insert (render, substitute (cfg.presentation['ocins'], {OCinSoutput})); -- append metadata to the citation
+
 
 
if #z.message_tail ~= 0 then
 
if #z.message_tail ~= 0 then
text = text .. " ";
+
table.insert (render, ' ');
 
for i,v in ipairs( z.message_tail ) do
 
for i,v in ipairs( z.message_tail ) do
 
if is_set(v[1]) then
 
if is_set(v[1]) then
 
if i == #z.message_tail then
 
if i == #z.message_tail then
text = text .. error_comment( v[1], v[2] );
+
table.insert (render, error_comment( v[1], v[2] ));
 
else
 
else
text = text .. error_comment( v[1] .. "; ", v[2] );
+
table.insert (render, error_comment( v[1] .. "; ", v[2] ));
 
end
 
end
 
end
 
end
Line 3,449: Line 3,367:     
if #z.maintenance_cats ~= 0 then
 
if #z.maintenance_cats ~= 0 then
text = text .. '<span class="citation-comment" style="display:none; color:#33aa33; margin-left:0.3em">';
+
table.insert (render, '<span class="citation-comment" style="display:none; color:#33aa33; margin-left:0.3em">');
 
for _, v in ipairs( z.maintenance_cats ) do -- append maintenance categories
 
for _, v in ipairs( z.maintenance_cats ) do -- append maintenance categories
text = text .. v .. ' ([[:Category:' .. v ..'|link]])';
+
table.insert (render, v);
 +
table.insert (render, ' (');
 +
table.insert (render, make_wikilink (':Category:' .. v, 'link'));
 +
table.insert (render, ') ');
 
end
 
end
text = text .. '</span>'; -- maintenance mesages (realy just the names of the categories for now)
+
table.insert (render, '</span>');
 
end
 
end
 
 
Line 3,459: Line 3,380:  
if in_array(no_tracking_cats, {"", "no", "false", "n"}) then
 
if in_array(no_tracking_cats, {"", "no", "false", "n"}) then
 
for _, v in ipairs( z.error_categories ) do
 
for _, v in ipairs( z.error_categories ) do
text = text .. '[[Category:' .. v ..']]';
+
table.insert (render, make_wikilink ('Category:' .. v));
 
end
 
end
 
for _, v in ipairs( z.maintenance_cats ) do -- append maintenance categories
 
for _, v in ipairs( z.maintenance_cats ) do -- append maintenance categories
text = text .. '[[Category:' .. v ..']]';
+
table.insert (render, make_wikilink ('Category:' .. v));
 
end
 
end
for _, v in ipairs( z.properties_cats ) do -- append maintenance categories
+
for _, v in ipairs( z.properties_cats ) do -- append properties categories
text = text .. '[[Category:' .. v ..']]';
+
table.insert (render, make_wikilink ('Category:' .. v));
 
end
 
end
 
end
 
end
+
 
return text
+
return table.concat (render);
 
end
 
end
      −
--[[--------------------------< C S 1 . C I T A T I O N >------------------------------------------------------
+
--[[--------------------------< V A L I D A T E >--------------------------------------------------------------
   −
This is used by templates such as {{cite book}} to create the actual citation text.
+
Looks for a parameter's name in one of several whitelists.
    +
Parameters in the whitelist can have three values:
 +
true - active, supported parameters
 +
false - deprecated, supported parameters
 +
nil - unsupported parameters
 +
 
]]
 
]]
   −
function cs1.citation(frame)
+
local function validate (name, cite_class)
Frame = frame; -- save a copy incase we need to display an error message in preview mode
+
local name = tostring (name);
local pframe = frame:getParent()
+
local state;
local validation, utilities, identifiers, metadata;
   
 
if nil ~= string.find (frame:getTitle(), 'sandbox', 1, true) then -- did the {{#invoke:}} use sandbox version?
+
if in_array (cite_class, {'arxiv', 'biorxiv', 'citeseerx'}) then -- limited parameter sets allowed for these templates
cfg = mw.loadData ('Module:Citation/CS1/Configuration/sandbox'); -- load sandbox versions of support modules
+
state = whitelist.limited_basic_arguments[name];
whitelist = mw.loadData ('Module:Citation/CS1/Whitelist/sandbox');
+
if true == state then return true; end -- valid actively supported parameter
utilities = require ('Module:Citation/CS1/Utilities/sandbox');
+
if false == state then
validation = require ('Module:Citation/CS1/Date_validation/sandbox');
+
deprecated_parameter (name); -- parameter is deprecated but still supported
identifiers = require ('Module:Citation/CS1/Identifiers/sandbox');
+
return true;
metadata = require ('Module:Citation/CS1/COinS/sandbox');
+
end
  −
else -- otherwise
  −
cfg = mw.loadData ('Module:Citation/CS1/Configuration'); -- load live versions of support modules
  −
whitelist = mw.loadData ('Module:Citation/CS1/Whitelist');
  −
utilities = require ('Module:Citation/CS1/Utilities');
  −
validation = require ('Module:Citation/CS1/Date_validation');
  −
identifiers = require ('Module:Citation/CS1/Identifiers');
  −
metadata = require ('Module:Citation/CS1/COinS');
  −
end
     −
utilities.set_selected_modules (cfg); -- so that functions in Utilities can see the cfg tables
+
state = whitelist[cite_class .. '_basic_arguments'][name]; -- look in the parameter-list for the template identified by cite_class
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 (utilities); -- so that functions in Date validataion can see 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
     −
dates = validation.dates; -- imported functions from Module:Citation/CS1/Date validation
+
if true == state then return true; end -- valid actively supported parameter
year_date_check = validation.year_date_check;
+
if false == state then
reformat_dates = validation.reformat_dates;
+
deprecated_parameter (name); -- parameter is deprecated but still supported
date_hyphen_to_dash = validation.date_hyphen_to_dash;
+
return true;
 +
end
 +
-- limited enumerated parameters list
 +
name = name:gsub("%d+", "#" ); -- replace digit(s) with # (last25 becomes last#) (mw.ustring because non-Western 'local' digits)
 +
state = whitelist.limited_numbered_arguments[name];
 +
if true == state then return true; end -- valid actively supported parameter
 +
if false == state then
 +
deprecated_parameter (name); -- parameter is deprecated but still supported
 +
return true;
 +
end
 +
 
 +
return false; -- not supported because not found or name is set to nil
 +
end -- end limited parameter-set templates
 +
 +
state = whitelist.basic_arguments[name]; -- all other templates; all normal parameters allowed
 +
 +
if true == state then return true; end -- valid actively supported parameter
 +
if false == state then
 +
deprecated_parameter (name); -- parameter is deprecated but still supported
 +
return true;
 +
end
 +
-- all enumerated parameters allowed
 +
name = name:gsub("%d+", "#" ); -- replace digit(s) with # (last25 becomes last#) (mw.ustring because non-Western 'local' digits)
 +
state = whitelist.numbered_arguments[name];
 +
 
 +
if true == state then return true; end -- valid actively supported parameter
 +
if false == state then
 +
deprecated_parameter (name); -- parameter is deprecated but still supported
 +
return true;
 +
end
 
 
is_set = utilities.is_set; -- imported functions from Module:Citation/CS1/Utilities
+
return false; -- not supported because not found or name is set to nil
in_array = utilities.in_array;
+
end
substitute = utilities.substitute;
  −
error_comment = utilities.error_comment;
  −
set_error = utilities.set_error;
  −
select_one = utilities.select_one;
  −
add_maint_cat = utilities.add_maint_cat;
  −
wrap_style = utilities.wrap_style;
  −
safe_for_italics = utilities.safe_for_italics;
  −
remove_wiki_link = utilities.remove_wiki_link;
     −
z = utilities.z; -- table of error and category tables in Module:Citation/CS1/Utilities
     −
extract_ids = identifiers.extract_ids; -- imported functions from Module:Citation/CS1/Identifiers
+
--[[--------------------------< M I S S I N G _ P I P E _ C H E C K >------------------------------------------
build_id_list = identifiers.build_id_list;
+
 
is_embargoed = identifiers.is_embargoed;
+
Look at the contents of a parameter. If the content has a string of characters and digits followed by an equal
extract_id_access_levels = identifiers.extract_id_access_levels;
+
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:
 +
{{cite ... |title=Title access-date=2016-03-17}}
 +
 
 +
cs1|2 shares some parameter names with xml/html atributes: class=, title=, etc.  To prevent false positives xml/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 (value)
 +
local capture;
 +
value = value:gsub ('%b<>', ''); -- remove xml/html tags because attributes: class=, title=, etc
 +
 
 +
capture = value:match ('%s+(%a[%a%d]+)%s*=') or value:match ('^(%a[%a%d]+)%s*='); -- find and categorize parameters with possible missing pipes
 +
if capture and validate (capture) then -- if the capture is a valid parameter name
 +
add_maint_cat ('missing_pipe');
 +
end
 +
end
 +
 
 +
 
 +
--[[--------------------------< C S 1 . C I T A T I O N >------------------------------------------------------
 +
 
 +
This is used by templates such as {{cite book}} to create the actual citation text.
 +
 
 +
]]
 +
 
 +
function cs1.citation(frame)
 +
Frame = frame; -- save a copy incase we need to display an error message in preview mode
 +
local pframe = frame:getParent()
 +
local validation, utilities, identifiers, metadata, styles;
 
 
make_coins_title = metadata.make_coins_title; -- imported functions from Module:Citation/CS1/COinS
+
if nil ~= string.find (frame:getTitle(), 'sandbox', 1, true) then -- did the {{#invoke:}} use sandbox version?
get_coins_pages = metadata.get_coins_pages;
+
cfg = mw.loadData ('Module:Citation/CS1/Configuration/sandbox'); -- load sandbox versions of support modules
COinS = metadata.COinS;
+
whitelist = mw.loadData ('Module:Citation/CS1/Whitelist/sandbox');
 
+
utilities = require ('Module:Citation/CS1/Utilities/sandbox');
local args = {}; -- table where we store all of the template's arguments
+
validation = require ('Module:Citation/CS1/Date_validation/sandbox');
local suggestions = {}; -- table where we store suggestions if we need to loadData them
+
identifiers = require ('Module:Citation/CS1/Identifiers/sandbox');
local error_text, error_state;
+
metadata = require ('Module:Citation/CS1/COinS/sandbox');
 
+
styles = 'Module:Citation/CS1/sandbox/styles.css';
local config = {}; -- table to store parameters from the module {{#invoke:}}
+
for k, v in pairs( frame.args ) do
+
else -- otherwise
config[k] = v;
+
cfg = mw.loadData ('Module:Citation/CS1/Configuration'); -- load live versions of support modules
-- args[k] = v; -- debug tool that allows us to render a citation from module {{#invoke:}}
+
whitelist = mw.loadData ('Module:Citation/CS1/Whitelist');
end
+
utilities = require ('Module:Citation/CS1/Utilities');
 
+
validation = require ('Module:Citation/CS1/Date_validation');
local capture; -- the single supported capture when matching unknown parameters using patterns
+
identifiers = require ('Module:Citation/CS1/Identifiers');
for k, v in pairs( pframe.args ) do
+
metadata = require ('Module:Citation/CS1/COinS');
if v ~= '' then
+
styles = 'Module:Citation/CS1/styles.css';
if not validate( k, config.CitationClass ) then
+
 
error_text = "";
+
end
if type( k ) ~= 'string' then
+
 
-- Exclude empty numbered parameters
+
utilities.set_selected_modules (cfg); -- so that functions in Utilities can see the cfg tables
if v:match("%S+") ~= nil then
+
identifiers.set_selected_modules (cfg, utilities); -- so that functions in Identifiers can see the selected cfg tables and selected Utilities module
error_text, error_state = set_error( 'text_ignored', {v}, true );
+
validation.set_selected_modules (cfg, utilities); -- so that functions in Date validataion can see selected cfg tables and the selected Utilities module
end
+
metadata.set_selected_modules (cfg, utilities); -- so that functions in COinS can see the selected cfg tables and selected Utilities module
elseif validate( k:lower(), config.CitationClass ) then  
+
 
error_text, error_state = set_error( 'parameter_ignored_suggest', {k, k:lower()}, true );
+
dates = validation.dates; -- imported functions from Module:Citation/CS1/Date validation
else
+
year_date_check = validation.year_date_check;
if nil == suggestions.suggestions then -- if this table is nil then we need to load it
+
reformat_dates = validation.reformat_dates;
if nil ~= string.find (frame:getTitle(), 'sandbox', 1, true) then -- did the {{#invoke:}} use sandbox version?
+
date_hyphen_to_dash = validation.date_hyphen_to_dash;
suggestions = mw.loadData( 'Module:Citation/CS1/Suggestions/sandbox' ); -- use the sandbox version
+
date_name_xlate = validation.date_name_xlate;
else
+
 
suggestions = mw.loadData( 'Module:Citation/CS1/Suggestions' ); -- use the live version
+
is_set = utilities.is_set; -- imported functions from Module:Citation/CS1/Utilities
end
+
in_array = utilities.in_array;
end
+
substitute = utilities.substitute;
for pattern, param in pairs (suggestions.patterns) do -- loop through the patterns to see if we can suggest a proper parameter
+
error_comment = utilities.error_comment;
capture = k:match (pattern); -- the whole match if no caputre in pattern else the capture if a match
+
set_error = utilities.set_error;
if capture then -- if the pattern matches  
+
select_one = utilities.select_one;
param = substitute( param, capture ); -- add the capture to the suggested parameter (typically the enumerator)
+
add_maint_cat = utilities.add_maint_cat;
error_text, error_state = set_error( 'parameter_ignored_suggest', {k, param}, true ); -- set the error message
+
wrap_style = utilities.wrap_style;
 +
safe_for_italics = utilities.safe_for_italics;
 +
is_wikilink = utilities.is_wikilink;
 +
make_wikilink = utilities.make_wikilink;
 +
 
 +
z = utilities.z; -- table of error and category tables in Module:Citation/CS1/Utilities
 +
 
 +
extract_ids = identifiers.extract_ids; -- imported functions from Module:Citation/CS1/Identifiers
 +
build_id_list = identifiers.build_id_list;
 +
is_embargoed = identifiers.is_embargoed;
 +
extract_id_access_levels = identifiers.extract_id_access_levels;
 +
 +
make_coins_title = metadata.make_coins_title; -- imported functions from Module:Citation/CS1/COinS
 +
get_coins_pages = metadata.get_coins_pages;
 +
COinS = metadata.COinS;
 +
 
 +
local args = {}; -- table where we store all of the template's arguments
 +
local suggestions = {}; -- table where we store suggestions if we need to loadData them
 +
local error_text, error_state;
 +
 
 +
local config = {}; -- table to store parameters from the module {{#invoke:}}
 +
for k, v in pairs( frame.args ) do
 +
config[k] = v;
 +
-- args[k] = v; -- debug tool that allows us to render a citation from module {{#invoke:}}
 +
end
 +
 
 +
local capture; -- the single supported capture when matching unknown parameters using patterns
 +
for k, v in pairs( pframe.args ) do
 +
if v ~= '' then
 +
if ('string' == type (k)) then
 +
k = mw.ustring.gsub (k, '%d', cfg.date_names.local_digits); -- for enumerated parameters, translate 'local' digits to Western 0-9
 +
end
 +
if not validate( k, config.CitationClass ) then
 +
error_text = "";
 +
if type( k ) ~= 'string' then
 +
-- Exclude empty numbered parameters
 +
if v:match("%S+") ~= nil then
 +
error_text, error_state = set_error( 'text_ignored', {v}, true );
 +
end
 +
elseif validate( k:lower(), config.CitationClass ) then  
 +
error_text, error_state = set_error( 'parameter_ignored_suggest', {k, k:lower()}, true ); -- suggest the lowercase version of the parameter
 +
else
 +
if nil == suggestions.suggestions then -- if this table is nil then we need to load it
 +
if nil ~= string.find (frame:getTitle(), 'sandbox', 1, true) then -- did the {{#invoke:}} use sandbox version?
 +
suggestions = mw.loadData( 'Module:Citation/CS1/Suggestions/sandbox' ); -- use the sandbox version
 +
else
 +
suggestions = mw.loadData( 'Module:Citation/CS1/Suggestions' ); -- use the live version
 +
end
 +
end
 +
for pattern, param in pairs (suggestions.patterns) do -- loop through the patterns to see if we can suggest a proper parameter
 +
capture = k:match (pattern); -- the whole match if no caputre in pattern else the capture if a match
 +
if capture then -- if the pattern matches  
 +
param = substitute (param, capture); -- add the capture to the suggested parameter (typically the enumerator)
 +
if validate (param, config.CitationClass) then -- validate the suggestion to make sure that the suggestion is supported by this template (necessary for limited parameter lists)
 +
error_text, error_state = set_error ('parameter_ignored_suggest', {k, param}, true); -- set the suggestion error message
 +
else
 +
error_text, error_state = set_error( 'parameter_ignored', {param}, true ); -- suggested param not supported by this template
 +
end
 
end
 
end
 
end
 
end
Line 3,584: Line 3,605:  
end
 
end
 
missing_pipe_check (v); -- do we think that there is a parameter that is missing a pipe?
 
missing_pipe_check (v); -- do we think that there is a parameter that is missing a pipe?
+
-- TODO: is this the best place for this translation?
args[k] = v;
  −
elseif args[k] ~= nil or (k == 'postscript') then
   
args[k] = v;
 
args[k] = v;
 +
elseif args[k] ~= nil or (k == 'postscript') then -- here when v is empty string
 +
args[k] = v; -- why do we do this?  we don't support 'empty' parameters
 
end
 
end
 
end
 
end
Line 3,596: Line 3,617:  
end
 
end
 
end
 
end
return citation0( config, args)
+
return table.concat ({citation0( config, args), frame:extensionTag ('templatestyles', '', {src=styles})});
 
end
 
end
    
return cs1;
 
return cs1;

Navigation menu