Module:Citation/CS1: Difference between revisions

From All Skies Encyclopaedia
imported>Dragons flight
(sync with sandbox, merge cite_web_title to bare_url, better style control on translations, category suppression by config, tweak display authors)
imported>Dragons flight
(sync to sandbox, mostly code cleaning / organization. Also, ref = none, layurl as alias for laysummary, tweaks to COinS output, and changes to bareurl error.)
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[message_key], {str} );
return substitute( cfg.messages[key], {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[k] then
local dummy = self[k]; --force the variable to be loaded.
return origin[k];
end
},
{
__index = function ( tbl, k )
if origin[k] ~= nil then
return nil;
return nil;
end
end
local list = cfg.argument_map[k];
local args, list, v = args, cfg.aliases[k];

if list == nil then
if list == nil then
error( cfg.message_list['unknown_argument_map'] );
error( cfg.messages['unknown_argument_map'] );
elseif type( list ) == 'string' then
elseif type( list ) == 'string' then
v = DATA[list];
v, origin[k] = args[list], list;
else
else
v = selectone( DATA, cfg.argument_map[k],
v, origin[k] = selectone( args, list, 'redundant_parameters' );
'redundant_parameters' );
if origin[k] == nil then
origin[k] = ''; --Empty string, not nil;
end
end
end
if v == nil then
if v == nil then
v = cfg.default_values[k];
v = cfg.defaults[k] or "";
origin[k] = ''; --Empty string, not nil;
end
end
SEEN[k] = 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[ error_id ];
local error_state = cfg.error_conditions[ error_id ];
prefix = prefix or "";
prefix = prefix or "";
suffix = suffix or "";
suffix = suffix or "";

if error_state == nil then
error( cfg.message_list['undefined_error'] );
end
if error_state.category ~= nil and error_state.category ~= "" then
if error_state == nil then
error( cfg.messages['undefined_error'] );
elseif is_set( error_state.category ) then
table.insert( z.error_categories, error_state.category );
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 .. " ([[" .. cfg.messages['help page link'] ..

message = wikiescape(message) .. " ([[" .. cfg.message_list['help page link'] ..
"#" .. error_state.anchor .. "|" ..
"#" .. error_state.anchor .. "|" ..
cfg.message_list['help page label'] .. "]])";
cfg.messages['help page label'] .. "]])";

z.error_ids[ error_id ] = true;
z.error_ids[ error_id ] = 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['citation_missing_title'] then
and z.error_ids['citation_missing_title'] 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|%s]]%s[%s%s%s %s]',
return "[[" .. options.link .. "|" .. options.label .. "]]" .. 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|%s]]%s[[%s%s%s|%s]]',
local sep = options.separator or " "
options.suffix = options.suffix or ""
options.link, options.label, options.separator or " ",
return "[[" .. options.link .. "|" .. options.label .. "]]" .. 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["bare_url_no_origin"] );
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({ "[", URL, " ", safeforurl( label ), "]", error_str });

return "[" .. URL .. ' ' .. safeforurl( label ) .. "]" .. 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 = "[[" .. handler.link .. "|" .. handler.label .. "]]:" .. id;
text = "[[" .. handler.link .. "|" .. handler.label .. "]]:" .. 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 .. ")"
inactive = " (" .. cfg.messages['inactive'] .. " " .. 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 [[A|B]] or [[B]] gives B
-- Gets the display text for a wikilink like [[A|B]] or [[B]] 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]', {
['['] = '[',
['['] = '[',
[']'] = ']',
[']'] = ']',
['\n'] = ' ' } );
['\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 = "[[" .. person.link .. "|" .. one .. "]]" end
if is_set(person.link) then one = "[[" .. person.link .. "|" .. 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-2] = " & ";
text[#text-2] = " & ";
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['et al'];
local etal_text = cfg.messages['et al'];
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[list_name .. '-Last'], 'redundant_parameters', i );
last = selectone( args, cfg.aliases[list_name .. '-Last'], '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[i] = {
last = last,
first = selectone( args, cfg.argument_map[list_name .. '-First'], 'redundant_parameters', i ),
link = selectone( args, cfg.argument_map[list_name .. '-Link'], 'redundant_parameters', i ),
mask = selectone( args, cfg.argument_map[list_name .. '-Mask'], 'redundant_parameters', i )
}
else
break;
break;
end
end
names[i] = {
last = last,
first = selectone( args, cfg.aliases[list_name .. '-First'], 'redundant_parameters', i ),
link = selectone( args, cfg.aliases[list_name .. '-Link'], 'redundant_parameters', i ),
mask = selectone( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i )
};
i = i + 1;
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[k] = selectone( args, v.parameters, 'redundant_parameters' );
v = selectone( args, v.parameters, 'redundant_parameters' );
if is_set(v) then id_list[k] = 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[k][i] 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( { ['id'] = 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[k] ) do
handler[k2] = v2;
end
handler['id'] = 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['unknown_ID_mode'] );
elseif k == 'DOI' then
table.insert( new_list, {handler.label, doi( v, options.DoiBroken ) } );
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 ) } );
elseif k == 'ISBN' then
table.insert( new_list, {handler.label, openlibrary( v ) } );
local ISBN = internallinkid( handler );
elseif k == 'ISBN' then
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, " ", "" );
end
ISBN = ISBN .. seterror( 'bad_isbn', {}, false, " ", "" );
end
table.insert( new_list, {handler.label, ISBN } );
table.insert( new_list, {handler.label, ISBN } );
else
error( cfg.message_list['unknown_manual_ID'] );
end
else
else
error( cfg.message_list['unknown_ID_mode'] );
error( cfg.messages['unknown_manual_ID'] );
end
end
end
end

function comp( a, b )
function comp( a, b )
return a[1] < b[1];
return a[1] < b[1];
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[v] ~= nil then
if is_set(args[v]) 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[v] ~= nil then
if is_set(args[v]) 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['parameter-separator'] 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['parameter-final-separator'];
else
else
error_str = error_str .. " and ";
error_str = error_str .. cfg.messages['parameter-pair-separator'];
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["rft.genre"] = "bookitem";
OCinSoutput["rft.btitle"] = data.Chapter;
OCinSoutput["rft.atitle"] = data.Title;
elseif is_set(data.Periodical) then
OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:journal";
OCinSoutput["rft.genre"] = "article";
OCinSoutput["rft.jtitle"] = data.Periodical;
OCinSoutput["rft.atitle"] = data.Title;
else
OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:book";
OCinSoutput["rft.genre"] = "book"
OCinSoutput["rft.btitle"] = data.Title;
end
OCinSoutput["rft.place"] = data.PublicationPlace;
OCinSoutput["rft.date"] = data.Date;
OCinSoutput["rft.series"] = data.Series;
OCinSoutput["rft.volume"] = data.Volume;
OCinSoutput["rft.issue"] = data.Issue;
OCinSoutput["rft.pages"] = data.Pages;
OCinSoutput["rft.edition"] = data.Edition;
OCinSoutput["rft.pub"] = data.PublisherName;
for k, v in pairs( data.ID_list ) do
local id, value = cfg.id_handlers[k].COinS;
if k == 'ISBN' then value = cleanisbn( v ); else value = v; end
if string.sub( id or "", 1, 4 ) == 'info' then
OCinSoutput["rft_id"] = table.concat{ id, "/", v };
else
OCinSoutput[ id ] = value;
end
end
local last, first;
for k, v in ipairs( data.Authors ) do
last, first = v.last, v.first;
if k == 1 then
if is_set(last) then
OCinSoutput["rft.aulast"] = last;
end
if is_set(first) then
OCinSoutput["rft.aufirst"] = first;
end
end
if is_set(last) and is_set(first) then
OCinSoutput["rft.au"] = table.concat{ last, ", ", first };
elseif is_set(last) then
OCinSoutput["rft.au"] = last;
end
end
OCinSoutput.rft_id = data.URL;
OCinSoutput.rfr_id = table.concat{ "info:sid/", mw.site.server:match( "[^/]*$" ), ":", data.RawPage };
OCinSoutput = setmetatable( OCinSoutput, nil );
-- sort with version string always first, and combine.
table.sort( OCinSoutput );
table.insert( OCinSoutput, 1, "ctx_ver=" .. ctx_ver ); -- such as "Z39.88-2004"
return table.concat(OCinSoutput, "&");
end
end


Line 686: Line 759:
local PPrefix = A['PPrefix']
local PPrefix = A['PPrefix']
local PPPrefix = A['PPPrefix']
local PPPrefix = A['PPPrefix']
if ( nil ~= A['NoPP'] ) then PPPrefix = "" PPrefix = "" end
if is_set( A['NoPP'] ) then PPPrefix = "" PPrefix = "" end
-- Pick out the relevant fields from the arguments. Different citation templates
-- Pick out the relevant fields from the arguments. Different citation templates
Line 715: Line 788:
local TitleType = A['TitleType'];
local TitleType = A['TitleType'];
local ArchiveURL = A['ArchiveURL'];
local ArchiveURL = A['ArchiveURL'];
local URL = A['URL'];
local URL = A['URL']
local URLorigin = A:ORIGIN('URL');
local ChapterURL = A['ChapterURL'];
local ChapterURL = A['ChapterURL'];
local ChapterURLorigin = A:ORIGIN('ChapterURL');
local ConferenceURL = A['ConferenceURL'];
local ConferenceURL = A['ConferenceURL'];
local ConferenceURLorigin = A:ORIGIN('ConferenceURL');
local Periodical = A['Periodical'];
local Periodical = A['Periodical'];
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['Volume'];
local Volume = A['Volume'];
local Issue = A['Issue'];
local Issue = A['Issue'];
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'] );
Pages = hyphentodash( A['Pages'] );
At = A['At'];
At = A['At'];
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['Edition'];
local Edition = A['Edition'];
local PublicationPlace = A['PublicationPlace']
local PublicationPlace = A['PublicationPlace']
local Place = A['Place'];
local Place = A['Place'];
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['PublisherName'];
local PublisherName = A['PublisherName'];
Line 772: Line 851:
local DeadURL = A['DeadURL']
local DeadURL = A['DeadURL']
local Language = A['Language'];
local Language = A['Language'];
local Format = A['Format']
local Format = A['Format'];
local Ref = A['Ref']
local Ref = A['Ref'];

local DoiBroken = A['DoiBroken']
local DoiBroken = A['DoiBroken'];
local ID = A['ID'];
local ID = A['ID'];
local ASINTLD = A['ASINTLD'];
local ASINTLD = A['ASINTLD'];
local IgnoreISBN = A['IgnoreISBN']
local IgnoreISBN = A['IgnoreISBN'];


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


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['PMC']) then
if (ID_list['PMC'] ~= nil) then
local Embargo = A['Embargo'];
local Embargo = A['Embargo'];
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['PMC'];
if good1 and good2 and tonumber( result1 ) < tonumber( result2 ) then
URL = "http://www.ncbi.nlm.nih.gov/pmc/articles/PMC" .. ID_list['PMC'];
URLorigin = cfg.id_handlers['PMC'].parameters[1];
end
else
URL = "http://www.ncbi.nlm.nih.gov/pmc/articles/PMC" .. ID_list['PMC'];
end
end
else
URL = "http://www.ncbi.nlm.nih.gov/pmc/articles/PMC" .. ID_list['PMC'];
URLorigin = cfg.id_handlers['PMC'].parameters[1];
end
end
end
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['AirDate']
local AirDate = A['AirDate'];
local SeriesLink = A['SeriesLink']
local SeriesLink = A['SeriesLink'];
local Season = A['Season']
local Season = A['Season'];
local SeriesNumber = A['SeriesNumber']
local SeriesNumber = A['SeriesNumber'];
local Network = A['Network']
local Network = A['Network'];
local Station = A['Station']
local Station = A['Station'];
local s = {}
local s, n = {}, {};
local Sep = (first_set(A["SeriesSeparator"], A["Separator"]) or "") .. " ";
if Issue ~= nil then table.insert(s, cfg.message_list["episode"] .. " " .. Issue) Issue = nil end
if Season ~= nil then table.insert(s, cfg.message_list["season"] .. " " .. Season) end
if SeriesNumber ~= nil then table.insert(s, cfg.message_list["series"] .. " " .. SeriesNumber) end
if is_set(Issue) then table.insert(s, cfg.messages["episode"] .. " " .. Issue); Issue = ''; end
if is_set(Season) then table.insert(s, cfg.messages["season"] .. " " .. Season); end
local n = {}
if Network ~= nil then table.insert(n, Network) end
if is_set(SeriesNumber) then table.insert(s, cfg.messages["series"] .. " " .. 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["SeriesSeparator"] or A["Separator"]) .. " "
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'] = Periodical,
local ctx_ver = "Z39.88-2004"
['Chapter'] = Chapter,
OCinSdata.rft_val_fmt = "info:ofi/fmt:kev:mtx:book"
if ( nil ~= Periodical ) then
['Title'] = Title,
['PublicationPlace'] = PublicationPlace,
OCinSdata.rft_val_fmt = "info:ofi/fmt:kev:mtx:journal"
['Date'] = first_set(Date, Year, PublicationDate),
OCinSdata["rft.genre"] = "article"
OCinSdata["rft.jtitle"] = Periodical
['Series'] = Series,
['Volume'] = Volume,
if ( nil ~= Title ) then OCinSdata["rft.atitle"] = Title end
['Issue'] = Issue,
end
['Pages'] = first_set(Page, Pages, At),
if ( nil ~= Chapter and "" ~= Chapter) then
['Edition'] = Edition,
OCinSdata.rft_val_fmt = "info:ofi/fmt:kev:mtx:book"
OCinSdata["rft.genre"] = "bookitem"
['PublisherName'] = PublisherName,
OCinSdata["rft.btitle"] = Chapter
['URL'] = first_set( URL, ChapterURL ),
['Authors'] = a,
if ( nil ~= Title ) then OCinSdata["rft.atitle"] = Title end
['ID_list'] = ID_list,
else
OCinSdata["rft.genre"] = "book"
['RawPage'] = this_page.prefixedText,
};
if ( nil ~= Title ) then OCinSdata["rft.btitle"] = Title end
end
OCinSdata["rft.place"] = PublicationPlace
OCinSdata["rft.date"] = Date or Year or PublicationDate
OCinSdata["rft.series"] = Series
OCinSdata["rft.volume"] = Volume
OCinSdata["rft.issue"] = Issue
OCinSdata["rft.pages"] = Page or Pages or At
OCinSdata["rft.edition"] = Edition
OCinSdata["rft.pub"] = PublisherName
for k, v in pairs( ID_list ) do
if k == 'ISBN' then
v = cleanisbn( v );
end
if string.sub( cfg.id_handlers[k].COinS or "info", 1, 4 ) ~= 'info' then
OCinSdata[ cfg.id_handlers[k].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["rft.aulast"] = last;
end
if first ~= nil then
OCinSdata["rft.aufirst"] = 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[k].COinS or "", 1, 4 ) == 'info' then
OCinSids[ cfg.id_handlers[k].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['DisplayAuthors'] );
local Maximum = tonumber( A['DisplayAuthors'] );
-- 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["AuthorSeparator"] .. " ",
sep = A["AuthorSeparator"] .. " ",
namesep = (A["AuthorNameSeparator"] or A["NameSeparator"]) .. " ",
namesep = (first_set(A["AuthorNameSeparator"], A["NameSeparator"]) or "") .. " ",
format = A["AuthorFormat"],
format = A["AuthorFormat"],
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['DisplayEditors'] );
local Maximum = tonumber( A['DisplayEditors'] );

-- 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["EditorSeparator"] .. " ",
sep = A["EditorSeparator"] .. " ",
namesep = (A["EditorNameSeparator"] or A["NameSeparator"]) .. " ",
namesep = (first_set(A["EditorNameSeparator"], A["NameSeparator"]) or "") .. " ",
format = A['EditorFormat'],
format = A['EditorFormat'],
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['Month'];
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['Month']
if ( Month ~= nil and Month ~= "") then
Date = Month .. " " .. Date
local Day = A['Day']
local Day = A['Day']
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 = "[[" .. ChapterLink .. "|" .. Chapter .. "]]";
Chapter = "[[" .. ChapterLink .. "|" .. 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
if not is_set(Chapter) then
TransError = " " .. seterror( 'trans_missing_chapter' );
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 = "[[" .. TitleLink .. "|" .. Title .. "]]"
Title = "[[" .. TitleLink .. "|" .. 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
if not is_set(Title) then
TransError = " " .. seterror( 'trans_missing_title' );
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['written']:lower(), {Place} ) .. sepc .. " ";
Place = " " .. substitute( cfg.messages['written']: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['Minutes'];
local Minutes = A['Minutes'];
if ( nil ~= Minutes ) then
if is_set(Minutes) then
Position = " " .. Minutes .. " " .. cfg.message_list['minutes'];
Position = " " .. Minutes .. " " .. cfg.messages['minutes'];
else
else
local Time = A['Time'];
local Time = A['Time'];
if ( nil ~= Time ) then
if is_set(Time) then
local TimeCaption = A['TimeCaption']
local TimeCaption = A['TimeCaption']
if TimeCaption == nil then
if not is_set(TimeCaption) then
TimeCaption = cfg.message_list['event'];
TimeCaption = cfg.messages['event'];
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 (" [" .. OrigYear .. "]") 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 = " [" .. 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['retrieved']
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['retrieved']
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['subscription'];
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['subscription'];
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['archived'];
local arch_text = cfg.messages['archived'];
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-not-dead'],
Archived = sepc .. " " .. substitute( cfg.messages['archived-not-dead'],
{ 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['archived-dead'];
if sepc ~= "." then arch_text = arch_text:lower() end
Archived = sepc .. " " .. substitute( arch_text,
{ externallink( OriginalURL, cfg.messages['original'] ), ArchiveDate } );
else
else
local arch_text = cfg.messages['archived-missing'];
if OriginalURL ~= nil and OriginalURL ~= '' then
local arch_text = cfg.message_list['archived-dead'];
if sepc ~= "." then arch_text = arch_text:lower() end
if (sepc ~= ".") then arch_text = arch_text:lower() end
Archived = sepc .. " " .. substitute( arch_text,
{ seterror('archive_missing_url'), ArchiveDate } );
Archived = sepc .. " " .. substitute( arch_text,
{ externallink( OriginalURL, cfg.message_list['original'] ), ArchiveDate } );
else
local arch_text = cfg.message_list['archived-missing'];
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['lay summary'] ) .. LaySource .. LayDate
Lay = sepc .. " " .. externallink( LayURL, cfg.messages['lay summary'] ) .. LaySource .. LayDate
else
else
Lay = sepc .. " " .. externallink( LaySummary, cfg.message_list['lay summary']:lower() ) .. LaySource .. LayDate
Lay = sepc .. " " .. externallink( LayURL, cfg.messages['lay summary']: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['AuthorSeparator'] .. " " .. Coauthors
Authors = Authors .. A['AuthorSeparator'] .. " " .. 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 .. " "
else
then Authors = Authors .. " "
else Authors = Authors .. sepc .. " "
Authors = Authors .. sepc .. " "
end
end
end
if ( "" ~= Editors) then
if is_set(Editors) then
local in_text = " in "
local in_text = " " .. cfg.messages['in'] .. " "
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['editor'];
Editors = Editors .. ", " .. cfg.messages['editor'];
else
else
Editors = Editors .. ", " .. cfg.message_list['editors'];
Editors = Editors .. ", " .. cfg.messages['editors'];
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['editor'] .. ")" .. sepc .. " "
Editors = Editors .. " (" .. cfg.messages['editor'] .. ")" .. sepc .. " "
else
else
Editors = Editors .. " (" .. cfg.message_list['editors'] .. ")" .. sepc .. " "
Editors = Editors .. " (" .. cfg.messages['editors'] .. ")" .. 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[i] = v.last
names[i] = 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[i] = v.last
names[i] = 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[1] ~= nil and v[1] ~= "" then
if is_set(v[1]) then
if i == #z.message_tail then
if i == #z.message_tail then
text = text .. errorcomment( v[1], v[2] );
text = text .. errorcomment( v[1], v[2] );
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 .. '[[Category:' .. v ..']]';
text = text .. '[[Category:' .. v ..']]';
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[k] = v;
args[k] = 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[k] = v;
args[k] = v;
elseif k == 'postscript' then
elseif args[k] ~= nil or (k == 'postscript') then
args[k] = v;
end
end

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

Revision as of 18:42, 25 April 2013

<section begin=header />

Lua error: bad argument #1 to "get" (not a valid title).<section end=header /> 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. <section begin=module_components_table /> 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 [edit] diff Rendering and support functions
Module:Citation/CS1/Configuration Module:Citation/CS1/Configuration/sandbox [edit] diff Translation tables; error and identifier handlers
Module:Citation/CS1/Whitelist Module:Citation/CS1/Whitelist/sandbox [edit] diff List of active and deprecated CS1|2 parameters
Module:Citation/CS1/Date validation Module:Citation/CS1/Date validation/sandbox [edit] diff Date format validation functions
Module:Citation/CS1/Identifiers Module:Citation/CS1/Identifiers/sandbox [edit] diff Functions that support the named identifiers (ISBN, DOI, PMID, etc.)
Module:Citation/CS1/Utilities Module:Citation/CS1/Utilities/sandbox [edit] diff Common functions and tables
Module:Citation/CS1/COinS Module:Citation/CS1/COinS/sandbox [edit] diff Functions that render a CS1|2 template's metadata
Module:Citation/CS1/styles.css Module:Citation/CS1/sandbox/styles.css [edit] diff CSS styles applied to the CS1|2 templates
Silver padlock Module:Citation/CS1/Suggestions Module:Citation/CS1/Suggestions/sandbox [edit] diff List that maps common erroneous parameter names to valid parameter names

<section end=module_components_table />

Other documentation:

testcases


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[key], {str} );
end

--[[
Argument wrapper.  This function provides support for argument 
mapping defined in the configuration file so that multiple names
can be transparently aliased to single internal variable.
]]
function argument_wrapper( args )
    local origin = {};
    
    return setmetatable({
        ORIGIN = function( self, k )
            local dummy = self[k]; --force the variable to be loaded.
            return origin[k];
        end
    },
    {
        __index = function ( tbl, k )
            if origin[k] ~= nil then
                return nil;
            end
            
            local args, list, v = args, cfg.aliases[k];
            
            if list == nil then
                error( cfg.messages['unknown_argument_map'] );
            elseif type( list ) == 'string' then
                v, origin[k] = args[list], list;
            else
                v, origin[k] = selectone( args, list, 'redundant_parameters' );
                if origin[k] == nil then
                    origin[k] = '';   --Empty string, not nil;
                end
            end
            
            if v == nil then
                v = cfg.defaults[k] or "";
                origin[k] = '';   --Empty string, not nil;
            end
            
            tbl = rawset( tbl, k, v );
            return v;
        end,
    });
end

-- Checks that parameter name is valid using the whitelist
function validate( name )
    name = tostring( name );
    
    -- Normal arguments
    if whitelist.basic_arguments[ name ] then
        return true;
    end
    
    -- Arguments with numbers in them
    name = name:gsub( "%d+", "#" );
    if whitelist.numbered_arguments[ name ] then
        return true;
    end
    
    -- Not found, argument not supported.
    return false
end

-- Formats a comment for error trapping
function errorcomment( content, hidden )
    return wrap( hidden and 'hidden-error' or 'visible-error', content );
end

--[[
Sets an error condition and returns the appropriate error message.  The actual placement
of the error message in the output is the responsibility of the calling function.
]]
function seterror( error_id, arguments, raw, prefix, suffix )
    local error_state = cfg.error_conditions[ error_id ];
    
    prefix = prefix or "";
    suffix = suffix or "";
    
    if error_state == nil then
        error( cfg.messages['undefined_error'] );
    elseif is_set( error_state.category ) then
        table.insert( z.error_categories, error_state.category );
    end
    
    local message = substitute( error_state.message, arguments );
    
    message = message .. " ([[" .. cfg.messages['help page link'] .. 
        "#" .. error_state.anchor .. "|" ..
        cfg.messages['help page label'] .. "]])";
    
    z.error_ids[ error_id ] = true;
    if inArray( error_id, { 'bare_url_missing_title', 'trans_missing_title' } )
            and z.error_ids['citation_missing_title'] then
        return '', false;
    end
    
    message = table.concat({ prefix, message, suffix });
    
    if raw == true then
        return message, error_state.hidden;
    end        
        
    return errorcomment( message, error_state.hidden );
end

-- Formats a wiki style external link
function externallinkid(options)
    local url_string = options.id;
    if options.encode == true or options.encode == nil then
        url_string = mw.uri.encode( url_string );
    end
    return mw.ustring.format( '[[%s|%s]]%s[%s%s%s %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|%s]]%s[[%s%s%s|%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["bare_url_no_origin"] );
        end            
    end
    if not checkurl( URL ) then
        error_str = seterror( 'bad_url', {}, false, " " ) .. error_str;
    end
    return table.concat({ "[", URL, " ", safeforurl( label ), "]", error_str });
end

-- Formats a link to Amazon
function amazon(id, domain)
    if not is_set(domain) then 
        domain = "com"
    elseif ( "jp" == domain or "uk" == domain ) then
        domain = "co." .. domain
    end
    local handler = cfg.id_handlers['ASIN'];
    return externallinkid({link = handler.link,
        label=handler.label , prefix="//www.amazon."..domain.."/dp/",id=id,
        encode=handler.encode, separator = handler.separator})
end

-- Formats a DOI and checks for DOI errors.
function doi(id, inactive)
    local cat = ""
    local handler = cfg.id_handlers['DOI'];
    
    local text;
    if is_set(inactive) then
        text = "[[" .. handler.link .. "|" .. handler.label .. "]]:" .. id;
        table.insert( z.error_categories, "Pages with DOIs inactive since " .. selectyear(inactive) );        
        inactive = " (" .. cfg.messages['inactive'] .. " " .. inactive .. ")" 
    else 
        text = externallinkid({link = handler.link, label = handler.label,
            prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode})
        inactive = "" 
    end
    if ( string.sub(id,1,3) ~= "10." ) then      
        cat = seterror( 'bad_doi' );
    end
    return text .. inactive .. cat 
end

-- Formats an OpenLibrary link, and checks for associated errors.
function openlibrary(id)
    local code = id:sub(-1,-1)
    local handler = cfg.id_handlers['OL'];
    if ( code == "A" ) then
        return externallinkid({link=handler.link, label=handler.label,
            prefix="http://openlibrary.org/authors/OL",id=id, separator=handler.separator,
            encode = handler.encode})
    elseif ( code == "M" ) then
        return externallinkid({link=handler.link, label=handler.label,
            prefix="http://openlibrary.org/books/OL",id=id, separator=handler.separator,
            encode = handler.encode})
    elseif ( code == "W" ) then
        return externallinkid({link=handler.link, label=handler.label,
            prefix= "http://openlibrary.org/works/OL",id=id, separator=handler.separator,
            encode = handler.encode})
    else
        return externallinkid({link=handler.link, label=handler.label,
            prefix= "http://openlibrary.org/OL",id=id, separator=handler.separator,
            encode = handler.encode}) .. 
            ' ' .. seterror( 'bad_ol' );
    end
end

--[[
Determines whether an URL string is valid

At present the only check is whether the string appears to 
be prefixed with a URI scheme.  It is not determined whether 
the URI scheme is valid or whether the URL is otherwise well 
formed.
]]
function checkurl( url_str )
    -- Protocol-relative or URL scheme
    return url_str:sub(1,2) == "//" or url_str:match( "^[^/]*:" ) ~= nil;
end

-- Removes irrelevant text and dashes from ISBN number
-- Similar to that used for Special:BookSources
function cleanisbn( isbn_str )
    return isbn_str:gsub( "[^-0-9X]", "" );
end

-- Determines whether an ISBN string is valid
function checkisbn( isbn_str )
    isbn_str = cleanisbn( isbn_str ):gsub( "-", "" );
    local len = isbn_str:len();
 
    if len ~= 10 and len ~= 13 then
        return false;
    end
 
    local temp = 0;
    if len == 10 then
        if isbn_str:match( "^%d*X?$" ) == nil then return false; end
        isbn_str = { isbn_str:byte(1, len) };
        for i, v in ipairs( isbn_str ) do
            if v == string.byte( "X" ) then
                temp = temp + 10*( 11 - i );
            else
                temp = temp + tonumber( string.char(v) )*(11-i);
            end
        end
        return temp % 11 == 0;
    else
        if isbn_str:match( "^%d*$" ) == nil then return false; end
        isbn_str = { isbn_str:byte(1, len) };
        for i, v in ipairs( isbn_str ) do
            temp = temp + (3 - 2*(i % 2)) * tonumber( string.char(v) );
        end
        return temp % 10 == 0;
    end
end

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

-- Escape sequences for content that will be used for URL descriptions
function safeforurl( str )
    if str:match( "%[%[.-%]%]" ) ~= nil then 
        table.insert( z.message_tail, { seterror( 'wikilink_in_url', {}, true ) } );
    end
    
    return str:gsub( '[%[%]\n]', {    
        ['['] = '&#91;',
        [']'] = '&#93;',
        ['\n'] = ' ' } );
end

-- Converts a hyphen to a dash
function hyphentodash( str )
    if not is_set(str) or str:match( "[%[%]{}<>]" ) ~= nil then
        return str;
    end    
    return str:gsub( '-', '–' );
end

-- Protects a string that will be wrapped in wiki italic markup '' ... ''
function safeforitalics( str )
    --[[ Note: We can not use <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 = "[[" .. person.link .. "|" .. one .. "]]" end
            end
            table.insert( text, one )
            table.insert( text, sep_one )
        end
    end

    local count = #text / 2;
    if count > 0 then 
        if count > 1 and is_set(lastauthoramp) and not etal then
            text[#text-2] = " & ";
        end
        text[#text] = nil; 
    end
    
    local result = table.concat(text) -- construct list
    if etal then 
        local etal_text = cfg.messages['et al'];
        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[list_name .. '-Last'], 'redundant_parameters', i );
        if not is_set(last) then
            -- just in case someone passed in an empty parameter
            break;
        end
        names[i] = {
            last = last,
            first = selectone( args, cfg.aliases[list_name .. '-First'], 'redundant_parameters', i ),
            link = selectone( args, cfg.aliases[list_name .. '-Link'], 'redundant_parameters', i ),
            mask = selectone( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i )
        };
        i = i + 1;
    end
    return names;
end

-- Populates ID table from arguments using configuration settings
function extractids( args )
    local id_list = {};
    for k, v in pairs( cfg.id_handlers ) do    
        v = selectone( args, v.parameters, 'redundant_parameters' );
        if is_set(v) then id_list[k] = v; end
    end
    return id_list;
end

-- Takes a table of IDs and turns it into a table of formatted ID outputs.
function buildidlist( id_list, options )
    local new_list, handler = {};
    
    function fallback(k) return { __index = function(t,i) return cfg.id_handlers[k][i] end } end;
    
    for k, v in pairs( id_list ) do
        -- fallback to read-only cfg
        handler = setmetatable( { ['id'] = v }, fallback(k) );
        
        if handler.mode == 'external' then
            table.insert( new_list, {handler.label, externallinkid( handler ) } );
        elseif handler.mode == 'internal' then
            table.insert( new_list, {handler.label, internallinkid( handler ) } );
        elseif handler.mode ~= 'manual' then
            error( cfg.messages['unknown_ID_mode'] );
        elseif k == 'DOI' then
            table.insert( new_list, {handler.label, doi( v, options.DoiBroken ) } );
        elseif k == 'ASIN' then
            table.insert( new_list, {handler.label, amazon( v, options.ASINTLD ) } ); 
        elseif k == 'OL' then
            table.insert( new_list, {handler.label, openlibrary( v ) } );
        elseif k == 'ISBN' then
            local ISBN = internallinkid( handler );
            if not checkisbn( v ) and not is_set(options.IgnoreISBN) then
                ISBN = ISBN .. seterror( 'bad_isbn', {}, false, " ", "" );
            end
            table.insert( new_list, {handler.label, ISBN } );                
        else
            error( cfg.messages['unknown_manual_ID'] );
        end
    end
    
    function comp( a, b )
        return a[1] < b[1];
    end
    
    table.sort( new_list, comp );
    for k, v in ipairs( new_list ) do
        new_list[k] = v[2];
    end
    
    return new_list;
end
  
-- Chooses one matching parameter from a list of parameters to consider
-- Generates an error if more than one match is present.
function selectone( args, possible, error_condition, index )
    local value = nil;
    local selected = '';
    local error_list = {};
    
    if index ~= nil then index = tostring(index); end
    
    -- Handle special case of "#" replaced by empty string
    if index == '1' then
        for _, v in ipairs( possible ) do
            v = v:gsub( "#", "" );
            if is_set(args[v]) then
                if value ~= nil and selected ~= v then
                    table.insert( error_list, v );
                else
                    value = args[v];
                    selected = v;
                end
            end
        end        
    end
    
    for _, v in ipairs( possible ) do
        if index ~= nil then
            v = v:gsub( "#", index );
        end
        if is_set(args[v]) then
            if value ~= nil and selected ~=  v then
                table.insert( error_list, v );
            else
                value = args[v];
                selected = v;
            end
        end
    end
    
    if #error_list > 0 then
        local error_str = "";
        for _, k in ipairs( error_list ) do
            if error_str ~= "" then error_str = error_str .. cfg.messages['parameter-separator'] end
            error_str = error_str .. wrap( 'parameter', k );
        end
        if #error_list > 1 then
            error_str = error_str .. cfg.messages['parameter-final-separator'];
        else
            error_str = error_str .. cfg.messages['parameter-pair-separator'];
        end
        error_str = error_str .. wrap( 'parameter', selected );
        table.insert( z.message_tail, { seterror( error_condition, {error_str}, true ) } );
    end
    
    return value, selected;
end

-- COinS metadata (see <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["rft.genre"] = "bookitem";
        OCinSoutput["rft.btitle"] = data.Chapter;
        OCinSoutput["rft.atitle"] = data.Title;
    elseif is_set(data.Periodical) then
        OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:journal";
        OCinSoutput["rft.genre"] = "article";
        OCinSoutput["rft.jtitle"] = data.Periodical;
        OCinSoutput["rft.atitle"] = data.Title;
    else
        OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:book";
        OCinSoutput["rft.genre"] = "book"
        OCinSoutput["rft.btitle"] = data.Title;
    end
    
    OCinSoutput["rft.place"] = data.PublicationPlace;
    OCinSoutput["rft.date"] = data.Date;
    OCinSoutput["rft.series"] = data.Series;
    OCinSoutput["rft.volume"] = data.Volume;
    OCinSoutput["rft.issue"] = data.Issue;
    OCinSoutput["rft.pages"] = data.Pages;
    OCinSoutput["rft.edition"] = data.Edition;
    OCinSoutput["rft.pub"] = data.PublisherName;
    
    for k, v in pairs( data.ID_list ) do
        local id, value = cfg.id_handlers[k].COinS;
        if k == 'ISBN' then value = cleanisbn( v ); else value = v; end
        if string.sub( id or "", 1, 4 ) == 'info' then
            OCinSoutput["rft_id"] = table.concat{ id, "/", v };
        else
            OCinSoutput[ id ] = value;
        end
    end
    
    local last, first;
    for k, v in ipairs( data.Authors ) do
        last, first = v.last, v.first;
        if k == 1 then
            if is_set(last) then
                OCinSoutput["rft.aulast"] = last;
            end
            if is_set(first) then 
                OCinSoutput["rft.aufirst"] = first;
            end
        end
        if is_set(last) and is_set(first) then
            OCinSoutput["rft.au"] = table.concat{ last, ", ", first };
        elseif is_set(last) then
            OCinSoutput["rft.au"] = last;
        end
    end
    
    OCinSoutput.rft_id = data.URL;
    OCinSoutput.rfr_id = table.concat{ "info:sid/", mw.site.server:match( "[^/]*$" ), ":", data.RawPage };
    OCinSoutput = setmetatable( OCinSoutput, nil );
    
    -- sort with version string always first, and combine.
    table.sort( OCinSoutput );
    table.insert( OCinSoutput, 1, "ctx_ver=" .. ctx_ver );  -- such as "Z39.88-2004"
    return table.concat(OCinSoutput, "&");
end

--[[
This is the main function foing the majority of the citation
formatting.
]]
function citation0( config, args)
    --[[ 
    Load Input Parameters
    The argment_wrapper facillitates the mapping of multiple
    aliases to single internal variable.
    ]]
    local A = argument_wrapper( args );

    local i 
    local PPrefix = A['PPrefix']
    local PPPrefix = A['PPPrefix']
    if is_set( A['NoPP'] ) then PPPrefix = "" PPrefix = "" end
    
    -- Pick out the relevant fields from the arguments.  Different citation templates
    -- define different field names for the same underlying things.    
    local Authors = A['Authors'];
    local a = extractnames( args, 'AuthorList' );

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

    local Year = A['Year'];
    local PublicationDate = A['PublicationDate'];
    local OrigYear = A['OrigYear'];
    local Date = A['Date'];
    local LayDate = A['LayDate'];
    ------------------------------------------------- Get title data
    local Title = A['Title'];
    local BookTitle = A['BookTitle'];
    local Conference = A['Conference'];
    local TransTitle = A['TransTitle'];
    local TitleNote = A['TitleNote'];
    local TitleLink = A['TitleLink'];
    local Chapter = A['Chapter'];
    local ChapterLink = A['ChapterLink'];
    local TransChapter = A['TransChapter'];
    local TitleType = A['TitleType'];
    local ArchiveURL = A['ArchiveURL'];
    local URL = A['URL']
    local URLorigin = A:ORIGIN('URL');
    local ChapterURL = A['ChapterURL'];
    local ChapterURLorigin = A:ORIGIN('ChapterURL');
    local ConferenceURL = A['ConferenceURL'];
    local ConferenceURLorigin = A:ORIGIN('ConferenceURL');
    local Periodical = A['Periodical'];
    
    if ( config.CitationClass == "encyclopaedia" ) then
        if not is_set(Chapter) then
            if not is_set(Title) then
                Title = Periodical;
                Periodical = '';
            else
                Chapter = Title
                TransChapter = TransTitle
                Title = '';
                TransTitle = '';
            end
        end
    end

    local Series = A['Series'];
    local Volume = A['Volume'];
    local Issue = A['Issue'];
    local Position = '';
    local Page, Pages, At, page_type;
    
    Page = A['Page'];
    Pages = hyphentodash( A['Pages'] );
    At = A['At'];
    
    if is_set(Page) then
        if is_set(Pages) or is_set(At) then
            Page = Page .. " " .. seterror('extra_pages');
            Pages = '';
            At = '';
        end
    elseif is_set(Pages) then
        if is_set(At) then
            Pages = Pages .. " " .. seterror('extra_pages');
            At = '';
        end
    end    
    
    local Edition = A['Edition'];
    local PublicationPlace = A['PublicationPlace']
    local Place = A['Place'];
    
    if not is_set(PublicationPlace) and is_set(Place) then
        PublicationPlace = Place;
    end
    
    if PublicationPlace == Place then Place = ''; end
    
    local PublisherName = A['PublisherName'];
    local SubscriptionRequired = A['SubscriptionRequired'];
    local Via = A['Via'];
    local AccessDate = A['AccessDate'];
    local ArchiveDate = A['ArchiveDate'];
    local Agency = A['Agency'];
    local DeadURL = A['DeadURL']
    local Language = A['Language'];
    local Format = A['Format'];
    local Ref = A['Ref'];
    
    local DoiBroken = A['DoiBroken'];
    local ID = A['ID'];
    local ASINTLD = A['ASINTLD'];
    local IgnoreISBN = A['IgnoreISBN'];

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

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

    if ( config.CitationClass == "journal" ) then
        if not is_set(URL) and is_set(ID_list['PMC']) then
            local Embargo = A['Embargo'];
            if is_set(Embargo) then
                local lang = mw.getContentLanguage();
                local good1, result1, good2, result2;
                good1, result1 = pcall( lang.formatDate, lang, 'U', Embargo );
                good2, result2 = pcall( lang.formatDate, lang, 'U' );
                
                if good1 and good2 and tonumber( result1 ) < tonumber( result2 ) then 
                    URL = "http://www.ncbi.nlm.nih.gov/pmc/articles/PMC" .. ID_list['PMC'];
                    URLorigin = cfg.id_handlers['PMC'].parameters[1];
                end
            else
                URL = "http://www.ncbi.nlm.nih.gov/pmc/articles/PMC" .. ID_list['PMC'];
                URLorigin = cfg.id_handlers['PMC'].parameters[1];
            end
        end
    end

    -- At this point fields may be nil if they weren't specified in the template use.  We can use that fact.
    
    -- Account for the oddity that is {{cite conference}}, before generation of COinS data.
    if is_set(BookTitle) then
        Chapter = Title;
        ChapterLink = TitleLink;
        TransChapter = TransTitle;
        Title = BookTitle;
        TitleLink = '';
        TransTitle = '';
    end
    -- Account for the oddity that is {{cite episode}}, before generation of COinS data.
    if config.CitationClass == "episode" then
        local AirDate = A['AirDate'];
        local SeriesLink = A['SeriesLink'];
        local Season = A['Season'];
        local SeriesNumber = A['SeriesNumber'];
        local Network = A['Network'];
        local Station = A['Station'];
        local s, n = {}, {};
        local Sep = (first_set(A["SeriesSeparator"], A["Separator"]) or "") .. " ";
        
        if is_set(Issue) then table.insert(s, cfg.messages["episode"] .. " " .. Issue); Issue = ''; end
        if is_set(Season) then table.insert(s, cfg.messages["season"] .. " " .. Season); end
        if is_set(SeriesNumber) then table.insert(s, cfg.messages["series"] .. " " .. SeriesNumber); end
        if is_set(Network) then table.insert(n, Network); end
        if is_set(Station) then table.insert(n, Station); end
        
        Date = Date or AirDate;
        Chapter = Title;
        ChapterLink = TitleLink;
        TransChapter = TransTitle;
        Title = Series;
        TitleLink = SeriesLink;
        TransTitle = '';
        
        Series = table.concat(s, Sep);
        ID = table.concat(n, Sep);
    end
    
    -- COinS metadata (see <http://ocoins.info/>) for
    -- automated parsing of citation information.
    local OCinSoutput = COinS{
        ['Periodical'] = Periodical,
        ['Chapter'] = Chapter,
        ['Title'] = Title,
        ['PublicationPlace'] = PublicationPlace,
        ['Date'] = first_set(Date, Year, PublicationDate),
        ['Series'] = Series,
        ['Volume'] = Volume,
        ['Issue'] = Issue,
        ['Pages'] = first_set(Page, Pages, At),
        ['Edition'] = Edition,
        ['PublisherName'] = PublisherName,
        ['URL'] = first_set( URL, ChapterURL ),
        ['Authors'] = a,
        ['ID_list'] = ID_list,
        ['RawPage'] = this_page.prefixedText,
    };

    if is_set(Periodical) and not is_set(Chapter) and is_set(Title) then
        Chapter = Title;
        ChapterLink = TitleLink;
        TransChapter = TransTitle;
        Title = '';
        TitleLink = '';
        TransTitle = '';
    end

    -- Now perform various field substitutions.
    -- We also add leading spaces and surrounding markup and punctuation to the
    -- various parts of the citation, but only when they are non-nil.
    if not is_set(Authors) then
        local Maximum = tonumber( A['DisplayAuthors'] );
        
        -- Preserve old-style implicit et al.
        if not is_set(Maximum) and #a == 9 then 
            Maximum = 8;
            table.insert( z.message_tail, { seterror('implict_etal_author', {}, true ) } );
        elseif not is_set(Maximum) then
            Maximum = #a + 1;
        end
            
        local control = { 
            sep = A["AuthorSeparator"] .. " ",
            namesep = (first_set(A["AuthorNameSeparator"], A["NameSeparator"]) or "") .. " ",
            format = A["AuthorFormat"],
            maximum = Maximum,
            lastauthoramp = LastAuthorAmp
        };
        
        -- If the coauthor field is also used, prevent ampersand and et al. formatting.
        if is_set(Coauthors) then
            control.lastauthoramp = nil;
            control.maximum = #a + 1;
        end
        
        Authors = listpeople(control, a) 
    end
    
    local EditorCount
    if not is_set(Editors) then
        local Maximum = tonumber( A['DisplayEditors'] );
        -- Preserve old-style implicit et al.
        if not is_set(Maximum) and #e == 4 then 
            Maximum = 3;
            table.insert( z.message_tail, { seterror('implict_etal_editor', {}, true) } );
        elseif not is_set(Maximum) then
            Maximum = #e + 1;
        end

        local control = { 
            sep = A["EditorSeparator"] .. " ",
            namesep = (first_set(A["EditorNameSeparator"], A["NameSeparator"]) or "") .. " ",
            format = A['EditorFormat'],
            maximum = Maximum,
            lastauthoramp = LastAuthorAmp
        };

        Editors, EditorCount = listpeople(control, e);
    else
        EditorCount = 1;
    end
    
    if not is_set(Date) then
        Date = Year;
        if is_set(Date) then
            local Month = A['Month'];
            if is_set(Month) then 
                Date = Month .. " " .. Date;
                local Day = A['Day']
                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 = "[[" .. ChapterLink .. "|" .. 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 = "[[" .. TitleLink .. "|" .. Title .. "]]"
    end
    
    if is_set(Periodical) then
        Title = wrap( 'quoted-title', Title );
        TransTitle = wrap( 'trans-quoted-title', TransTitle );
    elseif inArray(config.CitationClass, {"web","news","pressrelease"}) and
            not is_set(Chapter) then
        Title = wrap( 'quoted-title', Title );
        TransTitle = wrap( 'trans-quoted-title', TransTitle );
    else
        Title = wrap( 'italic-title', Title );
        TransTitle = wrap( 'trans-italic-title', TransTitle );
    end
    
    TransError = "";
    if is_set(TransTitle) then
        if not is_set(Title) then
            TransError = " " .. seterror( 'trans_missing_title' );
        else
            TransTitle = " " .. TransTitle;
        end
    end
    
    Title = Title .. TransTitle;
    
    if is_set(Title) then
        if not is_set(TitleLink) and is_set(URL) then 
            Title = externallink( URL, Title ) .. TransError .. Format       
            URL = "";
            Format = "";
        else
            Title = Title .. TransError;
        end
    end
    
    if is_set(Place) then
        if sepc == '.' then
            Place = " " .. wrap( 'written', Place ) .. sepc .. " ";
        else
            Place = " " .. substitute( cfg.messages['written']:lower(), {Place} ) .. sepc .. " ";
        end
    end
    
    if is_set(Conference) then
        if is_set(ConferenceURL) then
            Conference = externallink( ConferenceURL, Conference );
        end
        Conference = " " .. Conference
    elseif is_set(ConferenceURL) then
        Conference = " " .. externallink( ConferenceURL, nil, ConferenceURLorigin );
    end
    
    if not is_set(Position) then
        local Minutes = A['Minutes'];
        if is_set(Minutes) then
            Position = " " .. Minutes .. " " .. cfg.messages['minutes'];
        else
            local Time = A['Time'];
            if is_set(Time) then
                local TimeCaption = A['TimeCaption']
                if not is_set(TimeCaption) then
                    TimeCaption = cfg.messages['event'];
                    if sepc ~= '.' then
                        TimeCaption = TimeCaption:lower();
                    end
                end
                Position = " " .. TimeCaption .. " " .. Time;
            end
        end
    else
        Position = " " .. Position;
        At = '';
    end
    
    if not is_set(Page) then
        if is_set(Pages) then
            if is_set(Periodical) and
                not inArray(config.CitationClass, {"encyclopaedia","web","book","news"}) then
                Pages = ": " .. Pages;
            elseif 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 (" [" .. OrigYear .. "]") or "";
    Agency = is_set(Agency) and (sepc .. " " .. Agency) or "";
    
    if is_set(Volume) then
        if ( mw.ustring.len(Volume) > 4 )
          then Volume = sepc .." " .. Volume;
          else Volume = " <b>" .. 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['retrieved']
        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['subscription'];
    end
    
    if is_set(ID) then ID = sepc .." ".. ID; end
    
    ID_list = buildidlist( ID_list, {DoiBroken = DoiBroken, ASINTLD = ASINTLD, IgnoreISBN = IgnoreISBN} );

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

    if is_set(Quote) then
        if Quote:sub(1,1) == '"' and Quote:sub(-1,-1) == '"' then
            Quote = Quote:sub(2,-2);
        end
        Quote = sepc .." " .. wrap( 'quoted-text', Quote ); 
        PostScript = "";
    elseif PostScript:lower() == "none" then
        PostScript = "";
    end
    
    local Archived
    if is_set(ArchiveURL) then
        if not is_set(ArchiveDate) then
            ArchiveDate = seterror('archive_missing_date');
        end
        if "no" == DeadURL then
            local arch_text = cfg.messages['archived'];
            if sepc ~= "." then arch_text = arch_text:lower() end
            Archived = sepc .. " " .. substitute( cfg.messages['archived-not-dead'],
                { externallink( ArchiveURL, arch_text ), ArchiveDate } );
            if not is_set(OriginalURL) then
                Archived = Archived .. " " .. seterror('archive_missing_url');                               
            end
        elseif is_set(OriginalURL) then
            local arch_text = cfg.messages['archived-dead'];
            if sepc ~= "." then arch_text = arch_text:lower() end
            Archived = sepc .. " " .. substitute( arch_text,
                { externallink( OriginalURL, cfg.messages['original'] ), ArchiveDate } );
        else
            local arch_text = cfg.messages['archived-missing'];
            if sepc ~= "." then arch_text = arch_text:lower() end
            Archived = 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['lay summary'] ) .. LaySource .. LayDate
        else
            Lay = sepc .. " " .. externallink( LayURL, cfg.messages['lay summary']:lower() ) .. LaySource .. LayDate
        end            
    else
        Lay = "";
    end
    
    if is_set(Transcript) then
        if is_set(TranscriptURL) then Transcript = externallink( TranscriptURL, Transcript ); end
    elseif is_set(TranscriptURL) then
        Transcript = externallink( TranscriptURL, nil, TranscriptURLorigin );
    end
    
    local Publisher;
    if is_set(Periodical) and
        not inArray(config.CitationClass, {"encyclopaedia","web","pressrelease"}) then
        if is_set(PublisherName) then
            if is_set(PublicationPlace) then
                Publisher = PublicationPlace .. ": " .. PublisherName;
            else
                Publisher = PublisherName;  
            end
        elseif is_set(PublicationPlace) then
            Publisher= PublicationPlace;
        else 
            Publisher = "";
        end
        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['AuthorSeparator'] .. " " .. 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['in'] .. " "
            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['editor'];
            else
                Editors = Editors .. ", " .. cfg.messages['editors'];
            end
            Date = " (" .. Date ..")" .. OrigYear .. sepc .. " "
        else
            if EditorCount <= 1 then
                Editors = Editors .. " (" .. cfg.messages['editor'] .. ")" .. sepc .. " "
            else
                Editors = Editors .. " (" .. cfg.messages['editors'] .. ")" .. sepc .. " "
            end
        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[i] = v.last 
                    if i == 4 then break end
                end
            elseif is_set(Editors) then
                for i,v in ipairs(e) do 
                    names[i] = v.last 
                    if i == 4 then break end                
                end
            end
            names[ #names + 1 ] = Year;
            id = anchorid(names)
        end
        options.id = id;
    end
    
    if string.len(text:gsub("<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[1]) then
                if i == #z.message_tail then
                    text = text .. errorcomment( v[1], v[2] );
                else
                    text = text .. errorcomment( v[1] .. "; ", v[2] );
                end
            end
        end
    end
    
    no_tracking_cats = no_tracking_cats:lower();
    if inArray(no_tracking_cats, {"", "no", "false", "n"}) then
        for _, v in ipairs( z.error_categories ) do
            text = text .. '[[Category:' .. v ..']]';
        end
    end
    
    return text
end

-- This is used by templates such as {{cite book}} to create the actual citation text.
function z.citation(frame)
    local pframe = frame:getParent()
    
    local args = {};
    local suggestions = {};
    local error_text, error_state;

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

    for k, v in pairs( pframe.args ) do
        if v ~= '' then
            if not validate( k ) then            
                error_text = "";
                if type( k ) ~= 'string' then
                    -- Exclude empty numbered parameters
                    if v:match("%S+") ~= nil then
                        error_text, error_state = seterror( 'text_ignored', {v}, true );
                    end
                elseif validate( k:lower() ) then 
                    error_text, error_state = seterror( 'parameter_ignored_suggest', {k, k:lower()}, true );
                else
                    if #suggestions == 0 then
                        suggestions = mw.loadData( 'Module:Citation/CS1/Suggestions' );
                    end
                    if suggestions[ k:lower() ] ~= nil then
                        error_text, error_state = seterror( 'parameter_ignored_suggest', {k, suggestions[ k:lower() ]}, true );
                    else
                        error_text, error_state = seterror( 'parameter_ignored', {k}, true );
                    end
                end                  
                if error_text ~= '' then
                    table.insert( z.message_tail, {error_text, error_state} );
                end                
            end
            args[k] = v;
        elseif args[k] ~= nil or (k == 'postscript') then
            args[k] = v;
        end        
    end    
    
    return citation0( config, args)
end

return z