وحدة:Format TemplateData

من ويكيبيديا، الموسوعه الحره

يمكن إنشاء صفحة توثيق الوحدة في وحدة: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,    -- <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"
    --       "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
    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
    else
        if k == "params"  and  not access then
        v    = nil
        elem = nil
        elseif k == "format"  and  not access then
        elem = mw.text.decode( v )
        v    = nil
        elseif k == "inherits" then
        elem = v
        if not Data.heirs then
        Data.heirs = { }
        end
        Data.heirs[ slot ] = v
        v      = nil
        elseif k == "style" then
        elem = v
        v    = nil
        elseif s == "string" then
        v    = mw.text.nowiki( v )
        elem = v
        else
        elem = v
        end
    end
    if type( elem ) ~= "nil" then
        if not target then
        if access then
        if not Data.tree.params then
        Data.tree.params = { }
        end
        Data.tree.params[ slot ] = { }
        target = Data.tree.params[ slot ]
        else
        Data.tree = { }
        target    = Data.tree
        end
        end
        target[ k ] = elem
        elem    = false
    end
    if type( v ) ~= "nil" then
        if not tag then
        if access then
        if type( v ) == "string"  and
       v.sub( 1, 1 ) == "=" then
        v = nil
        else
        if not Data.params then
        Data.params = { }
        end
        Data.params[ slot ] = { }
        tag = Data.params[ slot ]
        end
        else
        Data.tag = { }
        tag  = Data.tag
        end
        end
        if type( v ) ~= "nil"  and
       k ~= "suggestedvalues" then
        tag[ k ] = v
        end
    end
    else
    s = string.format( "Type <code>%s</code> bad for %s",
           scope,  f( k, slot ) )
    Fault( s )
    end
    else
    Fault( "Unknown component " .. f( k, slot ) )
    end
    end -- for k, v
    if not access  and Data.got.sets then
    fellows()
    end
    else
    Fault( f() .. " needs to be of <code>object</code> type" )
    end
end -- focus()



local function format()
    -- Build formatted element
    -- Returns <inline>
    local source = Data.tree.format:lower()
    local r, s
    if source == "inline"  or  source == "block" then
    r = mw.html.create( "i" )
       :wikitext( source )
    else
    local code
    if source:find( "|", 1, true ) then
    local scan = "^[\n ]*%{%{[\n _]*|[\n _]*=[\n _]*%}%}[\n ]*$"
    if source:match( scan ) then
    code = source:gsub( "\n", "N" )
    else
    s = mw.text.nowiki( source ):gsub( "\n", "&#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 )
    else
    if unknown then
        unknown = string.format( "%s %s", unknown, s )
    else
        unknown = s
    end
    end
    end -- i = 1, #words
    if unknown then
    s = tostring( mw.html.create( "code" )
         :css( "white-space", "nowrap" )
         :wikitext( s ) )
    Fault( "Unknown/misplaced format keyword " .. s )
    source = false
    start  = false
    end
    if start == "inline" then
    if Permit.builder.half == true then
    show = "inline half"
    code = "{{_ |_=_}}"
    elseif Permit.builder.grouped == true then
    show = "inline grouped"
    code = "{{_ | _=_}}"
    elseif Permit.builder.spaced == true then
    show = "inline spaced"
    code = "{{_ | _ = _ }}"
    end
    if Permit.builder.newlines == true then
    show = show or "inline"
    code = code or "{{_|_=_}}"
    show = show .. " newlines"
    code = string.format( "N%sN", code )
    end
    elseif start == "block" then
    local space  = "" -- amid "|" and name
    local spaced = " "    -- preceding "="
    local spacer = " "    -- following "="
    local suffix = "N"    -- closing "}}" on new line
    show = "block"
    if Permit.builder.indent == true then
    start = " "
    show = "block indent"
    else
    start = ""
    end
    if Permit.builder.compressed == true then
    spaced = ""
    spacer = ""
    show   = show .. " compressed"
    if Permit.builder.last == true then
        show = show .. " last"
    else
        suffix = ""
    end
    else
    if Permit.builder.lead == true then
        show  = show .. " lead"
        space = " "
    end
    if type( Permit.builder.align ) ~= "string" then
        local n
        s = " align"
        if Permit.builder.align == true then
        n = 0
        if type( Data.got ) == "table"  and
       type( Data.got.params ) == "table" then
        for k, v in pairs( Data.got.params ) do
        if type( v ) == "table"  and
           not v.deprecated  and
           type( k ) == "string" then
        k = mw.ustring.len( k )
        if k > n then
            n = k
        end
        end
        end -- for k, v
        end
        else
        n = Permit.builder.align
        if type( n ) == "number"  and  n > 1 then
        s = string.format( "%s %d", s, n )
        else
        n = 0    -- How comes?
        end
        end
        if n > 1 then
        spaced = string.rep( "_",  n - 1 )  ..  " "
        end
        show = show .. s
    elseif Permit.builder.after == true then
        spaced = ""
        show   = show .. " after"
    elseif Permit.builder.dense == true then
        spaced = ""
        spacer = ""
        show   = show .. " dense"
    end
    if Permit.builder.last == true then
        suffix = spacer
        show   = show .. " last"
    end
    end
    code = string.format( "N{{_N%s|%s_%s=%s_%s}}N",
          start,
          space,
          spaced,
          spacer,
          suffix )
    if show == "block" then
    show = "block newlines"
    end
    end
    if show then
    r = mw.html.create( "span" )
       :wikitext( show )
    end
    end
    if code then
    source = code:gsub( "N", "\n" )
    code   = mw.text.nowiki( code ):gsub( "N", "&#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 then
    if type( Data.got.params ) == "table" then
    for k, v in pairs( Data.got.params ) do
    focus( k )
    end -- for k, v
    if Data.heirs then
    fathers()
    end
    end
    end
    Data.div:node( formatter() )
    if not Data.lazy then
    Data.slim = flush()
    if TemplateData.frame then
    local div   = mw.html.create( "div" )
    local tdata = { [ 1 ] = "templatedata",
        [ 2 ] = Data.slim }
    Data.strip = TemplateData.frame:callParserFunction( "#tag",
                tdata )
    div:wikitext( Data.strip )
    if Config.loudly then
    Data.div:node( mw.html.create( "hr" )
          :css( { height = "7ex" } ) )
    else
    div:css( "display", "none" )
    end
    Data.div:node( div )
    end
    end
    if Data.lasting then
    Fault( "deprecated type syntax" )
    end
    if Data.less then
    Fault( Config.solo )
    end
end -- full()



local function furnish( adapt, arglist )
    -- Analyze transclusion
    -- Parameter:
    -- adapt    -- table, #invoke parameters
    -- arglist  -- table, template parameters
    -- Returns string
    local source
    favorize()
    -- deprecated:
    for k, v in pairs( Config.basicCnf ) do
    if adapt[ k ]  and  adapt[ k ] ~= "" then
    Config[ v ] = adapt[ k ]
    end
    end -- for k, v
    if arglist.heading  and  arglist.heading:match( "^[3-6]$" ) then
    Config.nested = arglist.heading
    else
    Config.nested = "2"
    end
    Config.loudly = faculty( arglist.debug or adapt.debug )
    Data.lazy = faculty( arglist.lazy )  and  not Config.loudly
    Data.leading  = faculty( arglist.TOC )
    if Data.leading and arglist.TOCsibling then
    Data.sibling = mw.text.trim( arglist.TOCsibling )
    end
    if arglist.lang then
    Data.slang = arglist.lang:lower()
    elseif adapt.lang then
    Data.slang = adapt.lang:lower()
    end
    if arglist.JSON then
    source = arglist.JSON
    elseif arglist.Global then
    source = TemplateData.getGlobalJSON( arglist.Global,
         arglist.Local )
    elseif arglist[ 1 ] then
    local s = mw.text.trim( arglist[ 1 ] )
    local start = s:sub( 1, 1 )
    if start == "<" then
    Data.strip = s
    elseif start == "{" then
    source = s
    elseif mw.ustring.sub( s, 1, 8 ) ==
   mw.ustring.char( 127, 39, 34, 96, 85, 78, 73, 81 ) then
    Data.strip = s
    end
    end
    if type( arglist.vertical ) == "string"  and
   arglist.vertical:match( "^%d*%.?%d+[emprx]+$" ) then
    Data.scroll = arglist.vertical
    end
    if not source then
    Data.title = mw.title.getCurrentTitle()
    source = find()
    if not source  and
   not Data.title.text:match( Config.subpage ) then
    local s = string.format( Config.suffix,
         Data.title.prefixedText )
    Data.title = mw.title.new( s )
    if Data.title.exists then
    source = find()
    end
    end
    end
    if not Data.lazy then
    if not Data.title then
    Data.title = mw.title.getCurrentTitle()
    end
    Data.lazy = Data.title.text:match( Config.subpage )
    end
    if type( source ) == "string" then
    TemplateData.getPlainJSON( source )
    end
    return finalize( faculty( arglist.source ) )
end -- furnish()



Failsafe.failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    -- atleast  -- string, with required version
    --     or wikidata|item|~|@ or false
    -- Postcondition:
    -- Returns  string  -- with queried version/item, also if problem
    --      false   -- if appropriate
    -- 2020-08-17
    local since  = atleast
    local last   = ( since == "~" )
    local linked = ( since == "@" )
    local link   = ( since == "item" )
    local r
    if last  or  link  or  linked  or  since == "wikidata" then
    local item = Failsafe.item
    since = false
    if type( item ) == "number"  and  item > 0 then
    local suited = string.format( "Q%d", item )
    if link then
    r = suited
    else
    local entity = mw.wikibase.getEntity( suited )
    if type( entity ) == "table" then
    local seek = Failsafe.serialProperty or "P348"
    local vsn  = entity:formatPropertyValues( seek )
    if type( vsn ) == "table"  and
       type( vsn.value ) == "string"  and
       vsn.value ~= "" then
        if last  and  vsn.value == Failsafe.serial then
        r = false
        elseif linked then
        if mw.title.getCurrentTitle().prefixedText
       ==  mw.wikibase.getSitelink( suited ) then
        r = false
        else
        r = suited
        end
        else
        r = vsn.value
        end
    end
    end
    end
    end
    end
    if type( r ) == "nil" then
    if not since  or  since <= Failsafe.serial then
    r = Failsafe.serial
    else
    r = false
    end
    end
    return r
end -- Failsafe.failsafe()



TemplateData.getGlobalJSON = function ( access, adapt )
    -- Retrieve TemplateData from a global repository (JSON)
    -- Parameter:
    -- access  -- string, with page specifier (on WikiMedia Commons)
    -- adapt   -- JSON string or table with local overrides
    -- Returns true, if succeeded
    local plugin = Fetch( "/global" )
    local r
    if type( plugin ) == "table"  and
   type( plugin.fetch ) == "function" then
    local s, got = plugin.fetch( access, adapt )
    if got then
    Data.got    = got
    Data.order  = got.paramOrder
    Data.shared = s
    r   = true
    full()
    else
    Fault( s )
    end
    end
    return r
end -- TemplateData.getGlobalJSON()



TemplateData.getPlainJSON = function ( adapt )
    -- Reduce enhanced JSON data to plain text localized JSON
    -- Parameter:
    -- adapt  -- string, with enhanced JSON
    -- Returns string, or not
    if type( adapt ) == "string" then
    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