Modul:URIutil: Unterschied zwischen den Versionen

Aus FreeWiki
Zur Navigation springen Zur Suche springen
te>Umherirrender
K (Schützte „Modul:URIutil“: Häufig eingebundenes Modul ([Bearbeiten=Nur angemeldete, nicht neue Benutzer] (unbeschränkt) [Verschieben=Nur Administratoren] (unbeschränkt)))
K (41 Versionen importiert)
 
(39 dazwischenliegende Versionen von 4 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
--[=[ URIutil 2013-06-16
+
local URIutil = { suite  = "URIutil",
 +
                  serial = "2019-08-07",
 +
                  item  = 19644443 };
 +
--[=[
 
Utilities for URI etc.
 
Utilities for URI etc.
 
* coreISSN()
 
* coreISSN()
 
* formatISBN()
 
* formatISBN()
 
* formatISSN()
 
* formatISSN()
 +
* formatLCCN()
 
* isDNBvalid()
 
* isDNBvalid()
 
* isDOI()
 
* isDOI()
 
* isEscValid()
 
* isEscValid()
 
* isGTINvalid()
 
* isGTINvalid()
 +
* isHandle()
 
* isISBN()
 
* isISBN()
 
* isISBNvalid()
 
* isISBNvalid()
 
* isISSNvalid()
 
* isISSNvalid()
 
* isLCCN()
 
* isLCCN()
 +
* linkDNBopac()
 
* linkDOI()
 
* linkDOI()
 +
* linkHandle()
 
* linkISBN()
 
* linkISBN()
 
* linkISSN()
 
* linkISSN()
 +
* linkLCCN()
 +
* linkURN()
 +
* mayDOI()
 +
* mayHandle()
 
* mayISBN()
 
* mayISBN()
 
* mayISSN()
 
* mayISSN()
Zeile 21: Zeile 32:
 
* mayURN()
 
* mayURN()
 
* plainISBN()
 
* plainISBN()
 +
* targetISSN()
 
* uriDOI()
 
* uriDOI()
 +
* uriHandle()
 +
* uriURN()
 +
* failsafe()
 
* URIutil()
 
* URIutil()
 +
loadData: URIutil/config URIutil/isbn URIutil/urn
 
]=]
 
]=]
 
+
local Failsafe = URIutil
 
+
local CurrentPageName
 
 
-- table for export
 
local URIutil = {};
 
  
  
Zeile 140: Zeile 153:
 
     return  ( 11  -  r % 11 );
 
     return  ( 11  -  r % 11 );
 
end -- fair()
 
end -- fair()
 +
 +
 +
 +
local fault = function ( alert )
 +
    -- Format error message by class=error
 +
    -- Parameter:
 +
    --    alert  -- string, error message
 +
    -- Returns:
 +
    --    string, HTML span
 +
    return tostring( mw.html.create( "span" )
 +
                            :addClass( "error" )
 +
                            :wikitext( 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( "[[Category:%s]]", alert );
 +
    end
 +
    return r;
 +
end -- flop()
  
  
Zeile 166: Zeile 235:
  
  
local DNBfaith = function ( assert )
+
local fullPageName = function ()
     -- Compute DNB (also GND, ZDB) check digit and verify
+
     -- Retrieve current page name
    -- Precondition:
 
    --    assert  -- table; as of factory()
 
    --                .type  -- until 11 including check digit
 
 
     -- Postcondition:
 
     -- Postcondition:
     --    Returns  true: check digit matches
+
     --    Returns  string: current page name
     local k = fair( assert ) %  11;
+
     if not CurrentPageName then
     return ( k == assert[ assert.type ] );
+
        CurrentPageName = mw.title.getCurrentTitle().fullText;
end -- DNBfaith()
+
    end
 +
     return CurrentPageName;
 +
end -- fullPageName()
  
  
  
local DNB2011faith = function ( assert )
+
local DNBfaith = function ( assert, ancestor )
     -- Compute DNB check digit before 2012 and verify
+
     -- Compute DNB (also GND, ZDB) check digit and verify
 
     -- Precondition:
 
     -- Precondition:
     --    assert -- table; as of factory()
+
     --    assert   -- table; as of factory()
     --               .type  -- until 11 including check digit
+
     --                 .type  -- until 11 including check digit
 +
    --    ancestor  -- true: 2011 mode
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Returns  true: check digit matches
 
     --    Returns  true: check digit matches
     local k = ( 11 - fair( assert ) )  %  11;
+
    -- 2013-09-01
 +
     local k = fair( assert )  %  11;
 +
    if ancestor then
 +
        k  =  11 - k;
 +
    end
 
     return  ( k == assert[ assert.type ] );
 
     return  ( k == assert[ assert.type ] );
end -- DNB2011faith()
+
end -- DNBfaith()
  
  
Zeile 208: Zeile 281:
 
             lead = false;
 
             lead = false;
 
         else    -- even
 
         else    -- even
             r = r + k + k;
+
             r   = r + k + k;
 
             lead = true;
 
             lead = true;
 
         end
 
         end
Zeile 247: Zeile 320:
 
     local r;
 
     local r;
 
     if type( attempt ) == "string" then
 
     if type( attempt ) == "string" then
 +
        local s;
 
         r = factory( attempt, 10 );
 
         r = factory( attempt, 10 );
         if type( r ) == "table" then
+
        s = type( r );
 +
         if s == "number"  and  r ~= 10  and  r > 6  and  r < 13 then
 +
            r = factory( attempt, r );
 +
            s = type( r );
 +
        end
 +
        if s == "table" then
 
             if r.type == 13 then
 
             if r.type == 13 then
 
                 if r[1] ~= 9  or
 
                 if r[1] ~= 9  or
Zeile 327: Zeile 406:
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Returns  number of digits, at least 0
 
     --    Returns  number of digits, at least 0
     local r = 0;
+
     local r       = 0;
     local lucky, def = pcall( mw.loadData, "Module:URIutil/isbn" );
+
     local def     = fetch( "isbn" );
     if type( def ) == "table"  then
+
     local bookland = def[ apply ];
        local bookland = def[ apply ];
+
    if type( bookland ) == "table"  then
        if type( bookland ) == "table"  then
+
        local country = bookland[ allocate ];
            local country = bookland[ allocate ];
+
        if type( country ) == "table" then
            if type( country ) == "table" then
+
            local e, i, j, k, m, v;
                local e, i, j, k, m, v;
+
            for i = 1, 4 do
                for i = 1, 4 do
+
                v = country[ i ];
                    v = country[ i ];
+
                if type( v ) == "table" then
                    if type( v ) == "table" then
+
                    m  =  tonumber( format( assigned, already, i ) );
                        m  =  tonumber( format( assigned, already, i ) );
+
                    for k, e in pairs( v ) do
                        for k, e in pairs( v ) do
+
                        if m >= e[ 1 ]  and  m <= e[ 2 ] then
                            if m >= e[ 1 ]  and  m <= e[ 2 ] then
+
                            r = e[ 3 ];
                                r = e[ 3 ];
+
                            break; -- for k
                                break; -- for k
+
                        end
                            end
+
                    end -- for k
                        end -- for k
+
                end
                    end
+
                if r > 0 then
                    if r > 0 then
+
                    break; -- for i
                        break; -- for i
+
                end
                    end
+
            end -- for i
                end -- for i
 
            end
 
 
         end
 
         end
 
     end
 
     end
Zeile 382: Zeile 459:
 
     else
 
     else
 
         k = 2;
 
         k = 2;
         if assigned[ j + 1 ] > 4 then
+
         if assigned[ j ] == 9  and
 +
          assigned[ j + 1 ] > 4 then
 
             k = 3;
 
             k = 3;
 
             if assigned[ j + 1 ] > 8 then
 
             if assigned[ j + 1 ] > 8 then
 
                 k = 4;
 
                 k = 4;
 
                 if assigned[ j + 2 ] > 8 then
 
                 if assigned[ j + 2 ] > 8 then
                     k = false;   -- invalid
+
                     k = 5;
 
                 end
 
                 end
 
             end
 
             end
Zeile 394: Zeile 472:
 
     if k then
 
     if k then
 
         n = format( assigned, j, k );
 
         n = format( assigned, j, k );
         r = r .. n .. "-";
+
         r = string.format( "%s%s-", r, n );
 
         j = j + k;
 
         j = j + k;
 
         n = ISBNfold( assigned,  m,  tonumber( n ),  j );
 
         n = ISBNfold( assigned,  m,  tonumber( n ),  j );
 
         if n > 0 then
 
         if n > 0 then
             r = r .. format( assigned, j, n ) .. "-";
+
             r = string.format( "%s%s-",  rformat( assigned, j, n ) );
 
             j = j + n;
 
             j = j + n;
 
         end
 
         end
 
     end
 
     end
     r = r .. format( assigned,  j,  assigned.type - j - 1 );
+
     r = r .. format( assigned,  j,  assigned.type - j );
 
     if assigned[ assigned.type ] == 10 then
 
     if assigned[ assigned.type ] == 10 then
 
         r = r .. "-X";
 
         r = r .. "-X";
 
     else
 
     else
         r = r .. "-" .. tostring( assigned[ assigned.type ] );
+
         r = string.format( "%s-%s",
 +
                          r,
 +
                          tostring( assigned[ assigned.type ] ) );
 
     end
 
     end
 
     if not r then
 
     if not r then
Zeile 462: Zeile 542:
 
     local r;
 
     local r;
 
     if assert.type == 8 then
 
     if assert.type == 8 then
         r = ( fair( assert )  ==  assert[ 8 ] );
+
         local k = fair( assert );
 +
        if k == 11 then
 +
            r = ( assert[ 8 ] ==  0 );
 +
        else
 +
            r = ( assert[ 8 ] ==  k );
 +
        end
 
     elseif assert.type == 13 then
 
     elseif assert.type == 13 then
 
         r = GTINfaith( assert );
 
         r = GTINfaith( assert );
Zeile 474: Zeile 559:
  
 
local ISSNformat = function ( assigned, achieve )
 
local ISSNformat = function ( assigned, achieve )
     -- Hyphen formatting
+
     -- Hyphen formatting of ISSN
 
     -- Precondition:
 
     -- Precondition:
 
     --    assigned  -- table; as of ISSNfactory(), and valid
 
     --    assigned  -- table; as of ISSNfactory(), and valid
Zeile 484: Zeile 569:
 
         local x;
 
         local x;
 
         if assigned.type == 8 then
 
         if assigned.type == 8 then
             r = format( assigned, 1, 4 ) .. "-" ..
+
             r = string.format( "%s-%s",
                format( assigned, 5, 3 );
+
                              format( assigned, 1, 4 ),
 +
                              format( assigned, 5, 3 ) );
 
             x = assigned[ 8 ];
 
             x = assigned[ 8 ];
 
         elseif assigned.type == 13 then
 
         elseif assigned.type == 13 then
             r = format( assigned, 4, 4 ) .. "-" ..
+
             r = string.format( "%s-%s",
                format( assigned, 8, 3 );
+
                              format( assigned, 4, 4 ),
 +
                              format( assigned, 8, 3 ) );
 
             x = fair( assigned );
 
             x = fair( assigned );
 
         end
 
         end
Zeile 499: Zeile 586:
 
     elseif achieve == 13 then
 
     elseif achieve == 13 then
 
         if assigned.type == 8 then
 
         if assigned.type == 8 then
             r = "977-" .. format( assigned, 1, 7 ) .. "-00-"
+
             r = string.format( "977-%s-00-%s",
                .. GTINfair( assigned );
+
                              format( assigned, 1, 7 ),
 +
                              GTINfair( assigned ) );
 
         elseif assigned.type == 13 then
 
         elseif assigned.type == 13 then
             r = "977-" .. format( assigned, 4, 7 )
+
             r = string.format( "977-%s%s-%s",
                      .. format( assigned, 10, 2 ) .. "-"
+
                              format( assigned, 4, 7 ),
                      .. tostring( assigned[ 13 ] );
+
                              format( assigned, 10, 2 ),
 +
                              tostring( assigned[ 13 ] ) );
 
         end
 
         end
 
     end
 
     end
Zeile 512: Zeile 601:
  
  
local function LCCNfactory( attempt )
+
local LCCNfactory = function ( attempt, allow )
     -- Retrieve segments of LCCN attempt
+
     -- Retrieve segments of LCCN attempt (format since 2001)
 
     -- Precondition:
 
     -- Precondition:
 
     --    attempt  -- string with presumable LCCN
 
     --    attempt  -- string with presumable LCCN
 +
    --    allow    -- false or string: "/"
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Returns  table; success
 
     --    Returns  table; success
 
     --              false if not correct, bad data
 
     --              false if not correct, bad data
     local r = false;
+
    -- 2014-12-28
     local pre, sep, s = attempt:match( "%s*(%a%a?%a?)(/?)(%d%S+)%s*$" );
+
     local r   = false;
     if pre then
+
     local pat = "^%s*(%a*)(/?)(%d%S+)%s*$";
 +
    local pre, sep, s = attempt:match( pat );
 +
     if pre and s then
 
         local year, serial;
 
         local year, serial;
         pre = pre:lower();
+
         if pre == "" then
        if sep == "/" then
+
            pre = false;
            year, serial = s:match( "(%d+)/(%d.+)$" );
+
            if sep ~= "" then
         elseif s:find( "-", 2, true ) then
+
                s = false;
             year, serial = s:match( "(%d+)%-(%d.+)$" );
+
            end
 +
         elseif #pre > 3 then
 +
             s = false;
 
         else
 
         else
             year = false;
+
             pre = pre:lower();
            serial = s;
 
 
         end
 
         end
         if year then
+
         if s then
             if #year == 4 then
+
            if allow ~= "/"  or  sep == "/" then
                local n = tonumber( year );
+
                if sep == "/" then
                if n < 2000 then
+
                    year, serial = s:match( "^(%d+)/(%d.+)$" );
                    serial = false;
+
                elseif s:find( "-", 2, true ) then
                elseif n > tonumber( os.date( "%Y" ) ) then
+
                    year, serial = s:match( "^(%d+)%-(%d.+)$" );
 +
                else
 +
                    year = s:match( "^([%d]+)" );
 +
                    if year then
 +
                        if #year <= 8 then
 +
                            year  = s:sub( 1, 2 );
 +
                            serial = s:sub( 3 );
 +
                        elseif #year <= 10 then
 +
                            year  = s:sub( 1, 4 );
 +
                            serial = s:sub( 5 );
 +
                        else
 +
                            year  = false;
 +
                            serial = s;
 +
                        end
 +
                    elseif tonumber( s ) then
 +
                        serial = s;
 +
                    end
 +
                end
 +
            end
 +
             if year then
 +
                if #year == 4 then
 +
                    local n = tonumber( year );
 +
                    if n <= 2000 then
 +
                        -- 2000 -> "00"
 +
                        serial = false;
 +
                    elseif n > tonumber( os.date( "%Y" ) ) then
 +
                        serial = false;
 +
                    end
 +
                elseif #year ~= 2 then
 
                     serial = false;
 
                     serial = false;
 
                 end
 
                 end
             elseif #year ~= 2 then
+
             end
                 serial = false;
+
            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.serial = string.format( "%06d",
 +
                                              tonumber( serial ) );
 +
                elseif #serial > 6 then
 +
                    r = false;
 +
                end
 
             end
 
             end
 
         end
 
         end
        if serial then
+
    end
            r = { pre = pre, serial = serial };
+
    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
 
         end
         if year then
+
        r = r .. assigned.year;
             r.year = year;
+
         if achieve then
 +
             r = r .. achieve;
 
         end
 
         end
 +
    end
 +
    if assigned.serial then
 +
        r = r .. assigned.serial;
 +
    end
 +
    if assigned.qualifier then
 +
        r = string.format( "%s/%s", r, assigned.qualifier );
 
     end
 
     end
 
     return r;
 
     return r;
end -- LCCNfactory()
+
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()
  
  
Zeile 562: Zeile 767:
 
     --    acquire  -- string with identification
 
     --    acquire  -- string with identification
 
     -- Postcondition:
 
     -- Postcondition:
     --    Returns  false if no problem
+
     --    Returns  false if no problem detected
 
     --              string with violation
 
     --              string with violation
     local lucky, r = pcall( mw.loadData, "Module:URIutil/config" );
+
     local r;
     if type( r ) == "table" then
+
     if area == "urn" then
         r = r.urn;
+
         r = "urn:";
         if type( r ) == "table" then
+
    else
            local s = r.sns;
+
         local s = fetch( "urn" ).sns;
            if type( s ) == "string" then
+
        if type( s ) == "string" then
                r = ":" .. area .. ":";
+
            r = string.format( ":%s:", area );
                if s:match( r ) then
+
            if s:match( r ) then
                    s = "[^%w%(%)%+,%-%.:=@;%$_!%*'].*$";
+
                s = "[^%w%(%)%+,%-%.:=/@;%$_!%*'].*$";
                    r = acquire:match( s );
+
                r = acquire:match( s );
                else
+
            else
                    r = "&#58;" .. area .. ":";
+
                r = string.format( "&#58;%s:", area );
                end
+
            end
                if not r then
+
            if not r then
                    r = false;
+
                r = false;
                    if area == "isbn" then
+
                if area == "isbn" then
                        if not URIutil.isISBNvalid( acquire ) then
+
                    if not URIutil.isISBNvalid( acquire ) then
                            r = acquire;
+
                        r = acquire;
                        end
+
                    end
                    elseif area == "issn" then
+
                elseif area == "issn" then
                        if not URIutil.isISSNvalid( acquire ) then
+
                    if not URIutil.isISSNvalid( acquire ) then
                            r = acquire;
+
                        r = acquire;
                        end
 
 
                     end
 
                     end
 
                 end
 
                 end
            else
 
                r = "Module:URIutil/config.urn.sns";
 
 
             end
 
             end
        else
 
            r = "Module:URIutil/config.urn";
 
 
         end
 
         end
 
     end
 
     end
 
     return r;
 
     return r;
 
end -- URNnamespace()
 
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()
  
  
Zeile 624: Zeile 859:
  
  
function URIutil.formatISBN( attempt )
+
function URIutil.formatISBN( attempt, assigned )
 
     -- Format ISBN, if no hyphens present
 
     -- Format ISBN, if no hyphens present
 
     -- Precondition:
 
     -- Precondition:
     --    attempt -- string with presumable ISBN
+
     --    attempt   -- string with presumable ISBN
 +
    --    assigned  -- table or false; as of ISBNfactory()
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Returns  string with some hyphens, if not yet
 
     --    Returns  string with some hyphens, if not yet
 
     --              unmodified input if already hyphens or wrong
 
     --              unmodified input if already hyphens or wrong
 
     local r;
 
     local r;
     local isbn = ISBNfactory( attempt );
+
     local isbn;
 +
    if type( assigned ) == "table" then
 +
        isbn = assigned;
 +
    else
 +
        isbn = ISBNfactory( attempt );
 +
    end
 
     if type( isbn ) == "table" then
 
     if type( isbn ) == "table" then
 
         r = ISBNformat( attempt, isbn );
 
         r = ISBNformat( attempt, isbn );
Zeile 677: Zeile 918:
  
  
function URIutil.isDNBvalid( attempt )
+
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)?
 
     -- Is this DNB (also GND, ZDB) formally correct (check digit)?
 
     -- Precondition:
 
     -- Precondition:
 
     --    attempt  -- string with any presumable DNB code
 
     --    attempt  -- string with any presumable DNB code
 +
    --    also    -- string or nil; optional requirement DMA GND SWD
 +
    --                "ZDB"  -- permit hyphen, but use >2011 rule
 +
    --                DMA starting with 3 and no hyphen
 +
    --                GND not DNB2011
 +
    --                SWD DNB2011 starting with 4 or 7 and no X check
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Returns  number of digits or 2011, if valid
 
     --    Returns  number of digits or 2011, if valid
 
     --              false if not correct, bad data or check digit wrong
 
     --              false if not correct, bad data or check digit wrong
     local j = attempt:find( "/", 5, true );
+
    local s = mw.text.trim( attempt );
 +
     local j = s:find( "/", 5, true );
 
     local r = false;
 
     local r = false;
     local s;
+
     local dnb;
 
     if j then
 
     if j then
 
         s = attempt:sub( 1,  j - 1 );
 
         s = attempt:sub( 1,  j - 1 );
    else
 
        s = attempt;
 
 
     end
 
     end
     local dnb = factory( s, true );
+
     j = s:find( "-", 2, true );
    if type( dnb ) == "table" then
+
    if j then
        j = attempt:find( "-", 3, true );
+
         if j > 3  and  j <= 8 then
         if j then
+
             if s:match( "^[0-9]+-[0-9xX]$" ) then
             j = attempt:match( "^ *([0-9]+)-[0-9xX] *$" );
+
                 dnb = factory( s, true );
            if j then
 
                 j = ( #j < 8 );
 
 
             end
 
             end
 
         end
 
         end
 +
    elseif #s > 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 j then
             if DNB2011faith( dnb ) then
+
             if DNBfaith( dnb, true ) then
 
                 r = 2011;
 
                 r = 2011;
 +
            elseif type( also ) == "string" then
 +
                s = mw.text.trim( also );
 +
                if s == "ZDB" then
 +
                    if DNBfaith( dnb, false ) then
 +
                        r = dnb.type;
 +
                    end
 +
                end
 
             end
 
             end
 
         else
 
         else
             if DNBfaith( dnb ) then
+
             if DNBfaith( dnb, false ) then
 
                 r = dnb.type;
 
                 r = dnb.type;
 +
            elseif type( also ) == "string" then
 +
                s = mw.text.trim( also );
 +
                if s == "ZDB" then
 +
                    if DNBfaith( dnb, true ) then
 +
                        r = dnb.type;
 +
                    end
 +
                end
 
             end
 
             end
 
         end
 
         end
Zeile 724: Zeile 1.004:
 
     --              false if not correct, bad character or syntax
 
     --              false if not correct, bad character or syntax
 
     local r = false;
 
     local r = false;
     local k, s = attempt:match( "^ *10%.([1-9][0-9]+)/(.+) *$" );
+
     local k, s = attempt:match( "^%s*10%.([1-9][0-9]+)/(.+)%s*$" );
 
     if k then
 
     if k then
 
         k = tonumber( k );
 
         k = tonumber( k );
 
         if k >= 1000  and  k < 100000000 then
 
         if k >= 1000  and  k < 100000000 then
             local pc = "^[0-9A-Za-z]"
+
             local pc = "^[0-9A-Za-z%(%[<%./]"
                       .. "[%-0-9/A-Z%.a-z%(%)_%[%];:<>]+"
+
                       .. "[%-0-9/A-Z%.a-z%(%)_%[%];,:<>%+]*"
                       .. "[0-9A-Za-z]$";
+
                       .. "[0-9A-Za-z%)%]>%+#]$"
 
             s = mw.uri.decode( mw.text.decode( s ), "PATH" );
 
             s = mw.uri.decode( mw.text.decode( s ), "PATH" );
 
             if s:match( pc ) then
 
             if s:match( pc ) then
Zeile 796: Zeile 1.076:
 
     return r;
 
     return r;
 
end -- URIutil.isGTINvalid()
 
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()
  
  
Zeile 804: Zeile 1.116:
 
     --    attempt  -- string with presumable ISBN
 
     --    attempt  -- string with presumable ISBN
 
     -- Postcondition:
 
     -- Postcondition:
     --    Returns  10 if 10 digits and hyphens; also X at end of ISBN-10
+
     --    Returns
 +
    --        1 -- 10 if 10 digits and hyphens; also X at end of ISBN-10
 
     --              13 if 13 digits and hyphens; beginning with bookland
 
     --              13 if 13 digits and hyphens; beginning with bookland
 
     --              false if not an ISBN
 
     --              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 r;
 
     local isbn = ISBNfactory( attempt );
 
     local isbn = ISBNfactory( attempt );
Zeile 814: Zeile 1.133:
 
         r = false;
 
         r = false;
 
     end
 
     end
     return r;
+
     return r, isbn;
 
end -- URIutil.isISBN()
 
end -- URIutil.isISBN()
  
Zeile 824: Zeile 1.143:
 
     --    attempt  -- string with presumable ISBN
 
     --    attempt  -- string with presumable ISBN
 
     -- Postcondition:
 
     -- Postcondition:
     --    Returns  10 if 10 digits and hyphens; also X at end of ISBN-10
+
     --    Returns
 +
    --        1 -- 10 if 10 digits and hyphens; also X at end of ISBN-10
 
     --              13 if 13 digits and hyphens; beginning with bookland
 
     --              13 if 13 digits and hyphens; beginning with bookland
 
     --              false if not correct, bad data or check digit wrong
 
     --              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 r    = false;
 
     local isbn = ISBNfactory( attempt );
 
     local isbn = ISBNfactory( attempt );
Zeile 834: Zeile 1.160:
 
         end
 
         end
 
     end
 
     end
     return r;
+
     return r, isbn;
 
end -- URIutil.isISBNvalid()
 
end -- URIutil.isISBNvalid()
  
Zeile 859: Zeile 1.185:
  
  
function URIutil.isLCCN( attempt )
+
function URIutil.isLCCN( attempt, allow )
 
     -- Is this a syntactically correct LCCN?
 
     -- Is this a syntactically correct LCCN?
 
     -- Precondition:
 
     -- Precondition:
 
     --    attempt  -- string with presumable LCCN
 
     --    attempt  -- string with presumable LCCN
 +
    --    allow    -- false or string: "/"
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Returns  string with LCCN formatted aa9999-99999999
 
     --    Returns  string with LCCN formatted aa9999-99999999
 
     --              false if not correct, bad data
 
     --              false if not correct, bad data
 
     local r    = false;
 
     local r    = false;
     local lccn = LCCNfactory( attempt );
+
     local lccn = LCCNfactory( attempt, allow );
 
     if lccn then
 
     if lccn then
         r = lccn.pre;
+
         r = LCCNformat( lccn, "-" );
         if lccn.year then
+
    end
             r = r .. lccn.year .. "-";
+
    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  and  cnf.supportDNB ~= fullPageName() then
 +
            s = string.format( "[[%s|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]",
 +
                          s,
 +
                          "https://portal.dnb.de/opac.htm",
 +
                          "?referrer=Wikipedia",
 +
                          "&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
        r = r .. lccn.serial;
 
 
     end
 
     end
 
     return r;
 
     return r;
end -- URIutil.isLCCN()
+
end -- URIutil.linkDNBopac()
  
  
  
function URIutil.linkDOI( attempt )
+
function URIutil.linkDOI( attempt, any1, any2, any3, alert )
 
     -- Retrieve bracketed titled external link on DOI resolver
 
     -- Retrieve bracketed titled external link on DOI resolver
 
     -- Precondition:
 
     -- Precondition:
 
     --    attempt  -- string with presumable DOI
 
     --    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 );
 
     local r = URIutil.isDOI( attempt );
 
     if r then
 
     if r then
 +
        local e = mw.html.create( "span" )
 +
                        :addClass( "uri-handle" );
 +
        local s;
 +
        s, r = attempt:match( "^%s*(10%.[1-9][0-9]+/)(.+)%s*$" );
 +
        r = mw.text.decode( r, "PATH" );
 +
        r = string.format( "[%s/%s%s %s%s]",
 +
                          "//doi.org",
 +
                          s,
 +
                          mw.uri.encode( r ),
 +
                          s,
 +
                          mw.text.encode( r, "<>&%]" ) );
 +
        r = tostring( e:wikitext( 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
 +
        local e = mw.html.create( "span" )
 +
                        :addClass( "uri-handle" );
 
         r = mw.text.decode( mw.text.trim( attempt ),  "PATH" );
 
         r = mw.text.decode( mw.text.trim( attempt ),  "PATH" );
         r = string.format( "[%s %s]",
+
         r = string.format( "[%s%s %s]",
                           "http://dx.doi.org/" .. mw.uri.encode( r ),
+
                           "//hdl.handle.net/",
 +
                          mw.uri.encode( r ),
 
                           mw.text.encode( r, "<>&%]" ) );
 
                           mw.text.encode( r, "<>&%]" ) );
 +
        r = tostring( e:wikitext( r ) );
 +
    else
 +
        r = flop( alert );
 
     end
 
     end
 
     return r;
 
     return r;
end -- URIutil.linkDOI()
+
end -- URIutil.linkHandle()
  
  
  
function URIutil.linkISBN( attempt, allow, abbr, adhere )
+
function URIutil.linkISBN( attempt, allow, abbr, adhere, alert )
 
     -- Retrieve bracketed titled wikilink on booksources page with "ISBN"
 
     -- Retrieve bracketed titled wikilink on booksources page with "ISBN"
 
     -- Precondition:
 
     -- Precondition:
 
     --    attempt  -- string with presumable ISBN
 
     --    attempt  -- string with presumable ISBN
     --    allow    -- true: permit invalid check digit
+
     --    allow    -- true: permit invalid check digit or digit count
 
     --    abbr    -- true or string: link ISBN abbreviation
 
     --    abbr    -- true or string: link ISBN abbreviation
     --    adhere  -- true: use &nbsp;  else: use simple space;
+
     --    adhere  -- true: use &nbsp;  else: use simple space
 +
    --    alert    -- string with title of maintenance category, or nil
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Returns  link
 
     --    Returns  link
 
     local lapsus;
 
     local lapsus;
 
     local source = mw.text.trim( attempt );
 
     local source = mw.text.trim( attempt );
     local r      = "[[Special:Booksources/" .. source .. "|";
+
     local r      = string.format( "[[Special:Booksources/%s|", source );
 
     local isbn  = ISBNfactory( source );
 
     local isbn  = ISBNfactory( source );
 
     if type( isbn ) == "table" then
 
     if type( isbn ) == "table" then
Zeile 923: Zeile 1.337:
 
         r = r .. ISBNformat( attempt, isbn );
 
         r = r .. ISBNformat( attempt, isbn );
 
     else
 
     else
         lapsus = true;
+
         lapsus = not allow;
 
         r      = r .. source;
 
         r      = r .. source;
 
     end
 
     end
 
     r = r .. "]]";
 
     r = r .. "]]";
 
     if lapsus then
 
     if lapsus then
         r = "<span class='invalid-ISBN'>"
+
         local e = mw.html.create( "span" )
            .. r
+
                        :addClass( "invalid-ISBN" )
            .. "</span><span class='error'>(?!?!)</span>";
+
                        :wikitext( r );
 +
        r = tostring( e ) .. fault( "(?!?!)" );
 +
        if alert then
 +
            r = r .. flop( alert );
 +
        end
 
     end
 
     end
 
     if adhere then
 
     if adhere then
Zeile 938: Zeile 1.356:
 
     end
 
     end
 
     if abbr then
 
     if abbr then
         local s;
+
        local cnf = fetch( "config" );
         if type( abbr ) == "string" then
+
         local s   = cnf.supportISBN;
            s = abbr;
+
         if s then
 +
            if type( s ) ~= "string"
 +
              or  s == "" then
 +
                s = false;
 +
            end
 
         else
 
         else
 
             s = "International Standard Book Number";
 
             s = "International Standard Book Number";
 
         end
 
         end
         r = string.format( "[[%s|ISBN]]%s", s, r );
+
         if s  and  s ~= fullPageName() then
 +
            s = string.format( "[[%s|ISBN]]", s );
 +
        else
 +
            s = "ISBN";
 +
        end
 +
        r = string.format( "%s&#160;%s", s, r );
 
     else
 
     else
 
         r = "ISBN" .. r;
 
         r = "ISBN" .. r;
Zeile 953: Zeile 1.380:
  
  
function URIutil.linkISSN( attempt, allow, abbr, adhere )
+
function URIutil.linkISSN( attempt, allow, abbr, adhere, alert )
 
     -- Retrieve bracketed titled external link on ISSN DB with "ISSN"
 
     -- Retrieve bracketed titled external link on ISSN DB with "ISSN"
 
     -- Precondition:
 
     -- Precondition:
 
     --    attempt  -- string with presumable ISSN
 
     --    attempt  -- string with presumable ISSN
 
     --    allow    -- true: permit invalid check digit
 
     --    allow    -- true: permit invalid check digit
     --    abbr    -- true or string: link ISSN abbreviation
+
     --    abbr    -- true: link ISSN abbreviation
 
     --    adhere  -- true: use &nbsp;  else: use simple space;
 
     --    adhere  -- true: use &nbsp;  else: use simple space;
 +
    --    alert    -- string with title of maintenance category, or nil
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Returns  link
 
     --    Returns  link
     local issn  = ISSNfactory( attempt );
+
     local r = URIutil.targetISSN( attempt, allow, nil, nil, alert );
    local lapsus;
 
    local r;
 
    if type( issn ) == "table" then
 
        local lucky, cnf = pcall( mw.loadData, "Module:URIutil/config" );
 
        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 ) == "table" then
 
            if type( cnf.issn ) == "string" then
 
                r = string.format( "[%s %s]",
 
                                  cnf.issn:gsub( "%$1", r ),
 
                                  r );
 
            end
 
        end
 
    else
 
        lapsus = true;
 
        r      = attempt;
 
    end
 
    if lapsus then
 
        r = "<span class='invalid-ISSN'>"
 
            .. r
 
            .. "</span><span class='error'>(?!?!)</span>";
 
    end
 
 
     if adhere then
 
     if adhere then
         r = "&nbsp;" .. r;
+
         r = "&#160;" .. r;
 
     else
 
     else
 
         r = " " .. r;
 
         r = " " .. r;
 
     end
 
     end
 
     if abbr then
 
     if abbr then
         local s;
+
        local cnf  = fetch( "config" );
         if type( abbr ) == "string" then
+
         local s = cnf.supportISSN;
            s = abbr;
+
         if s then
 +
            if type( s ) ~= "string"
 +
              or  s == "" then
 +
                s = false;
 +
            end
 
         else
 
         else
 
             s = "International Standard Serial Number";
 
             s = "International Standard Serial Number";
 
         end
 
         end
         r = string.format( "[[%s|ISSN]]%s", s, r );
+
         if s  and  s ~= fullPageName() then
 +
            s = string.format( "[[%s|ISSN]]", s );
 +
        else
 +
            s = "ISSN";
 +
        end
 +
        r = string.format( "%s%s", s, r );
 
     else
 
     else
 
         r = "ISSN" .. r;
 
         r = "ISSN" .. r;
Zeile 1.013: Zeile 1.418:
 
     return r;
 
     return r;
 
end -- URIutil.linkISSN()
 
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, at, alone )
 +
    -- 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
 +
    --    at      -- fragment, or nil
 +
    --    alone    -- true, if link text not preceded by "urn:"
 +
    -- 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 URIutil.mayURN( attempt ) then
 +
        local e = mw.html.create( "span" )
 +
                        :addClass( "invalid-URN" );
 +
        local serial  = attempt;
 +
        local suffix  = flop( alert );
 +
        if serial:sub( 1, 4 ):lower() == "urn:" then
 +
            serial = serial:sub( 5 );
 +
        end
 +
        e:wikitext( serial );
 +
        r = tostring( e ) .. fault( "(?!?!)" );
 +
        if suffix then
 +
            r = r .. suffix;
 +
        end
 +
    else
 +
        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 );
 +
                            if at then
 +
                                r = string.format( "%s#%s", r, at );
 +
                            end
 +
                            if alone then
 +
                                r2 = true;
 +
                            else
 +
                                s = "urn:" .. s;
 +
                            end
 +
                            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
 +
                        r2 = not alone;
 +
                    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
 +
        r2 = false;
 +
    end
 +
    return r, r2;
 +
end -- URIutil.linkURN()
  
  
Zeile 1.039: Zeile 1.560:
 
     return r;
 
     return r;
 
end -- URIutil.mayDOI()
 
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()
  
  
Zeile 1.136: Zeile 1.683:
 
                 r = "&#35;" .. r;
 
                 r = "&#35;" .. r;
 
             elseif ascii then
 
             elseif ascii then
                 local pat = mw.ustring.char( 128,45,255 );
+
                 local p = string.format( "[%s].+$",
                pat = "[" .. pat .. "].+$";
+
                                        mw.ustring.char( 128,45,255 ) );
                 r   = mw.ustring.match( s, pat );
+
                 r = mw.ustring.match( s, p );
 
             end
 
             end
 
         end
 
         end
Zeile 1.150: Zeile 1.697:
 
     -- Is this a syntactically correct URN, or empty?
 
     -- Is this a syntactically correct URN, or empty?
 
     -- Precondition:
 
     -- Precondition:
     --    attempt  -- string with presumable URN
+
     --    attempt  -- string with presumable URN, starting with "urn:"
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Returns  false if no problem
 
     --    Returns  false if no problem
Zeile 1.158: Zeile 1.705:
 
         local s = attempt:match( "^%s*[uU][rR][nN]:(.+)$" );
 
         local s = attempt:match( "^%s*[uU][rR][nN]:(.+)$" );
 
         if s then
 
         if s then
             local ns, id = s:match( "^(%w+):(.+)$" );
+
             local space, id = s:match( "^(%w+):(.+)$" );
             if ns then
+
             if space then
                 r = URNnamespace( ns:lower(), id );
+
                 r = URNnamespace( space:lower(), id );
 
             else
 
             else
 
                 r = s;
 
                 r = s;
 
             end
 
             end
        elseif mw.text.trim( attempt ) == "" then
 
            r = false;
 
 
         else
 
         else
             r = "urn:";
+
             s = mw.text.trim( attempt );
 +
            if s == "" then
 +
                r = false;
 +
            elseif s:match( "^https?://" ) then
 +
                r = "http:";
 +
            else
 +
                r = "urn:";
 +
            end
 
         end
 
         end
 
     end
 
     end
Zeile 1.191: Zeile 1.743:
 
     return r;
 
     return r;
 
end -- URIutil.plainISBN()
 
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
 +
        local e = mw.html.create( "span" )
 +
                        :addClass( "invalid-ISSN" )
 +
                        :wikitext( r );
 +
        r = tostring( e ) .. fault( "(?!?!)" );
 +
        if alert then
 +
            r = r .. flop( alert );
 +
        end
 +
    end
 +
    return r;
 +
end -- URIutil.targetISSN()
  
  
Zeile 1.216: Zeile 1.817:
 
     return r;
 
     return r;
 
end -- URIutil.uriDOI()
 
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( "[[%s|hdl]]:%s", s, r );
 +
        else
 +
            r = "hdl:" .. r;
 +
        end
 +
    end
 +
    return r;
 +
end -- URIutil.uriHandle()
 +
 +
 +
 +
function URIutil.uriURN( attempt, anything, alter, alert, at )
 +
    -- 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
 +
    --    at        -- fragment, or nil
 +
    -- Postcondition:
 +
    --    Returns  link, or plain string if bad URN
 +
    local r, l =
 +
        URIutil.linkURN( attempt, alter, false, false, alert, at, true );
 +
    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( "[[%s|urn]]:%s", s, r );
 +
        else
 +
            r = "urn:" .. r;
 +
        end
 +
    end
 +
    return r;
 +
end -- URIutil.uriURN()
 +
 +
 +
 +
Failsafe.failsafe = function ( atleast )
 +
    -- Retrieve versioning and check for compliance
 +
    -- Precondition:
 +
    --    atleast  -- string, with required version or "wikidata" or "~"
 +
    --                or false
 +
    -- Postcondition:
 +
    --    Returns  string  -- with queried version, also if problem
 +
    --              false  -- if appropriate
 +
    local last  = ( atleast == "~" )
 +
    local since = atleast
 +
    local r
 +
    if last  or  since == "wikidata" then
 +
        local item = Failsafe.item
 +
        since = false
 +
        if type( item ) == "number"  and  item > 0 then
 +
            local entity = mw.wikibase.getEntity( string.format( "Q%d",
 +
                                                                item ) )
 +
            if type( entity ) == "table" then
 +
                local vsn = entity:formatPropertyValues( "P348" )
 +
                if type( vsn ) == "table"  and
 +
                  type( vsn.value ) == "string"  and
 +
                  vsn.value ~= "" then
 +
                    if last  and  vsn.value == Failsafe.serial then
 +
                        r = false
 +
                    else
 +
                        r = vsn.value
 +
                    end
 +
                end
 +
            end
 +
        end
 +
    end
 +
    if type( r ) == "nil" then
 +
        if not since  or  since <= Failsafe.serial then
 +
            r = Failsafe.serial
 +
        else
 +
            r = false
 +
        end
 +
    end
 +
    return r
 +
end -- Failsafe.failsafe()
  
  
Zeile 1.230: Zeile 1.930:
 
                             frame.args[ 2 ],
 
                             frame.args[ 2 ],
 
                             faculty( frame.args.link, true ),
 
                             faculty( frame.args.link, true ),
                             faculty( frame.args.nbsp, true ) );
+
                             faculty( frame.args.nbsp, true ),
 +
                            frame.args.cat,
 +
                            frame.args.fragment );
 
     if lucky then
 
     if lucky then
 
         if r then
 
         if r then
Zeile 1.238: Zeile 1.940:
 
         end
 
         end
 
     else
 
     else
         r = "<span class='error'>" .. r .. "</span>";
+
         r = fault( r );
 
     end
 
     end
 
     return r;
 
     return r;
Zeile 1.257: Zeile 1.959:
 
function p.formatISSN( frame )
 
function p.formatISSN( frame )
 
     return Template( frame, "formatISSN" );
 
     return Template( frame, "formatISSN" );
 +
end
 +
function p.formatLCCN( frame )
 +
    return Template( frame, "formatLCCN" );
 
end
 
end
 
function p.isDNBvalid( frame )
 
function p.isDNBvalid( frame )
Zeile 1.269: Zeile 1.974:
 
function p.isGTINvalid( frame )
 
function p.isGTINvalid( frame )
 
     return Template( frame, "isGTINvalid" );
 
     return Template( frame, "isGTINvalid" );
 +
end
 +
function p.isHandle( frame )
 +
    return Template( frame, "isHandle" );
 
end
 
end
 
function p.isISBN( frame )
 
function p.isISBN( frame )
Zeile 1.281: Zeile 1.989:
 
function p.isLCCN( frame )
 
function p.isLCCN( frame )
 
     return Template( frame, "isLCCN" );
 
     return Template( frame, "isLCCN" );
 +
end
 +
function p.linkDNBopac( frame )
 +
    return Template( frame, "linkDNBopac" );
 
end
 
end
 
function p.linkDOI( frame )
 
function p.linkDOI( frame )
 
     return Template( frame, "linkDOI" );
 
     return Template( frame, "linkDOI" );
 +
end
 +
function p.linkDOI( frame )
 +
    return Template( frame, "linkDOI" );
 +
end
 +
function p.linkHandle( frame )
 +
    return Template( frame, "linkHandle" );
 
end
 
end
 
function p.linkISBN( frame )
 
function p.linkISBN( frame )
Zeile 1.290: Zeile 2.007:
 
function p.linkISSN( frame )
 
function p.linkISSN( frame )
 
     return Template( frame, "linkISSN" );
 
     return Template( frame, "linkISSN" );
 +
end
 +
function p.linkLCCN( frame )
 +
    return Template( frame, "linkLCCN" );
 +
end
 +
function p.linkURN( frame )
 +
    return Template( frame, "linkURN" );
 
end
 
end
 
function p.mayDOI( frame )
 
function p.mayDOI( frame )
 
     return Template( frame, "mayDOI" );
 
     return Template( frame, "mayDOI" );
 +
end
 +
function p.mayHandle( frame )
 +
    return Template( frame, "mayHandle" );
 
end
 
end
 
function p.mayISBN( frame )
 
function p.mayISBN( frame )
Zeile 1.311: Zeile 2.037:
 
function p.plainISBN( frame )
 
function p.plainISBN( frame )
 
     return Template( frame, "plainISBN" );
 
     return Template( frame, "plainISBN" );
 +
end
 +
function p.targetISSN( frame )
 +
    return Template( frame, "targetISSN" );
 
end
 
end
 
function p.uriDOI( frame )
 
function p.uriDOI( frame )
 
     return Template( frame, "uriDOI" );
 
     return Template( frame, "uriDOI" );
 
end
 
end
function p.URIutil()
+
function p.uriHandle( frame )
     return URIutil;
+
    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 Failsafe.failsafe( since )  or  "";
 +
end -- p.failsafe()
 +
function p.URIutil( arg )
 +
    local r;
 +
    if arg then
 +
        r = "";
 +
    else
 +
        r = URIutil;
 +
    end
 +
     return r;
 
end
 
end
  
 
return p;
 
return p;

Aktuelle Version vom 6. September 2019, 12:27 Uhr

local URIutil = { suite = "URIutil",

                 serial = "2019-08-07",
                 item   = 19644443 };

--[=[ 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 Failsafe = URIutil local CurrentPageName


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 <= 57 then
               j      = j + 1;
               r[ j ] = c - 48;
               k      = false;
           elseif c == 45 then    -- hyphen
               if i > 1  and  i < n then
                   r.hyphens = r.hyphens + 1;
                   k         = i;
               else
                   r = j;
                   break;
               end
           elseif c == 88  or  c == 120 then    -- X x
               j = j + 1;
               if allowX  and  i == n then
                   if allowX == true  or  allowX == j then
                       r[ j ] = 10;
                   else
                       r = j;
                   end
               else
                   r = j;
               end
               break;
           else
               r = j;
               break;
           end
       end -- for i
       if type( r ) == "table" then
           r.type = j;
       end
   else
       r = 0;
   end
   return r;

end -- factory()


local faculty = function ( ask, auto )

   -- Evaluate possible string as boolean signal, if brief
   -- Precondition:
   --    ask   -- trimmed string or nil or other
   --    auto  -- fallback value if nil
   -- Postcondition:
   --     Returns appropriate value, or ask
   local r;
   if type( ask ) == "string" then
       if ask == "1"  or  ask == "" then
           r = true;
       elseif ask == "0"  or  ask == "-" then
           r = false;
       else
           r = ask;
       end
   elseif ask == nil then
       r = auto;
   else
       r = ask;
   end
   return r;

end -- faculty()


local fair = function ( assert )

   -- Compute check digit (11 minus modulo 11) for descending factor
   -- Precondition:
   --     assert  -- table; as of factory()
   --                .type  -- number of digits including check digit
   -- Postcondition:
   --     Returns checksum
   local i;
   local n = assert.type;
   local k = n;
   local r = 0;
   for i = 1,  n - 1 do
       r = r  +  k * assert[ i ];
       k = k - 1;
   end -- for i
   return  ( 11  -  r % 11 );

end -- fair()


local fault = function ( alert )

   -- Format error message by class=error
   -- Parameter:
   --     alert  -- string, error message
   -- Returns:
   --     string, HTML span
   return tostring( mw.html.create( "span" )
                           :addClass( "error" )
                           :wikitext( 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 fullPageName = function ()

   -- Retrieve current page name
   -- Postcondition:
   --     Returns  string: current page name
   if not CurrentPageName then
       CurrentPageName = mw.title.getCurrentTitle().fullText;
   end
   return CurrentPageName;

end -- fullPageName()


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 < 13 then
           r = factory( attempt, r );
           s = type( r );
       end
       if s == "table" then
           if r.type == 13 then
               if r[1] ~= 9  or
                  r[2] ~= 7  or
                  r[3] < 8 then
                   r = -2;
               end
           elseif r.type ~= 10 then
               r = -1;
           end
       end
   else
       r = 0;
   end
   return r;

end -- ISBNfactory()


local ISBNfaith = function ( assert )

   -- Compute ISBN check digit and verify
   -- Precondition:
   --     assert  -- table; as of ISBNfactory()
   --                       .type  -- 10 or 13
   -- Postcondition:
   --     Returns  true: check digit matches
   local r;
   if assert.type == 10 then
       local i;
       local k = 0;
       for i = 1, 9 do
           k = k  +  i * assert[ i ];
       end -- for i
       k = k % 11;
       r = ( k == assert[ 10 ] );
   elseif assert.type == 13 then
       r = GTINfaith( assert );
   else
       r = false;
   end
   return r;

end -- ISBNfaith()


local ISBNflat = function ( assigned )

   -- Plain digits of attempt ISBN
   -- Precondition:
   --     assigned  -- table; as of ISBNfactory()
   -- Postcondition:
   --     Returns  string with digits; ISBN-10 with 'X' at last position
   local i;
   local r = "";
   local n = assigned.type;
   if n == 10 then
       if assigned[ assigned.type ] == 10 then
           n = 9;
       end
   end
   for i = 1, n do
       r = r .. tostring( assigned[ i ] );
   end -- for i
   if n == 9 then
       r = r .. "X";
   end
   return r;

end -- ISBNflat()


local ISBNfold = function ( assigned, apply, allocate, already )

   -- Retrieve number of digits for ISBN publisher/group
   -- Precondition:
   --     assigned  -- table; as of ISBNfactory()
   --     apply     -- number; of bookland (978 or 979)
   --     allocate  -- number; of country
   --     already   -- number; position in assigned to inspect
   -- Postcondition:
   --     Returns  number of digits, at least 0
   local r        = 0;
   local def      = fetch( "isbn" );
   local bookland = def[ apply ];
   if type( bookland ) == "table"  then
       local country = bookland[ allocate ];
       if type( country ) == "table" then
           local e, i, j, k, m, v;
           for i = 1, 4 do
               v = country[ i ];
               if type( v ) == "table" then
                   m  =  tonumber( format( assigned, already, i ) );
                   for k, e in pairs( v ) do
                       if m >= e[ 1 ]  and  m <= e[ 2 ] then
                           r = e[ 3 ];
                           break; -- for k
                       end
                   end -- for k
               end
               if r > 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 ] < 8 then
       k = 1;
   else
       k = 2;
       if assigned[ j ] == 9  and
          assigned[ j + 1 ] > 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 <= 8 then
                           year   = s:sub( 1, 2 );
                           serial = s:sub( 3 );
                       elseif #year <= 10 then
                           year   = s:sub( 1, 4 );
                           serial = s:sub( 5 );
                       else
                           year   = false;
                           serial = s;
                       end
                   elseif tonumber( s ) then
                       serial = s;
                   end
               end
           end
           if year then
               if #year == 4 then
                   local n = tonumber( year );
                   if n <= 2000 then
                       -- 2000 -> "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.serial = string.format( "%06d",
                                             tonumber( serial ) );
               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( "%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 r;
   if area == "urn" then
       r = "urn:";
   else
       local s = fetch( "urn" ).sns;
       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
   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
   --                 "ZDB"  -- permit hyphen, but use >2011 rule
   --                 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
   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 <= 8 then
           if s:match( "^[0-9]+-[0-9xX]$" ) then
               dnb = factory( s, true );
           end
       end
   elseif #s > 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;
           elseif type( also ) == "string" then
               s = mw.text.trim( also );
               if s == "ZDB" then
                   if DNBfaith( dnb, false ) then
                       r = dnb.type;
                   end
               end
           end
       else
           if DNBfaith( dnb, false ) then
               r = dnb.type;
           elseif type( also ) == "string" then
               s = mw.text.trim( also );
               if s == "ZDB" then
                   if DNBfaith( dnb, true ) then
                       r = dnb.type;
                   end
               end
           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-9][0-9]+)/(.+)%s*$" );
   if k then
       k = tonumber( k );
       if k >= 1000  and  k < 100000000 then
           local pc = "^[0-9A-Za-z%(%[<%./]"
                      .. "[%-0-9/A-Z%.a-z%(%)_%[%];,:<>%+]*"
                      .. "[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   and  cnf.supportDNB ~= fullPageName() 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]",
                          s,
                          "https://portal.dnb.de/opac.htm",
                          "?referrer=Wikipedia",
                          "&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
       local e = mw.html.create( "span" )
                        :addClass( "uri-handle" );
       local s;
       s, r = attempt:match( "^%s*(10%.[1-9][0-9]+/)(.+)%s*$" );
       r = mw.text.decode( r, "PATH" );
       r = string.format( "[%s/%s%s %s%s]",
                          "//doi.org",
                          s,
                          mw.uri.encode( r ),
                          s,
                          mw.text.encode( r, "<>&%]" ) );
       r = tostring( e:wikitext( 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
       local e = mw.html.create( "span" )
                        :addClass( "uri-handle" );
       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 = tostring( e:wikitext( 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
       local e = mw.html.create( "span" )
                        :addClass( "invalid-ISBN" )
                        :wikitext( r );
       r = tostring( e ) .. 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  and  s ~= fullPageName() 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  and  s ~= fullPageName() 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, at, alone )

   -- 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
   --     at       -- fragment, or nil
   --     alone    -- true, if link text not preceded by "urn:"
   -- 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 URIutil.mayURN( attempt ) then
       local e = mw.html.create( "span" )
                        :addClass( "invalid-URN" );
       local serial  = attempt;
       local suffix  = flop( alert );
       if serial:sub( 1, 4 ):lower() == "urn:" then
           serial = serial:sub( 5 );
       end
       e:wikitext( serial );
       r = tostring( e ) .. fault( "(?!?!)" );
       if suffix then
           r = r .. suffix;
       end
   else
       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 );
                           if at then
                               r = string.format( "%s#%s", r, at );
                           end
                           if alone then
                               r2 = true;
                           else
                               s = "urn:" .. s;
                           end
                           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
                       r2 = not alone;
                   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
       r2 = false;
   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*[uU][rR][nN]:(.+)$" );
       if s then
           local space, id = s:match( "^(%w+):(.+)$" );
           if space then
               r = URNnamespace( space:lower(), id );
           else
               r = s;
           end
       else
           s = mw.text.trim( attempt );
           if s == "" then
               r = false;
           elseif s:match( "^https?://" ) then
               r = "http:";
           else
               r = "urn:";
           end
       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
       local e = mw.html.create( "span" )
                        :addClass( "invalid-ISSN" )
                        :wikitext( r );
       r = tostring( e ) .. 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, at )

   -- 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
   --     at        -- fragment, or nil
   -- Postcondition:
   --     Returns  link, or plain string if bad URN
   local r, l =
       URIutil.linkURN( attempt, alter, false, false, alert, at, true );
   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
   end
   return r;

end -- URIutil.uriURN()


Failsafe.failsafe = function ( atleast )

   -- Retrieve versioning and check for compliance
   -- Precondition:
   --     atleast  -- string, with required version or "wikidata" or "~"
   --                 or false
   -- Postcondition:
   --     Returns  string  -- with queried version, also if problem
   --              false   -- if appropriate
   local last  = ( atleast == "~" )
   local since = atleast
   local r
   if last  or  since == "wikidata" then
       local item = Failsafe.item
       since = false
       if type( item ) == "number"  and  item > 0 then
           local entity = mw.wikibase.getEntity( string.format( "Q%d",
                                                                item ) )
           if type( entity ) == "table" then
               local vsn = entity:formatPropertyValues( "P348" )
               if type( vsn ) == "table"  and
                  type( vsn.value ) == "string"  and
                  vsn.value ~= "" then
                   if last  and  vsn.value == Failsafe.serial then
                       r = false
                   else
                       r = vsn.value
                   end
               end
           end
       end
   end
   if type( r ) == "nil" then
       if not since  or  since <= Failsafe.serial then
           r = Failsafe.serial
       else
           r = false
       end
   end
   return r

end -- Failsafe.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,
                           frame.args.fragment );
   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 Failsafe.failsafe( since )  or  "";

end -- p.failsafe() function p.URIutil( arg )

   local r;
   if arg then
       r = "";
   else
       r = URIutil;
   end
   return r;

end

return p;