Difference between revisions of "Module:Citation/CS1"

From Timelines
Jump to: navigation, search
(sync with sandbox, merge cite_web_title to bare_url, better style control on translations, category suppression by config, tweak display authors)
(sync to sandbox, mostly code cleaning / organization. Also, ref = none, layurl as alias for laysummary, tweaks to COinS output, and changes to bareurl error.)
Line 5: Line 5:
 
}
 
}
  
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 10:42, 25 April 2013

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

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

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

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

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

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

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

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

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

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

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

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

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

-- Formats a wiki style external link
function externallinkid(options)
    local url_string = options.id;
    if options.encode == true or options.encode == nil then
        url_string = mw.uri.encode( url_string );
    end
    return mw.ustring.format( '[[%s|%s]]%s[%s%s%s %s]',
        options.link, options.label, options.separator or "&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