Module:Citation/CS1: Difference between revisions

From Zoophilia Wiki
Jump to navigationJump to search
meta>Dragons flight
(sync with sandbox, merge cite_web_title to bare_url, better style control on translations, category suppression by config, tweak display authors)
meta>Dragons flight
(sync to sandbox, mostly code cleaning / organization. Also, ref = none, layurl as alias for laysummary, tweaks to COinS output, and changes to bareurl error.)
Line 5: Line 5:
}
}


local SEEN = {};
-- Include translation message hooks, ID and error handling configuration settings.
local DATA = {};
-- 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' );


-- Include translation message hooks, ID and error handling configuration settings.
local cfg = require( 'Module:Citation/CS1/Configuration' );
-- 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' );


-- Populates numbered arguments in a message string using
-- Whether variable is set or not
-- an argument table.
function is_set( var )
function substitute( message, arguments )
    return not (var == nil or var == '');
     if arguments == nil then  
end
         return message;
 
-- First set variable or nil if none
function first_set(...)
    local list = {...};
    for _, var in pairs(list) do
        if is_set( var ) then
            return var;
        end
    end
end
 
-- 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;
    message = message .. " ";
end
    for k, v in ipairs( arguments ) do
 
        v = v:gsub( "%%", "%%%%" );
-- Populates numbered arguments in a message string using an argument table.
        message = message:gsub( "$" .. k .. "(%D)", v .. "%1" );
function substitute( msg, args )
    end   
    return args and tostring( mw.message.newRawMessage( msg, args ) ) or msg;
    message = message:sub(1,-2);
    return message;
end
end


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


Line 48: Line 63:
]]
]]
function argument_wrapper( args )
function argument_wrapper( args )
    DATA = args;
     local origin = {};
     local tbl = {};
      
      
     local mt = {
     return setmetatable({
         __index = function ( tbl, k )          
        ORIGIN = function( self, k )
             if SEEN[k] then
            local dummy = self[k]; --force the variable to be loaded.
            return origin[k];
        end
    },
    {
         __index = function ( tbl, k )
             if origin[k] ~= nil then
                 return nil;
                 return nil;
             end
             end
              
              
             local list = cfg.argument_map[k];                  
             local args, list, v = args, cfg.aliases[k];
 
           
             if list == nil then
             if list == nil then
                 error( cfg.message_list['unknown_argument_map'] );
                 error( cfg.messages['unknown_argument_map'] );
             elseif type( list ) == 'string' then
             elseif type( list ) == 'string' then
                 v = DATA[list];
                 v, origin[k] = args[list], list;
             else                  
             else
                 v = selectone( DATA, cfg.argument_map[k],
                 v, origin[k] = selectone( args, list, 'redundant_parameters' );
                     'redundant_parameters' );
                if origin[k] == nil then
                     origin[k] = '';   --Empty string, not nil;
                end
             end
             end
           
             if v == nil then
             if v == nil then
                 v = cfg.default_values[k];
                 v = cfg.defaults[k] or "";
                origin[k] = '';  --Empty string, not nil;
             end
             end
             SEEN[k] = true;
              
             tbl = rawset( tbl, k, v );
             tbl = rawset( tbl, k, v );
           
             return v;
             return v;
         end,
         end,
     }
     });
    return setmetatable( tbl, mt );
end
end


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


Line 113: Line 131:
function seterror( error_id, arguments, raw, prefix, suffix )
function seterror( error_id, arguments, raw, prefix, suffix )
     local error_state = cfg.error_conditions[ error_id ];
     local error_state = cfg.error_conditions[ error_id ];
   
     prefix = prefix or "";
     prefix = prefix or "";
     suffix = suffix or "";
     suffix = suffix or "";
 
   
     if error_state == nil then
     if error_state == nil then
         error( cfg.message_list['undefined_error'] );
         error( cfg.messages['undefined_error'] );
    elseif is_set( error_state.category ) then
        table.insert( z.error_categories, error_state.category );
     end
     end
      
      
     if error_state.category ~= nil and error_state.category ~= "" then
     local message = substitute( error_state.message, arguments );
        table.insert( z.error_categories, error_state.category );
    end
      
      
     local message = error_state.message;
     message = message .. " ([[" .. cfg.messages['help page link'] ..  
    message = substitute( message, arguments );
 
    message = wikiescape(message) .. " ([[" .. cfg.message_list['help page link'] ..  
         "#" .. error_state.anchor .. "|" ..
         "#" .. error_state.anchor .. "|" ..
         cfg.message_list['help page label'] .. "]])";
         cfg.messages['help page label'] .. "]])";
 
   
     z.error_ids[ error_id ] = true;
     z.error_ids[ error_id ] = true;
     if (error_id == 'bare_url_missing_title' or error_id == 'trans_missing_title')
     if inArray( error_id, { 'bare_url_missing_title', 'trans_missing_title' } )
             and z.error_ids['citation_missing_title'] then
             and z.error_ids['citation_missing_title'] then
         return '', false;
         return '', false;
     end
     end
      
      
     message = prefix .. message .. suffix;
     message = table.concat({ prefix, message, suffix });
      
      
     if raw == true then
     if raw == true then
Line 144: Line 160:
          
          
     return errorcomment( message, error_state.hidden );
     return errorcomment( message, error_state.hidden );
end
-- This returns a string with HTML character entities for wikitext markup characters.
function wikiescape(text)
    text = text:gsub( '[&\'%[%]{|}]', {   
            ['&'] = '&',   
            ["'"] = ''',   
            ['['] = '[',   
            [']'] = ']',   
            ['{'] = '{',   
            ['|'] = '|',   
            ['}'] = '}' } );
    return text;
end
end


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


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


-- Format an external link with error checking
-- Format an external link with error checking
function externallink( URL, label )
function externallink( URL, label, source )
     local error_str = "";
     local error_str = "";
     if label == nil or label == "" then
     if not is_set( label ) then
         label = URL;
         label = URL;
         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 "[" .. URL .. ' ' .. safeforurl( label ) .. "]" .. error_str;
end
end


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


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


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


Line 538: Line 536:
      
      
     while true do
     while true do
         last = selectone( args, cfg.argument_map[list_name .. '-Last'], 'redundant_parameters', i );
         last = selectone( args, cfg.aliases[list_name .. '-Last'], 'redundant_parameters', i );
         if ( last and "" < last ) then -- just in case someone passed in an empty parameter
         if not is_set(last) then
            names[i] = {
            -- just in case someone passed in an empty parameter
                last = last,
                first = selectone( args, cfg.argument_map[list_name .. '-First'], 'redundant_parameters', i ),
                link = selectone( args, cfg.argument_map[list_name .. '-Link'], 'redundant_parameters', i ),
                mask = selectone( args, cfg.argument_map[list_name .. '-Mask'], 'redundant_parameters', i )
            }               
        else
             break;
             break;
         end
         end
        names[i] = {
            last = last,
            first = selectone( args, cfg.aliases[list_name .. '-First'], 'redundant_parameters', i ),
            link = selectone( args, cfg.aliases[list_name .. '-Link'], 'redundant_parameters', i ),
            mask = selectone( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i )
        };
         i = i + 1;
         i = i + 1;
     end
     end
Line 557: Line 555:
function extractids( args )
function extractids( args )
     local id_list = {};
     local id_list = {};
   
     for k, v in pairs( cfg.id_handlers ) do     
     for k, v in pairs( cfg.id_handlers ) do     
         id_list[k] = selectone( args, v.parameters, 'redundant_parameters' );
         v = selectone( args, v.parameters, 'redundant_parameters' );
        if is_set(v) then id_list[k] = v; end
     end
     end
     return id_list;
     return id_list;
end
end
Line 567: Line 564:
-- Takes a table of IDs and turns it into a table of formatted ID outputs.
-- Takes a table of IDs and turns it into a table of formatted ID outputs.
function buildidlist( id_list, options )
function buildidlist( id_list, options )
     local handler;
     local new_list, handler = {};
     local new_list = {};
      
    function fallback(k) return { __index = function(t,i) return cfg.id_handlers[k][i] end } end;
      
      
     for k, v in pairs( id_list ) do
     for k, v in pairs( id_list ) do
        handler = {};
         -- fallback to read-only cfg
       
         handler = setmetatable( { ['id'] = v }, fallback(k) );
         --Becasue cfg is read-only we have to copy it the hard way.
         for k2, v2 in pairs( cfg.id_handlers[k] ) do
            handler[k2] = v2;
        end
        handler['id'] = v;
          
          
         if handler.mode == 'external' then      
         if handler.mode == 'external' then
             table.insert( new_list, {handler.label, externallinkid( handler ) } );
             table.insert( new_list, {handler.label, externallinkid( handler ) } );
         elseif handler.mode == 'internal' then
         elseif handler.mode == 'internal' then
             table.insert( new_list, {handler.label, internallinkid( handler ) } );
             table.insert( new_list, {handler.label, internallinkid( handler ) } );
         elseif handler.mode == 'manual' then
         elseif handler.mode ~= 'manual' then
             if k == 'DOI' then
             error( cfg.messages['unknown_ID_mode'] );
                table.insert( new_list, {handler.label, doi( v, options.DoiBroken ) } );
        elseif k == 'DOI' then
            elseif k == 'ASIN' then
            table.insert( new_list, {handler.label, doi( v, options.DoiBroken ) } );
                table.insert( new_list, {handler.label, amazon( v, options.ASINTLD ) } );  
        elseif k == 'ASIN' then
            elseif k == 'OL' then
            table.insert( new_list, {handler.label, amazon( v, options.ASINTLD ) } );  
                table.insert( new_list, {handler.label, openlibrary( v ) } );
        elseif k == 'OL' then
            elseif k == 'ISBN' then
            table.insert( new_list, {handler.label, openlibrary( v ) } );
                local ISBN = internallinkid( handler );
        elseif k == 'ISBN' then
                if not checkisbn( v ) and ( options.IgnoreISBN == nil or options.IgnoreISBN == "" ) then  
            local ISBN = internallinkid( handler );
                    ISBN = ISBN .. seterror( 'bad_isbn', {}, false, " ", "" );
            if not checkisbn( v ) and not is_set(options.IgnoreISBN) then
                end
                ISBN = ISBN .. seterror( 'bad_isbn', {}, false, " ", "" );
                table.insert( new_list, {handler.label, ISBN } );                 
            end
            else
            table.insert( new_list, {handler.label, ISBN } );                 
                error( cfg.message_list['unknown_manual_ID'] );
            end           
         else
         else
             error( cfg.message_list['unknown_ID_mode'] );
             error( cfg.messages['unknown_manual_ID'] );
         end
         end
     end
     end
 
   
     function comp( a, b )
     function comp( a, b )
         return a[1] < b[1];
         return a[1] < b[1];
     end
     end
 
   
     table.sort( new_list, comp );
     table.sort( new_list, comp );
     for k, v in ipairs( new_list ) do
     for k, v in ipairs( new_list ) do
Line 622: Line 613:
     local selected = '';
     local selected = '';
     local error_list = {};
     local error_list = {};
   
     if index ~= nil then index = tostring(index); end
     if index ~= nil then index = tostring(index); end
      
      
Line 628: Line 620:
         for _, v in ipairs( possible ) do
         for _, v in ipairs( possible ) do
             v = v:gsub( "#", "" );
             v = v:gsub( "#", "" );
             if args[v] ~= nil then
             if is_set(args[v]) then
                 if value ~= nil and selected ~= v then
                 if value ~= nil and selected ~= v then
                     table.insert( error_list, v );
                     table.insert( error_list, v );
Line 638: Line 630:
         end         
         end         
     end
     end
 
   
     for _, v in ipairs( possible ) do
     for _, v in ipairs( possible ) do
         if index ~= nil then
         if index ~= nil then
             v = v:gsub( "#", index );
             v = v:gsub( "#", index );
         end
         end
         if args[v] ~= nil then
         if is_set(args[v]) then
             if value ~= nil then
             if value ~= nil and selected ~=  v then
                 table.insert( error_list, v );
                 table.insert( error_list, v );
             else
             else
Line 652: Line 644:
         end
         end
     end
     end
 
   
     if #error_list > 0 then
     if #error_list > 0 then
         local error_str = "";
         local error_str = "";
         for _, k in ipairs( error_list ) do
         for _, k in ipairs( error_list ) do
             if error_str ~= "" then error_str = error_str .. ", " end
             if error_str ~= "" then error_str = error_str .. cfg.messages['parameter-separator'] end
             error_str = error_str .. "<code>|" .. k .. "=</code>";
             error_str = error_str .. wrap( 'parameter', k );
         end
         end
         if #error_list > 1 then
         if #error_list > 1 then
             error_str = error_str .. ", and ";
             error_str = error_str .. cfg.messages['parameter-final-separator'];
         else
         else
             error_str = error_str .. " and ";
             error_str = error_str .. cfg.messages['parameter-pair-separator'];
         end
         end
         error_str = error_str .. "<code>|" .. selected .. "=</code>";
         error_str = error_str .. wrap( 'parameter', selected );
         table.insert( z.message_tail, { seterror( error_condition, {error_str}, true ) } );
         table.insert( z.message_tail, { seterror( error_condition, {error_str}, true ) } );
     end
     end
           
   
     return value, selected;
     return value, selected;
end
-- COinS metadata (see <http://ocoins.info/>) allows automated tools to parse
-- the citation information.
function COinS(data)
    if 'table' ~= type(data) or nil == next(data) then
        return '';
    end
   
    local ctx_ver = "Z39.88-2004";
   
    -- treat table strictly as an array with only set values.
    local OCinSoutput = setmetatable( {}, {
        __newindex = function(self, key, value)
            if is_set(value) then
                rawset( self, #self+1, table.concat{ key, '=', mw.uri.encode( removewikilink( value ) ) } );
            end
        end
    });
   
    if is_set(data.Chapter) then
        OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:book";
        OCinSoutput["rft.genre"] = "bookitem";
        OCinSoutput["rft.btitle"] = data.Chapter;
        OCinSoutput["rft.atitle"] = data.Title;
    elseif is_set(data.Periodical) then
        OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:journal";
        OCinSoutput["rft.genre"] = "article";
        OCinSoutput["rft.jtitle"] = data.Periodical;
        OCinSoutput["rft.atitle"] = data.Title;
    else
        OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:book";
        OCinSoutput["rft.genre"] = "book"
        OCinSoutput["rft.btitle"] = data.Title;
    end
   
    OCinSoutput["rft.place"] = data.PublicationPlace;
    OCinSoutput["rft.date"] = data.Date;
    OCinSoutput["rft.series"] = data.Series;
    OCinSoutput["rft.volume"] = data.Volume;
    OCinSoutput["rft.issue"] = data.Issue;
    OCinSoutput["rft.pages"] = data.Pages;
    OCinSoutput["rft.edition"] = data.Edition;
    OCinSoutput["rft.pub"] = data.PublisherName;
   
    for k, v in pairs( data.ID_list ) do
        local id, value = cfg.id_handlers[k].COinS;
        if k == 'ISBN' then value = cleanisbn( v ); else value = v; end
        if string.sub( id or "", 1, 4 ) == 'info' then
            OCinSoutput["rft_id"] = table.concat{ id, "/", v };
        else
            OCinSoutput[ id ] = value;
        end
    end
   
    local last, first;
    for k, v in ipairs( data.Authors ) do
        last, first = v.last, v.first;
        if k == 1 then
            if is_set(last) then
                OCinSoutput["rft.aulast"] = last;
            end
            if is_set(first) then
                OCinSoutput["rft.aufirst"] = first;
            end
        end
        if is_set(last) and is_set(first) then
            OCinSoutput["rft.au"] = table.concat{ last, ", ", first };
        elseif is_set(last) then
            OCinSoutput["rft.au"] = last;
        end
    end
   
    OCinSoutput.rft_id = data.URL;
    OCinSoutput.rfr_id = table.concat{ "info:sid/", mw.site.server:match( "[^/]*$" ), ":", data.RawPage };
    OCinSoutput = setmetatable( OCinSoutput, nil );
   
    -- sort with version string always first, and combine.
    table.sort( OCinSoutput );
    table.insert( OCinSoutput, 1, "ctx_ver=" .. ctx_ver );  -- such as "Z39.88-2004"
    return table.concat(OCinSoutput, "&");
end
end


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


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


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


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


    local OCinStitle = "ctx_ver=" .. ctx_ver  -- such as "Z39.88-2004"
     if is_set(Periodical) and not is_set(Chapter) and is_set(Title) then
    for name,value in pairs(OCinSdata) do
        Chapter = Title;
        OCinStitle = OCinStitle .. "&" .. name .. "=" .. mw.uri.encode( removewikilink(value) );
        ChapterLink = TitleLink;
    end
        TransChapter = TransTitle;
    for _, value in ipairs(OCinSauthors) do
        Title = '';
        OCinStitle = OCinStitle .. "&rft.au=" .. mw.uri.encode( removewikilink(value) );
        TitleLink = '';
    end
        TransTitle = '';
    for name,value in pairs(OCinSids) do
        OCinStitle = OCinStitle .. "&rft_id=" .. mw.uri.encode(name .. "/" .. removewikilink(value) );
    end
   
    OCinStitle = OCinStitle .. "&rfr_id=info:sid/" .. mw.site.server:match( "[^/]*$" ) .. ":"
      .. this_page.prefixedText  -- end COinS data by page's non-encoded pagename
 
     if (Periodical ~= nil and Periodical ~= "") and
        (Chapter == nil or Chapter == '') and
        (Title ~= nil and Title ~= "") then
            Chapter = Title
            ChapterLink = TitleLink
            TransChapter = TransTitle
            Title = nil
            TitleLink = nil
            TransTitle = nil           
     end
     end


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


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


     -- Captures the value for Date prior to adding parens or other textual transformations
     -- Captures the value for Date prior to adding parens or other textual transformations
     local DateIn = Date
     local DateIn = Date;
      
      
     if ( URL == nil or URL == '' ) and
     if not is_set(URL) and
            ( ChapterURL == nil or ChapterURL == '' ) and
        not is_set(ChapterURL) and
            ( ArchiveURL == nil or ArchiveURL == '' ) and              
        not is_set(ArchiveURL) and
            ( ConferenceURL == nil or ConferenceURL == '' ) and              
        not is_set(ConferenceURL) and
            ( TranscriptURL == nil or TranscriptURL == '' ) then
        not is_set(TranscriptURL) then
 
       
         -- Test if cite web is called without giving a URL
         -- Test if cite web is called without giving a URL
         if ( config.CitationClass == "web" ) then
         if ( config.CitationClass == "web" ) then
             table.insert( z.message_tail, { seterror( 'cite_web_url', {}, true ) } );
             table.insert( z.message_tail, { seterror( 'cite_web_url', {}, true ) } );
         end
         end
 
       
         -- Test if accessdate is given without giving a URL
         -- Test if accessdate is given without giving a URL
         if ( AccessDate ~= nil and AccessDate ~= '' ) then
         if is_set(AccessDate) then
             table.insert( z.message_tail, { seterror( 'accessdate_missing_url', {}, true ) } );
             table.insert( z.message_tail, { seterror( 'accessdate_missing_url', {}, true ) } );
             AccessDate = nil;
             AccessDate = '';
         end    
         end
   
       
         -- Test if format is given without giving a URL
         -- Test if format is given without giving a URL
         if ( Format ~= nil and Format ~= '' ) then
         if is_set(Format) then
             Format = Format .. seterror( 'format_missing_url' );
             Format = Format .. seterror( 'format_missing_url' );
         end      
         end
     end  
     end
 
   
     -- Test if citation has no title
     -- Test if citation has no title
     if ( Chapter == nil or Chapter == "" ) and  
     if not is_set(Chapter) and
            ( Title == nil or Title == "" ) and
        not is_set(Title) and
            ( Periodical == nil or Periodical == "" ) and
        not is_set(Periodical) and
            ( Conference == nil or Conference == "" ) and  
        not is_set(Conference) and
            ( TransTitle == nil or TransTitle == "" ) and
        not is_set(TransTitle) and
            ( TransChapter == nil or TransChapter == "" ) then
        not is_set(TransChapter) then
         table.insert( z.message_tail, { seterror( 'citation_missing_title', {}, true ) } );
         table.insert( z.message_tail, { seterror( 'citation_missing_title', {}, true ) } );
     end
     end
 
   
     if ( Format ~= nil and Format ~="" ) then
     Format = is_set(Format) and " (" .. Format .. ")" or "";
        Format = " (" .. Format .. ")" else Format = "" end
      
      
     local OriginalURL = URL
     local OriginalURL = URL
Line 1,089: Line 1,090:
         end
         end
     end
     end
 
   
     -- Format chapter / article title
     -- Format chapter / article title
     if ( Chapter ~= nil and Chapter ~= "" ) and ( ChapterLink and "" < ChapterLink ) then  
     if is_set(Chapter) and is_set(ChapterLink) then  
         Chapter = "[[" .. ChapterLink .. "|" .. Chapter .. "]]";
         Chapter = "[[" .. ChapterLink .. "|" .. Chapter .. "]]";
     end
     end
     if ( Periodical and "" < Periodical ) and (Title ~= nil and Title ~= "" ) then
     if is_set(Periodical) and is_set(Title) then
         Chapter = wrap( 'italic-title', Chapter );
         Chapter = wrap( 'italic-title', Chapter );
         TransChapter = wrap( 'trans-italic-title', TransChapter );
         TransChapter = wrap( 'trans-italic-title', TransChapter );
Line 1,103: Line 1,104:
      
      
     local TransError = ""
     local TransError = ""
     if TransChapter ~= "" and Chapter == "" then
     if is_set(TransChapter) then
        TransError = " " .. seterror( 'trans_missing_chapter' );
        if not is_set(Chapter) then
            TransError = " " .. seterror( 'trans_missing_chapter' );
        else
            TransChapter = " " .. TransChapter;
        end
     end
     end
      
      
     if TransChapter ~= "" and Chapter ~= "" then TransChapter = " " .. TransChapter; end
     Chapter = Chapter .. TransChapter;
    Chapter = Chapter .. TransChapter
      
      
     if Chapter ~= "" then
     if is_set(Chapter) then
         if ( ChapterLink == nil ) then
         if not is_set(ChapterLink) then
             if ( ChapterURL and "" < ChapterURL ) then              
             if is_set(ChapterURL) then
                 Chapter = externallink( ChapterURL, Chapter ) .. TransError;
                 Chapter = externallink( ChapterURL, Chapter ) .. TransError;
                 if URL == nil or URL == "" then
                 if not is_set(URL) then
                     Chapter = Chapter .. Format;
                     Chapter = Chapter .. Format;
                     Format = "";
                     Format = "";
                 end
                 end
             elseif ( URL and "" < URL ) then  
             elseif is_set(URL) then  
                 Chapter = externallink( URL, Chapter ) .. TransError .. Format;
                 Chapter = externallink( URL, Chapter ) .. TransError .. Format;
                 URL = nil
                 URL = "";
                 Format = ""
                 Format = "";
             else
             else
                 Chapter = Chapter .. TransError;
                 Chapter = Chapter .. TransError;
             end             
             end             
         elseif ChapterURL ~= nil and ChapterURL ~= "" then
         elseif is_set(ChapterURL) then
             Chapter = Chapter .. " " .. externallink( ChapterURL ) ..  
             Chapter = Chapter .. " " .. externallink( ChapterURL, nil, ChapterURLorigin ) ..  
                 TransError;
                 TransError;
         else
         else
Line 1,132: Line 1,136:
         end
         end
         Chapter = Chapter .. sepc .. " " -- with end-space
         Chapter = Chapter .. sepc .. " " -- with end-space
     elseif ChapterURL ~= nil and ChapterURL ~= "" then
     elseif is_set(ChapterURL) then
         Chapter = " " .. externallink( ChapterURL ) .. sepc .. " ";
         Chapter = " " .. externallink( ChapterURL, nil, ChapterURLorigin ) .. sepc .. " ";
     end         
     end         
      
      
     -- Format main title.
     -- Format main title.
     if ( TitleLink and "" < TitleLink ) then
     if is_set(TitleLink) and is_set(Title) then
        if ( Title and "" < Title ) then
        Title = "[[" .. TitleLink .. "|" .. Title .. "]]"
            Title = "[[" .. TitleLink .. "|" .. Title .. "]]"  
        end
     end
     end
 
   
     if ( Periodical and "" < Periodical ) then
     if is_set(Periodical) then
         Title = wrap( 'quoted-title', Title );
         Title = wrap( 'quoted-title', Title );
         TransTitle = wrap( 'trans-quoted-title', TransTitle );
         TransTitle = wrap( 'trans-quoted-title', TransTitle );
     elseif ( config.CitationClass == "web"
     elseif inArray(config.CitationClass, {"web","news","pressrelease"}) and
            or config.CitationClass == "news"  
             not is_set(Chapter) then
            or config.CitationClass == "pressrelease" ) and  
             Chapter == "" then
         Title = wrap( 'quoted-title', Title );
         Title = wrap( 'quoted-title', Title );
         TransTitle = wrap( 'trans-quoted-title', TransTitle );
         TransTitle = wrap( 'trans-quoted-title', TransTitle );
Line 1,157: Line 1,157:
     end
     end
      
      
     local TransError = "";
     TransError = "";
     if TransTitle ~= "" and Title == "" then
     if is_set(TransTitle) then
        TransError = " " .. seterror( 'trans_missing_title' );
        if not is_set(Title) then
            TransError = " " .. seterror( 'trans_missing_title' );
        else
            TransTitle = " " .. TransTitle;
        end
     end
     end
      
      
     if TransTitle ~= "" and Title ~= "" then TransTitle = " " .. TransTitle; end
     Title = Title .. TransTitle;
    Title = Title .. TransTitle
      
      
     if Title ~= "" then
     if is_set(Title) then
         if ( TitleLink == nil and URL and "" < URL ) then  
         if not is_set(TitleLink) and is_set(URL) then  
             Title = externallink( URL, Title ) .. TransError .. Format       
             Title = externallink( URL, Title ) .. TransError .. Format       
             URL = nil
             URL = "";
             Format = ''
             Format = "";
         else
         else
             Title = Title .. TransError;
             Title = Title .. TransError;
         end
         end
     end
     end
 
   
     if ( Place ~= nil and Place ~= "" ) then
     if is_set(Place) then
         if sepc == '.' then
         if sepc == '.' then
             Place = " " .. wrap( 'written', Place ) .. sepc .. " ";
             Place = " " .. wrap( 'written', Place ) .. sepc .. " ";
         else
         else
             Place = " " .. substitute( cfg.message_list['written']:lower(), {Place} ) .. sepc .. " ";
             Place = " " .. substitute( cfg.messages['written']:lower(), {Place} ) .. sepc .. " ";
         end          
         end
    else
        Place = "";
     end
     end
      
      
     if ( Conference ~= nil and Conference ~="" ) then
     if is_set(Conference) then
         if ( ConferenceURL ~= nil ) then
         if is_set(ConferenceURL) then
             Conference = externallink( ConferenceURL, Conference );
             Conference = externallink( ConferenceURL, Conference );
         end
         end
         Conference = " " .. Conference
         Conference = " " .. Conference
     elseif ConferenceURL ~= nil and ConferenceURL ~= "" then
     elseif is_set(ConferenceURL) then
         Conference = " " .. externallink( ConferenceURL );
         Conference = " " .. externallink( ConferenceURL, nil, ConferenceURLorigin );
    else
        Conference = ""
     end
     end
     if ( nil ~= Position or nil ~= Page or nil ~= Pages ) then At = nil end
      
     if ( nil == Position and "" ~= Position ) then
     if not is_set(Position) then
         local Minutes = A['Minutes'];
         local Minutes = A['Minutes'];
         if ( nil ~= Minutes ) then
         if is_set(Minutes) then
             Position = " " .. Minutes .. " " .. cfg.message_list['minutes'];
             Position = " " .. Minutes .. " " .. cfg.messages['minutes'];
         else
         else
             local Time = A['Time'];
             local Time = A['Time'];
             if ( nil ~= Time ) then
             if is_set(Time) then
                 local TimeCaption = A['TimeCaption']
                 local TimeCaption = A['TimeCaption']
                 if TimeCaption == nil then
                 if not is_set(TimeCaption) then
                     TimeCaption = cfg.message_list['event'];
                     TimeCaption = cfg.messages['event'];
                     if sepc ~= '.' then
                     if sepc ~= '.' then
                         TimeCaption = TimeCaption:lower();
                         TimeCaption = TimeCaption:lower();
                     end
                     end
                 end              
                 end
                 Position = " " .. TimeCaption .. " " .. Time
                 Position = " " .. TimeCaption .. " " .. Time;
            else
                Position = ""
             end
             end
         end
         end
     else
     else
         Position = " " .. Position
         Position = " " .. Position;
        At = '';
     end
     end
     if ( nil == Page or "" == Page ) then  
   
        Page = ""
     if not is_set(Page) then
         if ( nil == Pages or "" == Pages) then  
         if is_set(Pages) then
             Pages = ""
             if is_set(Periodical) and
        elseif ( Periodical ~= nil and Periodical ~= "" and
                not inArray(config.CitationClass, {"encyclopaedia","web","book","news"}) then
                config.CitationClass ~= "encyclopaedia" and
                Pages = ": " .. Pages;
                config.CitationClass ~= "web" and
             elseif tonumber(Pages) ~= nil then
                config.CitationClass ~= "book" and
                Pages = sepc .." " .. PPrefix .. Pages;
                config.CitationClass ~= "news") then
             else
            Pages = ": " .. Pages
                Pages = sepc .." " .. PPPrefix .. Pages;
        else
             if ( tonumber(Pages) ~= nil ) then
              Pages = sepc .." " .. PPrefix .. Pages
             else Pages = sepc .." " .. PPPrefix .. Pages
             end
             end
         end
         end
     else
     else
        Pages = ""
         if is_set(Periodical) and
         if ( Periodical ~= nil and Periodical ~= "" and
            not inArray(config.CitationClass, {"encyclopaedia","web","book","news"}) then
            config.CitationClass ~= "encyclopaedia" and
             Page = ": " .. Page;
            config.CitationClass ~= "web" and
            config.CitationClass ~= "book" and
            config.CitationClass ~= "news") then
             Page = ": " .. Page
         else
         else
             Page = sepc .." " .. PPrefix .. Page
             Page = sepc .." " .. PPrefix .. Page;
         end
         end
     end
     end
     if ( At ~= nil and At ~="") then At = sepc .. " " .. At
      
    else At = "" end
    At = is_set(At) and (sepc .. " " .. At) or "";
    if ( Coauthors == nil ) then Coauthors = "" end
     Others = is_set(Others) and (sepc .. " " .. Others) or "";
     if ( Others ~= nil and Others ~="" ) then
     TitleType = is_set(TitleType) and (" (" .. TitleType .. ")") or "";
        Others = sepc .. " " .. Others else Others = "" end
     TitleNote = is_set(TitleNote) and (sepc .. " " .. TitleNote) or "";
     if ( TitleType ~= nil and TitleType ~="" ) then
     Language = is_set(Language) and (" " .. wrap( 'language', Language )) or "";
        TitleType = " (" .. TitleType .. ")" else TitleType = "" end
     Edition = is_set(Edition) and (" " .. wrap( 'edition', Edition )) or "";
     if ( TitleNote ~= nil and TitleNote ~="" ) then
    Issue = is_set(Issue) and (" (" .. Issue .. ")") or "";
        TitleNote = sepc .. " " .. TitleNote else TitleNote = "" end
     Series = is_set(Series) and (sepc .. " " .. Series) or "";
     if ( Language ~= nil and Language ~="" ) then
    OrigYear = is_set(OrigYear) and (" [" .. OrigYear .. "]") or "";
        Language = " " .. wrap( 'language', Language ) else Language = "" end
    Agency = is_set(Agency) and (sepc .. " " .. Agency) or "";
     if ( Edition ~= nil and Edition ~="" ) then
   
        Edition = " " .. wrap( 'edition', Edition ) else Edition = "" end
     if is_set(Volume) then
     if ( Volume ~= nil and Volume ~="" )
     then
         if ( mw.ustring.len(Volume) > 4 )
         if ( mw.ustring.len(Volume) > 4 )
           then Volume = sepc .." " .. Volume
           then Volume = sepc .." " .. Volume;
           else Volume = " <b>" .. hyphentodash(Volume) .. "</b>"
           else Volume = " <b>" .. hyphentodash(Volume) .. "</b>";
         end
         end
     else Volume = "" end
     end
     if ( Issue ~= nil and Issue ~="" ) then
      
        Issue = " (" .. Issue .. ")" else Issue = "" end
    if ( Series ~= nil and Series ~="" ) then
        Series = sepc .. " " .. Series else Series = "" end
    if ( OrigYear ~= nil and OrigYear ~="" ) then
        OrigYear = " [" .. OrigYear .. "]" else OrigYear = "" end
    if ( Agency ~= nil and Agency ~="" ) then
        Agency = sepc .. " " .. Agency else Agency = "" end
     ------------------------------------ totally unrelated data
     ------------------------------------ totally unrelated data
     if ( Date ~= nil ) then Date = Date else Date = "" end
     if is_set(Via) then Via = " " .. wrap( 'via', Via ); end
    if ( Via ~= nil and Via ~="" ) then
     if is_set(AccessDate) then
        Via = " " .. wrap( 'via', Via ) else Via = "" end
        local retrv_text = " " .. cfg.messages['retrieved']
     if ( AccessDate ~= nil and AccessDate ~="" )
        if (sepc ~= ".") then retrv_text = retrv_text:lower() end
    then local retrv_text = " " .. cfg.message_list['retrieved']
        AccessDate = '<span class="reference-accessdate">' .. sepc
        if (sepc ~= ".") then retrv_text = retrv_text:lower() end
            .. substitute( retrv_text, {AccessDate} ) .. '</span>'
        AccessDate = '<span class="reference-accessdate">' .. sepc
     end
            .. substitute( retrv_text, {AccessDate} ) .. '</span>'
   
     else AccessDate = "" end
     if is_set(SubscriptionRequired) then
     if ( SubscriptionRequired ~= nil and
         SubscriptionRequired = sepc .. " " .. cfg.messages['subscription'];
        SubscriptionRequired ~= "" ) then
         SubscriptionRequired = sepc .. " " .. cfg.message_list['subscription'];
    else
        SubscriptionRequired = ""
     end
     end
     if ( ID ~= nil and ID ~="") then ID = sepc .." ".. ID else ID="" end
   
 
     if is_set(ID) then ID = sepc .." ".. ID; end
   
     ID_list = buildidlist( ID_list, {DoiBroken = DoiBroken, ASINTLD = ASINTLD, IgnoreISBN = IgnoreISBN} );
     ID_list = buildidlist( ID_list, {DoiBroken = DoiBroken, ASINTLD = ASINTLD, IgnoreISBN = IgnoreISBN} );


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


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


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


     local tcommon
     local tcommon
     if ( ( (config.CitationClass == "journal") or (config.CitationClass == "citation") and
     if inArray(config.CitationClass, {"journal","citation"}) and is_set(Periodical) then
        Periodical ~= "" ) then
         if is_set(Others) then Others = Others .. sepc .. " " end
         if (Others ~= "") then Others = Others .. sepc .. " " end
         tcommon = safejoin( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series,  
         tcommon = safejoin( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series,  
             Language, Edition, Publisher, Agency, Volume, Issue, Position}, sepc );
             Language, Edition, Publisher, Agency, Volume, Issue, Position}, sepc );
Line 1,436: Line 1,405:
     else
     else
         ID_list = ID;
         ID_list = ID;
     end  
     end
   
     local idcommon = safejoin( { ID_list, URL, Archived, AccessDate, Via, SubscriptionRequired, Lay, Quote }, sepc );
     local idcommon = safejoin( { ID_list, URL, Archived, AccessDate, Via, SubscriptionRequired, Lay, Quote }, sepc );
 
     local text;
     local text
     local pgtext = Page .. Pages .. At;
     local pgtext = Page .. Pages .. At
      
      
     if ( "" ~= Authors ) then
     if is_set(Authors) then
         if (Coauthors ~= "")  
         if is_set(Coauthors) then
          then Authors = Authors .. A['AuthorSeparator'] .. " " .. Coauthors
            Authors = Authors .. A['AuthorSeparator'] .. " " .. Coauthors
         end
         end
         if ( "" ~= Date )
         if is_set(Date) then
          then Date = " ("..Date..")" .. OrigYear .. sepc .. " "
            Date = " ("..Date..")" .. OrigYear .. sepc .. " "
          else
        elseif string.sub(Authors,-1,-1) == sepc then
            if ( string.sub(Authors,-1,-1) == sepc) --check end character
            Authors = Authors .. " "
              then Authors = Authors .. " "
        else
              else Authors = Authors .. sepc .. " "
            Authors = Authors .. sepc .. " "
            end
         end
         end
         if ( "" ~= Editors) then
         if is_set(Editors) then
             local in_text = " in "
             local in_text = " " .. cfg.messages['in'] .. " "
             if (sepc == '.') then in_text = " In " end
             if (sepc ~= '.') then in_text = in_text:lower() end
             if (string.sub(Editors,-1,-1) == sepc)
             if (string.sub(Editors,-1,-1) == sepc)
                 then Editors = in_text .. Editors .. " "
                 then Editors = in_text .. Editors .. " "
Line 1,464: Line 1,432:
         text = safejoin( {Authors, Date, Chapter, Place, Editors, tcommon }, sepc );
         text = safejoin( {Authors, Date, Chapter, Place, Editors, tcommon }, sepc );
         text = safejoin( {text, pgtext, idcommon}, sepc );
         text = safejoin( {text, pgtext, idcommon}, sepc );
     elseif ( "" ~= Editors) then
     elseif is_set(Editors) then
         if ( "" ~= Date ) then
         if is_set(Date) then
             if EditorCount <= 1 then
             if EditorCount <= 1 then
                 Editors = Editors .. ", " .. cfg.message_list['editor'];
                 Editors = Editors .. ", " .. cfg.messages['editor'];
             else
             else
                 Editors = Editors .. ", " .. cfg.message_list['editors'];
                 Editors = Editors .. ", " .. cfg.messages['editors'];
             end
             end
             Date = " (" .. Date ..")" .. OrigYear .. sepc .. " "
             Date = " (" .. Date ..")" .. OrigYear .. sepc .. " "
         else
         else
             if EditorCount <= 1 then
             if EditorCount <= 1 then
                 Editors = Editors .. " (" .. cfg.message_list['editor'] .. ")" .. sepc .. " "
                 Editors = Editors .. " (" .. cfg.messages['editor'] .. ")" .. sepc .. " "
             else
             else
                 Editors = Editors .. " (" .. cfg.message_list['editors'] .. ")" .. sepc .. " "
                 Editors = Editors .. " (" .. cfg.messages['editors'] .. ")" .. sepc .. " "
             end
             end
         end
         end
Line 1,482: Line 1,450:
         text = safejoin( {text, pgtext, idcommon}, sepc );
         text = safejoin( {text, pgtext, idcommon}, sepc );
     else
     else
         if ( "" ~= Date ) then
         if is_set(Date) then
             if ( string.sub(tcommon,-1,-1) ~= sepc )
             if ( string.sub(tcommon,-1,-1) ~= sepc )
               then Date = sepc .." " .. Date .. OrigYear
               then Date = sepc .." " .. Date .. OrigYear
               else Date = " " .. Date .. OrigYear
               else Date = " " .. Date .. OrigYear
             end
             end
         end -- endif ""~=Date
         end
         if ( config.CitationClass=="journal" and Periodical ) then
         if config.CitationClass=="journal" and is_set(Periodical) then
          text = safejoin( {Chapter, Place, tcommon}, sepc );
            text = safejoin( {Chapter, Place, tcommon}, sepc );
          text = safejoin( {text, pgtext, Date, idcommon}, sepc );
            text = safejoin( {text, pgtext, Date, idcommon}, sepc );
         else
         else
          text = safejoin( {Chapter, Place, tcommon, Date}, sepc );
            text = safejoin( {Chapter, Place, tcommon, Date}, sepc );
          text = safejoin( {text, pgtext, idcommon}, sepc );
            text = safejoin( {text, pgtext, idcommon}, sepc );
         end
         end
     end
     end
      
      
     if PostScript ~= '' and PostScript ~= nil and PostScript ~= sepc then
     if is_set(PostScript) and PostScript ~= sepc then
         text = safejoin( {text, sepc}, sepc );  --Deals with italics, spaces, etc.
         text = safejoin( {text, sepc}, sepc );  --Deals with italics, spaces, etc.
         text = text:sub(1,-2); --Remove final seperator     
         text = text:sub(1,-2); --Remove final seperator     
Line 1,505: Line 1,473:


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


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

Revision as of 18:42, 25 April 2013

Documentation for this module may be created at Module:Citation/CS1/doc

local z = {
    error_categories = {};
    error_ids = {};
    message_tail = {};
}

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

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

-- Whether variable is set or not
function is_set( var )
    return not (var == nil or var == '');
end

-- First set variable or nil if none
function first_set(...)
    local list = {...};
    for _, var in pairs(list) do
        if is_set( var ) then
            return var;
        end
    end
end

-- Whether needle is in haystack
function inArray( needle, haystack )
    if needle == nil then
        return false;
    end
    for n,v in ipairs( haystack ) do
        if v == needle then
            return n;
        end
    end
    return false;
end

-- Populates numbered arguments in a message string using an argument table.
function substitute( msg, args )
    return args and tostring( mw.message.newRawMessage( msg, args ) ) or msg;
end

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

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

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

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

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

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

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

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

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

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

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

--[[
Determines whether an URL string is valid

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

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

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

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

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

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

-- Protects a string that will be wrapped in wiki italic markup '' ... ''
function safeforitalics( str )
    --[[ Note: We can not use <i> for italics, as the expected behavior for
    italics specified by ''...'' in the title is that they will be inverted
    (i.e. unitalicized) in the resulting references.  In addition, <i> and ''
    tend to interact poorly under Mediawiki's HTML tidy. ]]
    
    if not is_set(str) then
        return str;
    else
        if str:sub(1,1) == "'" then str = "<span />" .. str; end
        if str:sub(-1,-1) == "'" then str = str .. "<span />"; end
        
        -- Remove newlines as they break italics.
        return str:gsub( '\n', ' ' );
    end
end

--[[
Joins a sequence of strings together while checking for duplicate separation
characters.
]]
function safejoin( tbl, duplicate_char )
    --[[
    Note: we use string functions here, rather than ustring functions.
    
    This has considerably faster performance and should work correctly as 
    long as the duplicate_char is strict ASCII.  The strings
    in tbl may be ASCII or UTF8.
    ]]
    
    local str = '';
    local comp = '';
    local end_chr = '';
    local trim;
    for _, value in ipairs( tbl ) do
        if value == nil then value = ''; end
        
        if str == '' then
            str = value;
        elseif value ~= '' then
            if value:sub(1,1) == '<' then
                -- Special case of values enclosed in spans and other markup.
                comp = value:gsub( "%b<>", "" );
            else
                comp = value;
            end
            
            if comp:sub(1,1) == duplicate_char then
                trim = false;
                end_chr = str:sub(-1,-1);
                -- str = str .. "<HERE(enchr=" .. end_chr.. ")"
                if end_chr == duplicate_char then
                    str = str:sub(1,-2);
                elseif end_chr == "'" then
                    if str:sub(-3,-1) == duplicate_char .. "''" then
                        str = str:sub(1, -4) .. "''";
                    elseif str:sub(-5,-1) == duplicate_char .. "]]''" then
                        trim = true;
                    elseif str:sub(-4,-1) == duplicate_char .. "]''" then
                        trim = true;
                    end
                elseif end_chr == "]" then
                    if str:sub(-3,-1) == duplicate_char .. "]]" then
                        trim = true;
                    elseif str:sub(-2,-1) == duplicate_char .. "]" then
                        trim = true;
                    end
                elseif end_chr == " " then
                    if str:sub(-2,-1) == duplicate_char .. " " then
                        str = str:sub(1,-3);
                    end
                end

                if trim then
                    if value ~= comp then 
                        local dup2 = duplicate_char;
                        if dup2:match( "%A" ) then dup2 = "%" .. dup2; end
                        
                        value = value:gsub( "(%b<>)" .. dup2, "%1", 1 )
                    else
                        value = value:sub( 2, -1 );
                    end
                end
            end
            str = str .. value;
        end
    end
    return str;
end  

--[[
Return the year portion of a date string, if possible.  
Returns empty string if the argument can not be interpreted
as a year.
]]
function selectyear( str )
    -- Is the input a simple number?
    local num = tonumber( str ); 
    if num ~= nil and num > 0 and num < 2100 and num == math.floor(num) then
        return str;
    else
        -- Use formatDate to interpret more complicated formats
        local lang = mw.getContentLanguage();
        local good, result;
        good, result = pcall( lang.formatDate, lang, 'Y', str )
        if good then 
            return result;
        else
            -- Can't make sense of this input, return blank.
            return "";
        end
    end
end

-- Attempts to convert names to initials.
function reducetoinitials(first)
    local initials = {}
    for word in string.gmatch(first, "%S+") do
        table.insert(initials, string.sub(word,1,1)) -- Vancouver format does not include full stops.
    end
    return table.concat(initials) -- Vancouver format does not include spaces.
end

-- Formats a list of people (e.g. authors / editors) 
function listpeople(control, people)
    local sep = control.sep;
    local namesep = control.namesep
    local format = control.format
    local maximum = control.maximum
    local lastauthoramp = control.lastauthoramp;
    local text = {}
    local etal = false;
    
    if sep:sub(-1,-1) ~= " " then sep = sep .. " " end
    if maximum ~= nil and maximum < 1 then return "", 0; end
    
    for i,person in ipairs(people) do
        if is_set(person.last) then
            local mask = person.mask
            local one
            local sep_one = sep;
            if maximum ~= nil and i > maximum then
                etal = true;
                break;
            elseif (mask ~= nil) then
                local n = tonumber(mask)
                if (n ~= nil) then
                    one = string.rep("&mdash;",n)
                else
                    one = mask;
                    sep_one = " ";
                end
            else
                one = person.last
                local first = person.first
                if is_set(first) then 
                    if ( "vanc" == format ) then first = reducetoinitials(first) end
                    one = one .. namesep .. first 
                end
                if is_set(person.link) then one = "[[" .. person.link .. "|" .. one .. "]]" end
            end
            table.insert( text, one )
            table.insert( text, sep_one )
        end
    end

    local count = #text / 2;
    if count > 0 then 
        if count > 1 and is_set(lastauthoramp) and not etal then
            text[#text-2] = " & ";
        end
        text[#text] = nil; 
    end
    
    local result = table.concat(text) -- construct list
    if etal then 
        local etal_text = cfg.messages['et al'];
        result = result .. " " .. etal_text;
    end
    
    -- if necessary wrap result in <span> tag to format in Small Caps
    if ( "scap" == format ) then result = 
        '<span class="smallcaps" style="font-variant:small-caps">' .. result .. '</span>';
    end 
    return result, count
end

-- Generates a CITEREF anchor ID.
function anchorid( options )
    return "CITEREF" .. table.concat( options );
end

-- Gets name list from the input arguments
function extractnames(args, list_name)
    local names = {};
    local i = 1;
    local last;
    
    while true do
        last = selectone( args, cfg.aliases[list_name .. '-Last'], 'redundant_parameters', i );
        if not is_set(last) then
            -- just in case someone passed in an empty parameter
            break;
        end
        names[i] = {
            last = last,
            first = selectone( args, cfg.aliases[list_name .. '-First'], 'redundant_parameters', i ),
            link = selectone( args, cfg.aliases[list_name .. '-Link'], 'redundant_parameters', i ),
            mask = selectone( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i )
        };
        i = i + 1;
    end
    return names;
end

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

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

-- COinS metadata (see <http://ocoins.info/>) allows automated tools to parse
-- the citation information.
function COinS(data)
    if 'table' ~= type(data) or nil == next(data) then
        return '';
    end
    
    local ctx_ver = "Z39.88-2004";
    
    -- treat table strictly as an array with only set values.
    local OCinSoutput = setmetatable( {}, {
        __newindex = function(self, key, value)
            if is_set(value) then
                rawset( self, #self+1, table.concat{ key, '=', mw.uri.encode( removewikilink( value ) ) } );
            end
        end
    });
    
    if is_set(data.Chapter) then
        OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:book";
        OCinSoutput["rft.genre"] = "bookitem";
        OCinSoutput["rft.btitle"] = data.Chapter;
        OCinSoutput["rft.atitle"] = data.Title;
    elseif is_set(data.Periodical) then
        OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:journal";
        OCinSoutput["rft.genre"] = "article";
        OCinSoutput["rft.jtitle"] = data.Periodical;
        OCinSoutput["rft.atitle"] = data.Title;
    else
        OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:book";
        OCinSoutput["rft.genre"] = "book"
        OCinSoutput["rft.btitle"] = data.Title;
    end
    
    OCinSoutput["rft.place"] = data.PublicationPlace;
    OCinSoutput["rft.date"] = data.Date;
    OCinSoutput["rft.series"] = data.Series;
    OCinSoutput["rft.volume"] = data.Volume;
    OCinSoutput["rft.issue"] = data.Issue;
    OCinSoutput["rft.pages"] = data.Pages;
    OCinSoutput["rft.edition"] = data.Edition;
    OCinSoutput["rft.pub"] = data.PublisherName;
    
    for k, v in pairs( data.ID_list ) do
        local id, value = cfg.id_handlers[k].COinS;
        if k == 'ISBN' then value = cleanisbn( v ); else value = v; end
        if string.sub( id or "", 1, 4 ) == 'info' then
            OCinSoutput["rft_id"] = table.concat{ id, "/", v };
        else
            OCinSoutput[ id ] = value;
        end
    end
    
    local last, first;
    for k, v in ipairs( data.Authors ) do
        last, first = v.last, v.first;
        if k == 1 then
            if is_set(last) then
                OCinSoutput["rft.aulast"] = last;
            end
            if is_set(first) then 
                OCinSoutput["rft.aufirst"] = first;
            end
        end
        if is_set(last) and is_set(first) then
            OCinSoutput["rft.au"] = table.concat{ last, ", ", first };
        elseif is_set(last) then
            OCinSoutput["rft.au"] = last;
        end
    end
    
    OCinSoutput.rft_id = data.URL;
    OCinSoutput.rfr_id = table.concat{ "info:sid/", mw.site.server:match( "[^/]*$" ), ":", data.RawPage };
    OCinSoutput = setmetatable( OCinSoutput, nil );
    
    -- sort with version string always first, and combine.
    table.sort( OCinSoutput );
    table.insert( OCinSoutput, 1, "ctx_ver=" .. ctx_ver );  -- such as "Z39.88-2004"
    return table.concat(OCinSoutput, "&");
end

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

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

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

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

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

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

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

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

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

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

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

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

        Editors, EditorCount = listpeople(control, e);
    else
        EditorCount = 1;
    end
    
    if not is_set(Date) then
        Date = Year;
        if is_set(Date) then
            local Month = A['Month'];
            if is_set(Month) then 
                Date = Month .. " " .. Date;
                local Day = A['Day']
                if is_set(Day) then Date = Day .. " " .. Date end
            end
        end
    end
    
    if inArray(PublicationDate, {Date, Year}) then PublicationDate = ''; end
    if not is_set(Date) and is_set(PublicationDate) then
        Date = PublicationDate;
        PublicationDate = '';
    end

    -- Captures the value for Date prior to adding parens or other textual transformations
    local DateIn = Date;
    
    if  not is_set(URL) and
        not is_set(ChapterURL) and
        not is_set(ArchiveURL) and
        not is_set(ConferenceURL) and
        not is_set(TranscriptURL) then
        
        -- Test if cite web is called without giving a URL
        if ( config.CitationClass == "web" ) then
            table.insert( z.message_tail, { seterror( 'cite_web_url', {}, true ) } );
        end
        
        -- Test if accessdate is given without giving a URL
        if is_set(AccessDate) then
            table.insert( z.message_tail, { seterror( 'accessdate_missing_url', {}, true ) } );
            AccessDate = '';
        end
        
        -- Test if format is given without giving a URL
        if is_set(Format) then
            Format = Format .. seterror( 'format_missing_url' );
        end
    end
    
    -- Test if citation has no title
    if  not is_set(Chapter) and
        not is_set(Title) and
        not is_set(Periodical) and
        not is_set(Conference) and
        not is_set(TransTitle) and
        not is_set(TransChapter) then
        table.insert( z.message_tail, { seterror( 'citation_missing_title', {}, true ) } );
    end
    
    Format = is_set(Format) and " (" .. Format .. ")" or "";
    
    local OriginalURL = URL
    DeadURL = DeadURL:lower();
    if ( ArchiveURL and "" < ArchiveURL ) then
        if ( DeadURL ~= "no" ) then
            URL = ArchiveURL
        end
    end
    
    -- Format chapter / article title
    if is_set(Chapter) and is_set(ChapterLink) then 
        Chapter = "[[" .. ChapterLink .. "|" .. Chapter .. "]]";
    end
    if is_set(Periodical) and is_set(Title) then
        Chapter = wrap( 'italic-title', Chapter );
        TransChapter = wrap( 'trans-italic-title', TransChapter );
    else
        Chapter = wrap( 'quoted-title', Chapter );
        TransChapter = wrap( 'trans-quoted-title', TransChapter );
    end
    
    local TransError = ""
    if is_set(TransChapter) then
        if not is_set(Chapter) then
            TransError = " " .. seterror( 'trans_missing_chapter' );
        else
            TransChapter = " " .. TransChapter;
        end
    end
    
    Chapter = Chapter .. TransChapter;
    
    if is_set(Chapter) then
        if not is_set(ChapterLink) then
            if is_set(ChapterURL) then
                Chapter = externallink( ChapterURL, Chapter ) .. TransError;
                if not is_set(URL) then
                    Chapter = Chapter .. Format;
                    Format = "";
                end
            elseif is_set(URL) then 
                Chapter = externallink( URL, Chapter ) .. TransError .. Format;
                URL = "";
                Format = "";
            else
                Chapter = Chapter .. TransError;
            end            
        elseif is_set(ChapterURL) then
            Chapter = Chapter .. " " .. externallink( ChapterURL, nil, ChapterURLorigin ) .. 
                TransError;
        else
            Chapter = Chapter .. TransError;
        end
        Chapter = Chapter .. sepc .. " " -- with end-space
    elseif is_set(ChapterURL) then
        Chapter = " " .. externallink( ChapterURL, nil, ChapterURLorigin ) .. sepc .. " ";
    end        
    
    -- Format main title.
    if is_set(TitleLink) and is_set(Title) then
        Title = "[[" .. TitleLink .. "|" .. Title .. "]]"
    end
    
    if is_set(Periodical) then
        Title = wrap( 'quoted-title', Title );
        TransTitle = wrap( 'trans-quoted-title', TransTitle );
    elseif inArray(config.CitationClass, {"web","news","pressrelease"}) and
            not is_set(Chapter) then
        Title = wrap( 'quoted-title', Title );
        TransTitle = wrap( 'trans-quoted-title', TransTitle );
    else
        Title = wrap( 'italic-title', Title );
        TransTitle = wrap( 'trans-italic-title', TransTitle );
    end
    
    TransError = "";
    if is_set(TransTitle) then
        if not is_set(Title) then
            TransError = " " .. seterror( 'trans_missing_title' );
        else
            TransTitle = " " .. TransTitle;
        end
    end
    
    Title = Title .. TransTitle;
    
    if is_set(Title) then
        if not is_set(TitleLink) and is_set(URL) then 
            Title = externallink( URL, Title ) .. TransError .. Format       
            URL = "";
            Format = "";
        else
            Title = Title .. TransError;
        end
    end
    
    if is_set(Place) then
        if sepc == '.' then
            Place = " " .. wrap( 'written', Place ) .. sepc .. " ";
        else
            Place = " " .. substitute( cfg.messages['written']:lower(), {Place} ) .. sepc .. " ";
        end
    end
    
    if is_set(Conference) then
        if is_set(ConferenceURL) then
            Conference = externallink( ConferenceURL, Conference );
        end
        Conference = " " .. Conference
    elseif is_set(ConferenceURL) then
        Conference = " " .. externallink( ConferenceURL, nil, ConferenceURLorigin );
    end
    
    if not is_set(Position) then
        local Minutes = A['Minutes'];
        if is_set(Minutes) then
            Position = " " .. Minutes .. " " .. cfg.messages['minutes'];
        else
            local Time = A['Time'];
            if is_set(Time) then
                local TimeCaption = A['TimeCaption']
                if not is_set(TimeCaption) then
                    TimeCaption = cfg.messages['event'];
                    if sepc ~= '.' then
                        TimeCaption = TimeCaption:lower();
                    end
                end
                Position = " " .. TimeCaption .. " " .. Time;
            end
        end
    else
        Position = " " .. Position;
        At = '';
    end
    
    if not is_set(Page) then
        if is_set(Pages) then
            if is_set(Periodical) and
                not inArray(config.CitationClass, {"encyclopaedia","web","book","news"}) then
                Pages = ": " .. Pages;
            elseif tonumber(Pages) ~= nil then
                Pages = sepc .." " .. PPrefix .. Pages;
            else
                Pages = sepc .." " .. PPPrefix .. Pages;
            end
        end
    else
        if is_set(Periodical) and
            not inArray(config.CitationClass, {"encyclopaedia","web","book","news"}) then
            Page = ": " .. Page;
        else
            Page = sepc .." " .. PPrefix .. Page;
        end
    end
    
    At = is_set(At) and (sepc .. " " .. At) or "";
    Others = is_set(Others) and (sepc .. " " .. Others) or "";
    TitleType = is_set(TitleType) and (" (" .. TitleType .. ")") or "";
    TitleNote = is_set(TitleNote) and (sepc .. " " .. TitleNote) or "";
    Language = is_set(Language) and (" " .. wrap( 'language', Language )) or "";
    Edition = is_set(Edition) and (" " .. wrap( 'edition', Edition )) or "";
    Issue = is_set(Issue) and (" (" .. Issue .. ")") or "";
    Series = is_set(Series) and (sepc .. " " .. Series) or "";
    OrigYear = is_set(OrigYear) and (" [" .. OrigYear .. "]") or "";
    Agency = is_set(Agency) and (sepc .. " " .. Agency) or "";
    
    if is_set(Volume) then
        if ( mw.ustring.len(Volume) > 4 )
          then Volume = sepc .." " .. Volume;
          else Volume = " <b>" .. hyphentodash(Volume) .. "</b>";
        end
    end
    
    ------------------------------------ totally unrelated data
    if is_set(Via) then Via = " " .. wrap( 'via', Via ); end
    if is_set(AccessDate) then
        local retrv_text = " " .. cfg.messages['retrieved']
        if (sepc ~= ".") then retrv_text = retrv_text:lower() end
        AccessDate = '<span class="reference-accessdate">' .. sepc
            .. substitute( retrv_text, {AccessDate} ) .. '</span>'
    end
    
    if is_set(SubscriptionRequired) then
        SubscriptionRequired = sepc .. " " .. cfg.messages['subscription'];
    end
    
    if is_set(ID) then ID = sepc .." ".. ID; end
    
    ID_list = buildidlist( ID_list, {DoiBroken = DoiBroken, ASINTLD = ASINTLD, IgnoreISBN = IgnoreISBN} );

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

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

    -- Piece all bits together at last.  Here, all should be non-nil.
    -- We build things this way because it is more efficient in LUA
    -- not to keep reassigning to the same string variable over and over.

    local tcommon
    if inArray(config.CitationClass, {"journal","citation"}) and is_set(Periodical) then
        if is_set(Others) then Others = Others .. sepc .. " " end
        tcommon = safejoin( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series, 
            Language, Edition, Publisher, Agency, Volume, Issue, Position}, sepc );
    else 
        tcommon = safejoin( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language, 
            Volume, Issue, Others, Edition, Publisher, Agency, Position}, sepc );
    end
    
    if #ID_list > 0 then
        ID_list = safejoin( { sepc .. " ",  table.concat( ID_list, sepc .. " " ), ID }, sepc );
    else
        ID_list = ID;
    end
    
    local idcommon = safejoin( { ID_list, URL, Archived, AccessDate, Via, SubscriptionRequired, Lay, Quote }, sepc );
    local text;
    local pgtext = Page .. Pages .. At;
    
    if is_set(Authors) then
        if is_set(Coauthors) then
            Authors = Authors .. A['AuthorSeparator'] .. " " .. Coauthors
        end
        if is_set(Date) then
            Date = " ("..Date..")" .. OrigYear .. sepc .. " "
        elseif string.sub(Authors,-1,-1) == sepc then
            Authors = Authors .. " "
        else
            Authors = Authors .. sepc .. " "
        end
        if is_set(Editors) then
            local in_text = " " .. cfg.messages['in'] .. " "
            if (sepc ~= '.') then in_text = in_text:lower() end
            if (string.sub(Editors,-1,-1) == sepc)
                then Editors = in_text .. Editors .. " "
                else Editors = in_text .. Editors .. sepc .. " "
            end
        end
        text = safejoin( {Authors, Date, Chapter, Place, Editors, tcommon }, sepc );
        text = safejoin( {text, pgtext, idcommon}, sepc );
    elseif is_set(Editors) then
        if is_set(Date) then
            if EditorCount <= 1 then
                Editors = Editors .. ", " .. cfg.messages['editor'];
            else
                Editors = Editors .. ", " .. cfg.messages['editors'];
            end
            Date = " (" .. Date ..")" .. OrigYear .. sepc .. " "
        else
            if EditorCount <= 1 then
                Editors = Editors .. " (" .. cfg.messages['editor'] .. ")" .. sepc .. " "
            else
                Editors = Editors .. " (" .. cfg.messages['editors'] .. ")" .. sepc .. " "
            end
        end
        text = safejoin( {Editors, Date, Chapter, Place, tcommon}, sepc );
        text = safejoin( {text, pgtext, idcommon}, sepc );
    else
        if is_set(Date) then
            if ( string.sub(tcommon,-1,-1) ~= sepc )
              then Date = sepc .." " .. Date .. OrigYear
              else Date = " " .. Date .. OrigYear
            end
        end
        if config.CitationClass=="journal" and is_set(Periodical) then
            text = safejoin( {Chapter, Place, tcommon}, sepc );
            text = safejoin( {text, pgtext, Date, idcommon}, sepc );
        else
            text = safejoin( {Chapter, Place, tcommon, Date}, sepc );
            text = safejoin( {text, pgtext, idcommon}, sepc );
        end
    end
    
    if is_set(PostScript) and PostScript ~= sepc then
        text = safejoin( {text, sepc}, sepc );  --Deals with italics, spaces, etc.
        text = text:sub(1,-2); --Remove final seperator    
    end    
    
    text = safejoin( {text, PostScript}, sepc );

    -- Now enclose the whole thing in a <span/> element
    if not is_set(Year) then
        if is_set(DateIn) then
            Year = selectyear( DateIn );
        elseif is_set(PublicationDate) then
            Year = selectyear( PublicationDate );
        end
    end
    
    local options = {};
    
    if is_set(config.CitationClass) and config.CitationClass ~= "citation" then
        options.class = "citation " .. config.CitationClass;
    else
        options.class = "citation";
    end
    
    if is_set(Ref) and Ref:lower() ~= "none" then
        local id = Ref
        if ( "harv" == Ref ) then
            local names = {} --table of last names & year
            if is_set(Authors) then
                for i,v in ipairs(a) do 
                    names[i] = v.last 
                    if i == 4 then break end
                end
            elseif is_set(Editors) then
                for i,v in ipairs(e) do 
                    names[i] = v.last 
                    if i == 4 then break end                
                end
            end
            names[ #names + 1 ] = Year;
            id = anchorid(names)
        end
        options.id = id;
    end
    
    if string.len(text:gsub("<span[^>/]*>.-</span>", ""):gsub("%b<>","")) <= 2 then
        z.error_categories = {};
        text = seterror('empty_citation');
        z.message_tail = {};
    end
    
    if is_set(options.id) then 
        text = '<span id="' .. mw.uri.anchorEncode(options.id) ..'" class="' .. mw.text.nowiki(options.class) .. '">' .. text .. "</span>";
    else
        text = '<span class="' .. mw.text.nowiki(options.class) .. '">' .. text .. "</span>";
    end        

    local empty_span = '<span style="display:none;">&nbsp;</span>';
    
    -- Note: Using display: none on then COinS span breaks some clients.
    local OCinS = '<span title="' .. OCinSoutput .. '" class="Z3988">' .. empty_span .. '</span>';
    text = text .. OCinS;
    
    if #z.message_tail ~= 0 then
        text = text .. " ";
        for i,v in ipairs( z.message_tail ) do
            if is_set(v[1]) then
                if i == #z.message_tail then
                    text = text .. errorcomment( v[1], v[2] );
                else
                    text = text .. errorcomment( v[1] .. "; ", v[2] );
                end
            end
        end
    end
    
    no_tracking_cats = no_tracking_cats:lower();
    if inArray(no_tracking_cats, {"", "no", "false", "n"}) then
        for _, v in ipairs( z.error_categories ) do
            text = text .. '[[Category:' .. v ..']]';
        end
    end
    
    return text
end

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

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

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

return z