Misplaced Pages

Module:Citation/CS1: Difference between revisions

Article snapshot taken from[REDACTED] with creative commons attribution-sharealike license. Give it a read and then ask your questions in the chat. We can research this topic together.
Browse history interactively← Previous editNext edit →Content deleted Content added
Revision as of 23:07, 19 April 2013 view sourceDragons flight (talk | contribs)Edit filter managers, Extended confirmed users, Rollbackers, Template editors25,792 edits sync with sandbox, merge cite_web_title to bare_url, better style control on translations, category suppression by config, tweak display authors← Previous edit Revision as of 18:42, 25 April 2013 view source Dragons flight (talk | contribs)Edit filter managers, Extended confirmed users, Rollbackers, Template editors25,792 edits sync to sandbox, mostly code cleaning / organization. Also, ref = none, layurl as alias for laysummary, tweaks to COinS output, and changes to bareurl error.Next edit →
Line 4: Line 4:
message_tail = {}; message_tail = {};
} }

local SEEN = {};
local DATA = {};


-- Include translation message hooks, ID and error handling configuration settings. -- Include translation message hooks, ID and error handling configuration settings.
-- Note that require has tested to be significantly faster than loadData for this
local cfg = require( 'Module:Citation/CS1/Configuration' );
-- usage. This might be a side effect of the unnecessary cloning described
-- in bugzilla 47300.
local cfg = require( 'Module:Citation/CS1/Configuration/sandbox' );

-- Contains a list of all recognized parameters -- Contains a list of all recognized parameters
local whitelist = mw.loadData( 'Module:Citation/CS1/Whitelist' ); local whitelist = mw.loadData( 'Module:Citation/CS1/Whitelist' );


-- Whether variable is set or not
-- Populates numbered arguments in a message string using
function is_set( var )
-- an argument table.
return not (var == nil or var == '');
function substitute( message, arguments )
end
if arguments == nil then

return message;
-- 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
end

message = message .. " ";
-- Whether needle is in haystack
for k, v in ipairs( arguments ) do
function inArray( needle, haystack )
v = v:gsub( "%%", "%%%%" );
if needle == nil then
message = message:gsub( "$" .. k .. "(%D)", v .. "%1" );
end return false;
end
message = message:sub(1,-2);
for n,v in ipairs( haystack ) do
return message;
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 end


-- Wraps a string using a message_list configuration taking one argument -- Wraps a string using a message_list configuration taking one argument
function wrap( message_key, str ) function wrap( key, str )
if str == nil or str == "" then if not is_set( str ) then
return ""; return "";
elseif inArray( key, { 'italic-title', 'trans-italic-title' } ) then
end
if message_key == 'italic-title' or
message_key == 'trans-italic-title' then
str = safeforitalics( str ); str = safeforitalics( str );
end end
return substitute( cfg.message_list, {str} ); return substitute( cfg.messages, {str} );
end end


Line 48: Line 63:
]] ]]
function argument_wrapper( args ) function argument_wrapper( args )
DATA = args; local origin = {};
local tbl = {};
local mt = { return setmetatable({
__index = function ( tbl, k ) ORIGIN = function( self, k )
if SEEN then local dummy = self; --force the variable to be loaded.
return origin;
end
},
{
__index = function ( tbl, k )
if origin ~= nil then
return nil; return nil;
end end
local list = cfg.argument_map; local args, list, v = args, cfg.aliases;

if list == nil then if list == nil then
error( cfg.message_list ); error( cfg.messages );
elseif type( list ) == 'string' then elseif type( list ) == 'string' then
v = DATA; v, origin = args, list;
else else
v = selectone( DATA, cfg.argument_map, v, origin = selectone( args, list, 'redundant_parameters' );
'redundant_parameters' ); if origin == nil then
origin = ''; --Empty string, not nil;
end
end end
if v == nil then if v == nil then
v = cfg.default_values; v = cfg.defaults or "";
origin = ''; --Empty string, not nil;
end end
SEEN = true;
tbl = rawset( tbl, k, v );
tbl = rawset( tbl, k, v );
return v; return v;
end, end,
} });
return setmetatable( tbl, mt );
end end


Line 100: Line 122:
-- Formats a comment for error trapping -- Formats a comment for error trapping
function errorcomment( content, hidden ) function errorcomment( content, hidden )
return wrap( hidden and 'hidden-error' or 'visible-error', content );
if hidden then
return wrap( 'hidden-error', content );
else
return wrap( 'visible-error', content );
end
end end


Line 113: Line 131:
function seterror( error_id, arguments, raw, prefix, suffix ) function seterror( error_id, arguments, raw, prefix, suffix )
local error_state = cfg.error_conditions; local error_state = cfg.error_conditions;
prefix = prefix or ""; prefix = prefix or "";
suffix = suffix or ""; suffix = suffix or "";

if error_state == nil then
error( cfg.message_list );
end
if error_state.category ~= nil and error_state.category ~= "" then if error_state == nil then
error( cfg.messages );
elseif is_set( error_state.category ) then
table.insert( z.error_categories, error_state.category ); table.insert( z.error_categories, error_state.category );
end end
local message = error_state.message; local message = substitute( error_state.message, arguments );
message = substitute( message, arguments );
message = message .. " ( ..

message = wikiescape(message) .. " ( ..
"#" .. error_state.anchor .. "|" .. "#" .. error_state.anchor .. "|" ..
cfg.message_list .. "]])"; cfg.messages .. "]])";

z.error_ids = true; z.error_ids = true;
if (error_id == 'bare_url_missing_title' or error_id == 'trans_missing_title') if inArray( error_id, { 'bare_url_missing_title', 'trans_missing_title' } )
and z.error_ids then and z.error_ids then
return '', false; return '', false;
end end
message = prefix .. message .. suffix; message = table.concat({ prefix, message, suffix });
if raw == true then if raw == true then
Line 144: Line 160:
return errorcomment( message, error_state.hidden ); return errorcomment( message, error_state.hidden );
end

-- This returns a string with HTML character entities for wikitext markup characters.
function wikiescape(text)
text = text:gsub( '{|}]', {
= '&',
= ''',
= '[',
'] = ']',
= '{',
= '|',
= '}' } );
return text;
end end


-- Formats a wiki style external link -- Formats a wiki style external link
function externallinkid(options) function externallinkid(options)
local sep = options.separator or " " local url_string = options.id;
options.suffix = options.suffix or ""
local url_string = options.id
if options.encode == true or options.encode == nil then if options.encode == true or options.encode == nil then
url_string = mw.uri.encode( url_string ); url_string = mw.uri.encode( url_string );
end end
return mw.ustring.format( ']%s',
return "]" .. sep .. "[" .. options.link, options.label, options.separator or " ",
options.prefix .. url_string .. options.suffix .. " " .. mw.text.nowiki(options.id) .. "]" options.prefix, url_string, options.suffix or "",
mw.text.nowiki(options.id)
);
end end


-- Formats a wiki style internal link -- Formats a wiki style internal link
function internallinkid(options) function internallinkid(options)
return mw.ustring.format( ']%s]',
local sep = options.separator or " "
options.suffix = options.suffix or "" options.link, options.label, options.separator or " ",
return "]" .. sep .. "[[" .. options.prefix, options.id, options.suffix or "",
options.prefix .. options.id .. options.suffix .. "|" .. mw.text.nowiki(options.id) .. "]]" mw.text.nowiki(options.id)
);
end end


-- Format an external link with error checking -- Format an external link with error checking
function externallink( URL, label ) function externallink( URL, label, source )
local error_str = ""; local error_str = "";
if label == nil or label == "" then if not is_set( label ) then
label = URL; label = URL;
if is_set( source ) then
error_str = seterror( 'bare_url_missing_title', {}, false, " " );
error_str = seterror( 'bare_url_missing_title', { wrap( 'parameter', source ) }, false, " " );
else
error( cfg.messages );
end
end end
if not checkurl( URL ) then if not checkurl( URL ) then
error_str = seterror( 'bad_url', {}, false, " " ) .. error_str; error_str = seterror( 'bad_url', {}, false, " " ) .. error_str;
end end
return table.concat({ "", error_str });

return "" .. error_str;
end end


-- Formats a link to Amazon -- Formats a link to Amazon
function amazon(id, domain) function amazon(id, domain)
if ( nil == domain ) then if not is_set(domain) then
domain = "com" domain = "com"
elseif ( "jp" == domain or "uk" == domain ) then elseif ( "jp" == domain or "uk" == domain ) then
Line 213: Line 220:
local text; local text;
if ( inactive ~= nil ) then if is_set(inactive) then
text = "]:" .. id; text = "]:" .. id;
table.insert( z.error_categories, "Pages with DOIs inactive since " .. selectyear(inactive) ); table.insert( z.error_categories, "Pages with DOIs inactive since " .. selectyear(inactive) );
inactive = " (" .. cfg.message_list .. " " .. inactive .. ")" inactive = " (" .. cfg.messages .. " " .. inactive .. ")"
else else
text = externallinkid({link = handler.link, label = handler.label, text = externallinkid({link = handler.link, label = handler.label,
Line 261: Line 268:
]] ]]
function checkurl( url_str ) function checkurl( url_str )
-- Protocol-relative or URL scheme
if url_str:sub(1,2) == "//" then
return url_str:sub(1,2) == "//" or url_str:match( "^*:" ) ~= nil;
-- Protocol-less URLs
return true;
elseif url_str:match( "^*:" ) ~= nil then
-- Look for ":" prefix and assume it is a URI scheme
return true;
else
-- Anything else is an error
return false;
end
end end


Line 312: Line 311:
-- Gets the display text for a wikilink like ] or ] gives B -- Gets the display text for a wikilink like ] or ] gives B
function removewikilink( str ) function removewikilink( str )
str = str:gsub( "%]*|(]*)%]%]", "%1" ); return (str:gsub( "%]*)%]%]", function(l)
str = str:gsub( "%]*)%]%]", "%1" ); return l:gsub( "^*|(.*)$", "%1" ):gsub("^%s*(.-)%s*$", "%1");
return str end));
end end


Line 324: Line 323:
return str:gsub( '\n]', { return str:gsub( '\n]', {
= '[', = '[',
'] = ']', '] = ']',
= ' ' } ); = ' ' } );
end end
Line 331: Line 330:
-- Converts a hyphen to a dash -- Converts a hyphen to a dash
function hyphentodash( str ) function hyphentodash( str )
if str == nil then if not is_set(str) or str:match( "{}<>]" ) ~= nil then
return nil;
end
if str:match( "{}<>]" ) ~= nil then
return str; return str;
end end
Line 347: Line 343:
tend to interact poorly under Mediawiki's HTML tidy. ]] tend to interact poorly under Mediawiki's HTML tidy. ]]
if str == nil or str == '' then if not is_set(str) then
return str; return str;
else else
Line 439: Line 435:
-- Is the input a simple number? -- Is the input a simple number?
local num = tonumber( str ); local num = tonumber( str );
if num ~= nil and num > 0 and num < 2100 and num == math.abs(num) then if num ~= nil and num > 0 and num < 2100 and num == math.floor(num) then
return str; return str;
else else
Line 467: Line 463:
function listpeople(control, people) function listpeople(control, people)
local sep = control.sep; local sep = control.sep;
if sep:sub(-1,-1) ~= " " then sep = sep .. " " end
local namesep = control.namesep local namesep = control.namesep
local format = control.format local format = control.format
Line 474: Line 469:
local text = {} local text = {}
local etal = false; local etal = false;
if maximum < 1 then return "", 0; end
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 for i,person in ipairs(people) do
if (person.last ~= nil or person.last ~= "") then if is_set(person.last) then
local mask = person.mask local mask = person.mask
local one local one
local sep_one = sep; local sep_one = sep;
if ( maximum ~= nil and i > maximum ) then if maximum ~= nil and i > maximum then
etal = true; etal = true;
break; break;
Line 494: Line 492:
one = person.last one = person.last
local first = person.first local first = person.first
if (first ~= nil and first ~= '') then if is_set(first) then
if ( "vanc" == format ) then first = reducetoinitials(first) end if ( "vanc" == format ) then first = reducetoinitials(first) end
one = one .. namesep .. first one = one .. namesep .. first
end end
if (person.link ~= nil and person.link ~= "") then one = "]" end if is_set(person.link) then one = "]" end
end end
table.insert( text, one ) table.insert( text, one )
Line 507: Line 505:
local count = #text / 2; local count = #text / 2;
if count > 0 then if count > 0 then
if count > 1 and lastauthoramp ~= nil and lastauthoramp ~= "" and not etal then if count > 1 and is_set(lastauthoramp) and not etal then
text = " & "; text = " & ";
end end
Line 515: Line 513:
local result = table.concat(text) -- construct list local result = table.concat(text) -- construct list
if etal then if etal then
local etal_text = cfg.message_list; local etal_text = cfg.messages;
result = result .. " " .. etal_text; result = result .. " " .. etal_text;
end end
Line 528: Line 526:
-- Generates a CITEREF anchor ID. -- Generates a CITEREF anchor ID.
function anchorid( options ) function anchorid( options )
return "CITEREF" .. mw.uri.anchorEncode( table.concat( options ) ); return "CITEREF" .. table.concat( options );
end end


Line 538: Line 536:
while true do while true do
last = selectone( args, cfg.argument_map, 'redundant_parameters', i ); last = selectone( args, cfg.aliases, 'redundant_parameters', i );
if ( last and "" < last ) then -- just in case someone passed in an empty parameter if not is_set(last) then
-- just in case someone passed in an empty parameter
names = {
last = last,
first = selectone( args, cfg.argument_map, 'redundant_parameters', i ),
link = selectone( args, cfg.argument_map, 'redundant_parameters', i ),
mask = selectone( args, cfg.argument_map, 'redundant_parameters', i )
}
else
break; break;
end end
names = {
last = last,
first = selectone( args, cfg.aliases, 'redundant_parameters', i ),
link = selectone( args, cfg.aliases, 'redundant_parameters', i ),
mask = selectone( args, cfg.aliases, 'redundant_parameters', i )
};
i = i + 1; i = i + 1;
end end
Line 557: Line 555:
function extractids( args ) function extractids( args )
local id_list = {}; local id_list = {};
for k, v in pairs( cfg.id_handlers ) do for k, v in pairs( cfg.id_handlers ) do
id_list = selectone( args, v.parameters, 'redundant_parameters' ); v = selectone( args, v.parameters, 'redundant_parameters' );
if is_set(v) then id_list = v; end
end end

return id_list; return id_list;
end end
Line 567: Line 564:
-- Takes a table of IDs and turns it into a table of formatted ID outputs. -- Takes a table of IDs and turns it into a table of formatted ID outputs.
function buildidlist( id_list, options ) function buildidlist( id_list, options )
local handler; local new_list, handler = {};
local new_list = {};
function fallback(k) return { __index = function(t,i) return cfg.id_handlers end } end;
for k, v in pairs( id_list ) do for k, v in pairs( id_list ) do
handler = {}; -- fallback to read-only cfg
handler = setmetatable( { = v }, fallback(k) );
if handler.mode == 'external' then
--Becasue cfg is read-only we have to copy it the hard way.
for k2, v2 in pairs( cfg.id_handlers ) do
handler = v2;
end
handler = v;
if handler.mode == 'external' then
table.insert( new_list, {handler.label, externallinkid( handler ) } ); table.insert( new_list, {handler.label, externallinkid( handler ) } );
elseif handler.mode == 'internal' then elseif handler.mode == 'internal' then
table.insert( new_list, {handler.label, internallinkid( handler ) } ); table.insert( new_list, {handler.label, internallinkid( handler ) } );
elseif handler.mode == 'manual' then elseif handler.mode ~= 'manual' then
if k == 'DOI' then error( cfg.messages );
table.insert( new_list, {handler.label, doi( v, options.DoiBroken ) } ); elseif k == 'DOI' then
table.insert( new_list, {handler.label, doi( v, options.DoiBroken ) } );
elseif k == 'ASIN' then elseif k == 'ASIN' then
table.insert( new_list, {handler.label, amazon( v, options.ASINTLD ) } ); table.insert( new_list, {handler.label, amazon( v, options.ASINTLD ) } );
elseif k == 'OL' then elseif k == 'OL' then
table.insert( new_list, {handler.label, openlibrary( v ) } ); table.insert( new_list, {handler.label, openlibrary( v ) } );
elseif k == 'ISBN' then elseif k == 'ISBN' then
local ISBN = internallinkid( handler ); local ISBN = internallinkid( handler );
if not checkisbn( v ) and ( options.IgnoreISBN == nil or options.IgnoreISBN == "" ) then if not checkisbn( v ) and not is_set(options.IgnoreISBN) then
ISBN = ISBN .. seterror( 'bad_isbn', {}, false, " ", "" ); ISBN = ISBN .. seterror( 'bad_isbn', {}, false, " ", "" );
end end
table.insert( new_list, {handler.label, ISBN } ); table.insert( new_list, {handler.label, ISBN } );
else
error( cfg.message_list );
end
else else
error( cfg.message_list ); error( cfg.messages );
end end
end end

function comp( a, b ) function comp( a, b )
return a < b; return a < b;
end end

table.sort( new_list, comp ); table.sort( new_list, comp );
for k, v in ipairs( new_list ) do for k, v in ipairs( new_list ) do
Line 622: Line 613:
local selected = ''; local selected = '';
local error_list = {}; local error_list = {};
if index ~= nil then index = tostring(index); end if index ~= nil then index = tostring(index); end
Line 628: Line 620:
for _, v in ipairs( possible ) do for _, v in ipairs( possible ) do
v = v:gsub( "#", "" ); v = v:gsub( "#", "" );
if args ~= nil then if is_set(args) then
if value ~= nil and selected ~= v then if value ~= nil and selected ~= v then
table.insert( error_list, v ); table.insert( error_list, v );
Line 638: Line 630:
end end
end end

for _, v in ipairs( possible ) do for _, v in ipairs( possible ) do
if index ~= nil then if index ~= nil then
v = v:gsub( "#", index ); v = v:gsub( "#", index );
end end
if args ~= nil then if is_set(args) then
if value ~= nil then if value ~= nil and selected ~= v then
table.insert( error_list, v ); table.insert( error_list, v );
else else
Line 652: Line 644:
end end
end end

if #error_list > 0 then if #error_list > 0 then
local error_str = ""; local error_str = "";
for _, k in ipairs( error_list ) do for _, k in ipairs( error_list ) do
if error_str ~= "" then error_str = error_str .. ", " end if error_str ~= "" then error_str = error_str .. cfg.messages end
error_str = error_str .. "<code>|" .. k .. "=</code>"; error_str = error_str .. wrap( 'parameter', k );
end end
if #error_list > 1 then if #error_list > 1 then
error_str = error_str .. ", and "; error_str = error_str .. cfg.messages;
else else
error_str = error_str .. " and "; error_str = error_str .. cfg.messages;
end end
error_str = error_str .. "<code>|" .. selected .. "=</code>"; error_str = error_str .. wrap( 'parameter', selected );
table.insert( z.message_tail, { seterror( error_condition, {error_str}, true ) } ); table.insert( z.message_tail, { seterror( error_condition, {error_str}, true ) } );
end end
return value, selected; return value, selected;
end

-- COinS metadata (see <http://ocoins.info/>) allows automated tools to parse
-- 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 = "bookitem";
OCinSoutput = data.Chapter;
OCinSoutput = data.Title;
elseif is_set(data.Periodical) then
OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:journal";
OCinSoutput = "article";
OCinSoutput = data.Periodical;
OCinSoutput = data.Title;
else
OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:book";
OCinSoutput = "book"
OCinSoutput = data.Title;
end
OCinSoutput = data.PublicationPlace;
OCinSoutput = data.Date;
OCinSoutput = data.Series;
OCinSoutput = data.Volume;
OCinSoutput = data.Issue;
OCinSoutput = data.Pages;
OCinSoutput = data.Edition;
OCinSoutput = data.PublisherName;
for k, v in pairs( data.ID_list ) do
local id, value = cfg.id_handlers.COinS;
if k == 'ISBN' then value = cleanisbn( v ); else value = v; end
if string.sub( id or "", 1, 4 ) == 'info' then
OCinSoutput = table.concat{ id, "/", v };
else
OCinSoutput = 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 = last;
end
if is_set(first) then
OCinSoutput = first;
end
end
if is_set(last) and is_set(first) then
OCinSoutput = table.concat{ last, ", ", first };
elseif is_set(last) then
OCinSoutput = 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 end


Line 686: Line 759:
local PPrefix = A local PPrefix = A
local PPPrefix = A local PPPrefix = A
if ( nil ~= A ) then PPPrefix = "" PPrefix = "" end if is_set( A ) then PPPrefix = "" PPrefix = "" end
-- Pick out the relevant fields from the arguments. Different citation templates -- Pick out the relevant fields from the arguments. Different citation templates
Line 715: Line 788:
local TitleType = A; local TitleType = A;
local ArchiveURL = A; local ArchiveURL = A;
local URL = A; local URL = A
local URLorigin = A:ORIGIN('URL');
local ChapterURL = A; local ChapterURL = A;
local ChapterURLorigin = A:ORIGIN('ChapterURL');
local ConferenceURL = A; local ConferenceURL = A;
local ConferenceURLorigin = A:ORIGIN('ConferenceURL');
local Periodical = A; local Periodical = A;
if ( config.CitationClass == "encyclopaedia" ) then if ( config.CitationClass == "encyclopaedia" ) then
if ( Chapter == nil or Chapter == '' ) then if not is_set(Chapter) then
if (Title == nil or Title == "") then if not is_set(Title) then
Title = Periodical; Title = Periodical;
Periodical = nil; Periodical = '';
else else
Chapter = Title Chapter = Title
TransChapter = TransTitle TransChapter = TransTitle
Title = nil Title = '';
TransTitle = nil TransTitle = '';
end end
end end
Line 737: Line 813:
local Volume = A; local Volume = A;
local Issue = A; local Issue = A;
local Position = nil local Position = '';
local Page, Pages, At, page_type; local Page, Pages, At, page_type;
Line 743: Line 819:
Pages = hyphentodash( A ); Pages = hyphentodash( A );
At = A; At = A;
if Page ~= nil then
if Pages ~= nil or At ~= nil then if is_set(Page) then
if is_set(Pages) or is_set(At) then
Page = Page .. " " .. seterror('extra_pages'); Page = Page .. " " .. seterror('extra_pages');
Pages = nil; Pages = '';
At = nil; At = '';
end end
elseif Pages ~= nil then elseif is_set(Pages) then
if At ~= nil then if is_set(At) then
Pages = Pages .. " " .. seterror('extra_pages'); Pages = Pages .. " " .. seterror('extra_pages');
At = nil; At = '';
end end
end end
local Edition = A; local Edition = A;
local PublicationPlace = A local PublicationPlace = A
local Place = A; local Place = A;
if PublicationPlace == nil and Place ~= nil then
if not is_set(PublicationPlace) and is_set(Place) then
PublicationPlace = Place; PublicationPlace = Place;
end end
if PublicationPlace == Place then Place = nil end
if PublicationPlace == Place then Place = ''; end
local PublisherName = A; local PublisherName = A;
Line 772: Line 851:
local DeadURL = A local DeadURL = A
local Language = A; local Language = A;
local Format = A local Format = A;
local Ref = A local Ref = A;

local DoiBroken = A local DoiBroken = A;
local ID = A; local ID = A;
local ASINTLD = A; local ASINTLD = A;
local IgnoreISBN = A local IgnoreISBN = A;


local ID_list = extractids( args ); local ID_list = extractids( args );
local Quote = A; local Quote = A;
local PostScript = A local PostScript = A;
local LaySummary = A local LayURL = A;
local LaySource = A; local LaySource = A;
local Transcript = A; local Transcript = A;
local TranscriptURL = A; local TranscriptURL = A
local sepc = A local TranscriptURLorigin = A:ORIGIN('TranscriptURL');
local LastAuthorAmp = A local sepc = A;
local no_tracking_cats = A or ""; local LastAuthorAmp = A;
local no_tracking_cats = A;


local this_page = mw.title.getCurrentTitle(); --Also used for COinS local this_page = mw.title.getCurrentTitle(); --Also used for COinS
if no_tracking_cats == "" then
if not is_set(no_tracking_cats) then
for k, v in pairs( cfg.uncategorized_namespaces ) do for k, v in pairs( cfg.uncategorized_namespaces ) do
if this_page.nsText == v then if this_page.nsText == v then
Line 802: Line 883:
end end


if ( config.CitationClass == "journal" ) then if ( config.CitationClass == "journal" ) then
if (URL == nil or URL == "") then if not is_set(URL) and is_set(ID_list) then
if (ID_list ~= nil) then local Embargo = A;
local Embargo = A; if is_set(Embargo) then
if Embargo ~= nil then local lang = mw.getContentLanguage();
local lang = mw.getContentLanguage(); local good1, result1, good2, result2;
local good1, result1, good2, result2; good1, result1 = pcall( lang.formatDate, lang, 'U', Embargo );
good1, result1 = pcall( lang.formatDate, lang, 'U', Embargo ); good2, result2 = pcall( lang.formatDate, lang, 'U' );
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;
if good1 and good2 and tonumber( result1 ) < tonumber( result2 ) then
URL = "http://www.ncbi.nlm.nih.gov/pmc/articles/PMC" .. ID_list; URLorigin = cfg.id_handlers.parameters;
end
else
URL = "http://www.ncbi.nlm.nih.gov/pmc/articles/PMC" .. ID_list;
end end
else
URL = "http://www.ncbi.nlm.nih.gov/pmc/articles/PMC" .. ID_list;
URLorigin = cfg.id_handlers.parameters;
end end
end end
Line 825: Line 906:
-- Account for the oddity that is {{cite conference}}, before generation of COinS data. -- Account for the oddity that is {{cite conference}}, before generation of COinS data.
if ( BookTitle ) then if is_set(BookTitle) then
Chapter = Title Chapter = Title;
ChapterLink = TitleLink ChapterLink = TitleLink;
TransChapter = TransTitle TransChapter = TransTitle;
Title = BookTitle Title = BookTitle;
TitleLink = nil TitleLink = '';
TransTitle = nil TransTitle = '';
end end
-- Account for the oddity that is {{cite episode}}, before generation of COinS data. -- Account for the oddity that is {{cite episode}}, before generation of COinS data.
if config.CitationClass == "episode" then if config.CitationClass == "episode" then
local AirDate = A local AirDate = A;
local SeriesLink = A local SeriesLink = A;
local Season = A local Season = A;
local SeriesNumber = A local SeriesNumber = A;
local Network = A local Network = A;
local Station = A local Station = A;
local s = {} local s, n = {}, {};
local Sep = (first_set(A, A) or "") .. " ";
if Issue ~= nil then table.insert(s, cfg.message_list .. " " .. Issue) Issue = nil end
if Season ~= nil then table.insert(s, cfg.message_list .. " " .. Season) end
if SeriesNumber ~= nil then table.insert(s, cfg.message_list .. " " .. SeriesNumber) end if is_set(Issue) then table.insert(s, cfg.messages .. " " .. Issue); Issue = ''; end
if is_set(Season) then table.insert(s, cfg.messages .. " " .. Season); end
local n = {}
if Network ~= nil then table.insert(n, Network) end if is_set(SeriesNumber) then table.insert(s, cfg.messages .. " " .. SeriesNumber); end
if Station ~= nil then table.insert(n, Station) 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 Date = Date or AirDate;
TransChapter = TransTitle Chapter = Title;
Title = Series ChapterLink = TitleLink;
TitleLink = SeriesLink TransChapter = TransTitle;
TransTitle = nil Title = Series;
TitleLink = SeriesLink;
local Sep = (A or A) .. " "
Series = table.concat(s, Sep) TransTitle = '';
ID = table.concat(n, Sep)
Series = table.concat(s, Sep);
ID = table.concat(n, Sep);
end end
-- These data form a COinS tag (see <http://ocoins.info/>) which allows -- COinS metadata (see <http://ocoins.info/>) for
-- automated tools to parse the citation information. -- automated parsing of citation information.
local OCinSoutput = COinS{
local OCinSdata = {} -- COinS metadata excluding id, bibcode, doi, etc.
= Periodical,
local ctx_ver = "Z39.88-2004"
= Chapter,
OCinSdata.rft_val_fmt = "info:ofi/fmt:kev:mtx:book"
if ( nil ~= Periodical ) then = Title,
= PublicationPlace,
OCinSdata.rft_val_fmt = "info:ofi/fmt:kev:mtx:journal"
= first_set(Date, Year, PublicationDate),
OCinSdata = "article"
OCinSdata = Periodical = Series,
= Volume,
if ( nil ~= Title ) then OCinSdata = Title end
= Issue,
end
= first_set(Page, Pages, At),
if ( nil ~= Chapter and "" ~= Chapter) then
= Edition,
OCinSdata.rft_val_fmt = "info:ofi/fmt:kev:mtx:book"
OCinSdata = "bookitem" = PublisherName,
OCinSdata = Chapter = first_set( URL, ChapterURL ),
= a,
if ( nil ~= Title ) then OCinSdata = Title end
= ID_list,
else
OCinSdata = "book" = this_page.prefixedText,
};
if ( nil ~= Title ) then OCinSdata = Title end
end
OCinSdata = PublicationPlace
OCinSdata = Date or Year or PublicationDate
OCinSdata = Series
OCinSdata = Volume
OCinSdata = Issue
OCinSdata = Page or Pages or At
OCinSdata = Edition
OCinSdata = PublisherName
for k, v in pairs( ID_list ) do
if k == 'ISBN' then
v = cleanisbn( v );
end
if string.sub( cfg.id_handlers.COinS or "info", 1, 4 ) ~= 'info' then
OCinSdata.COinS ] = v;
end
end
OCinSdata.rft_id = URL or ChapterURL


if is_set(Periodical) and not is_set(Chapter) and is_set(Title) then
local last, first;
local OCinSauthors = {}; Chapter = Title;
for k, v in ipairs( a ) do ChapterLink = TitleLink;
last = v.last; TransChapter = TransTitle;
first = v.first; Title = '';
if k == 1 then TitleLink = '';
if last ~= nil then TransTitle = '';
OCinSdata = last;
end
if first ~= nil then
OCinSdata = first;
end
end
if last ~= nil and first ~= nil then
table.insert( OCinSauthors, last .. ", " .. first );
elseif last ~= nil then
table.insert( OCinSauthors, last );
end
end

local OCinSids = {} -- COinS data only for id, bibcode, doi, pmid, etc.
for k, v in pairs( ID_list ) do
if string.sub( cfg.id_handlers.COinS or "", 1, 4 ) == 'info' then
OCinSids.COinS ] = v;
end
end

local OCinStitle = "ctx_ver=" .. ctx_ver -- such as "Z39.88-2004"
for name,value in pairs(OCinSdata) do
OCinStitle = OCinStitle .. "&" .. name .. "=" .. mw.uri.encode( removewikilink(value) );
end
for _, value in ipairs(OCinSauthors) do
OCinStitle = OCinStitle .. "&rft.au=" .. mw.uri.encode( removewikilink(value) );
end
for name,value in pairs(OCinSids) do
OCinStitle = OCinStitle .. "&rft_id=" .. mw.uri.encode(name .. "/" .. removewikilink(value) );
end
OCinStitle = OCinStitle .. "&rfr_id=info:sid/" .. mw.site.server:match( "*$" ) .. ":"
.. this_page.prefixedText -- end COinS data by page's non-encoded pagename

if (Periodical ~= nil and Periodical ~= "") and
(Chapter == nil or Chapter == '') and
(Title ~= nil and Title ~= "") then
Chapter = Title
ChapterLink = TitleLink
TransChapter = TransTitle
Title = nil
TitleLink = nil
TransTitle = nil
end end


Line 955: Line 975:
-- We also add leading spaces and surrounding markup and punctuation to the -- We also add leading spaces and surrounding markup and punctuation to the
-- various parts of the citation, but only when they are non-nil. -- various parts of the citation, but only when they are non-nil.
if ( Authors == nil ) then if not is_set(Authors) then
local Maximum = tonumber( A ); local Maximum = tonumber( A );
-- Preserve old-style implicit et al. -- Preserve old-style implicit et al.
if Maximum == nil and #a == 9 then if not is_set(Maximum) and #a == 9 then
Maximum = 8; Maximum = 8;
table.insert( z.message_tail, { seterror('implict_etal_author', {}, true ) } ); table.insert( z.message_tail, { seterror('implict_etal_author', {}, true ) } );
elseif Maximum == nil then elseif not is_set(Maximum) then
Maximum = #a + 1; Maximum = #a + 1;
end end
Line 968: Line 988:
local control = { local control = {
sep = A .. " ", sep = A .. " ",
namesep = (A or A) .. " ", namesep = (first_set(A, A) or "") .. " ",
format = A, format = A,
maximum = Maximum, maximum = Maximum,
lastauthoramp = LastAuthorAmp lastauthoramp = LastAuthorAmp
} };
-- If the coauthor field is also used, prevent ampersand and et al. formatting. -- If the coauthor field is also used, prevent ampersand and et al. formatting.
if Coauthors ~= nil and Coauthors ~= "" then if is_set(Coauthors) then
control.lastauthoramp = nil; control.lastauthoramp = nil;
control.maximum = #a + 1; control.maximum = #a + 1;
end end
Authors = listpeople(control, a) Authors = listpeople(control, a)
end end
local EditorCount local EditorCount
if ( Editors == nil ) then if not is_set(Editors) then
local Maximum = tonumber( A ); local Maximum = tonumber( A );

-- Preserve old-style implicit et al. -- Preserve old-style implicit et al.
if Maximum == nil and #e == 4 then if not is_set(Maximum) and #e == 4 then
Maximum = 3; Maximum = 3;
table.insert( z.message_tail, { seterror('implict_etal_editor', {}, true) } ); table.insert( z.message_tail, { seterror('implict_etal_editor', {}, true) } );
elseif Maximum == nil then elseif not is_set(Maximum) then
Maximum = #e + 1; Maximum = #e + 1;
end end
Line 996: Line 1,016:
local control = { local control = {
sep = A .. " ", sep = A .. " ",
namesep = (A or A) .. " ", namesep = (first_set(A, A) or "") .. " ",
format = A, format = A,
maximum = Maximum, maximum = Maximum,
lastauthoramp = LastAuthorAmp lastauthoramp = LastAuthorAmp
} };


Editors, EditorCount = listpeople(control, e) Editors, EditorCount = listpeople(control, e);
else else
EditorCount = 1; EditorCount = 1;
end end
if ( Date == nil or Date == "") then
if not is_set(Date) then
-- there's something hinky with how this adds dashes to perfectly-good free-standing years
--[[ Date = Year Date = Year;
if ( Date ~= nil ) then if is_set(Date) then
local Month = args.month local Month = A;
if ( Month == nil ) then if is_set(Month) then
local Began = args.began Date = Month .. " " .. Date;
local Ended = args.ended
if Began ~= nil and Ended ~= nil then
Month = Began .. "&ndash;" .. Ended
else
Month = "&ndash;"
end
end
Date = Month .. " " .. Date
local Day = args.day
if ( Day ~= nil ) then Date = Day .. " " .. Date end
end
]] -- so let's use the original version for now
Date = Year
if ( Date ~= nil and Date ~="") then
local Month = A
if ( Month ~= nil and Month ~= "") then
Date = Month .. " " .. Date
local Day = A local Day = A
if ( Day ~= nil ) then Date = Day .. " " .. Date end if is_set(Day) then Date = Day .. " " .. Date end
else Month = ""
end end
else Date = ""
end end
end end
if ( PublicationDate == Date or PublicationDate == Year ) then PublicationDate = nil end
if( (Date == nil or Date == "") and PublicationDate ~= nil ) then if inArray(PublicationDate, {Date, Year}) then PublicationDate = ''; end
if not is_set(Date) and is_set(PublicationDate) then
Date = PublicationDate; Date = PublicationDate;
PublicationDate = nil; PublicationDate = '';
end end


-- Captures the value for Date prior to adding parens or other textual transformations -- Captures the value for Date prior to adding parens or other textual transformations
local DateIn = Date local DateIn = Date;
if ( URL == nil or URL == '' ) and if not is_set(URL) and
( ChapterURL == nil or ChapterURL == '' ) and not is_set(ChapterURL) and
( ArchiveURL == nil or ArchiveURL == '' ) and not is_set(ArchiveURL) and
( ConferenceURL == nil or ConferenceURL == '' ) and not is_set(ConferenceURL) and
( TranscriptURL == nil or TranscriptURL == '' ) then not is_set(TranscriptURL) then

-- Test if cite web is called without giving a URL -- Test if cite web is called without giving a URL
if ( config.CitationClass == "web" ) then if ( config.CitationClass == "web" ) then
table.insert( z.message_tail, { seterror( 'cite_web_url', {}, true ) } ); table.insert( z.message_tail, { seterror( 'cite_web_url', {}, true ) } );
end end

-- Test if accessdate is given without giving a URL -- Test if accessdate is given without giving a URL
if ( AccessDate ~= nil and AccessDate ~= '' ) then if is_set(AccessDate) then
table.insert( z.message_tail, { seterror( 'accessdate_missing_url', {}, true ) } ); table.insert( z.message_tail, { seterror( 'accessdate_missing_url', {}, true ) } );
AccessDate = nil; AccessDate = '';
end end
-- Test if format is given without giving a URL -- Test if format is given without giving a URL
if ( Format ~= nil and Format ~= '' ) then if is_set(Format) then
Format = Format .. seterror( 'format_missing_url' ); Format = Format .. seterror( 'format_missing_url' );
end end
end
end

-- Test if citation has no title -- Test if citation has no title
if ( Chapter == nil or Chapter == "" ) and if not is_set(Chapter) and
( Title == nil or Title == "" ) and not is_set(Title) and
( Periodical == nil or Periodical == "" ) and not is_set(Periodical) and
( Conference == nil or Conference == "" ) and not is_set(Conference) and
( TransTitle == nil or TransTitle == "" ) and not is_set(TransTitle) and
( TransChapter == nil or TransChapter == "" ) then not is_set(TransChapter) then
table.insert( z.message_tail, { seterror( 'citation_missing_title', {}, true ) } ); table.insert( z.message_tail, { seterror( 'citation_missing_title', {}, true ) } );
end end

if ( Format ~= nil and Format ~="" ) then Format = is_set(Format) and " (" .. Format .. ")" or "";
Format = " (" .. Format .. ")" else Format = "" end
local OriginalURL = URL local OriginalURL = URL
Line 1,089: Line 1,090:
end end
end end

-- Format chapter / article title -- Format chapter / article title
if ( Chapter ~= nil and Chapter ~= "" ) and ( ChapterLink and "" < ChapterLink ) then if is_set(Chapter) and is_set(ChapterLink) then
Chapter = "]"; Chapter = "]";
end end
if ( Periodical and "" < Periodical ) and (Title ~= nil and Title ~= "" ) then if is_set(Periodical) and is_set(Title) then
Chapter = wrap( 'italic-title', Chapter ); Chapter = wrap( 'italic-title', Chapter );
TransChapter = wrap( 'trans-italic-title', TransChapter ); TransChapter = wrap( 'trans-italic-title', TransChapter );
Line 1,103: Line 1,104:
local TransError = "" local TransError = ""
if TransChapter ~= "" and Chapter == "" then if is_set(TransChapter) then
TransError = " " .. seterror( 'trans_missing_chapter' ); if not is_set(Chapter) then
TransError = " " .. seterror( 'trans_missing_chapter' );
else
TransChapter = " " .. TransChapter;
end
end end
if TransChapter ~= "" and Chapter ~= "" then TransChapter = " " .. TransChapter; end Chapter = Chapter .. TransChapter;
Chapter = Chapter .. TransChapter
if Chapter ~= "" then if is_set(Chapter) then
if ( ChapterLink == nil ) then if not is_set(ChapterLink) then
if ( ChapterURL and "" < ChapterURL ) then if is_set(ChapterURL) then
Chapter = externallink( ChapterURL, Chapter ) .. TransError; Chapter = externallink( ChapterURL, Chapter ) .. TransError;
if URL == nil or URL == "" then if not is_set(URL) then
Chapter = Chapter .. Format; Chapter = Chapter .. Format;
Format = ""; Format = "";
end end
elseif ( URL and "" < URL ) then elseif is_set(URL) then
Chapter = externallink( URL, Chapter ) .. TransError .. Format; Chapter = externallink( URL, Chapter ) .. TransError .. Format;
URL = nil URL = "";
Format = "" Format = "";
else else
Chapter = Chapter .. TransError; Chapter = Chapter .. TransError;
end end
elseif ChapterURL ~= nil and ChapterURL ~= "" then elseif is_set(ChapterURL) then
Chapter = Chapter .. " " .. externallink( ChapterURL ) .. Chapter = Chapter .. " " .. externallink( ChapterURL, nil, ChapterURLorigin ) ..
TransError; TransError;
else else
Line 1,132: Line 1,136:
end end
Chapter = Chapter .. sepc .. " " -- with end-space Chapter = Chapter .. sepc .. " " -- with end-space
elseif ChapterURL ~= nil and ChapterURL ~= "" then elseif is_set(ChapterURL) then
Chapter = " " .. externallink( ChapterURL ) .. sepc .. " "; Chapter = " " .. externallink( ChapterURL, nil, ChapterURLorigin ) .. sepc .. " ";
end end
-- Format main title. -- Format main title.
if ( TitleLink and "" < TitleLink ) then if is_set(TitleLink) and is_set(Title) then
if ( Title and "" < Title ) then Title = "]"
Title = "]"
end
end end

if ( Periodical and "" < Periodical ) then if is_set(Periodical) then
Title = wrap( 'quoted-title', Title ); Title = wrap( 'quoted-title', Title );
TransTitle = wrap( 'trans-quoted-title', TransTitle ); TransTitle = wrap( 'trans-quoted-title', TransTitle );
elseif ( config.CitationClass == "web" elseif inArray(config.CitationClass, {"web","news","pressrelease"}) and
or config.CitationClass == "news" not is_set(Chapter) then
or config.CitationClass == "pressrelease" ) and
Chapter == "" then
Title = wrap( 'quoted-title', Title ); Title = wrap( 'quoted-title', Title );
TransTitle = wrap( 'trans-quoted-title', TransTitle ); TransTitle = wrap( 'trans-quoted-title', TransTitle );
Line 1,157: Line 1,157:
end end
local TransError = ""; TransError = "";
if TransTitle ~= "" and Title == "" then if is_set(TransTitle) then
TransError = " " .. seterror( 'trans_missing_title' ); if not is_set(Title) then
TransError = " " .. seterror( 'trans_missing_title' );
else
TransTitle = " " .. TransTitle;
end
end end
if TransTitle ~= "" and Title ~= "" then TransTitle = " " .. TransTitle; end Title = Title .. TransTitle;
Title = Title .. TransTitle
if Title ~= "" then if is_set(Title) then
if ( TitleLink == nil and URL and "" < URL ) then if not is_set(TitleLink) and is_set(URL) then
Title = externallink( URL, Title ) .. TransError .. Format Title = externallink( URL, Title ) .. TransError .. Format
URL = nil URL = "";
Format = '' Format = "";
else else
Title = Title .. TransError; Title = Title .. TransError;
end end
end end

if ( Place ~= nil and Place ~= "" ) then if is_set(Place) then
if sepc == '.' then if sepc == '.' then
Place = " " .. wrap( 'written', Place ) .. sepc .. " "; Place = " " .. wrap( 'written', Place ) .. sepc .. " ";
else else
Place = " " .. substitute( cfg.message_list:lower(), {Place} ) .. sepc .. " "; Place = " " .. substitute( cfg.messages:lower(), {Place} ) .. sepc .. " ";
end end
else
Place = "";
end end
if ( Conference ~= nil and Conference ~="" ) then if is_set(Conference) then
if ( ConferenceURL ~= nil ) then if is_set(ConferenceURL) then
Conference = externallink( ConferenceURL, Conference ); Conference = externallink( ConferenceURL, Conference );
end end
Conference = " " .. Conference Conference = " " .. Conference
elseif ConferenceURL ~= nil and ConferenceURL ~= "" then elseif is_set(ConferenceURL) then
Conference = " " .. externallink( ConferenceURL ); Conference = " " .. externallink( ConferenceURL, nil, ConferenceURLorigin );
else
Conference = ""
end end
if ( nil ~= Position or nil ~= Page or nil ~= Pages ) then At = nil end
if ( nil == Position and "" ~= Position ) then if not is_set(Position) then
local Minutes = A; local Minutes = A;
if ( nil ~= Minutes ) then if is_set(Minutes) then
Position = " " .. Minutes .. " " .. cfg.message_list; Position = " " .. Minutes .. " " .. cfg.messages;
else else
local Time = A; local Time = A;
if ( nil ~= Time ) then if is_set(Time) then
local TimeCaption = A local TimeCaption = A
if TimeCaption == nil then if not is_set(TimeCaption) then
TimeCaption = cfg.message_list; TimeCaption = cfg.messages;
if sepc ~= '.' then if sepc ~= '.' then
TimeCaption = TimeCaption:lower(); TimeCaption = TimeCaption:lower();
end end
end end
Position = " " .. TimeCaption .. " " .. Time Position = " " .. TimeCaption .. " " .. Time;
else
Position = ""
end end
end end
else else
Position = " " .. Position Position = " " .. Position;
At = '';
end end
if ( nil == Page or "" == Page ) then
Page = "" if not is_set(Page) then
if ( nil == Pages or "" == Pages) then if is_set(Pages) then
Pages = "" if is_set(Periodical) and
not inArray(config.CitationClass, {"encyclopaedia","web","book","news"}) then
elseif ( Periodical ~= nil and Periodical ~= "" and
config.CitationClass ~= "encyclopaedia" and Pages = ": " .. Pages;
config.CitationClass ~= "web" and elseif tonumber(Pages) ~= nil then
config.CitationClass ~= "book" and Pages = sepc .." " .. PPrefix .. Pages;
config.CitationClass ~= "news") then else
Pages = ": " .. Pages Pages = sepc .." " .. PPPrefix .. Pages;
else
if ( tonumber(Pages) ~= nil ) then
Pages = sepc .." " .. PPrefix .. Pages
else Pages = sepc .." " .. PPPrefix .. Pages
end end
end end
else else
Pages = "" if is_set(Periodical) and
not inArray(config.CitationClass, {"encyclopaedia","web","book","news"}) then
if ( Periodical ~= nil and Periodical ~= "" and
config.CitationClass ~= "encyclopaedia" and Page = ": " .. Page;
config.CitationClass ~= "web" and
config.CitationClass ~= "book" and
config.CitationClass ~= "news") then
Page = ": " .. Page
else else
Page = sepc .." " .. PPrefix .. Page Page = sepc .." " .. PPrefix .. Page;
end end
end end
if ( At ~= nil and At ~="") then At = sepc .. " " .. At
else At = "" end At = is_set(At) and (sepc .. " " .. At) or "";
Others = is_set(Others) and (sepc .. " " .. Others) or "";
if ( Coauthors == nil ) then Coauthors = "" end
if ( Others ~= nil and Others ~="" ) then TitleType = is_set(TitleType) and (" (" .. TitleType .. ")") or "";
Others = sepc .. " " .. Others else Others = "" end TitleNote = is_set(TitleNote) and (sepc .. " " .. TitleNote) or "";
Language = is_set(Language) and (" " .. wrap( 'language', Language )) or "";
if ( TitleType ~= nil and TitleType ~="" ) then
Edition = is_set(Edition) and (" " .. wrap( 'edition', Edition )) or "";
TitleType = " (" .. TitleType .. ")" else TitleType = "" end
if ( TitleNote ~= nil and TitleNote ~="" ) then Issue = is_set(Issue) and (" (" .. Issue .. ")") or "";
TitleNote = sepc .. " " .. TitleNote else TitleNote = "" end Series = is_set(Series) and (sepc .. " " .. Series) or "";
if ( Language ~= nil and Language ~="" ) then OrigYear = is_set(OrigYear) and (" ") or "";
Language = " " .. wrap( 'language', Language ) else Language = "" end Agency = is_set(Agency) and (sepc .. " " .. Agency) or "";
if ( Edition ~= nil and Edition ~="" ) then
if is_set(Volume) then
Edition = " " .. wrap( 'edition', Edition ) else Edition = "" end
if ( Volume ~= nil and Volume ~="" )
then
if ( mw.ustring.len(Volume) > 4 ) if ( mw.ustring.len(Volume) > 4 )
then Volume = sepc .." " .. Volume then Volume = sepc .." " .. Volume;
else Volume = " <b>" .. hyphentodash(Volume) .. "</b>" else Volume = " <b>" .. hyphentodash(Volume) .. "</b>";
end end
else Volume = "" end end
if ( Issue ~= nil and Issue ~="" ) then
Issue = " (" .. Issue .. ")" else Issue = "" end
if ( Series ~= nil and Series ~="" ) then
Series = sepc .. " " .. Series else Series = "" end
if ( OrigYear ~= nil and OrigYear ~="" ) then
OrigYear = " " else OrigYear = "" end
if ( Agency ~= nil and Agency ~="" ) then
Agency = sepc .. " " .. Agency else Agency = "" end
------------------------------------ totally unrelated data ------------------------------------ totally unrelated data
if ( Date ~= nil ) then Date = Date else Date = "" end if is_set(Via) then Via = " " .. wrap( 'via', Via ); end
if ( Via ~= nil and Via ~="" ) then if is_set(AccessDate) then
Via = " " .. wrap( 'via', Via ) else Via = "" end local retrv_text = " " .. cfg.messages
if ( AccessDate ~= nil and AccessDate ~="" ) if (sepc ~= ".") then retrv_text = retrv_text:lower() end
AccessDate = '<span class="reference-accessdate">' .. sepc
then local retrv_text = " " .. cfg.message_list
if (sepc ~= ".") then retrv_text = retrv_text:lower() end .. substitute( retrv_text, {AccessDate} ) .. '</span>'
AccessDate = '<span class="reference-accessdate">' .. sepc
.. substitute( retrv_text, {AccessDate} ) .. '</span>'
else AccessDate = "" end
if ( SubscriptionRequired ~= nil and
SubscriptionRequired ~= "" ) then
SubscriptionRequired = sepc .. " " .. cfg.message_list;
else
SubscriptionRequired = ""
end end
if ( ID ~= nil and ID ~="") then ID = sepc .." ".. ID else ID="" end
if is_set(SubscriptionRequired) then

SubscriptionRequired = sepc .. " " .. cfg.messages;
end
if is_set(ID) then ID = sepc .." ".. ID; end
ID_list = buildidlist( ID_list, {DoiBroken = DoiBroken, ASINTLD = ASINTLD, IgnoreISBN = IgnoreISBN} ); ID_list = buildidlist( ID_list, {DoiBroken = DoiBroken, ASINTLD = ASINTLD, IgnoreISBN = IgnoreISBN} );


if ( URL ~= nil and URL ~="") then if is_set(URL) then
URL = " " .. externallink( URL ); URL = " " .. externallink( URL, nil, URLorigin );
else
URL = ""
end end


if ( Quote and Quote ~="" ) then if is_set(Quote) then
if Quote:sub(1,1) == '"' and Quote:sub(-1,-1) == '"' then if Quote:sub(1,1) == '"' and Quote:sub(-1,-1) == '"' then
Quote = Quote:sub(2,-2); Quote = Quote:sub(2,-2);
end end
Quote = sepc .." " .. wrap( 'quoted-text', Quote ); Quote = sepc .." " .. wrap( 'quoted-text', Quote );
PostScript = "" PostScript = "";
elseif PostScript:lower() == "none" then
else
if ( PostScript == nil) then PostScript = "" end PostScript = "";
Quote = ""
end end
local Archived local Archived
if ( nil ~= ArchiveURL and "" ~= ArchiveURL ) then if is_set(ArchiveURL) then
if ( ArchiveDate == nil or ArchiveDate =="" ) then if not is_set(ArchiveDate) then
ArchiveDate = seterror('archive_missing_date'); ArchiveDate = seterror('archive_missing_date');
end end
if ( "no" == DeadURL ) then if "no" == DeadURL then
local arch_text = cfg.message_list; local arch_text = cfg.messages;
if (sepc ~= ".") then arch_text = arch_text:lower() end if sepc ~= "." then arch_text = arch_text:lower() end
Archived = sepc .. " " .. substitute( cfg.message_list, Archived = sepc .. " " .. substitute( cfg.messages,
{ externallink( ArchiveURL, arch_text ), ArchiveDate } ); { externallink( ArchiveURL, arch_text ), ArchiveDate } );
if OriginalURL == nil or OriginalUrl == '' then if not is_set(OriginalURL) then
Archived = Archived .. " " .. seterror('archive_missing_url'); Archived = Archived .. " " .. seterror('archive_missing_url');
end end
elseif is_set(OriginalURL) then
local arch_text = cfg.messages;
if sepc ~= "." then arch_text = arch_text:lower() end
Archived = sepc .. " " .. substitute( arch_text,
{ externallink( OriginalURL, cfg.messages ), ArchiveDate } );
else else
local arch_text = cfg.messages;
if OriginalURL ~= nil and OriginalURL ~= '' then
local arch_text = cfg.message_list; if sepc ~= "." then arch_text = arch_text:lower() end
if (sepc ~= ".") then arch_text = arch_text:lower() end Archived = sepc .. " " .. substitute( arch_text,
{ seterror('archive_missing_url'), ArchiveDate } );
Archived = sepc .. " " .. substitute( arch_text,
{ externallink( OriginalURL, cfg.message_list ), ArchiveDate } );
else
local arch_text = cfg.message_list;
if (sepc ~= ".") then arch_text = arch_text:lower() end
Archived = sepc .. " " .. substitute( arch_text,
{ seterror('archive_missing_url'), ArchiveDate } );
end
end end
else else
Archived = "" Archived = ""
end end
local Lay local Lay
if ( nil ~= LaySummary and "" ~= LaySummary ) then if is_set(LayURL) then
if ( LayDate ~= nil ) then LayDate = " (" .. LayDate .. ")" else LayDate = "" end if is_set(LayDate) then LayDate = " (" .. LayDate .. ")" end
if ( LaySource ~= nil ) then if is_set(LaySource) then
LaySource = " &ndash; ''" .. safeforitalics(LaySource) .. "''" LaySource = " &ndash; ''" .. safeforitalics(LaySource) .. "''";
else else
LaySource = "" LaySource = "";
end end
if sepc == '.' then if sepc == '.' then
Lay = sepc .. " " .. externallink( LaySummary, cfg.message_list ) .. LaySource .. LayDate Lay = sepc .. " " .. externallink( LayURL, cfg.messages ) .. LaySource .. LayDate
else else
Lay = sepc .. " " .. externallink( LaySummary, cfg.message_list:lower() ) .. LaySource .. LayDate Lay = sepc .. " " .. externallink( LayURL, cfg.messages:lower() ) .. LaySource .. LayDate
end end
else else
Lay = "" Lay = "";
end end
if ( nil ~= Transcript and "" ~= Transcript ) then
if is_set(Transcript) then
if ( TranscriptURL ~= nil ) then Transcript = externallink( TranscriptURL, Transcript ) end
elseif TranscriptURL ~= nil and TranscriptURL ~= "" then if is_set(TranscriptURL) then Transcript = externallink( TranscriptURL, Transcript ); end
Transcript = externallink( TranscriptURL ) elseif is_set(TranscriptURL) then
Transcript = externallink( TranscriptURL, nil, TranscriptURLorigin );
else
Transcript = ""
end end
local Publisher = ""
local Publisher;
if ( Periodical and Periodical ~= "" and
if is_set(Periodical) and
config.CitationClass ~= "encyclopaedia" and
config.CitationClass ~= "web" and not inArray(config.CitationClass, {"encyclopaedia","web","pressrelease"}) then
config.CitationClass ~= "pressrelease" ) then if is_set(PublisherName) then
if ( PublisherName ~= nil and PublisherName ~="" ) then if is_set(PublicationPlace) then
if (PublicationPlace ~= nil and PublicationPlace ~= '') then
Publisher = PublicationPlace .. ": " .. PublisherName; Publisher = PublicationPlace .. ": " .. PublisherName;
else else
Publisher = PublisherName; Publisher = PublisherName;
end end
elseif (PublicationPlace ~= nil and PublicationPlace ~= '') then elseif is_set(PublicationPlace) then
Publisher= PublicationPlace; Publisher= PublicationPlace;
else else
Publisher = ""; Publisher = "";
end end
if ( PublicationDate and PublicationDate ~="" ) then if is_set(PublicationDate) then
if Publisher ~= '' then if is_set(Publisher) then
Publisher = Publisher .. ", " .. wrap( 'published', PublicationDate ); Publisher = Publisher .. ", " .. wrap( 'published', PublicationDate );
else else
Line 1,387: Line 1,358:
end end
end end
if Publisher ~= "" then if is_set(Publisher) then
Publisher = " (" .. Publisher .. ")"; Publisher = " (" .. Publisher .. ")";
end end
else else
if ( PublicationDate and PublicationDate ~="" ) then if is_set(PublicationDate) then
PublicationDate = " (" .. wrap( 'published', PublicationDate ) .. ")" PublicationDate = " (" .. wrap( 'published', PublicationDate ) .. ")";
else
PublicationDate = ""
end end
if ( PublisherName ~= nil and PublisherName ~="" ) then if is_set(PublisherName) then
if (PublicationPlace ~= nil and PublicationPlace ~= '') then if is_set(PublicationPlace) then
Publisher = sepc .. " " .. PublicationPlace .. ": " .. PublisherName .. PublicationDate; Publisher = sepc .. " " .. PublicationPlace .. ": " .. PublisherName .. PublicationDate;
else else
Publisher = sepc .. " " .. PublisherName .. PublicationDate; Publisher = sepc .. " " .. PublisherName .. PublicationDate;
end end
elseif (PublicationPlace ~= nil and PublicationPlace ~= '') then elseif is_set(PublicationPlace) then
Publisher= sepc .. " " .. PublicationPlace .. PublicationDate; Publisher= sepc .. " " .. PublicationPlace .. PublicationDate;
else else
Line 1,408: Line 1,377:
end end
end end
-- Several of the above rely upon detecting this as nil, so do it last. -- Several of the above rely upon detecting this as nil, so do it last.
if ( Periodical ~= nil and Periodical ~="" ) then if is_set(Periodical) then
if ( Title and Title ~= "" ) or ( TitleNote and TitleNote ~= "" ) then if is_set(Title) or is_set(TitleNote) then
Periodical = sepc .. " " .. wrap( 'italic-title', Periodical ) Periodical = sepc .. " " .. wrap( 'italic-title', Periodical )
else else
Periodical = wrap( 'italic-title', Periodical ) Periodical = wrap( 'italic-title', Periodical )
end end
else Periodical = "" end end


-- Piece all bits together at last. Here, all should be non-nil. -- Piece all bits together at last. Here, all should be non-nil.
Line 1,422: Line 1,392:


local tcommon local tcommon
if ( ( (config.CitationClass == "journal") or (config.CitationClass == "citation") ) and if inArray(config.CitationClass, {"journal","citation"}) and is_set(Periodical) then
Periodical ~= "" ) then if is_set(Others) then Others = Others .. sepc .. " " end
if (Others ~= "") then Others = Others .. sepc .. " " end
tcommon = safejoin( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series, tcommon = safejoin( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series,
Language, Edition, Publisher, Agency, Volume, Issue, Position}, sepc ); Language, Edition, Publisher, Agency, Volume, Issue, Position}, sepc );
Line 1,436: Line 1,405:
else else
ID_list = ID; ID_list = ID;
end end
local idcommon = safejoin( { ID_list, URL, Archived, AccessDate, Via, SubscriptionRequired, Lay, Quote }, sepc ); local idcommon = safejoin( { ID_list, URL, Archived, AccessDate, Via, SubscriptionRequired, Lay, Quote }, sepc );
local text;

local text local pgtext = Page .. Pages .. At;
local pgtext = Page .. Pages .. At
if ( "" ~= Authors ) then if is_set(Authors) then
if (Coauthors ~= "") if is_set(Coauthors) then
then Authors = Authors .. A .. " " .. Coauthors Authors = Authors .. A .. " " .. Coauthors
end end
if ( "" ~= Date ) if is_set(Date) then
then Date = " ("..Date..")" .. OrigYear .. sepc .. " " Date = " ("..Date..")" .. OrigYear .. sepc .. " "
elseif string.sub(Authors,-1,-1) == sepc then
else
if ( string.sub(Authors,-1,-1) == sepc) --check end character Authors = Authors .. " "
then Authors = Authors .. " " else
Authors = Authors .. sepc .. " "
else Authors = Authors .. sepc .. " "
end
end end
if ( "" ~= Editors) then if is_set(Editors) then
local in_text = " in " local in_text = " " .. cfg.messages .. " "
if (sepc == '.') then in_text = " In " end if (sepc ~= '.') then in_text = in_text:lower() end
if (string.sub(Editors,-1,-1) == sepc) if (string.sub(Editors,-1,-1) == sepc)
then Editors = in_text .. Editors .. " " then Editors = in_text .. Editors .. " "
Line 1,464: Line 1,432:
text = safejoin( {Authors, Date, Chapter, Place, Editors, tcommon }, sepc ); text = safejoin( {Authors, Date, Chapter, Place, Editors, tcommon }, sepc );
text = safejoin( {text, pgtext, idcommon}, sepc ); text = safejoin( {text, pgtext, idcommon}, sepc );
elseif ( "" ~= Editors) then elseif is_set(Editors) then
if ( "" ~= Date ) then if is_set(Date) then
if EditorCount <= 1 then if EditorCount <= 1 then
Editors = Editors .. ", " .. cfg.message_list; Editors = Editors .. ", " .. cfg.messages;
else else
Editors = Editors .. ", " .. cfg.message_list; Editors = Editors .. ", " .. cfg.messages;
end end
Date = " (" .. Date ..")" .. OrigYear .. sepc .. " " Date = " (" .. Date ..")" .. OrigYear .. sepc .. " "
else else
if EditorCount <= 1 then if EditorCount <= 1 then
Editors = Editors .. " (" .. cfg.message_list .. ")" .. sepc .. " " Editors = Editors .. " (" .. cfg.messages .. ")" .. sepc .. " "
else else
Editors = Editors .. " (" .. cfg.message_list .. ")" .. sepc .. " " Editors = Editors .. " (" .. cfg.messages .. ")" .. sepc .. " "
end end
end end
Line 1,482: Line 1,450:
text = safejoin( {text, pgtext, idcommon}, sepc ); text = safejoin( {text, pgtext, idcommon}, sepc );
else else
if ( "" ~= Date ) then if is_set(Date) then
if ( string.sub(tcommon,-1,-1) ~= sepc ) if ( string.sub(tcommon,-1,-1) ~= sepc )
then Date = sepc .." " .. Date .. OrigYear then Date = sepc .." " .. Date .. OrigYear
else Date = " " .. Date .. OrigYear else Date = " " .. Date .. OrigYear
end end
end -- endif ""~=Date end
if ( config.CitationClass=="journal" and Periodical ) then if config.CitationClass=="journal" and is_set(Periodical) then
text = safejoin( {Chapter, Place, tcommon}, sepc ); text = safejoin( {Chapter, Place, tcommon}, sepc );
text = safejoin( {text, pgtext, Date, idcommon}, sepc ); text = safejoin( {text, pgtext, Date, idcommon}, sepc );
else else
text = safejoin( {Chapter, Place, tcommon, Date}, sepc ); text = safejoin( {Chapter, Place, tcommon, Date}, sepc );
text = safejoin( {text, pgtext, idcommon}, sepc ); text = safejoin( {text, pgtext, idcommon}, sepc );
end end
end end
if PostScript ~= '' and PostScript ~= nil and PostScript ~= sepc then if is_set(PostScript) and PostScript ~= sepc then
text = safejoin( {text, sepc}, sepc ); --Deals with italics, spaces, etc. text = safejoin( {text, sepc}, sepc ); --Deals with italics, spaces, etc.
text = text:sub(1,-2); --Remove final seperator text = text:sub(1,-2); --Remove final seperator
Line 1,505: Line 1,473:


-- Now enclose the whole thing in a <span/> element -- Now enclose the whole thing in a <span/> element
if ( Year == nil ) then if not is_set(Year) then
if ( DateIn ~= nil and DateIn ~= "" ) then if is_set(DateIn) then
Year = selectyear( DateIn ) Year = selectyear( DateIn );
elseif( PublicationDate ~= nil and PublicationDate ~= "" ) then elseif is_set(PublicationDate) then
Year = selectyear( PublicationDate ) Year = selectyear( PublicationDate );
else
Year = ""
end end
end end
local classname = "citation"
local options = {};
if ( config.CitationClass ~= "citation" )
then classname = "citation " .. (config.CitationClass or "") end
if is_set(config.CitationClass) and config.CitationClass ~= "citation" then
local options = { class=classname }
options.class = "citation " .. config.CitationClass;
if ( Ref ~= nil ) then
else
options.class = "citation";
end
if is_set(Ref) and Ref:lower() ~= "none" then
local id = Ref local id = Ref
if ( "harv" == Ref ) then if ( "harv" == Ref ) then
local names = {} --table of last names & year local names = {} --table of last names & year
if ( "" ~= Authors ) then if is_set(Authors) then
for i,v in ipairs(a) do for i,v in ipairs(a) do
names = v.last names = v.last
if i == 4 then break end if i == 4 then break end
end end
elseif ( "" ~= Editors ) then elseif is_set(Editors) then
for i,v in ipairs(e) do for i,v in ipairs(e) do
names = v.last names = v.last
Line 1,545: Line 1,516:
end end
if options.id ~= nil then if is_set(options.id) then
text = '<span id="' .. wikiescape(options.id) ..'" class="' .. wikiescape(options.class) .. '">' .. text .. "</span>"; text = '<span id="' .. mw.uri.anchorEncode(options.id) ..'" class="' .. mw.text.nowiki(options.class) .. '">' .. text .. "</span>";
else else
text = '<span class="' .. wikiescape(options.class) .. '">' .. text .. "</span>"; text = '<span class="' .. mw.text.nowiki(options.class) .. '">' .. text .. "</span>";
end end


Line 1,554: Line 1,525:
-- Note: Using display: none on then COinS span breaks some clients. -- Note: Using display: none on then COinS span breaks some clients.
local OCinS = '<span title="' .. wikiescape(OCinStitle) .. '" class="Z3988">' .. empty_span .. '</span>'; local OCinS = '<span title="' .. OCinSoutput .. '" class="Z3988">' .. empty_span .. '</span>';
text = text .. OCinS; text = text .. OCinS;
Line 1,560: Line 1,531:
text = text .. " "; text = text .. " ";
for i,v in ipairs( z.message_tail ) do for i,v in ipairs( z.message_tail ) do
if v ~= nil and v ~= "" then if is_set(v) then
if i == #z.message_tail then if i == #z.message_tail then
text = text .. errorcomment( v, v ); text = text .. errorcomment( v, v );
Line 1,571: Line 1,542:
no_tracking_cats = no_tracking_cats:lower(); no_tracking_cats = no_tracking_cats:lower();
if no_tracking_cats == "" or no_tracking_cats == "no" or if inArray(no_tracking_cats, {"", "no", "false", "n"}) then
no_tracking_cats == "false" or no_tracking_cats == "n" then
for _, v in ipairs( z.error_categories ) do for _, v in ipairs( z.error_categories ) do
text = text .. ']'; text = text .. ']';
Line 1,588: Line 1,558:
local suggestions = {}; local suggestions = {};
local error_text, error_state; local error_text, error_state;

local config = {};
for k, v in pairs( frame.args ) do
config = v;
args = v;
end

for k, v in pairs( pframe.args ) do for k, v in pairs( pframe.args ) do
if v ~= '' then if v ~= '' then
Line 1,612: Line 1,589:
table.insert( z.message_tail, {error_text, error_state} ); table.insert( z.message_tail, {error_text, error_state} );
end end
end end
args = v; args = v;
elseif k == 'postscript' then elseif args ~= nil or (k == 'postscript') then
args = v;
end
end

local config = {};
for k, v in pairs( frame.args ) do
config = v;
if args == nil and (v ~= '' or k == 'postscript') then
args = v; args = v;
end end

Revision as of 18:42, 25 April 2013

Module documentation[view] [edit] [history] [purge]
WarningThis Lua module is used on approximately 6,020,000 pages.
To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them.
ProtectedThis module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing.
This module can only be edited by administrators because it is transcluded onto one or more cascade-protected pages.
CSSThis module uses TemplateStyles:

This module and associated sub-modules support the Citation Style 1 and Citation Style 2 citation templates. In general, it is not intended to be called directly, but is called by one of the core CS1 and CS2 templates.

These files comprise the module support for CS1|2 citation templates:

CS1 | CS2 modules
live sandbox diff description
Gold padlock Module:Citation/CS1 Module:Citation/CS1/sandbox diff Rendering and support functions
Module:Citation/CS1/Configuration Module:Citation/CS1/Configuration/sandbox diff Translation tables; error and identifier handlers
Module:Citation/CS1/Whitelist Module:Citation/CS1/Whitelist/sandbox diff List of active and deprecated CS1|2 parameters
Module:Citation/CS1/Date validation Module:Citation/CS1/Date validation/sandbox diff Date format validation functions
Module:Citation/CS1/Identifiers Module:Citation/CS1/Identifiers/sandbox diff Functions that support the named identifiers (ISBN, DOI, PMID, etc.)
Module:Citation/CS1/Utilities Module:Citation/CS1/Utilities/sandbox diff Common functions and tables
Module:Citation/CS1/COinS Module:Citation/CS1/COinS/sandbox diff Functions that render a CS1|2 template's metadata
Module:Citation/CS1/styles.css Module:Citation/CS1/sandbox/styles.css diff CSS styles applied to the CS1|2 templates
Silver padlock Module:Citation/CS1/Suggestions Module:Citation/CS1/Suggestions/sandbox diff List that maps common erroneous parameter names to valid parameter names


Other documentation:

testcases

The above documentation is transcluded from Module:Citation/CS1/doc. (edit | history)
Editors can experiment in this module's sandbox (edit | diff) and testcases (edit | run) pages.
Subpages of this module.

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/sandbox' );

-- Contains a list of all recognized parameters
local whitelist = mw.loadData( 'Module:Citation/CS1/Whitelist' );

-- 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, {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; --force the variable to be loaded.
            return origin;
        end
    },
    {
        __index = function ( tbl, k )
            if origin ~= nil then
                return nil;
            end
            
            local args, list, v = args, cfg.aliases;
            
            if list == nil then
                error( cfg.messages );
            elseif type( list ) == 'string' then
                v, origin = args, list;
            else
                v, origin = selectone( args, list, 'redundant_parameters' );
                if origin == nil then
                    origin = '';   --Empty string, not nil;
                end
            end
            
            if v == nil then
                v = cfg.defaults or "";
                origin = '';   --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 then
        return true;
    end
    
    -- Arguments with numbers in them
    name = name:gsub( "%d+", "#" );
    if whitelist.numbered_arguments 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;
    
    prefix = prefix or "";
    suffix = suffix or "";
    
    if error_state == nil then
        error( cfg.messages );
    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 .. " ( .. 
        "#" .. error_state.anchor .. "|" ..
        cfg.messages .. "]])";
    
    z.error_ids = true;
    if inArray( error_id, { 'bare_url_missing_title', 'trans_missing_title' } )
            and z.error_ids 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',
        options.link, options.label, options.separator or "&nbsp;",
        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]',
        options.link, options.label, options.separator or "&nbsp;",
        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 );
        end            
    end
    if not checkurl( URL ) then
        error_str = seterror( 'bad_url', {}, false, " " ) .. error_str;
    end
    return table.concat({ "", 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;
    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;
    
    local text;
    if is_set(inactive) then
        text = "]:" .. id;
        table.insert( z.error_categories, "Pages with DOIs inactive since " .. selectyear(inactive) );        
        inactive = " (" .. cfg.messages .. " " .. 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;
    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( "", "" );
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 ] or ] 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 )
    if str:match( "%%]" ) ~= nil then 
        table.insert( z.message_tail, { seterror( 'wikilink_in_url', {}, true ) } );
    end
    
    return str:gsub( '\n]', {    
         = '&#91;',
        '] = '&#93;',
         = ' ' } );
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 <i> for italics, as the expected behavior for
    italics specified by ''...'' in the title is that they will be inverted
    (i.e. unitalicized) in the resulting references.  In addition, <i> and ''
    tend to interact poorly under Mediawiki's HTML tidy. ]]
    
    if not 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

--[[
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 .. "<HERE(enchr=" .. end_chr.. ")"
                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("&mdash;",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 = "]" 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 = " & ";
        end
        text = nil; 
    end
    
    local result = table.concat(text) -- construct list
    if etal then 
        local etal_text = cfg.messages;
        result = result .. " " .. etal_text;
    end
    
    -- if necessary wrap result in <span> tag to format in Small Caps
    if ( "scap" == format ) then result = 
        '<span class="smallcaps" style="font-variant:small-caps">' .. result .. '</span>';
    end 
    return result, count
end

-- Generates a CITEREF anchor ID.
function 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, 'redundant_parameters', i );
        if not is_set(last) then
            -- just in case someone passed in an empty parameter
            break;
        end
        names = {
            last = last,
            first = selectone( args, cfg.aliases, 'redundant_parameters', i ),
            link = selectone( args, cfg.aliases, 'redundant_parameters', i ),
            mask = selectone( args, cfg.aliases, '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 = 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 end } end;
    
    for k, v in pairs( id_list ) do
        -- fallback to read-only cfg
        handler = setmetatable( {  = 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 );
        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 );
        end
    end
    
    function comp( a, b )
        return a < b;
    end
    
    table.sort( new_list, comp );
    for k, v in ipairs( new_list ) do
        new_list = v;
    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) then
                if value ~= nil and selected ~= v then
                    table.insert( error_list, v );
                else
                    value = args;
                    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) then
            if value ~= nil and selected ~=  v then
                table.insert( error_list, v );
            else
                value = args;
                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 end
            error_str = error_str .. wrap( 'parameter', k );
        end
        if #error_list > 1 then
            error_str = error_str .. cfg.messages;
        else
            error_str = error_str .. cfg.messages;
        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 <http://ocoins.info/>) allows automated tools to parse
-- 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 = "bookitem";
        OCinSoutput = data.Chapter;
        OCinSoutput = data.Title;
    elseif is_set(data.Periodical) then
        OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:journal";
        OCinSoutput = "article";
        OCinSoutput = data.Periodical;
        OCinSoutput = data.Title;
    else
        OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:book";
        OCinSoutput = "book"
        OCinSoutput = data.Title;
    end
    
    OCinSoutput = data.PublicationPlace;
    OCinSoutput = data.Date;
    OCinSoutput = data.Series;
    OCinSoutput = data.Volume;
    OCinSoutput = data.Issue;
    OCinSoutput = data.Pages;
    OCinSoutput = data.Edition;
    OCinSoutput = data.PublisherName;
    
    for k, v in pairs( data.ID_list ) do
        local id, value = cfg.id_handlers.COinS;
        if k == 'ISBN' then value = cleanisbn( v ); else value = v; end
        if string.sub( id or "", 1, 4 ) == 'info' then
            OCinSoutput = table.concat{ id, "/", v };
        else
            OCinSoutput = 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 = last;
            end
            if is_set(first) then 
                OCinSoutput = first;
            end
        end
        if is_set(last) and is_set(first) then
            OCinSoutput = table.concat{ last, ", ", first };
        elseif is_set(last) then
            OCinSoutput = 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)
    --[[ 
    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
    local PPPrefix = A
    if is_set( A ) 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;
    local a = extractnames( args, 'AuthorList' );

    local Coauthors = A;
    local Others = A;
    local Editors = A;
    local e = extractnames( args, 'EditorList' );

    local Year = A;
    local PublicationDate = A;
    local OrigYear = A;
    local Date = A;
    local LayDate = A;
    ------------------------------------------------- Get title data
    local Title = A;
    local BookTitle = A;
    local Conference = A;
    local TransTitle = A;
    local TitleNote = A;
    local TitleLink = A;
    local Chapter = A;
    local ChapterLink = A;
    local TransChapter = A;
    local TitleType = A;
    local ArchiveURL = A;
    local URL = A
    local URLorigin = A:ORIGIN('URL');
    local ChapterURL = A;
    local ChapterURLorigin = A:ORIGIN('ChapterURL');
    local ConferenceURL = A;
    local ConferenceURLorigin = A:ORIGIN('ConferenceURL');
    local Periodical = A;
    
    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;
    local Volume = A;
    local Issue = A;
    local Position = '';
    local Page, Pages, At, page_type;
    
    Page = A;
    Pages = hyphentodash( A );
    At = A;
    
    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;
    local PublicationPlace = A
    local Place = A;
    
    if not is_set(PublicationPlace) and is_set(Place) then
        PublicationPlace = Place;
    end
    
    if PublicationPlace == Place then Place = ''; end
    
    local PublisherName = A;
    local SubscriptionRequired = A;
    local Via = A;
    local AccessDate = A;
    local ArchiveDate = A;
    local Agency = A;
    local DeadURL = A
    local Language = A;
    local Format = A;
    local Ref = A;
    
    local DoiBroken = A;
    local ID = A;
    local ASINTLD = A;
    local IgnoreISBN = A;

    local ID_list = extractids( args );
    
    local Quote = A;
    local PostScript = A;
    local LayURL = A;
    local LaySource = A;
    local Transcript = A;
    local TranscriptURL = A 
    local TranscriptURLorigin = A:ORIGIN('TranscriptURL');
    local sepc = A;
    local LastAuthorAmp = A;
    local no_tracking_cats = A;

    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) then
            local Embargo = A;
            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;
                    URLorigin = cfg.id_handlers.parameters;
                end
            else
                URL = "http://www.ncbi.nlm.nih.gov/pmc/articles/PMC" .. ID_list;
                URLorigin = cfg.id_handlers.parameters;
            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;
        local SeriesLink = A;
        local Season = A;
        local SeriesNumber = A;
        local Network = A;
        local Station = A;
        local s, n = {}, {};
        local Sep = (first_set(A, A) or "") .. " ";
        
        if is_set(Issue) then table.insert(s, cfg.messages .. " " .. Issue); Issue = ''; end
        if is_set(Season) then table.insert(s, cfg.messages .. " " .. Season); end
        if is_set(SeriesNumber) then table.insert(s, cfg.messages .. " " .. 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 <http://ocoins.info/>) for
    -- automated parsing of citation information.
    local OCinSoutput = COinS{
         = Periodical,
         = Chapter,
         = Title,
         = PublicationPlace,
         = first_set(Date, Year, PublicationDate),
         = Series,
         = Volume,
         = Issue,
         = first_set(Page, Pages, At),
         = Edition,
         = PublisherName,
         = first_set( URL, ChapterURL ),
         = a,
         = ID_list,
         = 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 );
        
        -- 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 .. " ",
            namesep = (first_set(A, A) or "") .. " ",
            format = A,
            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 );
        -- 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 .. " ",
            namesep = (first_set(A, A) or "") .. " ",
            format = A,
            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;
            if is_set(Month) then 
                Date = Month .. " " .. Date;
                local Day = A
                if is_set(Day) then Date = Day .. " " .. Date end
            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 ( ArchiveURL and "" < ArchiveURL ) then
        if ( DeadURL ~= "no" ) then
            URL = ArchiveURL
        end
    end
    
    -- Format chapter / article title
    if is_set(Chapter) and is_set(ChapterLink) then 
        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
        Chapter = Chapter .. sepc .. " " -- with end-space
    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 = "]"
    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: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;
        if is_set(Minutes) then
            Position = " " .. Minutes .. " " .. cfg.messages;
        else
            local Time = A;
            if is_set(Time) then
                local TimeCaption = A
                if not is_set(TimeCaption) then
                    TimeCaption = cfg.messages;
                    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 tonumber(Pages) ~= nil then
                Pages = sepc .." " .. PPrefix .. Pages;
            else
                Pages = sepc .." " .. PPPrefix .. Pages;
            end
        end
    else
        if is_set(Periodical) and
            not inArray(config.CitationClass, {"encyclopaedia","web","book","news"}) then
            Page = ": " .. Page;
        else
            Page = sepc .." " .. PPrefix .. Page;
        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 "";
    TitleNote = is_set(TitleNote) and (sepc .. " " .. TitleNote) or "";
    Language = is_set(Language) and (" " .. wrap( 'language', Language )) or "";
    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 (" ") 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>" .. hyphentodash(Volume) .. "</b>";
        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
        if (sepc ~= ".") then retrv_text = retrv_text:lower() end
        AccessDate = '<span class="reference-accessdate">' .. sepc
            .. substitute( retrv_text, {AccessDate} ) .. '</span>'
    end
    
    if is_set(SubscriptionRequired) then
        SubscriptionRequired = sepc .. " " .. cfg.messages;
    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;
            if sepc ~= "." then arch_text = arch_text:lower() end
            Archived = sepc .. " " .. substitute( cfg.messages,
                { 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;
            if sepc ~= "." then arch_text = arch_text:lower() end
            Archived = sepc .. " " .. substitute( arch_text,
                { externallink( OriginalURL, cfg.messages ), ArchiveDate } );
        else
            local arch_text = cfg.messages;
            if sepc ~= "." then arch_text = arch_text:lower() end
            Archived = sepc .. " " .. substitute( arch_text, 
                { seterror('archive_missing_url'), ArchiveDate } );
        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 = " &ndash; ''" .. safeforitalics(LaySource) .. "''";
        else
            LaySource = "";
        end
        if sepc == '.' then
            Lay = sepc .. " " .. externallink( LayURL, cfg.messages ) .. LaySource .. LayDate
        else
            Lay = sepc .. " " .. externallink( LayURL, cfg.messages: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
        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
        if is_set(PublicationDate) then
            PublicationDate = " (" .. wrap( 'published', PublicationDate ) .. ")";
        end
        if is_set(PublisherName) then
            if is_set(PublicationPlace) then
                Publisher = sepc .. " " .. PublicationPlace .. ": " .. PublisherName .. PublicationDate;
            else
                Publisher = sepc .. " " .. PublisherName .. PublicationDate;  
            end            
        elseif is_set(PublicationPlace) then 
            Publisher= sepc .. " " .. PublicationPlace .. PublicationDate;
        else 
            Publisher = PublicationDate;
        end
    end
    
    -- Several of the above rely upon detecting this as nil, so do it last.
    if 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, 
            Language, Edition, Publisher, Agency, Volume, Issue, Position}, sepc );
    else 
        tcommon = safejoin( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, 
            Volume, Issue, Others, Edition, Publisher, Agency, Position}, sepc );
    end
    
    if #ID_list > 0 then
        ID_list = safejoin( { sepc .. " ",  table.concat( ID_list, sepc .. " " ), ID }, sepc );
    else
        ID_list = ID;
    end
    
    local idcommon = safejoin( { ID_list, URL, Archived, AccessDate, Via, SubscriptionRequired, Lay, Quote }, sepc );
    local text;
    local pgtext = Page .. Pages .. At;
    
    if is_set(Authors) then
        if is_set(Coauthors) then
            Authors = Authors .. A .. " " .. Coauthors
        end
        if is_set(Date) then
            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 = " " .. cfg.messages .. " "
            if (sepc ~= '.') then in_text = in_text:lower() end
            if (string.sub(Editors,-1,-1) == sepc)
                then Editors = in_text .. Editors .. " "
                else Editors = in_text .. Editors .. sepc .. " "
            end
        end
        text = safejoin( {Authors, Date, Chapter, Place, Editors, tcommon }, sepc );
        text = safejoin( {text, pgtext, idcommon}, sepc );
    elseif is_set(Editors) then
        if is_set(Date) then
            if EditorCount <= 1 then
                Editors = Editors .. ", " .. cfg.messages;
            else
                Editors = Editors .. ", " .. cfg.messages;
            end
            Date = " (" .. Date ..")" .. OrigYear .. sepc .. " "
        else
            if EditorCount <= 1 then
                Editors = Editors .. " (" .. cfg.messages .. ")" .. sepc .. " "
            else
                Editors = Editors .. " (" .. cfg.messages .. ")" .. sepc .. " "
            end
        end
        text = safejoin( {Editors, Date, Chapter, Place, tcommon}, sepc );
        text = safejoin( {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 = safejoin( {Chapter, Place, tcommon}, sepc );
            text = safejoin( {text, pgtext, Date, idcommon}, sepc );
        else
            text = safejoin( {Chapter, Place, tcommon, Date}, sepc );
            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 <span/> 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 = v.last 
                    if i == 4 then break end
                end
            elseif is_set(Editors) then
                for i,v in ipairs(e) do 
                    names = v.last 
                    if i == 4 then break end                
                end
            end
            names = Year;
            id = anchorid(names)
        end
        options.id = id;
    end
    
    if string.len(text:gsub("<span*>.-</span>", ""):gsub("%b<>","")) <= 2 then
        z.error_categories = {};
        text = seterror('empty_citation');
        z.message_tail = {};
    end
    
    if is_set(options.id) then 
        text = '<span id="' .. mw.uri.anchorEncode(options.id) ..'" class="' .. mw.text.nowiki(options.class) .. '">' .. text .. "</span>";
    else
        text = '<span class="' .. mw.text.nowiki(options.class) .. '">' .. text .. "</span>";
    end        

    local empty_span = '<span style="display:none;">&nbsp;</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
        text = text .. " ";
        for i,v in ipairs( z.message_tail ) do
            if is_set(v) then
                if i == #z.message_tail then
                    text = text .. errorcomment( v, v );
                else
                    text = text .. errorcomment( v .. "; ", v );
                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 .. ']';
        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 = v;
        args = 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 ~= nil then
                        error_text, error_state = seterror( 'parameter_ignored_suggest', {k, suggestions}, 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 = v;
        elseif args ~= nil or (k == 'postscript') then
            args = v;
        end        
    end    
    
    return citation0( config, args)
end

return z
Categories:
Module:Citation/CS1: Difference between revisions Add topic