Modul:WLink

Aus FreeWiki
Zur Navigation springen Zur Suche springen

local WLink = { suite = "WLink",

               serial = "2019-11-16",
               item   = 19363224,
               globals = { URLutil = 10859193 } };

--[=[ ansiPercent() formatURL() getArticleBase() getBaseTitle() getEscapedTitle() getExtension() getFile() getFragment() getLanguage() getLinktextProblem() getNamespace() getNamespaced() getPlain() getProject() getTarget() getTalkPage() getTargetPage() getTitle() getWeblink() getWikilink() isBracketedLink() isBracketedURL() isCategorization() isExternalLink() isInterlanguage() isInterwiki() isMedia() isTalkPage() isTitledLink() isValidLink() isValidLinktext() isWikilink() pageLink() pageTarget() wikilink() failsafe() ]=]


-- local globals local URLutil = false; local Failsafe = WLink; local GlobalMod = WLink;


local htmlInline = { b = true,

                    bdi    = true,
                    bdo    = true,
                    big    = true,
                    code   = true,
                    em     = true,
                    i      = true,
                    kbd    = true,
                    s      = true,
                    samp   = true,
                    small  = true,
                    span   = true,
                    strong = true,
                    style  = true,
                    sub    = true,
                    sup    = true,
                    tt     = true,
                    var    = true };


local foreignModule = function ( access, advanced, append, alt, alert )

   -- Fetch global module
   -- Precondition:
   --     access    -- string, with name of base module
   --     advanced  -- true, for require(); else mw.loadData()
   --     append    -- string, with subpage part, if any; or false
   --     alt       -- number, of wikidata item of root; or false
   --     alert     -- true, for throwing error on data problem
   -- Postcondition:
   --     Returns whatever, probably table
   -- 2019-10-29
   local storage = access;
   local finer = function ()
                     if append then
                         storage = string.format( "%s/%s",
                                                  storage,
                                                  append );
                     end
                 end
   local fun, lucky, r, suited;
   if advanced then
       fun = require;
   else
       fun = mw.loadData;
   end
   GlobalMod.globalModules = GlobalMod.globalModules or { };
   suited = GlobalMod.globalModules[ access ];
   if not suited then
       finer();
       lucky, r = pcall( fun,  "Module:" .. storage );
   end
   if not lucky then
       if not suited  and
          type( alt ) == "number"  and
          alt > 0 then
           suited = string.format( "Q%d", alt );
           suited = mw.wikibase.getSitelink( suited );
           GlobalMod.globalModules[ access ] = suited or true;
       end
       if type( suited ) == "string" then
           storage = suited;
           finer();
           lucky, r = pcall( fun, storage );
       end
       if not lucky and alert then
           error( "Missing or invalid page: " .. storage, 0 );
       end
   end
   return r;

end -- foreignModule()


local utilURL = function ()

   -- Attach URLutil library module
   -- Postcondition:
   --     Returns  table, with URLutil library
   --     Throws error, if not available
   if not URLutil then
       local util = foreignModule( "URLutil",
                                   true,
                                   false,
                                   WLink.globals.URLutil );
       if type( util ) == "table" then
           URLutil = util.URLutil();
       else
           util = "library URLutil invalid";
       end
       if type( URLutil ) ~= "table" then
           error( util, 0 );
       end
   end
   return URLutil;

end -- utilURL()


local cleanWikilink = function ( access )

   -- Refine wikilink spacing and decode
   -- Precondition:
   --     access  -- string, with presumable link
   -- Postcondition:
   --     Returns  string, with pretty target
   local r = access:gsub( "_",        " " )
                   :gsub( " ",   " " )
                   :gsub( " ", " " )
                   :gsub( " ",   " " )
                   :gsub( " ",  " " )
                   :gsub( "%s+",      " " );
   r = mw.text.decode( r );
   return r;

end -- cleanWikilink()


local contentExtlink = function ( attempt )

   -- Retrieve span of external link between brackets
   -- Precondition:
   --     attempt  -- string, with presumable link
   --                         the first char is expected to be "["
   -- Postcondition:
   --     Returns  string, number, number
   --                  string including whitespace
   --                  number with index of relevant "["
   --                  number with index after relevant "]"
   --              false if nothing found
   local r1 = false;
   local r2 = false;
   local r3 = attempt:find( "]", 2, true );
   if r3 then
       local s = attempt:sub( 2,  r3 - 1 );
       local i = s:find( "[", 1, true );
       if i then
           r1 = s:sub( i + 1 );
           r2 = i;
       else
           r1 = s;
           r2 = 1;
       end
   else
       r3 = false;
   end
   return r1, r2, r3;

end -- contentExtlink()


local contentWikilink = function ( attempt )

   -- Retrieve span of wikilink between brackets
   -- Precondition:
   --     attempt  -- string, with presumable link
   --                        the first two chars are expected to be "[["
   -- Postcondition:
   --     Returns  string, number, number
   --                  string including whitespace
   --                  number with index of relevant "[["
   --                  number with index after relevant "]]"
   --              false if nothing found
   local r1 = false;
   local r2 = false;
   local r3 = attempt:find( "]]", 3, true );
   if r3 then
       local s = attempt:sub( 3,  r3 - 1 );
       local i = s:find( "[[", 1, true );
       if i then
           r1 = s:sub( i + 2 );
           r2 = i;
       else
           r1 = s;
           r2 = 1;
       end
   end
   return r1, r2, r3;

end -- contentWikilink()


local extractExtlink = function ( attempt )

   -- Retrieve external link
   -- Precondition:
   --     attempt  -- string, with presumable link
   --                        the first char is expected to be "["
   -- Postcondition:
   --     Returns  string, string
   --                  first with target and title
   --                  second result false if not titled
   --              false if nothing found
   local r1 = false;
   local r2 = false;
   local s = contentExtlink( attempt );
   if s then
       local i = s:find( "%s", 1 );
       if i then
           r1 = s:sub( 1,  i - 1 );
           r2 = mw.text.trim( s:sub( i + 1 ) );
           if r2 == "" then
               r2 = false;
           end
       else
           r1 = s;
       end
       if r1 then
           r1 = mw.text.trim( r1 );
           if r1 == ""  or
              not utilURL().isResourceURL( r1 ) then
               r1 = false;
           end
       end
       if not r1 then
           r2 = false;
       end
   end
   return r1, r2;

end -- extractExtlink()


local extractWikilink = function ( attempt )

   -- Retrieve wikilink
   -- Precondition:
   --     attempt  -- string, with presumable link
   --                        the first two chars are expected to be "[["
   -- Postcondition:
   --     Returns  string, string
   --                  first with target
   --                  second result title, or false if not piped
   --              false if nothing found
   local r1 = false;
   local r2 = false;
   local s = contentWikilink( attempt );
   if s then
       local i = s:find( "|", 1, true );
       if i then
           r1 = s:sub( 1,  i - 1 );
           r2 = s:sub( i + 1 );
       else
           r1 = s;
       end
       r1 = mw.text.trim( r1 );
       if r1 == "" then
           r1 = false;
       else
           r1 = cleanWikilink( r1 );
       end
   end
   return r1, r2;

end -- extractWikilink()


local farming = function ( already )

   -- Retrieve wikifarm project information
   -- Precondition:
   --     already  -- table, with wikilink components
   -- Postcondition:
   --     Returns  table, with wikilink components extended
   local r = already;
   if not r.project then
       local codes  = { mediawiki   = "mw",
                        wikibooks   = "b",
                        wikidata    = "d",
                        wikinews    = "n",
                        wikipedia   = "w",
                        wikiquote   = "q",
                        wikisource  = "s",
                        wikiversity = "v",
                        wikivoyage  = "voy",
                        wiktionary  = "wikt" };
       local server = mw.site.server:gsub( "([/.])m%.",
                                            "%1" )
                                     :gsub( "%.beta%.wmflabs%.org$",
                                            ".org" );
       local site   = server:match( "[/.](%l+)%.org$" );
       r.project = codes[ site ];
       if r.project then
           if not r.lang  and
              r.project ~= "mw"  and  r.project ~= "d" then
               r.lang = server:match( "//(%l+)%." );
           end
       else
           site = server:match( "//(%l+)%.wikimedia%.org$" );
           if site == "commons"  or  site == "meta" then
               r.project = site;
           end
       end
   end
   if r.project  and  r.ns  and
      ( r.project == "commons"  or
        r.project == "d"        or
        r.project == "meta"     or
        r.project == "mw" ) then
       r.language = true;
   end
   return r;

end -- farming()


local prefix = function ( ask )

   -- Interprete prefix of language or project type
   -- Precondition:
   --     ask    -- string, with presumable prefix
   -- Postcondition:
   --     Returns  string,string or nil
   --                     first  string one of "lead", "lang", "project"
   --                     second string is formatted value
   --                       type is one of "lead", "lang", "project"
   --              nil if nothing found
   local r1, r2;
   local prefixes = { b           = true,
                      c           = "commons",
                      d           = true,
                      commons     = true,
                      m           = "meta",
                      mediawiki   = "mw",
                      mw          = true,
                      meta        = true,
                      n           = true,
                      q           = true,
                      s           = true,
                      simple      = false,
                      v           = true,
                      voy         = true,
                      w           = true,
                      wikibooks   = "b",
                      wikidata    = "d",
                      wikinews    = "n",
                      wikipedia   = "w",
                      wikiquote   = "q",
                      wikisource  = "s",
                      wikiversity = "v",
                      wikivoyage  = "voy",
                      wikt        = true,
                      wiktionary  = "wikt"
                    };
   local s = mw.text.trim( ask );
   if s ~= "" then
       local p;
       s = s:lower();
       p = prefixes[ s ];
       if p == true then
           r1 = "project";
           r2 = s;
       elseif p then
           r1 = "project";
           r2 = p;
       elseif p == false then
           r1 = "lang";
           r2 = s;
       elseif s:match( "^%l%l%l?$" )
              and  mw.language.isSupportedLanguage( s ) then
           r1 = "lang";
           r2 = s;
       end
   end
   return r1, r2;

end -- prefix()


local target = function ( attempt, lonely )

   -- Retrieve first target (wikilink or URL), or entire string
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   --     lonely   -- remove fragment, if true
   -- Postcondition:
   --     Returns  string, number
   --                  string, with detected link target, or entire
   --                  number, with number of brackets, if found, or 2
   local r1, r2 = WLink.getTarget( attempt );
   if not r1 then
       r1 = mw.text.trim( attempt );
       r2 = 2;
   end
   if lonely then
       local i = r1:find( "#", 1, true );
       if i == 1 then
           r1 = "";
       elseif i then
           r1 = r1:sub( 1, i - 1 );
       end
   end
   return r1, r2;

end -- target()


function WLink.ansiPercent( attempt, alter )

   -- Convert string by ANSI encoding rather than UTF-8 encoding
   -- Precondition:
   --     attempt  -- string, with presumable ANSI characters
   --     alter    -- string or nil, to use for spaces instead of %20
   -- Postcondition:
   --     Returns  string, encoded
   local k, s;
   local r = attempt;
   if alter then
       r = r:gsub( " ", alter );
   end
   for i = mw.ustring.len( r ), 1, -1 do
       k = mw.ustring.codepoint( r, i, i );
       if k <= 32  or  k > 126 then
           if k > 255 then
               s = mw.ustring.sub( r, i, i );
               if k > 2047 then
                   s = string.format( "%%%2X%%%2X%%%2X",
                                      s:byte( 1, 1 ),
                                      s:byte( 2, 2 ),
                                      s:byte( 3, 3 ) );
               else
                   s = string.format( "%%%2X%%%2X",
                                      s:byte( 1, 1 ),
                                      s:byte( 2, 2 ) );
               end
           else
               s = string.format( "%%%02X", k );
           end
           r = string.format( "%s%s%s",
                              mw.ustring.sub( r,  1,  i - 1 ),
                              s,
                              mw.ustring.sub( r,  i + 1 ) );
       end
   end -- for --i
   return r;

end -- WLink.ansiPercent()


function WLink.formatURL( adjust, assure )

   -- Create bracketed link, if not yet
   -- Precondition:
   --     adjust  -- string, with URL or domain/path or bracketed link
   --     assure  -- boolean, true for secure HTTP
   -- Postcondition:
   --     Returns  string, with bracketed link
   --              false on invalid format
   local r;
   if type( adjust ) == "string" then
       if WLink.isBracketedLink( adjust ) then
           r = adjust;
       else
           local url = mw.text.trim( adjust );
           local host;
           utilURL();
           host = URLutil.getHost( adjust );
           if not host then
               url = "://" .. adjust;
               if assure then
                   url = "s" .. url;
               end
               url  = "http" .. url;
               host = URLutil.getHost( url );
           end
           if host then
               local path = URLutil.getRelativePath( url );
               local show;
               if path == "/" then
                   if not url:match( "/$" ) then
                       url = url .. "/";
                   end
                   show = host;
               else
                   local i = path:find( "#" );
                   if i then
                       path = path:sub( 1,  i - 1 );
                   end
                   show = host .. path;
               end
               r = string.format( "[%s %s]", url, show );
           else
               r = adjust;
           end
       end
   else
       r = false;
   end
   return r;

end -- WLink.formatURL()


function WLink.getArticleBase( attempt )

   -- Retrieve generic article title, no fragment nor brackets
   -- Precondition:
   --     attempt  -- string, with wikilink or page title
   --                         current page title, if missing
   -- Postcondition:
   --     Returns  string, with identified lemma, or all
   --              false on invalid format
   local r;
   if attempt then
       local m;
       r, m = target( attempt, true );
       if m ~= 2 then
           r = false;
       end
   else
       r = mw.title.getCurrentTitle().text;
   end
   if r then
       local sub = r:match( "^(.*%S) *%(.+%)$" );
       if sub then
           r = sub;
       end
   end
   return r;

end -- WLink.getArticleBase()


function WLink.getBaseTitle( attempt )

   -- Retrieve last segment in subpage, no fragment
   -- Precondition:
   --     attempt  -- string, with wikilink or page title
   -- Postcondition:
   --     Returns  string, with identified segment, or all
   local r;
   local s, m = target( attempt, true );
   if m == 2 then
       local sub = s:match( "/([^/]+)$" );
       if sub then
           r = sub;
       else
           r = s;
       end
   else
       r = false;
   end
   return r;

end -- WLink.getBaseTitle()


function WLink.getEscapedTitle( attempt )

   -- Retrieve escaped link title
   -- Precondition:
   --     attempt  -- string, with presumable link title
   -- Postcondition:
   --     Returns  string, with suitable link title
   local s = mw.text.trim( attempt );
   return s:gsub( "\n", " " )
           :gsub( "%[", "[" )
           :gsub( "%]", "]" )
           :gsub( "|",  "|" );

end -- WLink.getEscapedTitle()


function WLink.getExtension( attempt )

   -- Retrieve media extension
   -- Precondition:
   --     attempt  -- string, with wikilink (media link) or page title
   --                         if URL, PDF may be detected
   -- Postcondition:
   --     Returns  string, with detected downcased media type
   --              false if no extension found
   local r = false;
   local s, m = target( attempt );
   if m == 2 then
       s = s:match( "%.(%a+)$" );
       if s then
           r = s:lower();
       end
   elseif s:upper():match( "[%./](PDF)%W?" ) then
       r = "pdf";
   end
   return r;

end -- WLink.getExtension()


function WLink.getFile( attempt )

   -- Retrieve media page identifier
   -- Precondition:
   --     attempt  -- string, with wikilink (media link) or page title
   -- Postcondition:
   --     Returns  string, with detected file title
   --                      no namespace nor project
   --              false if no file found
   local r = false;
   local s, m = target( attempt );
   if m == 2 then
       local slow    = ":" .. s:lower();
       local find = function ( a )
                        local seek = string.format( ":%s:().+%%.%%a+$",
                                                    a:lower() );
                        local join = slow:find( seek );
                        local ret;
                        if join then
                            ret = s:sub( join + #a + 1 );
                        end
                        return ret;
                    end;
       r = find( "file" );
       if not r then
           local trsl = mw.site.namespaces[ 6 ];
           r = find( trsl.name );
           if not r then
               trsl = trsl.aliases;
               for k, v in pairs( trsl ) do
                   r = find( v );
                   if r then
                       break; -- for k, v
                   end
               end -- for k, v
           end
       end
   end
   return r;

end -- WLink.getFile()


function WLink.getFragment( attempt )

   -- Retrieve fragment
   -- Precondition:
   --     attempt  -- string, with presumable fragment
   -- Postcondition:
   --     Returns  string, with detected fragment
   --              false if no address found
   local r = false;
   local s, m = target( attempt );
   if s then
       local i = s:find( "#", 1, true );
       if i then
           if i > 1 then
               s = s:sub( i - 1 );
               i = 2;
           end
           if s:find( "&#", 1, true ) then
               s = mw.text.decode( s );
               i = s:find( "#", 1, true );
               if not i then
                  s = "";
                  i = 0;
               end
           end
           s = s:sub( i + 1 );
           r = mw.text.trim( s );
           if r == "" then
               r = false;
           elseif m == 2 then
               r = r:gsub( "%.(%x%x)", "%%%1" )
                    :gsub( "_", " " );
               r = mw.uri.decode( r, "PATH" );
           end
       end
   end
   return r;

end -- WLink.getFragment()


function WLink.getLanguage( attempt )

   -- Retrieve language project identifier
   -- Precondition:
   --     attempt  -- string, with wikilink or page title
   -- Postcondition:
   --     Returns  string, with detected downcased language identifier
   --              false if no project language found
   local r = false;
   local s, m = WLink.getTarget( attempt );
   if m == 2 then
       local w = WLink.wikilink( s );
       if w  and  w.lang then
           r = w.lang;
       end
   end
   return r;

end -- WLink.getLanguage()


function WLink.getLinktextProblem( attempt )

   -- Which problem has this presumable link text?
   -- Precondition:
   --     attempt  -- string, with presumable linktext
   -- Postcondition:
   --     Returns  string, with error message, or false
   local r;
   if attempt:find( "]", 1, true ) then
       r = "]";
   elseif mw.text.unstripNoWiki( attempt ) ~= attempt then
       r = "<nowiki>";
   elseif attempt:find( "\n", 1, true ) then
       r = "\n";
   elseif mw.text.unstrip( attempt ) ~= attempt then
       if not WLink.stripREF then
           WLink.stripREF = string.format( "%c%c%c%c%s%c%c%c%c",
                                           127, 39, 34, 96,
                                           "UNIQ%-+ref%-%x+%-QINU",
                                           96, 34, 39, 127 );
       end
       if mw.ustring.find( attempt, WLink.stripREF ) then
           r = "<ref>";
       end
   end
   if not r then
       local i = attempt:find( "<", 1, true );
       if i then
           local s = mw.ustring.lower( attempt:sub( i ) );
           local sign = true;
           local skip;
           while sign  and  not r do
               skip, sign = s:match( "^([^<]*< *)(%l[%l%d]*)[ /]*.*>" );
               if sign then
                   if htmlInline[ sign ] then
                       i = skip:len() + sign:len() + 1;
                       s = s:sub( i );
                   else
                       r = string.format( "<%s>", sign );
                   end
               end
           end    -- while sign and not r
       end
       if not r then
           local s = attempt .. " ";
           if s:find( "ISBN ", 1, true ) then
               r = s:match( "(ISBN %d[%-%d]+[%dxX])%W" );
           end
           if not r then
               if s:find( "PMID ", 1, true ) then
                   r = s:match( "(PMID [1-9]%d*)%W" );
               end
               if not r then
                   if s:find( "RFC ", 1, true ) then
                       r = s:match( "(RFC [1-9]%d?%d?%d?)%W" );
                   end
               end
           end
       end
   end
   return r or false;

end -- WLink.getLinktextProblem()


function WLink.getNamespace( attempt )

   -- Retrieve namespace number
   -- Precondition:
   --     attempt  -- string, with wikilink or page title
   -- Postcondition:
   --     Returns  number, of detected namespace
   --              false if no namespace found
   local r = false;
   local s, m = WLink.getTarget( attempt );
   if m == 2 then
       local w = WLink.wikilink( s );
       if w  and  not w.lang  and  not w.project  and  w.ns then
           r = w.ns;
       end
   end
   return r;

end -- WLink.getNamespace()


function WLink.getNamespaced( area, attempt )

   -- Retrieve page in namespace
   -- Precondition:
   --     area     -- string or number, with some namespace spec
   --     attempt  -- string, with wikilink or page title or page name
   -- Postcondition:
   --     Returns  page prefixed by namespace,
   --              false if invalid
   local r = false;
   local s = type( area );
   local room;
   if s == "string" then
       room = mw.site.namespaces[ tonumber( area )  or  area ];
   elseif s == "number" then
       room = mw.site.namespaces[ area ];
   end
   if room then
       local m;
       s, m = WLink.getTarget( attempt );
       if not s then
           s = attempt;
       elseif m ~= 2 then
           s = false;
       end
       if s then
           local w = WLink.wikilink( s );
           if w  and  not w.lang  and  not w.project  and
              ( not w.ns  or  w.ns == room.id ) then
               r = string.format( "%s:%s",
                                  room.name, w.title );
           end
       end
   end
   return r;

end -- WLink.getNamespaced()


function WLink.getPlain( attempt )

   -- Retrieve text with all links replaced by link titles
   -- Precondition:
   --     attempt  -- string, with wikitext
   -- Postcondition:
   --     Returns  string, with modified wikitext without links
   local r = attempt;
   local i = 1;
   local j, k, n, lean, s, shift, span, space, suffix;
   while ( true ) do
       j = r:find( "[", i, true );
       if j then
           suffix = r:sub( j );
           i      = j + 1;
           lean   = ( r:byte( i, i ) == 91 );
           if lean then
               s, k, n = contentWikilink( suffix );
           else
               s, k, n = contentExtlink( suffix );
           end
           if s then
               if k > 1 then
                   n      = n - k;
                   i      = j + k + 1;
                   j      = i - 1;
                   suffix = r:sub( j );
               end
               if lean then
                   s, shift = extractWikilink( suffix );
                   if s then
                       space = s:match( "^([^:]+):" );
                       if space then
                           space = mw.site.namespaces[ space ];
                           if space then
                               space = space.id;
                           end
                       end
                       if space == 6  or  space == 14 then
                           shift = "";
                       elseif not shift then
                           shift = s;
                       end
                   else
                       s     = "";
                       shift = "";
                   end
               else
                   span, shift = extractExtlink( suffix );
                   if span then
                       if not shift then
                           shift = "";
                       end
                   else
                       shift = string.format( "[%s]", s );
                   end
                   i = i - 1;
               end
               if j > 1 then
                   s = r:sub( 1, j - 1 );
               else
                   s = "";
               end
               r = string.format( "%s%s%s",
                                  s,  shift,  r:sub( n + i ) );
               i = i + #shift;
           else
               break; -- while true
           end
       else
           break; -- while true
       end
   end    -- while true
   return r;

end -- WLink.getPlain()


function WLink.getProject( attempt )

   -- Retrieve wikifarm project identifier
   -- Precondition:
   --     attempt  -- string, with wikilink or page title
   -- Postcondition:
   --     Returns  string, with detected downcased project identifier
   --              false if no project identifier found
   local r = false;
   local s, m = WLink.getTarget( attempt );
   if m == 2 then
       local w = WLink.wikilink( s );
       if w  and  w.project then
           r = w.project;
       end
   end
   return r;

end -- WLink.getProject()


function WLink.getTalkPage( attempt )

   -- Retrieve talk page name for attempt, or that page name itself
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   -- Postcondition:
   --     Returns  string  or  false
   local r = false;
   local s, m = WLink.getTarget( attempt );
   if m ~= 2  and  attempt then
       s = mw.text.trim( attempt );
   end
   if s  and  s ~= "" then
       local w = mw.title.new( s );
       if w then
           w = w.talkPageTitle;
           if w then
               r = w.prefixedText;
           end
       end
   end
   return r;

end -- WLink.getTalkPage()


function WLink.getTarget( attempt )

   -- Retrieve first target (wikilink or URL)
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   -- Postcondition:
   --     Returns  string, number
   --                  string, with first detected link target
   --                  number, with number of brackets, if found
   --              false if nothing found
   local r1 = false;
   local r2 = false;
   local i  = attempt:find( "[", 1, true );
   if i then
       local m;
       r1 = attempt:sub( i );
       if r1:byte( 2, 2 ) == 91 then
           m  = 2;
           r1 = extractWikilink( r1 );
       else
           m  = 1;
           r1 = extractExtlink( r1 );
       end
       if r1 then
           r2 = m;
       end
   else
       r1 = attempt:match( "%A?([hf]t?tps?://%S+)%s?" );
       if r1 then
           if utilURL().isResourceURL( r1 ) then
               r2 = 0;
           else
               r1 = false;
           end
       else
           r1 = false;
       end
   end
   return r1, r2;

end -- WLink.getTarget()


function WLink.getTargetPage( attempt )

   -- Retrieve first target page (page name or URL of page)
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   -- Postcondition:
   --     Returns  string, with first detected linked page
   --              false if nothing found
   local r1, r2 = WLink.getTarget( attempt );
   if r1 then
       local i = r1:find( "#", 1, true );
       if i then
           if i == 1 then
               r1 = false;
           else
               r1 = mw.text.trim( r1:sub( 1,  i - 1 ) );
           end
       end
   end
   return r1, r2;

end -- WLink.getTargetPage()


function WLink.getTitle( attempt )

   -- Retrieve first link title (wikilink or URL), or wikilink target
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   -- Postcondition:
   --     Returns  string, with first detected link target
   --              false if nothing found
   local r = false;
   local i = attempt:find( "[", 1, true );
   if i then
       local s1, s2;
       r = attempt:sub( i );
       if r:byte( 2, 2 ) == 91 then
           s1, s2 = extractWikilink( r );
           if s2 then
               r = s2;
           else
               r = s1;
           end
       else
           s1, r = extractExtlink( r );
       end
   end
   return r;

end -- WLink.getTitle()


function WLink.getWeblink( attempt, anURLutil )

   -- Retrieve bracketed link from resource URL
   -- Precondition:
   --     attempt    -- string, with URL, or something different
   --     anURLutil  -- library module object, or nil
   -- Postcondition:
   --     Returns  string, with first detected link target
   --              false if nothing found
   local second = ".ac.co.go.gv.or.";
   local r;
   if type( anURLutil ) == "table" then
       URLutil = anURLutil;
   else
       utilURL();
   end
   if URLutil.isResourceURL( attempt ) then
       local site    = URLutil.getAuthority( attempt );
       local service = attempt;
       local show;
       if #attempt == #site then
          site = site .. "/";
       end
       show = URLutil.getTop3domain( "//" .. site );
       if show then
           local scan   = "[%./](%a[%a%%%-]*%a)(%.%l%l%.)(%a+)$";
           local search = "." .. show;
           local s1, s2, s3 = search:match( scan );
           if s2 then
               if not second:find( s2, 1, true ) then
                   show = string.format( "%s%s",  s2:sub( 2 ),  s3 );
               end
           else
               show = false;
           end
       end
       if not show then
           show = URLutil.getTop2domain( "//" .. site );
           if not show then
               show = URLutil.getHost( "//" .. site );
           end
       end
       if not service:match( "^[a-z:]*//.+/" ) then
           service = service .. "/";
       end
       r = string.format( "[%s %s]", service, show );
   else
       r = attempt;
   end
   return r;

end -- WLink.getWeblink()


function WLink.getWikilink( attempt, appear )

   -- Retrieve bracketed link from text
   -- Precondition:
   --     attempt  -- string, with current target, or plain
   --     appear   -- string, with link title, or nil
   -- Postcondition:
   --     Returns  string, with first detected link target
   --              false if nothing found
   local r = WLink.pageTarget( attempt );
   if r then
       if appear then
           local show = WLink.getEscapedTitle( appear );
           if show ~= r  and  show ~= "" then
               r = string.format( "%s|%s", r, show );
           end
       end
       r = string.format( "%s", r );
   end
   return r;

end -- WLink.getWikilink()


function WLink.isBracketedLink( attempt )

   -- Does attempt match a bracketed link?
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   -- Postcondition:
   --     Returns  boolean
   local r = false;
   local i = attempt:find( "[", 1, true );
   if i then
       local s = attempt:sub( i );
       if s:byte( 2, 2 ) == 91 then
           s = extractWikilink( s );
       else
           s = extractExtlink( s );
       end
       if s then
           r = true;
       end
   end
   return r;

end -- WLink.isBracketedLink()


function WLink.isBracketedURL( attempt )

   -- Does attempt match a bracketed URL?
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   -- Postcondition:
   --     Returns  boolean
   local s, r = WLink.getTarget( attempt );
   return ( r == 1 );

end -- WLink.isBracketedURL()


function WLink.isCategorization( attempt )

   -- Does attempt match a categorization?
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   -- Postcondition:
   --     Returns  boolean
   local r = false;
   local s, m = WLink.getTarget( attempt );
   if m == 2 then
       local w = WLink.wikilink( s );
       if w  and  w.ns == 14
             and  not ( w.lead or w.lang or w.project )
             and  w.title ~= "" then
           r = true;
       end
   end
   return r;

end -- WLink.isCategorization()


function WLink.isExternalLink( attempt )

   -- Does attempt match an external link?
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   -- Postcondition:
   --     Returns  boolean
   local s, r = WLink.getTarget( attempt );
   if r then
       r = ( r < 2 );
   end
   return r;

end -- WLink.isExternalLink()


function WLink.isInterlanguage( attempt )

   -- Does attempt match an interlanguage link?
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   -- Postcondition:
   --     Returns  boolean
   local r = false;
   local s, m = WLink.getTarget( attempt );
   if m == 2 then
       local w = WLink.wikilink( s );
       if w and w.lang and not w.project and not w.lead
            and  w.title ~= "" then
           r = true;
       end
   end
   return r;

end -- WLink.isInterlanguage()


function WLink.isInterwiki( attempt )

   -- Does attempt match an interwiki link within wikifarm?
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   -- Postcondition:
   --     Returns  boolean
   local r = false;
   local s, m = WLink.getTarget( attempt );
   if m == 2 then
       local w = WLink.wikilink( s );
       if w  and  ( w.lang or w.project )  and  w.title ~= "" then
           r = true;
       end
   end
   return r;

end -- WLink.isInterwiki()


function WLink.isMedia( attempt )

   -- Does attempt match a media translusion?
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   -- Postcondition:
   --     Returns  boolean
   local r = false;
   local s, m = WLink.getTarget( attempt );
   if m == 2 then
       local w = WLink.wikilink( s );
       if w  and  w.ns == 6
          and  not ( w.lead or w.lang or w.project )
          and  w.title ~= ""
          and  WLink.getExtension( w.title ) then
           r = true;
       end
   end
   return r;

end -- WLink.isMedia()


function WLink.isTalkPage( attempt )

   -- Does attempt describe a talk page?
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   -- Postcondition:
   --     Returns  boolean
   local r = false;
   local s, m = WLink.getTarget( attempt );
   if m ~= 2  and  attempt then
       s = mw.text.trim( attempt );
   end
   if s  and  s ~= "" then
       local w = mw.title.new( s );
       if w then
           r = w.isTalkPage;
       end
   end
   return r;

end -- WLink.isTalkPage()


function WLink.isTitledLink( attempt )

   -- Does attempt match a titled link?
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   -- Postcondition:
   --     Returns  boolean
   local r = false;
   local i = attempt:find( "[", 1, true );
   if i then
       local c, n;
       local s = attempt:sub( i );
       if s:byte( 2, 2 ) == 91 then
           n = s:find( "%]%]", 5 );
           c = "|";
       else
           n = s:find( "%]", 8 );
           c = "%s%S";
       end
       if n then
           local m = s:find( c, 2 );
           if m  and  m + 1 < n  and  WLink.getTarget( attempt ) then
               r = true;
           end
       end
   end
   return r;

end -- WLink.isTitledLink()


function WLink.isValidLink( attempt )

   -- Does attempt match a link?
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   -- Postcondition:
   --     Returns  boolean
   local u, r = WLink.getTarget( attempt );
   if r then
       if r < 2 then
           if u:find( "", 1, true ) then
               r = false;
           else
               r = true;
           end
       else
           r = true;
       end
   end
   return r;

end -- WLink.isValidLink()


function WLink.isValidLinktext( attempt, allow )

   -- Is attempt a plain inline text?
   -- Precondition:
   --     attempt  -- string, with presumable linktext
   --     allow    -- boolean or nil, if multiline permitted
   -- Postcondition:
   --     Returns  boolean
   local s;
   if allow  and  s:find( "\n", 1, true ) then
       s = attempt:gsub( "\n", " " );
   else
       s = attempt;
   end
   return  not WLink.getLinktextProblem( s );

end -- WLink.isValidLinktext()


function WLink.isWikilink( attempt )

   -- Does attempt match a wikilink?
   -- Precondition:
   --     attempt  -- string, with presumable link somewhere
   -- Postcondition:
   --     Returns  boolean
   local s, m = WLink.getTarget( attempt );
   return ( m == 2 );

end -- WLink.isWikilink()


function WLink.pageLink( attempt, appear, assure )

   -- Create safely standardized wikilink target of a page
   --     attempt  -- string, with presumable link
   --     appear   -- string or true or nil, with link title
   --     assure   -- string or nil, shield against wiki template syntax
   --                 "URL" or "WIKI"
   -- Postcondition:
   --     Returns  string with link target
   local r = WLink.pageTarget( attempt, assure );
   if appear then
       local show;
       if type( appear ) == "string" then
           show = appear;
       else
           show = attempt;
       end
       r = string.format( "%s|%s", r, show );
   end
   return r;

end -- WLink.pageLink()


function WLink.pageTarget( attempt, assure )

   -- Create standardized wikilink target of a page
   -- Precondition:
   --     attempt  -- string, with presumable link
   --                         expected to be enclosed in "" ""
   --                         else wikilink
   --                 table, of assignments with { type, value }
   --                        type is one of "lead",
   --                             "project", "lang",
   --                             "ns", "space", "title"
   --     assure   -- string or nil, shield against wiki template syntax
   --                 "URL" or "WIKI"
   -- Postcondition:
   --     Returns  string with link target
   local p = type( attempt );
   local s = assure;
   local r;
   if p == "string" then
       p = WLink.wikilink( attempt );
   elseif p == "table" then
       p = attempt;
   else
       p = false;
   end
   if p then
       local site  = p.project;
       local slang = p.lang;
       local lead;
       if p.title:sub( 1, 1 ) == "#" then
           p.title = mw.title.getCurrentTitle().text + p.title;
       end
       if p.ns then
           if not slang then
               p = farming( p );
           end
           if p.lang  and
              p.lang ~= mw.language.getContentLanguage():getCode() then
               p.language = true;
           end
           if p.language then
               p.space = mw.site.namespaces[ p.ns ].canonicalName;
           end
           lead = ( p.ns == 6  or  p.ns == 14 );
       end
       if slang then
           lead = true;
       end
       if s == "WIKI" then
           if not site   and
              ( lead  or
                ( not p.space  and
                  p.title and p.title:match( "^[*;]" ) ) ) then
               p     = farming( p );
               site  = p.project;
               slang = p.lang;
           end
           s = false;
       end
       if site then
           r = site .. ":";
       elseif lead then
           r = ":";
       else
           r = "";
       end
       if slang then
           r = string.format( "%s%s:", r, slang );
       end
       if p.space then
           r = string.format( "%s%s:", r, p.space );
       end
       if p.title then
           r = r .. p.title;
       end
       if r == "" then
           r = false;
       end
   end
   if not r then
       p = { lang = mw.language.getContentLanguage():getCode() };
       if s == "WIKI" then
           r = WLink.pageTarget( p, s );
           s = false;
       else
           r = string.format( ":%s:", p.lang );
       end
   end
   if s == "URL"  and  r:match( "^[*#;:]" ) then
       r = mw.uri.encode( r:sub( 1, 1 ) )  ..  r:sub( 2 );
   end
   return r;

end -- WLink.pageTarget()


function WLink.wikilink( attempt )

   -- Retrieve wikilink components
   -- Precondition:
   --     attempt  -- string, with presumable link
   --                         expected to be enclosed in "" ""
   --                         else wikilink
   -- Postcondition:
   --     Returns  table or false
   --              table of assignments with { type, value }
   --                       type is one of "lead",
   --                          "project", "lang",
   --                          "ns", "space", "title"
   --              false if nothing found
   local s = contentWikilink( attempt or "" );
   local got, i, n, r;
   if not s then
       s = attempt;
   end
   if s:find( "%", 1, true ) then
       s = mw.uri.decode( s, "PATH" );
   end
   i = s:find( "|", 1, true );
   if i then
       s = s:sub( 1, i - 1 );
   end
   got = mw.text.split( s, ":" );
   n   = table.maxn( got );
   if n == 1 then
       r = { title = mw.text.trim( s ) };
   else
       local j, k, o, v;
       r = { title = "" };
       while ( got[ 1 ] == "" ) do
           r.lead = true;
           table.remove( got, 1 );
           n = n - 1;
       end    -- while  got[ 1 ] == ""
       if n > 4 then
           k = 4;
       elseif n > 1 then
           k = n - 1;
       else
           k = 1;
       end
       j = k;
       for i = 1, j do
           s = mw.text.trim( got[ i ] );
           if s ~= "" then
               o = mw.site.namespaces[ s ];
               if o then
                   r.ns    = o.id;
                   r.space = o.name;
                   k = i + 1;
                   j = i - 1;
                   break; -- for i
               end
           end
       end -- for i
       for i = 1, j do
           o, v = prefix( got[ i ] );
           if o then
               if r[ o ] then
                   k = i;
                   break; -- for i
               else
                   if i >= k then
                       k = i + 1;
                   end
                   r[ o ] = v;
               end
           else
               if i == 1  and  r.ns then
                   r.ns    = false;
                   r.space = false;
               end
               k = i;
               break; -- for i
           end
       end -- for i
       if k > 0 then
           for i = k, n do
               r.title = r.title .. got[ i ];
               if i < n then
                   r.title = r.title .. ":";
               end
           end -- for i
       end
   end
   r.title = cleanWikilink( r.title );
   if r.lead and
      ( r.project  or
        ( not r.lang  and  r.ns ~= 6  and  r.ns ~= 14 ) ) then
       r.lead = false;
   end
   return r;

end -- WLink.wikilink()


Failsafe.failsafe = function ( atleast )

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

end -- Failsafe.failsafe()


local function Template( frame, action, leave, lone )

   -- Run actual code from template transclusion
   -- Precondition:
   --     frame   -- object
   --     action  -- string, with function name
   --     leave   -- true: keep whitespace around
   --     lone    -- true: permit call without parameters
   -- Postcondition:
   --     Return string; might be error message
   local lucky = true;
   local s = false;
   local r = false;
   local safe, space;
   for k, v in pairs( frame.args ) do
       if k == 1 then
           if leave then
               s = v;
           else
               s = mw.text.trim( v );
           end
       elseif ( k == 2   and
                ( action == "getNamespaced"  or
                  action == "getWikilink"  or
                  action == "pageLink" ) )    or
              ( k == "space"  and  action == "ansiPercent" ) then
           v = mw.text.trim( v );
           if v ~= "" then
               space = v;
           end
       elseif k == "safe"  and  action == "pageLink" then
           v = mw.text.trim( v );
           if v ~= "" then
               safe = v;
           end
       elseif k == "lines"  and  action == "isValidLinktext" then
           space = ( k == "1" );
       elseif k ~= "template" then
           lucky = false;
           if r then
               r = r .. "|";
           else
               r = "Unknown parameter: ";
           end
           r = string.format( "%s%s=", r, k );
       end
   end -- for k, v
   if lucky then
       if s or lone then
           lucky, r = pcall( WLink[ action ],  s,  space,  safe );
       else
           r = "Parameter missing";
           lucky = false;
       end
   end
   if lucky then
       if type( r ) == "boolean" then
           if r then
               r = "1";
           else
               r = "";
           end
       end
   else
       local e = mw.html.create( "span" );
       r = tostring( e:addClass( "error" )
                      :wikitext( r ) );
   end
   return r;

end -- Template()


-- Export local p = { };

p.ansiPercent = function ( frame )

   return Template( frame, "ansiPercent" );

end p.formatURL = function ( frame )

   return Template( frame, "formatURL" );

end p.getArticleBase = function ( frame )

   return Template( frame, "getArticleBase", false, true );

end p.getBaseTitle = function ( frame )

   return Template( frame, "getBaseTitle" );

end p.getEscapedTitle = function ( frame )

   return Template( frame, "getEscapedTitle" );

end p.getExtension = function ( frame )

   return Template( frame, "getExtension" );

end p.getFile = function ( frame )

   return Template( frame, "getFile" );

end p.getFragment = function ( frame )

   return Template( frame, "getFragment" );

end p.getInterwiki = function ( frame )

   return Template( frame, "getInterwiki" );

end p.getLanguage = function ( frame )

   return Template( frame, "getLanguage" );

end p.getLinktextProblem = function ( frame )

   return Template( frame, "getLinktextProblem" );

end p.getNamespace = function ( frame )

   return tostring( Template( frame, "getNamespace" ) );

end p.getNamespaced = function ( frame )

   return tostring( Template( frame, "getNamespaced" ) );

end p.getPlain = function ( frame )

   return Template( frame, "getPlain" );

end p.getProject = function ( frame )

   return Template( frame, "getProject" );

end p.getTalkPage = function ( frame )

   return Template( frame, "getTalkPage" );

end p.getTarget = function ( frame )

   return Template( frame, "getTarget" );

end p.getTargetPage = function ( frame )

   return Template( frame, "getTargetPage" );

end p.getTitle = function ( frame )

   return Template( frame, "getTitle" );

end p.getWeblink = function ( frame )

   return Template( frame, "getWeblink" );

end p.getWikilink = function ( frame )

   return Template( frame, "getWikilink" );

end p.isBracketedLink = function ( frame )

   return Template( frame, "isBracketedLink" );

end p.isBracketedURL = function ( frame )

   return Template( frame, "isBracketedURL" );

end p.isCategorization = function ( frame )

   return Template( frame, "isCategorization" );

end p.isExternalLink = function ( frame )

   return Template( frame, "isExternalLink" );

end p.isInterlanguage = function ( frame )

   return Template( frame, "isInterlanguage" );

end p.isInterwiki = function ( frame )

   return Template( frame, "isInterwiki" );

end p.isMedia = function ( frame )

   return Template( frame, "isMedia" );

end p.isTalkPage = function ( frame )

   return Template( frame, "isTalkPage" );

end p.isTitledLink = function ( frame )

   return Template( frame, "isTitledLink" );

end p.isValidLink = function ( frame )

   return Template( frame, "isValidLink" );

end p.isValidLinktext = function ( frame )

   return Template( frame, "isValidLinktext" );

end p.isWeblink = function ( frame )

   return Template( frame, "isWeblink" );

end p.isWikilink = function ( frame )

   return Template( frame, "isWikilink" );

end p.pageLink = function ( frame )

   return Template( frame, "pageLink" );

end p.failsafe = function ( frame )

   local s = type( frame );
   local since;
   if s == "table" then
       since = frame.args[ 1 ];
   elseif s == "string" then
       since = frame;
   end
   if since then
       since = mw.text.trim( since );
       if since == "" then
           since = false;
       end
   end
   return Failsafe.failsafe( since ) or "";

end -- p.failsafe p.WLink = function ()

   return WLink;

end

return p;