Модуль:Citation/CS1/пісочниця/гілка1
Зовнішній вигляд
Документація модуля[перегляд] [редагувати] [історія] [очистити кеш]
Цей модуль використовується для тестування дуже ранніх ідей, які не планується поки включати в головну гілку тестування.
Документація вище включена з Модуль:Citation/CS1/пісочниця/гілка1/документація. (ред. | історія) Дописувачі можуть експериментувати на підсторінках пісочниця (створити | дзеркало) та тести (створити) цього модуля. Будь ласка, додавайте категорії до підсторінки /CS1/пісочниця/гілка1/документація. Підсторінки цієї сторінки. |
--[[--------------------------< 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
]]
--local validation; -- functions in Module:Citation/CS1/Date_validation (додано буде в наступних оновленнях)
local utilities; -- functions in Module:Citation/CS1/Utilities
local z = {}; -- table of tables in Module:Citation/CS1/Utilities
local identifiers; -- functions and tables in Module:Citation/CS1/Identifiers
local metadata; -- functions in Module:Citation/CS1/COinS
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
-- First set variable or nil if none
function first_set(...)
local list = {...};
for _, var in pairs(list) do
if utilities.is_set( var ) then
return var;
end
end
end
--[[--------------------------< A D D _ V A N C _ E R R O R >----------------------------------------------------
Adds a single Vancouver system error message to the template's output regardless of how many error actually exist.
To prevent duplication, added_vanc_errs is nil until an error message is emitted.
added_vanc_errs is a Boolean declared in page scope variables above
]]
local function add_vanc_error (source, position)
if added_vanc_errs then return end
added_vanc_errs = true; -- note that we've added this category
utilities.set_message ('err_vancouver', {source, position});
end
--[[--------------------------< I S _ S C H E M E >------------------------------------------------------------
does this thing that purports to be a URI scheme seem to be a valid scheme? The scheme is checked to see if it
is in agreement with http://tools.ietf.org/html/std66#section-3.1 which says:
Scheme names consist of a sequence of characters beginning with a
letter and followed by any combination of letters, digits, plus
("+"), period ("."), or hyphen ("-").
returns true if it does, else false
]]
local function is_scheme (scheme)
return scheme and scheme:match ('^%a[%a%d%+%.%-]*:'); -- true if scheme is set and matches the pattern
end
--[=[-------------------------< I S _ D O M A I N _ N A M E >--------------------------------------------------
Does this thing that purports to be a domain name seem to be a valid domain name?
Syntax defined here: http://tools.ietf.org/html/rfc1034#section-3.5
BNF defined here: https://tools.ietf.org/html/rfc4234
Single character names are generally reserved; see https://tools.ietf.org/html/draft-ietf-dnsind-iana-dns-01#page-15;
see also [[Single-letter second-level domain]]
list of TLDs: https://www.iana.org/domains/root/db
RFC 952 (modified by RFC 1123) requires the first and last character of a hostname to be a letter or a digit. Between
the first and last characters the name may use letters, digits, and the hyphen.
Also allowed are IPv4 addresses. IPv6 not supported
domain is expected to be stripped of any path so that the last character in the last character of the TLD. tld
is two or more alpha characters. Any preceding '//' (from splitting a URL with a scheme) will be stripped
here. Perhaps not necessary but retained in case it is necessary for IPv4 dot decimal.
There are several tests:
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
single-letter/digit second-level domains in the .org, .cash, and .today TLDs
q, x, and z SL domains in the .com TLD
i and q SL domains in the .net TLD
single-letter SL domains in the ccTLDs (where the ccTLD is two letters)
two-character SL domains in gTLDs (where the gTLD is two or more letters)
three-plus-character SL domains in gTLDs (where the gTLD is two or more letters)
IPv4 dot-decimal address format; TLD not allowed
returns true if domain appears to be a proper name and TLD or IPv4 address, else false
]=]
local function is_domain_name (domain)
if not domain then
return false; -- if not set, abandon
end
domain = domain:gsub ('^//', ''); -- strip '//' from domain name if present; done here so we only have to do it once
if not domain:match ('^[%w]') then -- first character must be letter or digit
return false;
end
if domain:match ('^%a+:') then -- hack to detect things that look like s:Page:Title where Page: is namespace at Wikisource
return false;
end
local patterns = { -- patterns that look like URLs
'%f[%w][%w][%w%-]+[%w]%.%a%a+$', -- three or more character hostname.hostname or hostname.tld
'%f[%w][%w][%w%-]+[%w]%.xn%-%-[%w]+$', -- internationalized domain name with ACE prefix
'%f[%a][qxz]%.com$', -- assigned one character .com hostname (x.com times out 2015-12-10)
'%f[%a][iq]%.net$', -- assigned one character .net hostname (q.net registered but not active 2015-12-10)
'%f[%w][%w]%.%a%a$', -- one character hostname and ccTLD (2 chars)
'%f[%w][%w][%w]%.%a%a+$', -- two character hostname and TLD
'^%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?', -- IPv4 address
}
for _, pattern in ipairs (patterns) do -- loop through the patterns list
if domain:match (pattern) then
return true; -- if a match then we think that this thing that purports to be a URL is a URL
end
end
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
return true
end
end
return false; -- no matches, we don't know what this thing is
end
--[[--------------------------< I S _ U R L >------------------------------------------------------------------
returns true if the scheme and domain parts of a URL appear to be a valid URL; else false.
This function is the last step in the validation process. This function is separate because there are cases that
are not covered by split_url(), for example is_parameter_ext_wikilink() which is looking for bracketted external
wikilinks.
]]
local function is_url (scheme, domain)
if utilities.is_set (scheme) then -- if scheme is set check it and domain
return is_scheme (scheme) and is_domain_name (domain);
else
return is_domain_name (domain); -- scheme not set when URL is protocol-relative
end
end
--[[--------------------------< S P L I T _ U R L >------------------------------------------------------------
Split a URL into a scheme, authority indicator, and domain.
First remove Fully Qualified Domain Name terminator (a dot following TLD) (if any) and any path(/), query(?) or fragment(#).
If protocol-relative URL, return nil scheme and domain else return nil for both scheme and domain.
When not protocol-relative, get scheme, authority indicator, and domain. If there is an authority indicator (one
or more '/' characters immediately following the scheme's colon), make sure that there are only 2.
Any URL that does not have news: scheme must have authority indicator (//). TODO: are there other common schemes
like news: that don't use authority indicator?
Strip off any port and path;
]]
local function split_url (url_str)
local scheme, authority, domain;
url_str = url_str:gsub ('([%a%d])%.?[/%?#].*$', '%1'); -- strip FQDN terminator and path(/), query(?), fragment (#) (the capture prevents false replacement of '//')
if url_str:match ('^//%S*') then -- if there is what appears to be a protocol-relative URL
domain = url_str:match ('^//(%S*)')
elseif url_str:match ('%S-:/*%S+') then -- if there is what appears to be a scheme, optional authority indicator, and domain name
scheme, authority, domain = url_str:match ('(%S-:)(/*)(%S+)'); -- extract the scheme, authority indicator, and domain portions
if utilities.is_set (authority) then
authority = authority:gsub ('//', '', 1); -- replace place 1 pair of '/' with nothing;
if utilities.is_set(authority) then -- if anything left (1 or 3+ '/' where authority should be) then
return scheme; -- return scheme only making domain nil which will cause an error message
end
else
if not scheme:match ('^news:') then -- except for news:..., MediaWiki won't link URLs that do not have authority indicator; TODO: a better way to do this test?
return scheme; -- return scheme only making domain nil which will cause an error message
end
end
domain = domain:gsub ('(%a):%d+', '%1'); -- strip port number if present
end
return scheme, domain;
end
--[[--------------------------< L I N K _ P A R A M _ O K >---------------------------------------------------
checks the content of |title-link=, |series-link=, |author-link=, etc. for properly formatted content: no wikilinks, no URLs
Link parameters are to hold the title of a Wikipedia article, so none of the WP:TITLESPECIALCHARACTERS are allowed:
# < > [ ] | { } _
except the underscore which is used as a space in wiki URLs and # which is used for section links
returns false when the value contains any of these characters.
When there are no illegal characters, this function returns TRUE if value DOES NOT appear to be a valid URL (the
|<param>-link= parameter is ok); else false when value appears to be a valid URL (the |<param>-link= parameter is NOT ok).
]]
local function link_param_ok (value)
local scheme, domain;
if value:find ('[<>%[%]|{}]') then -- if any prohibited characters
return false;
end
scheme, domain = split_url (value); -- get scheme or nil and domain or nil from URL;
return not is_url (scheme, domain); -- return true if value DOES NOT appear to be a valid URL
end
--[[--------------------------< L I N K _ T I T L E _ O K >---------------------------------------------------
Use link_param_ok() to validate |<param>-link= value and its matching |<title>= value.
|<title>= may be wiki-linked but not when |<param>-link= has a value. This function emits an error message when
that condition exists
check <link> for inter-language interwiki-link prefix. prefix must be a MediaWiki-recognized language
code and must begin with a colon.
]]
local function link_title_ok (link, lorig, title, torig)
local orig;
if utilities.is_set (link) then -- don't bother if <param>-link doesn't have a value
if not link_param_ok (link) then -- check |<param>-link= markup
orig = lorig; -- identify the failing link parameter
elseif title:find ('%[%[') then -- check |title= for wikilink markup
orig = torig; -- identify the failing |title= parameter
elseif link:match ('^%a+:') then -- if the link is what looks like an interwiki
local prefix = link:match ('^(%a+):'):lower(); -- get the interwiki prefix
if cfg.inter_wiki_map[prefix] then -- if prefix is in the map, must have preceding colon
orig = lorig; -- flag as error
end
end
end
if utilities.is_set (orig) then
link = ''; -- unset
utilities.set_message ('err_bad_paramlink', orig); -- URL or wikilink in |title= with |title-link=;
end
return link; -- link if ok, empty string else
end
-- Wraps a string using a message_list configuration taking one argument
function wrap( key, str )
if not utilities.is_set( str ) then
return "";
elseif utilities.in_array( key, { 'italic-title', 'trans-italic-title' } ) then
str = safe_for_italics( str );
end
return utilities.substitute( cfg.messages[key], {str} );
end
--utilities.substitute template {{ref-lang}} if possible
local refTemplateSupportedLangs =
{
[2] =
{
'uk', 'en', 'ru', 'be', 'pl', 'es', 'fr', 'de', 'ar', 'it', 'cs',
'ab', 'af', 'an', 'az', 'ba', 'bg', 'bl', 'bn', 'br', 'bs', 'ca',
'ce', 'co', 'cu', 'cv', 'cy', 'cz', 'da', 'et', 'fa', 'fi', 'fo',
'fy', 'ga', 'eo', 'eu', 'gd', 'ge', 'gl', 'el', 'ha', 'he', 'hi',
'hr', 'hu', 'hy', 'id', 'ie', 'is', 'ja', 'ka', 'kk', 'kl', 'km',
'kn', 'ko', 'kv', 'ky', 'la', 'le', 'lt', 'lb', 'lv', 'md', 'me',
'mg', 'mi', 'mk', 'mn', 'mo', 'ms', 'mt', 'ne', 'nl', 'no', 'oc',
'os', 'pt', 'rm', 'ro', 'sa', 'sh', 'sk', 'sl', 'sq', 'sr', 'sv',
'sw', 'te', 'ta', 'tg', 'th', 'tk', 'tl', 'tr', 'tt', 'ur', 'uz',
'vi', 'xal', 'yi', 'zh'
},
[3] =
{
'crh', 'rue', 'arz', 'chu', 'dsb', 'gag', 'grc', 'hsb', 'kaa',
'kdr', 'krl', 'ltg', 'mhr', 'myv', 'non', 'orv', 'pap', 'pnt',
'rmy', 'sah', 'sla', 'szl', 'udm', 'lld', 'uum'
},
[4] = {},
[5] = {'en-US'},
[6] = {'uk-old', 'old-ru', 'ru-dor'},
[7] = {'bat-smg'}
}
function SubstLangTemplateIfPossible(lang_code, is_dstu)
if #lang_code>=2 and #lang_code<=7 and utilities.in_array(lang_code, refTemplateSupportedLangs[#lang_code]) then
if is_dstu then
return '{{lg|'..lang_code..'}}'
else
return '{{ref-'..lang_code..'}}'
end
else
return nil
end
end
--convert date from 'YYYY-MM-DD' format to 'DD month YYYY' format
local monthsDaysCounts = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
local englishMonthNames =
{
"January", "February",
"March", "April", "May",
"June", "July", "August",
"September", "October", "November",
"December"
}
local ukrainianMonthNamesGen =
{
'січня', 'лютого',
'березня', 'квітня', 'травня',
'червня', 'липня', 'серпня',
'вересня', 'жовтня', 'листопада',
'грудня'
}
function ParseDateIfPossible(dateString, is_embargo_pmc)
local year, month, day = string.match(dateString, "^(%d%d%d%d)%-(%d%d)%-(%d%d)$")
if year then
local day_int, month_int = tonumber(day), tonumber(month)
if type(day_int)~='number' or type(month_int)~='number'
or month_int<1 or month_int>12
or day_int<1 or day_int>monthsDaysCounts[month_int] then
return dateString
end
return day_int..' '..ukrainianMonthNamesGen[month_int]..' '..year
end
local month, day, year = string.match(dateString, "^(%a+) +([1-9]%d?), (%d%d%d%d)$")
if not month then
day, month, year = string.match(dateString, "^+([1-9]%d?) (%a+) (%d%d%d%d)$")
end
if is_embargo_pmc then
day, month, year = mw.ustring.match(dateString, "^([1-9]%d?) (%a+) (%d%d%d%d)$")
end
if day and month then
local month_int
if is_embargo_pmc then
month_int = utilities.in_array(month, ukrainianMonthNamesGen)
else
month_int = utilities.in_array(month, englishMonthNames)
end
if not month_int then return dateString end
local day_int = tonumber(day)
if is_embargo_pmc then
return day_int..' '..englishMonthNames[month_int]..' '..year
end
return day_int..' '..ukrainianMonthNamesGen[month_int]..' '..year
end
return dateString
end
--[[
Argument wrapper. This function provides support for argument
mapping defined in the configuration file so that multiple names
can be transparently aliased to single internal variable.
]]
function argument_wrapper( args )
local origin = {};
return setmetatable({
ORIGIN = function( self, k )
local dummy = self[k]; --force the variable to be loaded.
return origin[k];
end
},
{
__index = function ( tbl, k )
if origin[k] ~= nil then
return nil;
end
local args, list, v = args, cfg.aliases[k];
if list == nil then
error( cfg.messages['unknown_argument_map'] );
elseif type( list ) == 'string' then
v, origin[k] = args[list], list;
else
v, origin[k] = select_one( args, list, 'err_redundant_parameters' );
if origin[k] == nil then
origin[k] = ''; --Empty string, not nil;
end
end
if v == nil then
v = cfg.defaults[k] or "";
origin[k] = ''; --Empty string, not nil;
end
tbl = rawset( tbl, k, v );
return v;
end,
});
end
--[[--------------------------< D E P R E C A T E D _ P A R A M E T E R >--------------------------------------
Categorize and emit an error message when the citation contains one or more deprecated parameters. The function includes the
offending parameter name to the error message. Only one error message is emitted regardless of the number of deprecated
parameters in the citation.
added_deprecated_cat is a Boolean declared in page scope variables above
]]
local function deprecated_parameter(name)
if not added_deprecated_cat then
added_deprecated_cat = true; -- note that we've added this category
utilities.set_message ('err_deprecated_params', {name}); -- add error message
end
end
--[[--------------------------< E X T E R N A L _ L I N K >----------------------------------------------------
Format an external link with error checking
]]
function external_link( URL, label, source, access)
local base_url;
if not utilities.is_set( label ) then
label = URL;
if utilities.is_set( source ) then
utilities.set_message( 'err_bare_url_missing_title', { wrap( 'parameter', source ) }, false, " " );
else
error( cfg.messages["bare_url_no_origin"] );
end
end
if not check_url( URL ) then
utilities.set_message( 'err_bad_url', {}, false, " " );
end
base_url = table.concat({ "[", URL, " ", safe_for_url( label ), "]"})
if utilities.is_set (access) then -- access level (subscription, registration, limited)
base_url = utilities.substitute (cfg.presentation['ext-link-access-signal'], {cfg.presentation[access].class, cfg.presentation[access].title, base_url}); -- add the appropriate icon
end
return base_url;
end
--[[--------------------------< F O R M A T _ S C R I P T _ V A L U E >----------------------------------------
|script-title= holds title parameters that are not written in Latin-based scripts: Chinese, Japanese, Arabic, Hebrew, etc. These scripts should
not be italicized and may be written right-to-left. The value supplied by |script-title= is concatenated onto Title after Title has been wrapped
in italic markup.
Regardless of language, all values provided by |script-title= are wrapped in <bdi>...</bdi> tags to isolate RTL languages from the English left to right.
|script-title= provides a unique feature. The value in |script-title= may be prefixed with a two-character ISO 639-1 language code and a colon:
|script-title=ja:*** *** (where * represents a Japanese character)
Spaces between the two-character code and the colon and the colon and the first script character are allowed:
|script-title=ja : *** ***
|script-title=ja: *** ***
|script-title=ja :*** ***
Spaces preceding the prefix are allowed: |script-title = ja:*** ***
The prefix is checked for validity. If it is a valid ISO 639-1 language code, the lang attribute (lang="ja") is added to the <bdi> tag so that browsers can
know the language the tag contains. This may help the browser render the script more correctly. If the prefix is invalid, the lang attribute
is not added. At this time there is no error message for this condition.
Supports |script-title=, |script-chapter=, |script-<periodical>=
]]
local function format_script_value (script_value, script_param)
local lang=''; -- initialize to empty string
local name;
if script_value:match('^%l%l%l?%s*:') then -- if first 3 or 4 non-space characters are script language prefix
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
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
end
-- if we get this far we have prefix and script
name = cfg.lang_tag_remap[lang] or mw.language.fetchLanguageName( lang, cfg.this_wiki_code ); -- get language name so that we can use it to categorize
if utilities.is_set (name) then -- is prefix a proper ISO 639-1 language code?
script_value = script_value:gsub ('^%l+%s*:%s*', ''); -- strip prefix from script
-- is prefix one of these language codes?
if utilities.in_array (lang, cfg.script_lang_codes) then
utilities.add_prop_cat ('script', {name, lang})
else
utilities.set_message ('err_script_parameter', {script_param, cfg.err_msg_supl['unknown language code']}); -- unknown script-language; add error message
end
lang = ' lang="' .. lang .. '" '; -- convert prefix into a lang attribute
else
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
end
else
utilities.set_message ('err_script_parameter', {script_param, cfg.err_msg_supl['missing prefix']}); -- no language code prefix; add error message
end
script_value = utilities.substitute (cfg.presentation['bdi'], {lang, script_value}); -- isolate in case script is RTL
return script_value;
end
--[[--------------------------< 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 wrapped in <bdi> tags.
]]
local function script_concatenate (title, script, script_param)
if utilities.is_set (script) then
script = format_script_value (script, script_param); -- <bdi> tags, lang attribute, categorization, etc.; returns empty string on error
if utilities.is_set (script) then
title = title .. ' ' .. script; -- concatenate title and script title
end
end
return title;
end
--[[--------------------------< W R A P _ M S G >--------------------------------------------------------------
Applies additional message text to various parameter values. Supplied string is wrapped using a message_list
configuration taking one argument. Supports lower case text for {{citation}} templates. Additional text taken
from citation_config.messages - the reason this function is similar to but separate from wrap_style().
]]
local function wrap_msg (key, str, lower)
if not utilities.is_set ( str ) then
return "";
end
if true == lower then
local msg;
msg = cfg.messages[key]:ulower(); -- set the message to lower case before
return utilities.substitute ( msg, str ); -- including template text
else
return utilities.substitute ( cfg.messages[key], str );
end
end
--[[
Determines whether an URL string is valid
At present the only check is whether the string appears to
be prefixed with a URI scheme. It is not determined whether
the URI scheme is valid or whether the URL is otherwise well
formed.
]]
function check_url( url_str )
-- Protocol-relative or URL scheme
return url_str:sub(1,2) == "//" or url_str:match( "^[^/]*:" ) ~= nil;
end
-- Gets the display text for a wikilink like [[A|B]] or [[B]] gives B
function remove_wiki_link( str )
return (str:gsub( "%[%[([^%[%]]*)%]%]", function(l)
return l:gsub( "^[^|]*|(.*)$", "%1" ):gsub("^%s*(.-)%s*$", "%1");
end));
end
-- Escape sequences for content that will be used for URL descriptions
function safe_for_url( str )
if str:match( "%[%[.-%]%]" ) ~= nil then
utilities.set_message( 'err_wikilink_in_url', {});
end
return str:gsub( '[%[%]\n]', {
['['] = '[',
[']'] = ']',
['\n'] = ' ' } );
end
-- Converts a hyphen to a dash
function hyphen_to_dash( str )
if not utilities.is_set(str) or str:match( "[%[%]{}<>]" ) ~= nil then
return str;
end
return str:gsub( '-', '–' );
end
-- Protects a string that will be wrapped in wiki italic markup '' ... ''
function safe_for_italics( str )
--[[ Note: We can not use <i> for italics, as the expected behavior for
italics specified by ''...'' in the title is that they will be inverted
(i.e. unitalicized) in the resulting references. In addition, <i> and ''
tend to interact poorly under Mediawiki's HTML tidy. ]]
if not utilities.is_set(str) then
return str;
else
if str:sub(1,1) == "'" then str = "<span></span>" .. str; end
if str:sub(-1,-1) == "'" then str = str .. "<span></span>"; end
-- Remove newlines as they break italics.
return str:gsub( '\n', ' ' );
end
end
--[[----------------< F O R M A T _ P E R I O D I C A L >-----------------------
Format the three periodical parameters: |script-<periodical>=, |<periodical>=,
and |trans-<periodical>= into a single Periodical meta-parameter.
]]
local function format_periodical (script_periodical, script_periodical_source, periodical, trans_periodical, trans_italic_title)
if not utilities.is_set (periodical) then
periodical = ''; -- to be safe for concatenation
else
periodical = wrap ('italic-title', periodical); -- style
end
periodical = script_concatenate (periodical, script_periodical, script_periodical_source); -- <bdi> tags, lang attribute, categorization, etc.; must be done after title is wrapped
if utilities.is_set (trans_periodical) then
trans_periodical = wrap (trans_italic_title, trans_periodical);
if utilities.is_set (periodical) then
periodical = periodical .. ' ' .. trans_periodical;
else -- here when trans-periodical without periodical or script-periodical
periodical = trans_periodical;
utilities.set_message ('err_trans_missing_title', {'periodical'});
end
end
return periodical;
end
--[[--------------------------< S E T _ T I T L E T Y P E >---------------------
This function sets default title types (equivalent to the citation including
|type=<default value>) for those templates that have defaults. Also handles the
special case where it is desirable to omit the title type from the rendered citation
(|type=none).
]]
local function set_titletype (cite_class, title_type)
if utilities.is_set (title_type) then
if 'none' == cfg.keywords_xlate[title_type] then
title_type = ''; -- if |type=none then type parameter not displayed
end
return title_type; -- if |type= has been set to any other value use that value
end
return cfg.title_types [cite_class] or ''; -- set template's default title type; else empty string for concatenation
end
--[[--------------------------< S A F E _ J O I N >-----------------------------
Joins a sequence of strings together while checking for duplicate separation characters.
]]
local function safe_join( tbl, duplicate_char, dstu_sepc )
local f = {}; -- create a function table appropriate to type of 'duplicate character'
if 1 == #duplicate_char then -- for single byte ASCII characters use the string library functions
f.gsub = string.gsub
f.match = string.match
f.sub = string.sub
else -- for multi-byte characters use the ustring library functions
f.gsub = mw.ustring.gsub
f.match = mw.ustring.match
f.sub = mw.ustring.sub
end
local str = ''; -- the output string
local comp = ''; -- what does 'comp' mean?
local end_chr = '';
local trim;
for _, value in ipairs( tbl ) do
if value == nil then value = ''; end
if str == '' then -- if output string is empty
str = value; -- assign value to it (first time through the loop)
elseif value ~= '' then
if value:sub(1, 1) == '<' then -- special case of values enclosed in spans and other markup.
comp = value:gsub( "%b<>", "" ); -- remove HTML markup (<span>string</span> -> string)
else
comp = value;
end
-- typically duplicate_char is sepc
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 the
-- preceding segment: 'First element' .. 'sepc next element' .. etc.?
trim = false;
end_chr = f.sub(str, -1, -1); -- get the last character of the output string
-- str = str .. "<HERE(enchr=" .. end_chr .. ")" -- debug stuff?
if end_chr == duplicate_char then -- if same as separator
str = f.sub(str, 1, -2); -- remove it
--elseif end_chr == "—" and utilities.is_set(dstu_sepc) then
--str = f.sub(str, 1, -dstu_sepc:len());
elseif end_chr == "'" then -- if it might be wiki-markup
if f.sub(str, -3, -1) == duplicate_char .. "''" then -- if last three chars of str are sepc''
str = f.sub(str, 1, -4) .. "''"; -- remove them and add back ''
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?
elseif f.sub(str, -4, -1) == duplicate_char .. "]''" then -- if last four chars of str are sepc]''
trim = true; -- same question
end
elseif end_chr == "]" then -- if it might be wiki-markup
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;
elseif f.sub(str, -2, -1) == duplicate_char .. "]" then -- if last two chars of str are sepc] external link
trim = true;
elseif f.sub(str, -4, -1) == duplicate_char .. "'']" then -- normal case when |url=something & |title=Title.
trim = true;
end
elseif end_chr == " " then -- if last char of output string is a space
if f.sub(str, -2, -1) == duplicate_char .. " " then -- if last two chars of str are <sepc><space>
str = f.sub(str, 1, -3); -- remove them both
--elseif f.sub(str, -2, -1) == "—" .. " " and utilities.is_set(dstu_sepc) then
--str = f.sub(str, 1, -dstu_sepc:len()-1);
end
end
if trim then
if value ~= comp then -- value does not equal comp when value contains HTML markup
local dup2 = duplicate_char;
if f.match(dup2, "%A" ) then dup2 = "%" .. dup2; end -- if duplicate_char not a letter then escape it
value = f.gsub(value, "(%b<>)" .. dup2, "%1", 1 ) -- remove duplicate_char if it follows HTML markup
else
value = f.sub(value, 2, -1 ); -- remove duplicate_char when it is first character
end
end
end
str = str .. value; -- add it to the output string
end
end
return str;
end
--[[
Return the year portion of a date string, if possible.
Returns empty string if the argument can not be interpreted
as a year.
]]
function select_year( str )
-- Is the input a simple number?
local num = tonumber( str );
if num ~= nil and num > 0 and num < 2100 and num == math.floor(num) then
return str;
else
-- Use formatDate to interpret more complicated formats
local lang = mw.getContentLanguage();
local good, result;
good, result = pcall( lang.formatDate, lang, 'Y', str )
if good then
return result;
else
-- Can't make sense of this input, return blank.
return "";
end
end
end
--[[--------------------------< I S _ S U F F I X >-----------------------------
returns true if suffix is properly formed Jr, Sr, or ordinal in the range 1–9.
Puncutation not allowed.
]]
local function is_suffix (suffix)
if utilities.in_array (suffix, {'Jr', 'Sr', 'Jnr', 'Snr', '1st', '2nd', '3rd'}) or suffix:match ('^%dth$') then
return true;
end
return false;
end
--[[--------------------< I S _ G O O D _ V A N C _ N A M E >-------------------
For Vancouver style, author/editor names are supposed to be rendered in Latin
(read ASCII) characters. When a name uses characters that contain diacritical
marks, those characters are to be converted to the corresponding Latin
character. When a name is written using a non-Latin alphabet or logogram, that
name is to be transliterated into Latin characters. The module doesn't do this
so editors may/must.
This test allows |first= and |last= names to contain any of the letters defined
in the four Unicode Latin character sets
[http://www.unicode.org/charts/PDF/U0000.pdf C0 Controls and Basic Latin] 0041–005A, 0061–007A
[http://www.unicode.org/charts/PDF/U0080.pdf C1 Controls and Latin-1 Supplement] 00C0–00D6, 00D8–00F6, 00F8–00FF
[http://www.unicode.org/charts/PDF/U0100.pdf Latin Extended-A] 0100–017F
[http://www.unicode.org/charts/PDF/U0180.pdf Latin Extended-B] 0180–01BF, 01C4–024F
|lastn= also allowed to contain hyphens, spaces, and apostrophes.
(http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35029/)
|firstn= also allowed to contain hyphens, spaces, apostrophes, and periods
This original test:
if nil == mw.ustring.find (last, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%']*$")
or nil == mw.ustring.find (first, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%'%.]+[2-6%a]*$") then
was written outside of the code editor and pasted here because the code editor
gets confused between character insertion point and cursor position. The test has
been rewritten to use decimal character escape sequence for the individual bytes
of the Unicode characters so that it is not necessary to use an external editor
to maintain this code.
\195\128-\195\150 – À-Ö (U+00C0–U+00D6 – C0 controls)
\195\152-\195\182 – Ø-ö (U+00D8-U+00F6 – C0 controls)
\195\184-\198\191 – ø-ƿ (U+00F8-U+01BF – C0 controls, Latin extended A & B)
\199\132-\201\143 – DŽ-ɏ (U+01C4-U+024F – Latin extended B)
]]
local function is_good_vanc_name (last, first, suffix, position)
if not suffix then
if first:find ('[,%s]') then -- when there is a space or comma, might be first name/initials + generational suffix
first = first:match ('(.-)[,%s]+'); -- get name/initials
suffix = first:match ('[,%s]+(.+)$'); -- get generational suffix
end
end
if utilities.is_set (suffix) then
if not is_suffix (suffix) then
add_vanc_error (cfg.err_msg_supl.suffix, position);
return false; -- not a name with an appropriate suffix
end
end
if nil == mw.ustring.find (last, "^[A-Za-z\195\128-\195\150\195\152-\195\182\195\184-\198\191\199\132-\201\143%-%s%']*$") or
nil == mw.ustring.find (first, "^[A-Za-z\195\128-\195\150\195\152-\195\182\195\184-\198\191\199\132-\201\143%-%s%'%.]*$") then
add_vanc_error (cfg.err_msg_supl['non-Latin char'], position);
return false; -- not a string of Latin characters; Vancouver requires Romanization
end;
return true;
end
--[[--------------------------< R E D U C E _ T O _ I N I T I A L S >------------------------------------------
Attempts to convert names to initials in support of |name-list-style=vanc.
Names in |firstn= may be separated by spaces or hyphens, or for initials, a period.
See http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35062/.
Vancouver style requires family rank designations (Jr, II, III, etc.) to be rendered
as Jr, 2nd, 3rd, etc. See http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35085/.
This code only accepts and understands generational suffix in the Vancouver format
because Roman numerals look like, and can be mistaken for, initials.
This function uses ustring functions because firstname initials may be any of the
Unicode Latin characters accepted by is_good_vanc_name ().
]]
local function reduce_to_initials(first, position)
local name, suffix = mw.ustring.match(first, "^(%u+) ([%dJS][%drndth]+)$");
if not name then -- if not initials and a suffix
name = mw.ustring.match(first, "^(%u+)$"); -- is it just initials?
end
if name then -- if first is initials with or without suffix
if 3 > mw.ustring.len (name) then -- if one or two initials
if suffix then -- if there is a suffix
if is_suffix (suffix) then -- is it legitimate?
return first; -- one or two initials and a valid suffix so nothing to do
else
add_vanc_error (cfg.err_msg_supl.suffix, position); -- one or two initials with invalid suffix so error message
return first; -- and return first unmolested
end
else
return first; -- one or two initials without suffix; nothing to do
end
end
end -- if here then name has 3 or more uppercase letters so treat them as a word
local initials, names = {}, {}; -- tables to hold name parts and initials
local i = 1; -- counter for number of initials
names = mw.text.split (first, '[%s,]+'); -- split into a table of names and possible suffix
while names[i] do -- loop through the table
if 1 < i and names[i]:match ('[%dJS][%drndth]+%.?$') then -- if not the first name, and looks like a suffix (may have trailing dot)
names[i] = names[i]:gsub ('%.', ''); -- remove terminal dot if present
if is_suffix (names[i]) then -- if a legitimate suffix
table.insert (initials, ' ' .. names[i]); -- add a separator space, insert at end of initials table
break; -- and done because suffix must fall at the end of a name
end -- no error message if not a suffix; possibly because of Romanization
end
if 3 > i then
table.insert (initials, mw.ustring.sub(names[i], 1, 1)); -- insert the initial at end of initials table
end
i = i + 1; -- bump the counter
end
return table.concat(initials) -- Vancouver format does not include spaces.
end
--[[--------------------------< D S T U _ R E D U C E _ T O _ I N I T I A L S >------------------------------------------
Attempts to convert names to initials in support of |mode=dstu.
This function uses ustring functions because firstname initials may be any of the
Unicode characters.
]]
local function dstu_reduce_to_initials(first, position)
local name, patronym = mw.ustring.match(first, "^(%u?%l?)%. (%u?%l?)%.$");
if not name then -- if not name or patronym
name = mw.ustring.match(first, "^(%u?%l?)%.$"); -- is it just initials?
end
if name then -- if first is initials with or without patronym
if 3 > mw.ustring.len (name) then -- if one or two initials
if patronym then
if 3 > mw.ustring.len (patronym) then
return first;
end
else
return first;
end
end
end
local initials, names = {}, {}; -- tables to hold name parts and initials
local i = 1; -- counter for number of initials
names = mw.text.split (first, '[%s,]+'); -- split into a table of names and possible patronym
while names[i] do
if 3 > i then
local sep;
if i%2==0 then
sep = '.';
else
sep = '. '
end
table.insert (initials, mw.ustring.sub(names[i], 1, 1) .. sep); -- insert the initial at end of initials table
end
i = i + 1; -- bump the counter
end
return table.concat(initials)
end
-- Formats a list of people (e.g. authors / editors)
function list_people(control, people, etal, is_dstu)
local sep;
local namesep;
local format = control.format;
local maximum = control.maximum;
local lastauthoramp = control.lastauthoramp;
local name_list = {};
if 'vanc' == format or is_dstu then -- Vancouver/DSTU2006-like name styling?
sep = cfg.presentation['sep_nl_vanc_dstu']; -- name-list separator between names is a comma
namesep = cfg.presentation['sep_name_vanc_dstu']; -- last/first separator is a space
elseif utilities.is_set(control.sep) and utilities.is_set(control.namesep) then --перевірка чи використовується стара реалізація цієї функції
sep = control.sep; --присвоєння роздільника між іменами
namesep = control.namesep; --присвоєння роздільника між прізвищем і ім'ям
else
sep = cfg.presentation['sep_nl']; -- name-list separator between names is a semicolon
namesep = cfg.presentation['sep_name']; -- last/first separator is <comma><space>
end
if sep:sub(-1,-1) ~= " " then sep = sep .. " " end
if utilities.is_set (maximum) and maximum < 1 then return "", 0; end -- returned 0 is for EditorCount; not used for other names
for i,person in ipairs(people) do
if utilities.is_set(person.last) then
local mask = person.mask
local one
local sep_one = sep;
if utilities.is_set (maximum) and i > maximum then
etal = true;
break;
end
if (mask ~= nil) then
local n = tonumber(mask) -- convert to a number if it can be converted; nil else
if (n ~= nil) then
one = string.rep("—",n) -- make a string of (n > 0) mdashes, nil else, to replace name
else
one = mask; -- replace name with mask text (must include name-list separator)
sep_one = " "; -- modify name-list separator
end
else
one = person.last -- get surname
local first = person.first -- get given name
if utilities.is_set(first) then
if ( "vanc" == format ) then
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, nil, i) then -- and name is all Latin characters; corporate authors not tested
first = reduce_to_initials (first, i); -- attempt to convert first name(s) to initials
end
end
if is_dstu then
local temp_name;
temp_name, accept = utilities.has_accept_as_written(first);
if accept then
first = temp_name;
else
first = dstu_reduce_to_initials (first, i);
end
end
one = one .. namesep .. first;
end
if utilities.is_set(person.link) then
one = "[[" .. person.link .. "|" .. one .. "]]"; -- link author/editor
end
end
table.insert( name_list, one )
table.insert( name_list, sep_one )
end
end
local count = #name_list / 2; -- (number of names + number of separators) divided by 2
if count > 0 then
if count > 1 and not etal then
if utilities.is_set(lastauthoramp) or 'amp' == format then
name_list[#name_list-2] = " & "; -- replace last separator with ampersand text
elseif 'and' == format then
name_list[#name_list-2] = cfg.presentation.sep_nl_and; -- replace last separator with 'and' text
end
end
name_list[#name_list] = nil;
end
local result = table.concat(name_list) -- construct list
if etal and utilities.is_set (result) then -- etal may be set by |display-authors=etal but we might not have a last-first list
result = result .. " " .. cfg.messages['et al']; -- we've got a last-first list and etal so add et al.
end
-- if necessary wrap result in <span> tag to format in Small Caps
if ( "scap" == format ) then result =
'<span class="smallcaps" style="font-variant:small-caps">' .. result .. '</span>';
end
return result, count
end
-- Generates a CITEREF anchor ID.
function anchor_id( options )
return "CITEREF" .. table.concat( options );
end
--[[--------------------------< C I T E _ C L A S S _A T T R I B U T E _M A K E >------------------------------
construct <cite> tag class attribute for this citation.
<cite_class> – config.CitationClass from calling template
<mode> – value from |mode= parameter
]]
local function cite_class_attribute_make (cite_class, mode)
local class_t = {};
table.insert (class_t, 'citation'); -- required for blue highlight
if 'citation' ~= cite_class then
table.insert (class_t, cite_class); -- identify this template for user css
table.insert (class_t, utilities.is_set (mode) and mode or 'cs1'); -- identify the citation style for user css or javascript
else
table.insert (class_t, utilities.is_set (mode) and mode or 'cs2'); -- identify the citation style for user css or javascript
end
for _, prop_key in ipairs (z.prop_keys_t) do
table.insert (class_t, prop_key); -- identify various properties for user css or javascript
end
return table.concat (class_t, ' '); -- make a big string and done
end
--[[---------------------< N A M E _ H A S _ E T A L >--------------------------
Evaluates the content of name parameters (author, editor, etc.) for variations on
the theme of et al. If found, the et al. is removed, a flag is set to true and
the function returns the modified name and the flag.
This function never sets the flag to false but returns its previous state because
it may have been set by previous passes through this function or by the associated
|display-<names>=etal parameter
]]
local function name_has_etal (name, etal, nocat, param)
if utilities.is_set (name) then -- name can be nil in which case just return
local patterns = cfg.et_al_patterns; -- get patterns from configuration
for _, pattern in ipairs (patterns) do -- loop through all of the patterns
if mw.ustring.match(name, pattern) then -- if this 'et al' pattern is found in name
name = mw.ustring.gsub (name, pattern, ''); -- remove the offending text
etal = true; -- set flag (may have been set previously here or by |display-<names>=etal)
if not nocat then -- no categorization for |vauthors=
utilities.set_message ('err_etal', {param}); -- and set an error if not added
end
end
end
end
return name, etal;
end
--[[----------------------< E X T R A C T _ N A M E S >-------------------------
Gets name list from the input arguments
Searches through args in sequential order to find |lastn= and |firstn= parameters
(or their aliases), and their matching link and mask parameters. Stops searching
when both |lastn= and |firstn= are not found in args after two sequential attempts:
found |last1=, |last2=, and |last3= but doesn't find |last4= and |last5= then the
search is done.
This function emits an error message when there is a |firstn= without a matching
|lastn=. When there are 'holes' in the list of last names, |last1= and |last3=
are present but |last2= is missing, an error message is emitted. |lastn= is not
required to have a matching |firstn=.
When an author or editor parameter contains some form of 'et al.', the 'et al.'
is stripped from the parameter and a flag (etal) returned that will cause list_people()
to add the static 'et al.' text from Module:Citation/CS1/Configuration. This keeps
'et al.' out of the template's metadata. When this occurs, an error is emitted.
]]
function extract_names(args, list_name)
local names = {}; -- table of names
local last; -- individual name components
local first;
local link;
local mask;
local i = 1;
local n = 1; -- output table indexer
local count = 0; -- used to count the number of times we haven't found a |last= (or alias for authors, |editor-last or alias for editors)
local etal = false; -- return value set to true when we find some form of et al. in an author parameter
local last_alias, first_alias;--, link_alias;
while true do
last, last_alias = select_one ( args, cfg.aliases[list_name .. '-Last'], 'err_redundant_parameters', i ); -- search through args for name components beginning at 1
first, first_alias = select_one ( args, cfg.aliases[list_name .. '-First'], 'err_redundant_parameters', i );
link = select_one ( args, cfg.aliases[list_name .. '-Link'], 'err_redundant_parameters', i );
mask = select_one ( args, cfg.aliases[list_name .. '-Mask'], 'err_redundant_parameters', i );
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.
if first and not last then -- if there is a firstn without a matching lastn
local alias = first_alias:find ('given', 1, true) and 'given' or 'first'; -- get first or given form of the alias
utilities.set_message ('err_first_missing_last', {
first_alias, -- param name of alias missing its mate
first_alias:gsub (alias, {['first'] = 'last', ['given'] = 'surname'}), -- make param name appropriate to the alias form
}); -- add this error message
elseif not first and not last then -- if both firstn and lastn aren't found, are we done?
count = count + 1; -- number of times we haven't found last and first
if 2 <= count then -- two missing names and we give up
break; -- normal exit or there is a two-name hole in the list; can't tell which
end
else
local result;
link = link_title_ok (link, link_alias, last, last_alias); -- check for improper wiki-markup
if first then
link = link_title_ok (link, link_alias, first, first_alias); -- check for improper wiki-markup
end
names[n] = {last = last, first = first, link = link, mask = mask, corporate = false}; -- add this name to our names list (corporate for |vauthors= only)
n = n + 1; -- point to next location in the names table
if 1 == count then -- if the previous name was missing
utilities.set_message ('err_missing_name', {list_name:match ("(%w+)List"):ulower(), i - 1}); -- add this error message
end
count = 0; -- reset the counter, we're looking for two consecutive missing names
end
i = i + 1;
end
return names, etal;
end
--[[-----------------------< S E T _ C S _ S T Y L E >--------------------------
Gets the default CS style configuration for the given mode.
Returns default separator and either postscript as passed in or the default.
In CS1, the default postscript and separator are '.'.
In CS2, the default postscript is the empty string and the default separator is ','.
]]
local function set_cs_style (postscript, mode)
if utilities.is_set(postscript) then
-- emit a maintenance message if user postscript is the default cs1 postscript
-- we catch the opposite case for cs2 in set_style
if mode == 'cs1' and postscript == cfg.presentation['ps_' .. mode] then
utilities.set_message ('maint_postscript');
end
else
postscript = cfg.presentation['ps_' .. mode];
end
return cfg.presentation['sep_' .. mode], postscript;
end
--[[--------------------------< S E T _ S T Y L E >-----------------------------
Sets the separator and postscript styles. Checks the |mode= first and the
#invoke CitationClass second. Removes the postscript if postscript == none.
]]
local function set_style (mode, postscript, cite_class)
local sep;
if 'cs2' == mode then
sep, postscript = set_cs_style (postscript, 'cs2');
elseif 'cs1' == mode then
sep, postscript = set_cs_style (postscript, 'cs1');
elseif 'dstu2006' == mode then
sep, postscript = set_cs_style (postscript, 'dstu2006');
-- elseif 'citation' == cite_class then
-- sep, postscript = set_cs_style (postscript, 'cs2');
else
sep, postscript = set_cs_style (postscript, 'cs1');
end
if cfg.keywords_xlate[postscript:ulower()] == 'none' then
-- emit a maintenance message if user postscript is the default cs2 postscript
-- we catch the opposite case for cs1 in set_cs_style
if 'cs2' == mode --[[or 'citation' == cite_class]] then
utilities.set_message ('maint_postscript');
end
postscript = '';
end
return sep, postscript
end
--[[---------------------< G E T _ D I S P L A Y _ N A M E S >------------------
Returns a number that defines the number of names displayed for author and editor
name lists and a Boolean flag to indicate when et al. should be appended to the name list.
When the value assigned to |display-xxxxors= is a number greater than or equal to zero,
return the number and the previous state of the 'etal' flag (false by default
but may have been set to true if the name list contains some variant of the text 'et al.').
When the value assigned to |display-xxxxors= is the keyword 'etal', return a number
that is one greater than the number of authors in the list and set the 'etal' flag true.
This will cause the list_people() to display all of the names in the name list followed by 'et al.'
In all other cases, returns nil and the previous state of the 'etal' flag.
inputs:
max: A['DisplayAuthors'] or A['DisplayEditors'], etc; a number or some flavor of etal
count: #a or #e
list_name: 'authors' or 'editors'
etal: author_etal or editor_etal
This function sets an error message when |display-xxxxors= value greater than or equal to number of names but
not when <max> comes from {{cs1 config}} global settings. When using global settings, <param> is set to the
keyword 'cs1 config' which is used to supress the normal error. Error is suppressed because it is to be expected
that some citations in an article will have the same or fewer names that the limit specified in {{cs1 config}}.
]]
local function get_display_names (max, count, list_name, etal, param)
if utilities.is_set (max) then
if 'etal' == max:ulower():gsub("[ '%.]", '') then -- the :gsub() portion makes 'etal' from a variety of 'et al.' spellings and stylings
max = count + 1; -- number of authors + 1 so display all author name plus et al.
etal = true; -- overrides value set by extract_names()
elseif max:match ('^%d+$') then -- if is a string of numbers
max = tonumber (max); -- make it a number
if (max >= count) and ('cs1 config' ~= param) then -- error when local |display-xxxxors= value greater than or equal to number of names; not an error when using global setting
utilities.set_message ('err_disp_name', {param, max}); -- add error message
max = nil;
end
else -- not a valid keyword or number
utilities.set_message ('err_disp_name', {param, max}); -- add error message
max = nil; -- unset; as if |display-xxxxors= had not been set
end
end
return max, etal;
end
-- Chooses one matching parameter from a list of parameters to consider
-- Generates an error if more than one match is present.
function select_one( args, possible, error_condition, index )
local value = nil;
local selected = '';
local error_list = {};
if index ~= nil then index = tostring(index); end
-- Handle special case of "#" replaced by empty string
if index == '1' then
for _, v in ipairs( possible ) do
v = v:gsub( "#", "" );
if utilities.is_set(args[v]) then
if value ~= nil and selected ~= v then
table.insert( error_list, v );
else
value = args[v];
selected = v;
end
end
end
end
for _, v in ipairs( possible ) do
if index ~= nil then
v = v:gsub( "#", index );
end
if utilities.is_set(args[v]) then
if value ~= nil and selected ~= v then
table.insert( error_list, v );
else
value = args[v];
selected = v;
end
end
end
if #error_list > 0 then
local error_str = "";
for _, k in ipairs( error_list ) do
if error_str ~= "" then error_str = error_str .. cfg.messages['parameter-separator'] end
error_str = error_str .. wrap( 'parameter', k );
end
if #error_list > 1 then
error_str = error_str .. cfg.messages['parameter-final-separator'];
else
error_str = error_str .. cfg.messages['parameter-pair-separator'];
end
error_str = error_str .. wrap( 'parameter', selected );
utilities.set_message( error_condition, {error_str});
end
return value, selected;
end
--[=[-------------------------< 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 parentheses were included in the
rendered citation and in the metadata. Individual author names may be wikilinked
|vauthors=Jones AB, [[E. B. White|White EB]], ((Black, Brown, and Co.))
]=]
local function get_v_name_table (vparam, output_table, output_link_table)
local name_table = mw.text.split(vparam, "%s*,%s*"); -- names are separated by commas
local wl_type, label, link; -- wl_type not used here; just a placeholder
local i = 1;
while name_table[i] do
if name_table[i]:match ('^%(%(.*[^%)][^%)]$') then -- first segment of corporate with one or more commas; this segment has the opening doubled parentheses
local name = name_table[i];
i = i + 1; -- bump indexer to next segment
while name_table[i] do
name = name .. ', ' .. name_table[i]; -- concatenate with previous segments
if name_table[i]:match ('^.*%)%)$') then -- if this table member has the closing doubled parentheses
break; -- and done reassembling so
end
i = i + 1; -- bump indexer
end
table.insert (output_table, name); -- and add corporate name to the output table
table.insert (output_link_table, ''); -- no wikilink
else
wl_type, label, link = utilities.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
i = i + 1;
end
return output_table;
end
--[[--------------------------< P A R S E _ V A U T H O R S _ V E D I T O R S >--------------------------------
This function extracts author / editor names from |vauthors= or |veditors= and finds matching |xxxxor-maskn= and
|xxxxor-linkn= in args. It then returns a table of assembled names just as extract_names() does.
Author / editor names in |vauthors= or |veditors= must be in Vancouver system style. Corporate or institutional names
may sometimes be required and because such names will often fail the is_good_vanc_name() and other format compliance
tests, are wrapped in doubled parentheses ((corporate name)) to suppress the format tests.
Supports generational suffixes Jr, 2nd, 3rd, 4th–6th.
This function sets the Vancouver error when a required comma is missing and when there is a space between an author's initials.
]]
local function parse_vauthors_veditors (args, vparam, list_name)
local names = {}; -- table of names assembled from |vauthors=, |author-maskn=, |author-linkn=
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 last, first, link, mask, suffix;
local corporate = false;
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)
v_name_table = get_v_name_table (vparam, v_name_table, v_link_table); -- names are separated by commas
for i, v_name in ipairs(v_name_table) do
first = ''; -- set to empty string for concatenation and because it may have been set for previous author/editor
local accept_name;
v_name, accept_name = utilities.has_accept_as_written (v_name); -- remove accept-this-as-written markup when it wraps all of <v_name>
if accept_name then
last = v_name;
corporate = true; -- flag used in list_people()
elseif string.find(v_name, "%s") then
if v_name:find('[;%.]') then -- look for commonly occurring punctuation characters;
add_vanc_error (cfg.err_msg_supl.punctuation, i);
end
local lastfirstTable = {}
lastfirstTable = mw.text.split(v_name, "%s+")
first = table.remove(lastfirstTable); -- removes and returns value of last element in table which should be initials or generational suffix
if not mw.ustring.match (first, '^%u+$') then -- mw.ustring here so that later we will catch non-Latin characters
suffix = first; -- not initials so assume that whatever we got is a generational suffix
first = table.remove(lastfirstTable); -- get what should be the initials from the table
end
last = table.concat(lastfirstTable, ' ') -- returns a string that is the concatenation of all other names that are not initials and generational suffix
if not utilities.is_set (last) then
first = ''; -- unset
last = v_name; -- last empty because something wrong with first
add_vanc_error (cfg.err_msg_supl.name, i);
end
if mw.ustring.match (last, '%a+%s+%u+%s+%a+') then
add_vanc_error (cfg.err_msg_supl['missing comma'], i); -- matches last II last; the case when a comma is missing
end
if mw.ustring.match (v_name, ' %u %u$') then -- this test is in the wrong place TODO: move or replace with a more appropriate test
add_vanc_error (cfg.err_msg_supl.initials, i); -- matches a space between two initials
end
else
last = v_name; -- last name or single corporate name? Doesn't support multiword corporate names? do we need this?
end
if utilities.is_set (first) then
if not mw.ustring.match (first, "^%u?%u$") then -- first shall contain one or two upper-case letters, nothing else
add_vanc_error (cfg.err_msg_supl.initials, i); -- too many initials; mixed case initials (which may be ok Romanization); hyphenated initials
end
is_good_vanc_name (last, first, suffix, i); -- check first and last before restoring the suffix which may have a non-Latin digit
if utilities.is_set (suffix) then
first = first .. ' ' .. suffix; -- if there was a suffix concatenate with the initials
suffix = ''; -- unset so we don't add this suffix to all subsequent names
end
else
if not corporate then
is_good_vanc_name (last, '', nil, i);
end
end
link = select_one ( args, cfg.aliases[list_name .. '-Link'], 'err_redundant_parameters', i ) or v_link_table[i];
mask = select_one ( args, cfg.aliases[list_name .. '-Mask'], 'err_redundant_parameters', i );
names[i] = {last = last, first = first, link = link, mask = mask, corporate = corporate}; -- add this assembled name to our names list
end
return names, etal; -- all done, return our list of names
end
--[[--------------------------< S E L E C T _ A U T H O R _ E D I T O R _ S O U R C E >------------------------
Select one of |authors=, |authorn= / |lastn / firstn=, or |vauthors= as the source of the author name list or
select one of |editorn= / editor-lastn= / |editor-firstn= or |veditors= as the source of the editor name list.
Only one of these appropriate three will be used. The hierarchy is: |authorn= (and aliases) highest and |authors= lowest;
|editorn= (and aliases) highest and |veditors= lowest (support for |editors= withdrawn)
When looking for |authorn= / |editorn= parameters, test |xxxxor1= and |xxxxor2= (and all of their aliases); stops after the second
test which mimicks the test used in extract_names() when looking for a hole in the author name list. There may be a better
way to do this, I just haven't discovered what that way is.
Emits an error message when more than one xxxxor name source is provided.
In this function, vxxxxors = vauthors or veditors; xxxxors = authors as appropriate.
]]
local function select_author_editor_source (vxxxxors, xxxxors, args, list_name)
local lastfirst = false;
if utilities.select_one ( args, cfg.aliases[list_name .. '-Last'], 'none', 1 ) or -- do this twice in case we have a |first1= without a |last1=; this ...
utilities.select_one ( args, cfg.aliases[list_name .. '-First'], 'none', 1 ) or -- ... also catches the case where |first= is used with |vauthors=
utilities.select_one ( args, cfg.aliases[list_name .. '-Last'], 'none', 2 ) or
utilities.select_one ( args, cfg.aliases[list_name .. '-First'], 'none', 2 ) then
lastfirst = true;
end
if (utilities.is_set (vxxxxors) and true == lastfirst) or -- these are the three error conditions
(utilities.is_set (vxxxxors) and utilities.is_set (xxxxors)) or
(true == lastfirst and utilities.is_set (xxxxors)) then
local err_name;
if 'AuthorList' == list_name then -- figure out which name should be used in error message
err_name = 'author';
else
err_name = 'editor';
end
utilities.set_message ('err_redundant_parameters', 'параметр' .. err_name .. '-name-list'); -- add error message
end
if true == lastfirst then return 1 end; -- return a number indicating which author name source to use
if utilities.is_set (vxxxxors) then return 2 end;
if utilities.is_set (xxxxors) then return 3 end;
return 1; -- no authors so return 1; this allows missing author name test to run in case there is a first without last
end
--[[--------------------------< I S _ V A L I D _ P A R A M E T E R _ V A L U E >------------------------------
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, live, dead, etc.). When the parameter value has not been assigned a value (missing
or empty in the source template) the function returns the value specified by ret_val. If the parameter value is one
of the list of allowed values returns the translated value; else, emits an error message and returns the value
specified by ret_val.
TODO: explain <invert>
]]
local function is_valid_parameter_value (value, name, possible, ret_val, invert)
if not utilities.is_set (value) then
return ret_val; -- an empty parameter is ok
end
if (not invert and utilities.in_array (value, possible)) then -- normal; <value> is in <possible> table
return cfg.keywords_xlate[value]; -- return translation of parameter keyword
elseif invert and not utilities.in_array (value, possible) then -- invert; <value> is not in <possible> table
return value; -- return <value> as it is
else
utilities.set_message ('err_invalid_param_val', {name, value}); -- not an allowed value so add error message
return ret_val;
end
end
--[[--------------------------< T E R M I N A T E _ N A M E _ L I S T >----------------------------------------
This function terminates a name list (author, contributor, editor) with a separator character (sepc) and a space
when the last character is not a sepc character or when the last three characters are not sepc followed by two
closing square brackets (close of a wikilink). When either of these is true, the name_list is terminated with a
single space character.
]]
local function terminate_name_list (name_list, sepc)
if (string.sub (name_list, -3, -1) == sepc .. '. ') then -- if already properly terminated
return name_list; -- just return the name list
elseif (string.sub (name_list, -1, -1) == sepc) or (string.sub (name_list, -3, -1) == sepc .. ']]') then -- if last name in list ends with sepc char
return name_list .. " "; -- don't add another
else
return name_list .. sepc .. ' '; -- otherwise terminate the name list
end
end
--[[-------------------------< 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 (or journal article number) parameters as a single
string; or formatted volume or formatted issue, or an empty string if neither are set.
]]
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) and not utilities.is_set (article) then
return '';
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 volume and (not is_numeric_vol and is_long_vol) then -- when not all digits or Roman numerals, is |volume= longer than 4 characters?
utilities.add_prop_cat ('long-vol'); -- yes, add properties cat
end
if is_journal then -- journal-style formatting
local vol = '';
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
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
if 'podcast' == cite_class and utilities.is_set (issue) then
return wrap_msg ('issue', {sepc, issue}, lower);
end
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
return wrap_msg ('vol-art', {sepc, utilities.hyphen_to_dash (volume), article}, lower);
elseif utilities.is_set (article) then -- article number alone; when volume alone, handled below
return wrap_msg ('art', {sepc, article}, lower);
end
end
-- 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
--[[
This is the main function foing the majority of the citation
formatting.
]]
function citation0( config, args)
--[[
Load Input Parameters
The argment_wrapper facillitates the mapping of multiple
aliases to single internal variable.
]]
local A = argument_wrapper( args );
local i
local PPrefix = A['PPrefix']
local PPPrefix = A['PPPrefix']
if utilities.is_set( A['NoPP'] ) then PPPrefix = "" PPrefix = "" end
-- Pick out the relevant fields from the arguments. Different citation templates
-- define different field names for the same underlying things.
local Authors = A['Authors'];
local author_etal;
local a = {};
local NameListStyle;
NameListStyle = is_valid_parameter_value (A['NameListStyle'], A:ORIGIN('NameListStyle'), cfg.keywords_lists['name-list-style'], '');
local Coauthors = A['Coauthors'];
do -- to limit scope of selected
local selected = select_author_editor_source (A['Vauthors'], A['Authors'], args, 'AuthorList');
if 1 == selected then
a, author_etal = extract_names (args, 'AuthorList'); -- fetch author list from |authorn= / |lastn= / |firstn=, |author-linkn=, and |author-maskn=
elseif 2 == selected then
NameListStyle = 'vanc'; -- override whatever |name-list-style= might be
a, author_etal = parse_vauthors_veditors (args, A['Vauthors'], 'AuthorList'); -- fetch author list from |vauthors=, |author-linkn=, and |author-maskn=
end --
end
local Others = A['Others'];
local Editors = A['Editors'];
local EditorCount;
local editor_etal;
local e = {};
do -- to limit scope of selected
local selected = select_author_editor_source (A['Veditors'], A['Editors'], args, 'EditorList'); -- support for |editors= withdrawn
if 1 == selected then
e, editor_etal = extract_names (args, 'EditorList'); -- fetch editor list from |editorn= / |editor-lastn= / |editor-firstn=, |editor-linkn=, and |editor-maskn=
elseif 2 == selected then
NameListStyle = 'vanc'; -- override whatever |name-list-style= might be
e, editor_etal = parse_vauthors_veditors (args, args.veditors, 'EditorList'); -- fetch editor list from |veditors=, |editor-linkn=, and |editor-maskn=
end
end
local Translators; -- assembled translators name list
local translator_etal;
local t = {}; -- translators list from |translator-lastn= / translator-firstn= pairs
t = extract_names (args, 'TranslatorList'); -- fetch translator list from |translatorn= / |translator-lastn=, -firstn=, -linkn=, -maskn=
local Interviewers;
local interviewers_list = {};
interviewers_list = extract_names (args, 'InterviewerList'); -- process preferred interviewers parameters
local interviewer_etal;
local Year = A['Year'];
local PublicationDate = A['PublicationDate'];
local Date = A['Date'];
local LayDate = A['LayDate'];
------------------------------------------------- Get title data
local Title = A['Title'];
local BookTitle = A['BookTitle'];
local Conference = A['Conference'];
local ScriptTitle = A['ScriptTitle'];
local TransTitle = A['TransTitle'];
local TitleNote = A['TitleNote'];
local TitleLink = A['TitleLink'];
TitleLink = link_title_ok (TitleLink, A:ORIGIN ('TitleLink'), Title, 'title'); -- check for wiki-markup in |title-link= or wiki-markup in |title= when |title-link= is set
local URL = A['URL'];
local UrlAccess = is_valid_parameter_value (A['UrlAccess'], A:ORIGIN('UrlAccess'), cfg.keywords_lists['url-access'], nil);
local SubscriptionRequired = A['SubscriptionRequired'];
if utilities.is_set(SubscriptionRequired) then
UrlAccess = is_valid_parameter_value ('subscription', A:ORIGIN('SubscriptionRequired'), cfg.keywords_lists['url-access'], nil);
end
if not utilities.is_set (URL) and utilities.is_set (UrlAccess) then
UrlAccess = nil;
utilities.set_message ('err_param_access_requires_param', 'url');
end
local ChapterURL = A['ChapterURL'];
local ChapterUrlAccess = is_valid_parameter_value (A['ChapterUrlAccess'], A:ORIGIN('ChapterUrlAccess'), cfg.keywords_lists['url-access'], nil);
if not utilities.is_set (ChapterURL) and utilities.is_set (ChapterUrlAccess) then
ChapterUrlAccess = nil;
utilities.set_message ('err_param_access_requires_param', {A:ORIGIN('ChapterUrlAccess'):gsub ('%-access', '')});
end
local MapUrlAccess = is_valid_parameter_value (A['MapUrlAccess'], A:ORIGIN('MapUrlAccess'), cfg.keywords_lists['url-access'], nil);
if not utilities.is_set (A['MapURL']) and utilities.is_set (MapUrlAccess) then
MapUrlAccess = nil;
utilities.set_message ('err_param_access_requires_param', {'map-url'});
end
local Chapter = A['Chapter'];
local ChapterLink = A['ChapterLink'];
local URL_origin = A:ORIGIN('URL');
local ChapterURL_origin = A:ORIGIN('ChapterURL');
local Chapter_origin = A:ORIGIN ('Chapter');
local ScriptChapter = A['ScriptChapter'];
local ScriptChapter_origin = A:ORIGIN ('ScriptChapter');
local TransChapter = A['TransChapter'];
local Section = ''; -- {{cite map}} only; preset to empty string for concatenation if not used
if 'map' == config.CitationClass and 'section' == Chapter_origin then
Section = A['Chapter']; -- get |section= from |chapter= alias list; |chapter= and the other aliases not supported in {{cite map}}
Chapter = ''; -- unset for now; will be reset later from |map= if present
end
local sepc = A['Separator'];
-- CS1/2 mode
local Mode;
--[[if cfg.global_cs1_config_t['Mode'] then -- global setting in {{cs1 config}} overrides local |mode= parameter value; nil when empty or assigned value invalid
Mode = is_valid_parameter_value (cfg.global_cs1_config_t['Mode'], 'cs1 config: mode', cfg.keywords_lists['mode'], ''); -- error messaging 'param' here is a hoax
else]]
Mode = is_valid_parameter_value (A['Mode'], A:ORIGIN('Mode'), cfg.keywords_lists['mode'], '');
--end
--[[if cfg.global_cs1_config_t['Mode'] and utilities.is_set (A['Mode']) then -- when template has |mode=<something> which global setting has overridden
utilities.set_message ('maint_overridden_setting'); -- set a maint message
end]]
-- separator character and postscript
local sepc, PostScript = set_style (Mode:ulower(), A['PostScript'], config.CitationClass);
-- controls capitalization of certain static text
local use_lowercase = ( sepc == ',' );
-- керує оформленням посилання якщо дсту
local is_dstu = ('dstu2006' == Mode:ulower());
-- controls capitalization of certain static text
local use_uppercase = (sepc == '.' or is_dstu);
-- cite map oddities
local Cartography = "";
local Scale = "";
--local Sheet = A['Sheet'] or ''; (у планах, поки не реалізовано)
--local Sheets = A['Sheets'] or ''; (у планах, поки не реалізовано)
if config.CitationClass == "map" then
if utilities.is_set (Chapter) then --TODO: make a function for this and similar?
utilities.set_message ('err_redundant_parameters', {wrap ('parameter', 'map') .. cfg.presentation['sep_list_pair'] .. wrap ('parameter', Chapter_origin)}); -- add error message
end
Chapter = A['Map'];
Chapter_origin = A:ORIGIN('Map');
ChapterURL = A['MapURL'];
ChapterURL_origin = A:ORIGIN('MapURL');
TransChapter = A['TransMap'];
ScriptChapter = A['ScriptMap'];
ScriptChapter_origin = A:ORIGIN('ScriptMap');
ChapterUrlAccess = MapUrlAccess;
--ChapterFormat = A['MapFormat']; (поки не реалізовано, потрібно переписувати код Chapter)
Cartography = A['Cartography'];
if utilities.is_set ( Cartography ) then
Cartography = sepc .. " " .. wrap_msg ('cartography', Cartography, use_lowercase);
end
Scale = A['Scale'];
if utilities.is_set ( Scale ) then
Scale = sepc .. " " .. Scale;
end
end
local TitleType = A['TitleType'];
local Degree = A['Degree'];
if utilities.in_array (config.CitationClass, {'AV-media-notes', 'document', 'interview', 'mailinglist', 'map', 'podcast', 'pressrelease', 'report', 'speech', 'techreport', 'thesis'}) then
TitleType = set_titletype (config.CitationClass, TitleType);
if utilities.is_set (Degree) and "Дипломна робота" == TitleType then -- special case for cite thesis
TitleType =cfg.title_types ['thesis'] .. ' ' .. Degree;
end
end
local ArchiveURL = A['ArchiveURL'];
local UrlStatus = is_valid_parameter_value (A['UrlStatus'], A:ORIGIN('UrlStatus'), cfg.keywords_lists['url-status'], '');
local ConferenceURL = A['ConferenceURL'];
local ConferenceURL_origin = A:ORIGIN('ConferenceURL');
local Periodical = A['Periodical'];
local Periodical_origin = A:ORIGIN('Periodical');
local ScriptPeriodical = A['ScriptPeriodical'];
local ScriptPeriodical_origin = A:ORIGIN('ScriptPeriodical');
local TransPeriodical = A['TransPeriodical'];
local TransPeriodical_origin = A:ORIGIN ('TransPeriodical');
if ( config.CitationClass == "encyclopaedia" ) then
if not utilities.is_set(Chapter) then
if not utilities.is_set(Title) or utilities.is_set (ScriptTitle) then
Title = Periodical;
Periodical = '';
else
Chapter = Title;
ScriptChapter= ScriptTitle;
ScriptChapter_origin = A:ORIGIN('ScriptTitle');
TransChapter = TransTitle;
Title = '';
ScriptTitle = '';
TransTitle = '';
end
end
end
if 'mailinglist' == config.CitationClass then -- special case for {{cite mailing list}}
if utilities.is_set (Periodical) and utilities.is_set (A ['MailingList']) then -- both set emit an error TODO: make a function for this and similar?
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', Periodical_origin) .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'mailinglist')});
end
Periodical = A ['MailingList']; -- error or no, set Periodical to |mailinglist= value because this template is {{cite mailing list}}
Periodical_origin = A:ORIGIN('MailingList');
end
local Series = A['Series'];
local Volume = A['Volume'];
local Issue = A['Issue'];
local Position = '';
local Page, Pages, At, page_type;
Page = A['Page'];
Pages = hyphen_to_dash( A['Pages'] );
At = A['At'];
if utilities.is_set(Page) then
if utilities.is_set(At) then
utilities.set_message('err_extra_pages');
At = '';
end
end
local Edition = A['Edition'];
local PublicationPlace = A['PublicationPlace']
local Place = A['Place'];
if not utilities.is_set(PublicationPlace) and utilities.is_set(Place) then
PublicationPlace = Place;
end
if PublicationPlace == Place then Place = ''; end
local PublisherName = A['PublisherName'];
local Via = A['Via'];
--[[
Go test all of the date-holding parameters for valid MOS:DATE format and make sure that dates are real dates. This must be done before we do COinS because here is where
we get the date used in the metadata.
Date validation supporting code is in Module:Citation/CS1/Date_validation
]]
local DF = is_valid_parameter_value (A['DF'], A:ORIGIN('DF'), cfg.keywords_lists['df'], '');
-- if not utilities.is_set (DF) then
-- DF = cfg.global_df; -- local |df= if present overrides global df set by {{use xxx date}} template
-- end
local AccessDate = A['AccessDate'];
local ArchiveDate = A['ArchiveDate'];
local Agency = A['Agency'];
local DeadURL = A['DeadURL']
local Language = A['Language'];
local Format = A['Format'];
local Ref = A['Ref'];
local DoiBroken = A['DoiBroken'];
local ID = A['ID'];
local ASINTLD = A['ASINTLD'];
local IgnoreISBN = A['IgnoreISBN'];
local Embargo = A['Embargo'];
local anchor_year;
do -- create defined block to contain local variables error_message, date_parameters_list, mismatch
local date_parameters_list = {
['access-date'] = {val = AccessDate, name = A:ORIGIN ('AccessDate')},
['archive-date'] = {val = ArchiveDate, name = A:ORIGIN ('ArchiveDate')},
['date'] = {val = Date, name = Date_origin},
['doi-broken-date'] = {val = DoiBroken, name = A:ORIGIN ('DoiBroken')},
['pmc-embargo-date'] = {val = Embargo, name = A:ORIGIN ('Embargo')},
['publication-date'] = {val = PublicationDate, name = A:ORIGIN ('PublicationDate')},
['year'] = {val = Year, name = A:ORIGIN ('Year')},
};
local error_list = {};
anchor_year, Embargo = validation.dates(date_parameters_list, COinS_date, error_list);
if utilities.is_set (Year) and utilities.is_set (Date) then -- both |date= and |year= not normally needed;
validation.year_date_check (Year, A:ORIGIN ('Year'), Date, A:ORIGIN ('Date'), error_list);
end
if 0 == #error_list then -- error free dates only; 0 when error_list is empty
local modified = false; -- flag
if utilities.is_set (DF) then -- if we need to reformat dates
modified = validation.reformat_dates (date_parameters_list, DF); -- reformat to DF format, use long month names if appropriate
end
if true == validation.date_hyphen_to_dash (date_parameters_list) then -- convert hyphens to dashes where appropriate
modified = true;
utilities.set_message ('maint_date_format'); -- hyphens were converted so add maint category
end
-- for those wikis that can and want to have English date names translated to the local language; not supported at en.wiki
if cfg.date_name_auto_xlate_enable and validation.date_name_xlate (date_parameters_list, cfg.date_digit_auto_xlate_enable ) then
validation.reformat_dates (date_parameters_list, 'dmy-all') -- для українського формату
utilities.set_message ('maint_date_auto_xlated'); -- add maint cat
modified = true;
end
if utilities.is_set(Embargo) then
Embargo = ParseDateIfPossible(Embargo, true) --translates dates from Ukrainian to English, as lang:formatdate() doesn't work with Ukrainian month's names
end
if modified then -- if the date_parameters_list values were modified
AccessDate = date_parameters_list['access-date'].val; -- overwrite date holding parameters with modified values
ArchiveDate = date_parameters_list['archive-date'].val;
Date = date_parameters_list['date'].val;
DoiBroken = date_parameters_list['doi-broken-date'].val;
PublicationDate = date_parameters_list['publication-date'].val;
end
if archive_url_timestamp and utilities.is_set (ArchiveDate) then
validation.archive_date_check (ArchiveDate, archive_url_timestamp, DF); -- does YYYYMMDD in archive_url_timestamp match date in ArchiveDate
end
else
utilities.set_message ('err_bad_date', {utilities.make_sep_list (#error_list, error_list)}); -- add this error message
end
end -- end of do
local ID_list = {};
local ID_list_coins = {}; -- table of identifiers and their values from args; key is same as cfg.id_handlers's key
local Class = A['Class']; -- arxiv class identifier
local ID_support = {
{A['ASINTLD'], 'ASIN', 'err_asintld_missing_asin', A:ORIGIN ('ASINTLD')},
{DoiBroken, 'DOI', 'err_doibroken_missing_doi', A:ORIGIN ('DoiBroken')},
{Embargo, 'PMC', 'err_embargo_missing_pmc', A:ORIGIN ('Embargo')},
}
ID_list, ID_list_coins = identifiers.identifier_lists_get (args, {DoiBroken = DoiBroken, IgnoreISBN = IgnoreISBN, ASINTLD = A['ASINTLD'], Embargo = Embargo, Class = Class}, ID_support);
-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, {{cite medrxiv}}, {{cite ssrn}}, before generation of COinS data.
if utilities.in_array (config.CitationClass, whitelist.preprint_template_list_t) then -- |arxiv= or |eprint= required for cite arxiv; |biorxiv=, |citeseerx=, |medrxiv=, |ssrn= required for their templates
if not (args[cfg.id_handlers[config.CitationClass:upper()].parameters[1]] or -- can't use ID_list_coins k/v table here because invalid parameters omitted
args[cfg.id_handlers[config.CitationClass:upper()].parameters[2]]) then -- which causes unexpected parameter missing error message
utilities.set_message ('err_' .. config.CitationClass .. '_missing'); -- add error message
end
Periodical = ({['arxiv'] = 'arXiv', ['biorxiv'] = 'bioRxiv', ['citeseerx'] = 'CiteSeerX', ['medrxiv'] = 'medRxiv', ['ssrn'] = 'Social Science Research Network'})[config.CitationClass];
end
-- Link the title of the work if no |url= was provided, but we have a |pmc= or a |doi= with |doi-access=free
if config.CitationClass == "journal" and not utilities.is_set (URL) and not utilities.is_set (TitleLink) and not utilities.in_array (cfg.keywords_xlate[Title], {'off', 'none'}) then -- TODO: remove 'none' once existing citations have been switched to 'off', so 'none' can be used as token for "no title" instead
if 'none' ~= cfg.keywords_xlate[auto_select] then -- if auto-linking not disabled
if identifiers.auto_link_urls[auto_select] then -- manual selection
URL = identifiers.auto_link_urls[auto_select]; -- set URL to be the same as identifier's external link
URL_origin = cfg.id_handlers[auto_select:upper()].parameters[1]; -- set URL_origin to parameter name for use in error message if citation is missing a |title=
elseif identifiers.auto_link_urls['pmc'] then -- auto-select PMC
URL = identifiers.auto_link_urls['pmc']; -- set URL to be the same as the PMC external link if not embargoed
URL_origin = cfg.id_handlers['PMC'].parameters[1]; -- set URL_origin to parameter name for use in error message if citation is missing a |title=
elseif identifiers.auto_link_urls['doi'] then -- auto-select DOI
URL = identifiers.auto_link_urls['doi'];
URL_origin = cfg.id_handlers['DOI'].parameters[1];
end
end
if utilities.is_set (URL) then -- set when using an identifier-created URL
if utilities.is_set (AccessDate) then -- |access-date= requires |url=; identifier-created URL is not |url=
utilities.set_message ('err_accessdate_missing_url'); -- add an error message
AccessDate = ''; -- unset
end
if utilities.is_set (ArchiveURL) then -- |archive-url= requires |url=; identifier-created URL is not |url=
utilities.set_message ('err_archive_missing_url'); -- add an error message
ArchiveURL = ''; -- unset
end
end
end
local Quote = A['Quote'];
local ScriptQuote = A['ScriptQuote'];
local TransQuote = A['TransQuote'];
local LayURL = A['LayURL'];
local LaySource = A['LaySource'];
local Transcript = A['Transcript'];
local TranscriptURL = A['TranscriptURL']
local TranscriptURL_origin = A:ORIGIN('TranscriptURL');
local LastAuthorAmp = A['LastAuthorAmp'];
local no_tracking_cats = A['NoTracking'];
local this_page = mw.title.getCurrentTitle(); --Also used for COinS
-- 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 cfg.uncategorized_namespaces[this_page.namespace] then -- is this page's namespace id one of the uncategorized namespace ids?
no_tracking_cats = "true"; -- set no_tracking_cats
end
for _, v in ipairs (cfg.uncategorized_subpages) do -- cycle through page name patterns
if this_page.text:match (v) then -- test page name against each pattern
no_tracking_cats = "true"; -- set no_tracking_cats
break; -- bail out if one is found
end
end
end
-- Account for the oddity that is {{cite conference}}, before generation of COinS data.
if utilities.is_set(BookTitle) then
Chapter = Title;
ChapterLink = TitleLink;
ChapterUrlAccess = UrlAccess;
ScriptChapter = ScriptTitle;
ScriptChapter_origin = A:ORIGIN('ScriptTitle');
TransChapter = TransTitle;
Title = BookTitle;
TitleLink = '';
ScriptTitle = '';
TransTitle = '';
end
-- Account for the oddity that is {{cite episode}} and {{cite serial}}, before generation of COinS data.
if 'episode' == config.CitationClass or 'serial' == config.CitationClass then
local SeriesLink = A['SeriesLink'];
SeriesLink = link_title_ok (SeriesLink, A:ORIGIN ('SeriesLink'), Series, 'series'); -- check for wiki-markup in |series-link= or wiki-markup in |series= when |series-link= is set
local Network = A['Network'];
local Station = A['Station'];
local s, n = {}, {};
-- do common parameters first
if utilities.is_set (Network) then table.insert(n, Network); end
if utilities.is_set (Station) then table.insert(n, Station); end
ID = table.concat(n, sepc .. ' ');
if 'episode' == config.CitationClass then -- handle the oddities that are strictly {{cite episode}}
local Season = A['Season'];
local SeriesNumber = A['SeriesNumber'];
if utilities.is_set (Season) and utilities.is_set (SeriesNumber) then -- these are mutually exclusive so if both are set TODO: make a function for this and similar?
utilities.set_message ('err_redundant_parameters', {utilities.wrap_style ('parameter', 'season') .. cfg.presentation['sep_list_pair'] .. utilities.wrap_style ('parameter', 'seriesno')}); -- add error message
SeriesNumber = ''; -- unset; prefer |season= over |seriesno=
end
-- assemble a table of parts concatenated later into Series
if utilities.is_set (Season) then table.insert(s, wrap_msg ('season', Season, use_lowercase)); end
if utilities.is_set (SeriesNumber) then table.insert(s, wrap_msg ('seriesnum', SeriesNumber, use_lowercase)); end
if utilities.is_set (Issue) then table.insert(s, wrap_msg ('episode', Issue, use_lowercase)); end
Issue = ''; -- unset because this is not a unique parameter
Chapter = Title; -- promote title parameters to chapter
ScriptChapter = ScriptTitle;
ScriptChapter_origin = A:ORIGIN('ScriptTitle');
ChapterLink = TitleLink; -- alias |episode-link=
TransChapter = TransTitle;
ChapterURL = URL;
ChapterUrlAccess = UrlAccess;
ChapterURL_origin = URL_origin;
ChapterFormat = Format;
Title = Series; -- promote series to title
TitleLink = SeriesLink;
Series = table.concat(s, sepc .. ' '); -- this is concatenation of season, seriesno, episode number
if utilities.is_set (ChapterLink) and not utilities.is_set (ChapterURL) then -- link but not URL
--Chapter = utilities.make_wikilink (ChapterLink, Chapter); ( поки не короктнео працює в нас через стару реалізацію chapter)
elseif utilities.is_set (ChapterLink) and utilities.is_set (ChapterURL) then -- if both are set, URL links episode;
Series = utilities.make_wikilink (ChapterLink, Series);
end
URL = ''; -- unset
TransTitle = '';
ScriptTitle = '';
Format = '';
else -- now oddities that are cite serial
Issue = ''; -- unset because this parameter no longer supported by the citation/core version of cite serial
Chapter = A['Episode']; -- TODO: make |episode= available to cite episode someday?
if utilities.is_set (Series) and utilities.is_set (SeriesLink) then
Series = utilities.make_wikilink (SeriesLink, Series);
end
Series = utilities.wrap_style ('italic-title', Series); -- series is italicized
end
end
-- end of {{cite episode}} stuff
-- COinS metadata (see <http://ocoins.info/>) for
-- automated parsing of citation information.
local OCinSoutput = metadata.COinS({
['Periodical'] = Periodical,
['Chapter'] = metadata.make_coins_title(Chapter, ScriptChapter),
['Title'] = metadata.make_coins_title (Title, ScriptTitle),
['PublicationPlace'] = PublicationPlace,
['Date'] = first_set(Date, Year, PublicationDate),
['Degree'] = Degree; -- cite thesis only
['Series'] = Series,
['Volume'] = Volume,
['Issue'] = Issue,
['Pages'] = metadata.get_coins_pages (first_set(Page, Pages, At)),
['Edition'] = Edition,
['PublisherName'] = PublisherName,
['URL'] = first_set( URL, ChapterURL ),
['Authors'] = a,
['ID_list'] = ID_list_coins,
['RawPage'] = this_page.prefixedText,
}, config.CitationClass);
-- Account for the oddities that are {{cite arxiv}}, {{cite biorxiv}}, {{cite citeseerx}}, {{cite medrxiv}}, and {{cite ssrn}} AFTER generation of COinS data.
if utilities.in_array (config.CitationClass, whitelist.preprint_template_list_t) then -- we have set rft.jtitle in COinS to arXiv, bioRxiv, CiteSeerX, medRxiv, or ssrn now unset so it isn't displayed
Periodical = ''; -- periodical not allowed in these templates; if article has been published, use cite journal
end
if (utilities.is_set(Periodical) or utilities.is_set(ScriptPeriodical)) and not (utilities.is_set(Chapter) or utilities.is_set(ScriptChapter)) and (utilities.is_set(Title) or utilities.is_set(ScriptTitle)) then
Chapter = Title;
ChapterLink = TitleLink;
ChapterUrlAccess = UrlAccess;
ScriptChapter = ScriptTitle;
ScriptChapter_origin = A:ORIGIN('ScriptTitle');
TransChapter = TransTitle;
Title = '';
TitleLink = '';
ScriptTitle = '';
TransTitle = '';
end
-- Now perform various field substitutions.
-- We also add leading spaces and surrounding markup and punctuation to the
-- various parts of the citation, but only when they are non-nil.
do
local last_first_list;
local control = {
sep = nil,
namesep = nil,
format = nil, -- empty string, '&', 'amp', 'and', or 'vanc' (у новій версії)
maximum = nil, -- as if display-authors or display-editors not set
lastauthoramp = nil
};
do
control.sep = A["AuthorSeparator"] .. " ";
control.namesep = (first_set(A["AuthorNameSeparator"], A["NameSeparator"]) or "") .. " ";
control.format = first_set(A["AuthorFormat"], NameListStyle);
control.maximum, author_etal = get_display_names (A['DisplayAuthors'], #a, 'authors', author_etal, A:ORIGIN ('DisplayAuthors'));
control.lastauthoramp = LastAuthorAmp;
-- If the coauthor field is also used, prevent ampersand and et al. formatting.
if utilities.is_set(Coauthors) then
control.lastauthoramp = nil;
control.maximum = #a + 1;
end
last_first_list = list_people(control, a, author_etal, is_dstu);
if utilities.is_set (Authors) then
Authors, author_etal = name_has_etal (Authors, author_etal, true, 'authors'); -- find and remove variations on et al.
if author_etal then
Authors = Authors .. ' ' .. cfg.messages['et al']; -- add et al. to authors parameter
end
else
Authors = last_first_list; -- either an author name list or an empty string
end
end
do
control.sep = A["EditorSeparator"] .. " ";
control.namesep = (first_set(A["EditorNameSeparator"], A["NameSeparator"]) or "") .. " ";
control.format = first_set(A["EditorFormat"], NameListStyle);
control.maximum, editor_etal = get_display_names (A['DisplayEditors'], #e, 'editors', editor_etal, A:ORIGIN ('DisplayEditors'));
control.lastauthoramp = LastAuthorAmp;
last_first_list, EditorCount = list_people(control, e, editor_etal, is_dstu);
if utilities.is_set (Editors) then
EditorCount = 1;
Editors, editor_etal = name_has_etal (Editors, editor_etal, true, 'editors'); -- find and remove variations on et al.
if editor_etal then
Editors = Editors .. ' ' .. cfg.messages['et al']; -- add et al. to authors parameter
end
else
Editors = last_first_list; -- either an author name list or an empty string
end
end
do
control.format = NameListStyle;
control.maximum, interviewer_etal = get_display_names (A['DisplayInterviewers'], #interviewers_list, 'interviewers', interviewer_etal, A:ORIGIN ('DisplayInterviewers'));
Interviewers = list_people (control, interviewers_list, interviewer_etal, is_dstu);
end
do
control.format = NameListStyle;
control.maximum, translator_etal = get_display_names (A['DisplayTranslators'], #t, 'translators', translator_etal, A:ORIGIN ('DisplayTranslators'));
Translators = list_people (control, t, translator_etal, is_dstu);
end
end
if not utilities.is_set(Date) then
Date = Year;
if utilities.is_set(Date) then
local Month = A['Month'];
if utilities.is_set(Month) then
Date = Month .. " " .. Date;
local Day = A['Day']
if utilities.is_set(Day) then Date = Day .. " " .. Date end
end
end
end
if utilities.in_array(PublicationDate, {Date, Year}) then PublicationDate = ''; end
if not utilities.is_set(Date) and utilities.is_set(PublicationDate) then
Date = PublicationDate;
PublicationDate = '';
end
-- Captures the value for Date prior to adding parens or other textual transformations
local DateIn = Date;
if not utilities.is_set(URL) and
not utilities.is_set(ChapterURL) and
not utilities.is_set(ArchiveURL) and
not utilities.is_set(ConferenceURL) and
not utilities.is_set(TranscriptURL) then
-- Test if cite web is called without giving a URL
if utilities.in_array (config.CitationClass, {"web", "podcast", "mailinglist"}) then
utilities.set_message( 'err_cite_web_url', {});
end
-- Test if accessdate is given without giving a URL
if utilities.is_set(AccessDate) then
utilities.set_message( 'err_accessdate_missing_url', {});
AccessDate = '';
end
-- Test if format is given without giving a URL
if utilities.is_set(Format) then
utilities.set_message( 'err_format_missing_url' );
end
end
-- Test if citation has no title
if not utilities.is_set(Chapter) and
not utilities.is_set(Title) and
not utilities.is_set(Periodical) and
not utilities.is_set(ScriptPeriodical) and
not utilities.is_set(TransPeriodical) and
not utilities.is_set(Conference) and
not utilities.is_set(ScriptTitle) and
not utilities.is_set(TransTitle) and
not utilities.is_set(ScriptChapter) and
not utilities.is_set(TransChapter) then
utilities.set_message( 'err_citation_missing_title', {});
end
Format = utilities.is_set(Format) and " (" .. Format .. ")" or "";
local OriginalURL = URL;
local OriginalAccess = URLAccess;
local OriginalURL_origin = URL_origin;
DeadURL = DeadURL:ulower();
UrlStatus = UrlStatus:ulower();
if utilities.is_set( ArchiveURL ) then
if ( DeadURL == "yes" and UrlStatus ~= "live") then
URL = ArchiveURL
URL_origin = A:ORIGIN('ArchiveURL')
URLAccess = nil
end
end
local trans_qoute_title;
local trans_italic_title;
if is_dstu then
trans_quoted_title = 'dstu-trans-quoted-title';
trans_italic_title = 'dstu-trans-italic-title';
else
trans_quoted_title = 'trans-quoted-title';
trans_italic_title = 'trans-italic-title';
end
-- Format chapter / article title
if utilities.is_set(Chapter) and utilities.is_set(ChapterLink) then
Chapter = "[[" .. ChapterLink .. "|" .. Chapter .. "]]";
end
if utilities.is_set(Periodical) and utilities.is_set(Title) then
Chapter = wrap( 'italic-title', Chapter );
Chapter = script_concatenate (Chapter, ScriptChapter, ScriptChapter_origin);
TransChapter = wrap( trans_italic_title, TransChapter );
else
Chapter = wrap( 'quoted-title', Chapter );
Chapter = script_concatenate (Chapter, ScriptChapter, ScriptChapter_origin);
TransChapter = wrap( trans_quoted_title, TransChapter );
end
if utilities.is_set(TransChapter) then
if not utilities.is_set(Chapter) then
utilities.set_message( 'err_trans_missing_title', {'chapter'});
else
TransChapter = " " .. TransChapter;
end
end
Chapter = Chapter .. TransChapter;
if utilities.is_set(Chapter) then
if not utilities.is_set(ChapterLink) then
if utilities.is_set(ChapterURL) then
Chapter = external_link( ChapterURL, Chapter, ChapterURL_origin, ChapterUrlAccess );
if not utilities.is_set(URL) then
Chapter = Chapter .. Format;
Format = "";
end
elseif utilities.is_set(URL) then
Chapter = external_link( URL, Chapter, nil, UrlAccess ) .. Format;
URL = "";
Format = "";
end
elseif utilities.is_set(ChapterURL) then
Chapter = Chapter .. " " .. external_link( ChapterURL, nil, ChapterURL_origin, ChapterUrlAccess );
end
if 'map' == config.CitationClass and utilities.is_set (TitleType) then
Chapter = Chapter .. ' ' .. TitleType; -- map annotation here; not after title
end
Chapter = Chapter .. sepc .. " " -- with end-space
elseif utilities.is_set(ChapterURL) then
Chapter = " " .. external_link( ChapterURL, nil, ChapterURL_origin, ChapterUrlAccess ) .. sepc .. " ";
end
-- Format main title.
if utilities.is_set(TitleLink) and utilities.is_set(Title) then
Title = "[[" .. TitleLink .. "|" .. Title .. "]]"
end
if utilities.is_set(Periodical) then
Title = wrap( 'quoted-title', Title );
Title = script_concatenate (Title, ScriptTitle, 'script-title');
TransTitle = wrap( trans_quoted_title, TransTitle );
elseif utilities.in_array(config.CitationClass, {"web","news","pressrelease"}) and
not utilities.is_set(Chapter) then
Title = wrap( 'quoted-title', Title );
Title = script_concatenate (Title, ScriptTitle, 'script-title');
TransTitle = wrap( trans_quoted_title, TransTitle );
else
Title = wrap( 'italic-title', Title );
Title = script_concatenate (Title, ScriptTitle, 'script-title');
TransTitle = wrap( trans_italic_title, TransTitle );
end
if utilities.is_set(TransTitle) then
if not utilities.is_set(Title) then
utilities.set_message( 'err_trans_missing_title', {'title'});
else
TransTitle = " " .. TransTitle;
end
end
Title = Title .. TransTitle;
if utilities.is_set(Title) then
if not utilities.is_set(TitleLink) and utilities.is_set(URL) then
Title = external_link( URL, Title, URL_origin, UrlAccess ) .. Format
URL = "";
Format = "";
end
end
if utilities.is_set(Place) then
if use_uppercase then
Place = " " .. wrap( 'written', Place ) .. sepc .. " ";
else
Place = " " .. utilities.substitute( cfg.messages['written']:ulower(), {Place} ) .. sepc .. " ";
end
end
if utilities.is_set(Conference) then
if utilities.is_set(ConferenceURL) then
Conference = external_link( ConferenceURL, Conference );
end
Conference = " " .. Conference
elseif utilities.is_set(ConferenceURL) then
Conference = " " .. external_link( ConferenceURL, nil, ConferenceURL_origin );
end
if not utilities.is_set(Position) then
local Minutes = A['Minutes'];
if utilities.is_set(Minutes) then
Position = " " .. wrap_msg ('minutes', Minutes, use_lowercase);
else
local Time = A['Time'];
if utilities.is_set(Time) then
local TimeCaption = A['TimeCaption']
if not utilities.is_set(TimeCaption) then
TimeCaption = cfg.messages['event'];
if not use_uppercase then
TimeCaption = TimeCaption:ulower();
end
end
Position = " " .. TimeCaption .. " " .. Time;
end
end
else
Position = " " .. Position;
At = '';
end
local colon;
if is_dstu then
colon = cfg.presentation['sep_colon_dstu2006'];
else
colon = ": ";
end
if utilities.is_set(Pages) then
if utilities.is_set(Periodical) and
not utilities.in_array(config.CitationClass, {"encyclopaedia","web","book","news"}) then
Pages = colon .. Pages;
elseif tonumber(Pages) ~= nil then
Pages = sepc .." " .. PPrefix .. Pages;
else
Pages = sepc .." " .. PPPrefix .. Pages;
end
end
if utilities.is_set(Page) then
if utilities.is_set(Periodical) and
not utilities.in_array(config.CitationClass, {"encyclopaedia","web","book","news"}) then
Page = colon .. Page;
else
Page = sepc .." " .. PPrefix .. Page;
end
end
At = utilities.is_set(At) and (sepc .. " " .. At) or "";
Position = utilities.is_set (Position) and (sepc .. " " .. Position) or "";
if config.CitationClass == 'map' then
local Sections = A['Sections']; -- Section (singular) is an alias of Chapter so set earlier
local Inset = A['Inset'];
if utilities.is_set ( Inset ) then
Inset = sepc .. " " .. wrap_msg ('inset', Inset, use_lowercase);
end
if utilities.is_set ( Sections ) then
Section = sepc .. " " .. wrap_msg ('sections', Sections, use_lowercase);
elseif utilities.is_set ( Section ) then
Section = sepc .. " " .. wrap_msg ('section', Section, use_lowercase);
end
At = At .. Inset .. Section;
end
Others = utilities.is_set(Others) and (sepc .. " " .. Others) or "";
if utilities.is_set (Translators) then
Others = safe_join ({sepc .. ' ', wrap_msg ('translated', Translators, use_lowercase), Others}, sepc);
end
if utilities.is_set (Interviewers) then
Others = safe_join ({sepc .. ' ', wrap_msg ('interview', Interviewers, use_lowercase), Others}, sepc);
end
if utilities.is_set(TitleType) then
--if is_dstu then
--if not utilities.is_set(Publisher) and utilities.is_set(Language) then
-- TitleType = cfg.presentation['sep_colon_dstu2006'] .. TitleType;
--else
-- TitleType = cfg.presentation['sep_colon_dstu2006'] .. TitleType;
--end
--else
TitleType = " (" .. TitleType .. ")";
--end
else
TitleType = "";
end
TitleNote = utilities.is_set(TitleNote) and (sepc .. " " .. TitleNote) or "";
if utilities.is_set(Language) then
if is_dstu then
Language = cfg.presentation['sep_colon_dstu2006'] .. (("[" .. SubstLangTemplateIfPossible(Language, is_dstu) .. "]") or wrap( 'dstulanguage', Language ));
else
Language = " " .. (SubstLangTemplateIfPossible(Language) or wrap( 'language', Language ));
end
else
Language = "";
end
Edition = utilities.is_set(Edition) and (" " .. wrap( 'edition', Edition )) or "";
Series = utilities.is_set (Series) and wrap_msg ('series', {sepc, Series}) or ""; -- not the same as SeriesNum
Agency = utilities.is_set(Agency) and (sepc .. " " .. Agency) or "";
Volume = format_volume_issue (Volume, Issue, ArticleNumber, config.CitationClass, Periodical_origin, sepc, use_lowercase);
------------------------------------ totally unrelated data
if utilities.is_set(Via) then Via = " " .. wrap( 'via', Via ); end
if utilities.is_set(AccessDate) then
local retrv_text;
if is_dstu then
retrv_text = " " .. cfg.messages['dsturetrieved']
else
retrv_text = " " .. cfg.messages['retrieved']
end
if not use_uppercase then retrv_text = retrv_text:ulower() end
--AccessDate = ParseDateIfPossible(AccessDate)
AccessDate = '<span class="reference-accessdate">' .. sepc
.. utilities.substitute( retrv_text, {AccessDate} ) .. '</span>'
end
-- if utilities.is_set(SubscriptionRequired) then
-- SubscriptionRequired = sepc .. " " .. cfg.messages['subscription'];
--end
if utilities.is_set(ID) then ID = sepc .." ".. ID; end
if utilities.is_set(URL) then
URL = " " .. external_link( URL, nil, URL_origin, UrlAccess);
end
if utilities.is_set(Quote) or utilities.is_set(TransQuote) or utilities.is_set (ScriptQuote) then
if utilities.is_set(Quote) then
if Quote:sub(1,1) == '"' and Quote:sub(-1,-1) == '"' then
Quote = Quote:sub(2,-2);
end
end
if utilities.is_set (ScriptQuote) then
Quote = script_concatenate (Quote, ScriptQuote, 'script-quote'); -- <bdi> tags, lang attribute, categorization, etc.; must be done after quote is wrapped;
if Quote:sub(1,1) == " " then -- після конкатування двох string додався пробіл, перевіряє чи не наявний пробілу на початку рядка
Quote = Quote:sub(2,-1); -- прибирання зайвого пробілу
end
end
if utilities.is_set (TransQuote) then
if not utilities.is_set(Quote) then
utilities.set_message('err_trans_missing_quote');
else
if TransQuote:sub(1, 1) == '"' and TransQuote:sub(-1, -1) == '"' then -- if first and last characters of |trans-quote are quote marks
TransQuote = TransQuote:sub(2, -2); -- strip them off
end
TransQuote = " " .. wrap (trans_quoted_title, TransQuote );
end
Quote = Quote .. TransQuote;
end
Quote = sepc .." " .. wrap( 'quoted-text', Quote );
PostScript = "";
elseif PostScript:ulower() == "none" then
PostScript = "";
end
local Archived
if utilities.is_set (ArchiveURL) then
if not utilities.is_set (ArchiveDate) then -- ArchiveURL set but ArchiveDate not set
utilities.set_message ('err_archive_missing_date'); -- emit an error message
ArchiveURL = ""; -- empty string for concatenation
ArchiveDate = ""; -- empty string for concatenation
end
else
if utilities.is_set (ArchiveDate) then -- ArchiveURL not set but ArchiveDate is set
utilities.set_message ('err_archive_date_missing_url'); -- emit an error message
ArchiveURL = ""; -- empty string for concatenation
ArchiveDate = ""; -- empty string for concatenation
end
end
if utilities.is_set(ArchiveURL) then
local arch_text;
-- ArchiveDate = ParseDateIfPossible(ArchiveDate)
if "yes" ~= DeadURL or UrlStatus == "live" then
arch_text = cfg.messages['archived'];
if not use_uppercase then arch_text = arch_text:ulower() end
if utilities.is_set (ArchiveDate) then
Archived = sepc .. " " .. utilities.substitute( cfg.messages['archived-not-dead'],
{ external_link( ArchiveURL, arch_text ), ArchiveDate } );
else
Archived = "";
end
if not utilities.is_set(OriginalURL) then
utilities.set_message('err_archive_missing_url');
Archived = ""; -- empty string for concatenation
end
elseif utilities.is_set(OriginalURL) then -- UrlStatus is empty, 'dead', 'unfit', 'usurped', 'bot: unknown'
if utilities.in_array (UrlStatus, {'unfit', 'usurped', 'bot: unknown'}) then
arch_text = cfg.messages['archived-unfit'];
if not use_uppercase then arch_text = arch_text:ulower() end
Archived = sepc .. " " .. arch_text .. ArchiveDate; -- format already styled
if 'bot: unknown' == UrlStatus then
utilities.set_message('maint_bot_unknown'); -- and add a category if not already added
else
utilities.set_message('maint_unfit'); -- and add a category if not already added
end
else -- UrlStatus is empty, 'dead'
arch_text = cfg.messages['archived-dead'];
if not use_uppercase then arch_text = arch_text:ulower() end
if utilities.is_set (ArchiveDate) then
Archived = sepc .. " " .. utilities.substitute( arch_text,
{ external_link( OriginalURL, cfg.messages['original'], OriginalURL_origin, OriginalAccess ), ArchiveDate } );
else
Archived = ""; -- unset for concatenation
end
end
else -- OriginalUrl not set
utilities.set_message ('err_archive_missing_url');
Archived = ""; -- empty string for concatenation
end
else
Archived = "";
end
local Lay
if utilities.is_set(LayURL) then
if utilities.is_set(LayDate) then LayDate = " (" .. LayDate .. ")" end
if utilities.is_set(LaySource) then
LaySource = " – ''" .. safe_for_italics(LaySource) .. "''";
else
LaySource = "";
end
if use_uppercase then
Lay = sepc .. " " .. external_link( LayURL, cfg.messages['lay summary'] ) .. LaySource .. LayDate
else
Lay = sepc .. " " .. external_link( LayURL, cfg.messages['lay summary']:ulower() ) .. LaySource .. LayDate
end
else
Lay = "";
end
if utilities.is_set (Transcript) then
if utilities.is_set (TranscriptURL) then
Transcript = external_link( TranscriptURL, Transcript, TranscriptURL_origin, nil );
end
Transcript = sepc .. ' ' .. Transcript --[[.. TranscriptFormat (непідтримується)]];
elseif utilities.is_set (TranscriptURL) then
Transcript = external_link( TranscriptURL, nil, TranscriptURL_origin, nil );
end
local Publisher;
if utilities.is_set(Periodical) and
not utilities.in_array(config.CitationClass, {"encyclopaedia","web","pressrelease"}) then
if utilities.is_set(PublisherName) then
if utilities.is_set(PublicationPlace) then
Publisher = PublicationPlace .. colon .. PublisherName;
else
Publisher = PublisherName;
end
elseif utilities.is_set(PublicationPlace) then
Publisher= PublicationPlace;
else
Publisher = "";
end
if utilities.is_set(PublicationDate) then
if utilities.is_set(Publisher) then
Publisher = Publisher .. ", " .. wrap( 'published', PublicationDate );
else
Publisher = PublicationDate;
end
end
if utilities.is_set(Publisher) then
Publisher = " (" .. Publisher .. ")";
end
else
if utilities.is_set(PublicationDate) then
PublicationDate = " (" .. wrap( 'published', PublicationDate ) .. ")";
end
if utilities.is_set(PublisherName) then
if utilities.is_set(PublicationPlace) then
Publisher = sepc .. " " .. PublicationPlace .. colon .. PublisherName .. PublicationDate;
else
Publisher = sepc .. " " .. PublisherName .. PublicationDate;
end
elseif utilities.is_set(PublicationPlace) then
Publisher= sepc .. " " .. PublicationPlace .. PublicationDate;
else
Publisher = PublicationDate;
end
end
-- Several of the above rely upon detecting this as nil, so do it last.
if (utilities.is_set(Periodical) or utilities.is_set (ScriptPeriodical) or utilities.is_set (TransPeriodical)) then
if utilities.is_set(Title) or utilities.is_set(TitleNote) then
Periodical = sepc .. " " .. format_periodical (ScriptPeriodical, ScriptPeriodical_origin, Periodical, TransPeriodical, TransPeriodical_origin, trans_italic_title);
else
Periodical = format_periodical (ScriptPeriodical, ScriptPeriodical_origin, Periodical, TransPeriodical, TransPeriodical_origin, trans_italic_title);
end
end
-- Piece all bits together at last. Here, all should be non-nil.
-- We build things this way because it is more efficient in LUA
-- not to keep reassigning to the same string variable over and over.
local tcommon
if utilities.in_array(config.CitationClass, {"journal","citation"}) and utilities.is_set(Periodical) then
if not (utilities.is_set (Authors) or utilities.is_set (Editors)) then
Others = Others:gsub ('^' .. sepc .. ' ', ''); -- when no authors and no editors, strip leading sepc and space
end
if utilities.is_set (Others) then Others = safe_join ({Others, sepc .. " "}, sepc) end -- add terminal punctuation & space; check for dup sepc; TODO why do we need to do this here?
tcommon = safe_join( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series,
Language, Edition, Publisher, Agency, Volume}, sepc );
elseif 'map' == config.CitationClass then -- special cases for cite map
if utilities.is_set (Chapter) then -- map in a book; TitleType is part of Chapter
tcommon = safe_join( {Title, Format, Edition, Scale, Series, Language, Cartography, Others, Publisher, Volume}, sepc );
elseif utilities.is_set (Periodical) then -- map in a periodical
tcommon = safe_join( {Title, TitleType, Format, Periodical, Scale, Series, Language, Cartography, Others, Publisher, Volume}, sepc );
else -- a sheet or stand-alone map
tcommon = safe_join( {Title, TitleType, Format, Edition, Scale, Series, Language, Cartography, Others, Publisher}, sepc );
end
elseif 'episode' == config.CitationClass then -- special case for cite episode
tcommon = safe_join( {Title, TitleNote, TitleType, Series, Language, Edition, Publisher}, sepc );
else
tcommon = safe_join( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language,
Volume, Others, Edition, Publisher, Agency}, sepc );
end
if #ID_list > 0 then
ID_list = safe_join( { sepc .. " ", table.concat( ID_list, sepc .. " " ), ID }, sepc );
else
ID_list = ID;
end
local idcommon;
if 'audio-visual' == config.CitationClass or 'episode' == config.CitationClass then -- special case for cite AV media & cite episode position transcript
idcommon = safe_join( { ID_list, URL, Archived, Transcript, AccessDate, Via, --[[SubscriptionRequired,]] Lay, Quote }, sepc );
else
idcommon = safe_join( { ID_list, URL, Archived, AccessDate, Via, --[[SubscriptionRequired,]] Lay, Quote }, sepc );
end
local text;
local pgtext = Position .. Page .. Pages .. At;
local OrigDate = A['OrigDate'];
OrigDate = utilities.is_set (OrigDate) and wrap_msg ('origdate', OrigDate) or '';
if utilities.is_set (Date) then
--Date = ParseDateIfPossible(Date)
if utilities.is_set (Authors) or utilities.is_set (Editors) then -- date follows authors or editors when authors not set
if is_dstu then
Date = " (" .. Date .. ")" .. OrigDate .. "." .. " "; -- in parentheses
else
Date = " (" .. Date .. ")" .. OrigDate .. sepc .. " "; -- in parentheses
end
else -- neither of authors and editors set
if (string.sub(tcommon, -1, -1) == sepc) then -- if the last character of tcommon is sepc
--[[if is_dstu then -- закоментував, поки не знаю чи потрібен цей код
Date = ", " .. Date .. OrigDate;
else]]
Date = " " .. Date .. OrigDate; -- Date does not begin with sepc
--end
else
Date = sepc .. " " .. Date .. OrigDate; -- Date begins with sepc
end
end
end
if utilities.is_set(Authors) then
if utilities.is_set(Coauthors) then
if is_dstu or NameListStyle == 'vanc' then
Authors = Authors .. cfg.presentation['sep_nl_vanc_dstu'] .. " " .. Coauthors
else
Authors = Authors .. A['AuthorSeparator'] .. " " .. Coauthors
end
end
if (not utilities.is_set (Date)) then -- when date is set it's in parentheses; no Authors termination
if is_dstu then
Authors = terminate_name_list (Authors, ".");
else
Authors = terminate_name_list (Authors, sepc); -- when no date, terminate with 0 or 1 sepc and a space
end
end
if utilities.is_set(Editors) then
local in_text = '';
local post_text = '';
if utilities.is_set (Chapter) --[[and 0 == #c]] then
in_text = cfg.messages['in'] .. ' ';
if not use_uppercase then
in_text = in_text:ulower(); -- lowercase for cs2
end
end
if EditorCount <= 1 then
post_text = ' (' .. cfg.messages['editor'] .. ')'; -- be consistent with no-author, no-date case
else
post_text = ' (' .. cfg.messages['editors'] .. ')';
end
Editors = terminate_name_list (in_text .. Editors .. post_text, sepc); -- terminate with 0 or 1 sepc and a space
end
text = safe_join( {Authors, Date, Chapter, Place, Editors, tcommon, pgtext, idcommon }, sepc );
elseif utilities.is_set(Editors) then
if utilities.is_set(Date) then
if EditorCount <= 1 then
Editors = Editors .. ", " .. cfg.messages['editor'];
else
Editors = Editors .. ", " .. cfg.messages['editors'];
end
else
if EditorCount <= 1 then
Editors = Editors .. " (" .. cfg.messages['editor'] .. ")" .. sepc .. " "
else
Editors = Editors .. " (" .. cfg.messages['editors'] .. ")" .. sepc .. " "
end
end
text = safe_join( {Editors, Date, Chapter, Place, tcommon, pgtext, idcommon}, sepc );
else
if config.CitationClass=="journal" and utilities.is_set(Periodical) then
text = safe_join( {Chapter, Place, tcommon, pgtext, Date, idcommon}, sepc );
else
text = safe_join( {Chapter, Place, tcommon, Date, pgtext, idcommon}, sepc );
end
end
--safe_join for dstu2006
--[[ if is_dstu then (поки не задіяний код, закоментував)
local dstucommon2 = safe_join( {Authors, Editors, Others}, ',', sepc);
local dstucommon;
if utilities.is_set(dstucommon2) then
dstucommon2 = cfg.presentation['sep_sp_dstu2006'] .. ' ' .. dstucommon2;
dstucommon = safe_join( {Chapter, Title, Conference, dstucommon2}, cfg.presentation['sep_dsp_dstu2006'], sepc);
else
dstucommon = safe_join( {Chapter, Title, Conference}, cfg.presentation['sep_dsp_dstu2006'], sepc);
end
if utilities.is_set(Language) then
dstucommon = dstucommon .. Language;
end
local dstucommon3 = safe_join( {Publisher, Date}, ',');
tcommon = safe_join( {dstucommon, Edition, Place, Publisher, Agency, Date, Volume, Issue, pgtext, Series, Position, idcommon}, sepc);
--text = tcommon;
end]]
if utilities.is_set(PostScript) and PostScript ~= sepc then
text = safe_join( {text, sepc}, sepc ); --Deals with italics, spaces, etc.
text = text:sub(1, -sepc:len() - 1); --Remove final seperator
end
text = safe_join( {text, PostScript}, sepc );
-- Now enclose the whole thing in a <span/> element
if not utilities.is_set(Year) then
if utilities.is_set(DateIn) then
Year = select_year( DateIn );
elseif utilities.is_set(PublicationDate) then
Year = select_year( PublicationDate );
end
end
local options = {};
options.class = cite_class_attribute_make (config.CitationClass, Mode);
if utilities.is_set(Ref) and Ref:lower() ~= "none" then
local id = Ref
if ( "harv" == Ref ) then
local names = {} --table of last names & year
if utilities.is_set(Authors) then
for i,v in ipairs(a) do
names[i] = v.last
if i == 4 then break end
end
elseif utilities.is_set(Editors) then
for i,v in ipairs(e) do
names[i] = v.last
if i == 4 then break end
end
end
names[ #names + 1 ] = Year;
id = anchor_id(names)
end
options.id = id;
end
if string.len(text:gsub("%b<>","")) <= 2 then
z.error_categories = {};
z.message_tail = {};
OCinSoutput = nil; -- blank the metadata string
text = ''; -- blank the the citation
utilities.set_message('err_empty_citation');
end
if utilities.is_set(options.id) then
text = utilities.substitute (cfg.presentation['cite-id'], {mw.uri.anchorEncode(options.id), mw.text.nowiki(options.class), text});
else
text = utilities.substitute (cfg.presentation['cite'], {mw.text.nowiki(options.class), text});
end
local empty_span = '<span style="display:none;"> </span>';
-- Note: Using display: none on then COinS span breaks some clients.
if OCinSoutput then
local OCinS = '<span title="' .. OCinSoutput .. '" class="Z3988">' .. empty_span .. '</span>';
text = text .. OCinS;
end
local template_name = ('citation' == config.CitationClass) and 'citation' or 'cite ' .. (cfg.citation_class_map_t[config.CitationClass] or config.CitationClass);
local template_link = '[[Шаблон:' .. template_name .. '|' .. template_name .. ']]';
local msg_prefix = '<code class="cs1-code">{{' .. template_link .. '}}</code>: ';
if #z.message_tail ~= 0 then
mw.addWarning (utilities.substitute (cfg.messages.warning_msg_e, template_link));
table.sort (z.message_tail);
local hidden = true; -- presume that the only error messages emited by this template are hidden
for _, v in ipairs (z.message_tail) do -- spin through the list of error messages
if v:find ('cs1-visible-error', 1, true) then -- look for the visible error class name
hidden = false; -- found one; so don't hide the error message prefix
break; -- and done because no need to look further
end
end
text = text .. " ";
z.message_tail[1] = table.concat ({utilities.error_comment (msg_prefix, hidden), z.message_tail[1]}); -- add error message prefix to first error message to prevent extraneous punctuation
text = text .. table.concat (z.message_tail, '; '); -- make a big string of error messages and add it to the rendering
end
if #z.maintaince_categories ~= 0 then
mw.addWarning (utilities.substitute (cfg.messages.warning_msg_m, template_link));
table.sort (z.maintaince_categories); -- sort the maintenance messages list
local maint_msgs_t = {};
if 0 == #z.message_tail then -- if no error messages
table.insert (maint_msgs_t, msg_prefix); -- insert message prefix in maint message livery
end
for _, v in ipairs( z.maintaince_categories ) do
-- append maintenance categories
table.insert (maint_msgs_t, -- assemble new maint message and add it to the maint_msgs_t table
table.concat ({v, ' (', utilities.substitute (cfg.messages[':cat wikilink'], v), ')'})
);
end
text = text .. utilities.substitute (cfg.presentation['hidden-maint'], table.concat (maint_msgs_t, ' ')); -- wrap the group of maint messages with proper presentation and save
end
no_tracking_cats = no_tracking_cats:ulower();
if utilities.in_array(no_tracking_cats, {"", "no", "false", "n"}) then
for _, v in ipairs( z.error_categories ) do
text = text .. '[[Category:' .. v ..']]';
end
for _, v in ipairs (z.maintaince_categories) do -- append maintenance categories
text = text .. '[[Category:' .. v ..']]';
end
for _, v in ipairs (z.properties_cats) do -- append properties categories
text = text .. utilities.substitute (cfg.messages['cat wikilink'], v);
end
end
return text
end
--[[--------------------------< V A L I D A T E >--------------------------------------------------------------
Looks for a parameter's name in one of several whitelists.
Parameters in the whitelist can have three values:
true - active, supported parameters
false - deprecated, supported parameters
nil - unsupported parameters
]]
function validate(name, cite_class, empty)
local name = tostring (name);
local enum_name; -- parameter name with enumerator (if any) replaced with '#'
local state;
local function state_test (state, name) -- local function to do testing of state values
if true == state then return true; end -- valid actively supported parameter
if false == state then
if empty then return nil; end -- empty deprecated parameters are treated as unknowns
deprecated_parameter (name); -- parameter is deprecated but still supported
return true;
end
if 'tracked' == state then
local base_name = name:gsub ('%d', ''); -- strip enumerators from parameter names that have them to get the base name
utilities.add_prop_cat ('tracked-param', {base_name}, base_name); -- add a properties category; <base_name> modifies <key>
return true;
end
return nil;
end
if name:find ('#') then -- # is a cs1|2 reserved character so parameters with # not permitted
return nil;
end
-- replace wnumerator digit(s) with # (|last25= becomes |last#=) (mw.ustring because non-Western 'local' digits)
enum_name = mw.ustring.gsub (name, '%d+$', '#'); -- where enumerator is last charaters in parameter name (these to protect |s2cid=)
enum_name = mw.ustring.gsub (enum_name, '%d+([%-l])', '#%1'); -- where enumerator is in the middle of the parameter name; |author#link= is the oddity
--[[if 'document' == cite_class then -- special case for {{cite document}}
state = whitelist.document_parameters_t[enum_name]; -- this list holds enumerated and nonenumerated parameters
if true == state_test (state, name) then return true; end
return false;
end (поки не використовується)]]
if utilities.in_array (cite_class, whitelist.preprint_template_list_t) then -- limited parameter sets allowed for these templates
state = whitelist.limited_parameters_t[enum_name]; -- this list holds enumerated and nonenumerated parameters
if true == state_test (state, name) then return true; end
state = whitelist.preprint_arguments_t[cite_class][name]; -- look in the parameter-list for the template identified by cite_class
if true == state_test (state, name) then return true; end
return false; -- not supported because not found or name is set to nil
end -- end limited parameter-set templates
if utilities.in_array (cite_class, whitelist.unique_param_template_list_t) then -- template-specific parameters for templates that accept parameters from the basic argument list
state = whitelist.unique_arguments_t[cite_class][name]; -- look in the template-specific parameter-lists for the template identified by cite_class
if true == state_test (state, name) then return true; end
end -- if here, fall into general validation
state = whitelist.common_parameters_t[enum_name]; -- all other templates; all normal parameters allowed; this list holds enumerated and nonenumerated parameters
if true == state_test (state, name) then return true; end
return false; -- not supported because not found or name is set to nil
end
--[[--------------------------< C I T A T I O N >--------------------------------------------------------------
This is used by templates such as {{cite book}} to create the actual citation text.
]]
local function citation(frame)
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 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 styles;
cfg = mw.loadData ('Module:Citation/CS1/Configuration' .. sandbox .. '/гілка1'); -- load sandbox versions of support modules when {{#invoke:Citation/CS1/sandbox|...}}; live modules else
whitelist = mw.loadData ('Module:Citation/CS1/Whitelist' .. sandbox);
utilities = require ('Module:Citation/CS1/Utilities' .. sandbox);
validation = require ('Module:Citation/CS1/Date_validation' .. sandbox);
identifiers = require ('Module:Citation/CS1/Identifiers' .. sandbox);
metadata = require ('Module:Citation/CS1/COinS' .. sandbox);
styles = 'Module:Citation/CS1' .. sandbox .. '/styles.css';
styles = 'Module:Citation/CS1/styles.css';
utilities.set_selected_modules (cfg); -- so that functions in Utilities can see the selected cfg tables
validation.set_selected_modules (cfg, utilities); -- so that functions in Date validataion can see selected cfg tables and the selected Utilities module (додано буде в наступних оновленнях)
identifiers.set_selected_modules (cfg, utilities); -- so that functions in Identifiers can see the selected cfg tables and selected Utilities module
metadata.set_selected_modules (cfg, utilities); -- so that functions in COinS can see the selected cfg tables and selected Utilities module
z = utilities.z;
local args = {}; -- table where we store all of the template's arguments
local suggestions = {}; -- table where we store suggestions if we need to loadData them
local error_text; -- used as a flag
local capture; -- the single supported capture when matching unknown parameters using patterns
local empty_unknowns = {}; -- sequence table to hold empty unknown params for error message listing
for k, v in pairs( pframe.args ) do -- get parameters from the parent (template) frame
v = mw.ustring.gsub (v, '^%s*(.-)%s*$', '%1'); -- trim leading/trailing whitespace; when v is only whitespace, becomes empty string
if v ~= '' then
if ('string' == type (k)) then
k = mw.ustring.gsub (k, '%d', cfg.date_names.local_digits); -- for enumerated parameters, translate 'local' digits to Western 0-9
end
if not validate( k, config.CitationClass ) then
if type (k) ~= 'string' then -- exclude empty numbered parameters
if v:match("%S+") ~= nil then
error_text = utilities.set_message ('err_text_ignored', {v});
end
elseif validate (k:ulower(), config.CitationClass) then
error_text = utilities.set_message ('err_parameter_ignored_suggest', {k, k:ulower()}); -- suggest the lowercase version of the parameter
else
if nil == suggestions.suggestions then -- if this table is nil then we need to load it
suggestions = mw.loadData ('Module:Citation/CS1/Suggestions' .. sandbox); --load sandbox version of suggestion module when {{#invoke:Citation/CS1/sandbox|...}}; live module else
end
for pattern, param in pairs (suggestions.patterns) do -- loop through the patterns to see if we can suggest a proper parameter
capture = k:match (pattern); -- the whole match if no capture in pattern else the capture if a match
if capture then -- if the pattern matches
param = utilities.substitute (param, capture); -- add the capture to the suggested parameter (typically the enumerator)
if validate (param, config.CitationClass) then -- validate the suggestion to make sure that the suggestion is supported by this template (necessary for limited parameter lists)
error_text = utilities.set_message ('err_parameter_ignored_suggest', {k, param}); -- set the suggestion error message
else
error_text = utilities.set_message ('err_parameter_ignored', {k}); -- suggested param not supported by this template
v = ''; -- unset
end
end
end
if not utilities.is_set (error_text) then -- couldn't match with a pattern, is there an explicit suggestion?
if (suggestions.suggestions[ k:ulower() ] ~= nil) and validate (suggestions.suggestions[ k:ulower() ], config.CitationClass) then
utilities.set_message ('err_parameter_ignored_suggest', {k, suggestions.suggestions[ k:ulower() ]});
else
utilities.set_message ('err_parameter_ignored', {k});
v = ''; -- unset value assigned to unrecognized parameters (this for the limited parameter lists)
end
end
end
end
args[k] = v; -- save this parameter and its value
elseif not utilities.is_set (v) then -- for empty parameters
if not validate (k, config.CitationClass, true) then -- is this empty parameter a valid parameter
k = ('' == k) and '(пустий рядок)' or k; -- when k is empty string (or was space(s) trimmed to empty string), replace with descriptive text
table.insert (empty_unknowns, utilities.wrap_style ('parameter', k)); -- format for error message and add to the list
end
-- crude debug support that allows us to render a citation from module {{#invoke:}} TODO: keep?
-- elseif args[k] ~= nil or (k == 'postscript') then -- when args[k] has a value from {{#invoke}} frame (we don't normally do that)
-- args[k] = v; -- overwrite args[k] with empty string from pframe.args[k] (template frame); v is empty string here
end -- not sure about the postscript bit; that gets handled in parameter validation; historical artifact?
end
if 0 ~= #empty_unknowns then -- create empty unknown error message
utilities.set_message ('err_param_unknown_empty', {
1 == #empty_unknowns and '' or 'и',
utilities.make_sep_list (#empty_unknowns, empty_unknowns),
1 == #empty_unknowns and 'ий' or 'і'
});
end
return table.concat ({
frame:extensionTag ('templatestyles', '', {src=styles}),
frame:preprocess( citation0( config, args) )
});
end
--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
]]
return {citation = citation};