模組:Citation/CS1

可在模組:Citation/CS1/doc建立此模組的說明文件

local z = {    error_categories = {};    error_ids = {};    message_tail = {};}-- Include translation message hooks, ID and error handling configuration settings.-- Note that require has tested to be significantly faster than loadData for this -- usage.  This might be a side effect of the unnecessary cloning described -- in bugzilla 47300.local cfg = require( 'Module:Citation/CS1/Configuration' );-- Contains a list of all recognized parameterslocal whitelist = mw.loadData( 'Module:Citation/CS1/Whitelist' );-- LOCALfunction is_zh( str )    return not not str:find( '[\128-\255]' )end-- END LOCAL-- Whether variable is set or notfunction is_set( var )    return not (var == nil or var == '');end-- First set variable or nil if nonefunction first_set(...)    local list = {...};    for _, var in pairs(list) do        if is_set( var ) then            return var;        end    endend-- Whether needle is in haystackfunction inArray( needle, haystack )    if needle == nil then        return false;    end    for n,v in ipairs( haystack ) do        if v == needle then            return n;        end    end    return false;end-- Populates numbered arguments in a message string using an argument table.function substitute( msg, args )    return args and tostring( mw.message.newRawMessage( msg, args ) ) or msg;end-- Wraps a string using a message_list configuration taking one argumentfunction wrap( key, str )    if not is_set( str ) then        return "";    elseif inArray( key, { 'italic-title', 'trans-italic-title' } ) then        str = safeforitalics( str );    end    return substitute( cfg.messages[key], {str} );end--[[Argument wrapper.  This function provides support for argument mapping defined in the configuration file so that multiple namescan be transparently aliased to single internal variable.]]function argument_wrapper( args )    local origin = {};        return setmetatable({        ORIGIN = function( self, k )            local dummy = self[k]; --force the variable to be loaded.            return origin[k];        end    },    {        __index = function ( tbl, k )            if origin[k] ~= nil then                return nil;            end                        local args, list, v = args, cfg.aliases[k];                        if list == nil then                error( cfg.messages['unknown_argument_map'] );            elseif type( list ) == 'string' then                v, origin[k] = args[list], list;            else                v, origin[k] = selectone( args, list, 'redundant_parameters' );                if origin[k] == nil then                    origin[k] = '';   --Empty string, not nil;                end            end                        if v == nil then                v = cfg.defaults[k] or "";                origin[k] = '';   --Empty string, not nil;            end                        tbl = rawset( tbl, k, v );            return v;        end,    });end-- Checks that parameter name is valid using the whitelistfunction 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 falseend-- Formats a comment for error trappingfunction errorcomment( content, hidden )    return wrap( hidden and 'hidden-error' or 'visible-error', content );end--[[Sets an error condition and returns the appropriate error message.  The actual placementof the error message in the output is the responsibility of the calling function.]]function seterror( error_id, arguments, raw, prefix, suffix )    local error_state = cfg.error_conditions[ error_id ];        prefix = prefix or "";    suffix = suffix or "";        if error_state == nil then        error( cfg.messages['undefined_error'] );    elseif is_set( error_state.category ) then        table.insert( z.error_categories, error_state.category );    end        local message = substitute( error_state.message, arguments );        message = message .. " ([[" .. cfg.messages['help page link'] ..         "#" .. error_state.anchor .. "|" ..        cfg.messages['help page label'] .. "]])";        z.error_ids[ error_id ] = true;    if inArray( error_id, { 'bare_url_missing_title', 'trans_missing_title' } )            and z.error_ids['citation_missing_title'] then        return '', false;    end        message = table.concat({ prefix, message, suffix });        if raw == true then        return message, error_state.hidden;    end                    return errorcomment( message, error_state.hidden );end-- Formats a wiki style external linkfunction externallinkid(options)    local url_string = options.id;    if options.encode == true or options.encode == nil then        url_string = mw.uri.encode( url_string );    end    return mw.ustring.format( '[[%s|%s]]%s[%s%s%s %s]',        options.link, options.label, options.separator or "&nbsp;",        options.prefix, url_string, options.suffix or "",        mw.text.nowiki(options.id)    );end-- Formats a wiki style internal linkfunction internallinkid(options)    return mw.ustring.format( '[[%s|%s]]%s[[%s%s%s|%s]]',        options.link, options.label, options.separator or "&nbsp;",        options.prefix, options.id, options.suffix or "",        mw.text.nowiki(options.id)    );end-- Format an external link with error checkingfunction externallink( URL, label, source )    local error_str = "";    if not is_set( label ) then        label = URL;        if is_set( source ) then            error_str = seterror( 'bare_url_missing_title', { wrap( 'parameter', source ) }, false, " " );        else            error( cfg.messages["bare_url_no_origin"] );        end                end    if not checkurl( URL ) then        error_str = seterror( 'bad_url', {}, false, " " ) .. error_str;    end    return table.concat({ "[", URL, " ", safeforurl( label ), "]", error_str });end-- Formats a link to Amazonfunction amazon(id, domain)    if not is_set(domain) then         domain = "com"    elseif ( "jp" == domain or "uk" == domain ) then        domain = "co." .. domain    end    local handler = cfg.id_handlers['ASIN'];    return externallinkid({link = handler.link,        label=handler.label , prefix="//www.amazon."..domain.."/dp/",id=id,        encode=handler.encode, separator = handler.separator})end-- Formats a DOI and checks for DOI errors.function doi(id, inactive)    local cat = ""    local handler = cfg.id_handlers['DOI'];        local text;    if is_set(inactive) then        text = "[[" .. handler.link .. "|" .. handler.label .. "]]:" .. id;        table.insert( z.error_categories, "Pages with DOIs inactive since " .. selectyear(inactive) );                inactive = " (" .. cfg.messages['inactive'] .. " " .. inactive .. ")"     else         text = externallinkid({link = handler.link, label = handler.label,            prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode})        inactive = ""     end    if ( string.sub(id,1,3) ~= "10." ) then              cat = seterror( 'bad_doi' );    end    return text .. inactive .. cat end-- Formats an OpenLibrary link, and checks for associated errors.function openlibrary(id)    local code = id:sub(-1,-1)    local handler = cfg.id_handlers['OL'];    if ( code == "A" ) then        return externallinkid({link=handler.link, label=handler.label,            prefix="http://openlibrary.org/authors/OL",id=id, separator=handler.separator,            encode = handler.encode})    elseif ( code == "M" ) then        return externallinkid({link=handler.link, label=handler.label,            prefix="http://openlibrary.org/books/OL",id=id, separator=handler.separator,            encode = handler.encode})    elseif ( code == "W" ) then        return externallinkid({link=handler.link, label=handler.label,            prefix= "http://openlibrary.org/works/OL",id=id, separator=handler.separator,            encode = handler.encode})    else        return externallinkid({link=handler.link, label=handler.label,            prefix= "http://openlibrary.org/OL",id=id, separator=handler.separator,            encode = handler.encode}) ..             ' ' .. seterror( 'bad_ol' );    endend--[[Determines whether an URL string is validAt present the only check is whether the string appears to be prefixed with a URI scheme.  It is not determined whether the URI scheme is valid or whether the URL is otherwise well formed.]]function checkurl( url_str )    -- Protocol-relative or URL scheme    return url_str:sub(1,2) == "//" or url_str:match( "^[^/]*:" ) ~= nil;end-- Removes irrelevant text and dashes from ISBN number-- Similar to that used for Special:BookSourcesfunction cleanisbn( isbn_str )    return isbn_str:gsub( "[^-0-9X]", "" );end-- Determines whether an ISBN string is validfunction 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;    endend-- Gets the display text for a wikilink like [[A|B]] or [[B]] gives Bfunction removewikilink( str )    return (str:gsub( "%[%[([^%[%]]*)%]%]", function(l)        return l:gsub( "^[^|]*|(.*)$", "%1" ):gsub("^%s*(.-)%s*$", "%1");    end));end-- Escape sequences for content that will be used for URL descriptionsfunction safeforurl( str )    do return str end -- LOCAL HACK https://en.wikipedia.org/w/index.php?title=Module_talk:Citation/CS1&diff=552318417&oldid=552202448    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 dashfunction hyphentodash( str )    if not is_set(str) or str:match( "[%[%]{}<>]" ) ~= nil then        return str;    end        return str:gsub( '-', '–' );end-- Protects a string that will be wrapped in wiki italic markup '' ... ''function safeforitalics( str )    --[[ Note: We can not use <i> for italics, as the expected behavior for    italics specified by ''...'' in the title is that they will be inverted    (i.e. unitalicized) in the resulting references.  In addition, <i> and ''    tend to interact poorly under Mediawiki's HTML tidy. ]]        if not is_set(str) then        return str;    else        if str:sub(1,1) == "'" then str = "<span />" .. str; end        if str:sub(-1,-1) == "'" then str = str .. "<span />"; end                -- Remove newlines as they break italics.        return str:gsub( '\n', ' ' );    endend--[[Joins a sequence of strings together while checking for duplicate separationcharacters.]]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 interpretedas a year.]]function selectyear( str )    -- Is the input a simple number?    local num = tonumber( str );     if num ~= nil and num > 0 and num < 2100 and num == math.floor(num) then        return str;    else        -- Use formatDate to interpret more complicated formats        local lang = mw.getContentLanguage();        local good, result;        good, result = pcall( lang.formatDate, lang, 'Y', str )        if good then             return result;        else            -- Can't make sense of this input, return blank.            return "";        end    endend-- Attempts to convert names to initials.function reducetoinitials(first)    local initials = {}    for word in string.gmatch(first, "%S+") do        table.insert(initials, string.sub(word,1,1)) -- Vancouver format does not include full stops.    end    return table.concat(initials) -- Vancouver format does not include spaces.end-- Formats a list of people (e.g. authors / editors) function listpeople(control, people)    local sep = control.sep;    local namesep = control.namesep    local format = control.format    local maximum = control.maximum    local lastauthoramp = control.lastauthoramp;    local text = {}    local etal = false;        if sep:sub(-1,-1) ~= " " then sep = sep .. " " end    if maximum ~= nil and maximum < 1 then return "", 0; end        for i,person in ipairs(people) do        if is_set(person.last) then            local mask = person.mask            local one            local sep_one = sep;            if maximum ~= nil and i > maximum then                etal = true;                break;            elseif (mask ~= nil) then                local n = tonumber(mask)                if (n ~= nil) then                    one = string.rep("&mdash;",n)                else                    one = mask;                    sep_one = " ";                end            else                one = person.last                local first = person.first                if is_set(first) then                     if ( "vanc" == format ) then first = reducetoinitials(first) end                    one = one .. namesep .. first                 end                if is_set(person.link) then one = "[[" .. person.link .. "|" .. one .. "]]" end            end            table.insert( text, one )            table.insert( text, sep_one )        end    end    local count = #text / 2;    if count > 0 then         if count > 1 and is_set(lastauthoramp) and not etal then            text[#text-2] = " & ";        end        text[#text] = nil;     end        local result = table.concat(text) -- construct list    if etal then         local etal_text = is_zh( result ) and cfg.messages['et al'] or '<i>et al</i>.'; -- LOCAL        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, countend-- Generates a CITEREF anchor ID.function anchorid( options )    return "CITEREF" .. table.concat( options );end-- Gets name list from the input argumentsfunction extractnames(args, list_name)    local names = {};    local i = 1;    local last;        while true do        last = selectone( args, cfg.aliases[list_name .. '-Last'], 'redundant_parameters', i );        if not is_set(last) then            -- just in case someone passed in an empty parameter            break;        end        names[i] = {            last = last,            first = selectone( args, cfg.aliases[list_name .. '-First'], 'redundant_parameters', i ),            link = selectone( args, cfg.aliases[list_name .. '-Link'], 'redundant_parameters', i ),            mask = selectone( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i )        };        i = i + 1;    end    return names;end-- Populates ID table from arguments using configuration settingsfunction extractids( args )    local id_list = {};    for k, v in pairs( cfg.id_handlers ) do            v = selectone( args, v.parameters, 'redundant_parameters' );        if is_set(v) then id_list[k] = v; end    end    return id_list;end-- Takes a table of IDs and turns it into a table of formatted ID outputs.function buildidlist( id_list, options )    local new_list, handler = {};        function fallback(k) return { __index = function(t,i) return cfg.id_handlers[k][i] end } end;        for k, v in pairs( id_list ) do        -- fallback to read-only cfg        handler = setmetatable( { ['id'] = v }, fallback(k) );                if handler.mode == 'external' then            table.insert( new_list, {handler.label, externallinkid( handler ) } );        elseif handler.mode == 'internal' then            table.insert( new_list, {handler.label, internallinkid( handler ) } );        elseif handler.mode ~= 'manual' then            error( cfg.messages['unknown_ID_mode'] );        elseif k == 'DOI' then            table.insert( new_list, {handler.label, doi( v, options.DoiBroken ) } );        elseif k == 'ASIN' then            table.insert( new_list, {handler.label, amazon( v, options.ASINTLD ) } );         elseif k == 'OL' then            table.insert( new_list, {handler.label, openlibrary( v ) } );        elseif k == 'ISBN' then            local ISBN = internallinkid( handler );            if not checkisbn( v ) and not is_set(options.IgnoreISBN) then                ISBN = ISBN .. seterror( 'bad_isbn', {}, false, " ", "" );            end            table.insert( new_list, {handler.label, ISBN } );                        else            error( cfg.messages['unknown_manual_ID'] );        end    end        function comp( a, b )        return a[1] < b[1];    end        table.sort( new_list, comp );    for k, v in ipairs( new_list ) do        new_list[k] = v[2];    end        return new_list;end  -- Chooses one matching parameter from a list of parameters to consider-- Generates an error if more than one match is present.function selectone( args, possible, error_condition, index )    local value = nil;    local selected = '';    local error_list = {};        if index ~= nil then index = tostring(index); end        -- Handle special case of "#" replaced by empty string    if index == '1' then        for _, v in ipairs( possible ) do            v = v:gsub( "#", "" );            if is_set(args[v]) then                if value ~= nil and selected ~= v then                    table.insert( error_list, v );                else                    value = args[v];                    selected = v;                end            end        end            end        for _, v in ipairs( possible ) do        if index ~= nil then            v = v:gsub( "#", index );        end        if is_set(args[v]) then            if value ~= nil and selected ~=  v then                table.insert( error_list, v );            else                value = args[v];                selected = v;            end        end    end        if #error_list > 0 then        local error_str = "";        for _, k in ipairs( error_list ) do            if error_str ~= "" then error_str = error_str .. cfg.messages['parameter-separator'] end            error_str = error_str .. wrap( 'parameter', k );        end        if #error_list > 1 then            error_str = error_str .. cfg.messages['parameter-final-separator'];        else            error_str = error_str .. cfg.messages['parameter-pair-separator'];        end        error_str = error_str .. wrap( 'parameter', selected );        table.insert( z.message_tail, { seterror( error_condition, {error_str}, true ) } );    end        return value, selected;end-- COinS metadata (see <http://ocoins.info/>) allows automated tools to parse-- the citation information.function COinS(data)    if 'table' ~= type(data) or nil == next(data) then        return '';    end        local ctx_ver = "Z39.88-2004";        -- treat table strictly as an array with only set values.    local OCinSoutput = setmetatable( {}, {        __newindex = function(self, key, value)            if is_set(value) then                rawset( self, #self+1, table.concat{ key, '=', mw.uri.encode( removewikilink( value ) ) } );            end        end    });        if is_set(data.Chapter) then        OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:book";        OCinSoutput["rft.genre"] = "bookitem";        OCinSoutput["rft.btitle"] = data.Chapter;        OCinSoutput["rft.atitle"] = data.Title;    elseif is_set(data.Periodical) then        OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:journal";        OCinSoutput["rft.genre"] = "article";        OCinSoutput["rft.jtitle"] = data.Periodical;        OCinSoutput["rft.atitle"] = data.Title;    else        OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:book";        OCinSoutput["rft.genre"] = "book"        OCinSoutput["rft.btitle"] = data.Title;    end        OCinSoutput["rft.place"] = data.PublicationPlace;    OCinSoutput["rft.date"] = data.Date;    OCinSoutput["rft.series"] = data.Series;    OCinSoutput["rft.volume"] = data.Volume;    OCinSoutput["rft.issue"] = data.Issue;    OCinSoutput["rft.pages"] = data.Pages;    OCinSoutput["rft.edition"] = data.Edition;    OCinSoutput["rft.pub"] = data.PublisherName;        for k, v in pairs( data.ID_list ) do        local id, value = cfg.id_handlers[k].COinS;        if k == 'ISBN' then value = cleanisbn( v ); else value = v; end        if string.sub( id or "", 1, 4 ) == 'info' then            OCinSoutput["rft_id"] = table.concat{ id, "/", v };        else            OCinSoutput[ id ] = value;        end    end        local last, first;    for k, v in ipairs( data.Authors ) do        last, first = v.last, v.first;        if k == 1 then            if is_set(last) then                OCinSoutput["rft.aulast"] = last;            end            if is_set(first) then                 OCinSoutput["rft.aufirst"] = first;            end        end        if is_set(last) and is_set(first) then            OCinSoutput["rft.au"] = table.concat{ last, ", ", first };        elseif is_set(last) then            OCinSoutput["rft.au"] = last;        end    end        OCinSoutput.rft_id = data.URL;    OCinSoutput.rfr_id = table.concat{ "info:sid/", mw.site.server:match( "[^/]*$" ), ":", data.RawPage };    OCinSoutput = setmetatable( OCinSoutput, nil );        -- sort with version string always first, and combine.    table.sort( OCinSoutput );    table.insert( OCinSoutput, 1, "ctx_ver=" .. ctx_ver );  -- such as "Z39.88-2004"    return table.concat(OCinSoutput, "&");end--[[This is the main function foing the majority of the citationformatting.]]function citation0( config, args, frame ) -- LOCAL    --[[     Load Input Parameters    The argment_wrapper facillitates the mapping of multiple    aliases to single internal variable.    ]]    local A = argument_wrapper( args );    local i     local PPrefix = A['PPrefix']    local PPPrefix = A['PPPrefix']    if is_set( A['NoPP'] ) then PPPrefix = "" PPrefix = "" end        -- Pick out the relevant fields from the arguments.  Different citation templates    -- define different field names for the same underlying things.        -- LOCAL    local PSuffix = A['PSuffix']    local PPSuffix = A['PPSuffix']    if ( nil ~= A['NoPP'] ) then PPSuffix = "" PSuffix = "" end    -- END LOCAL        -- Pick out the relevant fields from the arguments.  Different citation templates    -- define different field names for the same underlying things.        local Authors = A['Authors'];    local a = extractnames( args, 'AuthorList' );    local Coauthors = A['Coauthors'];    local Others = A['Others'];    local Editors = A['Editors'];    local e = extractnames( args, 'EditorList' );    local Year = A['Year'];    local PublicationDate = A['PublicationDate'];    local OrigYear = A['OrigYear'];    local Date = A['Date'];    local LayDate = A['LayDate'];    ------------------------------------------------- Get title data    local Title = A['Title'];    local BookTitle = A['BookTitle'];    local Conference = A['Conference'];    local TransTitle = A['TransTitle'];    local TitleNote = A['TitleNote'];    local TitleLink = A['TitleLink'];    local Chapter = A['Chapter'];    local ChapterLink = A['ChapterLink'];    local TransChapter = A['TransChapter'];    local TitleType = A['TitleType'];    local ArchiveURL = A['ArchiveURL'];    local URL = A['URL']    local URLorigin = A:ORIGIN('URL');    local ChapterURL = A['ChapterURL'];    local ChapterURLorigin = A:ORIGIN('ChapterURL');    local ConferenceURL = A['ConferenceURL'];    local ConferenceURLorigin = A:ORIGIN('ConferenceURL');    local Periodical = A['Periodical'];        if ( config.CitationClass == "encyclopaedia" ) then        if not is_set(Chapter) then            if not is_set(Title) then                Title = Periodical;                Periodical = '';            else                Chapter = Title                TransChapter = TransTitle                Title = '';                TransTitle = '';            end        end    end    local Series = A['Series'];    local Volume = A['Volume'];    local Issue = A['Issue'];    local Position = '';    local Page, Pages, At, page_type;        Page = A['Page'];    Pages = hyphentodash( A['Pages'] );    At = A['At'];        if is_set(Page) then        if is_set(Pages) or is_set(At) then            Page = Page .. " " .. seterror('extra_pages');            Pages = '';            At = '';        end    elseif is_set(Pages) then        if is_set(At) then            Pages = Pages .. " " .. seterror('extra_pages');            At = '';        end    end            local Edition = A['Edition'];    local PublicationPlace = A['PublicationPlace']    local Place = A['Place'];        if not is_set(PublicationPlace) and is_set(Place) then        PublicationPlace = Place;    end        if PublicationPlace == Place then Place = ''; end        local PublisherName = A['PublisherName'];    local SubscriptionRequired = A['SubscriptionRequired'];    local Via = A['Via'];    local AccessDate = A['AccessDate'];    local ArchiveDate = A['ArchiveDate'];    local Agency = A['Agency'];    local DeadURL = A['DeadURL']    local Language = A['Language'];    local Format = A['Format'];    local Ref = A['Ref'];        local DoiBroken = A['DoiBroken'];    local ID = A['ID'];    local ASINTLD = A['ASINTLD'];    local IgnoreISBN = A['IgnoreISBN'];    local ID_list = extractids( args );        local Quote = A['Quote'];    local PostScript = A['PostScript'];    local LayURL = A['LayURL'];    local LaySource = A['LaySource'];    local Transcript = A['Transcript'];    local TranscriptURL = A['TranscriptURL']     local TranscriptURLorigin = A:ORIGIN('TranscriptURL');    local sepc = A['Separator'];    local LastAuthorAmp = A['LastAuthorAmp'];    local no_tracking_cats = A['NoTracking'];    local this_page = mw.title.getCurrentTitle();  --Also used for COinS        if not is_set(no_tracking_cats) then        for k, v in pairs( cfg.uncategorized_namespaces ) do            if this_page.nsText == v then                no_tracking_cats = "true";                break;            end        end    end    if ( config.CitationClass == "journal" ) then        if not is_set(URL) and is_set(ID_list['PMC']) then            local Embargo = A['Embargo'];            if is_set(Embargo) then                local lang = mw.getContentLanguage();                local good1, result1, good2, result2;                good1, result1 = pcall( lang.formatDate, lang, 'U', Embargo );                good2, result2 = pcall( lang.formatDate, lang, 'U' );                                if good1 and good2 and tonumber( result1 ) < tonumber( result2 ) then                     URL = "http://www.ncbi.nlm.nih.gov/pmc/articles/PMC" .. ID_list['PMC'];                    URLorigin = cfg.id_handlers['PMC'].parameters[1];                end            else                URL = "http://www.ncbi.nlm.nih.gov/pmc/articles/PMC" .. ID_list['PMC'];                URLorigin = cfg.id_handlers['PMC'].parameters[1];            end        end    end    -- At this point fields may be nil if they weren't specified in the template use.  We can use that fact.        -- Account for the oddity that is {{cite conference}}, before generation of COinS data.    if is_set(BookTitle) then        Chapter = Title;        ChapterLink = TitleLink;        TransChapter = TransTitle;        Title = BookTitle;        TitleLink = '';        TransTitle = '';    end    -- Account for the oddity that is {{cite episode}}, before generation of COinS data.    if config.CitationClass == "episode" then        local AirDate = A['AirDate'];        local SeriesLink = A['SeriesLink'];        local Season = A['Season'];        local SeriesNumber = A['SeriesNumber'];        local Network = A['Network'];        local Station = A['Station'];        local s, n = {}, {};        local Sep = (first_set(A["SeriesSeparator"], A["Separator"]) or "") .. " ";                if is_set(Issue) then table.insert(s, cfg.messages["episode"] .. " " .. Issue); Issue = ''; end        if is_set(Season) then table.insert(s, cfg.messages["season"] .. " " .. Season); end        if is_set(SeriesNumber) then table.insert(s, cfg.messages["series"] .. " " .. SeriesNumber); end        if is_set(Network) then table.insert(n, Network); end        if is_set(Station) then table.insert(n, Station); end                Date = Date or AirDate;        Chapter = Title;        ChapterLink = TitleLink;        TransChapter = TransTitle;        Title = Series;        TitleLink = SeriesLink;        TransTitle = '';                Series = table.concat(s, Sep);        ID = table.concat(n, Sep);    end        -- COinS metadata (see <http://ocoins.info/>) for    -- automated parsing of citation information.    local OCinSoutput = COinS{        ['Periodical'] = Periodical,        ['Chapter'] = Chapter,        ['Title'] = Title,        ['PublicationPlace'] = PublicationPlace,        ['Date'] = first_set(Date, Year, PublicationDate),        ['Series'] = Series,        ['Volume'] = Volume,        ['Issue'] = Issue,        ['Pages'] = first_set(Page, Pages, At),        ['Edition'] = Edition,        ['PublisherName'] = PublisherName,        ['URL'] = first_set( URL, ChapterURL ),        ['Authors'] = a,        ['ID_list'] = ID_list,        ['RawPage'] = this_page.prefixedText,    };    if is_set(Periodical) and not is_set(Chapter) and is_set(Title) then        Chapter = Title;        ChapterLink = TitleLink;        TransChapter = TransTitle;        Title = '';        TitleLink = '';        TransTitle = '';    end    -- Now perform various field substitutions.    -- We also add leading spaces and surrounding markup and punctuation to the    -- various parts of the citation, but only when they are non-nil.    if not is_set(Authors) then        local Maximum = tonumber( A['DisplayAuthors'] );                -- Preserve old-style implicit et al.        if not is_set(Maximum) and #a == 9 then             Maximum = 8;            table.insert( z.message_tail, { seterror('implict_etal_author', {}, true ) } );        elseif not is_set(Maximum) then            Maximum = #a + 1;        end                    local control = {             sep = A["AuthorSeparator"] .. " ",            namesep = (first_set(A["AuthorNameSeparator"], A["NameSeparator"]) or "") .. " ",            format = A["AuthorFormat"],            maximum = Maximum,            lastauthoramp = LastAuthorAmp        };                -- If the coauthor field is also used, prevent ampersand and et al. formatting.        if is_set(Coauthors) then            control.lastauthoramp = nil;            control.maximum = #a + 1;        end                Authors = listpeople(control, a)     end        local EditorCount    if not is_set(Editors) then        local Maximum = tonumber( A['DisplayEditors'] );        -- Preserve old-style implicit et al.        if not is_set(Maximum) and #e == 4 then             Maximum = 3;            table.insert( z.message_tail, { seterror('implict_etal_editor', {}, true) } );        elseif not is_set(Maximum) then            Maximum = #e + 1;        end        local control = {             sep = A["EditorSeparator"] .. " ",            namesep = (first_set(A["EditorNameSeparator"], A["NameSeparator"]) or "") .. " ",            format = A['EditorFormat'],            maximum = Maximum,            lastauthoramp = LastAuthorAmp        };        Editors, EditorCount = listpeople(control, e);    else        EditorCount = 1;    end        if not is_set(Date) then        Date = Year;        if is_set(Date) then            local Month = A['Month'];            if is_set(Month) then                 Date = Date .. '.' .. Month; -- LOCAL                local Day = A['Day']                if is_set(Day) then Date = Date .. '.' .. Month end -- LOCAL            end        end    end        if inArray(PublicationDate, {Date, Year}) then PublicationDate = ''; end    if not is_set(Date) and is_set(PublicationDate) then        Date = PublicationDate;        PublicationDate = '';    end    -- Captures the value for Date prior to adding parens or other textual transformations    local DateIn = Date;        if  not is_set(URL) and        not is_set(ChapterURL) and        not is_set(ArchiveURL) and        not is_set(ConferenceURL) and        not is_set(TranscriptURL) then                -- Test if cite web is called without giving a URL        if ( config.CitationClass == "web" ) then            table.insert( z.message_tail, { seterror( 'cite_web_url', {}, true ) } );        end                -- Test if accessdate is given without giving a URL        if is_set(AccessDate) then            table.insert( z.message_tail, { seterror( 'accessdate_missing_url', {}, true ) } );            AccessDate = '';        end                -- Test if format is given without giving a URL        if is_set(Format) then            Format = Format .. seterror( 'format_missing_url' );        end    end        -- Test if citation has no title    if  not is_set(Chapter) and        not is_set(Title) and        not is_set(Periodical) and        not is_set(Conference) and        not is_set(TransTitle) and        not is_set(TransChapter) then        table.insert( z.message_tail, { seterror( 'citation_missing_title', {}, true ) } );    end        Format = is_set(Format) and " (" .. Format .. ")" or "";        local OriginalURL = URL    DeadURL = DeadURL:lower();    if is_set( ArchiveURL ) then        if ( DeadURL ~= "no" ) then            URL = ArchiveURL            URLorigin = A:ORIGIN('ArchiveURL')        end    end        -- Format chapter / article title    if is_set(Chapter) and is_set(ChapterLink) then         Chapter = "[[" .. ChapterLink .. "|" .. Chapter .. "]]";    end    if is_set(Periodical) and is_set(Title) then        Chapter = wrap( 'italic-title', Chapter );        TransChapter = wrap( 'trans-italic-title', TransChapter );    else        Chapter = wrap( 'quoted-title', Chapter );        TransChapter = wrap( 'trans-quoted-title', TransChapter );    end        local TransError = ""    if is_set(TransChapter) then        if not is_set(Chapter) then            TransError = " " .. seterror( 'trans_missing_chapter' );        else            TransChapter = " " .. TransChapter;        end    end        Chapter = Chapter .. TransChapter;        if is_set(Chapter) then        if not is_set(ChapterLink) then            if is_set(ChapterURL) then                Chapter = externallink( ChapterURL, Chapter ) .. TransError;                if not is_set(URL) then                    Chapter = Chapter .. Format;                    Format = "";                end            elseif is_set(URL) then                 Chapter = externallink( URL, Chapter ) .. TransError .. Format;                URL = "";                Format = "";            else                Chapter = Chapter .. TransError;            end                    elseif is_set(ChapterURL) then            Chapter = Chapter .. " " .. externallink( ChapterURL, nil, ChapterURLorigin ) ..                 TransError;        else            Chapter = Chapter .. TransError;        end        if is_set(Title) then -- LOCAL          Chapter = Chapter .. '//' -- LOCAL        else -- LOCAL          Chapter = Chapter .. sepc .. " " -- with end-space        end -- LOCAL    elseif is_set(ChapterURL) then        Chapter = " " .. externallink( ChapterURL, nil, ChapterURLorigin ) .. sepc .. " ";    end                -- Format main title.    if is_set(TitleLink) and is_set(Title) then        Title = "[[" .. TitleLink .. "|" .. Title .. "]]"    end        if is_set(Periodical) then        Title = wrap( 'quoted-title', Title );        TransTitle = wrap( 'trans-quoted-title', TransTitle );    elseif inArray(config.CitationClass, {"web","news","pressrelease"}) and            not is_set(Chapter) then        Title = wrap( 'quoted-title', Title );        TransTitle = wrap( 'trans-quoted-title', TransTitle );    else        Title = wrap( 'italic-title', Title );        TransTitle = wrap( 'trans-italic-title', TransTitle );    end        TransError = "";    if is_set(TransTitle) then        if not is_set(Title) then            TransError = " " .. seterror( 'trans_missing_title' );        else            TransTitle = " " .. TransTitle;        end    end        Title = Title .. TransTitle;        if is_set(Title) then        if not is_set(TitleLink) and is_set(URL) then             Title = externallink( URL, Title ) .. TransError .. Format                   URL = "";            Format = "";        else            Title = Title .. TransError;        end    end        if is_set(Place) then        if sepc == '.' then            Place = " " .. wrap( 'written', Place ) .. sepc .. " ";        else            Place = " " .. substitute( cfg.messages['written']:lower(), {Place} ) .. sepc .. " ";        end    end        if is_set(Conference) then        if is_set(ConferenceURL) then            Conference = externallink( ConferenceURL, Conference );        end        Conference = " " .. Conference    elseif is_set(ConferenceURL) then        Conference = " " .. externallink( ConferenceURL, nil, ConferenceURLorigin );    end        if not is_set(Position) then        local Minutes = A['Minutes'];        if is_set(Minutes) then            Position = " " .. Minutes .. " " .. cfg.messages['minutes'];        else            local Time = A['Time'];            if is_set(Time) then                local TimeCaption = A['TimeCaption']                if not is_set(TimeCaption) then                    TimeCaption = cfg.messages['event'];                    if sepc ~= '.' then                        TimeCaption = TimeCaption:lower();                    end                end                Position = " " .. TimeCaption .. " " .. Time;            end        end    else        Position = " " .. Position;        At = '';    end        if not is_set(Page) then        if is_set(Pages) then            if is_set(Periodical) and                not inArray(config.CitationClass, {"encyclopaedia","web","book","news"}) then                  Pages = ": " .. Pages;            elseif config.CitationClass == "news" then -- LOCAL                Pages = ": (" .. Pages .. ")" -- LOCAL            elseif config.CitationClass == "book" then -- LOCAL                Pages = ": " .. Pages -- LOCAL            elseif tonumber(Pages) ~= nil then                Pages = sepc .." " .. PPrefix .. Pages .. PSuffix; -- LOCAL            else                Pages = sepc .." " .. PPPrefix .. Pages .. PPSuffix; -- LOCAL            end        end    else        if is_set(Periodical) and            not inArray(config.CitationClass, {"encyclopaedia","web","book","news"}) then            Page = ": " .. Page;        else        if config.CitationClass == "news" then -- LOCAL            Page = ": (" .. Page .. ")" -- LOCAL        elseif config.CitationClass == "news" then -- LOCAL            Page = ": " .. Page -- LOCAL        else -- LOCAL            Page = sepc .." " .. PPrefix .. Page .. PSuffix; -- LOCAL        end -- LOCAL        end    end        At = is_set(At) and (sepc .. " " .. At) or "";    Others = is_set(Others) and (sepc .. " " .. Others) or "";    TitleType = is_set(TitleType) and (", " .. TitleType) or ""; -- LOCAL    TitleNote = is_set(TitleNote) and (sepc .. " " .. TitleNote) or "";    -- LOCAL    if is_set(Language) then        local langNameInLang = mw.getContentLanguage():getCode()        local languageCode = Language:lower()        if languageCode:sub( 1, 3 ) == 'zh-' then            langNameInLang = languageCode        end        local languageName = mw.language.fetchLanguageName( languageCode, langNameInLang )        if languageName == '' then            languageName = Language        end        Language = " " .. wrap( 'language', languageName )    else Language = "" end    -- END LOCAL    Edition = is_set(Edition) and (" " .. wrap( 'edition', Edition )) or "";    Issue = is_set(Issue) and (" (" .. Issue .. ")") or "";    Series = is_set(Series) and (sepc .. " " .. Series) or "";    OrigYear = is_set(OrigYear) and (" [" .. OrigYear .. "]") or "";    Agency = is_set(Agency) and (sepc .. " " .. Agency) or "";        if is_set(Volume) then        if ( mw.ustring.len(Volume) > 4 )          then Volume = sepc .." " .. Volume;          else Volume = " <b>" .. hyphentodash(Volume) .. "</b>";        end    end        ------------------------------------ totally unrelated data    if is_set(Via) then Via = " " .. wrap( 'via', Via ); end    if is_set(AccessDate) then        local retrv_text = " " .. cfg.messages['retrieved']        if (sepc ~= ".") then retrv_text = retrv_text:lower() end        AccessDate = '<span class="reference-accessdate">' -- LOCAL .. sepc            .. substitute( retrv_text, {AccessDate} ) .. '</span>'    end        if is_set(SubscriptionRequired) then        SubscriptionRequired = sepc .. " " .. cfg.messages['subscription'];    end        if is_set(ID) then ID = sepc .." ".. ID; end        ID_list = buildidlist( ID_list, {DoiBroken = DoiBroken, ASINTLD = ASINTLD, IgnoreISBN = IgnoreISBN} );    if is_set(URL) then        URL = " " .. externallink( URL, nil, URLorigin );    end    if is_set(Quote) then        if Quote:sub(1,1) == '"' and Quote:sub(-1,-1) == '"' then            Quote = Quote:sub(2,-2);        end        Quote = sepc .." " .. wrap( 'quoted-text', Quote );         PostScript = "";    elseif PostScript:lower() == "none" then        PostScript = "";    end        local Archived    if is_set(ArchiveURL) then        if not is_set(ArchiveDate) then            ArchiveDate = seterror('archive_missing_date');        end        if "no" == DeadURL then            local arch_text = cfg.messages['archived'];            if sepc ~= "." then arch_text = arch_text:lower() end            Archived = substitute( cfg.messages['archived-not-dead'],                { externallink( ArchiveURL, arch_text ), ArchiveDate } );            if not is_set(OriginalURL) then                Archived = Archived .. " " .. seterror('archive_missing_url');                                           end        elseif is_set(OriginalURL) then            local arch_text = cfg.messages['archived-dead'];            if sepc ~= "." then arch_text = arch_text:lower() end            Archived = sepc .. " " .. substitute( arch_text,                { externallink( OriginalURL, cfg.messages['original'] ), ArchiveDate } );        else            local arch_text = cfg.messages['archived-missing'];            if sepc ~= "." then arch_text = arch_text:lower() end            Archived = substitute( arch_text,                 { seterror('archive_missing_url'), ArchiveDate } ); -- LOCAL        end    else        Archived = ""    end        local Lay    if is_set(LayURL) then        if is_set(LayDate) then LayDate = " (" .. LayDate .. ")" end        if is_set(LaySource) then             LaySource = " &ndash; ''" .. safeforitalics(LaySource) .. "''";        else            LaySource = "";        end        if sepc == '.' then            Lay = sepc .. " " .. externallink( LayURL, cfg.messages['lay summary'] ) .. LaySource .. LayDate        else            Lay = sepc .. " " .. externallink( LayURL, cfg.messages['lay summary']:lower() ) .. LaySource .. LayDate        end                else        Lay = "";    end        if is_set(Transcript) then        if is_set(TranscriptURL) then Transcript = externallink( TranscriptURL, Transcript ); end    elseif is_set(TranscriptURL) then        Transcript = externallink( TranscriptURL, nil, TranscriptURLorigin );    end        local Publisher;    if is_set(Periodical) and        not inArray(config.CitationClass, {"encyclopaedia","web","pressrelease"}) then        if is_set(PublisherName) then            if is_set(PublicationPlace) then                Publisher = PublicationPlace .. ": " .. PublisherName;            else                Publisher = PublisherName;              end        elseif is_set(PublicationPlace) then            Publisher= PublicationPlace;        else             Publisher = "";        end        --[[ LOCAL        if is_set(PublicationDate) then            if is_set(Publisher) then                Publisher = Publisher .. ", " .. wrap( 'published', PublicationDate );            else                Publisher = PublicationDate;            end        end        ]]        if is_set(Publisher) then            Publisher = " (" .. Publisher .. ")";        end    else        --[[ LOCAL        if is_set(PublicationDate) then            PublicationDate = " (" .. wrap( 'published', PublicationDate ) .. ")";        end        ]]        if is_set(PublisherName) then            if is_set(PublicationPlace) then                Publisher = sepc .. " " .. PublicationPlace .. ": " .. PublisherName -- LOCAL .. PublicationDate;            else                Publisher = sepc .. " " .. PublisherName -- LOCAL .. PublicationDate;              end                    elseif is_set(PublicationPlace) then             Publisher= sepc .. " " .. PublicationPlace -- LOCAL .. PublicationDate;        else             Publisher = '' -- LOCAL .. PublicationDate;        end    end        -- Several of the above rely upon detecting this as nil, so do it last.    if is_set(Periodical) then        if is_set(Title) or is_set(TitleNote) then             Periodical = sepc .. " " .. wrap( 'italic-title', Periodical )         else             Periodical = wrap( 'italic-title', Periodical )        end    end    -- Piece all bits together at last.  Here, all should be non-nil.    -- We build things this way because it is more efficient in LUA    -- not to keep reassigning to the same string variable over and over.    local tcommon    if inArray(config.CitationClass, {"journal","citation"}) and is_set(Periodical) then        if is_set(Others) then Others = Others .. sepc .. " " end        tcommon = safejoin( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series,             Edition, Publisher, Agency, Place, Position}, sepc ); -- LOCAL    else         tcommon = safejoin( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series,             Volume, Issue, Others, Edition, Publisher, Agency, Place, Position}, sepc ); -- LOCAL    end        if #ID_list > 0 then        ID_list = safejoin( { sepc .. " ",  table.concat( ID_list, sepc .. " " ), ID }, sepc );    else        ID_list = ID;    end        -- LOCAL    local xDate = Date    local pgtext = Page .. Pages .. At    if ( is_set(Periodical) and Date ~= '' and      not inArray(config.CitationClass, {"encyclopaedia","web"}) )      or ( inArray(config.CitationClass, {"book","news"}) ) then        if inArray(config.CitationClass, {"journal","citation"}) and ( Volume ~= '' or Issue ~= '' ) then            xDate = xDate .. ',' .. Volume .. Issue        end        xDate = xDate .. pgtext        pgtext = ''    end    if PublicationDate and PublicationDate ~= '' then        xDate = xDate .. ' (' .. PublicationDate .. ')'    end    if OrigYear ~= '' then        xDate = xDate .. OrigYear    end    if AccessDate ~= '' then        xDate = xDate .. ' ' .. AccessDate    end    if xDate ~= '' then        xDate = sepc .. ' ' .. xDate    end    -- END LOCAL        local idcommon = safejoin( { URL, xDate, ID_list, Archived, Via, SubscriptionRequired, Lay, Language, Quote }, sepc ); -- LOCAL    local text;    -- LOCAL local pgtext = Page .. Pages .. At;        if is_set(Authors) then        if is_set(Coauthors) then            Authors = Authors .. A['AuthorSeparator'] .. " " .. Coauthors        end    --[[ LOCAL        if is_set(Date) then            Date = " ("..Date..")" .. OrigYear .. sepc .. " "        else]]if string.sub(Authors,-1,-1) == sepc then            Authors = Authors .. " "        else            Authors = Authors .. sepc .. " "        end        if is_set(Editors) then            local ed_is_zh = is_zh( Editors ) -- LOCAL            local in_text = ed_is_zh and '' or "In " -- LOCAL            if (sepc ~= '.') then in_text = in_text:lower() end            if (string.sub(Editors,-1,-1) == sepc)                or ed_is_zh -- LOCAL                then Editors = in_text .. Editors .. " "                else Editors = in_text .. Editors .. sepc .. " "            end            -- LOCAL            if ed_is_zh then                Editors = Editors .. ' ' .. cfg.messages['in'] .. sepc .. ' '            end            -- END LOCAL        end        text = safejoin( {Authors, Chapter, Editors, tcommon }, sepc ); -- LOCAL        text = safejoin( {text, pgtext, idcommon}, sepc );    elseif is_set(Editors) then        --[[ LOCAL        if is_set(Date) then            if EditorCount <= 1 then                Editors = Editors .. ", " .. cfg.messages['editor'];            else                Editors = Editors .. ", " .. cfg.messages['editors'];            end            Date = " (" .. Date ..")" .. OrigYear .. sepc .. " "        else        ]]            if EditorCount <= 1 then                Editors = Editors .. " (" .. cfg.messages['editor'] .. ")" .. sepc .. " "            else                Editors = Editors .. " (" .. cfg.messages['editors'] .. ")" .. sepc .. " "            end        --[[ LOCAL        end        ]]        text = safejoin( {Chapter, Editors, tcommon}, sepc ); -- LOCAL        text = safejoin( {text, pgtext, idcommon}, sepc );    else        --[[ LOCAL        if is_set(Date) then            if ( string.sub(tcommon,-1,-1) ~= sepc )              then Date = sepc .." " .. Date .. OrigYear              else Date = " " .. Date .. OrigYear            end        end    ]]        if config.CitationClass=="journal" and is_set(Periodical) then            text = safejoin( {Chapter, Place, tcommon}, sepc );            text = safejoin( {text, pgtext, idcommon}, sepc ); -- LOCAL        else            text = safejoin( {Chapter, tcommon}, sepc ); -- LOCAL            text = safejoin( {text, pgtext, idcommon}, sepc );        end    end        if is_set(PostScript) and PostScript ~= sepc then        text = safejoin( {text, sepc}, sepc );  --Deals with italics, spaces, etc.        text = text:sub(1,-2); --Remove final seperator        end            text = safejoin( {text, PostScript}, sepc );    -- Now enclose the whole thing in a <span/> element    if not is_set(Year) then        if is_set(DateIn) then            Year = selectyear( DateIn );        elseif is_set(PublicationDate) then            Year = selectyear( PublicationDate );        end    end        local options = {};        if is_set(config.CitationClass) and config.CitationClass ~= "citation" then        options.class = "citation " .. config.CitationClass;    else        options.class = "citation";    end        if is_set(Ref) and Ref:lower() ~= "none" then        local id = Ref        if ( "harv" == Ref ) then            local names = {} --table of last names & year            if is_set(Authors) then                for i,v in ipairs(a) do                     names[i] = v.last                     if i == 4 then break end                end            elseif is_set(Editors) then                for i,v in ipairs(e) do                     names[i] = v.last                     if i == 4 then break end                                end            end            names[ #names + 1 ] = Year;            id = anchorid(names)        end        options.id = id;    end        if string.len(text:gsub("<span[^>/]*>.-</span>", ""):gsub("%b<>","")) <= 2 then        z.error_categories = {};        text = seterror('empty_citation');        z.message_tail = {};    end        if is_set(options.id) then         text = '<span id="' .. mw.uri.anchorEncode(options.id) ..'" class="' .. mw.text.nowiki(options.class) .. '">' .. text .. "</span>";    else        text = '<span class="' .. mw.text.nowiki(options.class) .. '">' .. text .. "</span>";    end            local empty_span = '<span style="display:none;">&nbsp;</span>';        -- Note: Using display: none on then COinS span breaks some clients.    local OCinS = '<span title="' .. OCinSoutput .. '" class="Z3988">' .. empty_span .. '</span>';    text = text .. OCinS;        if #z.message_tail ~= 0 then        text = text .. " ";        for i,v in ipairs( z.message_tail ) do            if is_set(v[1]) then                if i == #z.message_tail then                    text = text .. errorcomment( v[1], v[2] );                else                    text = text .. errorcomment( v[1] .. "; ", v[2] );                end            end        end    end        no_tracking_cats = no_tracking_cats:lower();    if inArray(no_tracking_cats, {"", "no", "false", "n"}) then        for _, v in ipairs( z.error_categories ) do            text = text .. '[[Category:' .. v ..']]';        end    end        return textend-- This is used by templates such as {{cite book}} to create the actual citation text.function z.citation(frame)    local pframe = frame:getParent()        local args = {};    local suggestions = {};    local error_text, error_state;    local config = {};    for k, v in pairs( frame.args ) do        config[k] = v;        args[k] = v;           end        for k, v in pairs( pframe.args ) do        if v ~= '' then            if not validate( k ) then                            error_text = "";                if type( k ) ~= 'string' then                    -- Exclude empty numbered parameters                    if v:match("%S+") ~= nil then                        error_text, error_state = seterror( 'text_ignored', {v}, true );                    end                elseif validate( k:lower() ) then                     error_text, error_state = seterror( 'parameter_ignored_suggest', {k, k:lower()}, true );                else                    if #suggestions == 0 then                        suggestions = mw.loadData( 'Module:Citation/CS1/Suggestions' );                    end                    if suggestions[ k:lower() ] ~= nil then                        error_text, error_state = seterror( 'parameter_ignored_suggest', {k, suggestions[ k:lower() ]}, true );                    else                        error_text, error_state = seterror( 'parameter_ignored', {k}, true );                    end                end                                  if error_text ~= '' then                    table.insert( z.message_tail, {error_text, error_state} );                end                            end            args[k] = v;        elseif args[k] ~= nil or (k == 'postscript') then            args[k] = v;        end            end            return citation0( config, args, frame ) -- LOCALendreturn z