Modul:TemplUtl

Aus Wikipedia
Vorlagenprogrammierung Diskussionen Lua Test Unterseiten
Modul Deutsch English

Modul: Dokumentation

Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus

Weiterleitung der Diskussionsseite fehlt
Diese Seite ist eine lokale Kopie aus de.wikipedia.org und von dort zu aktualisieren.

Versionsbezeichnung auf WikiData: 2022-05-16


local TemplUtl = { suite  = "TemplUtl",
                   serial = "2022-05-16",
                   item   = 52364930 };
--[=[
Utilities to support template programming.
]=]
local Failsafe = TemplUtl;



if mw.site.server:find( ".beta.wmflabs.org", 4, true ) then
    require( "Module:No Globals" )
end



local fallible = function ( adjust, ahead )
    -- Check for leading character disturbing syntax
    -- Precondition:
    --    adjust  -- string; trimmed wikitext
    --    ahead   -- true, if leading syntax shall start on new line
    -- Postcondition:
    --    Returns string, modified if necessary
    local r = adjust;
    local c = r:byte( 1, 1 );
    local lead;
    if c <= 59  and
       ( c==35 or c==42 or c==58 or c==59 ) then
        lead = true;
    elseif c == 123  or  c == 124 then
        local c2 = r:byte( 2, 1 );
        if c == 123  and  c2 == 124 then
            lead = true;
        elseif ahead  and  c == 124  and
               ( c2 == 43  or  c2 == 45  or  c2 == 125 ) then
            lead = true;
        end
    end
    if lead then
        if ahead then
            r = "\n" .. r;
        else
            r = mw.text.nowiki( r:sub( 1, 1 ) )  ..  r:sub( 2 );
        end
    end
    return r;
end -- fallible()



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 )
    -- Ensure availability of frame object
    -- Precondition:
    --     frame  -- object; #invoke environment, or false
    -- Postcondition:
    --     Return frame object
    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.facets = function ( ask, adjust )
    local r = ask;
    if adjust == "%"  and  r:find( "%%%x%x" ) then
        r = mw.uri.decode( r, "PATH" );
    elseif r:find( "&", 1, true ) then
        r = mw.text.decode( r );
    end
    r = mw.ustring.gsub( r, "[%s%p%c]+", " " );
    r = mw.text.trim( r );
    return r;
end -- TemplUtl.facets()



TemplUtl.faculty = function ( analyze, another )
    -- Test template arg for boolean
    --     analyze  -- string, boolean, number or nil
    --     another  -- fallback: string, boolean, or nil
    --                 "-" to test for explicit vocabulary choice
    -- Returns boolean, or "-"
    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"  or
               r == "on" then
                r = true;
            elseif r == "n"  or
                   r == "no"  or
                   r == "false"  or
                   r == "off" then
                r = false;
            else
                if not TemplUtl.boolang then
                    -- TODO: page language
                    local l, d = pcall( mw.ext.data.get, "i18n/01.tab" );
                    if type( d ) == "table"  and
                       type( d.data ) == "table" then
                        local f = function ( at )
                                  local e = d.data[ at ];
                                  l = e[ 1 ];
                                  s = e[ 2 ];
                                  if type( l ) == "boolean"  and
                                     type( s ) == "string" then
                                      s = mw.text.split( s, "|" );
                                      for i = 1, #s do
                                          TemplUtl.boolang[ s[ i ] ] = l;
                                      end -- for i
                                  end
                              end
                        TemplUtl.boolang = { };
                        f( 1 );
                        f( 2 );
                    else
                        TemplUtl.boolang = true;
                    end
                end
                if type( TemplUtl.boolang ) == "table" then
                    s = TemplUtl.boolang[ r ];
                    if type( s ) == "boolean" then
                        r = s;
                    end
                end
                if type( r ) ~= "boolean" then
                    s = type( another );
                    if s == "nil" then
                        r = true;
                    elseif s == "boolean" then
                        r = another;
                    elseif s == "string" then
                        s = mw.text.trim( another );
                        if s == "-" then
                            r = "-";
                        elseif s == "" then
                            r = true;
                        else
                            r = TemplUtl.faculty( s );
                        end
                    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.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 )
    -- Simulation of template transclusion
    -- Precondition:
    --    access  -- string; page name (template)
    if type( access ) == "string" then
        local s = mw.text.trim( access );
        if s ~= "" then
            local t = mw.title.new( s, 10 );
            if not mw.title.equals( mw.title.getCurrentTitle(), t )  and
               t.exists then
                t:getContent();
            end
        end
    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.firstbreak = function ( adjust )
    -- Precede leading character with newline if specific syntax
    -- Precondition:
    --    adjust  -- string; trimmed wikitext
    -- Postcondition:
    --    Returns string, modified if necessary
    return fallible( adjust, true );
end -- TemplUtl.firstbreak()



TemplUtl.flat = function ( area )
    -- Remove syntax elements that hide effective syntax only
    -- Precondition:
    --     area  -- string; unparsed 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()



TemplUtl.nowiki1 = function ( adjust )
    -- HTML-escape leading character if disturbing syntax
    -- Precondition:
    --    adjust  -- string; trimmed wikitext
    -- Postcondition:
    --    Returns string, modified if necessary
    return fallible( adjust, false );
end -- TemplUtl.nowiki1()



Failsafe.failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     atleast  -- string, with required version
    --                         or wikidata|item|~|@ or false
    -- Postcondition:
    --     Returns  string  -- with queried version/item, also if problem
    --              false   -- if appropriate
    -- 2020-08-17
    local since = atleast
    local last    = ( since == "~" )
    local linked  = ( since == "@" )
    local link    = ( since == "item" )
    local r
    if last  or  link  or  linked  or  since == "wikidata" then
        local item = Failsafe.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local suited = string.format( "Q%d", item )
            if link then
                r = suited
            else
                local entity = mw.wikibase.getEntity( suited )
                if type( entity ) == "table" then
                    local seek = Failsafe.serialProperty or "P348"
                    local vsn  = entity:formatPropertyValues( seek )
                    if type( vsn ) == "table"  and
                       type( vsn.value ) == "string"  and
                       vsn.value ~= "" then
                        if last  and  vsn.value == Failsafe.serial then
                            r = false
                        elseif linked then
                            if mw.title.getCurrentTitle().prefixedText
                               ==  mw.wikibase.getSitelink( suited ) then
                                r = false
                            else
                                r = suited
                            end
                        else
                            r = vsn.value
                        end
                    end
                end
            end
        end
    end
    if type( r ) == "nil" then
        if not since  or  since <= Failsafe.serial then
            r = Failsafe.serial
        else
            r = false
        end
    end
    return r
end -- Failsafe.failsafe()



-- Export
local p = { };

function p.facets( frame )
    return TemplUtl.facets( frame.args[ 1 ]  or  "",
                            frame.args.decode );
end -- p.facets

function p.faculty( frame )
    local r = TemplUtl.faculty( frame.args[ 1 ],
                                frame.args[ 2 ] );
    if r ~= "-" then
        r = r and "1";
    end
    return r 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.firstbreak( frame )
    local r = ( frame.args[ 1 ] );
    if r then
        r = mw.text.trim( r );
        if r ~= "" then
            r = TemplUtl.firstbreak( r );
        end
    end
    return r or "";
end -- p.firstbreak

function p.from( frame )
    local r = frame:getParent():getTitle();
    if r then
        r = string.format( "&#123;&#123;%s&#125;&#125;", r );
    end
    return r or "";
end -- p.from

function p.isRedirect()
    return mw.title.getCurrentTitle().isRedirect and "1"  or  "";
end -- p.isRedirect

function p.nowiki1( frame )
    local r = ( frame.args[ 1 ] );
    if r then
        r = mw.text.trim( r );
        if r ~= "" then
            r = TemplUtl.nowiki1( r );
        end
    end
    return r or "";
end -- p.nowiki1

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 Failsafe.failsafe( since )  or  ""
end -- p.failsafe

p.TemplUtl = function ()
    return TemplUtl;
end -- p.TemplUtl()

setmetatable( p,  { __call = function ( func, ... )
                                 setmetatable( p, nil );
                                 return Failsafe;
                             end } );

return p;