Modul:TemplateData: Unterschied zwischen den Versionen

Aus FreeWiki
Zur Navigation springen Zur Suche springen
te>PerfektesChaos
(2018-02-10)
K (64 Versionen importiert)
 
(34 dazwischenliegende Versionen von einem anderen Benutzer werden nicht angezeigt)
Zeile 1: Zeile 1:
 
local TemplateData = { suite  = "TemplateData",
 
local TemplateData = { suite  = "TemplateData",
                       serial = "2018-02-10",
+
                       serial = "2019-07-02",
 
                       item  = 46997995 }
 
                       item  = 46997995 }
 
--[=[
 
--[=[
Zeile 28: Zeile 28:
 
                 helpURL      = "support4url",
 
                 helpURL      = "support4url",
 
                 helpUser      = "support4wiki-user-name",
 
                 helpUser      = "support4wiki-user-name",
                 msgDescMiss  = "solo" },
+
                 msgDescMiss  = "solo",
 +
                tStylesTOCnum = "stylesTOCnum" },
 
--  classParams    = "classTable",
 
--  classParams    = "classTable",
 
--  classTable    = false,    -- class for params table
 
--  classTable    = false,    -- class for params table
 +
    debugmultilang = "C0C0C0",
 
     loudly        = false,    -- show exported element, etc.
 
     loudly        = false,    -- show exported element, etc.
 
     solo          = false,    -- complaint on missing description
 
     solo          = false,    -- complaint on missing description
Zeile 36: Zeile 38:
 
     cssTable      = false,    -- styles for params table
 
     cssTable      = false,    -- styles for params table
 
     cssTabWrap    = false,    -- styles for params table wrapper
 
     cssTabWrap    = false,    -- styles for params table wrapper
    debug         = false,
+
    debug         = false,
 
     subpage        = false,    -- pattern to identify subpage
 
     subpage        = false,    -- pattern to identify subpage
 
     suffix        = false,    -- subpage creation scheme
 
     suffix        = false,    -- subpage creation scheme
     suppressTOCnum = false     -- class for TOC number suppression
+
     suppressTOCnum = false,    -- class for TOC number suppression
 +
    jsonDebug      = "json-code-lint"    -- class for jsonDebug tool
 
}
 
}
 
local Data = {
 
local Data = {
Zeile 53: Zeile 56:
 
     params  = false,    -- table, exported parameters
 
     params  = false,    -- table, exported parameters
 
     scream  = false,    -- error messages
 
     scream  = false,    -- error messages
     slang  = false,   -- project language code
+
    sibling = false,    -- TOC juxtaposed
 +
     slang  = nil,     -- project/user language code
 
     slim    = false,    -- JSON reduced to plain
 
     slim    = false,    -- JSON reduced to plain
 
     source  = false,    -- JSON input
 
     source  = false,    -- JSON input
Zeile 62: Zeile 66:
 
}
 
}
 
local Permit = {
 
local Permit = {
 +
    builder = { after      = "block",
 +
                align      = "block",
 +
                block      = "block",
 +
                compressed = "block",
 +
                dense      = "block",
 +
                grouped    = "inline",
 +
                half      = "inline",
 +
                indent    = "block",
 +
                inline    = "inline",
 +
                last      = "block",
 +
                lead      = "block",
 +
                newlines  = "*",
 +
                spaced    = "inline" },
 
     colors  = { tableheadbg = "B3B7FF",
 
     colors  = { tableheadbg = "B3B7FF",
 
                 required    = "EAF3FF",
 
                 required    = "EAF3FF",
Zeile 70: Zeile 87:
 
                 autovalue  = "string",
 
                 autovalue  = "string",
 
                 default    = "string table I18N nowiki",
 
                 default    = "string table I18N nowiki",
                 deprecated  = "boolean string",
+
                 deprecated  = "boolean string I18N",
 
                 description = "string table I18N",
 
                 description = "string table I18N",
 
                 example    = "string table I18N nowiki",
 
                 example    = "string table I18N nowiki",
Zeile 76: Zeile 93:
 
                 inherits    = "string",
 
                 inherits    = "string",
 
                 required    = "boolean",
 
                 required    = "boolean",
 +
                style      = "string table",
 
                 suggested  = "boolean",
 
                 suggested  = "boolean",
 
                 type        = "string" },
 
                 type        = "string" },
Zeile 118: Zeile 136:
  
  
local function Fetch( ask )
+
local function Fetch( ask, allow )
 
     -- Fetch module
 
     -- Fetch module
 
     -- Parameter:
 
     -- Parameter:
     --    ask -- string, with name
+
     --    ask   -- string, with name
     --                     "Multilingual"
+
     --                       "/global"
     --                     "Text"
+
    --                      "JSONutil"
     --                     "WLink"
+
    --                      "Multilingual"
 +
     --                       "Text"
 +
     --                       "WLink"
 +
    --    allow  -- true: no error if unavailable
 
     -- Returns table of module
 
     -- Returns table of module
 
     -- error: Module not available
 
     -- error: Module not available
     local r
+
    local sign = ask
 +
     local r, stem
 +
    if sign:sub( 1, 1 ) == "/" then
 +
        sign = TemplateData.frame:getTitle() .. sign
 +
    else
 +
        stem = sign
 +
        sign = "Module:" .. stem
 +
    end
 
     if TemplateData.extern then
 
     if TemplateData.extern then
         r = TemplateData.extern[ ask ]
+
         r = TemplateData.extern[ sign ]
 
     else
 
     else
 
         TemplateData.extern = { }
 
         TemplateData.extern = { }
 
     end
 
     end
 
     if not r then
 
     if not r then
         local lucky, g = pcall( require, "Module:" .. ask )
+
         local lucky, g = pcall( require, sign )
 
         if type( g ) == "table" then
 
         if type( g ) == "table" then
             r = g[ ask ]()
+
             if stem  and  type( g[ stem ] ) == "function" then
             TemplateData.extern[ ask ] = r
+
                r = g[ stem ]()
         else
+
            else
             error( string.format( "Fetch(%s) %s", ask, g ) )
+
                r = g
 +
            end
 +
             TemplateData.extern[ sign ] = r
 +
         elseif not allow then
 +
             error( string.format( "Fetch(%s) %s", sign, g ) )
 
         end
 
         end
 
     end
 
     end
 
     return r
 
     return r
 
end -- Fetch()
 
end -- Fetch()
 +
 +
 +
 +
local function Foreign()
 +
    -- Guess human language
 +
    -- Returns slang, or not
 +
    if type( Data.slang ) == "nil" then
 +
        local Multilingual = Fetch( "Multilingual", true )
 +
        if Multilingual  and
 +
          type( Multilingual.userLangCode ) == "function" then
 +
            Data.slang = Multilingual.userLangCode()
 +
        else
 +
            Data.slang = mw.language.getContentLanguage():getCode()
 +
                                                        :lower()
 +
        end
 +
    end
 +
    if Data.slang  and
 +
      mw.ustring.codepoint( Data.slang, 1, 1 ) > 122 then
 +
        Data.slang = false
 +
    end
 +
    return Data.slang
 +
end -- Foreign()
  
  
Zeile 182: Zeile 236:
 
     --    adapt  -- string, message ID after "templatedata-"
 
     --    adapt  -- string, message ID after "templatedata-"
 
     -- Returns string, with localized text
 
     -- Returns string, with localized text
     return mw.message.new( "templatedata-" .. adapt ):plain()
+
     local o = mw.message.new( "templatedata-" .. adapt )
 +
    if Foreign() then
 +
        o:inLanguage( Data.slang )
 +
    end
 +
    return o:plain()
 
end -- factory()
 
end -- factory()
  
Zeile 262: Zeile 320:
 
     return r
 
     return r
 
end -- fair()
 
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 )
 
local function faraway( alternatives )
     -- Retrieve project language version from multilingual text
+
     -- Retrieve best language version from multilingual text
 
     -- Parameter:
 
     -- Parameter:
 
     --    alternatives  -- table, to be evaluated
 
     --    alternatives  -- table, to be evaluated
Zeile 275: Zeile 378:
 
     local variants = { }
 
     local variants = { }
 
     local r1, r2
 
     local r1, r2
    if not Data.slang then
 
        Data.slang = mw.language.getContentLanguage():getCode()
 
    end
 
 
     for k, v in pairs( alternatives ) do
 
     for k, v in pairs( alternatives ) do
 
         if type( v ) == "string" then
 
         if type( v ) == "string" then
 
             v = mw.text.trim( v )
 
             v = mw.text.trim( v )
 
             if v ~= "" then
 
             if v ~= "" then
 +
                k = k:lower()
 
                 variants[ k ] = v
 
                 variants[ k ] = v
 
                 n            = n + 1
 
                 n            = n + 1
Zeile 288: Zeile 389:
 
     end -- for k, v
 
     end -- for k, v
 
     if n > 0 then
 
     if n > 0 then
         for k, v in pairs( variants ) do
+
         local Multilingual = Fetch( "Multilingual", true )
             if v then
+
        if Multilingual  and
                 if n == 1 then
+
          type( Multilingual.i18n ) == "function" then
                    r1 = v
+
            local show, slang = Multilingual.i18n( variants )
                 elseif k:lower() == Data.slang then
+
             if show then
                    variants[ k ] = nil
+
                 r1 = show
                    r1 = v
+
                 variants[ slang ] = nil
                    r2 = variants
+
                r2 = variants
                    break -- for k, v
 
                end
 
 
             end
 
             end
         end -- for k, v
+
         end
 
         if not r1 then
 
         if not r1 then
             local seek = string.format( "^%s-", Data.slang )
+
             Foreign()
 
             for k, v in pairs( variants ) do
 
             for k, v in pairs( variants ) do
                 if v and k:lower():match( seek ) then
+
                 if n == 1 then
 +
                    r1 = v
 +
                elseif Data.slang == k then
 
                     variants[ k ] = nil
 
                     variants[ k ] = nil
 
                     r1 = v
 
                     r1 = v
 
                     r2 = variants
 
                     r2 = variants
                    break -- for k, v
 
 
                 end
 
                 end
 
             end -- for k, v
 
             end -- for k, v
            if not r1 then
 
                local others = mw.language.getFallbacksFor( slang )
 
                table.insert( others, "en" )
 
                for i = 1, #others do
 
                    seek = others[ i ]
 
                    if variants[ seek ] then
 
                        r1                  = variants[ seek ]
 
                        variants[ seek ] = nil
 
                        r2                  = variants
 
                        break    -- for i
 
                    end
 
                end -- i = 1, #others
 
            end
 
            if not r1 then
 
                for k, v in pairs( variants ) do
 
                    if v then
 
                        variants[ k ] = nil
 
                        r1 = v
 
                        r2 = variants
 
                        break -- for k, v
 
                    end
 
                end -- for k, v
 
            end
 
 
         end
 
         end
         if r2 then
+
         if r2 and Multilingual then
            local Multilingual = Fetch( "Multilingual" )
 
 
             for k, v in pairs( r2 ) do
 
             for k, v in pairs( r2 ) do
                 if v  and  not Multilingual.isLang( k ) then
+
                 if v  and  not Multilingual.isLang( k, true ) then
                     Fault( string.format( "Invalid <code>lang=%s</code>",
+
                     Fault( string.format( "%s <code>lang=%s</code>",
 +
                                          "Invalid",
 
                                           k ) )
 
                                           k ) )
 
                 end
 
                 end
Zeile 346: Zeile 423:
 
     return r1, r2
 
     return r1, r2
 
end -- faraway()
 
end -- faraway()
 +
 +
 +
 +
local function fashioned( about, asked, assign )
 +
    -- Create description head
 +
    -- Parameter:
 +
    --    about  -- table, supposed to contain description
 +
    --    asked  -- true, if mandatory description
 +
    --    assign  -- <block>, if to be equipped
 +
    -- Returns <block>, with head, or nil
 +
    local para = assign or mw.html.create( "div" )
 +
    local plus, r
 +
    if about and about.description then
 +
        if type( about.description ) == "string" then
 +
            para:wikitext( about.description )
 +
        else
 +
            para:wikitext( about.description[ 1 ] )
 +
            plus = mw.html.create( "ul" )
 +
            plus:css( "text-align", "left" )
 +
            for k, v in pairs( about.description[ 2 ] ) do
 +
                plus:node( mw.html.create( "li" )
 +
                                  :node( mw.html.create( "code" )
 +
                                                :wikitext( k ) )
 +
                                  :node( mw.html.create( "br" ) )
 +
                                  :wikitext( fair( v ) ) )
 +
            end -- for k, v
 +
            if Config.loudly then
 +
                plus = mw.html.create( "div" )
 +
                              :css( "background-color",
 +
                                    "#" .. Config.debugmultilang )
 +
                              :node( plus )
 +
            else
 +
                plus:addClass( "templatedata-maintain" )
 +
                    :css( "display", "none" )
 +
            end
 +
        end
 +
    elseif Config.solo and asked then
 +
        para:addClass( "error" )
 +
            :wikitext( Config.solo )
 +
        Data.less = true
 +
    else
 +
        para = false
 +
    end
 +
    if para then
 +
        if plus then
 +
            r = mw.html.create( "div" )
 +
                      :node( para )
 +
                      :node( plus )
 +
        else
 +
            r = para
 +
        end
 +
    end
 +
    return r
 +
end -- fashioned()
 +
 +
 +
 +
local function fatten( access )
 +
    -- Create table row for sub-headline
 +
    -- Parameter:
 +
    --    access  -- string, with name
 +
    -- Returns <tr>
 +
    local param    = Data.tree.params[ access ]
 +
    local sub, sort = access:match( "(=+)%s*(%S.*)$" )
 +
    local headline  = mw.html.create( string.format( "h%d", #sub ) )
 +
    local r        = mw.html.create( "tr" )
 +
    local td        = mw.html.create( "td" )
 +
                            :attr( "colspan", "5" )
 +
                            :attr( "data-sort-value",  "!" .. sort )
 +
    local s
 +
    if param.style then
 +
        s = type( param.style )
 +
        if s == "table" then
 +
            td:css( param.style )
 +
        elseif s == "string" then
 +
            td:cssText( param.style )
 +
        end
 +
    end
 +
    s = fashioned( param, false, headline )
 +
    if s then
 +
        headline = s
 +
    else
 +
        headline:wikitext( sort )
 +
    end
 +
    td:node( headline )
 +
    r:node( td )
 +
    return r
 +
end -- fatten()
  
  
Zeile 359: Zeile 524:
 
     end -- for k, v
 
     end -- for k, v
 
     for i = 1, n do
 
     for i = 1, n do
         for k, v in pairs( Data.heirs ) do
+
         if Data.heirs then
            if v  and  not Data.heirs[ v ] then
+
            for k, v in pairs( Data.heirs ) do
                n              = n - 1
+
                if v  and  not Data.heirs[ v ] then
                t[ k ].inherits = nil
+
                    n              = n - 1
                Data.heirs[ k ] = nil
+
                    t[ k ].inherits = nil
                p2              = { }
+
                    Data.heirs[ k ] = nil
                t2              = { }
+
                    p2              = { }
                for k2, v2 in pairs( p[ v ] ) do
+
                    t2              = { }
                     p2[ k2 ] = v2
+
                     if p[ v ] then
                end -- for k2, v2
+
                        for k2, v2 in pairs( p[ v ] ) do
                if p[ k ] then
 
                    for k2, v2 in pairs( p[ k ] ) do
 
                        if type( v2 ) ~= "nil" then
 
 
                             p2[ k2 ] = v2
 
                             p2[ k2 ] = v2
 +
                        end -- for k2, v2
 +
                        if p[ k ] then
 +
                            for k2, v2 in pairs( p[ k ] ) do
 +
                                if type( v2 ) ~= "nil" then
 +
                                    p2[ k2 ] = v2
 +
                                end
 +
                            end -- for k2, v2
 
                         end
 
                         end
                    end -- for k2, v2
+
                        p[ k ] = p2
 +
                        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
                p[ k ] = p2
+
            end -- for k, v
                for k2, v2 in pairs( t[ v ] ) do
+
         end
                    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
 
            end
 
         end -- for k, v
 
 
     end -- i = 1, n
 
     end -- i = 1, n
 
     if n > 0 then
 
     if n > 0 then
Zeile 458: Zeile 629:
 
         end -- for k, v
 
         end -- for k, v
 
     end
 
     end
end -- favorize()
+
    if type( Config.subpage ) ~= "string"  or
 
+
      type( Config.suffix ) ~= "string" then
 
+
        local got = mw.message.new( "templatedata-doc-subpage" )
 
+
        local suffix
local function feasible( about, asked )
+
         if got:isDisabled() then
    -- Create description head
+
            suffix = "doc"
    -- Parameter:
 
    --    about  -- table, supposed to contain description
 
    --    asked  -- true, if mandatory description
 
    -- Returns <block>, with head, or nil
 
    local para = mw.html.create( "div" )
 
    local plus, r
 
    if about and about.description then
 
         if type( about.description ) == "string" then
 
            para:wikitext( about.description )
 
 
         else
 
         else
             para:wikitext( about.description[ 1 ] )
+
             suffix = got:plain()
            plus = mw.html.create( "ul" )
+
        end
            if not Config.loudly then
+
        if type( Config.subpage ) ~= "string" then
                plus:addClass( "templatedata-maintain" )
+
             Config.subpage = string.format( "/%s$", suffix )
                    :css( "display", "none" )
 
             end
 
            for k, v in pairs( about.description[ 2 ] ) do
 
                plus:node( mw.html.create( "li" )
 
                                  :node( mw.html.create( "code" )
 
                                                :wikitext( k ) )
 
                                  :node( mw.html.create( "br" ) )
 
                                  :wikitext( fair( v ) ) )
 
            end -- for k, v
 
 
         end
 
         end
    elseif Config.solo and asked then
+
         if type( Config.suffix ) ~= "string" then
         para:addClass( "error" )
+
             Config.suffix = string.format( "%%s/%s", suffix )
            :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
 
     end
 
     end
    return r
+
end -- favorize()
end -- feasible()
 
  
  
Zeile 527: Zeile 667:
 
             local pointers = { }
 
             local pointers = { }
 
             local points  = { }
 
             local points  = { }
 +
            local given    = { }
 
             for k, v in pairs( Data.tree.params ) do
 
             for k, v in pairs( Data.tree.params ) do
 
                 i = facet( k, 1 )
 
                 i = facet( k, 1 )
 +
                if type( v ) == "table" then
 +
                    if type( v.label ) == "string" then
 +
                        s = mw.text.trim( v.label )
 +
                        if s == "" then
 +
                            s = k
 +
                        end
 +
                    else
 +
                        s = k
 +
                    end
 +
                    if given[ s ] then
 +
                        if given[ s ] == 1 then
 +
                            local scream = "Parameter label '%s' detected multiple times"
 +
                            Fault( string.format( scream, s ) )
 +
                            given[ s ] = 2
 +
                        end
 +
                    else
 +
                        given[ s ] = 1
 +
                    end
 +
                end
 
                 if i then
 
                 if i then
 
                     table.insert( points, i )
 
                     table.insert( points, i )
Zeile 635: Zeile 795:
  
 
     -- description etc.
 
     -- description etc.
     s = feasible( param )
+
     s = fashioned( param )
 
     if s then
 
     if s then
 
         desc:node( s )
 
         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
 
     end
 
     if param.default or param.example or param.autovalue then
 
     if param.default or param.example or param.autovalue then
Zeile 685: Zeile 853:
  
 
     -- type
 
     -- type
 +
    if type( param.type ) == "string" then
 +
        param.type = mw.text.trim( param.type )
 +
        if param.type == "" then
 +
            param.type = false
 +
        end
 +
    end
 
     if param.type then
 
     if param.type then
 
         s    = Permit.types[ param.type ]
 
         s    = Permit.types[ param.type ]
Zeile 721: Zeile 895:
 
     if param.required then
 
     if param.required then
 
         mode = 1
 
         mode = 1
 +
        if param.autovalue then
 +
            Fault( string.format( "autovalued <code>%s</code> required",
 +
                                  access ) )
 +
            legal = false
 +
        end
 +
        if param.default then
 +
            Fault( string.format( "Defaulted <code>%s</code> required",
 +
                                  access ) )
 +
            legal = false
 +
        end
 
         if param.deprecated then
 
         if param.deprecated then
 
             Fault( string.format( "Required deprecated <code>%s</code>",
 
             Fault( string.format( "Required deprecated <code>%s</code>",
Zeile 769: Zeile 953:
 
     local r
 
     local r
 
     if Data.tree and Data.tree.params then
 
     if Data.tree and Data.tree.params then
         local tbl   = mw.html.create( "table" )
+
         local tbl = mw.html.create( "table" )
                            :addClass( "wikitable" )
+
                          :addClass( "wikitable" )
         local tr   = mw.html.create( "tr" )
+
         local tr = mw.html.create( "tr" )
 
         feat()
 
         feat()
 
         if Data.order  and  #Data.order > 1 then
 
         if Data.order  and  #Data.order > 1 then
Zeile 806: Zeile 990:
 
           :newline()
 
           :newline()
 
         if Data.order then
 
         if Data.order then
 +
            local leave, s
 
             for i = 1, #Data.order do
 
             for i = 1, #Data.order do
                 tbl:node( feature( Data.order[ i ] ) )
+
                 s = Data.order[ i ]
 +
                if s:sub( 1, 1 ) == "=" then
 +
                    leave = true
 +
                    tbl:node( fatten( s ) )
 +
                    Data.order[ i ] = false
 +
                elseif s:match( "[=|]" ) then
 +
                    Fault( string.format( "Bad param <code>%s</code>",
 +
                                          s ) )
 +
                else
 +
                    tbl:node( feature( s ) )
 +
                end
 
             end -- for i = 1, #Data.order
 
             end -- for i = 1, #Data.order
 +
            if leave then
 +
                for i = #Data.order, 1, -1 do
 +
                    if not Data.order[ i ] then
 +
                        table.remove( Data.order, i )
 +
                    end
 +
                end -- for i = #Data.order, 1, -1
 +
            end
 +
            Data.tag.paramOrder = Data.order
 
         end
 
         end
         if Config.cssTabWrap then
+
         if Config.cssTabWrap or Data.scroll then
 
             r = mw.html.create( "div" )
 
             r = mw.html.create( "div" )
 
             if type( Config.cssTabWrap ) == "table" then
 
             if type( Config.cssTabWrap ) == "table" then
Zeile 817: Zeile 1.020:
 
                 -- deprecated
 
                 -- deprecated
 
                 r:cssText( Config.cssTabWrap )
 
                 r:cssText( Config.cssTabWrap )
 +
            end
 +
            if Data.scroll then
 +
                r:css( "height",  Data.scroll )
 +
                :css( "overflow", "auto" )
 
             end
 
             end
 
             r:node( tbl )
 
             r:node( tbl )
Zeile 828: Zeile 1.035:
  
  
local function finalize()
+
local function finalize( advance )
 
     -- Wrap presentation into frame
 
     -- Wrap presentation into frame
 +
    -- Parameter:
 +
    --    advance  -- true, for nice
 
     -- Returns string
 
     -- Returns string
     local r
+
     local r, lapsus
 
     if Data.div then
 
     if Data.div then
 
         r = tostring( Data.div )
 
         r = tostring( Data.div )
Zeile 837: Zeile 1.046:
 
         r = Data.strip
 
         r = Data.strip
 
     else
 
     else
         r = ""
+
         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
 
     end
     return r .. failures()
+
     return r
 
end -- finalize()
 
end -- finalize()
  
Zeile 877: Zeile 1.098:
 
         if r:find( "<", 1, true ) then
 
         if r:find( "<", 1, true ) then
 
             local Text = Fetch( "Text" )
 
             local Text = Fetch( "Text" )
             r = Text.getPlain( r )
+
             r = Text.getPlain( r:gsub( "<br */?>", "\r\n" ) )
 
         end
 
         end
 
         if r:find( "[", 1, true ) then
 
         if r:find( "[", 1, true ) then
Zeile 962: Zeile 1.183:
 
             if scope then
 
             if scope then
 
                 s = type( v )
 
                 s = type( v )
                 if s == "string" then
+
                 if s == "string"  and  k ~= "format" then
 
                     v = mw.text.trim( v )
 
                     v = mw.text.trim( v )
 
                 end
 
                 end
Zeile 969: Zeile 1.190:
 
                         if s == "string" then
 
                         if s == "string" then
 
                             elem = fair( v )
 
                             elem = fair( v )
                         else
+
                         elseif s == "table" then
 
                             local translated
 
                             local translated
 
                             v, translated = faraway( v )
 
                             v, translated = faraway( v )
Zeile 984: Zeile 1.205:
 
                             end
 
                             end
 
                         end
 
                         end
                         if v then
+
                         if type( v ) == "string" then
                             if scope:find( "nowiki", 1, true ) then
+
                             if k == "deprecated" then
 +
                                if v == "1" then
 +
                                    v = true
 +
                                elseif v == "0" then
 +
                                    v = false
 +
                                end
 +
                                elem = v
 +
                            elseif scope:find( "nowiki", 1, true ) then
 
                                 elem = mw.text.nowiki( v )
 
                                 elem = mw.text.nowiki( v )
 +
                                elem = elem:gsub( "&#13;\n", "<br>" )
 +
                                v    = v:gsub( string.char( 13 ),  "" )
 
                             else
 
                             else
 
                                 v = flat( v )
 
                                 v = flat( v )
 +
                            end
 +
                        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
 
                         end
 
                         end
Zeile 996: Zeile 1.234:
 
                             elem = nil
 
                             elem = nil
 
                         elseif k == "format"  and  not access then
 
                         elseif k == "format"  and  not access then
                             v    = mw.text.decode( v )
+
                             elem = mw.text.decode( v )
                             elem = v
+
                             v    = nil
 
                         elseif k == "inherits" then
 
                         elseif k == "inherits" then
 
                             elem = v
 
                             elem = v
Zeile 1.005: Zeile 1.243:
 
                             Data.heirs[ slot ] = v
 
                             Data.heirs[ slot ] = v
 
                             v                  = nil
 
                             v                  = nil
 +
                        elseif k == "style" then
 +
                            elem = v
 +
                            v    = nil
 
                         elseif s == "string" then
 
                         elseif s == "string" then
 
                             v    = mw.text.nowiki( v )
 
                             v    = mw.text.nowiki( v )
Zeile 1.031: Zeile 1.272:
 
                         if not tag then
 
                         if not tag then
 
                             if access then
 
                             if access then
                                 if not Data.params then
+
                                 if type( v ) == "string"  and
                                     Data.params = { }
+
                                  v.sub( 1, 1 ) == "=" then
 +
                                    v = nil
 +
                                else
 +
                                    if not Data.params then
 +
                                        Data.params = { }
 +
                                    end
 +
                                     Data.params[ slot ] = { }
 +
                                    tag = Data.params[ slot ]
 
                                 end
 
                                 end
                                Data.params[ slot ] = { }
 
                                tag = Data.params[ slot ]
 
 
                             else
 
                             else
 
                                 Data.tag = { }
 
                                 Data.tag = { }
Zeile 1.041: Zeile 1.287:
 
                             end
 
                             end
 
                         end
 
                         end
                         tag[ k ] = v
+
                         if type( v ) ~= "nil" then
 +
                            tag[ k ] = v
 +
                        end
 
                     end
 
                     end
 
                 else
 
                 else
Zeile 1.060: Zeile 1.308:
  
 
local function format()
 
local function format()
 +
    -- Build formatted element
 +
    -- Returns <inline>
 +
    local source = Data.tree.format:lower()
 +
    local r, s
 +
    if source == "inline"  or  source == "block" then
 +
        r = mw.html.create( "i" )
 +
                  :wikitext( source )
 +
    else
 +
        local code
 +
        if source:find( "|", 1, true ) then
 +
            local scan = "^[\n ]*%{%{[\n _]*|[\n _]*=[\n _]*%}%}[\n ]*$"
 +
            if source:match( scan ) then
 +
                code = source:gsub( "\n", "N" )
 +
            else
 +
                s = mw.text.nowiki( source ):gsub( "\n", "&#92;n" )
 +
                s = tostring( mw.html.create( "code" )
 +
                                    :wikitext( s ) )
 +
                Fault( "Invalid format " .. s )
 +
                source = false
 +
            end
 +
        else
 +
            local words = mw.text.split( source, "%s+" )
 +
            local show, start, support, unknown
 +
            for i = 1, #words do
 +
                s = words[ i ]
 +
                if i == 1 then
 +
                    start = s
 +
                end
 +
                support = Permit.builder[ s ]
 +
                if support == start  or
 +
                  support == "*" then
 +
                    Permit.builder[ s ] = true
 +
                elseif s:match( "^[1-9]%d?" ) and
 +
                      Permit.builder.align then
 +
                    Permit.builder.align = tonumber( s )
 +
                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 ) .. " "
 +
                        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 then
 +
        Data.tag.format = source
 +
    end
 +
    return r
 +
end -- format()
 +
 +
 +
 +
local function formatter()
 
     -- Build presented documentation
 
     -- Build presented documentation
 
     -- Returns <div>
 
     -- Returns <div>
 
     local r = mw.html.create( "div" )
 
     local r = mw.html.create( "div" )
     local s = feasible( Data.tree, true )
+
     local x = fashioned( Data.tree, true, r )
     if s then
+
    local s
         r:node( s )
+
     if x then
 +
         r = x
 
     end
 
     end
 
     if Data.leading then
 
     if Data.leading then
 
         local toc = mw.html.create( "div" )
 
         local toc = mw.html.create( "div" )
 +
        local shift
 
         if Config.suppressTOCnum then
 
         if Config.suppressTOCnum then
 
             toc:addClass( Config.suppressTOCnum )
 
             toc:addClass( Config.suppressTOCnum )
 +
            if type( Config.stylesTOCnum ) == "string" then
 +
                local src = Config.stylesTOCnum .. "/styles.css"
 +
                s = TemplateData.frame:extensionTag( "templatestyles",
 +
                                                    nil,
 +
                                                    { src = src } )
 +
                r:newline()
 +
                :node( s )
 +
            end
 
         end
 
         end
 
         toc:css( "margin-top", "0.5em" )
 
         toc:css( "margin-top", "0.5em" )
 
           :wikitext( "__TOC__" )
 
           :wikitext( "__TOC__" )
 +
        if Data.sibling then
 +
            local block = mw.html.create( "div" )
 +
            if TemplateData.ltr then
 +
                shift = "right"
 +
            else
 +
                shift = "left"
 +
            end
 +
            block:css( "float", shift )
 +
                :wikitext( Data.sibling )
 +
            r:newline()
 +
            :node( block )
 +
            :newline()
 +
        end
 
         r:newline()
 
         r:newline()
 
         :node( toc )
 
         :node( toc )
 
         :newline()
 
         :newline()
 +
        if shift then
 +
            r:node( mw.html.create( "div" )
 +
                          :css( "clear", shift ) )
 +
            :newline()
 +
        end
 
     end
 
     end
 
     s = features()
 
     s = features()
Zeile 1.087: Zeile 1.540:
 
         r:node( s )
 
         r:node( s )
 
     end
 
     end
     if Data.tree and Data.tree.format then
+
     if Data.shared then
         local e, show, style
+
         local global = mw.html.create( "div" )
        s = Data.tree.format:lower( Data.tree.format )
+
                              :attr( "id", "templatedata-global" )
        if s == "inline" or  s == "block" then
+
        local shift
             style = "i"
+
        if TemplateData.ltr then
 +
             shift = "right"
 
         else
 
         else
             s    = mw.text.encode( s ):gsub( "\n", "&#92;n" )
+
             shift = "left"
            style = "code"
 
            --  TODO  Validate
 
 
         end
 
         end
mw.log(s)
+
        global:css( "float", shift )
         show = "Format"
+
              :wikitext( string.format( "[[%s|%s]]",
        if Config.supportFormat then
+
                                        Data.shared, "Global" ) )
            show = string.format( "[[%s|%s]]",
+
        r:newline()
                                  Config.supportFormat, show )
+
        :node( global )
 +
    end
 +
    if Data.tree and Data.tree.format then
 +
        local e = format()
 +
         if e then
 +
            local show = "Format"
 +
            if Config.supportFormat then
 +
                show = string.format( "[[%s|%s]]",
 +
                                      Config.supportFormat, show )
 +
            end
 +
            r:node( mw.html.create( "p" )
 +
                          :wikitext( show .. ": " )
 +
                          :node( e ) )
 
         end
 
         end
        r:node( mw.html.create( "p" )
 
                      :wikitext( show .. ": " )
 
                      :node( mw.html.create( style )
 
                                    :wikitext( s ) ) )
 
 
     end
 
     end
 
     return r
 
     return r
end -- format()
+
end -- formatter()
  
  
Zeile 1.136: Zeile 1.596:
 
         end
 
         end
 
     end
 
     end
     Data.div:node( format() )
+
     Data.div:node( formatter() )
 
     if not Data.lazy then
 
     if not Data.lazy then
 
         Data.slim = flush()
 
         Data.slim = flush()
Zeile 1.147: Zeile 1.607:
 
             div:wikitext( Data.strip )
 
             div:wikitext( Data.strip )
 
             if Config.loudly then
 
             if Config.loudly then
                 Data.div:node( mw.html.create( "hr" ) )
+
                 Data.div:node( mw.html.create( "hr" )
 +
                                      :css( { height = "7ex" } ) )
 
             else
 
             else
 
                 div:css( "display", "none" )
 
                 div:css( "display", "none" )
Zeile 1.153: Zeile 1.614:
 
             Data.div:node( div )
 
             Data.div:node( div )
 
         end
 
         end
 +
    end
 +
    if Data.lasting then
 +
        Fault( "deprecated type syntax" )
 +
    end
 +
    if Data.less then
 +
        Fault( Config.solo )
 
     end
 
     end
 
end -- full()
 
end -- full()
Zeile 1.164: Zeile 1.631:
 
     --    arglist  -- table, template parameters
 
     --    arglist  -- table, template parameters
 
     -- Returns string
 
     -- Returns string
--local spy=""
 
 
     local source
 
     local source
 
     favorize()
 
     favorize()
Zeile 1.174: Zeile 1.640:
 
     end -- for k, v
 
     end -- for k, v
 
     Config.loudly = faculty( arglist.debug or adapt.debug )
 
     Config.loudly = faculty( arglist.debug or adapt.debug )
--if mw.site.server:find( "//de.wikipedia.beta.wmflabs.org", 1, true ) then
 
--    Config.loudly  = true
 
--end
 
 
     Data.lazy    = faculty( arglist.lazy )  and  not Config.loudly
 
     Data.lazy    = faculty( arglist.lazy )  and  not Config.loudly
 
     Data.leading  = faculty( arglist.TOC )
 
     Data.leading  = faculty( arglist.TOC )
 +
    if Data.leading and arglist.TOCsibling then
 +
        Data.sibling = mw.text.trim( arglist.TOCsibling )
 +
    end
 +
    if arglist.lang then
 +
        Data.slang = arglist.lang:lower()
 +
    elseif adapt.lang then
 +
        Data.slang = adapt.lang:lower()
 +
    end
 
     if arglist.JSON then
 
     if arglist.JSON then
 
         source = arglist.JSON
 
         source = arglist.JSON
 +
    elseif arglist.Global then
 +
        source = TemplateData.getGlobalJSON( arglist.Global,
 +
                                            arglist.Local )
 
     elseif arglist[ 1 ] then
 
     elseif arglist[ 1 ] then
 
         local s    = mw.text.trim( arglist[ 1 ] )
 
         local s    = mw.text.trim( arglist[ 1 ] )
Zeile 1.192: Zeile 1.666:
 
             Data.strip = s
 
             Data.strip = s
 
         end
 
         end
 +
    end
 +
    if type( arglist.vertical ) == "string"  and
 +
      arglist.vertical:match( "^%d*%.?%d+[emprx]+$" ) then
 +
        Data.scroll = arglist.vertical
 
     end
 
     end
 
     if not source then
 
     if not source then
Zeile 1.197: Zeile 1.675:
 
         source = find()
 
         source = find()
 
         if not source  and
 
         if not source  and
          Config.subpage  and  Config.suffix  and
 
 
           not Data.title.text:match( Config.subpage ) then
 
           not Data.title.text:match( Config.subpage ) then
 
             local s = string.format( Config.suffix,
 
             local s = string.format( Config.suffix,
Zeile 1.206: Zeile 1.683:
 
             end
 
             end
 
         end
 
         end
--if source  and
 
--          ( source:find( "|", 1, true ) or
 
--            source:find( "}}", 1, true ) ) then
 
--                      --  <ref
 
--spy=string.format( "[[category:%s]]", Config.strange )
 
--end
 
 
     end
 
     end
     if not Data.lazy and  Config.subpage then
+
     if not Data.lazy then
 
         if not Data.title then
 
         if not Data.title then
 
             Data.title = mw.title.getCurrentTitle()
 
             Data.title = mw.title.getCurrentTitle()
Zeile 1.219: Zeile 1.690:
 
         Data.lazy = Data.title.text:match( Config.subpage )
 
         Data.lazy = Data.title.text:match( Config.subpage )
 
     end
 
     end
     TemplateData.getPlainJSON( source )
+
     if type( source ) == "string" then
     return finalize()
+
        TemplateData.getPlainJSON( source )
--return spy .. finalize()
+
    end
 +
     return finalize( faculty( arglist.source ) )
 
end -- furnish()
 
end -- furnish()
  
  
  
TemplateData.failsafe = function ( assert )
+
TemplateData.failsafe = function ( atleast )
 
     -- Retrieve versioning and check for compliance
 
     -- Retrieve versioning and check for compliance
 
     -- Precondition:
 
     -- Precondition:
     --    assert -- string, with required version or "wikidata",
+
     --    atleast -- string, with required version or "wikidata"
     --               or false
+
     --                 or false
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Returns  string with appropriate version, or false
 
     --    Returns  string with appropriate version, or false
     local since = assert
+
     local since = atleast
 
     local r
 
     local r
 
     if since == "wikidata" then
 
     if since == "wikidata" then
Zeile 1.244: Zeile 1.716:
 
                 local vsn = entity:formatPropertyValues( "P348" )
 
                 local vsn = entity:formatPropertyValues( "P348" )
 
                 if type( vsn ) == "table"  and
 
                 if type( vsn ) == "table"  and
                   type( vsn.value) == "string" and
+
                   type( vsn.value ) == "string" and
 
                   vsn.value ~= "" then
 
                   vsn.value ~= "" then
 
                     r = vsn.value
 
                     r = vsn.value
Zeile 1.260: Zeile 1.732:
 
     return r
 
     return r
 
end -- TemplateData.failsafe()
 
end -- TemplateData.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()
  
  
Zeile 1.269: Zeile 1.767:
 
     -- Returns string, or not
 
     -- Returns string, or not
 
     if type( adapt ) == "string" then
 
     if type( adapt ) == "string" then
 +
        local JSONutil = Fetch( "JSONutil", true )
 
         Data.source = adapt
 
         Data.source = adapt
 
         free()
 
         free()
         Data.got = mw.text.jsonDecode( Data.source )
+
         if JSONutil then
         if Data.got 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()
 
             full()
            if Data.lasting then
 
                Fault( "deprecated type syntax" )
 
            end
 
            if Data.less then
 
                Fault( Config.solo )
 
            end
 
 
         elseif not Data.strip then
 
         elseif not Data.strip then
             Fault( "fatal JSON error" )
+
             Fault( "fatal JSON error: " .. Data.got )
 
         end
 
         end
 
     end
 
     end

Aktuelle Version vom 6. September 2019, 12:54 Uhr

local TemplateData = { suite = "TemplateData",

                      serial = "2019-07-02",
                      item   = 46997995 }

--[=[ improve template:TemplateData ]=]


local Config = {

   -- multiple option names mapped into unique internal fields
   basicCnf = { catProblem    = "strange",
                classNoNumTOC = "suppressTOCnum",
                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" },

-- classParams = "classTable", -- classTable = false, -- class for params table

   debugmultilang = "C0C0C0",
   loudly         = false,    -- show exported element, etc.
   solo           = false,    -- complaint on missing description
   strange        = false,    -- title of maintenance category
   cssTable       = false,    -- styles for params table
   cssTabWrap     = false,    -- styles for params table wrapper
   debug          = false,
   subpage        = false,    -- pattern to identify subpage
   suffix         = false,    -- subpage creation scheme
   suppressTOCnum = false,    -- class for TOC number suppression
   jsonDebug      = "json-code-lint"    -- class for jsonDebug tool

} local Data = {

div = false, --

   got     = false,    -- table, initial templatedata object
   heirs   = false,    -- table, params that are inherited
   less    = false,    -- main description missing
   lasting = false,    -- old syntax encountered
   lazy    = false,    -- doc mode; do not generate effective <templatedata>
   leading = false,    -- show TOC

-- low = false, -- 1= mode

   order   = false,    -- parameter sequence
   params  = false,    -- table, exported parameters
   scream  = false,    -- error messages
   sibling = false,    -- TOC juxtaposed
   slang   = nil,      -- project/user language code
   slim    = false,    -- JSON reduced to plain
   source  = false,    -- JSON input
   strip   = false,    -- <templatedata> evaluation
   tag     = false,    -- table, exported root element
   title   = false,    -- page
   tree    = false     -- table, rewritten templatedata object

} local Permit = {

   builder = { after      = "block",
               align      = "block",
               block      = "block",
               compressed = "block",
               dense      = "block",
               grouped    = "inline",
               half       = "inline",
               indent     = "block",
               inline     = "inline",
               last       = "block",
               lead       = "block",
               newlines   = "*",
               spaced     = "inline" },
   colors  = { tableheadbg = "B3B7FF",
               required    = "EAF3FF",
               suggested   = "FFFFFF",
               optional    = "EAECF0",
               deprecated  = "FFCBCB" },
   params  = { aliases     = "table",
               autovalue   = "string",
               default     = "string table I18N nowiki",
               deprecated  = "boolean string I18N",
               description = "string table I18N",
               example     = "string table I18N nowiki",
               label       = "string table I18N",
               inherits    = "string",
               required    = "boolean",
               style       = "string table",
               suggested   = "boolean",
               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 ) )
       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 = Data.source:find( seek, at )
   local r, slice, source
   while i  and  not r do
       source = Data.source:sub( k + 1 )
       slice  = source:match( "^%s*\"([^\"]+)\"s*:" )
       if not slice then
           slice = source:match( "^%s*'([^']+)'%s*:" )
       end
       if ( slice and Permit.params[ slice ] )   or
          source:match( "^%s*%}" ) then
           r = k
       else
           i, k = Data.source:find( seek, k )
       end
   end    -- while i
   return r

end -- facet()


local function factory( adapt )

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

end -- factory()


local function faculty( adjust )

   -- Test template arg for boolean
   --     adjust  -- string or nil
   -- Returns boolean
   local s = type( adjust )
   local r
   if s == "string" then
       r = mw.text.trim( adjust )
       r = ( r ~= ""  and  r ~= "0" )
   elseif s == "boolean" then
       r = adjust
   else
       r = false
   end
   return r

end -- faculty()


local function failures()

   -- Retrieve error collection and category
   -- Returns string
   local r
   if Data.scream then
       local e = mw.html.create( "span" )
                        :addClass( "error" )
                        :wikitext( Data.scream )
       r = tostring( e )
       mw.addWarning( "TemplateData
" .. Data.scream ) if Config.strange then r = string.format( "%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 r
   if adjust:find( "<noexport>", 1, true ) then
       local i    = 1
       local j, k = adjust:find( "<noexport>", i, true )
       r = ""
       while j do
           if j > 1 then
               r = r .. f( adjust:sub( i,  j - 1 ) )
           end
           i = k + 1
           j, k = adjust:find( "</noexport>", i, true )
           if j then
               r    = r .. adjust:sub( i,  j - 1 )
               i    = k + 1
               j, k = adjust:find( "<noexport>", i, true )
           else
               Fault( "missing </noexport>" )
           end
       end    -- while j
       r = r .. adjust:sub( i )
   else
       r = f( adjust )
   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 ~= "" then
               k = k:lower()
               variants[ k ] = v
               n             = n + 1
           end
       end
   end -- for k, v
   if n > 0 then
       local Multilingual = Fetch( "Multilingual", true )
       if Multilingual  and
          type( Multilingual.i18n ) == "function" then
           local show, slang = Multilingual.i18n( variants )
           if show then
               r1 = show
               variants[ slang ] = nil
               r2 = variants
           end
       end
       if not r1 then
           Foreign()
           for k, v in pairs( variants ) do
               if n == 1 then
                   r1 = v
               elseif Data.slang == k then
                   variants[ k ] = nil
                   r1 = v
                   r2 = variants
               end
           end -- for k, v
       end
       if r2 and Multilingual then
           for k, v in pairs( r2 ) do
               if v  and  not Multilingual.isLang( k, true ) then
                   Fault( string.format( "%s lang=%s",
                                         "Invalid",
                                         k ) )
               end
           end -- for k, v
       end
   end
   return r1, r2

end -- faraway()


local function fashioned( about, asked, assign )

   -- Create description head
   -- Parameter:
   --     about   -- table, supposed to contain description
   --     asked   -- true, if mandatory description
   --     assign  -- <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 local param = Data.tree.params[ access ] local sub, sort = access:match( "(=+)%s*(%S.*)$" ) local headline = mw.html.create( string.format( "h%d", #sub ) ) local r = mw.html.create( "tr" ) local td = mw.html.create( "td" ) :attr( "colspan", "5" ) :attr( "data-sort-value", "!" .. sort ) local s if param.style then s = type( param.style ) if s == "table" then td:css( param.style ) elseif s == "string" then td:cssText( param.style ) end end s = fashioned( param, false, headline ) if s then headline = s else headline:wikitext( sort ) end td:node( headline ) r:node( td ) return r end -- fatten() local function fathers() -- Merge params with inherited values local n = 0 local p = Data.params local t = Data.tree.params local p2, t2 for k, v in pairs( Data.heirs ) do n = n + 1 end -- for k, v for i = 1, n do if Data.heirs then for k, v in pairs( Data.heirs ) do if v and not Data.heirs[ v ] then n = n - 1 t[ k ].inherits = nil Data.heirs[ k ] = nil p2 = { } t2 = { } if p[ v ] then for k2, v2 in pairs( p[ v ] ) do p2[ k2 ] = v2 end -- for k2, v2 if p[ k ] then for k2, v2 in pairs( p[ k ] ) do if type( v2 ) ~= "nil" then p2[ k2 ] = v2 end end -- for k2, v2 end p[ k ] = p2 for k2, v2 in pairs( t[ v ] ) do t2[ k2 ] = v2 end -- for k2, v2 for k2, v2 in pairs( t[ k ] ) do if type( v2 ) ~= "nil" then t2[ k2 ] = v2 end end -- for k2, v2 t[ k ] = t2 else Fault( "No params[] inherits " .. v ) end end end -- for k, v end end -- i = 1, n if n > 0 then local s for k, v in pairs( Data.heirs ) do if v then if s then s = string.format( "%s | %s", s, k ) else s = "Circular inherits: " .. k end end end -- for k, v Fault( s ) end end -- fathers() local function favorize() -- Local customization issues local boole = { ["font-size"] = "125%" } local l, cx = pcall( mw.loadData, TemplateData.frame:getTitle() .. "/config" ) local scripting TemplateData.ltr = not mw.language.getContentLanguage():isRTL() if TemplateData.ltr then scripting = "left" else scripting = "right" end boole[ "margin-" .. scripting ] = "3em" Permit.boole = { [false] = { css = boole, lead = true, show = "☐" }, [true] = { css = boole, lead = true, show = "☑" } } Permit.css = { } for k, v in pairs( Permit.colors ) do if k == "tableheadbg" then k = "tablehead" end Permit.css[ k ] = { ["background-color"] = "#" .. v } end -- for k, v if type( cx ) == "table" then local c, s if type( cx.permit ) == "table" then if type( cx.permit.boole ) == "table" then if type( cx.permit.boole[ true ] ) == "table" then Permit.boole[ false ] = cx.permit.boole[ false ] end if type( cx.permit.boole[ true ] ) == "table" then Permit.boole[ true ] = cx.permit.boole[ true ] end end if type( cx.permit.css ) == "table" then for k, v in pairs( cx.permit.css ) do if type( v ) == "table" then Permit.css[ k ] = v end end -- for k, v end end for k, v in pairs( Config.basicCnf ) do s = type( cx[ k ] ) if s == "string" or s == "table" then Config[ v ] = cx[ k ] end end -- for k, v end if type( Config.subpage ) ~= "string" or type( Config.suffix ) ~= "string" then local got = mw.message.new( "templatedata-doc-subpage" ) local suffix if got:isDisabled() then suffix = "doc" else suffix = got:plain() end if type( Config.subpage ) ~= "string" then Config.subpage = string.format( "/%s$", suffix ) end if type( Config.suffix ) ~= "string" then Config.suffix = string.format( "%%s/%s", suffix ) end end end -- favorize() local function feat() -- Check and store parameter sequence if Data.source then local i = 0 local s for k, v in pairs( Data.tree.params ) do if i == 0 then Data.order = { } i = 1 s = k else i = 2 break -- for k, v end end -- for k, v if i > 1 then local pointers = { } local points = { } local given = { } for k, v in pairs( Data.tree.params ) do i = facet( k, 1 ) if type( v ) == "table" then if type( v.label ) == "string" then s = mw.text.trim( v.label ) if s == "" then s = k end else s = k end if given[ s ] then if given[ s ] == 1 then local scream = "Parameter label '%s' detected multiple times" Fault( string.format( scream, s ) ) given[ s ] = 2 end else given[ s ] = 1 end end if i then table.insert( points, i ) pointers[ i ] = k i = facet( k, i ) if i then s = "Parameter '%s' detected twice" Fault( string.format( s, k ) ) end else s = "Parameter '%s' not detected" Fault( string.format( s, k ) ) end end -- for k, v table.sort( points ) for i = 1, #points do table.insert( Data.order, pointers[ points[ i ] ] ) end -- i = 1, #points elseif s then table.insert( Data.order, s ) end end end -- feat() local function feature( access ) -- Create table row for parameter, check and display violations -- Parameter: -- access -- string, with name -- Returns local mode, s, status local fine = function ( a ) s = mw.text.trim( a ) return a == s and a ~= "" and not a:find( "%|=\n" ) and not a:find( "%s%s" ) end local begin = mw.html.create( "td" ) local code = mw.html.create( "code" ) local desc = mw.html.create( "td" ) local legal = true local param = Data.tree.params[ access ] local ranking = { "required", "suggested", "optional", "deprecated" } local r = mw.html.create( "tr" ) local sort, typed for k, v in pairs( param ) do if v == "" then param[ k ] = false end end -- for k, v -- label sort = param.label or access if sort:match( "^%d+$" ) then begin:attr( "data-sort-value", string.format( "%05d", tonumber( sort ) ) ) end begin:css( "font-weight", "bold" ) :wikitext( sort ) -- name and aliases code:css( "font-size", "92%" ) :css( "white-space", "nowrap" ) :wikitext( access ) if not fine( access ) then code:addClass( "error" ) Fault( string.format( "Bad ID params.%s", access ) ) legal = false begin:attr( "data-sort-value", " " .. sort ) end code = mw.html.create( "td" ) :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 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" ) ) end code:wikitext( s ) else lapsus = true code:node( mw.html.create( "code" ) :addClass( "error" ) :wikitext( type( v ) ) ) end end -- for k, v if lapsus then s = string.format( "params.%s.aliases", access ) Fault( factory( "invalid-value" ):gsub( "$1", s ) ) legal = false end end -- description etc. s = fashioned( param ) if s then desc:node( s ) end if param.style then s = type( param.style ) if s == "table" then desc:css( param.style ) elseif s == "string" then desc:cssText( param.style ) end end if param.default or param.example or param.autovalue then local details = { "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" ) :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 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" ) if s then if s == "string" then Data.params[ access ].type = s typed:wikitext( factory( "doc-param-type-" .. s ) ) :tag( "br" ) typed:node( mw.html.create( "span" ) :addClass( "error" ) :wikitext( param.type ) ) Data.lasting = true else local support = Config[ "support4" .. param.type ] s = factory( "doc-param-type-" .. param.type ) if support then s = string.format( "%s", support, s ) end typed:wikitext( s ) end else Data.params[ access ].type = "unknown" typed:addClass( "error" ) :wikitext( "INVALID" ) s = string.format( "params.%s.type", access ) Fault( factory( "invalid-value" ):gsub( "$1", s ) ) legal = false end else typed = mw.html.create( "td" ) :wikitext( factory( "doc-param-type-unknown" ) ) end -- status if param.required then mode = 1 if param.autovalue then Fault( string.format( "autovalued %s required", access ) ) legal = false end if param.default then Fault( string.format( "Defaulted %s required", access ) ) legal = false end if param.deprecated then Fault( string.format( "Required deprecated %s", access ) ) legal = false end elseif param.deprecated then mode = 4 elseif param.suggested then mode = 2 else mode = 3 end status = ranking[ mode ] ranking = factory( "doc-param-status-" .. status ) if mode == 1 or mode == 4 then ranking = mw.html.create( "span" ) :css( "font-weight", "bold" ) :wikitext( ranking ) if type( param.deprecated ) == "string" then ranking:tag( "br" ) ranking:wikitext( param.deprecated ) end end -- r:attr( "id", "templatedata:" .. mw.uri.anchorEncode( access ) ) :css( Permit.css[ status ] ) :node( begin ) :node( code ) :node( desc ) :node( typed ) :node( mw.html.create( "td" ) :attr( "data-sort-value", tostring( mode ) ) :node( ranking ) ) :newline() if not legal then r:css( "border", "#FF0000 3px solid" ) end return r end -- feature() local function features() -- Create for parameters -- Returns
, or nil local r if Data.tree and Data.tree.params then local tbl = mw.html.create( "table" ) :addClass( "wikitable" ) local tr = mw.html.create( "tr" ) feat() if Data.order and #Data.order > 1 then tbl:addClass( "sortable" ) end -- if Config.classTable then -- tbl:addClass( Config.classTable ) -- end if Config.cssTable then if type( Config.cssTable ) == "table" then tbl:css( Config.cssTable ) elseif type( Config.cssTable ) == "string" then -- deprecated tbl:cssText( Config.cssTable ) end end tr:node( mw.html.create( "th" ) :attr( "colspan", "2" ) :css( Permit.css.tablehead ) :wikitext( factory( "doc-param-name" ) ) ) :node( mw.html.create( "th" ) :css( Permit.css.tablehead ) :wikitext( factory( "doc-param-desc" ) ) ) :node( mw.html.create( "th" ) :css( Permit.css.tablehead ) :wikitext( factory( "doc-param-type" ) ) ) :node( mw.html.create( "th" ) :css( Permit.css.tablehead ) :wikitext( factory( "doc-param-status" ) ) ) tbl:newline() -- :node( mw.html.create( "thead" ) :node( tr ) -- ) :newline() if Data.order then local leave, s for i = 1, #Data.order do s = Data.order[ i ] if s:sub( 1, 1 ) == "=" then leave = true tbl:node( fatten( s ) ) Data.order[ i ] = false elseif s:match( "[=|]" ) then Fault( string.format( "Bad param %s", s ) ) else tbl:node( feature( s ) ) end end -- for i = 1, #Data.order if leave then for i = #Data.order, 1, -1 do if not Data.order[ i ] then table.remove( Data.order, i ) end end -- for i = #Data.order, 1, -1 end Data.tag.paramOrder = Data.order end if Config.cssTabWrap or Data.scroll then r = mw.html.create( "div" ) if type( Config.cssTabWrap ) == "table" then r:css( Config.cssTabWrap ) elseif type( Config.cssTabWrap ) == "string" then -- deprecated r:cssText( Config.cssTabWrap ) end if Data.scroll then r:css( "height", Data.scroll ) :css( "overflow", "auto" ) end r:node( tbl ) else r = tbl end end return r end -- features() local function 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( "4580" ) 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 -- 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( "", 1, true ) then r = r:gsub( "'", "" ):gsub( "", "" ) end if r:find( "<", 1, true ) then local Text = Fetch( "Text" ) r = Text.getPlain( r:gsub( "
", "\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 ) 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( "params.%s", at ) else r = "root" end if a then r = string.format( "%s.%s", r, a ) end return r end local parent if access then parent = Data.got.params[ access ] else parent = Data.got end if type( parent ) == "table" then local elem, got, permit, s, scope, slot, tag, target if access then permit = Permit.params if type( access ) == "number" then slot = tostring( access ) else slot = access end else permit = Permit.root end for k, v in pairs( parent ) do scope = permit[ k ] if scope then s = type( v ) if s == "string" and k ~= "format" then v = mw.text.trim( v ) end if scope:find( s, 1, true ) then if scope:find( "I18N", 1, true ) then if s == "string" then elem = fair( v ) elseif s == "table" then local translated v, translated = faraway( v ) if v then if translated and k == "description" then elem = { [ 1 ] = fair( v ), [ 2 ] = translated } else elem = fair( v ) end else elem = false end end if type( v ) == "string" then if k == "deprecated" then if v == "1" then v = true elseif v == "0" then v = false end elem = v elseif scope:find( "nowiki", 1, true ) then elem = mw.text.nowiki( v ) elem = elem:gsub( "&#13;\n", "
" ) v = v:gsub( string.char( 13 ), "" ) else v = flat( v ) end elseif s == "boolean" then if scope:find( "boolean", 1, true ) then elem = v else s = "Type boolean bad for " .. f( k, slot ) Fault( s ) end end else if k == "params" and not access then v = nil elem = nil elseif k == "format" and not access then elem = mw.text.decode( v ) v = nil elseif k == "inherits" then elem = v if not Data.heirs then Data.heirs = { } end Data.heirs[ slot ] = v v = nil elseif k == "style" then elem = v v = nil elseif s == "string" then v = mw.text.nowiki( v ) elem = v else elem = v end end if type( elem ) ~= "nil" then if not target then if access then if not Data.tree.params then Data.tree.params = { } end Data.tree.params[ slot ] = { } target = Data.tree.params[ slot ] else Data.tree = { } target = Data.tree end end target[ k ] = elem elem = false end if type( v ) ~= "nil" then if not tag then if access then if type( v ) == "string" and v.sub( 1, 1 ) == "=" then v = nil else if not Data.params then Data.params = { } end Data.params[ slot ] = { } tag = Data.params[ slot ] end else Data.tag = { } tag = Data.tag end end if type( v ) ~= "nil" then tag[ k ] = v end end else s = string.format( "Type %s bad for %s", scope, f( k, slot ) ) Fault( s ) end else Fault( "Unknown component " .. f( k, slot ) ) end end -- for k, v else Fault( f() .. " needs to be of object type" ) end end -- focus() local function format() -- Build formatted element -- Returns <inline> local source = Data.tree.format:lower() local r, s if source == "inline" or source == "block" then r = mw.html.create( "i" ) :wikitext( source ) else local code if source:find( "|", 1, true ) then local scan = "^[\n ]*%{%{[\n _]*|[\n _]*=[\n _]*%}%}[\n ]*$" if source:match( scan ) then code = source:gsub( "\n", "N" ) else s = mw.text.nowiki( source ):gsub( "\n", "\n" ) s = tostring( mw.html.create( "code" ) :wikitext( s ) ) Fault( "Invalid format " .. s ) source = false end else local words = mw.text.split( source, "%s+" ) local show, start, support, unknown for i = 1, #words do s = words[ i ] if i == 1 then start = s end support = Permit.builder[ s ] if support == start or support == "*" then Permit.builder[ s ] = true elseif s:match( "^[1-9]%d?" ) and Permit.builder.align then Permit.builder.align = tonumber( s ) else if unknown then unknown = string.format( "%s %s", unknown, s ) else unknown = s end end end -- i = 1, #words if unknown then s = tostring( mw.html.create( "code" ) :css( "white-space", "nowrap" ) :wikitext( s ) ) Fault( "Unknown/misplaced format keyword " .. s ) source = false start = false end if start == "inline" then if Permit.builder.half == true then show = "inline half" code = "{{_ |_=_}}" elseif Permit.builder.grouped == true then show = "inline grouped" code = "{{_ | _=_}}" elseif Permit.builder.spaced == true then show = "inline spaced" code = "{{_ | _ = _ }}" end if Permit.builder.newlines == true then show = show or "inline" code = code or "{{_|_=_}}" show = show .. " newlines" code = string.format( "N%sN", code ) end elseif start == "block" then local space = "" -- amid "|" and name local spaced = " " -- preceding "=" local spacer = " " -- following "=" local suffix = "N" -- closing "}}" on new line show = "block" if Permit.builder.indent == true then start = " " show = "block indent" else start = "" end if Permit.builder.compressed == true then spaced = "" spacer = "" show = show .. " compressed" if Permit.builder.last == true then show = show .. " last" else suffix = "" end else if Permit.builder.lead == true then show = show .. " lead" space = " " end if type( Permit.builder.align ) ~= "string" then local n s = " align" if Permit.builder.align == true then n = 0 if type( Data.got ) == "table" and type( Data.got.params ) == "table" then for k, v in pairs( Data.got.params ) do if type( v ) == "table" and not v.deprecated and type( k ) == "string" then k = mw.ustring.len( k ) if k > n then n = k end end end -- for k, v end else n = Permit.builder.align if type( n ) == "number" and n > 1 then s = string.format( "%s %d", s, n ) else n = 0 -- How comes? end end if n > 1 then spaced = string.rep( "_", n ) .. " " 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( "NVorlage:N%sN", start, space, spaced, spacer, suffix ) if show == "block" then show = "block newlines" end end if show then r = mw.html.create( "span" ) :wikitext( show ) end end if code then source = code:gsub( "N", "\n" ) code = mw.text.nowiki( code ):gsub( "N", "\n" ) code = mw.html.create( "code" ) :css( "margin-left", "1em" ) :css( "margin-right", "1em" ) :wikitext( code ) if r then r = mw.html.create( "span" ) :node( r ) :node( code ) else r = code end end end if source then Data.tag.format = source end return r end -- format() local function formatter() -- Build presented documentation -- Returns
   local r = mw.html.create( "div" )
   local x = fashioned( Data.tree, true, r )
   local s
   if x then
       r = x
   end
   if Data.leading then
       local toc = mw.html.create( "div" )
       local shift
       if Config.suppressTOCnum then
           toc:addClass( Config.suppressTOCnum )
           if type( Config.stylesTOCnum ) == "string" then
               local src = Config.stylesTOCnum .. "/styles.css"
               s = TemplateData.frame:extensionTag( "templatestyles",
                                                    nil,
                                                    { src = src } )
               r:newline()
                :node( s )
           end
       end
       toc:css( "margin-top", "0.5em" )
          :wikitext( "" )
       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( "h2" )
                          :wikitext( factory( "doc-params" ) ) )
            :newline()
       end
       r:node( s )
   end
   if Data.shared then
       local global = mw.html.create( "div" )
                             :attr( "id", "templatedata-global" )
       local shift
       if TemplateData.ltr then
           shift = "right"
       else
           shift = "left"
       end
       global:css( "float", shift )
             :wikitext( string.format( "%s",
                                       Data.shared, "Global" ) )
       r:newline()
        :node( global )
   end
   if Data.tree and Data.tree.format then
       local e = format()
       if e then
           local show = "Format"
           if Config.supportFormat then
               show = string.format( "%s",
                                     Config.supportFormat, show )
           end
           r:node( mw.html.create( "p" )
                          :wikitext( show .. ": " )
                          :node( e ) )
       end
   end
   return r

end -- formatter()


local function free()

   -- Remove JSON comment lines
   Data.source:gsub( "([{,\"'])(%s*\n%s*//.*\n%s*)([},\"'])",
                     "%1%3" )

end -- free()


local function full()

   -- Build survey table from JSON data, append invisible <templatedata>
   Data.div = mw.html.create( "div" )
                     :addClass( "mw-templatedata-doc-wrap" )
   focus()
   if Data.tag then
       if type( Data.got.params ) == "table" then
           for k, v in pairs( Data.got.params ) do
               focus( k )
           end -- for k, v
           if Data.heirs then
               fathers()
           end
       end
   end
   Data.div:node( formatter() )
   if not Data.lazy then
       Data.slim = flush()
       if TemplateData.frame then
           local div   = mw.html.create( "div" )
           local tdata = { [ 1 ] = "templatedata",
                           [ 2 ] = Data.slim }
           Data.strip = TemplateData.frame:callParserFunction( "#tag",
                                                               tdata )
           div:wikitext( Data.strip )
           if Config.loudly then
               Data.div:node( mw.html.create( "hr" )
                                     :css( { height = "7ex" } ) )
           else
               div:css( "display", "none" )
           end
           Data.div:node( div )
       end
   end
   if Data.lasting then
       Fault( "deprecated type syntax" )
   end
   if Data.less then
       Fault( Config.solo )
   end

end -- full()


local function furnish( adapt, arglist )

   -- Analyze transclusion
   -- Parameter:
   --     adapt    -- table, #invoke parameters
   --     arglist  -- table, template parameters
   -- Returns string
   local source
   favorize()
   -- deprecated:
   for k, v in pairs( Config.basicCnf ) do
       if adapt[ k ]  and  adapt[ k ] ~= "" then
           Config[ v ] = adapt[ k ]
       end
   end -- for k, v
   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()


TemplateData.failsafe = function ( atleast )

   -- Retrieve versioning and check for compliance
   -- Precondition:
   --     atleast  -- string, with required version or "wikidata"
   --                 or false
   -- Postcondition:
   --     Returns  string with appropriate version, or false
   local since = atleast
   local r
   if since == "wikidata" then
       local item = TemplateData.item
       since = false
       if type( item ) == "number"  and  item > 0 then
           local entity = mw.wikibase.getEntity( string.format( "Q%d",
                                                                item ) )
           if type( entity ) == "table" then
               local vsn = entity:formatPropertyValues( "P348" )
               if type( vsn ) == "table"  and
                  type( vsn.value ) == "string" and
                  vsn.value ~= "" then
                   r = vsn.value
               end
           end
       end
   end
   if not r then
       if not since  or  since <= TemplateData.serial then
           r = TemplateData.serial
       else
           r = false
       end
   end
   return r

end -- TemplateData.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
           Fault( "fatal JSON error: " .. Data.got )
       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 TemplateData.failsafe( since )  or  ""

end -- p.failsafe()

p.TemplateData = function ()

   -- Module interface
   return TemplateData

end

return p