Modul:Webarchive: Perbedaan antara revisi

Dari Wiki Javasatu
Loncat ke navigasi Loncat ke pencarian
dw>WOSlinker
(use require('strict') instead of require('Module:No globals'))
(←Membuat halaman berisi '--[[ ---------------------------------- Lua module implementing the {{webarchive}} template. A merger of the functionality of three templates: {{wayback}}, {{webcite}} and {{cite archives}} ]] local p = {} --[[--------------------------< inlineError >----------------------- Critical error. Render output completely in red. Add to tracking category. ]] local function inlineError(arg, msg) track["Category:Galat templat Webarchive"]...')
Baris 1: Baris 1:
--[[ ----------------------------------
--[[ ----------------------------------


Lua module implementing the {{webarchive}} template.  
    Lua module implementing the {{webarchive}} template.  


A merger of the functionality of three templates: {{wayback}}, {{webcite}} and {{cite archives}}
      A merger of the functionality of three templates: {{wayback}}, {{webcite}} and {{cite archives}}
 
]]
  ]]


local p = {}


--[[--------------------------< D E P E N D E N C I E S >------------------------------------------------------
--[[--------------------------< inlineError >-----------------------
]]


require('strict');
    Critical error. Render output completely in red. Add to tracking category.
local getArgs = require ('Module:Arguments').getArgs;


]]


--[[--------------------------< F O R W A R D  D E C L A R A T I O N S >--------------------------------------
local function inlineError(arg, msg)
]]
 
local categories = {}; -- category names
local config = {}; -- global configuration settings
local digits = {}; -- for i18n; table that translates local-wiki digits to western digits
local err_warn_msgs = {}; -- error and warning messages
local excepted_pages = {};
local month_num = {}; -- for i18n; table that translates local-wiki month names to western digits
local prefixes = {}; -- service provider tail string prefixes
local services = {}; -- archive service provider data from
local s_text = {}; -- table of static text strings used to build final rendering
local uncategorized_namespaces = {}; -- list of namespaces that we should not categorize
local uncategorized_subpages = {}; -- list of subpages that should not be categorized


  track["Category:Galat templat Webarchive"] = 1
  return '<span style="font-size:100%" class="error citation-comment">Error in webarchive template: Check <code style="color:inherit; border:inherit; padding:inherit;">&#124;' .. arg .. '=</code> value. ' .. msg .. '</span>'


--[[--------------------------< P A G E  S C O P E  I D E N T I F I E R S >----------------------------------
]]
local non_western_digits; -- boolean flag set true when data.digits.enable is true
local this_page = mw.title.getCurrentTitle();
local track = {}; -- Associative array to hold tracking categories
local ulx = {}; -- Associative array to hold template data
--[[--------------------------< S U B S T I T U T E >----------------------------------------------------------
Populates numbered arguments in a message string using an argument table.
]]
local function substitute (msg, args)
return args and mw.message.newRawMessage (msg, args):plain() or msg;
end
end


--[[--------------------------< inlineRed >-----------------------


--[[--------------------------< tableLength >-----------------------
      Render a text fragment in red, such as a warning as part of the final output.
      Add tracking category.


Given a 1-D table, return number of elements
]]


]]
local function inlineRed(msg, trackmsg)
 
local function tableLength(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end


  if trackmsg == "warning" then
    track["Category:Peringatan templat Webarchive"] = 1
  elseif trackmsg == "error" then
    track["Category:Galat templat Webarchive"] = 1
  end


--[=[-------------------------< M A K E _ W I K I L I N K >----------------------------------------------------
  return '<span style="font-size:100%" class="error citation-comment">' .. msg .. '</span>'


Makes a wikilink; when both link and display text is provided, returns a wikilink in the form [[L|D]]; if only
link is provided, returns a wikilink in the form [[L]]; if neither are provided or link is omitted, returns an
empty string.
]=]
local function make_wikilink (link, display, no_link)
if nil == no_link then
if link and ('' ~= link) then
if display and ('' ~= display) then
return table.concat ({'[[', link, '|', display, ']]'});
else
return table.concat ({'[[', link, ']]'});
end
end
return display or ''; -- link not set so return the display text
else -- no_link
if display and ('' ~= display) then -- if there is display text
return display; -- return that
else
return link or ''; -- return the target article name or empty string
end
end
end
end


--[[--------------------------< trimArg >-----------------------


--[[--------------------------< createTracking >-----------------------
    trimArg returns nil if arg is "" while trimArg2 returns 'true' if arg is ""
    trimArg2 is for args that might accept an empty value, as an on/off switch like nolink=


Return data in track[] ie. tracking categories
]]
 
]]
 
local function createTracking()
if not excepted_pages[this_page.fullText] then -- namespace:title/fragment is allowed to be categorized (typically this module's / template's testcases page(s))
if uncategorized_namespaces[this_page.nsText] then
return ''; -- this page not to be categorized so return empty string
end
for _,v in ipairs (uncategorized_subpages) do -- cycle through page name patterns
if this_page.text:match (v) then -- test page name against each pattern
return ''; -- this subpage type not to be categorized so return empty string
end
end
end
 
local out = {};
if tableLength(track) > 0 then
for key, _ in pairs(track) do -- loop through table
table.insert (out, make_wikilink (key)); -- and convert category names to links
end
end
return table.concat (out); -- concat into one big string; empty string if table is empty


local function trimArg(arg)
  if arg == "" or arg == nil then
    return nil
  else
    return mw.text.trim(arg)
  end
end
end
 
local function trimArg2(arg)
 
  if arg == nil then
--[[--------------------------< inlineError >-----------------------
    return nil
 
  else
Critical error. Render output completely in red. Add to tracking category.
    return mw.text.trim(arg)
 
  end
This function called as the last thing before abandoning this module
 
]]
 
local function inlineError (msg, args)
track[categories.error] = 1
return table.concat ({
'<span style="font-size:100%" class="error citation-comment">Error in ', -- open the error message span
config.tname, -- insert the local language template name
' template: ',
substitute (msg, args), -- insert the formatted error message
'.</span>', -- close the span
createTracking() -- add the category
})
end
 
 
--[[--------------------------< inlineRed >-----------------------
 
Render a text fragment in red, such as a warning as part of the final output.
Add tracking category.
 
]]
 
local function inlineRed(msg, trackmsg)
if trackmsg == "warning" then
track[categories.warning] = 1;
elseif trackmsg == "error" then
track[categories.error] = 1;
end
 
return '<span style="font-size:100%" class="error citation-comment">' .. msg .. '</span>'
end
end


--[[--------------------------< base62 >-----------------------
--[[--------------------------< base62 >-----------------------


Convert base-62 to base-10
    Convert base-62 to base-10
Credit: https://de.wikipedia.org/wiki/Modul:Expr  
    Credit: https://de.wikipedia.org/wiki/Modul:Expr  


]]
  ]]


local function base62( value )
local function base62( value )
local r = 1 -- default return value is input value is malformed


if value:match ('%W') then -- value must only be in the set [0-9a-zA-Z]
    local r = 1
return; -- nil return when value contains extraneous characters
end


local n = #value -- number of characters in value
    if value:match( "^%w+$" ) then
local k = 1
        local n = #value
local c
        local k = 1
r = 0
        local c
for i = n, 1, -1 do -- loop through all characters in value from ls digit to ms digit
        r = 0
c = value:byte( i, i )
        for i = n, 1, -1 do
if c >= 48 and c <= 57 then -- character is digit 0-9
            c = value:byte( i, i )
c = c - 48
            if c >= 48 and c <= 57 then
elseif c >= 65 and c <= 90 then -- character is ascii a-z
                c = c - 48
c = c - 55
            elseif c >= 65 and c <= 90 then
else -- must be ascii A-Z
                c = c - 55
c = c - 61
            elseif c >= 97  and  c <= 122 then
end
                c = c - 61
r = r + c * k -- accumulate this base62 character's value
            else   -- How comes?
k = k * 62 -- bump for next
                r = 1
end -- for i
                break    -- for i
 
            end
return r
            r = r + c * k
            k = k * 62
        end -- for i
    end
    return r
end  
end  


--[[--------------------------< tableLength >-----------------------


--[[--------------------------< D E C O D E _ D A T E >--------------------------------------------------------
      Given a 1-D table, return number of elements


Given a date string, return it in iso format along with an indicator of the date's format.  Except that month names
  ]]
must be recognizable as legitimate month names with proper capitalization, and that the date string must match one
of the recognized date formats, no error checking is done here; return nil else


]]
local function tableLength(T)
 
  local count = 0
local function decode_date (date_str)
  for _ in pairs(T) do count = count + 1 end
local patterns = {
  return count
['dmy'] = {'^(%d%d?) +([^%s%d]+) +(%d%d%d%d)$', 'd', 'm', 'y'}, -- %a does not recognize unicode combining characters used by some languages
['mdy'] = {'^([^%s%d]+) (%d%d?), +(%d%d%d%d)$', 'm', 'd', 'y'},
['ymd'] = {'^(%d%d%d%d) +([^%s%d]+) (%d%d?)$', 'y', 'm', 'd'}, -- not mos compliant at en.wiki but may be acceptible at other wikis
};
local t = {};
 
if non_western_digits then -- this wiki uses non-western digits?
date_str = mw.ustring.gsub (date_str, '%d', digits); -- convert this wiki's non-western digits to western digits
end
 
if date_str:match ('^%d%d%d%d%-%d%d%-%d%d$') then -- already an iso format date, return western digits form
return date_str, 'iso';
end
for k, v in pairs (patterns) do
local c1, c2, c3 = mw.ustring.match (date_str, patterns[k][1]); -- c1 .. c3 are captured but we don't know what they hold
if c1 then -- set on match
t = { -- translate unspecified captures to y, m, and d
[patterns[k][2]] = c1, -- fill the table of captures with the captures
[patterns[k][3]] = c2, -- take index names from src_pattern table and assign sequential captures
[patterns[k][4]] = c3,
};
if month_num[t.m] then -- when month not already a number
t.m = month_num[t.m]; -- replace valid month name with a number
else
return nil, 'iso'; -- not a valid date form because month not valid
end
 
return mw.ustring.format ('%.4d-%.2d-%.2d', t.y, t.m, t.d), k; -- return date in iso format
end
end
return nil, 'iso'; -- date could not be decoded; return nil and default iso date
end
end


--[[--------------------------< makeDate >-----------------------


Given year, month, day numbers, (zero-padded or not) return a full date in df format
--[[--------------------------< dateFormat >-----------------------
where df may be one of:
mdy, dmy, iso, ymd


on entry, year, month, day are presumed to be correct for the date that they represent; all are required
    Given a date string, return its format: dmy, mdy, iso, ymd
      If unable to determine return nil


in this module, makeDate() is sometimes given an iso-format date in year:
  ]]
makeDate (2018-09-20, nil, nil, df)
this works because table.concat() sees only one table member


]]
local function dateFormat(date)


local function makeDate (year, month, day, df)
  local dt = {}
local format = {
  dt.split = {}
['dmy'] = 'j F Y',
['mdy'] = 'F j, Y',
['ymd'] = 'Y F j',
['iso'] = 'Y-m-d',
};


local date = table.concat ({year, month, day}, '-'); -- assemble year-initial numeric-format date (zero padding not required here)
  dt.split = mw.text.split(date, "-")
  if tableLength(dt.split) == 3 then
    if tonumber(dt.split[1]) > 1900 and tonumber(dt.split[1]) < 2200 and tonumber(dt.split[2]) and tonumber(dt.split[3]) then
      return "iso"
    else
      return nil
    end
  end 


if non_western_digits then -- this wiki uses non-western digits?
  dt.split = mw.text.split(date, " ")
date = mw.ustring.gsub (date, '%d', digits); -- convert this wiki's non-western digits to western digits
  if tableLength(dt.split) == 3 then
end
    if tonumber(dt.split[3]) then
      if tonumber(dt.split[3]) > 1900 and tonumber(dt.split[3]) < 2200 then
        if tonumber(dt.split[1]) then
          return "dmy"
        else
          return "mdy"
        end
      else
        if tonumber(dt.split[1]) then
          if tonumber(dt.split[1]) > 1900 and tonumber(dt.split[1]) < 2200 then
            return "ymd"
          end
        end
      end
    end
  end
  return nil


return mw.getContentLanguage():formatDate (format[df], date);
end
end


--[[--------------------------< makeDate >-----------------------


--[[--------------------------< I S _ V A L I D _ D A T E >----------------------------------------------------
    Given a zero-padded 4-digit year, 2-digit month and 2-digit day, return a full date in df format
    df = mdy, dmy, iso, ymd


Returns true if date is after 31 December 1899 (why is 1900 the min year? shouldn't the internet's date-of-birth
  ]]
be min year?), not after today's date, and represents a valid date (29 February 2017 is not a valid date). Applies
Gregorian leapyear rules.
 
all arguments are required


]]
local function makeDate(year, month, day, df)


local function is_valid_date (year, month, day)
  if not year or year == "" or not month or month == "" or not day or day == "" then
local days_in_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    return nil
local month_length;
  end
local y, m, d;
local today = os.date ('*t'); -- fetch a table of current date parts


if not year or '' == year or not month or '' == month or not day or '' == day then
  local zmonth = month                                                      -- month with leading 0
return false; -- something missing
  month = month:match("0*(%d+)")                                            -- month without leading 0
end
  if tonumber(month) < 1 or tonumber(month) > 12 then
    return year
y = tonumber (year);
  end
m = tonumber (month);
  local nmonth = os.date("%B", os.time{year=2000, month=month, day=1} )     -- month in name form     
d = tonumber (day);
  if not nmonth then
    return year
  end


if 1900 > y or today.year < y or 1 > m or 12 < m then -- year and month are within bounds TODO: 1900?
  local zday = day
return false;
  day = zday:match("0*(%d+)")
end
  if tonumber(day) < 1 or tonumber(day) > 31 then
    if df == "mdy" or df == "dmy" then
      return nmonth .. " " .. year
    elseif df == "iso" then
      return year .. "-" .. zmonth
    elseif df == "ymd" then
      return year .. " " .. nmonth
    else
      return nmonth .. " " .. year
    end
  end                                      


if (2==m) then -- if February
  if df == "mdy" then
month_length = 28; -- then 28 days unless
    return nmonth .. " " .. day .. ", " .. year        -- September 1, 2016
if (0==(y%4) and (0~=(y%100) or 0==(y%400))) then -- is a leap year?
  elseif df == "dmy" then
month_length = 29; -- if leap year then 29 days in February
    return day .. " " .. nmonth .. " " .. year          -- 1 September 2016
end
  elseif df == "iso" then
else
    return year .. "-" .. zmonth .. "-" .. zday        -- 2016-09-01
month_length=days_in_month[m];
  elseif df == "ymd" then
end
    return year .. " " .. nmonth .. " " .. day          -- 2016 September 1
  else
    return nmonth .. " " .. day .. ", " .. year        -- September 1, 2016
  end


if 1 > d or month_length < d then -- day is within bounds
return false;
end
-- here when date parts represent a valid date
return os.time({['year']=y, ['month']=m, ['day']=d, ['hour']=0}) <= os.time(); -- date at midnight must be less than or equal to current date/time
end
end


Baris 323: Baris 208:
--[[--------------------------< decodeWebciteDate >-----------------------
--[[--------------------------< decodeWebciteDate >-----------------------


Given a URI-path to Webcite (eg. /67xHmVFWP) return the encoded date in df format
      Given a URI-path to Webcite (eg. /67xHmVFWP) return the encoded date in df format


returns date string in df format - webcite date is a unix timestamp encoded as bae62
  ]]
or the string 'query'
local function decodeWebciteDate(path, df)


]]
    local dt = {}
    dt.split = {}


local function decodeWebciteDate(path, df)
    dt.split = mw.text.split(path, "/")


local dt = {};
    -- valid URL formats that are not base62
local decode;


dt = mw.text.split(path, "/")
    -- http://www.webcitation.org/query?id=1138911916587475
    -- http://www.webcitation.org/query?url=http..&date=2012-06-01+21:40:03
    -- http://www.webcitation.org/1138911916587475
    -- http://www.webcitation.org/cache/73e53dd1f16cf8c5da298418d2a6e452870cf50e
    -- http://www.webcitation.org/getfile.php?fileid=1c46e791d68e89e12d0c2532cc3cf629b8bc8c8e


-- valid URL formats that are not base62
    if mw.ustring.find( dt.split[2], "query", 1, true) or
      mw.ustring.find( dt.split[2], "cache", 1, true) or
      mw.ustring.find( dt.split[2], "getfile", 1, true) or
      tonumber(dt.split[2]) then
      return "query"
    end


-- http://www.webcitation.org/query?id=1138911916587475
    dt.full = os.date("%Y %m %d", string.sub(string.format("%d", base62(dt.split[2])),1,10) )
-- http://www.webcitation.org/query?url=http..&date=2012-06-01+21:40:03
    dt.split = mw.text.split(dt.full, " ")
-- http://www.webcitation.org/1138911916587475
    dt.year = dt.split[1]
-- http://www.webcitation.org/cache/73e53dd1f16cf8c5da298418d2a6e452870cf50e
    dt.month = dt.split[2]
-- http://www.webcitation.org/getfile.php?fileid=1c46e791d68e89e12d0c2532cc3cf629b8bc8c8e
    dt.day = dt.split[3]


if dt[2]:find ('query', 1, true) or  
    if not tonumber(dt.year) or not tonumber(dt.month) or not tonumber(dt.day) then
dt[2]:find ('cache', 1, true) or
      return inlineRed("[Date error] (1)", "error")
dt[2]:find ('getfile', 1, true) or
    end
tonumber(dt[2]) then
return 'query';
end


decode = base62(dt[2]); -- base62 string -> exponential number
    if tonumber(dt.month) > 12 or tonumber(dt.day) > 31 or tonumber(dt.month) < 1 then
if not decode then
      return inlineRed("[Date error] (2)", "error")
return nil; -- nil return when dt[2] contains characters not in %w
    end
end
    if tonumber(dt.year) > tonumber(os.date("%Y")) or tonumber(dt.year) < 1900 then
dt = os.date('*t', string.format("%d", decode):sub(1,10)) -- exponential number -> text -> first 10 characters (a unix timestamp) -> a table of date parts
      return inlineRed("[Date error] (3)", "error")
    end


decode = makeDate (dt.year, dt.month, dt.day, 'iso'); -- date comparisons are all done in iso format with western digits
    local fulldate = makeDate(dt.year, dt.month, dt.day, df)
if non_western_digits then -- this wiki uses non-western digits?
    if not fulldate then
decode = mw.ustring.gsub (decode, '%d', digits); -- convert this wiki's non-western digits to western digits
      return inlineRed("[Date error] (4)", "error")
end
    else
      return fulldate
    end


return decode;
end
end


--[[--------------------------< decodeWaybackDate >-----------------------
--[[--------------------------< decodeWaybackDate >-----------------------


Given a URI-path to Wayback (eg. /web/20160901010101/http://example.com )
Given a URI-path to Wayback (eg. /web/20160901010101/http://example.com )
or Library of Congress Web Archives (eg. /all/20160901010101/http://example.com)
  or Library of Congress Web Archives (/all/20160901010101/http://example.com)
or UK Government Web Archive (eg. /ukgwa/20160901010101/http://example.com or /tna/20160901010101/http://example.com)
  return the formatted date eg. "September 1, 2016" in df format
  Handle non-digits in snapshot ID such as "re_" and "-" and "*"


return the formatted date eg. "September 1, 2016" in df format
]]
Handle non-digits in snapshot ID such as "re_" and "-" and "*"
 
returns two values:
first value is one of these:
valid date string in df format - wayback date is valid (including the text string 'index' when date is '/*/')
empty string - wayback date is malformed (less than 8 digits, not a valid date)
nil - wayback date is '/save/' or otherwise not a number
second return value is an appropriate 'message' may or may not be formatted
 
]]


local function decodeWaybackDate(path, df)
local function decodeWaybackDate(path, df)


local msg, snapdate;
    local snapdate, snapdatelong, currdate, fulldate


snapdate = path:gsub ('^/web/', ''):gsub ('^/all/', ''):gsub ('^/ukgwa/', ''):gsub ('^/tna/', ''):gsub ('^/', ''); -- remove leading /web/, /all/, /ukgwa/, /tna/, or /
    local safe = path
snapdate = snapdate:match ('^[^/]+'); -- get timestamp
    snapdate = string.gsub(safe, "^/all/", "")                         -- Remove leading "/all/"
if snapdate == "*" then -- eg. /web/*/http.., etc.
    safe = snapdate
return 'index'; -- return indicator that this url has an index date
    snapdate = string.gsub(safe, "^/w?e?b?/?", "")                     -- Remove leading "/web/" or "/"
end
    safe = snapdate
    local N = mw.text.split(safe, "/")
    snapdate = N[1]
    if snapdate == "*" then                                             -- eg. /web/*/http.. or /all/*/http..
      return "index"
    end
    safe = snapdate
    snapdate = string.gsub(safe, "[a-z][a-z]_[0-9]?$", "")              -- Remove any trailing "re_" from date
    safe = snapdate
    snapdate = string.gsub(safe, "[-]", "")                            -- Remove dashes from date eg. 2015-01-01
    safe = snapdate
    snapdate = string.gsub(safe, "[*]$", "")                            -- Remove trailing "*"


snapdate = snapdate:gsub ('%a%a_%d?$', ''):gsub ('%-', ''); -- from date, remove any trailing "re_", dashes
    if not tonumber(snapdate) then
      return inlineRed("[Date error] (2)", "error")
    end
    local dlen = string.len(snapdate)
    if dlen < 4 then
      return inlineRed("[Date error] (3)", "error")
    end
    if dlen < 14 then
      snapdatelong = snapdate .. string.rep("0", 14 - dlen)
    else
      snapdatelong = snapdate
    end
    local year = string.sub(snapdatelong, 1, 4)
    local month = string.sub(snapdatelong, 5, 6)
    local day = string.sub(snapdatelong, 7, 8)
    if not tonumber(year) or not tonumber(month) or not tonumber(day) then
      return inlineRed("[Date error] (4)", "error")
    end
    if tonumber(month) > 12 or tonumber(day) > 31 or tonumber(month) < 1 then
      return inlineRed("[Date error] (5)", "error")
    end
    currdate = os.date("%Y")
    if tonumber(year) > tonumber(currdate) or tonumber(year) < 1900 then
      return inlineRed("[Date error] (6)", "error")
    end


msg = '';
    fulldate = makeDate(year, month, day, df)
if snapdate:match ('%*$') then -- a trailing '*' causes calendar display at archive .org
    if not fulldate then
snapdate = snapdate:gsub ('%*$', ''); -- remove so not part of length calc later
      return inlineRed("[Date error] (7)", "error")
msg = inlineRed (err_warn_msgs.ts_cal, 'warning'); -- make a message
    else
end
      return fulldate
    end


if not tonumber(snapdate) then
end
return nil, 'ts_nan'; -- return nil (fatal error flag) and message selector
end


local dlen = snapdate:len();
--[[--------------------------< decodeArchiveisDate >-----------------------
if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
return '', inlineRed (err_warn_msgs.ts_short, 'error'); -- return empty string and error message
end


local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here
  Given an Archive.is "long link" URI-path (e.g. /2016.08.28-144552/http://example.com)
  return the date in df format (e.g. if df = dmy, return 28 August 2016)
  Handles "." and "-" in snapshot date, so 2016.08.28-144552 is same as 20160828144552


if not is_valid_date (year, month, day) then
  ]]
return '', inlineRed (err_warn_msgs.ts_date, 'error'); -- return empty string and error message
end


snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
local function decodeArchiveisDate(path, df)
if 14 == dlen then
return snapdate, msg; -- return date with message if any
else
return snapdate, msg .. inlineRed (err_warn_msgs.ts_len, 'warning'); -- return date with warning message(s)
end
end


    local snapdate, snapdatelong, currdate, fulldate


--[[--------------------------< decodeArchiveisDate >-----------------------
    local safe = path
    local N = mw.text.split(safe, "/")
    safe = N[2]                                                        -- get snapshot date, e.g. 2016.08.28-144552
    snapdate = string.gsub(safe, "[%.%-]", "")                          -- remove periods and hyphens


Given an Archive.is "long link" URI-path (e.g. /2016.08.28-144552/http://example.com)
    if not tonumber(snapdate) then                                      -- if not numeric, it is "short link", not date
return the date in df format (e.g. if df = dmy, return 28 August 2016)
        return "short link"                                            -- e.g. http://archive.is/hD1qz
Handles "." and "-" in snapshot date, so 2016.08.28-144552 is same as 20160828144552
    end


returns two values:
    local dlen = string.len(snapdate)
first value is one of these:
    if dlen < 4 then
valid date string in df format - archive.is date is valid (including the text string 'short link' when url is the short form)
        return inlineRed("[Date error] (3)", "error")
empty string - wayback date is malformed (not a number, less than 8 digits, not a valid date)
    end
nil - wayback date is '/save/'
    if dlen < 14 then
        snapdatelong = snapdate .. string.rep("0", 14 - dlen)
second return value is an appropriate 'message' may or may not be formatted
    else
        snapdatelong = snapdate
    end
    local year = string.sub(snapdatelong, 1, 4)
    local month = string.sub(snapdatelong, 5, 6)
    local day = string.sub(snapdatelong, 7, 8)
    if not tonumber(year) or not tonumber(month) or not tonumber(day) then
        return inlineRed("[Date error] (4)", "error")
    end
    if tonumber(month) > 12 or tonumber(day) > 31 or tonumber(month) < 1 then
        return inlineRed("[Date error] (5)", "error")
    end
    currdate = os.date("%Y")
    if tonumber(year) > tonumber(currdate) or tonumber(year) < 1900 then
        return inlineRed("[Date error] (6)", "error")
    end


]]
    fulldate = makeDate(year, month, day, df)
    if not fulldate then
        return inlineRed("[Date error] (7)", "error")
    else
        return fulldate
    end


local function decodeArchiveisDate(path, df)
end
local snapdate


if path:match ('^/%w+$') then -- short form url path is '/' followed by some number of base 62 digits and nothing else
return "short link" -- e.g. http://archive.is/hD1qz
end


snapdate = mw.text.split (path, '/')[2]:gsub('[%.%-]', ''); -- get snapshot date, e.g. 2016.08.28-144552; remove periods and hyphens
--[[--------------------------< serviceName >-----------------------


local dlen = string.len(snapdate)
    Given a domain extracted by mw.uri.new() (eg. web.archive.org) set tail string and service ID
if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
return '', inlineRed (err_warn_msgs.ts_short, 'error'); -- return empty string and error message
end


local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here
  ]]


if not is_valid_date (year, month, day) then
local function serviceName(host, nolink)
return '', inlineRed (err_warn_msgs.ts_date, 'error'); -- return empty string and error message
end


snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
  local tracking = "Category:Templat webarchive arsip lain"
if 14 == dlen then
return snapdate; -- return date
else
return snapdate, inlineRed (err_warn_msgs.ts_len, 'warning'); -- return date with warning message
end
end


  local bracketopen = "[["
  local bracketclose = "]]"
  if nolink then
    bracketopen = ""
    bracketclose = ""
  end


--[[--------------------------< serviceName >-----------------------
  ulx.url1.service = "other"
  ulx.url1.tail = " di " .. ulx.url1.host .. " " .. inlineRed("Galat: URL arsip tidak dikenal")


Given a domain extracted by mw.uri.new() (eg. web.archive.org) set tail string and service ID
  host = string.lower(host)


]]
  if mw.ustring.find( host, "europarchive.org", 1, true ) then  -- any containing "archive.org" listed before Wayback to avoid disambiguation
    ulx.url1.tail = " di " .. bracketopen .. "National Library of Ireland" .. bracketclose
  elseif mw.ustring.find( host, "webarchive.org.uk", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "UK Web Archive" .. bracketclose
  elseif mw.ustring.find( host, "archive.org", 1, true ) then
    ulx.url1.service = "wayback"
    ulx.url1.tail = " di " .. bracketopen .. "Wayback Machine" .. bracketclose
    tracking = "Category:Templat webarchive tautan wayback"
  elseif mw.ustring.find( host, "webcitation.org", 1, true ) then
    ulx.url1.service = "webcite"
    ulx.url1.tail = " di " .. bracketopen .. "WebCite" .. bracketclose
    tracking = "Category:Templat webarchive tautan webcite"
  elseif mw.ustring.find( host, "archive.is", 1, true ) then
    ulx.url1.service = "archiveis"
    ulx.url1.tail = " di " .. bracketopen .. "Archive.is" .. bracketclose
    tracking = "Category:Templat webarchive tautan archiveis"
  elseif mw.ustring.find( host, "archive.fo", 1, true ) then
    ulx.url1.service = "archiveis"
    ulx.url1.tail = " di " .. bracketopen .. "Archive.is" .. bracketclose
    tracking = "Category:Templat webarchive tautan archiveis"
  elseif mw.ustring.find( host, "archive.today", 1, true ) then
    ulx.url1.service = "archiveis"
    ulx.url1.tail = " di " .. bracketopen .. "Archive.is" .. bracketclose
    tracking = "Category:Templat webarchive tautan archiveis"
  elseif mw.ustring.find( host, "archive.li", 1, true ) then
    ulx.url1.service = "archiveis"
    ulx.url1.tail = " di " .. bracketopen .. "Archive.is" .. bracketclose
    tracking = "Category:Templat webarchive tautan archiveis"
  elseif mw.ustring.find( host, "archive.ec", 1, true ) then
    ulx.url1.service = "archiveis"
    ulx.url1.tail = " di " .. bracketopen .. "Archive.is" .. bracketclose
    tracking = "Category:Templat webarchive tautan archiveis"
  elseif mw.ustring.find( host, "archive-it.org", 1, true ) then
    ulx.url1.service = "archiveit"
    ulx.url1.tail = " di " .. bracketopen .. "Archive-It" .. bracketclose
  elseif mw.ustring.find( host, "wikiwix.com", 1, true ) then
    ulx.url1.tail = " di Wikiwix"
  elseif mw.ustring.find( host, "arquivo.pt", 1, true) then
    ulx.url1.tail = " di " .. "Portuguese Web Archive"
  elseif mw.ustring.find( host, "webarchive.loc.gov", 1, true ) then
    ulx.url1.service = "locwebarchives"
    ulx.url1.tail = " di " .. bracketopen .. "Library of Congress" .. bracketclose .. " Web Archives"
  elseif mw.ustring.find( host, "loc.gov", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Library of Congress" .. bracketclose
  elseif mw.ustring.find( host, "webharvest.gov", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "National Archives and Records Administration" .. bracketclose
  elseif mw.ustring.find( host, "bibalex.org", 1, true ) then
    ulx.url1.tail = " di " .. "[[Bibliotheca_Alexandrina#Internet_Archive_partnership|Bibliotheca Alexandrina]]"
  elseif mw.ustring.find( host, "collectionscanada", 1, true ) then
    ulx.url1.tail = " di " .. "Canadian Government Web Archive"
  elseif mw.ustring.find( host, "haw.nsk", 1, true ) then
    ulx.url1.tail = " di " .. "Croatian Web Archive (HAW)"
  elseif mw.ustring.find( host, "veebiarhiiv.digar.ee", 1, true ) then
    ulx.url1.tail = " di " .. "Estonian Web Archive"
  elseif mw.ustring.find( host, "vefsafn.is", 1, true ) then
    ulx.url1.tail = " di " .. "[[National and University Library of Iceland]]"
  elseif mw.ustring.find( host, "proni.gov", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Public Record Office of Northern Ireland" .. bracketclose
  elseif mw.ustring.find( host, "uni-lj.si", 1, true ) then
    ulx.url1.tail = " di " .. "Slovenian Web Archive"
  elseif mw.ustring.find( host, "stanford.edu", 1, true ) then
    ulx.url1.tail = " di " .. "[[Stanford University Libraries|Stanford Web Archive]]"
  elseif mw.ustring.find( host, "nationalarchives.gov.uk", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "UK Government Web Archive" .. bracketclose
  elseif mw.ustring.find( host, "parliament.uk", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "UK Parliament's Web Archive" .. bracketclose
  elseif mw.ustring.find( host, "nlb.gov.sg", 1, true ) then
    ulx.url1.tail = " di " .. "Web Archive Singapore"
  elseif mw.ustring.find( host, "pandora.nla.gov.au", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Pandora Archive" .. bracketclose
  elseif mw.ustring.find( host, "perma.cc", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Perma.cc" .. bracketclose
  elseif mw.ustring.find( host, "perma-archives.cc", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Perma.cc" .. bracketclose
  elseif mw.ustring.find( host, "screenshots.com", 1, true ) then
    ulx.url1.tail = " di Screenshots"
  elseif mw.ustring.find( host, "freezepage.com", 1, true ) then
    ulx.url1.tail = " di Freezepage"
  elseif mw.ustring.find( host, "yorku.ca", 1, true ) then
    ulx.url1.tail = " di " .. "[[York University Libraries|York University Digital Library]]"
  elseif mw.ustring.find( host, "webcache.googleusercontent.com", 1, true ) then
    ulx.url1.tail = " di Google Cache"
  elseif mw.ustring.find( host, "timetravel.mementoweb.org", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Memento Project" .. bracketclose
  elseif mw.ustring.find( host, "langzeitarchivierung.bib-bvb.de", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Bavarian State Library" .. bracketclose
  elseif mw.ustring.find( host, "webrecorder.io", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "webrecorder.io" .. bracketclose
  elseif mw.ustring.find( host, "webarchive.bac-lac.gc.ca", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Library and Archives Canada" .. bracketclose
  else
    tracking = "Category:Templat webarchive arsip tidak diketahui"
  end


local function serviceName(host, no_link)
  track[tracking] = 1
local tracking;
local index;
host = host:lower():gsub ('^web%.(.+)', '%1'):gsub ('^www%.(.+)', '%1'); -- lowercase, remove web. and www. subdomains


if services[host] then
index = host;
else
for k, _ in pairs (services) do
if host:find ('%f[%a]'..k:gsub ('([%.%-])', '%%%1')) then
index = k;
break;
end
end
end
if index then
local out = {''}; -- empty string in [1] so that concatenated result has leading single space
ulx.url1.service = services[index][4] or 'other';
tracking = services[index][5] or categories.other;
-- build tail string
if false == services[index][1] then -- select prefix
table.insert (out, prefixes.at);
elseif true == services[index][1] then
table.insert (out, prefixes.atthe);
else
table.insert (out, services[index][1]);
end
table.insert (out, make_wikilink (services[index][2], services[index][3], no_link)); -- add article wikilink
if services[index][6] then -- add tail postfix if it exists
table.insert (out, services[index][6]);
end
ulx.url1.tail = table.concat (out, ' '); -- put it all together; result has leading space character
else -- here when unknown archive
ulx.url1.service = 'other';
tracking = categories.unknown;
ulx.url1.tail = table.concat ({'', prefixes.at, host, inlineRed (err_warn_msgs.unknown_url, error)}, ' ');
end
track[tracking] = 1
end
end


--[[--------------------------< parseExtraArgs >-----------------------
--[[--------------------------< parseExtraArgs >-----------------------


Parse numbered arguments starting at 2, such as url2..url10, date2..date10, title2..title10
    Parse numbered arguments starting at 2, such as url2..url10, date2..date10, title2..title10
For example: {{webarchive |url=.. |url4=.. |url7=..}}
      For example: {{webarchive |url=.. |url4=.. |url7=..}}
Three url arguments not in numeric sequence (1..4..7).  
        Three url arguments not in numeric sequence (1..4..7).  
Function only processes arguments numbered 2 or greater (in this case 4 and 7)
        Function only processes arguments numbered 2 or greater (in this case 4 and 7)
It creates numeric sequenced table entries like:
        It creates numeric sequenced table entries like:
urlx.url2.url = <argument value for url4>
          urlx.url2.url = <argument value for url4>
urlx.url3.url = <argument value for url7>
          urlx.url3.url = <argument value for url7>
Returns the number of URL arguments found numbered 2 or greater (in this case returns "2")
      Returns the number of URL arguments found numbered 2 or greater (in this case returns "2")


  ]]
  ]]


local function parseExtraArgs(args)
local function parseExtraArgs()


local i, j, argurl, argurl2, argdate, argtitle
  local i, j, argurl, argurl2, argdate, argtitle


j = 2
  j = 2
for i = 2, config.maxurls do
  for i = 2, maxurls do
argurl = "url" .. i
    argurl = "url" .. i
if args[argurl] then
    if trimArg(args[argurl]) then
argurl2 = "url" .. j
      argurl2 = "url" .. j
ulx[argurl2] = {}
      ulx[argurl2] = {}
ulx[argurl2]["url"] = args[argurl]
      ulx[argurl2]["url"] = args[argurl]
argdate = "date" .. i
      argdate = "date" .. j
if args[argdate] then
      if trimArg(args[argdate]) then
ulx[argurl2]["date"] = args[argdate]
        ulx[argurl2]["date"] = args[argdate]
else
      else
ulx[argurl2]["date"] = inlineRed (err_warn_msgs.date_miss, 'warning');
        ulx[argurl2]["date"] = inlineRed("[Date missing]", "warning")
end
      end
      argtitle = "title" .. j
argtitle = "title" .. i
      if trimArg(args[argtitle]) then
if args[argtitle] then
        ulx[argurl2]["title"] = args[argtitle]
ulx[argurl2]["title"] = args[argtitle]
      else
else
        ulx[argurl2]["title"] = nil
ulx[argurl2]["title"] = nil
      end
end
      j = j + 1
j = j + 1
    end
end
  end
end
 
  if j == 2 then
    return 0
  else
    return j - 2
  end


if j == 2 then
return 0
else
return j - 2
end
end
end


--[[--------------------------< comma >-----------------------
--[[--------------------------< comma >-----------------------


Given a date string, return "," if it's MDY  
    Given a date string, return "," if it's MDY  


]]
  ]]


local function comma(date)
local function comma(date)
return (date and date:match ('%a+ +%d%d?(,) +%d%d%d%d')) or '';
  local N = mw.text.split(date, " ")
  local O = mw.text.split(N[1], "-") -- for ISO
  if O[1] == "index" then return "" end
  if not tonumber(O[1]) then
    return ","
  else
    return ""
  end
end
end


--[[--------------------------< createTracking >-----------------------


--[[--------------------------< createRendering >-----------------------
    Return data in track[] ie. tracking categories


Return a rendering of the data in ulx[][]
  ]]


]]
local function createTracking()


local function createRendering()
  local sand = ""
 
  if tableLength(track) > 0 then                       
local displayfield
    for key,_ in pairs(track) do
local out = {};
      sand = sand .. "[[" .. key .. "]]"
    end
local index_date, msg = ulx.url1.date:match ('(index)(.*)'); -- when ulx.url1.date extract 'index' text and message text (if there is a message)
  end
ulx.url1.date = ulx.url1.date:gsub ('index.*', 'index'); -- remove message
  return sand
 
if 'none' == ulx.url1.format then -- For {{wayback}}, {{webcite}}
table.insert (out, '['); -- open extlink markup
table.insert (out, ulx.url1.url); -- add url


if ulx.url1.title then
table.insert (out, ' ') -- the required space
table.insert (out, ulx.url1.title) -- the title
table.insert (out, ']'); -- close extlink markup
table.insert (out, ulx.url1.tail); -- tail text
if ulx.url1.date then
table.insert (out, '&#32;('); -- open date text; TODO: why the html entity? replace with regular space?
table.insert (out, 'index' == ulx.url1.date and s_text.archive or s_text.archived); -- add text
table.insert (out, ' '); -- insert a space
table.insert (out, ulx.url1.date); -- add date
table.insert (out, ')'); -- close date text
end
else -- no title
if index_date then -- when url date is 'index'
table.insert (out, table.concat ({' ', s_text.Archive_index, ']'})); -- add the index link label
table.insert (out, msg or ''); -- add date mismatch message when url date is /*/ and |date= has valid date
else
table.insert (out, table.concat ({' ', s_text.Archived, '] '})); -- add link label for url has timestamp date (will include mismatch message if there is one)
end
if ulx.url1.date then
if 'index' ~= ulx.url1.date then
table.insert (out, ulx.url1.date); -- add date when data is not 'index'
end
table.insert (out, comma(ulx.url1.date)); -- add ',' if date format is mdy
table.insert (out, ulx.url1.tail); -- add tail text
else -- no date
table.insert (out, ulx.url1.tail); -- add tail text
end
end
if 0 < ulx.url1.extraurls then -- For multiple archive URLs
local tot = ulx.url1.extraurls + 1
table.insert (out, '.') -- terminate first url
table.insert (out, table.concat ({' ', s_text.addlarchives, ': '})); -- add header text
for i=2, tot do -- loop through the additionals
local index = table.concat ({'url', i}); -- make an index
displayfield = ulx[index]['title'] and 'title' or 'date'; -- choose display text
table.insert (out, '['); -- open extlink markup
table.insert (out, ulx[index]['url']); -- add the url
table.insert (out, ' '); -- the required space
table.insert (out, ulx[index][displayfield]); -- add the label
table.insert (out, ']'); -- close extlink markup
table.insert (out, i==tot and '.' or ', '); -- add terminator
end
end
return table.concat (out); -- make a big string and done
else -- For {{cite archives}}
if 'addlarchives' == ulx.url1.format then -- Multiple archive services
table.insert (out, table.concat ({s_text.addlarchives, ': '})); -- add header text
else -- Multiple pages from the same archive
table.insert (out, table.concat ({s_text.addlpages, ' '})); -- add header text
table.insert (out, ulx.url1.date); -- add date to header text
table.insert (out, ': '); -- close header text
end
local tot = ulx.url1.extraurls + 1;
for i=1, tot do -- loop through the additionals
local index = table.concat ({'url', i}); -- make an index
table.insert (out, '['); -- open extlink markup
table.insert (out, ulx[index]['url']); -- add url
table.insert (out, ' '); -- add required space
displayfield = ulx[index]['title'];
if 'addlarchives' == ulx.url1.format then
if not displayfield then
displayfield = ulx[index]['date']
end
else -- must be addlpages
if not displayfield then
displayfield = table.concat ({s_text.Page, ' ', i});
end
end
table.insert (out, displayfield); -- add title, date, page label text
table.insert (out, ']'); -- close extlink markup
table.insert (out, (i==tot and '.' or ', ')); -- add terminator
end
return table.concat (out); -- make a big string and done
end
end
end


--[[--------------------------< createRendering >-----------------------


--[[--------------------------< P A R A M E T E R _ N A M E _ X L A T E >--------------------------------------
    Return a rendering of the data in ulx[][]


for internaltionalization, translate local-language parameter names to their English equivalents
  ]]


TODO: return error message if multiple aliases of the same canonical parameter name are found?
local function createRendering()
 
returns two tables:
new_args - holds canonical form parameters and their values either from translation or because the parameter was already in canonical form
origin - maps canonical-form parameter names to their untranslated (local language) form for error messaging in the local language


unrecognized parameters are ignored
    local sand, displayheader, displayfield


]]
    local period1 = ""  -- For backwards compat with {{wayback}}
    local period2 = "."                                                           
 
    local indexstr = "diarsipkan tanggal"
    if ulx.url1.date == "index" then
      indexstr = "archive"
    end 
                                                                                          -- For {{wayback}}, {{webcite}}


local function parameter_name_xlate (args, params, enum_params)
    if ulx.url1.format == "none" then                                                   
local name; -- holds modifiable name of the parameter name during evaluation
      if not ulx.url1.title and not ulx.url1.date then                                    -- No title. No date
local enum; -- for enumerated parameters, holds the enumerator during evaluation
        sand = "[" .. ulx.url1.url .. " Diarsipkan]" .. ulx.url1.tail
local found = false; -- flag used to break out of nested for loops
      elseif not ulx.url1.title and ulx.url1.date then                                    -- No title. Date.
local new_args = {}; -- a table that holds canonical and translated parameter k/v pairs
        if ulx.url1.service == "wayback" then
local origin = {}; -- a table that maps original (local language) parameter names to their canonical name for local language error messaging
          period1 = "."
local unnamed_params; -- set true when unsupported positional parameters are detected
          period2 = ""
        end
for k, v in pairs (args) do -- loop through all of the arguments in the args table
        sand = "[" .. ulx.url1.url .. " Diarsipkan] " .. ulx.url1.date .. comma(ulx.url1.date) .. ulx.url1.tail .. period1
name = k; -- copy of original parameter name
      elseif ulx.url1.title and not ulx.url1.date then                                    -- Title. No date.
        sand = "[" .. ulx.url1.url .. " " .. ulx.url1.title .. "]" .. ulx.url1.tail
      elseif ulx.url1.title and ulx.url1.date then                                        -- Title. Date.
        sand = "[" .. ulx.url1.url .. " " .. ulx.url1.title .. "]" .. ulx.url1.tail .. "&#32;(" .. indexstr .. " " .. ulx.url1.date .. ")"
      else
        return nil
      end
      if ulx.url1.extraurls > 0 then                                                      -- For multiple archive URLs
        local tot = ulx.url1.extraurls + 1
        sand = sand .. period2 .. " Additional archives: "
        for i=2,tot do
          local indx = "url" .. i
          if ulx[indx]["title"] then
            displayfield = "title"
          else
            displayfield = "date"
          end
          sand = sand .. "[" .. ulx[indx]["url"] .. " " .. ulx[indx][displayfield] .. "]"
          if i == tot then
            sand = sand .. "."
          else
            sand = sand .. ", "
          end
        end
      else
        return sand 
      end
      return sand
                                                                                          -- For {{cite archives}}


if 'string' == type (k) then
    else                                                                 
if non_western_digits then -- true when non-western digits supported at this wiki
      if ulx.url1.format == "addlarchives" then                           -- Multiple archive services
name = mw.ustring.gsub (name, '%d', digits); -- convert this wiki's non-western digits to western digits
        displayheader = "Additional archives: "
end
      else                                                                -- Multiple pages from the same archive
        displayheader = "Additional pages archived&nbsp;on " .. ulx.url1.date .. ": "
enum = name:match ('%d+$'); -- get parameter enumerator if it exists; nil else
      end
      local tot = 1 + ulx.url1.extraurls
if not enum then -- no enumerator so looking for non-enumnerated parameters
      local sand = displayheader
-- TODO: insert shortcut here? if params[name] then name holds the canonical parameter name; no need to search further
      for i=1,tot do
for pname, aliases in pairs (params) do -- loop through each parameter the params table
        local indx = "url" .. i
for _, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
        displayfield = ulx[indx]["title"]
if name == alias then
        if ulx.url1.format == "addlarchives" then
new_args[pname] = v; -- create a new entry in the new_args table
          if not displayfield then  
origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
            displayfield = ulx[indx]["date"]
found = true; -- flag so that we can break out of these nested for loops
          end
break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
        else
end
          if not displayfield then  
end
            displayfield = "Page " .. i
          end
if found then -- true when we found an alias that matched name
        end
found = false; -- reset the flag
        sand = sand .. "[" .. ulx[indx]["url"] .. " " .. displayfield .. "]"
break; -- go do next args k/v pair
        if i == tot then
end
          sand = sand .. "."
end
        else
else -- enumerated parameters
          sand = sand .. ", "
name = name:gsub ('%d$', '#'); -- replace enumeration digits with place holder for table search
        end
-- TODO: insert shortcut here? if num_params[name] then name holds the canonical parameter name; no need to search further
      end
for pname, aliases in pairs (enum_params) do -- loop through each parameter the num_params table
      return sand
for _, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
    end
if name == alias then
pname = pname:gsub ('#$', enum); -- replace the '#' place holder with the actual enumerator
new_args[pname] = v; -- create a new entry in the new_args table
origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
found = true; -- flag so that we can break out of these nested for loops
break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
end
end
if found then -- true when we found an alias that matched name
found = false; -- reset the flag
break; -- go do next args k/v pair
end
end
end
else
unnamed_params = true; -- flag for unsupported positional parameters
end
end -- for k, v
return new_args, origin, unnamed_params;
end
end


function p.webarchive(frame)
  args = frame.args
  if (args[1]==nil) and (args["url"]==nil) then          -- if no argument provided than check parent template/module args
    args = frame:getParent().args
  end
  local tname = "Webarchive"                              -- name of calling template. Change if template rename.
  ulx = {}                                                -- Associative array to hold template data
  track = {}                                              -- Associative array to hold tracking categories
  maxurls = 10                                            -- Max number of URLs allowed.
  local verifydates = "no"                              -- See documentation. Set "no" to disable.


--[[--------------------------< W E B A R C H I V E >----------------------------------------------------------
                                                          -- URL argument (first)


template entry point
  local url1 = trimArg(args.url) or trimArg(args.url1)         
  if not url1 then
    return inlineError("url", "Empty.") .. createTracking()
  end
  if mw.ustring.find( url1, "https://web.http", 1, true ) then    -- track bug
    track["Category:Galat templat Webarchive"] = 1
    return inlineError("url", "https://web.http") .. createTracking()
  end
  if url1 == "https://web.archive.org/http:/" then                -- track bug
    track["Category:Galat templat Webarchive"] = 1
    return inlineError("url", "Invalid URL") .. createTracking()
  end


]]
  ulx.url1 = {}
  ulx.url1.url = url1
  if not mw.ustring.find( mw.ustring.lower(url1), "^http") then
    if not mw.ustring.find( url1, "^//") then
      ulx.url1.url = "http://" .. url1
    end
  end
  local uri1 = mw.uri.new(ulx.url1.url)
  ulx.url1.host = uri1.host
  ulx.url1.extraurls = parseExtraArgs()


local function webarchive(frame)
                                                          -- Nolink argument
local args = getArgs (frame);


local data = mw.loadData (table.concat ({ -- make a data module name; sandbox or live
  local nolink = trimArg2(args.nolink)
'Module:Webarchive/data',
frame:getTitle():find('sandbox', 1, true) and '/sandbox' or '' -- this instance is ./sandbox then append /sandbox
}));
categories = data.categories; -- fill in the forward declarations
config = data.config;
if data.digits.enable then
digits = data.digits; -- for i18n; table of digits in the local wiki's language
non_western_digits = true; -- use_non_western_digits
end
err_warn_msgs = data.err_warn_msgs;
excepted_pages = data.excepted_pages;
month_num = data.month_num; -- for i18n; table of month names in the local wiki's language
prefixes = data.prefixes;
services = data.services;
s_text = data.s_text;
uncategorized_namespaces = data.uncategorized_namespaces;
uncategorized_subpages = data.uncategorized_subpages;


local origin = {}; -- holds a map of English to local language parameter names used in the current template; not currently used
  serviceName(uri1.host, nolink)
local unnamed_params; -- boolean set to true when template call has unnamed parameters
args, origin, unnamed_params = parameter_name_xlate (args, data.params, data.enum_params); -- translate parameter names in args to English


local date, format, msg, udate, uri, url;
                                                          -- Date argument
local ldf = 'iso'; -- when there is no |date= parameter, render url dates in iso format
if args.url and args.url1 then -- URL argument (first)
return inlineError (data.crit_err_msgs.conflicting, {origin.url, origin.url1});
end
url = args.url or args.url1;
if not url then
return inlineError (data.crit_err_msgs.empty);
end
-- these iabot bugs perportedly fixed; removing these causes lua script error
--[[ -- at Template:Webarchive/testcases/Production; resolve that before deleting these tests
if mw.ustring.find( url, "https://web.http", 1, true ) then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
track[categories.error] = 1;
return inlineError (data.crit_err_msgs.iabot1);
end
if url == "https://web.archive.org/http:/" then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
track[categories.error] = 1;
return inlineError (data.crit_err_msgs.iabot2);
end
]]


if not (url:lower():find ('^http') or url:find ('^//')) then
  local date = trimArg(args.date) or trimArg(args.date1)
return inlineError (data.crit_err_msgs.invalid_url );
  if date == "*" and (ulx.url1.service == "wayback" or ulx.url1.service == "locwebarchives") then
end
    date = "index"
  elseif date and (ulx.url1.service == "wayback" or ulx.url1.service == "locwebarchives") and verifydates == "yes" then
    local ldf = dateFormat(date)
    if ldf then
      local udate = decodeWaybackDate( uri1.path, ldf )
      if udate ~= date then
        date = udate .. inlineRed("<sup>[Date mismatch]</sup>", "warning")     
      end
    end
  elseif date and ulx.url1.service == "webcite" and verifydates == "yes" then
    local ldf = dateFormat(date)
    if ldf then
      local udate = decodeWebciteDate( uri1.path, ldf )
      if udate == "query" then -- skip
      elseif udate ~= date then
        date = udate .. inlineRed("<sup>[Date mismatch]</sup>", "warning")    
      end
    end
  elseif date and ulx.url1.service == "archiveis" and verifydates == "yes" then
    local ldf = dateFormat(date)
    if ldf then
        local udate = decodeArchiveisDate( uri1.path, ldf )
        if udate == "short link" then -- skip
        elseif udate ~= date then
          date = udate .. inlineRed("<sup>[Date mismatch]</sup>", "warning")    
        end
    end
  elseif not date and (ulx.url1.service == "wayback" or ulx.url1.service == "locwebarchives") then
    date = decodeWaybackDate( uri1.path, "iso" )
    if not date then
      date = inlineRed("[Date error] (1)", "error")
    end
  elseif not date and ulx.url1.service == "webcite" then
    date = decodeWebciteDate( uri1.path, "iso" )
    if date == "query" then
      date = inlineRed("[Date missing]", "warning")
    elseif not date then
      date = inlineRed("[Date error] (1)", "error")
    end
  elseif not date and ulx.url1.service == "archiveis" then
    date = decodeArchiveisDate( uri1.path, "iso" )
    if date == "short link" then
        date = inlineRed("[Date missing]", "warning")
    elseif not date then
        date = inlineRed("[Date error] (1)", "error")
    end
  elseif not date then
    date = inlineRed("[Date missing]", "warning")
  end
  ulx.url1.date = date


ulx.url1 = {}
                                                          -- Format argument
ulx.url1.url = url


ulx.url1.extraurls = parseExtraArgs(args)
  local format = trimArg(args.format)
  if not format then
    format = "none"
  else
    if format == "addlpages" then
      if not ulx.url1.date then
        format = "none"
      end
    elseif format == "addlarchives" then
      format = "addlarchives"
    else
      format = "none"
    end
  end
  ulx.url1.format = format


local good = false;
                                                          -- Title argument
good, uri = pcall (mw.uri.new, ulx.url1.url); -- get a table of uri parts from this url; protected mode to prevent lua error when ulx.url1.url is malformed
if not good or nil == uri.host then -- abandon when ulx.url1.url is malformed
return inlineError (data.crit_err_msgs.invalid_url);
end
serviceName(uri.host, args.nolink)


if args.date and args.date1 then -- Date argument
  local title = trimArg(args.title) or trimArg(args.title1)
return inlineError (data.crit_err_msgs.conflicting, {origin.date, origin.date1});
  ulx.url1.title = title
end
 
date = args.date or args.date1;
date = date and date:gsub (' +', ' '); -- replace multiple spaces with a single space


if date and config.verifydates then
  local rend = createRendering()
if '*' == date then
  if not rend then
date = 'index';
    rend = '<span style="font-size:100%" class="error citation-comment">Error in [[:Template:' .. tname .. ']]: Unknown problem. Please report on template talk page.</span>'
ldf = 'iso'; -- set to default format
    track["Category:Galat templat Webarchive"] = 1
elseif 'mdy' == date then
  end
date = nil; -- if date extracted from URL,
ldf = 'mdy'; -- then |date=mdy overrides iso
elseif 'dmy' == date then
date = nil; -- if date extracted from URL,
ldf = 'dmy'; -- then |date=dmy overrides iso
elseif 'ymd' == date then
date = nil; -- if date extracted from URL,
ldf = 'ymd'; -- then |date=ymd overrides iso
else
date, ldf = decode_date (date); -- get an iso format date from date and get date's original format
end
end


if 'wayback' == ulx.url1.service or 'locwebarchives' == ulx.url1.service or 'ukgwa' == ulx.url1.service then
  return rend .. createTracking()
if date then
if config.verifydates then
if ldf then
udate, msg = decodeWaybackDate (uri.path); -- get the url date in iso format and format of date in |date=; 'index' when wayback url date is *
if not udate then -- this is the only 'fatal' error return
return inlineError (data.crit_err_msgs[msg]);
end
 
if udate ~= date then -- date comparison using iso format dates
date = udate;
msg = table.concat ({
inlineRed (err_warn_msgs.mismatch, 'warning'), -- add warning message
msg, -- add message if there is one
});
end
end
end
else -- no |date=
udate, msg = decodeWaybackDate (uri.path);
 
if not udate then -- this is the only 'fatal' error return
return inlineError (data.crit_err_msgs[msg]);
end
 
if '' == udate then
date = nil; -- unset
else
date = udate;
end
end
 
elseif 'webcite' == ulx.url1.service then
if date then
if config.verifydates then
if ldf then
udate = decodeWebciteDate (uri.path); -- get the url date in iso format
if 'query' ~= udate then -- skip if query
if udate ~= date then -- date comparison using iso format dates
date = udate;
msg = table.concat ({
inlineRed (err_warn_msgs.mismatch, 'warning'),
});
end
end
end
end
else
date = decodeWebciteDate( uri.path, "iso" )
if date == "query" then
date = nil; -- unset
msg = inlineRed (err_warn_msgs.date_miss, 'warning');
elseif not date then -- invalid base62 string
date = inlineRed (err_warn_msgs.date1, 'error');
end
end
 
elseif 'archiveis' == ulx.url1.service then
if date then
if config.verifydates then
if ldf then
udate, msg = decodeArchiveisDate (uri.path) -- get the url date in iso format
if 'short link' ~= udate then -- skip if short link
if udate ~= date then -- date comparison using iso format dates
date = udate;
msg = table.concat ({
inlineRed (err_warn_msgs.mismatch, 'warning'), -- add warning message
msg, -- add message if there is one
});
end
end
end
end
else -- no |date=
udate, msg = decodeArchiveisDate( uri.path, "iso" )
if udate == "short link" then
date = nil; -- unset
msg = inlineRed (err_warn_msgs.date_miss, 'warning');
elseif '' == udate then
date = nil; -- unset
else
date = udate;
end
end
else -- some other service
if not date then
msg = inlineRed (err_warn_msgs.date_miss, 'warning');
end
end
 
if 'index' == date then
ulx.url1.date = date .. (msg or ''); -- create index + message (if there is one)
elseif date then
ulx.url1.date = makeDate (date, nil, nil, ldf) .. (msg or ''); -- create a date in the wiki's local language + message (if there is one)
else
ulx.url1.date = msg;
end
format = args.format; -- Format argument
 
if not format then
format = "none"
else
for k, v in pairs (data.format_vals) do -- |format= accepts two specific values loop through a table of those values
local found; -- declare a nil flag
for _, p in ipairs (v) do -- loop through local language variants
if format == p then -- when |format= value matches
format = k; -- use name from table key
found = true; -- declare found so that we can break out of outer for loop
break; -- break out of inner for loop
end
end
if found then
break;
end
end
 
if format == "addlpages" then
if not ulx.url1.date then
format = "none"
end
elseif format == "addlarchives" then
format = "addlarchives"
else
format = "none"
end
end
ulx.url1.format = format
 
if args.title and args.title1 then -- Title argument
return inlineError (data.crit_err_msgs.conflicting, {origin.title, origin.title1});
end
 
ulx.url1.title = args.title or args.title1;
 
local rend = createRendering()
if not rend then
return inlineError (data.crit_err_msgs.unknown);
end
 
return rend .. ((unnamed_params and inlineRed (err_warn_msgs.unnamed_params, 'warning')) or '') .. createTracking();


end
end


 
return p
--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
]]
 
return {webarchive = webarchive};

Revisi per 4 Oktober 2023 13.40

Dokumentasi untuk modul ini dapat dibuat di Modul:Webarchive/doc

--[[ ----------------------------------

     Lua module implementing the {{webarchive}} template. 

       A merger of the functionality of three templates: {{wayback}}, {{webcite}} and {{cite archives}}
   
  ]]

local p = {}

--[[--------------------------< inlineError >-----------------------

     Critical error. Render output completely in red. Add to tracking category.

 ]]

local function inlineError(arg, msg)

  track["Category:Galat templat Webarchive"] = 1
  return '<span style="font-size:100%" class="error citation-comment">Error in webarchive template: Check <code style="color:inherit; border:inherit; padding:inherit;">&#124;' .. arg .. '=</code> value. ' .. msg .. '</span>'

end

--[[--------------------------< inlineRed >-----------------------

      Render a text fragment in red, such as a warning as part of the final output.
      Add tracking category.

 ]]

local function inlineRed(msg, trackmsg)

  if trackmsg == "warning" then
    track["Category:Peringatan templat Webarchive"] = 1 
  elseif trackmsg == "error" then
    track["Category:Galat templat Webarchive"] = 1 
  end

  return '<span style="font-size:100%" class="error citation-comment">' .. msg .. '</span>'

end

--[[--------------------------< trimArg >-----------------------

     trimArg returns nil if arg is "" while trimArg2 returns 'true' if arg is "" 
     trimArg2 is for args that might accept an empty value, as an on/off switch like nolink=

 ]]

local function trimArg(arg)
  if arg == "" or arg == nil then
    return nil
  else
    return mw.text.trim(arg)
  end
end
local function trimArg2(arg)
  if arg == nil then
    return nil
  else
    return mw.text.trim(arg)
  end
end

--[[--------------------------< base62 >-----------------------

     Convert base-62 to base-10
     Credit: https://de.wikipedia.org/wiki/Modul:Expr 

  ]]

local function base62( value )

    local r = 1

    if value:match( "^%w+$" ) then
        local n = #value
        local k = 1
        local c
        r = 0
        for i = n, 1, -1 do
            c = value:byte( i, i )
            if c >= 48  and  c <= 57 then
                c = c - 48
            elseif c >= 65  and  c <= 90 then
                c = c - 55
            elseif c >= 97  and  c <= 122 then
                c = c - 61
            else    -- How comes?
                r = 1
                break    -- for i
            end
            r = r + c * k
            k = k * 62
        end -- for i
    end
    return r
end 

--[[--------------------------< tableLength >-----------------------

      Given a 1-D table, return number of elements

  ]]

local function tableLength(T)
  local count = 0
  for _ in pairs(T) do count = count + 1 end
  return count
end


--[[--------------------------< dateFormat >-----------------------

     Given a date string, return its format: dmy, mdy, iso, ymd
       If unable to determine return nil

  ]]

local function dateFormat(date)

  local dt = {}
  dt.split = {}

  dt.split = mw.text.split(date, "-")
  if tableLength(dt.split) == 3 then
    if tonumber(dt.split[1]) > 1900 and tonumber(dt.split[1]) < 2200 and tonumber(dt.split[2]) and tonumber(dt.split[3]) then
      return "iso"
    else
      return nil
    end
  end  

  dt.split = mw.text.split(date, " ")
  if tableLength(dt.split) == 3 then
    if tonumber(dt.split[3]) then
      if tonumber(dt.split[3]) > 1900 and tonumber(dt.split[3]) < 2200 then
        if tonumber(dt.split[1]) then
          return "dmy"
        else
          return "mdy"
        end 
      else
        if tonumber(dt.split[1]) then
          if tonumber(dt.split[1]) > 1900 and tonumber(dt.split[1]) < 2200 then
            return "ymd"
          end
        end
      end
    end
  end
  return nil

end

--[[--------------------------< makeDate >-----------------------

     Given a zero-padded 4-digit year, 2-digit month and 2-digit day, return a full date in df format
     df = mdy, dmy, iso, ymd

 ]]

local function makeDate(year, month, day, df)

  if not year or year == "" or not month or month == "" or not day or day == "" then
    return nil
  end

  local zmonth = month                                                      -- month with leading 0
  month = month:match("0*(%d+)")                                            -- month without leading 0
  if tonumber(month) < 1 or tonumber(month) > 12 then
    return year
  end
  local nmonth = os.date("%B", os.time{year=2000, month=month, day=1} )     -- month in name form       
  if not nmonth then
    return year
  end

  local zday = day
  day = zday:match("0*(%d+)")
  if tonumber(day) < 1 or tonumber(day) > 31 then
    if df == "mdy" or df == "dmy" then
      return nmonth .. " " .. year
    elseif df == "iso" then
      return year .. "-" .. zmonth 
    elseif df == "ymd" then
      return year .. " " .. nmonth
    else
      return nmonth .. " " .. year
    end
  end                                       

  if df == "mdy" then
    return nmonth .. " " .. day .. ", " .. year         -- September 1, 2016
  elseif df == "dmy" then
    return day .. " " .. nmonth .. " " .. year          -- 1 September 2016
  elseif df == "iso" then
    return year .. "-" .. zmonth .. "-" .. zday         -- 2016-09-01
  elseif df == "ymd" then
    return year .. " " .. nmonth .. " " .. day          -- 2016 September 1
  else
    return nmonth .. " " .. day .. ", " .. year         -- September 1, 2016
  end

end


--[[--------------------------< decodeWebciteDate >-----------------------

      Given a URI-path to Webcite (eg. /67xHmVFWP) return the encoded date in df format

  ]]
local function decodeWebciteDate(path, df)

    local dt = {}
    dt.split = {}

    dt.split = mw.text.split(path, "/")

    -- valid URL formats that are not base62

    -- http://www.webcitation.org/query?id=1138911916587475
    -- http://www.webcitation.org/query?url=http..&date=2012-06-01+21:40:03
    -- http://www.webcitation.org/1138911916587475
    -- http://www.webcitation.org/cache/73e53dd1f16cf8c5da298418d2a6e452870cf50e
    -- http://www.webcitation.org/getfile.php?fileid=1c46e791d68e89e12d0c2532cc3cf629b8bc8c8e

    if mw.ustring.find( dt.split[2], "query", 1, true) or 
       mw.ustring.find( dt.split[2], "cache", 1, true) or
       mw.ustring.find( dt.split[2], "getfile", 1, true) or
       tonumber(dt.split[2]) then
      return "query"
    end

    dt.full = os.date("%Y %m %d", string.sub(string.format("%d", base62(dt.split[2])),1,10) )
    dt.split = mw.text.split(dt.full, " ")
    dt.year = dt.split[1]
    dt.month = dt.split[2]
    dt.day = dt.split[3]

    if not tonumber(dt.year) or not tonumber(dt.month) or not tonumber(dt.day) then
      return inlineRed("[Date error] (1)", "error")
    end

    if tonumber(dt.month) > 12 or tonumber(dt.day) > 31 or tonumber(dt.month) < 1 then
      return inlineRed("[Date error] (2)", "error")
    end
    if tonumber(dt.year) > tonumber(os.date("%Y")) or tonumber(dt.year) < 1900 then
      return inlineRed("[Date error] (3)", "error")
    end

    local fulldate = makeDate(dt.year, dt.month, dt.day, df)
    if not fulldate then
      return inlineRed("[Date error] (4)", "error")
    else
      return fulldate
    end

end

--[[--------------------------< decodeWaybackDate >-----------------------

Given a URI-path to Wayback (eg. /web/20160901010101/http://example.com )
  or Library of Congress Web Archives (/all/20160901010101/http://example.com)
  return the formatted date eg. "September 1, 2016" in df format 
  Handle non-digits in snapshot ID such as "re_" and "-" and "*"

 ]]

local function decodeWaybackDate(path, df)

    local snapdate, snapdatelong, currdate, fulldate

    local safe = path
    snapdate = string.gsub(safe, "^/all/", "")                          -- Remove leading "/all/"
    safe = snapdate
    snapdate = string.gsub(safe, "^/w?e?b?/?", "")                      -- Remove leading "/web/" or "/"
    safe = snapdate
    local N = mw.text.split(safe, "/")
    snapdate = N[1]
    if snapdate == "*" then                                             -- eg. /web/*/http.. or /all/*/http..
      return "index"
    end
    safe = snapdate
    snapdate = string.gsub(safe, "[a-z][a-z]_[0-9]?$", "")              -- Remove any trailing "re_" from date 
    safe = snapdate
    snapdate = string.gsub(safe, "[-]", "")                             -- Remove dashes from date eg. 2015-01-01 
    safe = snapdate
    snapdate = string.gsub(safe, "[*]$", "")                            -- Remove trailing "*" 

    if not tonumber(snapdate) then
      return inlineRed("[Date error] (2)", "error")
    end
    local dlen = string.len(snapdate)
    if dlen < 4 then
      return inlineRed("[Date error] (3)", "error")
    end
    if dlen < 14 then
      snapdatelong = snapdate .. string.rep("0", 14 - dlen)
    else
      snapdatelong = snapdate
    end
    local year = string.sub(snapdatelong, 1, 4)
    local month = string.sub(snapdatelong, 5, 6)
    local day = string.sub(snapdatelong, 7, 8)
    if not tonumber(year) or not tonumber(month) or not tonumber(day) then
      return inlineRed("[Date error] (4)", "error")
    end
    if tonumber(month) > 12 or tonumber(day) > 31 or tonumber(month) < 1 then
      return inlineRed("[Date error] (5)", "error")
    end
    currdate = os.date("%Y")
    if tonumber(year) > tonumber(currdate) or tonumber(year) < 1900 then
      return inlineRed("[Date error] (6)", "error")
    end

    fulldate = makeDate(year, month, day, df)
    if not fulldate then
      return inlineRed("[Date error] (7)", "error")
    else
      return fulldate
    end

end

--[[--------------------------< decodeArchiveisDate >-----------------------

  Given an Archive.is "long link" URI-path (e.g. /2016.08.28-144552/http://example.com)
  return the date in df format (e.g. if df = dmy, return 28 August 2016)
  Handles "." and "-" in snapshot date, so 2016.08.28-144552 is same as 20160828144552

  ]]

local function decodeArchiveisDate(path, df)

    local snapdate, snapdatelong, currdate, fulldate

    local safe = path
    local N = mw.text.split(safe, "/")
    safe = N[2]                                                         -- get snapshot date, e.g. 2016.08.28-144552
    snapdate = string.gsub(safe, "[%.%-]", "")                          -- remove periods and hyphens

    if not tonumber(snapdate) then                                      -- if not numeric, it is "short link", not date
        return "short link"                                             -- e.g. http://archive.is/hD1qz
    end

    local dlen = string.len(snapdate)
    if dlen < 4 then
        return inlineRed("[Date error] (3)", "error")
    end
    if dlen < 14 then
        snapdatelong = snapdate .. string.rep("0", 14 - dlen)
    else
        snapdatelong = snapdate
    end
    local year = string.sub(snapdatelong, 1, 4)
    local month = string.sub(snapdatelong, 5, 6)
    local day = string.sub(snapdatelong, 7, 8)
    if not tonumber(year) or not tonumber(month) or not tonumber(day) then
        return inlineRed("[Date error] (4)", "error")
    end
    if tonumber(month) > 12 or tonumber(day) > 31 or tonumber(month) < 1 then
        return inlineRed("[Date error] (5)", "error")
    end
    currdate = os.date("%Y")
    if tonumber(year) > tonumber(currdate) or tonumber(year) < 1900 then
        return inlineRed("[Date error] (6)", "error")
    end

    fulldate = makeDate(year, month, day, df)
    if not fulldate then
        return inlineRed("[Date error] (7)", "error")
    else
        return fulldate
    end

 end


--[[--------------------------< serviceName >-----------------------

     Given a domain extracted by mw.uri.new() (eg. web.archive.org) set tail string and service ID

  ]]

local function serviceName(host, nolink)

  local tracking = "Category:Templat webarchive arsip lain"

  local bracketopen = "[["
  local bracketclose = "]]"
  if nolink then
    bracketopen = ""
    bracketclose = ""
  end

  ulx.url1.service = "other"
  ulx.url1.tail = " di " .. ulx.url1.host .. " " .. inlineRed("Galat: URL arsip tidak dikenal")

  host = string.lower(host)

  if mw.ustring.find( host, "europarchive.org", 1, true ) then  -- any containing "archive.org" listed before Wayback to avoid disambiguation
    ulx.url1.tail = " di " .. bracketopen .. "National Library of Ireland" .. bracketclose
  elseif mw.ustring.find( host, "webarchive.org.uk", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "UK Web Archive" .. bracketclose
  elseif mw.ustring.find( host, "archive.org", 1, true ) then
    ulx.url1.service = "wayback"
    ulx.url1.tail = " di " .. bracketopen .. "Wayback Machine" .. bracketclose
    tracking = "Category:Templat webarchive tautan wayback"
  elseif mw.ustring.find( host, "webcitation.org", 1, true ) then
    ulx.url1.service = "webcite"
    ulx.url1.tail = " di " .. bracketopen .. "WebCite" .. bracketclose
    tracking = "Category:Templat webarchive tautan webcite"
  elseif mw.ustring.find( host, "archive.is", 1, true ) then
    ulx.url1.service = "archiveis"
    ulx.url1.tail = " di " .. bracketopen .. "Archive.is" .. bracketclose
    tracking = "Category:Templat webarchive tautan archiveis"
  elseif mw.ustring.find( host, "archive.fo", 1, true ) then
    ulx.url1.service = "archiveis"
    ulx.url1.tail = " di " .. bracketopen .. "Archive.is" .. bracketclose
    tracking = "Category:Templat webarchive tautan archiveis"
  elseif mw.ustring.find( host, "archive.today", 1, true ) then
    ulx.url1.service = "archiveis"
    ulx.url1.tail = " di " .. bracketopen .. "Archive.is" .. bracketclose
    tracking = "Category:Templat webarchive tautan archiveis"
  elseif mw.ustring.find( host, "archive.li", 1, true ) then
    ulx.url1.service = "archiveis"
    ulx.url1.tail = " di " .. bracketopen .. "Archive.is" .. bracketclose
    tracking = "Category:Templat webarchive tautan archiveis"
  elseif mw.ustring.find( host, "archive.ec", 1, true ) then
    ulx.url1.service = "archiveis"
    ulx.url1.tail = " di " .. bracketopen .. "Archive.is" .. bracketclose
    tracking = "Category:Templat webarchive tautan archiveis"
  elseif mw.ustring.find( host, "archive-it.org", 1, true ) then
    ulx.url1.service = "archiveit"
    ulx.url1.tail = " di " .. bracketopen .. "Archive-It" .. bracketclose
  elseif mw.ustring.find( host, "wikiwix.com", 1, true ) then
    ulx.url1.tail = " di Wikiwix" 
  elseif mw.ustring.find( host, "arquivo.pt", 1, true) then
    ulx.url1.tail = " di " .. "Portuguese Web Archive" 
  elseif mw.ustring.find( host, "webarchive.loc.gov", 1, true ) then
    ulx.url1.service = "locwebarchives"
    ulx.url1.tail = " di " .. bracketopen .. "Library of Congress" .. bracketclose .. " Web Archives"
  elseif mw.ustring.find( host, "loc.gov", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Library of Congress" .. bracketclose
  elseif mw.ustring.find( host, "webharvest.gov", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "National Archives and Records Administration" .. bracketclose
  elseif mw.ustring.find( host, "bibalex.org", 1, true ) then
    ulx.url1.tail = " di " .. "[[Bibliotheca_Alexandrina#Internet_Archive_partnership|Bibliotheca Alexandrina]]"
  elseif mw.ustring.find( host, "collectionscanada", 1, true ) then
    ulx.url1.tail = " di " .. "Canadian Government Web Archive"
  elseif mw.ustring.find( host, "haw.nsk", 1, true ) then
    ulx.url1.tail = " di " .. "Croatian Web Archive (HAW)"
  elseif mw.ustring.find( host, "veebiarhiiv.digar.ee", 1, true ) then
    ulx.url1.tail = " di " .. "Estonian Web Archive"
  elseif mw.ustring.find( host, "vefsafn.is", 1, true ) then
    ulx.url1.tail = " di " .. "[[National and University Library of Iceland]]"
  elseif mw.ustring.find( host, "proni.gov", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Public Record Office of Northern Ireland" .. bracketclose
  elseif mw.ustring.find( host, "uni-lj.si", 1, true ) then
    ulx.url1.tail = " di " .. "Slovenian Web Archive"
  elseif mw.ustring.find( host, "stanford.edu", 1, true ) then
    ulx.url1.tail = " di " .. "[[Stanford University Libraries|Stanford Web Archive]]"
  elseif mw.ustring.find( host, "nationalarchives.gov.uk", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "UK Government Web Archive" .. bracketclose
  elseif mw.ustring.find( host, "parliament.uk", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "UK Parliament's Web Archive" .. bracketclose
  elseif mw.ustring.find( host, "nlb.gov.sg", 1, true ) then
    ulx.url1.tail = " di " .. "Web Archive Singapore" 
  elseif mw.ustring.find( host, "pandora.nla.gov.au", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Pandora Archive" .. bracketclose 
  elseif mw.ustring.find( host, "perma.cc", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Perma.cc" .. bracketclose
  elseif mw.ustring.find( host, "perma-archives.cc", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Perma.cc" .. bracketclose
  elseif mw.ustring.find( host, "screenshots.com", 1, true ) then
    ulx.url1.tail = " di Screenshots" 
  elseif mw.ustring.find( host, "freezepage.com", 1, true ) then
    ulx.url1.tail = " di Freezepage" 
  elseif mw.ustring.find( host, "yorku.ca", 1, true ) then
    ulx.url1.tail = " di " .. "[[York University Libraries|York University Digital Library]]"
  elseif mw.ustring.find( host, "webcache.googleusercontent.com", 1, true ) then
    ulx.url1.tail = " di Google Cache" 
  elseif mw.ustring.find( host, "timetravel.mementoweb.org", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Memento Project" .. bracketclose
  elseif mw.ustring.find( host, "langzeitarchivierung.bib-bvb.de", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Bavarian State Library" .. bracketclose
  elseif mw.ustring.find( host, "webrecorder.io", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "webrecorder.io" .. bracketclose
  elseif mw.ustring.find( host, "webarchive.bac-lac.gc.ca", 1, true ) then
    ulx.url1.tail = " di " .. bracketopen .. "Library and Archives Canada" .. bracketclose
  else
    tracking = "Category:Templat webarchive arsip tidak diketahui"
  end

  track[tracking] = 1

end

--[[--------------------------< parseExtraArgs >-----------------------

     Parse numbered arguments starting at 2, such as url2..url10, date2..date10, title2..title10
       For example: {{webarchive |url=.. |url4=.. |url7=..}}
         Three url arguments not in numeric sequence (1..4..7). 
         Function only processes arguments numbered 2 or greater (in this case 4 and 7)
         It creates numeric sequenced table entries like:
           urlx.url2.url = <argument value for url4>
           urlx.url3.url = <argument value for url7>
       Returns the number of URL arguments found numbered 2 or greater (in this case returns "2")

 ]]

local function parseExtraArgs()

  local i, j, argurl, argurl2, argdate, argtitle

  j = 2
  for i = 2, maxurls do
    argurl = "url" .. i
    if trimArg(args[argurl]) then
      argurl2 = "url" .. j
      ulx[argurl2] = {}
      ulx[argurl2]["url"] = args[argurl]
      argdate = "date" .. j
      if trimArg(args[argdate]) then
        ulx[argurl2]["date"] = args[argdate]
      else
        ulx[argurl2]["date"] = inlineRed("[Date missing]", "warning")
      end
      argtitle = "title" .. j
      if trimArg(args[argtitle]) then
        ulx[argurl2]["title"] = args[argtitle]
      else
        ulx[argurl2]["title"] = nil
      end
      j = j + 1
    end
  end

  if j == 2 then
    return 0
  else
    return j - 2
  end

end

--[[--------------------------< comma >-----------------------

     Given a date string, return "," if it's MDY 

  ]]

local function comma(date)
  local N = mw.text.split(date, " ")
  local O = mw.text.split(N[1], "-") -- for ISO
  if O[1] == "index" then return "" end
  if not tonumber(O[1]) then
    return ","
  else
    return ""
  end
end

--[[--------------------------< createTracking >-----------------------

     Return data in track[] ie. tracking categories

  ]]

local function createTracking()

  local sand = ""
  if tableLength(track) > 0 then                        
    for key,_ in pairs(track) do
      sand = sand .. "[[" .. key .. "]]"
    end
  end
  return sand

end

--[[--------------------------< createRendering >-----------------------

     Return a rendering of the data in ulx[][]

  ]]

local function createRendering()

    local sand, displayheader, displayfield

    local period1 = ""   -- For backwards compat with {{wayback}}
    local period2 = "."                                                            
  
    local indexstr = "diarsipkan tanggal"
    if ulx.url1.date == "index" then
      indexstr = "archive"
    end  
                                                                                          -- For {{wayback}}, {{webcite}}

    if ulx.url1.format == "none" then                                                     
      if not ulx.url1.title and not ulx.url1.date then                                    -- No title. No date
        sand = "[" .. ulx.url1.url .. " Diarsipkan]" .. ulx.url1.tail
      elseif not ulx.url1.title and ulx.url1.date then                                    -- No title. Date.
        if ulx.url1.service == "wayback" then 
          period1 = "."
          period2 = "" 
        end
        sand = "[" .. ulx.url1.url .. " Diarsipkan] " .. ulx.url1.date .. comma(ulx.url1.date) .. ulx.url1.tail .. period1
      elseif ulx.url1.title and not ulx.url1.date then                                    -- Title. No date.
        sand = "[" .. ulx.url1.url .. " " .. ulx.url1.title .. "]" .. ulx.url1.tail
      elseif ulx.url1.title and ulx.url1.date then                                        -- Title. Date.
        sand = "[" .. ulx.url1.url .. " " .. ulx.url1.title .. "]" .. ulx.url1.tail .. "&#32;(" .. indexstr .. " " .. ulx.url1.date .. ")"
      else
        return nil
      end
      if ulx.url1.extraurls > 0 then                                                      -- For multiple archive URLs
        local tot = ulx.url1.extraurls + 1
        sand = sand .. period2 .. " Additional archives: "
        for i=2,tot do
          local indx = "url" .. i
          if ulx[indx]["title"] then 
            displayfield = "title"
          else
            displayfield = "date"
          end
          sand = sand .. "[" .. ulx[indx]["url"] .. " " .. ulx[indx][displayfield] .. "]"
          if i == tot then
            sand = sand .. "."
          else
            sand = sand .. ", "
          end
        end
      else
        return sand  
      end
      return sand
                                                                                          -- For {{cite archives}}

    else                                                                  
      if ulx.url1.format == "addlarchives" then                           -- Multiple archive services 
        displayheader = "Additional archives: "
      else                                                                -- Multiple pages from the same archive 
        displayheader = "Additional pages archived&nbsp;on " .. ulx.url1.date .. ": "
      end
      local tot = 1 + ulx.url1.extraurls
      local sand = displayheader
      for i=1,tot do
        local indx = "url" .. i
        displayfield = ulx[indx]["title"]
        if ulx.url1.format == "addlarchives" then
          if not displayfield then 
            displayfield = ulx[indx]["date"]
          end
        else
          if not displayfield then 
            displayfield = "Page " .. i
          end
        end
        sand = sand .. "[" .. ulx[indx]["url"] .. " " .. displayfield .. "]"
        if i == tot then
          sand = sand .. "."
        else
          sand = sand .. ", "
        end
      end
      return sand
    end
end

function p.webarchive(frame)
  args = frame.args
  if (args[1]==nil) and (args["url"]==nil) then           -- if no argument provided than check parent template/module args
    args = frame:getParent().args 
  end
 
  local tname = "Webarchive"                              -- name of calling template. Change if template rename.
  ulx = {}                                                -- Associative array to hold template data 
  track = {}                                              -- Associative array to hold tracking categories
  maxurls = 10                                            -- Max number of URLs allowed. 
  local verifydates = "no"                               -- See documentation. Set "no" to disable.

                                                          -- URL argument (first)

  local url1 = trimArg(args.url) or trimArg(args.url1)           
  if not url1 then
    return inlineError("url", "Empty.") .. createTracking()
  end
  if mw.ustring.find( url1, "https://web.http", 1, true ) then    -- track bug 
    track["Category:Galat templat Webarchive"] = 1 
    return inlineError("url", "https://web.http") .. createTracking()
  end 
  if url1 == "https://web.archive.org/http:/" then                 -- track bug
    track["Category:Galat templat Webarchive"] = 1 
    return inlineError("url", "Invalid URL") .. createTracking()
  end

  ulx.url1 = {}
  ulx.url1.url = url1
  if not mw.ustring.find( mw.ustring.lower(url1), "^http") then
    if not mw.ustring.find( url1, "^//") then
      ulx.url1.url = "http://" .. url1
    end
  end
  local uri1 = mw.uri.new(ulx.url1.url)
  ulx.url1.host = uri1.host
  ulx.url1.extraurls = parseExtraArgs()

                                                          -- Nolink argument 

  local nolink = trimArg2(args.nolink)

  serviceName(uri1.host, nolink)

                                                          -- Date argument

  local date = trimArg(args.date) or trimArg(args.date1)
  if date == "*" and (ulx.url1.service == "wayback" or ulx.url1.service == "locwebarchives") then
    date = "index"
  elseif date and (ulx.url1.service == "wayback" or ulx.url1.service == "locwebarchives") and verifydates == "yes" then
    local ldf = dateFormat(date)
    if ldf then
      local udate = decodeWaybackDate( uri1.path, ldf )
      if udate ~= date then
        date = udate .. inlineRed("<sup>[Date mismatch]</sup>", "warning")       
      end
    end
  elseif date and ulx.url1.service == "webcite" and verifydates == "yes" then 
    local ldf = dateFormat(date)
    if ldf then
      local udate = decodeWebciteDate( uri1.path, ldf )
      if udate == "query" then -- skip
      elseif udate ~= date then
        date = udate .. inlineRed("<sup>[Date mismatch]</sup>", "warning")      
      end
    end
  elseif date and ulx.url1.service == "archiveis" and verifydates == "yes" then 
     local ldf = dateFormat(date)
     if ldf then
        local udate = decodeArchiveisDate( uri1.path, ldf )
        if udate == "short link" then -- skip
        elseif udate ~= date then
           date = udate .. inlineRed("<sup>[Date mismatch]</sup>", "warning")      
        end
     end
  elseif not date and (ulx.url1.service == "wayback" or ulx.url1.service == "locwebarchives") then
    date = decodeWaybackDate( uri1.path, "iso" )
    if not date then 
      date = inlineRed("[Date error] (1)", "error") 
    end
  elseif not date and ulx.url1.service == "webcite" then
    date = decodeWebciteDate( uri1.path, "iso" )
    if date == "query" then
      date = inlineRed("[Date missing]", "warning")
    elseif not date then 
      date = inlineRed("[Date error] (1)", "error")
    end
  elseif not date and ulx.url1.service == "archiveis" then
     date = decodeArchiveisDate( uri1.path, "iso" )
     if date == "short link" then
        date = inlineRed("[Date missing]", "warning")
     elseif not date then 
        date = inlineRed("[Date error] (1)", "error")
     end
  elseif not date then
    date = inlineRed("[Date missing]", "warning")
  end
  ulx.url1.date = date

                                                          -- Format argument 

  local format = trimArg(args.format)
  if not format then
    format = "none"
  else
    if format == "addlpages" then
      if not ulx.url1.date then
        format = "none"
      end
    elseif format == "addlarchives" then
      format = "addlarchives"
    else
      format = "none"
    end
  end
  ulx.url1.format = format

                                                          -- Title argument 

  local title = trimArg(args.title) or trimArg(args.title1)
  ulx.url1.title = title
  

  local rend = createRendering()
  if not rend then
    rend = '<span style="font-size:100%" class="error citation-comment">Error in [[:Template:' .. tname .. ']]: Unknown problem. Please report on template talk page.</span>'
    track["Category:Galat templat Webarchive"] = 1 
  end

  return rend .. createTracking()

end

return p