32,512
edits
(bug fix in language_parameter();) |
m (1 revision imported: Modules) |
||
Line 1:
local z = {
error_categories = {}; -- for categorizing citations that contain errors
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 >--------------------------------------
]]
local dates, year_date_check -- functions in Module:Citation/CS1/Date_validation
local
local whitelist = {}; -- talbe of tables listing valid template parameter names; defined in Module:Citation/CS1/Whitelist
--[[--------------------------< I S _ S E T >------------------------------------------------------------------
Returns true if argument is set; false otherwise. Argument is 'set' when it exists (not nil) or when it is not an empty string.
This function is global because it is called from both this module and from Date validation
]]
function is_set( var )
return not (var == nil or var == '');
end
--[[--------------------------< F I R S T _ S E T >------------------------------------------------------------
First set variable or nil if none
]]
local function first_set
local
for _, var in pairs(list) do
if is_set(
return var;
end
end
end
--[[--------------------------<
Whether needle is in haystack
]]
local function in_array( needle, haystack )
if needle == nil then
return false;
end
for n,v in ipairs( haystack ) do
if v == needle then
return n;
end
end
return false;
end
--[[--------------------------<
Populates numbered arguments in a message string using an argument table.
]]
local function substitute( msg, args )
return args and mw.message.newRawMessage( msg, args ):plain() or msg;
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.
]]
local function error_comment( content, hidden )
return substitute( hidden and cfg.presentation['hidden-error'] or cfg.presentation['visible-error'], content );
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
the responsibility of the calling function.
]]
local function set_error( error_id, arguments, raw, prefix, suffix )
local error_state = cfg.error_conditions[ error_id ];
prefix = prefix or "";
suffix = suffix or "";
if error_state == nil then
error( cfg.messages['undefined_error'] );
elseif is_set( error_state.category ) then
table.insert( z.error_categories, error_state.category );
end
local message = substitute( error_state.message, arguments );
message = message .. " ([[" .. cfg.messages['help page link'] ..
"#" .. error_state.anchor .. "|" ..
cfg.messages['help page label'] .. "]])";
z.error_ids[ error_id ] = true;
if in_array( error_id, { 'bare_url_missing_title', 'trans_missing_title' } )
and z.error_ids['citation_missing_title'] then
return '', false;
end
message = table.concat({ prefix, message, suffix });
if raw == true then
return message, error_state.hidden;
end
return error_comment( message, error_state.hidden );
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.
]]
local function
table.insert( z.maintenance_cats, substitute (cfg.maint_cats [key], arguments)); -- make name then add to table
end
--[[--------------------------<
Adds a category to z.properties_cats using names from the configuration file with additional text if any.
]]
local function
table.insert( z.properties_cats, substitute (cfg.prop_cats [key], arguments)); -- make name then add to table
end
--[[--------------------------< C H E C K _ U R L >------------------------------------------------------------
Determines whether a URL string
At present the only check is whether the string appears to be prefixed with a URI scheme. It is not determined whether
the URI scheme is valid or whether the URL is otherwise well formed.
]]
local function check_url( url_str )
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 '' ... ''
Note: We cannot use <i> for italics, as the expected behavior for italics specified by ''...'' in the title is that
they will be inverted (i.e. unitalicized) in the resulting references. In addition, <i> and '' tend to interact
poorly under Mediawiki's HTML tidy.
]]
local function safe_for_italics( str )
if not is_set(str) then
return str;
else
if str:sub(1,1) == "'" then str = "<span />" .. str; end
if str:sub(-1,-1) == "'" then str = str .. "<span />"; end
-- Remove newlines as they break italics.
return str:gsub( '\n', ' ' );
end
end
--[[--------------------------< S A F E _ F O R _ U R L >------------------------------------------------------
Line 356 ⟶ 186:
[']'] = ']',
['\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
Line 375 ⟶ 223:
end
if not check_url( URL ) then
error_str = set_error( 'bad_url', {
end
return table.concat({ "[", URL, " ", safe_for_url( label ), "]", error_str });
end
--[[--------------------------<
Formats a wiki style external link
]]
local function external_link_id(options)
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)
if true ~= Page_in_deprecated_cat then -- if we haven't been here before then set a
table.insert( z.message_tail, { set_error( 'deprecated_params', {name}, true ) } ); -- add error message
end
Line 444 ⟶ 306:
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.
TODO: error messages when prefix is invalid ISO639-1 code; when script_value has prefix but no script;
Line 462 ⟶ 324:
script_value = script_value:gsub ('^%l%l%s*:%s*', ''); -- strip prefix from script
-- is prefix one of these language codes?
if in_array (lang, {
add_prop_cat ('script_with_name', {name, lang})
else
Line 509 ⟶ 371:
local msg;
msg = cfg.messages[key]:lower(); -- set the message to lower case before
return str;
else
return substitute( cfg.messages[key], {str} );
end
end
--[[--------------------------< S E L E C T _ O N E >----------------------------------------------------------
Chooses one matching parameter from a list of parameters to consider
Generates an error if more than one match is present.
]]
local function
local
local selected = '';
local error_list = {};
if index ~= nil then index = tostring(index); end
-- Handle special case of "#" replaced by empty string
for _, v in ipairs( possible ) do
if is_set(args[v]) then
if value ~= nil and selected ~= v then
table.insert( error_list, v );
else
value = args[v];
selected = v;
end
end
end
end
for _, v in ipairs( possible ) do
if index ~= nil then
v = v:gsub( "#", index );
end
if is_set
if value ~= nil and selected ~= v then
table.insert( error_list, v );
else
value = args[v];
selected = v;
end
end
end
if
local error_str = "";
for _, k in ipairs( error_list ) do
if error_str ~= "" then error_str = error_str .. cfg.messages['parameter-separator'] end
error_str = error_str .. wrap_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
return
end
--[[--------------------------<
Format the three chapter parameters: |chapter=, |trans-chapter=, and |chapter-url= into a single Chapter meta-
parameter (chapter_url_source used for error messages).
]]
local function
local chapter_error = '';
if not is_set (chapter) then
if is_set (transchapter) then
chapter = wrap_style ('trans-quoted-title', transchapter);
chapter_error = " " .. set_error ('trans_missing_chapter');
end
if is_set (chapterurl) then
chapter = external_link (chapterurl, chapter, chapter_url_source); -- adds bare_url_missing_title error if appropriate
end
return chapter .. chapter_error;
else -- here when chapter is set
chapter = kern_quotes (chapter); -- if necessary, separate chapter title's leading and trailing quote marks from Module provided quote marks
chapter = wrap_style ('quoted-title', chapter);
if is_set (transchapter) then
transchapter = wrap_style ('trans-quoted-title', transchapter);
chapter = chapter .. ' ' .. transchapter;
end
if is_set (chapterurl) then
chapter = external_link (chapterurl, chapter); -- adds bare_url_missing_title error if appropriate
end
end
return chapter;
end
--[[
Argument wrapper. This function provides support for argument
mapping defined in the configuration file so that multiple names
can be transparently aliased to single internal variable.
]]
Line 665 ⟶ 521:
end
--[[
Looks for a parameter's name in the whitelist.
Line 672 ⟶ 528:
false - deprecated, supported parameters
nil - unsupported parameters
]]
Line 696 ⟶ 551:
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
Line 715 ⟶ 580:
date = substitute (cfg.presentation['nowrap1'], date);
elseif date:match("^%a+%s*%d%d?,%s
cap, cap2 = string.match (date, "^(.*)%s+(%d%d%d%d)$");
date = substitute (cfg.presentation['nowrap2'], {cap, cap2});
Line 721 ⟶ 586:
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
Line 741 ⟶ 1,075:
end
--[[--------------------------< C L E A N _ I S B N >----------------------------------------------------------
Removes irrelevant text and dashes from ISBN number
Similar to that used for Special:BookSources
]]
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 )
if not is_set(str) or str:match( "[%[%]{}<>]" ) ~= nil then
Line 754 ⟶ 1,186:
return str:gsub( '-', '–' );
end
--[[--------------------------< S A F E _ J O I N >------------------------------------------------------------
Line 850 ⟶ 1,281:
|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
because the code editor gets confused between character insertion point and cursor position.
Line 857 ⟶ 1,288:
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 true ~= Page_in_vanc_error_cat then -- if we haven't been here before then set a sticky flag
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
end;
Line 894 ⟶ 1,328:
]]
local function list_people(control, people, etal
local sep;
local namesep;
Line 935 ⟶ 1,369:
if ( "vanc" == format ) then -- if vancouver format
one = one:gsub ('%.', ''); -- remove periods from surnames (http://www.ncbi.nlm.nih.gov/books/NBK7271/box/A35029/)
if
first = reduce_to_initials(first) -- attempt to convert first name(s) to initials
end
end
Line 942 ⟶ 1,376:
end
if is_set(person.link) and person.link ~= control.page_name then
one = "[[" .. person.link .. "|" .. one .. "]]"
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
table.insert( text, one )
Line 950 ⟶ 1,387:
end
local count = #text / 2;
if count > 0 then
if count > 1 and is_set(lastauthoramp) and not etal then
text[#text-2] = " & ";
end
text[#text] = nil;
end
local result = table.concat(text)
if etal then
result = result .. " " .. etal_text;
end
Line 970 ⟶ 1,408:
Generates a CITEREF anchor ID if we have at least one name or a date. Otherwise returns an empty string.
]]
local function anchor_id( options )
local id = table.concat( options ); -- concatenate names and year for CITEREF id
if is_set (id) then -- if concatenation is not an empty string
return "CITEREF" .. id; -- add the CITEREF portion
Line 986 ⟶ 1,417:
return ''; -- return an empty string; no reason to include CITEREF id in this citation
end
end
Line 1,048 ⟶ 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 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
while true do
last = select_one( args, cfg.aliases[list_name .. '-Last'], 'redundant_parameters', i ); -- search through args for name components beginning at 1
Line 1,056 ⟶ 1,455:
mask = select_one( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i );
local name = tostring(last);
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
Line 1,063 ⟶ 1,470:
elseif not first and not last then -- if both firstn and lastn aren't found, are we done?
count = count + 1; -- number of times we haven't found last and first
if 2
break; -- normal exit or there is a two-name hole in the list; can't tell which
end
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
n = n + 1; -- point to next location in the names table
if 1 == count then -- if the previous name was missing
Line 1,079 ⟶ 1,484:
end
if true == etal then
add_maint_cat ('etal');
end
return names, etal; -- all done, return our list of names
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 >------------------------------------------------
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
Line 1,099 ⟶ 1,667:
]]
local function get_iso639_code (lang
if 'norwegian' == lang:lower() then -- special case related to Wikimedia remap of code 'no' at Extension:CLDR
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)
local langlc = mw.ustring.lower(lang); -- lower case version for comparisons
Line 1,118 ⟶ 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.
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
the page is a mainspace page and the ISO639-1 code is not
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 ⟶ 1,707:
]]
local function language_parameter (lang, namespace)
local code; -- the ISO639-1 two character code
local name; -- the language name
local language_list = {}; -- table of language names to be rendered
local names_table = {}; -- table made from the value assigned to |language=
local unrec_cat = false -- flag so that we only add unrecognized category once
names_table = mw.text.split (lang, '%s*,%s*'); -- names should be a comma separated list
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');
end
if 2 == lang:len() then -- ISO639-1 language code are 2 characters (fetchLanguageName also supports 3 character codes)
name = mw.language.fetchLanguageName( lang:lower(),
end
Line 1,151 ⟶ 1,728:
code = lang:lower(); -- save it
else
name, code = get_iso639_code (lang
end
if is_set (code) then
if
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})
end
elseif false == unrec_cat then
add_maint_cat (unknown_lang);
end
Line 1,173 ⟶ 1,752:
name = table.concat (language_list, ', ') -- and concatenate with '<comma><space>' separators
end
return (" " .. wrap_msg ('language', name)); -- wrap with '(in ...)'
end
Line 1,240 ⟶ 1,816:
sep, ps = set_cs1_style (ps);
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
end
Line 1,251 ⟶ 1,830:
--[=[-------------------------< I S _ P D F >------------------------------------------------------------------
Determines if a url has the file extension
applying the pdf icon to external links.
Line 1,273 ⟶ 1,852:
local function style_format (format, url, fmt_param, url_param)
if is_set (format) then
format = wrap_style ('format', format:upper());
if not is_set (url) then
format = format .. set_error( 'format_missing_url', {fmt_param, url_param} ); -- add an error message
Line 1,287 ⟶ 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 >------------------------
Returns a number that
When the value assigned to |display-
the previous state of the 'etal' flag (false by default but may have been set to true if the name list contains
some variant of the text 'et al.').
When the value assigned to |display-
number of authors in the list and set the 'etal' flag true. This will cause the list_people() to display all of
the names in the name list followed by 'et al.'
In all other cases, returns nil and the previous state of the 'etal' flag.
]]
Line 1,315 ⟶ 1,887:
elseif max:match ('^%d+$') then -- if is a string of numbers
max = tonumber (max); -- make it a number
if max >= count and 'authors' == list_name then
add_maint_cat ('disp_auth_ed', list_name);
end
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
max = nil; -- unset
end
elseif 'authors' == list_name then -- AUTHORS ONLY need to clear implicit et al category
max = count + 1; -- number of authors + 1
end
return max, etal;
end
--[[--------------------------< C I T A T I O N 0 >------------------------------------------------------------
This is the main function doing the majority of the citation
formatting.
]]
Line 1,616 ⟶ 1,911:
--[[
Load Input Parameters
The argument_wrapper facilitates the mapping of multiple
aliases to single internal variable.
]]
local A = argument_wrapper( args );
local i
local PPrefix = A['PPrefix']
local PPPrefix = A['PPPrefix']
if is_set( A['NoPP'] ) then PPPrefix = "" PPrefix = "" end
-- Pick out the relevant fields from the arguments. Different citation templates
-- define different field names for the same underlying things.
local Authors = A['Authors'];
local author_etal;
local a, author_etal = extract_names( args, 'AuthorList' );
local Coauthors = A['Coauthors'];
local Others = A['Others'];
local Editors = A['Editors'];
local editor_etal;
local e, editor_etal = extract_names( args, 'EditorList' );
local NameListFormat = A['NameListFormat']; -- replaces |author-format= and |editor-format=
if is_set (NameListFormat) and ('vanc' ~= NameListFormat) then -- only accepted value for this parameter is 'vanc'
table.insert( z.message_tail, { set_error( 'invalid_param_val', {'name-list-format', NameListFormat}, true ) } ); -- not vanc so add error message
NameListFormat = ''; -- set to empty string
end
Line 1,707 ⟶ 1,952:
local TitleNote = A['TitleNote'];
local TitleLink = A['TitleLink'];
local Chapter = A['Chapter'];
local
local TransChapter = A['TransChapter'];
local TitleType = A['TitleType'];
Line 1,726 ⟶ 1,968:
local ConferenceURLorigin = A:ORIGIN('ConferenceURL'); -- get name of parameter that holds ConferenceURL
local Periodical = A['Periodical'];
local Series = A['Series'];
local Volume = A['Volume'];
local
local Position = '';
local Page = A['Page'];
local Pages = hyphen_to_dash( A['Pages'] );
local Edition = A['Edition'];
Line 1,755 ⟶ 1,983:
local PublisherName = A['PublisherName'];
local RegistrationRequired = A['RegistrationRequired'];
local SubscriptionRequired = A['SubscriptionRequired'];
local Via = A['Via'];
local AccessDate = A['AccessDate'];
Line 1,768 ⟶ 1,989:
local Agency = A['Agency'];
local DeadURL = A['DeadURL']
local Language = A['Language'];
local Format = A['Format'];
Line 1,779 ⟶ 1,996:
local ASINTLD = A['ASINTLD'];
local IgnoreISBN = A['IgnoreISBN'];
local Embargo = A['Embargo'];
local Class = A['Class']; -- arxiv class identifier
Line 1,798 ⟶ 2,012:
local LastAuthorAmp = A['LastAuthorAmp'];
local no_tracking_cats = A['NoTracking'];
--these are used by cite interview
Line 1,812 ⟶ 2,020:
--local variables that are not cs1 parameters
local use_lowercase;
local this_page = mw.title.getCurrentTitle();
local anchor_year;
local COinS_date
-- set default parameter values defined by |mode= parameter. If |mode= is empty or omitted, use CitationClass to set these values
local sepc; -- separator between citation elements for CS1 a period, for CS2, a comma
local PostScript;
local Ref;
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
Line 1,846 ⟶ 2,045:
end
-- check for extra |page=, |pages= or |at= parameters.
if is_set(Page) then
if is_set(Pages) or is_set(At) then
Page = Page .. " " .. set_error('extra_pages'); -- add error message
Pages = ''; -- unset the others
At = '';
end
elseif is_set(Pages) then
if is_set(At) then
Pages = Pages .. " " .. set_error('extra_pages'); -- add error messages
At = ''; -- unset
end
end
Line 1,887 ⟶ 2,077:
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 ⟶ 2,083:
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(Title
if not is_set(Chapter) then
Chapter = Title;
TransChapter = TransTitle;
ChapterURL = URL;
Title = Periodical;
ChapterFormat = Format;
Periodical = '';
TransTitle = ''; -- redundant so unset
URL = ''; -- redundant so unset
Format = ''; -- redundant so unset
end
else
Title = Periodical;
Periodical = '';
end
end
Line 1,920 ⟶ 2,104:
-- Special case for cite techreport.
if (config.CitationClass == "techreport") then
if is_set(
if not is_set(ID) then
ID =
Issue = ""; -- unset Issue so that "number" isn't duplicated in the rendered citation or COinS metadata
else -- can't use ID so emit error message
ID = ID .. " " .. set_error('redundant_parameters', '<code>|id=</code> and <code>|number=</code>');
end
end
Line 1,965 ⟶ 2,150:
if (config.CitationClass == "mailinglist") then
Periodical = A ['MailingList'];
end
Line 2,014 ⟶ 2,197:
if 'episode' == config.CitationClass or 'serial' == config.CitationClass then
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 Network = A['Network'];
local Station = A['Station'];
local s, n = {}, {};
-- do common parameters first
if is_set(Network) then table.insert(n, Network); end
Line 2,026 ⟶ 2,209:
ID = table.concat(n, sepc .. ' ');
if not is_set (Date
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
Line 2,045 ⟶ 2,236:
Chapter = Title; -- promote title parameters to chapter
ChapterLink = TitleLink; -- alias episodelink
TransChapter = TransTitle;
Line 2,061 ⟶ 2,251:
end
URL = ''; -- unset
TransTitle = ''; -- unset
else -- now oddities that are cite serial
Issue = '';
Chapter = A['Episode']; -- TODO: make |episode= available to cite episode someday?
if is_set (Series) and is_set (SeriesLink) then
Line 2,079 ⟶ 2,268:
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
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
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['OSTI'], ID_list['PMC'], ID_list['PMID'], ID_list['RFC'], ID_list['SSRN'], ID_list['USENETID'], ID_list['ZBL']
table.insert( z.message_tail, { set_error( 'arxiv_params_not_supported', {}, true ) } ); -- add error message
Line 2,102 ⟶ 2,286:
-- 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
TitleType = set_titletype (config.CitationClass, TitleType);
Line 2,110 ⟶ 2,295:
if is_set(TitleType) then -- if type parameter is specified
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.
if not is_set (Date) then
Date = Year;
Year = nil;
if is_set(Date) then
local Month = A['Month'];
if is_set(Month) then
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
if PublicationDate == Date then PublicationDate = ''; end
--[[
Line 2,134 ⟶ 2,324:
local error_message = '';
-- AirDate has been promoted to Date so not necessary to check it
anchor_year, COinS_date, error_message = dates({['
['embargo']=Embargo, ['
if is_set (Year) and is_set (Date) then -- both |date= and |year= not normally needed;
Line 2,151 ⟶ 2,341:
if is_set(error_message) then
table.insert( z.message_tail, { set_error( 'bad_date', {error_message}, true ) } ); -- add this error message
end
end -- end of do
Line 2,183 ⟶ 2,360:
not is_set(TransTitle) and
not is_set(ScriptTitle) then
table.insert( z.message_tail, { set_error( 'citation_missing_title', {}, true ) } );
end
if 'none' == Title and
Title = ''; -- set title to empty string
add_maint_cat ('untitled');
end
-- COinS metadata (see <http://ocoins.info/>) for automated parsing of citation information.
Line 2,215 ⟶ 2,381:
end
end
-- this is the function call to COinS()
local OCinSoutput = COinS({
['Periodical'] = Periodical,
['Chapter'] = strip_apostrophe_markup (coins_chapter), -- Chapter stripped of bold / italic wikimarkup
['Title'] = make_coins_title (coins_title, ScriptTitle), -- Title and ScriptTitle stripped of bold / italic wikimarkup
['PublicationPlace'] = PublicationPlace,
['Date'] = first_set(COinS_date
['Series'] = Series,
['Volume'] = Volume,
['Issue'] = Issue,
['Pages'] = get_coins_pages (first_set
['Edition'] = Edition,
['PublisherName'] = PublisherName,
['URL'] = first_set
['Authors'] =
['ID_list'] = ID_list,
['RawPage'] = this_page.prefixedText,
Line 2,251 ⟶ 2,409:
if 'newsgroup' == config.CitationClass then
if is_set (PublisherName) then
PublisherName =
end
end
Line 2,260 ⟶ 2,418:
-- We also add leading spaces and surrounding markup and punctuation to the
-- various parts of the citation, but only when they are non-nil.
local LastFirstAuthors;
local
Maximum , author_etal = get_display_authors_editors (Maximum, #a, 'authors', author_etal);
local control = {
format = NameListFormat, -- empty string or 'vanc'
maximum = Maximum,
lastauthoramp = LastAuthorAmp,
page_name = this_page.text
};
-- 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
if is_set (LastFirstAuthors) then -- if both author name styles
table.insert( z.message_tail, { set_error( 'redundant_parameters', {wrap_style ('parameter', 'authors') .. ' and ' .. wrap_style ('parameter', 'last')}, true ) } ); -- add error message
Authors = LastFirstAuthors; -- TODO: is this correct or should we use |authors= instead?
end
else
Authors = LastFirstAuthors; -- either an author name list or an empty string
end
end
table.insert( z.message_tail, { set_error('coauthors_missing_author', {}, true) } ); -- emit error message
end
local EditorCount; -- used only for choosing {ed.) or (eds.) annotation at end of editor name-list
if not is_set(Editors) then
local Maximum = A['DisplayEditors'];
Maximum , editor_etal = get_display_authors_editors (Maximum, #e, 'editors', editor_etal);
-- Preserve old-style implicit et al.
if not is_set(Maximum) and #e == 4 then
Maximum = 3;
table.insert( z.message_tail, { set_error('implict_etal_editor', {}, true) } );
end
local control = {
format = NameListFormat, -- empty string or 'vanc'
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
else
EditorCount = 1;
end
Line 2,330 ⟶ 2,482:
-- 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');
ChapterFormat = style_format (ChapterFormat, ChapterURL, 'chapter-format', 'chapter-url');
ConferenceFormat = style_format (ConferenceFormat, ConferenceURL, 'conference-format', 'conference-url');
Format = style_format (Format, URL, 'format', 'url');
Line 2,335 ⟶ 2,488:
TranscriptFormat = style_format (TranscriptFormat, TranscriptURL, 'transcript-format', 'transcripturl');
if not is_set(URL) then --and
-- not is_set(ArchiveURL) then --and -- prevents format_missing_url error from registering
-- not is_set(ConferenceURL) and -- TODO: keep this here? conference as part of cite web or cite podcast?
-- not is_set(TranscriptURL) then -- TODO: remove? |transcript-url= and |transcript= has separate test
-- Test if cite web or cite podcast |url= is missing or empty
if in_array(config.CitationClass, {"web","podcast", "mailinglist"}) then
table.insert( z.message_tail, { set_error( 'cite_web_url', {}, true ) } );
end
--
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 ) } );
Line 2,353 ⟶ 2,505:
end
local OriginalURL
DeadURL = DeadURL:lower(); -- used later when assembling archived text
if is_set( ArchiveURL ) then
if is_set (URL) then
OriginalURL = URL; -- save copy of original source URL
OriginalFormat = Format; -- and original |format=
if 'no' ~= DeadURL then -- if URL set then archive-url applies to it
Line 2,367 ⟶ 2,518:
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
OriginalFormat = ChapterFormat; -- and original |format=
if 'no' ~= DeadURL then
ChapterURL = ArchiveURL -- swap-in the archive's url
ChapterFormat = ArchiveFormat or ''; -- swap in archive's format
end
Line 2,377 ⟶ 2,527:
end
if in_array(config.CitationClass, {
('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
table.insert( z.message_tail, { set_error( 'chapter_ignored', {}, true ) } ); -- add error message
Chapter = ''; -- set to empty string to be safe with concatenation
TransChapter = '';
ChapterURL = '';
end
else -- otherwise, format chapter / article title
Chapter = format_chapter_title (Chapter, TransChapter, ChapterURL, ChapterURLorigin);
if is_set (Chapter) then
if 'map' == config.CitationClass and is_set (TitleType) then
Chapter = Chapter .. ' ' .. TitleType;
end
Chapter = Chapter .. ChapterFormat .. sepc .. ' ';
end
end
Line 2,425 ⟶ 2,550:
end
if in_array(config.CitationClass, {
('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
Line 2,442 ⟶ 2,567:
end
if is_set(TransTitle) then
if is_set(Title) then
TransTitle = " " .. TransTitle;
else
TransError = " " .. set_error( 'trans_missing_title'
end
end
Line 2,455 ⟶ 2,580:
if is_set(Title) then
if not is_set(TitleLink) and is_set(URL) then
Title = external_link( URL, Title
URL = "";
Format = "";
Line 2,469 ⟶ 2,594:
if is_set (Conference) then
if is_set (ConferenceURL) then
Conference = external_link( ConferenceURL, Conference
end
Conference = sepc .. " " .. Conference .. ConferenceFormat;
Line 2,478 ⟶ 2,603:
if not is_set(Position) then
local Minutes = A['Minutes'];
if is_set(Minutes) then
Position = " " .. Minutes .. " " .. cfg.messages['minutes'];
else
local Time = A['Time'];
if is_set(Time) then
local TimeCaption = A['TimeCaption']
Line 2,500 ⟶ 2,621:
Position = " " .. Position;
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
if 'map' == config.CitationClass then -- cite map oddity done after COinS call (and with other in-source locators)
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 "";
Line 2,524 ⟶ 2,695:
if is_set (Language) then
Language = language_parameter (Language, this_page.namespace);
else
Language=""; -- language not specified so make sure this is an empty string;
Line 2,530 ⟶ 2,701:
Others = is_set(Others) and (sepc .. " " .. Others) or "";
TitleNote = is_set(TitleNote) and (sepc .. " " .. TitleNote) or "";
Issue = is_set(Issue) and (" (" .. Issue .. ")") or "";
Series = is_set(Series) and (sepc .. " " .. Series) or "";
OrigYear = is_set(OrigYear) and (" [" .. OrigYear .. "]") or "";
Agency = is_set(Agency) and (sepc .. " " .. Agency) or "";
if is_set(Volume) then
if ( mw.ustring.len(Volume) > 4 )
then Volume = sepc .." " .. Volume;
else Volume = " <b>" .. hyphen_to_dash(Volume) .. "</b>";
end
end
------------------------------------ totally unrelated data
Line 2,561 ⟶ 2,726:
]]
if
SubscriptionRequired = sepc .. " " .. cfg.messages['subscription']; -- subscription required message
elseif
SubscriptionRequired = sepc .. " " .. cfg.messages['registration']; -- registration required message
else
Line 2,594 ⟶ 2,759:
if is_set(Quote) then
if Quote:sub(1,1) == '"' and Quote:sub(-1,-1) == '"' then
Quote = Quote:sub(2,-2);
end
Quote = sepc .." " .. wrap_style ('quoted-text', Quote ); -- wrap in <q>...</q> tags
PostScript = ""; --
end
Line 2,610 ⟶ 2,775:
if sepc ~= "." then arch_text = arch_text:lower() end
Archived = sepc .. " " .. substitute( cfg.messages['archived-not-dead'],
{ external_link( ArchiveURL, arch_text
if not is_set(OriginalURL) then
Archived = Archived .. " " .. set_error('archive_missing_url');
end
elseif is_set(OriginalURL) then
local arch_text = cfg.messages['archived-dead'];
if sepc ~= "." then arch_text = arch_text:lower() end
Archived = sepc .. " " .. substitute( arch_text,
else
local arch_text = cfg.messages['archived-missing'];
Line 2,644 ⟶ 2,805:
end
if sepc == '.' then
Lay = sepc .. " " .. external_link( LayURL, cfg.messages['lay summary']
else
Lay = sepc .. " " .. external_link( LayURL, cfg.messages['lay summary']:lower(
end
elseif is_set (LayFormat) then -- Test if |lay-format= is given without giving a |lay-url=
Line 2,654 ⟶ 2,815:
if is_set(Transcript) then
if is_set(TranscriptURL) then
Transcript = external_link( TranscriptURL, Transcript
end
Transcript = sepc .. ' ' .. Transcript .. TranscriptFormat;
Line 2,728 ⟶ 2,889:
-- not to keep reassigning to the same string variable over and over.
local tcommon
if in_array(config.CitationClass, {"journal","citation"}) and is_set(Periodical) then
if is_set(Others) then Others = Others .. sepc .. " " end
tcommon = safe_join( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series,
Language, Edition, Publisher, Agency, Volume, Issue}, sepc );
elseif 'map' == config.CitationClass then -- special cases for cite map
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 );
elseif is_set (Periodical) then -- map in a periodical
tcommon = safe_join( {Title, TitleType, Format, Periodical, Scale, Series, Language, Cartography, Others, Publisher, Volume, Issue}, sepc );
else -- a sheet or stand-alone map
tcommon = safe_join( {Title, TitleType, Format, Edition, Scale, Series, Language, Cartography, Others, Publisher}, sepc );
Line 2,757 ⟶ 2,908:
else -- all other CS1 templates
tcommon = safe_join( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language,
Volume, Issue, Others, Edition, Publisher, Agency}, sepc );
end
Line 2,768 ⟶ 2,919:
local idcommon = safe_join( { ID_list, URL, Archived, AccessDate, Via, SubscriptionRequired, Lay, Quote }, sepc );
local text;
local pgtext = Position .. Sheet
if is_set(Authors) then
if is_set(Coauthors) then
local sep = '; ';
if 'vanc' == NameListFormat then
sep = ', ';
end
Authors = Authors .. sep .. Coauthors;
end
if
Date = " ("..Date..")" .. OrigYear .. sepc .. " "
elseif string.sub(Authors,-1,-1) == sepc then
Authors = Authors .. " "
else
Authors = Authors .. sepc .. " "
end
if is_set(Editors) then
local in_text = " ";
local post_text = "";
if is_set(Chapter)
in_text = in_text .. cfg.messages['in'] .. " "
else
if EditorCount <= 1 then
Line 2,805 ⟶ 2,948:
end
end
if (sepc ~= '.') then in_text = in_text:lower() end
Editors = in_text .. Editors .. post_text;
if (string.sub(Editors,-1,-1) == sepc) or (string.sub(Editors,-3,-1) == sepc .. ']]') then -- if last editor name ends with sepc char
Editors = Editors .. " "; -- don't add another
else
end
end
text = safe_join( {Authors, Date, Chapter, Place, Editors, tcommon }, sepc );
text = safe_join( {text, pgtext, idcommon}, sepc );
elseif is_set(Editors) then
if is_set(Date) then
Line 2,828 ⟶ 2,965:
Editors = Editors .. ", " .. cfg.messages['editors'];
end
Date = " (" .. Date ..")" .. OrigYear .. sepc .. " "
else
if EditorCount <= 1 then
Line 2,835 ⟶ 2,973:
end
end
text = safe_join( {Editors, Date, Chapter, Place, tcommon
text = safe_join( {text, pgtext, idcommon}, sepc );
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
text = safe_join( {Chapter, Place, tcommon
text = safe_join( {text, pgtext, Date, idcommon}, sepc );
else
text = safe_join( {Chapter, Place, tcommon, Date
text = safe_join( {text, pgtext, idcommon}, sepc );
end
end
Line 2,851 ⟶ 2,998:
text = safe_join( {text, PostScript}, sepc );
-- Now enclose the whole thing in a <
local options = {};
if is_set(config.CitationClass) and config.CitationClass ~= "citation" then
options.class = "citation " .. config.CitationClass;
else
options.class = "citation";
end
if is_set(Ref) and Ref:lower() ~= "none" then
local id = Ref
if (
local
if #a > 0 then
for i,v in ipairs(a) do
names[i] = v.last
end
for i,v in ipairs(e) do
if i == 4 then break 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(names)
end
options.id = id;
Line 2,885 ⟶ 3,034:
end
if is_set(options.id) then
text =
else
text =
end
local empty_span = '<span style="display:none;"> </span>';
-- 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
Line 2,907 ⟶ 3,060:
if #z.maintenance_cats ~= 0 then
text = text .. ' <span class="citation-comment" style="display:none; color:#33aa33">';
for _, v in ipairs( z.maintenance_cats ) do -- append maintenance categories
text = text
end
text = text .. '</span>'; -- maintenance mesages (realy just the names of the categories for now)
Line 2,930 ⟶ 3,083:
end
-- This is used by templates such as {{cite book}} to create the actual citation text.
function z.citation(frame)
local pframe = frame:getParent()
local validation
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
whitelist = mw.loadData ('Module:Citation/CS1/Whitelist/sandbox');
else -- otherwise
cfg = mw.loadData ('Module:Citation/CS1/Configuration'); -- load live versions of
whitelist = mw.loadData ('Module:Citation/CS1/Whitelist');
end
year_date_check = validation.year_date_check;
local args = {};
local suggestions = {};
Line 2,998 ⟶ 3,112:
end
for k, v in pairs( pframe.args ) do
if v ~= '' then
Line 3,011 ⟶ 3,124:
error_text, error_state = set_error( 'parameter_ignored_suggest', {k, k:lower()}, true );
else
if #suggestions == 0 then
suggestions = mw.loadData( 'Module:Citation/CS1/Suggestions' );
end
if suggestions[ k:lower() ] ~= nil then
error_text, error_state = set_error( 'parameter_ignored_suggest', {k, suggestions[ k:lower() ]}, true );
else
error_text, error_state = set_error( 'parameter_ignored', {k}, true );
end
end
Line 3,042 ⟶ 3,142:
end
end
return citation0( config, args)
end
return
|