Modul:URIutil: Unterschied zwischen den Versionen

Aus FreeWiki
Zur Navigation springen Zur Suche springen
te>Umherirrender
K (Schützte „Modul:URIutil“: Häufig eingebundenes Modul ([Bearbeiten=Nur angemeldete, nicht neue Benutzer] (unbeschränkt) [Verschieben=Nur Administratoren] (unbeschränkt)))
te>PerfektesChaos
(update)
Zeile 1: Zeile 1:
--[=[ URIutil 2013-06-16
+
--[=[ URIutil 2013-06-30
 
Utilities for URI etc.
 
Utilities for URI etc.
 
* coreISSN()
 
* coreISSN()
Zeile 15: Zeile 15:
 
* linkISBN()
 
* linkISBN()
 
* linkISSN()
 
* linkISSN()
 +
* mayDOI()
 
* mayISBN()
 
* mayISBN()
 
* mayISSN()
 
* mayISSN()
Zeile 23: Zeile 24:
 
* uriDOI()
 
* uriDOI()
 
* URIutil()
 
* URIutil()
 +
loadData: URIutil/config URIutil/isbn
 
]=]
 
]=]
  
Zeile 520: Zeile 522:
 
     --              false if not correct, bad data
 
     --              false if not correct, bad data
 
     local r = false;
 
     local r = false;
     local pre, sep, s = attempt:match( "%s*(%a%a?%a?)(/?)(%d%S+)%s*$" );
+
     local pre, sep, s = attempt:match( "^%s*(%a%a?%a?)(/?)(%d%S+)%s*$" );
     if pre then
+
     if pre and s then
 
         local year, serial;
 
         local year, serial;
 
         pre = pre:lower();
 
         pre = pre:lower();
Zeile 530: Zeile 532:
 
         else
 
         else
 
             year = false;
 
             year = false;
             serial = s;
+
             if tonumber( s ) then
 +
                serial = s;
 +
            end
 
         end
 
         end
 
         if year then
 
         if year then
Zeile 546: Zeile 550:
 
         if serial then
 
         if serial then
 
             r = { pre = pre, serial = serial };
 
             r = { pre = pre, serial = serial };
        end
+
            if year then
        if year then
+
                r.year = year;
            r.year = year;
+
            end
 
         end
 
         end
 
     end
 
     end
Zeile 684: Zeile 688:
 
     --    Returns  number of digits or 2011, if valid
 
     --    Returns  number of digits or 2011, if valid
 
     --              false if not correct, bad data or check digit wrong
 
     --              false if not correct, bad data or check digit wrong
     local j = attempt:find( "/", 5, true );
+
    local s = mw.text.trim( attempt );
 +
     local j = s:find( "/", 5, true );
 
     local r = false;
 
     local r = false;
     local s;
+
     local dnb;
 
     if j then
 
     if j then
 
         s = attempt:sub( 1,  j - 1 );
 
         s = attempt:sub( 1,  j - 1 );
    else
 
        s = attempt;
 
 
     end
 
     end
     local dnb = factory( s, true );
+
     j = s:find( "-", 2, true );
    if type( dnb ) == "table" then
+
    if j then
        j = attempt:find( "-", 3, true );
+
         if j > 3  and  j <= 8 then
         if j then
+
             if s:match( "^[0-9]+-[0-9xX]$" ) then
             j = attempt:match( "^ *([0-9]+)-[0-9xX] *$" );
+
                 dnb = factory( s, #s );
            if j then
 
                 j = ( #j < 8 );
 
 
             end
 
             end
 
         end
 
         end
 +
    elseif #s > 7 then
 +
        if s:match( "^[0-9]+$" ) then
 +
            dnb = factory( s, false );
 +
        end
 +
    end
 +
    if type( dnb ) == "table" then
 
         if j then
 
         if j then
 
             if DNB2011faith( dnb ) then
 
             if DNB2011faith( dnb ) then

Version vom 4. Juli 2013, 09:32 Uhr

--[=[ URIutil 2013-06-30 Utilities for URI etc.

  • coreISSN()
  • formatISBN()
  • formatISSN()
  • isDNBvalid()
  • isDOI()
  • isEscValid()
  • isGTINvalid()
  • isISBN()
  • isISBNvalid()
  • isISSNvalid()
  • isLCCN()
  • linkDOI()
  • linkISBN()
  • linkISSN()
  • mayDOI()
  • mayISBN()
  • mayISSN()
  • mayLCCN()
  • mayURI()
  • mayURN()
  • plainISBN()
  • uriDOI()
  • URIutil()

loadData: URIutil/config URIutil/isbn ]=]


-- table for export local URIutil = {};


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 format = function ( assigned, ahead, amount )

   -- Convert part of digit sequence into string
   -- Precondition:
   --     assigned  -- table; as of factory()
   --     ahead     -- index of first digit
   --     amount    -- number of digits to append
   -- Postcondition:
   --     Returns  string with digits and hyphens
   local i, k;
   local r = "";
   for i = ahead,  ahead + amount - 1 do
       k = assigned[ i ];
       if k == 10 then
           r = r .. "X";
       else
           r = r .. tostring( k );
       end
   end -- for i
   return r;

end -- format()


local DNBfaith = function ( assert )

   -- Compute DNB (also GND, ZDB) check digit and verify
   -- Precondition:
   --     assert  -- table; as of factory()
   --                .type  -- until 11 including check digit
   -- Postcondition:
   --     Returns  true: check digit matches
   local k = fair( assert )  %  11;
   return  ( k == assert[ assert.type ] );

end -- DNBfaith()


local DNB2011faith = function ( assert )

   -- Compute DNB check digit before 2012 and verify
   -- Precondition:
   --     assert  -- table; as of factory()
   --                .type  -- until 11 including check digit
   -- Postcondition:
   --     Returns  true: check digit matches
   local k = ( 11 - fair( assert ) )  %  11;
   return  ( k == assert[ assert.type ] );

end -- DNB2011faith()


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
       r = factory( attempt, 10 );
       if type( r ) == "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 lucky, def = pcall( mw.loadData, "Module:URIutil/isbn" );
   if type( def ) == "table"  then
       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
   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 + 1 ] > 4 then
           k = 3;
           if assigned[ j + 1 ] > 8 then
               k = 4;
               if assigned[ j + 2 ] > 8 then
                   k = false;    -- invalid
               end
           end
       end
   end
   if k then
       n = format( assigned, j, k );
       r = r .. n .. "-";
       j = j + k;
       n = ISBNfold( assigned,  m,  tonumber( n ),  j );
       if n > 0 then
           r = r .. format( assigned, j, n ) .. "-";
           j = j + n;
       end
   end
   r = r .. format( assigned,  j,  assigned.type - j - 1 );
   if assigned[ assigned.type ] == 10 then
       r = r .. "-X";
   else
       r = 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
       r = ( fair( assert )  ==  assert[ 8 ] );
   elseif assert.type == 13 then
       r = GTINfaith( assert );
   else
       r = false;
   end
   return r;

end -- ISSNfaith()


local ISSNformat = function ( assigned, achieve )

   -- Hyphen formatting
   -- 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 = format( assigned, 1, 4 ) .. "-" ..
               format( assigned, 5, 3 );
           x = assigned[ 8 ];
       elseif assigned.type == 13 then
           r = 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 = "977-" .. format( assigned, 1, 7 ) .. "-00-"
               .. GTINfair( assigned );
       elseif assigned.type == 13 then
           r = "977-" .. format( assigned, 4, 7 )
                      .. format( assigned, 10, 2 ) .. "-"
                      .. tostring( assigned[ 13 ] );
       end
   end
   return r;

end -- ISSNformat()


local function LCCNfactory( attempt )

   -- Retrieve segments of LCCN attempt
   -- Precondition:
   --     attempt  -- string with presumable LCCN
   -- Postcondition:
   --     Returns  table; success
   --              false if not correct, bad data
   local r = false;
   local pre, sep, s = attempt:match( "^%s*(%a%a?%a?)(/?)(%d%S+)%s*$" );
   if pre and s then
       local year, serial;
       pre = pre:lower();
       if sep == "/" then
           year, serial = s:match( "(%d+)/(%d.+)$" );
       elseif s:find( "-", 2, true ) then
           year, serial = s:match( "(%d+)%-(%d.+)$" );
       else
           year = false;
           if tonumber( s ) then
               serial = s;
           end
       end
       if year then
           if #year == 4 then
               local n = tonumber( year );
               if n < 2000 then
                   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
       end
   end
   return r;

end -- LCCNfactory()


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
   --              string with violation
   local lucky, r = pcall( mw.loadData, "Module:URIutil/config" );
   if type( r ) == "table" then
       r = r.urn;
       if type( r ) == "table" then
           local s = r.sns;
           if type( s ) == "string" then
               r = ":" .. area .. ":";
               if s:match( r ) then
                   s = "[^%w%(%)%+,%-%.:=@;%$_!%*'].*$";
                   r = acquire:match( s );
               else
                   r = ":" .. 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
           else
               r = "Module:URIutil/config.urn.sns";
           end
       else
           r = "Module:URIutil/config.urn";
       end
   end
   return r;

end -- URNnamespace()


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 )

   -- Format ISBN, if no hyphens present
   -- Precondition:
   --     attempt  -- string with presumable ISBN
   -- Postcondition:
   --     Returns   string with some hyphens, if not yet
   --               unmodified input if already hyphens or wrong
   local r;
   local isbn = ISBNfactory( attempt );
   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.isDNBvalid( attempt )

   -- Is this DNB (also GND, ZDB) formally correct (check digit)?
   -- Precondition:
   --     attempt  -- string with any presumable DNB code
   -- 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, #s );
           end
       end
   elseif #s > 7 then
       if s:match( "^[0-9]+$" ) then
           dnb = factory( s, false );
       end
   end
   if type( dnb ) == "table" then
       if j then
           if DNB2011faith( dnb ) then
               r = 2011;
           end
       else
           if DNBfaith( dnb ) then
               r = dnb.type;
           end
       end
   end
   return r;

end -- URIutil.isDNBvalid()


function URIutil.isDOI( attempt )

   -- Is this a syntactically correct DOI?
   -- Precondition:
   --     attempt  -- string with presumable DOI code
   -- Postcondition:
   --     Returns  number of organization, if valid
   --              false if not correct, bad character or syntax
   local r = false;
   local k, s = attempt:match( "^ *10%.([1-9][0-9]+)/(.+) *$" );
   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.isISBN( attempt )

   -- Is this a syntactically correct ISBN?
   -- 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
   --              false if not an ISBN
   local r;
   local isbn = ISBNfactory( attempt );
   if type( isbn ) == "table" then
       r = isbn.type;
   else
       r = false;
   end
   return r;

end -- URIutil.isISBN()


function URIutil.isISBNvalid( attempt )

   -- Is this ISBN formally correct (check digit)?
   -- 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
   --              false if not correct, bad data or check digit wrong
   local r    = false;
   local isbn = ISBNfactory( attempt );
   if type( isbn ) == "table" then
       if ISBNfaith( isbn ) then
           r = isbn.type;
       end
   end
   return r;

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 )

   -- Is this a syntactically correct LCCN?
   -- Precondition:
   --     attempt  -- string with presumable LCCN
   -- Postcondition:
   --     Returns  string with LCCN formatted aa9999-99999999
   --              false if not correct, bad data
   local r    = false;
   local lccn = LCCNfactory( attempt );
   if lccn then
       r = lccn.pre;
       if lccn.year then
           r = r .. lccn.year .. "-";
       end
       r = r .. lccn.serial;
   end
   return r;

end -- URIutil.isLCCN()


function URIutil.linkDOI( attempt )

   -- Retrieve bracketed titled external link on DOI resolver
   -- Precondition:
   --     attempt  -- string with presumable DOI
   local r = URIutil.isDOI( attempt );
   if r then
       r = mw.text.decode( mw.text.trim( attempt ),  "PATH" );
       r = string.format( "[%s %s]",
                          "http://dx.doi.org/" .. mw.uri.encode( r ),
                          mw.text.encode( r, "<>&%]" ) );
   end
   return r;

end -- URIutil.linkDOI()


function URIutil.linkISBN( attempt, allow, abbr, adhere )

   -- Retrieve bracketed titled wikilink on booksources page with "ISBN"
   -- Precondition:
   --     attempt  -- string with presumable ISBN
   --     allow    -- true: permit invalid check digit
   --     abbr     -- true or string: link ISBN abbreviation
   --     adhere   -- true: use    else: use simple space;
   -- Postcondition:
   --     Returns  link
   local lapsus;
   local source = mw.text.trim( attempt );
   local r      = "";
   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 = true;
       r      = r .. source;
   end
   r = r .. "";
   if lapsus then
       r = ""
           .. r
           .. "(?!?!)";
   end
   if adhere then
       r = " " .. r;
   else
       r = " " .. r;
   end
   if abbr then
       local s;
       if type( abbr ) == "string" then
           s = abbr;
       else
           s = "International Standard Book Number";
       end
       r = string.format( "ISBN%s", s, r );
   else
       r = "ISBN" .. r;
   end
   return r;

end -- URIutil.linkISBN()


function URIutil.linkISSN( attempt, allow, abbr, adhere )

   -- Retrieve bracketed titled external link on ISSN DB with "ISSN"
   -- Precondition:
   --     attempt  -- string with presumable ISSN
   --     allow    -- true: permit invalid check digit
   --     abbr     -- true or string: link ISSN abbreviation
   --     adhere   -- true: use    else: use simple space;
   -- Postcondition:
   --     Returns  link
   local issn   = ISSNfactory( attempt );
   local lapsus;
   local r;
   if type( issn ) == "table" then
       local lucky, cnf = pcall( mw.loadData, "Module:URIutil/config" );
       local lenient;
       if type( allow ) == "string" then
           lenient = ( allow ~= "0" );
       else
           lenient = allow;
       end
       if lenient then
           lapsus = false;
       else
           lapsus = ( not ISSNfaith( issn ) );
       end
       r = ISSNformat( issn, issn.type );
       if type( cnf ) == "table" then
           if type( cnf.issn ) == "string" then
               r = string.format( "[%s %s]",
                                  cnf.issn:gsub( "%$1", r ),
                                  r );
           end
       end
   else
       lapsus = true;
       r      = attempt;
   end
   if lapsus then
       r = ""
           .. r
           .. "(?!?!)";
   end
   if adhere then
       r = " " .. r;
   else
       r = " " .. r;
   end
   if abbr then
       local s;
       if type( abbr ) == "string" then
           s = abbr;
       else
           s = "International Standard Serial Number";
       end
       r = string.format( "ISSN%s", s, r );
   else
       r = "ISSN" .. r;
   end
   return r;

end -- URIutil.linkISSN()


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.mayISBN( attempt )

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

end -- URIutil.mayISBN()


function URIutil.mayISSN( attempt )

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

end -- URIutil.mayISSN()


function URIutil.mayLCCN( attempt )

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

end -- URIutil.mayLCCN()


function URIutil.mayURI( attempt, ascii )

   -- Is this a syntactically correct URI, or empty?
   -- Precondition:
   --     attempt  -- string with presumable URI
   --     ascii    -- limit to ASCII (no IRI)
   -- Postcondition:
   --     Returns  false if no problem
   --              string with violation
   local r = URIutil.isEscValid( attempt );
   if not r then
       local s = mw.text.trim( attempt );
       r = s:match( "%s(.+)$" );
       if not r then
           r = s:match( "#(.*)$" );
           if r then
               r = "#" .. r;
           elseif ascii then
               local pat = mw.ustring.char( 128,45,255 );
               pat = "[" .. pat .. "].+$";
               r   = mw.ustring.match( s, pat );
           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
   -- 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 ns, id = s:match( "^(%w+):(.+)$" );
           if ns then
               r = URNnamespace( ns: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.uriDOI( attempt, anything, abbr )

   -- Retrieve linked URI on DOI resolver
   -- Precondition:
   --     attempt   -- string with presumable DOI
   --     anything  -- intentionally dummy parameter
   --     abbr      -- true or string: link doi: abbreviation
   local r = URIutil.linkDOI( attempt );
   if r then
       if abbr then
           local s;
           if type( abbr ) == "string" then
               s = abbr;
           else
               s = "Digital Object Identifier";
           end
           r = string.format( "doi:%s", s, r );
       else
           r = "doi:" .. r;
       end
   end
   return r;

end -- URIutil.uriDOI()


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 ) );
   if lucky then
       if r then
           r = tostring( r );
       else
           r = "";
       end
   else
       r = "" .. 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.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.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.linkDOI( frame )

   return Template( frame, "linkDOI" );

end function p.linkISBN( frame )

   return Template( frame, "linkISBN" );

end function p.linkISSN( frame )

   return Template( frame, "linkISSN" );

end function p.mayDOI( frame )

   return Template( frame, "mayDOI" );

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.uriDOI( frame )

   return Template( frame, "uriDOI" );

end function p.URIutil()

   return URIutil;

end

return p;