Modul:WLink

    Aus mxlinuxusers.de

    Die Dokumentation für dieses Modul kann unter Modul:WLink/Doku erstellt werden

    local WLink = { suite  = "WLink",
                    serial = "2019-05-25",
                    item   = 19363224 };
    --[=[
    ansiPercent()
    formatURL()
    getArticleBase()
    getBaseTitle()
    getEscapedTitle()
    getExtension()
    getFile()
    getFragment()
    getLanguage()
    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 htmlInline = { b      = true,
                         big    = true,
                         code   = true,
                         em     = true,
                         i      = true,
                         kbd    = true,
                         s      = true,
                         samp   = true,
                         small  = true,
                         span   = true,
                         strong = true,
                         sub    = true,
                         sup    = true,
                         tt     = true,
                         var    = true };
    
    
    
    local utilURL = function ()
        -- Attach URLutil library module
        -- Postcondition:
        --     Returns  table, with URLutil library
        --     Throws error, if not available
        if not URLutil then
            local lucky, util = pcall( require, "Module:URLutil" );
            if lucky then
                if type( util ) == "table" then
                    URLutil = util.URLutil();
                end
                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 )
        -- 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( "%[", "&#91;" )
                :gsub( "%]", "&#93;" )
                :gsub( "|",  "&#124;" );
    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.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;
                        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 s, r = WLink.getTarget( attempt );
        if r then
            r = true;
        end
        return r;
    end -- WLink.isValidLink()
    
    
    
    function WLink.isValidLinktext( attempt )
        -- Is attempt a plain inline text?
        -- Precondition:
        --     attempt  -- string, with presumable linktext
        -- Postcondition:
        --     Returns  boolean
        local r = true;
        local i = attempt:find( "]", 1, true );
        if i then
            r = false;
        else
            r = ( mw.text.unstrip( attempt ) == attempt );
        end
        if r then
            i = attempt:find( "<", 1, true );
            if i then
                local s = mw.ustring.lower( attempt:sub( i ) );
                local sign, skip;
                while i and r do
                    skip, sign = s:match( "^([^<]*< *)(%l[%l%d]*)[ /]*.*>" );
                    if sign then
                        r = htmlInline[ sign ];
                        if r then
                            i = skip:len() + sign:len() + 1;
                            s = s:sub( i );
                        end
                    else
                        i = false;
                    end
                end    -- while i and r
            end
        end
        if r then
            local s = attempt .. " ";
            i = s:find( "ISBN ", 1, true );
            if i then
                r = not s:match( "ISBN %d[%-%d]+[%dxX]%W" );
            end
            if r then
                i = s:find( "PMID ", 1, true );
                if i then
                    r = not s:match( "PMID %d+%W" );
                end
                if r then
                    i = s:find( "RFC ", 1, true );
                    if i then
                        r = not s:match( "RFC [1-6]%d?%d?%d?%W" );
                    end
                end
            end
        end
        return r or false;
    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, 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()
    
    
    
    function WLink.failsafe( assert )
        -- Retrieve versioning and check for compliance
        -- Precondition:
        --     assert  -- string, with required version or "wikidata",
        --                or false
        -- Postcondition:
        --     Returns  string with appropriate version, or false
        local since = assert;
        local r;
        if since == "wikidata" then
            local item = WLink.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
                        r = vsn.value;
                    end
                end
            end
        end
        if not r then
            if not since  or  since <= WLink.serial then
                r = WLink.serial;
            else
                r = false;
            end
        end
        return r;
    end -- WLink.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 ~= "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.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 WLink.failsafe( since ) or "";
    end
    p.WLink = function ()
        return WLink;
    end
    
    return p;