Modul:TemplUtl
Die Dokumentation für dieses Modul kann unter Modul:TemplUtl/doc erstellt werden
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( ">>>" ); sign = string.format( "[[#%s|%s]]", sign, tostring( btn ) ); top:wikitext( sign, " ", 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( "{{%s}}", 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;