|
|
Line 1: |
Line 1: |
− | local z = {
| |
− | error_categories = {};
| |
− | error_ids = {};
| |
− | message_tail = {};
| |
− | }
| |
| | | |
− | -- Include translation message hooks, ID and error handling configuration settings.
| |
− | --local cfg = mw.loadData( 'Module:Citation/CS1/Configuration/sandbox' );
| |
− |
| |
− | -- Contains a list of all recognized parameters
| |
− | --local whitelist = mw.loadData( 'Module:Citation/CS1/Whitelist/sandbox' );
| |
− |
| |
− | --local dates = require('Module:Citation/CS1/Date_validation/sandbox').dates -- location of date validation code
| |
− |
| |
− | -- 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
| |
− |
| |
− | --[[
| |
− | Categorize and emit an error message when the citation contains one or more deprecated parameters. Because deprecated parameters (currently |day=, |month=,
| |
− | |coauthor=, and |coauthors=) aren't related to each other and because these parameters may be concatenated into the variables used by |date= and |author#= (and aliases)
| |
− | details of which parameter caused the error message are not provided. Only one error message is emitted regarless of the number of deprecated parameters in the citation.
| |
− | ]]
| |
− | function deprecated_parameter()
| |
− | if true ~= Page_in_deprecated_cat then -- if we haven't been here before then set a
| |
− | Page_in_deprecated_cat=true; -- sticky flag so that if there are more than one deprecated parameter the category is added only once
| |
− | -- table.insert( z.message_tail, { seterror( 'deprecated_params', {error_message}, true ) } ); -- add error message
| |
− | table.insert( z.message_tail, { seterror( 'deprecated_params', {}, true ) } ); -- add error message
| |
− | end
| |
− | 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;
| |
− | return args and mw.message.newRawMessage( msg, args ):plain() or msg;
| |
− | end
| |
− |
| |
− | --[[
| |
− | Apply kerning to open the space between the quote mark provided by the Module and a leading or trailing quote mark contained in a |title= or |chapter= parameter's value.
| |
− | This function will positive kern either single or double quotes:
| |
− | "'Unkerned title with leading and trailing single quote marks'"
| |
− | " 'Kerned title with leading and trailing single quote marks' " (in real life the kerning isn't as wide as this example)
| |
− | ]]
| |
− | function kern_quotes (str)
| |
− | local left='<span style="padding-left:0.2em;">%1</span>'; -- spacing to use when title contains leading single or double quote mark
| |
− | local right='<span style="padding-right:0.2em;">%1</span>'; -- spacing to use when title contains trailing single or double quote mark
| |
− |
| |
− | if str:match ("^[\"\'][^\']") then
| |
− | str = string.gsub( str, "^[\"\']", left, 1 ); -- replace (captured) leading single or double quote with left-side <span>
| |
− | end
| |
− | if str:match ("[^\'][\"\']$") then
| |
− | str = string.gsub( str, "[\"\']$", right, 1 ); -- replace (captured) trailing single or double quote with right-side <span>
| |
− | end
| |
− | return str;
| |
− | end
| |
− |
| |
− | -- Wraps a string using a message_list configuration taking one argument
| |
− | function wrap( key, str, lower )
| |
− | if not is_set( str ) then
| |
− | return "";
| |
− | elseif inArray( key, { 'italic-title', 'trans-italic-title' } ) then
| |
− | str = safeforitalics( str );
| |
− | end
| |
− | if lower == true then
| |
− | return substitute( cfg.messages[key]:lower(), {str} );
| |
− | else
| |
− | return substitute( cfg.messages[key], {str} );
| |
− | end
| |
− | 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 type( list ) == 'table' then
| |
− | v, origin[k] = selectone( args, list, 'redundant_parameters' );
| |
− | if origin[k] == nil then
| |
− | origin[k] = ''; -- Empty string, not nil
| |
− | end
| |
− | elseif list ~= nil then
| |
− | v, origin[k] = args[list], list;
| |
− | else
| |
− | -- maybe let through instead of raising an error?
| |
− | -- v, origin[k] = args[k], k;
| |
− | error( cfg.messages['unknown_argument_map'] );
| |
− | end
| |
− |
| |
− | -- Empty strings, not nil;
| |
− | if v == nil then
| |
− | v = cfg.defaults[k] or '';
| |
− | origin[k] = '';
| |
− | end
| |
− |
| |
− | tbl = rawset( tbl, k, v );
| |
− | return v;
| |
− | end,
| |
− | });
| |
− | end
| |
− |
| |
− | --[[
| |
− | Looks for a parameter's name in the whitelist.
| |
− |
| |
− | Parameters in the whitelist can have three values:
| |
− | true - active, supported parameters
| |
− | false - deprecated, supported parameters
| |
− | nil - unsupported parameters
| |
− | ]]
| |
− | function validate( name )
| |
− | local name = tostring( name );
| |
− | local state = whitelist.basic_arguments[ name ];
| |
− |
| |
− | -- Normal arguments
| |
− | if true == state then return true; end -- valid actively supported parameter
| |
− | if false == state then
| |
− | deprecated_parameter (); -- parameter is deprecated but still supported
| |
− | return true;
| |
− | end
| |
− |
| |
− | -- Arguments with numbers in them
| |
− | name = name:gsub( "%d+", "#" ); -- replace digit(s) with # (last25 becomes last#
| |
− | state = whitelist.numbered_arguments[ name ];
| |
− | if true == state then return true; end -- valid actively supported parameter
| |
− | if false == state then
| |
− | deprecated_parameter (); -- parameter is deprecated but still supported
| |
− | return true;
| |
− | end
| |
− |
| |
− | return false; -- Not supported because not found or name is set to nil
| |
− | 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
| |
− |
| |
− | --[[
| |
− | Format LCCN link and do simple error checking. LCCN is a character string 8-12 characters long. The length of the LCCN dictates the character type of the first 1-3 characters; the
| |
− | rightmost eight are always digits. http://info-uri.info/registry/OAIHandler?verb=GetRecord&metadataPrefix=reg&identifier=info:lccn/
| |
− |
| |
− | length = 8 then all digits
| |
− | length = 9 then lccn[1] is alpha
| |
− | length = 10 then lccn[1] and lccn[2] are both alpha or both digits
| |
− | length = 11 then lccn[1] is alpha, lccn[2] and lccn[3] are both alpha or both digits
| |
− | length = 12 then lccn[1] and lccn[2] are both alpha
| |
− |
| |
− | ]]
| |
− | function lccn(id)
| |
− | local handler = cfg.id_handlers['LCCN'];
| |
− | local err_cat = ''; -- presume that LCCN is valid
| |
− |
| |
− | local len = id:len(); -- get the length of the lccn
| |
− |
| |
− | if 8 == len then
| |
− | if id:match("[^%d]") then -- if LCCN has anything but digits (nil if only digits)
| |
− | err_cat = ' ' .. seterror( 'bad_lccn' ); -- set an error message
| |
− | end
| |
− | elseif 9 == len then -- LCCN should be adddddddd
| |
− | if nil == id:match("%a%d%d%d%d%d%d%d%d") then -- does it match our pattern?
| |
− | err_cat = ' ' .. seterror( 'bad_lccn' ); -- set an error message
| |
− | end
| |
− | elseif 10 == len then -- LCCN should be aadddddddd or dddddddddd
| |
− | if id:match("[^%d]") then -- if LCCN has anything but digits (nil if only digits) ...
| |
− | if nil == id:match("^%a%a%d%d%d%d%d%d%d%d") then -- ... see if it matches our pattern
| |
− | err_cat = ' ' .. seterror( 'bad_lccn' ); -- no match, set an error message
| |
− | end
| |
− | end
| |
− | elseif 11 == len then -- LCCN should be aaadddddddd or adddddddddd
| |
− | if not (id:match("^%a%a%a%d%d%d%d%d%d%d%d") or id:match("^%a%d%d%d%d%d%d%d%d%d%d")) then -- see if it matches one of our patterns
| |
− | err_cat = ' ' .. seterror( 'bad_lccn' ); -- no match, set an error message
| |
− | end
| |
− | elseif 12 == len then -- LCCN should be aadddddddddd
| |
− | if not id:match("^%a%a%d%d%d%d%d%d%d%d%d%d") then -- see if it matches our pattern
| |
− | err_cat = ' ' .. seterror( 'bad_lccn' ); -- no match, set an error message
| |
− | end
| |
− | else
| |
− | err_cat = ' ' .. seterror( 'bad_lccn' ); -- wrong length, set an error message
| |
− | end
| |
− |
| |
− | return externallinkid({link = handler.link, label = handler.label,
| |
− | prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode}) .. err_cat;
| |
− | end
| |
− |
| |
− | --[[
| |
− | Format PMID and do simple error checking. PMIDs are sequential numbers beginning at 1 and counting up. This code checks the PMID to see that it
| |
− | contains only digits and is less than test_limit; the value in local variable test_limit will need to be updated periodically as more PMIDs are issued.
| |
− | ]]
| |
− | function pmid(id)
| |
− | local test_limit = 30000000; -- update this value as PMIDs approach
| |
− | local handler = cfg.id_handlers['PMID'];
| |
− | local err_cat = ''; -- presume that PMID is valid
| |
− |
| |
− | if id:match("[^%d]") then -- if PMID has anything but digits
| |
− | err_cat = ' ' .. seterror( 'bad_pmid' ); -- set an error message
| |
− | else -- PMID is only digits
| |
− | local id_num = tonumber(id); -- convert id to a number for range testing
| |
− | if 1 > id_num or test_limit < id_num then -- if PMID is outside test limit boundaries
| |
− | err_cat = ' ' .. seterror( 'bad_pmid' ); -- set an error message
| |
− | end
| |
− | end
| |
− |
| |
− | return externallinkid({link = handler.link, label = handler.label,
| |
− | prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode}) .. err_cat;
| |
− | end
| |
− |
| |
− | --[[
| |
− | Determines if a PMC identifier's online version is embargoed. Compares the date in |embargo= against today's date. If embargo date is
| |
− | in the future, returns true; otherwse, returns false because the embargo has expired or |embargo= not set in this cite.
| |
− | ]]
| |
− | function is_embargoed(embargo)
| |
− | if is_set(embargo) then
| |
− | local lang = mw.getContentLanguage();
| |
− | local good1, embargo_date, good2, todays_date;
| |
− | good1, embargo_date = pcall( lang.formatDate, lang, 'U', embargo );
| |
− | good2, todays_date = pcall( lang.formatDate, lang, 'U' );
| |
− |
| |
− | if good1 and good2 and tonumber( embargo_date ) >= tonumber( todays_date ) then --is embargo date is in the future?
| |
− | return true; -- still embargoed
| |
− | end
| |
− | end
| |
− | return false; -- embargo expired or |embargo= not set
| |
− | end
| |
− |
| |
− | --[[
| |
− | Format a PMC, do simple error checking, and check for embargoed articles.
| |
− |
| |
− | The embargo parameter takes a date for a value. If the embargo date is in the future
| |
− | the PMC identifier will not be linked to the article. If the embargo specifies a date in the past, or if it is empty or omitted, then
| |
− | the PMC identifier is linked to the article through the link at cfg.id_handlers['PMC'].prefix.
| |
− |
| |
− | PMCs are sequential numbers beginning at 1 and counting up. This code checks the PMC to see that it contains only digits and is less
| |
− | than test_limit; the value in local variable test_limit will need to be updated periodically as more PMCs are issued.
| |
− | ]]
| |
− | function pmc(id, embargo)
| |
− | local test_limit = 5000000; -- update this value as PMCs approach
| |
− | local handler = cfg.id_handlers['PMC'];
| |
− | local err_cat = ''; -- presume that PMC is valid
| |
− |
| |
− | local text;
| |
− |
| |
− | if id:match("[^%d]") then -- if PMC has anything but digits
| |
− | err_cat = ' ' .. seterror( 'bad_pmc' ); -- set an error message
| |
− | else -- PMC is only digits
| |
− | local id_num = tonumber(id); -- convert id to a number for range testing
| |
− | if 1 > id_num or test_limit < id_num then -- if PMC is outside test limit boundaries
| |
− | err_cat = ' ' .. seterror( 'bad_pmc' ); -- set an error message
| |
− | end
| |
− | end
| |
− |
| |
− | if is_embargoed(embargo) then
| |
− | text="[[" .. handler.link .. "|" .. handler.label .. "]]:" .. handler.separator .. id .. err_cat; --still embargoed so no external link
| |
− | else
| |
− | text = externallinkid({link = handler.link, label = handler.label, --no embargo date, ok to link to article
| |
− | prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode}) .. err_cat;
| |
− | end
| |
− | return text;
| |
− | end
| |
− |
| |
− | -- Formats a DOI and checks for DOI errors.
| |
− |
| |
− | -- DOI names contain two parts: prefix and suffix separated by a forward slash.
| |
− | -- Prefix: directory indicator '10.' followed by a registrant code
| |
− | -- Suffix: character string of any length chosen by the registrant
| |
− |
| |
− | -- This function checks a DOI name for: prefix/suffix. If the doi name contains spaces or endashes,
| |
− | -- or, if it ends with a period or a comma, this function will emit a bad_doi error message.
| |
− |
| |
− | -- DOI names are case-insensitive and can incorporate any printable Unicode characters so the test for spaces, endash,
| |
− | -- and terminal punctuation may not be technically correct but it appears, that in practice these characters are rarely if ever used in doi names.
| |
− |
| |
− | function doi(id, inactive)
| |
− | local cat = ""
| |
− | local handler = cfg.id_handlers['DOI'];
| |
− |
| |
− | local text;
| |
− | if is_set(inactive) then
| |
− | local inactive_year = inactive:match("%d%d%d%d") or ''; -- try to get the year portion from the inactive date
| |
− | text = "[[" .. handler.link .. "|" .. handler.label .. "]]:" .. id;
| |
− | if is_set(inactive_year) then
| |
− | table.insert( z.error_categories, "Pages with DOIs inactive since " .. inactive_year );
| |
− | else
| |
− | table.insert( z.error_categories, "Pages with inactive DOIs" ); -- when inactive doesn't contain a recognizable year
| |
− | end
| |
− | 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 nil == id:match("^10%.[^%s–]-/[^%s–]-[^%.,]$") then -- doi must begin with '10.', must contain a fwd slash, must not contain spaces or endashes, and must not end with period or comma
| |
− | 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
| |
− |
| |
− | --[[
| |
− | Validate and format an issn. This code fixes the case where an editor has included an ISSN in the citation but has separated the two groups of four
| |
− | digits with a space. When that condition occurred, the resulting link looked like this:
| |
− |
| |
− | |issn=0819 4327 gives: [http://www.worldcat.org/issn/0819 4327 0819 4327] -- can't have spaces in an external link
| |
− |
| |
− | This code now prevents that by inserting a hyphen at the issn midpoint. It also validates the issn for length and makes sure that the checkdigit agrees
| |
− | with the calculated value. Incorrect length (8 digits), characters other than 0-9 and X, or checkdigit / calculated value mismatch will all cause a check issn
| |
− | error message. The issn is always displayed with a hyphen, even if the issn was given as a single group of 8 digits.
| |
− | ]]
| |
− | function issn(id)
| |
− | local issn_copy = id; -- save a copy of unadulterated issn; use this version for display if issn does not validate
| |
− | local handler = cfg.id_handlers['ISSN'];
| |
− | local text;
| |
− | local valid_issn = true;
| |
− |
| |
− | id=id:gsub( "[%s-–]", "" ); -- strip spaces, hyphens, and ndashes from the issn
| |
− |
| |
− | if 8 ~= id:len() or nil == id:match( "^%d*X?$" ) then -- validate the issn: 8 didgits long, containing only 0-9 or X in the last position
| |
− | valid_issn=false; -- wrong length or improper character
| |
− | else
| |
− | valid_issn=is_valid_isxn(id, 8); -- validate issn
| |
− | end
| |
− |
| |
− | if true == valid_issn then
| |
− | id = string.sub( id, 1, 4 ) .. "-" .. string.sub( id, 5 ); -- if valid, display correctly formatted version
| |
− | else
| |
− | id = issn_copy; -- if not valid, use the show the invalid issn with error message
| |
− | end
| |
− |
| |
− | text = externallinkid({link = handler.link, label = handler.label,
| |
− | prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode})
| |
− |
| |
− | if false == valid_issn then
| |
− | text = text .. ' ' .. seterror( 'bad_issn' ) -- add an error message if the issn is invalid
| |
− | end
| |
− |
| |
− | return text
| |
− | end
| |
− |
| |
− | --[[
| |
− | This function sets default title types (equivalent to the citation including |type=<default value>) for those citations that have defaults.
| |
− | Also handles the special case where it is desireable to omit the title type from the rendered citation (|type=none).
| |
− | ]]
| |
− | function set_titletype(cite_class, title_type)
| |
− | if is_set(title_type) then
| |
− | if "none" == title_type then
| |
− | title_type = ""; -- if |type=none then type parameter not displayed
| |
− | end
| |
− | return title_type; -- if |type= has been set to any other value use that value
| |
− | end
| |
− |
| |
− | if "AV media notes" == cite_class or "DVD notes" == cite_class then -- if this citation is cite AV media notes or cite DVD notes
| |
− | return "Media notes"; -- display AV media notes / DVD media notes annotation
| |
− |
| |
− | elseif "podcast" == cite_class then -- if this citation is cite podcast
| |
− | return "Podcast"; -- display podcast annotation
| |
− |
| |
− | elseif "pressrelease" == cite_class then -- if this citation is cite press release
| |
− | return "Press release"; -- display press release annotation
| |
− |
| |
− | elseif "techreport" == cite_class then -- if this citation is cite techreport
| |
− | return "Technical report"; -- display techreport annotation
| |
− |
| |
− | elseif "thesis" == cite_class then -- if this citation is cite thesis (degree option handled after this function returns)
| |
− | return "Thesis"; -- display simple thesis annotation (without |degree= modification)
| |
− | end
| |
− | end
| |
− |
| |
− | --[[
| |
− | Determines whether a 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
| |
− |
| |
− | -- Extract page numbers from external wikilinks in any of the |page=, |pages=, or |at= parameters for use in COinS.
| |
− | function get_coins_pages (pages)
| |
− | if not is_set (pages) then return pages; end -- if no page numbers then we're done
| |
− |
| |
− | while true do
| |
− | pattern = pages:match("%[([%w/:\.]+%s+)[%w%d].*%]"); -- pattern is the opening bracket, the url and following space(s): "[url "
| |
− | if nil == pattern then break; end -- no more urls
| |
− | pages = pages:gsub(pattern, ""); -- remove as many instances of pattern as possible
| |
− | end
| |
− | pages = pages:gsub("[%[%]]", ""); -- remove the brackets
| |
− | pages = pages:gsub("–", "-" ); -- replace endashes with hyphens
| |
− | pages = pages:gsub("&%w+;", "-" ); -- and replace html entities (– etc) with hyphens; do we need to replace numerical entities like   and the like?
| |
− | return pages;
| |
− | end
| |
− |
| |
− | --[[
| |
− | ISBN-10 and ISSN validator code calculates checksum across all isbn/issn digits including the check digit. ISBN-13 is checked in checkisbn().
| |
− | If the number is valid the result will be 0. Before calling this function, issbn/issn must be checked for length and stripped of dashes,
| |
− | spaces and other non-isxn characters.
| |
− | ]]
| |
− | function is_valid_isxn (isxn_str, len)
| |
− | local temp = 0;
| |
− | isxn_str = { isxn_str:byte(1, len) }; -- make a table of bytes
| |
− | len = len+1; -- adjust to be a loop counter
| |
− | for i, v in ipairs( isxn_str ) do -- loop through all of the bytes and calculate the checksum
| |
− | if v == string.byte( "X" ) then -- if checkdigit is X
| |
− | temp = temp + 10*( len - i ); -- it represents 10 decimal
| |
− | else
| |
− | temp = temp + tonumber( string.char(v) )*(len-i);
| |
− | end
| |
− | end
| |
− | return temp % 11 == 0; -- returns true if calculation result is zero
| |
− | end
| |
− |
| |
− | -- Determines whether an ISBN string is valid
| |
− | function checkisbn( isbn_str )
| |
− | if nil ~= isbn_str:match("[^%s-0-9X]") then return false; end -- fail if isbn_str contains anything but digits, hyphens, or the uppercase X
| |
− | isbn_str = isbn_str:gsub( "-", "" ):gsub( " ", "" ); -- remove hyphens and spaces
| |
− | local len = isbn_str:len();
| |
− |
| |
− | if len ~= 10 and len ~= 13 then
| |
− | return false;
| |
− | end
| |
− |
| |
− | if len == 10 then
| |
− | if isbn_str:match( "^%d*X?$" ) == nil then return false; end
| |
− | return is_valid_isxn(isbn_str, 10);
| |
− | else
| |
− | local temp = 0;
| |
− | if isbn_str:match( "^97[89]%d*$" ) == nil then return false; end -- isbn13 begins with 978 or 979
| |
− | 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
| |
− |
| |
− | -- 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
| |
− | if is_set(person.link) and nil ~= person.link:find("//") then one = one .. " " .. seterror( 'bad_authorlink' ) end -- check for url in author link;
| |
− | 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 == 'LCCN' then
| |
− | table.insert( new_list, {handler.label, lccn( v ) } );
| |
− | elseif k == 'OL' then
| |
− | table.insert( new_list, {handler.label, openlibrary( v ) } );
| |
− | elseif k == 'PMC' then
| |
− | table.insert( new_list, {handler.label, pmc( v, options.Embargo ) } );
| |
− | elseif k == 'PMID' then
| |
− | table.insert( new_list, {handler.label, pmid( v ) } );
| |
− | elseif k == 'ISSN' then
| |
− | table.insert( new_list, {handler.label, issn( 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 ) -- used in following table.sort()
| |
− | 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 doing 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 Degree = A['Degree'];
| |
− | local Docket = A['Docket'];
| |
− | 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'];
| |
− |
| |
− | local Series = A['Series'];
| |
− | local Volume = A['Volume'];
| |
− | local Issue = A['Issue'];
| |
− | local Position = '';
| |
− | local Page = A['Page'];
| |
− | local Pages = hyphentodash( A['Pages'] );
| |
− | local At = A['At'];
| |
− |
| |
− | local Edition = A['Edition'];
| |
− | local PublicationPlace = A['PublicationPlace']
| |
− | local Place = A['Place'];
| |
− |
| |
− | local PublisherName = A['PublisherName'];
| |
− | local RegistrationRequired = A['RegistrationRequired'];
| |
− | 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 Embargo = A['Embargo'];
| |
− |
| |
− | 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'];
| |
− |
| |
− | --these are used by cite interview
| |
− | local Callsign = A['Callsign'];
| |
− | local City = A['City'];
| |
− | local Cointerviewers = A['Cointerviewers']; -- deprecated
| |
− | local Interviewer = A['Interviewer']; -- deprecated
| |
− | local Program = A['Program'];
| |
− |
| |
− | --local variables that are not cs1 parameters
| |
− | local page_type; -- is this needed? Doesn't appear to be used anywhere;
| |
− | local use_lowercase = ( sepc ~= '.' );
| |
− | local this_page = mw.title.getCurrentTitle(); --Also used for COinS and for language
| |
− | local anchor_year; -- used in the CITEREF identifier
| |
− | local COinS_date; -- used in the COinS metadata
| |
− |
| |
− | -- Set postscript default.
| |
− | if not is_set (PostScript) then -- if |postscript= has not been set (Postscript is nil which is the default for {{citation}}) and
| |
− | if (config.CitationClass ~= "citation") then -- this template is not a citation template
| |
− | PostScript = '.'; -- must be a cite xxx template so set postscript to default (period)
| |
− | end
| |
− | else
| |
− | if PostScript:lower() == 'none' then -- if |postscript=none then
| |
− | PostScript = ''; -- no postscript
| |
− | end
| |
− | end
| |
− |
| |
− | --check this page to see if it is in one of the namespaces that cs1 is not supposed to add to the error categories.
| |
− | if not is_set(no_tracking_cats) then -- ignore if we are already not going to categorize this page
| |
− | for k, v in pairs( cfg.uncategorized_namespaces ) do -- otherwise, spin through the list of namespaces we don't include in error categories
| |
− | if this_page.nsText == v then -- if we find one
| |
− | no_tracking_cats = "true"; -- set no_trackin_cats
| |
− | break; -- and we're done
| |
− | end
| |
− | end
| |
− | end
| |
− |
| |
− | -- check for extra |page=, |pages= or |at= parameters.
| |
− | if is_set(Page) then
| |
− | if is_set(Pages) or is_set(At) then
| |
− | Page = Page .. " " .. seterror('extra_pages'); -- add error message
| |
− | Pages = ''; -- unset the others
| |
− | At = '';
| |
− | end
| |
− | elseif is_set(Pages) then
| |
− | if is_set(At) then
| |
− | Pages = Pages .. " " .. seterror('extra_pages'); -- add error messages
| |
− | At = ''; -- unset
| |
− | end
| |
− | end
| |
− |
| |
− | -- both |publication-place= and |place= (|location=) allowed if different
| |
− | if not is_set(PublicationPlace) and is_set(Place) then
| |
− | PublicationPlace = Place; -- promote |place= (|location=) to |publication-place
| |
− | end
| |
− |
| |
− | if PublicationPlace == Place then Place = ''; end -- don't need both if they are the same
| |
− |
| |
− | --[[
| |
− | Parameter remapping for cite encyclopedia:
| |
− | When the citation has these parameters:
| |
− | |encyclopedia and |title then map |title to |article and |encyclopedia to |title
| |
− | |encyclopedia and |article then map |encyclopedia to |title
| |
− | |encyclopedia then map |encyclopedia to |title
| |
− |
| |
− | |trans_title maps to |trans_chapter when |title is re-mapped
| |
− |
| |
− | All other combinations of |encyclopedia, |title, and |article are not modified
| |
− | ]]
| |
− | if ( config.CitationClass == "encyclopaedia" ) then
| |
− | if is_set(Periodical) then -- Periodical is set when |encyclopedia is set
| |
− | if is_set(Title) then
| |
− | if not is_set(Chapter) then
| |
− | Chapter = Title; -- |encyclopedia and |title are set so map |title to |article and |encyclopedia to |title
| |
− | TransChapter = TransTitle;
| |
− | Title = Periodical;
| |
− | Periodical = ''; -- redundant so unset
| |
− | TransTitle = ''; -- redundant so unset
| |
− | end
| |
− | else -- |title not set
| |
− | Title = Periodical; -- |encyclopedia set and |article set or not set so map |encyclopedia to |title
| |
− | Periodical = ''; -- redundant so unset
| |
− | end
| |
− | end
| |
− | end
| |
− |
| |
− | --special cases for citation.
| |
− | if (config.CitationClass == "citation") then -- for citation templates
| |
− | if not is_set (Ref) then -- if |ref= is not set
| |
− | Ref = "harv"; -- set default |ref=harv
| |
− | end
| |
− | if not is_set (sepc) then -- if |separator= is not set
| |
− | sepc = ','; -- set citation separator to its default (comma)
| |
− | end
| |
− | else -- not a citation template
| |
− | if not is_set (sepc) then -- if |separator= has not been set
| |
− | sepc = '.'; -- set cite xxx separator to its default (period)
| |
− | end
| |
− | end
| |
− |
| |
− | -- check for specital case where |separator=none
| |
− | if 'none' == sepc:lower() then -- if |separator=none
| |
− | sepc = ''; -- then set it to a empty string
| |
− | end
| |
− |
| |
− | -- Special case for cite techreport.
| |
− | if (config.CitationClass == "techreport") then -- special case for cite techreport
| |
− | if is_set(Issue) then -- cite techreport uses 'number', which other citations aliase to 'issue'
| |
− | if not is_set(ID) then -- can we use ID for the "number"?
| |
− | ID = Issue; -- yes, use it
| |
− | Issue = ""; -- unset Issue so that "number" isn't duplicated in the rendered citation or COinS metadata
| |
− | else -- can't use ID so emit error message
| |
− | ID = ID .. " " .. seterror('redundant_parameters', '<code>|id=</code> and <code>|number=</code>');
| |
− | end
| |
− | end
| |
− | end
| |
− |
| |
− | -- special case for cite interview
| |
− | if (config.CitationClass == "interview") then
| |
− | if is_set(Program) then
| |
− | ID = ' ' .. Program;
| |
− | end
| |
− | if is_set(Callsign) then
| |
− | if is_set(ID) then
| |
− | ID = ID .. sepc .. ' ' .. Callsign;
| |
− | else
| |
− | ID = ' ' .. Callsign;
| |
− | end
| |
− | end
| |
− | if is_set(City) then
| |
− | if is_set(ID) then
| |
− | ID = ID .. sepc .. ' ' .. City;
| |
− | else
| |
− | ID = ' ' .. City;
| |
− | end
| |
− | end
| |
− |
| |
− | if is_set(Interviewer) then
| |
− | if is_set(TitleType) then
| |
− | Others = ' ' .. TitleType .. ' with ' .. Interviewer;
| |
− | TitleType = '';
| |
− | else
| |
− | Others = ' ' .. 'Interview with ' .. Interviewer;
| |
− | end
| |
− | if is_set(Cointerviewers) then
| |
− | Others = Others .. sepc .. ' ' .. Cointerviewers;
| |
− | end
| |
− | else
| |
− | Others = '(Interview)';
| |
− | end
| |
− | end
| |
− |
| |
− | --Account for the oddity that is {{cite journal}} with |pmc= set and |url= not set
| |
− | if config.CitationClass == "journal" and not is_set(URL) and is_set(ID_list['PMC']) then
| |
− | if not is_embargoed(Embargo) then
| |
− | URL=cfg.id_handlers['PMC'].prefix .. ID_list['PMC']; -- set url to be the same as the PMC external link if not embargoed
| |
− | URLorigin = cfg.id_handlers['PMC'].parameters[1]; -- set URLorigin to parameter name for use in error message if citation is missing a |title=
| |
− | end
| |
− | end
| |
− |
| |
− | -- Account for the oddity that is {{cite conference}}, before generation of COinS data.
| |
− | --TODO: if this is only for {{cite conference}}, shouldn't we be checking? (if config.CitationClass=='conference' then ...)
| |
− | 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.
| |
− | --[[ -- {{cite episode}} is not currently supported by this module
| |
− | 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
| |
− | -- end of {{cite episode}} stuff]]
| |
− |
| |
− | -- legacy: promote concatenation of |day=, |month=, and |year= to Date if Date not set; or, promote PublicationDate to Date if neither Date nor Year are set.
| |
− | if not is_set(Date) then
| |
− | Date = Year; -- promote Year to Date
| |
− | Year = nil; -- make nil so Year as empty string isn't used for CITEREF
| |
− | 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
| |
− | elseif is_set(PublicationDate) then -- use PublicationDate when |date= and |year= are not set
| |
− | Date = PublicationDate; -- promonte PublicationDate to Date
| |
− | PublicationDate = ''; -- unset, no longer needed
| |
− | end
| |
− | end
| |
− |
| |
− | if PublicationDate == Date then PublicationDate = ''; end -- if PublicationDate is same as Date, don't display in rendered citation
| |
− |
| |
− |
| |
− | --[[
| |
− | Go test all of the date-holding parameters for valid MOS:DATE format and make sure that dates are real dates. This must be done before we do COinS because here is where
| |
− | we get the date used in the metadata.
| |
− |
| |
− | Date validation supporting code is in Module:Citation/CS1/Date_validation
| |
− | ]]
| |
− | anchor_year, COinS_date, error_message = dates({['accessdate']=AccessDate, ['airdate']=AirDate, ['archivedate']=ArchiveDate, ['date']=Date, ['doi_brokendate']=DoiBroken,
| |
− | ['embargo']=Embargo, ['laydate']=LayDate, ['publicationdate']=PublicationDate, ['year']=Year});
| |
− | if is_set(error_message) then
| |
− | table.insert( z.message_tail, { seterror( 'bad_date', {error_message}, true ) } ); -- add this error message
| |
− | end
| |
− |
| |
− | -- At this point fields may be nil if they weren't specified in the template use. We can use that fact.
| |
− |
| |
− | -- 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(COinS_date, Date), -- COinS_date has correctly formatted date if Date is valid; any reason to keep Date here? Should we be including invalid dates in metadata?
| |
− | ['Series'] = Series,
| |
− | ['Volume'] = Volume,
| |
− | ['Issue'] = Issue,
| |
− | ['Pages'] = get_coins_pages (first_set(Page, Pages, At)), -- pages stripped of external links
| |
− | ['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
| |
− |
| |
− | if not is_set(Authors) and is_set(Coauthors) then -- coauthors aren't displayed if one of authors=, authorn=, or lastn= isn't specified
| |
− | table.insert( z.message_tail, { seterror('coauthors_missing_author', {}, true) } ); -- emit error message
| |
− | 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
| |
− |
| |
− | local Cartography = "";
| |
− | local Scale = "";
| |
− | if config.CitationClass == "map" then
| |
− | if not is_set( Authors ) and is_set( PublisherName ) then
| |
− | Authors = PublisherName;
| |
− | PublisherName = "";
| |
− | end
| |
− | Cartography = A['Cartography'];
| |
− | if is_set( Cartography ) then
| |
− | Cartography = sepc .. " " .. wrap( 'cartography', Cartography, use_lowercase );
| |
− | end
| |
− | Scale = A['Scale'];
| |
− | if is_set( Scale ) then
| |
− | Scale = sepc .. " " .. Scale;
| |
− | end
| |
− | end
| |
− |
| |
− | 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 or cite podcast |url= is missing or empty
| |
− | if inArray(config.CitationClass, {"web","podcast"}) then
| |
− | table.insert( z.message_tail, { seterror( 'cite_web_url', {}, true ) } );
| |
− | end
| |
− |
| |
− | -- Test if accessdate is given without giving a URL
| |
− | if is_set(AccessDate) then
| |
− | table.insert( z.message_tail, { seterror( 'accessdate_missing_url', {}, true ) } );
| |
− | AccessDate = '';
| |
− | end
| |
− |
| |
− | -- Test if format is given without giving a URL
| |
− | if is_set(Format) then
| |
− | Format = Format .. seterror( 'format_missing_url' );
| |
− | end
| |
− | end
| |
− |
| |
− | -- Test if citation has no title
| |
− | if not is_set(Chapter) and
| |
− | not is_set(Title) and
| |
− | not is_set(Periodical) and
| |
− | not is_set(Conference) and
| |
− | not is_set(TransTitle) and
| |
− | not is_set(TransChapter) then
| |
− | table.insert( z.message_tail, { seterror( 'citation_missing_title', {}, true ) } );
| |
− | end
| |
− |
| |
− | Format = is_set(Format) and " (" .. Format .. ")" or "";
| |
− |
| |
− | local OriginalURL = URL
| |
− | DeadURL = DeadURL:lower();
| |
− | if is_set( ArchiveURL ) then
| |
− | if ( DeadURL ~= "no" ) then
| |
− | URL = ArchiveURL
| |
− | URLorigin = A:ORIGIN('ArchiveURL')
| |
− | end
| |
− | end
| |
− |
| |
− | -- Format chapter / article title
| |
− | 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 = kern_quotes (Chapter); -- if necessary, separate chapter title's leading and trailing quote marks from Module provided quote marks
| |
− | 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 = kern_quotes (Title); -- if necessary, separate title's leading and trailing quote marks from Module provided quote marks
| |
− | Title = wrap( 'quoted-title', Title );
| |
− | TransTitle = wrap( 'trans-quoted-title', TransTitle );
| |
− | elseif inArray(config.CitationClass, {"web","news","pressrelease","conference","podcast"}) and
| |
− | not is_set(Chapter) then
| |
− | Title = kern_quotes (Title); -- if necessary, separate title's leading and trailing quote marks from Module provided quote marks
| |
− | 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
| |
− | Place = " " .. wrap( 'written', Place, use_lowercase ) .. sepc .. " ";
| |
− | end
| |
− |
| |
− | if is_set(Conference) then
| |
− | if is_set(ConferenceURL) then
| |
− | Conference = externallink( ConferenceURL, Conference );
| |
− | end
| |
− | Conference = sepc .. " " .. Conference
| |
− | elseif is_set(ConferenceURL) then
| |
− | Conference = sepc .. " " .. 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","podcast"}) 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","podcast"}) then
| |
− | Page = ": " .. Page;
| |
− | else
| |
− | Page = sepc .." " .. PPrefix .. Page;
| |
− | end
| |
− | end
| |
− |
| |
− | At = is_set(At) and (sepc .. " " .. At) or "";
| |
− | Position = is_set(Position) and (sepc .. " " .. Position) or "";
| |
− | if config.CitationClass == 'map' then
| |
− | local Section = A['Section'];
| |
− | local Inset = A['Inset'];
| |
− | if first_set( Pages, Page, At ) ~= nil or sepc ~= '.' then
| |
− | if is_set( Section ) then
| |
− | Section = ", " .. wrap( 'section', Section, true );
| |
− | end
| |
− | if is_set( Inset ) then
| |
− | Inset = ", " .. wrap( 'inset', Inset, true );
| |
− | end
| |
− | else
| |
− | if is_set( Section ) then
| |
− | Section = sepc .. " " .. wrap( 'section', Section, use_lowercase );
| |
− | if is_set( Inset ) then
| |
− | Inset = ", " .. wrap( 'inset', Inset, true );
| |
− | end
| |
− | elseif is_set( Inset ) then
| |
− | Inset = sepc .. " " .. wrap( 'inset', Inset, use_lowercase );
| |
− | end
| |
− | end
| |
− | At = At .. Section .. Inset;
| |
− | end
| |
− |
| |
− | --[[Look in the list of iso639-1 language codes to see if the value provided in the language parameter matches one of them. If a match is found,
| |
− | use that value; if not, then use the value that was provided with the language parameter.
| |
− |
| |
− | Categories are assigned in a manner similar to the {{xx icon}} templates - categorizes only mainspace citations and only when the language code is not 'en' (English).
| |
− | ]]
| |
− | if is_set (Language) then
| |
− | -- local name = mw.language.fetchLanguageName( Language:lower(), "en" ); -- experiment: this seems to return correct ISO 639-1 language names
| |
− | local name = cfg.iso639_1[Language:lower()]; -- get the language name if Language parameter has a valid iso 639-1 code
| |
− | if nil == name then
| |
− | Language=" " .. wrap( 'language', Language ); -- no match, use parameter's value
| |
− | else
| |
− | if 0 == this_page.namespace and 'en' ~= Language:lower() then --found a match; is this page main / article space and English not the language?
| |
− | Language=" " .. wrap( 'language', name .. '[[Category:Articles with ' .. name .. '-language external links]]' ); -- in main space and not English: categorize
| |
− | else
| |
− | Language=" " .. wrap( 'language', name ); --not in mainspace or language is English so don't categorize
| |
− | end
| |
− | end
| |
− | else
| |
− | Language=""; -- language not specified so make sure this is an empty string;
| |
− | end
| |
− |
| |
− | Others = is_set(Others) and (sepc .. " " .. Others) or "";
| |
− |
| |
− | -- handle type parameter for those CS1 citations that have default values
| |
− |
| |
− | if inArray(config.CitationClass, {"AV media notes", "DVD notes", "podcast", "pressrelease", "techreport", "thesis"}) then
| |
− | TitleType = set_titletype (config.CitationClass, TitleType);
| |
− | if is_set(Degree) and "Thesis" == TitleType then -- special case for cite thesis
| |
− | TitleType = Degree .. " thesis";
| |
− | end
| |
− | end
| |
− |
| |
− | if is_set(TitleType) then -- if type parameter is specified
| |
− | TitleType = " (" .. TitleType .. ")"; -- display it in parentheses
| |
− | end
| |
− |
| |
− | TitleNote = is_set(TitleNote) and (sepc .. " " .. TitleNote) 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
| |
− |
| |
− | --[[ This code commented out while discussion continues until after week of 2014-03-23 live module update;
| |
− | if is_set(Volume) then
| |
− | if ( mw.ustring.len(Volume) > 4 )
| |
− | then Volume = sepc .. " " .. Volume;
| |
− | else
| |
− | Volume = " <b>" .. hyphentodash(Volume) .. "</b>";
| |
− | if is_set(Series) then Volume = sepc .. Volume;
| |
− | end
| |
− | end
| |
− | end
| |
− | ]]
| |
− | ------------------------------------ totally unrelated data
| |
− | --[[ Loosely mimic {{subscription required}} template; Via parameter identifies a delivery source that is not the publisher; these sources often, but not always, exist
| |
− | behind a registration or paywall. So here, we've chosen to decouple via from subscription (via has never been part of the registration required template).
| |
− |
| |
− | Subscription implies paywall; Registration does not. If both are used in a citation, the subscription required link note is displayed. There are no error messages for this condition.
| |
− | ]]
| |
− | if is_set(Via) then
| |
− | Via = " " .. wrap( 'via', Via );
| |
− | end
| |
− |
| |
− | if is_set(SubscriptionRequired) then
| |
− | SubscriptionRequired = sepc .. " " .. cfg.messages['subscription']; --here when 'via' parameter not used but 'subscription' is
| |
− | elseif is_set(RegistrationRequired) then
| |
− | SubscriptionRequired = sepc .. " " .. cfg.messages['registration']; --here when 'via' and 'subscription' parameters not used but 'registration' is
| |
− | 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(ID) then ID = sepc .." ".. ID; end
| |
− | if "thesis" == config.CitationClass and is_set(Docket) then
| |
− | ID = sepc .." Docket ".. Docket .. ID;
| |
− | end
| |
− |
| |
− |
| |
− | ID_list = buildidlist( ID_list, {DoiBroken = DoiBroken, ASINTLD = ASINTLD, IgnoreISBN = IgnoreISBN, Embargo=Embargo} );
| |
− |
| |
− | 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 = ""; -- CS1 does not supply terminal punctuation when |quote= is set
| |
− | 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","podcast"}) 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
| |
− |
| |
− | --[[
| |
− | Handle the oddity that is cite speech. This code overrides whatever may be the value assigned to TitleNote (through |department=) and forces it to be " (Speech)" so that
| |
− | the annotation directly follows the |title= parameter value in the citation rather than the |event= parameter value (if provided).
| |
− | ]]
| |
− | if "speech" == config.CitationClass then -- cite speech only
| |
− | TitleNote = " (Speech)"; -- annotate the citation
| |
− | if is_set (Periodical) then -- if Periodical, perhaps because of an included |website= or |journal= parameter
| |
− | if is_set (Conference) then -- and if |event= is set
| |
− | Conference = Conference .. sepc .. " "; -- then add appropriate punctuation to the end of the Conference variable before rendering
| |
− | end
| |
− | 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, Scale, Series,
| |
− | Language, Cartography, Edition, Publisher, Agency, Volume, Issue}, sepc );
| |
− | else
| |
− | tcommon = safejoin( {Title, TitleNote, Conference, Periodical, Format, TitleType, Scale, Series, Language,
| |
− | Volume, Issue, Others, Cartography, Edition, Publisher, Agency}, 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 = Position .. 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 = " ";
| |
− | local post_text = "";
| |
− | if is_set(Chapter) then
| |
− | in_text = in_text .. cfg.messages['in'] .. " "
| |
− | else
| |
− | if EditorCount <= 1 then
| |
− | post_text = ", " .. cfg.messages['editor'];
| |
− | else
| |
− | post_text = ", " .. cfg.messages['editors'];
| |
− | end
| |
− | end
| |
− | if (sepc ~= '.') then in_text = in_text:lower() end
| |
− | Editors = in_text .. Editors .. post_text;
| |
− | if (string.sub(Editors,-1,-1) == sepc)
| |
− | then Editors = Editors .. " "
| |
− | else Editors = 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,-sepc:len()-1);
| |
− | -- text = text:sub(1,-2); --Remove final separator (assumes that sepc is only one character)
| |
− | end
| |
− |
| |
− | text = safejoin( {text, PostScript}, sepc );
| |
− |
| |
− | -- Now enclose the whole thing in a <span/> element
| |
− | 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 #a > 0 then
| |
− | for i,v in ipairs(a) do
| |
− | names[i] = v.last
| |
− | if i == 4 then break end
| |
− | end
| |
− | elseif #e > 0 then
| |
− | for i,v in ipairs(e) do
| |
− | names[i] = v.last
| |
− | if i == 4 then break end
| |
− | end
| |
− | end
| |
− | names[ #names + 1 ] = first_set(Year, anchor_year); -- Year first for legacy citations
| |
− | 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()
| |
− |
| |
− | if nil ~= string.find( frame:getTitle(), 'sandbox', 1, true ) then -- did the {{#invoke:}} use sandbox version?
| |
− | cfg = mw.loadData( 'Module:Citation/CS1/Configuration/sandbox' ); -- load sandbox versions of Configuration and Whitelist and ...
| |
− | whitelist = mw.loadData( 'Module:Citation/CS1/Whitelist/sandbox' );
| |
− | dates = require('Module:Citation/CS1/Date_validation/sandbox').dates -- ... sandbox version of date validation code
| |
− | else -- otherwise
| |
− | cfg = mw.loadData( 'Module:Citation/CS1/Configuration' ); -- load live versions of Configuration and Whitelist and ...
| |
− | whitelist = mw.loadData( 'Module:Citation/CS1/Whitelist' );
| |
− | dates = require('Module:Citation/CS1/Date_validation').dates -- ... live version of date validation code
| |
− | end
| |
− |
| |
− | 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
| |