Module:Citation/CS1

    From Nonbinary Wiki
    Revision as of 15:15, 11 April 2013 by m>Dragons flight (sync to sandbox, add additional redundant parameters checks, check for malformed URLs, removal of wikilinks in COinS data, and various code formatting.)

    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.
    local cfg = mw.loadData( 'Module:Citation/CS1/Configuration' );
     
    -- Contains a list of all recognized parameters
    local whitelist = mw.loadData( 'Module:Citation/CS1/Whitelist' );
    
    -- Checks that parameter name is valid
    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 )
        if hidden then 
            return '<span style="display:none;font-size:100%" class="error citation-comment">' .. content .. '</span>';
        else
            return '<span style="font-size:100%" class="error">' .. content .. '</span>';
        end        
    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, args, raw )
        local error_state = cfg.error_conditions[ error_id ];
    
        if error_state == nil then
            error( cfg.message_list['undefined_error'] );
        end
        
        if error_state.category ~= nil and error_state.category ~= "" then
            table.insert( z.error_categories, error_state.category );
        end
        
        local message = error_state.message;
        if args ~= nil then
            for k, m in ipairs( args ) do
                m = m:gsub( "%%", "%%%%" );
                message = message:gsub( "$" .. k .. "(%D)", m .. "%1" );
            end
        end
    
        message = wikiescape(message) .. " ([[" .. cfg.message_list['help page link'] .. 
            "#" .. error_state.anchor .. "|" ..
            cfg.message_list['help page label'] .. "]])";
    
        z.error_ids[ error_id ] = true;
        if (error_id == 'bare_url_missing_title' or error_id == 'trans_missing_title')
                and z.error_ids['citation_missing_title'] then
            return '', false;
        end
        
        if raw == true then
            return message, error_state.hidden;
        end        
            
        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( '[&\'%[%]{|}]', {    
                ['&'] = '&#38;',    
                ["'"] = '&#39;',    
                ['['] = '&#91;',    
                [']'] = '&#93;',    
                ['{'] = '&#123;',    
                ['|'] = '&#124;',	
                ['}'] = '&#125;' } );
        return text;
    end
    
    -- Formats a wiki style external link
    function externallinkid(options)
        local sep = options.separator or "&nbsp;"
        options.suffix = options.suffix or ""
        local url_string = options.id
        if options.encode == true or options.encode == nil then
            url_string = mw.uri.encode( url_string );
        end
        
        return "[[" .. options.link .. "|" .. options.label .. "]]" .. sep .. "[" .. 
                options.prefix .. url_string .. options.suffix .. " " .. mw.text.nowiki(options.id) .. "]"
    end
    
    -- Formats a wiki style internal link
    function internallinkid(options)
        local sep = options.separator or "&nbsp;"
        options.suffix = options.suffix or ""
        return "[[" .. options.link .. "|" .. options.label .. "]]" .. sep .. "[[" .. 
                options.prefix .. options.id .. options.suffix .. "|" .. mw.text.nowiki(options.id) .. "]]"
    end
    
    -- Format an external link with error checking
    function externallink( URL, label )
        local error_str = "";
        if label == nil or label == "" then
            label = URL;
            error_str = seterror( 'bare_url_missing_title' );
        end
        if not checkurl( URL ) then
            error_str = seterror( 'bad_url' ) .. error_str;
        end
    
        return "[" .. URL .. ' ' .. safeforurl( label ) .. "]" .. error_str;
    end
    
    -- Formats a link to Amazon
    function amazon(id, domain)
        if ( nil == 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 ( inactive ~= nil ) then 
            text = "[[" .. handler.link .. "|" .. handler.label .. "]]:" .. id;
            table.insert( z.error_categories, "Pages with DOIs inactive since " .. selectyear(inactive) );        
            inactive = " (" .. cfg.message_list['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 )
        if url_str:sub(1,2) == "//" then  
            -- Protocol-less URLs
            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
    
    -- Determines whether an ISBN string is valid
    function checkisbn( isbn_str )
        isbn_str = isbn_str:gsub("[- ]", ""):upper();
        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 )
        str = str:gsub( "%[%[[^|%]]*|([^%]]*)%]%]", "%1" );
        str = str:gsub( "%[%[([^%]]*)%]%]", "%1" );    
        return str
    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 str == nil then
            return nil;
        end    
        if 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 str == nil or 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
            return str;
        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.abs(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;
        if sep:sub(-1,-1) ~= " " then sep = sep .. " " end
        local namesep = control.namesep
        local format = control.format
        local maximum = control.maximum
        local lastauthoramp = control.lastauthoramp;
        local text = {}
        local etal = false;
        for i,person in ipairs(people) do
            if (person.last ~= nil or person.last ~= "") then
                local mask = person.mask
                local one
                if ( maximum ~= nil and i == maximum + 1 ) then
                    etal = true;
                    break;
                elseif (mask ~= nil) then
                    local n = tonumber(mask)
                    if (n ~= nil) then
                        one = string.rep("&mdash;",n)
                    else
                        one = mask
                    end
                else
                    one = person.last
                    local first = person.first
                    if (first ~= nil and first ~= '') then 
                        if ( "vanc" == format ) then first = reducetoinitials(first) end
                        one = one .. namesep .. first 
                    end
                    if (person.link ~= nil and person.link ~= "") then one = "[[" .. person.link .. "|" .. one .. "]]" end
                end
                table.insert(text, one)
            end
        end
        local count = #text;
        if count > 1 and lastauthoramp ~= nil and lastauthoramp ~= "" and not etal then
            text[count-1] = text[count-1] .. " & " .. text[count];
            text[count] = nil;
        end    
        local result = table.concat(text, sep) -- construct list
        if etal then 
            local etal_text = cfg.message_list['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" .. mw.uri.anchorEncode( table.concat( options ) );
    end
    
    -- Gets author list from the input arguments
    function extractauthors(args)
        local authors = {};
        local i = 1;
        local last;
        while true do
            if i == 1 then 
                last = selectone( args, {"author" .. i .. "-last", "author-last" .. i, 
                    "last" .. i, "surname" .. i, "Author" .. i, "author" .. i, 
                    "author-last", "last", "surname", "Author", "author", "authors"}, 'redundant_parameters' );
            else
                last = selectone( args, {"author" .. i .. "-last", "author-last" .. i, 
                    "last" .. i, "surname" .. i, "Author" .. i, "author" .. i}, 'redundant_parameters' );
            end
            if ( last and "" < last ) then -- just in case someone passed in an empty parameter
                if i == 1 then 
                    authors[i] = {
                        last = last,
                        first = selectone( args, {"author" .. i .. "-first", "author-first" .. i, 
                            "first" .. i, "given" .. i, "author-first", 
                            "first", "given"}, 'redundant_parameters' ),
                        link = selectone( args, {"author" .. i .. "-link", "author-link" .. i, 
                            "author" .. i .. "link", "authorlink" .. i, "author-link",  
                            "authorlink"}, 'redundant_parameters' ),
                        mask = selectone( args, {"author" .. i .. "-mask", "author-mask" .. i, 
                            "author" .. i .. "mask", "authormask" .. i, "author-mask", 
                            "authormask" }, 'redundant_parameters' )
                    }
                else
                    authors[i] = {
                        last = last,
                        first = selectone( args, {"author" .. i .. "-first", "author-first" .. i, 
                            "first" .. i, "given" .. i}, 'redundant_parameters' ),
                        link = selectone( args, {"author" .. i .. "-link", "author-link" .. i, 
                            "author" .. i .. "link", "authorlink" .. i}, 'redundant_parameters' ),
                        mask = selectone( args, {"author" .. i .. "-mask", "author-mask" .. i, 
                            "author" .. i .. "mask", "authormask" .. i}, 'redundant_parameters' )
                    }
                end            
            else
                break;
            end
            i = i + 1;
        end
        return authors;
    end
    
    -- Gets editor list from the input arguments
    function extracteditors(args)
        local editors = {};
        local i = 1;
        local last;
        
        while true do
            if i == 1 then
                last = selectone( args, {"editor" .. i .. "-last", "editor-last" .. i,
                    "EditorSurname" .. i, "Editor" .. i, "editor" .. i, "editor-last", 
                    "EditorSurname", "Editor", "editor", "editors"}, 'redundant_parameters' );
            else
                last = selectone( args, {"editor" .. i .. "-last", "editor-last" .. i,
                    "EditorSurname" .. i, "Editor" .. i, "editor" .. i}, 'redundant_parameters' );
            end        
            if ( last and "" < last ) then -- just in case someone passed in an empty parameter
                if i == 1 then
                    editors[i] = {
                        last = last,
                        first = selectone( args, {"editor" .. i .. "-first", 
                            "editor-first" .. i, "EditorGiven" .. i, "editor-first", 
                            "EditorGiven"}, 'redundant_parameters' ),
                        link = selectone( args, {"editor" .. i .. "-link", "editor-link" .. i, 
                            "editor" .. i .. "link", "editorlink" .. i, "editor-link", 
                            "editorlink"}, 'redundant_parameters' ),
                        mask = selectone( args, {"editor" .. i .. "-mask", "editor-mask" .. i, 
                            "editor" .. i .. "mask", "editormask" .. i, "editor-mask",  
                            "editormask"}, 'redundant_parameters' )
                    }                
                else
                    editors[i] = {
                        last = last,
                        first = selectone( args, {"editor" .. i .. "-first", 
                            "editor-first" .. i, "EditorGiven" .. i}, 'redundant_parameters' ),
                        link = selectone( args, {"editor" .. i .. "-link", "editor-link" .. i, 
                            "editor" .. i .. "link", "editorlink" .. i}, 'redundant_parameters' ),
                        mask = selectone( args, {"editor" .. i .. "-mask", "editor-mask" .. i, 
                            "editor" .. i .. "mask", "editormask" .. i}, 'redundant_parameters' )
                    }
                end
            else
                break;
            end
            i = i + 1;
        end
        return editors;
    end
    
    -- Populates ID table from arguments using configuration settings
    function extractids( args )
        local id_list = {};
        
        for k, v in pairs( cfg.id_handlers ) do    
            id_list[k] = selectone( args, v.parameters, 'redundant_parameters' );
        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 handler;
        local new_list = {};
        
        for k, v in pairs( id_list ) do
            handler = {};
            
            --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        
                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
                if 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 (IgnoreISBN == nil or IgnoreISBN == "") then 
                        ISBN = ISBN .. seterror( 'bad_isbn' );
                    end
                    table.insert( new_list, {handler.label, ISBN } );                
                else
                    error( cfg.message_list['unknown_manual_ID'] );
                end            
            else
                error( cfg.message_list['unknown_ID_mode'] );
            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 )
        local value = nil;
        local selected = '';
        local error_list = {};
        
        for _, v in ipairs( possible ) do
            if args[v] ~= nil then
                if value ~= nil 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 .. ", " end
                error_str = error_str .. "<code>|" .. k .. "=</code>";
            end
            if #error_list > 1 then
                error_str = error_str .. ", and ";
            else
                error_str = error_str .. " and ";
            end
            error_str = error_str .. "<code>|" .. selected .. "=</code>";
            table.insert( z.message_tail, { seterror( error_condition, {error_str}, true ) } );
        end
                
        return value, selected;
    end
    
    --[[
    This is the main function foing the majority of the citation
    formatting.
    ]]
    function citation0( config, args)
        -- Load Input Parameters
    
        local i 
        local PPrefix = config.PPrefix or "p.&nbsp;"
        local PPPrefix = config.PPPrefix or "pp.&nbsp;"
        if ( nil ~= args.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 = args.authors
        local a = extractauthors( args );
    
        local Coauthors = selectone( args, {'coauthors', 'coauthor' }, 'redundant_parameters' );
        local Others = args.others 
        local Editors = args.editors
        local e = extracteditors( args );
    
        local Year = args.year 
        local PublicationDate = selectone( args, {'publicationdate', 'publication-date' }, 'redundant_parameters' );
        local OrigYear = args.origyear
        local Date = args.date
        local LayDate = args.laydate
        ------------------------------------------------- Get title data
        local Title = args.title or args.encyclopaedia or args.encyclopedia or args.dictionary
        local BookTitle = args.booktitle
        local Conference = args.conference
        local TransTitle = selectone( args, {'trans-title', 'trans_title' }, 'redundant_parameters' );
        local TitleNote = args.department
        local TitleLink = selectone( args, {'titlelink', 'episodelink' }, 'redundant_parameters' );
        local Chapter = selectone( args, {'chapter', 'contribution', 'entry' }, 'redundant_parameters' );
        local ChapterLink = args.chapterlink
        local TransChapter = selectone( args, {'trans-chapter', 'trans_chapter' }, 'redundant_parameters' );
        local TitleType = args.type
        local ArchiveURL = selectone( args, {'archive-url', 'archiveurl' }, 'redundant_parameters' );
        local URL = selectone( args, {'url', 'URL'}, 'redundant_parameters' );
        local ChapterURL = selectone( args, {'chapter-url', 'chapterurl', 'contribution-url', 'contributionurl' }, 'redundant_parameters' );
        local ConferenceURL = selectone( args, {'conference-url', 'conferenceurl' }, 'redundant_parameters' );
        local Periodical = selectone( args, {'journal', 'newspaper', 'magazine', 'work', 'website', 
            'periodical', 'encyclopedia', 'encyclopaedia'}, 'redundant_parameters' );
                
        if ( config.CitationClass == "encyclopaedia" ) then
            if ( args.article and args.article ~= "") then
                if ( Title and Title ~= "") then Periodical = Title end
                Chapter = args.article
                TransChapter = TransTitle
                Title = nil          
                TransTitle = nil
            elseif ( Chapter == nil or Chapter == '' ) then
                if Title ~= args.encyclopedia then 
                    Chapter = Title
                    TransChapter = TransTitle
                    Title = nil 
                    TransTitle = nil
                end
            end
            if ( Periodical and Periodical ~= "") then
                if Periodical == Title or Periodical == Chapter then Periodical = nil end
            end
        end
        local Series = selectone( args, {'series', 'version'}, 'redundant_parameters' );
        local Volume = args.volume
        local Issue = selectone( args, {'issue', 'number'}, 'redundant_parameters' );
        local Position = nil
        local Page, Pages, At, page_type;
        
        Page, page_type = selectone( args, {'p', 'page', 'pp', 'pages', 'at'}, 
            'extra_pages' );
        if page_type == 'pp' or page_type == 'pages' then
            Pages = hyphentodash( Page );
            Page = nil;
        elseif page_type == 'at' then
            At = Page;
            Page = nil;
        end
                    
        local Edition = args.edition
        local PublicationPlace = selectone( args, {'publication-place', 'publicationplace' }, 'redundant_parameters' ); 
        local Place = selectone( args, {'place', 'location'}, 'redundant_parameters' );
        if PublicationPlace == nil and Place ~= nil then 
            PublicationPlace = Place;
        end
        if PublicationPlace == Place then Place = nil end
        
        local PublisherName = args.publisher
        local SubscriptionRequired = args.subscription
        local Via = args.via
        local AccessDate = selectone( args, {'access-date', 'accessdate' }, 'redundant_parameters' );
        local ArchiveDate = selectone( args, {'archive-date', 'archivedate' }, 'redundant_parameters' );
        local Agency = args.agency
        local DeadURL = args.deadurl or "yes"           -- Only used if ArchiveURL is present.
        local Language = selectone( args, {'language', 'in'}, 'redundant_parameters' );
        local Format = args.format
        local Ref = selectone( args, {'ref', 'Ref'}, 'redundant_parameters' );
    
        local DoiBroken = selectone( args, {'doi_inactivedate', 'doi_brokendate', 'DoiBroken'}, 'redundant_parameters' );
        local ID = selectone( args, {'id', 'ID', 'docket'}, 'redundant_parameters' );
        local ASINTLD = selectone( args, {'ASIN-TLD', 'asin-tld'}, 'redundant_parameters' );
        local IgnoreISBN = selectone( args, {'ignore-isbn-error', 'ignoreisbnerror'}, 'redundant_parameters' );
    
        local ID_list = extractids( args );
        
        local Quote = selectone( args, {'quote', 'quotation'}, 'redundant_parameters' );
        local PostScript = args.postscript or "."
        local LaySummary = args.laysummary
        local LaySource = args.laysource
        local Transcript = args.transcript
        local TranscriptURL = selectone( args, {'transcript-url', 'transcripturl'}, 'redundant_parameters' );
        local sepc = args.separator or "."
        local LastAuthorAmp = args.lastauthoramp
        local no_tracking_cats = selectone( args, {"template doc demo", 'nocat', 
            'notracking', "no-tracking"}, 'redundant_parameters' ) or "";
    
        if ( config.CitationClass == "journal" ) then        
            if (URL == nil or URL == "") then
                if (ID_list['PMC'] ~= nil) then 
                    local Embargo = args.embargo or args.Embargo;
                    if Embargo ~= nil 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'];
                        end
                    else
                        URL = "http://www.ncbi.nlm.nih.gov/pmc/articles/PMC" .. ID_list['PMC'];           
                    end
                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 ( BookTitle ) then
            Chapter = Title
            ChapterLink = TitleLink
            TransChapter = TransTitle
            Title = BookTitle
            TitleLink = nil
            TransTitle = nil
        end
        -- Account for the oddity that is {{cite episode}}, before generation of COinS data.
        if config.CitationClass == "episode" then
            local AirDate = args.airdate
            local SeriesLink = args.serieslink
            local Season = args.season
            local SeriesNumber = args.seriesnumber or args.seriesno
            local Network = args.network
            local Station = args.station
            local s = {}
            if Issue ~= nil then table.insert(s, cfg.message_list["episode"] .. " " .. Issue) Issue = nil end
            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
            local n = {}
            if Network ~= nil then table.insert(n, Network) end
            if Station ~= nil then table.insert(n, Station) end
            Date = Date or AirDate
            Chapter = Title
            ChapterLink = TitleLink
            TransChapter = TransTitle
            Title = Series
            TitleLink = SeriesLink
            TransTitle = nil
            local Sep = args["series-separator"] or args["separator"] or ". "
            Series = table.concat(s, Sep)
            ID = table.concat(n, Sep)
        end
        
        -- 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 string.sub( cfg.id_handlers[k].COinS or "info", 1, 4 ) ~= 'info' then
                OCinSdata[ cfg.id_handlers[k].COinS ] = v;
            end
        end
        
        OCinSdata.rft_id = URL or ChapterURL
    
        local last, first;
        local OCinSauthors = {};
        for k, v in ipairs( a ) do
            last = v.last;
            first = v.first;
            if k == 1 then
                if last ~= nil then
                    OCinSdata["rft.aulast"] = last;
                end
                if first ~= nil then 
                    OCinSdata["rft.aufirst"] = first;
                end
            end
            if last ~= nil and first ~= nil then
                table.insert( OCinSauthors, last .. (args.NameSep or ", ") .. first );
        	elseif last ~= nil then
                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"
        for name,value in pairs(OCinSdata) do
            OCinStitle = OCinStitle .. "&" .. name .. "=" .. mw.uri.encode( removewikilink(value) );
        end
        for _, value in ipairs(OCinSauthors) do
            OCinStitle = OCinStitle .. "&rft.au=" .. mw.uri.encode( removewikilink(value) );
        end
        for name,value in pairs(OCinSids) do
            OCinStitle = OCinStitle .. "&rft_id=" .. mw.uri.encode(name .. "/" .. removewikilink(value) );
        end
        
        local this_page = mw.title.getCurrentTitle();
        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
    
        -- 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 ( Authors == nil ) then 
            local Maximum = tonumber( (selectone( args, {"display-authors", "displayauthors"}, 'redundant_parameters' )) );
            
            -- Preserve old-style implicit et al.
            if Maximum == nil and #a == 9 then 
                Maximum = 8;
                table.insert( z.message_tail, { seterror('implict_etal_author', {}, true ) } );
            elseif Maximum == nil then
                Maximum = #a + 1;
            end
                
            local control = { 
                sep = (args["author-separator"] or ";") .. " ",
                namesep = (args["author-name-separator"] or args["name-separator"] or ",") .. " ",
                format = selectone( args, {"author-format", "authorformat" }, 'redundant_parameters' ),
                maximum = Maximum,
                lastauthoramp = LastAuthorAmp
            }
            
            -- If the coauthor field is also used, prevent ampersand and et al. formatting.
            if Coauthors ~= nil and Coauthors ~= "" then
                control.lastauthoramp = nil;
                control.maximum = #a + 1;
            end
                    
            Authors = listpeople(control, a) 
        end
        local EditorCount
        if ( Editors == nil ) then 
            local Maximum = tonumber( (selectone( args, {"display-editors", "displayeditors"}, 'redundant_parameters' )) );
    
            -- Preserve old-style implicit et al.
            if Maximum == nil and #e == 4 then 
                Maximum = 3;
                table.insert( z.message_tail, { seterror('implict_etal_editor', {}, true) } );
            elseif Maximum == nil then
                Maximum = #e + 1;
            end
    
            local control = { 
                sep = (args["editor-separator"] or ";") .. " ",
                namesep = (args["editor-name-separator"] or args["name-separator"] or ",") .. " ",
                format = selectone( args, {"editor-format", "editorformat" }, 'redundant_parameters' ),
                maximum = Maximum,
                lastauthoramp = LastAuthorAmp
                }
    
            Editors, EditorCount = listpeople(control, e) 
        else
            EditorCount = 1;
        end
        if ( Date == nil or Date == "") then
    --   there's something hinky with how this adds dashes to perfectly-good free-standing years
    --[[        Date = Year
            if ( Date ~= nil ) then
                local Month = args.month
                if ( Month == nil ) then 
                    local Began = args.began
                    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 = args.month
                if ( Month ~= nil and Month ~= "") then 
                    Date = Month .. " " .. Date 
                    local Day = args.day
                    if ( Day ~= nil ) then Date = Day .. " " .. Date end
                    else Month = ""
                end
                else Date = ""
            end
        end
        if ( PublicationDate == Date or PublicationDate == Year ) then PublicationDate = nil end
        if( (Date == nil or Date == "") and PublicationDate ~= nil ) then 
            Date = PublicationDate;
            PublicationDate = nil;
        end    
    
        -- Captures the value for Date prior to adding parens or other textual transformations
        local DateIn = Date
        
        if ( URL == nil or URL == '' ) and
                ( ChapterURL == nil or ChapterURL == '' ) and
                ( ArchiveURL == nil or ArchiveURL == '' ) and                
                ( ConferenceURL == nil or ConferenceURL == '' ) and                
                ( TranscriptURL == nil or 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 ( AccessDate ~= nil and AccessDate ~= '' ) then
                table.insert( z.message_tail, { seterror( 'accessdate_missing_url', {}, true ) } );
                AccessDate = nil;
            end      
        
            -- Test if format is given without giving a URL
            if ( Format ~= nil and Format ~= '' ) then
                Format = Format .. seterror( 'format_missing_url' );
            end        
        end    
    
        -- Test if citation has no title
        if ( Chapter == nil or Chapter == "" ) and 
                ( Title == nil or Title == "" ) and
                ( Periodical == nil or Periodical == "" ) and
                ( Conference == nil or Conference == "" ) and 
                ( TransTitle == nil or TransTitle == "" ) and
                ( TransChapter == nil or TransChapter == "" ) then
            table.insert( z.message_tail, { seterror( 'citation_missing_title', {}, true ) } );
        end
    
        if ( Format ~= nil and Format ~="" ) then
            Format = " (" .. Format .. ")" else Format = "" end
        
        local OriginalURL = URL
        DeadURL = DeadURL:lower();
        if ( ArchiveURL and "" < ArchiveURL ) then
            if ( DeadURL ~= "no" ) then
                URL = ArchiveURL
            end
        end
    
        if ( TransTitle and "" < TransTitle ) then TransTitle = " [" .. TransTitle .. "&#93;" else TransTitle = "" end
        if ( TransChapter and "" < TransChapter ) then TransChapter = " [" .. TransChapter .. "&#93;" else TransChapter = "" end
            
        -- Format chapter / article title
        if ( Chapter ~= nil and Chapter ~= "" ) then
            if ( ChapterLink and "" < ChapterLink ) then Chapter = "[[" .. ChapterLink .. "|" .. Chapter .. "]]" end
            if ( Periodical and "" < Periodical ) and (Title ~= nil and Title ~= "" )
            then
                Chapter = "''" .. safeforitalics(Chapter) .. "''"
            else
                Chapter = "\"" .. Chapter .. "\""
            end
        else
            Chapter = "";
        end
        
        local TransError = ""
        if TransChapter ~= "" and Chapter == "" then
            TransError = " " .. seterror( 'trans_missing_chapter' );
        end
        Chapter = Chapter .. TransChapter
        if Chapter ~= "" then
            if ( ChapterLink == nil ) then
                if ( ChapterURL and "" < ChapterURL ) then                
                    Chapter = externallink( ChapterURL, Chapter ) .. TransError;
                    if URL == nil or URL == "" then
                        Chapter = Chapter .. Format;
                        Format = "";
                    end
                elseif ( URL and "" < URL ) then 
                    Chapter = externallink( URL, Chapter ) .. TransError .. Format;
                    URL = nil
                    Format = ""
                else
                    Chapter = Chapter .. TransError;
                end            
            elseif ChapterURL ~= nil and ChapterURL ~= "" then
                Chapter = Chapter .. " " .. externallink( ChapterURL ) .. 
                    TransError;
            else
                Chapter = Chapter .. TransError;
            end
            Chapter = Chapter .. sepc .. " " -- with end-space
        elseif ChapterURL ~= nil and ChapterURL ~= "" then
            Chapter = " " .. externallink( ChapterURL ) .. sepc .. " ";
        end        
        
        -- Format main title.
        if ( Title and "" < Title ) then
            if ( TitleLink and "" < TitleLink ) then
                Title = "[[" .. TitleLink .. "|" .. Title .. "]]" end
            if ( Periodical and "" < Periodical ) then
                Title = "\"" .. Title .. "\""
            elseif ( config.CitationClass == "web"
                    or config.CitationClass == "news" 
                    or config.CitationClass == "pressrelease" ) and 
                    Chapter == "" then
                Title = "\"" .. Title .. "\""
            else
                Title = "''" .. safeforitalics(Title) .. "''"
            end
        else
            Title = "";
        end    
        
        local TransError = "";
        if TransTitle ~= "" and Title == "" then
            TransError = " " .. seterror( 'trans_missing_title' );
        end
        Title = Title .. TransTitle
        if Title ~= "" then
            if ( TitleLink == nil and URL and "" < URL ) then 
                Title = externallink( URL, Title ) .. TransError .. Format       
                URL = nil
                Format = ''
            else
                Title = Title .. TransError;
            end
        end
    
        if ( Place ~= nil and Place ~= "" ) then
            if sepc == '.' then
                Place = " " .. cfg.message_list['written'] .. " " .. Place .. sepc .. " ";
            else
                Place = " " .. cfg.message_list['written']:lower() .. " " .. Place .. sepc .. " ";
            end            
        else
            Place = "";
        end
        
        if ( Conference ~= nil and Conference ~="" ) then
            if ( ConferenceURL ~= nil ) then
                Conference = externallink( ConferenceURL, Conference );
            end
            Conference = " " .. Conference
        elseif ConferenceURL ~= nil and ConferenceURL ~= "" then
            Conference = " " .. externallink( ConferenceURL );
        else
            Conference = "" 
        end
        if ( nil ~= Position or nil ~= Page or nil ~= Pages ) then At = nil end
        if ( nil == Position and "" ~= Position ) then
            local Minutes = args.minutes
            if ( nil ~= Minutes ) then
                Position = " " .. Minutes .. " " .. cfg.message_list['minutes'];
            else
                local Time = args.time
                if ( nil ~= Time ) then
                    local TimeCaption = args.timecaption 
                    if TimeCaption == nil then
                        TimeCaption = cfg.message_list['event'];
                        if sepc ~= '.' then
                            TimeCaption = TimeCaption:lower();
                        end
                    end                
                    Position = " " .. TimeCaption .. " " .. Time
                else
                    Position = ""
                end
            end
        else
            Position = " " .. Position
        end
        if ( nil == Page or "" == Page ) then 
            Page = "" 
            if ( nil == Pages or "" == Pages) then 
                Pages = ""
            elseif ( Periodical ~= nil and Periodical ~= "" and
                     config.CitationClass ~= "encyclopaedia" and
                     config.CitationClass ~= "web" and
                     config.CitationClass ~= "book" and
                     config.CitationClass ~= "news") then
                Pages = ": " .. Pages
            else
                if ( tonumber(Pages) ~= nil ) then
                  Pages = sepc .." " .. PPrefix .. Pages
                else Pages = sepc .." " .. PPPrefix .. Pages
                end
            end
        else
            Pages = ""
            if ( Periodical ~= nil and Periodical ~= "" and
                 config.CitationClass ~= "encyclopaedia" and
                 config.CitationClass ~= "web" and
                 config.CitationClass ~= "book" and
                 config.CitationClass ~= "news") then
                Page = ": " .. Page
            else
                Page = sepc .." " .. PPrefix .. Page
            end
        end
        if ( At ~= nil and At ~="") then At = sepc .. " " .. At
        else At = "" end
        if ( Coauthors == nil ) then Coauthors = "" end
        if ( Others ~= nil and Others ~="" ) then
            Others = sepc .. " " .. Others else Others = "" end
        if ( TitleType ~= nil and TitleType ~="" ) then
            TitleType = " (" .. TitleType .. ")" else TitleType = "" end
        if ( TitleNote ~= nil and TitleNote ~="" ) then
            TitleNote = sepc .. " " .. TitleNote else TitleNote = "" end
        if ( Language ~= nil and Language ~="" ) then
            Language = " (" .. cfg.message_list['in'] .. " " .. Language .. ")" else Language = "" end
        if ( Edition ~= nil and Edition ~="" ) then
            Edition = " (" .. Edition .. " " .. cfg.message_list['edition'] .. ")" else Edition = "" end
        if ( Volume ~= nil and Volume ~="" )
        then
            if ( mw.ustring.len(Volume) > 4 )
              then Volume = sepc .." " .. Volume
              else Volume = " <b>" .. hyphentodash(Volume) .. "</b>"
            end
        else Volume = "" 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
        if ( Date ~= nil ) then Date = Date else Date = "" end
        if ( Via ~= nil and Via ~="" ) then
            Via = " &mdash; " .. cfg.message_list['via'] .. " " .. Via else Via = "" end
        if ( AccessDate ~= nil and AccessDate ~="" )
        then local retrv_text = " " .. cfg.message_list['retrieved'] .. " "
             if (sepc ~= ".") then retrv_text = retrv_text:lower() end
             AccessDate = '<span class="reference-accessdate">' .. sepc
                 .. retrv_text .. AccessDate .. '</span>'
        else AccessDate = "" end
        if ( SubscriptionRequired ~= nil and
             SubscriptionRequired ~= "" ) then
            SubscriptionRequired = sepc .. " " .. cfg.message_list['subscription'];
        else
            SubscriptionRequired = ""
        end
        if ( ID ~= nil and ID ~="") then ID = sepc .." ".. ID else ID="" end
    
        ID_list = buildidlist( ID_list, {DoiBroken = DoiBroken, ASINTLD = ASINTLD, IgnoreISBN = IgnoreISBN} );
    
        if ( URL ~= nil and URL ~="") then
            URL = " " .. externallink( URL, URL );
            local error_text = seterror( 'bare_url_missing_title' );
            if config.CitationClass == "web" then
                URL = URL .. " " .. seterror( 'cite_web_title' );
            else
                URL = URL .. error_text;
            end       
        else
            URL = ""
        end
    
        if ( Quote and Quote ~="" ) then 
            if Quote:sub(1,1) == '"' and Quote:sub(-1,-1) == '"' then
                Quote = Quote:sub(2,-2);
            end
            
            Quote = sepc .." \"" .. Quote .. "\"" 
            PostScript = ""
        else 
            if ( PostScript == nil) then PostScript = "" end
            Quote = "" 
        end
        
        local Archived
        if ( nil ~= ArchiveURL and "" ~= ArchiveURL ) then
            if ( ArchiveDate ~= nil and ArchiveDate ~="" ) then
                ArchiveDate = " " .. ArchiveDate
            else 
                ArchiveDate = " " .. seterror('archive_missing_date') .. " "
            end
            local arch_text = cfg.message_list['archived'];
            if (sepc ~= ".") then arch_text = arch_text:lower() end
            if ( "no" == DeadURL ) then
                Archived = sepc .. " " .. externallink( ArchiveURL, arch_text ) .. " " .. 
                    cfg.message_list['from'] .. " " .. cfg.message_list['original'] .. " " .. 
                    cfg.message_list['on'] .. ArchiveDate
                if OriginalURL == nil or OriginalUrl == '' then
                    Archived = Archived .. " " .. seterror('archive_missing_url_not_dead');                               
                end
            else
                if OriginalURL ~= nil and OriginalURL ~= '' then
                    Archived = sepc .. " " .. arch_text .. " " .. cfg.message_list['from'] .. 
                        " " .. externallink( OriginalURL, cfg.message_list['original'] ) .. " "
                        .. cfg.message_list['on'] .. ArchiveDate
                else
                    if config.CitationClass ~= 'web' then 
                        Archived = sepc .. " " .. arch_text .. " " .. cfg.message_list['from'] .. " " .. 
                        seterror('archive_missing_url') .. " " .. cfg.message_list['on'] .. ArchiveDate
                    else
                        Archived = sepc .. " " .. arch_text .. " " .. cfg.message_list['from'] .. 
                            " " .. cfg.message_list['original'] .. " " .. 
                            cfg.message_list['on'] .. ArchiveDate
                    end
                end                
            end
        else
            Archived = ""
        end
        local Lay
        if ( nil ~= LaySummary and "" ~= LaySummary ) then
            if ( LayDate ~= nil ) then LayDate = " (" .. LayDate .. ")" else LayDate = "" end
            if ( LaySource ~= nil ) then 
                LaySource = " &ndash; ''" .. safeforitalics(LaySource) .. "''" 
            else 
                LaySource = "" 
            end
            if sepc == '.' then
                Lay = sepc .. " [" .. LaySummary .. " " .. cfg.message_list['lay summary'] .. "]" .. LaySource .. LayDate
            else
                Lay = sepc .. " [" .. LaySummary .. " " .. cfg.message_list['lay summary']:lower() .. "]" .. LaySource .. LayDate
            end            
        else
            Lay = ""
        end
        if ( nil ~= Transcript and "" ~= Transcript ) then
            if ( TranscriptURL ~= nil ) then Transcript = externallink( TranscriptURL, Transcript ) end
        elseif TranscriptURL ~= nil and TranscriptURL ~= "" then
            Transcript = externallink( TranscriptURL )     
        else
            Transcript = ""
        end
        local Publisher = ""
        if ( Periodical and Periodical ~= "" and
             config.CitationClass ~= "encyclopaedia" and
             config.CitationClass ~= "web" and
             config.CitationClass ~= "pressrelease" ) then
            if ( PublisherName ~= nil and PublisherName ~="" ) then
                if (PublicationPlace ~= nil and PublicationPlace ~= '') then
                    Publisher = PublicationPlace .. ": " .. PublisherName;
                else
                    Publisher = PublisherName;  
                end            
            elseif (PublicationPlace ~= nil and PublicationPlace ~= '') then 
                Publisher= PublicationPlace;
            else 
                Publisher = "";
            end
            if ( PublicationDate and PublicationDate ~="" ) then
                if Publisher ~= '' then
                    Publisher = Publisher .. ", " .. cfg.message_list['published'] .. " " .. PublicationDate;
                else
                    Publisher = PublicationDate;
                end
            end
            if Publisher ~= "" then
                Publisher = " (" .. Publisher .. ")";
            end
        else
            if ( PublicationDate and PublicationDate ~="" ) then
                PublicationDate = " (" .. cfg.message_list['published'] .. " " .. PublicationDate .. ")"
            else 
                PublicationDate = ""
            end
            if ( PublisherName ~= nil and PublisherName ~="" ) then
                if (PublicationPlace ~= nil and PublicationPlace ~= '') then
                    Publisher = sepc .. " " .. PublicationPlace .. ": " .. PublisherName .. PublicationDate;
                else
                    Publisher = sepc .. " " .. PublisherName .. PublicationDate;  
                end            
            elseif (PublicationPlace ~= nil and 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 ( Periodical ~= nil and Periodical ~="" ) then 
            if ( Title and Title ~= "" ) or ( TitleNote and TitleNote ~= "" ) then 
                Periodical = sepc .. " ''" .. safeforitalics(Periodical) .. "''"
            else 
                Periodical = "''" .. safeforitalics(Periodical) .. "''"
            end
        else Periodical = "" 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 ( ( (config.CitationClass == "journal") or (config.CitationClass == "citation") )  and
             Periodical ~= "" ) then
            if (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 ( "" ~= Authors ) then
            if (Coauthors ~= "") 
              then Authors = Authors .. "; " .. Coauthors
            end
            if ( "" ~= Date )
              then Date = " ("..Date..")" .. OrigYear .. sepc .. " "
              else
                if ( string.sub(Authors,-1,-1) == sepc) --check end character
                  then Authors = Authors .. " "
                  else Authors = Authors .. sepc .. " "
                end
            end
            if ( "" ~= Editors) then
                local in_text = " in "
                if (sepc == '.') then in_text = " In " 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 ( "" ~= Editors) then
            if ( "" ~= Date ) then
                if EditorCount <= 1 then
                    Editors = Editors .. ", " .. cfg.message_list['editor'];
                else
                    Editors = Editors .. ", " .. cfg.message_list['editors'];
                end
                Date = " (" .. Date ..")" .. OrigYear .. sepc .. " "
            else
                if EditorCount <= 1 then
                    Editors = Editors .. " (" .. cfg.message_list['editor'] .. ")" .. sepc .. " "
                else
                    Editors = Editors .. " (" .. cfg.message_list['editors'] .. ")" .. sepc .. " "
                end
            end
            text = safejoin( {Editors, Date, Chapter, Place, tcommon}, sepc );
            text = safejoin( {text, pgtext, idcommon}, sepc );
        else
            if ( "" ~= Date ) then
                if ( string.sub(tcommon,-1,-1) ~= sepc )
                  then Date = sepc .." " .. Date .. OrigYear
                  else Date = " " .. Date .. OrigYear
                end
            end -- endif ""~=Date
            if ( config.CitationClass=="journal" and 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 PostScript ~= '' and PostScript ~= nil 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 ( Year == nil ) then
            if ( DateIn ~= nil and DateIn ~= "" ) then 
                Year = selectyear( DateIn )
            elseif( PublicationDate ~= nil and PublicationDate ~= "" ) then
                Year = selectyear( PublicationDate )
            else
                Year = ""
            end
        end
        local classname = "citation"
        if ( config.CitationClass ~= "citation" )
           then classname = "citation " .. (config.CitationClass or "") end
        local options = { class=classname }
        if ( Ref ~= nil ) then 
            local id = Ref
            if ( "harv" == Ref ) then
                local names = {} --table of last names & year
                if ( "" ~= Authors ) then
                    for i,v in ipairs(a) do 
                        names[i] = v.last 
                        if i == 4 then break end
                    end
                elseif ( "" ~= 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 options.id ~= nil then 
            text = '<span id="' .. wikiescape(options.id) ..'" class="' .. wikiescape(options.class) .. '">' .. text .. "</span>";
        else
            text = '<span class="' .. wikiescape(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="' .. wikiescape(OCinStitle) .. '" class="Z3988">' .. empty_span .. '</span>';
        text = text .. OCinS;
        
        if #z.message_tail ~= 0 then
            for i,v in ipairs( z.message_tail ) do
                if v[1] ~= nil and 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
        
        if no_tracking_cats == '' 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;
        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 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;
            end        
        end    
        
        return citation0( config, args)
    end
    
    return z
    ---------------------------------------------------------------------
    --NOTES
    --
    -- NOTE A1: This Lua module was originally designed to handle a mix
    --      of citation styles, crossing Vancouver style with Wikipedia's
    --      local Citation Style 1 (CS1) from {Template:Citation/core}.
    --      However, the conflicting positions of parameters, scattered
    --      in twisted locations across this module, led to a separate
    --      variation just to untangle the CS1 format of citations.
    --
    -- NOTE D2: The placement of dots and other separators between the
    --      displayed parameters has been a continual headache, to keep
    --      coordinated with the data in parentheses "(data)". There
    --      has been a need to pre-check for the existence of related
    --      options, to keep from putting double-dots ".." in some cases.
    --      In particular, the omission of the "title=" parameter has led
    --      to several cases of a spurious dot ". ." because the original
    --      design had treated the title as a mandatory parameter.
    --
    ------------------------------------------------------------------------
    --HISTORY:
    --18Oct2012 Fixed lead-space in Chapter by omitting " ".
    --18Oct2012 Fixed lead-space in Chapter/Title as end " " of Authors/Date/...
    --19Oct2012 Put HISTORY comments to log major changes (not typos).
    --19Oct2012 Fixed extra dot ".." in Title by omitting at end of "tcommon=...".
    --19Oct2012 For pages, put &nbsp in "p.&nbsp;" etc.
    --19Oct2012 Enhanced "pages=" to detect lone page as "p." else "pp." prefix.
    --19Oct2012 Fixed to show "." after Periodical name (work, newspaper...).
    --19Oct2012 Fixed web-link to have spaces "[...  Archived] from the original".
    --19Oct2012 Fixed to show ";" between authors & coauthors.
    --19Oct2012 Fixed to omit extra "." after coauthors.
    --20Oct2012 Fixed COinS data to not urlencode all, as "ctx_ver=Z39.88-2004"
    --20Oct2012 Fixed COinS to not end as "&" but use lead "&rft...=" form.
    --20Oct2012 Fixed COinS to not url.encode page's "rfr_id=..." pagename.
    --20Oct2012 Fixed COinS data when "web" to default to rft.genre "book".
    --05Nov2012 Add a span wrapper even when there is no Ref parameter
    --15Feb2013 Added Agency for "agency=xx".
    --19Feb2013 Put NOTES comments to explain module operation.
    --19Feb2013 Copied as Module:Citation/CS1 to alter to match wp:CS1 form.
    --19Feb2013 Changed OrigYear to use [__] for CS1 style.
    --19Feb2013 Fixed to not show duplicate Publisher/Agency.
    --19Feb2013 Moved page-number parameters to after final date.
    --19Feb2013 Fixed to not put double-dots after title again.
    --20Feb2013 Changed to omit dot "." if already ends with dot.
    --20Feb2013 If class "journal" shows Publisher after Periodical/Series.
    --20Feb2013 Shifted Format to after Language, and Others after Volume.
    --20Feb2013 Set AccessDate + <span class="reference-accessdate">
    --20Feb2013 Fixed url when deadurl=no.
    --20Feb2013 Added sepc for separator character between parameters.
    --20Feb2013 Put "OCLC" for "Online Computer Library Center".
    --20Feb2013 Fix empty "authorlink=" as person.link ~= "".
    --20Feb2013 Added space after AuthorSep & AuthorNameSep.
    --21Feb2013 Added args.contributor (was missing parameter).
    --21Feb2013 Fixed EditorSep (was misspelled "EdithorSep").
    --21Feb2013 Set OCinSdata.rft_val_fmt = "info:ofi/fmt:kev:mtx:book"
    --21Feb2013 Checked to omit blank codes (asin= | doi= etc.).
    --21Feb2013 Set enddot to end line if not config.CitationClass "citation".
    --21Feb2013 Fixed to show "issn=x" as the ISSN code.
    --21Feb2013 Fixed to show "id=x" after Zbl code.
    --21Feb2013 Changed to omit double-dot before date when already dot.
    --21Feb2013 Order config.CitationClass "citation": Volume, Issue, Publisher.
    --21Feb2013 Put warning "Bad DOI (expected "10."..)" in DOI result.
    --21Feb2013 Automatically unbolded volume+comma when > 4 long.
    --21Feb2013 Changed to allow lowercase "asin-tld".
    --22Feb2013 Fixed ref=harv to extract Year from Date.
    --22Feb2013 Set Harvard refer. span id if config.CitationClass "citation".
    --22Feb2013 Fixed config.CitationClass "citation" as span class="citation".
    --22Feb2013 Capitalized "Archived/Retrieved" only when sepc is dot ".".
    --23Feb2013 Fixed author editor for "in" or "In" and put space after sepc.
    --23Feb2013 Changed to omit dot in "et al." when sepc is "." separator.
    --23Feb2013 Fixed "author1-first" to also get args.given or args.given1.
    --23Feb2013 Fixed args.article to set Title, after Periodical is Title.
    --23Feb2013 Fixed to allow blank Title (such as "contribution=mytitle").
    --23Feb2013 Fixed double-dot ".." at end of Editors list
    --26Feb2013 Moved "issue=" data to show before "page=".
    --26Feb2013 Moved "type=" data to show after "format=".
    --26Feb2013 For "pmc=" link, omitted suffix "/?tool=pmcentrez".
    --27Feb2013 For coauthors, omitted extra separator after authors.
    --27Feb2013 For date, allowed empty date to use month/day/year.
    --27Feb2013 Fixed double-dot ".." at end of authors/coauthors list.
    --27Feb2013 Reset editor suffix as ", ed." when date exists.
    --27Feb2013 Removed duplicate display of "others=" data.
    --27Feb2013 Removed parentheses "( )" around "department" TitleNote.
    --05Mar2013 Moved Language to follow Periodical or Series.
    --05Mar2013 Fixed Edition to follow Series or Volume.
    --05Mar2013 Fixed class encyclopaedia to show article as quoted Chapter.
    --05Mar2013 Fixed class encyclopaedia to show page as "pp." or "p.".
    --07Mar2013 Changed class encyclopaedia to omit "( )" around publisher.
    --07Mar2013 Fixed end double-dot by string.sub(idcommon,-1,-1) was "-1,1".
    --13Mar2013 Removed enddot "." after "quote=" parameter.
    --13Mar2013 Changed config.CitationClass "news" to use "p." page format.
    --13Mar2013 Fixed missing "location=" when "web" or "encyclopaedia".
    --14Mar2013 Fixed end double-dot after book/work title.
    --14Mar2013 Fixed double-dot before "p." or "pp." page number.
    --14Mar2013 Fixed config.CitationClass "book" to use p./pp. page.
    --18Mar2013 Fixed "page=" to override "pages=" as in markup-based cites.
    --19Mar2013 Fixed date of class=journal Periodical to show after page.
    --19Mar2013 Changed null "postscript=" to suppress end-dot of citation.
    --20Mar2013 If CitationClass is journal, show "others=" before title.
    --20Mar2013 If CitationClass is book, show "others=" before edition.
    --20Mar2013 If CitationClass is journal, adjust "others=" to have sepc.
    --20Mar2013 For class "journal", use book format unless Periodical set.
    --03Apr2013 Changed safejoin() to omit "." after wikilink ".]]" end dot.
    --03Apr2013 Changed safejoin() to omit "." after external ".]" end dot.
    --03Apr2013 Changed safejoin() to omit "." at italic wikilink ".]]" end.
    --03Apr2013 Changed safejoin() to omit "." at italic external ".]" end.
    --04Apr2013 Moved sepc before <span class="reference-accessdate"> for "..".
    --
    --End