Module:Citation/CS1: Difference between revisions
Jump to navigation
Jump to search
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 18:42, 25 April 2013
<section begin=header />
Warning | This Lua module is used on 3,000,000+ pages. To avoid large-scale disruption and unnecessary server load, any changes to this module should first be tested in its /sandbox or /testcases subpages. The tested changes can then be added to this page in a single edit. Please consider discussing any changes on the talk page before implementing them. |
This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
<section end=header />
This module and associated sub-modules support the Citation Style 1 and Citation Style 2 citation templates. In general, it is not intended to be called directly, but is called by one of the core CS1 and CS2 templates. <section begin=module_components_table /> These files comprise the module support for cs1|2 citation templates:
<section end=module_components_table />
Other documentation:
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