Modul:Webarchive: Perbedaan antara revisi

Dari Wiki Javasatu
Loncat ke navigasi Loncat ke pencarian
dw>Argo Carpathians
k (Cleanup.)
 
dw>WOSlinker
(use require('strict') instead of require('Module:No globals'))
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 = {}


--[[--------------------------< inlineError >-----------------------
--[[--------------------------< D E P E N D E N C I E S >------------------------------------------------------
]]
 
require('strict');
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 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
 
 
--[[--------------------------< 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
 
 
--[[--------------------------< 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
 


    Critical error. Render output completely in red. Add to tracking category.
--[=[-------------------------< M A K E _ W I K I L I N K >----------------------------------------------------


]]
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 inlineError(arg, msg)
]=]


  track["Category:Galat templat Webarchive"] = 1
local function make_wikilink (link, display, no_link)
  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>'
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


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


      Render a text fragment in red, such as a warning as part of the final output.
--[[--------------------------< createTracking >-----------------------
      Add tracking category.
 
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
 
end
 


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


local function inlineRed(msg, trackmsg)
Critical error. Render output completely in red. Add to tracking category.


  if trackmsg == "warning" then
This function called as the last thing before abandoning this module
    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>'
]]


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
end


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


    trimArg returns nil if arg is "" while trimArg2 returns 'true' if arg is ""
--[[--------------------------< inlineRed >-----------------------
    trimArg2 is for args that might accept an empty value, as an on/off switch like nolink=
 
Render a text fragment in red, such as a warning as part of the final output.
Add tracking category.


  ]]
  ]]


local function trimArg(arg)
local function inlineRed(msg, trackmsg)
  if arg == "" or arg == nil then
if trackmsg == "warning" then
    return nil
track[categories.warning] = 1;
  else
elseif trackmsg == "error" then
    return mw.text.trim(arg)
track[categories.error] = 1;
  end
end
end
 
local function trimArg2(arg)
return '<span style="font-size:100%" class="error citation-comment">' .. msg .. '</span>'
  if arg == nil then
    return nil
  else
    return mw.text.trim(arg)
  end
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


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


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


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


      Given a 1-D table, return number of elements
--[[--------------------------< D E C O D E _ D A T E >--------------------------------------------------------
 
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 decode_date (date_str)
local patterns = {
['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


local function tableLength(T)
return mw.ustring.format ('%.4d-%.2d-%.2d', t.y, t.m, t.d), k; -- return date in iso format
  local count = 0
end
  for _ in pairs(T) do count = count + 1 end
end
  return count
return nil, 'iso'; -- date could not be decoded; return nil and default iso date
end
end


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


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


    Given a date string, return its format: dmy, mdy, iso, ymd
on entry, year, month, day are presumed to be correct for the date that they represent; all are required
      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 dt = {}
local function makeDate (year, month, day, df)
  dt.split = {}
local format = {
['dmy'] = 'j F Y',
['mdy'] = 'F j, Y',
['ymd'] = 'Y F j',
['iso'] = 'Y-m-d',
};


  dt.split = mw.text.split(date, "-")
local date = table.concat ({year, month, day}, '-'); -- assemble year-initial numeric-format date (zero padding not required here)
  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 non_western_digits then -- this wiki uses non-western digits?
  if tableLength(dt.split) == 3 then
date = mw.ustring.gsub (date, '%d', digits); -- convert this wiki's non-western digits to western digits
    if tonumber(dt.split[3]) then
end
      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 >-----------------------


    Given a zero-padded 4-digit year, 2-digit month and 2-digit day, return a full date in df format
--[[--------------------------< I S _ V A L I D _ D A T E >----------------------------------------------------
    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)
]]


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


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


  local zday = day
if 1900 > y or today.year < y or 1 > m or 12 < m then -- year and month are within bounds TODO: 1900?
  day = zday:match("0*(%d+)")
return false;
  if tonumber(day) < 1 or tonumber(day) > 31 then
end
    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
if (2==m) then -- if February
    return nmonth .. " " .. day .. ", " .. year        -- September 1, 2016
month_length = 28; -- then 28 days unless
  elseif df == "dmy" then
if (0==(y%4) and (0~=(y%100) or 0==(y%400))) then -- is a leap year?
    return day .. " " .. nmonth .. " " .. year          -- 1 September 2016
month_length = 29; -- if leap year then 29 days in February
  elseif df == "iso" then
end
    return year .. "-" .. zmonth .. "-" .. zday        -- 2016-09-01
else
  elseif df == "ymd" then
month_length=days_in_month[m];
    return year .. " " .. nmonth .. " " .. day          -- 2016 September 1
end
  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 208: Baris 323:
--[[--------------------------< 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
local function decodeWebciteDate(path, df)
or the string 'query'


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


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


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


    -- http://www.webcitation.org/query?id=1138911916587475
dt = mw.text.split(path, "/")
    -- 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
-- valid URL formats that are not base62
      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) )
-- http://www.webcitation.org/query?id=1138911916587475
    dt.split = mw.text.split(dt.full, " ")
-- http://www.webcitation.org/query?url=http..&date=2012-06-01+21:40:03
    dt.year = dt.split[1]
-- http://www.webcitation.org/1138911916587475
    dt.month = dt.split[2]
-- http://www.webcitation.org/cache/73e53dd1f16cf8c5da298418d2a6e452870cf50e
    dt.day = dt.split[3]
-- http://www.webcitation.org/getfile.php?fileid=1c46e791d68e89e12d0c2532cc3cf629b8bc8c8e


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


    if tonumber(dt.month) > 12 or tonumber(dt.day) > 31 or tonumber(dt.month) < 1 then
decode = base62(dt[2]); -- base62 string -> exponential number
      return inlineRed("[Date error] (2)", "error")
if not decode then
    end
return nil; -- nil return when dt[2] contains characters not in %w
    if tonumber(dt.year) > tonumber(os.date("%Y")) or tonumber(dt.year) < 1900 then
end
      return inlineRed("[Date error] (3)", "error")
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
    end


    local fulldate = makeDate(dt.year, dt.month, dt.day, df)
decode = makeDate (dt.year, dt.month, dt.day, 'iso'); -- date comparisons are all done in iso format with western digits
    if not fulldate then
if non_western_digits then -- this wiki uses non-western digits?
      return inlineRed("[Date error] (4)", "error")
decode = mw.ustring.gsub (decode, '%d', digits); -- convert this wiki's non-western digits to western digits
    else
end
      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 (/all/20160901010101/http://example.com)
or Library of Congress Web Archives (eg. /all/20160901010101/http://example.com)
  return the formatted date eg. "September 1, 2016" in df format
or UK Government Web Archive (eg. /ukgwa/20160901010101/http://example.com or /tna/20160901010101/http://example.com)
  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 snapdate, snapdatelong, currdate, fulldate
local msg, snapdate;
 
snapdate = path:gsub ('^/web/', ''):gsub ('^/all/', ''):gsub ('^/ukgwa/', ''):gsub ('^/tna/', ''):gsub ('^/', ''); -- remove leading /web/, /all/, /ukgwa/, /tna/, or /
snapdate = snapdate:match ('^[^/]+'); -- get timestamp
if snapdate == "*" then -- eg. /web/*/http.., etc.
return 'index'; -- return indicator that this url has an index date
end
 
snapdate = snapdate:gsub ('%a%a_%d?$', ''):gsub ('%-', ''); -- from date, remove any trailing "re_", dashes
 
msg = '';
if snapdate:match ('%*$') then -- a trailing '*' causes calendar display at archive .org
snapdate = snapdate:gsub ('%*$', ''); -- remove so not part of length calc later
msg = inlineRed (err_warn_msgs.ts_cal, 'warning'); -- make a message
end
 
if not tonumber(snapdate) then
return nil, 'ts_nan'; -- return nil (fatal error flag) and message selector
end


    local safe = path
local dlen = snapdate:len();
    snapdate = string.gsub(safe, "^/all/", "")                         -- Remove leading "/all/"
if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
    safe = snapdate
return '', inlineRed (err_warn_msgs.ts_short, 'error'); -- return empty string and error message
    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 "*"


    if not tonumber(snapdate) then
local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here
      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 is_valid_date (year, month, day) then
    if not fulldate then
return '', inlineRed (err_warn_msgs.ts_date, 'error'); -- return empty string and error message
      return inlineRed("[Date error] (7)", "error")
end
    else
      return fulldate
    end


snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
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
end


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


  Given an Archive.is "long link" URI-path (e.g. /2016.08.28-144552/http://example.com)
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)
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
Handles "." and "-" in snapshot date, so 2016.08.28-144552 is same as 20160828144552
 
returns two values:
first value is one of these:
valid date string in df format - archive.is date is valid (including the text string 'short link' when url is the short form)
empty string - wayback date is malformed (not a number, less than 8 digits, not a valid date)
nil - wayback date is '/save/'
second return value is an appropriate 'message' may or may not be formatted


  ]]
]]


local function decodeArchiveisDate(path, df)
local function decodeArchiveisDate(path, df)
local snapdate


    local snapdate, snapdatelong, currdate, fulldate
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


    local safe = path
snapdate = mw.text.split (path, '/')[2]:gsub('[%.%-]', ''); -- get snapshot date, e.g. 2016.08.28-144552; remove periods and hyphens
    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
local dlen = string.len(snapdate)
        return "short link"                                            -- e.g. http://archive.is/hD1qz
if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
    end
return '', inlineRed (err_warn_msgs.ts_short, 'error'); -- return empty string and error message
end


    local dlen = string.len(snapdate)
local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here
    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 is_valid_date (year, month, day) then
    if not fulldate then
return '', inlineRed (err_warn_msgs.ts_date, 'error'); -- return empty string and error message
        return inlineRed("[Date error] (7)", "error")
end
    else
        return fulldate
    end


snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
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
  end


Baris 380: Baris 475:
--[[--------------------------< serviceName >-----------------------
--[[--------------------------< serviceName >-----------------------


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


  ]]
]]


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


  local tracking = "Category:Templat webarchive arsip lain"
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


  local bracketopen = "[["
else -- here when unknown archive
  local bracketclose = "]]"
ulx.url1.service = 'other';
  if nolink then
tracking = categories.unknown;
    bracketopen = ""
ulx.url1.tail = table.concat ({'', prefixes.at, host, inlineRed (err_warn_msgs.unknown_url, error)}, ' ');
    bracketclose = ""
end
  end
track[tracking] = 1
end


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


  host = string.lower(host)
--[[--------------------------< parseExtraArgs >-----------------------


  if mw.ustring.find( host, "europarchive.org", 1, true ) then  -- any containing "archive.org" listed before Wayback to avoid disambiguation
Parse numbered arguments starting at 2, such as url2..url10, date2..date10, title2..title10
    ulx.url1.tail = " di " .. bracketopen .. "National Library of Ireland" .. bracketclose
For example: {{webarchive |url=.. |url4=.. |url7=..}}
  elseif mw.ustring.find( host, "webarchive.org.uk", 1, true ) then
Three url arguments not in numeric sequence (1..4..7).  
    ulx.url1.tail = " di " .. bracketopen .. "UK Web Archive" .. bracketclose
Function only processes arguments numbered 2 or greater (in this case 4 and 7)
  elseif mw.ustring.find( host, "archive.org", 1, true ) then
It creates numeric sequenced table entries like:
    ulx.url1.service = "wayback"
urlx.url2.url = <argument value for url4>
    ulx.url1.tail = " di " .. bracketopen .. "Wayback Machine" .. bracketclose
urlx.url3.url = <argument value for url7>
    tracking = "Category:Templat webarchive tautan wayback"
Returns the number of URL arguments found numbered 2 or greater (in this case returns "2")
  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
]]


local function parseExtraArgs(args)
local i, j, argurl, argurl2, argdate, argtitle
j = 2
for i = 2, config.maxurls do
argurl = "url" .. i
if args[argurl] then
argurl2 = "url" .. j
ulx[argurl2] = {}
ulx[argurl2]["url"] = args[argurl]
argdate = "date" .. i
if args[argdate] then
ulx[argurl2]["date"] = args[argdate]
else
ulx[argurl2]["date"] = inlineRed (err_warn_msgs.date_miss, 'warning');
end
argtitle = "title" .. i
if 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
end


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


    Parse numbered arguments starting at 2, such as url2..url10, date2..date10, title2..title10
--[[--------------------------< comma >-----------------------
      For example: {{webarchive |url=.. |url4=.. |url7=..}}
 
        Three url arguments not in numeric sequence (1..4..7).
Given a date string, return "," if it's MDY
        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>
local function comma(date)
      Returns the number of URL arguments found numbered 2 or greater (in this case returns "2")
return (date and date:match ('%a+ +%d%d?(,) +%d%d%d%d')) or '';
end
 


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


local function parseExtraArgs()
Return a rendering of the data in ulx[][]


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


  j = 2
local function createRendering()
  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
local displayfield
    return 0
local out = {};
  else
    return j - 2
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


end
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


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


    Given a date string, return "," if it's MDY
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


local function comma(date)
displayfield = ulx[index]['title'];
  local N = mw.text.split(date, " ")
if 'addlarchives' == ulx.url1.format then
  local O = mw.text.split(N[1], "-") -- for ISO
if not displayfield then
  if O[1] == "index" then return "" end
displayfield = ulx[index]['date']
  if not tonumber(O[1]) then
end
    return ","
else -- must be addlpages
  else
if not displayfield then
    return ""
displayfield = table.concat ({s_text.Page, ' ', i});
  end
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


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


    Return data in track[] ie. tracking categories
--[[--------------------------< P A R A M E T E R _ N A M E _ X L A T E >--------------------------------------
 
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?
 
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 function createTracking()
]]


  local sand = ""
local function parameter_name_xlate (args, params, enum_params)
  if tableLength(track) > 0 then                       
local name; -- holds modifiable name of the parameter name during evaluation
    for key,_ in pairs(track) do
local enum; -- for enumerated parameters, holds the enumerator during evaluation
      sand = sand .. "[[" .. key .. "]]"
local found = false; -- flag used to break out of nested for loops
    end
local new_args = {}; -- a table that holds canonical and translated parameter k/v pairs
  end
local origin = {}; -- a table that maps original (local language) parameter names to their canonical name for local language error messaging
  return sand
local unnamed_params; -- set true when unsupported positional parameters are detected
for k, v in pairs (args) do -- loop through all of the arguments in the args table
name = k; -- copy of original parameter name


if 'string' == type (k) then
if non_western_digits then -- true when non-western digits supported at this wiki
name = mw.ustring.gsub (name, '%d', digits); -- convert this wiki's non-western digits to western digits
end
enum = name:match ('%d+$'); -- get parameter enumerator if it exists; nil else
if not enum then -- no enumerator so looking for non-enumnerated parameters
-- TODO: insert shortcut here? if params[name] then name holds the canonical parameter name; no need to search further
for pname, aliases in pairs (params) do -- loop through each parameter the params table
for _, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
if name == alias then
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
else -- enumerated parameters
name = name:gsub ('%d$', '#'); -- replace enumeration digits with place holder for table search
-- TODO: insert shortcut here? if num_params[name] then name holds the canonical parameter name; no need to search further
for pname, aliases in pairs (enum_params) do -- loop through each parameter the num_params table
for _, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
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


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


    Return a rendering of the data in ulx[][]
--[[--------------------------< W E B A R C H I V E >----------------------------------------------------------
 
template entry point
 
]]
 
local function webarchive(frame)
local args = getArgs (frame);
 
local data = mw.loadData (table.concat ({ -- make a data module name; sandbox or live
'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
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 function createRendering()
local date, format, msg, udate, uri, url;
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
]]


    local sand, displayheader, displayfield
if not (url:lower():find ('^http') or url:find ('^//')) then
return inlineError (data.crit_err_msgs.invalid_url );
end


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


    if ulx.url1.format == "none" then                                                   
ulx.url1.extraurls = parseExtraArgs(args)
      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                                                                 
local good = false;
      if ulx.url1.format == "addlarchives" then                          -- Multiple archive services
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
        displayheader = "Additional archives: "
      else                                                                -- Multiple pages from the same archive
if not good or nil == uri.host then -- abandon when ulx.url1.url is malformed
        displayheader = "Additional pages archived&nbsp;on " .. ulx.url1.date .. ": "
return inlineError (data.crit_err_msgs.invalid_url);
      end
end
      local tot = 1 + ulx.url1.extraurls
      local sand = displayheader
serviceName(uri.host, args.nolink)
      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)
if args.date and args.date1 then -- Date argument
  args = frame.args
return inlineError (data.crit_err_msgs.conflicting, {origin.date, origin.date1});
  if (args[1]==nil) and (args["url"]==nil) then           -- if no argument provided than check parent template/module args
end
    args = frame:getParent().args
  end
date = args.date or args.date1;
date = date and date:gsub (' +', ' '); -- replace multiple spaces with a single space
  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)
if date and config.verifydates then
if '*' == date then
date = 'index';
ldf = 'iso'; -- set to default format
elseif 'mdy' == date then
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


  local url1 = trimArg(args.url) or trimArg(args.url1)         
if 'wayback' == ulx.url1.service or 'locwebarchives' == ulx.url1.service or 'ukgwa' == ulx.url1.service then
  if not url1 then
if date then
    return inlineError("url", "Empty.") .. createTracking()
if config.verifydates then
  end
if ldf then
  if mw.ustring.find( url1, "https://web.http", 1, true ) then    -- track bug
udate, msg = decodeWaybackDate (uri.path); -- get the url date in iso format and format of date in |date=; 'index' when wayback url date is *
    track["Category:Galat templat Webarchive"] = 1
if not udate then -- this is the only 'fatal' error return
    return inlineError("url", "https://web.http") .. createTracking()
return inlineError (data.crit_err_msgs[msg]);
  end
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 = {}
if udate ~= date then -- date comparison using iso format dates
  ulx.url1.url = url1
date = udate;
  if not mw.ustring.find( mw.ustring.lower(url1), "^http") then
msg = table.concat ({
    if not mw.ustring.find( url1, "^//") then
inlineRed (err_warn_msgs.mismatch, 'warning'), -- add warning message
      ulx.url1.url = "http://" .. url1
msg, -- add message if there is one
    end
});
  end
end
  local uri1 = mw.uri.new(ulx.url1.url)
end
  ulx.url1.host = uri1.host
end
  ulx.url1.extraurls = parseExtraArgs()
else -- no |date=
udate, msg = decodeWaybackDate (uri.path);


                                                          -- Nolink argument
if not udate then -- this is the only 'fatal' error return
return inlineError (data.crit_err_msgs[msg]);
end


  local nolink = trimArg2(args.nolink)
if '' == udate then
date = nil; -- unset
else
date = udate;
end
end


  serviceName(uri1.host, nolink)
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


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


  local date = trimArg(args.date) or trimArg(args.date1)
if 'index' == date then
  if date == "*" and (ulx.url1.service == "wayback" or ulx.url1.service == "locwebarchives") then
ulx.url1.date = date .. (msg or ''); -- create index + message (if there is one)
    date = "index"
elseif date then
  elseif date and (ulx.url1.service == "wayback" or ulx.url1.service == "locwebarchives") and verifydates == "yes" 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)
    local ldf = dateFormat(date)
else
    if ldf then
ulx.url1.date = msg;
      local udate = decodeWaybackDate( uri1.path, ldf )
end
      if udate ~= date then
        date = udate .. inlineRed("<sup>[Date mismatch]</sup>", "warning")      
format = args.format; -- Format argument
      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
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


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


                                                          -- Title argument  
if args.title and args.title1 then -- Title argument
return inlineError (data.crit_err_msgs.conflicting, {origin.title, origin.title1});
end


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


  local rend = createRendering()
local rend = createRendering()
  if not rend then
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>'
return inlineError (data.crit_err_msgs.unknown);
    track["Category:Galat templat Webarchive"] = 1
end
  end


  return rend .. createTracking()
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 22 Oktober 2022 19.04

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


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

require('strict');
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 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


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


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


--[=[-------------------------< M A K E _ W I K I L I N K >----------------------------------------------------

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


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

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

end


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

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

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


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

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

]]

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]
		return;																	-- nil return when value contains extraneous characters
	end

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

	return r
end 


--[[--------------------------< D E C O D E _ D A T E >--------------------------------------------------------

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 decode_date (date_str)
	local patterns = {
		['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

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

Given year, month, day numbers, (zero-padded or not) return a full date in df format
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

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 makeDate (year, month, day, df)
	local format = {
		['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)

	if non_western_digits then													-- this wiki uses non-western digits?
		date = mw.ustring.gsub (date, '%d', digits);							-- convert this wiki's non-western digits to western digits
	end

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


--[[--------------------------< I S _ V A L I D _ D A T E >----------------------------------------------------

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 is_valid_date (year, month, day)
	local days_in_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
	local month_length;
	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
		return false;															-- something missing
	end
	
	y = tonumber (year);
	m = tonumber (month);
	d = tonumber (day);

	if 1900 > y or today.year < y or 1 > m or 12 < m then						-- year and month are within bounds	TODO: 1900?
		return false;
	end

	if (2==m) then																-- if February
		month_length = 28;														-- then 28 days unless
		if (0==(y%4) and (0~=(y%100) or 0==(y%400))) then						-- is a leap year?
			month_length = 29;													-- if leap year then 29 days in February
		end
	else
		month_length=days_in_month[m];
	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


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

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 = {};
	local decode;

	dt = 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 dt[2]:find ('query', 1, true) or 
		dt[2]:find ('cache', 1, true) or
		dt[2]:find ('getfile', 1, true) or
		tonumber(dt[2]) then
			return 'query';
	end

	decode = base62(dt[2]);														-- base62 string -> exponential number
	if not decode then
		return nil;																-- nil return when dt[2] contains characters not in %w
	end
	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

	decode = makeDate (dt.year, dt.month, dt.day, 'iso');						-- date comparisons are all done in iso format with western digits
	if non_western_digits then													-- this wiki uses non-western digits?
		decode = mw.ustring.gsub (decode, '%d', digits);						-- convert this wiki's non-western digits to western digits
	end

	return decode;
end


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

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

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 msg, snapdate;

	snapdate = path:gsub ('^/web/', ''):gsub ('^/all/', ''):gsub ('^/ukgwa/', ''):gsub ('^/tna/', ''):gsub ('^/', '');	-- remove leading /web/, /all/, /ukgwa/, /tna/, or /
	snapdate = snapdate:match ('^[^/]+');										-- get timestamp
	if snapdate == "*" then														-- eg. /web/*/http.., etc.
		return 'index';															-- return indicator that this url has an index date
	end

	snapdate = snapdate:gsub ('%a%a_%d?$', ''):gsub ('%-', '');					-- from date, remove any trailing "re_", dashes

	msg = '';
	if snapdate:match ('%*$') then												-- a trailing '*' causes calendar display at archive .org
		snapdate = snapdate:gsub ('%*$', '');									-- remove so not part of length calc later
		msg = inlineRed (err_warn_msgs.ts_cal, 'warning');						-- make a message
	end

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

	local dlen = snapdate:len();
	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
		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
	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


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

returns two values:
	first value is one of these:
		valid date string in df format - archive.is date is valid (including the text string 'short link' when url is the short form)
		empty string - wayback date is malformed (not a number, less than 8 digits, not a valid date)
		nil - wayback date is '/save/'
	
	second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeArchiveisDate(path, df)
	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

	local dlen = string.len(snapdate)
	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
		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
	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


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

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

]]

local function serviceName(host, no_link)
	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


--[[--------------------------< 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(args)

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

	j = 2
	for i = 2, config.maxurls do
		argurl = "url" .. i
		if args[argurl] then
			argurl2 = "url" .. j
			ulx[argurl2] = {}
			ulx[argurl2]["url"] = args[argurl]
			argdate = "date" .. i
			if args[argdate] then
				ulx[argurl2]["date"] = args[argdate]
			else
				ulx[argurl2]["date"] = inlineRed (err_warn_msgs.date_miss, 'warning');
			end
	
			argtitle = "title" .. i
			if 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)
	return (date and date:match ('%a+ +%d%d?(,) +%d%d%d%d')) or '';
end


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

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

]]

local function createRendering()

	local displayfield
	local out = {};
	
	local index_date, msg = ulx.url1.date:match ('(index)(.*)');				-- when ulx.url1.date extract 'index' text and message text (if there is a message)
	ulx.url1.date = ulx.url1.date:gsub ('index.*', 'index');					-- remove message

	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


--[[--------------------------< P A R A M E T E R _ N A M E _ X L A T E >--------------------------------------

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?

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 function parameter_name_xlate (args, params, enum_params)
	local name;																	-- holds modifiable name of the parameter name during evaluation
	local enum;																	-- for enumerated parameters, holds the enumerator during evaluation
	local found = false;														-- flag used to break out of nested for loops
	local new_args = {};														-- a table that holds canonical and translated parameter k/v pairs
	local origin = {};															-- a table that maps original (local language) parameter names to their canonical name for local language error messaging
	local unnamed_params;														-- set true when unsupported positional parameters are detected
	
	for k, v in pairs (args) do													-- loop through all of the arguments in the args table
		name = k;																-- copy of original parameter name

		if 'string' == type (k) then
			if non_western_digits then											-- true when non-western digits supported at this wiki
				name = mw.ustring.gsub (name, '%d', digits);					-- convert this wiki's non-western digits to western digits
			end
			
			enum = name:match ('%d+$');											-- get parameter enumerator if it exists; nil else
			
			if not enum then													-- no enumerator so looking for non-enumnerated parameters
				-- TODO: insert shortcut here? if params[name] then name holds the canonical parameter name; no need to search further
				for pname, aliases in pairs (params) do							-- loop through each parameter the params table
					for _, alias in ipairs (aliases) do							-- loop through each alias in the parameter's aliases table
						if name == alias then
							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
			else																-- enumerated parameters
				name = name:gsub ('%d$', '#');									-- replace enumeration digits with place holder for table search
				-- TODO: insert shortcut here? if num_params[name] then name holds the canonical parameter name; no need to search further
				for pname, aliases in pairs (enum_params) do					-- loop through each parameter the num_params table
					for _, alias in ipairs (aliases) do							-- loop through each alias in the parameter's aliases table
						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


--[[--------------------------< W E B A R C H I V E >----------------------------------------------------------

template entry point

]]

local function webarchive(frame)
	local args = getArgs (frame);

	local data = mw.loadData (table.concat ({									-- make a data module name; sandbox or live
		'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
	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;
	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
		return inlineError (data.crit_err_msgs.invalid_url );
	end

	ulx.url1 = {}
	ulx.url1.url = url

	ulx.url1.extraurls = parseExtraArgs(args)

	local good = false;
	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
		return inlineError (data.crit_err_msgs.conflicting, {origin.date, origin.date1});
	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
		if '*' == date then
			date = 'index';
			ldf = 'iso';														-- set to default format
		elseif 'mdy' == date then
			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
		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


--[[--------------------------< E X P O R T E D 	 F U N C T I O N S >------------------------------------------
]]

return {webarchive = webarchive};