Mô đun:URIutil

local URIutil = { suite  = "URIutil",
serial = "2017-01-10" };
--=
Utilities for URI etc.- coreISSN()- formatISBN()- formatISSN()- formatLCCN()- isDNBvalid()- isDOI()- isEscValid()- isGTINvalid()- isHandle()- isISBN()- isISBNvalid()- isISSNvalid()- isLCCN()- linkDNBopac()- linkDOI()- linkHandle()- linkISBN()- linkISSN()- linkLCCN()- linkURN()- mayDOI()- mayHandle()- mayISBN()- mayISSN()- mayLCCN()- mayURI()- mayURN()- plainISBN()- targetISSN()- uriDOI()- uriHandle()- uriURN()- failsafe()- URIutil()
loadData: URIutil/config URIutil/isbn URIutil/urn
**
local factory = function ( attempt, allowX )
-- Retrieve plain digits of attempt
-- Precondition:
--     attempt  -- string; with digits (+xX) and hyphens, not trimmed
--     allowX   -- number; of (last) position for permitted xX
--                 boolean; xX at last position permitted
-- Postcondition:
--     Returns   table; success
--                      1...8/10...13  -- digits 0...9
--                                                10 at last position
--                      .hyphens  -- number of hyphens
--                      .type     -- number of digits
--               number; no string or bad length or data
--                        0  -- no string
--                       >0  -- unexpected char at position (trimmed)
local r;
if type( attempt ) == "string" then
local c, i;
local j = 0;
local k = 1;
local s = mw.text.trim( attempt );
local n = mw.ustring.len( s );
r = { hyphens = 0 };
for i = 1, n do
c = mw.ustring.codepoint( s,  i,  i + 1 );
if c >= 48  and  c  1  and  i %s", alert );
end -- fault()
local fetch = function ( acquire )
-- Load data submodule
-- Precondition:
--     acquire  -- string, one of
--                  "config"
--                  "isbn"
--                  "urn"
-- Postcondition:
--     Returns any table
local r;
if URIutil.data then
r = URIutil.data acquire ;
else
URIutil.data = { };
end
if not r then
local lucky;
lucky, r = pcall( mw.loadData,  "Module:URIutil/" .. acquire );
if type( r ) ~= "table" then
r = { };
end
URIutil.data acquire  = r;
end
return r;
end -- fetch()
local flop = function ( alert )
-- Create link to (maintenance) category
-- Precondition:
--     alert  -- trimmed string with title, not empty, or nil
-- Postcondition:
--     Returns  link, or false
local r;
if type( alert ) == "string"  and  alert ~= "" then
r = string.format( "", alert );
end
return r;
end -- flop()
local format = function ( assigned, ahead, amount )
-- Convert part of digit sequence into string
-- Precondition:
--     assigned  -- table; as of factory()
--     ahead     -- index of first digit
--     amount    -- number of digits to append
-- Postcondition:
--     Returns  string with digits and hyphens
local i, k;
local r = "";
for i = ahead,  ahead + amount - 1 do
k = assigned i ;
if k == 10 then
r = r .. "X";
else
r = r .. tostring( k );
end
end -- for i
return r;
end -- format()
local DNBfaith = function ( assert, ancestor )
-- Compute DNB (also GND, ZDB) check digit and verify
-- Precondition:
--     assert    -- table; as of factory()
--                  .type  -- until 11 including check digit
--     ancestor  -- true: 2011 mode
-- Postcondition:
--     Returns  true: check digit matches
-- 2013-09-01
local k = fair( assert )  %  11;
if ancestor then
k  =  11 - k;
end
return  ( k == assert assert.type  );
end -- DNBfaith()
local GTINfair = function ( assert )
-- Compute GTIN check digit
-- Precondition:
--     assert  -- table; ~ 13 digits
--                       .type  -- 13 ...
-- Postcondition:
--     Returns  number 0...9
local i, k;
local lead = true;
local r    = 0;
for i = 1,  assert.type - 1 do
k   = assert i ;
r = r + k;
if lead then    -- odd
lead = false;
else    -- even
r  = r + k + k;
lead = true;
end
end -- for i
r = (10  -  r % 10)   %   10;
return  r;
end -- GTINfair()
local GTINfaith = function ( assert )
-- Compute GTIN check digit and verify
-- Precondition:
--     assert  -- table; ~ 13 digits
--                       .type  -- 13 ...
-- Postcondition:
--     Returns  true: check digit matches
return  ( GTINfair( assert )  ==  assert assert.type  );
end -- GTINfaith()
local ISBNfactory = function ( attempt )
-- Retrieve plain digits of ISBN attempt
-- Precondition:
--     attempt  -- string with digits (+xX) and hyphens, not trimmed
-- Postcondition:
--     Returns   table; success
--                      1...13  -- digits 0...9
--                                     10 at ISBN-10 last position
--                      .type       -- 10 or 13
--                      .hyphens    -- 0... number of hyphens
--               number; no string or bad length or data
--                        0  -- no string
--                       >0  -- unexpected char at position (trimmed)
--                       -1  -- bad digit count
--                       -2  -- bad bookland
local r;
if type( attempt ) == "string" then
local s;
r = factory( attempt, 10 );
s = type( r );
if s == "number"  and  r ~= 10  and  r > 6  and  r = e 1   and  m  0 then
break; -- for i
end
end -- for i
end
end
return r;
end -- ISBNfold()
local ISBNformat = function ( attempt, assigned )
-- Hyphen formatting; at least try minimum
-- Precondition:
--     attempt   -- string with presumable ISBN
--     assigned  -- table; as of ISBNfactory()
--                         .type     -- 10 or 13
--                         .hyphens  -- 0...4
-- Postcondition:
--     Returns  string with digits and hyphens
local r = false;
local j, k, m, n;
if assigned.type == 10 then
m = 978;
r = "";
j = 1;
else
m = 970 + assigned 3 ;
r = tostring( m ) .. "-";
j = 4;
end
if assigned j   4 then
k = 3;
if assigned j + 1  > 8 then
k = 4;
if assigned j + 2  > 8 then
k = 5;
end
end
end
end
if k then
n = format( assigned, j, k );
r = string.format( "%s%s-", r, n );
j = j + k;
n = ISBNfold( assigned,  m,  tonumber( n ),  j );
if n > 0 then
r = string.format( "%s%s-",  r,  format( assigned, j, n ) );
j = j + n;
end
end
r = r .. format( assigned,  j,  assigned.type - j );
if assigned assigned.type  == 10 then
r = r .. "-X";
else
r = string.format( "%s-%s",
r,
tostring( assigned assigned.type  ) );
end
if not r then
r = mw.ustring.upper( mw.text.trim( attempt ) );
end
return r;
end -- ISBNformat()
local ISSNfactory = function ( attempt )
-- Retrieve plain digits of ISSN attempt
-- Precondition:
--     attempt  -- string with digits (+xX) and hyphens, not trimmed
-- Postcondition:
--     Returns   table; success
--                      1...13  -- digits 0...9
--                                     10 at ISSN-8 last position
--                      .type       -- 8 or 13
--                      .hyphens    -- 0... number of hyphens
--               number; no string or bad length or data
--                        0  -- no string
--                       >0  -- unexpected char at position (trimmed)
--                       -1  -- bad digit count
--                       -2  -- bad issnland
local r;
if type( attempt ) == "string" then
r = factory( attempt, 8 );
if type( r ) == "table" then
if r.type == 13 then
if r1 ~= 9  or
r2 ~= 7  or
r3 ~= 7 then
r = -2;
end
elseif r.type ~= 8 then
r = -1;
end
end
else
r = 0;
end
return r;
end -- ISSNfactory()
local ISSNfaith = function ( assert )
-- Compute ISSN check digit and verify
-- Precondition:
--     assert  -- table; as of ISSNfactory()
--                       .type  -- 8 or 13
-- Postcondition:
--     Returns  true: check digit matches
local r;
if assert.type == 8 then
local k = fair( assert );
if k == 11 then
r = ( assert 8   ==  0 );
else
r = ( assert 8   ==  k );
end
elseif assert.type == 13 then
r = GTINfaith( assert );
else
r = false;
end
return r;
end -- ISSNfaith()
local ISSNformat = function ( assigned, achieve )
-- Hyphen formatting of ISSN
-- Precondition:
--     assigned  -- table; as of ISSNfactory(), and valid
--     achieve   -- 8 or 13
-- Postcondition:
--     Returns  string with digits and hyphens
local r;
if achieve == 8 then
local x;
if assigned.type == 8 then
r = string.format( "%s-%s",
format( assigned, 1, 4 ),
format( assigned, 5, 3 ) );
x = assigned 8 ;
elseif assigned.type == 13 then
r = string.format( "%s-%s",
format( assigned, 4, 4 ),
format( assigned, 8, 3 ) );
x = fair( assigned );
end
if x == 10 then
r = r .. "X";
else
r = r .. tostring( x );
end
elseif achieve == 13 then
if assigned.type == 8 then
r = string.format( "977-%s-00-%s",
format( assigned, 1, 7 ),
GTINfair( assigned ) );
elseif assigned.type == 13 then
r = string.format( "977-%s%s-%s",
format( assigned, 4, 7 ),
format( assigned, 10, 2 ),
tostring( assigned 13  ) );
end
end
return r;
end -- ISSNformat()
local LCCNfactory = function ( attempt, allow )
-- Retrieve segments of LCCN attempt (format since 2001)
-- Precondition:
--     attempt  -- string with presumable LCCN
--     allow    -- false or string: "/"
-- Postcondition:
--     Returns  table; success
--              false if not correct, bad data
-- 2014-12-28
local r   = false;
local pat = "^%s*(%a*)(/?)(%d%S+)%s*$";
local pre, sep, s = attempt:match( pat );
if pre and s then
local year, serial;
if pre == "" then
pre = false;
if sep ~= "" then
s = false;
end
elseif #pre > 3 then
s = false;
else
pre = pre:lower();
end
if s then
if allow ~= "/"  or  sep == "/" then
if sep == "/" then
year, serial = s:match( "^(%d+)/(%d.+)$" );
elseif s:find( "-", 2, true ) then
year, serial = s:match( "^(%d+)%-(%d.+)$" );
else
year = s:match( "^(%d+)" );
if year then
if #year  "00"
serial = false;
elseif n > tonumber( os.date( "%Y" ) ) then
serial = false;
end
elseif #year ~= 2 then
serial = false;
end
end
if serial then
r = { pre = pre, serial = serial };
if year then
r.year = year;
end
if serial:find( "/", 2, true ) then
local q;
serial, q = serial:lower()
:match( "^(%d+)/(a-z+)$" );
if q == "dc" or
q == "mads" or
q == "marcxml" or
q == "mods" then
r.serial    = serial;
r.qualifier = q;
end
end
if serial then
serial = serial:match( "^0*(1-9%d*)$" );
end
if not serial then
r = false;
elseif #serial  6 then
r = false;
end
end
end
end
return r;
end -- LCCNfactory()
local LCCNformat = function ( assigned, achieve )
-- Standard or hyphen or slash formatting of LCCN
-- Precondition:
--     assigned  -- table; as of LCCNfactory(), and valid
--     achieve   -- additional formatting desires, like "-" or "/"
-- Postcondition:
--     Returns  string with letters, digits and hyphens
-- 2013-07-14
local r;
if assigned.pre then
r = assigned.pre;
else
r = "";
end
if assigned.year then
if achieve == "/"  and  r ~= "" then
r = r .. "/";
end
r = r .. assigned.year;
if achieve then
r = r .. achieve;
end
end
if assigned.serial then
r = r .. assigned.serial;
end
if assigned.qualifier then
r = string.format( "%s/%s", r, assigned.qualifier );
end
return r;
end -- LCCNformat()
local LCCNforward = function ( attempt, achieve )
-- Retrieve bracketed titled external LCCN permalink
-- Precondition:
--     attempt  -- string with presumable LCCN
--     achieve  -- additional title formatting desires, like "-"
-- Postcondition:
--     Returns  link, or plain attempt if bad LCCN
-- 2015-08-10
local lccn = LCCNfactory( attempt );
local r;
if lccn then
r = LCCNformat( lccn, false );
if r then
local s;
if achieve then
s = LCCNformat( lccn, achieve );
else
s = r;
end
r = string.format( "//lccn.loc.gov/%s %s", r, s );
end
else
r = attempt;
end
return r;
end -- LCCNforward()
local URNnamespace = function ( area, acquire )
-- Are these parts of a correct URN?
-- Precondition:
--     area     -- string with lowercase namespace
--     acquire  -- string with identification
-- Postcondition:
--     Returns  false if no problem detected
--              string with violation
local s = fetch( "urn" ).sns;
local r;
if type( s ) == "string" then
r = string.format( ":%s:", area );
if s:match( r ) then
s = "^%w%(%)%+,%-%.:=@;%$_!%*'.*$";
r = acquire:match( s );
else
r = string.format( ":%s:", area );
end
if not r then
r = false;
if area == "isbn" then
if not URIutil.isISBNvalid( acquire ) then
r = acquire;
end
elseif area == "issn" then
if not URIutil.isISSNvalid( acquire ) then
r = acquire;
end
end
end
end
return r;
end -- URNnamespace()
local URNresolve = function ( assigned, ask, alter )
-- Resolve URN within space
-- Precondition:
--     assigned  -- table with resolvers for this space
--     ask       -- string with ID within this space
--     alter     -- string with alternative resolver, or not
-- Postcondition:
--     Returns
--         1.    URL of resolver, or nil
--         2.    modified ask
local resolver = assigned;
local sign     = ask;
local subset   = assigned ":" ;
local r;
if subset then
local s = sign:match( subset );
if s then
s    = s:lower();
sign = s .. sign:sub( #s + 1 )
if assigned s  then
resolver = assigned s ;
end
end
end
if alter then
r = resolver alter ;
end
if not r then
r = resolver "*" ;
end
return r, sign;
end -- URNresolve()
function URIutil.coreISSN( attempt )
-- Fetch significant ISSN
-- Precondition:
--     attempt  -- string with presumable ISSN
-- Postcondition:
--     Returns   string with 7 digits, without check digit nor GTIN
--               unmodified input if wrong
local r;
local issn = ISSNfactory( attempt );
if type( issn ) == "table" then
if issn.type == 8 then
r = format( issn, 1, 7 );
elseif issn.type == 13 then
r = format( issn, 4, 7 );
end
else
r = mw.ustring.upper( mw.text.trim( attempt ) );
end
return r;
end -- URIutil.coreISSN()
function URIutil.formatISBN( attempt, assigned )
-- Format ISBN, if no hyphens present
-- Precondition:
--     attempt   -- string with presumable ISBN
--     assigned  -- table or false; as of ISBNfactory()
-- Postcondition:
--     Returns   string with some hyphens, if not yet
--               unmodified input if already hyphens or wrong
local r;
local isbn;
if type( assigned ) == "table" then
isbn = assigned;
else
isbn = ISBNfactory( attempt );
end
if type( isbn ) == "table" then
r = ISBNformat( attempt, isbn );
else
r = mw.ustring.upper( mw.text.trim( attempt ) );
end
return r;
end -- URIutil.formatISBN()
function URIutil.formatISSN( attempt, achieve )
-- Format ISSN
-- Precondition:
--     attempt  -- string with presumable ISSN
--     achieve  -- false or 8 or 13; requested presentation
-- Postcondition:
--     Returns   string with some hyphens, if not yet
--               unmodified input if already hyphens or wrong
local r    = false;
local issn = ISSNfactory( attempt );
if type( issn ) == "table" then
if ISSNfaith( issn ) then
local k, m;
if type( achieve ) == "string" then
m = tonumber( achieve );
else
m = achieve;
end
if m == 8  or m == 13 then
k = m;
else
k = issn.type;
end
r = ISSNformat( issn, k );
end
end
if not r then
r = mw.ustring.upper( mw.text.trim( attempt ) );
end
return r;
end -- URIutil.formatISSN()
function URIutil.formatLCCN( attempt, achieve )
-- Standard or hyphen formatting of LCCN
-- Precondition:
--     attempt  -- string with presumable LCCN
--     achieve   -- additional formatting desires, like "-"
-- Postcondition:
--     Returns  string with letters, digits and hyphens
--              unmodified input if wrong
local r = LCCNfactory( attempt );
if r then
r = LCCNformat( r, achieve );
end
return r;
end -- URIutil.formatLCCN()
function URIutil.isDNBvalid( attempt, also )
-- Is this DNB (also GND, ZDB) formally correct (check digit)?
-- Precondition:
--     attempt  -- string with any presumable DNB code
--     also     -- string or nil; optional requirement DMA GND SWD
--                 currently not implemented
--                 DMA starting with 3 and no hyphen
--                 GND not DNB2011
--                 SWD DNB2011 starting with 4 or 7 and no X check
-- Postcondition:
--     Returns  number of digits or 2011, if valid
--              false if not correct, bad data or check digit wrong
-- 2013-09-01
local s = mw.text.trim( attempt );
local j = s:find( "/", 5, true );
local r = false;
local dnb;
if j then
s = attempt:sub( 1,  j - 1 );
end
j = s:find( "-", 2, true );
if j then
if j > 3  and  j  6 then
if s:match( "^0-9+0-9xX$" ) then
dnb = factory( s, #s );
end
end
if type( dnb ) == "table" then
if j then
if DNBfaith( dnb, true ) then
r = 2011;
end
else
if DNBfaith( dnb, false ) then
r = dnb.type;
end
end
end
return r;
end -- URIutil.isDNBvalid()
function URIutil.isDOI( attempt )
-- Is this a syntactically correct DOI?
-- Precondition:
--     attempt  -- string with presumable DOI code
-- Postcondition:
--     Returns  number of organization, if valid
--              false if not correct, bad character or syntax
local r = false;
local k, s = attempt:match( "^%s*10%.(1-90-9+)/(.+)%s*$" );
if k then
k = tonumber( k );
if k >= 1000  and  k %+*"
.. "0-9A-Za-z%)%>%+#$"
s = mw.uri.decode( mw.text.decode( s ), "PATH" );
if s:match( pc ) then
r = k;
end
end
end
return r;
end -- URIutil.isDOI()
function URIutil.isEscValid( attempt )
-- Are bad percent escapings in attempt?
-- Precondition:
--     attempt  -- string with possible percent escapings
-- Postcondition:
--     Returns  string with violating sequence
--              false if correct
local i = 0;
local r = false;
local h, s;
while i do
i = attempt:find( "%", i, true );
if i then
s = attempt:sub( i + 1,  i + 2 );
h = s:match( "%x%x" );
if h then
if h == "00" then
r = "%00";
break; -- while i
end
i = i + 2;
else
r = "%" .. s;
break; -- while i
end
end
end -- while i
return r;
end -- URIutil.isEscValid()
function URIutil.isGTINvalid( attempt )
-- Is this GTIN (EAN) formally correct (check digit)?
-- Precondition:
--     attempt  -- string with presumable GTIN
-- Postcondition:
--     Returns  GTIN length
--              false if not correct, bad data or check digit wrong
local r;
local gtin = factory( attempt, false );
if type( gtin ) == "table" then
if gtin.type == 13 then
if GTINfaith( gtin ) then
r = gtin.type;
end
else
r = false;
end
else
r = false;
end
return r;
end -- URIutil.isGTINvalid()
function URIutil.isHandle( attempt )
-- Is this a meaningful handle for handle.net?
-- Precondition:
--     attempt  -- string with presumable handle code
-- Postcondition:
--     Returns  number of primary authority, if valid
--              false if not correct, bad character or syntax
local r = attempt:match( "^%s*(^/%s+)/%S+%s*$" );
if r then
local k = r:find( ".", 1, true );
if k then
if k == 1  or  r:match( "%.$" ) then
r = false;
else
r = r:sub( 1,  k - 1 );
end
end
if r then
if r:match( "^1-90-9+$" ) then
r = tonumber( r );
else
r = false;
end
end
else
r = false;
end
return r;
end -- URIutil.isHandle()
function URIutil.isISBN( attempt )
-- Is this a syntactically correct ISBN?
-- Precondition:
--     attempt  -- string with presumable ISBN
-- Postcondition:
--     Returns
--        1  -- 10 if 10 digits and hyphens; also X at end of ISBN-10
--              13 if 13 digits and hyphens; beginning with bookland
--              false if not an ISBN
--        2  -- internal table, if (1)
--              number; no string or bad length or data
--                       0  -- no string
--                      >0  -- unexpected char at position (trimmed)
--                      -1  -- bad digit count
--                      -2  -- bad bookland
local r;
local isbn = ISBNfactory( attempt );
if type( isbn ) == "table" then
r = isbn.type;
else
r = false;
end
return r, isbn;
end -- URIutil.isISBN()
function URIutil.isISBNvalid( attempt )
-- Is this ISBN formally correct (check digit)?
-- Precondition:
--     attempt  -- string with presumable ISBN
-- Postcondition:
--     Returns
--        1  -- 10 if 10 digits and hyphens; also X at end of ISBN-10
--              13 if 13 digits and hyphens; beginning with bookland
--              false if not correct, bad data or check digit wrong
--        2  -- internal table, if (1)
--              number; no string or bad length or data
--                       0  -- no string
--                      >0  -- unexpected char at position (trimmed)
--                      -1  -- bad digit count
--                      -2  -- bad bookland
local r    = false;
local isbn = ISBNfactory( attempt );
if type( isbn ) == "table" then
if ISBNfaith( isbn ) then
r = isbn.type;
end
end
return r, isbn;
end -- URIutil.isISBNvalid()
function URIutil.isISSNvalid( attempt )
-- Is this ISSN formally correct (check digit)?
-- Precondition:
--     attempt  -- string with presumable ISSN
-- Postcondition:
--     Returns  8 if 8 digits and up to 1 hyphen; also X at end
--              13 if 13 digits and hyphens; beginning with 977
--              false if not correct, bad data or check digit wrong
local r    = false;
local issn = ISSNfactory( attempt );
if type( issn ) == "table" then
if ISSNfaith( issn ) then
r = issn.type;
end
end
return r;
end -- URIutil.isISSNvalid()
function URIutil.isLCCN( attempt, allow )
-- Is this a syntactically correct LCCN?
-- Precondition:
--     attempt  -- string with presumable LCCN
--     allow    -- false or string: "/"
-- Postcondition:
--     Returns  string with LCCN formatted aa9999-99999999
--              false if not correct, bad data
local r    = false;
local lccn = LCCNfactory( attempt, allow );
if lccn then
r = LCCNformat( lccn, "-" );
end
return r;
end -- URIutil.isLCCN()
function URIutil.linkDNBopac( attempt, about, allow, abbr, alert )
-- Retrieve bracketed titled external DNB opac link
-- Precondition:
--     attempt  -- string with presumable DNB ID
--     about    -- title, or false
--     allow    -- true: permit invalid ID
--     abbr     -- true: link DNB abbreviation
--     alert    -- string with title of maintenance category, or nil
-- Postcondition:
--     Returns  link, or plain string if bad DNB
local r = allow  or  URIutil.isDNBvalid( attempt );
local s = "DNB";
if abbr  and  not about then
local cnf = fetch( "config" );
if cnf.supportDNB then
s = string.format( "DNB", cnf.supportDNB );
end
end
if r then
if about then
r = about;
else
r = attempt;
end
r = string.format( "%s %s%s%s%s %s",
s,
"https://portal.dnb.de/opac.htm",
"?method=simpleSearch&cqlMode=true",
"&query=idn%3D",
attempt,
r );
else
r = string.format( "%s %s", s, attempt );
if about then
r = string.format( "%s %s", r, about );
end
if alert then
r = r .. flop( alert );
end
end
return r;
end -- URIutil.linkDNBopac()
function URIutil.linkDOI( attempt, any1, any2, any3, alert )
-- Retrieve bracketed titled external link on DOI resolver
-- Precondition:
--     attempt  -- string with presumable DOI
--     any1     -- intentionally dummy parameter
--     any2     -- intentionally dummy parameter
--     any3     -- intentionally dummy parameter
--     alert    -- string with title of maintenance category, or nil
-- Postcondition:
--     Returns  external link, or false
local r = URIutil.isDOI( attempt );
if r then
r = mw.text.decode( mw.text.trim( attempt ),  "PATH" );
r = string.format( "%s%s %s",
"//dx.doi.org/",
mw.uri.encode( r ),
mw.text.encode( r, "&%" ) );
r = string.format( "%s",  r );
else
r = flop( alert );
end
return r;
end -- URIutil.linkDOI()
function URIutil.linkHandle( attempt, any1, any2, any3, alert )
-- Retrieve bracketed titled external link on handle resolver
-- Precondition:
--     attempt  -- string with presumable handle
--     any1     -- intentionally dummy parameter
--     any2     -- intentionally dummy parameter
--     any3     -- intentionally dummy parameter
--     alert    -- string with title of maintenance category, or nil
-- Postcondition:
--     Returns  external link, or false
local r = URIutil.isHandle( attempt );
if r then
r = mw.text.decode( mw.text.trim( attempt ),  "PATH" );
r = string.format( "%s%s %s",
"//hdl.handle.net/",
mw.uri.encode( r ),
mw.text.encode( r, "&%" ) );
r = string.format( "%s",  r );
else
r = flop( alert );
end
return r;
end -- URIutil.linkHandle()
function URIutil.linkISBN( attempt, allow, abbr, adhere, alert )
-- Retrieve bracketed titled wikilink on booksources page with "ISBN"
-- Precondition:
--     attempt  -- string with presumable ISBN
--     allow    -- true: permit invalid check digit or digit count
--     abbr     -- true or string: link ISBN abbreviation
--     adhere   -- true: use    else: use simple space
--     alert    -- string with title of maintenance category, or nil
-- Postcondition:
--     Returns  link
local lapsus;
local source = mw.text.trim( attempt );
local r      = string.format( "", source );
local isbn   = ISBNfactory( source );
if type( isbn ) == "table" then
local lenient;
if type( allow ) == "string" then
lenient = ( allow ~= "0" );
else
lenient = allow;
end
if lenient then
lapsus = false;
else
lapsus = ( not ISBNfaith( isbn ) );
end
r = r .. ISBNformat( attempt, isbn );
else
lapsus = not allow;
r      = r .. source;
end
r = r .. "";
if lapsus then
r = string.format( "%s%s",
r,  fault( "(?!?!)" ) );
if alert then
r = r .. flop( alert );
end
end
if adhere then
r = " " .. r;
else
r = " " .. r;
end
if abbr then
local cnf = fetch( "config" );
local s   = cnf.supportISBN;
if s then
if type( s ) ~= "string"
or  s == "" then
s = false;
end
else
s = "International Standard Book Number";
end
if s then
s = string.format( "ISBN", s );
else
s = "ISBN";
end
r = string.format( "%s %s", s, r );
else
r = "ISBN" .. r;
end
return r;
end -- URIutil.linkISBN()
function URIutil.linkISSN( attempt, allow, abbr, adhere, alert )
-- Retrieve bracketed titled external link on ISSN DB with "ISSN"
-- Precondition:
--     attempt  -- string with presumable ISSN
--     allow    -- true: permit invalid check digit
--     abbr     -- true: link ISSN abbreviation
--     adhere   -- true: use    else: use simple space;
--     alert    -- string with title of maintenance category, or nil
-- Postcondition:
--     Returns  link
local r = URIutil.targetISSN( attempt, allow, nil, nil, alert );
if adhere then
r = " " .. r;
else
r = " " .. r;
end
if abbr then
local cnf  = fetch( "config" );
local s = cnf.supportISSN;
if s then
if type( s ) ~= "string"
or  s == "" then
s = false;
end
else
s = "International Standard Serial Number";
end
if s then
s = string.format( "ISSN", s );
else
s = "ISSN";
end
r = string.format( "%s%s", s, r );
else
r = "ISSN" .. r;
end
return r;
end -- URIutil.linkISSN()
function URIutil.linkLCCN( attempt, achieve, any1, any2, alert )
-- Retrieve bracketed titled external LCCN permalink
-- Precondition:
--     attempt  -- string with presumable LCCN
--     achieve  -- additional title formatting desires, like "-"
--     any1     -- intentionally dummy parameter
--     any2     -- intentionally dummy parameter
--     alert    -- string with title of maintenance category, or nil
-- Postcondition:
--     Returns  link, or false if bad LCCN
local r = LCCNforward( attempt, achieve );
if not r then
r = flop( alert );
end
return r;
end -- URIutil.linkLCCN()
function URIutil.linkURN( attempt, alter, any1, any2, alert )
-- Retrieve bracketed titled external URN link
-- Precondition:
--     attempt  -- string, with presumable URN, starting with "urn:"
--     alter    -- alternative handler
--     any1     -- intentionally dummy parameter
--     any2     -- intentionally dummy parameter
--     alert    -- string, with title of maintenance category, or nil
-- Postcondition:
--     Returns
--         1.    linked ID, or plain string if bad URN
--         2.    true, if to be preceded by "urn:"
local r2 = true;
local r;
if not URIutil.mayURI( attempt, true ) then
local s = attempt:match( "^%s*uUrRnN:(%S+)%s*$" );
if s then
local space, sign = s:match( "^(%w+):(.+)$" );
if space then
local defs = fetch( "urn" );
if type( defs ) == "table" then
local resolver = defs.resolver;
space    = space:lower();
resolver = resolver space ;
r2       = ( resolver ~= true );
if type( resolver ) == "table" then
r, sign = URNresolve( resolver, sign, alter );
s = string.format( "%s:%s", space, sign );
if r then
r = r:gsub( "%$1",  "urn:" .. s );
r = string.format( "%s %s", r, s );
end
elseif r2 then
if type( defs.sns ) == "string" then
s = string.format( ":%s:", space );
if not defs.sns:find( s, 1, true ) then
s = false;
end
else
s = false;
end
if s then
r = string.format( "%s:%s", space, sign );
else
r = string.format( "%s:%s",
space, sign );
end
else
s = "link" .. space:upper();
r = URIutil s ( sign, alter, nil, nil, alert );
end
else
r =  fault( "Bad structure in Module:URIutil/urn" );
end
end
end
end
if not r then
if alert then
r = flop( alert )  or  "";
if attempt then
r = attempt .. r;
end
else
r = mw.text.trim( attempt );
end
end
return r, r2;
end -- URIutil.linkURN()
function URIutil.mayDOI( attempt )
-- Is this a syntactically correct DOI, or empty?
-- Precondition:
--     attempt  -- string with presumable DOI
-- Postcondition:
--     Returns  number of organization
--              0  if empty
--              false if not empty and not a DOI
local r;
if type( attempt ) == "string" then
local s = mw.text.trim( attempt );
if #s >= 10 then
r = URIutil.isDOI( attempt );
elseif #s == 0 then
r = 0;
else
r = false;
end
else
r = false;
end
return r;
end -- URIutil.mayDOI()
function URIutil.mayHandle( attempt )
-- Is this a meaningful handle, or empty?
-- Precondition:
--     attempt  -- string with presumable handle
-- Postcondition:
--     Returns  number of organization
--              0  if empty
--              false if not empty and not a DOI
local r;
if type( attempt ) == "string" then
local s = mw.text.trim( attempt );
if #s > 5 then
r = URIutil.isHandle( attempt );
elseif #s == 0 then
r = 0;
else
r = false;
end
else
r = false;
end
return r;
end -- URIutil.mayHandle()
function URIutil.mayISBN( attempt )
-- Is this a syntactically correct ISBN, or empty?
-- Precondition:
--     attempt  -- string with presumable ISBN
-- Postcondition:
--     Returns  10 if 10 digits and hyphens; also X at end of ISBN-10
--              13 if 13 digits and hyphens; beginning with bookland
--              0  if empty
--              false if not empty and not an ISBN
local r;
if type( attempt ) == "string" then
local s = mw.text.trim( attempt );
if #s >= 10 then
r = URIutil.isISBN( attempt );
elseif #s == 0 then
r = 0;
else
r = false;
end
else
r = false;
end
return r;
end -- URIutil.mayISBN()
function URIutil.mayISSN( attempt )
-- Is this a correct ISSN, or empty?
-- Precondition:
--     attempt  -- string with presumable ISSN
-- Postcondition:
--     Returns  8 if 8 digits and hyphens; also X at end
--              13 if 13 digits and hyphens; beginning with issnland
--              0  if empty
--              false if not empty and not an ISSN
local r;
if type( attempt ) == "string" then
local s = mw.text.trim( attempt );
if #s >= 8 then
r = URIutil.isISSNvalid( attempt );
elseif #s == 0 then
r = 0;
else
r = false;
end
else
r = false;
end
return r;
end -- URIutil.mayISSN()
function URIutil.mayLCCN( attempt )
-- Is this a syntactically correct LCCN?
-- Precondition:
--     attempt  -- string with presumable LCCN
-- Postcondition:
--     Returns  string with LCCN formatted aa9999-99999999
--              0  if empty
--              false if not recognized
if type( attempt ) == "string" then
local s = mw.text.trim( attempt );
if  s == "" then
r = 0;
else
r = URIutil.isLCCN( s );
end
else
r = false;
end
return r;
end -- URIutil.mayLCCN()
function URIutil.mayURI( attempt, ascii )
-- Is this a syntactically correct URI, or empty?
-- Precondition:
--     attempt  -- string with presumable URI
--     ascii    -- limit to ASCII (no IRI)
-- Postcondition:
--     Returns  false if no problem
--              string with violation
local r = URIutil.isEscValid( attempt );
if not r then
local s = mw.text.trim( attempt );
r = s:match( "%s(.+)$" );
if not r then
r = s:match( "#(.*)$" );
if r then
r = "#" .. r;
elseif ascii then
local p = string.format( "%s.+$",
mw.ustring.char( 128,45,255 ) );
r = mw.ustring.match( s, p );
end
end
end
return r;
end -- URIutil.mayURI()
function URIutil.mayURN( attempt )
-- Is this a syntactically correct URN, or empty?
-- Precondition:
--     attempt  -- string with presumable URN, starting with "urn:"
-- Postcondition:
--     Returns  false if no problem
--              string with violation
local r = URIutil.mayURI( attempt, true );
if not r then
local s = attempt:match( "^%s*uUrRnN:(.+)$" );
if s then
local space, id = s:match( "^(%w+):(.+)$" );
if space then
r = URNnamespace( space:lower(), id );
else
r = s;
end
elseif mw.text.trim( attempt ) == "" then
r = false;
else
r = "urn:";
end
end
return r;
end -- URIutil.mayURN()
function URIutil.plainISBN( attempt )
-- Format ISBN as digits (and 'X') only string
-- Precondition:
--     attempt  -- string with presumable ISBN
-- Postcondition:
--     Returns  string with 10 or 13 chars
--              false if not empty and not an ISBN
local r;
local isbn = ISBNfactory( attempt );
if type( isbn ) == "table" then
r = ISBNflat( isbn );
else
r = false;
end
return r;
end -- URIutil.plainISBN()
function URIutil.targetISSN( attempt, allow, any1, any2, alert )
-- Retrieve bracketed titled external link on ISSN DB without "ISSN"
-- Precondition:
--     attempt  -- string with presumable ISSN
--     allow    -- true: permit invalid check digit
--     any1     -- intentionally dummy parameter
--     any2     -- intentionally dummy parameter
--     alert    -- string with title of maintenance category, or nil
-- Postcondition:
--     Returns  link
local cnf  = fetch( "config" );
local issn = ISSNfactory( attempt );
local lapsus, r;
if type( issn ) == "table" then
local lenient;
if type( allow ) == "string" then
lenient = ( allow ~= "0" );
else
lenient = allow;
end
if lenient then
lapsus = false;
else
lapsus = ( not ISSNfaith( issn ) );
end
r = ISSNformat( issn, issn.type );
if type( cnf.issn ) == "string" then
r = string.format( "%s %s",
cnf.issn:gsub( "%$1", r ),
r );
end
else
lapsus = true;
r      = attempt;
end
if lapsus then
r = string.format( "%s%s",
r,  fault( "(?!?!)" ) );
if alert then
r = r .. flop( alert );
end
end
return r;
end -- URIutil.targetISSN()
function URIutil.uriDOI( attempt, anything, abbr )
-- Retrieve linked URI on DOI resolver
-- Precondition:
--     attempt   -- string with presumable DOI
--     anything  -- intentionally dummy parameter
--     abbr      -- true or string: link doi: abbreviation
local r = URIutil.linkDOI( attempt );
if r then
if abbr then
local s;
if type( abbr ) == "string" then
s = abbr;
else
s = "Digital Object Identifier";
end
r = string.format( "doi:%s", s, r );
else
r = "doi:" .. r;
end
end
return r;
end -- URIutil.uriDOI()
function URIutil.uriHandle( attempt, anything, abbr )
-- Retrieve linked URI on handle resolver
-- Precondition:
--     attempt   -- string with presumable handle
--     anything  -- intentionally dummy parameter
--     abbr      -- true or string: link hdl: abbreviation
local r = URIutil.linkHandle( attempt );
if r then
local s;
if type( abbr ) == "string" then
s = abbr;
end
if s then
r = string.format( "hdl:%s", s, r );
else
r = "hdl:" .. r;
end
end
return r;
end -- URIutil.uriHandle()
function URIutil.uriURN( attempt, anything, alter, alert )
-- Retrieve linked URI on URN resolver
-- Precondition:
--     attempt   -- string with presumable URN, starting with "urn:"
--     anything  -- intentionally dummy parameter
--     alter     -- string with alternative handler, or nil
--     alert     -- string with title of maintenance category, or nil
-- Postcondition:
--     Returns  link, or plain string if bad URN
local r, l = URIutil.linkURN( attempt, alter, false, false, alert );
if l then
local s = fetch( "config" ).supportURN;
if s then
if type( s ) ~= "string"
or  s == "" then
s = false;
end
else
s = "Uniform Resource Name";
end
if s then
r = string.format( "urn:%s", s, r );
else
r = "urn:" .. r;
end
else
end
return r;
end -- URIutil.uriURN()
URIutil.failsafe = function ( assert )
local r
if not assert  or  assert <= URIutil.serial then
r = URIutil.serial
else
r = false
end
return r
end -- URIutil.failsafe()
local Template = function ( frame, action )
-- Retrieve library result for template access
-- Precondition:
--     frame   -- object
--     action  -- string; function name
-- Postcondition:
--     Returns  appropriate string, or error message (development)
local lucky, r = pcall( URIutil action ,
frame.args 1  or "",
frame.args 2 ,
faculty( frame.args.link, true ),
faculty( frame.args.nbsp, true ),
frame.args.cat );
if lucky then
if r then
r = tostring( r );
else
r = "";
end
else
r = fault( r );
end
return r;
end -- Template()
-- Provide template access and expose URIutil table to require()
local p = {};
function p.coreISSN( frame )
return Template( frame, "coreISSN" );
end
function p.formatISBN( frame )
return Template( frame, "formatISBN" );
end
function p.formatISSN( frame )
return Template( frame, "formatISSN" );
end
function p.formatLCCN( frame )
return Template( frame, "formatLCCN" );
end
function p.isDNBvalid( frame )
return Template( frame, "isDNBvalid" );
end
function p.isDOI( frame )
return Template( frame, "isDOI" );
end
function p.isEscValid( frame )
return Template( frame, "isEscValid" );
end
function p.isGTINvalid( frame )
return Template( frame, "isGTINvalid" );
end
function p.isHandle( frame )
return Template( frame, "isHandle" );
end
function p.isISBN( frame )
return Template( frame, "isISBN" );
end
function p.isISBNvalid( frame )
return Template( frame, "isISBNvalid" );
end
function p.isISSNvalid( frame )
return Template( frame, "isISSNvalid" );
end
function p.isLCCN( frame )
return Template( frame, "isLCCN" );
end
function p.linkDNBopac( frame )
return Template( frame, "linkDNBopac" );
end
function p.linkDOI( frame )
return Template( frame, "linkDOI" );
end
function p.linkDOI( frame )
return Template( frame, "linkDOI" );
end
function p.linkHandle( frame )
return Template( frame, "linkHandle" );
end
function p.linkISBN( frame )
return Template( frame, "linkISBN" );
end
function p.linkISSN( frame )
return Template( frame, "linkISSN" );
end
function p.linkLCCN( frame )
return Template( frame, "linkLCCN" );
end
function p.linkURN( frame )
return Template( frame, "linkURN" );
end
function p.mayDOI( frame )
return Template( frame, "mayDOI" );
end
function p.mayHandle( frame )
return Template( frame, "mayHandle" );
end
function p.mayISBN( frame )
return Template( frame, "mayISBN" );
end
function p.mayISSN( frame )
return Template( frame, "mayISSN" );
end
function p.mayLCCN( frame )
return Template( frame, "mayLCCN" );
end
function p.mayURI( frame )
return Template( frame, "mayURI" );
end
function p.mayURN( frame )
return Template( frame, "mayURN" );
end
function p.plainISBN( frame )
return Template( frame, "plainISBN" );
end
function p.targetISSN( frame )
return Template( frame, "targetISSN" );
end
function p.uriDOI( frame )
return Template( frame, "uriDOI" );
end
function p.uriHandle( frame )
return Template( frame, "uriHandle" );
end
function p.uriURN( frame )
return Template( frame, "uriURN" );
end
p.failsafe = function ( frame )
local s = type( frame )
local since
if s == "table" then
since = frame.args 1 
elseif s == "string" then
since = frame
end
if since then
since = mw.text.trim( since )
if since == "" then
since = false
end
end
return URIutil.failsafe( since )  or  ""
end -- p.failsafe()
function p.URIutil()
return URIutil;
end
return p;