Li Livres Dou Trésor und Modul:URIutil: Unterschied zwischen den Seiten

Aus AnthroWiki
(Unterschied zwischen Seiten)
imported>Odyssee
 
imported>Odyssee
(Die Seite wurde neu angelegt: „local URIutil = { suite = "URIutil", serial = "2017-10-04" }; --[=[ Utilities for URI etc. * coreISSN() * formatISBN() * formatISSN() * form…“)
 
Zeile 1: Zeile 1:
[[File:Latini - Livres dou Tresor, per Gioan Antonio & fratelli da Sabbio, ad instanza di Nicolo Garanta & Francesco da Salo libbrari & compagni - 4620219 968525 00005.tif|mini|300px|Frontispiz von Brunetto Latinis „Il Tesoro“ in der italienischen Übersetzung von 1528 [http://gutenberg.beic.it/webclient/DeliveryManager?pid=4620219]]]
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


'''Li livres dou Trésor''' ({{FrS}} „Schatzbücher“), kurz auch '''Trésor''' genannt,  ist eine von  dem [[italien]]ische [[Dichter]] und Gelehrten [[Brunetto Latini]] (* um 1220; † 1294) in [[Französische Sprache|französischer Sprache]] verfasste [[Enzyklopädie]], die einen auch für [[Laie]]n verständlichen Überblick über das Wissen seiner Zeit gibt und vor allem auch praktisches Wissen übermitteln sollte. Sie entstand in den Jahren 1260 – 1267 während Brunettos Exil in [[Wikipedia:Paris|Paris]]. Er schuf damit die erste bedeutsame volkssprachliche Enzyklopädie für das breite Volk. [[Dante Alighieri]] (1265-1321), der Brunettos Schüler war, erwähnt den ''Trésor'' in seiner „[[Göttliche Komödie|Göttlichen Komödie]]“, wo er Brunetto wie folgt zitiert: ''„Mein Schatz sei dir empfohlen. Ich leb’ in ihm noch – mehr begehr ich nicht.“'' (Inferno 15, 119)


[[Francesco da Barberino]] (1264-1348), ebenfalls ein Schüler Brunettos, hat später mit seinen «[[I Documenti d'Amore]]», die vermutlich zwischen 1309 und 1313 geschrieben wurden, ein einzigartiges [[Enzyklopädie|enzyklopädisches]] Kompendium verfasst, das eine Übersicht über die gesamten literarischen und wissenschaftlichen Erkenntnisse seiner Zeit gibt und damit Brunettos Tradition folgt.


1528 wurde die italienische Übersetzung von Brunettos Werk unter dem Titel „Il Tesor“ gedruckt. 1863 wurde das französische Original in Paris von ''François Adrien Polycarpe Chabeille'' (1796-1863) erneut aufgelegt.
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()


== Die drei Bücher des «Trésor» ==


''Li livres dou Trésor'' besteht aus drei Büchern, die sich jeweils in mehrere Teile und Kapitel untergliedern. Der Stil von Brunetto Latini ist regelmäßig, klar und schmucklos.


* Das erste Buch beschäftigt sich mit dem Ursprung und der Geschichte der Welt, wie sie im [[Altes Testament|Alten]] und [[Neues Testament|Neuen Testament]] beschrieben wird. Weiters wird über die ersten Herrschaftsformen berichtet, ebenso über die [[Astronomie]], die [[Geographie]] und [[Naturgeschichte]], hauptsächlich unter Berufung auf [[Aristoteles]] und [[Plinius der Ältere|Plinius]].
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()


* Das zweite Buch besteht aus zwei Teilen. Der erste Teil ist ein Auszug aus der [[Moral]]lehre des Aristoteles. Der zweite, wesentlich umfangreichere Teil gibt dazu einen ausführlichen Kommentar, der auf die praktische und logisch folgerichtige Anwendung der Moralprinzipien abzielt.


* Das dritte Buch behandelt speziell die Politik und Regierung der Stadt Florenz und beginnt mit einer langen Abhandlung über die [[Rhetorik]]. Brunetto stützt sich dabei vornehmlich auf [[Cicero]], dessen ''De Inventione'' er bereits zuvor übersetzt und weitläufig kommentiert hatte und als eine der wichtigsten Quellen der altitalienischen Sprache gilt. Daneben werden auch andere [[antike]] Autoren ausführlich zitiert. Der letzte Abschnitt ist einer der kürzesten, aber zweifellos der originellste und interessanteste von allen. Hier geht es nicht um die Politik im Allgemeinen, sondern speziell um die Regierungen der italienischen Republiken gegen Ende des elften Jahrhunderts. Dieser Teil enthält unter anderem eine sehr bemerkenswerte Passage über die [[Folter]]. Diese solle nur für Gewaltdelikte und nur bei entsprechend zuverlässigen Beweisen angewendet werden. Die Art und Weise der Befragung solle dabei zugunsten des Angeklagten geführt werden. Die letzten drei Kapitel zeigen eine merkwürdige Annäherung an einen Artikel der Verordnung von Saint Louis von 1254 zur Reform der Moral.


== Weblinks ==
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()


* [http://archive.org/details/lilivresdoutreso00latiuoft ''Li livres dou Trésor, par Brunetto Latini''], Paris 1863 auf: [http://archive.org archive.org]


[[Kategorie:Literatur]] [[Kategorie:Wissen]] [[Kategorie:Dante]]
 
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 = "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 = "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;

Version vom 13. November 2017, 15:28 Uhr

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 = "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 = "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;