Module:Format TemplateData
local TemplateData = { serial = &quot;2017-11-06&quot;,
                        suite  = &quot;TemplateData&quot; }
 local plaintext = require(&quot;Module:Plain text&quot;)
 --[=[
 improve template:TemplateData
 ]=]
 local Config = {
     -- multiple #invoke option names mapped into unique internal fields
     cat            = &quot;strange&quot;,
     classNoNumTOC  = &quot;suppressTOCnum&quot;,
 --  classParams    = &quot;classTable&quot;,
     cssParams      = &quot;stylesTable&quot;,
     cssParWrap     = &quot;stylesTabWrap&quot;,
     debug          = false,
     docpageCreate  = &quot;suffix&quot;,
     docpageDetect  = &quot;subpage&quot;,
     msgDescMiss    = &quot;solo&quot;,
 --  classTable     = false,    -- class for params table
     loudly         = false,    -- show exported element, etc.
     solo           = false,    -- complaint on missing description
     strange        = false,    -- title of maintenance category
     stylesTable    = false,    -- styles for params table
     stylesTabWrap  = false,    -- styles for params table wrapper
     subpage        = false,    -- pattern to identify subpage
     suffix         = false,     -- subpage creation scheme
     suppressTOCnum = false    -- class for TOC number suppression
 }
 local Data = {
     div     = false,    -- &lt;div class=&quot;mw-templatedata-doc-wrap&quot;&gt;
     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 &lt;templatedata&gt;
     leading = false,    -- show TOC
 --  low     = false,    -- 1= mode
     order   = false,    -- parameter sequence
     params  = false,    -- table, exported parameters
     scream  = false,    -- error messages
     slang   = false,    -- project language code
     slim    = false,    -- JSON reduced to plain
     source  = false,    -- JSON input
     strip   = false,    -- &lt;templatedata&gt; evaluation
     tag     = false,    -- table, exported root element
     title   = false,    -- page
     tree    = false     -- table, rewritten templatedata object
 }
 local Permit = {
     styles  = { required    = &quot;border-left: 3px solid black;&quot;,
                 suggested   = &quot;border-left: 3px solid #888;&quot;,
                 optional    = &quot;border-left: 3px solid #ccc&quot;,
                 deprecated  = &quot;border-left: 3px dotted red; background-color: #FDD;&quot;,
                 tableheadbg = &quot;background-color: #B3B7FF;&quot; },
     params  = { aliases     = &quot;table&quot;,
                 autovalue   = &quot;string&quot;,
                 default     = &quot;string table I18N nowiki&quot;,
                 deprecated  = &quot;boolean string&quot;,
                 description = &quot;string table I18N&quot;,
                 example     = &quot;string table I18N nowiki&quot;,
                 label       = &quot;string table I18N&quot;,
                 inherits    = &quot;string&quot;,
                 required    = &quot;boolean&quot;,
                 suggested   = &quot;boolean&quot;,
                 type        = &quot;string&quot; },
     root    = { description = &quot;string table I18N&quot;,
                 format      = &quot;string&quot;,
                 maps        = &quot;table&quot;,
                 params      = &quot;table&quot;,
                 paramOrder  = &quot;table&quot;,
                 sets        = &quot;table&quot; },
     search  = &quot;[{,]%%s*(['\&quot;])%s%%1%%s*:%%s*%%{&quot;,
     types   = { boolean                   = true,
                 content                   = true,
                 date                      = true,
                 line                      = true,
                 number                    = true,
                 string                    = true,
                 unknown                   = true,
                 url                       = true,
                 [&quot;wiki-file-name&quot;]        = true,
                 [&quot;wiki-page-name&quot;]        = true,
                 [&quot;wiki-template-name&quot;]    = true,
                 [&quot;wiki-user-name&quot;]        = true,
                 [&quot;unbalanced-wikitext&quot;]   = true,
                 [&quot;string/line&quot;]           = &quot;line&quot;,
                 [&quot;string/wiki-page-name&quot;] = &quot;wiki-page-name&quot;,
                 [&quot;string/wiki-user-name&quot;] = &quot;wiki-user-name&quot; }
 }
 
 --
 -- Generic utility functions
 --
 
 local function Fault( alert )
     -- Memorize error message
     -- Parameter:
     --     alert  -- string, error message
     if Data.scream then
         Data.scream = string.format( &quot;%s *** %s&quot;, Data.scream, alert )
     else
         Data.scream = alert
     end
 end -- Fault()
 
 local function collapseWhitespace ( a )
 	-- Collapses whitespace, HTML style.
     return a:gsub( &quot;%s*\n%s*&quot;, &quot; &quot; )
             :gsub( &quot;%s%s+&quot;, &quot; &quot; )
 end -- collapseWhitespace
 
 --------------------------------------------------------------------------------
 --
 
 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( &quot;%%&quot;, &quot;%%%%&quot; )
                                    :gsub( &quot;([%-.()+*?^$%[%]])&quot;,
                                           &quot;%%%1&quot; ) )
     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( &quot;^%s*\&quot;([^\&quot;]+)\&quot;s*:&quot; )
         if not slice then
             slice = source:match( &quot;^%s*'([^']+)'%s*:&quot; )
         end
         if ( slice and Permit.params[ slice ] )   or
            source:match( &quot;^%s*%}&quot; ) then
             r = k
         else
             i, k = Data.source:find( seek, k )
         end
     end    -- while i
     return r
 end -- facet()
 
 
 
 local function getLocalizedText( adapt )
     -- Retrieve localized text from system message
     -- Parameter:
     --     adapt  -- string, message ID after &quot;templatedata-&quot;
     -- Returns string, with localized text
     return mw.message.new( &quot;templatedata-&quot; .. adapt ):plain()
 end -- getLocalizedText()
 
 
 
 local function faculty( adjust )
     -- Test template arg for boolean
     --     adjust  -- string or nil
     -- Returns boolean
     local s = type( adjust )
     local r
     if s == &quot;string&quot; then
         r = mw.text.trim( adjust )
         r = ( r ~= &quot;&quot;  and  r ~= &quot;0&quot; )
     elseif s == &quot;boolean&quot; 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( &quot;span&quot; )
                          :addClass( &quot;error&quot; )
                          :wikitext( Data.scream )
         r = tostring( e )
         mw.addWarning( &quot;'''TemplateData'''&lt;br /&gt;&quot; .. Data.scream )
         if Config.strange then
             r = string.format( &quot;%s[[category:%s]]&quot;,
                                r,
                                Config.strange )
         end
     else
         r = &quot;&quot;
     end
     return r
 end -- failures()
 
 
 
 local function handleNoexportWhitespace( adjust )
     -- Reduces runs of spaces, including newlines, to a single space, so the
     -- whole string is on one line. &lt;noexport&gt; blocks are left alone, but the
     -- &lt;noexport&gt; tags themselves are removed.
     --     adjust  -- string
     -- Returns string, with adjusted text
     local r
     if adjust:find( &quot;&lt;noexport&gt;&quot;, 1, true ) then
         local i    = 1
         local j, k = adjust:find( &quot;&lt;noexport&gt;&quot;, i, true )
         r = &quot;&quot;
         while j do
             if j &gt; 1 then
                 r = r .. collapseWhitespace( adjust:sub( i,  j - 1 ) )
             end
             i = k + 1
             j, k = adjust:find( &quot;&lt;/noexport&gt;&quot;, i, true )
             if j then
                 r    = r .. adjust:sub( i,  j - 1 )
                 i    = k + 1
                 j, k = adjust:find( &quot;&lt;noexport&gt;&quot;, i, true )
             else
                 Fault( &quot;missing &lt;/noexport&gt;&quot; )
             end
         end    -- while j
         r = r .. adjust:sub( i )
     else
         r = collapseWhitespace( adjust )
     end
     return r
 end -- handleNoexportWhitespace()
 
 
 
 local function faraway( alternatives )
     -- Retrieve project 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
     if not Data.slang then
         Data.slang = mw.language.getContentLanguage():getCode()
     end
     for k, v in pairs( alternatives ) do
         if type( v ) == &quot;string&quot; then
             v = mw.text.trim( v )
             if v ~= &quot;&quot; then
                 variants[ k ] = v
                 n             = n + 1
             end
         end
     end -- for k, v
     if n &gt; 0 then
         for k, v in pairs( variants ) do
             if v then
                 if n == 1 then
                     r1 = v
                 elseif k:lower() == Data.slang then
                     variants[ k ] = nil
                     r1 = v
                     r2 = variants
                     break -- for k, v
                 end
             end
         end -- for k, v
         if not r1 then
             local seek = string.format( &quot;^%s-&quot;, Data.slang )
             for k, v in pairs( variants ) do
                 if v and k:lower():match( seek ) then
                     variants[ k ] = nil
                     r1 = v
                     r2 = variants
                     break -- for k, v
                 end
             end -- for k, v
             if not r1 then
                 local others = mw.language.getFallbacksFor( slang )
                 table.insert( others, &quot;en&quot; )
                 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
         if r2 then
             for k, v in pairs( r2 ) do
                 if v then
                 	local baseCode = k:match( &quot;^%s*(%a%a%a?)-?%a*%s*$&quot; )
                 	if not baseCode or not mw.language.isKnownLanguageTag( baseCode ) then
                     	Fault( string.format( &quot;Invalid &lt;code&gt;lang=%s&lt;/code&gt;&quot;, k ))
                     end
                 end
             end -- for k, v
         end
     end
     return r1, r2
 end -- faraway()
 
 
 
 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
         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              = { }
                 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 ) ~= &quot;nil&quot; 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 ) ~= &quot;nil&quot; then
                         t2[ k2 ] = v2
                     end
                 end -- for k2, v2
                 t[ k ] = t2
             end
         end -- for k, v
     end -- i = 1, n
     if n &gt; 0 then
         local s
         for k, v in pairs( Data.heirs ) do
             if v then
                 if s then
                     s = string.format( &quot;%s &amp;#124; %s&quot;, s, k )
                 else
                     s = &quot;Circular inherits: &quot; .. k
                 end
             end
         end -- for k, v
         Fault( s )
     end
 end -- fathers()
 
 
 
 local function feasible( about, asked )
     -- Create description head
     -- Parameter:
     --     about  -- table, supposed to contain description
     --     asked  -- true, if mandatory description
     -- Returns &lt;block&gt;, with head, or nil
     local para = mw.html.create( &quot;div&quot; )
     local plus, r
     if about and about.description then
         if type( about.description ) == &quot;string&quot; then
             para:wikitext( about.description )
         else
             para:wikitext( about.description[ 1 ] )
             plus = mw.html.create( &quot;ul&quot; )
             if not Config.loudly then
                 plus:addClass( &quot;templatedata-maintain&quot; )
                     :css( &quot;display&quot;, &quot;none&quot; )
             end
             for k, v in pairs( about.description[ 2 ] ) do
                 plus:node( mw.html.create( &quot;li&quot; )
                                   :node( mw.html.create( &quot;code&quot; )
                                                 :wikitext( k ) )
                                   :node( mw.html.create( &quot;br&quot; ) )
                                   :wikitext( handleNoexportWhitespace( v ) ) )
             end -- for k, v
         end
     elseif Config.solo and asked then
         para:addClass( &quot;error&quot; )
             :wikitext( Config.solo )
         Data.less = true
     else
         para = false
     end
     if para then
         if plus then
             r = mw.html.create( &quot;div&quot; )
                        :node( para )
                        :node( plus )
         else
             r = para
         end
     end
     return r
 end -- feasible()
 
 
 
 local function feat()
     -- Check and store parameter sequence
     if Data.source then
         local i = 0
         local s
         for k, v in pairs( Data.tree.params ) do
             if i == 0 then
                 Data.order = { }
                 i = 1
                 s = k
             else
                 i = 2
                 break -- for k, v
             end
         end -- for k, v
         if i &gt; 1 then
             local pointers = { }
             local points   = { }
             for k, v in pairs( Data.tree.params ) do
                 i = facet( k, 1 )
                 if i then
                     table.insert( points, i )
                     pointers[ i ] = k
                     i = facet( k, i )
                     if i then
                         s = &quot;Parameter '%s' detected twice&quot;
                         Fault( string.format( s, k ) )
                     end
                 else
                     s = &quot;Parameter '%s' not detected&quot;
                     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 &lt;tr&gt;
     local mode, s, status
     local fine    = function ( a )
                         s = mw.text.trim( a )
                         return a == s  and
                                a ~= &quot;&quot;  and
                                not a:find( &quot;%|=\n&quot; )  and
                                not a:find( &quot;%s%s&quot; )
                     end
     local begin   = mw.html.create( &quot;td&quot; )
     local code    = mw.html.create( &quot;code&quot; )
     local desc    = mw.html.create( &quot;td&quot; )
     local legal   = true
     local param   = Data.tree.params[ access ]
     local ranking = { &quot;required&quot;, &quot;suggested&quot;, &quot;optional&quot;, &quot;deprecated&quot; }
     local r       = mw.html.create( &quot;tr&quot; )
     local sort, typed
 
     for k, v in pairs( param ) do
         if v == &quot;&quot; then
             param[ k ] = false
         end
     end -- for k, v
 
     -- label
     sort = param.label or access
     if sort:match( &quot;^%d+$&quot; ) then
         begin:attr( &quot;data-sort-value&quot;,
                     string.format( &quot;%05d&quot;, tonumber( sort ) ) )
     end
     begin:css( &quot;font-weight&quot;, &quot;bold&quot; )
          :wikitext( sort )
 
     -- name and aliases
     code:css( &quot;font-size&quot;, &quot;92%&quot; )
         :css( &quot;white-space&quot;, &quot;nowrap&quot; )
         :wikitext( access )
     if not fine( access ) then
         code:addClass( &quot;error&quot; )
         Fault( string.format( &quot;Bad ID params.&lt;code&gt;%s&lt;/code&gt;&quot;, access ) )
         legal = false
         begin:attr( &quot;data-sort-value&quot;,  &quot; &quot; .. sort )
     end
     code = mw.html.create( &quot;td&quot; )
                   :node( code )
     if access:match( &quot;^%d+$&quot; ) then
         code:attr( &quot;data-sort-value&quot;,
                    string.format( &quot;%05d&quot;, tonumber( access ) ) )
     end
     if type( param.aliases ) == &quot;table&quot; then
         local lapsus
         for k, v in pairs( param.aliases ) do
             code:tag( &quot;br&quot; )
             if type( v ) == &quot;string&quot; then
                 if not fine( v ) then
                     lapsus = true
                     code:node( mw.html.create( &quot;span&quot; )
                                       :addClass( &quot;error&quot; )
                                       :css( &quot;font-style&quot;, &quot;italic&quot; )
                                       :wikitext( &quot;string&quot; ) )
                 end
                 code:wikitext( s )
             else
                 lapsus = true
                 code:node( mw.html.create( &quot;code&quot; )
                                   :addClass( &quot;error&quot; )
                                   :wikitext( type( v ) ) )
             end
         end -- for k, v
         if lapsus then
             s = string.format( &quot;params.&lt;code&gt;%s&lt;/code&gt;.aliases&quot;, access )
             Fault(  getLocalizedText( &quot;invalid-value&quot; ):gsub( &quot;$1&quot;, s )  )
             legal = false
         end
     end
 
     -- description etc.
     s = feasible( param )
     if s then
         desc:node( s )
     end
     if param.default or param.example or param.autovalue then
         local details = { &quot;default&quot;, &quot;example&quot;, &quot;autovalue&quot; }
         local dl      = mw.html.create( &quot;dl&quot; )
         local dd, section, show
         for i = 1, #details do
             s    = details[ i ]
             show = param[ s ]
             if show then
                 section = getLocalizedText( &quot;doc-param-&quot; .. s )
                 dt      = mw.html.create( &quot;dt&quot; ):wikitext( section )
                 dd      = mw.html.create( &quot;dd&quot; )
                 if (string.len(show) &lt; 80) then
                 	dt:cssText(&quot;float: left;margin-right: 1.6em;&quot;)
                 end
                 if param.type == &quot;boolean&quot; then
                 	if show == &quot;0&quot; then
                     	dd:wikitext(&quot;&lt;span style=\&quot;color: #610; font-weight: bold;\&quot;&gt;False&lt;/span&gt; (&lt;code&gt;0&lt;/code&gt;)&quot;)
                     elseif show == &quot;1&quot; then
                     	dd:wikitext(&quot;&lt;span style=\&quot;color: #050; font-weight: bold;\&quot;&gt;True&lt;/span&gt; (&lt;code&gt;1&lt;/code&gt;)&quot;)
                     else
                     	dd:wikitext( show )
                     end
                 else
                 	dd:wikitext( show )
                 end
                 dl:node( dt )
                   :node( dd )
             end
         end -- i = 1, #details
         desc:node( dl )
     end
 
     -- type
     if param.type then
         s     = Permit.types[ param.type ]
         typed = mw.html.create( &quot;td&quot; )
         if s then
             if s == &quot;string&quot; then
                 Data.params[ access ].type = s
                 typed:wikitext( getLocalizedText( &quot;doc-param-type-&quot; .. s ) )
                      :tag( &quot;br&quot; )
                 typed:node( mw.html.create( &quot;span&quot; )
                                    :addClass( &quot;error&quot; )
                                    :wikitext( param.type ) )
                 Data.lasting = true
             else
                 s = getLocalizedText( &quot;doc-param-type-&quot; .. param.type )
                 typed:wikitext( s )
             end
         else
             Data.params[ access ].type = &quot;unknown&quot;
             typed:addClass( &quot;error&quot; )
                  :wikitext( &quot;INVALID&quot; )
             s = string.format( &quot;params.&lt;code&gt;%s&lt;/code&gt;.type&quot;, access )
             Fault(  getLocalizedText( &quot;invalid-value&quot; ):gsub( &quot;$1&quot;, s )  )
             legal = false
         end
     else
         typed = mw.html.create( &quot;td&quot; )
                    :wikitext( getLocalizedText( &quot;doc-param-type-unknown&quot; ) )
     end
 
     -- status
     if param.required then
         mode = 1
         if param.deprecated then
             Fault( string.format( &quot;Required deprecated &lt;code&gt;%s&lt;/code&gt;&quot;,
                                   access ) )
             legal = false
         end
     elseif param.deprecated then
         mode = 4
     elseif param.suggested then
         mode = 2
     else
         mode = 3
     end
     status = ranking[ mode ]
     ranking = getLocalizedText( &quot;doc-param-status-&quot; .. status )
     if mode == 1  or  mode == 4 then
         ranking = mw.html.create( &quot;span&quot; )
                          :css( &quot;font-weight&quot;, &quot;bold&quot; )
                          :wikitext( ranking )
         if type( param.deprecated ) == &quot;string&quot; then
             ranking:tag( &quot;br&quot; )
             ranking:wikitext( param.deprecated )
         end
     end
 
     -- &lt;tr&gt;
     r:attr( &quot;id&quot;,  &quot;templatedata:&quot; .. mw.uri.anchorEncode( access ) )
      :cssText( Permit.styles[ status ] )
      :node( begin )
      :node( code )
      :node( desc )
      :node( typed )
      :node( mw.html.create( &quot;td&quot; )
                    :attr( &quot;data-sort-value&quot;, tostring( mode ) )
                    :node( ranking ) )
      :newline()
     if not legal then
         r:css( &quot;border&quot;, &quot;#FF0000 3px solid&quot; )
     end
     return r
 end -- feature()
 
 
 
 local function features()
     -- Create &lt;table&gt; for parameters
     -- Returns &lt;table&gt;, or nil
     local r
     if Data.tree and Data.tree.params then
         local style = Permit.styles.tableheadbg
         local tbl   = mw.html.create( &quot;table&quot; )
                              :addClass( &quot;wikitable&quot; )
         local tr    = mw.html.create( &quot;tr&quot; )
         feat()
         if Data.order  and  #Data.order &gt; 1 then
             tbl:addClass( &quot;sortable&quot; )
         end
 --      if Config.classTable then
 --          tbl:addClass( Config.classTable )
 --      end
         if Config.stylesTable then
             tbl:cssText( Config.stylesTable )
         end
         tr:node( mw.html.create( &quot;th&quot; )
                         :attr( &quot;colspan&quot;, &quot;2&quot; )
                         :cssText( style )
                         :wikitext( getLocalizedText( &quot;doc-param-name&quot; ) ) )
           :node( mw.html.create( &quot;th&quot; )
                         :cssText( style )
                         :wikitext( getLocalizedText( &quot;doc-param-desc&quot; ) ) )
           :node( mw.html.create( &quot;th&quot; )
                         :cssText( style )
                         :wikitext( getLocalizedText( &quot;doc-param-type&quot; ) ) )
           :node( mw.html.create( &quot;th&quot; )
                         :cssText( style )
                         :wikitext( getLocalizedText( &quot;doc-param-status&quot; ) ) )
         tbl:newline()
 --         :node( mw.html.create( &quot;thead&quot; )
                          :node( tr )
 --             )
            :newline()
         if Data.order then
             for i = 1, #Data.order do
                 tbl:node( feature( Data.order[ i ] ) )
             end -- for i = 1, #Data.order
         end
         if Config.stylesTabWrap then
             r = mw.html.create( &quot;div&quot; )
                    :cssText( Config.stylesTabWrap )
                    :node( tbl )
         else
             r = tbl
         end
     end
     return r
 end -- features()
 
 
 
 local function finalize()
     -- Wrap presentation into frame
     -- Returns string
     local r
     if Data.div then
         r = tostring( Data.div )
     elseif Data.strip then
         r = Data.strip
     else
         r = &quot;&quot;
     end
     return r .. failures()
 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( &quot;&lt;templatedata&gt;&quot;, 1, true )
     local r
     if i then
         local k = s:find( &quot;&lt;/templatedata&gt;&quot;, 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( &quot;\n&quot;, &quot; &quot; )
         if r:find( &quot;&lt;noexport&gt;&quot;, 1, true ) then
             r = r:gsub( &quot;&lt;noexport&gt;(.*)&lt;/noexport&gt;&quot;, &quot;&quot; )
         end
         r = plaintext._main(r)
         if r:find( &quot;&amp;&quot;, 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 &lt;templatedata&gt; JSON string
     local r
     if Data.tag then
         r = mw.text.jsonEncode( Data.tag ):gsub( &quot;%}$&quot;, &quot;,&quot; )
     else
         r = &quot;{&quot;
     end
     r = r .. &quot;\n\&quot;params\&quot;:{&quot;
     if Data.order then
         local sep = &quot;&quot;
         local s
         for i = 1, #Data.order do
             s   = Data.order[ i ]
             r   = string.format( &quot;%s%s\n%s:%s&quot;,
                                  r,
                                  sep,
                                  mw.text.jsonEncode( s ),
                                  mw.text.jsonEncode( Data.params[ s ] ) )
             sep = &quot;,\n&quot;
         end -- for i = 1, #Data.order
     end
     r = r .. &quot;\n}\n}&quot;
     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( &quot;&lt;code&gt;params.%s&lt;/code&gt;&quot;, at )
                     else
                         r = &quot;''root''&quot;
                     end
                     if a then
                         r = string.format( &quot;%s&lt;code&gt;.%s&lt;/code&gt;&quot;, r, a )
                     end
                     return r
                 end
     local parent
     if access then
         parent = Data.got.params[ access ]
     else
         parent = Data.got
     end
     if type( parent ) == &quot;table&quot; then
         local elem, got, permit, s, scope, slot, tag, target
         if access then
             permit = Permit.params
             if type( access ) == &quot;number&quot; 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 == &quot;string&quot; then
                     v = mw.text.trim( v )
                 end
                 if scope:find( s, 1, true ) then
                     if scope:find( &quot;I18N&quot;, 1, true ) then
                         if s == &quot;string&quot; then
                             elem = handleNoexportWhitespace( v )
                         else
                             local translated
                             v, translated = faraway( v )
                             if v then
                                 if translated  and
                                    k == &quot;description&quot; then
                                     elem = { [ 1 ] = handleNoexportWhitespace( v ),
                                              [ 2 ] = translated }
                                 else
                                     elem = handleNoexportWhitespace( v )
                                 end
                             else
                                 elem = false
                             end
                         end
                         if v then
                             if scope:find( &quot;nowiki&quot;, 1, true ) then
                                 elem = mw.text.nowiki( v )
                             else
                                 v = flat( v )
                             end
                         end
                     else
                         if k == &quot;params&quot;  and  not access then
                             v    = nil
                             elem = nil
                         elseif k == &quot;format&quot;  and  not access then
                             v    = mw.text.decode( v )
                             elem = v
                         elseif k == &quot;inherits&quot; then
                             elem = v
                             if not Data.heirs then
                                 Data.heirs = { }
                             end
                             Data.heirs[ slot ] = v
                             v                  = nil
                         elseif s == &quot;string&quot; then
                             v    = mw.text.nowiki( v )
                             elem = v
                         else
                             elem = v
                         end
                     end
                     if type( elem ) ~= &quot;nil&quot; 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 ) ~= &quot;nil&quot; then
                         if not tag then
                             if access then
                                 if not Data.params then
                                     Data.params = { }
                                 end
                                 Data.params[ slot ] = { }
                                 tag = Data.params[ slot ]
                             else
                                 Data.tag = { }
                                 tag      = Data.tag
                             end
                         end
                         tag[ k ] = v
                     end
                 else
                     s = string.format( &quot;Type &lt;code&gt;%s&lt;/code&gt; bad for %s&quot;,
                                        scope,  f( k, slot ) )
                     Fault( s )
                 end
             else
                 Fault( &quot;Unknown component &quot; .. f( k, slot ) )
             end
         end -- for k, v
     else
         Fault( f() .. &quot; needs to be of &lt;code&gt;object&lt;/code&gt; type&quot; )
     end
 end -- focus()
 
 
 
 local function format()
     -- Build presented documentation
     -- Returns &lt;div&gt;
     local r = mw.html.create( &quot;div&quot; )
     local s = feasible( Data.tree, true )
     if s then
         r:node( s )
     end
     if Data.leading then
         local toc = mw.html.create( &quot;div&quot; )
         if Config.suppressTOCnum then
             toc:addClass( Config.suppressTOCnum )
         end
         toc:css( &quot;margin-top&quot;, &quot;0.5em&quot; )
            :wikitext( &quot;__TOC__&quot; )
         r:newline()
          :node( toc )
          :newline()
     end
     s = features()
     if s then
         if Data.leading then
             r:node( mw.html.create( &quot;h2&quot; )
                            :wikitext( getLocalizedText( &quot;doc-params&quot; ) ) )
              :newline()
         end
         r:node( s )
     end
     if Data.tree and Data.tree.format then
         local e, style
         s = Data.tree.format:lower( Data.tree.format )
         if s == &quot;inline&quot;  or  s == &quot;block&quot; then
             style = &quot;i&quot;
         else
             style = &quot;code&quot;
         end
         r:node( mw.html.create( &quot;p&quot; )
                        :wikitext( &quot;Format: &quot; )
                        :node( mw.html.create( style )
                                      :wikitext( s ) ) )
     end
     return r
 end -- format()
 
 
 
 local function free()
     -- Remove JSON comment lines
     Data.source:gsub( &quot;([{,\&quot;'])(%s*\n%s*//.*\n%s*)([},\&quot;'])&quot;,
                       &quot;%1%3&quot; )
 end -- free()
 
 
 
 local function full()
     -- Build HTML table for display from JSON data, and append an invisible
     -- &lt;templatedata&gt; block.
     Data.div = mw.html.create( &quot;div&quot; )
                       :addClass( &quot;mw-templatedata-doc-wrap&quot; )
     focus()
     if Data.tag then
         if type( Data.got.params ) == &quot;table&quot; 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( format() )
     if not Data.lazy then
         Data.slim = flush()
         if TemplateData.frame then
             local div   = mw.html.create( &quot;div&quot; )
             local tdata = { [ 1 ] = &quot;templatedata&quot;,
                             [ 2 ] = Data.slim }
             Data.strip = TemplateData.frame:callParserFunction( &quot;#tag&quot;,
                                                                 tdata )
             div:wikitext( Data.strip )
             if Config.loudly then
             	-- Display raw templatedata table all the time.
                 Data.div:node( mw.html.create( &quot;hr&quot; ) )
                 Data.div:node( div )
             else
             	-- Creates an expand link to check raw templatedata table.
             	local wrapper = mw.html.create( &quot;div&quot; )
                 wrapper:addClass( &quot;mw-collapsible&quot; )
                 wrapper:addClass( &quot;mw-collapsed&quot; )
                 wrapper:css( &quot;font-size&quot;, &quot;85%&quot; )
                 div:addClass( &quot;mw-collapsible-content&quot; )
                 wrapper:wikitext( &quot;'''Test of raw TemplateData output''': &quot; )
                 wrapper:node( div )
                 Data.div:node( wrapper )
             end
         end
     end
 end -- full()
 
 
 
 local function furnish( adapt, arglist )
     -- Called by f, this function is the first to do any real work when the
     -- module is invoked.
     -- Parameter:
     --     adapt    -- table, #invoke parameters
     --     arglist  -- table, template parameters
     -- Returns string
 --local spy=&quot;&quot;
     local source
     for k, v in pairs( Config ) do
         if adapt[ k ]  and  adapt[ k ] ~= &quot;&quot; then
             Config[ v ] = adapt[ k ]
         end
     end -- for k, v
     Config.loudly = faculty( arglist.debug or adapt.debug )
 --if mw.site.server:find( &quot;//de.wikipedia.beta.wmflabs.org&quot;, 1, true ) then
 --    Config.loudly  = true
 --end
     Data.lazy     = faculty( arglist.lazy )  and  not Config.loudly
     Data.leading  = faculty( arglist.TOC )
     if arglist.JSON then
         source = arglist.JSON
     elseif arglist[ 1 ] then
         local s     = mw.text.trim( arglist[ 1 ] )
         local start = s:sub( 1, 1 )
         if start == &quot;&lt;&quot; then
             Data.strip = s
         elseif start == &quot;{&quot; then
             source = s
         elseif mw.ustring.sub( s, 1, 8 ) ==
                mw.ustring.char( 127, 39, 34, 96, 85, 78, 73, 81 ) then -- &lt;DEL&gt; ' &quot; ` U N I Q
             Data.strip = s
         end
     end
     if not source then
         Data.title = mw.title.getCurrentTitle()
         source = find()
         if not source  and
            Config.subpage  and  Config.suffix  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
 --if source  and
 --           ( source:find( &quot;|&quot;, 1, true ) or
 --             source:find( &quot;}}&quot;, 1, true ) ) then
 --                      --  &lt;ref
 --spy=string.format( &quot;[[category:%s]]&quot;, Config.strange )
 --end
     end
     if not Data.lazy  and  Config.subpage then
         if not Data.title then
             Data.title = mw.title.getCurrentTitle()
         end
         Data.lazy = Data.title.text:match( Config.subpage )
     end
     TemplateData.getPlainJSON( source )
     return finalize()
 --return spy .. finalize()
 end -- furnish()
 
 
 
 TemplateData.failsafe = function ( assert )
 	-- Checks the age of this implementation against some minimum (&quot;assert&quot;).
     local r
     if not assert  or  assert &lt;= TemplateData.serial then
         r = TemplateData.serial
     else
         r = false
     end
     return r
 end -- TemplateData.failsafe()
 
 
 
 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 ) == &quot;string&quot; then
         Data.source = adapt
         free()
         Data.got = mw.text.jsonDecode( Data.source )
         if Data.got then
             full()
             if Data.lasting then
                 Fault( &quot;deprecated type syntax&quot; )
             end
             if Data.less then
                 Fault( Config.solo )
             end
         elseif not Data.strip then
             Fault( &quot;fatal JSON error&quot; )
         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 )
     -- The entry point for templates invoking the module.
     -- Just wraps furnish in an exception handler.
     local lucky, result
     TemplateData.frame = frame
     lucky, result = pcall( furnish, frame.args, frame:getParent().args )
     if not lucky then
         Fault( &quot;INTERNAL: &quot; .. result )
         result = failures()
     end
     return result
 end -- p.f()
 
 p.failsafe = function ( frame )
     -- Versioning interface
     local s = type( frame )
     local since
     if s == &quot;table&quot; then
         since = frame.args[ 1 ]
     elseif s == &quot;string&quot; then
         since = frame
     end
     if since then
         since = mw.text.trim( since )
         if since == &quot;&quot; then
             since = false
         end
     end
     return TemplateData.failsafe( since )  or  &quot;&quot;
 end -- p.failsafe()
 
 p.TemplateData = function ()
     -- Module interface
     return TemplateData
 end