Modul:WikidataScheme

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen
Vorlagenprogrammierung Diskussionen Lua Unterseiten
Modul Deutsch English

Modul: Dokumentation

Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus

Dies ist die (produktive) Mutterversion eines global benutzten Lua-Moduls.
Wenn die serial-Information nicht übereinstimmt, müsste eine Kopie hiervon in das lokale Wiki geschrieben werden.
Versionsbezeichnung auf WikiData: 2019-12-16

local WikidataScheme = { suite   = "WikidataScheme",
                         serial  = "2019-12-16",
                         item    = 74420405,
                         globals = { Multilingual = 47541920 } }
--[=[
Pattern for new entities on Wikidata as internationalized guide
]=]
local Failsafe   = WikidataScheme
local GlobalMod  = WikidataScheme
local Config     = { }
local JSONexport = { }
local Resolver   = { }
local Table      = { }
local Text       = { }



Config.coded    = { dir  = "ltr",
                    lang = "en" }
Config.colors   = { tableheadbg = "B3B7FF",
                    required    = "EAF3FF",
                    suggested   = "FFFFFF",
                    optional    = "EAECF0",
                    deprecated  = "FFCBCB" }
Config.spaces   = { L = "Lexeme:",
                    P = "Property:",
                    Q = false }
Config.types    = { "P", "Q", "L" }
Config.errors   = { bag = false }
Config.i18n     = { onLabel = "Q461984",    -- naming convention
                    onDesc  = "Q1200750",   -- description
                            -- wikibase-newentity-description
                    onAlias = "wikibase-entitytermsforlanguagelistview-aliases" }
                            -- Q18616576   Wikidata property
                            -- Q19798642   Wikidata value
Config.defaults = { err_BadExample      = "Invalid property example",
                                           -- ToDo: I18n
                    err_BadQualifier    = "Invalid qualifier definition",
                    err_InvalidClaim    = "Invalid claim",
                    err_InvalidNameType = "Invalid entity name type",
                    err_NameBad         = "is no assigned entity name",
                    err_NameEmpty       = "entity name empty",
                    err_NameMissed      = "unresolved",
                    err_NoEntity        = "is not an entity",
                    err_NoNameResolver  = "needs",
                    err_NoProperty      = "is not a Property",
                    err_q_qlist         = "Both q and qlist specified",
                    err_Unguided        = "Missing guidance"
                  }



local foreignModule = function ( access, advanced, append, alt, alert )
    -- Fetch global module
    -- Precondition:
    --     access    -- string, with name of base module
    --     advanced  -- true, for require(); else mw.loadData()
    --     append    -- string, with subpage part, if any; or false
    --     alt       -- number, of wikidata item of root; or false
    --     alert     -- true, for throwing error on data problem
    -- Postcondition:
    --     Returns  whatever, probably table
    -- 2019-10-29
    local storage = access
    local finer = function ()
                      if append then
                          storage = string.format( "%s/%s",
                                                   storage,
                                                   append )
                      end
                  end
    local fun, lucky, r, suited
    if advanced then
        fun = require
    else
        fun = mw.loadData
    end
    GlobalMod.globalModules = GlobalMod.globalModules or { }
    suited = GlobalMod.globalModules[ access ]
    if not suited then
        finer()
        lucky, r = pcall( fun,  "Module:" .. storage )
    end
    if not lucky then
        if not suited  and
           type( alt ) == "number"  and
           alt > 0 then
            suited = string.format( "Q%d", alt )
            suited = mw.wikibase.getSitelink( suited )
            GlobalMod.globalModules[ access ] = suited or true
        end
        if type( suited ) == "string" then
            storage = suited
            finer()
            lucky, r = pcall( fun, storage )
        end
        if not lucky and alert then
            error( "Missing or invalid page: " .. storage, 0 )
        end
    end
    return r
end -- foreignModule()



local failures = function ()
    -- Summarize all faults
    -- Postcondition:
    --     Returns  string, hopefully empty
    local r
    if Config.errors.bag then
        local max    = 1000000000
        local id     = math.floor( os.clock() * max )
        local show   = string.format( "%s * Errors",
                                      WikidataScheme.suite )
        local sign   = string.format( "error_%d", id )
        local errors = mw.html.create( "ul" )
                         :addClass( "error" )
        local e      = mw.html.create( "h2" )
                         :addClass( "error" )
                         :attr( "id", sign )
                         :wikitext( show )
        for i = 1, #Config.errors.bag do
            errors:newline()
                  :node( mw.html.create( "li" )
                           :addClass( "error" )
                           :wikitext( Config.errors.bag[ i ] ) )
        end -- for i
        r = string.format( "%s\n%s\n",
                           tostring( e ), tostring( errors ) )
        show = string.format( "[[#%s|%s]]", sign, WikidataScheme.suite )
        mw.addWarning( show )
    end
    Config.errors.bag = false
    return r or ""
end -- failures()



local fault = function ( alert )
    -- Format one error message in HTML
    -- Precondition:
    --     alert  -- string, plain error message
    -- Postcondition:
    --     Returns  string, HTML error message
    local e = mw.html.create( "span" )
                     :addClass( "error" )
                     :wikitext( alert )
    Config.errors.bag = Config.errors.bag  or  { }
    table.insert( Config.errors.bag,  alert or "??fault??" )
    return tostring( e )
end -- fault()



local fix = function ( adjust )
    -- Escape and trim string
    -- Precondition:
    --     adjust  -- string, with some text
    -- Postcondition:
    --     Returns  string, with some text
    return mw.text.trim( adjust ):gsub( "\\", "\\\\" )
                                 :gsub( "\"", "\\\"" )
end -- fix()



Config.feature = function ( area, about, allow )
    local r
    if Config.options then
    end
    return r
end -- Config.feature()



Config.field = function ( ask )
    -- Retrieve general I18n text
    -- Precondition:
    --     ask  -- string, with text ID, entity ID or system message
    -- Postcondition:
    --     Returns  string, with message
    local r
    Config.texts = Config.texts  or  { }
    r = Config.texts[ ask ]
    if not r then
        r = Config.i18n[ ask ]
        if type( r ) == "string" then
            if r:match( "^Q%d+$" ) then
                r = Text.wikibase( r )
            else
                r = mw.message.new( r ):plain()
            end
            Config.texts[ ask ] = r
        end
    end
    return r or "??????????"
end -- Config.field()



Config.first = function ()
    -- Initialize or reset Config
    if not Config.statelist then
        Config.css       = { }
        Config.statelist = { }
        for k, v in pairs( Config.colors ) do
            if k == "tableheadbg" then
                k = "tablehead"
            else
                Config.statelist[ k ] = true
            end
            Config.css[ k ] = { ["background-color"]  =  "#" .. v }
        end -- for k, v
    end
    Config.options = false
    --  aus /config neu, mit .Request überschreiben
end -- Config.first()



JSONexport.family = function ( assign, access, align, adjust )
    -- Create list of claims or qualifiers
    -- Precondition:
    --     assign  -- table, with definition of element
    --     access  -- string, with key in assign
    --     align   -- number, level of alignment
    --     adjust  -- true, for resolving symbolic IDs
    -- Postcondition:
    --     Returns  string with extended JSON code, or not
    local collect = assign[ access ]
    local r
    if type( collect ) == "table" then
        local indent = align + 1
        local n      = #collect
        local shift  = string.rep( "  ", align )
        local list
        if align > 1  and  type( assign.list ) == "boolean" then
            list = assign.list
            r = string.format( "%s\"%s\": %s",
                               shift, tostring( list ) )
        else
            r = ""
        end
        if n > 0 then
            local sep = ""
            if list then
                r = string.format( "%s\n%s", r, shift )
            end
            r = string.format( "%s%s\"%s\": [",
                               r, shift, access )
            for i = 1, n do
                r, sep = JSONexport.fiat( collect[ i ],
                                          indent,
                                          r,
                                          sep,
                                          adjust )
            end -- for i
            r = string.format( "%s\n%s%s    ]",
                               r,  shift,  string.rep( " ", #access ) )
        end
    end
    return r
end -- JSONexport.family()



JSONexport.favour = function ( all )
    -- Create resolve component
    -- Precondition:
    --     all     -- table, with entire request
    local r
    if type( all.resolve ) == "table" then
        local n     = 0
        local order = { }
        local j, coll, s
        for k, v in pairs( all.resolve ) do
            if type( k ) == "string"  and
               type( v ) == "table" then
                k = mw.text.trim( k )
                if k ~= "" then
                    for i = 1, #Config.types do
                        s = Config.types[ i ]
                        j = v[ s ]
                        if type( j ) == "number"  and
                           j >= 0  and
                           j == math.floor( j ) then
                            coll      = coll  or  { }
                            k         = fix( k )
                            coll[ k ] = string.format( "{ \"%s\": %d }",
                                                          s, j )
                            table.insert( order, k )
                            j = mw.ustring.len( k )
                            if j > n then
                                n = j
                            end
                            break -- for i
                        end
                    end -- for i
                end
            end
        end -- for k, v
        if #order > 0 then
            local sep = ""
            local k, v
            table.sort( order )
            r = "  \"resolve\": { "
            n = n + 1
            for i = 1, #order do
                k = order[ i ]
                v = coll[ k ]
                j = mw.ustring.len( k )
                s = string.rep( " ",   n - mw.ustring.len( k ) )
                r = string.format( "%s%s\"%s\": %s%s",
                                     r, sep, k, s, v )
                sep = ",\n               "
            end -- for k, v
            r = r ..  "\n           }"
        end
    end
    return r
end -- JSONexport.favour()



JSONexport.features = function ( assign, align, adjust )
    -- Create value element
    -- Precondition:
    --     assign  -- table, with definition of value
    --     align   -- number, level of alignment
    --     adjust  -- true, for resolving symbolic IDs
    -- Postcondition:
    --     Returns  string, if not empty
    local r
    if type( assign ) == "table" then
        local indent = align + 1
        local shift  = string.rep( "  ", align )
        local f = function ( a )
                      if a  and a ~= "" then
                          if r then
                              r = string.format( "%s,\n%s", r, a )
                          else
                              r = "\n" .. a
                          end
                      end
                  end -- f()
        f( JSONexport.flat( assign, "intro", indent ) )
        f( JSONexport.filled( assign, indent, adjust ) )
        f( JSONexport.fixed( assign, "q", indent, adjust ) )
        f( JSONexport.fixed( assign, "qdefault", indent, adjust ) )
        f( JSONexport.fixed( assign, "qqexample", indent, adjust ) )
        f( JSONexport.flat( assign, "example", indent ) )
        f( JSONexport.family( assign, "qualifiers", indent, adjust ) )
        f( JSONexport.flat( assign, "terminate", indent ) )
        if r then
            r = string.format( "%s   {%s\n%s             }",
                               shift, r, shift )
        end
    end
    return r
end -- JSONexport.features()



JSONexport.fetch = function ( access, adjust, alike )
    -- Resolve symbolic entity name, if necessary
    -- Precondition:
    --     access  -- string, with entity name or ID
    --     adjust  -- true, for resolving symbolic IDs
    --     alike   -- string, if Property or Item required
    -- Postcondition:
    --     Returns: string, if valid, with quoted thing
    local r
    if type( access ) == "string" then
        local s = mw.text.trim( access )
        if s ~= "" then
            if adjust then
                r = Resolver.fetch( s, alike )
            else
                r = s
            end
            if r then
                r = string.format( "\"%s\"",
                                   r:gsub( "\"", "" )
                                    :gsub( "\\", "" ) )
            end
        end
    end
    return r
end -- JSONexport.fetch()



JSONexport.fiat = function ( assign, align, apply, after, adjust )
    -- Create independent entry (in claims or qualifiers) as JSON
    -- Precondition:
    --     assign  -- table, with definition of element
    --     align   -- number, level of alignment
    --     apply   -- string, with JSON code to be extended
    --     after   -- string, with preceding separator
    --     adjust  -- true, for resolving symbolic IDs
    -- Postcondition:
    --     Extend apply if element present
    --     Returns:
    --         1. string with extended JSON code
    --         2. separator, for next element
    local r  = apply
    local r2 = after
    if type( assign ) == "table" then
        local s = JSONexport.fetch( assign.subject, adjust, true )
        if s then
            local shift  = string.rep( "  ", align )
            local indent = align + 1
            local f = function ( a )
                          if a  and a ~= "" then
                              if r then
                                  r = string.format( "%s,\n%s", r, a )
                              else
                                  r = "\n" .. a
                              end
                          end
                      end -- f()
            local state
            r = string.format( "%s%s\n%s{ \"subject\": %s",
                               r, r2, shift, s )
            f( JSONexport.flat( assign, "intro", indent ) )
            if Config.statelist[ assign.state ] then
                state = assign.state
            else
                state = "optional"
            end
            r = string.format( "%s,\n%s  \"state\":   \"%s\"",
                               r, shift, state )
            f( JSONexport.filled( assign, indent, adjust ) )
            f( JSONexport.fixed( assign, "q", indent, adjust ) )
            f( JSONexport.fixed( assign, "qdefault", indent, adjust ) )
            f( JSONexport.fixed( assign, "qqexample", indent, adjust ) )
            if type( assign.values ) == "table" then
                local vals = { }
                local v
                for i = 1, #assign.values do
                    v = JSONexport.features( assign.values[ i ],
                                             indent,
                                             adjust )
                    if v then
                        table.insert( vals, v )
                    end
                end -- for i
                if #vals then
                    local sep = ""
                    r = string.format( "%s,\n%s  \"values\":  [",
                                       r, shift )
                    for i = 1, #vals do
                        r   = string.format( "%s%s\n%s      %s",
                                             r, sep, shift, vals[ i ] )
                        sep = ","
                    end -- for i
                    r = string.format( "%s\n%s             ]", r, shift )
                end
            end
            f( JSONexport.flat( assign, "example", indent ) )
            f( JSONexport.flat( assign, "terminate", indent ) )
            f( JSONexport.further( assign, "class", indent ) )
            f( JSONexport.further( assign, "style", indent ) )
            r = string.format( "%s\n%s}", r, shift )
            r2 = ","
        end
    end
    return r, r2
end -- JSONexport.fiat()



JSONexport.filled = function ( assign, align, adjust )
    -- Create independent JSON list of suggested items (Q), if present
    -- Precondition:
    --     assign  -- table, with definition of element
    --     align   -- number, level of alignment
    --     adjust  -- true, for resolving symbolic IDs
    -- Postcondition:
    --     Returns  string with JSON code, or not
    local r
    if type( assign ) == "table"  and
       type( assign.qlist ) == "table" then
        local n = #assign.qlist
        if n > 0 then
            local indent = align + 1
            local sep    = ""
            local shift  = string.rep( "  ", align )
            local q, s
            r = shift .. "\"qlist\": [ "
            for i = 1, n do
                q = assign.qlist[ i ]
                if type( q ) == "number" then
                    s = tostring( q )
                    if s:find( "^[1-9]%d*$" ) then
                        q = "Q" .. s
                    end
                end
                if type( q ) == "string" then
                    s = JSONexport.fetch( q, adjust, false )
                elseif i == n  and  q == true then
                    s = "true"
                end
                if s then
                    r   = string.format( "%s%s%s",
                                         r, sep, s )
                    sep = ",\n           " .. shift
                end
            end -- for i
            r = r .. " ]"
        end
    end
    return r
end -- JSONexport.filled()



JSONexport.fixed = function ( assign, at, align, adjust )
    -- Create mandatory/default item (Q), if present
    -- Precondition:
    --     assign  -- table, with definition of element
    --     at      -- string, with ID within assign
    --                "q", "qdefault", "qqexample"
    --     align   -- number, level of alignment
    --     adjust  -- true, for resolving symbolic IDs
    -- Postcondition:
    --     Returns  string with JSON code, or not
    local r
    if type( assign ) == "table" then
        local v = assign[ at ]
        local s = type( v )
        if s == "string" then
            s = v
        elseif s == "number" then
            if v > 0  and
               v == math.floor( v ) then
                s = string.format( "Q%d", v )
            else
                s = false
            end
        elseif s == "table" then
            local n = #v
            if n == 1 then
                s = v[ 1 ]
            elseif n > 1 then
                local collect = { }
                local k
                for i = 1, n do
                     s = JSONexport.fetch( v[ i ], adjust, false )
                     if s then
                         table.insert( collect, s )
                         k = i
                     end
                end -- for i
                n = #collect
                if at == "qqexample"  and  n ~= 2 then
                    n = 0
                end
                if n == 1 then
                    s = v[ k ]
                else
                    if n > 1 then
                        local shift = string.rep( "  ", align )
                        local sep   = "\n"
                        r = string.format( "%s\"%s\": [", shift, at )
                        for i = 1, n do
                            r = string.format( "%s%s%s  %s",
                                               r,
                                               sep,
                                               shift,
                                               collect[ i ] )
                            sep = ",\n"
                        end -- for i
                        s = string.rep( " ",  2*align + #at + 4 )
                        r = string.format( "%s\n%s]", r, s )
                    end
                    s = false
                end
            else
                s = false
            end
        else
            s = false
        end
        if s  and  not r  and  at ~= "qqexample" then
            s = JSONexport.fetch( s, adjust, false )
            if s then
                local shift = string.rep( "  ", align )
                r = string.format( "%s\"%s\": %s", shift, at, s )
            end
        end
    end
    return r
end -- JSONexport.fixed()



JSONexport.flat = function ( all, at, align )
    -- Create JSON for textual element
    -- Precondition:
    --     all    -- table, with request branch
    --     at     -- string, with ID within { all }
    --     align  -- number, level of alignment
    -- Postcondition:
    --     Returns  string with extended JSON code
    local e = all[ at ]
    local o = { }
    local s = type( e )
    local r, shift, t
    if s == "table" then
        local parts
        for k, v in pairs( e ) do
            if type( k ) == "string"  and
               k:find( "^%l%l" ) then
                if type( v ) == "string" then
                    if k ~= "class"  and  k ~= "style" then
                        parts = mw.text.split( k, "-" )
                        if parts[ 1 ]:find( "^%l%l%l?$" ) then
                            if #parts >= 2  and
                               not parts[ 2 ]:find( "^%u%l%l%l$" )  and
                               not parts[ 2 ]:find( "^%l%l%l" )  and
                               not parts[ 2 ]:find( "^%u%u$" ) then
                                k = false
                            end
                        else
                            k = false
                        end
                    end
                elseif type( v ) ~= "table"   or
                       ( k ~= "class"  and  k ~= "style" ) then
                    k = false
                end
                if k then
                    t = t  or  { }
                    t[ k ] = v
                    if k ~= "class"  and  k ~= "style" then
                        table.insert( o, k )
                    end
                end
            end
        end -- for k, v
        if t then
            table.sort( o )
            if t.class then
                table.insert( o, "class" )
            end
            if t.style then
                table.insert( o, "style" )
            end
        end
    elseif s == "string" then
        if e:match( "^[PQL]%d+$" ) then
            r = string.format( "\"%s\"", e )
        else
            t = { }
            t.und = e
            table.insert( o, "und" )
        end
    end
    if r or t then
        shift = string.rep( "  ", align )
    end
    if t then
        local sep = ""
        local data, slang
        r = "{\n"
        for i = 1, #o do
            slang = o[ i ]
            data  = t[ slang ]
            if type( data ) == "string" then
                s = fix( data )
            else
                local sept = ""
                s = "{\n"
                for k, v in pairs( data ) do
                    s    = string.format( "%s%s%s    \"%s\": \"%s\"",
                                          s, sept, shift, k, v )
                    sept = ",\n"
                end -- for k, v
                s = string.format( "%s\n%s  %s}",
                                   s,
                                   shift,
                                   string.rep( " ",  #slang + 4 ) )
            end
            r   = string.format( "%s%s%s  \"%s\": \"%s\"",
                                 r, sep, shift, slang, s )
            sep = ",\n"
        end -- for i
        r = string.format( "%s\n%s%s}",
                           r,
                           shift,
                           string.rep( " ",  #at + 4 ) )
    end
    if r then
        r = string.format( "%s\"%s\": %s", shift, at, r )
    end
    return r
end -- JSONexport.flat()



JSONexport.full = function ( all, adjust )
    -- Create independent JSON for registration elements
    -- Precondition:
    --     all     -- table, with entire request
    --     adjust  -- true, for resolving symbolic IDs
    -- Postcondition:
    --     Returns  string with JSON code, or error message
    local r = string.format( "{ \"@generated\": \"%s\"",
                             Config.frame:callParserFunction( "#timel",
                                                              "c" ) )
    local f = function ( a )
                  if a  and a ~= "" then
                      r = string.format( "%s,\n%s", r, a )
                  end
              end -- f()
    r = string.format( "%s,\n  \"@format\": \"%s %s\"",
                       r, WikidataScheme.suite, WikidataScheme.serial )
    f( JSONexport.flat( all, "caption", 1 ) )
    f( JSONexport.flat( all, "onLabel", 1 ) )
    f( JSONexport.flat( all, "onDesc",  1 ) )
    f( JSONexport.flat( all, "onAlias", 1 ) )
    f( JSONexport.family( all, "claims", 1, adjust ) )
    f( JSONexport.flat( all, "footer", 1 ) )
    if not adjust then
        f( JSONexport.favour( all ) )
    end
    f( JSONexport.further( all, "id", 1 ) )
    f( JSONexport.further( all, "class", 1 ) )
    f( JSONexport.further( all, "style", 1 ) )
    f( JSONexport.further( all, "options", 1 ) )
    r = r .. "\n}"
    return r
end -- JSONexport.full()



JSONexport.further = function ( all, at, align )
    -- Create entry for auxilary elements
    -- Precondition:
    --     all    -- table, with entire request
    --     at     -- string, with ID within { all }
    --     align  -- number, level of alignment
    -- Postcondition:
    --     Returns  string with JSON code, or not
    local permit = { id_string     = true,
                     class_string  = true,
                     class_table   = true,
                     style_string  = true,
                     style_table   = true,
                     options_table = true }
    local e = all[ at ]
    local s = type( e )
    local r
    if permit[ string.format( "%s_%s", at, s ) ] then
        if s == "string" then
            e = mw.text.trim( e )
            if e == "" then
                e = false
            elseif at == "class" then
                e = mw.text.split( e, "%s+" )
                s = "table"
            end
        elseif not pairs( e ) then
            e = false
        end
        if e then
            local shift = string.rep( "  ", align )
            if s == "string" then
                r = string.format( "%s%s%s  \"%s\": \"%s\"",
                                   shift,  at,  fix( e ) )
            else
                local sep = ""
                r = "{\n"
                for k, v in pairs( e ) do
                    r   = string.format( "%s%s%s  \"%s\": \"%s\"",
                                         r,  sep,  shift,  k,  fix( e ) )
                    sep = ",\n"
                end -- for k, v
                r = string.format( "%s\n%s%s}",
                                   r,
                                   shift,
                                   string.rep( " ",  #at + 4 ) )
            end
        end
    end
    return r
end -- JSONexport.further()



Resolver.fetch = function ( access, alike )
    -- Resolve entity name
    -- Precondition:
    --     access  -- string, with entity name or ID
    --     alike   -- string, if Property or Item required, "P" or "Q"
    -- Postcondition:
    --     Returns
    --         1. string or not, with entity ID
    --         2. string, with error message
    local r1, r2
    if type( access ) == "string" then
        r2 = mw.text.trim( access )
        if r2 == "" then
            r2 = Text.flip( "err_NameEmpty" )
        else
            if r2:find( "^[PQL]%d+$" ) then
                r1 = r2
            elseif type( WikidataScheme.Request.resolve ) == "table" then
                local entry = WikidataScheme.Request.resolve[ r2 ]
                if type( entry ) == "table" then
                    for k, v in pairs( Config.spaces ) do
                        if type( entry[ k ] ) == "number" then
                            r1 = string.format( "%s%d", k, entry[ k ] )
                            break -- for k, v
                        end
                    end -- for k, v
                    if not r1 then
                        r2 = string.format( "<%s> %s",
                                            r2,
                                            Text.flip( "err_NameBad" )
                                          )
                    end
                else
                    r2 = string.format( "<%s> %s",
                                        r2,
                                        Text.flip( "err_NameMissed" ) )
                end
            else
                r2 = string.format( "<%s> %s .resolve",
                                   r2,
                                   Text.flip( "err_NoNameResolver" ) )
            end
            if r1 then
                if alike  and  r1:sub( 1, 1 ) ~= alike then
                    r2 = string.format( "%s %s",
                                        r1,
                                        Text.flip( "err_InvalidNameType"
                                                 ) )
                elseif not mw.wikibase.isValidEntityId( r1 ) then
                    r2 = string.format( "%s %s",
                                        r1,
                                        Text.flip( "err_NoEntity" ) )
                    r1 = false
                else
                    r2 = false
                end
            end
        end
    else
        r2 = string.format( "%s %s",
                            Text.flip( "err_InvalidNameType" ),
                            type( access ) )
    end
    return r1, r2
end -- Resolver.fetch()



Resolver.fire = function ( all )
    -- Create <table> for resolve assignment
    -- Precondition:
    --     all  -- table, with entire request
    -- Postcondition:
    --     Returns  string with entire HTML table, or not
    local r
    if type( all.resolve ) == "table" then
        local order = { }
        local j, coll, s
        for k, v in pairs( all.resolve ) do
            if type( k ) == "string"  and
               type( v ) == "table" then
                k = mw.text.trim( k )
                if k ~= "" then
                    for i = 1, #Config.types do
                        s = Config.types[ i ]
                        j = v[ s ]
                        if type( j ) == "number"  and
                           j >= 0  and
                           j == math.floor( j ) then
                            coll      = coll  or  { }
                            k         = fix( k )
                            coll[ k ] = string.format( "%s%d", s, j )
                            table.insert( order, k )
                            break -- for i
                        end
                    end -- for i
                end
            end
        end -- for k, v
        if #order > 0 then
            local tbl = mw.html.create( "table" )
                          :addClass( "wikitable sortable" )
                          :addClass( WikidataScheme.suite .. "-resolve" )
            local tr  = mw.html.create( "tr" )
            local e, e2, k, s, td
            tr:newline()
              :node( mw.html.create( "th" )
                            :attr( "title", "symbol" )
                            :css( Config.css.tablehead )
                            :wikitext( "symbol" ) )
              :newline()
              :node( mw.html.create( "th" )
                            :attr( "title", "entity" )
                            :css( Config.css.tablehead )
                            :wikitext( "entity" ) )
              :newline()
              :node( mw.html.create( "th" )
                            :attr( "title", "label" )
                            :css( Config.css.tablehead )
                            :wikitext( "label" ) )
            tbl:newline()
            table.sort( order )
            for i = 1, #order do
                k  = order[ i ]
                tr = mw.html.create( "tr" )
                e  = mw.html.create( "code" )
                            :attr( Config.coded )
                            :wikitext( k )
                td = mw.html.create( "td" )
                            :node( e )
                tr:newline()
                  :node( td )
                e, e2 = Table.fetch( coll[ k ], false, true )
                td = mw.html.create( "td" )
                            :node( e )
                tr:newline()
                  :node( td )
                td = mw.html.create( "td" )
                            :node( e2 )
                tr:newline()
                  :node( td )
                tbl:newline()
                   :node( tr )
            end -- for k, v
            r = tostring( tbl:newline() )
        end
    end
    return r
end -- Resolver.fire()



Text.fashion = function ( at, apply )
    -- Assign class and style
    -- Precondition:
    --     at     -- mw.html element
    --     apply  -- table, or not, may contain components class or style
    -- Postcondition:
    --     element modified
    if type( apply ) == "table" then
        local class = apply.class
        local css   = apply.style
        if class then
            if type( class ) == "string" then
                at:addClass( class )
            elseif type( class ) == "table" then
                for i = 1, #class do
                    at:addClass( class[ i ] )
                end -- for i
            end
        end
        if css then
            if type( css ) == "string" then
                at:cssText( css )
            elseif type( css ) == "table" then
                at:css( css )
            end
        end
    end
end -- Text.fashion()



Text.find = function ( available )
    -- Find best match of I18N options
    -- Precondition:
    --     available  -- table, I18N
    -- Postcondition:
    --     Returns
    --         1. string, with selected message, or not
    --         2. string, with language code, or not
    local r, r2
    local f = function ()
                  if type( r ) == "string" then
                      r = mw.text.trim( r )
                      if r == "" then
                          r = false
                      end
                  end
              end -- f()
    if type( available ) == "table" then
        if Text.Multilingual then
            r, r2 = Text.Multilingual.i18n( available,
                                            false,
                                            Config.frame )
        else
            Text.foreign()
            r = available[ Text.slang ]
            f()
            if r then
                r2 = Text.slang
            else
                r  = available.en
                r2 = "en"
            end
        end
    elseif type( available ) == "string" then
        r = available
    end
    f()
    if not r then
        for k, v in pairs( available ) do
            r = v
            f()
            if r then
                r2 = k
                break -- for k, v
            end
        end -- for k, v
    end
    f()
    if not r then
        r2 = false
    end
    return r, r2
end -- Text.find()



Text.flip = function ( about, apply )
    -- Retrieve specific module I18n text
    -- Precondition:
    --     about  -- string, with text ID
    --     apply  -- sequence table, with parameters
    -- Postcondition:
    --     Returns
    --         1. string, with selected message
    --         2. string, with language code
    local r, r2
    if type( Config.globals ) == "nil" then
        Text.flipper( WikidataScheme.suite )
    end
    if Config.globals then
        r = Config.globals[ about ]
    end
    if r then
        r, r2 = Text.find( r )
    end
    if not r then
        r = Config.defaults[ about ]
        if r then
            r2 = "en"
        else
            if type( about ) == "string" then
                r = string.format( "???I18N(%s)I18N???", about )
            else
                r = fault( "Invalid message ID type" )
            end
        end
    end
    if apply  and
       r:find( "$", 1, true )  and
       type( apply ) == "table" then
        r = mw.message.newRawMessage( r, apply ):plain()
    end
    return r, r2
end -- Text.flip()



Text.flipper = function ( apply )
    -- Retrieve global specific I18N translations
    -- Precondition:
    --     apply  -- string, with package ID
    -- Postcondition:
    local storage = string.format( "I18n/Module:%s.tab", apply )
    local lucky, t = pcall( mw.ext.data.get, storage, "_" )
    Config.globals = false
    if type( t ) == "table" then
        t = t.data
        if type( t ) == "table" then
            local e
            for i = 1, #t do
                e = t[ i ]
                if type( e ) == "table"  and
                   type( e[ 1 ] ) == "string"  and
                   type( e[ 2 ] ) == "table" then
                    Config.globals = Config.globals  or  { }
                    Config.globals[ e[ 1 ] ] = e[ 2 ]
                else
                    error( storage .. " has unexpected scheme",  0 )
                end
            end   -- for i
        else
            error( storage .. " corrupted",  0 )
        end
    end
end -- Text.flipper()



Text.flow = function ( at, alien, applied )
    -- Assign differing language and script direction
    -- Precondition:
    --     at       -- mw.html element
    --     alien    -- string, with language (or script) code, or not
    --     applied  -- string, with plain text, or not
    -- Postcondition:
    --     element modified if not matching
    --     Returns  true if script direction changed
    local dir = function ( a )
                    local r
                    if a then
                        r = "ltr"
                    else
                        r = "rtl"
                    end
                    return r
                end -- dir()
    local isRTL = function ( a )
                      local r
                      if Text.Multilingual then
                          r = Text.Multilingual.isRTL( a )
                      else
                          r = mw.language.new( a ):isRTL()
                      end
                      return r
                  end -- isRTL()
    local shift, slang
    if not Text.shift then
        Text.foreign( true )
        if Text.slang then
            Text.ltr = not isRTL( Text.slang )
        end
        if type( Text.ltr ) ~= "boolean" then
            Text.ltr = not mw.language.getContentLanguage():isRTL()
        end
        Text.shift = dir( Text.ltr )
    end
    if alien then
        Text.dirs = Text.dirs or { }
        shift     = Text.dirs[ alien ]
        if not shift then
            shift = dir( not isRTL( alien ) )
            Text.dirs[ alien ] = shift
        end
        if applied  and  shift == "rtl" then
            if not Text.Latn then
                Text.Latn = string.format( "^[ -%s]*$",
                                           mw.ustring.char( 0x052F ) )
            end
            if mw.ustring.find( applied, Text.Latn ) then
                shift = "ltr"
            end
        end
        if shift == Text.shift then
            shift = false
        end
        if alien ~= Text.slang then
            slang = alien
        end
    else
        shift = Text.shift
        slang = Text.slang
    end
    if shift then
        at:attr( "dir", shift )
    end
    if slang then
        at:attr( "lang", slang )
    end
    return  true and shift
end -- Text.flow()



Text.foreign = function ( again )
    -- Guess human language
    -- Precondition:
    --     again  -- true, for re-evaluation
    -- Postcondition:
    --     Returns  language code, or not
    if again  or  type( Text.slang ) == "nil" then
        if Text.Multilingual then
            Text.slang = Text.Multilingual.userLangCode()
        elseif not Text.slang then
            Text.slang = mw.language.getContentLanguage():getCode()
                                                         :lower()
        end
    end
    if Text.slang  and
       mw.ustring.codepoint( Text.slang, 1, 1 ) > 122 then
        Text.slang = false
    end
    return Text.slang
end -- Text.foreign()



Text.html = function ( available, as, alt, alien, across )
    -- Create HTML text element
    -- Precondition:
    --     available  -- table, I18N, string: plain text or ID, or not
    --     as         -- string, HTML tag name
    --     alt        -- string, with fallback text
    --     alien      -- string, with language (or script) code, or not
    --     across     -- number, of columns, for TD
    -- Postcondition:
    --     Returns  mw.html element
    local r = type( available )
    local show, slang
    if r == "table" then
        show, slang = Text.find( available )
    elseif r == "string" then
        if available:find( "^[QPL]%d+$" ) then
            local sign = available
            if mw.wikibase.isValidEntityId( sign ) then
                local s = Config.spaces[ sign:sub( 1, 1 ) ]
                if s then
                    sign = s .. sign
                else
                    sign = sign
                end
                show, slang = Text.wikibase( sign )
            else
                show = string.format( "%s %s",
                                        sign,
                                        Text.flip( "err_NoEntity" ) )
            end
        end
        if not show then
            show  = available
        end
        slang = slang or alien
    else
        show = alt
    end
    if show then
        r = mw.html.create( as )
        if slang then
            local bidi
            if as == "code" then
                r:attr( Config.coded )
                bidi = not Text.ltr
            else
                bidi = Text.flow( r, slang, show )
            end
            if bidi  and  as == "span" then
                r = mw.html.create( "bdo" )
                      :node( r )
                Text.flow( r, slang )
            end
        end
        r:wikitext( show )
        Text.fashion( r, available )
    else
        r = mw.html.create( "code" )
              :attr( Config.coded )
              :wikitext( as )
    end
    if across  and  across > 1 then
        r:attr( "colspan", tostring( across ) )
    end
    return r
end -- Text.html()



Text.templatedata = function ( area, about )
    -- Retrieve TemplateData system message
    -- Precondition:
    --     area   -- One of: "type", "desc", "status",
    --                       "example", "default"
    --     about  -- nil or in case of area=status: "required",
    --                                              "suggested",
    --                                              "optional",
    --                                              "deprecated"
    -- Postcondition:
    --     Returns  string, in general,
    --              mw.html span for area=example|default
    local s = "templatedata-doc-param-" .. area
    local r
    if about  and  area == "status" then
        s = string.format( "%s-%s", s, about )
    end
    Text.templatedataCache = Text.templatedataCache  or  { }
    r = Text.templatedataCache[ s ]
    if not r then
        local o = mw.message.new( s )
        if o:exists() then
            if Text.foreign( true ) then
                o:inLanguage( Text.slang )
            end
            r = o:plain()
        else
            r = string.format( "???(%s)???", s )
        end
        if area == "example"  or  area == "default" then
            r = mw.html.create( "span" )
                       :addClass( string.format( "%s-label-%s",
                                                 WikidataScheme.suite,
                                                 area ) )
                       :wikitext( r )
        end
        Text.templatedataCache[ s ] = r
    end
    return r
end -- Text.templatedata()



Text.wikibase = function ( all, about, attempt )
    -- Translation of wikibase component
    -- Precondition:
    --     all      -- string or table, object ID or entity
    --     about    -- boolean, true "descriptions" or false "labels"
    --     attempt  -- string or not, code of preferred language
    -- Postcondition:
    --     Returns
    --         1. string, with selected message, or not
    --         2. string, with language code, or not
    local r, r2
    if Text.Multilingual then
        r, r2 = Text.Multilingual.wikibase( all,
                                            about,
                                            attempt,
                                            Config.frame )
    else
        local s = type( all )
        local object
        if s == "table" then
            object = all
        elseif s == "string" then
            object = mw.wikibase.getEntity( all )
            r      = all
        end
        if type( object ) == "table" then
            if about then
                s = "descriptions"
            else
                s = "labels"
            end
            object = object[ s ]
            if type( object ) == "table" then
                if object[ attempt ] then
                    r  = object[ attempt ].value
                    r2 = attempt
                elseif object.en then
                    r  = object.en.value
                    r2 = "en"
                else
                    local poly
                    for k, v in pairs( object ) do
                        r  = v.value
                        r2 = k
                        break -- for k, v
                    end -- for k, v
                end
            end
        end
    end
    return r or "????????", r2
end -- Text.wikibase()



Table.features = function ( assigned, across, already, asked, above )
    -- Describe entry features
    -- Precondition:
    --     assigned  -- table, with definition of one entry
    --     across    -- number, of columns, or nil on second level
    --     already   -- true, if first element to be created as TR
    --     asked     -- true, if first element to be created as TR
    --     above     -- string, with context property ID
    -- Postcondition:
    --     Returns  sequence table with mw.html objects
    --         1. table, with TD
    --         2. table, with TR, or not
    local r1, r2, td
    local f = function ( add, also, a )
                  if add then
                      if across  and  across > 1  and  not a then
                          add:attr( "colspan", tostring( across ) )
                      end
                      if already or r1 then
                          local tr
                          if add or not r2 then
                              tr = mw.html.create( "tr" )
                                          :node( add )
                              Table.flag( tr, asked )
                              if also then
                                  tr:newline()
                                    :node( also )
                              end
                              r2 = r2  or  { }
                              table.insert( r2, tr )
                          else
                              tr = r2[ #r2 ]
                          end
                      else
                          r1 = { }
                          table.insert( r1, add )
                          if also then
                              table.insert( r1, also )
                          end
                      end
                  end
              end -- f()
    local f1 = function ( already, add, about )
                   local ttd = Text.templatedata( about )
                   local sel = string.format( "%s-%s",
                                              WikidataScheme.suite,
                                              about )
                   local r   = already
                   local lift
                   if r then
                       r:newline()
                   else
                       r    = mw.html.create( "td" )
                       lift = true
                   end
                   r:node( mw.html.create( "div" )
                                  :node( ttd ) )
                    :newline()
                    :node( add:addClass( sel ) )
                   if lift then
                       f( r )
                   end
                   return r
               end -- f1()
    local f2 = function ( add, about )
                   local ttd = Text.templatedata( about )
                   local sel = string.format( "%s-%s",
                                              WikidataScheme.suite,
                                              about )
                   local r   = already
                   f( mw.html.create( "td" )
                             :node( ttd ),
                      add:addClass( sel ),
                      true )
               end -- f2()
    if type( assigned ) == "table" then
        f( Table.field( assigned, "intro", 2 ) )
        if type( assigned.qlist ) == "table" then
            td = mw.html.create( "td" )
            if type( assigned.q ) == "nil" then
                local ul = Table.filled( assigned.qlist )
                if ul then
                    f( td:node( ul ) )
                end
            else
                f(td:wikitext( fault( Text.flip( "err_q_qlist" ) ) ) )
            end
        elseif type( assigned.q ) ~= "nil" then
            td = mw.html.create( "td" )
            f( td:node( Table.fixed( assigned, "q" ) ) )
        end
        if type( assigned.qdefault ) ~= "nil" then
            local q = Table.fixed( assigned, "qdefault" )
            if across == 2 then
                q = mw.html.create( "td" )
                           :node( q )
                f2( q, "default" )
            else
                td = f1( td, q, "default" )
            end
        end
        if type( assigned.example ) ~= "nil" then
            if across == 2 then
                local q = Table.field( assigned, "example" )
                f2( q, "example" )
            else
                local q = Text.html( assigned.example, "div" )
                td = f1( td, q, "example" )
            end
        elseif type( assigned.qqexample ) == "table" then
            local qpq = Table.fruit( above, assigned.qqexample )
            if across == 2 then
                f2( mw.html.create( "td" )
                           :node( qpq ),
                    "example" )
            else
                td = f1( td, qpq, "example" )
            end
        end
        if type( assigned.qualifiers ) == "table" then
            if across == 2 then
                local hd, q, rows
                for i = 1, #assigned.qualifiers do
                    q = assigned.qualifiers[ i ]
                    if type( q ) == "table" then
                        hd, rows = Table.further( q, hd )
                        if hd then
                            f( hd[ 1 ], hd[ 2 ], true )
                        end
                        if rows then
                            r2 = r2  or  { }
                            for k = 1, #rows do
                                table.insert( r2, rows[ k ] )
                            end -- for k
                        end
                    else
                        q = Text.flip( "err_BadQualifier" )
                        f( mw.html.create( "td" )
                             :wikitext( fault( q ) ) )
                    end
                end -- for i
            else
                td = mw.html.create( "td" )
                       :wikitext( fault( Text.flip( "err_Nesting" ) ) )
                f( td )
            end
        end
        f( Table.field( assigned, "terminate", 2 ) )
    end
    if not ( r1 ) then
        f( mw.html.create( "td" ) )
    end
    return r1, r2
end -- Table.features()



Table.fetch = function ( access, alike, atom, alone )
    -- Describe registration entity
    -- Precondition:
    --     access  -- string, with entity name
    --     alike   -- string, if Property or Item required, "P" or "Q"
    --     atom    -- true, if two elements required
    --     alone   -- true, if combined linked element required
    -- Postcondition:
    --     Returns
    --         1. string, with entity ID and label, or error message,
    --            or mw.html code element for atom
    --         2. mw.html span element for atom
    local sign, r1 = Resolver.fetch( access, alike )
    local r2
    if sign then
        local s = Config.spaces[ sign:sub( 1, 1 ) ]
        local say, slang
        if s then
            s = s .. sign
        else
            s = sign
        end
        say, slang = Text.wikibase( sign, alike )
        r2 = Text.html( say, "span", false, slang )
        if alone then
            r1 = string.format( "[[d:%s|%s]]", s, tostring( r2 ) )
        else
            s  = string.format( "[[d:%s|%s]]", s, sign )
            r1 = Text.html( s, "code", false, "en" )
            if not atom then
                r1 = string.format( "%s %s",
                                    tostring( r1 ),
                                    tostring( r2 ) )
            end
        end
    elseif r1 then
        r1 = fault( r1 )
    else
        r1 = fault( "Table.fetch()" )
    end
    return r1, r2
end -- Table.fetch()



Table.fiat = function ( assign )
    -- Describe registration claim as major table row
    -- Precondition:
    --     assign  -- table, with definition of one entry
    -- Postcondition:
    --     Returns  sequence table, with mw.html.TR objects
    local tr = mw.html.create( "tr" )
    local td = mw.html.create( "td" )
    local r  = { }
    if type( assign ) == "table" then
        local state = Table.flag( tr, assign.state )
        local how, n, rows, v
        Text.fashion( tr, assign )
        tr:addClass( string.format( "%s-%s",
                                    WikidataScheme.suite, state ) )
        v = Table.fetch( assign.subject )
        td:wikitext( v )
        if type( assign.values ) == "table" then
            for i = 1, #assign.values do
                how, v = Table.features( assign.values[ i ],
                                         2,
                                         how,
                                         state,
                                         assign.subject )
                if v then
                    rows = rows or { }
                    for i = 1, #v do
                        table.insert( rows, v[ i ] )
                    end -- for i
                end
            end -- for i
        else
            how, rows = Table.features( false,
                                        2,
                                        false,
                                        state,
                                        assign.subject )
            --[==[ if not ( how or rows ) then
                td = mw.html.create( "td" )
                       :wikitext( fault( Text.flip( "err_Unguided" ) ) )
                f( td )
            end ]==]
        end
        if rows then
            n = #rows + 1
        else
            n = 0
        end
        if n > 1 then
            td:attr( "rowspan", tostring( n ) )
        end
        tr:newline()
          :node( td )
        if how then
            for i = 1, #how do
                tr:newline()
                  :node( how[ i ] )
            end -- for i
        end
        td = mw.html.create( "td" )
               :wikitext( Text.templatedata( "status", state ) )
        if n > 1 then
            td:attr( "rowspan", tostring( n ) )
        end
        tr:newline()
          :node( td )
        table.insert( r, tr )
        for i = 1, n do
            table.insert( r, rows[ i ] )
        end -- for i
    else
        td:attr( "colspan", "3" )
          :css( { ["background-color"] = "#FFFF00" } )
          :wikitext( fault( Text.flip( "err_InvalidClaim" ) ) )
        tr:node( td )
        table.insert( r, tr )
    end
    return r
end -- Table.fiat()



Table.field = function ( all, ask, across )
    -- Insert plain text table cell
    -- Precondition:
    --     all     -- table, with request
    --     ask     -- string, with key in all
    --     across  -- number, of columns, or not
    -- Postcondition:
    --     Returns  mw.html.TD object, or nothing
    local q = all[ ask ]
    local r
    if q then
        local n = across or 1
        r = Text.html( q, "td", false, false, n )
    end
    return r
end -- Table.field()



Table.filled = function ( all )
    -- Create unordered list of items
    -- Precondition:
    --     all  -- sequence table, with entity names, and true as last
    -- Postcondition:
    --     Returns  mw.html.UL, or nothing
    local q, r, s
    for i = 1, #all do
        r = r  or  mw.html.create( "ul" )
        q = all[ i ]
        if type( q ) == "number" then
            s = tostring( q )
            if s:find( "^[1-9]%d*$" ) then
                q = "Q" .. s
            end
        end
        if type( q ) == "string" then
            s = Table.fetch( q )
        elseif i == #all  and  q == true then
            s = "&hellip;"
        else
            q = mw.html.create( "code" )
                  :wikitext( tostring( q ) )
            s = Text.flip( "err_InvalidNameType" )
            s = string.format( "%s %s",  tostring( q ),  fault( s ) )
        end
        r:newline()
         :node( mw.html.create( "li" )
                  :wikitext( s ) )
    end -- for i
    r:newline()
    return r
end -- Table.filled()



Table.fixed = function ( assigned, at )
    -- Create mandatory/default item (Q)
    -- Precondition:
    --     assigned  -- string or number or sequence table of strings,
    --               with item identifier
    --     at     -- string, with ID within assign, "q" or "qdefault"
    --               -> string or number or sequence table of strings,
    --               with item identifier
    -- Postcondition:
    --     Returns  mw.html.span
    local v = assigned[ at ]
    local s = type( v )
    local r
    if s == "string" then
        s = v
    elseif s == "number" then
        if v > 0  and
           v == math.floor( v ) then
            s = string.format( "Q%d", v )
        else
            s = false
        end
    elseif s == "table" then
        local n = #v
        if n == 1 then
            r = Table.fixed( v[ 1 ], at )
        elseif n > 1 then
            r = mw.html.create( "span" )
            for i = 1, #n do
                if i > 1 then
                    r:node( mw.html.create( "br" ) )
                     :newline()
                end
                r:node( Table.fixed( v[ i ], at ) )
            end -- for i
        else
            s = false
        end
    else
        s = false
    end
    if not r then
        if s then
            s = Table.fetch( s )
        else
            s = fault( Text.flip( "err_NoEntity" ) )
        end
        r = mw.html.create( "span" )
                   :wikitext( s )
    end
    return r
end -- Table.fixed()



Table.flag = function ( adjust, assign )
    -- Equip element with state style
    -- Precondition:
    --     adjust  -- mw.html object, to be flagged
    --     assign  -- state name
    -- Postcondition:
    --     element modified
    --     Returns defined state name
    local r = assign
    local css
    if Config.css[ r ] then
        css = Config.css[ r ]
    else
        r   = "optional"
        css = { ["background-color"] = "#FFFF00" }
    end
    adjust:css( css )
    css = Config.feature( "css", r, "table string" )
    if type( css ) == "string" then
        adjust:cssText( css )
    elseif type( css ) == "table" then
        adjust:css( css )
    end
    return r
end -- Table.flag()



Table.flat = function ( all, ask, across, append )
    -- Insert plain text table row
    -- Precondition:
    --     all     -- table, with request
    --     ask     -- string, with key in all
    --     across  -- number, of columns, or not
    --     append  -- mw.html object, to be added to, or nothing
    -- Postcondition:
    --     Returns  mw.html.TR object, or nothing
    local q = all[ ask ]
    local r
    if q then
        local n = across or 1
        local s = Config.field( ask )
        local td
        r = mw.html.create( "tr" )
        if s then
            td = mw.html.create( "td" )
                   :wikitext( s )
            r:newline()
             :node( td )
            n = 2
        end
        td = Text.html( q, "td", false, false, n )
        r:newline()
         :node( td )
        if append then
            append:newline()
                  :node( r )
        end
    end
    return r
end -- Table.flat()



Table.form = function ( all )
    -- Create <table> for registration elements
    -- Precondition:
    --     all  -- table, with entire request
    -- Postcondition:
    --     Returns  string with entire HTML table
    local tbl = mw.html.create( "table" )
                  :addClass( "wikitable" )
                  :addClass( WikidataScheme.suite .. "-table" )
    local tr, rows
    if type( all.claims ) == "table" then
        local got
        for i = 1, #all.claims do
            got = Table.fiat( all.claims[ i ] )
            for k = 1, #got do
                rows = rows or { }
                table.insert( rows, got[ k ] )
            end -- for k
        end -- for i
    end
    if not rows then
        rows = { Table.fiat( false ) }
    end
    if all.caption then
        local o = type( all.caption )
        if o == "string"  or  o == "table" then
            o = Text.html( all.caption, "caption" )
            Text.fashion( caption, all.caption )
            tbl:newline()
               :node( o )
        end
    end
    Table.flat( all, "onLabel", 2, tbl )
    Table.flat( all, "onDesc",  2, tbl )
    Table.flat( all, "onAlias", 2, tbl )
    Text.shift = false
    Text.flow( tbl )
    Text.fashion( tbl, all )
    if type( all.id ) == "string" then
        tbl:attr( "id", all.id )
    end
    tr = mw.html.create( "tr" )
    tr:newline()
      :node( mw.html.create( "th" )
                    :attr( "title", "type" )
                    :css( Config.css.tablehead )
                    :wikitext( Text.templatedata( "type" ) ) )
      :newline()
      :node( mw.html.create( "th" )
                    :attr( "colspan", "2" )
                    :css( Config.css.tablehead )
                    :wikitext( Text.templatedata( "desc" ) ) )
      :newline()
      :node( mw.html.create( "th" )
                    :attr( "title", "status" )
                    :css( Config.css.tablehead )
                    :wikitext( Text.templatedata( "status" ) ) )
    tbl:newline()
--     :node( mw.html.create( "thead" )
                     :node( tr )
--          )
    for i = 1, #rows do
        tbl:newline()
           :node( rows[ i ] )
    end -- for i
    if all.footer then
        local o = type( all.footer )
        if o == "string"  or  o == "table" then
            o = Table.flat( all, "footer", 3 )
            Text.fashion( footer, all.footer )
            tbl:newline()
               :node( o )
        end
    end
    return  tostring( tbl:newline() )
end -- Table.form()



Table.fruit = function ( assigned, adjoin )
    -- Return content of a qqexample chain
    -- Precondition:
    --     assigned   -- string, with name of P
    --     adjoin     -- table, with 2 elements as Q names: Item, P-value
    -- Postcondition:
    local e, r, s
    if type( adjoin ) == "table"  and  #adjoin == 2 then
        local p
        p, e = Resolver.fetch( assigned, "P" )
        if p then
            local q1
            q1, e = Resolver.fetch( adjoin[ 1 ], "Q" )
            if q1 then
                local q2
                q2, e = Resolver.fetch( adjoin[ 2 ], "Q" )
                if q2 then
                    local x
                    r = mw.html.create( "span" )
                    x, p = Table.fetch( p, false, true )
                    q1   = Table.fetch( q1, false, false, true )
                    q2   = Table.fetch( q2, false, false, true )
                    r:wikitext( q1 )
                     :wikitext( " &lt;" )
                     :node( p )
                     :wikitext( "&gt; " )
                     :wikitext( q2 )
                end
            end
        end
    end
    if not r then
        e = Text.flip( "err_BadExample" )
    end
    if e then
        r = fault( e )
    end
    return r
end -- Table.fruit()



Table.further = function ( assign, already )
    -- Describe one qualifier as middle column table row
    -- Precondition:
    --     assign   -- table, with definition of one qualifier
    --     already  -- true, if first element to be created as TR
    -- Postcondition:
    --     Returns sequence tables with mw.html objects, or not
    --         1. TD, if not already
    --         2. TR, if any
    local state, r1, r2, tr
    local f = function ( add, also )
                  if add then
                      if state then
                          Table.flag( add, state )
                          if also then
                              Table.flag( also, state )
                          end
                      end
                      if already or r1 then
                          tr = mw.html.create( "tr" )
                                 :newline()
                                 :node( add )
                          if also then
                              tr:newline()
                                :node( also )
                          end
                          r2 = r2  or  { }
                          table.insert( r2, tr )
                      else
                          r1 = { }
                          table.insert( r1, add )
                          if also then
                              table.insert( r1, also )
                          end
                      end
                  end
              end -- f()
    local td
    local how, rows
    if Config.statelist[ assign.state ] then
        state = assign.state
    else
        state = "optional"
    end
    f( Table.flat( assign, "intro", 2 ) )
    how, rows = Table.features( assign, 1, r1, state, assign.subject )
    r1 = r1 or how
    if rows then
        r2 = r2  or  { }
        for i = 1, #rows do
            table.insert( r2, rows[ i ] )
        end -- for i
    end
    f( Table.flat( assign, "terminate" ) )
    td = mw.html.create( "td" )
           :node( Table.fetch( assign.subject, "P" ) )
    if r1 then
        table.insert( r1, 1, td )
    elseif r2 then
        r2[ 1 ]:newline()
               :node( td )
    else
        f( td )
    end
    return r1, r2
end -- Table.further()



WikidataScheme.fetch = function ( about )
    -- Retrieve Lua table
    -- Precondition:
    --     about  -- table or JSON string or mw.loadData page name
    -- Postcondition:
    --     Returns  string with error message, or not
    --     Makes Lua table available as .Request
    local s = type( about )
    local r
    if s == "string" then
        local lucky, d
        s = mw.text.trim( about )
        if s:sub( 1, 1 ) == "{"  and
           s:sub( -1 ) == "}" then
            lucky, d = pcall( mw.text.jsonDecode, about )
        elseif not s:find( "\n", 2, true ) then
            lucky, d = pcall( mw.loadData, about )
        end
        s = type( d )
        if s == "table" then
            WikidataScheme.Request = d
        elseif s == "string" then
            r = d
        else
            r = "Invalid data for WikidataScheme"
        end
    elseif s == "table" then
        WikidataScheme.Request = about
    else
        r = "Invalid request for WikidataScheme"
    end
    return r
end -- WikidataScheme.fetch()



WikidataScheme.flat = function ( about, adjust, frame )
    -- Export registration description as JSON
    -- Precondition:
    --     about   -- table or JSON string or mw.loadData page name
    --     adjust  -- true, for resolving symbolic IDs
    --     frame   -- frame, if available
    -- Postcondition:
    --     Returns  string with JSON or error message
    local r = WikidataScheme.fetch( about )
    if not r then
        Config.frame = Config.frame  or  frame  or  mw.getCurrentFrame()
        Config.first()
        r = JSONexport.full( WikidataScheme.Request, adjust )
        r = failures() .. r
    end
    return r
end -- WikidataScheme.flat()



WikidataScheme.form = function ( about, assigned, frame )
    -- Describe registration elements
    -- Precondition:
    --     about     -- table or JSON string or mw.loadData page name
    --     assigned  -- true, for resolve table only
    --     frame     -- frame, if available
    -- Postcondition:
    --     Returns  string with entire HTML table
    local r
    Config.frame = Config.frame  or  frame  or  mw.getCurrentFrame()
    if not Text.Multilingual  and  Text.Multilingual ~= false then
        local bib = foreignModule( "Multilingual",
                                   true,
                                   false,
                                   WikidataScheme.globals.Multilingual,
                                   true )
        if type( bib ) == "table"  and
           type( bib.Multilingual ) == "function" then
            Text.Multilingual = bib.Multilingual()
        else
            Text.Multilingual = false
        end
    end
    r = WikidataScheme.fetch( about )
    if not r then
        Config.first()
        if assigned then
            r = Resolver.fire( WikidataScheme.Request )
        else
            r = Table.form( WikidataScheme.Request )
        end
    end
    r = failures() .. r
    return r
end -- WikidataScheme.form()



Failsafe.failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     atleast  -- string, with required version or "wikidata" or "~"
    --                 or false
    -- Postcondition:
    --     Returns  string  -- with queried version, also if problem
    --              false   -- if appropriate
    -- 2019-10-15
    local last  = ( atleast == "~" )
    local since = atleast
    local r
    if last  or  since == "wikidata" then
        local item = Failsafe.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 seek = Failsafe.serialProperty or "P348"
                local vsn  = entity:formatPropertyValues( seek )
                if type( vsn ) == "table"  and
                   type( vsn.value ) == "string"  and
                   vsn.value ~= "" then
                    if last  and  vsn.value == Failsafe.serial then
                        r = false
                    else
                        r = vsn.value
                    end
                end
            end
        end
    end
    if type( r ) == "nil" then
        if not since  or  since <= Failsafe.serial then
            r = Failsafe.serial
        else
            r = false
        end
    end
    return r
end -- Failsafe.failsafe()



-- Export
local p = { }

p.form = function ( frame )
    local s = mw.text.trim( frame.args[ 1 ]  or  "" )
    local r
    if s ~= "" then
        r = WikidataScheme.form( s, false, frame )
    end
    return r or ""
end -- p.form

p.format = function ( frame )
    local s = mw.text.trim( frame.args[ 1 ]  or  "" )
    local r
    if s ~= "" then
        r = WikidataScheme.flat( s, false, frame )
    end
    return r or ""
end -- p.form

p.furnished = function ( frame )
    local s = mw.text.trim( frame.args[ 1 ]  or  "" )
    local r
    if s ~= "" then
        r = WikidataScheme.form( s, true, frame )
    end
    return r or ""
end -- p.furnished

p.JSON = function ( frame )
    local s = mw.text.trim( frame.args[ 1 ]  or  "" )
    local r
    if s ~= "" then
        r = WikidataScheme.flat( s, true, frame )
    end
    return r or ""
end -- p.JSON

p.failsafe = function ( frame )
    -- Versioning interface
    local s = type( frame )
    local since
    if s == "table" then
        since = frame.args[ 1 ]
    elseif s == "string" then
        since = frame
    end
    if since then
        since = mw.text.trim( since )
        if since == "" then
            since = false
        end
    end
    return Failsafe.failsafe( since )  or  ""
end -- p.failsafe()

p.WikidataScheme = function ()
    WikidataScheme.Text = Text
    return WikidataScheme
end -- p.WikidataScheme

return p