모듈:Format TemplateData

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,    -- 
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 
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,    --  evaluation
tag     = false,    -- table, exported root element
title   = false,    -- page
tree    = false     -- table, rewritten templatedata object
}
local Permit = {
builder = { after           = "block",
align           = "block",
block           = "block",
compressed      = "block",
dense           = "block",
grouped         = "inline",
half            = "inline",
indent          = "block",
inline          = "inline",
last            = "block",
lead            = "block",
newlines        = "*",
spaced          = "inline" },
colors  = { tableheadbg = "B3B7FF",
required    = "EAF3FF",
suggested   = "FFFFFF",
optional    = "EAECF0",
deprecated  = "FFCBCB" },
params  = { aliases         = "table",
autovalue       = "string",
default         = "string table I18N nowiki",
deprecated      = "boolean string I18N",
description     = "string table I18N",
example         = "string table I18N nowiki",
label           = "string table I18N",
inherits        = "string",
required        = "boolean",
style           = "string table",
suggested       = "boolean",
suggestedvalues = "string table number 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"
--                       "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" .. Data.scream )
if Config.strange then
r = string.format( "%scategory:%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 = "",
stop  = "" },
{ start = "",
stop  = "",
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( ".*", "" )
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 lang=%s",
"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  -- , if to be equipped
-- Returns , 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 
local param     = Data.tree.params access 
local sub, sort = access:match( "(=+)%s*(%S.*)$" )
local headline  = mw.html.create( string.format( "h%d", #sub ) )
local r         = mw.html.create( "tr" )
local td        = mw.html.create( "td" )
:attr( "colspan", "5" )
:attr( "data-sort-value",  "!" .. sort )
local s
if param.style then
s = type( param.style )
if s == "table" then
td:css( param.style )
elseif s == "string" then
td:cssText( param.style )
end
end
s = fashioned( param, false, headline )
if s then
headline = s
else
headline:wikitext( sort )
end
td:node( headline )
r:node( td )
return r
end -- fatten()
local function fathers()
-- Merge params with inherited values
local n = 0
local p = Data.params
local t = Data.tree.params
local p2, t2
for k, v in pairs( Data.heirs ) do
n = n + 1
end -- for k, v
for i = 1, n do
if Data.heirs then
for k, v in pairs( Data.heirs ) do
if v  and  not Data.heirs v  then
n               = n - 1
t k .inherits = nil
Data.heirs k  = nil
p2              = { }
t2              = { }
if p v  then
for k2, v2 in pairs( p v  ) do
p2 k2  = v2
end -- for k2, v2
if p k  then
for k2, v2 in pairs( p k  ) do
if type( v2 ) ~= "nil" then
p2 k2  = v2
end
end -- for k2, v2
end
p k  = p2
for k2, v2 in pairs( t v  ) do
t2 k2  = v2
end -- for k2, v2
for k2, v2 in pairs( t k  ) do
if type( v2 ) ~= "nil" then
t2 k2  = v2
end
end -- for k2, v2
t k  = t2
else
Fault( "No params inherits " .. v )
end
end
end -- for k, v
end
end -- i = 1, n
if n > 0 then
local s
for k, v in pairs( Data.heirs ) do
if v then
if s then
s = string.format( "%s | %s", s, k )
else
s = "Circular inherits: " .. k
end
end
end -- for k, v
Fault( s )
end
end -- fathers()
local function favorize()
-- Local customization issues
local boole  = { "font-size" = "125%" }
local l, cx = pcall( mw.loadData,
TemplateData.frame:getTitle() .. "/config" )
local scripting, 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 = "☐" },
true  = { css  = boole,
lead = true,
show = "☑" } }
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 
--     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",
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",
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( "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 
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.%s", 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.%s.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", support, s )
end
typed:wikitext( s )
end
else
Data.params access .type = "unknown"
typed:addClass( "error" )
:wikitext( "INVALID" )
s = string.format( "params.%s.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 type" )
legal = false
end
end
typed:addClass( "navigation-not-searchable" )
-- status
if param.required then
mode = 1
if param.autovalue then
Fault( string.format( "autovalued %s required",
access ) )
legal = false
end
if param.default then
Fault( string.format( "Defaulted %s required",
access ) )
legal = false
end
if param.deprecated then
Fault( string.format( "Required deprecated %s",
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 %s",
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" ) )
-- 
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  for parameters
-- Returns , 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 %s",
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 = "sets%d.params%s??"
Fault( string.format( s,
at,
mw.text.nowiki( tostring( any ) ) ) )
elseif type( assigned ) == "string" then
if not Data.got.params assigned  then
s = "sets%d.params %s is undefined"
Fault( string.format( s, at, assigned ) )
end
else
s = "sets%d.params%d = %s??"
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 = "sets%d.label??"
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  = "sets%d%s??"
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( "sets%s%s??",
k, v )
Fault( s )
end
else
k = mw.text.nowiki( tostring( k ) )
s = string.format( "sets%s ?????", k )
Fault( s )
end
end -- for k, v
else
s = "params required for sets"
Fault( s )
end
else
s = "sets needs to be of object 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( "" )
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( "", 1, true )
local r
if i then
local k = s:find( "", 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( "", 1, true ) then
r = r:gsub( ".*", "" )
end
if r:find( "", 1, true ) then
r = r:gsub( "", "" )
end
if r:find( "", 1, true ) then
r = r:gsub( "", "" ):gsub( "", "" )
end
if r:find( "", "\r\n" ) )
end
if r:find( "", 1, true ) then
local WLink = Fetch( "WLink" )
if WLink.isBracketedURL( r ) then
r = r:gsub( "%(hftt?ps?://%S+) ^%+%", "%1" )
end
r = WLink.getPlain( r )
end
if r:find( "&", 1, true ) then
r = mw.text.decode( r )
if r:find( "­", 1, true ) then
r = r:gsub( "­", "" )
end
end
end
return r
end -- flat()
local function flush()
-- JSON encode narrowed input; obey unnamed (numerical) parameters
-- Returns  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( "params.%s", at )
else
r = "root"
end
if a then
r = string.format( "%s.%s", r, a )
end
return r
end
local parent
if access then
parent = Data.got.params access 
else
parent = Data.got
end
if type( parent ) == "table" then
local elem, got, permit, s, scope, slot, tag, target
if access then
permit = Permit.params
if type( access ) == "number" then
slot = tostring( access )
else
slot = access
end
else
permit = Permit.root
end
for k, v in pairs( parent ) do
scope = permit k 
if scope then
s = type( v )
if s == "string"  and  k ~= "format" then
v = mw.text.trim( v )
end
if scope:find( s, 1, true ) then
if scope:find( "I18N", 1, true ) then
if s == "string" then
elem = fair( v )
elseif s == "table" then
local translated
v, translated = faraway( v )
if v then
if translated  and
k == "description" then
elem = {  1  = fair( v ),
2  = translated }
else
elem = fair( v )
end
else
elem = false
end
end
if type( v ) == "string" then
if k == "deprecated" then
if v == "1" then
v = true
elseif v == "0" then
v = false
end
elem = v
elseif scope:find( "nowiki", 1, true ) then
elem = mw.text.nowiki( v )
elem = elem:gsub( "\n", "" )
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 boolean bad for "
.. f( k, slot )
Fault( s )
end
end
else
if k == "params"  and  not access then
v    = nil
elem = nil
elseif k == "format"  and  not access then
elem = mw.text.decode( v )
v    = nil
elseif k == "inherits" then
elem = v
if not Data.heirs then
Data.heirs = { }
end
Data.heirs slot  = v
v                  = nil
elseif k == "style" then
elem = v
v    = nil
elseif s == "string" then
v    = mw.text.nowiki( v )
elem = v
else
elem = v
end
end
if type( elem ) ~= "nil" then
if not target then
if access then
if not Data.tree.params then
Data.tree.params = { }
end
Data.tree.params slot  = { }
target = Data.tree.params slot 
else
Data.tree = { }
target    = Data.tree
end
end
target k  = elem
elem        = false
end
if type( v ) ~= "nil" then
if not tag then
if access then
if type( v ) == "string"  and
v.sub( 1, 1 ) == "=" then
v = nil
else
if not Data.params then
Data.params = { }
end
Data.params slot  = { }
tag = Data.params slot 
end
else
Data.tag = { }
tag      = Data.tag
end
end
if type( v ) ~= "nil"  and
k ~= "suggestedvalues" then
tag k  = v
end
end
else
s = string.format( "Type %s 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 object type" )
end
end -- focus()
local function format()
-- Build formatted element
-- Returns 
local source = Data.tree.format:lower()
local r, s
if source == "inline"  or  source == "block" then
r = mw.html.create( "i" )
:wikitext( source )
else
local code
if source:find( "|", 1, true ) then
local scan = "^\n *%{%{\n _*|\n _*=\n _*%}%}\n *$"
if source:match( scan ) then
code = source:gsub( "\n", "N" )
else
s = mw.text.nowiki( source ):gsub( "\n", "\n" )
s = tostring( mw.html.create( "code" )
:wikitext( s ) )
Fault( "Invalid format " .. s )
source = false
end
else
local words = mw.text.split( source, "%s+" )
local show, start, support, unknown
for i = 1, #words do
s = words i 
if i == 1 then
start = s
end
support = Permit.builder s 
if support == start  or
support == "*" then
Permit.builder s  = true
elseif s:match( "^1-9%d?" ) and
Permit.builder.align then
Permit.builder.align = tonumber( s )
else
if unknown then
unknown = string.format( "%s %s", unknown, s )
else
unknown = s
end
end
end -- i = 1, #words
if unknown then
s = tostring( mw.html.create( "code" )
:css( "white-space", "nowrap" )
:wikitext( s ) )
Fault( "Unknown/misplaced format keyword " .. s )
source = false
start  = false
end
if start == "inline" then
if Permit.builder.half == true then
show = "inline half"
code = "_=_"
elseif Permit.builder.grouped == true then
show = "inline grouped"
code = " _=_"
elseif Permit.builder.spaced == true then
show = "inline spaced"
code = " _ = _ "
end
if Permit.builder.newlines == true then
show = show or "inline"
code = code or "_=_"
show = show .. " newlines"
code = string.format( "N%sN", code )
end
elseif start == "block" then
local space  = ""     -- amid "|" and name
local spaced = " "    -- preceding "="
local spacer = " "    -- following "="
local suffix = "N"    -- closing "}}" on new line
show = "block"
if Permit.builder.indent == true then
start = " "
show = "block indent"
else
start = ""
end
if Permit.builder.compressed == true then
spaced = ""
spacer = ""
show   = show .. " compressed"
if Permit.builder.last == true then
show = show .. " last"
else
suffix = ""
end
else
if Permit.builder.lead == true then
show  = show .. " lead"
space = " "
end
if type( Permit.builder.align ) ~= "string" then
local n
s = " align"
if Permit.builder.align == true then
n = 0
if type( Data.got ) == "table"  and
type( Data.got.params ) == "table" then
for k, v in pairs( Data.got.params ) do
if type( v ) == "table"  and
not v.deprecated  and
type( k ) == "string" then
k = mw.ustring.len( k )
if k > n then
n = k
end
end
end -- for k, v
end
else
n = Permit.builder.align
if type( n ) == "number"  and  n > 1 then
s = string.format( "%s %d", s, n )
else
n = 0    -- How comes?
end
end
if n > 1 then
spaced = string.rep( "_",  n - 1 )  ..  " "
end
show = show .. s
elseif Permit.builder.after == true then
spaced = ""
show   = show .. " after"
elseif Permit.builder.dense == true then
spaced = ""
spacer = ""
show   = show .. " dense"
end
if Permit.builder.last == true then
suffix = spacer
show   = show .. " last"
end
end
code = string.format( "N%s_%s=%s_%sN",
start,
space,
spaced,
spacer,
suffix )
if show == "block" then
show = "block newlines"
end
end
if show then
r = mw.html.create( "span" )
:wikitext( show )
end
end
if code then
source = code:gsub( "N", "\n" )
code   = mw.text.nowiki( code ):gsub( "N", "\n" )
code   = mw.html.create( "code" )
:css( "margin-left",  "1em" )
:css( "margin-right", "1em" )
:wikitext( code )
if r then
r = mw.html.create( "span" )
:node( r )
:node( code )
else
r = code
end
end
end
if source and Data.tag then
Data.tag.format = source
end
return r
end -- format()
local function formatter()
-- Build presented documentation
-- Returns 
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",
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",
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 
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 then
if type( Data.got.params ) == "table" then
for k, v in pairs( Data.got.params ) do
focus( k )
end -- for k, v
if Data.heirs then
fathers()
end
end
end
Data.div:node( formatter() )
if not Data.lazy then
Data.slim = flush()
if TemplateData.frame then
local div   = mw.html.create( "div" )
local tdata = {  1  = "templatedata",
2  = Data.slim }
Data.strip = TemplateData.frame:callParserFunction( "#tag",
tdata )
div:wikitext( Data.strip )
if Config.loudly then
Data.div:node( mw.html.create( "hr" )
:css( { height = "7ex" } ) )
else
div:css( "display", "none" )
end
Data.div:node( div )
end
end
if Data.lasting then
Fault( "deprecated type syntax" )
end
if Data.less then
Fault( Config.solo )
end
end -- full()
local function furnish( adapt, arglist )
-- Analyze transclusion
-- Parameter:
--     adapt    -- table, #invoke parameters
--     arglist  -- table, template parameters
-- Returns string
local source
favorize()
-- deprecated:
for k, v in pairs( Config.basicCnf ) do
if adapt k   and  adapt k  ~= "" then
Config v  = adapt k 
end
end -- for k, v
if arglist.heading  and  arglist.heading:match( "^3-6$" ) then
Config.nested = arglist.heading
else
Config.nested = "2"
end
Config.loudly = faculty( arglist.debug or adapt.debug )
Data.lazy     = faculty( arglist.lazy )  and  not Config.loudly
Data.leading  = faculty( arglist.TOC )
if Data.leading and arglist.TOCsibling then
Data.sibling = mw.text.trim( arglist.TOCsibling )
end
if arglist.lang then
Data.slang = arglist.lang:lower()
elseif adapt.lang then
Data.slang = adapt.lang:lower()
end
if arglist.JSON then
source = arglist.JSON
elseif arglist.Global then
source = TemplateData.getGlobalJSON( arglist.Global,
arglist.Local )
elseif arglist 1  then
local s     = mw.text.trim( arglist 1  )
local start = s:sub( 1, 1 )
if start == " 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
Data.source = adapt
free()
local lucky
lucky, Data.got = pcall( mw.text.jsonDecode, Data.source )
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