gemeinsam neue Wege der Erkenntnis gehen
Eine freie Initiative von Menschen bei anthrowiki.at anthrowiki.at, anthro.world anthro.world, biodyn.wiki biodyn.wiki und steiner.wiki steiner.wiki
mit online Lesekreisen, Übungsgruppen, Vorträgen ...
Wie Sie die Entwicklung von AnthroWiki durch Ihre Spende unterstützen können, erfahren Sie hier.

Use Google Translate for a raw translation of our pages into more than 100 languages.
Please note that some mistranslations can occur due to machine translation.
Alle Banner auf einen Klick
Grundkurs zur Ausbildung in Biographie-Arbeit
11 Wochenenden in Mannheim
5. April 2025 bis 8. Feb. 2026
Leitung Joop Grün und Walter Seyffer
+49 (0) 6203 84 390 60
www.akademie-biographiearbeit.org
Der neue Glomer Katalog 2024/25 ist da!

Aktuelle Neuerscheinungen und alle lieferbaren Bücher anthroposophischer Verlage
Anthroposophie, Waldorf, Jugend & Kinderbücher, Gesundheit, Lebensphasen, Wissenschaften mit mehr als 7.500 Titeln aus über 80 Verlagen.

Rudolf Steiner: Die Prüfung der Seele
Sonntag, 4. Mai 2025, 20h
Bild 10 - 13, mit Einführung und anschließendem Publikumsgespräch

Livestream: Anmeldung auf https://holiversitaet.de/kultur/

Modul:WLink

Aus AnthroWiki

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

local WLink = { suite  = "WLink",
                serial = "2018-07-19",
                item   = 19363224 };
--[=[
ansiPercent()
formatURL()
getArticleBase()
getBaseTitle()
getEscapedTitle()
getExtension()
getFile()
getFragment()
getLanguage()
getNamespace()
getNamespaced()
getPlain()
getProject()
getTarget()
getTalkPage()
getTargetPage()
getTitle()
getWeblink()
isBracketedLink()
isBracketedURL()
isCategorization()
isExternalLink()
isInterlanguage()
isInterwiki()
isMedia()
isTalkPage()
isTitledLink()
isValidLink()
isWikilink()
wikilink()
failsafe()
]=]



-- local globals
local URLutil = false;



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 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 = r1:gsub( "_",        " " )
                   :gsub( " ",   " " )
                   :gsub( " ", " " )
                   :gsub( " ",   " " )
                   :gsub( " ",  " " )
                   :gsub( "  +",      " " );
            r1 = mw.text.decode( r1 );
        end
    end
    return r1, r2;
end -- extractWikilink()



local prefix = function ( ask, ahead )
    -- Interprete prefix of language or project type
    -- Precondition:
    --     ask    -- string, with presumable prefix
    --     ahead  -- true, if first segment
    -- 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
        if ahead then
            r1 = "lead";
            r2 = true;
        end
    else
        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( "%%%2X", 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
    -- 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  = "http://" .. adjust;
                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
    return attempt: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.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.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.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 );
    local got, n, r;
    if not s then
        s = attempt;
    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 = "" };
        if n > 4 then
            k = 4;
        else
            k = n - 1;
        end
        j = k;
        for i = 1, j do
            s = mw.text.trim( got[ i ] );
            if s ~= "" then
                o = mw.site.namespaces[ mw.text.trim( got[ i ] ) ];
                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 ],  ( i == 1 ) );
            if o then
                if r[ o ] then
                    k = i;
                    break; -- for i
                else
                    r[ o ] = v;
                end
            else
                k = i;
                break; -- for i
            end
        end -- for i
        for i = k, n do
            r.title = r.title .. got[ i ];
            if i < n then
                r.title = r.title .. ":";
            end
        end -- for i
    end
    if r.lead and
       ( r.project  or  not r.title  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 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
               ( k == "space"  and  action == "ansiPercent" ) then
            if v ~= "" then
                space = 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 );
        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.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.isWeblink = function ( frame )
    return Template( frame, "isWeblink" );
end
p.isWikilink = function ( frame )
    return Template( frame, "isWikilink" );
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;