Modul:TemplateData: Unterschied zwischen den Versionen
Erscheinungsbild
w>PerfektesChaos 2018-03-12 |
K 78 Versionen von skanwiki:Modul:TemplateData importiert |
||
| (35 dazwischenliegende Versionen von 5 Benutzern werden nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
local TemplateData = { suite = "TemplateData", | local TemplateData = { suite = "TemplateData", | ||
serial = " | serial = "2021-07-05", | ||
item = 46997995 } | item = 46997995 } | ||
--[=[ | --[==[ | ||
improve template:TemplateData | improve template:TemplateData | ||
]=] | ]==] | ||
local Failsafe = TemplateData | |||
| Zeile 12: | Zeile 13: | ||
basicCnf = { catProblem = "strange", | basicCnf = { catProblem = "strange", | ||
classNoNumTOC = "suppressTOCnum", | classNoNumTOC = "suppressTOCnum", | ||
classTable = "classTable", | |||
cssParWrap = "cssTabWrap", | cssParWrap = "cssTabWrap", | ||
cssParams = "cssTable", | cssParams = "cssTable", | ||
| Zeile 28: | Zeile 30: | ||
helpURL = "support4url", | helpURL = "support4url", | ||
helpUser = "support4wiki-user-name", | helpUser = "support4wiki-user-name", | ||
msgDescMiss = "solo" | msgDescMiss = "solo", | ||
tStylesTOCnum = "stylesTOCnum" }, | |||
classTable = { "wikitable" }, -- classes for params table | |||
debugmultilang = "C0C0C0", | debugmultilang = "C0C0C0", | ||
loudly = false, -- show exported element, etc. | loudly = false, -- show exported element, etc. | ||
| Zeile 47: | Zeile 49: | ||
got = false, -- table, initial templatedata object | got = false, -- table, initial templatedata object | ||
heirs = false, -- table, params that are inherited | heirs = false, -- table, params that are inherited | ||
jump = false, -- source position at end of "params" | |||
less = false, -- main description missing | less = false, -- main description missing | ||
lasting = false, -- old syntax encountered | lasting = false, -- old syntax encountered | ||
| Zeile 55: | Zeile 58: | ||
params = false, -- table, exported parameters | params = false, -- table, exported parameters | ||
scream = false, -- error messages | scream = false, -- error messages | ||
slang = | sibling = false, -- TOC juxtaposed | ||
slang = nil, -- project/user language code | |||
slim = false, -- JSON reduced to plain | slim = false, -- JSON reduced to plain | ||
source = false, -- JSON input | source = false, -- JSON input | ||
| Zeile 64: | Zeile 68: | ||
} | } | ||
local Permit = { | local Permit = { | ||
builder = { after | builder = { after = "block", | ||
align | align = "block", | ||
block | block = "block", | ||
compressed = "block", | compressed = "block", | ||
dense | dense = "block", | ||
grouped | grouped = "inline", | ||
half | half = "inline", | ||
indent | indent = "block", | ||
inline | inline = "inline", | ||
last | last = "block", | ||
lead | lead = "block", | ||
newlines | newlines = "*", | ||
spaced | spaced = "inline" }, | ||
colors = { tableheadbg | colors = { tableheadbg = "B3B7FF", | ||
required | required = "EAF3FF", | ||
suggested | suggested = "FFFFFF", | ||
optional | optional = "EAECF0", | ||
deprecated | deprecated = "FFCBCB" }, | ||
params = { aliases | params = { aliases = "table", | ||
autovalue | autovalue = "string", | ||
default | default = "string table I18N nowiki", | ||
deprecated | deprecated = "boolean string I18N", | ||
description = "string table I18N", | description = "string table I18N", | ||
example | example = "string table I18N nowiki", | ||
label | label = "string table I18N", | ||
inherits | inherits = "string", | ||
required | required = "boolean", | ||
suggested | style = "string table", | ||
type | suggested = "boolean", | ||
suggestedvalues = "string table number", | |||
type = "string" }, | |||
root = { description = "string table I18N", | root = { description = "string table I18N", | ||
format = "string", | format = "string", | ||
| Zeile 133: | Zeile 139: | ||
local function Fetch( ask ) | local function Fetch( ask, allow ) | ||
-- Fetch module | -- Fetch module | ||
-- Parameter: | -- Parameter: | ||
-- ask | -- ask -- string, with name | ||
-- | -- "/global" | ||
-- | -- "JSONutil" | ||
-- | -- "Multilingual" | ||
-- "Text" | |||
-- "WLink" | |||
-- allow -- true: no error if unavailable | |||
-- Returns table of module | -- Returns table of module | ||
-- error: Module not available | -- error: Module not available | ||
local r | local sign = ask | ||
local r, stem | |||
if sign:sub( 1, 1 ) == "/" then | |||
sign = TemplateData.frame:getTitle() .. sign | |||
else | |||
stem = sign | |||
sign = "Module:" .. stem | |||
end | |||
if TemplateData.extern then | if TemplateData.extern then | ||
r = TemplateData.extern[ | r = TemplateData.extern[ sign ] | ||
else | else | ||
TemplateData.extern = { } | TemplateData.extern = { } | ||
end | end | ||
if not r then | if not r then | ||
local lucky, g = pcall( require, | local lucky, g = pcall( require, sign ) | ||
if type( g ) == "table" then | if type( g ) == "table" then | ||
r = g[ | if stem and type( g[ stem ] ) == "function" then | ||
TemplateData.extern[ | r = g[ stem ]() | ||
else | |||
error( string.format( "Fetch(%s) %s", | r = g | ||
end | |||
TemplateData.extern[ sign ] = r | |||
elseif not allow then | |||
error( string.format( "Fetch(%s) %s", sign, g ), 0 ) | |||
end | end | ||
end | end | ||
return r | return r | ||
end -- Fetch() | end -- Fetch() | ||
local function Foreign() | |||
-- Guess human language | |||
-- Returns slang, or not | |||
if type( Data.slang ) == "nil" then | |||
local Multilingual = Fetch( "Multilingual", true ) | |||
if Multilingual and | |||
type( Multilingual.userLangCode ) == "function" then | |||
Data.slang = Multilingual.userLangCode() | |||
else | |||
Data.slang = mw.language.getContentLanguage():getCode() | |||
:lower() | |||
end | |||
end | |||
if Data.slang and | |||
mw.ustring.codepoint( Data.slang, 1, 1 ) > 122 then | |||
Data.slang = false | |||
end | |||
return Data.slang | |||
end -- Foreign() | |||
| Zeile 172: | Zeile 214: | ||
:gsub( "([%-.()+*?^$%[%]])", | :gsub( "([%-.()+*?^$%[%]])", | ||
"%%%1" ) ) | "%%%1" ) ) | ||
local i, k = Data.source:find( | local i, k, r, slice, source | ||
if not Data.jump then | |||
Data.jump = Data.source:find( "params", 2 ) | |||
if Data.jump then | |||
Data.jump = Data.jump + 7 | |||
else | |||
Data.jump = 1 | |||
end | |||
end | |||
i, k = Data.source:find( seek, at + Data.jump ) | |||
while i and not r do | while i and not r do | ||
source = Data.source:sub( k + 1 ) | source = Data.source:sub( k + 1 ) | ||
| Zeile 184: | Zeile 234: | ||
r = k | r = k | ||
else | else | ||
i, k = Data.source:find( seek, k ) | i, k = Data.source:find( seek, k ) | ||
end | end | ||
end -- while i | end -- while i | ||
| Zeile 198: | Zeile 248: | ||
-- Returns string, with localized text | -- Returns string, with localized text | ||
local o = mw.message.new( "templatedata-" .. adapt ) | local o = mw.message.new( "templatedata-" .. adapt ) | ||
if | if Foreign() then | ||
o:inLanguage( Data.slang ) | o:inLanguage( Data.slang ) | ||
end | end | ||
| Zeile 252: | Zeile 302: | ||
-- adjust -- string | -- adjust -- string | ||
-- Returns string, with adjusted text | -- Returns string, with adjusted text | ||
local f = function ( a ) | local f = function ( a ) | ||
return a:gsub( "%s*\n%s*", " " ) | |||
:gsub( "%s%s+", " " ) | |||
end | |||
local | local tags = { { start = "<noexport>", | ||
stop = "</noexport>" }, | |||
{ start = "<exportonly>", | |||
stop = "</exportonly>", | |||
l = false } | |||
} | |||
local r = adjust | |||
local i, j, k, s, tag | |||
for m = 1, 2 do | |||
i = | tag = tags[ m ] | ||
j, k = | if r:find( tag.start, 1, true ) then | ||
if j then | s = r | ||
r = "" | |||
i = 1 | |||
tag.l = true | |||
j, k = s:find( tag.start, i, true ) | |||
while j do | |||
if j > 1 then | |||
r = r .. f( s:sub( i, j - 1 ) ) | |||
end | |||
i = k + 1 | i = k + 1 | ||
j, k = | j, k = s:find( tag.stop, i, true ) | ||
if j then | |||
if m == 1 then | |||
r = r .. s:sub( i, j - 1 ) | |||
end | |||
i = k + 1 | |||
j, k = s:find( tag.start, i, true ) | |||
r = | else | ||
Fault( "missing " .. tag.stop ) | |||
end | |||
end -- while j | |||
r = r .. s:sub( i ) | |||
elseif m == 1 then | |||
r = f( r ) | |||
end | |||
end -- for m | |||
if tags[ 2 ].l then | |||
r = r:gsub( "<exportonly>.*</exportonly>", "" ) | |||
end | end | ||
return r | return r | ||
end -- fair() | end -- fair() | ||
| Zeile 330: | Zeile 396: | ||
local function faraway( alternatives ) | local function faraway( alternatives ) | ||
-- Retrieve | -- Retrieve best language version from multilingual text | ||
-- Parameter: | -- Parameter: | ||
-- alternatives -- table, to be evaluated | -- alternatives -- table, to be evaluated | ||
| Zeile 339: | Zeile 405: | ||
local variants = { } | local variants = { } | ||
local r1, r2 | local r1, r2 | ||
for k, v in pairs( alternatives ) do | for k, v in pairs( alternatives ) do | ||
if type( v ) == "string" then | if type( v ) == "string" then | ||
v = mw.text.trim( v ) | v = mw.text.trim( v ) | ||
if v ~= "" then | if v ~= "" and type( k ) == "string" then | ||
k = k:lower() | |||
variants[ k ] = v | variants[ k ] = v | ||
n = n + 1 | n = n + 1 | ||
| Zeile 352: | Zeile 416: | ||
end -- for k, v | end -- for k, v | ||
if n > 0 then | if n > 0 then | ||
local Multilingual = Fetch( "Multilingual", true ) | |||
if | if Multilingual and | ||
type( Multilingual.i18n ) == "function" then | |||
local show, slang = Multilingual.i18n( variants ) | |||
if show then | |||
r1 = show | |||
variants[ slang ] = nil | |||
r2 = variants | |||
end | end | ||
end | end | ||
if not r1 then | if not r1 then | ||
Foreign() | |||
for k, v in pairs( variants ) do | for k, v in pairs( variants ) do | ||
if v | if n == 1 then | ||
r1 = v | |||
elseif Data.slang == k then | |||
variants[ k ] = nil | variants[ k ] = nil | ||
r1 = v | r1 = v | ||
r2 = variants | r2 = variants | ||
end | end | ||
end -- for k, v | end -- for k, v | ||
end | end | ||
if r2 then | if r2 and Multilingual then | ||
for k, v in pairs( r2 ) do | for k, v in pairs( r2 ) do | ||
if v and not Multilingual.isLang( k ) then | if v and not Multilingual.isLang( k, true ) then | ||
Fault( string.format( " | Fault( string.format( "%s <code>lang=%s</code>", | ||
"Invalid", | |||
k ) ) | k ) ) | ||
end | end | ||
| Zeile 413: | Zeile 453: | ||
local function | local function fashioned( about, asked, assign ) | ||
-- | -- Create description head | ||
-- Parameter: | |||
-- about -- table, supposed to contain description | |||
-- asked -- true, if mandatory description | |||
-- assign -- <block>, if to be equipped | |||
-- Returns <block>, with head, or nil | |||
local para = assign or mw.html.create( "div" ) | |||
local plus, r | |||
if about and about.description then | |||
if type( about.description ) == "string" then | |||
para:wikitext( about.description ) | |||
else | |||
para:wikitext( about.description[ 1 ] ) | |||
plus = mw.html.create( "ul" ) | |||
plus:css( "text-align", "left" ) | |||
for k, v in pairs( about.description[ 2 ] ) do | |||
plus:node( mw.html.create( "li" ) | |||
:node( mw.html.create( "code" ) | |||
:wikitext( k ) ) | |||
:node( mw.html.create( "br" ) ) | |||
:wikitext( fair( v ) ) ) | |||
end -- for k, v | |||
if Config.loudly then | |||
plus = mw.html.create( "div" ) | |||
:css( "background-color", | |||
"#" .. Config.debugmultilang ) | |||
:node( plus ) | |||
else | |||
plus:addClass( "templatedata-maintain" ) | |||
:css( "display", "none" ) | |||
if | |||
end | end | ||
end | end | ||
elseif Config.solo and asked then | |||
end | para:addClass( "error" ) | ||
end -- | :wikitext( Config.solo ) | ||
Data.less = true | |||
else | |||
para = false | |||
end | |||
if para then | |||
if plus then | |||
r = mw.html.create( "div" ) | |||
:node( para ) | |||
:node( plus ) | |||
else | |||
r = para | |||
end | |||
end | |||
return r | |||
end -- fashioned() | |||
local function | local function fatten( access ) | ||
-- | -- Create table row for sub-headline | ||
local | -- Parameter: | ||
local | -- access -- string, with name | ||
-- Returns <tr> | |||
local | local param = Data.tree.params[ access ] | ||
local sub, sort = access:match( "(=+)%s*(%S.*)$" ) | |||
if | local headline = mw.html.create( string.format( "h%d", #sub ) ) | ||
local r = mw.html.create( "tr" ) | |||
local td = mw.html.create( "td" ) | |||
:attr( "colspan", "5" ) | |||
:attr( "data-sort-value", "!" .. sort ) | |||
local s | |||
if param.style then | |||
s = type( param.style ) | |||
if s == "table" then | |||
td:css( param.style ) | |||
elseif s == "string" then | |||
td:cssText( param.style ) | |||
end | |||
end | |||
s = fashioned( param, false, headline ) | |||
if s then | |||
headline = s | |||
else | else | ||
headline:wikitext( sort ) | |||
end | end | ||
td:node( headline ) | |||
r:node( td ) | |||
return r | |||
end -- fatten() | |||
local function fathers() | |||
for k, v in pairs( | -- Merge params with inherited values | ||
local n = 0 | |||
local p = Data.params | |||
local t = Data.tree.params | |||
local p2, t2 | |||
for k, v in pairs( Data.heirs ) do | |||
n = n + 1 | |||
end -- for k, v | end -- for k, v | ||
if | for i = 1, n do | ||
if Data.heirs then | |||
for k, v in pairs( Data.heirs ) do | |||
if v and not Data.heirs[ v ] then | |||
if | n = n - 1 | ||
t[ k ].inherits = nil | |||
Data.heirs[ k ] = nil | |||
p2 = { } | |||
t2 = { } | |||
if p[ v ] then | |||
for k2, v2 in pairs( p[ v ] ) do | |||
p2[ k2 ] = v2 | |||
end -- for k2, v2 | |||
if p[ k ] then | |||
for k2, v2 in pairs( p[ k ] ) do | |||
if type( v2 ) ~= "nil" then | |||
p2[ k2 ] = v2 | |||
end | |||
end -- for k2, v2 | |||
end | |||
p[ k ] = p2 | |||
for k2, v2 in pairs( t[ v ] ) do | |||
t2[ k2 ] = v2 | |||
end -- for k2, v2 | |||
for k2, v2 in pairs( t[ k ] ) do | |||
if type( v2 ) ~= "nil" then | |||
t2[ k2 ] = v2 | |||
end | |||
end -- for k2, v2 | |||
t[ k ] = t2 | |||
else | |||
Fault( "No params[] inherits " .. v ) | |||
end | |||
end | |||
end -- for k, v | |||
end | |||
end -- i = 1, n | |||
if n > 0 then | |||
local s | |||
for k, v in pairs( Data.heirs ) do | |||
if v then | |||
if s then | |||
s = string.format( "%s | %s", s, k ) | |||
else | |||
s = "Circular inherits: " .. k | |||
end | end | ||
end | end | ||
end -- for k, v | end -- for k, v | ||
Fault( s ) | |||
end | end | ||
end -- | end -- fathers() | ||
local function | local function favorize() | ||
-- | -- Local customization issues | ||
local boole = { ["font-size"] = "125%" } | |||
local l, cx = pcall( mw.loadData, | |||
TemplateData.frame:getTitle() .. "/config" ) | |||
local scripting | |||
TemplateData.ltr = not mw.language.getContentLanguage():isRTL() | |||
local | if TemplateData.ltr then | ||
if | scripting = "left" | ||
else | |||
scripting = "right" | |||
end | |||
boole[ "margin-" .. scripting ] = "3em" | |||
Permit.boole = { [false] = { css = boole, | |||
lead = true, | |||
show = "☐" }, | |||
[true] = { css = boole, | |||
lead = true, | |||
show = "☑" } } | |||
Permit.css = { } | |||
for k, v in pairs( Permit.colors ) do | |||
if k == "tableheadbg" then | |||
k = "tablehead" | |||
end | end | ||
Permit.css[ k ] = { ["background-color"] = "#" .. v } | |||
end -- for k, v | |||
if type( cx ) == "table" then | |||
local c, s | |||
if type( cx.permit ) == "table" then | |||
if type( cx.permit.boole ) == "table" then | |||
end | if type( cx.permit.boole[ true ] ) == "table" then | ||
if | Permit.boole[ false ] = cx.permit.boole[ false ] | ||
end | |||
if type( cx.permit.boole[ true ] ) == "table" then | |||
Permit.boole[ true ] = cx.permit.boole[ true ] | |||
end | |||
end | |||
if type( cx.permit.css ) == "table" then | |||
end | for k, v in pairs( cx.permit.css ) do | ||
if type( v ) == "table" then | |||
Permit.css[ k ] = v | |||
end -- | end | ||
end -- for k, v | |||
end | |||
end | |||
for k, v in pairs( Config.basicCnf ) do | |||
s = type( cx[ k ] ) | |||
if s == "string" or s == "table" then | |||
Config[ v ] = cx[ k ] | |||
end | |||
end -- for k, v | |||
end | |||
if type( Config.subpage ) ~= "string" or | |||
type( Config.suffix ) ~= "string" then | |||
local got = mw.message.new( "templatedata-doc-subpage" ) | |||
local suffix | |||
if got:isDisabled() then | |||
suffix = "doc" | |||
else | |||
suffix = got:plain() | |||
end | |||
if type( Config.subpage ) ~= "string" then | |||
Config.subpage = string.format( "/%s$", suffix ) | |||
end | |||
if type( Config.suffix ) ~= "string" then | |||
Config.suffix = string.format( "%%s/%s", suffix ) | |||
end | |||
end | |||
end -- favorize() | |||
local function | local function feasible( about, at ) | ||
-- | -- Deal with suggestedvalues within parameter | ||
-- Parameter: | |||
-- about -- parameter details | |||
-- .suggestedvalues -- table|string|number, | |||
for | -- value and possibly description | ||
-- .code -- mandatory | |||
-- .label -- table|string | |||
-- .icon -- string | |||
s = | -- .class -- table|string | ||
-- .css -- table | |||
-- .style -- string | |||
-- .less -- true: suppress code | |||
-- .type | |||
-- at -- string, with parameter name | |||
if | -- Returns | ||
-- 1: mw.html object | |||
local | -- 2: sequence table with values, or nil | ||
local p = about.suggestedvalues | |||
local s = type( p ) | |||
local e, r1, r2, v | |||
table. | if s == "table" then | ||
if #p > 0 then | |||
for i = 1, #p do | |||
if | e = p[ i ] | ||
s = " | s = type( e ) | ||
Fault( string.format( s, | if s == "table" then | ||
if type( e.code ) == "string" then | |||
s = mw.text.trim( e.code ) | |||
if s == "" then | |||
e = nil | |||
else | |||
e.code = s | |||
end | |||
else | |||
e = nil | |||
s = string.format( "params.%s.%s[%d] %s", | |||
at, | |||
"suggestedvalues", | |||
i, | |||
"MISSING 'code:'" ) | |||
end | |||
elseif s == "string" then | |||
s = mw.text.trim( e ) | |||
if s == "" then | |||
e = nil | |||
s = string.format( "params.%s.%s[%d] EMPTY", | |||
at, "suggestedvalues", i ) | |||
Fault( s ) | |||
else | |||
e = { code = s } | |||
end | |||
elseif s == "number" then | |||
e = { code = tostring( e ) } | |||
else | |||
s = string.format( "params.%s.%s[%d] INVALID", | |||
at, "suggestedvalues", i ) | |||
Fault( s ) | |||
e = false | |||
end | |||
if e then | |||
v = v or { } | |||
table.insert( v, e ) | |||
end | |||
end -- for i | |||
else | |||
Fault( string.format( "params.%s.suggestedvalues %s", | |||
at, "NOT AN ARRAY" ) ) | |||
end | |||
elseif s == "string" then | |||
s = mw.text.trim( p ) | |||
if s ~= "" then | |||
v = { } | |||
table.insert( v, | |||
{ code = s } ) | |||
end | |||
elseif s == "number" then | |||
v = { } | |||
table.insert( v, | |||
{ code = tostring( p ) } ) | |||
end | |||
if v then | |||
local d, less, story, swift, t, u | |||
r1 = mw.html.create( "ul" ) | |||
r2 = { } | |||
for i = 1, #v do | |||
u = mw.html.create( "li" ) | |||
e = v[ i ] | |||
table.insert( r2, e.code ) | |||
story = false | |||
less = ( e.less == true ) | |||
if not less then | |||
swift = e.code | |||
if e.support then | |||
local scream, support | |||
s = type( e.support ) | |||
if s == "string" then | |||
support = e.support | |||
elseif s == "table" then | |||
support = faraway( e.support ) | |||
else | |||
scream = "INVALID" | |||
end | |||
if support then | |||
s = mw.text.trim( support ) | |||
if s == "" then | |||
scream = "EMPTY" | |||
elseif s:find( "[%[%]|%<%>]" ) then | |||
scream = "BAD PAGE" | |||
else | |||
support = s | |||
end | |||
end | |||
if scream then | |||
s = string.format( "params.%s.%s[%d].support %s", | |||
at, | |||
"suggestedvalues", | |||
i, | |||
scream ) | |||
Fault( s ) | |||
else | |||
swift = string.format( "[[:%s|%s]]", | |||
support, swift ) | |||
end | |||
end | |||
if about.type:sub( 1, 5 ) == "wiki-" and | |||
swift == e.code then | |||
local rooms = { file = 6, | |||
temp = 10, | |||
user = 2 } | |||
local ns = rooms[ about.type:sub( 6, 9 ) ] or 0 | |||
t = mw.title.makeTitle( ns, swift ) | |||
if t and t.exists then | |||
swift = string.format( "[[:%s|%s]]", | |||
t.prefixedText, swift ) | |||
end | end | ||
end | |||
u:node( mw.html.create( "code" ) | |||
:css( "white-space", "nowrap" ) | |||
:wikitext( swift ) ) | |||
end | |||
if e.class then | |||
s = type( e.class ) | |||
if s == "string" then | |||
u:addClass( e.class ) | |||
elseif s == "table" then | |||
for k, s in pairs( e.class ) do | |||
u:addClass( s ) | |||
end -- for k, s | |||
else | else | ||
s = " | s = string.format( "params.%s.%s[%d].class INVALID", | ||
Fault | at, "suggestedvalues", i ) | ||
Fault( s ) | |||
end | end | ||
end | end | ||
table. | if e.css then | ||
if type( e.css ) == "table" then | |||
u:css( e.css ) | |||
end -- i = | else | ||
s = string.format( "params.%s.%s[%d].css INVALID", | |||
at, "suggestedvalues", i ) | |||
Fault( s ) | |||
end | |||
end | |||
if e.style then | |||
if type( e.style ) == "string" then | |||
u:cssText( e.style ) | |||
local | else | ||
s = string.format( "params.%s.%s[%d].style INVALID", | |||
at, "suggestedvalues", i ) | |||
Fault( s ) | |||
end | |||
end | |||
if about.type == "wiki-file-name" and not e.icon then | |||
e.icon = e.code | |||
end | |||
if e.label then | |||
s = type( e.label ) | |||
if s == "string" then | |||
s = mw.text.trim( e.label ) | |||
if s == "" then | |||
s = string.format( "params.%s.%s[%d].label %s", | |||
at, | |||
"suggestedvalues", | |||
i, | |||
"EMPTY" ) | |||
Fault( s ) | |||
else | |||
story = s | |||
end | |||
elseif s == "table" then | |||
story = faraway( e.label ) | |||
else | |||
s = string.format( "params.%s.%s[%d].label INVALID", | |||
at, "suggestedvalues", i ) | |||
Fault( s ) | |||
end | |||
end | |||
s = false | |||
if type( e.icon ) == "string" then | |||
t = mw.title.makeTitle( 6, e.icon ) | |||
if t and t.file.exists then | |||
local g = mw.html.create( "span" ) | |||
s = string.format( "[[%s|16px]]", t.prefixedText ) | |||
g:attr( "role", "presentation" ) | |||
:wikitext( s ) | |||
s = tostring( g ) | |||
end | |||
end | |||
if not s and not less and e.label then | |||
s = mw.ustring.char( 0x2013 ) | |||
end | |||
if s then | |||
d = mw.html.create( "span" ) | |||
:wikitext( s ) | |||
if TemplateData.ltr then | |||
if not less then | |||
d:css( "margin-left", "0.5em" ) | |||
end | |||
if story then | |||
d:css( "margin-right", "0.5em" ) | |||
end | |||
else | |||
if not less then | |||
d:css( "margin-right", "0.5em" ) | |||
end | |||
if story then | |||
d:css( "margin-left", "0.5em" ) | |||
end | end | ||
end | |||
u:node( d ) | |||
end | |||
if story then | |||
u:wikitext( story ) | |||
end | |||
r1:newline() | |||
:node( u ) | |||
end -- for i | |||
end | |||
if not r1 then | |||
Fault( string.format( "params.%s.suggestedvalues INVALID", at ) ) | |||
r1 = mw.html.create( "code" ) | |||
:addClass( "error" ) | |||
:wikitext( "INVALID" ) | |||
end | |||
return r1, r2 | |||
end -- feasible() | |||
local function feat() | |||
-- Check and store parameter sequence | |||
if Data.source then | |||
local i = 0 | |||
local s | |||
for k, v in pairs( Data.tree.params ) do | |||
if i == 0 then | |||
Data.order = { } | |||
i = 1 | |||
s = k | |||
else | |||
i = 2 | |||
break -- for k, v | |||
end | |||
local | |||
for k, v in pairs( | |||
if | |||
else | |||
end | |||
end -- for k, v | end -- for k, v | ||
if | if i > 1 then | ||
local pointers = { } | |||
local points = { } | |||
local given = { } | |||
for k, v in pairs( Data.tree.params ) do | |||
i = facet( k, 1 ) | |||
if type( v ) == "table" then | |||
if type( v.label ) == "string" then | |||
s = mw.text.trim( v.label ) | |||
if s == "" then | |||
s = k | |||
end | |||
else | |||
s = k | |||
if | |||
end | end | ||
if | if given[ s ] then | ||
if given[ s ] == 1 then | |||
local scream = "Parameter label '%s' detected multiple times" | |||
Fault( string.format( scream, s ) ) | |||
given[ s ] = 2 | |||
end | end | ||
else | |||
given[ s ] = 1 | |||
end | end | ||
end | |||
if i then | |||
table.insert( points, i ) | |||
if | pointers[ i ] = k | ||
i = facet( k, i ) | |||
if i then | |||
s = "Parameter '%s' detected twice" | |||
Fault( string.format( s, k ) ) | |||
end | end | ||
else | else | ||
s = "Parameter '%s' not detected" | |||
Fault( string.format( s, k ) ) | |||
end | end | ||
end -- for k, v | |||
table.sort( points ) | |||
for i = 1, #points do | |||
table.insert( Data.order, pointers[ points[ i ] ] ) | |||
end -- i = 1, #points | |||
elseif s then | |||
table.insert( Data.order, s ) | |||
end | |||
end | end | ||
end -- feat() | |||
-- | local function feature( access ) | ||
-- Create table row for parameter, check and display violations | |||
-- Parameter: | |||
-- access -- string, with name | |||
-- Returns <tr> | |||
local mode, s, status | |||
local fine = function ( a ) | |||
s = mw.text.trim( a ) | |||
return a == s and | |||
a ~= "" and | |||
not a:find( "%|=\n" ) and | |||
not a:find( "%s%s" ) | |||
end | |||
local begin = mw.html.create( "td" ) | |||
local code = mw.html.create( "code" ) | |||
local desc = mw.html.create( "td" ) | |||
local eager = mw.html.create( "td" ) | |||
local legal = true | |||
local param = Data.tree.params[ access ] | |||
local ranking = { "required", "suggested", "optional", "deprecated" } | |||
local r = mw.html.create( "tr" ) | |||
local styles = "mw-templatedata-doc-param-" | |||
local sort, typed | |||
for k, v in pairs( param ) do | |||
if v == "" then | |||
param[ k ] = false | |||
end | end | ||
end -- for k, v | |||
-- | -- label | ||
sort = param.label or access | |||
if sort:match( "^%d+$" ) then | |||
begin:attr( "data-sort-value", | |||
string.format( "%05d", tonumber( sort ) ) ) | |||
end | end | ||
begin:css( "font-weight", "bold" ) | |||
:wikitext( sort ) | |||
-- name and aliases | |||
code:css( "font-size", "92%" ) | |||
:css( "white-space", "nowrap" ) | |||
:wikitext( access ) | |||
if not fine( access ) then | |||
code:addClass( "error" ) | |||
Fault( string.format( "Bad ID params.<code>%s</code>", access ) ) | |||
legal = false | |||
begin:attr( "data-sort-value", " " .. sort ) | |||
end | |||
code = mw.html.create( "td" ) | |||
:addClass( styles .. "name" ) | |||
:node( code ) | |||
if access:match( "^%d+$" ) then | |||
code:attr( "data-sort-value", | |||
string.format( "%05d", tonumber( access ) ) ) | |||
end | end | ||
if type( param.aliases ) == "table" then | |||
local lapsus, syn | |||
for k, v in pairs( param.aliases ) do | |||
code:tag( "br" ) | |||
if type( v ) == "string" then | |||
if not fine( v ) then | |||
lapsus = true | |||
code:node( mw.html.create( "span" ) | |||
:addClass( "error" ) | |||
:css( "font-style", "italic" ) | |||
:wikitext( "string" ) ) | |||
:wikitext( s ) | |||
else | |||
syn = mw.html.create( "span" ) | |||
:addClass( styles .. "alias" ) | |||
:css( "white-space", "nowrap" ) | |||
:wikitext( s ) | |||
code:node( syn ) | |||
end | |||
else | |||
lapsus = true | |||
code:node( mw.html.create( "code" ) | |||
:addClass( "error" ) | |||
:wikitext( type( v ) ) ) | |||
end | |||
end -- for k, v | |||
if lapsus then | |||
s = string.format( "params.<code>%s</code>.aliases", access ) | |||
Fault( factory( "invalid-value" ):gsub( "$1", s ) ) | |||
legal = false | |||
end | |||
end | end | ||
-- description etc. | |||
s = fashioned( param ) | |||
if s then | |||
desc:node( s ) | |||
end | |||
if param.style then | |||
if | s = type( param.style ) | ||
if s == "table" then | |||
desc:css( param.style ) | |||
elseif s == "string" then | |||
desc:cssText( param.style ) | |||
end | end | ||
end | |||
if param.suggestedvalues or | |||
param.default or | |||
param.example or | |||
param.autovalue then | |||
local details = { "suggestedvalues", | |||
"default", | |||
"example", | |||
"autovalue" } | |||
local dl = mw.html.create( "dl" ) | |||
local dd, section, show | |||
for i = 1, #details do | |||
s = details[ i ] | |||
show = param[ s ] | |||
if show then | |||
dd = mw.html.create( "dd" ) | |||
section = factory( "doc-param-" .. s ) | |||
if param.type == "boolean" and | |||
( show == "0" or show == "1" ) then | |||
:css( | local boole = Permit.boole[ ( show == "1" ) ] | ||
: | if boole.lead == true then | ||
dd:node( mw.html.create( "code" ) | |||
: | :wikitext( show ) ) | ||
:wikitext | :wikitext( " " ) | ||
end | |||
if type( boole.show ) == "string" then | |||
local v = mw.html.create( "span" ) | |||
:attr( "aria-hidden", "true" ) | |||
:wikitext( boole.show ) | |||
if boole.css then | |||
v:css( boole.css ) | |||
end | |||
dd:node( v ) | |||
end | |||
if type( boole.suffix ) == "string" then | |||
dd:wikitext( boole.suffix ) | |||
end | |||
if boole.lead == false then | |||
dd:wikitext( " " ) | |||
:node( mw.html.create( "code" ) | |||
:wikitext( show ) ) | |||
end | |||
elseif s == "suggestedvalues" then | |||
local html, values = feasible( param, access ) | |||
dd:newline() | |||
:node( html ) | |||
Data.params[ access ].suggestedvalues = values | |||
else | |||
dd:wikitext( show ) | |||
end | |||
dl:node( mw.html.create( "dt" ) | |||
:wikitext( section ) ) | |||
:node( dd ) | |||
end | end | ||
end -- i = 1, #details | |||
desc:node( dl ) | |||
end | |||
-- type | |||
if type( param.type ) == "string" then | |||
param.type = mw.text.trim( param.type ) | |||
if param.type == "" then | |||
param.type = false | |||
end | end | ||
end | end | ||
if param.type then | |||
s = Permit.types[ param.type ] | |||
typed = mw.html.create( "td" ) | |||
:addClass( styles .. "type" ) | |||
if s then | |||
if s == "string" then | |||
Data.params[ access ].type = s | |||
typed:wikitext( factory( "doc-param-type-" .. s ) ) | |||
:tag( "br" ) | |||
typed:node( mw.html.create( "span" ) | |||
:addClass( "error" ) | |||
:wikitext( param.type ) ) | |||
Data.lasting = true | |||
else | |||
local support = Config[ "support4" .. param.type ] | |||
s = factory( "doc-param-type-" .. param.type ) | |||
if support then | |||
s = string.format( "[[%s|%s]]", support, s ) | |||
end | |||
typed:wikitext( s ) | |||
end | |||
else | |||
Data.params[ access ].type = "unknown" | |||
typed:addClass( "error" ) | |||
:wikitext( "INVALID" ) | |||
s = string.format( "params.<code>%s</code>.type", access ) | |||
Fault( factory( "invalid-value" ):gsub( "$1", s ) ) | |||
legal = false | |||
end | end | ||
if | else | ||
typed = mw.html.create( "td" ) | |||
:wikitext( factory( "doc-param-type-unknown" ) ) | |||
Data.params[ access ].type = "unknown" | |||
if param.default then | |||
Data.params[ access ].default = nil | |||
Fault( "Default value requires <code>type</code>" ) | |||
legal = false | |||
end | end | ||
end | end | ||
-- status | |||
if param.required then | |||
mode = 1 | |||
if param.autovalue then | |||
Fault( string.format( "autovalued <code>%s</code> required", | |||
access ) ) | |||
legal = false | |||
end | |||
if param.default then | |||
Fault( string.format( "Defaulted <code>%s</code> required", | |||
access ) ) | |||
legal = false | |||
end | end | ||
if param.deprecated then | |||
Fault( string.format( "Required deprecated <code>%s</code>", | |||
access ) ) | |||
legal = false | |||
if | |||
end | end | ||
elseif param.deprecated then | |||
mode = 4 | |||
elseif param.suggested then | |||
if | mode = 2 | ||
else | |||
mode = 3 | |||
end | |||
status = ranking[ mode ] | |||
ranking = factory( "doc-param-status-" .. status ) | |||
if mode == 1 or mode == 4 then | |||
ranking = mw.html.create( "span" ) | |||
:css( "font-weight", "bold" ) | |||
:wikitext( ranking ) | |||
if type( param.deprecated ) == "string" then | |||
ranking:tag( "br" ) | |||
ranking:wikitext( param.deprecated ) | |||
end | end | ||
if | if param.suggested and mode == 4 then | ||
s = string.format( "Suggesting deprecated <code>%s</code>", | |||
access ) | |||
Fault( s ) | |||
legal = false | |||
end | end | ||
end | end | ||
eager:attr( "data-sort-value", tostring( mode ) ) | |||
:node( ranking ) | |||
:addClass( string.format( "%sstatus-%s", | |||
styles, status ) ) | |||
-- <tr> | |||
r:attr( "id", "templatedata:" .. mw.uri.anchorEncode( access ) ) | |||
local function | :css( Permit.css[ status ] ) | ||
-- | :addClass( styles .. status ) | ||
-- Returns < | :node( begin ) | ||
:node( code ) | |||
:node( desc ) | |||
:node( typed ) | |||
:node( eager ) | |||
:newline() | |||
if not legal then | |||
r:css( "border", "#FF0000 3px solid" ) | |||
end | |||
return r | |||
end -- feature() | |||
local function features() | |||
-- Create <table> for parameters | |||
-- Returns <table>, or nil | |||
local r | local r | ||
if Data. | if Data.tree and Data.tree.params then | ||
local tbl = mw.html.create( "table" ) | |||
local tr = mw.html.create( "tr" ) | |||
feat() | |||
if Data.order and #Data.order > 1 then | |||
tbl:addClass( "sortable" ) | |||
end | |||
if type( Config.classTable ) == "table" then | |||
for k, v in pairs( Config.classTable ) do | |||
for | tbl:addClass( v ) | ||
end -- for k, v | |||
end | |||
if type( Config.cssTable ) == "table" then | |||
tbl:css( Config.cssTable ) | |||
end | |||
tr:node( mw.html.create( "th" ) | |||
:attr( "colspan", "2" ) | |||
:css( Permit.css.tablehead ) | |||
:wikitext( factory( "doc-param-name" ) ) ) | |||
:node( mw.html.create( "th" ) | |||
:css( Permit.css.tablehead ) | |||
:wikitext( factory( "doc-param-desc" ) ) ) | |||
:node( mw.html.create( "th" ) | |||
:css( Permit.css.tablehead ) | |||
:wikitext( factory( "doc-param-type" ) ) ) | |||
:node( mw.html.create( "th" ) | |||
:css( Permit.css.tablehead ) | |||
:wikitext( factory( "doc-param-status" ) ) ) | |||
tbl:newline() | |||
-- :node( mw.html.create( "thead" ) | |||
:node( tr ) | |||
-- ) | |||
:newline() | |||
if Data.order then | |||
local leave, s | |||
for i = 1, #Data.order do | |||
s = Data.order[ i ] | |||
if s:sub( 1, 1 ) == "=" then | |||
leave = true | |||
tbl:node( fatten( s ) ) | |||
Data.order[ i ] = false | |||
elseif s:match( "[=|]" ) then | |||
Fault( string.format( "Bad param <code>%s</code>", | |||
s ) ) | |||
else | |||
tbl:node( feature( s ) ) | |||
end | |||
end -- for i = 1, #Data.order | |||
if leave then | |||
for i = #Data.order, 1, -1 do | |||
if not Data.order[ i ] then | |||
table.remove( Data.order, i ) | |||
end | end | ||
end -- for i = #Data.order, 1, -1 | |||
end | |||
end | end | ||
Data.tag.paramOrder = Data.order | |||
end | end | ||
if Config.cssTabWrap or Data.scroll then | |||
r = mw.html.create( "div" ) | |||
if | if type( Config.cssTabWrap ) == "table" then | ||
r:css( Config.cssTabWrap ) | |||
if | elseif type( Config.cssTabWrap ) == "string" then | ||
-- deprecated | |||
r:cssText( Config.cssTabWrap ) | |||
end | |||
if Data.scroll then | |||
r:css( "height", Data.scroll ) | |||
:css( "overflow", "auto" ) | |||
end | |||
r:node( tbl ) | |||
else | |||
if | r = tbl | ||
if | end | ||
end | |||
return r | |||
end -- features() | |||
local function fellow( any, assigned, at ) | |||
-- Check sets[] parameter and issue error message, if necessary | |||
-- Parameter: | |||
-- any -- should be number | |||
-- assigned -- parameter name | |||
-- at -- number, of set | |||
local s | |||
if type( any ) ~= "number" then | |||
s = "<code>sets[%d].params[%s]</code>??" | |||
Fault( string.format( s, | |||
at, | |||
mw.text.nowiki( tostring( any ) ) ) ) | |||
elseif type( assigned ) == "string" then | |||
if not Data.got.params[ assigned ] then | |||
s = "<code>sets[%d].params %s</code> is undefined" | |||
Fault( string.format( s, at, assigned ) ) | |||
end | |||
else | |||
s = "<code>sets[%d].params[%d] = %s</code>??" | |||
Fault( string.format( s, k, type( assigned ) ) ) | |||
end | |||
end -- fellow() | |||
local function fellows() | |||
-- Check sets[] and issue error message, if necessary | |||
local s | |||
if type( Data.got.sets ) == "table" then | |||
if type( Data.got.params ) == "table" then | |||
for k, v in pairs( Data.got.sets ) do | |||
if type( k ) == "number" then | |||
if type( v ) == "table" then | |||
for ek, ev in pairs( v ) do | |||
if ek == "label" then | |||
s = type( ev ) | |||
if s ~= "string" and | |||
s ~= "table" then | |||
s = "<code>sets[%d].label</code>??" | |||
Fault( string.format( s, k ) ) | |||
end | end | ||
elseif ek == "params" and | |||
type( ev ) == "table" then | |||
for pk, pv in pairs( ev ) do | |||
fellow( pk, pv, k ) | |||
end -- for pk, pv | |||
else | else | ||
ek = mw.text.nowiki( tostring( ek ) ) | |||
s = "<code>sets[%d][%s]</code>??" | |||
Fault( string.format( s, k, ek ) ) | |||
end | end | ||
end | end -- for ek, ev | ||
else | |||
k = mw.text.nowiki( tostring( k ) ) | |||
v = mw.text.nowiki( tostring( v ) ) | |||
s = string.format( "<code>sets[%s][%s]</code>??", | |||
k, v ) | |||
Fault( s ) | |||
end | |||
else | |||
k = mw.text.nowiki( tostring( k ) ) | |||
s = string.format( "<code>sets[%s]</code> ?????", k ) | |||
Fault( s ) | |||
end | |||
end -- for k, v | |||
else | |||
s = "<code>params</code> required for <code>sets</code>" | |||
Fault( s ) | |||
end | |||
else | |||
s = "<code>sets</code> needs to be of <code>object</code> type" | |||
Fault( s ) | |||
end | |||
end -- fellows() | |||
local function finalize( advance ) | |||
-- Wrap presentation into frame | |||
-- Parameter: | |||
-- advance -- true, for nice | |||
-- Returns string | |||
local r, lapsus | |||
if Data.div then | |||
r = tostring( Data.div ) | |||
elseif Data.strip then | |||
r = Data.strip | |||
else | |||
lapsus = true | |||
r = "" | |||
end | |||
r = r .. failures() | |||
if Data.source then | |||
local live = ( advance or lapsus ) | |||
if not live then | |||
live = TemplateData.frame:preprocess( "{{REVISIONID}}" ) | |||
live = ( live == "" ) | |||
end | |||
if live then | |||
r = r .. fancy( advance, lapsus ) | |||
end | |||
end | |||
return r | |||
end -- finalize() | |||
end | |||
end -- | |||
local function | local function find() | ||
-- | -- Find JSON data within page source (title) | ||
-- Returns | -- Returns string, or nil | ||
local | local s = Data.title:getContent() | ||
local | local i, j = s:find( "<templatedata>", 1, true ) | ||
if | local r | ||
if i then | |||
local k = s:find( "</templatedata>", j, true ) | |||
if k then | |||
r = mw.text.trim( s:sub( j + 1, k - 1 ) ) | |||
if | end | ||
end | |||
return r | |||
end -- find() | |||
local function flat( adjust ) | |||
-- Remove formatting from text string for VE | |||
-- Parameter: | |||
-- arglist -- string, to be stripped, or nil | |||
-- Returns string, or nil | |||
local r | |||
if adjust then | |||
r = adjust:gsub( "\n", " " ) | |||
if r:find( "<noexport>", 1, true ) then | |||
r = r:gsub( "<noexport>.*</noexport>", "" ) | |||
end | |||
if r:find( "<exportonly>", 1, true ) then | |||
r = r:gsub( "</?exportonly>", "" ) | |||
end | |||
if r:find( "''", 1, true ) then | |||
r = r:gsub( "'''", "" ):gsub( "''", "" ) | |||
end | |||
if r:find( "<", 1, true ) then | |||
local Text = Fetch( "Text" ) | |||
r = Text.getPlain( r:gsub( "<br */?>", "\r\n" ) ) | |||
end | |||
if r:find( "[", 1, true ) then | |||
local WLink = Fetch( "WLink" ) | |||
if WLink.isBracketedURL( r ) then | |||
r = r:gsub( "%[([hf]tt?ps?://%S+) [^%]]+%]", "%1" ) | |||
end | end | ||
r = WLink.getPlain( r ) | |||
end | |||
if r:find( "&", 1, true ) then | |||
r = mw.text.decode( r ) | |||
if r:find( "­", 1, true ) then | |||
r = r:gsub( "­", "" ) | |||
end | end | ||
end | |||
end | |||
return r | |||
end -- flat() | |||
local function flush() | |||
-- JSON encode narrowed input; obey unnamed (numerical) parameters | |||
-- Returns <templatedata> JSON string | |||
local r | |||
if Data.tag then | |||
r = mw.text.jsonEncode( Data.tag ):gsub( "%}$", "," ) | |||
else | |||
r = "{" | |||
end | |||
r = r .. "\n\"params\":{" | |||
if Data.order then | |||
local sep = "" | |||
local s | |||
for i = 1, #Data.order do | |||
s = Data.order[ i ] | |||
r = string.format( "%s%s\n%s:%s", | |||
r, | |||
sep, | |||
mw.text.jsonEncode( s ), | |||
mw.text.jsonEncode( Data.params[ s ] ) ) | |||
sep = ",\n" | |||
end -- for i = 1, #Data.order | |||
end | |||
r = r .. "\n}\n}" | |||
if | return r | ||
end -- flush() | |||
local function focus( access ) | |||
-- Check components; focus multilingual description, build trees | |||
-- Parameter: | |||
-- access -- string, name of parameter, nil for root | |||
local f = function ( a, at ) | |||
local r | |||
if at then | |||
r = string.format( "<code>params.%s</code>", at ) | |||
else | else | ||
r = "''root''" | |||
end | end | ||
if a then | |||
if | r = string.format( "%s<code>.%s</code>", r, a ) | ||
end | end | ||
return r | |||
end | end | ||
local parent | |||
if access then | |||
parent = Data.got.params[ access ] | |||
else | |||
parent = Data.got | |||
end | |||
if type( parent ) == "table" then | |||
local elem, got, permit, s, scope, slot, tag, target | |||
if access then | |||
permit = Permit.params | |||
if | if type( access ) == "number" then | ||
slot = tostring( access ) | |||
else | |||
slot = access | |||
end | end | ||
else | |||
permit = Permit.root | |||
end | end | ||
if | for k, v in pairs( parent ) do | ||
scope = permit[ k ] | |||
if scope then | |||
s = type( v ) | |||
if s == "string" and k ~= "format" then | |||
v = mw.text.trim( v ) | |||
end | |||
if scope:find( s, 1, true ) then | |||
if scope:find( "I18N", 1, true ) then | |||
if s == "string" then | |||
elem = fair( v ) | |||
elseif s == "table" then | |||
local translated | |||
v, translated = faraway( v ) | |||
if v then | |||
if translated and | |||
k == "description" then | |||
elem = { [ 1 ] = fair( v ), | |||
[ 2 ] = translated } | |||
else | |||
end | elem = fair( v ) | ||
end | |||
else | |||
elem = false | |||
end | |||
end | |||
if type( v ) == "string" then | |||
if k == "deprecated" then | |||
if v == "1" then | |||
v = true | |||
elseif v == "0" then | |||
v = false | |||
end | |||
elem = v | |||
elseif scope:find( "nowiki", 1, true ) then | |||
elem = mw.text.nowiki( v ) | |||
elem = elem:gsub( " \n", "<br>" ) | |||
v = v:gsub( string.char( 13 ), "" ) | |||
else | |||
v = flat( v ) | |||
end | |||
elseif s == "boolean" then | |||
if scope:find( "boolean", 1, true ) then | |||
elem = v | |||
else | |||
s = "Type <code>boolean</code> bad for " | |||
.. f( k, slot ) | |||
Fault( s ) | |||
end | |||
end | |||
else | |||
if k == "params" and not access then | |||
v = nil | |||
elem = nil | |||
elseif k == "format" and not access then | |||
elem = mw.text.decode( v ) | |||
v = nil | |||
elseif k == "inherits" then | |||
elem = v | |||
if not Data.heirs then | |||
Data.heirs = { } | |||
end | |||
Data.heirs[ slot ] = v | |||
v = nil | |||
elseif k == "style" then | |||
elem = v | |||
v = nil | |||
elseif s == "string" then | |||
v = mw.text.nowiki( v ) | |||
elem = v | |||
else | |||
elem = v | |||
end | |||
end | |||
if type( elem ) ~= "nil" then | |||
if not target then | |||
if access then | |||
if not Data.tree.params then | |||
Data.tree.params = { } | |||
end | |||
Data.tree.params[ slot ] = { } | |||
target = Data.tree.params[ slot ] | |||
else | |||
Data.tree = { } | |||
target = Data.tree | |||
end | |||
end | |||
target[ k ] = elem | |||
elem = false | |||
end | |||
if type( v ) ~= "nil" then | |||
if not tag then | |||
if access then | |||
if type( v ) == "string" and | |||
v.sub( 1, 1 ) == "=" then | |||
v = nil | |||
else | |||
if not Data.params then | |||
Data.params = { } | |||
end | |||
Data.params[ slot ] = { } | |||
tag = Data.params[ slot ] | |||
end | |||
else | |||
Data.tag = { } | |||
tag = Data.tag | |||
end | |||
end | |||
if type( v ) ~= "nil" and | |||
k ~= "suggestedvalues" then | |||
tag[ k ] = v | |||
end | |||
end | |||
else | |||
s = string.format( "Type <code>%s</code> bad for %s", | |||
scope, f( k, slot ) ) | |||
Fault( s ) | |||
end | |||
else | |||
Fault( "Unknown component " .. f( k, slot ) ) | |||
end | end | ||
end -- for k, v | |||
if not access and Data.got.sets then | |||
fellows() | |||
end | end | ||
else | |||
Fault( f() .. " needs to be of <code>object</code> type" ) | |||
end | end | ||
end -- focus() | |||
local function format() | |||
local | -- Build formatted element | ||
-- Returns <inline> | |||
local source = Data.tree.format:lower() | |||
local r, s | |||
if source == "inline" or source == "block" then | |||
r = mw.html.create( "i" ) | |||
:wikitext( source ) | |||
else | |||
local code | |||
if source:find( "|", 1, true ) then | |||
local scan = "^[\n ]*%{%{[\n _]*|[\n _]*=[\n _]*%}%}[\n ]*$" | |||
if source:match( scan ) then | |||
code = source:gsub( "\n", "N" ) | |||
else | else | ||
s = mw.text.nowiki( source ):gsub( "\n", "\n" ) | |||
s = tostring( mw.html.create( "code" ) | |||
:wikitext( s ) ) | |||
Fault( "Invalid format " .. s ) | |||
source = false | |||
end | end | ||
else | |||
local words = mw.text.split( source, "%s+" ) | |||
local show, start, support, unknown | |||
for i = 1, #words do | |||
s = words[ i ] | |||
if i == 1 then | |||
start = s | |||
end | |||
support = Permit.builder[ s ] | |||
if support == start or | |||
support == "*" then | |||
Permit.builder[ s ] = true | |||
elseif s:match( "^[1-9]%d?" ) and | |||
Permit.builder.align then | |||
Permit.builder.align = tonumber( s ) | |||
else | |||
if unknown then | |||
unknown = string.format( "%s %s", unknown, s ) | |||
else | |||
unknown = s | |||
end | |||
end | |||
end -- i = 1, #words | |||
if unknown then | |||
s = tostring( mw.html.create( "code" ) | |||
:css( "white-space", "nowrap" ) | |||
:wikitext( s ) ) | |||
Fault( "Unknown/misplaced format keyword " .. s ) | |||
source = false | |||
start = false | |||
end | |||
if start == "inline" then | |||
if Permit.builder.half == true then | |||
show = "inline half" | |||
code = "{{_ |_=_}}" | |||
elseif Permit.builder.grouped == true then | |||
show = "inline grouped" | |||
code = "{{_ | _=_}}" | |||
elseif Permit.builder.spaced == true then | |||
show = "inline spaced" | |||
code = "{{_ | _ = _ }}" | |||
end | |||
if Permit.builder.newlines == true then | |||
show = show or "inline" | |||
code = code or "{{_|_=_}}" | |||
show = show .. " newlines" | |||
code = string.format( "N%sN", code ) | |||
end | |||
elseif start == "block" then | |||
local space = "" -- amid "|" and name | |||
local spaced = " " -- preceding "=" | |||
local spacer = " " -- following "=" | |||
local suffix = "N" -- closing "}}" on new line | |||
show = "block" | |||
if Permit.builder.indent == true then | |||
start = " " | |||
show = "block indent" | |||
else | |||
start = "" | |||
end | |||
if Permit.builder.compressed == true then | |||
spaced = "" | |||
spacer = "" | |||
show = show .. " compressed" | |||
if Permit.builder.last == true then | |||
--end | show = show .. " last" | ||
else | |||
suffix = "" | |||
end | |||
else | |||
if Permit.builder.lead == true then | |||
show = show .. " lead" | |||
space = " " | |||
end | |||
if type( Permit.builder.align ) ~= "string" then | |||
local n | |||
s = " align" | |||
if Permit.builder.align == true then | |||
n = 0 | |||
if type( Data.got ) == "table" and | |||
type( Data.got.params ) == "table" then | |||
for k, v in pairs( Data.got.params ) do | |||
if type( v ) == "table" and | |||
not v.deprecated and | |||
type( k ) == "string" then | |||
k = mw.ustring.len( k ) | |||
if k > n then | |||
n = k | |||
end | |||
end | |||
end -- for k, v | |||
end | |||
else | |||
n = Permit.builder.align | |||
if type( n ) == "number" and n > 1 then | |||
s = string.format( "%s %d", s, n ) | |||
else | |||
n = 0 -- How comes? | |||
end | |||
end | |||
if n > 1 then | |||
spaced = string.rep( "_", n - 1 ) .. " " | |||
end | |||
show = show .. s | |||
elseif Permit.builder.after == true then | |||
spaced = "" | |||
show = show .. " after" | |||
elseif Permit.builder.dense == true then | |||
spaced = "" | |||
spacer = "" | |||
show = show .. " dense" | |||
end | |||
if Permit.builder.last == true then | |||
suffix = spacer | |||
show = show .. " last" | |||
end | |||
end | |||
code = string.format( "N{{_N%s|%s_%s=%s_%s}}N", | |||
start, | |||
space, | |||
spaced, | |||
spacer, | |||
suffix ) | |||
if show == "block" then | |||
show = "block newlines" | |||
end | |||
end | |||
if show then | |||
r = mw.html.create( "span" ) | |||
:wikitext( show ) | |||
end | |||
end | |||
if code then | |||
source = code:gsub( "N", "\n" ) | |||
code = mw.text.nowiki( code ):gsub( "N", "\n" ) | |||
code = mw.html.create( "code" ) | |||
:css( "margin-left", "1em" ) | |||
:css( "margin-right", "1em" ) | |||
:wikitext( code ) | |||
if r then | |||
r = mw.html.create( "span" ) | |||
:node( r ) | |||
:node( code ) | |||
else | |||
r = code | |||
end | |||
end | |||
end | |||
if source and Data.tag then | |||
Data.tag.format = source | |||
end | |||
return r | |||
end -- format() | |||
local function formatter() | |||
-- Build presented documentation | |||
-- Returns <div> | |||
local r = mw.html.create( "div" ) | |||
local x = fashioned( Data.tree, true, r ) | |||
local s | |||
if x then | |||
r = x | |||
end | |||
if Data.leading then | |||
local toc = mw.html.create( "div" ) | |||
local shift | |||
if Config.suppressTOCnum then | |||
toc:addClass( Config.suppressTOCnum ) | |||
if type( Config.stylesTOCnum ) == "string" then | |||
local src = Config.stylesTOCnum .. "/styles.css" | |||
s = TemplateData.frame:extensionTag( "templatestyles", | |||
nil, | |||
{ src = src } ) | |||
r:newline() | |||
:node( s ) | |||
end | |||
end | |||
toc:css( "margin-top", "0.5em" ) | |||
:wikitext( "__TOC__" ) | |||
if Data.sibling then | |||
local block = mw.html.create( "div" ) | |||
if TemplateData.ltr then | |||
shift = "right" | |||
else | |||
shift = "left" | |||
end | |||
block:css( "float", shift ) | |||
:wikitext( Data.sibling ) | |||
r:newline() | |||
:node( block ) | |||
:newline() | |||
end | |||
r:newline() | |||
:node( toc ) | |||
:newline() | |||
if shift then | |||
r:node( mw.html.create( "div" ) | |||
:css( "clear", shift ) ) | |||
:newline() | |||
end | |||
end | |||
s = features() | |||
if s then | |||
if Data.leading then | |||
r:node( mw.html.create( "h" .. Config.nested ) | |||
:wikitext( factory( "doc-params" ) ) ) | |||
:newline() | |||
end | |||
r:node( s ) | |||
end | |||
if Data.shared then | |||
local global = mw.html.create( "div" ) | |||
:attr( "id", "templatedata-global" ) | |||
local shift | |||
if TemplateData.ltr then | |||
shift = "right" | |||
else | |||
shift = "left" | |||
end | |||
global:css( "float", shift ) | |||
:wikitext( string.format( "[[%s|%s]]", | |||
Data.shared, "Global" ) ) | |||
r:newline() | |||
:node( global ) | |||
end | |||
if Data.tree and Data.tree.format then | |||
local e = format() | |||
if e then | |||
local show = "Format" | |||
if Config.supportFormat then | |||
show = string.format( "[[%s|%s]]", | |||
Config.supportFormat, show ) | |||
end | |||
r:node( mw.html.create( "p" ) | |||
:wikitext( show .. ": " ) | |||
:node( e ) ) | |||
end | |||
end | |||
return r | |||
end -- formatter() | |||
local function free() | |||
-- Remove JSON comment lines | |||
if Data.source:find( "//", 1, true ) then | |||
Data.source:gsub( "([{,\"'])(%s*\n%s*//.*\n%s*)([{},\"'])", | |||
"%1%3" ) | |||
end | |||
end -- free() | |||
local function full() | |||
-- Build survey table from JSON data, append invisible <templatedata> | |||
Data.div = mw.html.create( "div" ) | |||
:addClass( "mw-templatedata-doc-wrap" ) | |||
focus() | |||
if Data.tag then | |||
if type( Data.got.params ) == "table" then | |||
for k, v in pairs( Data.got.params ) do | |||
focus( k ) | |||
end -- for k, v | |||
if Data.heirs then | |||
fathers() | |||
end | |||
end | |||
end | |||
Data.div:node( formatter() ) | |||
if not Data.lazy then | |||
Data.slim = flush() | |||
if TemplateData.frame then | |||
local div = mw.html.create( "div" ) | |||
local tdata = { [ 1 ] = "templatedata", | |||
[ 2 ] = Data.slim } | |||
Data.strip = TemplateData.frame:callParserFunction( "#tag", | |||
tdata ) | |||
div:wikitext( Data.strip ) | |||
if Config.loudly then | |||
Data.div:node( mw.html.create( "hr" ) | |||
:css( { height = "7ex" } ) ) | |||
else | |||
div:css( "display", "none" ) | |||
end | |||
Data.div:node( div ) | |||
end | |||
end | |||
if Data.lasting then | |||
Fault( "deprecated type syntax" ) | |||
end | |||
if Data.less then | |||
Fault( Config.solo ) | |||
end | |||
end -- full() | |||
local function furnish( adapt, arglist ) | |||
-- Analyze transclusion | |||
-- Parameter: | |||
-- adapt -- table, #invoke parameters | |||
-- arglist -- table, template parameters | |||
-- Returns string | |||
local source | |||
favorize() | |||
-- deprecated: | |||
for k, v in pairs( Config.basicCnf ) do | |||
if adapt[ k ] and adapt[ k ] ~= "" then | |||
Config[ v ] = adapt[ k ] | |||
end | |||
end -- for k, v | |||
if arglist.heading and arglist.heading:match( "^[3-6]$" ) then | |||
Config.nested = arglist.heading | |||
else | |||
Config.nested = "2" | |||
end | |||
Config.loudly = faculty( arglist.debug or adapt.debug ) | |||
Data.lazy = faculty( arglist.lazy ) and not Config.loudly | |||
Data.leading = faculty( arglist.TOC ) | |||
if Data.leading and arglist.TOCsibling then | |||
Data.sibling = mw.text.trim( arglist.TOCsibling ) | |||
end | |||
if arglist.lang then | |||
Data.slang = arglist.lang:lower() | |||
elseif adapt.lang then | |||
Data.slang = adapt.lang:lower() | |||
end | |||
if arglist.JSON then | |||
source = arglist.JSON | |||
elseif arglist.Global then | |||
source = TemplateData.getGlobalJSON( arglist.Global, | |||
arglist.Local ) | |||
elseif arglist[ 1 ] then | |||
local s = mw.text.trim( arglist[ 1 ] ) | |||
local start = s:sub( 1, 1 ) | |||
if start == "<" then | |||
Data.strip = s | |||
elseif start == "{" then | |||
source = s | |||
elseif mw.ustring.sub( s, 1, 8 ) == | |||
mw.ustring.char( 127, 39, 34, 96, 85, 78, 73, 81 ) then | |||
Data.strip = s | |||
end | |||
end | |||
if type( arglist.vertical ) == "string" and | |||
arglist.vertical:match( "^%d*%.?%d+[emprx]+$" ) then | |||
Data.scroll = arglist.vertical | |||
end | |||
if not source then | |||
Data.title = mw.title.getCurrentTitle() | |||
source = find() | |||
if not source and | |||
not Data.title.text:match( Config.subpage ) then | |||
local s = string.format( Config.suffix, | |||
Data.title.prefixedText ) | |||
Data.title = mw.title.new( s ) | |||
if Data.title.exists then | |||
source = find() | |||
end | |||
end | |||
end | |||
if not Data.lazy then | |||
if not Data.title then | |||
Data.title = mw.title.getCurrentTitle() | |||
end | |||
Data.lazy = Data.title.text:match( Config.subpage ) | |||
end | |||
if type( source ) == "string" then | |||
TemplateData.getPlainJSON( source ) | |||
end | |||
return finalize( faculty( arglist.source ) ) | |||
end -- furnish() | |||
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 | local r | ||
if since == "wikidata" then | if last or link or linked or since == "wikidata" then | ||
local item = | local item = Failsafe.item | ||
since = false | since = false | ||
if type( item ) == "number" and item > 0 then | if type( item ) == "number" and item > 0 then | ||
local | 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 ) | |||
end | if type( vsn ) == "table" and | ||
end | type( vsn.value ) == "string" and | ||
end | vsn.value ~= "" then | ||
end | if last and vsn.value == Failsafe.serial then | ||
if | r = false | ||
if not since or since <= | elseif linked then | ||
r = | if mw.title.getCurrentTitle().prefixedText | ||
else | == mw.wikibase.getSitelink( suited ) then | ||
r = false | r = false | ||
end | else | ||
end | r = suited | ||
return r | end | ||
end -- TemplateData. | else | ||
r = vsn.value | |||
end | |||
end | |||
TemplateData.getPlainJSON = function ( adapt ) | end | ||
-- Reduce enhanced JSON data to plain text localized JSON | end | ||
-- Parameter: | end | ||
-- adapt -- string, with enhanced JSON | end | ||
-- Returns string, or not | if type( r ) == "nil" then | ||
if type( adapt ) == "string" then | if not since or since <= Failsafe.serial then | ||
local | r = Failsafe.serial | ||
Data.source = adapt | else | ||
free() | r = false | ||
lucky, Data.got = pcall( mw.text.jsonDecode, Data.source ) | end | ||
if | end | ||
full() | return r | ||
if Data. | end -- Failsafe.failsafe() | ||
TemplateData.getGlobalJSON = function ( access, adapt ) | |||
-- Retrieve TemplateData from a global repository (JSON) | |||
-- Parameter: | |||
-- access -- string, with page specifier (on WikiMedia Commons) | |||
-- adapt -- JSON string or table with local overrides | |||
-- Returns true, if succeeded | |||
local plugin = Fetch( "/global" ) | |||
local r | |||
if type( plugin ) == "table" and | |||
type( plugin.fetch ) == "function" then | |||
local s, got = plugin.fetch( access, adapt ) | |||
if got then | |||
Data.got = got | |||
Data.order = got.paramOrder | |||
Data.shared = s | |||
r = true | |||
full() | |||
else | |||
Fault( s ) | |||
end | |||
end | |||
return r | |||
end -- TemplateData.getGlobalJSON() | |||
TemplateData.getPlainJSON = function ( adapt ) | |||
-- Reduce enhanced JSON data to plain text localized JSON | |||
-- Parameter: | |||
-- adapt -- string, with enhanced JSON | |||
-- Returns string, or not | |||
if type( adapt ) == "string" then | |||
local JSONutil = Fetch( "JSONutil", true ) | |||
Data.source = adapt | |||
free() | |||
if JSONutil then | |||
local Multilingual = Fetch( "Multilingual", true ) | |||
local f | |||
if Multilingual then | |||
f = Multilingual.i18n | |||
end | |||
Data.got = JSONutil.fetch( Data.source, true, f ) | |||
else | |||
local lucky | |||
lucky, Data.got = pcall( mw.text.jsonDecode, Data.source ) | |||
end | |||
if type( Data.got ) == "table" then | |||
full() | |||
elseif not Data.strip then | |||
local scream = type( Data.got ) | |||
if scream == "string" then | |||
scream = Data.got | |||
else | |||
scream = "Data.got: " .. scream | |||
end | end | ||
Fault( "fatal JSON error: " .. scream ) | |||
Fault( "fatal JSON error: " .. | |||
end | end | ||
end | end | ||
| Zeile 1.551: | Zeile 2.242: | ||
end | end | ||
return r | return r | ||
end -- p.f | end -- p.f | ||
p.failsafe = function ( frame ) | p.failsafe = function ( frame ) | ||
| Zeile 1.568: | Zeile 2.259: | ||
end | end | ||
end | end | ||
return | return Failsafe.failsafe( since ) or "" | ||
end -- p.failsafe | end -- p.failsafe | ||
p.TemplateData = function () | p.TemplateData = function () | ||
Aktuelle Version vom 27. März 2026, 02:48 Uhr
Die Dokumentation für dieses Modul kann unter Modul:TemplateData/doc erstellt werden
local TemplateData = { suite = "TemplateData",
serial = "2021-07-05",
item = 46997995 }
--[==[
improve template:TemplateData
]==]
local Failsafe = TemplateData
local Config = {
-- multiple option names mapped into unique internal fields
basicCnf = { catProblem = "strange",
classNoNumTOC = "suppressTOCnum",
classTable = "classTable",
cssParWrap = "cssTabWrap",
cssParams = "cssTable",
docpageCreate = "suffix",
docpageDetect = "subpage",
helpBoolean = "support4boolean",
helpContent = "support4content",
helpDate = "support4date",
helpFile = "support4wiki-file-name",
helpFormat = "supportFormat",
helpLine = "support4line",
helpNumber = "support4number",
helpPage = "support4wiki-page-name",
helpString = "support4string",
helpTemplate = "support4wiki-template-name",
helpURL = "support4url",
helpUser = "support4wiki-user-name",
msgDescMiss = "solo",
tStylesTOCnum = "stylesTOCnum" },
classTable = { "wikitable" }, -- classes for params table
debugmultilang = "C0C0C0",
loudly = false, -- show exported element, etc.
solo = false, -- complaint on missing description
strange = false, -- title of maintenance category
cssTable = false, -- styles for params table
cssTabWrap = false, -- styles for params table wrapper
debug = false,
subpage = false, -- pattern to identify subpage
suffix = false, -- subpage creation scheme
suppressTOCnum = false, -- class for TOC number suppression
jsonDebug = "json-code-lint" -- class for jsonDebug tool
}
local Data = {
div = false, -- <div class="mw-templatedata-doc-wrap">
got = false, -- table, initial templatedata object
heirs = false, -- table, params that are inherited
jump = false, -- source position at end of "params"
less = false, -- main description missing
lasting = false, -- old syntax encountered
lazy = false, -- doc mode; do not generate effective <templatedata>
leading = false, -- show TOC
-- low = false, -- 1= mode
order = false, -- parameter sequence
params = false, -- table, exported parameters
scream = false, -- error messages
sibling = false, -- TOC juxtaposed
slang = nil, -- project/user language code
slim = false, -- JSON reduced to plain
source = false, -- JSON input
strip = false, -- <templatedata> evaluation
tag = false, -- table, exported root element
title = false, -- page
tree = false -- table, rewritten templatedata object
}
local Permit = {
builder = { after = "block",
align = "block",
block = "block",
compressed = "block",
dense = "block",
grouped = "inline",
half = "inline",
indent = "block",
inline = "inline",
last = "block",
lead = "block",
newlines = "*",
spaced = "inline" },
colors = { tableheadbg = "B3B7FF",
required = "EAF3FF",
suggested = "FFFFFF",
optional = "EAECF0",
deprecated = "FFCBCB" },
params = { aliases = "table",
autovalue = "string",
default = "string table I18N nowiki",
deprecated = "boolean string I18N",
description = "string table I18N",
example = "string table I18N nowiki",
label = "string table I18N",
inherits = "string",
required = "boolean",
style = "string table",
suggested = "boolean",
suggestedvalues = "string table number",
type = "string" },
root = { description = "string table I18N",
format = "string",
maps = "table",
params = "table",
paramOrder = "table",
sets = "table" },
search = "[{,]%%s*(['\"])%s%%1%%s*:%%s*%%{",
types = { boolean = true,
content = true,
date = true,
line = true,
number = true,
string = true,
unknown = true,
url = true,
["wiki-file-name"] = true,
["wiki-page-name"] = true,
["wiki-template-name"] = true,
["wiki-user-name"] = true,
["unbalanced-wikitext"] = true,
["string/line"] = "line",
["string/wiki-page-name"] = "wiki-page-name",
["string/wiki-user-name"] = "wiki-user-name" }
}
local function Fault( alert )
-- Memorize error message
-- Parameter:
-- alert -- string, error message
if Data.scream then
Data.scream = string.format( "%s *** %s", Data.scream, alert )
else
Data.scream = alert
end
end -- Fault()
local function Fetch( ask, allow )
-- Fetch module
-- Parameter:
-- ask -- string, with name
-- "/global"
-- "JSONutil"
-- "Multilingual"
-- "Text"
-- "WLink"
-- allow -- true: no error if unavailable
-- Returns table of module
-- error: Module not available
local sign = ask
local r, stem
if sign:sub( 1, 1 ) == "/" then
sign = TemplateData.frame:getTitle() .. sign
else
stem = sign
sign = "Module:" .. stem
end
if TemplateData.extern then
r = TemplateData.extern[ sign ]
else
TemplateData.extern = { }
end
if not r then
local lucky, g = pcall( require, sign )
if type( g ) == "table" then
if stem and type( g[ stem ] ) == "function" then
r = g[ stem ]()
else
r = g
end
TemplateData.extern[ sign ] = r
elseif not allow then
error( string.format( "Fetch(%s) %s", sign, g ), 0 )
end
end
return r
end -- Fetch()
local function Foreign()
-- Guess human language
-- Returns slang, or not
if type( Data.slang ) == "nil" then
local Multilingual = Fetch( "Multilingual", true )
if Multilingual and
type( Multilingual.userLangCode ) == "function" then
Data.slang = Multilingual.userLangCode()
else
Data.slang = mw.language.getContentLanguage():getCode()
:lower()
end
end
if Data.slang and
mw.ustring.codepoint( Data.slang, 1, 1 ) > 122 then
Data.slang = false
end
return Data.slang
end -- Foreign()
local function facet( ask, at )
-- Find physical position of parameter definition in JSON
-- Parameter:
-- ask -- string, parameter name
-- at -- number, physical position within definition
-- Returns number, or nil
local seek = string.format( Permit.search,
ask:gsub( "%%", "%%%%" )
:gsub( "([%-.()+*?^$%[%]])",
"%%%1" ) )
local i, k, r, slice, source
if not Data.jump then
Data.jump = Data.source:find( "params", 2 )
if Data.jump then
Data.jump = Data.jump + 7
else
Data.jump = 1
end
end
i, k = Data.source:find( seek, at + Data.jump )
while i and not r do
source = Data.source:sub( k + 1 )
slice = source:match( "^%s*\"([^\"]+)\"s*:" )
if not slice then
slice = source:match( "^%s*'([^']+)'%s*:" )
end
if ( slice and Permit.params[ slice ] ) or
source:match( "^%s*%}" ) then
r = k
else
i, k = Data.source:find( seek, k )
end
end -- while i
return r
end -- facet()
local function factory( adapt )
-- Retrieve localized text from system message
-- Parameter:
-- adapt -- string, message ID after "templatedata-"
-- Returns string, with localized text
local o = mw.message.new( "templatedata-" .. adapt )
if Foreign() then
o:inLanguage( Data.slang )
end
return o:plain()
end -- factory()
local function faculty( adjust )
-- Test template arg for boolean
-- adjust -- string or nil
-- Returns boolean
local s = type( adjust )
local r
if s == "string" then
r = mw.text.trim( adjust )
r = ( r ~= "" and r ~= "0" )
elseif s == "boolean" then
r = adjust
else
r = false
end
return r
end -- faculty()
local function failures()
-- Retrieve error collection and category
-- Returns string
local r
if Data.scream then
local e = mw.html.create( "span" )
:addClass( "error" )
:wikitext( Data.scream )
r = tostring( e )
mw.addWarning( "'''TemplateData'''<br />" .. Data.scream )
if Config.strange then
r = string.format( "%s[[category:%s]]",
r,
Config.strange )
end
else
r = ""
end
return r
end -- failures()
local function fair( adjust )
-- Reduce text to one line of plain text, or noexport wikitext blocks
-- adjust -- string
-- Returns string, with adjusted text
local f = function ( a )
return a:gsub( "%s*\n%s*", " " )
:gsub( "%s%s+", " " )
end
local tags = { { start = "<noexport>",
stop = "</noexport>" },
{ start = "<exportonly>",
stop = "</exportonly>",
l = false }
}
local r = adjust
local i, j, k, s, tag
for m = 1, 2 do
tag = tags[ m ]
if r:find( tag.start, 1, true ) then
s = r
r = ""
i = 1
tag.l = true
j, k = s:find( tag.start, i, true )
while j do
if j > 1 then
r = r .. f( s:sub( i, j - 1 ) )
end
i = k + 1
j, k = s:find( tag.stop, i, true )
if j then
if m == 1 then
r = r .. s:sub( i, j - 1 )
end
i = k + 1
j, k = s:find( tag.start, i, true )
else
Fault( "missing " .. tag.stop )
end
end -- while j
r = r .. s:sub( i )
elseif m == 1 then
r = f( r )
end
end -- for m
if tags[ 2 ].l then
r = r:gsub( "<exportonly>.*</exportonly>", "" )
end
return r
end -- fair()
local function fancy( advance, alert )
-- Present JSON source
-- Parameter:
-- advance -- true, for nice
-- alert -- true, for visible
-- Returns string
local r
if Data.source then
local support = Config.jsonDebug
local css
if advance then
css = { height = "6em",
resize = "vertical" }
r = { [ 1 ] = "syntaxhighlight",
[ 2 ] = Data.source,
lang = "json",
style = table.concat( css, ";" ) }
if alert then
r.class( support )
end
r = TemplateData.frame:callParserFunction( "#tag", r )
else
css = { [ "font-size" ] = "77%",
[ "line-height" ] = "1.35" }
if alert then
css.resize = "vertical"
else
css.display = "none"
end
r = mw.html.create( "pre" )
:addClass( support )
:css( css )
:wikitext( mw.text.encode( Data.source ) )
r = tostring( r )
end
r = "\n".. r
else
r = ""
end
return r
end -- fancy()
local function faraway( alternatives )
-- Retrieve best language version from multilingual text
-- Parameter:
-- alternatives -- table, to be evaluated
-- Returns
-- 1 -- string, with best match
-- 2 -- table of other versions, if any
local n = 0
local variants = { }
local r1, r2
for k, v in pairs( alternatives ) do
if type( v ) == "string" then
v = mw.text.trim( v )
if v ~= "" and type( k ) == "string" then
k = k:lower()
variants[ k ] = v
n = n + 1
end
end
end -- for k, v
if n > 0 then
local Multilingual = Fetch( "Multilingual", true )
if Multilingual and
type( Multilingual.i18n ) == "function" then
local show, slang = Multilingual.i18n( variants )
if show then
r1 = show
variants[ slang ] = nil
r2 = variants
end
end
if not r1 then
Foreign()
for k, v in pairs( variants ) do
if n == 1 then
r1 = v
elseif Data.slang == k then
variants[ k ] = nil
r1 = v
r2 = variants
end
end -- for k, v
end
if r2 and Multilingual then
for k, v in pairs( r2 ) do
if v and not Multilingual.isLang( k, true ) then
Fault( string.format( "%s <code>lang=%s</code>",
"Invalid",
k ) )
end
end -- for k, v
end
end
return r1, r2
end -- faraway()
local function fashioned( about, asked, assign )
-- Create description head
-- Parameter:
-- about -- table, supposed to contain description
-- asked -- true, if mandatory description
-- assign -- <block>, if to be equipped
-- Returns <block>, with head, or nil
local para = assign or mw.html.create( "div" )
local plus, r
if about and about.description then
if type( about.description ) == "string" then
para:wikitext( about.description )
else
para:wikitext( about.description[ 1 ] )
plus = mw.html.create( "ul" )
plus:css( "text-align", "left" )
for k, v in pairs( about.description[ 2 ] ) do
plus:node( mw.html.create( "li" )
:node( mw.html.create( "code" )
:wikitext( k ) )
:node( mw.html.create( "br" ) )
:wikitext( fair( v ) ) )
end -- for k, v
if Config.loudly then
plus = mw.html.create( "div" )
:css( "background-color",
"#" .. Config.debugmultilang )
:node( plus )
else
plus:addClass( "templatedata-maintain" )
:css( "display", "none" )
end
end
elseif Config.solo and asked then
para:addClass( "error" )
:wikitext( Config.solo )
Data.less = true
else
para = false
end
if para then
if plus then
r = mw.html.create( "div" )
:node( para )
:node( plus )
else
r = para
end
end
return r
end -- fashioned()
local function fatten( access )
-- Create table row for sub-headline
-- Parameter:
-- access -- string, with name
-- Returns <tr>
local param = Data.tree.params[ access ]
local sub, sort = access:match( "(=+)%s*(%S.*)$" )
local headline = mw.html.create( string.format( "h%d", #sub ) )
local r = mw.html.create( "tr" )
local td = mw.html.create( "td" )
:attr( "colspan", "5" )
:attr( "data-sort-value", "!" .. sort )
local s
if param.style then
s = type( param.style )
if s == "table" then
td:css( param.style )
elseif s == "string" then
td:cssText( param.style )
end
end
s = fashioned( param, false, headline )
if s then
headline = s
else
headline:wikitext( sort )
end
td:node( headline )
r:node( td )
return r
end -- fatten()
local function fathers()
-- Merge params with inherited values
local n = 0
local p = Data.params
local t = Data.tree.params
local p2, t2
for k, v in pairs( Data.heirs ) do
n = n + 1
end -- for k, v
for i = 1, n do
if Data.heirs then
for k, v in pairs( Data.heirs ) do
if v and not Data.heirs[ v ] then
n = n - 1
t[ k ].inherits = nil
Data.heirs[ k ] = nil
p2 = { }
t2 = { }
if p[ v ] then
for k2, v2 in pairs( p[ v ] ) do
p2[ k2 ] = v2
end -- for k2, v2
if p[ k ] then
for k2, v2 in pairs( p[ k ] ) do
if type( v2 ) ~= "nil" then
p2[ k2 ] = v2
end
end -- for k2, v2
end
p[ k ] = p2
for k2, v2 in pairs( t[ v ] ) do
t2[ k2 ] = v2
end -- for k2, v2
for k2, v2 in pairs( t[ k ] ) do
if type( v2 ) ~= "nil" then
t2[ k2 ] = v2
end
end -- for k2, v2
t[ k ] = t2
else
Fault( "No params[] inherits " .. v )
end
end
end -- for k, v
end
end -- i = 1, n
if n > 0 then
local s
for k, v in pairs( Data.heirs ) do
if v then
if s then
s = string.format( "%s | %s", s, k )
else
s = "Circular inherits: " .. k
end
end
end -- for k, v
Fault( s )
end
end -- fathers()
local function favorize()
-- Local customization issues
local boole = { ["font-size"] = "125%" }
local l, cx = pcall( mw.loadData,
TemplateData.frame:getTitle() .. "/config" )
local scripting
TemplateData.ltr = not mw.language.getContentLanguage():isRTL()
if TemplateData.ltr then
scripting = "left"
else
scripting = "right"
end
boole[ "margin-" .. scripting ] = "3em"
Permit.boole = { [false] = { css = boole,
lead = true,
show = "☐" },
[true] = { css = boole,
lead = true,
show = "☑" } }
Permit.css = { }
for k, v in pairs( Permit.colors ) do
if k == "tableheadbg" then
k = "tablehead"
end
Permit.css[ k ] = { ["background-color"] = "#" .. v }
end -- for k, v
if type( cx ) == "table" then
local c, s
if type( cx.permit ) == "table" then
if type( cx.permit.boole ) == "table" then
if type( cx.permit.boole[ true ] ) == "table" then
Permit.boole[ false ] = cx.permit.boole[ false ]
end
if type( cx.permit.boole[ true ] ) == "table" then
Permit.boole[ true ] = cx.permit.boole[ true ]
end
end
if type( cx.permit.css ) == "table" then
for k, v in pairs( cx.permit.css ) do
if type( v ) == "table" then
Permit.css[ k ] = v
end
end -- for k, v
end
end
for k, v in pairs( Config.basicCnf ) do
s = type( cx[ k ] )
if s == "string" or s == "table" then
Config[ v ] = cx[ k ]
end
end -- for k, v
end
if type( Config.subpage ) ~= "string" or
type( Config.suffix ) ~= "string" then
local got = mw.message.new( "templatedata-doc-subpage" )
local suffix
if got:isDisabled() then
suffix = "doc"
else
suffix = got:plain()
end
if type( Config.subpage ) ~= "string" then
Config.subpage = string.format( "/%s$", suffix )
end
if type( Config.suffix ) ~= "string" then
Config.suffix = string.format( "%%s/%s", suffix )
end
end
end -- favorize()
local function feasible( about, at )
-- Deal with suggestedvalues within parameter
-- Parameter:
-- about -- parameter details
-- .suggestedvalues -- table|string|number,
-- value and possibly description
-- .code -- mandatory
-- .label -- table|string
-- .icon -- string
-- .class -- table|string
-- .css -- table
-- .style -- string
-- .less -- true: suppress code
-- .type
-- at -- string, with parameter name
-- Returns
-- 1: mw.html object
-- 2: sequence table with values, or nil
local p = about.suggestedvalues
local s = type( p )
local e, r1, r2, v
if s == "table" then
if #p > 0 then
for i = 1, #p do
e = p[ i ]
s = type( e )
if s == "table" then
if type( e.code ) == "string" then
s = mw.text.trim( e.code )
if s == "" then
e = nil
else
e.code = s
end
else
e = nil
s = string.format( "params.%s.%s[%d] %s",
at,
"suggestedvalues",
i,
"MISSING 'code:'" )
end
elseif s == "string" then
s = mw.text.trim( e )
if s == "" then
e = nil
s = string.format( "params.%s.%s[%d] EMPTY",
at, "suggestedvalues", i )
Fault( s )
else
e = { code = s }
end
elseif s == "number" then
e = { code = tostring( e ) }
else
s = string.format( "params.%s.%s[%d] INVALID",
at, "suggestedvalues", i )
Fault( s )
e = false
end
if e then
v = v or { }
table.insert( v, e )
end
end -- for i
else
Fault( string.format( "params.%s.suggestedvalues %s",
at, "NOT AN ARRAY" ) )
end
elseif s == "string" then
s = mw.text.trim( p )
if s ~= "" then
v = { }
table.insert( v,
{ code = s } )
end
elseif s == "number" then
v = { }
table.insert( v,
{ code = tostring( p ) } )
end
if v then
local d, less, story, swift, t, u
r1 = mw.html.create( "ul" )
r2 = { }
for i = 1, #v do
u = mw.html.create( "li" )
e = v[ i ]
table.insert( r2, e.code )
story = false
less = ( e.less == true )
if not less then
swift = e.code
if e.support then
local scream, support
s = type( e.support )
if s == "string" then
support = e.support
elseif s == "table" then
support = faraway( e.support )
else
scream = "INVALID"
end
if support then
s = mw.text.trim( support )
if s == "" then
scream = "EMPTY"
elseif s:find( "[%[%]|%<%>]" ) then
scream = "BAD PAGE"
else
support = s
end
end
if scream then
s = string.format( "params.%s.%s[%d].support %s",
at,
"suggestedvalues",
i,
scream )
Fault( s )
else
swift = string.format( "[[:%s|%s]]",
support, swift )
end
end
if about.type:sub( 1, 5 ) == "wiki-" and
swift == e.code then
local rooms = { file = 6,
temp = 10,
user = 2 }
local ns = rooms[ about.type:sub( 6, 9 ) ] or 0
t = mw.title.makeTitle( ns, swift )
if t and t.exists then
swift = string.format( "[[:%s|%s]]",
t.prefixedText, swift )
end
end
u:node( mw.html.create( "code" )
:css( "white-space", "nowrap" )
:wikitext( swift ) )
end
if e.class then
s = type( e.class )
if s == "string" then
u:addClass( e.class )
elseif s == "table" then
for k, s in pairs( e.class ) do
u:addClass( s )
end -- for k, s
else
s = string.format( "params.%s.%s[%d].class INVALID",
at, "suggestedvalues", i )
Fault( s )
end
end
if e.css then
if type( e.css ) == "table" then
u:css( e.css )
else
s = string.format( "params.%s.%s[%d].css INVALID",
at, "suggestedvalues", i )
Fault( s )
end
end
if e.style then
if type( e.style ) == "string" then
u:cssText( e.style )
else
s = string.format( "params.%s.%s[%d].style INVALID",
at, "suggestedvalues", i )
Fault( s )
end
end
if about.type == "wiki-file-name" and not e.icon then
e.icon = e.code
end
if e.label then
s = type( e.label )
if s == "string" then
s = mw.text.trim( e.label )
if s == "" then
s = string.format( "params.%s.%s[%d].label %s",
at,
"suggestedvalues",
i,
"EMPTY" )
Fault( s )
else
story = s
end
elseif s == "table" then
story = faraway( e.label )
else
s = string.format( "params.%s.%s[%d].label INVALID",
at, "suggestedvalues", i )
Fault( s )
end
end
s = false
if type( e.icon ) == "string" then
t = mw.title.makeTitle( 6, e.icon )
if t and t.file.exists then
local g = mw.html.create( "span" )
s = string.format( "[[%s|16px]]", t.prefixedText )
g:attr( "role", "presentation" )
:wikitext( s )
s = tostring( g )
end
end
if not s and not less and e.label then
s = mw.ustring.char( 0x2013 )
end
if s then
d = mw.html.create( "span" )
:wikitext( s )
if TemplateData.ltr then
if not less then
d:css( "margin-left", "0.5em" )
end
if story then
d:css( "margin-right", "0.5em" )
end
else
if not less then
d:css( "margin-right", "0.5em" )
end
if story then
d:css( "margin-left", "0.5em" )
end
end
u:node( d )
end
if story then
u:wikitext( story )
end
r1:newline()
:node( u )
end -- for i
end
if not r1 then
Fault( string.format( "params.%s.suggestedvalues INVALID", at ) )
r1 = mw.html.create( "code" )
:addClass( "error" )
:wikitext( "INVALID" )
end
return r1, r2
end -- feasible()
local function feat()
-- Check and store parameter sequence
if Data.source then
local i = 0
local s
for k, v in pairs( Data.tree.params ) do
if i == 0 then
Data.order = { }
i = 1
s = k
else
i = 2
break -- for k, v
end
end -- for k, v
if i > 1 then
local pointers = { }
local points = { }
local given = { }
for k, v in pairs( Data.tree.params ) do
i = facet( k, 1 )
if type( v ) == "table" then
if type( v.label ) == "string" then
s = mw.text.trim( v.label )
if s == "" then
s = k
end
else
s = k
end
if given[ s ] then
if given[ s ] == 1 then
local scream = "Parameter label '%s' detected multiple times"
Fault( string.format( scream, s ) )
given[ s ] = 2
end
else
given[ s ] = 1
end
end
if i then
table.insert( points, i )
pointers[ i ] = k
i = facet( k, i )
if i then
s = "Parameter '%s' detected twice"
Fault( string.format( s, k ) )
end
else
s = "Parameter '%s' not detected"
Fault( string.format( s, k ) )
end
end -- for k, v
table.sort( points )
for i = 1, #points do
table.insert( Data.order, pointers[ points[ i ] ] )
end -- i = 1, #points
elseif s then
table.insert( Data.order, s )
end
end
end -- feat()
local function feature( access )
-- Create table row for parameter, check and display violations
-- Parameter:
-- access -- string, with name
-- Returns <tr>
local mode, s, status
local fine = function ( a )
s = mw.text.trim( a )
return a == s and
a ~= "" and
not a:find( "%|=\n" ) and
not a:find( "%s%s" )
end
local begin = mw.html.create( "td" )
local code = mw.html.create( "code" )
local desc = mw.html.create( "td" )
local eager = mw.html.create( "td" )
local legal = true
local param = Data.tree.params[ access ]
local ranking = { "required", "suggested", "optional", "deprecated" }
local r = mw.html.create( "tr" )
local styles = "mw-templatedata-doc-param-"
local sort, typed
for k, v in pairs( param ) do
if v == "" then
param[ k ] = false
end
end -- for k, v
-- label
sort = param.label or access
if sort:match( "^%d+$" ) then
begin:attr( "data-sort-value",
string.format( "%05d", tonumber( sort ) ) )
end
begin:css( "font-weight", "bold" )
:wikitext( sort )
-- name and aliases
code:css( "font-size", "92%" )
:css( "white-space", "nowrap" )
:wikitext( access )
if not fine( access ) then
code:addClass( "error" )
Fault( string.format( "Bad ID params.<code>%s</code>", access ) )
legal = false
begin:attr( "data-sort-value", " " .. sort )
end
code = mw.html.create( "td" )
:addClass( styles .. "name" )
:node( code )
if access:match( "^%d+$" ) then
code:attr( "data-sort-value",
string.format( "%05d", tonumber( access ) ) )
end
if type( param.aliases ) == "table" then
local lapsus, syn
for k, v in pairs( param.aliases ) do
code:tag( "br" )
if type( v ) == "string" then
if not fine( v ) then
lapsus = true
code:node( mw.html.create( "span" )
:addClass( "error" )
:css( "font-style", "italic" )
:wikitext( "string" ) )
:wikitext( s )
else
syn = mw.html.create( "span" )
:addClass( styles .. "alias" )
:css( "white-space", "nowrap" )
:wikitext( s )
code:node( syn )
end
else
lapsus = true
code:node( mw.html.create( "code" )
:addClass( "error" )
:wikitext( type( v ) ) )
end
end -- for k, v
if lapsus then
s = string.format( "params.<code>%s</code>.aliases", access )
Fault( factory( "invalid-value" ):gsub( "$1", s ) )
legal = false
end
end
-- description etc.
s = fashioned( param )
if s then
desc:node( s )
end
if param.style then
s = type( param.style )
if s == "table" then
desc:css( param.style )
elseif s == "string" then
desc:cssText( param.style )
end
end
if param.suggestedvalues or
param.default or
param.example or
param.autovalue then
local details = { "suggestedvalues",
"default",
"example",
"autovalue" }
local dl = mw.html.create( "dl" )
local dd, section, show
for i = 1, #details do
s = details[ i ]
show = param[ s ]
if show then
dd = mw.html.create( "dd" )
section = factory( "doc-param-" .. s )
if param.type == "boolean" and
( show == "0" or show == "1" ) then
local boole = Permit.boole[ ( show == "1" ) ]
if boole.lead == true then
dd:node( mw.html.create( "code" )
:wikitext( show ) )
:wikitext( " " )
end
if type( boole.show ) == "string" then
local v = mw.html.create( "span" )
:attr( "aria-hidden", "true" )
:wikitext( boole.show )
if boole.css then
v:css( boole.css )
end
dd:node( v )
end
if type( boole.suffix ) == "string" then
dd:wikitext( boole.suffix )
end
if boole.lead == false then
dd:wikitext( " " )
:node( mw.html.create( "code" )
:wikitext( show ) )
end
elseif s == "suggestedvalues" then
local html, values = feasible( param, access )
dd:newline()
:node( html )
Data.params[ access ].suggestedvalues = values
else
dd:wikitext( show )
end
dl:node( mw.html.create( "dt" )
:wikitext( section ) )
:node( dd )
end
end -- i = 1, #details
desc:node( dl )
end
-- type
if type( param.type ) == "string" then
param.type = mw.text.trim( param.type )
if param.type == "" then
param.type = false
end
end
if param.type then
s = Permit.types[ param.type ]
typed = mw.html.create( "td" )
:addClass( styles .. "type" )
if s then
if s == "string" then
Data.params[ access ].type = s
typed:wikitext( factory( "doc-param-type-" .. s ) )
:tag( "br" )
typed:node( mw.html.create( "span" )
:addClass( "error" )
:wikitext( param.type ) )
Data.lasting = true
else
local support = Config[ "support4" .. param.type ]
s = factory( "doc-param-type-" .. param.type )
if support then
s = string.format( "[[%s|%s]]", support, s )
end
typed:wikitext( s )
end
else
Data.params[ access ].type = "unknown"
typed:addClass( "error" )
:wikitext( "INVALID" )
s = string.format( "params.<code>%s</code>.type", access )
Fault( factory( "invalid-value" ):gsub( "$1", s ) )
legal = false
end
else
typed = mw.html.create( "td" )
:wikitext( factory( "doc-param-type-unknown" ) )
Data.params[ access ].type = "unknown"
if param.default then
Data.params[ access ].default = nil
Fault( "Default value requires <code>type</code>" )
legal = false
end
end
-- status
if param.required then
mode = 1
if param.autovalue then
Fault( string.format( "autovalued <code>%s</code> required",
access ) )
legal = false
end
if param.default then
Fault( string.format( "Defaulted <code>%s</code> required",
access ) )
legal = false
end
if param.deprecated then
Fault( string.format( "Required deprecated <code>%s</code>",
access ) )
legal = false
end
elseif param.deprecated then
mode = 4
elseif param.suggested then
mode = 2
else
mode = 3
end
status = ranking[ mode ]
ranking = factory( "doc-param-status-" .. status )
if mode == 1 or mode == 4 then
ranking = mw.html.create( "span" )
:css( "font-weight", "bold" )
:wikitext( ranking )
if type( param.deprecated ) == "string" then
ranking:tag( "br" )
ranking:wikitext( param.deprecated )
end
if param.suggested and mode == 4 then
s = string.format( "Suggesting deprecated <code>%s</code>",
access )
Fault( s )
legal = false
end
end
eager:attr( "data-sort-value", tostring( mode ) )
:node( ranking )
:addClass( string.format( "%sstatus-%s",
styles, status ) )
-- <tr>
r:attr( "id", "templatedata:" .. mw.uri.anchorEncode( access ) )
:css( Permit.css[ status ] )
:addClass( styles .. status )
:node( begin )
:node( code )
:node( desc )
:node( typed )
:node( eager )
:newline()
if not legal then
r:css( "border", "#FF0000 3px solid" )
end
return r
end -- feature()
local function features()
-- Create <table> for parameters
-- Returns <table>, or nil
local r
if Data.tree and Data.tree.params then
local tbl = mw.html.create( "table" )
local tr = mw.html.create( "tr" )
feat()
if Data.order and #Data.order > 1 then
tbl:addClass( "sortable" )
end
if type( Config.classTable ) == "table" then
for k, v in pairs( Config.classTable ) do
tbl:addClass( v )
end -- for k, v
end
if type( Config.cssTable ) == "table" then
tbl:css( Config.cssTable )
end
tr:node( mw.html.create( "th" )
:attr( "colspan", "2" )
:css( Permit.css.tablehead )
:wikitext( factory( "doc-param-name" ) ) )
:node( mw.html.create( "th" )
:css( Permit.css.tablehead )
:wikitext( factory( "doc-param-desc" ) ) )
:node( mw.html.create( "th" )
:css( Permit.css.tablehead )
:wikitext( factory( "doc-param-type" ) ) )
:node( mw.html.create( "th" )
:css( Permit.css.tablehead )
:wikitext( factory( "doc-param-status" ) ) )
tbl:newline()
-- :node( mw.html.create( "thead" )
:node( tr )
-- )
:newline()
if Data.order then
local leave, s
for i = 1, #Data.order do
s = Data.order[ i ]
if s:sub( 1, 1 ) == "=" then
leave = true
tbl:node( fatten( s ) )
Data.order[ i ] = false
elseif s:match( "[=|]" ) then
Fault( string.format( "Bad param <code>%s</code>",
s ) )
else
tbl:node( feature( s ) )
end
end -- for i = 1, #Data.order
if leave then
for i = #Data.order, 1, -1 do
if not Data.order[ i ] then
table.remove( Data.order, i )
end
end -- for i = #Data.order, 1, -1
end
Data.tag.paramOrder = Data.order
end
if Config.cssTabWrap or Data.scroll then
r = mw.html.create( "div" )
if type( Config.cssTabWrap ) == "table" then
r:css( Config.cssTabWrap )
elseif type( Config.cssTabWrap ) == "string" then
-- deprecated
r:cssText( Config.cssTabWrap )
end
if Data.scroll then
r:css( "height", Data.scroll )
:css( "overflow", "auto" )
end
r:node( tbl )
else
r = tbl
end
end
return r
end -- features()
local function fellow( any, assigned, at )
-- Check sets[] parameter and issue error message, if necessary
-- Parameter:
-- any -- should be number
-- assigned -- parameter name
-- at -- number, of set
local s
if type( any ) ~= "number" then
s = "<code>sets[%d].params[%s]</code>??"
Fault( string.format( s,
at,
mw.text.nowiki( tostring( any ) ) ) )
elseif type( assigned ) == "string" then
if not Data.got.params[ assigned ] then
s = "<code>sets[%d].params %s</code> is undefined"
Fault( string.format( s, at, assigned ) )
end
else
s = "<code>sets[%d].params[%d] = %s</code>??"
Fault( string.format( s, k, type( assigned ) ) )
end
end -- fellow()
local function fellows()
-- Check sets[] and issue error message, if necessary
local s
if type( Data.got.sets ) == "table" then
if type( Data.got.params ) == "table" then
for k, v in pairs( Data.got.sets ) do
if type( k ) == "number" then
if type( v ) == "table" then
for ek, ev in pairs( v ) do
if ek == "label" then
s = type( ev )
if s ~= "string" and
s ~= "table" then
s = "<code>sets[%d].label</code>??"
Fault( string.format( s, k ) )
end
elseif ek == "params" and
type( ev ) == "table" then
for pk, pv in pairs( ev ) do
fellow( pk, pv, k )
end -- for pk, pv
else
ek = mw.text.nowiki( tostring( ek ) )
s = "<code>sets[%d][%s]</code>??"
Fault( string.format( s, k, ek ) )
end
end -- for ek, ev
else
k = mw.text.nowiki( tostring( k ) )
v = mw.text.nowiki( tostring( v ) )
s = string.format( "<code>sets[%s][%s]</code>??",
k, v )
Fault( s )
end
else
k = mw.text.nowiki( tostring( k ) )
s = string.format( "<code>sets[%s]</code> ?????", k )
Fault( s )
end
end -- for k, v
else
s = "<code>params</code> required for <code>sets</code>"
Fault( s )
end
else
s = "<code>sets</code> needs to be of <code>object</code> type"
Fault( s )
end
end -- fellows()
local function finalize( advance )
-- Wrap presentation into frame
-- Parameter:
-- advance -- true, for nice
-- Returns string
local r, lapsus
if Data.div then
r = tostring( Data.div )
elseif Data.strip then
r = Data.strip
else
lapsus = true
r = ""
end
r = r .. failures()
if Data.source then
local live = ( advance or lapsus )
if not live then
live = TemplateData.frame:preprocess( "{{REVISIONID}}" )
live = ( live == "" )
end
if live then
r = r .. fancy( advance, lapsus )
end
end
return r
end -- finalize()
local function find()
-- Find JSON data within page source (title)
-- Returns string, or nil
local s = Data.title:getContent()
local i, j = s:find( "<templatedata>", 1, true )
local r
if i then
local k = s:find( "</templatedata>", j, true )
if k then
r = mw.text.trim( s:sub( j + 1, k - 1 ) )
end
end
return r
end -- find()
local function flat( adjust )
-- Remove formatting from text string for VE
-- Parameter:
-- arglist -- string, to be stripped, or nil
-- Returns string, or nil
local r
if adjust then
r = adjust:gsub( "\n", " " )
if r:find( "<noexport>", 1, true ) then
r = r:gsub( "<noexport>.*</noexport>", "" )
end
if r:find( "<exportonly>", 1, true ) then
r = r:gsub( "</?exportonly>", "" )
end
if r:find( "''", 1, true ) then
r = r:gsub( "'''", "" ):gsub( "''", "" )
end
if r:find( "<", 1, true ) then
local Text = Fetch( "Text" )
r = Text.getPlain( r:gsub( "<br */?>", "\r\n" ) )
end
if r:find( "[", 1, true ) then
local WLink = Fetch( "WLink" )
if WLink.isBracketedURL( r ) then
r = r:gsub( "%[([hf]tt?ps?://%S+) [^%]]+%]", "%1" )
end
r = WLink.getPlain( r )
end
if r:find( "&", 1, true ) then
r = mw.text.decode( r )
if r:find( "­", 1, true ) then
r = r:gsub( "­", "" )
end
end
end
return r
end -- flat()
local function flush()
-- JSON encode narrowed input; obey unnamed (numerical) parameters
-- Returns <templatedata> JSON string
local r
if Data.tag then
r = mw.text.jsonEncode( Data.tag ):gsub( "%}$", "," )
else
r = "{"
end
r = r .. "\n\"params\":{"
if Data.order then
local sep = ""
local s
for i = 1, #Data.order do
s = Data.order[ i ]
r = string.format( "%s%s\n%s:%s",
r,
sep,
mw.text.jsonEncode( s ),
mw.text.jsonEncode( Data.params[ s ] ) )
sep = ",\n"
end -- for i = 1, #Data.order
end
r = r .. "\n}\n}"
return r
end -- flush()
local function focus( access )
-- Check components; focus multilingual description, build trees
-- Parameter:
-- access -- string, name of parameter, nil for root
local f = function ( a, at )
local r
if at then
r = string.format( "<code>params.%s</code>", at )
else
r = "''root''"
end
if a then
r = string.format( "%s<code>.%s</code>", r, a )
end
return r
end
local parent
if access then
parent = Data.got.params[ access ]
else
parent = Data.got
end
if type( parent ) == "table" then
local elem, got, permit, s, scope, slot, tag, target
if access then
permit = Permit.params
if type( access ) == "number" then
slot = tostring( access )
else
slot = access
end
else
permit = Permit.root
end
for k, v in pairs( parent ) do
scope = permit[ k ]
if scope then
s = type( v )
if s == "string" and k ~= "format" then
v = mw.text.trim( v )
end
if scope:find( s, 1, true ) then
if scope:find( "I18N", 1, true ) then
if s == "string" then
elem = fair( v )
elseif s == "table" then
local translated
v, translated = faraway( v )
if v then
if translated and
k == "description" then
elem = { [ 1 ] = fair( v ),
[ 2 ] = translated }
else
elem = fair( v )
end
else
elem = false
end
end
if type( v ) == "string" then
if k == "deprecated" then
if v == "1" then
v = true
elseif v == "0" then
v = false
end
elem = v
elseif scope:find( "nowiki", 1, true ) then
elem = mw.text.nowiki( v )
elem = elem:gsub( " \n", "<br>" )
v = v:gsub( string.char( 13 ), "" )
else
v = flat( v )
end
elseif s == "boolean" then
if scope:find( "boolean", 1, true ) then
elem = v
else
s = "Type <code>boolean</code> bad for "
.. f( k, slot )
Fault( s )
end
end
else
if k == "params" and not access then
v = nil
elem = nil
elseif k == "format" and not access then
elem = mw.text.decode( v )
v = nil
elseif k == "inherits" then
elem = v
if not Data.heirs then
Data.heirs = { }
end
Data.heirs[ slot ] = v
v = nil
elseif k == "style" then
elem = v
v = nil
elseif s == "string" then
v = mw.text.nowiki( v )
elem = v
else
elem = v
end
end
if type( elem ) ~= "nil" then
if not target then
if access then
if not Data.tree.params then
Data.tree.params = { }
end
Data.tree.params[ slot ] = { }
target = Data.tree.params[ slot ]
else
Data.tree = { }
target = Data.tree
end
end
target[ k ] = elem
elem = false
end
if type( v ) ~= "nil" then
if not tag then
if access then
if type( v ) == "string" and
v.sub( 1, 1 ) == "=" then
v = nil
else
if not Data.params then
Data.params = { }
end
Data.params[ slot ] = { }
tag = Data.params[ slot ]
end
else
Data.tag = { }
tag = Data.tag
end
end
if type( v ) ~= "nil" and
k ~= "suggestedvalues" then
tag[ k ] = v
end
end
else
s = string.format( "Type <code>%s</code> bad for %s",
scope, f( k, slot ) )
Fault( s )
end
else
Fault( "Unknown component " .. f( k, slot ) )
end
end -- for k, v
if not access and Data.got.sets then
fellows()
end
else
Fault( f() .. " needs to be of <code>object</code> type" )
end
end -- focus()
local function format()
-- Build formatted element
-- Returns <inline>
local source = Data.tree.format:lower()
local r, s
if source == "inline" or source == "block" then
r = mw.html.create( "i" )
:wikitext( source )
else
local code
if source:find( "|", 1, true ) then
local scan = "^[\n ]*%{%{[\n _]*|[\n _]*=[\n _]*%}%}[\n ]*$"
if source:match( scan ) then
code = source:gsub( "\n", "N" )
else
s = mw.text.nowiki( source ):gsub( "\n", "\n" )
s = tostring( mw.html.create( "code" )
:wikitext( s ) )
Fault( "Invalid format " .. s )
source = false
end
else
local words = mw.text.split( source, "%s+" )
local show, start, support, unknown
for i = 1, #words do
s = words[ i ]
if i == 1 then
start = s
end
support = Permit.builder[ s ]
if support == start or
support == "*" then
Permit.builder[ s ] = true
elseif s:match( "^[1-9]%d?" ) and
Permit.builder.align then
Permit.builder.align = tonumber( s )
else
if unknown then
unknown = string.format( "%s %s", unknown, s )
else
unknown = s
end
end
end -- i = 1, #words
if unknown then
s = tostring( mw.html.create( "code" )
:css( "white-space", "nowrap" )
:wikitext( s ) )
Fault( "Unknown/misplaced format keyword " .. s )
source = false
start = false
end
if start == "inline" then
if Permit.builder.half == true then
show = "inline half"
code = "{{_ |_=_}}"
elseif Permit.builder.grouped == true then
show = "inline grouped"
code = "{{_ | _=_}}"
elseif Permit.builder.spaced == true then
show = "inline spaced"
code = "{{_ | _ = _ }}"
end
if Permit.builder.newlines == true then
show = show or "inline"
code = code or "{{_|_=_}}"
show = show .. " newlines"
code = string.format( "N%sN", code )
end
elseif start == "block" then
local space = "" -- amid "|" and name
local spaced = " " -- preceding "="
local spacer = " " -- following "="
local suffix = "N" -- closing "}}" on new line
show = "block"
if Permit.builder.indent == true then
start = " "
show = "block indent"
else
start = ""
end
if Permit.builder.compressed == true then
spaced = ""
spacer = ""
show = show .. " compressed"
if Permit.builder.last == true then
show = show .. " last"
else
suffix = ""
end
else
if Permit.builder.lead == true then
show = show .. " lead"
space = " "
end
if type( Permit.builder.align ) ~= "string" then
local n
s = " align"
if Permit.builder.align == true then
n = 0
if type( Data.got ) == "table" and
type( Data.got.params ) == "table" then
for k, v in pairs( Data.got.params ) do
if type( v ) == "table" and
not v.deprecated and
type( k ) == "string" then
k = mw.ustring.len( k )
if k > n then
n = k
end
end
end -- for k, v
end
else
n = Permit.builder.align
if type( n ) == "number" and n > 1 then
s = string.format( "%s %d", s, n )
else
n = 0 -- How comes?
end
end
if n > 1 then
spaced = string.rep( "_", n - 1 ) .. " "
end
show = show .. s
elseif Permit.builder.after == true then
spaced = ""
show = show .. " after"
elseif Permit.builder.dense == true then
spaced = ""
spacer = ""
show = show .. " dense"
end
if Permit.builder.last == true then
suffix = spacer
show = show .. " last"
end
end
code = string.format( "N{{_N%s|%s_%s=%s_%s}}N",
start,
space,
spaced,
spacer,
suffix )
if show == "block" then
show = "block newlines"
end
end
if show then
r = mw.html.create( "span" )
:wikitext( show )
end
end
if code then
source = code:gsub( "N", "\n" )
code = mw.text.nowiki( code ):gsub( "N", "\n" )
code = mw.html.create( "code" )
:css( "margin-left", "1em" )
:css( "margin-right", "1em" )
:wikitext( code )
if r then
r = mw.html.create( "span" )
:node( r )
:node( code )
else
r = code
end
end
end
if source and Data.tag then
Data.tag.format = source
end
return r
end -- format()
local function formatter()
-- Build presented documentation
-- Returns <div>
local r = mw.html.create( "div" )
local x = fashioned( Data.tree, true, r )
local s
if x then
r = x
end
if Data.leading then
local toc = mw.html.create( "div" )
local shift
if Config.suppressTOCnum then
toc:addClass( Config.suppressTOCnum )
if type( Config.stylesTOCnum ) == "string" then
local src = Config.stylesTOCnum .. "/styles.css"
s = TemplateData.frame:extensionTag( "templatestyles",
nil,
{ src = src } )
r:newline()
:node( s )
end
end
toc:css( "margin-top", "0.5em" )
:wikitext( "__TOC__" )
if Data.sibling then
local block = mw.html.create( "div" )
if TemplateData.ltr then
shift = "right"
else
shift = "left"
end
block:css( "float", shift )
:wikitext( Data.sibling )
r:newline()
:node( block )
:newline()
end
r:newline()
:node( toc )
:newline()
if shift then
r:node( mw.html.create( "div" )
:css( "clear", shift ) )
:newline()
end
end
s = features()
if s then
if Data.leading then
r:node( mw.html.create( "h" .. Config.nested )
:wikitext( factory( "doc-params" ) ) )
:newline()
end
r:node( s )
end
if Data.shared then
local global = mw.html.create( "div" )
:attr( "id", "templatedata-global" )
local shift
if TemplateData.ltr then
shift = "right"
else
shift = "left"
end
global:css( "float", shift )
:wikitext( string.format( "[[%s|%s]]",
Data.shared, "Global" ) )
r:newline()
:node( global )
end
if Data.tree and Data.tree.format then
local e = format()
if e then
local show = "Format"
if Config.supportFormat then
show = string.format( "[[%s|%s]]",
Config.supportFormat, show )
end
r:node( mw.html.create( "p" )
:wikitext( show .. ": " )
:node( e ) )
end
end
return r
end -- formatter()
local function free()
-- Remove JSON comment lines
if Data.source:find( "//", 1, true ) then
Data.source:gsub( "([{,\"'])(%s*\n%s*//.*\n%s*)([{},\"'])",
"%1%3" )
end
end -- free()
local function full()
-- Build survey table from JSON data, append invisible <templatedata>
Data.div = mw.html.create( "div" )
:addClass( "mw-templatedata-doc-wrap" )
focus()
if Data.tag then
if type( Data.got.params ) == "table" then
for k, v in pairs( Data.got.params ) do
focus( k )
end -- for k, v
if Data.heirs then
fathers()
end
end
end
Data.div:node( formatter() )
if not Data.lazy then
Data.slim = flush()
if TemplateData.frame then
local div = mw.html.create( "div" )
local tdata = { [ 1 ] = "templatedata",
[ 2 ] = Data.slim }
Data.strip = TemplateData.frame:callParserFunction( "#tag",
tdata )
div:wikitext( Data.strip )
if Config.loudly then
Data.div:node( mw.html.create( "hr" )
:css( { height = "7ex" } ) )
else
div:css( "display", "none" )
end
Data.div:node( div )
end
end
if Data.lasting then
Fault( "deprecated type syntax" )
end
if Data.less then
Fault( Config.solo )
end
end -- full()
local function furnish( adapt, arglist )
-- Analyze transclusion
-- Parameter:
-- adapt -- table, #invoke parameters
-- arglist -- table, template parameters
-- Returns string
local source
favorize()
-- deprecated:
for k, v in pairs( Config.basicCnf ) do
if adapt[ k ] and adapt[ k ] ~= "" then
Config[ v ] = adapt[ k ]
end
end -- for k, v
if arglist.heading and arglist.heading:match( "^[3-6]$" ) then
Config.nested = arglist.heading
else
Config.nested = "2"
end
Config.loudly = faculty( arglist.debug or adapt.debug )
Data.lazy = faculty( arglist.lazy ) and not Config.loudly
Data.leading = faculty( arglist.TOC )
if Data.leading and arglist.TOCsibling then
Data.sibling = mw.text.trim( arglist.TOCsibling )
end
if arglist.lang then
Data.slang = arglist.lang:lower()
elseif adapt.lang then
Data.slang = adapt.lang:lower()
end
if arglist.JSON then
source = arglist.JSON
elseif arglist.Global then
source = TemplateData.getGlobalJSON( arglist.Global,
arglist.Local )
elseif arglist[ 1 ] then
local s = mw.text.trim( arglist[ 1 ] )
local start = s:sub( 1, 1 )
if start == "<" then
Data.strip = s
elseif start == "{" then
source = s
elseif mw.ustring.sub( s, 1, 8 ) ==
mw.ustring.char( 127, 39, 34, 96, 85, 78, 73, 81 ) then
Data.strip = s
end
end
if type( arglist.vertical ) == "string" and
arglist.vertical:match( "^%d*%.?%d+[emprx]+$" ) then
Data.scroll = arglist.vertical
end
if not source then
Data.title = mw.title.getCurrentTitle()
source = find()
if not source and
not Data.title.text:match( Config.subpage ) then
local s = string.format( Config.suffix,
Data.title.prefixedText )
Data.title = mw.title.new( s )
if Data.title.exists then
source = find()
end
end
end
if not Data.lazy then
if not Data.title then
Data.title = mw.title.getCurrentTitle()
end
Data.lazy = Data.title.text:match( Config.subpage )
end
if type( source ) == "string" then
TemplateData.getPlainJSON( source )
end
return finalize( faculty( arglist.source ) )
end -- furnish()
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()
TemplateData.getGlobalJSON = function ( access, adapt )
-- Retrieve TemplateData from a global repository (JSON)
-- Parameter:
-- access -- string, with page specifier (on WikiMedia Commons)
-- adapt -- JSON string or table with local overrides
-- Returns true, if succeeded
local plugin = Fetch( "/global" )
local r
if type( plugin ) == "table" and
type( plugin.fetch ) == "function" then
local s, got = plugin.fetch( access, adapt )
if got then
Data.got = got
Data.order = got.paramOrder
Data.shared = s
r = true
full()
else
Fault( s )
end
end
return r
end -- TemplateData.getGlobalJSON()
TemplateData.getPlainJSON = function ( adapt )
-- Reduce enhanced JSON data to plain text localized JSON
-- Parameter:
-- adapt -- string, with enhanced JSON
-- Returns string, or not
if type( adapt ) == "string" then
local JSONutil = Fetch( "JSONutil", true )
Data.source = adapt
free()
if JSONutil then
local Multilingual = Fetch( "Multilingual", true )
local f
if Multilingual then
f = Multilingual.i18n
end
Data.got = JSONutil.fetch( Data.source, true, f )
else
local lucky
lucky, Data.got = pcall( mw.text.jsonDecode, Data.source )
end
if type( Data.got ) == "table" then
full()
elseif not Data.strip then
local scream = type( Data.got )
if scream == "string" then
scream = Data.got
else
scream = "Data.got: " .. scream
end
Fault( "fatal JSON error: " .. scream )
end
end
return Data.slim
end -- TemplateData.getPlainJSON()
TemplateData.test = function ( adapt, arglist )
TemplateData.frame = mw.getCurrentFrame()
return furnish( adapt, arglist )
end -- TemplateData.test()
-- Export
local p = { }
p.f = function ( frame )
-- Template call
local lucky, r
TemplateData.frame = frame
lucky, r = pcall( furnish, frame.args, frame:getParent().args )
if not lucky then
Fault( "INTERNAL: " .. r )
r = failures()
end
return r
end -- p.f
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.TemplateData = function ()
-- Module interface
return TemplateData
end
return p