Jump to content

Module:Citation/CS1

ꯋꯤꯀꯤꯄꯦꯗꯤꯌꯥ ꯗꯒꯤ


This module and associated sub-modules support the Citation Style 1 and Citation Style 2 citation templates. In general, it is not intended to be called directly, but is called by one of the core CS1 and CS2 templates.

These files comprise the module support for cs1|2 citation templates:

cs1 | cs2 modules
  live sandbox description
Gold padlock Module:Citation/CS1 Module:Citation/CS1/sandbox [edit] Rendering and support functions
Module:Citation/CS1/Configuration Module:Citation/CS1/Configuration/sandbox [edit] Translation tables; error and identifier handlers
Module:Citation/CS1/Whitelist Module:Citation/CS1/Whitelist/sandbox [edit] List of active, deprecated, and obsolete cs1|2 parameters
Module:Citation/CS1/Date validation Module:Citation/CS1/Date validation/sandbox [edit] Date format validation functions
Module:Citation/CS1/Identifiers Module:Citation/CS1/Identifiers/sandbox [edit] Functions that support the named identifiers (isbn, doi, pmid, etc)
Module:Citation/CS1/Utilities Module:Citation/CS1/Utilities/sandbox [edit] Common functions and tables
Module:Citation/CS1/COinS Module:Citation/CS1/COinS/sandbox [edit] Functions that render a cs1|2 template's metadata
Module:Citation/CS1/styles.css Module:Citation/CS1/sandbox/styles.css [edit] CSS styles applied to the cs1|2 templates
Silver padlock Module:Citation/CS1/Suggestions Module:Citation/CS1/Suggestions/sandbox [edit] List that maps common erroneous parameter names to valid parameter names


Other documentation:


local devdigit = require('Module:EnDigitConverter') local z = {  error_categories = {};  error_ids = {};  message_tail = {}; }  -- Include translation message hooks, ID and error handling configuration settings. local cfg = mw.loadData( 'Module: Citation/CS1/Configuration' );  -- Contains a list of all recognized parameters local whitelist = mw.loadData( 'Module: Citation/CS1/Whitelist' );  -- Whether variable is set or not function is_set( var )  return not (var == nil or var == ''); end  -- First set variable or nil if none function first_set(...)  local list = {...};  for _, var in pairs(list) do  if is_set( var ) then  return var;  end  end end  -- Whether needle is in haystack function inArray( needle, haystack )  if needle == nil then  return false;  end  for n,v in ipairs( haystack ) do  if v == needle then  return n;  end  end  return false; end  -- Populates numbered arguments in a message string using an argument table. function substitute( msg, args )  return args and tostring( mw.message.newRawMessage( msg, args ) ) or msg; end  -- Wraps a string using a message_list configuration taking one argument function wrap( key, str, lower )  if not is_set( str ) then  return "";  elseif inArray( key, { 'italic-title', 'trans-italic-title' } ) then  str = safeforitalics( str );  end  if lower == true then  return substitute( cfg.messages[key]:lower(), {str} );  else  return substitute( cfg.messages[key], {str} );  end  end  --[[ Argument wrapper. This function provides support for argument  mapping defined in the configuration file so that multiple names can be transparently aliased to single internal variable. ]] function argument_wrapper( args )  local origin = {};    return setmetatable({  ORIGIN = function( self, k )  local dummy = self[k]; --force the variable to be loaded.  return origin[k];  end  },  {  __index = function ( tbl, k )  if origin[k] ~= nil then  return nil;  end    local args, list, v = args, cfg.aliases[k];    if type( list ) == 'table' then  v, origin[k] = selectone( args, list, 'redundant_parameters' );  if origin[k] == nil then  origin[k] = ''; -- Empty string, not nil  end  elseif list ~= nil then  v, origin[k] = args[list], list;  else  -- maybe let through instead of raising an error?  -- v, origin[k] = args[k], k;  error( cfg.messages['unknown_argument_map'] );  end    -- Empty strings, not nil;  if v == nil then  v = cfg.defaults[k] or '';  origin[k] = '';  end    tbl = rawset( tbl, k, v );  return v;  end,  }); end  -- Checks that parameter name is valid using the whitelist function validate( name )  name = tostring( name );    -- Normal arguments  if whitelist.basic_arguments[ name ] then  return true;  end    -- Arguments with numbers in them  name = name:gsub( "%d+", "#" );  if whitelist.numbered_arguments[ name ] then  return true;  end    -- Not found, argument not supported.  return false end  -- Formats a comment for error trapping function errorcomment( content, hidden )  return wrap( hidden and 'hidden-error' or 'visible-error', content ); end  --[[ Sets an error condition and returns the appropriate error message. The actual placement of the error message in the output is the responsibility of the calling function. ]] function seterror( error_id, arguments, raw, prefix, suffix )  local error_state = cfg.error_conditions[ error_id ];    prefix = prefix or "";  suffix = suffix or "";    if error_state == nil then  error( cfg.messages['undefined_error'] );  elseif is_set( error_state.category ) then  table.insert( z.error_categories, error_state.category );  end    local message = substitute( error_state.message, arguments );    message = message .. " ([[" .. cfg.messages['help page link'] ..   "#" .. error_state.anchor .. "|" ..  cfg.messages['help page label'] .. "]])";    z.error_ids[ error_id ] = true;  if inArray( error_id, { 'bare_url_missing_title', 'trans_missing_title' } )  and z.error_ids['citation_missing_title'] then  return '', false;  end    message = table.concat({ prefix, message, suffix });    if raw == true then  return message, error_state.hidden;  end     return errorcomment( message, error_state.hidden ); end  -- Formats a wiki style external link function externallinkid(options)  local url_string = options.id;  if options.encode == true or options.encode == nil then  url_string = mw.uri.encode( url_string );  end  return mw.ustring.format( '[[%s|%s]]%s[%s%s%s %s]',  options.link, options.label, options.separator or "&nbsp;",  options.prefix, url_string, options.suffix or "",  mw.text.nowiki(options.id)  ); end  -- Formats a wiki style internal link function internallinkid(options)  return mw.ustring.format( '[[%s|%s]]%s[[%s%s%s|%s]]',  options.link, options.label, options.separator or "&nbsp;",  options.prefix, options.id, options.suffix or "",  mw.text.nowiki(options.id)  ); end  -- Format an external link with error checking function externallink( URL, label, source )  local error_str = "";  if not is_set( label ) then  label = URL;  if is_set( source ) then  error_str = seterror( 'bare_url_missing_title', { wrap( 'parameter', source ) }, false, " " );  else  error( cfg.messages["bare_url_no_origin"] );  end   end  if not checkurl( URL ) then  error_str = seterror( 'bad_url', {}, false, " " ) .. error_str;  end  return table.concat({ "[", URL, " ", safeforurl( label ), "]", error_str }); end  -- Formats a link to Amazon function amazon(id, domain)  if not is_set(domain) then   domain = "com"  elseif ( "jp" == domain or "uk" == domain ) then  domain = "co." .. domain  end  local handler = cfg.id_handlers['ASIN'];  return externallinkid({link = handler.link,  label=handler.label , prefix="//www.amazon."..domain.."/dp/",id=id,  encode=handler.encode, separator = handler.separator}) end  --[[ Formats a PMC and checks for embargoed articles. The embargo parameter takes a date for a value. If the embargo date is in the futue the PMC identifier will not be linked to the article. If the embargo specifies a date in the past, or if it is empty or omitted, then the PMC identifier is linked to the article through the link at cfg.id_handlers['PMC'].link.  The {{citation/core}} version of {{cite journal}} links the citation title (if url parameter is empty) when embargo date is in the past or when embargo parameter is missing or empty. That behavior is inconsistent with the behavior of other identifiers used in CS1 and is not supported here. ]] function pmc(id, embargo) local handler = cfg.id_handlers['PMC'];   local text;  if is_set(embargo) then local lang = mw.getContentLanguage(); local good1, embargo_date, good2, todays_date; good1, embargo_date = pcall( lang.formatDate, lang, 'U', embargo ); good2, todays_date = pcall( lang.formatDate, lang, 'U' );  if good1 and good2 and tonumber( embargo_date ) < tonumber( todays_date ) then--if embargo date is in the past then text = externallinkid({link = handler.link, label = handler.label,--ok to link to article prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode}) else text="[[" .. handler.link .. "|" .. handler.label .. "]]:" .. handler.separator .. id;--still embargoed so no external link end else text = externallinkid({link = handler.link, label = handler.label,--no embargo date, ok to link to article prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode}) end return text 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, "".. selectyear(inactive) .. " থেকে নিষ্ক্রিয় ডিওআইসহ পাতা" );   inactive = " (" .. cfg.messages['inactive'] .. " " .. inactive .. ")"  else   text = externallinkid({link = handler.link, label = handler.label,  prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode})  inactive = ""   end  if ( string.sub(id,1,3) ~= "10." ) then   cat = seterror( 'bad_doi' );  end  return text .. inactive .. cat  end  -- Formats an OpenLibrary link, and checks for associated errors. function openlibrary(id)  local code = id:sub(-1,-1)  local handler = cfg.id_handlers['OL'];  if ( code == "A" ) then  return externallinkid({link=handler.link, label=handler.label,  prefix="http://openlibrary.org/authors/OL",id=id, separator=handler.separator,  encode = handler.encode})  elseif ( code == "M" ) then  return externallinkid({link=handler.link, label=handler.label,  prefix="http://openlibrary.org/books/OL",id=id, separator=handler.separator,  encode = handler.encode})  elseif ( code == "W" ) then  return externallinkid({link=handler.link, label=handler.label,  prefix= "http://openlibrary.org/works/OL",id=id, separator=handler.separator,  encode = handler.encode})  else  return externallinkid({link=handler.link, label=handler.label,  prefix= "http://openlibrary.org/OL",id=id, separator=handler.separator,  encode = handler.encode}) ..   ' ' .. seterror( 'bad_ol' );  end end  --[[ Validate and format an issn. This code fixes the case where an editor has included an ISSN in the citation but has separated the two groups of four digits with a space. When that condition occurred, the resulting link looked like this:  |issn=0819 4327 gives: [http://www.worldcat.org/issn/0819 4327 0819 4327] -- can't have spaces in an external link  This code now prevents that by inserting a hyphen at the issn midpoint. It also validates the issn for length and makes sure that the checkdigit agrees with the calculated value. Incorrect length (8 digits), characters other than 0-9 and X, or checkdigit / calculated value mismatch will all cause a check issn error message. The issn is always displayed with a hyphen, even if the issn was given as a single group of 8 digits. ]] function issn(id) local clean_issn; local issn_copy = id;-- save a copy of unadulterated issn; use this version for display if issn does not validate local handler = cfg.id_handlers['ISSN']; local temp = 0; local text; local valid_issn = true;  id=id:gsub( "[%s-–]", "" );-- strip spaces, hyphens, and ndashes from the issn clean_issn=string.sub( id, 1, 4 ) .. "-" .. string.sub( id, 5 );-- make a copy with a hyphen after 4 digits; use this version for display if issn validates  if 8 ~= id:len() or nil == id:match( "^%d*X?$" ) then-- validate the issn: 8 didgits long, containing only 0-9 or X in the last position valid_issn=false;-- wrong length or improper character else id = { id:byte(1, 8) };-- table of individual bytes for i, v in ipairs( id ) do-- loop through all of the byte an calculate the checksum if v == string.byte( "X" ) then-- if checkdigit is X temp = temp + 10*( 9 - i );-- it represents 10 decimal else temp = temp + tonumber( string.char(v) )*(9-i); end end valid_issn = temp % 11 == 0;-- checksum must be zero for valid issn end  if true == valid_issn then id = clean_issn;-- if valid, use the cleaned-up version for the display else id = issn_copy;-- if not valid, use the show the invalid issn with error message end  text = externallinkid({link = handler.link, label = handler.label, prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode})   if false == valid_issn then text = text .. ' ' .. seterror( 'bad_issn' )-- add an error message if the issn is invalid end   return text end   --[[ Determines whether an URL string is valid  At present the only check is whether the string appears to  be prefixed with a URI scheme. It is not determined whether  the URI scheme is valid or whether the URL is otherwise well  formed. ]] function checkurl( url_str )  -- Protocol-relative or URL scheme  return url_str:sub(1,2) == "//" or url_str:match( "^[^/]*:" ) ~= nil; end  -- Removes irrelevant text and dashes from ISBN number -- Similar to that used for Special:BookSources function cleanisbn( isbn_str )  return isbn_str:gsub( "[^-0-9X]", "" ); end  -- Determines whether an ISBN string is valid function checkisbn( isbn_str )  isbn_str = cleanisbn( isbn_str ):gsub( "-", "" );  local len = isbn_str:len();    if len ~= 10 and len ~= 13 then  return false;  end    local temp = 0;  if len == 10 then  if isbn_str:match( "^%d*X?$" ) == nil then return false; end  isbn_str = { isbn_str:byte(1, len) };  for i, v in ipairs( isbn_str ) do  if v == string.byte( "X" ) then  temp = temp + 10*( 11 - i );  else  temp = temp + tonumber( string.char(v) )*(11-i);  end  end  return temp % 11 == 0;  else  if isbn_str:match( "^%d*$" ) == nil then return false; end  isbn_str = { isbn_str:byte(1, len) };  for i, v in ipairs( isbn_str ) do  temp = temp + (3 - 2*(i % 2)) * tonumber( string.char(v) );  end  return temp % 10 == 0;  end end  -- Gets the display text for a wikilink like [[A|B]] or [[B]] gives B function removewikilink( str )  return (str:gsub( "%[%[([^%[%]]*)%]%]", function(l)  return l:gsub( "^[^|]*|(.*)$", "%1" ):gsub("^%s*(.-)%s*$", "%1");  end)); end  -- Escape sequences for content that will be used for URL descriptions function safeforurl( str )  if str:match( "%[%[.-%]%]" ) ~= nil then   table.insert( z.message_tail, { seterror( 'wikilink_in_url', {}, true ) } );  end    return str:gsub( '[%[%]\n]', {   ['['] = '&#91;',  [']'] = '&#93;',  ['\n'] = ' ' } ); end  -- Converts a hyphen to a dash function hyphentodash( str )  if not is_set(str) or str:match( "[%[%]{}<>]" ) ~= nil then  return str;  end   return str:gsub( '-', '–' ); end  -- Protects a string that will be wrapped in wiki italic markup '' ... '' function safeforitalics( str )  --[[ Note: We can not use <i> for italics, as the expected behavior for  italics specified by ''...'' in the title is that they will be inverted  (i.e. unitalicized) in the resulting references. In addition, <i> and ''  tend to interact poorly under Mediawiki's HTML tidy. ]]    if not is_set(str) then  return str;  else  if str:sub(1,1) == "'" then str = "<span ></span>" .. str; end  if str:sub(-1,-1) == "'" then str = str .. "<span ></span>"; end    -- Remove newlines as they break italics.  return str:gsub( '\n', ' ' );  end end  --[[ Joins a sequence of strings together while checking for duplicate separation characters. ]] function safejoin( tbl, duplicate_char )  --[[  Note: we use string functions here, rather than ustring functions.    This has considerably faster performance and should work correctly as   long as the duplicate_char is strict ASCII. The strings  in tbl may be ASCII or UTF8.  ]]    local str = '';  local comp = '';  local end_chr = '';  local trim;  for _, value in ipairs( tbl ) do  if value == nil then value = ''; end    if str == '' then  str = value;  elseif value ~= '' then  if value:sub(1,1) == '<' then  -- Special case of values enclosed in spans and other markup.  comp = value:gsub( "%b<>", "" );  else  comp = value;  end    if comp:sub(1,1) == duplicate_char then  trim = false;  end_chr = str:sub(-1,-1);  -- str = str .. "<HERE(enchr=" .. end_chr.. ")"  if end_chr == duplicate_char then  str = str:sub(1,-2);  elseif end_chr == "'" then  if str:sub(-3,-1) == duplicate_char .. "''" then  str = str:sub(1, -4) .. "''";  elseif str:sub(-5,-1) == duplicate_char .. "]]''" then  trim = true;  elseif str:sub(-4,-1) == duplicate_char .. "]''" then  trim = true;  end  elseif end_chr == "]" then  if str:sub(-3,-1) == duplicate_char .. "]]" then  trim = true;  elseif str:sub(-2,-1) == duplicate_char .. "]" then  trim = true;  end  elseif end_chr == " " then  if str:sub(-2,-1) == duplicate_char .. " " then  str = str:sub(1,-3);  end  end   if trim then  if value ~= comp then   local dup2 = duplicate_char;  if dup2:match( "%A" ) then dup2 = "%" .. dup2; end    value = value:gsub( "(%b<>)" .. dup2, "%1", 1 )  else  value = value:sub( 2, -1 );  end  end  end  str = str .. value;  end  end  return str; end   --[[ Return the year portion of a date string, if possible.  Returns empty string if the argument can not be interpreted as a year. ]] function selectyear( str )  -- Is the input a simple number?  local num = tonumber( str );   if num ~= nil and num > 0 and num < 2100 and num == math.floor(num) then  return str;  else  -- Use formatDate to interpret more complicated formats  local lang = mw.getContentLanguage();  local good, result;  good, result = pcall( lang.formatDate, lang, 'Y', str );  if good then   return result;  else  -- extract year if the date uses seasons  str=string.lower (str);  local seasons={"winter", "spring", "summer", "fall", "autumn"};  local date_string_split=mw.text.split (str, "[%s%-/–]"); -- split date string into parts; white space, hyphen, forward slash, and ndash are allowed separators  local has_season=false;    for n,season_value in ipairs(seasons) do -- for each season ...  for n,split_value in ipairs(date_string_split) do -- ... loop through date string values   if split_value == season_value then -- does the split value match the season value?  if has_season==false then -- found one. if this one is the first we've found ...  has_season=true; -- ... remember that we found a season  end  elseif has_season==true then -- if split_value isn't a season, and we've previously found a season ...  num = tonumber( split_value ); -- ... convert current split value to a number if we can  if num ~= nil and num > 0 and num < 2100 and num == math.floor(num) then -- if it's a suitable number  return tostring( num ); -- return it as a string  end -- if num  end -- if string.find  end -- for split value loop  end -- season value loop  end -- if good  end -- if num end -- selectyear  -- Attempts to convert names to initials. function reducetoinitials(first)  local initials = {}  for word in string.gmatch(first, "%S+") do  table.insert(initials, string.sub(word,1,1)) -- Vancouver format does not include full stops.  end  return table.concat(initials) -- Vancouver format does not include spaces. end  -- Formats a list of people (e.g. authors / editors)  function listpeople(control, people)  local sep = control.sep;  local namesep = control.namesep  local format = control.format  local maximum = control.maximum  local lastauthoramp = control.lastauthoramp;  local text = {}  local etal = false;    if sep:sub(-1,-1) ~= " " then sep = sep .. " " end  if maximum ~= nil and maximum < 1 then return "", 0; end    for i,person in ipairs(people) do  if is_set(person.last) then  local mask = person.mask  local one  local sep_one = sep;  if maximum ~= nil and i > maximum then  etal = true;  break;  elseif (mask ~= nil) then  local n = tonumber(mask)  if (n ~= nil) then  one = string.rep("&mdash;",n)  else  one = mask;  sep_one = " ";  end  else  one = person.last  local first = person.first  if is_set(first) then   if ( "vanc" == format ) then first = reducetoinitials(first) end  one = one .. namesep .. first   end  if is_set(person.link) then one = "[[" .. person.link .. "|" .. one .. "]]" end  end  table.insert( text, one )  table.insert( text, sep_one )  end  end   local count = #text / 2;  if count > 0 then   if count > 1 and is_set(lastauthoramp) and not etal then  text[#text-2] = " & ";  end  text[#text] = nil;   end    local result = table.concat(text) -- construct list  if etal then   local etal_text = cfg.messages['et al'];  result = result .. " " .. etal_text;  end    -- if necessary wrap result in <span> tag to format in Small Caps  if ( "scap" == format ) then result =   '<span class="smallcaps" style="font-variant:small-caps">' .. result .. '</span>';  end   return result, count end  -- Generates a CITEREF anchor ID. function anchorid( options )  return "CITEREF" .. table.concat( options ); end  -- Gets name list from the input arguments function extractnames(args, list_name)  local names = {};  local i = 1;  local last;    while true do  last = selectone( args, cfg.aliases[list_name .. '-Last'], 'redundant_parameters', i );  if not is_set(last) then  -- just in case someone passed in an empty parameter  break;  end  names[i] = {  last = last,  first = selectone( args, cfg.aliases[list_name .. '-First'], 'redundant_parameters', i ),  link = selectone( args, cfg.aliases[list_name .. '-Link'], 'redundant_parameters', i ),  mask = selectone( args, cfg.aliases[list_name .. '-Mask'], 'redundant_parameters', i )  };  i = i + 1;  end  return names; end  -- Populates ID table from arguments using configuration settings function extractids( args )  local id_list = {};  for k, v in pairs( cfg.id_handlers ) do   v = selectone( args, v.parameters, 'redundant_parameters' );  if is_set(v) then id_list[k] = v; end  end  return id_list; end  -- Takes a table of IDs and turns it into a table of formatted ID outputs. function buildidlist( id_list, options )  local new_list, handler = {};    function fallback(k) return { __index = function(t,i) return cfg.id_handlers[k][i] end } end;    for k, v in pairs( id_list ) do  -- fallback to read-only cfg  handler = setmetatable( { ['id'] = v }, fallback(k) );    if handler.mode == 'external' then  table.insert( new_list, {handler.label, externallinkid( handler ) } );  elseif handler.mode == 'internal' then  table.insert( new_list, {handler.label, internallinkid( handler ) } );  elseif handler.mode ~= 'manual' then  error( cfg.messages['unknown_ID_mode'] );  elseif k == 'DOI' then  table.insert( new_list, {handler.label, doi( v, options.DoiBroken ) } );  elseif k == 'ASIN' then  table.insert( new_list, {handler.label, amazon( v, options.ASINTLD ) } );   elseif k == 'OL' then  table.insert( new_list, {handler.label, openlibrary( v ) } );  elseif k == 'PMC' then  table.insert( new_list, {handler.label, pmc( v, options.Embargo ) } );  elseif k == 'ISSN' then  table.insert( new_list, {handler.label, issn( v ) } );  elseif k == 'ISBN' then  local ISBN = internallinkid( handler );  if not checkisbn( v ) and not is_set(options.IgnoreISBN) then  ISBN = ISBN .. seterror( 'bad_isbn', {}, false, " ", "" );  end  table.insert( new_list, {handler.label, ISBN } );   else  error( cfg.messages['unknown_manual_ID'] );  end  end    function comp( a, b )  return a[1] < b[1];  end    table.sort( new_list, comp );  for k, v in ipairs( new_list ) do  new_list[k] = v[2];  end    return new_list; end   -- Chooses one matching parameter from a list of parameters to consider -- Generates an error if more than one match is present. function selectone( args, possible, error_condition, index )  local value = nil;  local selected = '';  local error_list = {};    if index ~= nil then index = tostring(index); end    -- Handle special case of "#" replaced by empty string  if index == '1' then  for _, v in ipairs( possible ) do  v = v:gsub( "#", "" );  if is_set(args[v]) then  if value ~= nil and selected ~= v then  table.insert( error_list, v );  else  value = args[v];  selected = v;  end  end  end   end    for _, v in ipairs( possible ) do  if index ~= nil then  v = v:gsub( "#", index );  end  if is_set(args[v]) then  if value ~= nil and selected ~= v then  table.insert( error_list, v );  else  value = args[v];  selected = v;  end  end  end    if #error_list > 0 then  local error_str = "";  for _, k in ipairs( error_list ) do  if error_str ~= "" then error_str = error_str .. cfg.messages['parameter-separator'] end  error_str = error_str .. wrap( 'parameter', k );  end  if #error_list > 1 then  error_str = error_str .. cfg.messages['parameter-final-separator'];  else  error_str = error_str .. cfg.messages['parameter-pair-separator'];  end  error_str = error_str .. wrap( 'parameter', selected );  table.insert( z.message_tail, { seterror( error_condition, {error_str}, true ) } );  end    return value, selected; end  -- COinS metadata (see <http://ocoins.info/>) allows automated tools to parse -- the citation information. function COinS(data)  if 'table' ~= type(data) or nil == next(data) then  return '';  end    local ctx_ver = "Z39.88-2004";    -- treat table strictly as an array with only set values.  local OCinSoutput = setmetatable( {}, {  __newindex = function(self, key, value)  if is_set(value) then  rawset( self, #self+1, table.concat{ key, '=', mw.uri.encode( removewikilink( value ) ) } );  end  end  });    if is_set(data.Chapter) then  OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:book";  OCinSoutput["rft.genre"] = "bookitem";  OCinSoutput["rft.btitle"] = data.Chapter;  OCinSoutput["rft.atitle"] = data.Title;  elseif is_set(data.Periodical) then  OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:journal";  OCinSoutput["rft.genre"] = "article";  OCinSoutput["rft.jtitle"] = data.Periodical;  OCinSoutput["rft.atitle"] = data.Title;  else  OCinSoutput.rft_val_fmt = "info:ofi/fmt:kev:mtx:book";  OCinSoutput["rft.genre"] = "book"  OCinSoutput["rft.btitle"] = data.Title;  end    OCinSoutput["rft.place"] = data.PublicationPlace;  OCinSoutput["rft.date"] = data.Date;  OCinSoutput["rft.series"] = data.Series;  OCinSoutput["rft.volume"] = data.Volume;  OCinSoutput["rft.issue"] = data.Issue;  OCinSoutput["rft.pages"] = data.Pages;  OCinSoutput["rft.edition"] = data.Edition;  OCinSoutput["rft.pub"] = data.PublisherName;    for k, v in pairs( data.ID_list ) do  local id, value = cfg.id_handlers[k].COinS;  if k == 'ISBN' then value = cleanisbn( v ); else value = v; end  if string.sub( id or "", 1, 4 ) == 'info' then  OCinSoutput["rft_id"] = table.concat{ id, "/", v };  else  OCinSoutput[ id ] = value;  end  end    local last, first;  for k, v in ipairs( data.Authors ) do  last, first = v.last, v.first;  if k == 1 then  if is_set(last) then  OCinSoutput["rft.aulast"] = last;  end  if is_set(first) then   OCinSoutput["rft.aufirst"] = first;  end  end  if is_set(last) and is_set(first) then  OCinSoutput["rft.au"] = table.concat{ last, ", ", first };  elseif is_set(last) then  OCinSoutput["rft.au"] = last;  end  end    OCinSoutput.rft_id = data.URL;  OCinSoutput.rfr_id = table.concat{ "info:sid/", mw.site.server:match( "[^/]*$" ), ":", data.RawPage };  OCinSoutput = setmetatable( OCinSoutput, nil );    -- sort with version string always first, and combine.  table.sort( OCinSoutput );  table.insert( OCinSoutput, 1, "ctx_ver=" .. ctx_ver ); -- such as "Z39.88-2004"  return table.concat(OCinSoutput, "&"); end  --[[ This is the main function foing the majority of the citation formatting. ]] function citation0( config, args)  --[[   Load Input Parameters  The argment_wrapper facillitates the mapping of multiple  aliases to single internal variable.  ]]  local A = argument_wrapper( args );   local i   local PPrefix = A['PPrefix']  local PPPrefix = A['PPPrefix']  if is_set( A['NoPP'] ) then PPPrefix = "" PPrefix = "" end    -- Pick out the relevant fields from the arguments. Different citation templates  -- define different field names for the same underlying things.   local Authors = A['Authors'];  local a = extractnames( args, 'AuthorList' );   local Coauthors = A['Coauthors'];  local Others = A['Others'];  local Editors = A['Editors'];  local e = extractnames( args, 'EditorList' );   local Year = devdigit._main(A['Year']);  local PublicationDate = devdigit._main(A['PublicationDate']);  local OrigYear = devdigit._main(A['OrigYear']);  local Date = devdigit._main(A['Date']);  local LayDate = devdigit._main(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 = devdigit._main(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 RegistrationRequired = A['RegistrationRequired'];  local SubscriptionRequired = A['SubscriptionRequired'];  local Via = A['Via'];  local AccessDate = devdigit._main(A['AccessDate']);  local ArchiveDate = devdigit._main(A['ArchiveDate']);  local Agency = A['Agency'];  local DeadURL = A['DeadURL']  local Language = A['Language'];  local Wikisource = A['Wikisource'];  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 Embargo = A['Embargo'];   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 use_lowercase = ( sepc ~= '.' );  local this_page = mw.title.getCurrentTitle(); --Also used for COinS and for language    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   -- 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 = devdigit._main(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  if not is_set(Authors) and is_set(Coauthors) then-- coauthors aren't displayed if one of authors=, authorn=, or lastn= isn't specified table.insert( z.message_tail, { seterror('coauthors_missing_author', {}, true) } );-- emit error message 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   local Cartography = "";  local Scale = "";  if config.CitationClass == "map" then  if not is_set( Authors ) and is_set( PublisherName ) then  Authors = PublisherName;  PublisherName = "";  end  Cartography = A['Cartography'];  if is_set( Cartography ) then  Cartography = sepc .. " " .. wrap( 'cartography', Cartography, use_lowercase );  end   Scale = A['Scale'];  if is_set( Scale ) then  Scale = sepc .. " " .. Scale;  end   end    if not is_set(Date) then  Date = Year;  if is_set(Date) then  local Month = A['Month'];  if is_set(Month) then   Date = Month .. " " .. Date;  local Day = A['Day']  if is_set(Day) then Date = Day .. " " .. Date end  end  end  end    if inArray(PublicationDate, {Date, Year}) then PublicationDate = ''; end  if not is_set(Date) and is_set(PublicationDate) then  Date = PublicationDate;  PublicationDate = '';  end   -- Captures the value for Date prior to adding parens or other textual transformations  local DateIn = Date;    if not is_set(URL) and  not is_set(ChapterURL) and  not is_set(ArchiveURL) and  not is_set(ConferenceURL) and  not is_set(TranscriptURL) then    -- Test if cite web is called without giving a URL  if ( config.CitationClass == "web" ) then  table.insert( z.message_tail, { seterror( 'cite_web_url', {}, true ) } );  end    -- Test if accessdate is given without giving a URL  if is_set(AccessDate) then  table.insert( z.message_tail, { seterror( 'accessdate_missing_url', {}, true ) } );  AccessDate = '';  end    -- Test if format is given without giving a URL  if is_set(Format) then  Format = Format .. seterror( 'format_missing_url' );  end  end    -- Test if citation has no title  if not is_set(Chapter) and  not is_set(Title) and  not is_set(Periodical) and  not is_set(Conference) and  not is_set(TransTitle) and  not is_set(TransChapter) then  table.insert( z.message_tail, { seterror( 'citation_missing_title', {}, true ) } );  end    Format = is_set(Format) and " (" .. Format .. ")" or "";    local OriginalURL = URL  DeadURL = DeadURL:lower();  if 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  Chapter = Chapter .. sepc .. " " -- with end-space  elseif is_set(ChapterURL) then  Chapter = " " .. externallink( ChapterURL, nil, ChapterURLorigin ) .. sepc .. " ";  end     -- Format main title.  if is_set(TitleLink) and is_set(Title) then  Title = "[[" .. TitleLink .. "|" .. Title .. "]]"  end    if is_set(Periodical) then  Title = wrap( 'quoted-title', Title );  TransTitle = wrap( 'trans-quoted-title', TransTitle );  elseif inArray(config.CitationClass, {"web","news","pressrelease","conference"}) 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  Place = " " .. wrap( 'written', Place, use_lowercase ) .. sepc .. " ";  end    if is_set(Conference) then  if is_set(ConferenceURL) then  Conference = externallink( ConferenceURL, Conference );  end  Conference = sepc .. " " .. Conference  elseif is_set(ConferenceURL) then  Conference = sepc .. " " .. externallink( ConferenceURL, nil, ConferenceURLorigin );  end    if not is_set(Position) then  local Minutes = A['Minutes'];  if is_set(Minutes) then  Position = " " .. Minutes .. " " .. cfg.messages['minutes'];  else  local Time = A['Time'];  if is_set(Time) then  local TimeCaption = A['TimeCaption']  if not is_set(TimeCaption) then  TimeCaption = cfg.messages['event'];  if sepc ~= '.' then  TimeCaption = TimeCaption:lower();  end  end  Position = " " .. TimeCaption .. " " .. Time;  end  end  else  Position = " " .. Position;  At = '';  end    if not is_set(Page) then  if is_set(Pages) then  if is_set(Periodical) and  not inArray(config.CitationClass, {"encyclopaedia","web","book","news"}) then  Pages = ": " .. Pages;  elseif tonumber(Pages) ~= nil then  Pages = sepc .." " .. PPrefix .. Pages;  else  Pages = sepc .." " .. PPPrefix .. Pages;  end  end  else  if is_set(Periodical) and  not inArray(config.CitationClass, {"encyclopaedia","web","book","news"}) then  Page = ": " .. Page;  else  Page = sepc .." " .. PPrefix .. Page;  end  end    At = is_set(At) and (sepc .. " " .. At) or "";  Position = is_set(Position) and (sepc .. " " .. Position) or "";  if config.CitationClass == 'map' then  local Section = A['Section'];  local Inset = A['Inset'];  if first_set( Pages, Page, At ) ~= nil or sepc ~= '.' then  if is_set( Section ) then  Section = ", " .. wrap( 'section', Section, true );  end  if is_set( Inset ) then  Inset = ", " .. wrap( 'inset', Inset, true );  end  else  if is_set( Section ) then  Section = sepc .. " " .. wrap( 'section', Section, use_lowercase );  if is_set( Inset ) then  Inset = ", " .. wrap( 'inset', Inset, true );  end  elseif is_set( Inset ) then  Inset = sepc .. " " .. wrap( 'inset', Inset, use_lowercase );  end   end   At = At .. Section .. Inset;   end   --[[Look in the list of iso639-1 language codes to see if the value provided in the language parameter matches one of them. If a match is found,  use that value; if not, then use the value that was provided with the language parameter.  Categories are assigned in a manner similar to the {{xx icon}} templates - categorizes only mainspace citations and only when the language code is not 'en' (English). ]] if is_set (Language) then local name = cfg.iso639_1[Language:lower()];-- get the language name if Language parameter has a valid iso 639-1 code if nil == name then Language=" " .. wrap( 'language', Language );-- no match, use parameter's value else if 0 == this_page.namespace and 'mni' ~= Language:lower() then--found a match; is this page main / article space and বাংলা not the language? Language=" " .. wrap( 'language', name .. '[[Category:' .. name .. ' ꯂꯥꯎꯁꯤꯒꯤ ꯕꯥꯍꯩꯒꯤ ꯀꯥꯗꯤꯍꯩꯁꯤꯡ]]' );-- in main space and not English: categorize else Language=" " .. wrap( 'language', name );--not in mainspace or language is English so don't categorize end end else Language="";-- language not specified so make sure this is an empty string; end   Wikisource = is_set(Wikisource) and (sepc .. " [[s:" .. Wikisource .. "|উইকিসংকলনে বই]]") or "";  Others = is_set(Others) and (sepc .. " " .. Others) or "";  TitleType = is_set(TitleType) and (" (" .. TitleType .. ")") or "";  TitleNote = is_set(TitleNote) and (sepc .. " " .. TitleNote) or "";  Edition = is_set(Edition) and (" " .. wrap( 'edition', Edition )) or "";  Issue = is_set(Issue) and (" (" .. Issue .. ")") or "";  Series = is_set(Series) and (sepc .. " " .. Series) or "";  OrigYear = is_set(OrigYear) and (" [" .. OrigYear .. "]") or "";  Agency = is_set(Agency) and (sepc .. " " .. Agency) or "";    if is_set(Volume) then  if ( mw.ustring.len(Volume) > 4 )  then Volume = sepc .." " .. Volume;  else Volume = " <b>" .. hyphentodash(Volume) .. "</b>";  end  end    ------------------------------------ totally unrelated data  --[[ Loosely mimic {{subscription required}} template; Via parameter identifies a delivery source that is not the publisher; these sources often, but not always, exist  behind a registration or paywall. So here, we've chosen to decouple via from subscription (via has never been part of the registration required template).    Subscription implies paywall; Registration does not. If both are used in a citation, the subscription required link note is displayed. There are no error messages for this condition.  ]]  if is_set(Via) then  Via = " " .. wrap( 'via', Via );  end  if is_set(SubscriptionRequired) then  SubscriptionRequired = sepc .. " " .. cfg.messages['subscription']; --here when 'via' parameter not used but 'subscription' is  elseif is_set(RegistrationRequired) then  SubscriptionRequired = sepc .. " " .. cfg.messages['registration']; --here when 'via' and 'subscription' parameters not used but 'registration' is  end   if is_set(AccessDate) then  local retrv_text = " " .. cfg.messages['retrieved']  if (sepc ~= ".") then retrv_text = retrv_text:lower() end  AccessDate = '<span class="reference-accessdate">' .. sepc  .. substitute( retrv_text, {AccessDate} ) .. '</span>'  end    if is_set(ID) then ID = sepc .." ".. ID; end    ID_list = buildidlist( ID_list, {DoiBroken = DoiBroken, ASINTLD = ASINTLD, IgnoreISBN = IgnoreISBN, Embargo=Embargo} );   if is_set(URL) then  URL = " " .. externallink( URL, nil, URLorigin );  end   if is_set(Quote) then  if Quote:sub(1,1) == '"' and Quote:sub(-1,-1) == '"' then  Quote = Quote:sub(2,-2);  end  Quote = sepc .." " .. wrap( 'quoted-text', Quote );   PostScript = "";  elseif PostScript:lower() == "none" then  PostScript = "";  end    local Archived  if is_set(ArchiveURL) then  if not is_set(ArchiveDate) then  ArchiveDate = seterror('archive_missing_date');  end  if "no" == DeadURL then  local arch_text = cfg.messages['archived'];  if sepc ~= "." then arch_text = arch_text:lower() end  Archived = sepc .. " " .. substitute( cfg.messages['archived-not-dead'],  { externallink( ArchiveURL, arch_text ), ArchiveDate } );  if not is_set(OriginalURL) then  Archived = Archived .. " " .. seterror('archive_missing_url');   end  elseif is_set(OriginalURL) then  local arch_text = cfg.messages['archived-dead'];  if sepc ~= "." then arch_text = arch_text:lower() end  Archived = sepc .. " " .. substitute( arch_text,  { externallink( OriginalURL, cfg.messages['original'] ), ArchiveDate } );  else  local arch_text = cfg.messages['archived-missing'];  if sepc ~= "." then arch_text = arch_text:lower() end  Archived = sepc .. " " .. substitute( arch_text,   { seterror('archive_missing_url'), ArchiveDate } );  end  else  Archived = ""  end    local Lay  if is_set(LayURL) then  if is_set(LayDate) then LayDate = " (" .. LayDate .. ")" end  if is_set(LaySource) then   LaySource = " &ndash; ''" .. safeforitalics(LaySource) .. "''";  else  LaySource = "";  end  if sepc == '.' then  Lay = sepc .. " " .. externallink( LayURL, cfg.messages['lay summary'] ) .. LaySource .. LayDate  else  Lay = sepc .. " " .. externallink( LayURL, cfg.messages['lay summary']:lower() ) .. LaySource .. LayDate  end   else  Lay = "";  end    if is_set(Transcript) then  if is_set(TranscriptURL) then Transcript = externallink( TranscriptURL, Transcript ); end  elseif is_set(TranscriptURL) then  Transcript = externallink( TranscriptURL, nil, TranscriptURLorigin );  end    local Publisher;  if is_set(Periodical) and  not inArray(config.CitationClass, {"encyclopaedia","web","pressrelease"}) then  if is_set(PublisherName) then  if is_set(PublicationPlace) then  Publisher = PublicationPlace .. ": " .. PublisherName;  else  Publisher = PublisherName;   end  elseif is_set(PublicationPlace) then  Publisher= PublicationPlace;  else   Publisher = "";  end  if is_set(PublicationDate) then  if is_set(Publisher) then  Publisher = Publisher .. ", " .. wrap( 'published', PublicationDate );  else  Publisher = PublicationDate;  end  end  if is_set(Publisher) then  Publisher = " (" .. Publisher .. ")";  end  else  if is_set(PublicationDate) then  PublicationDate = " (" .. wrap( 'published', PublicationDate ) .. ")";  end  if is_set(PublisherName) then  if is_set(PublicationPlace) then  Publisher = sepc .. " " .. PublicationPlace .. ": " .. PublisherName .. PublicationDate;  else  Publisher = sepc .. " " .. PublisherName .. PublicationDate;   end   elseif is_set(PublicationPlace) then   Publisher= sepc .. " " .. PublicationPlace .. PublicationDate;  else   Publisher = PublicationDate;  end  end    -- Several of the above rely upon detecting this as nil, so do it last.  if is_set(Periodical) then  if is_set(Title) or is_set(TitleNote) then   Periodical = sepc .. " " .. wrap( 'italic-title', Periodical )   else   Periodical = wrap( 'italic-title', Periodical )  end  end   -- Piece all bits together at last. Here, all should be non-nil.  -- We build things this way because it is more efficient in LUA  -- not to keep reassigning to the same string variable over and over.   local tcommon  if inArray(config.CitationClass, {"journal","citation"}) and is_set(Periodical) then  if is_set(Others) then Others = Others .. sepc .. " " end  tcommon = safejoin( {Others, Title, TitleNote, Conference, Periodical, Format, TitleType, Scale, Series,   Language, Cartography, Edition, Publisher, Agency, Volume, Issue}, sepc );  else   tcommon = safejoin( {Title, TitleNote, Conference, Periodical, Format, TitleType, Scale, Series, Language, Wikisource,   Volume, Issue, Others, Cartography, Edition, Publisher, Agency}, 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 = Position .. Page .. Pages .. At;    if is_set(Authors) then  if is_set(Coauthors) then  Authors = Authors .. A['AuthorSeparator'] .. " " .. Coauthors  end  if is_set(Date) then  Date = " ("..Date..")" .. OrigYear .. sepc .. " "  elseif string.sub(Authors,-1,-1) == sepc then  Authors = Authors .. " "  else  Authors = Authors .. sepc .. " "  end  if is_set(Editors) then  local in_text = " ";  local post_text = "";  if is_set(Chapter) then  in_text = in_text .. cfg.messages['in'] .. " "  else  if EditorCount <= 1 then  post_text = ", " .. cfg.messages['editor'];  else  post_text = ", " .. cfg.messages['editors'];  end  end   if (sepc ~= '.') then in_text = in_text:lower() end  Editors = in_text .. Editors .. post_text;  if (string.sub(Editors,-1,-1) == sepc)  then Editors = Editors .. " "  else Editors = Editors .. sepc .. " "  end  end  text = safejoin( {Authors, Date, Chapter, Place, Editors, tcommon }, sepc );  text = safejoin( {text, pgtext, idcommon}, sepc );  elseif is_set(Editors) then  if is_set(Date) then  if EditorCount <= 1 then  Editors = Editors .. ", " .. cfg.messages['editor'];  else  Editors = Editors .. ", " .. cfg.messages['editors'];  end  Date = " (" .. Date ..")" .. OrigYear .. sepc .. " "  else  if EditorCount <= 1 then  Editors = Editors .. " (" .. cfg.messages['editor'] .. ")" .. sepc .. " "  else  Editors = Editors .. " (" .. cfg.messages['editors'] .. ")" .. sepc .. " "  end  end  text = safejoin( {Editors, Date, Chapter, Place, tcommon}, sepc );  text = safejoin( {text, pgtext, idcommon}, sepc );  else  if is_set(Date) then  if ( string.sub(tcommon,-1,-1) ~= sepc )  then Date = sepc .." " .. Date .. OrigYear  else Date = " " .. Date .. OrigYear  end  end  if config.CitationClass=="journal" and is_set(Periodical) then  text = safejoin( {Chapter, Place, tcommon}, sepc );  text = safejoin( {text, pgtext, Date, idcommon}, sepc );  else  text = safejoin( {Chapter, Place, tcommon, Date}, sepc );  text = safejoin( {text, pgtext, idcommon}, sepc );  end  end    if is_set(PostScript) and PostScript ~= sepc then  text = safejoin( {text, sepc}, sepc ); --Deals with italics, spaces, etc.  text = text:sub(1,-2); --Remove final seperator   end     text = safejoin( {text, PostScript}, sepc );   -- Now enclose the whole thing in a <span/> element  if not is_set(Year) then  if is_set(DateIn) then  Year = selectyear( DateIn );  elseif is_set(PublicationDate) then  Year = selectyear( PublicationDate );  end  end    local options = {};    if is_set(config.CitationClass) and config.CitationClass ~= "citation" then  options.class = "citation " .. config.CitationClass;  else  options.class = "citation";  end    if is_set(Ref) and Ref:lower() ~= "none" then  local id = Ref  if ( "harv" == Ref ) then  local names = {} --table of last names & year  if #a > 0 then  for i,v in ipairs(a) do   names[i] = v.last   if i == 4 then break end  end  elseif #e > 0 then  for i,v in ipairs(e) do   names[i] = v.last   if i == 4 then break end   end  end  names[ #names + 1 ] = Year;  id = anchorid(names)  end  options.id = id;  end    if string.len(text:gsub("<span[^>/]*>.-</span>", ""):gsub("%b<>","")) <= 2 then  z.error_categories = {};  text = seterror('empty_citation');  z.message_tail = {};  end    if is_set(options.id) then   text = '<span id="' .. mw.uri.anchorEncode(options.id) ..'" class="' .. mw.text.nowiki(options.class) .. '">' .. text .. "</span>";  else  text = '<span class="' .. mw.text.nowiki(options.class) .. '">' .. text .. "</span>";  end    local empty_span = '<span style="display:none;">&nbsp;</span>';    -- Note: Using display: none on then COinS span breaks some clients.  local OCinS = '<span title="' .. OCinSoutput .. '" class="Z3988">' .. empty_span .. '</span>';  text = text .. OCinS;    if #z.message_tail ~= 0 then  text = text .. " ";  for i,v in ipairs( z.message_tail ) do  if is_set(v[1]) then  if i == #z.message_tail then  text = text .. errorcomment( v[1], v[2] );  else  text = text .. errorcomment( v[1] .. "; ", v[2] );  end  end  end  end    no_tracking_cats = no_tracking_cats:lower();  if inArray(no_tracking_cats, {"", "no", "false", "n"}) then  for _, v in ipairs( z.error_categories ) do  text = text .. '[[Category:' .. v ..']]';  end  end    return text end  -- This is used by templates such as {{cite book}} to create the actual citation text. function z.citation(frame)  local pframe = frame:getParent()    local args = {};  local suggestions = {};  local error_text, error_state;   local config = {};  for k, v in pairs( frame.args ) do  config[k] = v;  args[k] = v;   end    for k, v in pairs( pframe.args ) do  if v ~= '' then  if not validate( k ) then   error_text = "";  if type( k ) ~= 'string' then  -- Exclude empty numbered parameters  if v:match("%S+") ~= nil then  error_text, error_state = seterror( 'text_ignored', {v}, true );  end  elseif validate( k:lower() ) then   error_text, error_state = seterror( 'parameter_ignored_suggest', {k, k:lower()}, true );  else  if #suggestions == 0 then  suggestions = mw.loadData( 'Module:Citation/CS1/Suggestions' );  end  if suggestions[ k:lower() ] ~= nil then  error_text, error_state = seterror( 'parameter_ignored_suggest', {k, suggestions[ k:lower() ]}, true );  else  error_text, error_state = seterror( 'parameter_ignored', {k}, true );  end  end   if error_text ~= '' then  table.insert( z.message_tail, {error_text, error_state} );  end   end  args[k] = v;  elseif args[k] ~= nil or (k == 'postscript') then  args[k] = v;  end   end     return citation0( config, args) end  return z 
"https://mni.wikipedia.org/w/index.php?title=Module:Citation/CS1&oldid=59418" ꯃꯐꯝꯗꯨꯗꯒꯤ ꯂꯧꯈꯠꯂꯛꯄꯥ