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 r[1] ~= 9 or
r[2] ~= 7 or
r[3] ~= 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( "&58;%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( "^%s10%.([1-9][0-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-9][0-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&nbsp;[%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&nbsp;%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 &nbsp; 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 = "&nbsp;" .. 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&160;%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 &nbsp; 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 = "&160;" .. 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[uU][rR][nN]:(%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&58;%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 = "&35;" .. 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[uU][rR][nN]:(.+)$" );
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;