Modul:TemplUtl

    Aus mxlinuxusers.de

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

    local TemplUtl = { suite  = "TemplUtl",
                       serial = "2019-02-20" };
    
    
    
    local fiatTitleRegExp = function ( accept )
        -- Create pattern to detect page name
        -- Precondition:
        --     accept  -- string; trimmed title
        -- Postcondition:
        --     Returns string with pattern
        local start = mw.ustring.sub( accept, 1, 1 );
        local r;
        if mw.ustring.match( start, "%a" ) then
            r = string.format( "[%s%s]%s",
                               mw.ustring.lower( start ),
                               mw.ustring.upper( start ),
                               mw.ustring.sub( accept, 2 ) );
        else
            r = accept;
        end
        if r:match( " " ) then
            r = r:gsub( "%", "%%" )
                 :gsub( "[%-^.?+*()$]", "%$1" )
                 :gsub( "_", " " )
                 :gsub( "%s+", "[%s_]+" );
        end
        return r;
    end -- fiatTitleRegExp()
    
    
    
    local framing = function ( frame )
        if not TemplUtl.frame then
            if type( frame ) == "table" then
                TemplUtl.frame = frame;
            else
                TemplUtl.frame = mw.getCurrentFrame();
            end
        end
        return TemplUtl.frame;
    end -- framing()
    
    
    
    TemplUtl.faculty = function ( analyze, another )
        -- Test template arg for boolean
        --     analyze  -- string, boolean, number or nil
        --     another  -- fallback: string, boolean, or nil
        -- Returns boolean
        local s = type( analyze );
        local r;
        if s == "string" then
            r = mw.text.trim( analyze );
            if r == ""  then
                r = TemplUtl.faculty( another, nil );
            elseif r:find( "1", 1, true )  and
                   r:match( "^[0%-]*1[01%-]*$") then
                r = true;
            elseif r:match( "^[0%-]+$") then
                r = false;
            else
                r = r:lower();
                if r == "y"  or  r == "yes"  or  r == "true" then
                    r = true;
                elseif r == "n"  or  r == "no"  or  r == "false" then
                    r = false;
                else
                    if not TemplUtl.L10N then
                        local lucky;
                        s = string.format( "Module:%s/local",
                                           TemplUtl.suite );
                        lucky, TemplUtl.L10N = pcall( mw.loadData, s );
                    end
                    if type( TemplUtl.L10N ) == "table" then
                        local entry;
                        if not TemplUtl.lang then
                            -- TODO: page language
                            TemplUtl.lang =
                                  mw.language.getContentLanguage():getCode();
                        end
                        entry = TemplUtl.L10N[ TemplUtl.lang ];
                        if type( entry ) == "table" then
                            s = entry[ r ];
                            if type( s ) == "boolean" then
                                r = s;
                            end
                        end
                    else
                        TemplUtl.L10N = true;
                    end
                    if type( r ) ~= "boolean" then
                        if type( another ) ~= "nil"  then
                            r = TemplUtl.faculty( another );
                        else
                            r = true;
                        end
                    end
                end
            end
        elseif s == "boolean" then
            r = analyze;
        elseif s == "number" then
            r = ( analyze ~= 0 );
        else
            r = false;
        end
        return r;
    end -- TemplUtl.faculty()
    
    
    
    TemplUtl.failsafe = function ( assert )
        local r
        if not assert  or  assert <= TemplUtl.serial then
            r = TemplUtl.serial
        else
            r = false
        end
        return r
    end -- TemplUtl.failsafe()
    
    
    
    TemplUtl.failure = function ( alert, always, addClass, frame )
        -- Format error message, mostly hidden
        --     alert     -- string: message
        --     always    -- boolean, or nil: do not hide
        --     addClass  -- string, or nil: add classes to element
        --     frame     -- object, or nil
        -- Returns string
        local err  = mw.html.create( "span" )
                            :addClass( "error" )
                            :wikitext( alert );
        local live = ( framing( frame ):preprocess( "{{REVISIONID}}" )
                       == "" );
        if type( addClass ) == "string" then
            err:addClass( addClass )
        end
        if live then
            local max  = 1000000000;
            local id   = math.floor( os.clock() * max );
            local sign = string.format( "error_%d", id );
            local btn  = mw.html.create( "span" );
            local top  = mw.html.create( "div" );
            err:attr( "id", sign );
            -- TODO: LTR
            btn:css( { ["background"]      = "#FFFF00",
                       ["border"]          = "#FF0000 3px solid",
                       ["font-weight"]     = "bold",
                       ["padding"]         = "2px",
                       ["text-decoration"] = "none" } )
               :wikitext( "&gt;&gt;&gt;" );
            sign = string.format( "[[#%s|%s]]",  sign,  tostring( btn ) );
            top:wikitext( sign, "&#160;", alert );
            mw.addWarning( tostring( top:attr( "role", "alert" ) ) );
        elseif not always then
            err:css( { ["display"]     = "none" } );
    --      err:css( { ["display"]     = "inline-block",
    --                 ["line-height"] = "0",
    --                 ["max-height"]  = "0",
    --                 ["max-width"]   = "0",
    --                 ["visibility"]  = "hidden" } );
        end
        return tostring( err );
    end -- TemplUtl.failure()
    
    
    
    TemplUtl.fake = function ( access, frame )
        -- Simulation of template transclusion
        -- Precondition:
        --    access  -- string, or nil; page name (template)
        --    frame   -- object, or nil
        local s;
        if type( access ) == "string" then
            s = mw.text.trim( access );
            if s == "" then
                s = false;
            end
        end
        if not s then
            local cnf, lucky;
            s = string.format( "Module:%s/self", TemplUtl.suite );
            lucky, cnf = pcall( mw.loadData, s );
            if type( cnf ) == "table"  and
               cnf.loop == true then
                s = mw.title.getCurrentTitle().text;
            else
                s = false;
            end
        end
        if s then
            local f = function ()
                          framing( frame ):expandTemplate{ title = s };
                      end
            pcall( f );
        end
    end -- TemplUtl.fake()
    
    
    
    TemplUtl.fakes = function ( array, frame, ahead, answer )
        -- Simulation of template transclusions
        -- Precondition:
        --    array   -- table, with template title strings
        --    frame   -- object, or nil
        --    ahead   -- string, or nil, with common prefix
        --    answer  -- true, or nil, for list creation
        -- Postcondition:
        --     Returns string, if answer requested
        local e = framing( frame );
        local f = function ( a )
                      e:expandTemplate{ title = a };
                  end
        local s = ahead or "";
        local r;
        for k, v in pairs( array ) do
            if type( k ) == "number" and
               type( v ) == "string" then
                v = s .. mw.text.trim( v );
                pcall( f, v );
                if answer then
                    if r then
                        r = r .. "\n";
                    else
                        r = "";
                    end
                    r = string.format( "%s* [[Template:%s|%s]]", r, v, v );
                end
            end
        end -- for k, v
        return r;
    end -- TemplUtl.fakes()
    
    
    
    TemplUtl.feasible = function ( address )
        -- Does this describe an URL beginning?
        -- Precondition:
        --    address  -- string; what to inspect, URL presumed
        -- Postcondition:
        --     Returns true, if URL beginning
        local start, r = address:match( "^%s*((%a*:?)//)" );
        if start then
            if r == "" then
                r = true;
            elseif r:sub( -1, -1 ) == ":" then
                local schemes = ":ftp:ftps:http:https:";
                r = ":" .. r:lower();
                if schemes:find( r, 1, true ) then
                    r = true;
                else
                    r = false;
                end
            else
                r = false;
            end
        end
        return r;
    end -- TemplUtl.feasible()
    
    
    
    TemplUtl.feed = function ( area, ahead, at, after )
        -- Detect next free "|" or "}}"
        -- Precondition:
        --     area   -- string; template transclusion
        --     ahead  -- string; opening element, or false
        --     at     -- number; byte position in area where to start
        --     after  -- true, if only to search for "}}"
        -- Postcondition:
        --     Returns
        --          -- number; byte position in area
        --             -- before "|" or "}}", may be at end
        --             -- to continue search; ahead has been closed
        --          -- true, if to be continued at number
        local j    = at;
        local loop = true;
        local c, k, r, s, seek;
        if after then
            seek = "[{}<]";
        else
            seek = "[%[%]|{}<:]";
        end
        while loop do
            j = area:find( seek, j );
            if j then
                c = area:byte( j, j );
                if c == 123 then    -- {
                    k = j + 1;
                    if area:byte( k, k ) == 123 then
                        k = k + 1;
                        if area:byte( k, k ) == 123 then
                            j, loop = TemplUtl.feed( area, "{{{", k, after );
                        else
                            k = k - 1;
                            j, loop = TemplUtl.feed( area, "{{", k, after );
                        end
                        if not loop then
                            r = j;
                        end
                    end
                elseif c == 125 then    -- }
                    k = j + 1;
                    if area:byte( k, k ) == 125 then
                        if ahead == "{{" then
                            r = k;
                            break;    -- while loop;
                        elseif ahead == "{{{" then
                            k = k + 1;
                            if area:byte( k, k ) == 125 then
                                r = k;
                                break;    -- while loop;
                            end
                        elseif not ahead then
                            r    = j - 1;
                            loop = false;
                        end
                    end
                elseif c == 60 then    -- <
                    k = j + 3;
                    if area:sub( j, k ) == "<!--" then
                        k = area:find( "-->", k );
                        if k then
                            j = k + 2;
                        end
                    else
                        local skip;
                        s    = area:sub( j + 1 ):lower();
                        skip = s:match( "^%s*nowiki%s*>" );
                        if skip then
                            local n = skip:len();
                            n, k = s:find( "<%s*/%s*nowiki%s*>", n );
                            if k then
                                j = j + k;
                            else
                                loop = false;
                            end
                        end
                    end
                elseif c == 124 then    -- |
                    if not r then
                        r = j - 1;
                    end
                    if not ahead then
                        loop = false;
                    end
                elseif c == 91 then    -- [
                    k = j + 1;
                    if area:byte( k, k ) == 91 then
                        k = k + 1;
                        j, loop = TemplUtl.feed( area, "[[", k, after );
                    elseif TemplUtl.feasible( area:sub( k ) ) then
                        k = k + 3;
                        j, loop = TemplUtl.feed( area, "[", k, after );
                    end
                    if not loop then
                        r = j;
                    end
                elseif c == 93 then    -- ]
                    if ahead == "[" then
                        r = j;
                        break;    -- while loop
                    elseif ahead == "[[" then
                        k = j + 1;
                        if area:byte( k, k ) == 93 then
                            r = k;
                            break;    -- while loop
                        end
                    end
                elseif c == 58 then    -- :
                    s = area:sub( j + 1,  j + 2 );
                    if s == "//" then
                        s = " " .. area:sub( 1,  j + 2 );
                        s = s:match( "%s(%a+://)$" );
                        if s  and  TemplUtl.feasible( s ) then
                            s = area .. " ";
                            s = s:match( "([^%s|]+)%s", j );
                            if s then
                                k = s:find( "}}" );
                                if k then
                                    j = j + k + 1;
                                else
                                    j = j + s:len();
                                end
                            end
                        end
                    end
                end
                j = j + 1;
            else
                loop = false;
            end
        end -- while loop
        if not r then
            r = area:len();
        end
        return r, loop;
    end -- TemplUtl.feed()
    
    
    
    TemplUtl.feeder = function ( area, at )
        -- Retrieve all parameters
        -- Precondition:
        --     area   -- string; template transclusion
        --     at     -- optional number; byte position in area of "{{"
        -- Postcondition:
        --     Returns
        --          -- table
        --              [0]       -- template, page, parser function name
        --              [1]       -- unnamed parameter
        --              ["name"]  -- named parameter
        --          -- string; error message, if any, else nil
        local n = 0;
        local j, k, p, r, r2, s, v;
        if type( at ) == "number" then
            j = at + 2;
        else
            j = 3;
        end
        while true do
            k = TemplUtl.feed( area, false, j );
            s = area:sub( j, k );
            s = s:gsub( "<!--.*-->", "" );
            if n == 0 then
                r = { [ 0 ] = s };
                n = 1;
            else
                p, v = s:match( "^([^=]*)=(.*)$" );
                if p then
                    if p:match( "^%s*%d+%s*$" )  then
                        p = tonumber( p );
                    else
                        p = mw.text.trim( p );
                    end
                    v = mw.text.trim( v );
                else
                    p = n;
                    v = s;
                    n = n + 1;
                end
                if r[ p ] then
                    if r2 then
                        r2 = r2 .. " * ";
                    else
                        r2 = "";
                    end
                    r2 = string.format( "%s%s '%s'",
                                        r2,
                                        "duplicated parameter",
                                        tostring( p ) );
                end
                r[ p ] = v;
            end
            s = area:sub( k + 1,  k + 2 );
            if s == "}}" then
                break;    -- while true
            elseif s == "" then
                r2 = "template not closed";
                break;    -- while true
            end
            j = k + 2;
        end -- while true
        return r, r2;
    end -- TemplUtl.feeder()
    
    
    
    TemplUtl.fetch = function ( area, ask )
        -- Find assignment of a named template parameter
        -- Precondition:
        --     area  -- string; template transclusion
        --     ask   -- string; parameter name
        -- Postcondition:
        --     Returns string with trimmed parameter value, or nil
        --     Does not return value if template inside
        local r;
        local scan = string.format( "%s%s%s",
                                    "|%s*", ask, "%s*=(.+)$" );
        r = mw.ustring.match( area, scan );
        if r then
            local j = TemplUtl.feed( r, false, 1 );
            r = r:sub( 1, j );
            if r then
                r = mw.text.trim( r );
                if r == "" then
                    r = nil;
                end
            end
        end
        return r;
    end -- TemplUtl.fetch()
    
    
    
    TemplUtl.find = function ( area, access, at, alter )
        -- Find next occurrence of a template
        -- Precondition:
        --     area    -- string; where to search
        --     access  -- string; trimmed (template) title
        --     at      -- optional number; ustring position in area, if not 1
        --     alter   -- optional string; lowercase namespace pattern
        --                                 "" for article
        --                                 no colon (:)
        -- Postcondition:
        --     Returns ustring position of "{{" in area, or false
        -- Requires:
        --     fiatTitleRegExp()
        local scan = string.format( "{{%s%s%s",
                                    "([%w_%s:]*)%s*",
                                    fiatTitleRegExp( access ),
                                    "%s*([|}<]!?)" );
        local r, space, start, suffix;
        if type( at ) == "number" then
            r = at;
        else
            r = 1;
        end
        while true do
            r = mw.ustring.find( area, scan, r );
            if r then
                start, suffix = mw.ustring.match( area, scan, r );
                if start then
                    start = mw.text.trim( start );
                    if start == "" then
                        break; -- while true
                    elseif alter then
                        if not space then
                            space = string.format( "^:?%s:$", alter );
                        end
                        start = mw.ustring.lower( start );
                        if mw.ustring.match( start, space ) then
                            break; -- while true
                        end
                    else
                        start = start:match( "^:?(.+):$" );
                        if start then
                            start = mw.ustring.lower( start );
                            if start == "template" then
                                break; -- while true
                            else
                                if not space then
                                    space = mw.site.namespaces[ 10 ].name;
                                    space = mw.ustring.lower( space );
                                end
                                start = start:gsub( "_", " " )
                                             :gsub( "%s+", " " );
                                if start == space then
                                    break; -- while true
                                end
                            end
                        end
                    end
                else
                    break; -- while true
                end
                r = r + 2;
            else
                r = false;
                break; -- while true
            end
        end -- while true
        return r;
    end -- TemplUtl.find()
    
    
    -- finder()
    --      1 page name
    --      2 template title / page name
    --      3 4 5 6
    --        more like 2
    
    
    
    TemplUtl.flat = function ( area )
        -- Remove syntax elements that hide effective syntax only
        -- Precondition:
        --     area  -- string; wikitext to be reduced
        -- Postcondition:
        --     Returns cleared wikitext
        local delimiters = { { "<%s*NOWIKI%s*>", "<%s*/%s*NOWIKI%s*>" },
                             { "<!--", "-->", true },
                             { "<%s*PRE%s*>", "<%s*/%s*PRE%s*>" },
                             { "<%s*SYNTAXHIGHLIGHT[^<>]*>",
                               "<%s*/%s*SYNTAXHIGHLIGHT%s*>" } };
        local i          = 1;
        local r          = area;
        local k, m, n;
        if not TemplUtl.Delimiters then
            local c, sD, sP;
            TemplUtl.Delimiters = { };
            for j = 1, #delimiters do
                table.insert( TemplUtl.Delimiters, { } );
                for ji = 1, 2 do
                    sD = delimiters[ j ][ ji ];
                    sP = "";
                    for js = 1, #sD, 1 do
                        c = sD:byte( js, js );
                        if c >= 65  and  c <= 90 then
                            sP = string.format( "%s[%c%c]",
                                                sP,  c,  c + 32 );
                        else
                            sP = sP .. string.char( c );
                        end
                    end -- for js
                    table.insert( TemplUtl.Delimiters[ j ], sP );
                end -- for ji
            end -- for j
        end
        while ( true ) do
            k = false;
            for j = 1, #delimiters do
                m = r:find( TemplUtl.Delimiters[ j ][ 1 ],
                            i,
                            TemplUtl.Delimiters[ j ][ 3 ] );
                if m  and  ( not k  or  m < k ) then
                    k = m;
                    n = j;
                end
            end -- for j
            if k then
                local s
                if k > 1 then
                    i = k - 1;
                    s = r:sub( 1, i );
                else
                    s = "";
                end
                j, m  =  r:find( TemplUtl.Delimiters[ n ][ 2 ],
                                 k + 1,
                                 TemplUtl.Delimiters[ n ][ 3 ] );
                if m then
                    r = s  ..  r:sub( m + 1 );
                else
                    r = s;
                    break; -- while true
                end
            else
                break; -- while true
            end
        end -- while true
        return r;
    end -- TemplUtl.flat()
    
    
    
    -- Export
    local p = { };
    
    function p.faculty( frame )
        return TemplUtl.faculty( frame.args[ 1 ],
                                 frame.args[ 2 ] )  and  "1"
               or   "";
    end -- p.faculty
    
    function p.failure( frame )
        local scream = mw.text.trim( frame.args[ 1 ]  or  "" );
        local loud   = frame.args[ 2 ];
        local select = frame.args.class;
        if scream == "" then
            scream = "?????????";
        end
        if loud then
            loud = TemplUtl.faculty( loud, nil );
        end
        return TemplUtl.failure( scream, loud, select, frame );
    end -- p.failure
    
    function p.fake( frame )
        TemplUtl.fake( frame.args[ 1 ]  or  "",  frame );
        return "";
    end -- p.fake
    
    function p.fakes( frame )
        local list = ( frame.args.list == "1" );
        local r    = TemplUtl.fakes( frame.args,
                                     frame,
                                     frame.args.prefix,
                                     list );
        return r or "";
    end -- p.fakes
    
    function p.isRedirect()
        return mw.title.getCurrentTitle().isRedirect and "1"  or  "";
    end -- p.isRedirect
    
    p.failsafe = function ( frame )
        -- Versioning interface
        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 TemplUtl.failsafe( since )  or  ""
    end -- p.failsafe()
    
    p.TemplUtl = function ()
        return TemplUtl;
    end -- p.TemplUtl()
    
    return p;