Module:Citation/CS1: Difference between revisions
Content added Content deleted
(bug fix in language_parameter();) |
m (1 revision imported: Modules) |
||
Line 1: | Line 1: | ||
local z = { |
|||
error_categories = {}; -- for categorizing citations that contain errors |
|||
local cs1 ={}; |
|||
error_ids = {}; |
|||
message_tail = {}; |
|||
maintenance_cats = {}; -- for categorizing citations that aren't erroneous per se, but could use a little work |
|||
properties_cats = {}; -- for categorizing citations based on certain properties, language of source for instance |
|||
} |
|||
--[[--------------------------< F O R W A R D D E C L A R A T I O N S >-------------------------------------- |
--[[--------------------------< F O R W A R D D E C L A R A T I O N S >-------------------------------------- |
||
]] |
]] |
||
local dates, year_date_check -- functions in Module:Citation/CS1/Date_validation |
|||
local |
local cfg = {}; -- table of configuration tables that are defined in Module:Citation/CS1/Configuration |
||
local whitelist = {}; -- talbe of tables listing valid template parameter names; defined in Module:Citation/CS1/Whitelist |
|||
--[[--------------------------< I S _ S E T >------------------------------------------------------------------ |
|||
local is_set, in_array, substitute, error_comment, set_error, select_one, -- functions in Module:Citation/CS1/Utilities |
|||
add_maint_cat, wrap_style, safe_for_italics, remove_wiki_link; |
|||
Returns true if argument is set; false otherwise. Argument is 'set' when it exists (not nil) or when it is not an empty string. |
|||
local z ={}; -- tables in Module:Citation/CS1/Utilities |
|||
This function is global because it is called from both this module and from Date validation |
|||
]] |
|||
local extract_ids, build_id_list, is_embargoed; -- functions in Module:Citation/CS1/Identifiers |
|||
function is_set( var ) |
|||
return not (var == nil or var == ''); |
|||
local make_coins_title, get_coins_pages, COinS; -- functions in Module:Citation/CS1/COinS |
|||
end |
|||
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 |
|||
--[[--------------------------< F I R S T _ S E T >------------------------------------------------------------ |
--[[--------------------------< F I R S T _ S E T >------------------------------------------------------------ |
||
First set variable or nil if none |
|||
Locates and returns the first set value in a table of values where the order established in the table, |
|||
left-to-right (or top-to-bottom), is the order in which the values are evaluated. Returns nil if none are set. |
|||
This version replaces the original 'for _, val in pairs do' and a similar version that used ipairs. With the pairs |
|||
version the order of evaluation could not be guaranteed. With the ipairs version, a nil value would terminate |
|||
the for-loop before it reached the actual end of the list. |
|||
]] |
]] |
||
local function first_set |
local function first_set(...) |
||
local |
local list = {...}; |
||
for _, var in pairs(list) do |
|||
while i <= count do -- loop through all items in list |
|||
if is_set( |
if is_set( var ) then |
||
return var; |
|||
return list[i]; -- return the first set list member |
|||
end |
end |
||
i = i + 1; -- point to next |
|||
end |
end |
||
end |
end |
||
--[[--------------------------< |
--[[--------------------------< I N _ A R R A Y >-------------------------------------------------------------- |
||
Whether needle is in haystack |
|||
Adds a category to z.properties_cats using names from the configuration file with additional text if any. |
|||
]] |
]] |
||
local function in_array( needle, haystack ) |
|||
local added_prop_cats = {} -- list of property categories that have been added to z.properties_cats |
|||
if needle == nil then |
|||
local function add_prop_cat (key, arguments) |
|||
return false; |
|||
if not added_prop_cats [key] then |
|||
added_prop_cats [key] = true; -- note that we've added this category |
|||
table.insert( z.properties_cats, substitute (cfg.prop_cats [key], arguments)); -- make name then add to table |
|||
end |
end |
||
for n,v in ipairs( haystack ) do |
|||
if v == needle then |
|||
return n; |
|||
end |
|||
end |
|||
return false; |
|||
end |
end |
||
--[[--------------------------< |
--[[--------------------------< S U B S T I T U T E >---------------------------------------------------------- |
||
Populates numbered arguments in a message string using an argument table. |
|||
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. |
|||
]] |
]] |
||
local function substitute( msg, args ) |
|||
local added_vanc_errs; -- flag so we only emit one Vancouver error / category |
|||
return args and mw.message.newRawMessage( msg, args ):plain() or msg; |
|||
local function add_vanc_error () |
|||
if not added_vanc_errs then |
|||
added_vanc_errs = true; -- note that we've added this category |
|||
table.insert( z.message_tail, { set_error( 'vancouver', {}, true ) } ); |
|||
end |
|||
end |
end |
||
--[[--------------------------< E R R O R _ C O M M E N T >---------------------------------------------------- |
|||
Wraps error messages with css markup according to the state of hidden. |
|||
--[[--------------------------< 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 error_comment( content, hidden ) |
|||
return substitute( hidden and cfg.presentation['hidden-error'] or cfg.presentation['visible-error'], content ); |
|||
local function is_scheme (scheme) |
|||
return scheme and scheme:match ('^%a[%a%d%+%.%-]*:'); -- true if scheme is set and matches the pattern |
|||
end |
end |
||
--[[--------------------------< S E T _ E R R O R >-------------------------------------------------------------- |
|||
Sets an error condition and returns the appropriate error message. The actual placement of the error message in the output is |
|||
--[=[-------------------------< I S _ D O M A I N _ N A M E >-------------------------------------------------- |
|||
the responsibility of the calling function. |
|||
]] |
|||
Does this thing that purports to be a domain name seem to be a valid domain name? |
|||
local function set_error( error_id, arguments, raw, prefix, suffix ) |
|||
local error_state = cfg.error_conditions[ error_id ]; |
|||
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 |
|||
rfc952 (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 incase 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 |
|||
single-letter/digit second-level domains in the .org TLD |
|||
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 |
|||
prefix = prefix or ""; |
|||
domain = domain:gsub ('^//', ''); -- strip '//' from domain name if present; done here so we only have to do it once |
|||
suffix = suffix or ""; |
|||
if error_state == nil then |
|||
if not domain:match ('^[%a%d]') then -- first character must be letter or digit |
|||
error( cfg.messages['undefined_error'] ); |
|||
return false; |
|||
elseif is_set( error_state.category ) then |
|||
table.insert( z.error_categories, error_state.category ); |
|||
end |
end |
||
-- Do most common case first |
|||
if domain:match ('%f[%a%d][%a%d][%a%d%-]+[%a%d]%.%a%a+$') then -- three or more character hostname.hostname or hostname.tld |
|||
return true; |
|||
elseif domain:match ('%f[%a%d][%a%d]%.org$') then -- one character .org hostname |
|||
return true; |
|||
elseif domain:match ('%f[%a][qxz]%.com$') then -- assigned one character .com hostname (x.com times out 2015-12-10) |
|||
return true; |
|||
elseif domain:match ('%f[%a][iq]%.net$') then -- assigned one character .net hostname (q.net registered but not active 2015-12-10) |
|||
return true; |
|||
elseif domain:match ('%f[%a%d][%a%d]%.%a%a$') then -- one character hostname and cctld (2 chars) |
|||
return true; |
|||
elseif domain:match ('%f[%a%d][%a%d][%a%d]%.%a%a+$') then -- two character hostname and tld |
|||
return true; |
|||
elseif domain:match ('^%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?') then -- IPv4 address |
|||
return true; |
|||
else |
|||
return false; |
|||
end |
|||
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 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. |
|||
Strip off any port and path; |
|||
]] |
|||
local function split_url (url_str) |
|||
local scheme, authority, domain; |
|||
local message = substitute( error_state.message, arguments ); |
|||
url_str = url_str:gsub ('([%a%d])%.?[/%?#].*$', '%1'); -- strip FQDN terminator and path(/), query(?), fragment (#) (the capture prevents false replacement of '//') |
|||
message = message .. " ([[" .. cfg.messages['help page link'] .. |
|||
if url_str:match ('^//%S*') then -- if there is what appears to be a protocol relative url |
|||
"#" .. error_state.anchor .. "|" .. |
|||
domain = url_str:match ('^//(%S*)') |
|||
cfg.messages['help page label'] .. "]])"; |
|||
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 |
|||
z.error_ids[ error_id ] = true; |
|||
authority = authority:gsub ('//', '', 1); -- replace place 1 pair of '/' with nothing; |
|||
if in_array( error_id, { 'bare_url_missing_title', 'trans_missing_title' } ) |
|||
if is_set(authority) then -- if anything left (1 or 3+ '/' where authority should be) then |
|||
and z.error_ids['citation_missing_title'] then |
|||
return scheme; -- return scheme only making domain nil which will cause an error message |
|||
return '', false; |
|||
end |
|||
domain = domain:gsub ('(%a):%d+', '%1'); -- strip port number if present |
|||
end |
end |
||
message = table.concat({ prefix, message, suffix }); |
|||
return scheme, domain; |
|||
if raw == true then |
|||
return message, error_state.hidden; |
|||
end |
|||
return error_comment( message, error_state.hidden ); |
|||
end |
end |
||
--[[--------------------------< A D D _ M A I N T _ C A T >------------------------------------------------------ |
|||
Adds a category to z.maintenance_cats using names from the configuration file with additional text if any. |
|||
--[[--------------------------< 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 |
local function add_maint_cat (key, arguments) |
||
table.insert( z.maintenance_cats, substitute (cfg.maint_cats [key], arguments)); -- make name then add to table |
|||
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 |
end |
||
--[[--------------------------< |
--[[--------------------------< A D D _ P R O P _ C A T >-------------------------------------------------------- |
||
Adds a category to z.properties_cats using names from the configuration file with additional text if any. |
|||
Use link_param_ok() to validate |<param>-link= value and its matching |<title>= value. |
|||
|<title>= may be wikilinked but not when |<param>-link= has a value. This function emits an error message when |
|||
that condition exists |
|||
]] |
]] |
||
local function |
local function add_prop_cat (key, arguments) |
||
table.insert( z.properties_cats, substitute (cfg.prop_cats [key], arguments)); -- make name then add to table |
|||
local orig; |
|||
if is_set (link) then -- don't bother if 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 link parameter |
|||
end |
|||
end |
|||
if is_set (orig) then |
|||
table.insert( z.message_tail, { set_error( 'bad_paramlink', orig)}); -- url or wikilink in |title= with |title-link=; |
|||
end |
|||
end |
end |
||
--[[--------------------------< C H E C K _ U R L >------------------------------------------------------------ |
--[[--------------------------< C H E C K _ U R L >------------------------------------------------------------ |
||
Determines whether a URL string |
Determines whether a 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 |
|||
First we test for space characters. If any are found, return false. Then split the url into scheme and domain |
|||
the URI scheme is valid or whether the URL is otherwise well formed. |
|||
portions, or for protocol relative (//example.com) urls, just the domain. Use is_url() to validate the two |
|||
portions of the url. If both are valid, or for protocol relative if domain is valid, return true, else false. |
|||
Because it is different from a standard url, and because this module used external_link() to make external links |
|||
that work for standard and news: links, we validate newsgroup names here. The specification for a newsgroup name |
|||
is at https://tools.ietf.org/html/rfc5536#section-3.1.4 |
|||
]] |
]] |
||
local function check_url( url_str ) |
local function check_url( url_str ) |
||
return url_str:sub(1,2) == "//" or url_str:match( "^[^/]*:" ) ~= nil; -- Protocol-relative or URL scheme |
|||
return false; |
|||
end |
|||
local scheme, domain; |
|||
scheme, domain = split_url (url_str); -- get scheme or nil and domain or nil from url; |
|||
if 'news:' == scheme then -- special case for newsgroups |
|||
return domain:match('^[%a%d%+%-_]+%.[%a%d%+%-_%.]*[%a%d%+%-_]$'); |
|||
end |
|||
return is_url (scheme, domain); -- return true if value appears to be a valid url |
|||
end |
end |
||
--[[--------------------------< S A F E _ F O R _ I T A L I C S >---------------------------------------------- |
|||
Protects a string that will be wrapped in wiki italic markup '' ... '' |
|||
--[=[-------------------------< I S _ P A R A M E T E R _ E X T _ W I K I L I N K >---------------------------- |
|||
Note: We cannot use <i> for italics, as the expected behavior for italics specified by ''...'' in the title is that |
|||
Return true if a parameter value has a string that begins and ends with square brackets [ and ] and the first |
|||
they will be inverted (i.e. unitalicized) in the resulting references. In addition, <i> and '' tend to interact |
|||
non-space characters following the opening bracket appear to be a url. The test will also find external wikilinks |
|||
poorly under Mediawiki's HTML tidy. |
|||
that use protocol relative urls. Also finds bare urls. |
|||
]] |
|||
The frontier pattern prevents a match on interwiki links which are similar to scheme:path urls. The tests that |
|||
find bracketed urls are required because the parameters that call this test (currently |title=, |chapter=, |work=, |
|||
and |publisher=) may have wikilinks and there are articles or redirects like '//Hus' so, while uncommon, |title=[[//Hus]] |
|||
is possible as might be [[en://Hus]]. |
|||
local function safe_for_italics( str ) |
|||
]=] |
|||
if not is_set(str) then |
|||
return str; |
|||
local function is_parameter_ext_wikilink (value) |
|||
local scheme, domain; |
|||
if value:match ('%f[%[]%[%a%S*:%S+.*%]') then -- if ext wikilink with scheme and domain: [xxxx://yyyyy.zzz] |
|||
scheme, domain = split_url (value:match ('%f[%[]%[(%a%S*:%S+).*%]')); |
|||
elseif value:match ('%f[%[]%[//%S+.*%]') then -- if protocol relative ext wikilink: [//yyyyy.zzz] |
|||
scheme, domain = split_url (value:match ('%f[%[]%[(//%S+).*%]')); |
|||
elseif value:match ('%a%S*:%S+') then -- if bare url with scheme; may have leading or trailing plain text |
|||
scheme, domain = split_url (value:match ('(%a%S*:%S+)')); |
|||
elseif value:match ('//%S+') then -- if protocol relative bare url: //yyyyy.zzz; may have leading or trailing plain text |
|||
scheme, domain = split_url (value:match ('(//%S+)')); -- what is left should be the domain |
|||
else |
else |
||
if str:sub(1,1) == "'" then str = "<span />" .. str; end |
|||
return false; -- didn't find anything that is obviously a url |
|||
if str:sub(-1,-1) == "'" then str = str .. "<span />"; end |
|||
-- Remove newlines as they break italics. |
|||
return str:gsub( '\n', ' ' ); |
|||
end |
end |
||
return is_url (scheme, domain); -- return true if value appears to be a valid url |
|||
end |
end |
||
--[[-------------------------< C H E C K _ F O R _ U R L >----------------------------------------------------- |
|||
loop through a list of parameters and their values. Look at the value and if it has an external link, emit an error message. |
|||
]] |
|||
local function check_for_url (parameter_list) |
|||
local error_message = ''; |
|||
for k, v in pairs (parameter_list) do -- for each parameter in the list |
|||
if is_parameter_ext_wikilink (v) then -- look at the value; if there is a url add an error message |
|||
if is_set(error_message) then -- once we've added the first portion of the error message ... |
|||
error_message=error_message .. ", "; -- ... add a comma space separator |
|||
end |
|||
error_message=error_message .. "|" .. k .. "="; -- add the failed parameter |
|||
end |
|||
end |
|||
if is_set (error_message) then -- done looping, if there is an error message, display it |
|||
table.insert( z.message_tail, { set_error( 'param_has_ext_link', {error_message}, true ) } ); |
|||
end |
|||
end |
|||
--[[--------------------------< S A F E _ F O R _ U R L >------------------------------------------------------ |
--[[--------------------------< S A F E _ F O R _ U R L >------------------------------------------------------ |
||
Line 356: | Line 186: | ||
[']'] = ']', |
[']'] = ']', |
||
['\n'] = ' ' } ); |
['\n'] = ' ' } ); |
||
end |
|||
--[[--------------------------< W R A P _ S T Y L E >---------------------------------------------------------- |
|||
Applies styling to various parameters. Supplied string is wrapped using a message_list configuration taking one |
|||
argument; protects italic styled parameters. Additional text taken from citation_config.presentation - the reason |
|||
this function is similar to but separate from wrap_msg(). |
|||
]] |
|||
local function wrap_style (key, str) |
|||
if not is_set( str ) then |
|||
return ""; |
|||
elseif in_array( key, { 'italic-title', 'trans-italic-title' } ) then |
|||
str = safe_for_italics( str ); |
|||
end |
|||
return substitute( cfg.presentation[key], {str} ); |
|||
end |
end |
||
Line 375: | Line 223: | ||
end |
end |
||
if not check_url( URL ) then |
if not check_url( URL ) then |
||
error_str = set_error( 'bad_url', { |
error_str = set_error( 'bad_url', {}, false, " " ) .. error_str; |
||
end |
end |
||
return table.concat({ "[", URL, " ", safe_for_url( label ), "]", error_str }); |
return table.concat({ "[", URL, " ", safe_for_url( label ), "]", error_str }); |
||
end |
end |
||
--[[--------------------------< |
--[[--------------------------< E X T E R N A L _ L I N K _ I D >---------------------------------------------- |
||
Formats a wiki style external link |
|||
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. |
|||
]] |
]] |
||
local function external_link_id(options) |
|||
local page_in_deprecated_cat; -- sticky flag so that the category is added only once |
|||
local url_string = options.id; |
|||
if options.encode == true or options.encode == nil then |
|||
url_string = mw.uri.encode( url_string ); |
|||
end |
|||
return mw.ustring.format( '[[%s|%s]]%s[%s%s%s %s]', |
|||
options.link, options.label, options.separator or " ", |
|||
options.prefix, url_string, options.suffix or "", |
|||
mw.text.nowiki(options.id) |
|||
); |
|||
end |
|||
--[[ |
|||
Categorize and emit an error message when the citation contains one or more deprecated parameters. Because deprecated parameters (currently |month=, |
|||
|coauthor=, and |coauthors=) aren't related to each other and because these parameters may be concatenated into the variables used by |date= and |author#= (and aliases) |
|||
details of which parameter caused the error message are not provided. Only one error message is emitted regardless of the number of deprecated parameters in the citation. |
|||
]] |
|||
local function deprecated_parameter(name) |
local function deprecated_parameter(name) |
||
if true ~= Page_in_deprecated_cat then -- if we haven't been here before then set a |
|||
if not page_in_deprecated_cat then |
|||
Page_in_deprecated_cat=true; -- sticky flag so that if there are more than one deprecated parameter the category is added only once |
|||
table.insert( z.message_tail, { set_error( 'deprecated_params', {name}, true ) } ); -- add error message |
table.insert( z.message_tail, { set_error( 'deprecated_params', {name}, true ) } ); -- add error message |
||
end |
end |
||
Line 444: | Line 306: | ||
is not added. At this time there is no error message for this condition. |
is not added. At this time there is no error message for this condition. |
||
At this writing, only |script-title= is supported. It is anticipated that additional parameters will be created to use this function. |
|||
Supports |script-title= and |script-chapter= |
|||
TODO: error messages when prefix is invalid ISO639-1 code; when script_value has prefix but no script; |
TODO: error messages when prefix is invalid ISO639-1 code; when script_value has prefix but no script; |
||
Line 462: | Line 324: | ||
script_value = script_value:gsub ('^%l%l%s*:%s*', ''); -- strip prefix from script |
script_value = script_value:gsub ('^%l%l%s*:%s*', ''); -- strip prefix from script |
||
-- is prefix one of these language codes? |
-- is prefix one of these language codes? |
||
if in_array (lang, { |
if in_array (lang, {'ar', 'bg', 'bs', 'dv', 'el', 'fa', 'hy', 'ja', 'ka', 'ko', 'ku', 'he', 'ps', 'ru', 'sd', 'sr', 'th', 'uk', 'ug', 'yi', 'zh'}) then |
||
add_prop_cat ('script_with_name', {name, lang}) |
add_prop_cat ('script_with_name', {name, lang}) |
||
else |
else |
||
Line 509: | Line 371: | ||
local msg; |
local msg; |
||
msg = cfg.messages[key]:lower(); -- set the message to lower case before |
msg = cfg.messages[key]:lower(); -- set the message to lower case before |
||
str = substitute( msg, {str} ); -- including template text |
|||
return str; |
|||
else |
else |
||
return substitute( cfg.messages[key], str ); |
return substitute( cfg.messages[key], {str} ); |
||
end |
end |
||
end |
end |
||
--[[--------------------------< S E L E C T _ O N E >---------------------------------------------------------- |
|||
Chooses one matching parameter from a list of parameters to consider |
|||
--[[--------------------------< F O R M A T _ C H A P T E R _ T I T L E >-------------------------------------- |
|||
Generates an error if more than one match is present. |
|||
Format the four chapter parameters: |script-chapter=, |chapter=, |trans-chapter=, and |chapter-url= into a single Chapter meta- |
|||
parameter (chapter_url_source used for error messages). |
|||
]] |
]] |
||
local function |
local function select_one( args, possible, error_condition, index ) |
||
local |
local value = nil; |
||
local selected = ''; |
|||
local error_list = {}; |
|||
if index ~= nil then index = tostring(index); end |
|||
if not is_set (chapter) then |
|||
chapter = ''; -- to be safe for concatenation |
|||
-- Handle special case of "#" replaced by empty string |
|||
else |
|||
if index == '1' then |
|||
for _, v in ipairs( possible ) do |
|||
chapter = kern_quotes (chapter); -- if necessary, separate chapter title's leading and trailing quote marks from Module provided quote marks |
|||
v = v:gsub( "#", "" ); |
|||
if is_set(args[v]) then |
|||
end |
|||
if value ~= nil and selected ~= v then |
|||
table.insert( error_list, v ); |
|||
else |
|||
value = args[v]; |
|||
selected = v; |
|||
end |
|||
end |
|||
end |
|||
end |
end |
||
for _, v in ipairs( possible ) do |
|||
chapter = script_concatenate (chapter, scriptchapter) -- <bdi> tags, lang atribute, categorization, etc; must be done after title is wrapped |
|||
if index ~= nil then |
|||
v = v:gsub( "#", index ); |
|||
if is_set (transchapter) then |
|||
end |
|||
transchapter = wrap_style ('trans-quoted-title', transchapter); |
|||
if is_set |
if is_set(args[v]) then |
||
if value ~= nil and selected ~= v then |
|||
chapter = chapter .. ' ' .. transchapter; |
|||
table.insert( error_list, v ); |
|||
else -- here when transchapter without chapter or script-chapter |
|||
else |
|||
chapter = transchapter; -- |
|||
value = args[v]; |
|||
chapter_error = ' ' .. set_error ('trans_missing_title', {'chapter'}); |
|||
selected = v; |
|||
end |
|||
end |
end |
||
end |
end |
||
if |
if #error_list > 0 then |
||
local error_str = ""; |
|||
chapter = external_link (chapterurl, chapter, chapter_url_source); -- adds bare_url_missing_title error if appropriate |
|||
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_style ('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_style ('parameter', selected); |
|||
table.insert( z.message_tail, { set_error( error_condition, {error_str}, true ) } ); |
|||
end |
end |
||
return |
return value, selected; |
||
end |
end |
||
--[[--------------------------< |
--[[--------------------------< F O R M A T _ C H A P T E R _ T I T L E >-------------------------------------- |
||
Format the three chapter parameters: |chapter=, |trans-chapter=, and |chapter-url= into a single Chapter meta- |
|||
This function searches a parameter's value for nonprintable or invisible characters. The search stops at the |
|||
parameter (chapter_url_source used for error messages). |
|||
first match. |
|||
This function will detect the visible replacement character when it is part of the wikisource. |
|||
Detects but ignores nowiki and math stripmarkers. Also detects other named stripmarkers (gallery, math, pre, ref) |
|||
and identifies them with a slightly different error message. See also coins_cleanup(). |
|||
Detects but ignores the character pattern that results from the transclusion of {{'}} templates. |
|||
Output of this function is an error message that identifies the character or the Unicode group, or the stripmarker |
|||
that was detected along with its position (or, for multi-byte characters, the position of its first byte) in the |
|||
parameter value. |
|||
]] |
]] |
||
local function |
local function format_chapter_title (chapter, transchapter, chapterurl, chapter_url_source) |
||
local chapter_error = ''; |
|||
local position = ''; -- position of invisible char or starting position of stripmarker |
|||
local dummy; -- end of matching string; not used but required to hold end position when a capture is returned |
|||
local capture; -- used by stripmarker detection to hold name of the stripmarker |
|||
local i=1; |
|||
local stripmarker, apostrophe; |
|||
if not is_set (chapter) then |
|||
capture = string.match (v, '[%w%p ]*'); -- Test for values that are simple ASCII text and bypass other tests if true |
|||
chapter = ''; -- just to be safe for concatenation |
|||
if is_set (transchapter) then |
|||
return; |
|||
chapter = wrap_style ('trans-quoted-title', transchapter); |
|||
end |
|||
chapter_error = " " .. set_error ('trans_missing_chapter'); |
|||
end |
|||
while cfg.invisible_chars[i] do |
|||
if is_set (chapterurl) then |
|||
local char=cfg.invisible_chars[i][1] -- the character or group name |
|||
chapter = external_link (chapterurl, chapter, chapter_url_source); -- adds bare_url_missing_title error if appropriate |
|||
local pattern=cfg.invisible_chars[i][2] -- the pattern used to find it |
|||
end |
|||
position, dummy, capture = mw.ustring.find (v, pattern) -- see if the parameter value contains characters that match the pattern |
|||
return chapter .. chapter_error; |
|||
else -- here when chapter is set |
|||
if position then |
|||
chapter = kern_quotes (chapter); -- if necessary, separate chapter title's leading and trailing quote marks from Module provided quote marks |
|||
-- if 'nowiki' == capture or 'math' == capture or ('ref' == capture and 'quote' == param) then -- nowiki, math, or quote param and ref stripmarker (not an error condition) |
|||
chapter = wrap_style ('quoted-title', chapter); |
|||
if 'nowiki' == capture or 'math' == capture then -- nowiki, math stripmarker (not an error condition) |
|||
if is_set (transchapter) then |
|||
stripmarker = true; -- set a flag |
|||
transchapter = wrap_style ('trans-quoted-title', transchapter); |
|||
elseif true == stripmarker and 'delete' == char then -- because stripmakers begin and end with the delete char, assume that we've found one end of a stripmarker |
|||
chapter = chapter .. ' ' .. transchapter; |
|||
position = nil; -- unset |
|||
end |
|||
elseif 'apostrophe' == char then -- apostrophe template uses ‍, hair space and zero-width space |
|||
if is_set (chapterurl) then |
|||
apostrophe = true; |
|||
chapter = external_link (chapterurl, chapter); -- adds bare_url_missing_title error if appropriate |
|||
elseif true == apostrophe and in_array (char, {'zero width joiner', 'zero width space', 'hair space'}) then |
|||
position = nil; -- unset |
|||
else |
|||
local err_msg; |
|||
if capture then |
|||
err_msg = capture .. ' ' .. char; |
|||
else |
|||
err_msg = char .. ' ' .. 'character'; |
|||
end |
|||
table.insert( z.message_tail, { set_error( 'invisible_char', {err_msg, wrap_style ('parameter', param), position}, true ) } ); -- add error message |
|||
return; -- and done with this parameter |
|||
end |
|||
end |
end |
||
i=i+1; -- bump our index |
|||
end |
end |
||
return chapter; |
|||
end |
end |
||
--[[ |
|||
Argument wrapper. This function provides support for argument |
|||
--[[--------------------------< A R G U M E N T _ W R A P P E R >---------------------------------------------- |
|||
mapping defined in the configuration file so that multiple names |
|||
can be transparently aliased to single internal variable. |
|||
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. |
|||
]] |
]] |
||
Line 665: | Line 521: | ||
end |
end |
||
--[[ |
|||
--[[--------------------------< V A L I D A T E >-------------------------------------------------------------- |
|||
Looks for a parameter's name in the whitelist. |
Looks for a parameter's name in the whitelist. |
||
Line 672: | Line 528: | ||
false - deprecated, supported parameters |
false - deprecated, supported parameters |
||
nil - unsupported parameters |
nil - unsupported parameters |
||
]] |
]] |
||
Line 696: | Line 551: | ||
return false; -- Not supported because not found or name is set to nil |
return false; -- Not supported because not found or name is set to nil |
||
end |
|||
-- Formats a wiki style internal link |
|||
local function internal_link_id(options) |
|||
return mw.ustring.format( '[[%s|%s]]%s[[%s%s%s|%s]]', |
|||
options.link, options.label, options.separator or " ", |
|||
options.prefix, options.id, options.suffix or "", |
|||
mw.text.nowiki(options.id) |
|||
); |
|||
end |
end |
||
Line 715: | Line 580: | ||
date = substitute (cfg.presentation['nowrap1'], date); |
date = substitute (cfg.presentation['nowrap1'], date); |
||
elseif date:match("^%a+%s*%d%d?,%s |
elseif date:match("^%a+%s*%d%d?,%s*%d%d%d%d$") or date:match ("^%d%d?%s*%a+%s*%d%d%d%d$") then |
||
cap, cap2 = string.match (date, "^(.*)%s+(%d%d%d%d)$"); |
cap, cap2 = string.match (date, "^(.*)%s+(%d%d%d%d)$"); |
||
date = substitute (cfg.presentation['nowrap2'], {cap, cap2}); |
date = substitute (cfg.presentation['nowrap2'], {cap, cap2}); |
||
Line 721: | Line 586: | ||
return date; |
return date; |
||
end |
|||
--[[--------------------------< IS _ V A L I D _ I S X N >----------------------------------------------------- |
|||
ISBN-10 and ISSN validator code calculates checksum across all isbn/issn digits including the check digit. ISBN-13 is checked in check_isbn(). |
|||
If the number is valid the result will be 0. Before calling this function, issbn/issn must be checked for length and stripped of dashes, |
|||
spaces and other non-isxn characters. |
|||
]] |
|||
local function is_valid_isxn (isxn_str, len) |
|||
local temp = 0; |
|||
isxn_str = { isxn_str:byte(1, len) }; -- make a table of byte values '0' → 0x30 .. '9' → 0x39, 'X' → 0x58 |
|||
len = len+1; -- adjust to be a loop counter |
|||
for i, v in ipairs( isxn_str ) do -- loop through all of the bytes and calculate the checksum |
|||
if v == string.byte( "X" ) then -- if checkdigit is X (compares the byte value of 'X' which is 0x58) |
|||
temp = temp + 10*( len - i ); -- it represents 10 decimal |
|||
else |
|||
temp = temp + tonumber( string.char(v) )*(len-i); |
|||
end |
|||
end |
|||
return temp % 11 == 0; -- returns true if calculation result is zero |
|||
end |
|||
--[[--------------------------< C H E C K _ I S B N >------------------------------------------------------------ |
|||
Determines whether an ISBN string is valid |
|||
]] |
|||
local function check_isbn( isbn_str ) |
|||
if nil ~= isbn_str:match("[^%s-0-9X]") then return false; end -- fail if isbn_str contains anything but digits, hyphens, or the uppercase X |
|||
isbn_str = isbn_str:gsub( "-", "" ):gsub( " ", "" ); -- remove hyphens and spaces |
|||
local len = isbn_str:len(); |
|||
if len ~= 10 and len ~= 13 then |
|||
return false; |
|||
end |
|||
if len == 10 then |
|||
if isbn_str:match( "^%d*X?$" ) == nil then return false; end |
|||
return is_valid_isxn(isbn_str, 10); |
|||
else |
|||
local temp = 0; |
|||
if isbn_str:match( "^97[89]%d*$" ) == nil then return false; end -- isbn13 begins with 978 or 979 |
|||
isbn_str = { isbn_str:byte(1, len) }; |
|||
for i, v in ipairs( isbn_str ) do |
|||
temp = temp + (3 - 2*(i % 2)) * tonumber( string.char(v) ); |
|||
end |
|||
return temp % 10 == 0; |
|||
end |
|||
end |
|||
--[[--------------------------< I S S N >---------------------------------------------------------------------- |
|||
Validate and format an issn. This code fixes the case where an editor has included an ISSN in the citation but has separated the two groups of four |
|||
digits with a space. When that condition occurred, the resulting link looked like this: |
|||
|issn=0819 4327 gives: [http://www.worldcat.org/issn/0819 4327 0819 4327] -- can't have spaces in an external link |
|||
This code now prevents that by inserting a hyphen at the issn midpoint. It also validates the issn for length and makes sure that the checkdigit agrees |
|||
with the calculated value. Incorrect length (8 digits), characters other than 0-9 and X, or checkdigit / calculated value mismatch will all cause a check issn |
|||
error message. The issn is always displayed with a hyphen, even if the issn was given as a single group of 8 digits. |
|||
]] |
|||
local function issn(id) |
|||
local issn_copy = id; -- save a copy of unadulterated issn; use this version for display if issn does not validate |
|||
local handler = cfg.id_handlers['ISSN']; |
|||
local text; |
|||
local valid_issn = true; |
|||
id=id:gsub( "[%s-–]", "" ); -- strip spaces, hyphens, and endashes from the issn |
|||
if 8 ~= id:len() or nil == id:match( "^%d*X?$" ) then -- validate the issn: 8 digits long, containing only 0-9 or X in the last position |
|||
valid_issn=false; -- wrong length or improper character |
|||
else |
|||
valid_issn=is_valid_isxn(id, 8); -- validate issn |
|||
end |
|||
if true == valid_issn then |
|||
id = string.sub( id, 1, 4 ) .. "-" .. string.sub( id, 5 ); -- if valid, display correctly formatted version |
|||
else |
|||
id = issn_copy; -- if not valid, use the show the invalid issn with error message |
|||
end |
|||
text = external_link_id({link = handler.link, label = handler.label, |
|||
prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode}) |
|||
if false == valid_issn then |
|||
text = text .. ' ' .. set_error( 'bad_issn' ) -- add an error message if the issn is invalid |
|||
end |
|||
return text |
|||
end |
|||
--[[--------------------------< A M A Z O N >------------------------------------------------------------------ |
|||
Formats a link to Amazon. Do simple error checking: asin must be mix of 10 numeric or uppercase alpha |
|||
characters. If a mix, first character must be uppercase alpha; if all numeric, asins must be 10-digit |
|||
isbn. If 10-digit isbn, add a maintenance category so a bot or awb script can replace |asin= with |isbn=. |
|||
Error message if not 10 characters, if not isbn10, if mixed and first character is a digit. |
|||
]] |
|||
local function amazon(id, domain) |
|||
local err_cat = "" |
|||
if not id:match("^[%d%u][%d%u][%d%u][%d%u][%d%u][%d%u][%d%u][%d%u][%d%u][%d%u]$") then |
|||
err_cat = ' ' .. set_error ('bad_asin'); -- asin is not a mix of 10 uppercase alpha and numeric characters |
|||
else |
|||
if id:match("^%d%d%d%d%d%d%d%d%d[%dX]$") then -- if 10-digit numeric (or 9 digits with terminal X) |
|||
if check_isbn( id ) then -- see if asin value is isbn10 |
|||
add_maint_cat ('ASIN'); |
|||
elseif not is_set (err_cat) then |
|||
err_cat = ' ' .. set_error ('bad_asin'); -- asin is not isbn10 |
|||
end |
|||
elseif not id:match("^%u[%d%u]+$") then |
|||
err_cat = ' ' .. set_error ('bad_asin'); -- asin doesn't begin with uppercase alpha |
|||
end |
|||
end |
|||
if not is_set(domain) then |
|||
domain = "com"; |
|||
elseif in_array (domain, {'jp', 'uk'}) then -- Japan, United Kingdom |
|||
domain = "co." .. domain; |
|||
elseif in_array (domain, {'au', 'br', 'mx'}) then -- Australia, Brazil, Mexico |
|||
domain = "com." .. domain; |
|||
end |
|||
local handler = cfg.id_handlers['ASIN']; |
|||
return external_link_id({link = handler.link, |
|||
label=handler.label , prefix="//www.amazon."..domain.."/dp/",id=id, |
|||
encode=handler.encode, separator = handler.separator}) .. err_cat; |
|||
end |
|||
--[[--------------------------< A R X I V >-------------------------------------------------------------------- |
|||
See: http://arxiv.org/help/arxiv_identifier |
|||
format and error check arXiv identifier. There are three valid forms of the identifier: |
|||
the first form, valid only between date codes 9108 and 0703 is: |
|||
arXiv:<archive>.<class>/<date code><number><version> |
|||
where: |
|||
<archive> is a string of alpha characters - may be hyphenated; no other punctuation |
|||
<class> is a string of alpha characters - may be hyphenated; no other punctuation |
|||
<date code> is four digits in the form YYMM where YY is the last two digits of the four-digit year and MM is the month number January = 01 |
|||
first digit of YY for this form can only 9 and 0 |
|||
<number> is a three-digit number |
|||
<version> is a 1 or more digit number preceded with a lowercase v; no spaces (undocumented) |
|||
the second form, valid from April 2007 through December 2014 is: |
|||
arXiv:<date code>.<number><version> |
|||
where: |
|||
<date code> is four digits in the form YYMM where YY is the last two digits of the four-digit year and MM is the month number January = 01 |
|||
<number> is a four-digit number |
|||
<version> is a 1 or more digit number preceded with a lowercase v; no spaces |
|||
the third form, valid from January 2015 is: |
|||
arXiv:<date code>.<number><version> |
|||
where: |
|||
<date code> and <version> are as defined for 0704-1412 |
|||
<number> is a five-digit number |
|||
]] |
|||
local function arxiv (id, class) |
|||
local handler = cfg.id_handlers['ARXIV']; |
|||
local year, month, version; |
|||
local err_cat = ''; |
|||
local text; |
|||
if id:match("^%a[%a%.%-]+/[90]%d[01]%d%d%d%d$") or id:match("^%a[%a%.%-]+/[90]%d[01]%d%d%d%dv%d+$") then -- test for the 9108-0703 format w/ & w/o version |
|||
year, month = id:match("^%a[%a%.%-]+/([90]%d)([01]%d)%d%d%d[v%d]*$"); |
|||
year = tonumber(year); |
|||
month = tonumber(month); |
|||
if ((not (90 < year or 8 > year)) or (1 > month or 12 < month)) or -- if invalid year or invalid month |
|||
((91 == year and 7 > month) or (7 == year and 3 < month)) then -- if years ok, are starting and ending months ok? |
|||
err_cat = ' ' .. set_error( 'bad_arxiv' ); -- set error message |
|||
end |
|||
elseif id:match("^%d%d[01]%d%.%d%d%d%d$") or id:match("^%d%d[01]%d%.%d%d%d%dv%d+$") then -- test for the 0704-1412 w/ & w/o version |
|||
year, month = id:match("^(%d%d)([01]%d)%.%d%d%d%d[v%d]*$"); |
|||
year = tonumber(year); |
|||
month = tonumber(month); |
|||
if ((7 > year) or (14 < year) or (1 > month or 12 < month)) or -- is year invalid or is month invalid? (doesn't test for future years) |
|||
((7 == year) and (4 > month)) then --or -- when year is 07, is month invalid (before April)? |
|||
err_cat = ' ' .. set_error( 'bad_arxiv' ); -- set error message |
|||
end |
|||
elseif id:match("^%d%d[01]%d%.%d%d%d%d%d$") or id:match("^%d%d[01]%d%.%d%d%d%d%dv%d+$") then -- test for the 1501- format w/ & w/o version |
|||
year, month = id:match("^(%d%d)([01]%d)%.%d%d%d%d%d[v%d]*$"); |
|||
year = tonumber(year); |
|||
month = tonumber(month); |
|||
if ((15 > year) or (1 > month or 12 < month)) then -- is year invalid or is month invalid? (doesn't test for future years) |
|||
err_cat = ' ' .. set_error( 'bad_arxiv' ); -- set error message |
|||
end |
|||
else |
|||
err_cat = ' ' .. set_error( 'bad_arxiv' ); -- arXiv id doesn't match any format |
|||
end |
|||
text = external_link_id({link = handler.link, label = handler.label, |
|||
prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode}) .. err_cat; |
|||
if is_set (class) then |
|||
class = ' [[' .. '//arxiv.org/archive/' .. class .. ' ' .. class .. ']]'; -- external link within square brackets, not wikilink |
|||
else |
|||
class = ''; -- empty string for concatenation |
|||
end |
|||
return text .. class; |
|||
end |
|||
--[[ |
|||
lccn normalization (http://www.loc.gov/marc/lccn-namespace.html#normalization) |
|||
1. Remove all blanks. |
|||
2. If there is a forward slash (/) in the string, remove it, and remove all characters to the right of the forward slash. |
|||
3. If there is a hyphen in the string: |
|||
a. Remove it. |
|||
b. Inspect the substring following (to the right of) the (removed) hyphen. Then (and assuming that steps 1 and 2 have been carried out): |
|||
1. All these characters should be digits, and there should be six or less. (not done in this function) |
|||
2. If the length of the substring is less than 6, left-fill the substring with zeroes until the length is six. |
|||
Returns a normalized lccn for lccn() to validate. There is no error checking (step 3.b.1) performed in this function. |
|||
]] |
|||
local function normalize_lccn (lccn) |
|||
lccn = lccn:gsub ("%s", ""); -- 1. strip whitespace |
|||
if nil ~= string.find (lccn,'/') then |
|||
lccn = lccn:match ("(.-)/"); -- 2. remove forward slash and all character to the right of it |
|||
end |
|||
local prefix |
|||
local suffix |
|||
prefix, suffix = lccn:match ("(.+)%-(.+)"); -- 3.a remove hyphen by splitting the string into prefix and suffix |
|||
if nil ~= suffix then -- if there was a hyphen |
|||
suffix=string.rep("0", 6-string.len (suffix)) .. suffix; -- 3.b.2 left fill the suffix with 0s if suffix length less than 6 |
|||
lccn=prefix..suffix; -- reassemble the lccn |
|||
end |
|||
return lccn; |
|||
end |
|||
--[[ |
|||
Format LCCN link and do simple error checking. LCCN is a character string 8-12 characters long. The length of the LCCN dictates the character type of the first 1-3 characters; the |
|||
rightmost eight are always digits. http://info-uri.info/registry/OAIHandler?verb=GetRecord&metadataPrefix=reg&identifier=info:lccn/ |
|||
length = 8 then all digits |
|||
length = 9 then lccn[1] is lower case alpha |
|||
length = 10 then lccn[1] and lccn[2] are both lower case alpha or both digits |
|||
length = 11 then lccn[1] is lower case alpha, lccn[2] and lccn[3] are both lower case alpha or both digits |
|||
length = 12 then lccn[1] and lccn[2] are both lower case alpha |
|||
]] |
|||
local function lccn(lccn) |
|||
local handler = cfg.id_handlers['LCCN']; |
|||
local err_cat = ''; -- presume that LCCN is valid |
|||
local id = lccn; -- local copy of the lccn |
|||
id = normalize_lccn (id); -- get canonical form (no whitespace, hyphens, forward slashes) |
|||
local len = id:len(); -- get the length of the lccn |
|||
if 8 == len then |
|||
if id:match("[^%d]") then -- if LCCN has anything but digits (nil if only digits) |
|||
err_cat = ' ' .. set_error( 'bad_lccn' ); -- set an error message |
|||
end |
|||
elseif 9 == len then -- LCCN should be adddddddd |
|||
if nil == id:match("%l%d%d%d%d%d%d%d%d") then -- does it match our pattern? |
|||
err_cat = ' ' .. set_error( 'bad_lccn' ); -- set an error message |
|||
end |
|||
elseif 10 == len then -- LCCN should be aadddddddd or dddddddddd |
|||
if id:match("[^%d]") then -- if LCCN has anything but digits (nil if only digits) ... |
|||
if nil == id:match("^%l%l%d%d%d%d%d%d%d%d") then -- ... see if it matches our pattern |
|||
err_cat = ' ' .. set_error( 'bad_lccn' ); -- no match, set an error message |
|||
end |
|||
end |
|||
elseif 11 == len then -- LCCN should be aaadddddddd or adddddddddd |
|||
if not (id:match("^%l%l%l%d%d%d%d%d%d%d%d") or id:match("^%l%d%d%d%d%d%d%d%d%d%d")) then -- see if it matches one of our patterns |
|||
err_cat = ' ' .. set_error( 'bad_lccn' ); -- no match, set an error message |
|||
end |
|||
elseif 12 == len then -- LCCN should be aadddddddddd |
|||
if not id:match("^%l%l%d%d%d%d%d%d%d%d%d%d") then -- see if it matches our pattern |
|||
err_cat = ' ' .. set_error( 'bad_lccn' ); -- no match, set an error message |
|||
end |
|||
else |
|||
err_cat = ' ' .. set_error( 'bad_lccn' ); -- wrong length, set an error message |
|||
end |
|||
if not is_set (err_cat) and nil ~= lccn:find ('%s') then |
|||
err_cat = ' ' .. set_error( 'bad_lccn' ); -- lccn contains a space, set an error message |
|||
end |
|||
return external_link_id({link = handler.link, label = handler.label, |
|||
prefix=handler.prefix,id=lccn,separator=handler.separator, encode=handler.encode}) .. err_cat; |
|||
end |
|||
--[[ |
|||
Format PMID and do simple error checking. PMIDs are sequential numbers beginning at 1 and counting up. This code checks the PMID to see that it |
|||
contains only digits and is less than test_limit; the value in local variable test_limit will need to be updated periodically as more PMIDs are issued. |
|||
]] |
|||
local function pmid(id) |
|||
local test_limit = 30000000; -- update this value as PMIDs approach |
|||
local handler = cfg.id_handlers['PMID']; |
|||
local err_cat = ''; -- presume that PMID is valid |
|||
if id:match("[^%d]") then -- if PMID has anything but digits |
|||
err_cat = ' ' .. set_error( 'bad_pmid' ); -- set an error message |
|||
else -- PMID is only digits |
|||
local id_num = tonumber(id); -- convert id to a number for range testing |
|||
if 1 > id_num or test_limit < id_num then -- if PMID is outside test limit boundaries |
|||
err_cat = ' ' .. set_error( 'bad_pmid' ); -- set an error message |
|||
end |
|||
end |
|||
return external_link_id({link = handler.link, label = handler.label, |
|||
prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode}) .. err_cat; |
|||
end |
|||
--[[--------------------------< I S _ E M B A R G O E D >------------------------------------------------------ |
|||
Determines if a PMC identifier's online version is embargoed. Compares the date in |embargo= against today's date. If embargo date is |
|||
in the future, returns the content of |embargo=; otherwise, returns and empty string because the embargo has expired or because |
|||
|embargo= was not set in this cite. |
|||
]] |
|||
local function is_embargoed (embargo) |
|||
if is_set (embargo) then |
|||
local lang = mw.getContentLanguage(); |
|||
local good1, embargo_date, good2, todays_date; |
|||
good1, embargo_date = pcall( lang.formatDate, lang, 'U', embargo ); |
|||
good2, todays_date = pcall( lang.formatDate, lang, 'U' ); |
|||
if good1 and good2 then -- if embargo date and today's date are good dates |
|||
if tonumber( embargo_date ) >= tonumber( todays_date ) then -- is embargo date is in the future? |
|||
return embargo; -- still embargoed |
|||
else |
|||
add_maint_cat ('embargo') |
|||
return ''; -- unset because embargo has expired |
|||
end |
|||
end |
|||
end |
|||
return ''; -- |embargo= not set return empty string |
|||
end |
|||
--[[--------------------------< P M C >------------------------------------------------------------------------ |
|||
Format a PMC, do simple error checking, and check for embargoed articles. |
|||
The embargo parameter takes a date for a value. If the embargo date is in the future the PMC identifier will not |
|||
be linked to the article. If the embargo date is today or in the past, or if it is empty or omitted, then the |
|||
PMC identifier is linked to the article through the link at cfg.id_handlers['PMC'].prefix. |
|||
PMC embargo date testing is done in function is_embargoed () which is called earlier because when the citation |
|||
has |pmc=<value> but does not have a |url= then |title= is linked with the PMC link. Function is_embargoed () |
|||
returns the embargo date if the PMC article is still embargoed, otherwise it returns an empty string. |
|||
PMCs are sequential numbers beginning at 1 and counting up. This code checks the PMC to see that it contains only digits and is less |
|||
than test_limit; the value in local variable test_limit will need to be updated periodically as more PMCs are issued. |
|||
]] |
|||
local function pmc(id, embargo) |
|||
local test_limit = 5000000; -- update this value as PMCs approach |
|||
local handler = cfg.id_handlers['PMC']; |
|||
local err_cat = ''; -- presume that PMC is valid |
|||
local text; |
|||
if id:match("[^%d]") then -- if PMC has anything but digits |
|||
err_cat = ' ' .. set_error( 'bad_pmc' ); -- set an error message |
|||
else -- PMC is only digits |
|||
local id_num = tonumber(id); -- convert id to a number for range testing |
|||
if 1 > id_num or test_limit < id_num then -- if PMC is outside test limit boundaries |
|||
err_cat = ' ' .. set_error( 'bad_pmc' ); -- set an error message |
|||
end |
|||
end |
|||
if is_set (embargo) then -- is PMC is still embargoed? |
|||
text="[[" .. handler.link .. "|" .. handler.label .. "]]:" .. handler.separator .. id .. err_cat; -- still embargoed so no external link |
|||
else |
|||
text = external_link_id({link = handler.link, label = handler.label, -- no embargo date or embargo has expired, ok to link to article |
|||
prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode}) .. err_cat; |
|||
end |
|||
return text; |
|||
end |
|||
-- Formats a DOI and checks for DOI errors. |
|||
-- DOI names contain two parts: prefix and suffix separated by a forward slash. |
|||
-- Prefix: directory indicator '10.' followed by a registrant code |
|||
-- Suffix: character string of any length chosen by the registrant |
|||
-- This function checks a DOI name for: prefix/suffix. If the doi name contains spaces or endashes, |
|||
-- or, if it ends with a period or a comma, this function will emit a bad_doi error message. |
|||
-- DOI names are case-insensitive and can incorporate any printable Unicode characters so the test for spaces, endash, |
|||
-- and terminal punctuation may not be technically correct but it appears, that in practice these characters are rarely if ever used in doi names. |
|||
local function doi(id, inactive) |
|||
local cat = "" |
|||
local handler = cfg.id_handlers['DOI']; |
|||
local text; |
|||
if is_set(inactive) then |
|||
local inactive_year = inactive:match("%d%d%d%d") or ''; -- try to get the year portion from the inactive date |
|||
text = "[[" .. handler.link .. "|" .. handler.label .. "]]:" .. id; |
|||
if is_set(inactive_year) then |
|||
table.insert( z.error_categories, "Pages with DOIs inactive since " .. inactive_year ); |
|||
else |
|||
table.insert( z.error_categories, "Pages with inactive DOIs" ); -- when inactive doesn't contain a recognizable year |
|||
end |
|||
inactive = " (" .. cfg.messages['inactive'] .. " " .. inactive .. ")" |
|||
else |
|||
text = external_link_id({link = handler.link, label = handler.label, |
|||
prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode}) |
|||
inactive = "" |
|||
end |
|||
if nil == id:match("^10%.[^%s–]-/[^%s–]-[^%.,]$") then -- doi must begin with '10.', must contain a fwd slash, must not contain spaces or endashes, and must not end with period or comma |
|||
cat = ' ' .. set_error( 'bad_doi' ); |
|||
end |
|||
return text .. inactive .. cat |
|||
end |
|||
-- Formats an OpenLibrary link, and checks for associated errors. |
|||
local function openlibrary(id) |
|||
local code = id:match("^%d+([AMW])$"); -- only digits followed by 'A', 'M', or 'W' |
|||
local handler = cfg.id_handlers['OL']; |
|||
if ( code == "A" ) then |
|||
return external_link_id({link=handler.link, label=handler.label, |
|||
prefix="http://openlibrary.org/authors/OL",id=id, separator=handler.separator, |
|||
encode = handler.encode}) |
|||
elseif ( code == "M" ) then |
|||
return external_link_id({link=handler.link, label=handler.label, |
|||
prefix="http://openlibrary.org/books/OL",id=id, separator=handler.separator, |
|||
encode = handler.encode}) |
|||
elseif ( code == "W" ) then |
|||
return external_link_id({link=handler.link, label=handler.label, |
|||
prefix= "http://openlibrary.org/works/OL",id=id, separator=handler.separator, |
|||
encode = handler.encode}) |
|||
else |
|||
return external_link_id({link=handler.link, label=handler.label, |
|||
prefix= "http://openlibrary.org/OL",id=id, separator=handler.separator, |
|||
encode = handler.encode}) .. |
|||
' ' .. set_error( 'bad_ol' ); |
|||
end |
|||
end |
|||
--[[--------------------------< M E S S A G E _ I D >---------------------------------------------------------- |
|||
Validate and format a usenet message id. Simple error checking, looks for 'id-left@id-right' not enclosed in |
|||
'<' and/or '>' angle brackets. |
|||
]] |
|||
local function message_id (id) |
|||
local handler = cfg.id_handlers['USENETID']; |
|||
text = external_link_id({link = handler.link, label = handler.label, |
|||
prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode}) |
|||
if not id:match('^.+@.+$') or not id:match('^[^<].*[^>]$')then -- doesn't have '@' or has one or first or last character is '< or '>' |
|||
text = text .. ' ' .. set_error( 'bad_message_id' ) -- add an error message if the message id is invalid |
|||
end |
|||
return text |
|||
end |
end |
||
Line 741: | Line 1,075: | ||
end |
end |
||
--[[--------------------------< C L E A N _ I S B N >---------------------------------------------------------- |
|||
Removes irrelevant text and dashes from ISBN number |
|||
--[[--------------------------< H Y P H E N _ T O _ D A S H >-------------------------------------------------- |
|||
Similar to that used for Special:BookSources |
|||
]] |
|||
Converts a hyphen to a dash |
|||
local function clean_isbn( isbn_str ) |
|||
return isbn_str:gsub( "[^-0-9X]", "" ); |
|||
end |
|||
--[[--------------------------< E S C A P E _ L U A _ M A G I C _ C H A R S >---------------------------------- |
|||
Returns a string where all of lua's magic characters have been escaped. This is important because functions like |
|||
string.gsub() treat their pattern and replace strings as patterns, not literal strings. |
|||
]] |
]] |
||
local function escape_lua_magic_chars (argument) |
|||
argument = argument:gsub("%%", "%%%%"); -- replace % with %% |
|||
argument = argument:gsub("([%^%$%(%)%.%[%]%*%+%-%?])", "%%%1"); -- replace all other lua magic pattern characters |
|||
return argument; |
|||
end |
|||
--[[--------------------------< S T R I P _ A P O S T R O P H E _ M A R K U P >-------------------------------- |
|||
Strip wiki italic and bold markup from argument so that it doesn't contaminate COinS metadata. |
|||
This function strips common patterns of apostrophe markup. We presume that editors who have taken the time to |
|||
markup a title have, as a result, provided valid markup. When they don't, some single apostrophes are left behind. |
|||
]] |
|||
local function strip_apostrophe_markup (argument) |
|||
if not is_set (argument) then return argument; end |
|||
while true do |
|||
if argument:match ("%'%'%'%'%'") then -- bold italic (5) |
|||
argument=argument:gsub("%'%'%'%'%'", ""); -- remove all instances of it |
|||
elseif argument:match ("%'%'%'%'") then -- italic start and end without content (4) |
|||
argument=argument:gsub("%'%'%'%'", ""); |
|||
elseif argument:match ("%'%'%'") then -- bold (3) |
|||
argument=argument:gsub("%'%'%'", ""); |
|||
elseif argument:match ("%'%'") then -- italic (2) |
|||
argument=argument:gsub("%'%'", ""); |
|||
else |
|||
break; |
|||
end |
|||
end |
|||
return argument; -- done |
|||
end |
|||
--[[--------------------------< M A K E _ C O I N S _ T I T L E >---------------------------------------------- |
|||
Makes a title for COinS from Title and / or ScriptTitle (or any other name-script pairs) |
|||
Apostrophe markup (bold, italics) is stripped from each value so that the COinS metadata isn't correupted with strings |
|||
of %27%27... |
|||
]] |
|||
local function make_coins_title (title, script) |
|||
if is_set (title) then |
|||
title = strip_apostrophe_markup (title); -- strip any apostrophe markup |
|||
else |
|||
title=''; -- if not set, make sure title is an empty string |
|||
end |
|||
if is_set (script) then |
|||
script = script:gsub ('^%l%l%s*:%s*', ''); -- remove language prefix if present (script value may now be empty string) |
|||
script = strip_apostrophe_markup (script); -- strip any apostrophe markup |
|||
else |
|||
script=''; -- if not set, make sure script is an empty string |
|||
end |
|||
if is_set (title) and is_set (script) then |
|||
script = ' ' .. script; -- add a space before we concatenate |
|||
end |
|||
return title .. script; -- return the concatenation |
|||
end |
|||
--[[--------------------------< G E T _ C O I N S _ P A G E S >------------------------------------------------ |
|||
Extract page numbers from external wikilinks in any of the |page=, |pages=, or |at= parameters for use in COinS. |
|||
]] |
|||
local function get_coins_pages (pages) |
|||
local pattern; |
|||
if not is_set (pages) then return pages; end -- if no page numbers then we're done |
|||
while true do |
|||
pattern = pages:match("%[(%w*:?//[^ ]+%s+)[%w%d].*%]"); -- pattern is the opening bracket, the url and following space(s): "[url " |
|||
if nil == pattern then break; end -- no more urls |
|||
pattern = escape_lua_magic_chars (pattern); -- pattern is not a literal string; escape lua's magic pattern characters |
|||
pages = pages:gsub(pattern, ""); -- remove as many instances of pattern as possible |
|||
end |
|||
pages = pages:gsub("[%[%]]", ""); -- remove the brackets |
|||
pages = pages:gsub("–", "-" ); -- replace endashes with hyphens |
|||
pages = pages:gsub("&%w+;", "-" ); -- and replace html entities (– etc.) with hyphens; do we need to replace numerical entities like   and the like? |
|||
return pages; |
|||
end |
|||
-- Gets the display text for a wikilink like [[A|B]] or [[B]] gives B |
|||
local function remove_wiki_link( str ) |
|||
return (str:gsub( "%[%[([^%[%]]*)%]%]", function(l) |
|||
return l:gsub( "^[^|]*|(.*)$", "%1" ):gsub("^%s*(.-)%s*$", "%1"); |
|||
end)); |
|||
end |
|||
-- Converts a hyphen to a dash |
|||
local function hyphen_to_dash( str ) |
local function hyphen_to_dash( str ) |
||
if not is_set(str) or str:match( "[%[%]{}<>]" ) ~= nil then |
if not is_set(str) or str:match( "[%[%]{}<>]" ) ~= nil then |
||
Line 754: | Line 1,186: | ||
return str:gsub( '-', '–' ); |
return str:gsub( '-', '–' ); |
||
end |
end |
||
--[[--------------------------< S A F E _ J O I N >------------------------------------------------------------ |
--[[--------------------------< S A F E _ J O I N >------------------------------------------------------------ |
||
Line 850: | Line 1,281: | ||
|firstn= also allowed to contain hyphens, spaces, apostrophes, and periods |
|firstn= also allowed to contain hyphens, spaces, apostrophes, and periods |
||
At the time of this writing, I had to write the 'if nil == mw.ustring.find ...' test ouside of the code editor and |
At the time of this writing, I had to write the 'if nil == mw.ustring.find ...' test ouside of the code editor and past it here |
||
because the code editor gets confused between character insertion point and cursor position. |
because the code editor gets confused between character insertion point and cursor position. |
||
Line 857: | Line 1,288: | ||
local function is_good_vanc_name (last, first) |
local function is_good_vanc_name (last, first) |
||
if nil == mw.ustring.find (last, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%']*$") or nil == mw.ustring.find (first, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%'%.]*$") then |
if nil == mw.ustring.find (last, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%']*$") or nil == mw.ustring.find (first, "^[A-Za-zÀ-ÖØ-öø-ƿDŽ-ɏ%-%s%'%.]*$") then |
||
if true ~= Page_in_vanc_error_cat then -- if we haven't been here before then set a sticky flag |
|||
add_vanc_error (); |
|||
Page_in_vanc_error_cat=true; -- so that if there are more than one error the category is added only once |
|||
table.insert( z.message_tail, { set_error( 'vancouver', {}, true ) } ); |
|||
end |
|||
return false; -- not a string of latin characters; Vancouver required Romanization |
return false; -- not a string of latin characters; Vancouver required Romanization |
||
end; |
end; |
||
Line 894: | Line 1,328: | ||
]] |
]] |
||
local function list_people(control, people, etal |
local function list_people(control, people, etal) |
||
local sep; |
local sep; |
||
local namesep; |
local namesep; |
||
Line 935: | Line 1,369: | ||
if ( "vanc" == format ) then -- if vancouver format |
if ( "vanc" == format ) then -- if vancouver format |
||
one = one:gsub ('%.', ''); -- remove periods from surnames (http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35029/) |
one = one:gsub ('%.', ''); -- remove periods from surnames (http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35029/) |
||
if |
if is_good_vanc_name (one, first) then -- and name is all Latin characters |
||
first = reduce_to_initials(first) -- attempt to convert first name(s) to initials |
first = reduce_to_initials(first) -- attempt to convert first name(s) to initials |
||
end |
end |
||
end |
end |
||
Line 942: | Line 1,376: | ||
end |
end |
||
if is_set(person.link) and person.link ~= control.page_name then |
if is_set(person.link) and person.link ~= control.page_name then |
||
one = "[[" .. person.link .. "|" .. one .. "]]" |
one = "[[" .. person.link .. "|" .. one .. "]]" -- link author/editor if this page is not the author's/editor's page |
||
end |
end |
||
if is_set(person.link) and ((nil ~= person.link:find("//")) or (nil ~= person.link:find("[%[%]]"))) then |
|||
one = one .. " " .. set_error( 'bad_authorlink' ) end -- url or wikilink in author link; |
|||
end |
end |
||
table.insert( text, one ) |
table.insert( text, one ) |
||
Line 950: | Line 1,387: | ||
end |
end |
||
local count = #text / 2; |
local count = #text / 2; |
||
if count > 0 then |
if count > 0 then |
||
if count > 1 and is_set(lastauthoramp) and not etal then |
if count > 1 and is_set(lastauthoramp) and not etal then |
||
text[#text-2] = " & "; |
text[#text-2] = " & "; |
||
end |
end |
||
text[#text] = nil; |
text[#text] = nil; |
||
end |
end |
||
local result = table.concat(text) |
local result = table.concat(text) -- construct list |
||
if etal then |
|||
if etal and is_set (result) then -- etal may be set by |display-authors=etal but we might not have a last-first list |
|||
local etal_text = cfg.messages['et al']; |
|||
result = result .. " " .. etal_text; |
|||
end |
end |
||
Line 970: | Line 1,408: | ||
Generates a CITEREF anchor ID if we have at least one name or a date. Otherwise returns an empty string. |
Generates a CITEREF anchor ID if we have at least one name or a date. Otherwise returns an empty string. |
||
]] |
|||
namelist is one of the contributor-, author-, or editor-name lists chosen in that order. year is Year or anchor_year. |
|||
local function anchor_id( options ) |
|||
]] |
|||
local id = table.concat( options ); -- concatenate names and year for CITEREF id |
|||
local function anchor_id (namelist, year) |
|||
local names={}; -- a table for the one to four names and year |
|||
for i,v in ipairs (namelist) do -- loop through the list and take up to the first four last names |
|||
names[i] = v.last |
|||
if i == 4 then break end -- if four then done |
|||
end |
|||
table.insert (names, year); -- add the year at the end |
|||
local id = table.concat(names); -- concatenate names and year for CITEREF id |
|||
if is_set (id) then -- if concatenation is not an empty string |
if is_set (id) then -- if concatenation is not an empty string |
||
return "CITEREF" .. id; -- add the CITEREF portion |
return "CITEREF" .. id; -- add the CITEREF portion |
||
Line 986: | Line 1,417: | ||
return ''; -- return an empty string; no reason to include CITEREF id in this citation |
return ''; -- return an empty string; no reason to include CITEREF id in this citation |
||
end |
end |
||
end |
|||
--[[--------------------------< N A M E _ H A S _ E T A L >---------------------------------------------------- |
|||
Evaluates the content of author and editor name parameters 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 it's previous state because it may have been set by |
|||
previous passes through this function or by the parameters |display-authors=etal or |display-editors=etal |
|||
]] |
|||
local function name_has_etal (name, etal, nocat) |
|||
if is_set (name) then -- name can be nil in which case just return |
|||
local etal_pattern = "[;,]? *[\"']*%f[%a][Ee][Tt] *[Aa][Ll][%.\"']*$" -- variations on the 'et al' theme |
|||
local others_pattern = "[;,]? *%f[%a]and [Oo]thers"; -- and alternate to et al. |
|||
if name:match (etal_pattern) then -- variants on et al. |
|||
name = name:gsub (etal_pattern, ''); -- if found, remove |
|||
etal = true; -- set flag (may have been set previously here or by |display-authors=etal) |
|||
if not nocat then -- no categorization for |vauthors= |
|||
add_maint_cat ('etal'); -- and add a category if not already added |
|||
end |
|||
elseif name:match (others_pattern) then -- if not 'et al.', then 'and others'? |
|||
name = name:gsub (others_pattern, ''); -- if found, remove |
|||
etal = true; -- set flag (may have been set previously here or by |display-authors=etal) |
|||
if not nocat then -- no categorization for |vauthors= |
|||
add_maint_cat ('etal'); -- and add a category if not already added |
|||
end |
|||
end |
|||
end |
|||
return name, etal; -- |
|||
end |
end |
||
Line 1,048: | Line 1,445: | ||
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 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 etal=false; -- return value set to true when we find some form of et al. in an author parameter |
||
local pattern = ",? *'*[Ee][Tt] *[Aa][Ll][%.']*$" -- variations on the 'et al' theme |
|||
local err_msg_list_name = list_name:match ("(%w+)List") .. 's list'; -- modify AuthorList or EditorList for use in error messages if necessary |
local err_msg_list_name = list_name:match ("(%w+)List") .. 's list'; -- modify AuthorList or EditorList for use in error messages if necessary |
||
while true do |
while true do |
||
last = select_one( args, cfg.aliases[list_name .. '-Last'], 'redundant_parameters', i ); -- search through args for name components beginning at 1 |
last = select_one( args, cfg.aliases[list_name .. '-Last'], 'redundant_parameters', i ); -- search through args for name components beginning at 1 |
||
Line 1,056: | Line 1,455: | ||
mask = select_one( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i ); |
mask = select_one( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i ); |
||
local name = tostring(last); |
|||
last, etal = name_has_etal (last, etal, false); -- find and remove variations on et al. |
|||
if name:match (pattern) then -- varients on et al. |
|||
last = name:gsub (pattern, ''); -- if found, remove |
|||
etal = true; |
|||
end |
|||
name = tostring(first); |
|||
if name:match (pattern) then -- varients on et al. |
|||
first = name:gsub (pattern, ''); -- if found, remove |
|||
etal = true; |
|||
end |
|||
if first and not last then -- if there is a firstn without a matching lastn |
if first and not last then -- if there is a firstn without a matching lastn |
||
Line 1,063: | Line 1,470: | ||
elseif not first and not last then -- if both firstn and lastn aren't found, are we done? |
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 |
count = count + 1; -- number of times we haven't found last and first |
||
if 2 |
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 |
break; -- normal exit or there is a two-name hole in the list; can't tell which |
||
end |
end |
||
else -- we have last with or without a first |
else -- we have last with or without a first |
||
names[n] = {last = last, first = first, link = link, mask = mask}; -- add this name to our names list |
|||
link_title_ok (link, list_name:match ("(%w+)List"):lower() .. '-link' .. i, last, list_name:match ("(%w+)List"):lower() .. '-last' .. i); -- check for improper wikimarkup |
|||
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 |
n = n + 1; -- point to next location in the names table |
||
if 1 == count then -- if the previous name was missing |
if 1 == count then -- if the previous name was missing |
||
Line 1,079: | Line 1,484: | ||
end |
end |
||
if true == etal then |
|||
add_maint_cat ('etal'); |
|||
end |
|||
return names, etal; -- all done, return our list of names |
return names, etal; -- all done, return our list of names |
||
end |
end |
||
-- Populates ID table from arguments using configuration settings |
|||
local function extract_ids( args ) |
|||
local id_list = {}; |
|||
for k, v in pairs( cfg.id_handlers ) do |
|||
v = select_one( args, v.parameters, 'redundant_parameters' ); |
|||
if is_set(v) then id_list[k] = v; end |
|||
end |
|||
return id_list; |
|||
end |
|||
--[[--------------------------< B U I L D _ I D _ L I S T >-------------------------------------------------------- |
|||
Takes a table of IDs and turns it into a table of formatted ID outputs. |
|||
]] |
|||
local function build_id_list( id_list, options ) |
|||
local new_list, handler = {}; |
|||
function fallback(k) return { __index = function(t,i) return cfg.id_handlers[k][i] end } end; |
|||
for k, v in pairs( id_list ) do |
|||
-- fallback to read-only cfg |
|||
handler = setmetatable( { ['id'] = v }, fallback(k) ); |
|||
if handler.mode == 'external' then |
|||
table.insert( new_list, {handler.label, external_link_id( handler ) } ); |
|||
elseif handler.mode == 'internal' then |
|||
table.insert( new_list, {handler.label, internal_link_id( handler ) } ); |
|||
elseif handler.mode ~= 'manual' then |
|||
error( cfg.messages['unknown_ID_mode'] ); |
|||
elseif k == 'DOI' then |
|||
table.insert( new_list, {handler.label, doi( v, options.DoiBroken ) } ); |
|||
elseif k == 'ARXIV' then |
|||
table.insert( new_list, {handler.label, arxiv( v, options.Class ) } ); |
|||
elseif k == 'ASIN' then |
|||
table.insert( new_list, {handler.label, amazon( v, options.ASINTLD ) } ); |
|||
elseif k == 'LCCN' then |
|||
table.insert( new_list, {handler.label, lccn( v ) } ); |
|||
elseif k == 'OL' then |
|||
table.insert( new_list, {handler.label, openlibrary( v ) } ); |
|||
elseif k == 'PMC' then |
|||
table.insert( new_list, {handler.label, pmc( v, options.Embargo ) } ); |
|||
elseif k == 'PMID' then |
|||
table.insert( new_list, {handler.label, pmid( v ) } ); |
|||
elseif k == 'ISSN' then |
|||
table.insert( new_list, {handler.label, issn( v ) } ); |
|||
elseif k == 'ISBN' then |
|||
local ISBN = internal_link_id( handler ); |
|||
if not check_isbn( v ) and not is_set(options.IgnoreISBN) then |
|||
ISBN = ISBN .. set_error( 'bad_isbn', {}, false, " ", "" ); |
|||
end |
|||
table.insert( new_list, {handler.label, ISBN } ); |
|||
elseif k == 'USENETID' then |
|||
table.insert( new_list, {handler.label, message_id( v ) } ); |
|||
else |
|||
error( cfg.messages['unknown_manual_ID'] ); |
|||
end |
|||
end |
|||
function comp( a, b ) -- used in following table.sort() |
|||
return a[1] < b[1]; |
|||
end |
|||
table.sort( new_list, comp ); |
|||
for k, v in ipairs( new_list ) do |
|||
new_list[k] = v[2]; |
|||
end |
|||
return new_list; |
|||
end |
|||
-- COinS metadata (see <http://ocoins.info/>) allows automated tools to parse |
|||
-- the citation information. |
|||
local function COinS(data, class) |
|||
if 'table' ~= type(data) or nil == next(data) then |
|||
return ''; |
|||
end |
|||
local ctx_ver = "Z39.88-2004"; |
|||
-- treat table strictly as an array with only set values. |
|||
local OCinSoutput = setmetatable( {}, { |
|||
__newindex = function(self, key, value) |
|||
if is_set(value) then |
|||
rawset( self, #self+1, table.concat{ key, '=', mw.uri.encode( remove_wiki_link( value ) ) } ); |
|||
end |
|||
end |
|||
}); |
|||
if is_set(data.Chapter) then |
|||
OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:book"; |
|||
OCinSoutput["rft.genre"] = "bookitem"; |
|||
OCinSoutput["rft.atitle"] = data.Chapter; |
|||
OCinSoutput["rft.btitle"] = data.Title; |
|||
elseif is_set(data.Periodical) then |
|||
OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:journal"; |
|||
if 'arxiv' == class then |
|||
OCinSoutput["rft.genre"] = "preprint"; -- cite arxiv |
|||
else |
|||
OCinSoutput["rft.genre"] = "article"; |
|||
end |
|||
OCinSoutput["rft.jtitle"] = data.Periodical; |
|||
OCinSoutput["rft.atitle"] = data.Title; |
|||
else |
|||
OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:book"; |
|||
OCinSoutput["rft.genre"] = "book" |
|||
OCinSoutput["rft.btitle"] = data.Title; |
|||
end |
|||
OCinSoutput["rft.place"] = data.PublicationPlace; |
|||
OCinSoutput["rft.date"] = data.Date; |
|||
OCinSoutput["rft.series"] = data.Series; |
|||
OCinSoutput["rft.volume"] = data.Volume; |
|||
OCinSoutput["rft.issue"] = data.Issue; |
|||
OCinSoutput["rft.pages"] = data.Pages; |
|||
OCinSoutput["rft.edition"] = data.Edition; |
|||
OCinSoutput["rft.pub"] = data.PublisherName; |
|||
for k, v in pairs( data.ID_list ) do |
|||
local id, value = cfg.id_handlers[k].COinS; |
|||
if k == 'ISBN' then value = clean_isbn( v ); else value = v; end |
|||
if string.sub( id or "", 1, 4 ) == 'info' then |
|||
OCinSoutput["rft_id"] = table.concat{ id, "/", v }; |
|||
else |
|||
OCinSoutput[ id ] = value; |
|||
end |
|||
end |
|||
local last, first; |
|||
for k, v in ipairs( data.Authors ) do |
|||
last, first = v.last, v.first; |
|||
if k == 1 then |
|||
if is_set(last) then |
|||
OCinSoutput["rft.aulast"] = last; |
|||
end |
|||
if is_set(first) then |
|||
OCinSoutput["rft.aufirst"] = first; |
|||
end |
|||
end |
|||
if is_set(last) and is_set(first) then |
|||
OCinSoutput["rft.au"] = table.concat{ last, ", ", first }; |
|||
elseif is_set(last) then |
|||
OCinSoutput["rft.au"] = last; |
|||
end |
|||
end |
|||
OCinSoutput.rft_id = data.URL; |
|||
OCinSoutput.rfr_id = table.concat{ "info:sid/", mw.site.server:match( "[^/]*$" ), ":", data.RawPage }; |
|||
OCinSoutput = setmetatable( OCinSoutput, nil ); |
|||
-- sort with version string always first, and combine. |
|||
table.sort( OCinSoutput ); |
|||
table.insert( OCinSoutput, 1, "ctx_ver=" .. ctx_ver ); -- such as "Z39.88-2004" |
|||
return table.concat(OCinSoutput, "&"); |
|||
end |
|||
--[[--------------------------< G E T _ I S O 6 3 9 _ C O D E >------------------------------------------------ |
--[[--------------------------< G E T _ I S O 6 3 9 _ C O D E >------------------------------------------------ |
||
Validates language names provided in |language= parameter if not an ISO639-1 code. |
Validates language names provided in |language= parameter if not an ISO639-1 code. Handles the special case that is Norwegian where |
||
ISO639-1 code 'no' is mapped to language name 'Norwegian Bokmål' by Extention:CLDR. |
|||
Returns the language name and associated ISO639-1 code. Because case of the source may be incorrect or different from the case that Wikimedia |
Returns the language name and associated ISO639-1 code. Because case of the source may be incorrect or different from the case that Wikimedia |
||
Line 1,099: | Line 1,667: | ||
]] |
]] |
||
local function get_iso639_code (lang |
local function get_iso639_code (lang) |
||
if 'norwegian' == lang:lower() then -- special case related to Wikimedia remap of code 'no' at Extension:CLDR |
|||
local languages = mw.language.fetchLanguageNames(this_wiki_code, 'all') -- get a list of language names known to Wikimedia |
|||
return 'Norwegian', 'no'; -- Make sure rendered version is properly capitalized |
|||
end |
|||
local languages = mw.language.fetchLanguageNames('en', 'all') -- get a list of language names known to Wikimedia |
|||
-- ('all' is required for North Ndebele, South Ndebele, and Ojibwa) |
-- ('all' is required for North Ndebele, South Ndebele, and Ojibwa) |
||
local langlc = mw.ustring.lower(lang); -- lower case version for comparisons |
local langlc = mw.ustring.lower(lang); -- lower case version for comparisons |
||
Line 1,118: | Line 1,690: | ||
Get language name from ISO639-1 code value provided. If a code is valid use the returned name; if not, then use the value that was provided with the language parameter. |
Get language name from ISO639-1 code value provided. If a code is valid use the returned name; if not, then use the value that was provided with the language parameter. |
||
There is an exception. There are three ISO639-1 codes for Norewegian language variants. There are two official variants: Norwegian Bokmål (code 'nb') and |
|||
Norwegian Nynorsk (code 'nn'). The third, code 'no', is defined by ISO639-1 as 'Norwegian' though in Norway this is pretty much meaningless. However, it appears |
|||
that on enwiki, editors are for the most part unaware of the nb and nn variants (compare page counts for these variants at Category:Articles with non-English-language external links. |
|||
Because Norwegian Bokmål is the most common language variant, Media wiki has been modified to return Norwegian Bokmål for ISO639-1 code 'no'. Here we undo that and |
|||
return 'Norwegian' when editors use |language=no. We presume that editors don't know about the variants or can't descriminate between them. |
|||
See Help talk:Citation Style_1#An ISO 639-1 language name test |
|||
When |language= contains a valid ISO639-1 code, the page is assigned to the category for that code: Category:Norwegian-language sources (no) if |
When |language= contains a valid ISO639-1 code, the page is assigned to the category for that code: Category:Norwegian-language sources (no) if |
||
the page is a mainspace page and the ISO639-1 code is not |
the page is a mainspace page and the ISO639-1 code is not 'en'. Similarly, if the parameter is |language=Norwegian, it will be categorized in the same way. |
||
it will be categorized in the same way. |
|||
This function supports multiple languages in the form |language=nb, French, th where the language names or codes are separated from each other by commas. |
This function supports multiple languages in the form |language=nb, French, th where the language names or codes are separated from each other by commas. |
||
Line 1,127: | Line 1,707: | ||
]] |
]] |
||
local function language_parameter (lang) |
local function language_parameter (lang, namespace) |
||
local code; -- the ISO639-1 two character code |
local code; -- the ISO639-1 two character code |
||
local name; -- the language name |
local name; -- the language name |
||
local language_list = {}; -- table of language names to be rendered |
local language_list = {}; -- table of language names to be rendered |
||
local names_table = {}; -- table made from the value assigned to |language= |
local names_table = {}; -- table made from the value assigned to |language= |
||
local unrec_cat = false -- flag so that we only add unrecognized category once |
|||
local this_wiki = mw.getContentLanguage(); -- get a language object for this wiki |
|||
local this_wiki_code = this_wiki:getCode() -- get this wiki's language code |
|||
local this_wiki_name = mw.language.fetchLanguageName(this_wiki_code, this_wiki_code); -- get this wiki's language name |
|||
names_table = mw.text.split (lang, '%s*,%s*'); -- names should be a comma separated list |
names_table = mw.text.split (lang, '%s*,%s*'); -- names should be a comma separated list |
||
for _, lang in ipairs (names_table) do -- reuse lang |
for _, lang in ipairs (names_table) do -- reuse lang |
||
if 0 == namespace and (('en' == lang:lower()) or ('english' == lang:lower())) then |
|||
add_maint_cat ('english'); |
|||
if lang:match ('^%a%a%-') then -- strip ietf language tags from code |
|||
lang = lang:match ('(%a%a)%-') -- keep only 639-1 code portion to lang; TODO: do something with 3166 alpha 2 country code? |
|||
end |
end |
||
if 2 == lang:len() then -- ISO639-1 language code are 2 characters (fetchLanguageName also supports 3 character codes) |
if 2 == lang:len() then -- ISO639-1 language code are 2 characters (fetchLanguageName also supports 3 character codes) |
||
name = mw.language.fetchLanguageName( lang:lower(), |
name = mw.language.fetchLanguageName( lang:lower(), "en" ); -- get ISO 639-1 language name if Language is a proper code |
||
end |
end |
||
Line 1,151: | Line 1,728: | ||
code = lang:lower(); -- save it |
code = lang:lower(); -- save it |
||
else |
else |
||
name, code = get_iso639_code (lang |
name, code = get_iso639_code (lang); -- attempt to get code from name (assign name here so that we are sure of proper capitalization) |
||
end |
end |
||
if is_set (code) then |
if is_set (code) then |
||
if |
if 'no' == code then name = 'Norwegian' end; -- override wikimedia when code is 'no' |
||
if 0 == namespace and 'en' ~= code then -- is this page main / article space and English not the language? |
|||
add_prop_cat ('foreign_lang_source', {name, code}) -- categorize it |
|||
add_prop_cat ('foreign_lang_source', {name, code}) |
|||
end |
end |
||
elseif false == unrec_cat then |
|||
else |
|||
unrec_cat = true; -- only add this category once |
|||
add_maint_cat (unknown_lang); |
|||
end |
end |
||
Line 1,173: | Line 1,752: | ||
name = table.concat (language_list, ', ') -- and concatenate with '<comma><space>' separators |
name = table.concat (language_list, ', ') -- and concatenate with '<comma><space>' separators |
||
end |
end |
||
return (" " .. wrap_msg ('language', name)); -- wrap with '(in ...)' |
|||
if this_wiki_name == name then |
|||
return ''; -- if one language and that language is this wiki's return an empty string (no annotation) |
|||
end |
|||
return (" " .. wrap_msg ('language', name)); -- otherwise wrap with '(in ...)' |
|||
end |
end |
||
Line 1,240: | Line 1,816: | ||
sep, ps = set_cs1_style (ps); |
sep, ps = set_cs1_style (ps); |
||
else -- anything but cs1 or cs2 |
else -- anything but cs1 or cs2 |
||
if is_set (mode) then |
|||
table.insert( z.message_tail, { set_error( 'invalid_param_val', {'mode', mode}, true ) } ); -- add error message |
|||
end |
|||
sep, ps, ref = get_settings_from_cite_class (ps, ref, cite_class); -- get settings based on the template's CitationClass |
sep, ps, ref = get_settings_from_cite_class (ps, ref, cite_class); -- get settings based on the template's CitationClass |
||
end |
end |
||
Line 1,251: | Line 1,830: | ||
--[=[-------------------------< I S _ P D F >------------------------------------------------------------------ |
--[=[-------------------------< I S _ P D F >------------------------------------------------------------------ |
||
Determines if a url has the file extension |
Determines if a url has the file extension is one of the pdf file extensions used by [[MediaWiki:Common.css]] when |
||
applying the pdf icon to external links. |
applying the pdf icon to external links. |
||
Line 1,273: | Line 1,852: | ||
local function style_format (format, url, fmt_param, url_param) |
local function style_format (format, url, fmt_param, url_param) |
||
if is_set (format) then |
if is_set (format) then |
||
format = wrap_style ('format', format); |
format = wrap_style ('format', format:upper()); -- force upper case, add leading space, parenthases, resize |
||
if not is_set (url) then |
if not is_set (url) then |
||
format = format .. set_error( 'format_missing_url', {fmt_param, url_param} ); -- add an error message |
format = format .. set_error( 'format_missing_url', {fmt_param, url_param} ); -- add an error message |
||
Line 1,287: | Line 1,866: | ||
--[[--------------------------< G E T _ D I S P L A Y _ A U T H O R S _ E D I T O R S >------------------------ |
--[[--------------------------< G E T _ D I S P L A Y _ A U T H O R S _ E D I T O R S >------------------------ |
||
Returns a number that |
Returns a number that may or may not limit the length of the author or editor name lists. |
||
to indicate when et al. should be appended to the name list. |
|||
When the value assigned to |display- |
When the value assigned to |display-authors= 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 |
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.'). |
some variant of the text 'et al.'). |
||
When the value assigned to |display- |
When the value assigned to |display-authors= 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 |
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.' |
the names in the name list followed by 'et al.' |
||
In all other cases, returns nil and the previous state of the 'etal' flag. |
In all other cases, returns nil and the previous state of the 'etal' flag. |
||
inputs: |
|||
max: A['DisplayAuthors'] or A['DisplayEditors']; a number or some flavor of etal |
|||
count: #a or #e |
|||
list_name: 'authors' or 'editors' |
|||
etal: author_etal or editor_etal |
|||
]] |
]] |
||
Line 1,315: | Line 1,887: | ||
elseif max:match ('^%d+$') then -- if is a string of numbers |
elseif max:match ('^%d+$') then -- if is a string of numbers |
||
max = tonumber (max); -- make it a number |
max = tonumber (max); -- make it a number |
||
if max >= count then |
if max >= count and 'authors' == list_name then -- AUTHORS ONLY -- if |display-xxxxors= value greater than or equal to number of authors/editors |
||
add_maint_cat ('disp_auth_ed', list_name); |
add_maint_cat ('disp_auth_ed', list_name); |
||
end |
end |
||
else -- not a valid keyword or number |
else -- not a valid keyword or number |
||
table.insert( z.message_tail, { set_error( 'invalid_param_val', {'display-' .. list_name, max}, true ) } ); -- add error message |
table.insert( z.message_tail, { set_error( 'invalid_param_val', {'display-' .. list_name, max}, true ) } ); -- add error message |
||
max = nil; -- unset |
max = nil; -- unset |
||
end |
end |
||
elseif 'authors' == list_name then -- AUTHORS ONLY need to clear implicit et al category |
|||
max = count + 1; -- number of authors + 1 |
|||
end |
end |
||
return max, etal; |
return max, etal; |
||
end |
|||
--[[--------------------------< E X T R A _ T E X T _ I N _ P A G E _ C H E C K >------------------------------ |
|||
Adds page to Category:CS1 maint: extra text if |page= or |pages= has what appears to be some form of p. or pp. |
|||
abbreviation in the first characters of the parameter content. |
|||
check Page and Pages for extraneous p, p., pp, and pp. at start of parameter value: |
|||
good pattern: '^P[^%.P%l]' matches when |page(s)= begins PX or P# but not Px where x and X are letters and # is a dgiit |
|||
bad pattern: '^[Pp][Pp]' matches matches when |page(s)= begins pp or pP or Pp or PP |
|||
]] |
|||
local function extra_text_in_page_check (page) |
|||
-- local good_pattern = '^P[^%.P%l]'; |
|||
local good_pattern = '^P[^%.Pp]'; -- ok to begin with uppercase P: P7 (pg 7 of section P) but not p123 (page 123) TODO: add Gg for PG or Pg? |
|||
-- local bad_pattern = '^[Pp][Pp]'; |
|||
local bad_pattern = '^[Pp]?[Pp]%.?[ %d]'; |
|||
if not page:match (good_pattern) and (page:match (bad_pattern) or page:match ('^[Pp]ages?')) then |
|||
add_maint_cat ('extra_text'); |
|||
end |
|||
-- if Page:match ('^[Pp]?[Pp]%.?[ %d]') or Page:match ('^[Pp]ages?[ %d]') or |
|||
-- Pages:match ('^[Pp]?[Pp]%.?[ %d]') or Pages:match ('^[Pp]ages?[ %d]') then |
|||
-- add_maint_cat ('extra_text'); |
|||
-- end |
|||
end |
|||
--[[--------------------------< 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 paranethese ((corporate name)) to suppress the format tests. |
|||
This function sets the vancouver error when a reqired comma is missing and when there is a space between an author's initials. |
|||
TODO: check for names like Coon V JS (Coon JS 5th at PMID 25205766, John S. Coon V at doi:10.1093/humupd/dmu048)? |
|||
]] |
|||
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 etal = false; -- return value set to true when we find some form of et al. vauthors parameter |
|||
local last, first, link, mask; |
|||
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) |
|||
if vparam:find ('%[%[') or vparam:find ('%]%]') then -- no wikilinking vauthors names |
|||
add_vanc_error (); |
|||
end |
|||
v_name_table = mw.text.split(vparam, "%s*,%s*") -- names are separated by commas |
|||
for i, v_name in ipairs(v_name_table) do |
|||
if v_name:match ('^%(%(.+%)%)$') then -- corporate authors are wrapped in doubled parenthese to supress vanc formatting and error detection |
|||
first = ''; -- set to empty string for concatenation and because it may have been set for previous author/editor |
|||
last = v_name:match ('^%(%((.+)%)%)$') |
|||
corporate = true; |
|||
elseif string.find(v_name, "%s") then |
|||
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 author intials |
|||
last = table.concat(lastfirstTable, " ") -- returns a string that is the concatenation of all other names that are not initials |
|||
if mw.ustring.match (last, '%a+%s+%u+%s+%a+') or mw.ustring.match (v_name, ' %u %u$') then |
|||
add_vanc_error (); -- matches last II last; the case when a comma is missing or a space between two intiials |
|||
end |
|||
else |
|||
first = ''; -- set to empty string for concatenation and because it may have been set for previous author/editor |
|||
last = v_name; -- last name or single corporate name? Doesn't support multiword corporate names? do we need this? |
|||
end |
|||
if is_set (first) and not mw.ustring.match (first, "^%u?%u$") then -- first shall contain one or two upper-case letters, nothing else |
|||
add_vanc_error (); |
|||
end |
|||
-- this from extract_names () |
|||
link = select_one( args, cfg.aliases[list_name .. '-Link'], 'redundant_parameters', i ); |
|||
mask = select_one( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i ); |
|||
names[i] = {last = last, first = first, link = link, mask = mask, corporate=corporate}; -- add this assembled name to our names list |
|||
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 |editors=, |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 and |
|||
similarly, |editorn= (and aliases) highest and |editors= lowest |
|||
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 or editors as appropriate. |
|||
]] |
|||
local function select_author_editor_source (vxxxxors, xxxxors, args, list_name) |
|||
local lastfirst = false; |
|||
if select_one( args, cfg.aliases[list_name .. '-Last'], 'none', 1 ) or -- do this twice incase we have a first 1 without a last1 |
|||
select_one( args, cfg.aliases[list_name .. '-Last'], 'none', 2 ) then |
|||
lastfirst=true; |
|||
end |
|||
if (is_set (vxxxxors) and true == lastfirst) or -- these are the three error conditions |
|||
(is_set (vxxxxors) and is_set (xxxxors)) or |
|||
(true == lastfirst and 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 |
|||
table.insert( z.message_tail, { set_error( 'redundant_parameters', |
|||
{err_name .. '-name-list parameters'}, true ) } ); -- add error message |
|||
end |
|||
if true == lastfirst then return 1 end; -- return a number indicating which author name source to use |
|||
if is_set (vxxxxors) then return 2 end; |
|||
if 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, no, etc). When the parameter value has not been assigned a value (missing or empty |
|||
in the source template) the function refurns true. If the parameter value is one of the list of allowed values returns |
|||
true; else, emits an error message and returns false. |
|||
]] |
|||
local function is_valid_parameter_value (value, name, possible) |
|||
if not is_set (value) then |
|||
return true; -- an empty parameter is ok |
|||
elseif in_array(value:lower(), possible) then |
|||
return true; |
|||
else |
|||
table.insert( z.message_tail, { set_error( 'invalid_param_val', {name, value}, true ) } ); -- not an allowed value so add error message |
|||
return false |
|||
end |
|||
end |
|||
--[[--------------------------< 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,-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 terninate 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 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, cite_class, origin, sepc, lower) |
|||
if not is_set (volume) and not is_set (issue) then |
|||
return ''; |
|||
end |
|||
if 'magazine' == cite_class or (in_array (cite_class, {'citation', 'map'}) and 'magazine' == origin) then |
|||
if is_set (volume) and is_set (issue) then |
|||
return wrap_msg ('vol-no', {sepc, volume, issue}, lower); |
|||
elseif is_set (volume) then |
|||
return wrap_msg ('vol', {sepc, volume}, lower); |
|||
else |
|||
return wrap_msg ('issue', {sepc, issue}, lower); |
|||
end |
|||
end |
|||
local vol = ''; |
|||
if is_set (volume) then |
|||
if (4 < mw.ustring.len(volume)) then |
|||
vol = substitute (cfg.messages['j-vol'], {sepc, volume}); |
|||
else |
|||
vol = wrap_style ('vol-bold', hyphen_to_dash(volume)); |
|||
end |
|||
end |
|||
if is_set (issue) then |
|||
return vol .. substitute (cfg.messages['j-issue'], issue); |
|||
end |
|||
return vol; |
|||
end |
|||
--[[-------------------------< N O R M A L I Z E _ P A G E _ L I S T >----------------------------------------- |
|||
not currently used |
|||
normalizes a comma, ampersand, and/or space separated list to be '<value>, <value>, ..., <value>' |
|||
returns list unchanged if there are no commas else strips whitespace and then reformats the list |
|||
]] |
|||
--[[ |
|||
local function normalize_page_list (list) |
|||
if not list:find ('[,& ]') then return list end -- if list is not delimited with commas, ampersands, or spaces; done |
|||
list = mw.text.split (list, '[,&%s]+'); -- make a table of values |
|||
list = table.concat (list, ', '); -- and now make a normalized list |
|||
return list; |
|||
end |
|||
]] |
|||
--[[-------------------------< F O R M A T _ P A G E S _ S H E E T S >----------------------------------------- |
|||
adds static text to one of |page(s)= or |sheet(s)= values and returns it with all of the others set to empty strings. |
|||
The return order is: |
|||
page, pages, sheet, sheets |
|||
Singular has priority over plural when both are provided. |
|||
]] |
|||
local function format_pages_sheets (page, pages, sheet, sheets, cite_class, origin, sepc, nopp, lower) |
|||
if 'map' == cite_class then -- only cite map supports sheet(s) as in-source locators |
|||
if is_set (sheet) then |
|||
if 'journal' == origin then |
|||
return '', '', wrap_msg ('j-sheet', sheet, lower), ''; |
|||
else |
|||
return '', '', wrap_msg ('sheet', {sepc, sheet}, lower), ''; |
|||
end |
|||
elseif is_set (sheets) then |
|||
if 'journal' == origin then |
|||
return '', '', '', wrap_msg ('j-sheets', sheets, lower); |
|||
else |
|||
return '', '', '', wrap_msg ('sheets', {sepc, sheets}, lower); |
|||
end |
|||
end |
|||
end |
|||
local is_journal = 'journal' == cite_class or (in_array (cite_class, {'citation', 'map'}) and 'journal' == origin); |
|||
if is_set (page) then |
|||
if is_journal then |
|||
return substitute (cfg.messages['j-page(s)'], page), '', '', ''; |
|||
elseif not nopp then |
|||
return substitute (cfg.messages['p-prefix'], {sepc, page}), '', '', ''; |
|||
else |
|||
return substitute (cfg.messages['nopp'], {sepc, page}), '', '', ''; |
|||
end |
|||
elseif is_set(pages) then |
|||
if is_journal then |
|||
return substitute (cfg.messages['j-page(s)'], pages), '', '', ''; |
|||
elseif tonumber(pages) ~= nil and not nopp then -- if pages is only digits, assume a single page number |
|||
return '', substitute (cfg.messages['p-prefix'], {sepc, pages}), '', ''; |
|||
elseif not nopp then |
|||
return '', substitute (cfg.messages['pp-prefix'], {sepc, pages}), '', ''; |
|||
else |
|||
return '', substitute (cfg.messages['nopp'], {sepc, pages}), '', ''; |
|||
end |
|||
end |
|||
return '', '', '', ''; -- return empty strings |
|||
end |
end |
||
--[[--------------------------< C I T A T I O N 0 >------------------------------------------------------------ |
--[[--------------------------< C I T A T I O N 0 >------------------------------------------------------------ |
||
This is the main function doing the majority of the citation |
This is the main function doing the majority of the citation |
||
formatting. |
|||
]] |
]] |
||
Line 1,616: | Line 1,911: | ||
--[[ |
--[[ |
||
Load Input Parameters |
Load Input Parameters |
||
The argument_wrapper facilitates the mapping of multiple |
The argument_wrapper facilitates the mapping of multiple |
||
aliases to single internal variable. |
|||
]] |
]] |
||
local A = argument_wrapper( args ); |
local A = argument_wrapper( args ); |
||
local i |
local i |
||
local PPrefix = A['PPrefix'] |
|||
local PPPrefix = A['PPPrefix'] |
|||
if is_set( A['NoPP'] ) then PPPrefix = "" PPrefix = "" end |
|||
-- Pick out the relevant fields from the arguments. Different citation templates |
-- Pick out the relevant fields from the arguments. Different citation templates |
||
-- define different field names for the same underlying things. |
-- define different field names for the same underlying things. |
||
local Authors = A['Authors']; |
|||
local author_etal; |
local author_etal; |
||
local a, author_etal = extract_names( args, 'AuthorList' ); |
|||
local a = {}; -- authors list from |lastn= / |firstn= pairs or |vauthors= |
|||
local Authors; |
|||
local NameListFormat = A['NameListFormat']; |
|||
local Collaboration = A['Collaboration']; |
|||
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 |
|||
NameListFormat = 'vanc'; -- override whatever |name-list-format= might be |
|||
a, author_etal = parse_vauthors_veditors (args, args.vauthors, 'AuthorList'); -- fetch author list from |vauthors=, |author-linkn=, and |author-maskn= |
|||
elseif 3 == selected then |
|||
Authors = A['Authors']; -- use content of |authors= |
|||
end |
|||
if is_set (Collaboration) then |
|||
author_etal = true; -- so that |display-authors=etal not required |
|||
end |
|||
end |
|||
local Coauthors = A['Coauthors']; |
local Coauthors = A['Coauthors']; |
||
local Others = A['Others']; |
local Others = A['Others']; |
||
local Editors = A['Editors']; |
|||
local editor_etal; |
local editor_etal; |
||
local e, editor_etal = extract_names( args, 'EditorList' ); |
|||
local e = {}; -- editors list from |editor-lastn= / |editor-firstn= pairs or |veditors= |
|||
local Editors; |
|||
local NameListFormat = A['NameListFormat']; -- replaces |author-format= and |editor-format= |
|||
do -- to limit scope of selected |
|||
if is_set (NameListFormat) and ('vanc' ~= NameListFormat) then -- only accepted value for this parameter is 'vanc' |
|||
local selected = select_author_editor_source (A['Veditors'], A['Editors'], args, 'EditorList'); |
|||
table.insert( z.message_tail, { set_error( 'invalid_param_val', {'name-list-format', NameListFormat}, true ) } ); -- not vanc so add error message |
|||
if 1 == selected then |
|||
NameListFormat = ''; -- set to empty string |
|||
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 |
|||
NameListFormat = 'vanc'; -- override whatever |name-list-format= might be |
|||
e, editor_etal = parse_vauthors_veditors (args, args.veditors, 'EditorList'); -- fetch editor list from |veditors=, |editor-linkn=, and |editor-maskn= |
|||
elseif 3 == selected then |
|||
Editors = A['Editors']; -- use content of |editors= |
|||
end |
|||
end |
|||
local t = {}; -- translators list from |translator-lastn= / translator-firstn= pairs |
|||
local Translators; -- assembled translators name list |
|||
t = extract_names (args, 'TranslatorList'); -- fetch translator list from |translatorn= / |translator-lastn=, -firstn=, -linkn=, -maskn= |
|||
local c = {}; -- contributors list from |contributor-lastn= / contributor-firstn= pairs |
|||
local Contributors; -- assembled contributors name list |
|||
local Contribution = A['Contribution']; |
|||
if in_array(config.CitationClass, {"book","citation"}) and not is_set(A['Periodical']) then -- |contributor= and |contribution= only supported in book cites |
|||
c = extract_names (args, 'ContributorList'); -- fetch contributor list from |contributorn= / |contributor-lastn=, -firstn=, -linkn=, -maskn= |
|||
if 0 < #c then |
|||
if not is_set (Contribution) then -- |contributor= requires |contribution= |
|||
table.insert( z.message_tail, { set_error( 'contributor_missing_required_param', 'contribution')}); -- add missing contribution error message |
|||
c = {}; -- blank the contributors' table; it is used as a flag later |
|||
end |
|||
if 0 == #a then -- |contributor= requires |author= |
|||
table.insert( z.message_tail, { set_error( 'contributor_missing_required_param', 'author')}); -- add missing author error message |
|||
c = {}; -- blank the contributors' table; it is used as a flag later |
|||
end |
|||
end |
|||
else -- if not a book cite |
|||
if select_one (args, cfg.aliases['ContributorList-Last'], 'redundant_parameters', 1 ) then -- are there contributor name list parameters? |
|||
table.insert( z.message_tail, { set_error( 'contributor_ignored')}); -- add contributor ignored error message |
|||
end |
|||
Contribution = nil; -- unset |
|||
end |
|||
if not is_valid_parameter_value (NameListFormat, 'name-list-format', cfg.keywords['name-list-format']) then -- only accepted value for this parameter is 'vanc' |
|||
NameListFormat = ''; -- anything else, set to empty string |
|||
end |
end |
||
Line 1,707: | Line 1,952: | ||
local TitleNote = A['TitleNote']; |
local TitleNote = A['TitleNote']; |
||
local TitleLink = A['TitleLink']; |
local TitleLink = A['TitleLink']; |
||
link_title_ok (TitleLink, A:ORIGIN ('TitleLink'), Title, 'title'); -- check for wikimarkup in |title-link= or wikimarkup in |title= when |title-link= is set |
|||
local Chapter = A['Chapter']; |
local Chapter = A['Chapter']; |
||
local |
local ChapterLink = A['ChapterLink']; -- deprecated |
||
local ChapterLink -- = A['ChapterLink']; -- deprecated as a parameter but still used internally by cite episode |
|||
local TransChapter = A['TransChapter']; |
local TransChapter = A['TransChapter']; |
||
local TitleType = A['TitleType']; |
local TitleType = A['TitleType']; |
||
Line 1,726: | Line 1,968: | ||
local ConferenceURLorigin = A:ORIGIN('ConferenceURL'); -- get name of parameter that holds ConferenceURL |
local ConferenceURLorigin = A:ORIGIN('ConferenceURL'); -- get name of parameter that holds ConferenceURL |
||
local Periodical = A['Periodical']; |
local Periodical = A['Periodical']; |
||
local Periodical_origin = A:ORIGIN('Periodical'); -- get the name of the periodical parameter |
|||
local Series = A['Series']; |
local Series = A['Series']; |
||
local Volume = A['Volume']; |
|||
local |
local Issue = A['Issue']; |
||
local Issue; |
|||
local Page; |
|||
local Pages; |
|||
local At; |
|||
if in_array (config.CitationClass, cfg.templates_using_volume) and not ('conference' == config.CitationClass and not is_set (Periodical)) then |
|||
Volume = A['Volume']; |
|||
end |
|||
if in_array (config.CitationClass, cfg.templates_using_issue) and not (in_array (config.CitationClass, {'conference', 'map'}) and not is_set (Periodical))then |
|||
Issue = A['Issue']; |
|||
end |
|||
local Position = ''; |
local Position = ''; |
||
local Page = A['Page']; |
|||
if not in_array (config.CitationClass, cfg.templates_not_using_page) then |
|||
local Pages = hyphen_to_dash( A['Pages'] ); |
|||
Page = A['Page']; |
|||
local At = A['At']; |
|||
At = A['At']; |
|||
end |
|||
local Edition = A['Edition']; |
local Edition = A['Edition']; |
||
Line 1,755: | Line 1,983: | ||
local PublisherName = A['PublisherName']; |
local PublisherName = A['PublisherName']; |
||
local RegistrationRequired = A['RegistrationRequired']; |
local RegistrationRequired = A['RegistrationRequired']; |
||
if not is_valid_parameter_value (RegistrationRequired, 'registration', cfg.keywords ['yes_true_y']) then |
|||
RegistrationRequired=nil; |
|||
end |
|||
local SubscriptionRequired = A['SubscriptionRequired']; |
local SubscriptionRequired = A['SubscriptionRequired']; |
||
if not is_valid_parameter_value (SubscriptionRequired, 'subscription', cfg.keywords ['yes_true_y']) then |
|||
SubscriptionRequired=nil; |
|||
end |
|||
local Via = A['Via']; |
local Via = A['Via']; |
||
local AccessDate = A['AccessDate']; |
local AccessDate = A['AccessDate']; |
||
Line 1,768: | Line 1,989: | ||
local Agency = A['Agency']; |
local Agency = A['Agency']; |
||
local DeadURL = A['DeadURL'] |
local DeadURL = A['DeadURL'] |
||
if not is_valid_parameter_value (DeadURL, 'dead-url', cfg.keywords ['deadurl']) then -- set in config.defaults to 'yes' |
|||
DeadURL = ''; -- anything else, set to empty string |
|||
end |
|||
local Language = A['Language']; |
local Language = A['Language']; |
||
local Format = A['Format']; |
local Format = A['Format']; |
||
Line 1,779: | Line 1,996: | ||
local ASINTLD = A['ASINTLD']; |
local ASINTLD = A['ASINTLD']; |
||
local IgnoreISBN = A['IgnoreISBN']; |
local IgnoreISBN = A['IgnoreISBN']; |
||
if not is_valid_parameter_value (IgnoreISBN, 'ignore-isbn-error', cfg.keywords ['yes_true_y']) then |
|||
IgnoreISBN = nil; -- anything else, set to empty string |
|||
end |
|||
local Embargo = A['Embargo']; |
local Embargo = A['Embargo']; |
||
local Class = A['Class']; -- arxiv class identifier |
local Class = A['Class']; -- arxiv class identifier |
||
Line 1,798: | Line 2,012: | ||
local LastAuthorAmp = A['LastAuthorAmp']; |
local LastAuthorAmp = A['LastAuthorAmp']; |
||
if not is_valid_parameter_value (LastAuthorAmp, 'last-author-amp', cfg.keywords ['yes_true_y']) then |
|||
LastAuthorAmp = nil; -- set to empty string |
|||
end |
|||
local no_tracking_cats = A['NoTracking']; |
local no_tracking_cats = A['NoTracking']; |
||
if not is_valid_parameter_value (no_tracking_cats, 'no-tracking', cfg.keywords ['yes_true_y']) then |
|||
no_tracking_cats = nil; -- set to empty string |
|||
end |
|||
--these are used by cite interview |
--these are used by cite interview |
||
Line 1,812: | Line 2,020: | ||
--local variables that are not cs1 parameters |
--local variables that are not cs1 parameters |
||
local use_lowercase; |
local use_lowercase; -- controls capitalization of certain static text |
||
local this_page = mw.title.getCurrentTitle(); |
local this_page = mw.title.getCurrentTitle(); -- also used for COinS and for language |
||
local anchor_year; |
local anchor_year; -- used in the CITEREF identifier |
||
local COinS_date |
local COinS_date; -- used in the COinS metadata |
||
local DF = A['DF']; -- date format set in cs1|2 template |
|||
if not is_valid_parameter_value (DF, 'df', cfg.keywords['date-format']) then -- validate reformatting keyword |
|||
DF = ''; -- not valid, set to empty string |
|||
end |
|||
-- set default parameter values defined by |mode= parameter. If |mode= is empty or omitted, use CitationClass to set these values |
-- set default parameter values defined by |mode= parameter. If |mode= is empty or omitted, use CitationClass to set these values |
||
local Mode = A['Mode']; |
|||
if not is_valid_parameter_value (Mode, 'mode', cfg.keywords['mode']) then |
|||
Mode = ''; |
|||
end |
|||
local sepc; -- separator between citation elements for CS1 a period, for CS2, a comma |
local sepc; -- separator between citation elements for CS1 a period, for CS2, a comma |
||
local PostScript; |
local PostScript; |
||
local Ref; |
local Ref; |
||
sepc, PostScript, Ref = set_style (Mode:lower(), A['PostScript'], A['Ref'], config.CitationClass); |
sepc, PostScript, Ref = set_style (A['Mode']:lower(), A['PostScript'], A['Ref'], config.CitationClass); |
||
use_lowercase = ( sepc == ',' ); -- used to control capitalization for certain static text |
use_lowercase = ( sepc == ',' ); -- used to control capitalization for certain static text |
||
Line 1,846: | Line 2,045: | ||
end |
end |
||
-- check for extra |page=, |pages= or |at= parameters. |
-- check for extra |page=, |pages= or |at= parameters. |
||
select_one( args, {'page', 'p', 'pp', 'pages', 'at', 'sheet', 'sheets'}, 'redundant_parameters' ); -- this is a dummy call simply to get the error message and category |
|||
local NoPP = A['NoPP'] |
|||
if is_set (NoPP) and is_valid_parameter_value (NoPP, 'nopp', cfg.keywords ['yes_true_y']) then |
|||
NoPP = true; |
|||
else |
|||
NoPP = nil; -- unset, used as a flag later |
|||
end |
|||
if is_set(Page) then |
if is_set(Page) then |
||
if is_set(Pages) or is_set(At) then |
if is_set(Pages) or is_set(At) then |
||
Page = Page .. " " .. set_error('extra_pages'); -- add error message |
|||
Pages = ''; -- unset the others |
|||
Pages = ''; -- unset the others |
|||
At = ''; |
At = ''; |
||
end |
end |
||
extra_text_in_page_check (Page); -- add this page to maint cat if |page= value begins with what looks like p. or pp. |
|||
elseif is_set(Pages) then |
elseif is_set(Pages) then |
||
if is_set(At) then |
if is_set(At) then |
||
Pages = Pages .. " " .. set_error('extra_pages'); -- add error messages |
|||
At = ''; -- unset |
|||
At = ''; -- unset |
|||
end |
end |
||
extra_text_in_page_check (Pages); -- add this page to maint cat if |pages= value begins with what looks like p. or pp. |
|||
end |
end |
||
Line 1,887: | Line 2,077: | ||
All other combinations of |encyclopedia, |title, and |article are not modified |
All other combinations of |encyclopedia, |title, and |article are not modified |
||
TODO: script-title to script-chapter if and when we support script-chapter |
|||
]] |
]] |
||
Line 1,893: | Line 2,083: | ||
if ( config.CitationClass == "encyclopaedia" ) or ( config.CitationClass == "citation" and is_set (Encyclopedia)) then -- test code for citation |
if ( config.CitationClass == "encyclopaedia" ) or ( config.CitationClass == "citation" and is_set (Encyclopedia)) then -- test code for citation |
||
if is_set(Periodical) then |
if is_set(Periodical) then -- Periodical is set when |encyclopedia is set |
||
if is_set(Title |
if is_set(Title) then |
||
if not is_set(Chapter) then |
if not is_set(Chapter) then |
||
Chapter = Title; |
Chapter = Title; -- |encyclopedia and |title are set so map |title to |article and |encyclopedia to |title |
||
ScriptChapter = ScriptTitle; |
|||
TransChapter = TransTitle; |
TransChapter = TransTitle; |
||
ChapterURL = URL; |
ChapterURL = URL; |
||
if not is_set (ChapterURL) and is_set (TitleLink) then |
|||
Chapter= '[[' .. TitleLink .. '|' .. Chapter .. ']]'; |
|||
end |
|||
Title = Periodical; |
Title = Periodical; |
||
ChapterFormat = Format; |
ChapterFormat = Format; |
||
Periodical = ''; |
Periodical = ''; -- redundant so unset |
||
TransTitle = ''; |
TransTitle = ''; -- redundant so unset |
||
URL = ''; |
URL = ''; -- redundant so unset |
||
Format = ''; |
Format = ''; -- redundant so unset |
||
TitleLink = ''; |
|||
ScriptTitle = ''; |
|||
end |
end |
||
else |
else -- |title not set |
||
Title = Periodical; |
Title = Periodical; -- |encyclopedia set and |article set or not set so map |encyclopedia to |title |
||
Periodical = ''; |
Periodical = ''; -- redundant so unset |
||
end |
end |
||
end |
end |
||
Line 1,920: | Line 2,104: | ||
-- Special case for cite techreport. |
-- Special case for cite techreport. |
||
if (config.CitationClass == "techreport") then |
if (config.CitationClass == "techreport") then -- special case for cite techreport |
||
if is_set( |
if is_set(Issue) then -- cite techreport uses 'number', which other citations aliase to 'issue' |
||
if not is_set(ID) then |
if not is_set(ID) then -- can we use ID for the "number"? |
||
ID = |
ID = Issue; -- yes, use it |
||
Issue = ""; -- unset Issue so that "number" isn't duplicated in the rendered citation or COinS metadata |
|||
else -- ID has a value so emit error message |
|||
else -- can't use ID so emit error message |
|||
table.insert( z.message_tail, { set_error('redundant_parameters', {wrap_style ('parameter', 'id') .. ' and ' .. wrap_style ('parameter', 'number')}, true )}); |
|||
ID = ID .. " " .. set_error('redundant_parameters', '<code>|id=</code> and <code>|number=</code>'); |
|||
end |
end |
||
end |
end |
||
Line 1,965: | Line 2,150: | ||
if (config.CitationClass == "mailinglist") then |
if (config.CitationClass == "mailinglist") then |
||
Periodical = A ['MailingList']; |
Periodical = A ['MailingList']; |
||
elseif 'mailinglist' == A:ORIGIN('Periodical') then |
|||
Periodical = ''; -- unset because mailing list is only used for cite mailing list |
|||
end |
end |
||
Line 2,014: | Line 2,197: | ||
if 'episode' == config.CitationClass or 'serial' == config.CitationClass then |
if 'episode' == config.CitationClass or 'serial' == config.CitationClass then |
||
local AirDate = A['AirDate']; |
local AirDate = A['AirDate']; |
||
local Began = A['Began']; -- these two are deprecated because the module understands date ranges |
|||
local Ended = A['Ended']; |
|||
local SeriesLink = A['SeriesLink']; |
local SeriesLink = A['SeriesLink']; |
||
link_title_ok (SeriesLink, A:ORIGIN ('SeriesLink'), Series, 'series'); -- check for wikimarkup in |series-link= or wikimarkup in |series= when |series-link= is set |
|||
local Network = A['Network']; |
local Network = A['Network']; |
||
local Station = A['Station']; |
local Station = A['Station']; |
||
local s, n = {}, {}; |
local s, n = {}, {}; |
||
-- do common parameters first |
-- do common parameters first |
||
if is_set(Network) then table.insert(n, Network); end |
if is_set(Network) then table.insert(n, Network); end |
||
Line 2,026: | Line 2,209: | ||
ID = table.concat(n, sepc .. ' '); |
ID = table.concat(n, sepc .. ' '); |
||
if not is_set (Date |
if not is_set (Date) then -- promote airdate or Began/Ended to date |
||
if is_set (AirDate) then |
|||
Date = AirDate; |
|||
elseif is_set (Began) then -- deprecated |
|||
if Began:match('%s') or Ended:match('%s') then -- so we don't create errors: if either has spaces then |
|||
Date = Began .. ' – ' .. Ended; -- use spaced ndash as separator |
|||
else |
|||
Date = Began .. '–' .. Ended; -- elsewise no spaces |
|||
end |
|||
end |
|||
end |
end |
||
Line 2,045: | Line 2,236: | ||
Chapter = Title; -- promote title parameters to chapter |
Chapter = Title; -- promote title parameters to chapter |
||
ScriptChapter = ScriptTitle; |
|||
ChapterLink = TitleLink; -- alias episodelink |
ChapterLink = TitleLink; -- alias episodelink |
||
TransChapter = TransTitle; |
TransChapter = TransTitle; |
||
Line 2,061: | Line 2,251: | ||
end |
end |
||
URL = ''; -- unset |
URL = ''; -- unset |
||
TransTitle = ''; |
TransTitle = ''; -- unset |
||
ScriptTitle = ''; |
|||
else -- now oddities that are cite serial |
else -- now oddities that are cite serial |
||
Issue = ''; |
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? |
Chapter = A['Episode']; -- TODO: make |episode= available to cite episode someday? |
||
if is_set (Series) and is_set (SeriesLink) then |
if is_set (Series) and is_set (SeriesLink) then |
||
Line 2,079: | Line 2,268: | ||
if not is_set (ID_list['ARXIV']) then -- |arxiv= or |eprint= required for cite arxiv |
if not is_set (ID_list['ARXIV']) then -- |arxiv= or |eprint= required for cite arxiv |
||
table.insert( z.message_tail, { set_error( 'arxiv_missing', {}, true ) } ); -- add error message |
table.insert( z.message_tail, { set_error( 'arxiv_missing', {}, true ) } ); -- add error message |
||
elseif is_set (Series) then -- series is an alias of version |
|||
ID_list['ARXIV'] = ID_list['ARXIV'] .. Series; -- concatenate version onto the end of the arxiv identifier |
|||
Series = ''; -- unset |
|||
deprecated_parameter ('version'); -- deprecated parameter but only for cite arxiv |
|||
end |
end |
||
if first_set (AccessDate, At, Chapter, Format, Page, Pages, Periodical, PublisherName, URL, -- a crude list of parameters that are not supported by cite arxiv |
|||
if first_set ({AccessDate, At, Chapter, Format, Page, Pages, Periodical, PublisherName, URL, -- a crude list of parameters that are not supported by cite arxiv |
|||
ID_list['ASIN'], ID_list['BIBCODE'], ID_list['DOI'], ID_list['ISBN'], ID_list['ISSN'], |
ID_list['ASIN'], ID_list['BIBCODE'], ID_list['DOI'], ID_list['ISBN'], ID_list['ISSN'], |
||
ID_list['JFM'], ID_list['JSTOR'], ID_list['LCCN'], ID_list['MR'], ID_list['OCLC'], ID_list['OL'], |
ID_list['JFM'], ID_list['JSTOR'], ID_list['LCCN'], ID_list['MR'], ID_list['OCLC'], ID_list['OL'], |
||
ID_list['OSTI'], ID_list['PMC'], ID_list['PMID'], ID_list['RFC'], ID_list['SSRN'], ID_list['USENETID'], ID_list['ZBL'] |
ID_list['OSTI'], ID_list['PMC'], ID_list['PMID'], ID_list['RFC'], ID_list['SSRN'], ID_list['USENETID'], ID_list['ZBL']) then |
||
table.insert( z.message_tail, { set_error( 'arxiv_params_not_supported', {}, true ) } ); -- add error message |
table.insert( z.message_tail, { set_error( 'arxiv_params_not_supported', {}, true ) } ); -- add error message |
||
Line 2,102: | Line 2,286: | ||
-- handle type parameter for those CS1 citations that have default values |
-- handle type parameter for those CS1 citations that have default values |
||
if in_array(config.CitationClass, {"AV-media-notes", "DVD-notes", "mailinglist", "map", "podcast", "pressrelease", "report", "techreport", "thesis"}) then |
if in_array(config.CitationClass, {"AV-media-notes", "DVD-notes", "mailinglist", "map", "podcast", "pressrelease", "report", "techreport", "thesis"}) then |
||
TitleType = set_titletype (config.CitationClass, TitleType); |
TitleType = set_titletype (config.CitationClass, TitleType); |
||
Line 2,110: | Line 2,295: | ||
if is_set(TitleType) then -- if type parameter is specified |
if is_set(TitleType) then -- if type parameter is specified |
||
TitleType = " (" .. TitleType .. ")"; -- display it in parentheses |
|||
end |
end |
||
-- legacy: promote concatenation of |month=, and |year= to Date if Date not set; or, promote PublicationDate to Date if neither Date nor Year are set. |
-- legacy: promote concatenation of |month=, and |year= to Date if Date not set; or, promote PublicationDate to Date if neither Date nor Year are set. |
||
if not is_set (Date) then |
if not is_set (Date) then |
||
Date = Year; |
Date = Year; -- promote Year to Date |
||
Year = nil; |
Year = nil; -- make nil so Year as empty string isn't used for CITEREF |
||
if is_set(Date) then |
|||
if not is_set (Date) and is_set(PublicationDate) then -- use PublicationDate when |date= and |year= are not set |
|||
local Month = A['Month']; |
|||
Date = PublicationDate; -- promote PublicationDate to Date |
|||
if is_set(Month) then |
|||
PublicationDate = ''; -- unset, no longer needed |
|||
Date = Month .. " " .. Date; |
|||
end |
|||
elseif is_set(PublicationDate) then -- use PublicationDate when |date= and |year= are not set |
|||
Date = PublicationDate; -- promote PublicationDate to Date |
|||
PublicationDate = ''; -- unset, no longer needed |
|||
end |
end |
||
end |
end |
||
if PublicationDate == Date then PublicationDate = ''; end |
if PublicationDate == Date then PublicationDate = ''; end -- if PublicationDate is same as Date, don't display in rendered citation |
||
--[[ |
--[[ |
||
Line 2,134: | Line 2,324: | ||
local error_message = ''; |
local error_message = ''; |
||
-- AirDate has been promoted to Date so not necessary to check it |
-- AirDate has been promoted to Date so not necessary to check it |
||
anchor_year, error_message = dates({[' |
anchor_year, COinS_date, error_message = dates({['accessdate']=AccessDate, ['archivedate']=ArchiveDate, ['date']=Date, ['doi_brokendate']=DoiBroken, |
||
['embargo']=Embargo, [' |
['embargo']=Embargo, ['laydate']=LayDate, ['publicationdate']=PublicationDate, ['year']=Year}); |
||
if is_set (Year) and is_set (Date) then -- both |date= and |year= not normally needed; |
if is_set (Year) and is_set (Date) then -- both |date= and |year= not normally needed; |
||
Line 2,151: | Line 2,341: | ||
if is_set(error_message) then |
if is_set(error_message) then |
||
table.insert( z.message_tail, { set_error( 'bad_date', {error_message}, true ) } ); -- add this error message |
table.insert( z.message_tail, { set_error( 'bad_date', {error_message}, true ) } ); -- add this error message |
||
elseif is_set (DF) then |
|||
local date_parameters_list = {['access-date']=AccessDate, ['archive-date']=ArchiveDate, ['date']=Date, ['doi-broken-date']=DoiBroken, |
|||
['embargo']=Embargo, ['lay-date']=LayDate, ['publication-date']=PublicationDate}; |
|||
if reformat_dates (date_parameters_list, DF, false) then -- reformat to DF format, use long month names if appropriate |
|||
AccessDate = date_parameters_list['access-date']; -- overwrite date holding parameters with reformatted values |
|||
ArchiveDate = date_parameters_list['archive-date']; -- TODO: move all dates into this table at the beginning and ... |
|||
Date = date_parameters_list['date']; -- ... use them from this table? no in-and-out like we're doing here |
|||
DoiBroken = date_parameters_list['doi-broken-date']; |
|||
Embargo = date_parameters_list['embargo']; |
|||
LayDate = date_parameters_list['lay-date']; |
|||
PublicationDate = date_parameters_list['publication-date']; |
|||
end |
|||
end |
end |
||
end -- end of do |
end -- end of do |
||
Line 2,183: | Line 2,360: | ||
not is_set(TransTitle) and |
not is_set(TransTitle) and |
||
not is_set(ScriptTitle) then |
not is_set(ScriptTitle) then |
||
table.insert( z.message_tail, { set_error( 'citation_missing_title', {}, true ) } ); |
|||
if 'episode' == config.CitationClass then -- special case for cite episode; TODO: is there a better way to do this? |
|||
table.insert( z.message_tail, { set_error( 'citation_missing_title', {'series'}, true ) } ); |
|||
else |
|||
table.insert( z.message_tail, { set_error( 'citation_missing_title', {'title'}, true ) } ); |
|||
end |
|||
end |
end |
||
if 'none' == Title and |
if 'none' == Title and is_set(Periodical) and not (( config.CitationClass == "encyclopaedia" ) or ( config.CitationClass == "citation" and is_set (Encyclopedia))) then -- special case |
||
Title = ''; -- set title to empty string |
Title = ''; -- set title to empty string |
||
add_maint_cat ('untitled'); |
add_maint_cat ('untitled'); |
||
end |
end |
||
check_for_url ({ -- add error message when any of these parameters contains a URL |
|||
['title']=Title, |
|||
[A:ORIGIN('Chapter')]=Chapter, |
|||
[A:ORIGIN('Periodical')]=Periodical, |
|||
[A:ORIGIN('PublisherName')] = PublisherName |
|||
}); |
|||
-- COinS metadata (see <http://ocoins.info/>) for automated parsing of citation information. |
-- COinS metadata (see <http://ocoins.info/>) for automated parsing of citation information. |
||
Line 2,215: | Line 2,381: | ||
end |
end |
||
end |
end |
||
local coins_author = a; -- default for coins rft.au |
|||
if 0 < #c then -- but if contributor list |
|||
coins_author = c; -- use that instead |
|||
end |
|||
-- this is the function call to COinS() |
-- this is the function call to COinS() |
||
local OCinSoutput = COinS({ |
local OCinSoutput = COinS({ |
||
['Periodical'] = Periodical, |
['Periodical'] = Periodical, |
||
['Chapter'] = strip_apostrophe_markup (coins_chapter), -- Chapter stripped of bold / italic wikimarkup |
|||
['Encyclopedia'] = Encyclopedia, |
|||
['Chapter'] = make_coins_title (coins_chapter, ScriptChapter), -- Chapter and ScriptChapter stripped of bold / italic wikimarkup |
|||
['Degree'] = Degree; -- cite thesis only |
|||
['Title'] = make_coins_title (coins_title, ScriptTitle), -- Title and ScriptTitle stripped of bold / italic wikimarkup |
['Title'] = make_coins_title (coins_title, ScriptTitle), -- Title and ScriptTitle stripped of bold / italic wikimarkup |
||
['PublicationPlace'] = PublicationPlace, |
['PublicationPlace'] = PublicationPlace, |
||
['Date'] = COinS_date |
['Date'] = first_set(COinS_date, Date), -- COinS_date has correctly formatted date if Date is valid; any reason to keep Date here? Should we be including invalid dates in metadata? |
||
['Season'] = COinS_date.rftssn, |
|||
['Chron'] = COinS_date.rftchron or (not COinS_date.rftdate and Date) or '', -- chron but if not set and invalid date format use Date; keep this last bit? |
|||
['Series'] = Series, |
['Series'] = Series, |
||
['Volume'] = Volume, |
['Volume'] = Volume, |
||
['Issue'] = Issue, |
['Issue'] = Issue, |
||
['Pages'] = get_coins_pages (first_set |
['Pages'] = get_coins_pages (first_set(Sheet, Sheets, Page, Pages, At)), -- pages stripped of external links |
||
['Edition'] = Edition, |
['Edition'] = Edition, |
||
['PublisherName'] = PublisherName, |
['PublisherName'] = PublisherName, |
||
['URL'] = first_set |
['URL'] = first_set( URL, ChapterURL ), |
||
['Authors'] = |
['Authors'] = a, |
||
['ID_list'] = ID_list, |
['ID_list'] = ID_list, |
||
['RawPage'] = this_page.prefixedText, |
['RawPage'] = this_page.prefixedText, |
||
Line 2,251: | Line 2,409: | ||
if 'newsgroup' == config.CitationClass then |
if 'newsgroup' == config.CitationClass then |
||
if is_set (PublisherName) then |
if is_set (PublisherName) then |
||
PublisherName = |
PublisherName = '[[Newsgroup]]: ' .. external_link( 'news:' .. PublisherName, PublisherName ); |
||
end |
end |
||
end |
end |
||
Line 2,260: | Line 2,418: | ||
-- We also add leading spaces and surrounding markup and punctuation to the |
-- We also add leading spaces and surrounding markup and punctuation to the |
||
-- various parts of the citation, but only when they are non-nil. |
-- various parts of the citation, but only when they are non-nil. |
||
do -- do-block to limit scope of LastFirstAuthors |
|||
local LastFirstAuthors; |
|||
do |
|||
local |
local Maximum = A['DisplayAuthors']; |
||
-- local maximum; |
|||
Maximum , author_etal = get_display_authors_editors (Maximum, #a, 'authors', author_etal); |
|||
local control = { |
local control = { |
||
format = NameListFormat, -- empty string or 'vanc' |
format = NameListFormat, -- empty string or 'vanc' |
||
maximum = Maximum, |
|||
maximum = nil, -- as if display-authors or display-editors not set |
|||
lastauthoramp = LastAuthorAmp, |
lastauthoramp = LastAuthorAmp, |
||
page_name = this_page.text |
page_name = this_page.text -- get current page name so that we don't wikilink to it via authorlinkn |
||
}; |
}; |
||
-- If the coauthor field is also used, prevent ampersand and et al. formatting. |
|||
if is_set(Coauthors) then |
|||
control.lastauthoramp = nil; |
|||
control.maximum = #a + 1; |
|||
end |
|||
LastFirstAuthors = list_people(control, a, author_etal); |
|||
if is_set (Authors) then |
|||
do -- do editor name list first because coauthors can modify control table |
|||
if is_set (LastFirstAuthors) then -- if both author name styles |
|||
control.maximum , editor_etal = get_display_authors_editors (A['DisplayEditors'], #e, 'editors', editor_etal); |
|||
table.insert( z.message_tail, { set_error( 'redundant_parameters', {wrap_style ('parameter', 'authors') .. ' and ' .. wrap_style ('parameter', 'last')}, true ) } ); -- add error message |
|||
last_first_list, EditorCount = list_people(control, e, editor_etal, 'editor'); |
|||
Authors = LastFirstAuthors; -- TODO: is this correct or should we use |authors= instead? |
|||
if is_set (Editors) then |
|||
if editor_etal then |
|||
Editors = Editors .. ' ' .. cfg.messages['et al']; -- add et al. to editors parameter beause |display-editors=etal |
|||
EditorCount = 2; -- with et al., |editors= is multiple names; spoof to display (eds.) annotation |
|||
else |
|||
EditorCount = 2; -- we don't know but assume |editors= is multiple names; spoof to display (eds.) annotation |
|||
end |
|||
else |
|||
Editors = last_first_list; -- either an author name list or an empty string |
|||
end |
end |
||
else |
|||
Authors = LastFirstAuthors; -- either an author name list or an empty string |
|||
if 1 == EditorCount and (true == editor_etal or 1 < #e) then -- only one editor displayed but includes etal then |
|||
EditorCount = 2; -- spoof to display (eds.) annotation |
|||
end |
|||
end |
|||
do -- now do translators |
|||
control.maximum = #t; -- number of translators |
|||
Translators = list_people(control, t, false, 'translator'); -- et al not currently supported |
|||
end |
|||
do -- now do contributors |
|||
control.maximum = #c; -- number of contributors |
|||
Contributors = list_people(control, c, false, 'contributor'); -- et al not currently supported |
|||
end |
end |
||
end -- end of do |
|||
control.maximum , author_etal = get_display_authors_editors (A['DisplayAuthors'], #a, 'authors', author_etal); |
|||
if not is_set(Authors) and is_set(Coauthors) then -- coauthors aren't displayed if one of authors=, authorn=, or lastn= isn't specified |
|||
table.insert( z.message_tail, { set_error('coauthors_missing_author', {}, true) } ); -- emit error message |
|||
control.lastauthoramp = nil; |
|||
end |
|||
control.maximum = #a + 1; |
|||
end |
|||
last_first_list = list_people(control, a, author_etal, 'author'); |
|||
local EditorCount; -- used only for choosing {ed.) or (eds.) annotation at end of editor name-list |
|||
if is_set (Authors) then |
|||
if not is_set(Editors) then |
|||
Authors, author_etal = name_has_etal (Authors, author_etal, false); -- find and remove variations on et al. |
|||
local Maximum = A['DisplayEditors']; |
|||
if author_etal then |
|||
Authors = Authors .. ' ' .. cfg.messages['et al']; -- add et al. to authors parameter |
|||
Maximum , editor_etal = get_display_authors_editors (Maximum, #e, 'editors', editor_etal); |
|||
end |
|||
-- Preserve old-style implicit et al. |
|||
else |
|||
if not is_set(Maximum) and #e == 4 then |
|||
Authors = last_first_list; -- either an author name list or an empty string |
|||
Maximum = 3; |
|||
end |
|||
table.insert( z.message_tail, { set_error('implict_etal_editor', {}, true) } ); |
|||
end -- end of do |
|||
if is_set (Authors) and is_set (Collaboration) then |
|||
Authors = Authors .. ' (' .. Collaboration .. ')'; -- add collaboration after et al. |
|||
end |
end |
||
local control = { |
|||
if not is_set(Authors) and is_set(Coauthors) then -- coauthors aren't displayed if one of authors=, authorn=, or lastn= isn't specified |
|||
format = NameListFormat, -- empty string or 'vanc' |
|||
table.insert( z.message_tail, { set_error('coauthors_missing_author', {}, true) } ); -- emit error message |
|||
maximum = Maximum, |
|||
lastauthoramp = LastAuthorAmp, |
|||
page_name = this_page.text -- get current page name so that we don't wikilink to it via editorlinkn |
|||
}; |
|||
Editors, EditorCount = list_people(control, e, editor_etal); |
|||
if 1 == EditorCount and (true == editor_etal or 1 < #e) then -- only one editor displayed but includes etal then |
|||
EditorCount = 2; -- spoof to display (eds.) annotation |
|||
end |
end |
||
else |
|||
EditorCount = 1; |
|||
end |
end |
||
Line 2,330: | Line 2,482: | ||
-- an error message if the associated url is not set, or an empty string for concatenation |
-- an error message if the associated url is not set, or an empty string for concatenation |
||
ArchiveFormat = style_format (ArchiveFormat, ArchiveURL, 'archive-format', 'archive-url'); |
ArchiveFormat = style_format (ArchiveFormat, ArchiveURL, 'archive-format', 'archive-url'); |
||
ChapterFormat = style_format (ChapterFormat, ChapterURL, 'chapter-format', 'chapter-url'); |
|||
ConferenceFormat = style_format (ConferenceFormat, ConferenceURL, 'conference-format', 'conference-url'); |
ConferenceFormat = style_format (ConferenceFormat, ConferenceURL, 'conference-format', 'conference-url'); |
||
Format = style_format (Format, URL, 'format', 'url'); |
Format = style_format (Format, URL, 'format', 'url'); |
||
Line 2,335: | Line 2,488: | ||
TranscriptFormat = style_format (TranscriptFormat, TranscriptURL, 'transcript-format', 'transcripturl'); |
TranscriptFormat = style_format (TranscriptFormat, TranscriptURL, 'transcript-format', 'transcripturl'); |
||
if not is_set(URL) then --and |
|||
-- special case for chapter format so no error message or cat when chapter not supported |
|||
-- not is_set(ArchiveURL) then --and -- prevents format_missing_url error from registering |
|||
if not (in_array(config.CitationClass, {'web','news','journal', 'magazine', 'pressrelease','podcast', 'newsgroup', 'arxiv'}) or |
|||
-- not is_set(ConferenceURL) and -- TODO: keep this here? conference as part of cite web or cite podcast? |
|||
('citation' == config.CitationClass and is_set (Periodical) and not is_set (Encyclopedia))) then |
|||
-- not is_set(TranscriptURL) then -- TODO: remove? |transcript-url= and |transcript= has separate test |
|||
ChapterFormat = style_format (ChapterFormat, ChapterURL, 'chapter-format', 'chapter-url'); |
|||
end |
|||
-- Test if cite web or cite podcast |url= is missing or empty |
|||
if in_array(config.CitationClass, {"web","podcast", "mailinglist"}) then |
|||
if not is_set(URL) then |
|||
if in_array(config.CitationClass, {"web","podcast", "mailinglist"}) then -- test if cite web or cite podcast |url= is missing or empty |
|||
table.insert( z.message_tail, { set_error( 'cite_web_url', {}, true ) } ); |
table.insert( z.message_tail, { set_error( 'cite_web_url', {}, true ) } ); |
||
end |
end |
||
-- |
-- Test if accessdate is given without giving a URL |
||
if is_set(AccessDate) and not is_set(ChapterURL)then -- ChapterURL may be set when the others are not set; TODO: move this to a separate test? |
if is_set(AccessDate) and not is_set(ChapterURL)then -- ChapterURL may be set when the others are not set; TODO: move this to a separate test? |
||
table.insert( z.message_tail, { set_error( 'accessdate_missing_url', {}, true ) } ); |
table.insert( z.message_tail, { set_error( 'accessdate_missing_url', {}, true ) } ); |
||
Line 2,353: | Line 2,505: | ||
end |
end |
||
local OriginalURL |
local OriginalURL, OriginalFormat; -- TODO: swap chapter and title here so that archive applies to most specific if both are set? |
||
DeadURL = DeadURL:lower(); -- used later when assembling archived text |
DeadURL = DeadURL:lower(); -- used later when assembling archived text |
||
if is_set( ArchiveURL ) then |
if is_set( ArchiveURL ) then |
||
if is_set (URL) then |
if is_set (URL) then |
||
OriginalURL = URL; -- save copy of original source URL |
OriginalURL = URL; -- save copy of original source URL |
||
OriginalURLorigin = URLorigin; -- name of url parameter for error messages |
|||
OriginalFormat = Format; -- and original |format= |
OriginalFormat = Format; -- and original |format= |
||
if 'no' ~= DeadURL then -- if URL set then archive-url applies to it |
if 'no' ~= DeadURL then -- if URL set then archive-url applies to it |
||
Line 2,367: | Line 2,518: | ||
elseif is_set (ChapterURL) then -- URL not set so if chapter-url is set apply archive url to it |
elseif is_set (ChapterURL) then -- URL not set so if chapter-url is set apply archive url to it |
||
OriginalURL = ChapterURL; -- save copy of source chapter's url for archive text |
OriginalURL = ChapterURL; -- save copy of source chapter's url for archive text |
||
OriginalURLorigin = ChapterURLorigin; -- name of chapter-url parameter for error messages |
|||
OriginalFormat = ChapterFormat; -- and original |format= |
OriginalFormat = ChapterFormat; -- and original |format= |
||
if 'no' ~= DeadURL then |
if 'no' ~= DeadURL then |
||
ChapterURL = ArchiveURL -- swap-in the archive's url |
ChapterURL = ArchiveURL -- swap-in the archive's url |
||
URLorigin = A:ORIGIN('ArchiveURL') -- name of archive url parameter for error messages |
|||
ChapterFormat = ArchiveFormat or ''; -- swap in archive's format |
ChapterFormat = ArchiveFormat or ''; -- swap in archive's format |
||
end |
end |
||
Line 2,377: | Line 2,527: | ||
end |
end |
||
if in_array(config.CitationClass, { |
if in_array(config.CitationClass, {"web","news","journal","pressrelease","podcast", "newsgroup", 'arxiv'}) or |
||
('citation' == config.CitationClass and is_set (Periodical) and not is_set (Encyclopedia)) then |
('citation' == config.CitationClass and is_set (Periodical) and not is_set (Encyclopedia)) then |
||
if is_set (Chapter) or is_set (TransChapter) or is_set (ChapterURL)then -- chapter parameters not supported for these citation types |
|||
local chap_param; |
|||
table.insert( z.message_tail, { set_error( 'chapter_ignored', {}, true ) } ); -- add error message |
|||
if is_set (Chapter) then -- get a parameter name from one of these chapter related meta-parameters |
|||
Chapter = ''; -- set to empty string to be safe with concatenation |
|||
chap_param = A:ORIGIN ('Chapter') |
|||
elseif is_set (TransChapter) then |
|||
chap_param = A:ORIGIN ('TransChapter') |
|||
elseif is_set (ChapterURL) then |
|||
chap_param = A:ORIGIN ('ChapterURL') |
|||
elseif is_set (ScriptChapter) then |
|||
chap_param = A:ORIGIN ('ScriptChapter') |
|||
else is_set (ChapterFormat) |
|||
chap_param = A:ORIGIN ('ChapterFormat') |
|||
end |
|||
if is_set (chap_param) then -- if we found one |
|||
table.insert( z.message_tail, { set_error( 'chapter_ignored', {chap_param}, true ) } ); -- add error message |
|||
Chapter = ''; -- and set them to empty string to be safe with concatenation |
|||
TransChapter = ''; |
TransChapter = ''; |
||
ChapterURL = ''; |
ChapterURL = ''; |
||
ScriptChapter = ''; |
|||
ChapterFormat = ''; |
|||
end |
end |
||
else -- otherwise, format chapter / article title |
else -- otherwise, format chapter / article title |
||
Chapter = format_chapter_title (Chapter, TransChapter, ChapterURL, ChapterURLorigin); |
|||
local no_quotes = false; -- default assume that we will be quoting the chapter parameter value |
|||
if is_set (Contribution) and 0 < #c then -- if this is a contribution with contributor(s) |
|||
if in_array (Contribution:lower(), cfg.keywords.contribution) then -- and a generic contribution title |
|||
no_quotes = true; -- then render it unquoted |
|||
end |
|||
end |
|||
Chapter = format_chapter_title (ScriptChapter, Chapter, TransChapter, ChapterURL, ChapterURLorigin, no_quotes); -- Contribution is also in Chapter |
|||
if is_set (Chapter) then |
if is_set (Chapter) then |
||
Chapter = Chapter .. ChapterFormat ; |
|||
if 'map' == config.CitationClass and is_set (TitleType) then |
if 'map' == config.CitationClass and is_set (TitleType) then |
||
Chapter = Chapter .. ' ' .. TitleType; |
Chapter = Chapter .. ' ' .. TitleType; |
||
end |
end |
||
Chapter = Chapter.. sepc .. ' '; |
Chapter = Chapter .. ChapterFormat .. sepc .. ' '; |
||
elseif is_set (ChapterFormat) then -- |chapter= not set but |chapter-format= is so ... |
|||
Chapter = ChapterFormat .. sepc .. ' '; -- ... ChapterFormat has error message, we want to see it |
|||
end |
end |
||
end |
end |
||
Line 2,425: | Line 2,550: | ||
end |
end |
||
if in_array(config.CitationClass, { |
if in_array(config.CitationClass, {"web","news","journal","pressrelease","podcast", "newsgroup", "mailinglist", 'arxiv'}) or |
||
('citation' == config.CitationClass and is_set (Periodical) and not is_set (Encyclopedia)) or |
('citation' == config.CitationClass and is_set (Periodical) and not is_set (Encyclopedia)) or |
||
('map' == config.CitationClass and is_set (Periodical)) then -- special case for cite map when the map is in a periodical treat as an article |
('map' == config.CitationClass and is_set (Periodical)) then -- special case for cite map when the map is in a periodical treat as an article |
||
Line 2,442: | Line 2,567: | ||
end |
end |
||
TransError = ""; |
|||
if is_set(TransTitle) then |
if is_set(TransTitle) then |
||
if is_set(Title) then |
if is_set(Title) then |
||
TransTitle = " " .. TransTitle; |
TransTitle = " " .. TransTitle; |
||
else |
else |
||
TransError = " " .. set_error( 'trans_missing_title' |
TransError = " " .. set_error( 'trans_missing_title' ); |
||
end |
end |
||
end |
end |
||
Line 2,455: | Line 2,580: | ||
if is_set(Title) then |
if is_set(Title) then |
||
if not is_set(TitleLink) and is_set(URL) then |
if not is_set(TitleLink) and is_set(URL) then |
||
Title = external_link( URL, Title |
Title = external_link( URL, Title ) .. TransError .. Format; |
||
URL = ""; |
URL = ""; |
||
Format = ""; |
Format = ""; |
||
Line 2,469: | Line 2,594: | ||
if is_set (Conference) then |
if is_set (Conference) then |
||
if is_set (ConferenceURL) then |
if is_set (ConferenceURL) then |
||
Conference = external_link( ConferenceURL, Conference |
Conference = external_link( ConferenceURL, Conference ); |
||
end |
end |
||
Conference = sepc .. " " .. Conference .. ConferenceFormat; |
Conference = sepc .. " " .. Conference .. ConferenceFormat; |
||
Line 2,478: | Line 2,603: | ||
if not is_set(Position) then |
if not is_set(Position) then |
||
local Minutes = A['Minutes']; |
local Minutes = A['Minutes']; |
||
local Time = A['Time']; |
|||
if is_set(Minutes) then |
if is_set(Minutes) then |
||
if is_set (Time) then |
|||
table.insert( z.message_tail, { set_error( 'redundant_parameters', {wrap_style ('parameter', 'minutes') .. ' and ' .. wrap_style ('parameter', 'time')}, true ) } ); |
|||
end |
|||
Position = " " .. Minutes .. " " .. cfg.messages['minutes']; |
Position = " " .. Minutes .. " " .. cfg.messages['minutes']; |
||
else |
else |
||
local Time = A['Time']; |
|||
if is_set(Time) then |
if is_set(Time) then |
||
local TimeCaption = A['TimeCaption'] |
local TimeCaption = A['TimeCaption'] |
||
Line 2,500: | Line 2,621: | ||
Position = " " .. Position; |
Position = " " .. Position; |
||
At = ''; |
At = ''; |
||
end |
|||
if not is_set(Page) then |
|||
if is_set(Pages) then |
|||
if is_set(Periodical) and |
|||
not in_array(config.CitationClass, {"encyclopaedia","web","book","news","podcast"}) then |
|||
Pages = ": " .. Pages; |
|||
elseif tonumber(Pages) ~= nil then |
|||
Pages = sepc .." " .. PPrefix .. Pages; |
|||
else |
|||
Pages = sepc .." " .. PPPrefix .. Pages; |
|||
end |
|||
end |
|||
else |
|||
if is_set(Periodical) and |
|||
not in_array(config.CitationClass, {"encyclopaedia","web","book","news","podcast"}) then |
|||
Page = ": " .. Page; |
|||
else |
|||
Page = sepc .." " .. PPrefix .. Page; |
|||
end |
|||
end |
end |
||
if 'map' == config.CitationClass then -- cite map oddity done after COinS call (and with other in-source locators) |
|||
Page, Pages, Sheet, Sheets = format_pages_sheets (Page, Pages, Sheet, Sheets, config.CitationClass, Periodical_origin, sepc, NoPP, use_lowercase); |
|||
if is_set (Sheet) or is_set (Sheets) then |
|||
local err_msg1 = 'sheet=, |sheets'; -- default error message in case any of page pages or at are set |
|||
local err_msg2; |
|||
if is_set (Page) or is_set (Pages) or is_set (At) then -- are any set? |
|||
err_msg2 = 'page=, |pages=, |at'; -- a generic error message |
|||
Page = ''; Pages = ''; At = ''; |
|||
elseif is_set (Sheet) and is_set (Sheets) then -- if both are set make error message |
|||
err_msg1 = 'sheet'; |
|||
err_msg2 = 'sheets'; |
|||
end |
|||
if is_set (err_msg2) then |
|||
table.insert( z.message_tail, { set_error( 'redundant_parameters', {wrap_style ('parameter', err_msg1) .. ' and ' .. wrap_style ('parameter', err_msg2)}, true ) } ); -- add error message |
|||
end |
|||
if not is_set (Sheet) then -- do sheet static text and formatting; Sheet has priority over Sheets if both provided |
|||
if is_set (Sheets) then |
|||
if is_set (Periodical) then |
|||
Sheet = ": Sheets " .. Sheets; -- because Sheet has priority, no need to support both later on |
|||
else |
|||
Sheet = sepc .. " Sheets " .. Sheets; |
|||
end |
|||
end |
|||
else |
|||
if is_set (Periodical) then |
|||
Sheet = ": Sheet " .. Sheet; |
|||
else |
|||
Sheet = sepc .. " Sheet " .. Sheet; |
|||
end |
|||
end |
|||
end |
|||
end |
|||
At = is_set(At) and (sepc .. " " .. At) or ""; |
At = is_set(At) and (sepc .. " " .. At) or ""; |
||
Line 2,524: | Line 2,695: | ||
if is_set (Language) then |
if is_set (Language) then |
||
Language = language_parameter (Language); |
Language = language_parameter (Language, this_page.namespace); -- format, categories (article namespace only), name from ISO639-1, etc |
||
else |
else |
||
Language=""; -- language not specified so make sure this is an empty string; |
Language=""; -- language not specified so make sure this is an empty string; |
||
Line 2,530: | Line 2,701: | ||
Others = is_set(Others) and (sepc .. " " .. Others) or ""; |
Others = is_set(Others) and (sepc .. " " .. Others) or ""; |
||
if is_set (Translators) then |
|||
Others = sepc .. ' Translated by ' .. Translators .. Others; |
|||
end |
|||
TitleNote = is_set(TitleNote) and (sepc .. " " .. TitleNote) or ""; |
TitleNote = is_set(TitleNote) and (sepc .. " " .. TitleNote) or ""; |
||
Edition = is_set(Edition) and (" " .. wrap_msg ('edition', Edition)) or ""; |
|||
Issue = is_set(Issue) and (" (" .. Issue .. ")") or ""; |
|||
if Edition:match ('%f[%a][Ee]d%.?$') or Edition:match ('%f[%a][Ee]dition$') then |
|||
add_maint_cat ('extra_text', 'edition'); |
|||
end |
|||
Edition = " " .. wrap_msg ('edition', Edition); |
|||
else |
|||
Edition = ''; |
|||
end |
|||
Series = is_set(Series) and (sepc .. " " .. Series) or ""; |
Series = is_set(Series) and (sepc .. " " .. Series) or ""; |
||
OrigYear = is_set(OrigYear) and (" [" .. OrigYear .. "]") or ""; |
OrigYear = is_set(OrigYear) and (" [" .. OrigYear .. "]") or ""; |
||
Agency = is_set(Agency) and (sepc .. " " .. Agency) or ""; |
Agency = is_set(Agency) and (sepc .. " " .. Agency) or ""; |
||
if is_set(Volume) then |
|||
Volume = format_volume_issue (Volume, Issue, config.CitationClass, Periodical_origin, sepc, use_lowercase); |
|||
if ( mw.ustring.len(Volume) > 4 ) |
|||
then Volume = sepc .." " .. Volume; |
|||
else Volume = " <b>" .. hyphen_to_dash(Volume) .. "</b>"; |
|||
end |
|||
end |
|||
------------------------------------ totally unrelated data |
------------------------------------ totally unrelated data |
||
Line 2,561: | Line 2,726: | ||
]] |
]] |
||
if |
if in_array(SubscriptionRequired:lower(), {'yes', 'true', 'y'}) then |
||
SubscriptionRequired = sepc .. " " .. cfg.messages['subscription']; -- subscription required message |
SubscriptionRequired = sepc .. " " .. cfg.messages['subscription']; -- subscription required message |
||
elseif |
elseif in_array(RegistrationRequired:lower(), {'yes', 'true', 'y'}) then |
||
SubscriptionRequired = sepc .. " " .. cfg.messages['registration']; -- registration required message |
SubscriptionRequired = sepc .. " " .. cfg.messages['registration']; -- registration required message |
||
else |
else |
||
Line 2,594: | Line 2,759: | ||
if is_set(Quote) then |
if is_set(Quote) then |
||
if Quote:sub(1,1) == '"' and Quote:sub(-1,-1) == '"' then |
if Quote:sub(1,1) == '"' and Quote:sub(-1,-1) == '"' then |
||
Quote = Quote:sub(2,-2); |
Quote = Quote:sub(2,-2); |
||
end |
end |
||
Quote = sepc .." " .. wrap_style ('quoted-text', Quote ); -- wrap in <q>...</q> tags |
Quote = sepc .." " .. wrap_style ('quoted-text', Quote ); -- wrap in <q>...</q> tags |
||
PostScript = ""; -- |
PostScript = ""; -- CS1 does not supply terminal punctuation when |quote= is set |
||
end |
end |
||
Line 2,610: | Line 2,775: | ||
if sepc ~= "." then arch_text = arch_text:lower() end |
if sepc ~= "." then arch_text = arch_text:lower() end |
||
Archived = sepc .. " " .. substitute( cfg.messages['archived-not-dead'], |
Archived = sepc .. " " .. substitute( cfg.messages['archived-not-dead'], |
||
{ external_link( ArchiveURL, arch_text |
{ external_link( ArchiveURL, arch_text ) .. ArchiveFormat, ArchiveDate } ); |
||
if not is_set(OriginalURL) then |
if not is_set(OriginalURL) then |
||
Archived = Archived .. " " .. set_error('archive_missing_url'); |
Archived = Archived .. " " .. set_error('archive_missing_url'); |
||
end |
end |
||
elseif is_set(OriginalURL) then |
elseif is_set(OriginalURL) then |
||
local arch_text = cfg.messages['archived-dead']; |
local arch_text = cfg.messages['archived-dead']; |
||
if sepc ~= "." then arch_text = arch_text:lower() end |
if sepc ~= "." then arch_text = arch_text:lower() end |
||
Archived = sepc .. " " .. substitute( arch_text, |
|||
if in_array (DeadURL, {'unfit', 'usurped'}) then |
|||
{ external_link( OriginalURL, cfg.messages['original'] ) .. OriginalFormat, ArchiveDate } ); -- format already styled |
|||
else -- DeadURL is empty, 'yes', 'true', or 'y' |
|||
Archived = sepc .. " " .. substitute( arch_text, |
|||
{ external_link( OriginalURL, cfg.messages['original'], OriginalURLorigin ) .. OriginalFormat, ArchiveDate } ); -- format already styled |
|||
end |
|||
else |
else |
||
local arch_text = cfg.messages['archived-missing']; |
local arch_text = cfg.messages['archived-missing']; |
||
Line 2,644: | Line 2,805: | ||
end |
end |
||
if sepc == '.' then |
if sepc == '.' then |
||
Lay = sepc .. " " .. external_link( LayURL, cfg.messages['lay summary'] |
Lay = sepc .. " " .. external_link( LayURL, cfg.messages['lay summary'] ) .. LayFormat .. LaySource .. LayDate |
||
else |
else |
||
Lay = sepc .. " " .. external_link( LayURL, cfg.messages['lay summary']:lower( |
Lay = sepc .. " " .. external_link( LayURL, cfg.messages['lay summary']:lower() ) .. LayFormat .. LaySource .. LayDate |
||
end |
end |
||
elseif is_set (LayFormat) then -- Test if |lay-format= is given without giving a |lay-url= |
elseif is_set (LayFormat) then -- Test if |lay-format= is given without giving a |lay-url= |
||
Line 2,654: | Line 2,815: | ||
if is_set(Transcript) then |
if is_set(Transcript) then |
||
if is_set(TranscriptURL) then |
if is_set(TranscriptURL) then |
||
Transcript = external_link( TranscriptURL, Transcript |
Transcript = external_link( TranscriptURL, Transcript ); |
||
end |
end |
||
Transcript = sepc .. ' ' .. Transcript .. TranscriptFormat; |
Transcript = sepc .. ' ' .. Transcript .. TranscriptFormat; |
||
Line 2,728: | Line 2,889: | ||
-- not to keep reassigning to the same string variable over and over. |
-- not to keep reassigning to the same string variable over and over. |
||
local tcommon |
local tcommon |
||
local tcommon2; -- used for book cite when |contributor= is set |
|||
if in_array(config.CitationClass, {"journal","citation"}) and is_set(Periodical) then |
if in_array(config.CitationClass, {"journal","citation"}) and is_set(Periodical) then |
||
if is_set(Others) then Others = Others .. sepc .. " " end |
if is_set(Others) then Others = Others .. sepc .. " " end |
||
tcommon = safe_join( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series, |
tcommon = safe_join( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series, |
||
Language, Edition, Publisher, Agency, Volume}, sepc ); |
Language, Edition, Publisher, Agency, Volume, Issue}, sepc ); |
||
elseif in_array(config.CitationClass, {"book","citation"}) and not is_set(Periodical) then -- special cases for book cites |
|||
if is_set (Contributors) then -- when we are citing foreword, preface, introduction, etc |
|||
tcommon = safe_join( {Title, TitleNote}, sepc ); -- author and other stuff will come after this and before tcommon2 |
|||
tcommon2 = safe_join( {Conference, Periodical, Format, TitleType, Series, Language, Volume, Others, Edition, Publisher, Agency}, sepc ); |
|||
else |
|||
tcommon = safe_join( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, Volume, Others, Edition, Publisher, Agency}, sepc ); |
|||
end |
|||
elseif 'map' == config.CitationClass then -- special cases for cite map |
elseif 'map' == config.CitationClass then -- special cases for cite map |
||
if is_set (Chapter) then -- map in a book; TitleType is part of Chapter |
if 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 ); |
tcommon = safe_join( {Title, Format, Edition, Scale, Series, Language, Cartography, Others, Publisher, Volume}, sepc ); |
||
elseif is_set (Periodical) then -- map in a periodical |
elseif is_set (Periodical) then -- map in a periodical |
||
tcommon = safe_join( {Title, TitleType, Format, Periodical, Scale, Series, Language, Cartography, Others, Publisher, Volume}, sepc ); |
tcommon = safe_join( {Title, TitleType, Format, Periodical, Scale, Series, Language, Cartography, Others, Publisher, Volume, Issue}, sepc ); |
||
else -- a sheet or stand-alone map |
else -- a sheet or stand-alone map |
||
tcommon = safe_join( {Title, TitleType, Format, Edition, Scale, Series, Language, Cartography, Others, Publisher}, sepc ); |
tcommon = safe_join( {Title, TitleType, Format, Edition, Scale, Series, Language, Cartography, Others, Publisher}, sepc ); |
||
Line 2,757: | Line 2,908: | ||
else -- all other CS1 templates |
else -- all other CS1 templates |
||
tcommon = safe_join( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, |
tcommon = safe_join( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, |
||
Volume, Others, Edition, Publisher, Agency}, sepc ); |
Volume, Issue, Others, Edition, Publisher, Agency}, sepc ); |
||
end |
end |
||
Line 2,768: | Line 2,919: | ||
local idcommon = safe_join( { ID_list, URL, Archived, AccessDate, Via, SubscriptionRequired, Lay, Quote }, sepc ); |
local idcommon = safe_join( { ID_list, URL, Archived, AccessDate, Via, SubscriptionRequired, Lay, Quote }, sepc ); |
||
local text; |
local text; |
||
local pgtext = Position .. Sheet |
local pgtext = Position .. Sheet .. Page .. Pages .. At; |
||
if is_set(Date) then |
|||
if is_set (Authors) or is_set (Editors) then -- date follows authors or editors when authors not set |
|||
Date = " (" .. Date ..")" .. OrigYear .. sepc .. " "; -- in paranetheses |
|||
else -- neither of authors and editors set |
|||
if (string.sub(tcommon,-1,-1) == sepc) then -- if the last character of tcommon is sepc |
|||
Date = " " .. Date .. OrigYear; -- Date does not begin with sepc |
|||
else |
|||
Date = sepc .. " " .. Date .. OrigYear; -- Date begins with sepc |
|||
end |
|||
end |
|||
end |
|||
if is_set(Authors) then |
if is_set(Authors) then |
||
if is_set(Coauthors) then |
if is_set(Coauthors) then |
||
local sep = '; '; |
|||
if 'vanc' == NameListFormat then -- separate authors and coauthors with proper name-list-separator |
|||
if 'vanc' == NameListFormat then |
|||
Authors = Authors .. ', ' .. Coauthors; |
|||
sep = ', '; |
|||
else |
|||
Authors = Authors .. '; ' .. Coauthors; |
|||
end |
end |
||
Authors = Authors .. sep .. Coauthors; |
|||
end |
end |
||
if |
if is_set(Date) then |
||
Date = " ("..Date..")" .. OrigYear .. sepc .. " " |
|||
Authors = terminate_name_list (Authors, sepc); -- when no date, terminate with 0 or 1 sepc and a space |
|||
elseif string.sub(Authors,-1,-1) == sepc then |
|||
Authors = Authors .. " " |
|||
else |
|||
Authors = Authors .. sepc .. " " |
|||
end |
end |
||
if is_set(Editors) then |
if is_set(Editors) then |
||
local in_text = " "; |
local in_text = " "; |
||
local post_text = ""; |
local post_text = ""; |
||
if is_set(Chapter) |
if is_set(Chapter) then |
||
in_text = in_text .. cfg.messages['in'] .. " " |
in_text = in_text .. cfg.messages['in'] .. " " |
||
if (sepc ~= '.') then in_text = in_text:lower() end -- lowercase for cs2 |
|||
else |
else |
||
if EditorCount <= 1 then |
if EditorCount <= 1 then |
||
Line 2,805: | Line 2,948: | ||
end |
end |
||
end |
end |
||
if (sepc ~= '.') then in_text = in_text:lower() end |
|||
Editors = terminate_name_list (in_text .. Editors .. post_text, sepc); -- terminate with 0 or 1 sepc and a space |
|||
Editors = in_text .. Editors .. post_text; |
|||
end |
|||
if (string.sub(Editors,-1,-1) == sepc) or (string.sub(Editors,-3,-1) == sepc .. ']]') then -- if last editor name ends with sepc char |
|||
if is_set (Contributors) then -- book cite and we're citing the intro, preface, etc |
|||
Editors = Editors .. " "; -- don't add another |
|||
local by_text = sepc .. ' ' .. cfg.messages['by'] .. ' '; |
|||
else |
|||
if (sepc ~= '.') then by_text = by_text:lower() end -- lowercase for cs2 |
|||
Editors = Editors .. sepc .. " " -- otherwise terninate the editor list |
|||
if is_set (Editors) then -- when Editors make sure that Authors gets terminated |
|||
Authors = terminate_name_list (Authors, sepc); -- terminate with 0 or 1 sepc and a space |
|||
end |
end |
||
if not is_set (Date) then -- when date is set it's in parentheses; no Contributors termination |
|||
Contributors = terminate_name_list (Contributors, sepc); -- terminate with 0 or 1 sepc and a space |
|||
end |
|||
text = safe_join( {Contributors, Date, Chapter, tcommon, Authors, Place, Editors, tcommon2, pgtext, idcommon }, sepc ); |
|||
else |
|||
text = safe_join( {Authors, Date, Chapter, Place, Editors, tcommon, pgtext, idcommon }, sepc ); |
|||
end |
end |
||
text = safe_join( {Authors, Date, Chapter, Place, Editors, tcommon }, sepc ); |
|||
text = safe_join( {text, pgtext, idcommon}, sepc ); |
|||
elseif is_set(Editors) then |
elseif is_set(Editors) then |
||
if is_set(Date) then |
if is_set(Date) then |
||
Line 2,828: | Line 2,965: | ||
Editors = Editors .. ", " .. cfg.messages['editors']; |
Editors = Editors .. ", " .. cfg.messages['editors']; |
||
end |
end |
||
Date = " (" .. Date ..")" .. OrigYear .. sepc .. " " |
|||
else |
else |
||
if EditorCount <= 1 then |
if EditorCount <= 1 then |
||
Line 2,835: | Line 2,973: | ||
end |
end |
||
end |
end |
||
text = safe_join( {Editors, Date, Chapter, Place, tcommon |
text = safe_join( {Editors, Date, Chapter, Place, tcommon}, sepc ); |
||
text = safe_join( {text, pgtext, idcommon}, sepc ); |
|||
else |
else |
||
if is_set(Date) then |
|||
if ( string.sub(tcommon,-1,-1) ~= sepc ) |
|||
then Date = sepc .." " .. Date .. OrigYear |
|||
else Date = " " .. Date .. OrigYear |
|||
end |
|||
end |
|||
if config.CitationClass=="journal" and is_set(Periodical) then |
if config.CitationClass=="journal" and is_set(Periodical) then |
||
text = safe_join( {Chapter, Place, tcommon |
text = safe_join( {Chapter, Place, tcommon}, sepc ); |
||
text = safe_join( {text, pgtext, Date, idcommon}, sepc ); |
|||
else |
else |
||
text = safe_join( {Chapter, Place, tcommon, Date |
text = safe_join( {Chapter, Place, tcommon, Date}, sepc ); |
||
text = safe_join( {text, pgtext, idcommon}, sepc ); |
|||
end |
end |
||
end |
end |
||
Line 2,851: | Line 2,998: | ||
text = safe_join( {text, PostScript}, sepc ); |
text = safe_join( {text, PostScript}, sepc ); |
||
-- Now enclose the whole thing in a < |
-- Now enclose the whole thing in a <span/> element |
||
local options = {}; |
local options = {}; |
||
if is_set(config.CitationClass) and config.CitationClass ~= "citation" then |
if is_set(config.CitationClass) and config.CitationClass ~= "citation" then |
||
options.class = config.CitationClass; |
options.class = "citation " .. config.CitationClass; |
||
options.class = "citation " .. config.CitationClass; -- class=citation required for blue highlight when used with |ref= |
|||
else |
else |
||
options.class = "citation"; |
options.class = "citation"; |
||
end |
end |
||
if is_set(Ref) and Ref:lower() ~= "none" then |
if is_set(Ref) and Ref:lower() ~= "none" then |
||
local id = Ref |
local id = Ref |
||
if ( |
if ( "harv" == Ref ) then |
||
local |
local names = {} --table of last names & year |
||
if #a > 0 then |
|||
local year = first_set ({Year, anchor_year}, 2); -- Year first for legacy citations and for YMD dates that require disambiguation |
|||
for i,v in ipairs(a) do |
|||
names[i] = v.last |
|||
if #c > 0 then -- if there is a contributor list |
|||
if i == 4 then break end |
|||
end |
|||
elseif #a > 0 then -- or an author list |
|||
elseif #e > 0 then |
|||
for i,v in ipairs(e) do |
|||
elseif #e > 0 then -- or an editor list |
|||
names[i] = v.last |
|||
if i == 4 then break end |
|||
end |
|||
end |
end |
||
names[ #names + 1 ] = first_set(Year, anchor_year); -- Year first for legacy citations and for YMD dates that require disambiguation |
|||
id = anchor_id (namelist, year); -- go make the CITEREF anchor |
|||
id = anchor_id(names) |
|||
end |
end |
||
options.id = id; |
options.id = id; |
||
Line 2,885: | Line 3,034: | ||
end |
end |
||
if is_set(options.id) then |
if is_set(options.id) then |
||
text = |
text = '<span id="' .. mw.uri.anchorEncode(options.id) ..'" class="' .. mw.text.nowiki(options.class) .. '">' .. text .. "</span>"; |
||
else |
else |
||
text = |
text = '<span class="' .. mw.text.nowiki(options.class) .. '">' .. text .. "</span>"; |
||
end |
end |
||
local empty_span = '<span style="display:none;"> </span>'; |
|||
text = text .. substitute (cfg.presentation['ocins'], {OCinSoutput}); -- append metadata to the citation |
|||
-- Note: Using display: none on then COinS span breaks some clients. |
|||
local OCinS = '<span title="' .. OCinSoutput .. '" class="Z3988">' .. empty_span .. '</span>'; |
|||
text = text .. OCinS; |
|||
if #z.message_tail ~= 0 then |
if #z.message_tail ~= 0 then |
||
Line 2,907: | Line 3,060: | ||
if #z.maintenance_cats ~= 0 then |
if #z.maintenance_cats ~= 0 then |
||
text = text .. '<span class="citation-comment" style="display:none; color:#33aa33">'; |
text = text .. ' <span class="citation-comment" style="display:none; color:#33aa33">'; |
||
for _, v in ipairs( z.maintenance_cats ) do -- append maintenance categories |
for _, v in ipairs( z.maintenance_cats ) do -- append maintenance categories |
||
text = text |
text = text .. v .. ' ([[:Category:' .. v ..'|link]])'; |
||
end |
end |
||
text = text .. '</span>'; -- maintenance mesages (realy just the names of the categories for now) |
text = text .. '</span>'; -- maintenance mesages (realy just the names of the categories for now) |
||
Line 2,930: | Line 3,083: | ||
end |
end |
||
-- This is used by templates such as {{cite book}} to create the actual citation text. |
|||
--[[--------------------------< C S 1 . C I T A T I O N >------------------------------------------------------ |
|||
function z.citation(frame) |
|||
This is used by templates such as {{cite book}} to create the actual citation text. |
|||
]] |
|||
function cs1.citation(frame) |
|||
local pframe = frame:getParent() |
local pframe = frame:getParent() |
||
local validation |
local validation; |
||
if nil ~= string.find (frame:getTitle(), 'sandbox', 1, true) then -- did the {{#invoke:}} use sandbox version? |
if nil ~= string.find (frame:getTitle(), 'sandbox', 1, true) then -- did the {{#invoke:}} use sandbox version? |
||
cfg = mw.loadData ('Module:Citation/CS1/Configuration/sandbox'); -- load sandbox versions of |
cfg = mw.loadData ('Module:Citation/CS1/Configuration/sandbox'); -- load sandbox versions of Configuration and Whitelist and ... |
||
whitelist = mw.loadData ('Module:Citation/CS1/Whitelist/sandbox'); |
whitelist = mw.loadData ('Module:Citation/CS1/Whitelist/sandbox'); |
||
validation = require ('Module:Citation/CS1/Date_validation/sandbox'); -- ... sandbox version of date validation code |
|||
validation = require ('Module:Citation/CS1/Date_validation/sandbox'); |
|||
identifiers = require ('Module:Citation/CS1/Identifiers/sandbox'); |
|||
metadata = require ('Module:Citation/CS1/COinS/sandbox'); |
|||
else -- otherwise |
else -- otherwise |
||
cfg = mw.loadData ('Module:Citation/CS1/Configuration'); -- load live versions of |
cfg = mw.loadData ('Module:Citation/CS1/Configuration'); -- load live versions of Configuration and Whitelist and ... |
||
whitelist = mw.loadData ('Module:Citation/CS1/Whitelist'); |
whitelist = mw.loadData ('Module:Citation/CS1/Whitelist'); |
||
validation = require ('Module:Citation/CS1/Date_validation'); -- ... live version of date validation code |
|||
validation = require ('Module:Citation/CS1/Date_validation'); |
|||
identifiers = require ('Module:Citation/CS1/Identifiers'); |
|||
metadata = require ('Module:Citation/CS1/COinS'); |
|||
end |
end |
||
dates = validation.dates; -- imported functions |
|||
identifiers.set_selected_modules (cfg, utilities); -- so that functions in Identifiers can see the selected cfg tables and selected Utilities module |
|||
validation.set_selected_modules (utilities); -- so that functions in Date validataion can see the selected Utilities module |
|||
metadata.set_selected_modules (cfg, utilities); -- so that functions in COinS can see the selected cfg tables and selected Utilities module |
|||
dates = validation.dates; -- imported functions from Module:Citation/CS1/Date validation |
|||
year_date_check = validation.year_date_check; |
year_date_check = validation.year_date_check; |
||
reformat_dates = validation.reformat_dates; |
|||
is_set = utilities.is_set; -- imported functions from Module:Citation/CS1/Utilities |
|||
in_array = utilities.in_array; |
|||
substitute = utilities.substitute; |
|||
error_comment = utilities.error_comment; |
|||
set_error = utilities.set_error; |
|||
select_one = utilities.select_one; |
|||
add_maint_cat = utilities.add_maint_cat; |
|||
wrap_style = utilities.wrap_style; |
|||
safe_for_italics = utilities.safe_for_italics; |
|||
remove_wiki_link = utilities.remove_wiki_link; |
|||
z = utilities.z; -- table of error and category tables in Module:Citation/CS1/Utilities |
|||
extract_ids = identifiers.extract_ids; -- imported functions from Module:Citation/CS1/Utilities |
|||
build_id_list = identifiers.build_id_list; |
|||
is_embargoed = identifiers.is_embargoed; |
|||
make_coins_title = metadata.make_coins_title; -- imported functions from Module:Citation/CS1/COinS |
|||
get_coins_pages = metadata.get_coins_pages; |
|||
COinS = metadata.COinS; |
|||
local args = {}; |
local args = {}; |
||
local suggestions = {}; |
local suggestions = {}; |
||
Line 2,998: | Line 3,112: | ||
end |
end |
||
local capture; -- the single supported capture when matching unknown parameters using patterns |
|||
for k, v in pairs( pframe.args ) do |
for k, v in pairs( pframe.args ) do |
||
if v ~= '' then |
if v ~= '' then |
||
Line 3,011: | Line 3,124: | ||
error_text, error_state = set_error( 'parameter_ignored_suggest', {k, k:lower()}, true ); |
error_text, error_state = set_error( 'parameter_ignored_suggest', {k, k:lower()}, true ); |
||
else |
else |
||
if #suggestions == 0 then |
|||
if nil == suggestions.suggestions then -- if this table is nil then we need to load it |
|||
suggestions = mw.loadData( 'Module:Citation/CS1/Suggestions' ); |
|||
if nil ~= string.find (frame:getTitle(), 'sandbox', 1, true) then -- did the {{#invoke:}} use sandbox version? |
|||
suggestions = mw.loadData( 'Module:Citation/CS1/Suggestions/sandbox' ); -- use the sandbox version |
|||
else |
|||
suggestions = mw.loadData( 'Module:Citation/CS1/Suggestions' ); -- use the live version |
|||
end |
|||
end |
end |
||
if suggestions[ k:lower() ] ~= nil then |
|||
for pattern, param in pairs (suggestions.patterns) do -- loop through the patterns to see if we can suggest a proper parameter |
|||
error_text, error_state = set_error( 'parameter_ignored_suggest', {k, suggestions[ k:lower() ]}, true ); |
|||
capture = k:match (pattern); -- the whole match if no caputre in pattern else the capture if a match |
|||
else |
|||
if capture then -- if the pattern matches |
|||
error_text, error_state = set_error( 'parameter_ignored', {k}, true ); |
|||
param = substitute( param, capture ); -- add the capture to the suggested parameter (typically the enumerator) |
|||
error_text, error_state = set_error( 'parameter_ignored_suggest', {k, param}, true ); -- set the error message |
|||
end |
|||
end |
|||
if not is_set (error_text) then -- couldn't match with a pattern, is there an expicit suggestion? |
|||
if suggestions.suggestions[ k:lower() ] ~= nil then |
|||
error_text, error_state = set_error( 'parameter_ignored_suggest', {k, suggestions.suggestions[ k:lower() ]}, true ); |
|||
else |
|||
error_text, error_state = set_error( 'parameter_ignored', {k}, true ); |
|||
end |
|||
end |
end |
||
end |
end |
||
Line 3,042: | Line 3,142: | ||
end |
end |
||
end |
end |
||
for k, v in pairs( args ) do |
|||
if 'string' == type (k) then -- don't evaluate positional parameters |
|||
has_invisible_chars (k, v); |
|||
end |
|||
end |
|||
return citation0( config, args) |
return citation0( config, args) |
||
end |
end |
||
return |
return z |