Zum Inhalt springen

„Modul:WikidataScheme“ – Versionsunterschied

aus Wikipedia, der freien Enzyklopädie
[gesichtete Version][gesichtete Version]
Inhalt gelöscht Inhalt hinzugefügt
2019-12-03
2019-12-04
Zeile 1: Zeile 1:
local WikidataScheme = { suite = "WikidataScheme",
local WikidataScheme = { suite = "WikidataScheme",
serial = "2019-12-03",
serial = "2019-12-04",
item = 74420405,
item = 74420405,
globals = { Multilingual = 47541920 } }
globals = { Multilingual = 47541920 } }
Zeile 262: Zeile 262:
end
end
Config.options = false
Config.options = false
-- aus /config neu, mit .Request überschreiben
end -- Config.first()
end -- Config.first()


Zeile 280: Zeile 281:
local indent = align + 1
local indent = align + 1
local n = #collect
local n = #collect
local shift = string.rep( " ", 2*align + 1 )
local shift = string.rep( " ", align )
local list
local list
if align == 1 then
if align > 1 and type( assign.list ) == "boolean" then
r = ",\n"
elseif type( assign.list ) == "boolean" then
list = assign.list
list = assign.list
r = string.format( "%s\"%s\": %s",
r = string.format( "%s\"%s\": %s",
Zeile 294: Zeile 293:
local sep = ""
local sep = ""
if list then
if list then
r = string.format( "%s,\n%s", r, shift )
r = string.format( "%s\n%s", r, shift )
end
end
r = string.format( "%s%s\"%s\": [",
r = string.format( "%s%s\"%s\": [",
Zeile 311: Zeile 310:
return r
return r
end -- JSONexport.family()
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 types = { "P", "Q", "L" }
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, #types do
s = types[ i ]
j = v[ s ]
if type( j ) == "number" and
j >= 0 and
j == math.floor( j ) then
coll = coll or { }
k = k:gsub( "\\", "\\\\" )
:gsub( "\"", "\\\"" )
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()




Zeile 325: Zeile 384:
if type( assign ) == "table" then
if type( assign ) == "table" then
local indent = align + 1
local indent = align + 1
local shift = string.rep( " ", 2*align + 1 )
local shift = string.rep( " ", align )
local f = function ( a )
local f = function ( a )
if a and a ~= "" then
if a and a ~= "" then
Zeile 396: Zeile 455:
local s = JSONexport.fetch( assign.subject, adjust, true )
local s = JSONexport.fetch( assign.subject, adjust, true )
if s then
if s then
local shift = string.rep( " ", 2*align + 1 )
local shift = string.rep( " ", align )
local indent = align + 1
local indent = align + 1
local f = function ( a )
local f = function ( a )
Zeile 469: Zeile 528:
local indent = align + 1
local indent = align + 1
local sep = ""
local sep = ""
local shift = string.rep( " ", 2*align + 1 )
local shift = string.rep( " ", align )
local q, s
local q, s
r = shift .. "\"qlist\": [ "
r = shift .. "\"qlist\": [ "
Zeile 587: Zeile 646:
f( JSONexport.family( all, "claims", 1, adjust ) )
f( JSONexport.family( all, "claims", 1, adjust ) )
f( JSONexport.flat( all, "footer", 1 ) )
f( JSONexport.flat( all, "footer", 1 ) )
if not adjust then
f( JSONexport.favour( all ) )
end
r = r .. "\n}"
r = r .. "\n}"
return r
return r
Zeile 1.642: Zeile 1.704:
return WikidataScheme
return WikidataScheme
end -- p.WikidataScheme
end -- p.WikidataScheme



return p
return p

Version vom 7. Dezember 2019, 20:01 Uhr

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

Updating notwendig

(lokal: 2019-12-04)

local WikidataScheme = { suite   = "WikidataScheme",
                         serial  = "2019-12-04",
                         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 Table      = { }
local Text       = { }



Config.colors   = { tableheadbg = "B3B7FF",
                    required    = "EAF3FF",
                    suggested   = "FFFFFF",
                    optional    = "EAECF0",
                    deprecated  = "FFCBCB" }
Config.spaces   = { L = "Lexeme:",
                    P = "Property:",
                    Q = false }
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_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_NoGuidance      = "Missing guidance",
                    err_NoNameResolver  = "needs",
                    err_NoProperty      = "is not a Property" }



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 fetch = function ( access, alike )
    -- Resolve entity name
    -- Precondition:
    --     access  -- string, with entity name or ID
    --     alike   -- true, if Property required
    -- 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 ) ~= "P" then
                    r2 = string.format( "%s %s",
                                        r1,
                                        Text.flip( "err_NoProperty" ) )
                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 -- fetch()



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.qlist  then
        Config.css   = { }
        Config.qlist = { }
        for k, v in pairs( Config.colors ) do
            if k == "tableheadbg" then
                k = "tablehead"
            else
                Config.qlist  = k
            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 types = { "P", "Q", "L" }
        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, #types do
                        s = types[ i ]
                        j = v[ s ]
                        if type( j ) == "number"  and
                           j >= 0  and
                           j == math.floor( j ) then
                            coll = coll  or  { }
                            k    = k:gsub( "\\", "\\\\" )
                                    :gsub( "\"", "\\\"" )
                            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.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   -- true, if Property required
    -- Postcondition:
    local r
    if type( access ) == "string" then
        local s = mw.text.trim( access )
        if s ~= "" then
            if adjust then
                r = 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 assign.state == "required"  or
               assign.state == "suggested" 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.flat( assign, "example", indent ) )
            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, "terminate", 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.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 s = type( e )
    local r, shift, t
    if s == "table" then
        for k, v in pairs( e ) do
            local parts
            if type( k ) == "string"  and
               type( v ) == "string"  and
               k:find( "^%l%l" ) 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( "^%u%u$" ) then
                        k = false
                    end
                else
                    k = false
                end
                if k then
                    t = t  or  { }
                    t[ k ] = v
                end
            end
        end -- for k, v
    elseif s == "string" then
        if e:match( "^[PQL]%d+$" ) then
            r = string.format( "\"%s\"", e )
        else
            t = { }
            t.und = e
        end
    end
    if r or t then
        shift = string.rep( "  ", align )
    end
    if t then
        local sep = ""
        r = "{\n"
        for k, v in pairs( t ) do
            v   = v:gsub( "\\", "\\\\" )
                   :gsub( "\"", "\\\"" )
            r   = string.format( "%s%s%s  \"%s\": \"%s\"",
                                 r, sep, shift, k, v )
            sep = ",\n"
        end -- for k, v
        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()
    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
    r = r .. "\n}"
    return r
end -- JSONexport.full()



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( "dir", "ltr" )
                bidi = not Text.ltr
            else
                bidi = Text.flow( r, slang, show )
            end
            if bidi   and
               ( as == "span"  or  as == "code" ) 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( "dir", "ltr" )
              :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"
    --     about  -- nil or one of: "required", "suggested", "optional"
    -- Postcondition:
    --     Returns  string
    local s = "templatedata-doc-param-" .. area
    local r
    if about 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
        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 )
    -- 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
    -- Postcondition:
    --     Returns  sequence table with mw.html objects
    --         1. table, with TD
    --         2. table, with TR, or not
    local r1, r2
    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 = mw.html.create( "tr" )
                                       :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
    if type( assigned ) == "table" then
        f( Table.field( assigned, "intro", 2 ) )
        if type( assigned.qlist ) == "table" then
            local ul = Table.filled( assigned.qlist )
            if ul then
                f( mw.html.create( "td" )
                     :node( ul ) )
            end
        end
        if type( assigned.qualifiers ) == "table" then
            if across 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 or r2 ) then
        td = mw.html.create( "td" )
               :wikitext( fault( Text.flip( "err_NoGuidance" ) ) )
        f( td )
    end
    return r1, r2
end -- Table.features()



Table.fetch = function ( access, alike )
    -- Describe registration entity
    -- Precondition:
    --     access  -- string, with entity name
    --     alike   -- true, if Property required
    -- Postcondition:
    --     Returns  string, with entity ID and label, or error message
    local sign, r = fetch( access, alike )
    if sign then
        local s = Config.spaces[ sign:sub( 1, 1 ) ]
        local e1, e2, slang
        if s then
            s = s .. sign
        else
            s = sign
        end
        s        = string.format( "[[d:%s|%s]]", s, sign )
        e1       = Text.html( s, "code", false, "en" )
        s, slang = Text.wikibase( sign, alike )
        e2       = Text.html( s, "span", false, slang )
        r = string.format( "%s %s",
                           tostring( e1 ),
                           tostring( e2 ) )
    elseif r then
        r = fault( r )
    else
        r = fault( "Table.fetch()" )
    end
    return r
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
        Text.fashion( tr, assign )
        tr:addClass( string.format( "%s-%s",
                                    WikidataScheme.suite, state ) )
        td:wikitext( Table.fetch( assign.subject ) )
        if type( assign.values ) == "table" then
            local v
            for i = 1, #assign.values do
                how, v = Table.features( assign.values[ i ], 2, how )
                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 )
        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, false )
        elseif i == #all  and  q == true then
            s = "…"
        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.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.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 assign.state == "required"  or assign.state == "suggested" then
        state = assign.state
    end
    f( Table.flat( assign, "intro", 2 ) )
    how, rows = Table.features( assign, 1, r1 )
    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" )
           :wikitext( Table.fetch( assign.subject, true ) )
    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()
        r = JSONexport.full( WikidataScheme.Request, adjust )
        r = failures() .. r
    end
    return r
end -- WikidataScheme.flat()



WikidataScheme.form = function ( about, frame )
    -- Describe registration elements
    -- Precondition:
    --     about  -- table or JSON string or mw.loadData page name
    --     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()
        r = Table.form( WikidataScheme.Request )
    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, 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.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