imported>PerfektesChaos (2017-11-11) |
imported>PerfektesChaos (2022-05-16) |
||
Zeile 1: | Zeile 1: | ||
local TemplUtl = { suite = "TemplUtl", | local TemplUtl = { suite = "TemplUtl", | ||
serial = " | 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() | |||
Zeile 9: | Zeile 48: | ||
-- accept -- string; trimmed title | -- accept -- string; trimmed title | ||
-- Postcondition: | -- Postcondition: | ||
-- | -- Returns string with pattern | ||
local start = mw.ustring.sub( accept, 1, 1 ); | local start = mw.ustring.sub( accept, 1, 1 ); | ||
local r; | local r; | ||
Zeile 32: | Zeile 71: | ||
local framing = function ( frame ) | 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 not TemplUtl.frame then | ||
if type( frame ) == "table" then | if type( frame ) == "table" then | ||
Zeile 39: | Zeile 83: | ||
end | end | ||
end | end | ||
return TemplUtl.frame; | |||
end -- framing() | 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() | |||
Zeile 47: | Zeile 106: | ||
-- analyze -- string, boolean, number or nil | -- analyze -- string, boolean, number or nil | ||
-- another -- fallback: string, boolean, or nil | -- another -- fallback: string, boolean, or nil | ||
-- Returns boolean | -- "-" to test for explicit vocabulary choice | ||
-- Returns boolean, or "-" | |||
local s = type( analyze ); | local s = type( analyze ); | ||
local r; | local r; | ||
Zeile 54: | Zeile 114: | ||
if r == "" then | if r == "" then | ||
r = TemplUtl.faculty( another, nil ); | r = TemplUtl.faculty( another, nil ); | ||
elseif r | elseif r:find( "1", 1, true ) and | ||
r:match( "^[0%-]*1[01%-]*$" ) then | |||
r = true; | r = true; | ||
elseif r | elseif r:match( "^[0%-]+$" ) then | ||
r = false; | r = false; | ||
else | else | ||
r = r:lower(); | r = r:lower(); | ||
if r == "y" or | if r == "y" or | ||
r == "yes" or | |||
r == "true" or | |||
r == "on" then | |||
r = true; | r = true; | ||
elseif r == "n" or | elseif r == "n" or | ||
r == "no" or | |||
r == "false" or | |||
r == "off" then | |||
r = false; | r = false; | ||
else | else | ||
if not TemplUtl. | if not TemplUtl.boolang then | ||
local | -- TODO: page language | ||
s = string. | 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 | end | ||
if type( TemplUtl. | if type( TemplUtl.boolang ) == "table" then | ||
s = TemplUtl.boolang[ r ]; | |||
if | if type( s ) == "boolean" then | ||
r = s; | |||
end | end | ||
end | end | ||
if type( r ) ~= "boolean" then | if type( r ) ~= "boolean" then | ||
s = type( another ); | |||
if s == "nil" then | |||
r = true; | 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 | ||
Zeile 119: | Zeile 203: | ||
:addClass( "error" ) | :addClass( "error" ) | ||
:wikitext( alert ); | :wikitext( alert ); | ||
local live | local live = ( framing( frame ):preprocess( "{{REVISIONID}}" ) | ||
== "" ); | |||
if type( addClass ) == "string" then | if type( addClass ) == "string" then | ||
err:addClass( addClass ) | err:addClass( addClass ) | ||
Zeile 141: | Zeile 224: | ||
sign = string.format( "[[#%s|%s]]", sign, tostring( btn ) ); | sign = string.format( "[[#%s|%s]]", sign, tostring( btn ) ); | ||
top:wikitext( sign, " ", alert ); | top:wikitext( sign, " ", alert ); | ||
mw.addWarning( tostring( top ) ); | mw.addWarning( tostring( top:attr( "role", "alert" ) ) ); | ||
elseif not always then | elseif not always then | ||
err:css( { ["display"] = "none" } ); | err:css( { ["display"] = "none" } ); | ||
Zeile 152: | Zeile 235: | ||
return tostring( err ); | return tostring( err ); | ||
end -- TemplUtl.failure() | 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() | |||
Zeile 160: | Zeile 296: | ||
-- address -- string; what to inspect, URL presumed | -- address -- string; what to inspect, URL presumed | ||
-- Postcondition: | -- Postcondition: | ||
-- | -- Returns true, if URL beginning | ||
local start, r = address:match( "^%s*((%a*:?)//)" ); | local start, r = address:match( "^%s*((%a*:?)//)" ); | ||
if start then | if start then | ||
Zeile 190: | Zeile 326: | ||
-- after -- true, if only to search for "}}" | -- after -- true, if only to search for "}}" | ||
-- Postcondition: | -- Postcondition: | ||
-- | -- Returns | ||
-- -- number; byte position in area | -- -- number; byte position in area | ||
-- -- before "|" or "}}", may be at end | -- -- before "|" or "}}", may be at end | ||
Zeile 327: | Zeile 463: | ||
-- at -- optional number; byte position in area of "{{" | -- at -- optional number; byte position in area of "{{" | ||
-- Postcondition: | -- Postcondition: | ||
-- | -- Returns | ||
-- -- table | -- -- table | ||
-- [0] -- template, page, parser function name | -- [0] -- template, page, parser function name | ||
Zeile 394: | Zeile 530: | ||
-- ask -- string; parameter name | -- ask -- string; parameter name | ||
-- Postcondition: | -- Postcondition: | ||
-- | -- Returns string with trimmed parameter value, or nil | ||
-- Does not return value if template inside | -- Does not return value if template inside | ||
local r; | local r; | ||
Zeile 425: | Zeile 561: | ||
-- no colon (:) | -- no colon (:) | ||
-- Postcondition: | -- Postcondition: | ||
-- | -- Returns ustring position of "{{" in area, or false | ||
-- Requires: | -- Requires: | ||
-- fiatTitleRegExp() | -- fiatTitleRegExp() | ||
Zeile 491: | Zeile 627: | ||
-- 3 4 5 6 | -- 3 4 5 6 | ||
-- more like 2 | -- 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() | |||
Zeile 497: | Zeile 644: | ||
-- Remove syntax elements that hide effective syntax only | -- Remove syntax elements that hide effective syntax only | ||
-- Precondition: | -- Precondition: | ||
-- area -- string; wikitext to be reduced | -- area -- string; unparsed wikitext to be reduced | ||
-- Postcondition: | -- Postcondition: | ||
-- | -- Returns cleared wikitext | ||
local delimiters = { { "<%s*NOWIKI%s*>", "<%s*/%s*NOWIKI%s*>" }, | local delimiters = { { "<%s*NOWIKI%s*>", "<%s*/%s*NOWIKI%s*>" }, | ||
{ "<!--", "-->", true }, | { "<!--", "-->", true }, | ||
Zeile 563: | Zeile 710: | ||
return r; | return r; | ||
end -- TemplUtl.flat() | 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() | |||
Zeile 568: | Zeile 784: | ||
-- Export | -- Export | ||
local p = { }; | local p = { }; | ||
function p.facets( frame ) | |||
return TemplUtl.facets( frame.args[ 1 ] or "", | |||
frame.args.decode ); | |||
end -- p.facets | |||
function p.faculty( frame ) | 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 | end -- p.faculty | ||
Zeile 587: | Zeile 811: | ||
return TemplUtl.failure( scream, loud, select, frame ); | return TemplUtl.failure( scream, loud, select, frame ); | ||
end -- p.failure | 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() | function p.isRedirect() | ||
Zeile 592: | Zeile 849: | ||
end -- p.isRedirect | end -- p.isRedirect | ||
function p. | function p.nowiki1( frame ) | ||
local r = ( frame.args[ 1 ] ); | |||
end -- p.failsafe() | 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 () | p.TemplUtl = function () | ||
return TemplUtl; | return TemplUtl; | ||
end -- p.TemplUtl() | end -- p.TemplUtl() | ||
setmetatable( p, { __call = function ( func, ... ) | |||
setmetatable( p, nil ); | |||
return Failsafe; | |||
end } ); | |||
return p; | return p; |
Die Dokumentation für dieses Modul kann unter Modul:TemplUtl/Doku 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;