Module:Citation/CS1/sandbox: Difference between revisions

m
use comma from Configuartions Module to support other languages
m (1 revision imported)
m (use comma from Configuartions Module to support other languages)
Line 1: Line 1:
--[[
--[[
History of changes since last sync: 2021-04-10
History of changes since last sync: 2022-07-01
 
2021-04-12: detect generic author, editor, etc names; see Help_talk:Citation_Style_1#junk_author_names
2021-04-19: emit error when |first= is wikilinked; see Help_talk:Citation_Style_1#wikilinks_in_|first=
2021-04-20: add "CS1 uses parameter: $1" properties tracking category; see Help_talk:Citation_Style_1#RFC_reclosed
2021-05-01: revise how date month-name auto-translation is enabled; see Help_talk:Citation_Style_1#i18n_tweaks
2021-05-02: add support to allow editors to see citations that emit properties cats; see Help_talk:Citation_Style_1#highlighting_cs1|2_citations_that_emit_properties_categories
2021-05-15: |url-status= without |archive-url= maint cat; see Help_talk:Citation_Style_1#url-status_usurped
2021-06-04: more consistent support of |type= with cite speech; see Help_talk:Citation_Style_1#Cite_lecture?
2021-06-06: check all but url-holding and insource-locator parameters for inappropriate urls; see Help_talk:Citation_Style_1#url_in_name_parameters
2021-06-25: recognize stand-alone |script-chapter= in cite encyclopedia; see Help_talk:Citation_Style_1#%7Cscript-chapter%3D_%28and_aliases%29_without_%7Cchapter%3D_%28or_aliases%29_not_recognized_in_cite_encyclopedia
2021-07-23: fix flaw in ref-duplicates-default detection; see Help_talk:Citation_Style_1#ref-duplicates-default_detector_flaw
2021-07-28: reworked error messaging; see Help_talk:Citation_Style_1#error_messaging
2021-08-01: added preview error summary; see Help_talk:Citation_Style_1#summary_messaging_in_the_preview_warning_header
2021-08-05: fix archive-url preview url; see Help_talk:Citation_Style_1#when_%7Carchive-url%3D_is_broken


2022-07-29: ~/Suggestions v. ~Suggestions/sandbox selection tweak; see Help_talk:Citation_Style_1#automatic_live_v._sandbox_suggestions-module_selection_tweak
2022-09-22: add support for |article_number= in journal and conference cites; see Help_talk:Citation_Style_1/Archive_85#article_numbers
2022-09-25: move list of single-letter second-level TLDs to ~/Configuration/sandbox; see Help_talk:Citation_Style_1#CS1_flagging_seemingly_fine_url_as_valid
2022-10-05: annotate namelist entries when interwikilinked; see Help_talk:Citation_Style_1#author-link=interlanguage
2022-10-17: fix unexpected 'preprint' parameter required error; see Help_talk:Citation_Style_1#Update_SSRN_limit
2022-10-21: inhibit leading punctuation when |display-authors=0 / display-editors=0 and |others= has a value; see Help_talk:Citation_Style_1#cite_journal:_starting_with_a_stop
2022-11-16: kern nested quotes in |quote=; see Help_talk:Citation_Style_1#Nested_quotations_in_the_quote_parameter
]]
]]


require('Module:No globals');
require('strict');


--[[--------------------------< F O R W A R D  D E C L A R A T I O N S >--------------------------------------
--[[--------------------------< F O R W A R D  D E C L A R A T I O N S >--------------------------------------
each of these counts against the Lua upvalue limit
each of these counts against the Lua upvalue limit
]]
]]


Line 33: Line 28:
local cfg = {}; -- table of configuration tables that are defined in Module:Citation/CS1/Configuration
local cfg = {}; -- table of configuration tables that are defined in Module:Citation/CS1/Configuration
local whitelist = {}; -- table of tables listing valid template parameter names; defined in Module:Citation/CS1/Whitelist
local whitelist = {}; -- table of tables listing valid template parameter names; defined in Module:Citation/CS1/Whitelist


--[[------------------< P A G E  S C O P E  V A R I A B L E S >---------------
--[[------------------< P A G E  S C O P E  V A R I A B L E S >---------------
declare variables here that have page-wide scope that are not brought in from
declare variables here that have page-wide scope that are not brought in from
other modules; that are created here and used here
other modules; that are created here and used here
]]
]]
local added_deprecated_cat; -- Boolean flag so that the category is added only once
local added_deprecated_cat; -- Boolean flag so that the category is added only once
local added_vanc_errs; -- Boolean flag so we only emit one Vancouver error / category
local added_vanc_errs; -- Boolean flag so we only emit one Vancouver error / category
Line 44: Line 43:
local is_preview_mode; -- true when article is in preview mode; false when using 'Preview page with this template' (previewing the module)
local is_preview_mode; -- true when article is in preview mode; false when using 'Preview page with this template' (previewing the module)
local is_sandbox; -- true when using sandbox modules to render citation
local is_sandbox; -- true when using sandbox modules to render citation


--[[--------------------------< F I R S T _ S E T >------------------------------------------------------------
--[[--------------------------< F I R S T _ S E T >------------------------------------------------------------
Line 166: Line 166:
end
end


for _, d in ipairs ({'cash', 'company', 'today', 'org'}) do -- look for single letter second level domain names for these top level domains
for _, d in ipairs (cfg.single_letter_2nd_lvl_domains_t) do -- look for single letter second level domain names for these top level domains
if domain:match ('%f[%w][%w]%.' .. d) then
if domain:match ('%f[%w][%w]%.' .. d) then
return true
return true
Line 473: Line 473:
local function kern_quotes (str)
local function kern_quotes (str)
local cap = '';
local cap = '';
local cap2 = '';
local wl_type, label, link;
local wl_type, label, link;


Line 480: Line 479:
if 1 == wl_type then -- [[D]] simple wikilink with or without quote marks
if 1 == wl_type then -- [[D]] simple wikilink with or without quote marks
if mw.ustring.match (str, '%[%[[\"“”\'‘’].+[\"“”\'‘’]%]%]') then -- leading and trailing quote marks
if mw.ustring.match (str, '%[%[[\"“”\'‘’].+[\"“”\'‘’]%]%]') then -- leading and trailing quote marks
str = utilities.substitute (cfg.presentation['kern-wl-both'], str);
str = utilities.substitute (cfg.presentation['kern-left'], str);
str = utilities.substitute (cfg.presentation['kern-right'], str);
elseif mw.ustring.match (str, '%[%[[\"“”\'‘’].+%]%]') then -- leading quote marks
elseif mw.ustring.match (str, '%[%[[\"“”\'‘’].+%]%]') then -- leading quote marks
str = utilities.substitute (cfg.presentation['kern-wl-left'], str);
str = utilities.substitute (cfg.presentation['kern-left'], str);
elseif mw.ustring.match (str, '%[%[.+[\"“”\'‘’]%]%]') then -- trailing quote marks
elseif mw.ustring.match (str, '%[%[.+[\"“”\'‘’]%]%]') then -- trailing quote marks
str = utilities.substitute (cfg.presentation['kern-wl-right'], str);
str = utilities.substitute (cfg.presentation['kern-right'], str);
end
end


Line 491: Line 491:
label = mw.ustring.gsub (label, '[‘’]', '\''); -- replace ‘’ (U+2018 & U+2019) with ' (typewriter single quote mark)
label = mw.ustring.gsub (label, '[‘’]', '\''); -- replace ‘’ (U+2018 & U+2019) with ' (typewriter single quote mark)


cap, cap2 = mw.ustring.match (label, "^([\"\'])([^\'].+)"); -- match leading double or single quote but not doubled single quotes (italic markup)
cap = mw.ustring.match (label, "^([\"\'][^\'].+)"); -- match leading double or single quote but not doubled single quotes (italic markup)
if utilities.is_set (cap) then
if utilities.is_set (cap) then
label = utilities.substitute (cfg.presentation['kern-left'], {cap, cap2});
label = utilities.substitute (cfg.presentation['kern-left'], cap);
end
end
cap, cap2 = mw.ustring.match (label, "^(.+[^\'])([\"\'])$") -- match trailing double or single quote but not doubled single quotes (italic markup)
cap = mw.ustring.match (label, "^(.+[^\'][\"\'])$") -- match trailing double or single quote but not doubled single quotes (italic markup)
if utilities.is_set (cap) then
if utilities.is_set (cap) then
label = utilities.substitute (cfg.presentation['kern-right'], {cap, cap2});
label = utilities.substitute (cfg.presentation['kern-right'], cap);
end
end
Line 541: Line 541:
lang = script_value:match('^(%l%l%l?)%s*:%s*%S.*'); -- get the language prefix or nil if there is no script
lang = script_value:match('^(%l%l%l?)%s*:%s*%S.*'); -- get the language prefix or nil if there is no script
if not utilities.is_set (lang) then
if not utilities.is_set (lang) then
utilities.set_message ('err_script_parameter', {script_param, 'missing title part'}); -- prefix without 'title'; add error message
utilities.set_message ('err_script_parameter', {script_param, cfg.err_msg_supl['missing title part']}); -- prefix without 'title'; add error message
return ''; -- script_value was just the prefix so return empty string
return ''; -- script_value was just the prefix so return empty string
end
end
Line 552: Line 552:
utilities.add_prop_cat ('script', {name, lang})
utilities.add_prop_cat ('script', {name, lang})
else
else
utilities.set_message ('err_script_parameter', {script_param, 'unknown language code'}); -- unknown script-language; add error message
utilities.set_message ('err_script_parameter', {script_param, cfg.err_msg_supl['unknown language code']}); -- unknown script-language; add error message
end
end
lang = ' lang="' .. lang .. '" '; -- convert prefix into a lang attribute
lang = ' lang="' .. lang .. '" '; -- convert prefix into a lang attribute
else
else
utilities.set_message ('err_script_parameter', {script_param, 'invalid language code'}); -- invalid language code; add error message
utilities.set_message ('err_script_parameter', {script_param, cfg.err_msg_supl['invalid language code']}); -- invalid language code; add error message
lang = ''; -- invalid so set lang to empty string
lang = ''; -- invalid so set lang to empty string
end
end
else
else
utilities.set_message ('err_script_parameter', {script_param, 'missing prefix'}); -- no language code prefix; add error message
utilities.set_message ('err_script_parameter', {script_param, cfg.err_msg_supl['missing prefix']}); -- no language code prefix; add error message
end
end
script_value = utilities.substitute (cfg.presentation['bdi'], {lang, script_value}); -- isolate in case script is RTL
script_value = utilities.substitute (cfg.presentation['bdi'], {lang, script_value}); -- isolate in case script is RTL
Line 570: Line 570:
--[[--------------------------< 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 >------------------------------------------


Initially for |title= and |script-title=, this function concatenates those two parameter values after the script value has been  
Initially for |title= and |script-title=, this function concatenates those two parameter values after the script
wrapped in <bdi> tags.
value has been wrapped in <bdi> tags.
 
]]
]]


Line 775: Line 776:
if mw.ustring.find (v, cfg.indic_script) then -- it's ok if one of the Indic scripts
if mw.ustring.find (v, cfg.indic_script) then -- it's ok if one of the Indic scripts
position = nil; -- unset position
position = nil; -- unset position
elseif cfg.emoji[mw.ustring.codepoint (v, position+1)] then -- is zwj followed by a character listed in emoji{}?
elseif cfg.emoji_t[mw.ustring.codepoint (v, position+1)] then -- is zwj followed by a character listed in emoji{}?
position = nil; -- unset position
position = nil; -- unset position
end
end
Line 897: Line 898:


return cfg.title_types [cite_class] or ''; -- set template's default title type; else empty string for concatenation
return cfg.title_types [cite_class] or ''; -- set template's default title type; else empty string for concatenation
end
--[[--------------------------< H Y P H E N _ T O _ D A S H >--------------------------------------------------
Converts a hyphen to a dash under certain conditions.  The hyphen must separate
like items; unlike items are 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.
str may be a comma- or semicolon-separated list
]]
local function hyphen_to_dash( str )
if not utilities.is_set (str) then
return str;
end
local accept; -- Boolean
str = str:gsub ("(%(%(.-%)%))", function(m) return m:gsub(",", ","):gsub(";", ";") end) -- replace commas and semicolons in accept-as-written markup with similar unicode characters so they'll be ignored during the split
str = str:gsub ('&[nm]dash;', {['&ndash;'] = '–', ['&mdash;'] = '—'}); -- replace &mdash; and &ndash; entities with their characters; semicolon mucks up the text.split
str = str:gsub ('&#45;', '-'); -- replace HTML numeric entity with hyphen character
str = str:gsub ('&nbsp;', ' '); -- replace &nbsp; entity with generic keyboard space character
local out = {};
local list = mw.text.split (str, '%s*[,;]%s*'); -- split str at comma or semicolon separators if there are any
for _, item in ipairs (list) do -- for each item in the list
item, accept = utilities.has_accept_as_written (item); -- remove accept-this-as-written markup when it wraps all of item
if not accept and mw.ustring.match (item, '^%w*[%.%-]?%w+%s*[%-–—]%s*%w*[%.%-]?%w+$') then -- if a hyphenated range or has endash or emdash separators
if item:match ('^%a+[%.%-]?%d+%s*%-%s*%a+[%.%-]?%d+$') or -- letterdigit hyphen letterdigit (optional separator between letter and digit)
item:match ('^%d+[%.%-]?%a+%s*%-%s*%d+[%.%-]?%a+$') or -- digitletter hyphen digitletter (optional separator between digit and letter)
item:match ('^%d+[%.%-]%d+%s*%-%s*%d+[%.%-]%d+$') or -- digit separator digit hyphen digit separator digit
item:match ('^%d+%s*%-%s*%d+$') or -- digit hyphen digit
item:match ('^%a+%s*%-%s*%a+$') then -- letter hyphen letter
item = item:gsub ('(%w*[%.%-]?%w+)%s*%-%s*(%w*[%.%-]?%w+)', '%1–%2'); -- replace hyphen, remove extraneous space characters
else
item = mw.ustring.gsub (item, '%s*[–—]%s*', '–'); -- for endash or emdash separated ranges, replace em with en, remove extraneous whitespace
end
end
table.insert (out, item); -- add the (possibly modified) item to the output table
end
local temp_str = ''; -- concatenate the output table into a comma separated string
temp_str, accept = utilities.has_accept_as_written (table.concat (out, ', ')); -- remove accept-this-as-written markup when it wraps all of concatenated out
if accept then
temp_str = utilities.has_accept_as_written (str); -- when global markup removed, return original str; do it this way to suppress boolean second return value
return temp_str:gsub(",", ","):gsub(";", ";");
else
return temp_str:gsub(",", ","):gsub(";", ";"); -- else, return assembled temp_str
end
end
end


Line 1,046: Line 987:
--[[--------------------------< I S _ S U F F I X >-----------------------------
--[[--------------------------< I S _ S U F F I X >-----------------------------


returns true is suffix is properly formed Jr, Sr, or ordinal in the range 1–9.
returns true if suffix is properly formed Jr, Sr, or ordinal in the range 1–9.
Puncutation not allowed.
Puncutation not allowed.


Line 1,179: Line 1,120:




--[[--------------------------< L I S T _ P E O P L E >--------------------------
--[[--------------------------< I N T E R W I K I _ P R E F I X E N _ G E T >----------------------------------


Formats a list of people (authors, contributors, editors, interviewers, translators)
extract interwiki prefixen from <value>.  Returns two one or two values:
 
false – no prefixen
names in the list will be linked when
nil – prefix exists but not recognized
|<name>-link= has a value
project prefix, language prefix – when value has either of:
|<name>-mask- does NOT have a value; masked names are presumed to have been
:<project>:<language>:<article>
rendered previously so should have been linked there
:<language>:<project>:<article>
project prefix, nil – when <value> has only a known single-letter prefix
nil, language prefix – when <value> has only a known language prefix


when |<name>-mask=0, the associated name is not rendered
accepts single-letter project prefixen: 'd' (wikidata), 's' (wikisource), and 'w' (wikipedia) prefixes; at this
writing, the other single-letter prefixen (b (wikibook), c (commons), m (meta), n (wikinews), q (wikiquote), and
v (wikiversity)) are not supported.


]]
]]


local function list_people (control, people, etal)
local function interwiki_prefixen_get (value, is_link)
if not value:find (':%l+:') then -- if no prefix
return false; -- abandon; boolean here to distinguish from nil fail returns later
end
 
local prefix_patterns_linked_t = { -- sequence of valid interwiki and inter project prefixen
'^%[%[:([dsw]):(%l%l+):', -- wikilinked; project and language prefixes
'^%[%[:(%l%l+):([dsw]):', -- wikilinked; language and project prefixes
'^%[%[:([dsw]):', -- wikilinked; project prefix
'^%[%[:(%l%l+):', -- wikilinked; language prefix
}
local prefix_patterns_unlinked_t = { -- sequence of valid interwiki and inter project prefixen
'^:([dsw]):(%l%l+):', -- project and language prefixes
'^:(%l%l+):([dsw]):', -- language and project prefixes
'^:([dsw]):', -- project prefix
'^:(%l%l+):', -- language prefix
}
local cap1, cap2;
for _, pattern in ipairs ((is_link and prefix_patterns_linked_t) or prefix_patterns_unlinked_t) do
cap1, cap2 = value:match (pattern);
if cap1 then
break; -- found a match so stop looking
end
end
if cap1 and cap2 then -- when both then :project:language: or :language:project: (both forms allowed)
if 1 == #cap1 then -- length == 1 then :project:language:
if cfg.inter_wiki_map[cap2] then -- is language prefix in the interwiki map?
return cap1, cap2; -- return interwiki project and interwiki language
end
else -- here when :language:project:
if cfg.inter_wiki_map[cap1] then -- is language prefix in the interwiki map?
return cap2, cap1; -- return interwiki project and interwiki language
end
end
return nil; -- unknown interwiki language
elseif not (cap1 or cap2) then -- both are nil?
return nil; -- we got something that looks like a project prefix but isn't; return fail
elseif 1 == #cap1 then -- here when one capture
return cap1, nil; -- length is 1 so return project, nil language
else -- here when one capture and its length it more than 1
if cfg.inter_wiki_map[cap1] then -- is language prefix in the interwiki map?
return nil, cap1; -- return nil project, language
end
end
end
 
 
--[[--------------------------< L I S T _ P E O P L E >--------------------------
 
Formats a list of people (authors, contributors, editors, interviewers, translators)
 
names in the list will be linked when
|<name>-link= has a value
|<name>-mask- does NOT have a value; masked names are presumed to have been
rendered previously so should have been linked there
 
when |<name>-mask=0, the associated name is not rendered
 
]]
 
local function list_people (control, people, etal)
local sep;
local sep;
local namesep;
local namesep;
Line 1,246: Line 1,254:
one = utilities.make_wikilink (person.link, one); -- link author/editor
one = utilities.make_wikilink (person.link, one); -- link author/editor
end
end
if one then -- if <one> has a value (name, mdash replacement, or mask text replacement)
if one then -- if <one> has a value (name, mdash replacement, or mask text replacement)
local proj, tag = interwiki_prefixen_get (one, true); -- get the interwiki prefixen if present
if 'w' == proj and ('Wikipedia' == mw.site.namespaces.Project['name']) then
proj = nil; -- for stuff like :w:de:<article>, :w is unnecessary TODO: maint cat?
end
if proj then
proj = ({['d'] = 'Wikidata', ['s'] = 'Wikisource', ['w'] = 'Wikipedia'})[proj]; -- :w (wikipedia) for linking from a non-wikipedia project
if proj then
one = one .. utilities.wrap_style ('interproj', proj); -- add resized leading space, brackets, static text, language name
tag = nil; -- unset; don't do both project and language
end
end
if tag == cfg.this_wiki_code then
tag = nil; -- stuff like :en:<article> at en.wiki is pointless TODO: maint cat?
end
if tag then
local lang = cfg.lang_code_remap[tag] or cfg.mw_languages_by_tag_t[tag];
if lang then -- error messaging done in extract_names() where we know parameter names
one = one .. utilities.wrap_style ('interwiki', lang); -- add resized leading space, brackets, static text, language name
end
end
-- local tag = one:match ('^%[%[:(%l[^:]+):');
-- local lang = tag and (cfg.lang_code_remap[tag] or cfg.mw_languages_by_tag_t[tag]); --TODO: if tag but not lang then error message err_bad_paramlink
-- if tag and lang then -- error messaging done in extract_names() where we know parameter names
-- one = one .. utilities.wrap_style ('interwiki', lang); -- add resized leading space, brackets, static text, language name
-- end
table.insert (name_list, one); -- add it to the list of names
table.insert (name_list, one); -- add it to the list of names
table.insert (name_list, sep_one); -- add the proper name-list separator
table.insert (name_list, sep_one); -- add the proper name-list separator
Line 1,276: Line 1,312:
return result, count; -- return name-list string and count of number of names (count used for editor names only)
return result, count; -- return name-list string and count of number of names (count used for editor names only)
end
end


--[[--------------------< M A K E _ C I T E R E F _ I D >-----------------------
--[[--------------------< M A K E _ C I T E R E F _ I D >-----------------------
Line 1,380: Line 1,417:




--[[-------------------< N A M E _ H A S _ E D _ M A R K U P >------------------
--[[-----------------< N A M E _ H A S _ M U L T _ N A M E S >------------------
 
Evaluates the content of author and editor parameters for extraneous editor annotations:
ed, ed., eds, (Ed.), etc. These annotations do not belong in author parameters and
are redundant in editor parameters.  If found, the function adds the editor markup
maintenance category.
 
returns nothing
 
]]
 
local function name_has_ed_markup (name, list_name)
local patterns = cfg.editor_markup_patterns; -- get patterns from configuration
 
if utilities.is_set (name) then
for _, pattern in ipairs (patterns) do -- spin through patterns table and
if name:match (pattern) then
utilities.set_message ('maint_extra_text_names', cfg.special_case_translation [list_name]); -- add a maint cat for this template
break;
end
end
end
end
 
 
--[[-----------------< N A M E _ H A S _ M U L T _ N A M E S >------------------


Evaluates the content of last/surname (authors etc.) parameters for multiple names.
Evaluates the content of last/surname (authors etc.) parameters for multiple names.
Line 1,438: Line 1,450:




--[[--------------------------< I S _ G E N E R I C >----------------------------------------------------------
--[=[-------------------------< I S _ G E N E R I C >----------------------------------------------------------
 
Compares values assigned to various parameters according to the string provided as <item> in the function call.
<item> can have on of two values:
'generic_names' – for name-holding parameters: |last=, |first=, |editor-last=, etc
'generic_titles' – for |title=


Compares values assigned to various parameter according to the string provided as <item> in the function call:
There are two types of generic tests.  The 'accept' tests look for a pattern that should not be rejected by the
'generic_names': |last=, |first=, |editor-last=, etc value against list of known generic name patterns
'reject' test.  For example,
'generic_titles': |title=
|author=[[John Smith (author)|Smith, John]]
Returns true when pattern matches; nil else
would be rejected by the 'author' reject test.  But piped wikilinks with 'author' disambiguation should not be
rejected so the 'accept' test prevents that from happening.  Accept tests are always performed before reject
tests.


The k/v pairs in cfg.special_case_translation[item] each contain two tables, one for English and one for another
Each of the 'accept' and 'reject' sequence tables hold tables for en.wiki (['en']) and local.wiki (['local'])
'local' language.Each of those tables contain another table that holds the string or pattern (whole or fragment)
that each can hold a test sequence table  The sequence table holds, at index [1], a test pattern, and, at index
in index [1]. index [2] is a Boolean that tells string.find() or mw.ustring.find() to do plain-text search (true)
[2], a boolean control value.  The control value tells string.find() or mw.ustring.find() to do plain-text search (true)
or a pattern search (false).  The intent of all this complexity is to make these searches as fast as possible so
or a pattern search (false).  The intent of all this complexity is to make these searches as fast as possible so
that we don't run out of processing time on very large articles.
that we don't run out of processing time on very large articles.


]]
Returns
true when a reject test finds the pattern or string
false when an accept test finds the pattern or string
nil else
 
]=]
 
local function is_generic (item, value, wiki)
local test_val;
local str_lower = { -- use string.lower() for en.wiki (['en']) and use mw.ustring.lower() or local.wiki (['local'])
['en'] = string.lower,
['local'] = mw.ustring.lower,
}
local str_find = { -- use string.find() for en.wiki (['en']) and use mw.ustring.find() or local.wiki (['local'])
['en'] = string.find,
['local'] = mw.ustring.find,
}
 
local function test (val, test_t, wiki) -- local function to do the testing; <wiki> selects lower() and find() functions
val = test_t[2] and str_lower[wiki](value) or val; -- when <test_t[2]> set to 'true', plaintext search using lowercase value
return str_find[wiki] (val, test_t[1], 1, test_t[2]); -- return nil when not found or matched
end
local test_types_t = {'accept', 'reject'}; -- test accept patterns first, then reject patterns
local wikis_t = {'en', 'local'}; -- do tests for each of these keys; en.wiki first, local.wiki second


local function is_generic (item, value)
for _, test_type in ipairs (test_types_t) do -- for each test type
value = mw.ustring.lower(value); -- switch value to lower case
for _, generic_value in pairs (cfg.special_case_translation[item][test_type]) do -- spin through the list of generic value fragments to accept or reject
for _, generic_value in ipairs (cfg.special_case_translation[item]) do -- spin through the list of known generic value fragments
for _, wiki in ipairs (wikis_t) do
if value:find (generic_value['en'][1], 1, generic_value['en'][2]) then
if generic_value[wiki] then
return true; -- found English generic value so done
if test (value, generic_value[wiki], wiki) then -- go do the test
elseif generic_value['local'] then -- to keep work load down, generic_<value>['local'] should be nil except when there is a local version of the generic value
return ('reject' == test_type); -- param value rejected, return true; false else
if mw.ustring.find (value, generic_value['local'][1], 1, generic_value['local'][2]) then -- mw.ustring() because might not be Latin script
end
return true; -- found local generic value so done
end
end
end
end
end
Line 1,468: Line 1,511:


--[[--------------------------< N A M E _ I S _ G E N E R I C >------------------------------------------------
--[[--------------------------< N A M E _ I S _ G E N E R I C >------------------------------------------------
calls is_generic() to determine if <name> is a 'generic name' listed in cfg.generic_names; <name_alias> is the
parameter name used in error messaging


]]
]]
Line 1,493: Line 1,539:
if not accept_name then -- <last> not wrapped in accept-as-written markup
if not accept_name then -- <last> not wrapped in accept-as-written markup
name_has_mult_names (last, list_name); -- check for multiple names in the parameter (last only)
name_has_mult_names (last, list_name); -- check for multiple names in the parameter (last only)
name_has_ed_markup (last, list_name); -- check for extraneous 'editor' annotation
name_is_numeric (last, list_name); -- check for names that are composed of digits and punctuation
name_is_numeric (last, list_name); -- check for names that are composed of digits and punctuation
name_is_generic (last, last_alias); -- check for names found in the generic names list
name_is_generic (last, last_alias); -- check for names found in the generic names list
Line 1,503: Line 1,548:


if not accept_name then -- <first> not wrapped in accept-as-written markup
if not accept_name then -- <first> not wrapped in accept-as-written markup
name_has_ed_markup (first, list_name); -- check for extraneous 'editor' annotation
name_is_numeric (first, list_name); -- check for names that are composed of digits and punctuation
name_is_numeric (first, list_name); -- check for names that are composed of digits and punctuation
name_is_generic (first, first_alias); -- check for names found in the generic names list
name_is_generic (first, first_alias); -- check for names found in the generic names list
Line 1,519: Line 1,563:


--[[----------------------< E X T R A C T _ N A M E S >-------------------------
--[[----------------------< E X T R A C T _ N A M E S >-------------------------
Gets name list from the input arguments
Gets name list from the input arguments


Line 1,556: Line 1,601:
link, link_alias = utilities.select_one ( args, cfg.aliases[list_name .. '-Link'], 'err_redundant_parameters', i );
link, link_alias = utilities.select_one ( args, cfg.aliases[list_name .. '-Link'], 'err_redundant_parameters', i );
mask = utilities.select_one ( args, cfg.aliases[list_name .. '-Mask'], 'err_redundant_parameters', i );
mask = utilities.select_one ( args, cfg.aliases[list_name .. '-Mask'], 'err_redundant_parameters', i );
 
if last then -- error check |lastn= alias for unknown interwiki link prefix; done here because this is where we have the parameter name
local project, language = interwiki_prefixen_get (last, true); -- true because we expect interwiki links in |lastn= to be wikilinked
if nil == project and nil == language then -- when both are nil
utilities.set_message ('err_bad_paramlink', last_alias); -- not known, emit an error message -- TODO: err_bad_interwiki?
last = utilities.remove_wiki_link (last); -- remove wikilink markup; show display value only
end
-- local tag = last:match ('^%[%[:(%l+):'); -- must look like an interwiki link
-- if tag and not cfg.inter_wiki_map[tag] then -- tag must exist in the interwiki map
-- utilities.set_message ('err_bad_paramlink', last_alias); -- not known, emit an error message -- TODO: err_bad_interwiki?
-- last = utilities.remove_wiki_link (last); -- remove wikilink markup; show display value only
-- end
end
if link then -- error check |linkn= alias for unknown interwiki link prefix
local project, language = interwiki_prefixen_get (link, false); -- false because we wiki links in |author-linkn= is an error
if nil == project and nil == language then -- when both are nil
utilities.set_message ('err_bad_paramlink', link_alias); -- not known, emit an error message -- TODO: err_bad_interwiki?
link = nil; -- unset so we don't link
link_alias = nil;
end
-- local tag = link:match ('^:(%l+):'); -- must look like an interwiki link (without link markup)
-- if tag and not cfg.inter_wiki_map[tag] then -- tag must exist in the interwiki map
-- utilities.set_message ('err_bad_paramlink', link_alias); -- not known, emit an error message -- TODO: err_bad_interwiki?
-- link = nil; -- unset so we don't link
-- link_alias = nil;
-- end
end
last, etal = name_has_etal (last, etal, false, last_alias); -- find and remove variations on et al.
last, etal = name_has_etal (last, etal, false, last_alias); -- find and remove variations on et al.
first, etal = name_has_etal (first, etal, false, first_alias); -- find and remove variations on et al.
first, etal = name_has_etal (first, etal, false, first_alias); -- find and remove variations on et al.
Line 1,594: Line 1,667:




--[[---------------------< G E T _ I S O 6 3 9 _ C O D E >----------------------
--[[--------------------------< N A M E _ T A G _ G E T >------------------------------------------------------


Validates language names provided in |language= parameter if not an ISO639-1 or 639-2 code.
attempt to decode |language=<lang_param> and return language name and matching tag; nil else.


Returns the language name and associated two- or three-character code. Because
This function looks for:
case of the source may be incorrect or different from the case that WikiMedia uses,
<lang_param> as a tag in cfg.lang_code_remap{}
the name comparisons are done in lower case and when a match is found, the Wikimedia
<lang_param> as a name in cfg.lang_name_remap{}
version (assumed to be correct) is returned along with the codeWhen there is no
match, we return the original language name string.
<lang_param> as a name in cfg.mw_languages_by_name_t
<lang_param> as a tag in cfg.mw_languages_by_tag_t
when those fail, presume that <lang_param> is an IETF-like tag that MediaWiki does not recognizeStrip all
script, region, variant, whatever subtags from <lang_param> to leave just a two or three character language tag
and look for the new <lang_param> in cfg.mw_languages_by_tag_t{}


mw.language.fetchLanguageNames(<local wiki language>, 'all') returns a list of
on success, returns name (in properly capitalized form) and matching tag (in lowercase); on failure returns nil
languages that in some cases may include extensions. For example, code 'cbk-zam'
and its associated name 'Chavacano de Zamboanga' (MediaWiki does not support
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.
A list of language names and codes supported by fetchLanguageNames() can be found
at Template:Citation Style documentation/language/doc


Names that are included in the list will be found if that name is provided in the
]]
|language= parameter.  For example, if |language=Chavacano de Zamboanga, that name
will be found with the associated code 'cbk-zam'.  When names are found and the
associated code is not two or three characters, this function returns only the
WikiMedia language name.


Some language names have multiple entries under different codes:
local function name_tag_get (lang_param)
Aromanian has code rup and code roa-rup
local lang_param_lc = mw.ustring.lower (lang_param); -- use lowercase as an index into the various tables
When this occurs, this function returns the language name and the 2- or 3-character code
local name;
local tag;


Adapted from code taken from Module:Check ISO 639-1.
name = cfg.lang_code_remap[lang_param_lc]; -- assume <lang_param_lc> is a tag; attempt to get remapped language name
if name then -- when <name>, <lang_param> is a tag for a remapped language name
return name, lang_param_lc; -- so return <name> from remap and <lang_param_lc>
end


]]
tag = lang_param_lc:match ('^(%a%a%a?)%-.*'); -- still assuming that <lang_param_lc> is a tag; strip script, region, variant subtags
name = cfg.lang_code_remap[tag]; -- attempt to get remapped language name with language subtag only
if name then -- when <name>, <tag> is a tag for a remapped language name
return name, tag; -- so return <name> from remap and <tag>
end
 
if cfg.lang_name_remap[lang_param_lc] then -- not a tag, assume <lang_param_lc> is a name; attempt to get remapped language tag
return cfg.lang_name_remap[lang_param_lc][1], cfg.lang_name_remap[lang_param_lc][2]; -- for this <lang_param_lc>, return a (possibly) new name and appropriate tag
end


local function get_iso639_code (lang, this_wiki_code)
tag = cfg.mw_languages_by_name_t[lang_param_lc]; -- assume that <lang_param_lc> is a language name; attempt to get its matching tag
if 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 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
if tag then
return cfg.mw_languages_by_tag_t[tag], tag; -- <lang_param_lc> is a name so return the name from the table and <tag>
end
end


local ietf_code; -- because some languages have both IETF-like codes and ISO 639-like codes
name = cfg.mw_languages_by_tag_t[lang_param_lc]; -- assume that <lang_param_lc> is a tag; attempt to get its matching language name
local ietf_name;
local langlc = mw.ustring.lower (lang); -- lower-case version for comparisons
if name then
return name, lang_param_lc; -- <lang_param_lc> is a tag so return it and <name>
end
tag = lang_param_lc:match ('^(%a%a%a?)%-.*'); -- is <lang_param_lc> an IETF-like tag that MediaWiki doesn't recognize? <tag> gets the language subtag; nil else


for code, name in pairs (cfg.languages) do -- scan the list to see if we can find our language
if tag then
if langlc == mw.ustring.lower (name) then
name = cfg.mw_languages_by_tag_t[tag]; -- attempt to get a language name using the shortened <tag>
if 2 == #code or 3 == #code then -- two- or three-character codes only; IETF extensions not supported
if name then
return name, code; -- so return the name and the code
return name, tag; -- <lang_param_lc> is an unrecognized IETF-like tag so return <name> and language subtag
end
ietf_code = code; -- remember that we found an IETF-like code and save its name
ietf_name = name; -- but keep looking for a 2- or 3-char code
end
end
end
end
-- didn't find name with 2- or 3-char code; if IETF-like code found return
return ietf_code and ietf_name or lang; -- associated name; return original language text else
end
end


Line 1,672: Line 1,750:


local function language_parameter (lang)
local function language_parameter (lang)
local code; -- the two- or three-character language code
local tag; -- some form of IETF-like language tag; language subtag with optional region, sript, vatiant, etc subtags
local lang_subtag; -- ve populates |language= with mostly unecessary region subtags the MediaWiki does not recognize; this is the base language subtag
local name; -- the language name
local name; -- the language name
local language_list = {}; -- table of language names to be rendered
local language_list = {}; -- table of language names to be rendered
local names_table = {}; -- table made from the value assigned to |language=
local names_t = {}; -- table made from the value assigned to |language=


local this_wiki_name = mw.language.fetchLanguageName (cfg.this_wiki_code, cfg.this_wiki_code); -- get this wiki's language name
local this_wiki_name = mw.language.fetchLanguageName (cfg.this_wiki_code, cfg.this_wiki_code); -- get this wiki's language name


names_table = mw.text.split (lang, '%s*,%s*'); -- names should be a comma separated list
names_t = mw.text.split (lang, '%s*,%s*'); -- names should be a comma separated list


for _, lang in ipairs (names_table) do -- reuse lang
for _, lang in ipairs (names_t) do -- reuse lang here because we don't yet know if lang is a language name or a language tag
name = cfg.lang_code_remap[lang:lower()]; -- first see if this is a code that is not supported by MediaWiki but is in remap
name, tag = name_tag_get (lang); -- attempt to get name/tag pair for <lang>; <name> has proper capitalization; <tag> is lowercase


if name then -- there was a remapped code so
if utilities.is_set (tag) then
if not lang:match ('^%a%a%a?%-x%-%a+$') then -- if not a private IETF tag
lang_subtag = tag:gsub ('^(%a%a%a?)%-.*', '%1'); -- for categorization, strip any IETF-like tags from language tag
lang = lang:gsub ('^(%a%a%a?)%-.*', '%1'); -- strip IETF tags from code
end
else
lang = lang:gsub ('^(%a%a%a?)%-.*', '%1'); -- strip any IETF-like tags from code
if 2 == lang:len() or 3 == lang:len() then -- if two-or three-character code
name = mw.language.fetchLanguageName (lang:lower(), cfg.this_wiki_code); -- get language name if |language= is a proper code
end
end


if utilities.is_set (name) then -- if |language= specified a valid code
if cfg.this_wiki_code ~= lang_subtag then -- when the language is not the same as this wiki's language
code = lang:lower(); -- save it
if 2 == lang_subtag:len() then -- and is a two-character tag
else
-- utilities.add_prop_cat ('foreign-lang-source', {name, lang_subtag}, lang_subtag); -- categorize it; tag appended to allow for multiple language categorization
name, code = get_iso639_code (lang, cfg.this_wiki_code); -- attempt to get code from name (assign name here so that we are sure of proper capitalization)
utilities.add_prop_cat ('foreign-lang-source', {name, tag}, lang_subtag); -- categorize it; tag appended to allow for multiple language categorization
end
else -- or is a recognized language (but has a three-character tag)
utilities.add_prop_cat ('foreign-lang-source-2', {lang_subtag}, lang_subtag); -- categorize it differently TODO: support multiple three-character tag categories per cs1|2 template?
if utilities.is_set (code) then -- only 2- or 3-character codes
name = cfg.lang_code_remap[code] or name; -- override wikimedia when they misuse language codes/names
 
if cfg.this_wiki_code ~= code then -- when the language is not the same as this wiki's language
if 2 == code:len() then -- and is a two-character code
utilities.add_prop_cat ('foreign-lang-source', {name, code}, code); -- categorize it; code appended to allow for multiple language categorization
else -- or is a recognized language (but has a three-character code)
utilities.add_prop_cat ('foreign-lang-source-2', {code}, code); -- categorize it differently TODO: support multiple three-character code categories per cs1|2 template
end
end
elseif cfg.local_lang_cat_enable then -- when the language and this wiki's language are the same and categorization is enabled
elseif cfg.local_lang_cat_enable then -- when the language and this wiki's language are the same and categorization is enabled
utilities.add_prop_cat ('local-lang-source', {name, code}); -- categorize it
utilities.add_prop_cat ('local-lang-source', {name, lang_subtag}); -- categorize it
end
end
else
else
name = lang; -- return whatever <lang> has so that we show something
utilities.set_message ('maint_unknown_lang'); -- add maint category if not already added
utilities.set_message ('maint_unknown_lang'); -- add maint category if not already added
end
end
Line 1,722: Line 1,786:
   
   
name = utilities.make_sep_list (#language_list, language_list);
name = utilities.make_sep_list (#language_list, language_list);
 
if (1 == #language_list) and (lang_subtag == cfg.this_wiki_code) then -- when only one language, find lang name in this wiki lang name; for |language=en-us, 'English' in 'American English'
if this_wiki_name == name then
return ''; -- if one language and that language is this wiki's return an empty string (no annotation)
return ''; -- if one language and that language is this wiki's return an empty string (no annotation)
end
end
Line 1,731: Line 1,794:
]]
]]
end
end


--[[-----------------------< S E T _ C S _ S T Y L E >--------------------------
--[[-----------------------< S E T _ C S _ S T Y L E >--------------------------
Gets the default CS style configuration for the given mode.
Gets the default CS style configuration for the given mode.
Returns default separator and either postscript as passed in or the default.
Returns default separator and either postscript as passed in or the default.
In CS1, the default postscript and separator are '.'.
In CS1, the default postscript and separator are '.'.
In CS2, the default postscript is the empty string and the default separator is ','.
In CS2, the default postscript is the empty string and the default separator is ','.
]]
]]
local function set_cs_style (postscript, mode)
local function set_cs_style (postscript, mode)
if utilities.is_set(postscript) then
if utilities.is_set(postscript) then
Line 1,752: Line 1,819:


--[[--------------------------< S E T _ S T Y L E >-----------------------------
--[[--------------------------< S E T _ S T Y L E >-----------------------------
Sets the separator and postscript styles. Checks the |mode= first and the
Sets the separator and postscript styles. Checks the |mode= first and the
#invoke CitationClass second. Removes the postscript if postscript == none.
#invoke CitationClass second. Removes the postscript if postscript == none.
]]
]]
local function set_style (mode, postscript, cite_class)
local function set_style (mode, postscript, cite_class)
local sep;
local sep;
Line 2,154: Line 2,224:




--[[-------------------------< F O R M A T _ V O L U M E _ I S S U E >----------------------------------------
--[[-------------------------< F O R M A T _ V O L U M E _ I S S U E >-----------------------------------------


returns the concatenation of the formatted volume and issue parameters as a single string; or formatted volume
returns the concatenation of the formatted volume and issue (or journal article number) parameters as a single
or formatted issue, or an empty string if neither are set.
string; or formatted volume or formatted issue, or an empty string if neither are set.


]]
]]
local function format_volume_issue (volume, issue, cite_class, origin, sepc, lower)
local function format_volume_issue (volume, issue, article, cite_class, origin, sepc, lower)
if not utilities.is_set (volume) and not utilities.is_set (issue) then
if not utilities.is_set (volume) and not utilities.is_set (issue) and not utilities.is_set (article) then
return '';
return '';
end
end
-- same condition as in format_pages_sheets()
local is_journal = 'journal' == cite_class or (utilities.in_array (cite_class, {'citation', 'map', 'interview'}) and 'journal' == origin);
local is_numeric_vol = volume and (volume:match ('^[MDCLXVI]+$') or volume:match ('^%d+$')); -- is only uppercase roman numerals or only digits?
local is_long_vol = volume and (4 < mw.ustring.len(volume)); -- is |volume= value longer than 4 characters?
if 'magazine' == cite_class or (utilities.in_array (cite_class, {'citation', 'map'}) and 'magazine' == origin) then
if volume and (not is_numeric_vol and is_long_vol) then -- when not all digits or Roman numerals, is |volume= longer than 4 characters?
if utilities.is_set (volume) and utilities.is_set (issue) then
utilities.add_prop_cat ('long-vol'); -- yes, add properties cat
return wrap_msg ('vol-no', {sepc, hyphen_to_dash (volume), issue}, lower);
end
elseif utilities.is_set (volume) then
 
return wrap_msg ('vol', {sepc, hyphen_to_dash (volume)}, lower);
if is_journal then -- journal-style formatting
else
local vol = '';
return wrap_msg ('issue', {sepc, issue}, lower);
 
if utilities.is_set (volume) then
if is_numeric_vol then -- |volume= value all digits or all uppercase Roman numerals?
vol = utilities.substitute (cfg.presentation['vol-bold'], {sepc, volume}); -- render in bold face
elseif is_long_vol then -- not all digits or Roman numerals; longer than 4 characters?
vol = utilities.substitute (cfg.messages['j-vol'], {sepc, utilities.hyphen_to_dash (volume)}); -- not bold
else -- four or fewer characters
vol = utilities.substitute (cfg.presentation['vol-bold'], {sepc, utilities.hyphen_to_dash (volume)}); -- bold
end
end
end
vol = vol .. (utilities.is_set (issue) and utilities.substitute (cfg.messages['j-issue'], issue) or '')
vol = vol .. (utilities.is_set (article) and utilities.substitute (cfg.messages['j-article-num'], article) or '')
return vol;
end
end
 
if 'podcast' == cite_class and utilities.is_set (issue) then
if 'podcast' == cite_class and utilities.is_set (issue) then
return wrap_msg ('issue', {sepc, issue}, lower);
return wrap_msg ('issue', {sepc, issue}, lower);
end
end
 
local vol = ''; -- here for all cites except magazine
if 'conference' == cite_class and utilities.is_set (article) then -- |article-number= supported only in journal and conference cites
if utilities.is_set (volume) and utilities.is_set (article) then -- both volume and article number
if utilities.is_set (volume) then
return wrap_msg ('vol-art', {sepc, utilities.hyphen_to_dash (volume), article}, lower);
if volume:match ('^[MDCLXVI]+$') or volume:match ('^%d+$') then -- volume value is all digits or all uppercase Roman numerals
elseif utilities.is_set (article) then -- article number alone; when volume alone, handled below
vol = utilities.substitute (cfg.presentation['vol-bold'], {sepc, volume}); -- render in bold face
return wrap_msg ('art', {sepc, article}, lower);
elseif (4 < mw.ustring.len(volume)) then -- not all digits or Roman numerals and longer than 4 characters
vol = utilities.substitute (cfg.messages['j-vol'], {sepc, hyphen_to_dash (volume)}); -- not bold
utilities.add_prop_cat ('long-vol');
else -- four or less characters
vol = utilities.substitute (cfg.presentation['vol-bold'], {sepc, hyphen_to_dash (volume)}); -- bold
end
end
end
end
if utilities.is_set (issue) then
 
return vol .. utilities.substitute (cfg.messages['j-issue'], issue);
-- all other types of citation
if utilities.is_set (volume) and utilities.is_set (issue) then
return wrap_msg ('vol-no', {sepc, utilities.hyphen_to_dash (volume), issue}, lower);
elseif utilities.is_set (volume) then
return wrap_msg ('vol', {sepc, utilities.hyphen_to_dash (volume)}, lower);
else
return wrap_msg ('issue', {sepc, issue}, lower);
end
end
return vol;
end
end


Line 2,582: Line 2,669:
if 'citation' == config.CitationClass then
if 'citation' == config.CitationClass then
if utilities.is_set (Periodical) then
if utilities.is_set (Periodical) then
if not utilities.in_array (Periodical_origin, {'website', 'mailinglist'}) then -- {{citation}} does not render volume for these 'periodicals'
if not utilities.in_array (Periodical_origin, cfg.citation_no_volume_t) then -- {{citation}} does not render |volume= when these parameters are used
Volume = A['Volume']; -- but does for all other 'periodicals'
Volume = A['Volume']; -- but does for all other 'periodicals'
end
end
Line 2,599: Line 2,686:
local Issue;
local Issue;
if 'citation' == config.CitationClass then
if 'citation' == config.CitationClass then
if utilities.is_set (Periodical) and utilities.in_array (Periodical_origin, {'journal', 'magazine', 'newspaper', 'periodical', 'work'}) or -- {{citation}} renders issue for these 'periodicals'
if utilities.is_set (Periodical) and utilities.in_array (Periodical_origin, cfg.citation_issue_t) then -- {{citation}} may render |issue= when these parameters are used
utilities.is_set (ScriptPeriodical) and utilities.in_array (ScriptPeriodical_origin, {'script-journal', 'script-magazine', 'script-newspaper', 'script-periodical', 'script-work'}) then -- and these 'script-periodicals'
Issue = utilities.hyphen_to_dash (A['Issue']);
Issue = hyphen_to_dash (A['Issue']);
end
end
elseif utilities.in_array (config.CitationClass, cfg.templates_using_issue) then -- conference & map books do not support issue; {{citation}} listed here because included in settings table
elseif utilities.in_array (config.CitationClass, cfg.templates_using_issue) then -- conference & map books do not support issue; {{citation}} listed here because included in settings table
if not (utilities.in_array (config.CitationClass, {'conference', 'map', 'citation'}) and not (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical))) then
if not (utilities.in_array (config.CitationClass, {'conference', 'map', 'citation'}) and not (utilities.is_set (Periodical) or utilities.is_set (ScriptPeriodical))) then
Issue = hyphen_to_dash (A['Issue']);
Issue = utilities.hyphen_to_dash (A['Issue']);
end
end
end
end
local ArticleNumber;
if utilities.in_array (config.CitationClass, {'journal', 'conference'}) or ('citation' == config.CitationClass and utilities.is_set (Periodical) and 'journal' == Periodical_origin) then
ArticleNumber = A['ArticleNumber'];
end
extra_text_in_vol_iss_check (Issue, A:ORIGIN ('Issue'), 'i');
extra_text_in_vol_iss_check (Issue, A:ORIGIN ('Issue'), 'i');


Line 2,613: Line 2,706:
local Pages;
local Pages;
local At;
local At;
if not utilities.in_array (config.CitationClass, cfg.templates_not_using_page) then
local QuotePage;
local QuotePages;
if not utilities.in_array (config.CitationClass, cfg.templates_not_using_page) then -- TODO: rewrite to emit ignored parameter error message?
Page = A['Page'];
Page = A['Page'];
Pages = hyphen_to_dash (A['Pages']);
Pages = utilities.hyphen_to_dash (A['Pages']);
At = A['At'];
At = A['At'];
QuotePage = A['QuotePage'];
QuotePages = utilities.hyphen_to_dash (A['QuotePages']);
end
end


Line 2,670: Line 2,767:
-- 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 utilities.is_set (no_tracking_cats) then -- ignore if we are already not going to categorize this page
if not utilities.is_set (no_tracking_cats) then -- ignore if we are already not going to categorize this page
if utilities.in_array (this_page.nsText, cfg.uncategorized_namespaces) then
-- if utilities.in_array (this_page.nsText, cfg.uncategorized_namespaces) then
if cfg.uncategorized_namespaces[this_page.namespace] then -- is this pages namespace id one of the uncategorized namespace ids?
no_tracking_cats = "true"; -- set no_tracking_cats
no_tracking_cats = "true"; -- set no_tracking_cats
end
end
Line 2,826: Line 2,924:
local Sheets = A['Sheets'] or '';
local Sheets = A['Sheets'] or '';
if config.CitationClass == "map" then
if config.CitationClass == "map" then
if utilities.is_set (Chapter) then --TODO: make a function for this and similar?
if utilities.is_set (Chapter) then --TODO: make a function for this and similar?
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'map') .. ' and ' .. utilities.wrap_style ('parameter', Chapter_origin)}); -- add error message
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'map') .. ' and ' .. utilities.wrap_style ('parameter', Chapter_origin)}); -- add error message
end
end
Line 2,887: Line 2,985:
ChapterUrlAccess = UrlAccess;
ChapterUrlAccess = UrlAccess;
ChapterURL_origin = URL_origin;
ChapterURL_origin = URL_origin;
ChapterFormat = Format;
 
Title = Series; -- promote series to title
Title = Series; -- promote series to title
TitleLink = SeriesLink;
TitleLink = SeriesLink;
Line 2,900: Line 2,999:
TransTitle = '';
TransTitle = '';
ScriptTitle = '';
ScriptTitle = '';
Format = '';
else -- now oddities that are cite serial
else -- now oddities that are cite serial
Line 3,048: Line 3,148:


-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, {{cite ssrn}}, before generation of COinS data.
-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, {{cite ssrn}}, before generation of COinS data.
if utilities.in_array (config.CitationClass, whitelist.preprint_template_list) then
if utilities.in_array (config.CitationClass, whitelist.preprint_template_list) then -- |arxiv= or |eprint= required for cite arxiv; |biorxiv=, |citeseerx=, |ssrn= required for their templates
if not utilities.is_set (ID_list_coins[config.CitationClass:upper()]) then -- |arxiv= or |eprint= required for cite arxiv; |biorxiv= & |citeseerx= required for their templates
if not (args[cfg.id_handlers[config.CitationClass:upper()].parameters[1]] or -- can't use ID_list_coins k/v table here because invalid parameters omitted
utilities.set_message ('err_' .. config.CitationClass .. '_missing'); -- add error message
args[cfg.id_handlers[config.CitationClass:upper()].parameters[2]]) then -- which causes unexpected parameter missing error message
utilities.set_message ('err_' .. config.CitationClass .. '_missing'); -- add error message
end
end


Line 3,072: Line 3,173:
  end
  end


if utilities.is_set (URL) and utilities.is_set (AccessDate) then -- access date requires |url=; identifier-created URL is not |url=
if utilities.is_set (URL) then -- set when using an identifier-created URL
utilities.set_message ('err_accessdate_missing_url'); -- add an error message
if utilities.is_set (AccessDate) then -- |access-date= requires |url=; identifier-created URL is not |url=
AccessDate = ''; -- unset
utilities.set_message ('err_accessdate_missing_url'); -- add an error message
AccessDate = ''; -- unset
end
 
if utilities.is_set (ArchiveURL) then -- |archive-url= requires |url=; identifier-created URL is not |url=
utilities.set_message ('err_archive_missing_url'); -- add an error message
ArchiveURL = ''; -- unset
end
end
end
end
end
Line 3,110: Line 3,218:
end
end
local QuotePage = A['QuotePage'];
local QuotePages = hyphen_to_dash (A['QuotePages']);
-- this is the function call to COinS()
-- this is the function call to COinS()
local OCinSoutput = metadata.COinS({
local OCinSoutput = metadata.COinS({
['Periodical'] = utilities.strip_apostrophe_markup (Periodical), -- no markup in the metadata
['Periodical'] = utilities.strip_apostrophe_markup (Periodical), -- no markup in the metadata
['Encyclopedia'] = Encyclopedia, -- just a flag; content ignored by ~/COinS
['Encyclopedia'] = Encyclopedia, -- just a flag; content ignored by ~/COinS
['Chapter'] = metadata.make_coins_title (coins_chapter, ScriptChapter), -- Chapter and ScriptChapter stripped of bold / italic wiki-markup
['Chapter'] = metadata.make_coins_title (coins_chapter, ScriptChapter), -- Chapter and ScriptChapter stripped of bold / italic / accept-as-written markup
['Degree'] = Degree; -- cite thesis only
['Degree'] = Degree; -- cite thesis only
['Title'] = metadata.make_coins_title (coins_title, ScriptTitle), -- Title and ScriptTitle stripped of bold / italic wiki-markup
['Title'] = metadata.make_coins_title (coins_title, ScriptTitle), -- Title and ScriptTitle stripped of bold / italic / accept-as-written markup
['PublicationPlace'] = PublicationPlace,
['PublicationPlace'] = PublicationPlace,
['Date'] = COinS_date.rftdate, -- COinS_date has correctly formatted date if Date is valid;
['Date'] = COinS_date.rftdate, -- COinS_date has correctly formatted date if Date is valid;
Line 3,128: Line 3,233:
['Volume'] = Volume,
['Volume'] = Volume,
['Issue'] = Issue,
['Issue'] = Issue,
['ArticleNumber'] = ArticleNumber,
['Pages'] = coins_pages or metadata.get_coins_pages (first_set ({Sheet, Sheets, Page, Pages, At, QuotePage, QuotePages}, 7)), -- pages stripped of external links
['Pages'] = coins_pages or metadata.get_coins_pages (first_set ({Sheet, Sheets, Page, Pages, At, QuotePage, QuotePages}, 7)), -- pages stripped of external links
['Edition'] = Edition,
['Edition'] = Edition,
Line 3,497: Line 3,603:
local Agency = A['Agency'];
local Agency = A['Agency'];
Agency = utilities.is_set (Agency) and wrap_msg ('agency', {sepc, Agency}) or "";
Agency = utilities.is_set (Agency) and wrap_msg ('agency', {sepc, Agency}) or "";
Volume = format_volume_issue (Volume, Issue, config.CitationClass, Periodical_origin, sepc, use_lowercase);
Volume = format_volume_issue (Volume, Issue, ArticleNumber, config.CitationClass, Periodical_origin, sepc, use_lowercase);


if utilities.is_set (AccessDate) then
if utilities.is_set (AccessDate) then
Line 3,533: Line 3,639:
end
end
end
end
 
Quote = kern_quotes (Quote); -- kern if needed
Quote = utilities.wrap_style ('quoted-text', Quote ); -- wrap in <q>...</q> tags
Quote = utilities.wrap_style ('quoted-text', Quote ); -- wrap in <q>...</q> tags
Line 3,731: Line 3,838:
if utilities.in_array (config.CitationClass, {"journal", "citation"}) and utilities.is_set (Periodical) then
if utilities.in_array (config.CitationClass, {"journal", "citation"}) and utilities.is_set (Periodical) then
if not (utilities.is_set (Authors) or utilities.is_set (Editors)) then
Others = Others:gsub ('^' .. sepc .. ' ', ''); -- when no authors and no editors, strip leading sepc and space
end
if utilities.is_set (Others) then Others = safe_join ({Others, sepc .. " "}, sepc) end -- add terminal punctuation & space; check for dup sepc; TODO why do we need to do this here?
if utilities.is_set (Others) then Others = safe_join ({Others, sepc .. " "}, sepc) end -- add terminal punctuation & space; check for dup sepc; TODO why do we need to do this here?
tcommon = safe_join( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, Edition, Publisher, Agency, Volume}, sepc );
tcommon = safe_join( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, Edition, Publisher, Agency, Volume}, sepc );
Line 3,826: Line 3,936:
if utilities.is_set (Date) then
if utilities.is_set (Date) then
if EditorCount <= 1 then
if EditorCount <= 1 then
Editors = Editors .. ", " .. cfg.messages['editor'];
Editors = Editors .. cfg.presentation['sep_name'] .. cfg.messages['editor'];
else
else
Editors = Editors .. ", " .. cfg.messages['editors'];
Editors = Editors .. cfg.presentation['sep_name'] .. cfg.messages['editors'];
end
end
else
else
Line 3,903: Line 4,013:


local template_name = ('citation' == config.CitationClass) and 'citation' or 'cite ' .. (cfg.citation_class_map_t[config.CitationClass] or config.CitationClass);
local template_name = ('citation' == config.CitationClass) and 'citation' or 'cite ' .. (cfg.citation_class_map_t[config.CitationClass] or config.CitationClass);
local template_link = '[[Template:' .. template_name .. '|' .. template_name .. ']]'; -- TODO: if kept, these require some sort of i18n
local template_link = '[[Template:' .. template_name .. '|' .. template_name .. ']]';
local msg_prefix = '<code class="cs1-code">{{' .. template_link .. '}}</code>: ';
local msg_prefix = '<code class="cs1-code">{{' .. template_link .. '}}</code>: ';


Line 3,969: Line 4,079:
]]
]]


local function validate (name, cite_class, empty)
local function validate (name, cite_class, empty)
Line 4,091: Line 4,200:
if value:match ('[,;:]$') then
if value:match ('[,;:]$') then
utilities.set_message ('maint_extra_punct'); -- has extraneous punctuation; add maint cat
end
if value:match ('^=') then -- sometimes an extraneous '=' character appears ...
utilities.set_message ('maint_extra_punct'); -- has extraneous punctuation; add maint cat
utilities.set_message ('maint_extra_punct'); -- has extraneous punctuation; add maint cat
end
end
Line 4,121: Line 4,233:
local function citation(frame)
local function citation(frame)
Frame = frame; -- save a copy in case we need to display an error message in preview mode
Frame = frame; -- save a copy in case we need to display an error message in preview mode
is_sandbox = nil ~= string.find (frame:getTitle(), 'sandbox', 1, true);
 
local config = {}; -- table to store parameters from the module {{#invoke:}}
for k, v in pairs( frame.args ) do -- get parameters from the {{#invoke}} frame
config[k] = v;
-- args[k] = v; -- crude debug support that allows us to render a citation from module {{#invoke:}}; skips parameter validation; TODO: keep?
end
-- i18n: set the name that your wiki uses to identify sandbox subpages from sandbox template invoke (or can be set here)
local sandbox = ((config.SandboxPath and '' ~= config.SandboxPath) and config.SandboxPath) or '/sandbox'; -- sandbox path from {{#invoke:Citation/CS1/sandbox|citation|SandboxPath=/...}}
is_sandbox = nil ~= string.find (frame:getTitle(), sandbox, 1, true); -- is this invoke the sandbox module?
sandbox = is_sandbox and sandbox or ''; -- use i18n sandbox to load sandbox modules when this module is the sandox; live modules else
 
local pframe = frame:getParent()
local pframe = frame:getParent()
local styles;
local styles;
if is_sandbox then -- did the {{#invoke:}} use sandbox version?
cfg = mw.loadData ('Module:Citation/CS1/Configuration' .. sandbox); -- load sandbox versions of support modules when {{#invoke:Citation/CS1/sandbox|...}}; live modules else
cfg = mw.loadData ('Module:Citation/CS1/Configuration/sandbox'); -- load sandbox versions of support modules
whitelist = mw.loadData ('Module:Citation/CS1/Whitelist' .. sandbox);
whitelist = mw.loadData ('Module:Citation/CS1/Whitelist/sandbox');
utilities = require ('Module:Citation/CS1/Utilities' .. sandbox);
utilities = require ('Module:Citation/CS1/Utilities/sandbox');
validation = require ('Module:Citation/CS1/Date_validation' .. sandbox);
validation = require ('Module:Citation/CS1/Date_validation/sandbox');
identifiers = require ('Module:Citation/CS1/Identifiers' .. sandbox);
identifiers = require ('Module:Citation/CS1/Identifiers/sandbox');
metadata = require ('Module:Citation/CS1/COinS' .. sandbox);
metadata = require ('Module:Citation/CS1/COinS/sandbox');
styles = 'Module:Citation/CS1' .. sandbox .. '/styles.css';
styles = 'Module:Citation/CS1/sandbox/styles.css';
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');
styles = 'Module:Citation/CS1/styles.css';
end


utilities.set_selected_modules (cfg); -- so that functions in Utilities can see the selected cfg tables
utilities.set_selected_modules (cfg); -- so that functions in Utilities can see the selected cfg tables
Line 4,157: Line 4,268:
local error_text; -- used as a flag
local error_text; -- used as a flag


local config = {}; -- table to store parameters from the module {{#invoke:}}
-- local config = {}; -- table to store parameters from the module {{#invoke:}}
for k, v in pairs( frame.args ) do -- get parameters from the {{#invoke}} frame
-- for k, v in pairs( frame.args ) do -- get parameters from the {{#invoke}} frame
config[k] = v;
-- config[k] = v;
-- args[k] = v; -- crude debug support that allows us to render a citation from module {{#invoke:}}; skips parameter validation; TODO: keep?
-- -- args[k] = v; -- crude debug support that allows us to render a citation from module {{#invoke:}}; skips parameter validation; TODO: keep?
end
-- end


local capture; -- the single supported capture when matching unknown parameters using patterns
local capture; -- the single supported capture when matching unknown parameters using patterns
Line 4,180: Line 4,291:
else
else
if nil == suggestions.suggestions then -- if this table is nil then we need to load it
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); --load sandbox version of suggestion module when {{#invoke:Citation/CS1/sandbox|...}}; live module else
if is_sandbox 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
end
for pattern, param in pairs (suggestions.patterns) do -- loop through the patterns to see if we can suggest a proper parameter
for pattern, param in pairs (suggestions.patterns) do -- loop through the patterns to see if we can suggest a proper parameter