پودمان: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"
        --                       "JSONutil"
        --                       "Multilingual"
        --                       "Text"
        --                       "WLink"
        --     allow  -- true: no error if unavailable
        -- Returns table of module
        -- error: Module not available
        local sign = ask
        local r, stem
        if sign:sub( 1, 1 ) == "/" then
            sign = TemplateData.frame:getTitle() .. sign
        else
            stem = sign
            sign = "Module:" .. stem
        end
        if TemplateData.extern then
            r = TemplateData.extern[ sign ]
        else
            TemplateData.extern = { }
        end
        if not r then
            local lucky, g = pcall( require, sign )
            if type( g ) == "table" then
                if stem  and  type( g[ stem ] ) == "function" then
                    r = g[ stem ]()
                else
                    r = g
                end
                TemplateData.extern[ sign ] = r
            elseif not allow then
                error( string.format( "Fetch(%s) %s", sign, g ), 0 )
            end
        end
        return r
    end -- Fetch()
    
    
    
    local function Foreign()
        -- Guess human language
        -- Returns slang, or not
        if type( Data.slang ) == "nil" then
            local Multilingual = Fetch( "Multilingual", true )
            if Multilingual  and
               type( Multilingual.userLangCode ) == "function" then
                Data.slang = Multilingual.userLangCode()
            else
                Data.slang = mw.language.getContentLanguage():getCode()
                                                             :lower()
            end
        end
        if Data.slang  and
           mw.ustring.codepoint( Data.slang, 1, 1 ) > 122 then
            Data.slang = false
        end
        return Data.slang
    end -- Foreign()
    
    
    
    local function facet( ask, at )
        -- Find physical position of parameter definition in JSON
        -- Parameter:
        --     ask  -- string, parameter name
        --     at   -- number, physical position within definition
        -- Returns number, or nil
        local seek = string.format( Permit.search,
                                    ask:gsub( "%%", "%%%%" )
                                       :gsub( "([%-.()+*?^$%[%]])",
                                              "%%%1" ) )
        local i, k, r, slice, source
        if not Data.jump then
            Data.jump = Data.source:find( "params", 2 )
            if Data.jump then
                Data.jump = Data.jump + 7
            else
                Data.jump = 1
            end
        end
        i, k = Data.source:find( seek,  at + Data.jump )
        while i  and  not r do
            source = Data.source:sub( k + 1 )
            slice  = source:match( "^%s*\"([^\"]+)\"s*:" )
            if not slice then
                slice = source:match( "^%s*'([^']+)'%s*:" )
            end
            if ( slice and Permit.params[ slice ] )   or
               source:match( "^%s*%}" ) then
                r = k
            else
                i, k = Data.source:find( seek,  k )
            end
        end    -- while i
        return r
    end -- facet()
    
    
    
    local function facilities( apply )
        -- Retrieve details of suggestedvalues
        -- Parameter:
        --     apply  -- table, with plain or enhanced values
        --               .suggestedvalues  -- table|string|number, or more
        -- Returns
        --     1  -- table, with suggestedvalues
        --     2  -- table, with CSS map, or not
        --     3  -- string, with class, or not
        --     4  -- string, with templatestyles, or not
        local elements = apply.suggestedvalues
        local s        = type( elements )
        local r1, r2, r3, r4
        if s == "table" then
            local values = elements.values
            if type( values ) == "table" then
                r1 = values
                if type( elements.scroll ) == "string" then
                    r2 = r2  or  { }
                    r2.height   = apply.scroll
                    r2.overflow = "auto"
                end
                if type( elements.minwidth ) == "string" then
                    local s = type( elements.maxcolumns )
                    r2 = r2  or  { }
                    r2["column-width"] = elements.minwidth
                    if s == "string"  or
                       s == "number" then
                        s = tostring( elements.maxcolumns )
                        r2["column-count"] = s
                    end
                    if type( Config.selMultClm ) == "string" then
                        r3 = Config.selMultClm
                    end
                    if type( Config.stylesMultClm ) == "string" then
                        local src = Config.stylesMultClm .. "/styles.css"
                        r4 = TemplateData.frame
                                         :extensionTag( "templatestyles",
                                                        nil,
                                                        { src = src } )
                    end
                end
            elseif elements  and  elements ~= "" then
                r1 = elements
            end
        elseif s == "string" then
            s = mw.text.trim( about )
            if s ~= "" then
                r1 = { }
                table.insert( r1,
                              { code = s } )
            end
        elseif s == "number" then
            r1 = { }
            table.insert( r1,
                          { code = tostring( elements ) } )
        end
        return r1, r2, r3, r4
    end -- facilities()
    
    
    
    local function factory( adapt )
        -- Retrieve localized text from system message
        -- Parameter:
        --     adapt  -- string, message ID after "templatedata-"
        -- Returns string, with localized text
        local o = mw.message.new( "templatedata-" .. adapt )
        if Foreign() then
            o:inLanguage( Data.slang )
        end
        return o:plain()
    end -- factory()
    
    
    
    local function faculty( adjust )
        -- Test template arg for boolean
        --     adjust  -- string or nil
        -- Returns boolean
        local s = type( adjust )
        local r
        if s == "string" then
            r = mw.text.trim( adjust )
            r = ( r ~= ""  and  r ~= "0" )
        elseif s == "boolean" then
            r = adjust
        else
            r = false
        end
        return r
    end -- faculty()
    
    
    
    local function failures()
        -- Retrieve error collection and category
        -- Returns string
        local r
        if Data.scream then
            local e = mw.html.create( "span" )
                             :addClass( "error" )
                             :wikitext( Data.scream )
            r = tostring( e )
            mw.addWarning( "'''TemplateData'''<br />" .. Data.scream )
            if Config.strange then
                r = string.format( "%s[[category:%s]]",
                                   r,
                                   Config.strange )
            end
        else
            r = ""
        end
        return r
    end -- failures()
    
    
    
    local function fair( adjust )
        -- Reduce text to one line of plain text, or noexport wikitext blocks
        --     adjust  -- string
        -- Returns string, with adjusted text
        local f    = function ( a )
                         return a:gsub( "%s*\n%s*", " " )
                                 :gsub( "%s%s+", " " )
                     end
        local tags = { { start = "<noexport>",
                         stop  = "</noexport>" },
                       { start = "<exportonly>",
                         stop  = "</exportonly>",
                         l     = false }
                     }
        local r = adjust
        local i, j, k, s, tag
        for m = 1, 2 do
            tag = tags[ m ]
            if r:find( tag.start, 1, true ) then
                s     = r
                r     = ""
                i     = 1
                tag.l = true
                j, k  = s:find( tag.start, i, true )
                while j do
                    if j > 1 then
                        r = r .. f( s:sub( i,  j - 1 ) )
                    end
                    i    = k + 1
                    j, k = s:find( tag.stop, i, true )
                    if j then
                        if m == 1 then
                            r = r .. s:sub( i,  j - 1 )
                        end
                        i    = k + 1
                        j, k = s:find( tag.start, i, true )
                    else
                        Fault( "missing " .. tag.stop )
                    end
                end    -- while j
                r = r .. s:sub( i )
            elseif m == 1 then
                r = f( r )
            end
        end -- for m
        if tags[ 2 ].l then
            r = r:gsub( "<exportonly>.*</exportonly>", "" )
        end
        return r
    end -- fair()
    
    
    
    local function fancy( advance, alert )
        -- Present JSON source
        -- Parameter:
        --     advance  -- true, for nice
        --     alert    -- true, for visible
        -- Returns string
        local r
        if Data.source then
            local support = Config.jsonDebug
            local css
            if advance then
                css = { height = "6em",
                        resize = "vertical" }
                r   = { [ 1 ] = "syntaxhighlight",
                        [ 2 ] = Data.source,
                        lang  = "json",
                        style = table.concat( css, ";" ) }
                if alert then
                    r.class( support )
                end
                r = TemplateData.frame:callParserFunction( "#tag", r )
            else
                css = { [ "font-size" ]   = "77%",
                        [ "line-height" ] = "1.35" }
                if alert then
                    css.resize = "vertical"
                else
                    css.display = "none"
                end
                r = mw.html.create( "pre" )
                           :addClass( support )
                           :css( css )
                           :wikitext( mw.text.encode( Data.source ) )
                r = tostring( r )
            end
            r = "\n".. r
        else
            r = ""
        end
        return r
    end -- fancy()
    
    
    
    local function faraway( alternatives )
        -- Retrieve best language version from multilingual text
        -- Parameter:
        --     alternatives  -- table, to be evaluated
        -- Returns
        --     1  -- string, with best match
        --     2  -- table of other versions, if any
        local n = 0
        local variants = { }
        local r1, r2
        for k, v in pairs( alternatives ) do
            if type( v ) == "string" then
                v = mw.text.trim( v )
                if v ~= ""  and  type( k ) == "string" then
                    k = k:lower()
                    variants[ k ] = v
                    n             = n + 1
                end
            end
        end -- for k, v
        if n > 0 then
            local Multilingual = Fetch( "Multilingual", true )
            if Multilingual  and
               type( Multilingual.i18n ) == "function" then
                local show, slang = Multilingual.i18n( variants )
                if show then
                    r1 = show
                    variants[ slang ] = nil
                    r2 = variants
                end
            end
            if not r1 then
                Foreign()
                for k, v in pairs( variants ) do
                    if n == 1 then
                        r1 = v
                    elseif Data.slang == k then
                        variants[ k ] = nil
                        r1 = v
                        r2 = variants
                    end
                end -- for k, v
            end
            if r2 and Multilingual then
                for k, v in pairs( r2 ) do
                    if v  and  not Multilingual.isLang( k, true ) then
                        Fault( string.format( "%s <code>lang=%s</code>",
                                              "Invalid",
                                              k ) )
                    end
                end -- for k, v
            end
        end
        return r1, r2
    end -- faraway()
    
    
    
    local function fashioned( about, asked, assign )
        -- Create description head
        -- Parameter:
        --     about   -- table, supposed to contain description
        --     asked   -- true, if mandatory description
        --     assign  -- <block>, if to be equipped
        -- Returns <block>, with head, or nil
        local para = assign or mw.html.create( "div" )
        local plus, r
        if about and about.description then
            if type( about.description ) == "string" then
                para:wikitext( about.description )
            else
                para:wikitext( about.description[ 1 ] )
                plus = mw.html.create( "ul" )
                plus:css( "text-align", "left" )
                for k, v in pairs( about.description[ 2 ] ) do
                    plus:node( mw.html.create( "li" )
                                      :node( mw.html.create( "code" )
                                                    :wikitext( k ) )
                                      :node( mw.html.create( "br" ) )
                                      :wikitext( fair( v ) ) )
                end -- for k, v
                if Config.loudly then
                    plus = mw.html.create( "div" )
                                  :css( "background-color",
                                        "#" .. Config.debugmultilang )
                                  :node( plus )
                else
                    plus:addClass( "templatedata-maintain" )
                        :css( "display", "none" )
                end
            end
        elseif Config.solo and asked then
            para:addClass( "error" )
                :wikitext( Config.solo )
            Data.less = true
        else
            para = false
        end
        if para then
            if plus then
                r = mw.html.create( "div" )
                           :node( para )
                           :node( plus )
            else
                r = para
            end
        end
        return r
    end -- fashioned()
    
    
    
    local function fatten( access )
        -- Create table row for sub-headline
        -- Parameter:
        --     access  -- string, with name
        -- Returns <tr>
        local param     = Data.tree.params[ access ]
        local sub, sort = access:match( "(=+)%s*(%S.*)$" )
        local headline  = mw.html.create( string.format( "h%d", #sub ) )
        local r         = mw.html.create( "tr" )
        local td        = mw.html.create( "td" )
                                 :attr( "colspan", "5" )
                                 :attr( "data-sort-value",  "!" .. sort )
        local s
        if param.style then
            s = type( param.style )
            if s == "table" then
                td:css( param.style )
            elseif s == "string" then
                td:cssText( param.style )
            end
        end
        s = fashioned( param, false, headline )
        if s then
            headline = s
        else
            headline:wikitext( sort )
        end
        td:node( headline )
        r:node( td )
        return r
    end -- fatten()
    
    
    
    local function fathers()
        -- Merge params with inherited values
        local n = 0
        local p = Data.params
        local t = Data.tree.params
        local p2, t2
        for k, v in pairs( Data.heirs ) do
            n = n + 1
        end -- for k, v
        for i = 1, n do
            if Data.heirs then
                for k, v in pairs( Data.heirs ) do
                    if v  and  not Data.heirs[ v ] then
                        n               = n - 1
                        t[ k ].inherits = nil
                        Data.heirs[ k ] = nil
                        p2              = { }
                        t2              = { }
                        if p[ v ] then
                            for k2, v2 in pairs( p[ v ] ) do
                                p2[ k2 ] = v2
                            end -- for k2, v2
                            if p[ k ] then
                                for k2, v2 in pairs( p[ k ] ) do
                                    if type( v2 ) ~= "nil" then
                                        p2[ k2 ] = v2
                                    end
                                end -- for k2, v2
                            end
                            p[ k ] = p2
                            for k2, v2 in pairs( t[ v ] ) do
                                t2[ k2 ] = v2
                            end -- for k2, v2
                            for k2, v2 in pairs( t[ k ] ) do
                                if type( v2 ) ~= "nil" then
                                    t2[ k2 ] = v2
                                end
                            end -- for k2, v2
                            t[ k ] = t2
                        else
                            Fault( "No params[] inherits " .. v )
                        end
                    end
                end -- for k, v
            end
        end -- i = 1, n
        if n > 0 then
            local s
            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
            local JSONutil = Fetch( "JSONutil", true )
            Data.source = adapt
            free()
            if JSONutil then
                local Multilingual = Fetch( "Multilingual", true )
                local f
                if Multilingual then
                    f = Multilingual.i18n
                end
                Data.got = JSONutil.fetch( Data.source, true, f )
            else
                local lucky
                lucky, Data.got = pcall( mw.text.jsonDecode, Data.source )
            end
            if type( Data.got ) == "table" then
                full()
            elseif not Data.strip then
                local scream = type( Data.got )
                if scream == "string" then
                    scream = Data.got
                else
                    scream = "Data.got: " .. scream
                end
                Fault( "fatal JSON error: " .. scream )
            end
        end
        return Data.slim
    end -- TemplateData.getPlainJSON()
    
    
    
    TemplateData.test = function ( adapt, arglist )
        TemplateData.frame = mw.getCurrentFrame()
        return furnish( adapt, arglist )
    end -- TemplateData.test()
    
    
    
    -- Export
    local p = { }
    
    p.f = function ( frame )
        -- Template call
        local lucky, r
        TemplateData.frame = frame
        lucky, r = pcall( furnish, frame.args, frame:getParent().args )
        if not lucky then
            Fault( "INTERNAL: " .. r )
            r = failures()
        end
        return r
    end -- p.f
    
    p.failsafe = function ( frame )
        -- Versioning interface
        local s = type( frame )
        local since
        if s == "table" then
            since = frame.args[ 1 ]
        elseif s == "string" then
            since = frame
        end
        if since then
            since = mw.text.trim( since )
            if since == "" then
                since = false
            end
        end
        return Failsafe.failsafe( since )  or  ""
    end -- p.failsafe
    
    p.TemplateData = function ()
        -- Module interface
        return TemplateData
    end
    
    return p