Module:Citation/CS1

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' );

-- 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

-- 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 = tostring( name ); -- Normal arguments if whitelist.basic_arguments[ name ] then return true; end -- Arguments with numbers in them name = name:gsub( "%d+", "#" ); if whitelist.numbered_arguments[ name ] then return true; end -- Not found, argument not supported. return false end

-- Formats a comment for error trapping function errorcomment( content, hidden ) if hidden then return substitute( cfg.message_list['hidden-error'], { content } ); else return substitute( cfg.message_list['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 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 " " 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.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 " " options.suffix = options.suffix or "" return "" .. options.label .. "" .. sep .. "" .. 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.label .. ":" .. id; table.insert( z.error_categories, "Pages with DOIs inactive since " .. selectyear(inactive) ); inactive = " (" .. cfg.message_list['inactive'] .. " " .. inactive .. ")" else text = externallinkid({link = handler.link, label = handler.label,           prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode}) inactive = "" end if ( string.sub(id,1,3) ~= "10." ) then cat = seterror( 'bad_doi' ); end return text .. inactive .. cat end

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

--[[ Determines whether an URL string is valid

At present the only check is whether the string appears to be prefixed with a URI scheme. It is not determined whether the URI scheme is valid or whether the URL is otherwise well formed. ]] function checkurl( url_str ) if url_str:sub(1,2) == "//" then -- Protocol-less URLs return true; elseif url_str:match( "^[^/]*:" ) ~= nil then -- Look for ":" prefix and assume it is a URI scheme return true; else -- Anything else is an error return false; end end

-- 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  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,  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 = " " .. str; end if str:sub(-1,-1) == "'" then str = str .. " "; 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 string.gmatch(first, "%S+") do       table.insert(initials, string.sub(word,1,1)) -- Vancouver format does not include full stops. end return table.concat(initials) -- Vancouver format does not include spaces. end

-- Formats a list of people (e.g. authors / editors) function listpeople(control, people) local sep = control.sep; if sep:sub(-1,-1) ~= " " then sep = sep .. " " end local namesep = control.namesep local format = control.format local maximum = control.maximum local lastauthoramp = control.lastauthoramp; local text = {} local etal = false; for i,person in ipairs(people) do       if (person.last ~= nil or person.last ~= "") then local mask = person.mask local one if ( maximum ~= nil and i == maximum + 1 ) then etal = true; break; elseif (mask ~= nil) then local n = tonumber(mask) if (n ~= nil) then one = string.rep("&mdash;",n) else one = mask end else one = person.last local first = person.first if (first ~= nil and first ~= '') then if ( "vanc" == format ) then first = reducetoinitials(first) end one = one .. namesep .. first end if (person.link ~= nil and person.link ~= "") then one = "" .. one .. "" end end table.insert(text, one) end end local count = #text; if count > 1 and lastauthoramp ~= nil and lastauthoramp ~= "" and not etal then text[count-1] = text[count-1] .. " & " .. text[count]; text[count] = nil; end local result = table.concat(text, sep) -- construct list if etal then local etal_text = cfg.message_list['et al']; result = result .. " " .. etal_text; end -- if necessary wrap result in tag to format in Small Caps if ( "scap" == format ) then result = ' ' .. result .. ' ';   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( "#", "" ); 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 .. " ";       end if #error_list > 1 then error_str = error_str .. ", and "; else error_str = error_str .. " and "; end error_str = error_str .. " ";       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'];

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, 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, 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 ) 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 local this_page = mw.title.getCurrentTitle; OCinStitle = OCinStitle .. "&rfr_id=info:sid/" .. mw.site.server:match( "[^/]*$" ) .. ":"      .. this_page.prefixedText  -- end COinS data by page's non-encoded pagename

if (Periodical ~= nil and Periodical ~= "") and (Chapter == nil or Chapter == '') and (Title ~= nil and Title ~= "") then Chapter = Title ChapterLink = TitleLink TransChapter = TransTitle Title = nil TitleLink = nil TransTitle = nil end

-- Now perform various field substitutions. -- We also add leading spaces and surrounding markup and punctuation to the -- various parts of the citation, but only when they are non-nil. if ( Authors == nil ) then local Maximum = tonumber( 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 .. "–" .. Ended                else                    Month = "–"                end            end            Date = Month .. " " .. Date            local Day = args.day            if ( Day ~= nil ) then Date = Day .. " " .. Date end        end  -- so let's use the original version for now Date = Year if ( Date ~= nil and Date ~="") then local Month = A['Month'] if ( Month ~= nil and Month ~= "") then Date = Month .. " " .. Date local Day = A['Day'] if ( Day ~= nil ) then Date = Day .. " " .. Date end else Month = "" end else Date = "" end end if ( PublicationDate == Date or PublicationDate == Year ) then PublicationDate = nil end if( (Date == nil or Date == "") and PublicationDate ~= nil ) then Date = PublicationDate; PublicationDate = nil; end

-- Captures the value for Date prior to adding parens or other textual transformations local DateIn = Date if ( URL == nil or URL == '' ) and ( ChapterURL == nil or ChapterURL == '' ) and ( ArchiveURL == nil or ArchiveURL == '' ) and ( ConferenceURL == nil or ConferenceURL == '' ) and ( TranscriptURL == nil or TranscriptURL == '' ) then

-- Test if cite web is called without giving a URL if ( config.CitationClass == "web" ) then table.insert( z.message_tail, { seterror( 'cite_web_url', {}, true ) } ); end

-- Test if accessdate is given without giving a URL if ( AccessDate ~= nil and AccessDate ~= '' ) then table.insert( z.message_tail, { seterror( 'accessdate_missing_url', {}, true ) } ); AccessDate = nil; end -- Test if format is given without giving a URL if ( Format ~= nil and Format ~= '' ) then Format = Format .. seterror( 'format_missing_url' ); end end

-- Test if citation has no title if ( Chapter == nil or Chapter == "" ) and ( Title == nil or Title == "" ) and ( Periodical == nil or Periodical == "" ) and ( Conference == nil or Conference == "" ) and ( TransTitle == nil or TransTitle == "" ) and ( TransChapter == nil or TransChapter == "" ) then table.insert( z.message_tail, { seterror( 'citation_missing_title', {}, true ) } ); end

if ( Format ~= nil and Format ~="" ) then Format = " (" .. Format .. ")" else Format = "" end local OriginalURL = URL DeadURL = DeadURL:lower; if ( ArchiveURL and "" < ArchiveURL ) then if ( DeadURL ~= "no" ) then URL = ArchiveURL end end

if ( TransTitle and "" < TransTitle ) then TransTitle = " " .. substitute( cfg.message_list['trans-title'], { TransTitle } ) else TransTitle = "" end if ( TransChapter and "" < TransChapter ) then TransChapter = " " .. substitute( cfg.message_list['trans-title'], { TransChapter } ) else TransChapter = "" end -- Format chapter / article title if ( Chapter ~= nil and Chapter ~= "" ) then if ( ChapterLink and "" < ChapterLink ) then Chapter = "" .. Chapter .. "" end if ( Periodical and "" < Periodical ) and (Title ~= nil and Title ~= "" ) then Chapter = substitute( cfg.message_list['italic-title'], { (safeforitalics(Chapter)) } ); else Chapter = substitute( cfg.message_list['quoted-title'], { Chapter } ); end else Chapter = ""; end local TransError = "" if TransChapter ~= "" and Chapter == "" then TransError = " " .. seterror( 'trans_missing_chapter' ); end Chapter = Chapter .. TransChapter if Chapter ~= "" then if ( ChapterLink == nil ) then if ( ChapterURL and "" < ChapterURL ) then Chapter = externallink( ChapterURL, Chapter ) .. TransError; if URL == nil or URL == "" then Chapter = Chapter .. Format; Format = ""; end elseif ( URL and "" < URL ) then Chapter = externallink( URL, Chapter ) .. TransError .. Format; URL = nil Format = "" else Chapter = Chapter .. TransError; end elseif ChapterURL ~= nil and ChapterURL ~= "" then Chapter = Chapter .. " " .. externallink( ChapterURL ) .. TransError; else Chapter = Chapter .. TransError; end Chapter = Chapter .. sepc .. " " -- with end-space elseif ChapterURL ~= nil and ChapterURL ~= "" then Chapter = " " .. externallink( ChapterURL ) .. sepc .. " ";   end -- Format main title. if ( Title and "" < Title ) then if ( TitleLink and "" < TitleLink ) then Title = "" .. Title .. "" end if ( Periodical and "" < Periodical ) then Title = substitute( cfg.message_list['quoted-title'], { Title } ); elseif ( config.CitationClass == "web"               or config.CitationClass == "news"                 or config.CitationClass == "pressrelease" ) and Chapter == "" then Title = substitute( cfg.message_list['quoted-title'], { Title } ); else Title = substitute( cfg.message_list['italic-title'], { (safeforitalics(Title)) } ); end else Title = ""; end local TransError = ""; if TransTitle ~= "" and Title == "" then TransError = " " .. seterror( 'trans_missing_title' ); end Title = Title .. TransTitle if Title ~= "" then if ( TitleLink == nil and URL and "" < URL ) then Title = externallink( URL, Title ) .. TransError .. Format URL = nil Format = '' else Title = Title .. TransError; end end

if ( Place ~= nil and Place ~= "" ) then if sepc == '.' then Place = " " .. substitute( cfg.message_list['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 = " " .. substitute( cfg.message_list['language'], {Language} ) else Language = "" end if ( Edition ~= nil and Edition ~="" ) then Edition = " " .. substitute( cfg.message_list['edition'], {Edition} ) else Edition = "" end if ( Volume ~= nil and Volume ~="" ) then if ( mw.ustring.len(Volume) > 4 ) then Volume = sepc .." " .. Volume else Volume = " '''" .. hyphentodash(Volume) .. "'''"       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 = " " .. substitute( cfg.message_list['via'], {Via} ) else Via = "" end if ( AccessDate ~= nil and AccessDate ~="" ) then local retrv_text = " " .. cfg.message_list['retrieved'] if (sepc ~= ".") then retrv_text = retrv_text:lower end AccessDate = ' ' .. sepc .. substitute( retrv_text, {AccessDate} ) .. ' '   else AccessDate = "" end if ( SubscriptionRequired ~= nil and        SubscriptionRequired ~= "" ) then SubscriptionRequired = sepc .. " " .. cfg.message_list['subscription']; else SubscriptionRequired = "" end if ( ID ~= nil and ID ~="") then ID = sepc .." ".. ID else ID="" end

ID_list = buildidlist( ID_list, {DoiBroken = DoiBroken, ASINTLD = ASINTLD, IgnoreISBN = IgnoreISBN} );

if ( URL ~= nil and URL ~="") then URL = " " .. externallink( URL, URL ); local error_text = seterror( 'bare_url_missing_title' ); if config.CitationClass == "web" then URL = URL .. " " .. seterror( 'cite_web_title' ); else URL = URL .. error_text; end else URL = "" end

if ( Quote and Quote ~="" ) then if Quote:sub(1,1) == '"' and Quote:sub(-1,-1) == '"' then Quote = Quote:sub(2,-2); end Quote = sepc .." " .. substitute( cfg.message_list['quoted-text'], { 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'); end if ( "no" == DeadURL ) then local arch_text = cfg.message_list['archived']; if (sepc ~= ".") then arch_text = arch_text:lower 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 = arch_text:lower 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 = " (" .. LayDate .. ")" else LayDate = "" end if ( LaySource ~= nil ) then LaySource = " – ''" .. safeforitalics(LaySource) .. "''"        else LaySource = "" end if sepc == '.' then Lay = sepc .. " [" .. LaySummary .. " " .. cfg.message_list['lay summary'] .. "]" .. LaySource .. LayDate else Lay = sepc .. " [" .. LaySummary .. " " .. cfg.message_list['lay summary']:lower .. "]" .. LaySource .. LayDate end else Lay = "" end if ( nil ~= Transcript and "" ~= Transcript ) then if ( TranscriptURL ~= nil ) then Transcript = externallink( TranscriptURL, Transcript ) end elseif TranscriptURL ~= nil and TranscriptURL ~= "" then Transcript = externallink( TranscriptURL ) else Transcript = "" end local Publisher = "" if ( Periodical and Periodical ~= "" and        config.CitationClass ~= "encyclopaedia" and         config.CitationClass ~= "web" and         config.CitationClass ~= "pressrelease" ) then if ( PublisherName ~= nil and PublisherName ~="" ) then if (PublicationPlace ~= nil and PublicationPlace ~= '') then Publisher = PublicationPlace .. ": " .. PublisherName; else Publisher = PublisherName; end elseif (PublicationPlace ~= nil and PublicationPlace ~= '') then Publisher= PublicationPlace; else Publisher = ""; end if ( PublicationDate and PublicationDate ~="" ) then if Publisher ~= '' then Publisher = Publisher .. ", " .. substitute( cfg.message_list['published'], {PublicationDate} ); else Publisher = PublicationDate; end end if Publisher ~= "" then Publisher = " (" .. Publisher .. ")"; end else if ( PublicationDate and PublicationDate ~="" ) then PublicationDate = " (" .. substitute( cfg.message_list['published'], {PublicationDate} ) .. ")" else PublicationDate = "" end if ( PublisherName ~= nil and PublisherName ~="" ) then if (PublicationPlace ~= nil and PublicationPlace ~= '') then Publisher = sepc .. " " .. PublicationPlace .. ": " .. PublisherName .. PublicationDate; else Publisher = sepc .. " " .. PublisherName .. PublicationDate; end elseif (PublicationPlace ~= nil and PublicationPlace ~= '') then Publisher= sepc .. " " .. PublicationPlace .. PublicationDate; else Publisher = PublicationDate; end end -- Several of the above rely upon detecting this as nil, so do it last. if ( Periodical ~= nil and Periodical ~="" ) then if ( Title and Title ~= "" ) or ( TitleNote and TitleNote ~= "" ) then Periodical = sepc .. " " .. substitute( cfg.message_list['italic-title'], { (safeforitalics(Periodical)) } ) else Periodical = substitute( cfg.message_list['italic-title'], { (safeforitalics(Periodical)) } ) end else Periodical = "" end

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

local tcommon if ( ( (config.CitationClass == "journal") or (config.CitationClass == "citation") ) and         Periodical ~= "" ) then if (Others ~= "") then Others = Others .. sepc .. " " end tcommon = safejoin( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Series,            Language, Edition, Publisher, Agency, Volume, Issue, Position}, sepc ); else tcommon = safejoin( {Title, TitleNote, Conference, Periodical, Format, TitleType, Series, Language,            Volume, Issue, Others, Edition, Publisher, Agency, Position}, sepc ); end if #ID_list > 0 then ID_list = safejoin( { sepc .. " ", table.concat( ID_list, sepc .. " " ), ID }, sepc ); else ID_list = ID; end local idcommon = safejoin( { ID_list, URL, Archived, AccessDate, Via, SubscriptionRequired, Lay, Quote }, sepc );

local text local pgtext = Page .. Pages .. At   if ( "" ~= Authors ) then if (Coauthors ~= "") then Authors = Authors .. 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 = " in " if (sepc == '.') then in_text = " In " end if (string.sub(Editors,-1,-1) == sepc) then Editors = in_text .. Editors .. " "               else Editors = in_text .. Editors .. sepc .. " "           end end text = safejoin( {Authors, Date, Chapter, Place, Editors, tcommon }, sepc ); text = safejoin( {text, pgtext, idcommon}, sepc ); elseif ( "" ~= Editors) then if ( "" ~= Date ) then if EditorCount <= 1 then Editors = Editors .. ", " .. cfg.message_list['editor']; else Editors = Editors .. ", " .. cfg.message_list['editors']; end Date = " (" .. Date ..")" .. OrigYear .. sepc .. " "       else if EditorCount <= 1 then Editors = Editors .. " (" .. cfg.message_list['editor'] .. ")" .. sepc .. " "           else Editors = Editors .. " (" .. cfg.message_list['editors'] .. ")" .. sepc .. " "           end end text = safejoin( {Editors, Date, Chapter, Place, tcommon}, sepc ); text = safejoin( {text, pgtext, idcommon}, sepc ); else if ( "" ~= Date ) then if ( string.sub(tcommon,-1,-1) ~= sepc ) then Date = sepc .." " .. Date .. OrigYear else Date = " " .. Date .. OrigYear end end -- endif ""~=Date if ( config.CitationClass=="journal" and Periodical ) then text = safejoin( {Chapter, Place, tcommon}, sepc ); text = safejoin( {text, pgtext, Date, idcommon}, sepc ); else text = safejoin( {Chapter, Place, tcommon, Date}, sepc ); text = safejoin( {text, pgtext, idcommon}, sepc ); end end if PostScript ~= '' and PostScript ~= nil and PostScript ~= sepc then text = safejoin( {text, sepc}, sepc ); --Deals with italics, spaces, etc.        text = text:sub(1,-2); --Remove final seperator end text = safejoin( {text, PostScript}, sepc );

-- Now enclose the whole thing in a 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("/]*>.- ", ""):gsub("%b<>","")) <= 2 then z.error_categories = {}; text = seterror('empty_citation'); z.message_tail = {}; end if options.id ~= nil then text = '' .. text .. " ";   else text = '' .. text .. " ";   end

local empty_span = ' '; -- Note: Using display: none on then COinS span breaks some clients. local OCinS = '' .. empty_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 if no_tracking_cats == nil then for _, v in ipairs( z.error_categories ) do           text = text .. '';       end end return text end

-- This is used by templates such as 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