Dokumentation für das Modul TemplUtl[Ansicht] [Bearbeiten] [Versionsgeschichte] [Aktualisieren]

Funktion

Dieses Modul stellt Hilfsfunktionen zur Vorlagenprogrammierung zur Verfügung.
Hinweise
local TemplUtl = { suite  = "TemplUtl",                    serial = "2022-05-16",                    item   = 52364930 }; --[=[ Utilities to support template programming. ]=] local Failsafe = TemplUtl;    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;