モジュール:URIutil

モジュールの解説
local URIutil = { suite  = "URIutil",                  serial = "2022-06-04",                  item   = 19644443 };--[=[Utilities for URI etc.* coreISSN()* formatISBN()* formatISSN()* formatLCCN()* isDNBvalid()* isDOI()* isEscValid()* isGTINvalid()* isHandle()* isISBN()* isISBNvalid()* isISSNvalid()* isLCCN()* linkDNBopac()* linkDOI()* linkHandle()* linkISBN()* linkISSN()* linkLCCN()* linkPMID()* linkURN()* mayDOI()* mayHandle()* mayISBN()* mayISSN()* mayLCCN()* mayURI()* mayURN()* plainISBN()* targetISSN()* uriDOI()* uriHandle()* uriURN()* failsafe()* URIutil()loadData: URIutil/config URIutil/isbn URIutil/urn]=]local Failsafe = URIutil;local CurrentPageName;local factory = function ( attempt, allowX )    -- Retrieve plain digits of attempt    -- Precondition:    --     attempt  -- string; with digits (+xX) and hyphens, not trimmed    --     allowX   -- number; of (last) position for permitted xX    --                 boolean; xX at last position permitted    -- Postcondition:    --     Returns   table; success    --                      [1]...[8]/[10]...[13]  -- digits 0...9    --                                                10 at last position    --                      .hyphens  -- number of hyphens    --                      .type     -- number of digits    --               number; no string or bad length or data    --                        0  -- no string    --                       >0  -- unexpected char at position (trimmed)    local r;    if type( attempt ) == "string" then        local c, i;        local j = 0;        local k = 1;        local s = mw.text.trim( attempt );        local n = mw.ustring.len( s );        r = { hyphens = 0 };        for i = 1, n do            c = mw.ustring.codepoint( s,  i,  i + 1 );            if c >= 48  and  c <= 57 then                j      = j + 1;                r[ j ] = c - 48;                k      = false;            elseif c == 45 then    -- hyphen                if i > 1  and  i < n then                    r.hyphens = r.hyphens + 1;                    k         = i;                else                    r = j;                    break;                end            elseif c == 88  or  c == 120 then    -- X x                j = j + 1;                if allowX  and  i == n then                    if allowX == true  or  allowX == j then                        r[ j ] = 10;                    else                        r = j;                    end                else                    r = j;                end                break;            else                r = j;                break;            end        end -- for i        if type( r ) == "table" then            r.type = j;        end    else        r = 0;    end    return r;end -- factory()local faculty = function ( ask, auto )    -- Evaluate possible string as boolean signal, if brief    -- Precondition:    --    ask   -- trimmed string or nil or other    --    auto  -- fallback value if nil    -- Postcondition:    --     Returns appropriate value, or ask    local r;    if type( ask ) == "string" then        if ask == "1"  or  ask == "" then            r = true;        elseif ask == "0"  or  ask == "-" then            r = false;        else            r = ask;        end    elseif ask == nil then        r = auto;    else        r = ask;    end    return r;end -- faculty()local fair = function ( assert )    -- Compute check digit (11 minus modulo 11) for descending factor    -- Precondition:    --     assert  -- table; as of factory()    --                .type  -- number of digits including check digit    -- Postcondition:    --     Returns checksum    local i;    local n = assert.type;    local k = n;    local r = 0;    for i = 1,  n - 1 do        r = r  +  k * assert[ i ];        k = k - 1;    end -- for i    return  ( 11  -  r % 11 );end -- fair()local fault = function ( alert )    -- Format error message by class=error    -- Parameter:    --     alert  -- string, error message    -- Returns:    --     string, HTML span    return tostring( mw.html.create( "span" )                            :addClass( "error" )                            :wikitext( alert ) );end -- fault()local fetch = function ( acquire )    -- Load data submodule    -- Precondition:    --     acquire  -- string, one of    --                  "config"    --                  "isbn"    --                  "urn"    -- Postcondition:    --     Returns any table    local r;    if URIutil.data then        r = URIutil.data[ acquire ];    else        URIutil.data = { };    end    if not r then        local lucky, storage;        storage = "Module:URIutil/" .. acquire;        lucky, r = pcall( mw.loadData, storage );        if not lucky then            if not URIutil.suited  and               type( URIutil.item ) == "number" then                local suited = string.format( "Q%d", URIutil.item );                URIutil.suited = mw.wikibase.getSitelink( suited )                                 or  true;            end            if type( URIutil.suited ) == "string" then                storage = string.format( "%s/%s",                                         URIutil.suited, acquire );                lucky, r = pcall( mw.loadData, storage );            end        end        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 ~= "-"  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 e, s;            if achieve then                s = LCCNformat( lccn, achieve );                if s:find( "-", 2, true ) then                    e = mw.html.create( "span" )                               :css( "white-space", "nowrap" );                end                        else                s = r;            end                        s = string.format( "[https://lccn.loc.gov/%s %s]", r, s );            if e then                r = tostring( e:wikitext( s ) );            else                r = s;            end                    end    else        r = attempt;    end    return r;end -- LCCNforward()local URNnamespace = function ( area, acquire )    -- Are these parts of a correct URN?    -- Precondition:    --     area     -- string with lowercase namespace    --     acquire  -- string with identification    -- Postcondition:    --     Returns  false if no problem detected    --              string with violation    local r;    if area == "urn" then        r = "urn:";    else        local s = fetch( "urn" ).sns;        if type( s ) == "string" then            r = string.format( ":%s:", area );            if s:match( r ) then                s = "[^%w%(%)%+,%-%.:=/@;%$_!%*'].*$";                r = acquire:match( s );            else                r = string.format( "&#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    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 )  or  s:match( "^%w$" ) 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        local e = mw.html.create( "span" )                         :addClass( "uri-handle" )                         :css( "white-space", "nowrap" );        local s;        s, r = attempt:match( "^%s*(10%.[1-9][0-9]+/)(.+)%s*$" );        r = mw.text.decode( r, "PATH" );        r = string.format( "[%s/%s%s %s%s]",                           "https://doi.org",                           s,                           mw.uri.encode( r ),                           s,                           mw.text.encode( r, "<>&%]" ) );        r = tostring( e:wikitext( r ) );    else        r = flop( alert );    end    return r;end -- URIutil.linkDOI()function URIutil.linkHandle( attempt, any1, any2, any3, alert )    -- Retrieve bracketed titled external link on handle resolver    -- Precondition:    --     attempt  -- string with presumable handle    --     any1     -- intentionally dummy parameter    --     any2     -- intentionally dummy parameter    --     any3     -- intentionally dummy parameter    --     alert    -- string with title of maintenance category, or nil    -- Postcondition:    --     Returns  external link, or false    local r = URIutil.isHandle( attempt );    if r then        local e = mw.html.create( "span" )                         :addClass( "uri-handle" )                         :css( "white-space", "nowrap" );        r = mw.text.decode( mw.text.trim( attempt ),  "PATH" );        r = string.format( "[%s%s %s]",                           "https://hdl.handle.net/",                           mw.uri.encode( r ),                           mw.text.encode( r, "<>&%]" ) );        r = tostring( e:wikitext( r ) );    else        r = flop( alert );    end    return r;end -- URIutil.linkHandle()function URIutil.linkISBN( attempt, allow, abbr, adhere, alert )    -- Retrieve bracketed titled wikilink on booksources page with "ISBN"    -- Precondition:    --     attempt  -- string with presumable ISBN    --     allow    -- true: permit invalid check digit or digit count    --     abbr     -- true or string: link ISBN abbreviation    --     adhere   -- true: use &nbsp;  else: use simple space    --     alert    -- string with title of maintenance category, or nil    -- Postcondition:    --     Returns  link    local e      = mw.html.create( "span" );    local source = mw.text.trim( attempt );    local r      = string.format( "[[Special:Booksources/%s|", source );    local isbn   = ISBNfactory( source );    local lapsus;    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 .. "]]";    e:css( "white-space", "nowrap" );    if lapsus then        r = r .. fault( "(?!?!)" );        e:addClass( "invalid-ISBN" )         :wikitext( r );        r = tostring( e );        if alert then            r = r .. flop( alert );        end    else        e:wikitext( r );        r = tostring( e );    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 == "-"               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 == "-"               or  s == "" then                s = false;            end        else            s = "International Standard Serial Number";        end        if s  and  s ~= fullPageName() then            if s == "ISSN" then                s = "[[ISSN]]";            else                s = string.format( "[[%s|ISSN]]", s );            end        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.linkPMID( attempt, any, abbr, adhere, alert )    -- Retrieve external PMID link with "PMID"    -- Precondition:    --     attempt  -- string or number with presumable PMID    --     any      -- intentionally dummy parameter    --     abbr     -- true: link PMID abbreviation    --     adhere   -- true: use &nbsp;  else: use simple space;    --     alert    -- string with title of maintenance category, or nil    -- Postcondition:    --     Returns  link    local s = type( attempt );    local legal, r;    if s == "string" then        r = mw.text.trim( attempt );        if r == "" then            r = "PMID";            s = "empty";        end    elseif s == "number" then        r = tostring( attempt );        s = "string";    else        r = "PMID";    end    if s == "string" then        legal = r:match( "^[1-9]%d*$" );        if legal then            local cnf = fetch( "config" );            if cnf then                s = cnf.pmid;                if type( s ) == "string"  and                   s ~= "-"  and                   s ~= "" then                    local sep;                    if s:find( "$1", 5, true ) then                        s = s:gsub( "%$1", r );                    else                        s = s .. r;                    end                    r = string.format( "[%s %s]", s, r );                    if adhere then                        sep = "&#160;";                    else                        sep = " ";                    end                    if abbr then                        s = cnf.supportPMID;                        if type( s ) == "string" and                           s ~= "-"  and                           s ~= ""  and                           s ~= fullPageName() then                            if s == "PMID" then                                s = "[[PMID]]";                            else                                s = string.format( "[[%s|PMID]]", s );                            end                        else                            s = "PMID";                        end                    else                        s = "PMID";                    end                    r = string.format( "%s%s%s", s, sep, r );                else                    cnf = false;                end            end            if not cnf then                r = string.format( "PMID %s", r );            end        else            r = string.format( "PMID %s", r );        end    end    if not legal then        local e = mw.html.create( "span" )                         :addClass( "invalid-PMID" )                         :wikitext( r );        r = tostring( e ) .. fault( "(?!?!)" );        if alert then            r = r .. flop( alert );        end    end    return r;end -- URIutil.linkPMID()function URIutil.linkURN( attempt, alter, any1, any2, alert, at, alone )    -- Retrieve bracketed titled external URN link    -- Precondition:    --     attempt  -- string, with presumable URN, starting with "urn:"    --     alter    -- alternative handler    --     any1     -- intentionally dummy parameter    --     any2     -- intentionally dummy parameter    --     alert    -- string, with title of maintenance category, or nil    --     at       -- fragment, or nil    --     alone    -- true, if link text not preceded by "urn:"    -- Postcondition:    --     Returns    --         1.    linked ID, or plain string if bad URN    --         2.    true, if to be preceded by "urn:"    local r2 = true;    local r;    if URIutil.mayURN( attempt ) then        local e = mw.html.create( "span" )                         :addClass( "invalid-URN" );        local serial  = attempt;        local suffix  = flop( alert );        if serial:sub( 1, 4 ):lower() == "urn:" then            serial = serial:sub( 5 );        end        e:wikitext( serial );        r = tostring( e ) .. fault( "(?!?!)" );        if suffix then            r = r .. suffix;        end    else        local s = attempt:match( "^%s*[uU][rR][nN]:(%S+)%s*$" );        if s then            local space, sign = s:match( "^(%w+):(.+)$" );            if space then                local defs = fetch( "urn" );                if type( defs ) == "table" then                    local resolver = defs.resolver;                    space    = space:lower();                    resolver = resolver[ space ];                    r2       = ( resolver ~= true );                    if type( resolver ) == "table" then                        r, sign = URNresolve( resolver, sign, alter );                        s = string.format( "%s:%s", space, sign );                        if r then                            r = r:gsub( "%$1",  "urn:" .. s );                            if at then                                r = string.format( "%s#%s", r, at );                            end                            if alone then                                r2 = true;                            else                                s = "urn:" .. s;                            end                            r = string.format( "[%s %s]", r, s );                        end                    elseif r2 then                        if type( defs.sns ) == "string" then                            s = string.format( ":%s:", space );                            if not defs.sns:find( s, 1, true ) then                                s = false;                            end                        else                            s = false;                        end                        if s then                            r = string.format( "%s:%s", space, sign );                        else                            r = string.format( "%s&#58;%s",                                               space, sign );                        end                        r2 = not alone;                    else                        s = "link" .. space:upper();                        r = URIutil[ s ]( sign, alter, nil, nil, alert );                    end                else                    r =  fault( "Bad structure in Module:URIutil/urn" );                end            end        end    end    if not r then        if alert then            r = flop( alert )  or  "";            if attempt then                r = attempt .. r;            end        else            r = mw.text.trim( attempt );        end        r2 = false;    end    return r, r2;end -- URIutil.linkURN()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    local r;    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        else            s = mw.text.trim( attempt );            if s == "" then                r = false;            elseif s:match( "^https?://" ) then                r = "http:";            else                r = "urn:";            end        end    end    return r;end -- URIutil.mayURN()function URIutil.plainISBN( attempt )    -- Format ISBN as digits (and 'X') only string    -- Precondition:    --     attempt  -- string with presumable ISBN    -- Postcondition:    --     Returns  string with 10 or 13 chars    --              false if not empty and not an ISBN    local r;    local isbn = ISBNfactory( attempt );    if type( isbn ) == "table" then        r = ISBNflat( isbn );    else        r = false;    end    return r;end -- URIutil.plainISBN()function URIutil.targetISSN( attempt, allow, any1, any2, alert )    -- Retrieve bracketed titled external link on ISSN DB without "ISSN"    -- Precondition:    --     attempt  -- string with presumable ISSN    --     allow    -- true: permit invalid check digit    --     any1     -- intentionally dummy parameter    --     any2     -- intentionally dummy parameter    --     alert    -- string with title of maintenance category, or nil    -- Postcondition:    --     Returns  link    local e = mw.html.create( "span" );    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            local s = cnf.issn            if s:find( "$1", 5, true ) then                s = s:gsub( "%$1", r );            else                s = s .. r;            end            r = string.format( "[%s %s]", s, r );        end    else        lapsus = true;        r      = attempt;    end    e:css( "white-space", "nowrap" )     :wikitext( r );    if lapsus then        e:addClass( "invalid-ISSN" );        r = tostring( e ) .. fault( "(?!?!)" );        if alert then            r = r .. flop( alert );        end    else        r = tostring( e );    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            if s ~= fullPageName() then                 r = string.format( "[[%s|doi]]:%s", s, r );            else                r = "doi:" .. r;            end        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    -- Postcondition:    --     Returns  combined linked URI, or false    local r = URIutil.linkHandle( attempt );    if r then        local s;        if type( abbr ) == "string" then            s = abbr;        else            local cnf = fetch( "config" );            if cnf then                s = cnf.supportHandle;                if type( s ) ~= "string"  or                   s == "-"  or                   s == ""  or                   s == fullPageName() then                    s = false;                end            end        end        if s then            r = string.format( "[[%s|hdl]]:%s", s, r );        else            r = "hdl:" .. r;        end    end    return r;end -- URIutil.uriHandle()function URIutil.uriURN( attempt, anything, alter, alert, at )    -- Retrieve linked URI on URN resolver    -- Precondition:    --     attempt   -- string with presumable URN, starting with "urn:"    --     anything  -- intentionally dummy parameter    --     alter     -- string with alternative handler, or nil    --     alert     -- string with title of maintenance category, or nil    --     at        -- fragment, or nil    -- Postcondition:    --     Returns  link, or plain string if bad URN    local r, l =        URIutil.linkURN( attempt, alter, false, false, alert, at, true );    if l then        local s = fetch( "config" ).supportURN;        if s then            if type( s ) ~= "string"               or  s == "-"               or  s == "" then                s = false;            end        else            s = "Uniform Resource Name";        end        if s  and  s ~= fullPageName() then            r = string.format( "[[%s|urn]]:%s", s, r );        else            r = "urn:" .. r;        end    end    return r;end -- URIutil.uriURN()Failsafe.failsafe = function ( atleast )    -- Retrieve versioning and check for compliance    -- Precondition:    --     atleast  -- string, with required version    --                         or wikidata|item|~|@ or false    -- Postcondition:    --     Returns  string  -- with queried version/item, also if problem    --              false   -- if appropriate    -- 2020-08-17    local since = atleast    local last    = ( since == "~" )    local linked  = ( since == "@" )    local link    = ( since == "item" )    local r    if last  or  link  or  linked  or  since == "wikidata" then        local item = Failsafe.item        since = false        if type( item ) == "number"  and  item > 0 then            local suited = string.format( "Q%d", item )            if link then                r = suited            else                local entity = mw.wikibase.getEntity( suited )                if type( entity ) == "table" then                    local seek = Failsafe.serialProperty or "P348"                    local vsn  = entity:formatPropertyValues( seek )                    if type( vsn ) == "table"  and                       type( vsn.value ) == "string"  and                       vsn.value ~= "" then                        if last  and  vsn.value == Failsafe.serial then                            r = false                        elseif linked then                            if mw.title.getCurrentTitle().prefixedText                               ==  mw.wikibase.getSitelink( suited ) then                                r = false                            else                                r = suited                            end                        else                            r = vsn.value                        end                    end                end            end        end    end    if type( r ) == "nil" then        if not since  or  since <= Failsafe.serial then            r = Failsafe.serial        else            r = false        end    end    return rend -- Failsafe.failsafe()local Template = function ( frame, action )    -- Retrieve library result for template access    -- Precondition:    --     frame   -- object    --     action  -- string; function name    -- Postcondition:    --     Returns  appropriate string, or error message (development)    local lucky, r = pcall( URIutil[ action ],                            frame.args[ 1 ] or "",                            frame.args[ 2 ],                            faculty( frame.args.link, true ),                            faculty( frame.args.nbsp, true ),                            frame.args.cat,                            frame.args.fragment );    if lucky then        if r then            r = tostring( r );        else            r = "";        end    else        r = fault( r );    end    return r;end -- Template()-- Provide template access and expose URIutil table to require()local p = {};function p.coreISSN( frame )    return Template( frame, "coreISSN" );endfunction p.formatISBN( frame )    return Template( frame, "formatISBN" );endfunction p.formatISSN( frame )    return Template( frame, "formatISSN" );endfunction p.formatLCCN( frame )    return Template( frame, "formatLCCN" );endfunction p.isDNBvalid( frame )    return Template( frame, "isDNBvalid" );endfunction p.isDOI( frame )    return Template( frame, "isDOI" );endfunction p.isEscValid( frame )    return Template( frame, "isEscValid" );endfunction p.isGTINvalid( frame )    return Template( frame, "isGTINvalid" );endfunction p.isHandle( frame )    return Template( frame, "isHandle" );endfunction p.isISBN( frame )    return Template( frame, "isISBN" );endfunction p.isISBNvalid( frame )    return Template( frame, "isISBNvalid" );endfunction p.isISSNvalid( frame )    return Template( frame, "isISSNvalid" );endfunction p.isLCCN( frame )    return Template( frame, "isLCCN" );endfunction p.linkDNBopac( frame )    return Template( frame, "linkDNBopac" );endfunction p.linkDOI( frame )    return Template( frame, "linkDOI" );endfunction p.linkHandle( frame )    return Template( frame, "linkHandle" );endfunction p.linkISBN( frame )    return Template( frame, "linkISBN" );endfunction p.linkISSN( frame )    return Template( frame, "linkISSN" );endfunction p.linkLCCN( frame )    return Template( frame, "linkLCCN" );endfunction p.linkPMID( frame )    return Template( frame, "linkPMID" );endfunction p.linkURN( frame )    return Template( frame, "linkURN" );endfunction p.mayDOI( frame )    return Template( frame, "mayDOI" );endfunction p.mayHandle( frame )    return Template( frame, "mayHandle" );endfunction p.mayISBN( frame )    return Template( frame, "mayISBN" );endfunction p.mayISSN( frame )    return Template( frame, "mayISSN" );endfunction p.mayLCCN( frame )    return Template( frame, "mayLCCN" );endfunction p.mayURI( frame )    return Template( frame, "mayURI" );endfunction p.mayURN( frame )    return Template( frame, "mayURN" );endfunction p.plainISBN( frame )    return Template( frame, "plainISBN" );endfunction p.targetISSN( frame )    return Template( frame, "targetISSN" );endfunction p.uriDOI( frame )    return Template( frame, "uriDOI" );endfunction p.uriHandle( frame )    return Template( frame, "uriHandle" );endfunction p.uriURN( frame )    return Template( frame, "uriURN" );endp.failsafe = function ( frame )    local s = type( frame );    local since;    if s == "table" then        since = frame.args[ 1 ];    elseif s == "string" then        since = frame;    end    if since then        since = mw.text.trim( since );        if since == "" then            since = false;        end    end    return Failsafe.failsafe( since )  or  "";end -- p.failsafe()function p.URIutil( arg )    local r;    if arg then        r = "";    else        r = URIutil;    end    return r;endsetmetatable( p,  { __call = function ( func, ... )                                 setmetatable( p, nil )                                 return Failsafe                             end } )return p;