Mô đun:Citation/CS1

Bách khoa toàn thư mở Wikipedia
Bước tới: menu, tìm kiếm
Hình tượng tài liệu Tài liệu mô đun[xem] [sửa] [lịch sử] [làm tươi]

Mô đun này có các hàm thực hiện các chú thích nguồn gốc theo Văn phong Chú thích 1.

Nói chung, mô đun này không nên được gọi trực tiếp mà được sử dụng qua các bản mẫu chú thích, chẳng hạn:

Mô đun này dựa trên các mô đun và kịch bản phụ sau:

local z = {
    error_categories = {};
    error_ids = {};
    message_tail = {};
}
 
local SEEN = {};
local DATA = {};
 
-- Include translation message hooks, ID and error handling configuration settings.
local cfg = require( 'Module:Citation/CS1/Configuration' );
 
-- Contains a list of all recognized parameters
local whitelist = mw.loadData( 'Module:Citation/CS1/Whitelist' );
 
local contentLanguage = mw.getContentLanguage()
 
---Định dạng ngày tháng. Thay thế [[Bản mẫu:Ngày chuẩn]].
function vi_formatdate(rawDate, dayPrefix)
    local formatStr = 'j F "năm" Y'
    if dayPrefix then formatStr = '"ngày" ' .. formatStr end
 
    -- Năm
    if tonumber(rawDate) then
        formatStr = 'Y'
    -- Năm tháng
    elseif mw.ustring.match(rawDate, "^[Tt]háng %d%d?,? %d%d%d%d$") or
        mw.ustring.match(rawDate, "^[Tt]háng %d%d?,? năm %d%d%d%d$") or
        mw.ustring.match(rawDate, "^[Tt]háng %a+,? năm %d%d%d%d$") or
        mw.ustring.match(rawDate, "^%a+%.?,? %d%d%d%d$") or
        mw.ustring.match(rawDate, "^%d%d%d%d%-%d%d$") then
        rawDate = mw.ustring.gsub(rawDate, ",", "")
        formatStr = 'F "năm" Y'
    end
 
    local good, formattedDate = pcall(contentLanguage.formatDate, contentLanguage, formatStr, rawDate)
    if good then return formattedDate else return rawDate end
end
function z._vi_formatdate(frame)
    return vi_formatdate(frame.args[1], frame.args[2])
end
 
---Cho ra tên tiếng Việt của ngôn ngữ có mã được đưa vào. Nếu một tên ngôn ngữ
-- được đưa vào, tên đó được cho ra.
function vi_formatlanguage(rawLanguage)
    local languageName = mw.language.fetchLanguageName(rawLanguage, contentLanguage:getCode())
    if not languageName or #languageName < 1 then return rawLanguage end
 
    return (mw.ustring.gsub(languageName, "^[Tt]iếng ", "tiếng ", 1))
end
function z._vi_formatlanguage(frame)
    return vi_formatlanguage(frame.args[1])
end
 
---Định dạng số ấn bản.
function vi_formatedition(rawEdition)
    if tonumber(rawEdition) then return rawEdition end
 
    local num = mw.ustring.match(rawEdition, "(%d+)th") or
        mw.ustring.match(rawEdition, "(%d+)st") or
        mw.ustring.match(rawEdition, "(%d+)nd") or
        mw.ustring.match(rawEdition, "(%d+)rd")
 
    return num or ""
end
function z._vi_formatedition(frame)
    return vi_formatedition(frame.args[1])
end
 
-- Populates numbered arguments in a message string using
-- an argument table.
function substitute( message, arguments )
    if arguments == nil then 
        return message;
    end
 
    message = message .. " ";
    for k, v in ipairs( arguments ) do
        v = v:gsub( "%%", "%%%%" );
        message = message:gsub( "$" .. k .. "(%D)", v .. "%1" );
    end     
    message = message:sub(1,-2);
    return message;
end
 
-- Wraps a string using a message_list configuration taking one argument
function wrap( message_key, str )
    if str == nil or str == "" then
        return "";
    end
    if message_key == 'italic-title' or 
            message_key == 'trans-italic-title' then
        str = safeforitalics( str );
    end
    return substitute( cfg.message_list[message_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 )
    DATA = args;
    local tbl = {};
 
    local mt = {
        __index = function ( tbl, k )            
            if SEEN[k] then
                return nil;
            end
 
            local list = cfg.argument_map[k];                    
 
            if list == nil then
                error( cfg.message_list['unknown_argument_map'] );
            elseif type( list ) == 'string' then
                v = DATA[list];
            else                    
                v = selectone( DATA, cfg.argument_map[k],
                    'redundant_parameters' );
            end
            if v == nil then
                v = cfg.default_values[k];
            end
            SEEN[k] = true;
            tbl = rawset( tbl, k, v );
 
            return v;
        end,
    }
    return setmetatable( tbl, mt );
end
 
-- Checks that parameter name is valid using the whitelist
function validate( name )
    name = mw.text.trim(tostring( name ));
 
    -- Normal arguments
    if whitelist.basic_arguments[ name ] then
        return true;
    end
 
    -- Arguments with numbers in them
    name = mw.ustring.gsub(name, "%d+", "#" );
    if whitelist.numbered_arguments[ name ] then
        return true;
    end
    name = mw.ustring.gsub(name, "%s%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 wrap( 'hidden-error', content );
    else
        return wrap( 'visible-error', content );
    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, 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.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;
    message = substitute( message, arguments );
 
    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
 
    message = prefix .. message .. suffix;
 
    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', {}, false, " " );
    end
    if not checkurl( URL ) then
        error_str = seterror( 'bad_url', {}, false, " " ) .. 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, "Trang có DOI không tích cực" );
--        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
 
-- 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 )
    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
 
        -- 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.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 mw.ustring.gmatch(first, "%S+") do
        table.insert(initials, mw.ustring.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;
    if maximum < 1 then return "", 0; end
    for i,person in ipairs(people) do
        if (person.last ~= nil or 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 (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 )
            table.insert( text, sep_one )
        end
    end
 
    local count = #text / 2;
    if count > 0 then 
        if count > 1 and lastauthoramp ~= nil and 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.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 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.argument_map[list_name .. '-Last'], 'redundant_parameters', i );
        if ( last and "" < last ) then -- just in case someone passed in an empty parameter
            names[i] = {
                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;
        end
        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    
        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 ( options.IgnoreISBN == nil or options.IgnoreISBN == "" ) then 
                    ISBN = ISBN .. seterror( 'bad_isbn', {}, false, " ", "" );
                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, 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( "%s*#", "" );
            if args[v] ~= nil 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 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 .. ", và ";
        else
            error_str = error_str .. " và ";
        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
    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 ( nil ~= 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 ChapterURL = A['ChapterURL'];
    local ConferenceURL = A['ConferenceURL'];
    local Periodical = A['Periodical'];
 
    if ( config.CitationClass == "encyclopaedia" ) then
        if ( Chapter == nil or Chapter == '' ) then    
            if (Title == nil or Title == "") then
                Title = Periodical;
                Periodical = nil;
            else
                Chapter = Title
                TransChapter = TransTitle
                Title = nil 
                TransTitle = nil
            end
        end
    end
 
    local Series = A['Series'];
    local Volume = A['Volume'];
    local Issue = A['Issue'];
    local Position = nil
    local Page, Pages, At, page_type;
 
    Page = A['Page'];
    Pages = hyphentodash( A['Pages'] );
    At = A['At'];
    if Page ~= nil then
        if Pages ~= nil or At ~= nil then
            Page = Page .. " " .. seterror('extra_pages');
            Pages = nil;
            At = nil;
        end
    elseif Pages ~= nil then
        if At ~= nil then
            Pages = Pages .. " " .. seterror('extra_pages');
            At = nil;
        end
    end    
 
    local Edition = A['Edition'];
    local PublicationPlace = A['PublicationPlace']
    local Place = A['Place'];
    if PublicationPlace == nil and Place ~= nil then 
        PublicationPlace = Place;
    end
    if PublicationPlace == Place then Place = nil 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 LaySummary = A['LaySummary']
    local LaySource = A['LaySource'];
    local Transcript = A['Transcript'];
    local TranscriptURL = A['TranscriptURL'];
    local sepc = A['Separator']
    local LastAuthorAmp = A['LastAuthorAmp']
    local no_tracking_cats = A['NoTracking'] or "";
 
    local this_page = mw.title.getCurrentTitle();  --Also used for COinS
    if 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 (URL == nil or URL == "") then
            if (ID_list['PMC'] ~= nil) then 
                local Embargo = A['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 = A['AirDate']
        local SeriesLink = A['SeriesLink']
        local Season = A['Season']
        local SeriesNumber = A['SeriesNumber']
        local Network = A['Network']
        local Station = A['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 = (A["SeriesSeparator"] or A["Separator"]) .. " "
        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 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
 
    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 .. ", " .. 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
 
    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( A['DisplayAuthors'] );
 
        -- 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 = A["AuthorSeparator"] .. " ",
            namesep = (A["AuthorNameSeparator"] or A["NameSeparator"]) .. " ",
            format = A["AuthorFormat"],
            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( A['DisplayEditors'] );
 
        -- 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 = A["EditorSeparator"] .. " ",
            namesep = (A["EditorNameSeparator"] or A["NameSeparator"]) .. " ",
            format = A['EditorFormat'],
            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 = A['Month']
            if ( Month ~= nil and Month ~= "") then
                if tonumber(Year) then Date = "năm " .. Date end
                if tonumber(Month) then Month = "tháng " .. Month end
                Date = Month .. " " .. Date 
                local Day = A['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 = vi_formatdate(PublicationDate);
        PublicationDate = nil;
    end
    if Date and #Date > 0 then Date = vi_formatdate(Date) end
    if PublicationDate and #PublicationDate > 0 then PublicationDate = vi_formatdate(PublicationDate) 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" and DeadURL ~= "không" ) then
            URL = ArchiveURL
        end
    end
 
    -- Format chapter / article title
    if ( Chapter ~= nil and Chapter ~= "" ) and ( ChapterLink and "" < ChapterLink ) then 
        Chapter = "[[" .. ChapterLink .. "|" .. Chapter .. "]]";
    end
    if ( Periodical and "" < Periodical ) and (Title ~= nil and 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 TransChapter ~= "" and Chapter == "" then
        TransError = " " .. seterror( 'trans_missing_chapter' );
    end
 
    if TransChapter ~= "" and Chapter ~= "" then TransChapter = " " .. TransChapter; 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 ( TitleLink and "" < TitleLink ) then
        if ( Title and "" < Title ) then
            Title = "[[" .. TitleLink .. "|" .. Title .. "]]" 
        end
    end
 
    if ( Periodical and "" < Periodical ) then
        Title = wrap( 'quoted-title', Title );
        TransTitle = wrap( 'trans-quoted-title', TransTitle );
    elseif ( config.CitationClass == "web"
            or config.CitationClass == "news" 
            or config.CitationClass == "pressrelease" ) and 
            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
 
    local TransError = "";
    if TransTitle ~= "" and Title == "" then
        TransError = " " .. seterror( 'trans_missing_title' );
    end
 
    if TransTitle ~= "" and Title ~= "" then TransTitle = " " .. TransTitle; 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 = " " .. wrap( 'written', Place ) .. sepc .. " ";
        else
            Place = " " .. substitute( 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 = A['Minutes'];
        if ( nil ~= Minutes ) then
            Position = " " .. Minutes .. " " .. cfg.message_list['minutes'];
        else
            local Time = A['Time'];
            if ( nil ~= Time ) then
                local TimeCaption = A['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 = " " .. wrap( 'language', vi_formatlanguage(Language) ) else Language = "" end
    if ( Edition ~= nil and Edition ~="" ) then
        Edition = " " .. wrap( 'edition', vi_formatedition(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 = " " .. wrap( 'via', Via ) else Via = "" end
    if ( AccessDate ~= nil and AccessDate ~="" )
    then local retrv_text = " " .. cfg.message_list['retrieved']
         if (sepc ~= ".") then retrv_text = mw.ustring.lower(retrv_text) end
         AccessDate = '<span class="reference-accessdate">' .. sepc
             .. substitute( retrv_text, {vi_formatdate(AccessDate, true)} ) .. '</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 );
    else
        URL = ""
    end
 
    if ( Quote and Quote ~="" ) then 
        if mw.ustring.match(Quote, '^".+"$') or mw.ustring.match(Quote, '^“.+”$') then
            Quote = mw.ustring.sub(Quote, 2, -2)
        end
 
        local quoteFormats = cfg.message_list['quoted-text']
        if mw.ustring.match(Quote, '%b“”') or mw.ustring.match(Quote, '".-"') then
            Quote = substitute( quoteFormats[2], {Quote} )
        else
            Quote = substitute( quoteFormats[1], {Quote} )
        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 or ArchiveDate =="" ) then
            ArchiveDate = seterror('archive_missing_date');
        else
            ArchiveDate = vi_formatdate(ArchiveDate, true)
        end
        if ( "no" == DeadURL or "không" == DeadURL ) then
            local arch_text = cfg.message_list['archived'];
            if (sepc ~= ".") then arch_text = mw.ustring.lower(arch_text) end
            Archived = sepc .. " " .. substitute( cfg.message_list['archived-not-dead'],
                { externallink( ArchiveURL, arch_text ), ArchiveDate } );
            if OriginalURL == nil or OriginalUrl == '' then
                Archived = Archived .. " " .. seterror('archive_missing_url');                               
            end
        else
            if OriginalURL ~= nil and OriginalURL ~= '' then
                local arch_text = cfg.message_list['archived-dead'];
                if (sepc ~= ".") then arch_text = mw.ustring.lower(arch_text) end
                Archived = sepc .. " " .. substitute( arch_text,
                    { 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
    else
        Archived = ""
    end
    local Lay
    if ( nil ~= LaySummary and "" ~= LaySummary ) then
        if ( LayDate ~= nil ) then LayDate = " (" .. vi_formatdate(LayDate) .. ")" else LayDate = "" end
        if ( LaySource ~= nil ) then 
            LaySource = " &ndash; ''" .. safeforitalics(LaySource) .. "''" 
        else 
            LaySource = "" 
        end
        if sepc == '.' then
            Lay = sepc .. " " .. externallink( LaySummary, cfg.message_list['lay summary'] ) .. LaySource .. LayDate
        else
            Lay = sepc .. " " .. externallink( 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 .. ", " .. wrap( 'published', PublicationDate );
            else
                Publisher = PublicationDate;
            end
        end
        if Publisher ~= "" then
            Publisher = " (" .. Publisher .. ")";
        end
    else
        if ( PublicationDate and PublicationDate ~="" ) then
            PublicationDate = " (" .. wrap( '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 .. " " .. wrap( 'italic-title', Periodical ) 
        else 
            Periodical = wrap( 'italic-title', 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 .. A['AuthorSeparator'] .. " " .. 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 = " trong "
            if (sepc == '.') then in_text = " Trong " 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 .." " .. contentLanguage:ucfirst(Date) .. OrigYear
              else Date = " " .. contentLanguage:ucfirst(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
        text = text .. " ";
        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
 
    no_tracking_cats = no_tracking_cats:lower();
    if no_tracking_cats == "" or no_tracking_cats == "no" or
            no_tracking_cats == "false" or no_tracking_cats == "n" or
            no_tracking_cats == "không" or no_tracking_cats == "ko" or no_tracking_cats == "k" then
        for _, v in ipairs( z.error_categories ) do
            text = text .. '[[Thể loại:' .. v ..']]';
        end
    end
 
    return text
end
 
-- This is used by templates such as {{chú thích sách}} 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