Module:Sandbox/Liangent/zhwiki/Module:Citation/CS1
local z = {
error_categories = {};
error_ids = {};
message_tail = {};
}
-- Include translation message hooks, ID and error handling configuration settings.
-- Note that require has tested to be significantly faster than loadData for this
-- usage. This might be a side effect of the unnecessary cloning described
-- in bugzilla 47300.
local cfg = require( 'Module:Citation/CS1/Configuration' );
-- Contains a list of all recognized parameters
local whitelist = mw.loadData( 'Module:Citation/CS1/Whitelist' );
-- LOCAL
function is_zh( str )
return not not str:find( '[\128-\255]' )
end
-- END LOCAL
-- Whether variable is set or not
function is_set( var )
return not (var == nil or var == '');
end
-- First set variable or nil if none
function first_set(...)
local list = {...};
for _, var in pairs(list) do
if is_set( var ) then
return var;
end
end
end
-- Whether needle is in haystack
function inArray( 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.
function substitute( msg, args )
return args and tostring( mw.message.newRawMessage( msg, args ) ) or msg;
end
-- Wraps a string using a message_list configuration taking one argument
function wrap( key, str )
if not is_set( str ) then
return "";
elseif inArray( key, { 'italic-title', 'trans-italic-title' } ) then
str = safeforitalics( str );
end
return substitute( cfg.messages[key], {str} );
end
--[[
Argument wrapper. This function provides support for argument
mapping defined in the configuration file so that multiple names
can be transparently aliased to single internal variable.
]]
function argument_wrapper( args )
local origin = {};
return setmetatable({
ORIGIN = function( self, k )
local dummy = self[k]; --force the variable to be loaded.
return origin[k];
end
},
{
__index = function ( tbl, k )
if origin[k] ~= nil then
return nil;
end
local args, list, v = args, cfg.aliases[k];
if list == nil then
error( cfg.messages['unknown_argument_map'] );
elseif type( list ) == 'string' then
v, origin[k] = args[list], list;
else
v, origin[k] = selectone( args, list, 'redundant_parameters' );
if origin[k] == nil then
origin[k] = ''; --Empty string, not nil;
end
end
if v == nil then
v = cfg.defaults[k] or "";
origin[k] = ''; --Empty string, not nil;
end
tbl = rawset( tbl, k, v );
return v;
end,
});
end
-- Checks that parameter name is valid using the whitelist
function validate( name )
name = tostring( name );
-- Normal arguments
if whitelist.basic_arguments[ name ] then
return true;
end
-- Arguments with numbers in them
name = name:gsub( "%d+", "#" );
if whitelist.numbered_arguments[ name ] then
return true;
end
-- Not found, argument not supported.
return false
end
-- Formats a comment for error trapping
function errorcomment( content, hidden )
return wrap( hidden and 'hidden-error' or 'visible-error', content );
end
--[[
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.
]]
function seterror( 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 inArray( 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 errorcomment( message, error_state.hidden );
end
-- Formats a wiki style external link
function externallinkid(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]',
options.link, options.label, options.separator or " ",
options.prefix, url_string, options.suffix or "",
mw.text.nowiki(options.id)
);
end
-- Formats a wiki style internal link
function internallinkid(options)
return mw.ustring.format( '%s%s%s',
options.link, options.label, options.separator or " ",
options.prefix, options.id, options.suffix or "",
mw.text.nowiki(options.id)
);
end
-- Format an external link with error checking
function externallink( URL, label, source )
local error_str = "";
if not is_set( label ) then
label = URL;
if is_set( source ) then
error_str = seterror( 'bare_url_missing_title', { wrap( 'parameter', source ) }, false, " " );
else
error( cfg.messages["bare_url_no_origin"] );
end
end
if not checkurl( URL ) then
error_str = seterror( 'bad_url', {}, false, " " ) .. error_str;
end
return table.concat({ "[", URL, " ", safeforurl( label ), "]", error_str });
end
-- Formats a link to Amazon
function amazon(id, domain)
if not is_set(domain) then
domain = "com"
elseif ( "jp" == domain or "uk" == domain ) then
domain = "co." .. domain
end
local handler = cfg.id_handlers['ASIN'];
return externallinkid({link = handler.link,
label=handler.label , prefix="//www.amazon."..domain.."/dp/",id=id,
encode=handler.encode, separator = handler.separator})
end
-- Formats a DOI and checks for DOI errors.
function doi(id, inactive)
local cat = ""
local handler = cfg.id_handlers['DOI'];
local text;
if is_set(inactive) then
text = "" .. handler.label .. ":" .. id;
table.insert( z.error_categories, "Pages with DOIs inactive since " .. selectyear(inactive) );
inactive = " (" .. cfg.messages['inactive'] .. " " .. inactive .. ")"
else
text = externallinkid({link = handler.link, label = handler.label,
prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode})
inactive = ""
end
if ( string.sub(id,1,3) ~= "10." ) then
cat = seterror( 'bad_doi' );
end
return text .. inactive .. cat
end
-- Formats an OpenLibrary link, and checks for associated errors.
function openlibrary(id)
local code = id:sub(-1,-1)
local handler = cfg.id_handlers['OL'];
if ( code == "A" ) then
return externallinkid({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 externallinkid({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 externallinkid({link=handler.link, label=handler.label,
prefix= "http://openlibrary.org/works/OL",id=id, separator=handler.separator,
encode = handler.encode})
else
return externallinkid({link=handler.link, label=handler.label,
prefix= "http://openlibrary.org/OL",id=id, separator=handler.separator,
encode = handler.encode}) ..
' ' .. seterror( 'bad_ol' );
end
end
--[[
Determines whether an URL string is valid
At present the only check is whether the string appears to
be prefixed with a URI scheme. It is not determined whether
the URI scheme is valid or whether the URL is otherwise well
formed.
]]
function checkurl( url_str )
-- Protocol-relative or URL scheme
return url_str:sub(1,2) == "//" or url_str:match( "^[^/]*:" ) ~= nil;
end
-- Removes irrelevant text and dashes from ISBN number
-- Similar to that used for Special:BookSources
function cleanisbn( isbn_str )
return isbn_str:gsub( "[^-0-9X]", "" );
end
-- Determines whether an ISBN string is valid
function checkisbn( isbn_str )
isbn_str = cleanisbn( isbn_str ):gsub( "-", "" );
local len = isbn_str:len();
if len ~= 10 and len ~= 13 then
return false;
end
local temp = 0;
if len == 10 then
if isbn_str:match( "^%d*X?$" ) == nil then return false; end
isbn_str = { isbn_str:byte(1, len) };
for i, v in ipairs( isbn_str ) do
if v == string.byte( "X" ) then
temp = temp + 10*( 11 - i );
else
temp = temp + tonumber( string.char(v) )*(11-i);
end
end
return temp % 11 == 0;
else
if isbn_str:match( "^%d*$" ) == nil then return false; end
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
-- Gets the display text for a wikilink like B or B gives B
function removewikilink( str )
return (str:gsub( "%[%[([^%[%]]*)%]%]", function(l)
return l:gsub( "^[^|]*|(.*)$", "%1" ):gsub("^%s*(.-)%s*$", "%1");
end));
end
-- Escape sequences for content that will be used for URL descriptions
function safeforurl( str )
do return str end -- LOCAL HACK https://en.wikipedia.org/w/index.php?title=Module_talk:Citation/CS1&diff=552318417&oldid=552202448
if str:match( "%[%[.-%]%]" ) ~= nil then
table.insert( z.message_tail, { seterror( 'wikilink_in_url', {}, true ) } );
end
return str:gsub( '[%[%]\n]', {
['['] = '[',
[']'] = ']',
['\n'] = ' ' } );
end
-- Converts a hyphen to a dash
function hyphentodash( str )
if not is_set(str) or str:match( "[%[%]{}<>]" ) ~= nil then
return str;
end
return str:gsub( '-', '–' );
end
-- Protects a string that will be wrapped in wiki italic markup ...
function safeforitalics( str )
--[[ Note: We can not use 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, and ''
tend to interact poorly under Mediawiki's HTML tidy. ]]
if not is_set(str) then
return str;
else
if str:sub(1,1) == "'" then str = "" .. str; end
if str:sub(-1,-1) == "'" then str = str .. ""; end
-- Remove newlines as they break italics.
return str:gsub( '\n', ' ' );
end
end
--[[
Joins a sequence of strings together while checking for duplicate separation
characters.
]]
function safejoin( tbl, duplicate_char )
--[[
Note: we use string functions here, rather than ustring functions.
This has considerably faster performance and should work correctly as
long as the duplicate_char is strict ASCII. The strings
in tbl may be ASCII or UTF8.
]]
local str = '';
local comp = '';
local end_chr = '';
local trim;
for _, value in ipairs( tbl ) do
if value == nil then value = ''; end
if str == '' then
str = value;
elseif value ~= '' then
if value:sub(1,1) == '<' then
-- Special case of values enclosed in spans and other markup.
comp = value:gsub( "%b<>", "" );
else
comp = value;
end
if comp:sub(1,1) == duplicate_char then
trim = false;
end_chr = str:sub(-1,-1);
-- str = str .. " if end_chr == duplicate_char then str = str:sub(1,-2); elseif end_chr == "'" then if str:sub(-3,-1) == duplicate_char .. "''" then str = str:sub(1, -4) .. "''"; elseif str:sub(-5,-1) == duplicate_char .. "]]''" then trim = true; elseif str:sub(-4,-1) == duplicate_char .. "]''" then trim = true; end elseif end_chr == "]" then if str:sub(-3,-1) == duplicate_char .. "]]" then trim = true; elseif str:sub(-2,-1) == duplicate_char .. "]" then trim = true; end elseif end_chr == " " then if str:sub(-2,-1) == duplicate_char .. " " then str = str:sub(1,-3); end end if trim then if value ~= comp then local dup2 = duplicate_char; if dup2:match( "%A" ) then dup2 = "%" .. dup2; end value = value:gsub( "(%b<>)" .. dup2, "%1", 1 ) else value = value:sub( 2, -1 ); end end end str = str .. value; end end return str; end --[[ Return the year portion of a date string, if possible. Returns empty string if the argument can not be interpreted as a year. ]] function selectyear( str ) -- Is the input a simple number? local num = tonumber( str ); if num ~= nil and num > 0 and num < 2100 and num == math.floor(num) then return str; else -- Use formatDate to interpret more complicated formats local lang = mw.getContentLanguage(); local good, result; good, result = pcall( lang.formatDate, lang, 'Y', str ) if good then return result; else -- Can't make sense of this input, return blank. return ""; end end end -- Attempts to convert names to initials. function reducetoinitials(first) local initials = {} for word in string.gmatch(first, "%S+") do table.insert(initials, string.sub(word,1,1)) -- Vancouver format does not include full stops. end return table.concat(initials) -- Vancouver format does not include spaces. end -- Formats a list of people (e.g. authors / editors) function listpeople(control, people) local sep = control.sep; local namesep = control.namesep local format = control.format local maximum = control.maximum local lastauthoramp = control.lastauthoramp; local text = {} local etal = false; if sep:sub(-1,-1) ~= " " then sep = sep .. " " end if maximum ~= nil and maximum < 1 then return "", 0; end for i,person in ipairs(people) do if is_set(person.last) then local mask = person.mask local one local sep_one = sep; if maximum ~= nil and i > maximum then etal = true; break; elseif (mask ~= nil) then local n = tonumber(mask) if (n ~= nil) then one = string.rep("—",n) else one = mask; sep_one = " "; end else one = person.last local first = person.first if is_set(first) then if ( "vanc" == format ) then first = reducetoinitials(first) end one = one .. namesep .. first end if is_set(person.link) then one = "" .. one .. "" end end table.insert( text, one ) table.insert( text, sep_one ) end 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) -- construct list if etal then local etal_text = is_zh( result ) and cfg.messages['et al'] or 'et al.'; -- LOCAL result = result .. " " .. etal_text; end -- if necessary wrap result in tag to format in Small Caps if ( "scap" == format ) then result = '' .. result .. ''; end return result, count end -- Generates a CITEREF anchor ID. function anchorid( options ) return "CITEREF" .. table.concat( options ); end -- Gets name list from the input arguments function extractnames(args, list_name) local names = {}; local i = 1; local last; while true do last = selectone( args, cfg.aliases[list_name .. '-Last'], 'redundant_parameters', i ); if not is_set(last) then -- just in case someone passed in an empty parameter break; end names[i] = { last = last, first = selectone( args, cfg.aliases[list_name .. '-First'], 'redundant_parameters', i ), link = selectone( args, cfg.aliases[list_name .. '-Link'], 'redundant_parameters', i ), mask = selectone( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i ) }; i = i + 1; end return names; end -- Populates ID table from arguments using configuration settings function extractids( args ) local id_list = {}; for k, v in pairs( cfg.id_handlers ) do v = selectone( args, v.parameters, 'redundant_parameters' ); if is_set(v) then id_list[k] = v; end end return id_list; end -- Takes a table of IDs and turns it into a table of formatted ID outputs. function buildidlist( 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, externallinkid( handler ) } ); elseif handler.mode == 'internal' then table.insert( new_list, {handler.label, internallinkid( 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 == 'ASIN' then table.insert( new_list, {handler.label, amazon( v, options.ASINTLD ) } ); elseif k == 'OL' then table.insert( new_list, {handler.label, openlibrary( v ) } ); elseif k == 'ISBN' then local ISBN = internallinkid( handler ); if not checkisbn( v ) and not is_set(options.IgnoreISBN) then ISBN = ISBN .. seterror( 'bad_isbn', {}, false, " ", "" ); end table.insert( new_list, {handler.label, ISBN } ); else error( cfg.messages['unknown_manual_ID'] ); end end function comp( a, b ) 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 -- Chooses one matching parameter from a list of parameters to consider -- Generates an error if more than one match is present. function selectone( args, possible, error_condition, index ) local value = nil; local selected = ''; local error_list = {}; if index ~= nil then index = tostring(index); end -- Handle special case of "#" replaced by empty string if index == '1' then for _, v in ipairs( possible ) do v = v:gsub( "#", "" ); if 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(args[v]) then if value ~= nil and selected ~= v then table.insert( error_list, v ); else value = args[v]; selected = v; end end end if #error_list > 0 then local error_str = ""; for _, k in ipairs( error_list ) do if error_str ~= "" then error_str = error_str .. cfg.messages['parameter-separator'] end error_str = error_str .. wrap( 'parameter', k ); end if #error_list > 1 then error_str = error_str .. cfg.messages['parameter-final-separator']; else error_str = error_str .. cfg.messages['parameter-pair-separator']; end error_str = error_str .. wrap( 'parameter', selected ); table.insert( z.message_tail, { seterror( error_condition, {error_str}, true ) } ); end return value, selected; end -- COinS metadata (see -- the citation information. function COinS(data) 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( removewikilink( 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.btitle"] = data.Chapter; OCinSoutput["rft.atitle"] = data.Title; elseif is_set(data.Periodical) then OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:journal"; OCinSoutput["rft.genre"] = "article"; 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 = cleanisbn( 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 --[[ This is the main function foing the majority of the citation formatting. ]] function citation0( config, args, frame ) -- LOCAL --[[ Load Input Parameters The argment_wrapper facillitates the mapping of multiple aliases to single internal variable. ]] local A = argument_wrapper( args ); local i local PPrefix = A['PPrefix'] local PPPrefix = A['PPPrefix'] if 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 local PSuffix = A['PSuffix'] local PPSuffix = A['PPSuffix'] if ( nil ~= A['NoPP'] ) then PPSuffix = "" PSuffix = "" end -- END LOCAL -- 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 a = extractnames( args, 'AuthorList' ); local Coauthors = A['Coauthors']; local Others = A['Others']; local Editors = A['Editors']; local e = extractnames( args, 'EditorList' ); local Year = A['Year']; local PublicationDate = A['PublicationDate']; local OrigYear = A['OrigYear']; local Date = A['Date']; local LayDate = A['LayDate']; ------------------------------------------------- Get title data local Title = A['Title']; local BookTitle = A['BookTitle']; local Conference = A['Conference']; local TransTitle = A['TransTitle']; local TitleNote = A['TitleNote']; local TitleLink = A['TitleLink']; local Chapter = A['Chapter']; local ChapterLink = A['ChapterLink']; local TransChapter = A['TransChapter']; local TitleType = A['TitleType']; local ArchiveURL = A['ArchiveURL']; local URL = A['URL'] local URLorigin = A:ORIGIN('URL'); local ChapterURL = A['ChapterURL']; local ChapterURLorigin = A:ORIGIN('ChapterURL'); local ConferenceURL = A['ConferenceURL']; local ConferenceURLorigin = A:ORIGIN('ConferenceURL'); local Periodical = A['Periodical']; if ( config.CitationClass == "encyclopaedia" ) then if not is_set(Chapter) then if not is_set(Title) then Title = Periodical; Periodical = ''; else Chapter = Title TransChapter = TransTitle Title = ''; TransTitle = ''; end end end local Series = A['Series']; local Volume = A['Volume']; local Issue = A['Issue']; local Position = ''; local Page, Pages, At, page_type; Page = A['Page']; Pages = hyphentodash( A['Pages'] ); At = A['At']; if is_set(Page) then if is_set(Pages) or is_set(At) then Page = Page .. " " .. seterror('extra_pages'); Pages = ''; At = ''; end elseif is_set(Pages) then if is_set(At) then Pages = Pages .. " " .. seterror('extra_pages'); At = ''; end end local Edition = A['Edition']; local PublicationPlace = A['PublicationPlace'] local Place = A['Place']; if not is_set(PublicationPlace) and is_set(Place) then PublicationPlace = Place; end if PublicationPlace == Place then Place = ''; end local PublisherName = A['PublisherName']; local SubscriptionRequired = A['SubscriptionRequired']; local Via = A['Via']; local AccessDate = A['AccessDate']; local ArchiveDate = A['ArchiveDate']; local Agency = A['Agency']; local DeadURL = A['DeadURL'] local Language = A['Language']; local Format = A['Format']; local Ref = A['Ref']; local DoiBroken = A['DoiBroken']; local ID = A['ID']; local ASINTLD = A['ASINTLD']; local IgnoreISBN = A['IgnoreISBN']; local ID_list = extractids( args ); local Quote = A['Quote']; local PostScript = A['PostScript']; local LayURL = A['LayURL']; local LaySource = A['LaySource']; local Transcript = A['Transcript']; local TranscriptURL = A['TranscriptURL'] local TranscriptURLorigin = A:ORIGIN('TranscriptURL'); local sepc = A['Separator']; local LastAuthorAmp = A['LastAuthorAmp']; local no_tracking_cats = A['NoTracking']; local this_page = mw.title.getCurrentTitle(); --Also used for COinS if not is_set(no_tracking_cats) then for k, v in pairs( cfg.uncategorized_namespaces ) do if this_page.nsText == v then no_tracking_cats = "true"; break; end end end if ( config.CitationClass == "journal" ) then if not is_set(URL) and is_set(ID_list['PMC']) then local Embargo = A['Embargo']; if is_set(Embargo) then local lang = mw.getContentLanguage(); local good1, result1, good2, result2; good1, result1 = pcall( lang.formatDate, lang, 'U', Embargo ); good2, result2 = pcall( lang.formatDate, lang, 'U' ); if good1 and good2 and tonumber( result1 ) < tonumber( result2 ) then URL = "http://www.ncbi.nlm.nih.gov/pmc/articles/PMC" .. ID_list['PMC']; URLorigin = cfg.id_handlers['PMC'].parameters[1]; end else URL = "http://www.ncbi.nlm.nih.gov/pmc/articles/PMC" .. ID_list['PMC']; URLorigin = cfg.id_handlers['PMC'].parameters[1]; end end end -- At this point fields may be nil if they weren't specified in the template use. We can use that fact. -- Account for the oddity that is {{cite conference}}, before generation of COinS data. if is_set(BookTitle) then Chapter = Title; ChapterLink = TitleLink; TransChapter = TransTitle; Title = BookTitle; TitleLink = ''; TransTitle = ''; end -- Account for the oddity that is {{cite episode}}, before generation of COinS data. if config.CitationClass == "episode" then local AirDate = A['AirDate']; local SeriesLink = A['SeriesLink']; local Season = A['Season']; local SeriesNumber = A['SeriesNumber']; local Network = A['Network']; local Station = A['Station']; local s, n = {}, {}; local Sep = (first_set(A["SeriesSeparator"], A["Separator"]) or "") .. " "; if is_set(Issue) then table.insert(s, cfg.messages["episode"] .. " " .. Issue); Issue = ''; end if is_set(Season) then table.insert(s, cfg.messages["season"] .. " " .. Season); end if is_set(SeriesNumber) then table.insert(s, cfg.messages["series"] .. " " .. SeriesNumber); end if is_set(Network) then table.insert(n, Network); end if is_set(Station) then table.insert(n, Station); end Date = Date or AirDate; Chapter = Title; ChapterLink = TitleLink; TransChapter = TransTitle; Title = Series; TitleLink = SeriesLink; TransTitle = ''; Series = table.concat(s, Sep); ID = table.concat(n, Sep); end -- COinS metadata (see -- automated parsing of citation information. local OCinSoutput = COinS{ ['Periodical'] = Periodical, ['Chapter'] = Chapter, ['Title'] = Title, ['PublicationPlace'] = PublicationPlace, ['Date'] = first_set(Date, Year, PublicationDate), ['Series'] = Series, ['Volume'] = Volume, ['Issue'] = Issue, ['Pages'] = first_set(Page, Pages, At), ['Edition'] = Edition, ['PublisherName'] = PublisherName, ['URL'] = first_set( URL, ChapterURL ), ['Authors'] = a, ['ID_list'] = ID_list, ['RawPage'] = this_page.prefixedText, }; if is_set(Periodical) and not is_set(Chapter) and is_set(Title) then Chapter = Title; ChapterLink = TitleLink; TransChapter = TransTitle; Title = ''; TitleLink = ''; TransTitle = ''; end -- Now perform various field substitutions. -- We also add leading spaces and surrounding markup and punctuation to the -- various parts of the citation, but only when they are non-nil. if not is_set(Authors) then local Maximum = tonumber( A['DisplayAuthors'] ); -- Preserve old-style implicit et al. if not is_set(Maximum) and #a == 9 then Maximum = 8; table.insert( z.message_tail, { seterror('implict_etal_author', {}, true ) } ); elseif not is_set(Maximum) then Maximum = #a + 1; end local control = { sep = A["AuthorSeparator"] .. " ", namesep = (first_set(A["AuthorNameSeparator"], A["NameSeparator"]) or "") .. " ", format = A["AuthorFormat"], maximum = Maximum, lastauthoramp = LastAuthorAmp }; -- 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 Authors = listpeople(control, a) end local EditorCount if not is_set(Editors) then local Maximum = tonumber( A['DisplayEditors'] ); -- Preserve old-style implicit et al. if not is_set(Maximum) and #e == 4 then Maximum = 3; table.insert( z.message_tail, { seterror('implict_etal_editor', {}, true) } ); elseif not is_set(Maximum) then Maximum = #e + 1; end local control = { sep = A["EditorSeparator"] .. " ", namesep = (first_set(A["EditorNameSeparator"], A["NameSeparator"]) or "") .. " ", format = A['EditorFormat'], maximum = Maximum, lastauthoramp = LastAuthorAmp }; Editors, EditorCount = listpeople(control, e); else EditorCount = 1; end if not is_set(Date) then Date = Year; if is_set(Date) then local Month = A['Month']; if is_set(Month) then Date = Date .. '.' .. Month; -- LOCAL local Day = A['Day'] if is_set(Day) then Date = Date .. '.' .. Month end -- LOCAL end end end if inArray(PublicationDate, {Date, Year}) then PublicationDate = ''; end if not is_set(Date) and is_set(PublicationDate) then Date = PublicationDate; PublicationDate = ''; end -- Captures the value for Date prior to adding parens or other textual transformations local DateIn = Date; if not is_set(URL) and not is_set(ChapterURL) and not is_set(ArchiveURL) and not is_set(ConferenceURL) and not is_set(TranscriptURL) then -- Test if cite web is called without giving a URL if ( config.CitationClass == "web" ) then table.insert( z.message_tail, { seterror( 'cite_web_url', {}, true ) } ); end -- Test if accessdate is given without giving a URL if is_set(AccessDate) then table.insert( z.message_tail, { seterror( 'accessdate_missing_url', {}, true ) } ); AccessDate = ''; end -- Test if format is given without giving a URL if is_set(Format) then Format = Format .. seterror( 'format_missing_url' ); end end -- Test if citation has no title if not is_set(Chapter) and not is_set(Title) and not is_set(Periodical) and not is_set(Conference) and not is_set(TransTitle) and not is_set(TransChapter) then table.insert( z.message_tail, { seterror( 'citation_missing_title', {}, true ) } ); end Format = is_set(Format) and " (" .. Format .. ")" or ""; local OriginalURL = URL DeadURL = DeadURL:lower(); if is_set( ArchiveURL ) then if ( DeadURL ~= "no" ) then URL = ArchiveURL URLorigin = A:ORIGIN('ArchiveURL') end end -- Format chapter / article title -- LOCAL if not is_set(Language) or Language:lower() == 'zh' or Language:sub( 1, 3 ):lower() == 'zh-' then xLCFlags = 'zh;zh-hans;zh-hant' else xLCFlags = 'R' end if is_set(Chapter) then Chapter = '-{' .. xLCFlags .. '|' .. Chapter .. '}-' end if is_set(Title) then Title = '-{' .. xLCFlags .. '|' .. Title .. '}-' end -- END LOCAL if is_set(Chapter) and is_set(ChapterLink) then Chapter = "" .. Chapter .. ""; end if is_set(Periodical) and is_set(Title) then Chapter = wrap( 'italic-title', Chapter ); TransChapter = wrap( 'trans-italic-title', TransChapter ); else Chapter = wrap( 'quoted-title', Chapter ); TransChapter = wrap( 'trans-quoted-title', TransChapter ); end local TransError = "" if is_set(TransChapter) then if not is_set(Chapter) then TransError = " " .. seterror( 'trans_missing_chapter' ); else TransChapter = " " .. TransChapter; end end Chapter = Chapter .. TransChapter; if is_set(Chapter) then if not is_set(ChapterLink) then if is_set(ChapterURL) then Chapter = externallink( ChapterURL, Chapter ) .. TransError; if not is_set(URL) then Chapter = Chapter .. Format; Format = ""; end elseif is_set(URL) then Chapter = externallink( URL, Chapter ) .. TransError .. Format; URL = ""; Format = ""; else Chapter = Chapter .. TransError; end elseif is_set(ChapterURL) then Chapter = Chapter .. " " .. externallink( ChapterURL, nil, ChapterURLorigin ) .. TransError; else Chapter = Chapter .. TransError; end if is_set(Title) then -- LOCAL Chapter = Chapter .. '//' -- LOCAL else -- LOCAL Chapter = Chapter .. sepc .. " " -- with end-space end -- LOCAL elseif is_set(ChapterURL) then Chapter = " " .. externallink( ChapterURL, nil, ChapterURLorigin ) .. sepc .. " "; end -- Format main title. if is_set(TitleLink) and is_set(Title) then Title = "" .. Title .. "" end if is_set(Periodical) then Title = wrap( 'quoted-title', Title ); TransTitle = wrap( 'trans-quoted-title', TransTitle ); elseif inArray(config.CitationClass, {"web","news","pressrelease"}) and not is_set(Chapter) then Title = wrap( 'quoted-title', Title ); TransTitle = wrap( 'trans-quoted-title', TransTitle ); else Title = wrap( 'italic-title', Title ); TransTitle = wrap( 'trans-italic-title', TransTitle ); end TransError = ""; if is_set(TransTitle) then if not is_set(Title) then TransError = " " .. seterror( 'trans_missing_title' ); else TransTitle = " " .. TransTitle; end end Title = Title .. TransTitle; if is_set(Title) then if not is_set(TitleLink) and is_set(URL) then Title = externallink( URL, Title ) .. TransError .. Format URL = ""; Format = ""; else Title = Title .. TransError; end end if is_set(Place) then if sepc == '.' then Place = " " .. wrap( 'written', Place ) .. sepc .. " "; else Place = " " .. substitute( cfg.messages['written']:lower(), {Place} ) .. sepc .. " "; end end if is_set(Conference) then if is_set(ConferenceURL) then Conference = externallink( ConferenceURL, Conference ); end Conference = " " .. Conference elseif is_set(ConferenceURL) then Conference = " " .. externallink( ConferenceURL, nil, ConferenceURLorigin ); end 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'] if not is_set(TimeCaption) then TimeCaption = cfg.messages['event']; if sepc ~= '.' then TimeCaption = TimeCaption:lower(); end end Position = " " .. TimeCaption .. " " .. Time; end end else Position = " " .. Position; At = ''; end if not is_set(Page) then if is_set(Pages) then if is_set(Periodical) and not inArray(config.CitationClass, {"encyclopaedia","web","book","news"}) then Pages = ": " .. Pages; elseif config.CitationClass == "news" then -- LOCAL Pages = ": (" .. Pages .. ")" -- LOCAL elseif config.CitationClass == "book" then -- LOCAL Pages = ": " .. Pages -- LOCAL elseif tonumber(Pages) ~= nil then Pages = sepc .." " .. PPrefix .. Pages .. PSuffix; -- LOCAL else Pages = sepc .." " .. PPPrefix .. Pages .. PPSuffix; -- LOCAL end end else if is_set(Periodical) and not inArray(config.CitationClass, {"encyclopaedia","web","book","news"}) then Page = ": " .. Page; else if config.CitationClass == "news" then -- LOCAL Page = ": (" .. Page .. ")" -- LOCAL elseif config.CitationClass == "news" then -- LOCAL Page = ": " .. Page -- LOCAL else -- LOCAL Page = sepc .." " .. PPrefix .. Page .. PSuffix; -- LOCAL end -- LOCAL end end At = is_set(At) and (sepc .. " " .. At) or ""; Others = is_set(Others) and (sepc .. " " .. Others) or ""; TitleType = is_set(TitleType) and (", " .. TitleType) or ""; -- LOCAL TitleNote = is_set(TitleNote) and (sepc .. " " .. TitleNote) or ""; -- LOCAL if is_set(Language) then local langNameInLang = mw.getContentLanguage():getCode() local languageCode = Language:lower() if languageCode:sub( 1, 3 ) == 'zh-' then langNameInLang = languageCode end local languageName = mw.language.fetchLanguageName( languageCode, langNameInLang ) if languageName == '' then languageName = Language end Language = " " .. wrap( 'language', languageName ) else Language = "" end -- END LOCAL Edition = is_set(Edition) and (" " .. wrap( 'edition', Edition )) 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 = " " .. hyphentodash(Volume) .. ""; end end ------------------------------------ totally unrelated data if is_set(Via) then Via = " " .. wrap( 'via', Via ); end if is_set(AccessDate) then local retrv_text = " " .. cfg.messages['retrieved'] if (sepc ~= ".") then retrv_text = retrv_text:lower() end AccessDate = '' -- LOCAL .. sepc .. substitute( retrv_text, {AccessDate} ) .. '' end if is_set(SubscriptionRequired) then SubscriptionRequired = sepc .. " " .. cfg.messages['subscription']; end if is_set(ID) then ID = sepc .." ".. ID; end ID_list = buildidlist( ID_list, {DoiBroken = DoiBroken, ASINTLD = ASINTLD, IgnoreISBN = IgnoreISBN} ); if is_set(URL) then URL = " " .. externallink( URL, nil, URLorigin ); end 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( 'quoted-text', Quote ); PostScript = ""; elseif PostScript:lower() == "none" then PostScript = ""; end local Archived if is_set(ArchiveURL) then if not is_set(ArchiveDate) then ArchiveDate = seterror('archive_missing_date'); end if "no" == DeadURL then local arch_text = cfg.messages['archived']; if sepc ~= "." then arch_text = arch_text:lower() end Archived = substitute( cfg.messages['archived-not-dead'], { externallink( ArchiveURL, arch_text ), ArchiveDate } ); if not is_set(OriginalURL) then Archived = Archived .. " " .. seterror('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, { externallink( OriginalURL, cfg.messages['original'] ), ArchiveDate } ); else local arch_text = cfg.messages['archived-missing']; if sepc ~= "." then arch_text = arch_text:lower() end Archived = substitute( arch_text, { seterror('archive_missing_url'), ArchiveDate } ); -- LOCAL end else Archived = "" end local Lay if is_set(LayURL) then if is_set(LayDate) then LayDate = " (" .. LayDate .. ")" end if is_set(LaySource) then LaySource = " – " .. safeforitalics(LaySource) .. ""; else LaySource = ""; end if sepc == '.' then Lay = sepc .. " " .. externallink( LayURL, cfg.messages['lay summary'] ) .. LaySource .. LayDate else Lay = sepc .. " " .. externallink( LayURL, cfg.messages['lay summary']:lower() ) .. LaySource .. LayDate end else Lay = ""; end if is_set(Transcript) then if is_set(TranscriptURL) then Transcript = externallink( TranscriptURL, Transcript ); end elseif is_set(TranscriptURL) then Transcript = externallink( TranscriptURL, nil, TranscriptURLorigin ); end local Publisher; if is_set(Periodical) and not inArray(config.CitationClass, {"encyclopaedia","web","pressrelease"}) then if is_set(PublisherName) then if is_set(PublicationPlace) then Publisher = PublicationPlace .. ": " .. PublisherName; else Publisher = PublisherName; end elseif is_set(PublicationPlace) then Publisher= PublicationPlace; else Publisher = ""; end --[[ LOCAL if is_set(PublicationDate) then if is_set(Publisher) then Publisher = Publisher .. ", " .. wrap( 'published', PublicationDate ); else Publisher = PublicationDate; end end ]] if is_set(Publisher) then Publisher = " (" .. Publisher .. ")"; end else --[[ LOCAL if is_set(PublicationDate) then PublicationDate = " (" .. wrap( 'published', PublicationDate ) .. ")"; end ]] if is_set(PublisherName) then if is_set(PublicationPlace) then Publisher = sepc .. " " .. PublicationPlace .. ": " .. PublisherName -- LOCAL .. PublicationDate; else Publisher = sepc .. " " .. PublisherName -- LOCAL .. PublicationDate; end elseif is_set(PublicationPlace) then Publisher= sepc .. " " .. PublicationPlace -- LOCAL .. PublicationDate; else Publisher = '' -- LOCAL .. PublicationDate; end end -- Several of the above rely upon detecting this as nil, so do it last. if is_set(Periodical) then if is_set(Title) or is_set(TitleNote) then Periodical = sepc .. " " .. wrap( 'italic-title', Periodical ) else Periodical = wrap( 'italic-title', Periodical ) end end -- Piece all bits together at last. Here, all should be non-nil. -- We build things this way because it is more efficient in LUA -- not to keep reassigning to the same string variable over and over. local tcommon if inArray(config.CitationClass, {"journal","citation"}) and is_set(Periodical) then if is_set(Others) then Others = Others .. sepc .. " " end tcommon = safejoin( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Edition, Publisher, Agency, Place, Position}, sepc ); -- LOCAL else tcommon = safejoin( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Volume, Issue, Others, Edition, Publisher, Agency, Place, Position}, sepc ); -- LOCAL end if #ID_list > 0 then ID_list = safejoin( { sepc .. " ", table.concat( ID_list, sepc .. " " ), ID }, sepc ); else ID_list = ID; end -- LOCAL local xDate = Date local pgtext = Page .. Pages .. At if ( is_set(Periodical) and Date ~= '' and not inArray(config.CitationClass, {"encyclopaedia","web"}) ) or ( inArray(config.CitationClass, {"book","news"}) ) then if inArray(config.CitationClass, {"journal","citation"}) and ( Volume ~= or Issue ~= ) then xDate = xDate .. ',' .. Volume .. Issue end xDate = xDate .. pgtext pgtext = '' end if PublicationDate and PublicationDate ~= '' then xDate = xDate .. ' (' .. PublicationDate .. ')' end if OrigYear ~= '' then xDate = xDate .. OrigYear end if AccessDate ~= '' then xDate = xDate .. ' ' .. AccessDate end if xDate ~= '' then xDate = sepc .. ' ' .. xDate end -- END LOCAL local idcommon = safejoin( { URL, xDate, ID_list, Archived, Via, SubscriptionRequired, Lay, Language, Quote }, sepc ); -- LOCAL local text; -- LOCAL local pgtext = Page .. Pages .. At; if is_set(Authors) then if is_set(Coauthors) then Authors = Authors .. A['AuthorSeparator'] .. " " .. Coauthors end --[[ LOCAL if is_set(Date) then Date = " ("..Date..")" .. OrigYear .. sepc .. " " else]]if string.sub(Authors,-1,-1) == sepc then Authors = Authors .. " " else Authors = Authors .. sepc .. " " end if is_set(Editors) then local ed_is_zh = is_zh( Editors ) -- LOCAL local in_text = ed_is_zh and '' or "In " -- LOCAL if (sepc ~= '.') then in_text = in_text:lower() end if (string.sub(Editors,-1,-1) == sepc) or ed_is_zh -- LOCAL then Editors = in_text .. Editors .. " " else Editors = in_text .. Editors .. sepc .. " " end -- LOCAL if ed_is_zh then Editors = Editors .. ' ' .. cfg.messages['in'] .. sepc .. ' ' end -- END LOCAL end text = safejoin( {Authors, Chapter, Editors, tcommon }, sepc ); -- LOCAL text = safejoin( {text, pgtext, idcommon}, sepc ); elseif is_set(Editors) then --[[ LOCAL if is_set(Date) then if EditorCount <= 1 then Editors = Editors .. ", " .. cfg.messages['editor']; else Editors = Editors .. ", " .. cfg.messages['editors']; end Date = " (" .. Date ..")" .. OrigYear .. sepc .. " " else ]] if EditorCount <= 1 then Editors = Editors .. " (" .. cfg.messages['editor'] .. ")" .. sepc .. " " else Editors = Editors .. " (" .. cfg.messages['editors'] .. ")" .. sepc .. " " end --[[ LOCAL end ]] text = safejoin( {Chapter, Editors, tcommon}, sepc ); -- LOCAL text = safejoin( {text, pgtext, idcommon}, sepc ); else --[[ LOCAL 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 = safejoin( {Chapter, Place, tcommon}, sepc ); text = safejoin( {text, pgtext, idcommon}, sepc ); -- LOCAL else text = safejoin( {Chapter, tcommon}, sepc ); -- LOCAL text = safejoin( {text, pgtext, idcommon}, sepc ); end end if is_set(PostScript) and PostScript ~= sepc then text = safejoin( {text, sepc}, sepc ); --Deals with italics, spaces, etc. text = text:sub(1,-2); --Remove final seperator end text = safejoin( {text, PostScript}, sepc ); -- Now enclose the whole thing in a element if not is_set(Year) then if is_set(DateIn) then Year = selectyear( DateIn ); elseif is_set(PublicationDate) then Year = selectyear( PublicationDate ); end end 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 ( "harv" == Ref ) then local names = {} --table of last names & year if is_set(Authors) then for i,v in ipairs(a) do names[i] = v.last if i == 4 then break end end elseif is_set(Editors) then for i,v in ipairs(e) do names[i] = v.last if i == 4 then break end end end names[ #names + 1 ] = Year; id = anchorid(names) end options.id = id; end if string.len(text:gsub("/]*>.-", ""):gsub("%b<>","")) <= 2 then z.error_categories = {}; text = seterror('empty_citation'); z.message_tail = {}; end if is_set(options.id) then text = ' else text = ' end local empty_span = ' -- Note: Using display: none on then COinS span breaks some clients. local OCinS = '' .. empty_span .. ''; text = text .. OCinS; if #z.message_tail ~= 0 then text = text .. " "; for i,v in ipairs( z.message_tail ) do if is_set(v[1]) then if i == #z.message_tail then text = text .. errorcomment( v[1], v[2] ); else text = text .. errorcomment( v[1] .. "; ", v[2] ); end end end end no_tracking_cats = no_tracking_cats:lower(); if inArray(no_tracking_cats, {"", "no", "false", "n"}) then for _, v in ipairs( z.error_categories ) do text = text .. 'Category:' .. v ..''; end end return text 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 args = {}; local suggestions = {}; local error_text, error_state; local config = {}; for k, v in pairs( frame.args ) do config[k] = v; args[k] = v; end for k, v in pairs( pframe.args ) do if v ~= '' then if not validate( k ) then error_text = ""; if type( k ) ~= 'string' then -- Exclude empty numbered parameters if v:match("%S+") ~= nil then error_text, error_state = seterror( 'text_ignored', {v}, true ); end elseif validate( k:lower() ) then error_text, error_state = seterror( '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 = seterror( 'parameter_ignored_suggest', {k, suggestions[ k:lower() ]}, true ); else error_text, error_state = seterror( 'parameter_ignored', {k}, true ); end end if error_text ~= '' then table.insert( z.message_tail, {error_text, error_state} ); end end args[k] = v; elseif args[k] ~= nil or (k == 'postscript') then args[k] = v; end end return citation0( config, args, frame ) -- LOCAL end return z