gemeinsam neue Wege der Erkenntnis gehen
Eine freie Initiative von Menschen bei anthrowiki.at anthrowiki.at, anthro.world anthro.world, biodyn.wiki biodyn.wiki und steiner.wiki steiner.wiki
mit online Lesekreisen, Übungsgruppen, Vorträgen ...
Wie Sie die Entwicklung von AnthroWiki durch Ihre Spende unterstützen können, erfahren Sie hier.

Use Google Translate for a raw translation of our pages into more than 100 languages.
Please note that some mistranslations can occur due to machine translation.
Alle Banner auf einen Klick
Grundkurs zur Ausbildung in Biographie-Arbeit
11 Wochenenden in Mannheim
5. April 2025 bis 8. Feb. 2026
Leitung Joop Grün und Walter Seyffer
+49 (0) 6203 84 390 60
www.akademie-biographiearbeit.org
Der neue Glomer Katalog 2024/25 ist da!

Aktuelle Neuerscheinungen und alle lieferbaren Bücher anthroposophischer Verlage
Anthroposophie, Waldorf, Jugend & Kinderbücher, Gesundheit, Lebensphasen, Wissenschaften mit mehr als 7.500 Titeln aus über 80 Verlagen.

Rudolf Steiner: Die Prüfung der Seele
Sonntag, 4. Mai 2025, 20h
Bild 10 - 13, mit Einführung und anschließendem Publikumsgespräch

Livestream: Anmeldung auf https://holiversitaet.de/kultur/

Modul:URIutil

Aus AnthroWiki

Modul:LuaWiki:168: No transclude page 'Wikipedia:Lua/Modul-Navigationsfehler'


local URIutil = { suite  = "URIutil",
                  serial = "2017-10-04" };
--[=[
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 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 function fault( alert )
    -- Format error message by class=error
    -- Parameter:
    --     alert  -- string, error message
    -- Returns:
    --     string, HTML span
    return string.format( "<span class=\"error\">%s</span>", 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()



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( "[//lccn.loc.gov/%s %s]", r, s );
        end
    else
        r = attempt;
    end
    return r;
end -- LCCNforward()



local URNnamespace = function ( area, acquire )
    -- Are these parts of a correct URN?
    -- Precondition:
    --     area     -- string with lowercase namespace
    --     acquire  -- string with identification
    -- Postcondition:
    --     Returns  false if no problem detected
    --              string with violation
    local s = fetch( "urn" ).sns;
    local r;
    if type( s ) == "string" then
        r = string.format( ":%s:", area );
        if s:match( r ) then
            s = "[^%w%(%)%+,%-%.:=@;%$_!%*'].*$";
            r = acquire:match( s );
        else
            r = string.format( "&#58;%s:", area );
        end
        if not r then
            r = false;
            if area == "isbn" then
                if not URIutil.isISBNvalid( acquire ) then
                    r = acquire;
                end
            elseif area == "issn" then
                if not URIutil.isISSNvalid( acquire ) then
                    r = acquire;
                end
            end
        end
    end
    return r;
end -- URNnamespace()



local URNresolve = function ( assigned, ask, alter )
    -- Resolve URN within space
    -- Precondition:
    --     assigned  -- table with resolvers for this space
    --     ask       -- string with ID within this space
    --     alter     -- string with alternative resolver, or not
    -- Postcondition:
    --     Returns
    --         1.    URL of resolver, or nil
    --         2.    modified ask
    local resolver = assigned;
    local sign     = ask;
    local subset   = assigned[ ":" ];
    local r;
    if subset then
        local s = sign:match( subset );
        if s then
            s    = s:lower();
            sign = s .. sign:sub( #s + 1 )
            if assigned[ s ] then
                resolver = assigned[ s ];
            end
        end
    end
    if alter then
        r = resolver[ alter ];
    end
    if not r then
        r = resolver[ "*" ];
    end
    return r, sign;
end -- URNresolve()



function URIutil.coreISSN( attempt )
    -- Fetch significant ISSN
    -- Precondition:
    --     attempt  -- string with presumable ISSN
    -- Postcondition:
    --     Returns   string with 7 digits, without check digit nor GTIN
    --               unmodified input if wrong
    local r;
    local issn = ISSNfactory( attempt );
    if type( issn ) == "table" then
        if issn.type == 8 then
            r = format( issn, 1, 7 );
        elseif issn.type == 13 then
            r = format( issn, 4, 7 );
        end
    else
        r = mw.ustring.upper( mw.text.trim( attempt ) );
    end
    return r;
end -- URIutil.coreISSN()



function URIutil.formatISBN( attempt, assigned )
    -- Format ISBN, if no hyphens present
    -- Precondition:
    --     attempt   -- string with presumable ISBN
    --     assigned  -- table or false; as of ISBNfactory()
    -- Postcondition:
    --     Returns   string with some hyphens, if not yet
    --               unmodified input if already hyphens or wrong
    local r;
    local isbn;
    if type( assigned ) == "table" then
        isbn = assigned;
    else
        isbn = ISBNfactory( attempt );
    end
    if type( isbn ) == "table" then
        r = ISBNformat( attempt, isbn );
    else
        r = mw.ustring.upper( mw.text.trim( attempt ) );
    end
    return r;
end -- URIutil.formatISBN()



function URIutil.formatISSN( attempt, achieve )
    -- Format ISSN
    -- Precondition:
    --     attempt  -- string with presumable ISSN
    --     achieve  -- false or 8 or 13; requested presentation
    -- Postcondition:
    --     Returns   string with some hyphens, if not yet
    --               unmodified input if already hyphens or wrong
    local r    = false;
    local issn = ISSNfactory( attempt );
    if type( issn ) == "table" then
        if ISSNfaith( issn ) then
            local k, m;
            if type( achieve ) == "string" then
                m = tonumber( achieve );
            else
                m = achieve;
            end
            if m == 8  or m == 13 then
                k = m;
            else
                k = issn.type;
            end
            r = ISSNformat( issn, k );
        end
    end
    if not r then
        r = mw.ustring.upper( mw.text.trim( attempt ) );
    end
    return r;
end -- URIutil.formatISSN()



function URIutil.formatLCCN( attempt, achieve )
    -- Standard or hyphen formatting of LCCN
    -- Precondition:
    --     attempt  -- string with presumable LCCN
    --     achieve   -- additional formatting desires, like "-"
    -- Postcondition:
    --     Returns  string with letters, digits and hyphens
    --              unmodified input if wrong
    local r = LCCNfactory( attempt );
    if r then
        r = LCCNformat( r, achieve );
    end
    return r;
end -- URIutil.formatLCCN()



function URIutil.isDNBvalid( attempt, also )
    -- Is this DNB (also GND, ZDB) formally correct (check digit)?
    -- Precondition:
    --     attempt  -- string with any presumable DNB code
    --     also     -- string or nil; optional requirement DMA GND SWD
    --                 "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( "[[%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
    return r;
end -- URIutil.linkDNBopac()



function URIutil.linkDOI( attempt, any1, any2, any3, alert )
    -- Retrieve bracketed titled external link on DOI resolver
    -- Precondition:
    --     attempt  -- string with presumable DOI
    --     any1     -- intentionally dummy parameter
    --     any2     -- intentionally dummy parameter
    --     any3     -- intentionally dummy parameter
    --     alert    -- string with title of maintenance category, or nil
    -- Postcondition:
    --     Returns  external link, or false
    local r = URIutil.isDOI( attempt );
    if r then
        r = mw.text.decode( mw.text.trim( attempt ),  "PATH" );
        r = string.format( "[%s%s %s]",
                           "//dx.doi.org/",
                           mw.uri.encode( r ),
                           mw.text.encode( r, "<>&%]" ) );
        r = string.format( "<span class='uri-handle'>%s</span>",  r );
    else
        r = flop( alert );
    end
    return r;
end -- URIutil.linkDOI()



function URIutil.linkHandle( attempt, any1, any2, any3, alert )
    -- Retrieve bracketed titled external link on handle resolver
    -- Precondition:
    --     attempt  -- string with presumable handle
    --     any1     -- intentionally dummy parameter
    --     any2     -- intentionally dummy parameter
    --     any3     -- intentionally dummy parameter
    --     alert    -- string with title of maintenance category, or nil
    -- Postcondition:
    --     Returns  external link, or false
    local r = URIutil.isHandle( attempt );
    if r then
        r = mw.text.decode( mw.text.trim( attempt ),  "PATH" );
        r = string.format( "[%s%s %s]",
                           "//hdl.handle.net/",
                           mw.uri.encode( r ),
                           mw.text.encode( r, "<>&%]" ) );
        r = string.format( "<span class='uri-handle'>%s</span>",  r );
    else
        r = flop( alert );
    end
    return r;
end -- URIutil.linkHandle()



function URIutil.linkISBN( attempt, allow, abbr, adhere, alert )
    -- Retrieve bracketed titled wikilink on booksources page with "ISBN"
    -- Precondition:
    --     attempt  -- string with presumable ISBN
    --     allow    -- true: permit invalid check digit or digit count
    --     abbr     -- true or string: link ISBN abbreviation
    --     adhere   -- true: use &nbsp;  else: use simple space
    --     alert    -- string with title of maintenance category, or nil
    -- Postcondition:
    --     Returns  link
    local lapsus;
    local source = mw.text.trim( attempt );
    local r      = string.format( "[[Special:Booksources/%s|", source );
    local isbn   = ISBNfactory( source );
    if type( isbn ) == "table" then
        local lenient;
        if type( allow ) == "string" then
            lenient = ( allow ~= "0" );
        else
            lenient = allow;
        end
        if lenient then
            lapsus = false;
        else
            lapsus = ( not ISBNfaith( isbn ) );
        end
        r = r .. ISBNformat( attempt, isbn );
    else
        lapsus = not allow;
        r      = r .. source;
    end
    r = r .. "]]";
    if lapsus then
        r = string.format( "<span class='invalid-ISBN'>%s</span>%s",
                           r,  fault( "(?!?!)" ) );
        if alert then
            r = r .. flop( alert );
        end
    end
    if adhere then
        r = "&nbsp;" .. r;
    else
        r = " " .. r;
    end
    if abbr then
        local cnf = fetch( "config" );
        local s   = cnf.supportISBN;
        if s then
            if type( s ) ~= "string"
               or  s == "" then
                s = false;
            end
        else
            s = "Wikipedia:International Standard Book Number";
        end
        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
        r = "ISBN" .. r;
    end
    return r;
end -- URIutil.linkISBN()



function URIutil.linkISSN( attempt, allow, abbr, adhere, alert )
    -- Retrieve bracketed titled external link on ISSN DB with "ISSN"
    -- Precondition:
    --     attempt  -- string with presumable ISSN
    --     allow    -- true: permit invalid check digit
    --     abbr     -- true: link ISSN abbreviation
    --     adhere   -- true: use &nbsp;  else: use simple space;
    --     alert    -- string with title of maintenance category, or nil
    -- Postcondition:
    --     Returns  link
    local r = URIutil.targetISSN( attempt, allow, nil, nil, alert );
    if adhere then
        r = "&#160;" .. r;
    else
        r = " " .. r;
    end
    if abbr then
        local cnf  = fetch( "config" );
        local s = cnf.supportISSN;
        if s then
            if type( s ) ~= "string"
               or  s == "" then
                s = false;
            end
        else
            s = "Wikipedia:International Standard Serial Number";
        end
        if s  and  s ~= fullPageName() then
            s = string.format( "[[%s|ISSN]]", s );
        else
            s = "ISSN";
        end
        r = string.format( "%s%s", s, r );
    else
        r = "ISSN" .. r;
    end
    return r;
end -- URIutil.linkISSN()



function URIutil.linkLCCN( attempt, achieve, any1, any2, alert )
    -- Retrieve bracketed titled external LCCN permalink
    -- Precondition:
    --     attempt  -- string with presumable LCCN
    --     achieve  -- additional title formatting desires, like "-"
    --     any1     -- intentionally dummy parameter
    --     any2     -- intentionally dummy parameter
    --     alert    -- string with title of maintenance category, or nil
    -- Postcondition:
    --     Returns  link, or false if bad LCCN
    local r = LCCNforward( attempt, achieve );
    if not r then
        r = flop( alert );
    end
    return r;
end -- URIutil.linkLCCN()



function URIutil.linkURN( attempt, alter, any1, any2, alert )
    -- Retrieve bracketed titled external URN link
    -- Precondition:
    --     attempt  -- string, with presumable URN, starting with "urn:"
    --     alter    -- alternative handler
    --     any1     -- intentionally dummy parameter
    --     any2     -- intentionally dummy parameter
    --     alert    -- string, with title of maintenance category, or nil
    -- Postcondition:
    --     Returns
    --         1.    linked ID, or plain string if bad URN
    --         2.    true, if to be preceded by "urn:"
    local r2 = true;
    local r;
    if not URIutil.mayURI( attempt, true ) then
        local s = attempt:match( "^%s*[uU][rR][nN]:(%S+)%s*$" );
        if s then
            local space, sign = s:match( "^(%w+):(.+)$" );
            if space then
                local defs = fetch( "urn" );
                if type( defs ) == "table" then
                    local resolver = defs.resolver;
                    space    = space:lower();
                    resolver = resolver[ space ];
                    r2       = ( resolver ~= true );
                    if type( resolver ) == "table" then
                        r, sign = URNresolve( resolver, sign, alter );
                        s = string.format( "%s:%s", space, sign );
                        if r then
                            r = r:gsub( "%$1",  "urn:" .. s );
                            r = string.format( "[%s %s]", r, s );
                        end
                    elseif r2 then
                        if type( defs.sns ) == "string" then
                            s = string.format( ":%s:", space );
                            if not defs.sns:find( s, 1, true ) then
                                s = false;
                            end
                        else
                            s = false;
                        end
                        if s then
                            r = string.format( "%s:%s", space, sign );
                        else
                            r = string.format( "%s&#58;%s",
                                               space, sign );
                        end
                    else
                        s = "link" .. space:upper();
                        r = URIutil[ s ]( sign, alter, nil, nil, alert );
                    end
                else
                    r =  fault( "Bad structure in Module:URIutil/urn" );
                end
            end
        end
    end
    if not r then
        if alert then
            r = flop( alert )  or  "";
            if attempt then
                r = attempt .. r;
            end
        else
            r = mw.text.trim( attempt );
        end
    end
    return r, r2;
end -- URIutil.linkURN()



function URIutil.mayDOI( attempt )
    -- Is this a syntactically correct DOI, or empty?
    -- Precondition:
    --     attempt  -- string with presumable DOI
    -- Postcondition:
    --     Returns  number of organization
    --              0  if empty
    --              false if not empty and not a DOI
    local r;
    if type( attempt ) == "string" then
        local s = mw.text.trim( attempt );
        if #s >= 10 then
            r = URIutil.isDOI( attempt );
        elseif #s == 0 then
            r = 0;
        else
            r = false;
        end
    else
        r = false;
    end
    return r;
end -- URIutil.mayDOI()



function URIutil.mayHandle( attempt )
    -- Is this a meaningful handle, or empty?
    -- Precondition:
    --     attempt  -- string with presumable handle
    -- Postcondition:
    --     Returns  number of organization
    --              0  if empty
    --              false if not empty and not a DOI
    local r;
    if type( attempt ) == "string" then
        local s = mw.text.trim( attempt );
        if #s > 5 then
            r = URIutil.isHandle( attempt );
        elseif #s == 0 then
            r = 0;
        else
            r = false;
        end
    else
        r = false;
    end
    return r;
end -- URIutil.mayHandle()



function URIutil.mayISBN( attempt )
    -- Is this a syntactically correct ISBN, or empty?
    -- Precondition:
    --     attempt  -- string with presumable ISBN
    -- Postcondition:
    --     Returns  10 if 10 digits and hyphens; also X at end of ISBN-10
    --              13 if 13 digits and hyphens; beginning with bookland
    --              0  if empty
    --              false if not empty and not an ISBN
    local r;
    if type( attempt ) == "string" then
        local s = mw.text.trim( attempt );
        if #s >= 10 then
            r = URIutil.isISBN( attempt );
        elseif #s == 0 then
            r = 0;
        else
            r = false;
        end
    else
        r = false;
    end
    return r;
end -- URIutil.mayISBN()



function URIutil.mayISSN( attempt )
    -- Is this a correct ISSN, or empty?
    -- Precondition:
    --     attempt  -- string with presumable ISSN
    -- Postcondition:
    --     Returns  8 if 8 digits and hyphens; also X at end
    --              13 if 13 digits and hyphens; beginning with issnland
    --              0  if empty
    --              false if not empty and not an ISSN
    local r;
    if type( attempt ) == "string" then
        local s = mw.text.trim( attempt );
        if #s >= 8 then
            r = URIutil.isISSNvalid( attempt );
        elseif #s == 0 then
            r = 0;
        else
            r = false;
        end
    else
        r = false;
    end
    return r;
end -- URIutil.mayISSN()



function URIutil.mayLCCN( attempt )
    -- Is this a syntactically correct LCCN?
    -- Precondition:
    --     attempt  -- string with presumable LCCN
    -- Postcondition:
    --     Returns  string with LCCN formatted aa9999-99999999
    --              0  if empty
    --              false if not recognized
    if type( attempt ) == "string" then
        local s = mw.text.trim( attempt );
        if  s == "" then
            r = 0;
        else
            r = URIutil.isLCCN( s );
        end
    else
        r = false;
    end
    return r;
end -- URIutil.mayLCCN()



function URIutil.mayURI( attempt, ascii )
    -- Is this a syntactically correct URI, or empty?
    -- Precondition:
    --     attempt  -- string with presumable URI
    --     ascii    -- limit to ASCII (no IRI)
    -- Postcondition:
    --     Returns  false if no problem
    --              string with violation
    local r = URIutil.isEscValid( attempt );
    if not r then
        local s = mw.text.trim( attempt );
        r = s:match( "%s(.+)$" );
        if not r then
            r = s:match( "#(.*)$" );
            if r then
                r = "&#35;" .. r;
            elseif ascii then
                local p = string.format( "[%s].+$",
                                         mw.ustring.char( 128,45,255 ) );
                r = mw.ustring.match( s, p );
            end
        end
    end
    return r;
end -- URIutil.mayURI()



function URIutil.mayURN( attempt )
    -- Is this a syntactically correct URN, or empty?
    -- Precondition:
    --     attempt  -- string with presumable URN, starting with "urn:"
    -- Postcondition:
    --     Returns  false if no problem
    --              string with violation
    local r = URIutil.mayURI( attempt, true );
    if not r then
        local s = attempt:match( "^%s*[uU][rR][nN]:(.+)$" );
        if s then
            local space, id = s:match( "^(%w+):(.+)$" );
            if space then
                r = URNnamespace( space:lower(), id );
            else
                r = s;
            end
        elseif mw.text.trim( attempt ) == "" then
            r = false;
        else
            r = "urn:";
        end
    end
    return r;
end -- URIutil.mayURN()



function URIutil.plainISBN( attempt )
    -- Format ISBN as digits (and 'X') only string
    -- Precondition:
    --     attempt  -- string with presumable ISBN
    -- Postcondition:
    --     Returns  string with 10 or 13 chars
    --              false if not empty and not an ISBN
    local r;
    local isbn = ISBNfactory( attempt );
    if type( isbn ) == "table" then
        r = ISBNflat( isbn );
    else
        r = false;
    end
    return r;
end -- URIutil.plainISBN()



function URIutil.targetISSN( attempt, allow, any1, any2, alert )
    -- Retrieve bracketed titled external link on ISSN DB without "ISSN"
    -- Precondition:
    --     attempt  -- string with presumable ISSN
    --     allow    -- true: permit invalid check digit
    --     any1     -- intentionally dummy parameter
    --     any2     -- intentionally dummy parameter
    --     alert    -- string with title of maintenance category, or nil
    -- Postcondition:
    --     Returns  link
    local cnf  = fetch( "config" );
    local issn = ISSNfactory( attempt );
    local lapsus, r;
    if type( issn ) == "table" then
        local lenient;
        if type( allow ) == "string" then
            lenient = ( allow ~= "0" );
        else
            lenient = allow;
        end
        if lenient then
            lapsus = false;
        else
            lapsus = ( not ISSNfaith( issn ) );
        end
        r = ISSNformat( issn, issn.type );
        if type( cnf.issn ) == "string" then
            r = string.format( "[%s %s]",
                               cnf.issn:gsub( "%$1", r ),
                               r );
        end
    else
        lapsus = true;
        r      = attempt;
    end
    if lapsus then
        r = string.format( "<span class='invalid-ISSN'>%s</span>%s",
                           r,  fault( "(?!?!)" ) );
        if alert then
            r = r .. flop( alert );
        end
    end
    return r;
end -- URIutil.targetISSN()



function URIutil.uriDOI( attempt, anything, abbr )
    -- Retrieve linked URI on DOI resolver
    -- Precondition:
    --     attempt   -- string with presumable DOI
    --     anything  -- intentionally dummy parameter
    --     abbr      -- true or string: link doi: abbreviation
    local r = URIutil.linkDOI( attempt );
    if r then
        if abbr then
            local s;
            if type( abbr ) == "string" then
                s = abbr;
            else
                s = "Digital Object Identifier";
            end
            r = string.format( "[[%s|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( "[[%s|hdl]]:%s", s, r );
        else
            r = "hdl:" .. r;
        end
    end
    return r;
end -- URIutil.uriHandle()



function URIutil.uriURN( attempt, anything, alter, alert )
    -- Retrieve linked URI on URN resolver
    -- Precondition:
    --     attempt   -- string with presumable URN, starting with "urn:"
    --     anything  -- intentionally dummy parameter
    --     alter     -- string with alternative handler, or nil
    --     alert     -- string with title of maintenance category, or nil
    -- Postcondition:
    --     Returns  link, or plain string if bad URN
    local r, l = URIutil.linkURN( attempt, alter, false, false, alert );
    if l then
        local s = fetch( "config" ).supportURN;
        if s then
            if type( s ) ~= "string"
               or  s == "" then
                s = false;
            end
        else
            s = "Uniform Resource Name";
        end
        if s then
            r = string.format( "[[%s|urn]]:%s", s, r );
        else
            r = "urn:" .. r;
        end
    end
    return r;
end -- URIutil.uriURN()



URIutil.failsafe = function ( assert )
    local r;
    if not assert  or  assert <= URIutil.serial then
        r = URIutil.serial;
    else
        r = false;
    end
    return r;
end -- URIutil.failsafe()



local Template = function ( frame, action )
    -- Retrieve library result for template access
    -- Precondition:
    --     frame   -- object
    --     action  -- string; function name
    -- Postcondition:
    --     Returns  appropriate string, or error message (development)
    local lucky, r = pcall( URIutil[ action ],
                            frame.args[ 1 ] or "",
                            frame.args[ 2 ],
                            faculty( frame.args.link, true ),
                            faculty( frame.args.nbsp, true ),
                            frame.args.cat );
    if lucky then
        if r then
            r = tostring( r );
        else
            r = "";
        end
    else
        r = fault( r );
    end
    return r;
end -- Template()



-- Provide template access and expose URIutil table to require()

local p = {};

function p.coreISSN( frame )
    return Template( frame, "coreISSN" );
end
function p.formatISBN( frame )
    return Template( frame, "formatISBN" );
end
function p.formatISSN( frame )
    return Template( frame, "formatISSN" );
end
function p.formatLCCN( frame )
    return Template( frame, "formatLCCN" );
end
function p.isDNBvalid( frame )
    return Template( frame, "isDNBvalid" );
end
function p.isDOI( frame )
    return Template( frame, "isDOI" );
end
function p.isEscValid( frame )
    return Template( frame, "isEscValid" );
end
function p.isGTINvalid( frame )
    return Template( frame, "isGTINvalid" );
end
function p.isHandle( frame )
    return Template( frame, "isHandle" );
end
function p.isISBN( frame )
    return Template( frame, "isISBN" );
end
function p.isISBNvalid( frame )
    return Template( frame, "isISBNvalid" );
end
function p.isISSNvalid( frame )
    return Template( frame, "isISSNvalid" );
end
function p.isLCCN( frame )
    return Template( frame, "isLCCN" );
end
function p.linkDNBopac( frame )
    return Template( frame, "linkDNBopac" );
end
function p.linkDOI( frame )
    return Template( frame, "linkDOI" );
end
function p.linkDOI( frame )
    return Template( frame, "linkDOI" );
end
function p.linkHandle( frame )
    return Template( frame, "linkHandle" );
end
function p.linkISBN( frame )
    return Template( frame, "linkISBN" );
end
function p.linkISSN( frame )
    return Template( frame, "linkISSN" );
end
function p.linkLCCN( frame )
    return Template( frame, "linkLCCN" );
end
function p.linkURN( frame )
    return Template( frame, "linkURN" );
end
function p.mayDOI( frame )
    return Template( frame, "mayDOI" );
end
function p.mayHandle( frame )
    return Template( frame, "mayHandle" );
end
function p.mayISBN( frame )
    return Template( frame, "mayISBN" );
end
function p.mayISSN( frame )
    return Template( frame, "mayISSN" );
end
function p.mayLCCN( frame )
    return Template( frame, "mayLCCN" );
end
function p.mayURI( frame )
    return Template( frame, "mayURI" );
end
function p.mayURN( frame )
    return Template( frame, "mayURN" );
end
function p.plainISBN( frame )
    return Template( frame, "plainISBN" );
end
function p.targetISSN( frame )
    return Template( frame, "targetISSN" );
end
function p.uriDOI( frame )
    return Template( frame, "uriDOI" );
end
function p.uriHandle( frame )
    return Template( frame, "uriHandle" );
end
function p.uriURN( frame )
    return Template( frame, "uriURN" );
end
p.failsafe = function ( frame )
    local s = type( frame );
    local since;
    if s == "table" then
        since = frame.args[ 1 ];
    elseif s == "string" then
        since = frame;
    end
    if since then
        since = mw.text.trim( since );
        if since == "" then
            since = false;
        end
    end
    return URIutil.failsafe( since )  or  "";
end -- p.failsafe()
function p.URIutil( arg )
    local r;
    if arg then
        r = "";
    else
        r = URIutil;
    end
    return r;
end

return p;