Difference between revisions of "Module:Citation/CS1"
From Timelines
(sync with sandbox, merge cite_web_title to bare_url, better style control on translations, category suppression by config, tweak display authors) |
(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 5: | Line 5: | ||
} | } | ||
− | + | -- Include translation message hooks, ID and error handling configuration settings. | |
− | local | + | -- 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 | -- 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 |
− | -- | + | function is_set( var ) |
− | function | + | return not (var == nil or var == ''); |
− | if | + | end |
− | return | + | |
+ | -- 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 | 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( | + | function wrap( key, str ) |
− | if str | + | if not is_set( str ) then |
return ""; | return ""; | ||
− | + | elseif inArray( key, { 'italic-title', 'trans-italic-title' } ) then | |
− | |||
− | |||
str = safeforitalics( str ); | str = safeforitalics( str ); | ||
end | end | ||
− | return substitute( cfg. | + | return substitute( cfg.messages[key], {str} ); |
end | end | ||
Line 48: | Line 63: | ||
]] | ]] | ||
function argument_wrapper( args ) | function argument_wrapper( args ) | ||
− | + | local origin = {}; | |
− | local | ||
− | local | + | return setmetatable({ |
− | __index = function ( tbl, k ) | + | ORIGIN = function( self, k ) |
− | if | + | 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. | + | local args, list, v = args, cfg.aliases[k]; |
− | + | ||
if list == nil then | if list == nil then | ||
− | error( cfg. | + | error( cfg.messages['unknown_argument_map'] ); |
elseif type( list ) == 'string' then | elseif type( list ) == 'string' then | ||
− | v = | + | v, origin[k] = args[list], list; |
− | else | + | else |
− | v = selectone( | + | v, origin[k] = selectone( args, list, '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. | + | v = cfg.defaults[k] or ""; |
+ | origin[k] = ''; --Empty string, not nil; | ||
end | end | ||
− | + | ||
tbl = rawset( tbl, k, v ); | tbl = rawset( tbl, k, v ); | ||
− | |||
return v; | return v; | ||
end, | end, | ||
− | } | + | }); |
− | |||
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 ); | |
− | |||
− | |||
− | |||
− | |||
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 | if error_state == nil then | ||
− | error( cfg. | + | error( cfg.messages['undefined_error'] ); |
+ | elseif is_set( error_state.category ) then | ||
+ | table.insert( z.error_categories, error_state.category ); | ||
end | end | ||
− | + | local message = substitute( error_state.message, arguments ); | |
− | |||
− | |||
− | + | message = message .. " ([[" .. cfg.messages['help page link'] .. | |
− | |||
− | |||
− | |||
"#" .. error_state.anchor .. "|" .. | "#" .. error_state.anchor .. "|" .. | ||
− | cfg. | + | cfg.messages['help page label'] .. "]])"; |
− | + | ||
z.error_ids[ error_id ] = true; | z.error_ids[ error_id ] = true; | ||
− | if (error_id | + | 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 = 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 | end | ||
-- Formats a wiki style external link | -- Formats a wiki style external link | ||
function externallinkid(options) | function externallinkid(options) | ||
− | + | local url_string = options.id; | |
− | |||
− | 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, options.separator or " ", |
− | + | 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]]', | |
− | + | options.link, options.label, options.separator or " ", | |
− | + | options.prefix, options.id, options.suffix or "", | |
− | + | 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 | + | if not is_set( label ) then |
label = URL; | label = URL; | ||
− | error_str = seterror( 'bare_url_missing_title', {}, false, " " ); | + | 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 | 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 "[" | ||
end | end | ||
-- Formats a link to Amazon | -- Formats a link to Amazon | ||
function amazon(id, domain) | function amazon(id, domain) | ||
− | if ( | + | 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 | + | 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. | + | 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 | |
− | + | return url_str:sub(1,2) == "//" or url_str:match( "^[^/]*:" ) ~= nil; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
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 ) | ||
− | + | return (str:gsub( "%[%[([^%[%]]*)%]%]", function(l) | |
− | + | return l:gsub( "^[^|]*|(.*)$", "%1" ):gsub("^%s*(.-)%s*$", "%1"); | |
− | + | 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 | + | if not is_set(str) or 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 | + | 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. | + | 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; | ||
− | |||
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 | + | 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 | + | 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 | + | 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 | + | 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 | + | 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. | + | 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" .. | + | return "CITEREF" .. table.concat( options ); |
end | end | ||
Line 538: | Line 536: | ||
while true do | while true do | ||
− | last = selectone( args, cfg. | + | last = selectone( args, cfg.aliases[list_name .. '-Last'], 'redundant_parameters', i ); |
− | if ( | + | if not is_set(last) then |
− | + | -- just in case someone passed in an empty parameter | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
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 | ||
− | + | 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 = {}; |
− | + | ||
+ | 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 | ||
− | + | -- fallback to read-only cfg | |
− | + | handler = setmetatable( { ['id'] = v }, fallback(k) ); | |
− | -- | ||
− | |||
− | |||
− | |||
− | |||
− | if handler.mode == 'external' then | + | 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 | + | 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 | else | ||
− | error( cfg. | + | 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] | + | 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] | + | 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 .. | + | if error_str ~= "" then error_str = error_str .. cfg.messages['parameter-separator'] end |
− | error_str = error_str .. | + | error_str = error_str .. wrap( 'parameter', k ); |
end | end | ||
if #error_list > 1 then | if #error_list > 1 then | ||
− | error_str = error_str .. | + | error_str = error_str .. cfg.messages['parameter-final-separator']; |
else | else | ||
− | error_str = error_str .. | + | error_str = error_str .. cfg.messages['parameter-pair-separator']; |
end | end | ||
− | error_str = error_str .. | + | 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 ( | + | 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 | + | if not is_set(Chapter) then |
− | if (Title | + | if not is_set(Title) then |
Title = Periodical; | Title = Periodical; | ||
− | Periodical = | + | Periodical = ''; |
else | else | ||
Chapter = Title | Chapter = Title | ||
TransChapter = TransTitle | TransChapter = TransTitle | ||
− | Title = | + | Title = ''; |
− | TransTitle = | + | 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 = | + | 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 | + | |
− | if Pages | + | 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 = | + | Pages = ''; |
− | At = | + | At = ''; |
end | end | ||
− | elseif Pages | + | elseif is_set(Pages) then |
− | if At | + | if is_set(At) then |
Pages = Pages .. " " .. seterror('extra_pages'); | Pages = Pages .. " " .. seterror('extra_pages'); | ||
− | At = | + | 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 | + | |
+ | if not is_set(PublicationPlace) and is_set(Place) then | ||
PublicationPlace = Place; | PublicationPlace = Place; | ||
end | end | ||
− | if PublicationPlace == Place then Place = | + | |
+ | 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 | + | 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'] | + | 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 | + | |
+ | 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 | + | 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 | 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 = | + | TitleLink = ''; |
− | TransTitle = | + | 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 = {}, {}; |
− | if Issue | + | local Sep = (first_set(A["SeriesSeparator"], A["Separator"]) or "") .. " "; |
− | if Season | + | |
− | if SeriesNumber | + | 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 Network | + | if is_set(SeriesNumber) then table.insert(s, cfg.messages["series"] .. " " .. SeriesNumber); end |
− | if Station | + | if is_set(Network) then table.insert(n, Network); end |
− | Date = Date or AirDate | + | if is_set(Station) then table.insert(n, Station); end |
− | Chapter = Title | + | |
− | ChapterLink = TitleLink | + | Date = Date or AirDate; |
− | TransChapter = TransTitle | + | Chapter = Title; |
− | Title = Series | + | ChapterLink = TitleLink; |
− | TitleLink = SeriesLink | + | TransChapter = TransTitle; |
− | TransTitle = | + | Title = Series; |
− | + | TitleLink = SeriesLink; | |
− | Series = table.concat(s, Sep) | + | TransTitle = ''; |
− | ID = table.concat(n, Sep | + | |
− | + | Series = table.concat(s, Sep); | |
− | + | ID = table.concat(n, Sep); | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
end | end | ||
− | + | -- COinS metadata (see <http://ocoins.info/>) for | |
− | + | -- automated parsing of citation information. | |
− | + | local OCinSoutput = COinS{ | |
− | local | + | ['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 = ''; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | if (Periodical | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
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 | + | 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 | + | 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 | + | 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"] | + | 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 | + | 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 | + | 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 | + | 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 | + | 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"] | + | 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 not is_set(Date) then | |
− | + | Date = Year; | |
− | + | if is_set(Date) then | |
− | + | local Month = A['Month']; | |
− | + | if is_set(Month) then | |
− | + | Date = Month .. " " .. Date; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | Date = Year | ||
− | if ( Date | ||
− | local Month = A['Month'] | ||
− | if ( Month | ||
− | Date = Month .. " " .. Date | ||
local Day = A['Day'] | local Day = A['Day'] | ||
− | if ( Day | + | if is_set(Day) then Date = Day .. " " .. Date end |
− | |||
end | end | ||
− | |||
end | end | ||
end | end | ||
− | if ( PublicationDate | + | |
− | if | + | if inArray(PublicationDate, {Date, Year}) then PublicationDate = ''; end |
+ | if not is_set(Date) and is_set(PublicationDate) then | ||
Date = PublicationDate; | Date = PublicationDate; | ||
− | PublicationDate = | + | 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 | + | 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 | -- 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 | + | 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 = | + | AccessDate = ''; |
− | end | + | end |
− | + | ||
-- Test if format is given without giving a URL | -- Test if format is given without giving a URL | ||
− | if ( Format | + | 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 | + | 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 ) } ); | table.insert( z.message_tail, { seterror( 'citation_missing_title', {}, true ) } ); | ||
end | end | ||
− | + | ||
− | + | Format = is_set(Format) and " (" .. Format .. ")" or ""; | |
− | |||
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 | + | if is_set(Chapter) and is_set(ChapterLink) then |
Chapter = "[[" .. ChapterLink .. "|" .. Chapter .. "]]"; | Chapter = "[[" .. ChapterLink .. "|" .. Chapter .. "]]"; | ||
end | end | ||
− | if ( | + | 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 | + | if is_set(TransChapter) then |
− | + | if not is_set(Chapter) then | |
+ | TransError = " " .. seterror( 'trans_missing_chapter' ); | ||
+ | else | ||
+ | TransChapter = " " .. TransChapter; | ||
+ | end | ||
end | end | ||
− | + | Chapter = Chapter .. TransChapter; | |
− | |||
− | if Chapter | + | if is_set(Chapter) then |
− | if ( ChapterLink | + | if not is_set(ChapterLink) then |
− | if ( | + | if is_set(ChapterURL) then |
Chapter = externallink( ChapterURL, Chapter ) .. TransError; | Chapter = externallink( ChapterURL, Chapter ) .. TransError; | ||
− | if URL | + | if not is_set(URL) then |
Chapter = Chapter .. Format; | Chapter = Chapter .. Format; | ||
Format = ""; | Format = ""; | ||
end | end | ||
− | elseif ( | + | elseif is_set(URL) then |
Chapter = externallink( URL, Chapter ) .. TransError .. Format; | Chapter = externallink( URL, Chapter ) .. TransError .. Format; | ||
− | URL = | + | URL = ""; |
− | Format = "" | + | Format = ""; |
else | else | ||
Chapter = Chapter .. TransError; | Chapter = Chapter .. TransError; | ||
end | end | ||
− | elseif ChapterURL | + | 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 | + | 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 | + | if is_set(TitleLink) and is_set(Title) then |
− | + | Title = "[[" .. TitleLink .. "|" .. Title .. "]]" | |
− | |||
− | |||
end | end | ||
− | + | ||
− | if ( | + | 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 | + | elseif inArray(config.CitationClass, {"web","news","pressrelease"}) and |
− | + | not is_set(Chapter) then | |
− | |||
− | Chapter | ||
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 | ||
− | + | TransError = ""; | |
− | if TransTitle | + | if is_set(TransTitle) then |
− | + | if not is_set(Title) then | |
+ | TransError = " " .. seterror( 'trans_missing_title' ); | ||
+ | else | ||
+ | TransTitle = " " .. TransTitle; | ||
+ | end | ||
end | end | ||
− | + | Title = Title .. TransTitle; | |
− | |||
− | if Title | + | if is_set(Title) then |
− | if ( TitleLink | + | if not is_set(TitleLink) and is_set(URL) then |
Title = externallink( URL, Title ) .. TransError .. Format | Title = externallink( URL, Title ) .. TransError .. Format | ||
− | URL = | + | URL = ""; |
− | Format = | + | Format = ""; |
else | else | ||
Title = Title .. TransError; | Title = Title .. TransError; | ||
end | end | ||
end | end | ||
− | + | ||
− | if ( Place | + | 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. | + | Place = " " .. substitute( cfg.messages['written']:lower(), {Place} ) .. sepc .. " "; |
− | end | + | end |
− | |||
− | |||
end | end | ||
− | if ( Conference | + | if is_set(Conference) then |
− | if ( ConferenceURL | + | if is_set(ConferenceURL) then |
Conference = externallink( ConferenceURL, Conference ); | Conference = externallink( ConferenceURL, Conference ); | ||
end | end | ||
Conference = " " .. Conference | Conference = " " .. Conference | ||
− | elseif ConferenceURL | + | elseif is_set(ConferenceURL) then |
− | Conference = " " .. externallink( ConferenceURL ); | + | Conference = " " .. externallink( ConferenceURL, nil, ConferenceURLorigin ); |
− | |||
− | |||
end | end | ||
− | + | ||
− | if ( | + | if not is_set(Position) then |
local Minutes = A['Minutes']; | local Minutes = A['Minutes']; | ||
− | if ( | + | if is_set(Minutes) then |
− | Position = " " .. Minutes .. " " .. cfg. | + | Position = " " .. Minutes .. " " .. cfg.messages['minutes']; |
else | else | ||
local Time = A['Time']; | local Time = A['Time']; | ||
− | if ( | + | if is_set(Time) then |
local TimeCaption = A['TimeCaption'] | local TimeCaption = A['TimeCaption'] | ||
− | if TimeCaption | + | if not is_set(TimeCaption) then |
− | TimeCaption = cfg. | + | 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; |
− | |||
− | |||
end | end | ||
end | end | ||
else | else | ||
− | Position = " " .. Position | + | Position = " " .. Position; |
+ | At = ''; | ||
end | end | ||
− | if ( | + | |
− | + | if not is_set(Page) then | |
− | if ( | + | 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; | |
− | |||
− | |||
− | |||
− | else Pages = sepc .." " .. PPPrefix .. Pages | ||
end | end | ||
end | end | ||
else | else | ||
− | + | if is_set(Periodical) and | |
− | if ( Periodical | + | not inArray(config.CitationClass, {"encyclopaedia","web","book","news"}) then |
− | + | Page = ": " .. Page; | |
− | |||
− | |||
− | |||
− | Page = ": " .. Page | ||
else | else | ||
− | Page = sepc .." " .. PPrefix .. Page | + | Page = sepc .." " .. PPrefix .. Page; |
end | end | ||
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 | |
− | |||
− | 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 | ||
− | + | end | |
− | + | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
------------------------------------ totally unrelated data | ------------------------------------ totally unrelated data | ||
− | if ( | + | if is_set(Via) then Via = " " .. wrap( 'via', Via ); end |
− | + | if is_set(AccessDate) then | |
− | + | local retrv_text = " " .. cfg.messages['retrieved'] | |
− | if ( AccessDate | + | 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 | |
− | if ( SubscriptionRequired | + | SubscriptionRequired = sepc .. " " .. cfg.messages['subscription']; |
− | |||
− | SubscriptionRequired = sepc .. " " .. cfg. | ||
− | |||
− | |||
end | end | ||
− | if ( ID | + | |
− | + | 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 | + | if is_set(URL) then |
− | URL = " " .. externallink( URL ); | + | URL = " " .. externallink( URL, nil, URLorigin ); |
− | |||
− | |||
end | end | ||
− | if ( Quote | + | 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 | |
− | + | PostScript = ""; | |
− | |||
end | end | ||
local Archived | local Archived | ||
− | if ( | + | if is_set(ArchiveURL) then |
− | if ( ArchiveDate | + | if not is_set(ArchiveDate) then |
ArchiveDate = seterror('archive_missing_date'); | ArchiveDate = seterror('archive_missing_date'); | ||
end | end | ||
− | if | + | if "no" == DeadURL then |
− | local arch_text = cfg. | + | local arch_text = cfg.messages['archived']; |
− | if | + | if sepc ~= "." then arch_text = arch_text:lower() end |
− | Archived = sepc .. " " .. substitute( cfg. | + | Archived = sepc .. " " .. substitute( cfg.messages['archived-not-dead'], |
{ externallink( ArchiveURL, arch_text ), ArchiveDate } ); | { externallink( ArchiveURL, arch_text ), ArchiveDate } ); | ||
− | if OriginalURL | + | 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 sepc ~= "." then arch_text = arch_text:lower() end | |
− | + | Archived = sepc .. " " .. substitute( arch_text, | |
− | + | { seterror('archive_missing_url'), ArchiveDate } ); | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
end | end | ||
else | else | ||
Archived = "" | Archived = "" | ||
end | end | ||
+ | |||
local Lay | local Lay | ||
− | if ( | + | if is_set(LayURL) then |
− | if ( LayDate | + | if is_set(LayDate) then LayDate = " (" .. LayDate .. ")" end |
− | if ( LaySource | + | if is_set(LaySource) then |
− | LaySource = " – ''" .. safeforitalics(LaySource) .. "''" | + | LaySource = " – ''" .. safeforitalics(LaySource) .. "''"; |
− | else | + | else |
− | LaySource = "" | + | LaySource = ""; |
end | end | ||
if sepc == '.' then | if sepc == '.' then | ||
− | Lay = sepc .. " " .. externallink( | + | Lay = sepc .. " " .. externallink( LayURL, cfg.messages['lay summary'] ) .. LaySource .. LayDate |
else | else | ||
− | Lay = sepc .. " " .. externallink( | + | Lay = sepc .. " " .. externallink( LayURL, cfg.messages['lay summary']:lower() ) .. LaySource .. LayDate |
end | end | ||
else | else | ||
− | Lay = "" | + | Lay = ""; |
end | end | ||
− | if ( | + | |
− | if ( TranscriptURL | + | if is_set(Transcript) then |
− | elseif TranscriptURL | + | if is_set(TranscriptURL) then Transcript = externallink( TranscriptURL, Transcript ); end |
− | Transcript = externallink( TranscriptURL ) | + | elseif is_set(TranscriptURL) then |
− | + | Transcript = externallink( TranscriptURL, nil, TranscriptURLorigin ); | |
− | |||
end | end | ||
− | local Publisher | + | |
− | if ( Periodical | + | local Publisher; |
− | + | if is_set(Periodical) and | |
− | + | not inArray(config.CitationClass, {"encyclopaedia","web","pressrelease"}) then | |
− | + | if is_set(PublisherName) then | |
− | if ( PublisherName | + | if is_set(PublicationPlace) then |
− | if (PublicationPlace | ||
Publisher = PublicationPlace .. ": " .. PublisherName; | Publisher = PublicationPlace .. ": " .. PublisherName; | ||
else | else | ||
Publisher = PublisherName; | Publisher = PublisherName; | ||
− | end | + | end |
− | elseif (PublicationPlace | + | elseif is_set(PublicationPlace) then |
Publisher= PublicationPlace; | Publisher= PublicationPlace; | ||
else | else | ||
Publisher = ""; | Publisher = ""; | ||
end | end | ||
− | if ( PublicationDate | + | if is_set(PublicationDate) then |
− | if Publisher | + | 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 | + | if is_set(Publisher) then |
Publisher = " (" .. Publisher .. ")"; | Publisher = " (" .. Publisher .. ")"; | ||
end | end | ||
else | else | ||
− | if ( PublicationDate | + | if is_set(PublicationDate) then |
− | PublicationDate = " (" .. wrap( 'published', PublicationDate ) .. ")" | + | PublicationDate = " (" .. wrap( 'published', PublicationDate ) .. ")"; |
− | |||
− | |||
end | end | ||
− | if ( PublisherName | + | if is_set(PublisherName) then |
− | if (PublicationPlace | + | 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 | + | 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 | + | if is_set(Periodical) then |
− | if ( Title | + | 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 | ||
− | + | 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 | + | if inArray(config.CitationClass, {"journal","citation"}) and is_set(Periodical) then |
− | + | if is_set(Others) then Others = Others .. sepc .. " " end | |
− | if (Others | ||
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 ( | + | if is_set(Authors) then |
− | if (Coauthors | + | if is_set(Coauthors) then |
− | + | Authors = Authors .. A['AuthorSeparator'] .. " " .. Coauthors | |
end | end | ||
− | if ( | + | if is_set(Date) then |
− | + | Date = " ("..Date..")" .. OrigYear .. sepc .. " " | |
− | + | elseif string.sub(Authors,-1,-1) == sepc then | |
− | + | Authors = Authors .. " " | |
− | + | else | |
− | + | Authors = Authors .. sepc .. " " | |
− | |||
end | end | ||
− | if ( | + | if is_set(Editors) then |
− | local in_text = " in " | + | local in_text = " " .. cfg.messages['in'] .. " " |
− | if (sepc | + | 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 ( | + | elseif is_set(Editors) then |
− | if ( | + | if is_set(Date) then |
if EditorCount <= 1 then | if EditorCount <= 1 then | ||
− | Editors = Editors .. ", " .. cfg. | + | Editors = Editors .. ", " .. cfg.messages['editor']; |
else | else | ||
− | Editors = Editors .. ", " .. cfg. | + | 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. | + | Editors = Editors .. " (" .. cfg.messages['editor'] .. ")" .. sepc .. " " |
else | else | ||
− | Editors = Editors .. " (" .. cfg. | + | 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 ( | + | 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 | + | end |
− | if | + | if config.CitationClass=="journal" and is_set(Periodical) then |
− | + | text = safejoin( {Chapter, Place, tcommon}, sepc ); | |
− | + | text = safejoin( {text, pgtext, Date, idcommon}, sepc ); | |
else | else | ||
− | + | text = safejoin( {Chapter, Place, tcommon, Date}, sepc ); | |
− | + | text = safejoin( {text, pgtext, idcommon}, sepc ); | |
end | end | ||
end | end | ||
− | if PostScript | + | 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 | + | if not is_set(Year) then |
− | if ( DateIn | + | if is_set(DateIn) then |
− | Year = selectyear( DateIn ) | + | Year = selectyear( DateIn ); |
− | elseif( PublicationDate | + | elseif is_set(PublicationDate) then |
− | Year = selectyear( PublicationDate ) | + | Year = selectyear( PublicationDate ); |
− | |||
− | |||
end | end | ||
end | end | ||
− | local | + | |
− | if ( config.CitationClass ~= "citation" | + | local options = {}; |
− | + | ||
− | + | if is_set(config.CitationClass) and config.CitationClass ~= "citation" then | |
− | if ( Ref ~= | + | options.class = "citation " .. config.CitationClass; |
+ | 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 ( | + | 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 ( | + | 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 | + | if is_set(options.id) then |
− | text = '<span id="' .. | + | text = '<span id="' .. mw.uri.anchorEncode(options.id) ..'" class="' .. mw.text.nowiki(options.class) .. '">' .. text .. "</span>"; |
else | else | ||
− | text = '<span class="' .. | + | 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="' .. | + | 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] | + | 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 | + | if inArray(no_tracking_cats, {"", "no", "false", "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 | + | elseif args[k] ~= nil or (k == 'postscript') then |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
args[k] = v; | args[k] = v; | ||
end | end |
Revision as of 10:42, 25 April 2013
Documentation for this module may be created at Module:Citation/CS1/doc
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 " ", 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 " ", 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]', { ['['] = '[', [']'] = ']', ['\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("—",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 = " – ''" .. 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;"> </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