Jump to content

Module:Format TemplateData/sandbox: Difference between revisions

From Wikipedia, the free encyclopedia
Content deleted Content added
m Pppery moved page Module:TemplateData/sandbox to Module:Format TemplateData/sandbox without leaving a redirect
Synced; some simplifications
 
Line 1: Line 1:
local TemplateData = { serial = "2017-11-06",
local TemplateData = { suite = "TemplateData",
suite = "TemplateData" }
serial = "2022-03-10",
item = 46997995 }
--[=[
--[==[
improve template:TemplateData
improve template:TemplateData
]=]
]==]
local Failsafe = TemplateData





local Config = {
local Config = {
-- multiple #invoke option names mapped into unique internal fields
-- multiple option names mapped into unique internal fields
cat = "strange",
basicCnf = { catProblem = "strange",
classMultiColumns = "selMultClm",
classNoNumTOC = "suppressTOCnum",
classNoNumTOC = "suppressTOCnum",
-- classParams = "classTable",
cssParams = "stylesTable",
classTable = "classTable",
cssParWrap = "stylesTabWrap",
cssParWrap = "cssTabWrap",
debug = false,
cssParams = "cssTable",
docpageCreate = "suffix",
docpageCreate = "suffix",
docpageDetect = "subpage",
docpageDetect = "subpage",
msgDescMiss = "solo",
helpBoolean = "support4boolean",
helpContent = "support4content",
-- classTable = false, -- class for params table
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",
tStylesMultiColumns = "stylesMultClm" },
classTable = { "wikitable" }, -- classes for params table
debugmultilang = "C0C0C0",
loudly = false, -- show exported element, etc.
loudly = false, -- show exported element, etc.
solo = false, -- complaint on missing description
solo = false, -- complaint on missing description
strange = false, -- title of maintenance category
strange = false, -- title of maintenance category
stylesTable = false, -- styles for params table
cssTable = false, -- styles for params table
stylesTabWrap = false, -- styles for params table wrapper
cssTabWrap = false, -- styles for params table wrapper
debug = false,
subpage = false, -- pattern to identify subpage
subpage = false, -- pattern to identify subpage
suffix = false, -- subpage creation scheme
suffix = false, -- subpage creation scheme
suppressTOCnum = false -- class for TOC number suppression
suppressTOCnum = false, -- class for TOC number suppression
jsonDebug = "json-code-lint" -- class for jsonDebug tool
}
}
local Data = {
local Data = {
Line 32: Line 50:
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
Line 40: Line 59:
params = false, -- table, exported parameters
params = false, -- table, exported parameters
scream = false, -- error messages
scream = false, -- error messages
slang = false, -- project language code
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
Line 49: Line 69:
}
}
local Permit = {
local Permit = {
styles = { required = "border-left: 3px solid black;",
builder = { after = "block",
suggested = "border-left: 3px solid #888;",
align = "block",
optional = "border-left: 3px solid #ccc",
block = "block",
deprecated = "border-left: 3px dotted red; background-color: #FDD;",
compressed = "block",
tableheadbg = "background-color: #B3B7FF;" },
dense = "block",
params = { aliases = "table",
grouped = "inline",
autovalue = "string",
half = "inline",
default = "string table I18N nowiki",
indent = "block",
deprecated = "boolean string",
inline = "inline",
description = "string table I18N",
last = "block",
example = "string table I18N nowiki",
lead = "block",
label = "string table I18N",
newlines = "*",
inherits = "string",
spaced = "inline" },
required = "boolean",
colors = { bg = "FFFFFF",
suggested = "boolean",
fg = "000000",
type = "string" },
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 boolean",
type = "string" },
root = { description = "string table I18N",
root = { description = "string table I18N",
format = "string",
format = "string",
Line 90: Line 127:
}
}



--
-- Generic utility functions
--


local function Fault( alert )
local function Fault( alert )
Line 107: Line 142:




local function Fetch( ask )
local function Fetch( ask, allow )
-- Fetch module
-- Fetch module
-- Parameter:
-- Parameter:
-- ask -- string, with name
-- ask -- string, with name
-- "Multilingual"
-- "/global"
-- "Text"
-- "JSONutil"
-- "WLink"
-- "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[ ask ]
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, "Module:" .. ask )
local lucky, g = pcall( require, sign )
if type( g ) == "table" then
if type( g ) == "table" then
r = g[ ask ]()
if stem and type( g[ stem ] ) == "function" then
TemplateData.extern[ ask ] = r
r = g[ stem ]()
else
else
error( string.format( "Fetch(%s) %s", ask, g ) )
r = g
end
TemplateData.extern[ sign ] = r
elseif not allow then
error( string.format( "Fetch(%s) %s", sign, g ), 0 )
end
end
end
end
Line 134: Line 183:
end -- Fetch()
end -- Fetch()


local function collapseWhitespace ( a )
-- Collapses whitespace, HTML style.
return a:gsub( "%s*\n%s*", " " )
:gsub( "%s%s+", " " )
end -- collapseWhitespace



--------------------------------------------------------------------------------
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 )
local function facet( ask, at )
Line 155: Line 217:
:gsub( "([%-.()+*?^$%[%]])",
:gsub( "([%-.()+*?^$%[%]])",
"%%%1" ) )
"%%%1" ) )
local i, k = Data.source:find( seek, at )
local i, k, r, slice, source
local 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 )
Line 167: Line 237:
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
Line 175: Line 245:




local function getLocalizedText( adapt )
local function facilities( apply )
-- Retrieve details of suggestedvalues
-- Parameter:
-- apply -- table, with plain or enhanced values
-- .suggestedvalues -- table|string|number, or more
-- Returns
-- 1 -- table, with suggestedvalues
-- 2 -- table, with CSS map, or not
-- 3 -- string, with class, or not
-- 4 -- string, with templatestyles, or not
local elements = apply.suggestedvalues
local s = type( elements )
local r1, r2, r3, r4
if s == "table" then
local values = elements.values
if type( values ) == "table" then
r1 = values
if type( elements.scroll ) == "string" then
r2 = r2 or { }
r2.height = apply.scroll
r2.overflow = "auto"
end
if type( elements.minwidth ) == "string" then
local s = type( elements.maxcolumns )
r2 = r2 or { }
r2["column-width"] = elements.minwidth
if s == "string" or
s == "number" then
s = tostring( elements.maxcolumns )
r2["column-count"] = s
end
if type( Config.selMultClm ) == "string" then
r3 = Config.selMultClm
end
if type( Config.stylesMultClm ) == "string" then
local src = Config.stylesMultClm .. "/styles.css"
r4 = TemplateData.frame
:extensionTag( "templatestyles",
nil,
{ src = src } )
end
end
elseif elements and elements ~= "" then
r1 = elements
end
elseif s == "string" then
s = mw.text.trim( about )
if s ~= "" then
r1 = { }
table.insert( r1,
{ code = s } )
end
elseif s == "number" then
r1 = { }
table.insert( r1,
{ code = tostring( elements ) } )
end
return r1, r2, r3, r4
end -- facilities()



local function factory( adapt )
-- Retrieve localized text from system message
-- Retrieve localized text from system message
-- Parameter:
-- Parameter:
-- adapt -- string, message ID after "templatedata-"
-- adapt -- string, message ID after "templatedata-"
-- Returns string, with localized text
-- Returns string, with localized text
return mw.message.new( "templatedata-" .. adapt ):plain()
local o = mw.message.new( "templatedata-" .. adapt )
if Foreign() then
end -- getLocalizedText()
o:inLanguage( Data.slang )
end
return o:plain()
end -- factory()




Line 227: Line 363:




local function handleNoexportWhitespace( adjust )
local function fair( adjust )
-- Reduce text to one line of plain text, or noexport wikitext blocks
-- Reduces runs of spaces, including newlines, to a single space, so the
-- whole string is on one line. <noexport> blocks are left alone, but the
-- <noexport> tags themselves are removed.
-- adjust -- string
-- adjust -- string
-- Returns string, with adjusted text
-- 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
local r
if adjust:find( "<noexport>", 1, true ) then
if Data.source then
local i = 1
local support = Config.jsonDebug
local j, k = adjust:find( "<noexport>", i, true )
local css
r = ""
if advance then
while j do
css = { height = "6em",
if j > 1 then
resize = "vertical" }
r = r .. collapseWhitespace( adjust:sub( i, j - 1 ) )
r = { [ 1 ] = "syntaxhighlight",
[ 2 ] = Data.source,
lang = "json",
style = table.concat( css, ";" ) }
if alert then
r.class( support )
end
end
i = k + 1
r = TemplateData.frame:callParserFunction( "#tag", r )
else
j, k = adjust:find( "</noexport>", i, true )
if j then
css = { [ "font-size" ] = "77%",
r = r .. adjust:sub( i, j - 1 )
[ "line-height" ] = "1.35" }
i = k + 1
if alert then
j, k = adjust:find( "<noexport>", i, true )
css.resize = "vertical"
else
else
Fault( "missing </noexport>" )
css.display = "none"
end
end
end -- while j
r = mw.html.create( "pre" )
r = r .. adjust:sub( i )
:addClass( support )
:css( css )
:wikitext( mw.text.encode( Data.source ) )
r = tostring( r )
end
r = "\n".. r
else
else
r = collapseWhitespace( adjust )
r = ""
end
end
return r
return r
end -- handleNoexportWhitespace()
end -- fancy()






local function faraway( alternatives )
local function faraway( alternatives )
-- Retrieve project language version from multilingual text
-- Retrieve best language version from multilingual text
-- Parameter:
-- Parameter:
-- alternatives -- table, to be evaluated
-- alternatives -- table, to be evaluated
Line 271: Line 470:
local variants = { }
local variants = { }
local r1, r2
local r1, r2
if not Data.slang then
Data.slang = mw.language.getContentLanguage():getCode()
end
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
Line 284: Line 481:
end -- for k, v
end -- for k, v
if n > 0 then
if n > 0 then
for k, v in pairs( variants ) do
local Multilingual = Fetch( "Multilingual", true )
if v then
if Multilingual and
if n == 1 then
type( Multilingual.i18n ) == "function" then
r1 = v
local show, slang = Multilingual.i18n( variants )
elseif k:lower() == Data.slang then
if show then
variants[ k ] = nil
r1 = show
r1 = v
variants[ slang ] = nil
r2 = variants
r2 = variants
break -- for k, v
end
end
end
end -- for k, v
end
if not r1 then
if not r1 then
local seek = string.format( "^%s-", Data.slang )
Foreign()
for k, v in pairs( variants ) do
for k, v in pairs( variants ) do
if v and k:lower():match( seek ) then
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
break -- for k, v
end
end
end -- for k, v
end -- for k, v
if not r1 then
local others = mw.language.getFallbacksFor( slang )
table.insert( others, "en" )
for i = 1, #others do
seek = others[ i ]
if variants[ seek ] then
r1 = variants[ seek ]
variants[ seek ] = nil
r2 = variants
break -- for i
end
end -- i = 1, #others
end
if not r1 then
for k, v in pairs( variants ) do
if v then
variants[ k ] = nil
r1 = v
r2 = variants
break -- for k, v
end
end -- for k, v
end
end
end
if r2 then
if r2 and Multilingual then
local Multilingual = Fetch( "Multilingual" )
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( "Invalid <code>lang=%s</code>",
Fault( string.format( "%s <code>lang=%s</code>",
"Invalid",
k ) )
k ) )
end
end
Line 342: Line 515:
return r1, r2
return r1, r2
end -- faraway()
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()




Line 355: Line 616:
end -- for k, v
end -- for k, v
for i = 1, n do
for i = 1, n do
for k, v in pairs( Data.heirs ) do
if Data.heirs then
if v and not Data.heirs[ v ] then
for k, v in pairs( Data.heirs ) do
n = n - 1
if v and not Data.heirs[ v ] then
t[ k ].inherits = nil
n = n - 1
Data.heirs[ k ] = nil
t[ k ].inherits = nil
p2 = { }
Data.heirs[ k ] = nil
t2 = { }
p2 = { }
for k2, v2 in pairs( p[ v ] ) do
t2 = { }
p2[ k2 ] = v2
if p[ v ] then
end -- for k2, v2
for k2, v2 in pairs( p[ v ] ) do
if p[ k ] then
for k2, v2 in pairs( p[ k ] ) do
if type( v2 ) ~= "nil" then
p2[ k2 ] = v2
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
end
end -- for k2, v2
p[ k ] = p2
end
for k2, v2 in pairs( t[ v ] ) do
p[ k ] = p2
t2[ k2 ] = v2
for k2, v2 in pairs( t[ v ] ) do
end -- for k2, v2
t2[ k2 ] = v2
for k2, v2 in pairs( t[ k ] ) do
end -- for k2, v2
if type( v2 ) ~= "nil" then
for k2, v2 in pairs( t[ k ] ) do
t2[ k2 ] = v2
if type( v2 ) ~= "nil" then
end
t2[ k2 ] = v2
end -- for k2, v2
t[ k ] = t2
else
Fault( "No params[] inherits " .. v )
end
end
end -- for k2, v2
end
t[ k ] = t2
end -- for k, v
end
end
end -- for k, v
end -- i = 1, n
end -- i = 1, n
if n > 0 then
if n > 0 then
local s
local s
-- The following could be made more efficient by iterating through Data.heirs *backwards*,
-- and breaking as soon as a match is found
for k, v in pairs( Data.heirs ) do
for k, v in pairs( Data.heirs ) do
if v then
if v then
Line 402: Line 671:




local function feasible( about, asked )
local function favorize()
-- Create description head
-- Local customization issues
local boole = { ["font-size"] = "125%" }
-- Parameter:
local l, cx = pcall( mw.loadData,
-- about -- table, supposed to contain description
TemplateData.frame:getTitle() .. "/config" )
-- asked -- true, if mandatory description
local scripting, style
-- Returns <block>, with head, or nil
TemplateData.ltr = not mw.language.getContentLanguage():isRTL()
local para = mw.html.create( "div" )
if TemplateData.ltr then
local plus, r
scripting = "left"
if about and about.description then
else
if type( about.description ) == "string" then
scripting = "right"
para:wikitext( about.description )
end
boole[ "margin-" .. scripting ] = "3em"
Permit.boole = { [false] = { css = boole,
lead = true,
show = "&#x2610;" },
[true] = { css = boole,
lead = true,
show = "&#x2611;" } }
Permit.css = { }
for k, v in pairs( Permit.colors ) do
if k == "tableheadbg" then
k = "tablehead"
end
if k == "fg" then
style = "color"
else
else
para:wikitext( about.description[ 1 ] )
style = "background-color"
end
plus = mw.html.create( "ul" )
if not Config.loudly then
Permit.css[ k ] = { }
plus:addClass( "templatedata-maintain" )
Permit.css[ k ][ style ] = "#" .. v
end -- for k, v
:css( "display", "none" )
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( about.description[ 2 ] ) do
plus:node( mw.html.create( "li" )
:node( mw.html.create( "code" )
:wikitext( k ) )
:node( mw.html.create( "br" ) )
:wikitext( handleNoexportWhitespace( v ) ) )
end -- for k, v
end
end
elseif Config.solo and asked then
for k, v in pairs( Config.basicCnf ) do
para:addClass( "error" )
s = type( cx[ k ] )
:wikitext( Config.solo )
if s == "string" or s == "table" then
Data.less = true
Config[ v ] = cx[ k ]
else
end
para = false
end -- for k, v
end
end
if type( Config.subpage ) ~= "string" or
if para then
if plus then
type( Config.suffix ) ~= "string" then
r = mw.html.create( "div" )
local got = mw.message.new( "templatedata-doc-subpage" )
:node( para )
local suffix
:node( plus )
if got:isDisabled() then
suffix = "doc"
else
else
r = para
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
end
end -- favorize()
return r



local function feasible( all, at, about )
-- Deal with suggestedvalues within parameter
-- Parameter:
-- all -- parameter details
-- .default
-- .type
-- at -- string, with parameter name
-- about -- suggestedvalues -- table,
-- value and possibly description
-- table may have elements:
-- .code -- mandatory
-- .label -- table|string
-- .support -- table|string
-- .icon -- string
-- .class -- table|string
-- .css -- table
-- .style -- string
-- .less -- true: suppress code
-- Returns
-- 1: mw.html object <ul>
-- 2: sequence table with values, or nil
local h = { }
local e, r1, r2, s, v
if #about > 0 then
for i = 1, #about do
e = about[ 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 )
if h[ e.code ] then
s = string.format( "params.%s.%s REPEATED %s",
at,
"suggestedvalues",
e.code )
Fault( s )
else
h[ e.code ] = true
end
end
end -- for i
else
Fault( string.format( "params.%s.suggestedvalues %s",
at, "NOT AN ARRAY" ) )
end
if v then
local code, d, k, 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 all.type:sub( 1, 5 ) == "wiki-" and
swift == e.code then
local rooms = { file = 6,
temp = 10,
user = 2 }
local ns = rooms[ all.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
if e.code == all.default then
k = 800
else
k = 300
end
code = mw.html.create( "code" )
:css( "font-weight", tostring( k ) )
:css( "white-space", "nowrap" )
:wikitext( swift )
u:node( code )
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 all.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 and v ~= false then
Fault( string.format( "params.%s.suggestedvalues INVALID", at ) )
r1 = mw.html.create( "code" )
:addClass( "error" )
:wikitext( "INVALID" )
end
return r1, r2
end -- feasible()
end -- feasible()


Line 467: Line 1,022:
local pointers = { }
local pointers = { }
local points = { }
local points = { }
local given = { }
for k, v in pairs( Data.tree.params ) do
for k, v in pairs( Data.tree.params ) do
i = facet( k, 1 )
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
if i then
table.insert( points, i )
table.insert( points, i )
Line 510: Line 1,085:
local code = mw.html.create( "code" )
local code = mw.html.create( "code" )
local desc = mw.html.create( "td" )
local desc = mw.html.create( "td" )
local eager = mw.html.create( "td" )
local legal = true
local legal = true
local param = Data.tree.params[ access ]
local param = Data.tree.params[ access ]
local ranking = { "required", "suggested", "optional", "deprecated" }
local ranking = { "required", "suggested", "optional", "deprecated" }
local r = mw.html.create( "tr" )
local r = mw.html.create( "tr" )
local styles = "mw-templatedata-doc-param-"
local sort, typed
local sort, typed


Line 542: Line 1,119:
end
end
code = mw.html.create( "td" )
code = mw.html.create( "td" )
:addClass( styles .. "name" )
:node( code )
:node( code )
if access:match( "^%d+$" ) then
if access:match( "^%d+$" ) then
Line 548: Line 1,126:
end
end
if type( param.aliases ) == "table" then
if type( param.aliases ) == "table" then
local lapsus
local lapsus, syn
for k, v in pairs( param.aliases ) do
for k, v in pairs( param.aliases ) do
code:tag( "br" )
code:tag( "br" )
Line 558: Line 1,136:
:css( "font-style", "italic" )
:css( "font-style", "italic" )
:wikitext( "string" ) )
:wikitext( "string" ) )
:wikitext( s )
else
syn = mw.html.create( "span" )
:addClass( styles .. "alias" )
:css( "white-space", "nowrap" )
:wikitext( s )
code:node( syn )
end
end
code:wikitext( s )
else
else
lapsus = true
lapsus = true
Line 569: Line 1,153:
if lapsus then
if lapsus then
s = string.format( "params.<code>%s</code>.aliases", access )
s = string.format( "params.<code>%s</code>.aliases", access )
Fault( getLocalizedText( "invalid-value" ):gsub( "$1", s ) )
Fault( factory( "invalid-value" ):gsub( "$1", s ) )
legal = false
legal = false
end
end
Line 575: Line 1,159:


-- description etc.
-- description etc.
s = feasible( param )
s = fashioned( param )
if s then
if s then
desc:node( s )
desc:node( s )
end
end
if param.default or param.example or param.autovalue then
if param.style then
local details = { "default", "example", "autovalue" }
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 dl = mw.html.create( "dl" )
local dd, section, show
local dd, section, show
Line 587: Line 1,185:
show = param[ s ]
show = param[ s ]
if show then
if show then
section = getLocalizedText( "doc-param-" .. s )
dt = mw.html.create( "dt" ):wikitext( section )
dd = mw.html.create( "dd" )
dd = mw.html.create( "dd" )
if (string.len(show) < 80) then
section = factory( "doc-param-" .. s )
dt:cssText("display: inline-block;float: left;margin-right: 1.6em;")
if param.type == "boolean" and
end
( show == "0" or show == "1" ) then
if param.type == "boolean" then
local boole = Permit.boole[ ( show == "1" ) ]
if show == "0" then
if boole.lead == true then
dd:wikitext("<span style=\"color: #610; font-weight: bold;\">False</span> (<code>0</code>)")
dd:node( mw.html.create( "code" )
elseif show == "1" then
:wikitext( show ) )
dd:wikitext("<span style=\"color: #050; font-weight: bold;\">True</span> (<code>1</code>)")
:wikitext( " " )
else
end
dd:wikitext( show )
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 v, css, class, ts = facilities( param )
if v then
local ul
ul, v = feasible( param, access, v )
if v then
dd:newline()
:node( ul )
if css then
dd:css( css )
if class then
dd:addClass( class )
end
if ts then
dd:newline()
dd:node( ts )
end
end
Data.params[ access ].suggestedvalues = v
end
end
end
else
else
dd:wikitext( show )
dd:wikitext( show )
end
end
dl:node( dt )
dl:node( mw.html.create( "dt" )
:wikitext( section ) )
:node( dd )
:node( dd )
end
end
Line 612: Line 1,245:


-- type
-- 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
if param.type then
s = Permit.types[ param.type ]
s = Permit.types[ param.type ]
typed = mw.html.create( "td" )
typed = mw.html.create( "td" )
:addClass( styles .. "type" )
if s then
if s then
if s == "string" then
if s == "string" then
Data.params[ access ].type = s
Data.params[ access ].type = s
typed:wikitext( getLocalizedText( "doc-param-type-" .. s ) )
typed:wikitext( factory( "doc-param-type-" .. s ) )
:tag( "br" )
:tag( "br" )
typed:node( mw.html.create( "span" )
typed:node( mw.html.create( "span" )
Line 625: Line 1,265:
Data.lasting = true
Data.lasting = true
else
else
s = getLocalizedText( "doc-param-type-" .. param.type )
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 )
typed:wikitext( s )
end
end
Line 633: Line 1,277:
:wikitext( "INVALID" )
:wikitext( "INVALID" )
s = string.format( "params.<code>%s</code>.type", access )
s = string.format( "params.<code>%s</code>.type", access )
Fault( getLocalizedText( "invalid-value" ):gsub( "$1", s ) )
Fault( factory( "invalid-value" ):gsub( "$1", s ) )
legal = false
legal = false
end
end
else
else
typed = mw.html.create( "td" )
typed = mw.html.create( "td" )
:wikitext( getLocalizedText( "doc-param-type-unknown" ) )
: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
typed:addClass( "navigation-not-searchable" )

-- status
-- status
if param.required then
if param.required then
mode = 1
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
if param.deprecated then
Fault( string.format( "Required deprecated <code>%s</code>",
Fault( string.format( "Required deprecated <code>%s</code>",
Line 657: Line 1,317:
end
end
status = ranking[ mode ]
status = ranking[ mode ]
ranking = getLocalizedText( "doc-param-status-" .. status )
ranking = factory( "doc-param-status-" .. status )
if mode == 1 or mode == 4 then
if mode == 1 or mode == 4 then
ranking = mw.html.create( "span" )
ranking = mw.html.create( "span" )
Line 665: Line 1,325:
ranking:tag( "br" )
ranking:tag( "br" )
ranking:wikitext( param.deprecated )
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
end
end
eager:attr( "data-sort-value", tostring( mode ) )
:node( ranking )
:addClass( string.format( "%sstatus-%s %s",
styles, status,
"navigation-not-searchable" ) )


-- <tr>
-- <tr>
r:attr( "id", "templatedata:" .. mw.uri.anchorEncode( access ) )
r:attr( "id", "templatedata:" .. mw.uri.anchorEncode( access ) )
:cssText( Permit.styles[ status ] )
:css( Permit.css[ status ] )
:addClass( styles .. status )
:node( begin )
:node( begin )
:node( code )
:node( code )
:node( desc )
:node( desc )
:node( typed )
:node( typed )
:node( mw.html.create( "td" )
:node( eager )
:attr( "data-sort-value", tostring( mode ) )
:node( ranking ) )
:newline()
:newline()
if not legal then
if not legal then
Line 692: Line 1,362:
local r
local r
if Data.tree and Data.tree.params then
if Data.tree and Data.tree.params then
local style = Permit.styles.tableheadbg
local tbl = mw.html.create( "table" )
local tbl = mw.html.create( "table" )
local tr = mw.html.create( "tr" )
:addClass( "wikitable" )
local tr = mw.html.create( "tr" )
feat()
feat()
if Data.order and #Data.order > 1 then
if Data.order and #Data.order > 1 then
tbl:addClass( "sortable" )
tbl:addClass( "sortable" )
end
end
-- if Config.classTable then
if type( Config.classTable ) == "table" then
-- tbl:addClass( Config.classTable )
for k, v in pairs( Config.classTable ) do
tbl:addClass( v )
-- end
if Config.stylesTable then
end -- for k, v
tbl:cssText( Config.stylesTable )
end
end
tr:node( mw.html.create( "th" )
if type( Config.cssTable ) == "table" then
tbl:css( Config.cssTable )
end
tr:addClass( "navigation-not-searchable" )
:node( mw.html.create( "th" )
:attr( "colspan", "2" )
:attr( "colspan", "2" )
:cssText( style )
:css( Permit.css.tablehead )
:wikitext( getLocalizedText( "doc-param-name" ) ) )
:wikitext( factory( "doc-param-name" ) ) )
:node( mw.html.create( "th" )
:node( mw.html.create( "th" )
:cssText( style )
:css( Permit.css.tablehead )
:wikitext( getLocalizedText( "doc-param-desc" ) ) )
:wikitext( factory( "doc-param-desc" ) ) )
:node( mw.html.create( "th" )
:node( mw.html.create( "th" )
:cssText( style )
:css( Permit.css.tablehead )
:wikitext( getLocalizedText( "doc-param-type" ) ) )
:wikitext( factory( "doc-param-type" ) ) )
:node( mw.html.create( "th" )
:node( mw.html.create( "th" )
:cssText( style )
:css( Permit.css.tablehead )
:wikitext( getLocalizedText( "doc-param-status" ) ) )
:wikitext( factory( "doc-param-status" ) ) )
tbl:newline()
tbl:newline()
-- :node( mw.html.create( "thead" )
-- :node( mw.html.create( "thead" )
:node( tr )
:node( tr )
-- )
-- )
:newline()
:newline()
if Data.order then
if Data.order then
local leave, s
for i = 1, #Data.order do
for i = 1, #Data.order do
tbl:node( feature( Data.order[ i ] ) )
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
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
end
if Config.stylesTabWrap then
if Config.cssTabWrap or Data.scroll then
r = mw.html.create( "div" )
r = mw.html.create( "div" )
:cssText( Config.stylesTabWrap )
if type( Config.cssTabWrap ) == "table" then
:node( tbl )
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
else
r = tbl
r = tbl
Line 742: Line 1,441:




local function finalize()
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
-- Wrap presentation into frame
-- Parameter:
-- advance -- true, for nice
-- Returns string
-- Returns string
local r
local r, lapsus
if Data.div then
if Data.div then
r = tostring( Data.div )
r = tostring( Data.div )
Line 751: Line 1,529:
r = Data.strip
r = Data.strip
else
else
r = ""
lapsus = true
r = ""
end
end
return r .. failures()
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 -- finalize()


Line 776: Line 1,566:


local function flat( adjust )
local function flat( adjust )
-- Remove formatting from text string
-- Remove formatting from text string for VE
-- Parameter:
-- Parameter:
-- arglist -- string, to be stripped, or nil
-- arglist -- string, to be stripped, or nil
Line 784: Line 1,574:
r = adjust:gsub( "\n", " " )
r = adjust:gsub( "\n", " " )
if r:find( "<noexport>", 1, true ) then
if r:find( "<noexport>", 1, true ) then
r = r:gsub( "<noexport>(.*)</noexport>", "" )
r = r:gsub( "<noexport>.*</noexport>", "" )
end
if r:find( "<exportonly>", 1, true ) then
r = r:gsub( "</?exportonly>", "" )
end
end
if r:find( "''", 1, true ) then
if r:find( "''", 1, true ) then
Line 791: Line 1,584:
if r:find( "<", 1, true ) then
if r:find( "<", 1, true ) then
local Text = Fetch( "Text" )
local Text = Fetch( "Text" )
r = Text.getPlain( r )
r = Text.getPlain( r:gsub( "<br */?>", "\r\n" ) )
end
end
if r:find( "[", 1, true ) then
if r:find( "[", 1, true ) then
Line 802: Line 1,595:
if r:find( "&", 1, true ) then
if r:find( "&", 1, true ) then
r = mw.text.decode( r )
r = mw.text.decode( r )
if r:find( "&shy;", 1, true ) then
r = r:gsub( "&shy;", "" )
end
end
end
end
end
Line 876: Line 1,672:
if scope then
if scope then
s = type( v )
s = type( v )
if s == "string" then
if s == "string" and k ~= "format" then
v = mw.text.trim( v )
v = mw.text.trim( v )
end
end
Line 882: Line 1,678:
if scope:find( "I18N", 1, true ) then
if scope:find( "I18N", 1, true ) then
if s == "string" then
if s == "string" then
elem = handleNoexportWhitespace( v )
elem = fair( v )
else
elseif s == "table" then
local translated
local translated
v, translated = faraway( v )
v, translated = faraway( v )
Line 889: Line 1,685:
if translated and
if translated and
k == "description" then
k == "description" then
elem = { [ 1 ] = handleNoexportWhitespace( v ),
elem = { [ 1 ] = fair( v ),
[ 2 ] = translated }
[ 2 ] = translated }
else
else
elem = handleNoexportWhitespace( v )
elem = fair( v )
end
end
else
else
Line 898: Line 1,694:
end
end
end
end
if v then
if type( v ) == "string" then
if scope:find( "nowiki", 1, true ) 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 = mw.text.nowiki( v )
elem = elem:gsub( "&#13;\n", "<br>" )
v = v:gsub( string.char( 13 ), "" )
else
else
v = flat( v )
v = flat( v )
end
end
end
elseif s == "boolean" then
else
if scope:find( "boolean", 1, true ) then
if k == "params" and not access then
elem = v
v = nil
else
elem = nil
s = "Type <code>boolean</code> bad for "
elseif k == "format" and not access then
.. f( k, slot )
v = mw.text.decode( v )
Fault( s )
elem = v
elseif k == "inherits" then
elem = v
if not Data.heirs then
Data.heirs = { }
end
end
Data.heirs[ slot ] = v
v = nil
elseif s == "string" then
v = mw.text.nowiki( v )
elem = v
else
elem = v
end
end
elseif 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 type( elem ) ~= "nil" then
if not target then
if not target then
if access then
if access then
if not Data.tree.params then
if not Data.tree.params then
Data.tree.params = { }
Data.tree.params = { }
end
end
Data.tree.params[ slot ] = { }
Data.tree.params[ slot ] = { }
target = Data.tree.params[ slot ]
target = Data.tree.params[ slot ]
else
else
Data.tree = { }
Data.tree = { }
target = Data.tree
target = Data.tree
end
end
end
end
target[ k ] = elem
target[ k ] = elem
elem = false
elem = false
end
end
if type( v ) ~= "nil" then
if v ~= nil then
if not tag then
if not tag then
if access then
if access then
if not Data.params then
if type( v ) == "string" and
Data.params = { }
v.sub( 1, 1 ) == "=" then
v = nil
else
if not Data.params then
Data.params = { }
end
Data.params[ slot ] = { }
tag = Data.params[ slot ]
end
end
Data.params[ slot ] = { }
tag = Data.params[ slot ]
else
else
Data.tag = { }
Data.tag = { }
Line 955: Line 1,774:
end
end
end
end
tag[ k ] = v
if v ~= nil and
k ~= "suggestedvalues" then
tag[ k ] = v
end
end
end
else
else
Line 966: Line 1,788:
end
end
end -- for k, v
end -- for k, v
if not access and Data.got.sets then
fellows()
end
else
else
Fault( f() .. " needs to be of <code>object</code> type" )
Fault( f() .. " needs to be of <code>object</code> type" )
Line 974: Line 1,799:


local function format()
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", "&#92;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 )
elseif unknown then
unknown = string.format( "%s %s", unknown, s )
else
unknown = s
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", "&#92;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
-- Build presented documentation
-- Returns <div>
-- Returns <div>
local r = mw.html.create( "div" )
local r = mw.html.create( "div" )
local s = feasible( Data.tree, true )
local x = fashioned( Data.tree, true, r )
if s then
local s
r:node( s )
if x then
r = x
end
end
if Data.leading then
if Data.leading then
local toc = mw.html.create( "div" )
local toc = mw.html.create( "div" )
local shift
if Config.suppressTOCnum then
if Config.suppressTOCnum then
toc:addClass( Config.suppressTOCnum )
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
end
toc:css( "margin-top", "0.5em" )
toc:addClass( "navigation-not-searchable" )
:css( "margin-top", "0.5em" )
:wikitext( "__TOC__" )
: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()
r:newline()
:node( toc )
:node( toc )
:newline()
:newline()
if shift then
r:node( mw.html.create( "div" )
:css( "clear", shift ) )
:newline()
end
end
end
s = features()
s = features()
if s then
if s then
if Data.leading then
if Data.leading then
r:node( mw.html.create( "h2" )
r:node( mw.html.create( "h" .. Config.nested )
:wikitext( getLocalizedText( "doc-params" ) ) )
:wikitext( factory( "doc-params" ) ) )
:newline()
:newline()
end
end
r:node( s )
r:node( s )
end
end
if Data.tree and Data.tree.format then
if Data.shared then
local e, style
local global = mw.html.create( "div" )
s = Data.tree.format:lower( Data.tree.format )
:attr( "id", "templatedata-global" )
local shift
if s == "inline" or s == "block" then
style = "i"
if TemplateData.ltr then
shift = "right"
else
else
style = "code"
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" )
:addClass( "navigation-not-searchable" )
:wikitext( show .. ": " )
:node( e ) )
end
end
r:node( mw.html.create( "p" )
:wikitext( "Format: " )
:node( mw.html.create( style )
:wikitext( s ) ) )
end
end
return r
return r
end -- format()
end -- formatter()




Line 1,021: Line 2,066:
local function free()
local function free()
-- Remove JSON comment lines
-- Remove JSON comment lines
Data.source:gsub( "([{,\"'])(%s*\n%s*//.*\n%s*)([},\"'])",
if Data.source:find( "//", 1, true ) then
Data.source:gsub( "([{,\"'])(%s*\n%s*//.*\n%s*)([{},\"'])",
"%1%3" )
"%1%3" )
end
end -- free()
end -- free()


Line 1,028: Line 2,075:


local function full()
local function full()
-- Build HTML table for display from JSON data, and append an invisible
-- Build survey table from JSON data, append invisible <templatedata>
-- <templatedata> block.
Data.div = mw.html.create( "div" )
Data.div = mw.html.create( "div" )
:addClass( "mw-templatedata-doc-wrap" )
:addClass( "mw-templatedata-doc-wrap" )
if Permit.css.bg then
Data.div:css( Permit.css.bg )
end
if Permit.css.fg then
Data.div:css( Permit.css.fg )
end
focus()
focus()
if Data.tag then
if Data.tag and type( Data.got.params ) == "table" then
if type( Data.got.params ) == "table" then
for k, v in pairs( Data.got.params ) do
for k, v in pairs( Data.got.params ) do
focus( k )
focus( k )
end -- for k, v
end -- for k, v
if Data.heirs then
if Data.heirs then
fathers()
fathers()
end
end
end
end
end
Data.div:node( format() )
Data.div:node( formatter() )
if not Data.lazy then
if not Data.lazy then
Data.slim = flush()
Data.slim = flush()
Line 1,054: Line 2,104:
div:wikitext( Data.strip )
div:wikitext( Data.strip )
if Config.loudly then
if Config.loudly then
-- Display raw templatedata table all the time.
Data.div:node( mw.html.create( "hr" )
Data.div:node( mw.html.create( "hr" ) )
:css( { height = "7ex" } ) )
Data.div:node( div )
else
else
-- Creates an expand link to check raw templatedata table.
div:css( "display", "none" )
local wrapper = mw.html.create( "div" )
wrapper:addClass( "mw-collapsible" )
wrapper:addClass( "mw-collapsed" )
wrapper:css( "font-size", "85%" )
div:addClass( "mw-collapsible-content" )
wrapper:wikitext( "'''Test of raw TemplateData output''': " )
wrapper:node( div )
Data.div:node( wrapper )
end
end
Data.div:node( div )
end
end
end
if Data.lasting then
Fault( "deprecated type syntax" )
end
if Data.less then
Fault( Config.solo )
end
end
end -- full()
end -- full()
Line 1,075: Line 2,123:


local function furnish( adapt, arglist )
local function furnish( adapt, arglist )
-- Analyze transclusion
-- Called by f, this function is the first to do any real work when the
-- module is invoked.
-- Parameter:
-- Parameter:
-- adapt -- table, #invoke parameters
-- adapt -- table, #invoke parameters
-- arglist -- table, template parameters
-- arglist -- table, template parameters
-- Returns string
-- Returns string
--local spy=""
local source
local source
favorize()
for k, v in pairs( Config ) do
-- deprecated:
for k, v in pairs( Config.basicCnf ) do
if adapt[ k ] and adapt[ k ] ~= "" then
if adapt[ k ] and adapt[ k ] ~= "" then
Config[ v ] = adapt[ k ]
Config[ v ] = adapt[ k ]
end
end
end -- for k, v
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 )
Config.loudly = faculty( arglist.debug or adapt.debug )
--if mw.site.server:find( "//de.wikipedia.beta.wmflabs.org", 1, true ) then
-- Config.loudly = true
--end
Data.lazy = faculty( arglist.lazy ) and not Config.loudly
Data.lazy = faculty( arglist.lazy ) and not Config.loudly
Data.leading = faculty( arglist.TOC )
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
if arglist.JSON then
source = arglist.JSON
source = arglist.JSON
elseif arglist.Global then
source = TemplateData.getGlobalJSON( arglist.Global,
arglist.Local )
elseif arglist[ 1 ] then
elseif arglist[ 1 ] then
local s = mw.text.trim( arglist[ 1 ] )
local s = mw.text.trim( arglist[ 1 ] )
Line 1,104: Line 2,165:
source = s
source = s
elseif mw.ustring.sub( s, 1, 8 ) ==
elseif mw.ustring.sub( s, 1, 8 ) ==
mw.ustring.char( 127, 39, 34, 96, 85, 78, 73, 81 ) then -- <DEL> ' " ` U N I Q
mw.ustring.char( 127, 39, 34, 96, 85, 78, 73, 81 ) then
Data.strip = s
Data.strip = s
end
end
end
if type( arglist.vertical ) == "string" and
arglist.vertical:match( "^%d*%.?%d+[emprx]+$" ) then
Data.scroll = arglist.vertical
end
end
if not source then
if not source then
Line 1,112: Line 2,177:
source = find()
source = find()
if not source and
if not source and
Config.subpage and Config.suffix and
not Data.title.text:match( Config.subpage ) then
not Data.title.text:match( Config.subpage ) then
local s = string.format( Config.suffix,
local s = string.format( Config.suffix,
Line 1,121: Line 2,185:
end
end
end
end
--if source and
-- ( source:find( "|", 1, true ) or
-- source:find( "}}", 1, true ) ) then
-- -- <ref
--spy=string.format( "[[category:%s]]", Config.strange )
--end
end
end
if not Data.lazy and Config.subpage then
if not Data.lazy then
if not Data.title then
if not Data.title then
Data.title = mw.title.getCurrentTitle()
Data.title = mw.title.getCurrentTitle()
Line 1,134: Line 2,192:
Data.lazy = Data.title.text:match( Config.subpage )
Data.lazy = Data.title.text:match( Config.subpage )
end
end
TemplateData.getPlainJSON( source )
if type( source ) == "string" then
TemplateData.getPlainJSON( source )
return finalize()
end
--return spy .. finalize()
return finalize( faculty( arglist.source ) )
end -- furnish()
end -- furnish()






TemplateData.failsafe = function ( assert )
Failsafe.failsafe = function ( atleast )
-- Retrieve versioning and check for compliance
-- Checks the age of this implementation against some minimum ("assert").
-- 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 not assert or assert <= TemplateData.serial then
if last or link or linked or since == "wikidata" then
r = TemplateData.serial
local item = Failsafe.item
since = false
else
if type( item ) == "number" and item > 0 then
r = false
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
end
return r
return r
end -- TemplateData.failsafe()
end -- TemplateData.getGlobalJSON()




Line 1,160: Line 2,290:
-- Returns string, or not
-- Returns string, or not
if type( adapt ) == "string" then
if type( adapt ) == "string" then
local JSONutil = Fetch( "JSONutil", true )
Data.source = adapt
Data.source = adapt
free()
free()
if JSONutil then
Data.got = mw.text.jsonDecode( Data.source )
local Multilingual = Fetch( "Multilingual", true )
if Data.got then
full()
local f
if Data.lasting then
if Multilingual then
Fault( "deprecated type syntax" )
f = Multilingual.i18n
end
if Data.less then
Fault( Config.solo )
end
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
elseif not Data.strip then
Fault( "fatal JSON error" )
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
end
end
Line 1,191: Line 2,332:


p.f = function ( frame )
p.f = function ( frame )
-- Template call
-- The entry point for templates invoking the module.
local lucky, r
-- Just wraps furnish in an exception handler.
local lucky, result
TemplateData.frame = frame
TemplateData.frame = frame
lucky, result = pcall( furnish, frame.args, frame:getParent().args )
lucky, r = pcall( furnish, frame.args, frame:getParent().args )
if not lucky then
if not lucky then
Fault( "INTERNAL: " .. result )
Fault( "INTERNAL: " .. r )
result = failures()
r = failures()
end
end
return result
return r
end -- p.f()
end -- p.f


p.failsafe = function ( frame )
p.failsafe = function ( frame )
Line 1,218: Line 2,358:
end
end
end
end
return TemplateData.failsafe( since ) or ""
return Failsafe.failsafe( since ) or ""
end -- p.failsafe()
end -- p.failsafe


p.TemplateData = function ()
p.TemplateData = function ()

Latest revision as of 15:06, 12 April 2023

local TemplateData = { suite  = "TemplateData",
                       serial = "2022-03-10",
                       item   = 46997995 }
--[==[
improve template:TemplateData
]==]
local Failsafe = TemplateData


local Config = {
    -- multiple option names mapped into unique internal fields
    basicCnf = { catProblem          = "strange",
                 classMultiColumns   = "selMultClm",
                 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",
                 tStylesMultiColumns = "stylesMultClm" },
    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  = { bg          = "FFFFFF",
                fg          = "000000",
                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 boolean",
                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 facilities( apply )
    -- Retrieve details of suggestedvalues
    -- Parameter:
    --     apply  -- table, with plain or enhanced values
    --               .suggestedvalues  -- table|string|number, or more
    -- Returns
    --     1  -- table, with suggestedvalues
    --     2  -- table, with CSS map, or not
    --     3  -- string, with class, or not
    --     4  -- string, with templatestyles, or not
    local elements = apply.suggestedvalues
    local s        = type( elements )
    local r1, r2, r3, r4
    if s == "table" then
        local values = elements.values
        if type( values ) == "table" then
            r1 = values
            if type( elements.scroll ) == "string" then
                r2 = r2  or  { }
                r2.height   = apply.scroll
                r2.overflow = "auto"
            end
            if type( elements.minwidth ) == "string" then
                local s = type( elements.maxcolumns )
                r2 = r2  or  { }
                r2["column-width"] = elements.minwidth
                if s == "string"  or
                   s == "number" then
                    s = tostring( elements.maxcolumns )
                    r2["column-count"] = s
                end
                if type( Config.selMultClm ) == "string" then
                    r3 = Config.selMultClm
                end
                if type( Config.stylesMultClm ) == "string" then
                    local src = Config.stylesMultClm .. "/styles.css"
                    r4 = TemplateData.frame
                                     :extensionTag( "templatestyles",
                                                    nil,
                                                    { src = src } )
                end
            end
        elseif elements  and  elements ~= "" then
            r1 = elements
        end
    elseif s == "string" then
        s = mw.text.trim( about )
        if s ~= "" then
            r1 = { }
            table.insert( r1,
                          { code = s } )
        end
    elseif s == "number" then
        r1 = { }
        table.insert( r1,
                      { code = tostring( elements ) } )
    end
    return r1, r2, r3, r4
end -- facilities()



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
        -- The following could be made more efficient by iterating through Data.heirs *backwards*,
        -- and breaking as soon as a match is found
        for k, v in pairs( Data.heirs ) do
            if v then
                if s then
                    s = string.format( "%s &#124; %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, style
    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 = "&#x2610;" },
                     [true]  = { css  = boole,
                                 lead = true,
                                 show = "&#x2611;" } }
    Permit.css   = { }
    for k, v in pairs( Permit.colors ) do
        if k == "tableheadbg" then
            k = "tablehead"
        end
        if k == "fg" then
            style = "color"
        else
            style = "background-color"
        end
        Permit.css[ k ] = { }
        Permit.css[ k ][ style ] = "#" .. 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( all, at, about )
    -- Deal with suggestedvalues within parameter
    -- Parameter:
    --     all    -- parameter details
    --               .default
    --               .type
    --     at     -- string, with parameter name
    --     about  -- suggestedvalues  -- table,
    --                                   value and possibly description
    --                                   table may have elements:
    --                                    .code    -- mandatory
    --                                    .label   -- table|string
    --                                    .support -- table|string
    --                                    .icon    -- string
    --                                    .class   -- table|string
    --                                    .css     -- table
    --                                    .style   -- string
    --                                    .less    -- true: suppress code
    -- Returns
    --     1: mw.html object <ul>
    --     2: sequence table with values, or nil
    local h = { }
    local e, r1, r2, s, v
    if #about > 0 then
        for i = 1, #about do
            e = about[ 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 )
                if h[ e.code ] then
                    s = string.format( "params.%s.%s REPEATED %s",
                                       at,
                                       "suggestedvalues",
                                       e.code )
                    Fault( s )
                else
                    h[ e.code ] = true
                end
            end
        end -- for i
    else
        Fault( string.format( "params.%s.suggestedvalues %s",
                              at, "NOT AN ARRAY" ) )
    end
    if v then
        local code, d, k, 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 all.type:sub( 1, 5 ) == "wiki-"  and
                   swift == e.code then
                    local rooms = { file = 6,
                                    temp = 10,
                                    user = 2 }
                    local ns = rooms[ all.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
                if e.code == all.default then
                    k = 800
                else
                    k = 300
                end
                code = mw.html.create( "code" )
                              :css( "font-weight", tostring( k ) )
                              :css( "white-space", "nowrap" )
                              :wikitext( swift )
                u:node( code )
            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 all.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  and  v ~= false 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 v, css, class, ts = facilities( param )
                    if v then
                        local ul
                        ul, v = feasible( param, access, v )
                        if v then
                            dd:newline()
                              :node( ul )
                            if css then
                                dd:css( css )
                                if class then
                                    dd:addClass( class )
                                end
                                if ts then
                                    dd:newline()
                                    dd:node( ts )
                                end
                            end
                            Data.params[ access ].suggestedvalues = v
                        end
                    end
                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
    typed:addClass( "navigation-not-searchable" )
    -- 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 %s",
                                          styles, status,
                                          "navigation-not-searchable" ) )

    -- <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:addClass( "navigation-not-searchable" )
          :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( "&shy;", 1, true ) then
                r = r:gsub( "&shy;", "" )
            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( "&#13;\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
                    elseif 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
                    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 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 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", "&#92;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 )
                elseif unknown then
                    unknown = string.format( "%s %s", unknown, s )
                else
                    unknown = s
                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", "&#92;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:addClass( "navigation-not-searchable" )
           :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" )
                           :addClass( "navigation-not-searchable" )
                           :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" )
    if Permit.css.bg then
        Data.div:css( Permit.css.bg )
    end
    if Permit.css.fg then
        Data.div:css( Permit.css.fg )
    end
    focus()
    if Data.tag and 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
    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