Modul:TemplatePar: Unterschied zwischen den Versionen

Aus Muri
Zur Navigation springen Zur Suche springen
K (1 Version importiert)
de>Crazy1880
(Update auf Anfrage)
Zeile 1: Zeile 1:
local TemplatePar = { serial = "2018-08-10",
+
local TemplatePar = { serial = "2023-03-20",
                       suite = "TemplatePar",
+
                       suite   = "TemplatePar",
                       item   = 15393417,
+
                       item   = 15393417,
                       extern = { },
+
                       globals = { DateTime    = 20652535,
                      frame  = false }
+
                                  FileMedia    = 24765326,
 +
                                  Multilingual = 47541920,
 +
                                  TemplUtl    = 52364930,
 +
                                  URLutil      = 10859193 } }
 
--[=[
 
--[=[
 
Template parameter utility
 
Template parameter utility
Zeile 18: Zeile 21:
 
* failsafe()
 
* failsafe()
 
]=]
 
]=]
 +
 +
 +
local Local    = { frame = false }
 +
local Failsafe  = TemplatePar
 +
local GlobalMod = Local
  
  
  
 
-- Module globals
 
-- Module globals
local MessagePrefix = "lua-module-TemplatePar-"
+
Local.messagePrefix = "lua-module-TemplatePar-"
local L10nDef = {}
+
Local.L10nDef = {}
L10nDef.en = {
+
Local.L10nDef.en = {
 
     badPattern  = "#invoke:TemplatePar pattern syntax error",
 
     badPattern  = "#invoke:TemplatePar pattern syntax error",
 
     dupOpt      = "#invoke:TemplatePar repeated optional parameter",
 
     dupOpt      = "#invoke:TemplatePar repeated optional parameter",
Zeile 39: Zeile 47:
 
     tooLong    = "Error in template * parameter too long",
 
     tooLong    = "Error in template * parameter too long",
 
     tooShort    = "Error in template * parameter too short",
 
     tooShort    = "Error in template * parameter too short",
 +
    unavailable = "Error in template * parameter name missing",
 
     undefined  = "Error in template * mandatory parameter missing",
 
     undefined  = "Error in template * mandatory parameter missing",
 
     unknown    = "Error in template * unknown parameter name",
 
     unknown    = "Error in template * unknown parameter name",
 
     unknownRule = "#invoke:TemplatePar unknown rule"
 
     unknownRule = "#invoke:TemplatePar unknown rule"
 
}
 
}
local Patterns = {
+
Local.patterns = {
 
     [ "ASCII" ]    = "^[ -~]*$",
 
     [ "ASCII" ]    = "^[ -~]*$",
 
     [ "ASCII+" ]  = "^[ -~]+$",
 
     [ "ASCII+" ]  = "^[ -~]+$",
Zeile 83: Zeile 92:
 
     [ "+" ]        = "%S"
 
     [ "+" ]        = "%S"
 
}
 
}
local patternCJK = false
+
Local.boolean = { ["1"]    = true,
 +
                  ["true"]  = true,
 +
                  y        = true,
 +
                  yes      = true,
 +
                  on        = true,
 +
                  ["0"]    = true,
 +
                  ["false"] = true,
 +
                  ["-"]    = true,
 +
                  n        = true,
 +
                  no        = true,
 +
                  off      = true }
 +
Local.patternCJK = false
  
  
  
local function containsCJK( s )
+
local foreignModule = function ( access, advanced, append, alt, alert )
     -- Is any CJK character present?
+
     -- Fetch global module
 
     -- Precondition:
 
     -- Precondition:
     --    s -- string
+
     --    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:
 
     -- Postcondition:
     --    Return false iff no CJK present
+
     --    Returns whatever, probably table
     -- Uses:
+
     -- 2020-01-01
    --    >< patternCJK
+
     local storage = access
     --    mw.ustring.char()
+
     local finer = function ()
    --    mw.ustring.match()
+
                      if append then
     local r = false
+
                          storage = string.format( "%s/%s",
     if not patternCJK then
+
                                                  storage,
         patternCJK = mw.ustring.char( 91,
+
                                                  append )
                                      13312, 4540959,
+
                      end
                                      131072, 45, 178207,
+
                  end
                                      93 )
+
     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
 
     end
     if mw.ustring.match( s, patternCJK ) then
+
     if not lucky then
         r = true
+
        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 )
 +
         end
 
     end
 
     end
 
     return r
 
     return r
end -- containsCJK()
+
end -- foreignModule()
  
  
  
local function facility( accept, attempt )
+
local function Foreign( access  )
     -- Check string as possible file name or other source page
+
     -- Access standardized library
 
     -- Precondition:
 
     -- Precondition:
     --    accept  -- string; requirement
+
     --    access -- string, with name of base module
    --                        file
+
     -- Postcondition:
    --                        file+
+
     --    Return library table, or not
    --                        file:
+
     -- Uses:
    --                        file:+
+
     local r
    --                        image
+
     if Local[ access ] then
    --                        image+
+
        r = Local[ access ]
    --                        image:
+
     else
    --                        image:+
+
         local bib = foreignModule( access,
    --    attempt -- string; to be tested
+
                                  true,
     -- Postcondition:
+
                                  false,
     --    Return error keyword, or false
+
                                  TemplatePar.globals[ access ],
     -- Uses:
+
                                  false )
     --    >< TemplatePar.extern.FileMedia
+
         if type( bib ) == "table"  and
     --    Module:FileMedia
+
          type( bib[ access ] ) == "function" then
    --    FileMedia.isType()
+
             bib = bib[ access ]()
    local r
+
             if type( bib ) == "table" then
     if attempt and attempt ~= "" then
+
                 r              = bib
         local FileMedia
+
                 Local[ access ] = bib
        if TemplatePar.extern.FileMedia then
 
            FileMedia = TemplatePar.extern.FileMedia
 
         else
 
            local lucky
 
             lucky, FileMedia = pcall( require, "Module:FileMedia" )
 
             if type( FileMedia ) == "table" then
 
                 FileMedia                    = FileMedia.FileMedia()
 
                 TemplatePar.extern.FileMedia = FileMedia
 
 
             end
 
             end
 
         end
 
         end
        if type( FileMedia ) == "table" then
+
    end
            local s, live = accept:match( "^([a-z]+)(:?)%+?$" )
+
    return r
            if live then
+
end -- Foreign()
                if FileMedia.isType( attempt, s ) then
+
 
                    if FileMedia.isFile( attempt ) then
+
 
                        r = false
+
 
                    else
+
local function containsCJK( analyse )
                        r = "notFound"
+
    -- Is any CJK character present?
                    end
+
    -- Precondition:
                else
+
    --    analyse  -- string
                    r = "invalid"
+
    -- Postcondition:
                end
+
    --    Return false iff no CJK present
            elseif FileMedia.isType( attempt, s ) then
+
    -- Uses:
                r = false
+
    --    >< Local.patternCJK
            else
+
    --    mw.ustring.char()
                r = "invalid"
+
    --    mw.ustring.match()
            end
+
    local r = false
        else
+
    if not Local.patternCJK then
            r = "missing"
+
        Local.patternCJK = mw.ustring.char( 91,
        end
+
                                      13312, 45,  40959,
     elseif accept:match( "%+$" ) then
+
                                      131072, 45, 178207,
         r = "empty"
+
                                      93 )
    else
+
    end
        r = false
+
     if mw.ustring.match( analyse, Local.patternCJK ) then
     end
+
         r = true
     return r
+
     end
end -- facility()
+
     return r
 +
end -- containsCJK()
 +
 
  
  
 
+
local function facility( accept, attempt )
local function factory( say )
+
     -- Check string as possible file name or other source page
     -- Retrieve localized message string in content language
 
 
     -- Precondition:
 
     -- Precondition:
     --    say  -- string; message ID
+
     --    accept  -- string; requirement
     -- Postcondition:
+
    --                        file
     --    Return some message string
+
     --                         file+
     -- Uses:
+
    --                        file:
     --    > MessagePrefix
+
    --                        file:+
     --    >  L10nDef
+
    --                        image
     --    mw.language.getContentLanguage()
+
     --                         image+
     --    mw.message.new()
+
     --                        image:
     local c = mw.language.getContentLanguage():getCode()
+
     --                         image:+
     local m = mw.message.new( MessagePrefix .. say )
+
     --    attempt -- string; to be tested
     local r = false
+
    -- Postcondition:
     if m:isBlank() then
+
     --    Return error keyword, or false
         local l10n = L10nDef[ c ]
+
    -- Uses:
         if not l10n then
+
     --    Module:FileMedia
             local lucky
+
     --    Foreign()
            lucky, l10n = pcall( mw.loadData,
+
     --    FileMedia.isFile()
                                string.format( "Module:%s/%s",
+
     --    FileMedia.isType()
                                                TemplatePar.suite, c ) )
+
     local r
            if type( l10n ) == "table" then
+
     if attempt and attempt ~= "" then
                 L10nDef[ c ] = l10n
+
         local FileMedia = Foreign( "FileMedia" )
            end
+
         if FileMedia  and  type( FileMedia.isFile ) == "function"
        end
+
                      and  type( FileMedia.isType ) == "function" then
        if type( l10n ) ~= "table" then
+
             local s, live = accept:match( "^([a-z]+)(:?)%+?$" )
            l10n = L10nDef.en
+
            if live then
        end
+
                if FileMedia.isType( attempt, s ) then
        r = l10n[ say ]
+
                    if FileMedia.isFile( attempt ) then
         if not r then
+
                        r = false
             r = L10nDef.en[ say ]
+
                    else
         end
+
                        r = "notFound"
     else
+
                    end
        m:inLanguage( c )
+
                 else
         r = m:plain()
+
                    r = "invalid"
     end
+
                end
    if not r then
+
            elseif FileMedia.isType( attempt, s ) then
         r = string.format( "(((%s)))", say )
+
                r = false
 +
            else
 +
                r = "invalid"
 +
            end
 +
         else
 +
             r = "missing"
 +
         end
 +
     elseif accept:match( "%+$" ) then
 +
         r = "empty"
 +
     else
 +
         r = false
 
     end
 
     end
 
     return r
 
     return r
end -- factory()
+
end -- facility()
  
  
  
local function failure( spec, suspect, options )
+
local function factory( say )
     -- Submit localized error message
+
     -- Retrieve localized message string in content language
 
     -- Precondition:
 
     -- Precondition:
     --    spec    -- string; message ID
+
     --    say  -- string; message ID
    --    suspect  -- string or nil; additional information
 
    --    options  -- table or nil; optional details
 
    --                options.template
 
 
     -- Postcondition:
 
     -- Postcondition:
     --    Return string
+
     --    Return some message string
 
     -- Uses:
 
     -- Uses:
     --    factory()
+
     --    >  Local.messagePrefix
     local r = factory( spec )
+
    --    >  Local.L10nDef
     if type( options ) == "table" then
+
    --    mw.message.new()
        if type( options.template ) == "string" then
+
    --    mw.language.getContentLanguage()
            if #options.template > 0 then
+
    --    Module:Multilingual
                 r = string.format( "%s (%s)", r, options.template )
+
    --    Foreign()
 +
    --    TemplatePar.framing()
 +
    --    Multilingual.tabData()
 +
     local m = mw.message.new( Local.messagePrefix .. say )
 +
    local r = false
 +
     if m:isBlank() then
 +
        local c = mw.language.getContentLanguage():getCode()
 +
        local l10n = Local.L10nDef[ c ]
 +
        if l10n then
 +
            r = l10n[ say ]
 +
        else
 +
            local MultiL = Foreign( "Multilingual" )
 +
            if MultiL  and  type( MultiL.tabData ) == "function" then
 +
                local lang
 +
                 r, lang = MultiL.tabData( "I18n/Module:TemplatePar",
 +
                                          say,
 +
                                          false,
 +
                                          TemplatePar.framing() )
 
             end
 
             end
 
         end
 
         end
     end
+
        if not r then
     if suspect then
+
            r = Local.L10nDef.en[ say ]
         r = string.format( "%s: %s", r, suspect )
+
        end
 +
    else
 +
        m:inLanguage( c )
 +
        r = m:plain()
 +
     end
 +
     if not r then
 +
         r = string.format( "(((%s)))", say )
 
     end
 
     end
 
     return r
 
     return r
end -- failure()
+
end -- factory()
  
  
  
local function fair( story, scan )
+
local function faculty( accept, attempt )
     -- Test for match (possibly user-defined with syntax error)
+
     -- Check string as possible boolean
 
     -- Precondition:
 
     -- Precondition:
     --    story  -- string; parameter value
+
     --    accept  -- string; requirement
     --    scan  -- string; pattern
+
    --                        boolean
 +
    --                        boolean+
 +
     --    attempt  -- string; to be tested
 
     -- Postcondition:
 
     -- Postcondition:
     --    Return nil, if not matching, else non-nil
+
     --    Return error keyword, or false
 
     -- Uses:
 
     -- Uses:
     --    mw.ustring.match()
+
     --    Module:TemplUtl
     return mw.ustring.match( story, scan )
+
    --    Foreign()
end -- fair()
+
    --    TemplUtl.faculty()
 
+
    local r
 +
    r = mw.text.trim( attempt ):lower()
 +
    if r == "" then
 +
        if accept == "boolean+" then
 +
            r = "empty"
 +
        else
 +
            r = false
 +
        end
 +
    elseif Local.boolean[ r ]  or  r:match( "^[01%-]+$" ) then
 +
        r = false
 +
     else
 +
        local TemplUtl = Foreign( "TemplUtl" )
 +
        if TemplUtl  and type( TemplUtl.faculty ) == "function" then
 +
            r = TemplUtl.faculty( r, "-" )
 +
            if r == "-" then
 +
                r = "invalid"
 +
            else
 +
                r = false
 +
            end
 +
        else
 +
            r = "invalid"
 +
        end
 +
    end
 +
    return r
 +
end -- faculty()
 +
 
  
  
local function familiar( accept, attempt )
+
local function failure( spec, suspect, options )
     -- Check string as possible language name or list
+
     -- Submit localized error message
 
     -- Precondition:
 
     -- Precondition:
     --    accept  -- string; requirement
+
     --    spec    -- string; message ID
     --                         lang
+
     --    suspect  -- string or nil; additional information
     --                         langs
+
     --    options  -- table or nil; optional details
     --                         langW
+
     --                 options.template
     --                         langsW
+
     -- Postcondition:
    --                        lang+
+
     --    Return string
    --                        langs+
 
     --                         langW+
 
    --                        langsW+
 
    --    attempt  -- string; to be tested
 
     -- Postcondition:
 
     --    Return error keyword, or false
 
 
     -- Uses:
 
     -- Uses:
     --    >< TemplatePar.extern.Multilingual
+
     --    factory()
    --    Module:Multilingual
+
     local r = factory( spec )
    --    Multilingual.isType()
+
     if type( options ) == "table" then
     local r
+
         if type( options.template ) == "string" then
     if attempt and attempt ~= "" then
+
             if #options.template > 0 then
        local Multilingual
+
                 r = string.format( "%s (%s)", r, options.template )
         if TemplatePar.extern.Multilingual then
 
            Multilingual = TemplatePar.extern.Multilingual
 
        else
 
            local lucky
 
            lucky, Multilingual = pcall( require, "Module:Multilingual" )
 
             if type( Multilingual ) == "table" then
 
                 Multilingual = Multilingual.Multilingual()
 
                TemplatePar.extern.Multilingual = Multilingual
 
 
             end
 
             end
 
         end
 
         end
        if type( Multilingual ) == "table" then
+
    end
            local lazy = accept:find( "W", 1, true )
+
    if suspect then
            if accept:find( "s", 1, true ) then
+
        r = string.format( "%s: %s", r, suspect )
                local group = mw.text.split( attempt, "%s+" )
+
     end
                r = false
+
     return r
                for i = 1, #group do
+
end -- failure()
                    if not Multilingual.isLang( group[ i ], lazy ) then
 
                        r = "invalid"
 
                        break -- for i
 
                    end
 
                end -- for i
 
            elseif Multilingual.isLang( attempt, lazy ) then
 
                r = false
 
            else
 
                r = "invalid"
 
            end
 
        else
 
            r = "missing"
 
        end
 
    elseif accept:find( "+", 1, true ) then
 
        r = "empty"
 
    else
 
        r = false
 
     end
 
     return r
 
end -- familiar()
 
  
  
  
local function far( accept, attempt )
+
local function fair( story, scan )
     -- Check string as possible URL
+
     -- Test for match (possibly user-defined with syntax error)
 
     -- Precondition:
 
     -- Precondition:
     --    accept  -- string; requirement
+
    --    story  -- string; parameter value
     --                        url
+
    --    scan  -- string; pattern
     --                        url+
+
    -- Postcondition:
 +
    --    Return nil, if not matching, else non-nil
 +
    -- Uses:
 +
    --    mw.ustring.match()
 +
    return  mw.ustring.match( story, scan )
 +
end -- fair()
 +
 
 +
 
 +
 
 +
local function familiar( accept, attempt )
 +
    -- Check string as possible language name or list
 +
    -- Precondition:
 +
     --    accept  -- string; requirement
 +
     --                        lang
 +
    --                        langs
 +
    --                        langW
 +
    --                        langsW
 +
    --                        lang+
 +
    --                        langs+
 +
    --                        langW+
 +
     --                        langsW+
 
     --    attempt  -- string; to be tested
 
     --    attempt  -- string; to be tested
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Return error keyword, or false
 
     --    Return error keyword, or false
 
     -- Uses:
 
     -- Uses:
    --    >< TemplatePar.extern.Multilingual
 
 
     --    Module:Multilingual
 
     --    Module:Multilingual
     --    Multilingual.isType()
+
    --    Foreign()
 +
     --    Multilingual.isLang()
 
     local r
 
     local r
 
     if attempt and attempt ~= "" then
 
     if attempt and attempt ~= "" then
         local URLutil
+
         local MultiL = Foreign( "Multilingual" )
         if TemplatePar.extern.URLutil then
+
         if MultiL  and  type( MultiL.isLang ) == "function" then
            URLutil = TemplatePar.extern.URLutil
+
             local lazy = accept:find( "W", 1, true )
        else
+
             if accept:find( "s", 1, true ) then
             local lucky
+
                 local group = mw.text.split( attempt, "%s+" )
            lucky, URLutil = pcall( require, "Module:URLutil" )
+
                 r = false
             if type( URLutil ) == "table" then
+
                for i = 1, #group do
                 URLutil                    = URLutil.URLutil()
+
                    if not MultiL.isLang( group[ i ], lazy ) then
                 TemplatePar.extern.URLutil = URLutil
+
                        r = "invalid"
            end
+
                        break -- for i
        end
+
                    end
        if type( URLutil ) == "table" then
+
                end -- for i
             if URLutil.isWebURL( attempt ) then
+
             elseif MultiL.isLang( attempt, lazy ) then
 
                 r = false
 
                 r = false
 
             else
 
             else
Zeile 361: Zeile 445:
 
     end
 
     end
 
     return r
 
     return r
end -- far()
+
end -- familiar()
  
  
  
local function fault( store, key )
+
local function far( accept, attempt )
     -- Add key to collection string and insert separator
+
     -- Check string as possible URL
 
     -- Precondition:
 
     -- Precondition:
     --    store  -- string or nil or false; collection string
+
     --    accept  -- string; requirement
     --    key    -- string or number; to be appended
+
    --                        url
 +
    --                        url+
 +
     --    attempt  -- string; to be tested
 
     -- Postcondition:
 
     -- Postcondition:
     --    Return string; extended
+
     --    Return error keyword, or false
 +
    -- Uses:
 +
    --    Module:URLutil
 +
    --    Foreign()
 +
    --    URLutil.isWebURL()
 
     local r
 
     local r
     local s
+
     if attempt and attempt ~= "" then
    if type( key ) == "number" then
+
        local URLutil = Foreign( "URLutil" )
         s = tostring( key )
+
        if URLutil  and  type( URLutil.isWebURL ) == "function" then
 +
            if URLutil.isWebURL( attempt ) then
 +
                r = false
 +
            else
 +
                r = "invalid"
 +
            end
 +
         else
 +
            r = "missing"
 +
        end
 +
    elseif accept:find( "+", 1, true ) then
 +
        r = "empty"
 
     else
 
     else
        s = key
+
         r = false
    end
 
    if store then
 
        r = string.format( "%s; %s", store, s )
 
    else
 
         r = s
 
 
     end
 
     end
 
     return r
 
     return r
end -- fault()
+
end -- far()
  
  
  
local function feasible( analyze, options, abbr )
+
local function fast( accept, attempt )
     -- Check content of a value
+
     -- Check string as possible date or time
 
     -- Precondition:
 
     -- Precondition:
     --    analyze  -- string to be analyzed
+
     --    accept  -- string; requirement
     --    options  -- table or nil; optional details
+
    --                        datetime
     --                 options.pattern
+
     --                         datetime+
     --                 options.key
+
    --                        datetime/y
     --                 options.say
+
     --                         datetime/y+
     --    abbr     -- true: abbreviated error message
+
     --                         datetime/ym
 +
     --                         datetime/ym+
 +
     --                         datetime/ymd
 +
     --                         datetime/ymd+
 +
     --     attempt  -- string; to be tested
 
     -- Postcondition:
 
     -- Postcondition:
     --    Return string with error message as configured;
+
     --    Return error keyword, or false
    --            false if valid or no answer permitted
 
 
     -- Uses:
 
     -- Uses:
     --    >  Patterns
+
     --    Module:DateTime
     --    failure()
+
     --    Foreign()
     --    mw.text.trim()
+
     --    DateTime.DateTime()
    --    facility()
+
     local r
    --    fair()
+
     r = mw.text.trim( attempt )
    --    containsCJK()
+
     if r == "" then
     local r   = false
+
         if accept:find( "+", 1, true ) then
     local s    = false
+
             r = "empty"
    local show = nil
 
    local scan = false
 
     if type( options.pattern ) == "string" then
 
         if options.key then
 
             r = failure( "dupRule", false, options )
 
 
         else
 
         else
             scan = options.pattern
+
             r = false
 
         end
 
         end
 
     else
 
     else
         if type( options.key ) == "string" then
+
        local DateTime = Foreign( "DateTime" )
             s = mw.text.trim( options.key )
+
         if type( DateTime ) == "table" then
 +
             local d = DateTime( attempt )
 +
            if type( d ) == "table" then
 +
                if accept:find( "/", 1, true ) then
 +
                    r = "invalid"
 +
                    if accept:sub( 1, 10 ) == "datetime/y" then
 +
                        if d.year then
 +
                            r = false
 +
                            if accept:sub( 1, 11 ) == "datetime/ym" then
 +
                                if d.month then
 +
                                    if accept:sub( 1, 12 )
 +
                                                  == "datetime/ymd" then
 +
                                        if not d.dom then
 +
                                            r = "invalid"
 +
                                        end
 +
                                    end
 +
                                else
 +
                                    r = "invalid"
 +
                                end
 +
                            end
 +
                        end
 +
                    end
 +
                else
 +
                    r = false
 +
                end
 +
            else
 +
                r = "invalid"
 +
            end
 
         else
 
         else
             s = "+"
+
             r = "invalid"
 
         end
 
         end
        if s ~= "*" then
+
    end
            scan = Patterns[ s ]
+
    return r
        end
+
end -- fast()
        if type( scan ) == "string" then
+
 
            if s == "n" or s == "0,0" or s == "0.0" then
+
 
                if not analyze:match( "[0-9]" ) and
+
 
                  not analyze:match( "^%s*$" ) then
+
local function fault( store, key )
                    scan = false
+
    -- Add key to collection string and insert separator
                    if options.say then
+
    -- Precondition:
                        show = string.format( "'%s'", options.say )
+
    --    store  -- string or nil or false; collection string
                    end
+
    --    key    -- string or number; to be appended
                    if abbr then
+
    -- Postcondition:
                        r = show
+
    --    Return string; extended
                    else
+
    local r
                        r = failure( "invalid", show, options )
+
    local s
                    end
+
    if type( key ) == "number" then
                end
+
        s = tostring( key )
            end
+
    else
        elseif s ~= "*" then
+
        s = key
            local op, n, plus = s:match( "([<!=>]=?)([-0-9][%S]*)(+?)" )
+
    end
            if op then
+
    if store then
                n = tonumber( n )
+
        r = string.format( "%s; %s", store, s )
                if n then
+
    else
                    local i = tonumber( analyze )
+
        r = s
                    if i then
+
    end
                        if op == "<" then
+
    return r
                            i = ( i < n )
+
end -- fault()
                        elseif op == "<=" then
+
 
                            i = ( i <= n )
+
 
                        elseif op == ">" then
+
 
                            i = ( i > n )
+
local function feasible( analyze, options, abbr )
                        elseif op == ">=" then
+
    -- Check content of a value
                            i = ( i >= n )
+
    -- Precondition:
                        elseif op == "==" then
+
    --    analyze  -- string to be analyzed
                            i = ( i == n )
+
    --    options  -- table or nil; optional details
                        elseif op == "!=" then
+
    --                options.pattern
                            i = ( i ~= n )
+
    --                options.key
                        else
+
    --                options.say
                            n = false
+
    --    abbr    -- true: abbreviated error message
                        end
+
    -- Postcondition:
                    end
+
    --    Return string with error message as configured;
                    if not i then
+
    --            false if valid or no answer permitted
                        r = "invalid"
+
    -- Uses:
                    end
+
    --    >  Local.patterns
                elseif plus then
+
    --    failure()
                    r = "undefined"
+
    --    mw.text.trim()
                end
+
    --    faculty()
            elseif s:match( "^image%+?:?$" ) or
+
    --    fast()
                  s:match( "^file%+?:?$" ) then
+
    --    facility()
                r = facility( s, analyze )
+
    --    familiar()
                n = true
+
    --    far()
            elseif s:match( "langs?W?%+?" ) then
+
    --     fair()
                r = familiar( s, analyze )
+
    --     containsCJK()
                n = true
+
    local r     = false
            elseif s:match( "url%+?" ) then
+
    local s    = false
                r = far( s, analyze )
+
    local show  = nil
                n = true
+
    local scan  = false
            end
+
    local stuff = mw.text.trim( analyze )
-- datetime+
+
    if type( options.pattern ) == "string" then
-- iso8631+
+
        if options.key then
-- line+
+
            r = failure( "dupRule", false, options )
            if not n and not r then
+
        else
                r = "unknownRule"
+
             scan = options.pattern
            end
 
            if r then
 
                if options.say then
 
                    show = string.format( "'%s' %s", options.say, s )
 
                else
 
                    show = s
 
                end
 
                if abbr then
 
                    r = show
 
                else
 
                    r = failure( r, show, options )
 
                end
 
             end
 
 
         end
 
         end
     end
+
     else
    if scan then
+
        if type( options.key ) == "string" then
        local legal, got = pcall( fair, analyze, scan )
+
            s = mw.text.trim( options.key )
         if legal then
+
        else
             if not got then
+
            s = "+"
                if s == "aa" then
+
        end
                    got = containsCJK( analyze )
+
         if s ~= "*" then
                end
+
             scan = Local.patterns[ s ]
                if not got then
+
        end
 +
        if type( scan ) == "string" then
 +
            if s == "n" or s == "0,0" or s == "0.0" then
 +
                if not stuff:match( "[0-9]" ) and
 +
                  not stuff:match( "^%s*$" ) then
 +
                    scan = false
 
                     if options.say then
 
                     if options.say then
                         show = string.format( "'%s'", options.say )
+
                         show = string.format( "&quot;%s&quot;", options.say )
 
                     end
 
                     end
 
                     if abbr then
 
                     if abbr then
Zeile 520: Zeile 632:
 
                 end
 
                 end
 
             end
 
             end
         else
+
         elseif s ~= "*" then
             r = failure( "badPattern",
+
             local op, n, plus = s:match( "([<!=>]=?)([-0-9][%S]*)(+?)" )
                        string.format( "%s *** %s", scan, got ),
+
            if op then
                        options )
+
                n = tonumber( n )
        end
+
                if n then
    end
+
                    local i = tonumber( stuff )
    return r
+
                    if i then
end -- feasible()
+
                        if op == "<" then
 
+
                            i = ( i < n )
 
+
                        elseif op == "<=" then
 
+
                            i = ( i <= n )
local function fed( haystack, needle )
+
                        elseif op == ">" then
    -- Find needle in haystack map
+
                            i = ( i > n )
    -- Precondition:
+
                        elseif op == ">=" then
    --    haystack  -- table; map of key values
+
                            i = ( i >= n )
    --    needle    -- any; identifier
+
                        elseif op == "==" then
    -- Postcondition:
+
                            i = ( i == n )
    --    Return true iff found
+
                        elseif op == "!=" then
    local k, v, r
+
                            i = ( i ~= n )
    for k, v in pairs( haystack ) do
+
                        else
        if k == needle then
+
                            n = false
            r = true
+
                        end
        end
+
                    end
    end -- for k, v
+
                    if not i then
    return r or false
+
                        r = "invalid"
end -- fed()
+
                    end
 
+
                elseif plus then
 
+
                    r = "undefined"
 
+
                end
local function fetch( light, options )
+
            elseif s:match( "^boolean%+?$" ) then
    -- Return regular table with all parameters
+
                r = faculty( s, stuff )
    -- Precondition:
+
                n = true
    --    light    -- true: template transclusion;  false: #invoke
+
            elseif s:match( "^datetime/?y?m?d?%+?$" ) then
    --    options  -- table; optional details
+
                r = fast( s, stuff )
    --                 options.low
+
                n = true
    -- Postcondition:
+
            elseif s:match( "^image%+?:?$" )  or
    --    Return table; whitespace-only values as false
+
                  s:match( "^file%+?:?$" ) then
    -- Uses:
+
                r = facility( s, stuff )
    --     TemplatePar.downcase()
+
                 n = true
    --     mw.getCurrentFrame()
+
            elseif s:match( "langs?W?%+?" ) then
    --     frame:getParent()
+
                r = familiar( s, stuff )
    local g, k, v
+
                n = true
    local r = { }
+
            elseif s:match( "url%+?" ) then
    if options.low then
+
                r = far( s, stuff )
        g = TemplatePar.downcase( options )
+
                n = true
    else
+
            end
        TemplatePar.frame = mw.getCurrentFrame()
+
-- datetime+
        g                 = TemplatePar.frame
+
-- iso8631+
        if light then
+
-- line+
            g = g:getParent()
+
            if not n and not r then
 +
                r = "unknownRule"
 +
            end
 +
            if r then
 +
                if options.say then
 +
                    show = string.format( "&quot;%s&quot; %s", options.say, s )
 +
                else
 +
                    show = s
 +
                 end
 +
                if abbr then
 +
                    r = show
 +
                else
 +
                    r = failure( r, show, options )
 +
                end
 +
            end
 
         end
 
         end
        g = g.args
 
 
     end
 
     end
     if type( g ) == "table"  then
+
     if scan then
         r = { }
+
         local legal, got = pcall( fair, stuff, scan )
         for k, v in pairs( g ) do
+
         if legal then
             if type( v ) == "string" then
+
             if not got then
                 if v:match( "^%s*$" ) then
+
                if s == "aa" then
                     v = false
+
                    got = containsCJK( stuff )
 +
                 end
 +
                if not got then
 +
                    if options.say then
 +
                        show = string.format( "&quot;%s&quot;", options.say )
 +
                    end
 +
                    if abbr then
 +
                        r = show
 +
                     else
 +
                        r = failure( "invalid", show, options )
 +
                    end
 
                 end
 
                 end
            else
 
                v = false
 
 
             end
 
             end
             if type( k ) == "number" then
+
        else
                k = tostring( k )
+
             r = failure( "badPattern",
            end
+
                        string.format( "%s *** %s", scan, got ),
            r[ k ] = v
+
                        options )
         end -- for k, v
+
         end
    else
 
        r = g
 
 
     end
 
     end
 
     return r
 
     return r
end -- fetch()
+
end -- feasible()
  
  
  
local function figure( append, options )
+
local function fed( haystack, needle )
     -- Extend options by rule from #invoke strings
+
     -- Find needle in haystack map
 
     -- Precondition:
 
     -- Precondition:
     --    append  -- string or nil; requested rule
+
     --    haystack -- table; map of key values
    --    options -- table; details
+
     --    needle    -- any; identifier
     --                 ++ .key
 
     --                 ++ .pattern
 
 
     -- Postcondition:
 
     -- Postcondition:
     --    Return sequence table
+
     --    Return true iff found
     local r = options
+
     local k, v, r
     if type( append ) == "string" then
+
     for k, v in pairs( haystack ) do
        local story = mw.text.trim( append )
+
         if k == needle then
        local sub  = story:match( "^/(.*%S)/$" )
+
             r = true
         if type( sub ) == "string" then
 
             sub            = sub:gsub( "%%!", "|" )
 
                                :gsub( "%%%(%(", "{{" )
 
                                :gsub( "%%%)%)", "}}" )
 
                                :gsub( "\\n", string.char( 10 ) )
 
            options.pattern = sub
 
            options.key    = nil
 
        else
 
            options.key    = story
 
            options.pattern = nil
 
 
         end
 
         end
     end
+
     end -- for k, v
     return r
+
     return r or false
end -- figure()
+
end -- fed()
  
  
  
local function fill( specified )
+
local function fetch( light, options )
     -- Split requirement string separated by '='
+
     -- Return regular table with all parameters
 
     -- Precondition:
 
     -- Precondition:
     --    specified -- string or nil; requested parameter set
+
     --    light    -- true: template transclusion;  false: #invoke
 +
    --    options -- table; optional details
 +
    --                options.low
 
     -- Postcondition:
 
     -- Postcondition:
     --    Return sequence table
+
     --    Return table; whitespace-only values as false
 
     -- Uses:
 
     -- Uses:
     --    mw.text.split()
+
     --    TemplatePar.downcase()
     local r
+
    --    TemplatePar.framing()
     if specified then
+
     --    frame:getParent()
        local i, s
+
     local g, k, v
        r = mw.text.split( specified, "%s*=%s*" )
+
    local r = { }
        for i = #r, 1, -1 do
+
    if options.low then
            s = r[ i ]
+
        g = TemplatePar.downcase( options )
            if #s == 0 then
 
                table.remove( r, i )
 
            end
 
        end -- for i, -1
 
 
     else
 
     else
 +
        g = TemplatePar.framing()
 +
        if light then
 +
            g = g:getParent()
 +
        end
 +
        g = g.args
 +
    end
 +
    if type( g ) == "table"  then
 
         r = { }
 
         r = { }
     end
+
        for k, v in pairs( g ) do
     return r
+
            if type( v ) == "string" then
end -- fill()
+
                if v:match( "^%s*$" ) then
 
+
                    v = false
 
+
                end
 
+
            else
local function finalize( submit, options )
+
                v = false
     -- Finalize message
+
            end
 +
            if type( k ) == "number" then
 +
                k = tostring( k )
 +
            end
 +
            r[ k ] = v
 +
        end -- for k, v
 +
    else
 +
        r = g
 +
     end
 +
     return r
 +
end -- fetch()
 +
 
 +
 
 +
 
 +
local function figure( append, options )
 +
     -- Extend options by rule from #invoke strings
 
     -- Precondition:
 
     -- Precondition:
     --    submit   -- string or false or nil; non-empty error message
+
     --    append   -- string or nil; requested rule
     --    options  -- table or nil; optional details
+
     --    options  -- table; details
     --                 options.format
+
     --                 ++ .key
     --                 options.preview
+
     --                 ++ .pattern
    --                options.cat
 
    --                options.template
 
 
     -- Postcondition:
 
     -- Postcondition:
     --    Return string or false
+
     --    Return sequence table
    -- Uses:
+
     local r = options
    --    factory()
+
     if type( append ) == "string" then
     local r = false
+
        local story = mw.text.trim( append )
     if submit then
+
        local sub  = story:match( "^/(.*%S)/$" )
        local lazy  = false
+
        if type( sub ) == "string" then
        local learn = false
+
            sub            = sub:gsub( "%%!", "|" )
        local show  = false
+
                                :gsub( "%%%(%(", "{{" )
        local opt, s
+
                                :gsub( "%%%)%)", "}}" )
        if type( options ) == "table" then
+
                                :gsub( "\\n", string.char( 10 ) )
            opt  = options
+
            options.pattern = sub
            show = opt.format
+
            options.key    = nil
            lazy = ( show == ""  or  show == "0"  or  show == "-" )
 
            s    = opt.preview
 
            if type( s ) == "string" and
 
              s ~= "" and  s ~= "0" and  s ~= "-" then
 
                local sniffer = "{{REVISIONID}}"
 
                if lazy then
 
                    show = ""
 
                    lazy = false
 
                end
 
                if not TemplatePar.frame then
 
                    TemplatePar.frame = mw.getCurrentFrame()
 
                end
 
                if TemplatePar.frame:preprocess( sniffer ) == "" then
 
                    if s == "1" then
 
                        show = "*"
 
                    else
 
                        show = s
 
                    end
 
                    learn = true
 
                end
 
            end
 
 
         else
 
         else
             opt = { }
+
             options.key    = story
 +
            options.pattern = nil
 
         end
 
         end
        if lazy then
+
    end
            if not opt.cat then
+
    return r
                r = string.format( "%s %s",
+
end -- figure()
                                  submit, factory( "noMSGnoCAT" ) )
+
 
 +
 
 +
 
 +
local function fill( specified )
 +
    -- Split requirement string separated by '='
 +
    -- Precondition:
 +
    --    specified  -- string or nil; requested parameter set
 +
    -- Postcondition:
 +
    --    Return sequence table
 +
    -- Uses:
 +
    --    mw.text.split()
 +
    local r
 +
    if specified then
 +
        local i, s
 +
        r = mw.text.split( specified, "%s*=%s*" )
 +
        for i = #r, 1, -1 do
 +
            s = r[ i ]
 +
            if #s == 0 then
 +
                table.remove( r, i )
 
             end
 
             end
         else
+
         end -- for i, -1
            r = submit
+
    else
        end
+
        r = { }
        if r and  not lazy then
+
    end
            local i
+
    return r
            if not show or show == "*" then
+
end -- fill()
                 local e = mw.html.create( "span" )
+
 
                                :attr( "class", "error" )
+
 
                                :wikitext( "@@@" )
+
 
                if learn then
+
local function finalize( submit, options )
                    local max = 1000000000
+
    -- Finalize message
                    local id  = math.floor( os.clock() * max )
+
    -- Precondition:
                    local sign = string.format( "error_%d", id )
+
    --    submit  -- string or false or nil; non-empty error message
                    local btn = mw.html.create( "span" )
+
    --    options -- table or nil; optional details
                    local top = mw.html.create( "div" )
+
    --                options.format
                    e:attr( "id", sign )
+
    --                options.preview
                    btn:css( { ["background"]      = "#FFFF00",
+
    --                 options.cat
                              ["border"]          = "#FF0000 3px solid",
+
    --                options.template
                              ["font-weight"]    = "bold",
+
    -- Postcondition:
                              ["padding"]        = "2px",
+
    --    Return string or false
                              ["text-decoration"] = "none" } )
+
    -- Uses:
                      :wikitext( "&gt;&gt;&gt;" )
+
    --    TemplatePar.framing()
                     sign = string.format( "[[#%s|%s]]",
+
    --    factory()
                                          sign,  tostring( btn ) )
+
    local r = false
                    top:wikitext( sign, "&#160;", submit )
+
    if submit then
                     mw.addWarning( tostring( top ) )
+
        local lazy = false
                end
+
        local learn = false
                 show = tostring( e )
+
        local show  = false
 +
        local opt, s
 +
        if type( options ) == "table" then
 +
            opt = options
 +
            show = opt.format
 +
            lazy = ( show == ""  or  show == "0" or  show == "-" )
 +
            s    = opt.preview
 +
            if type( s ) == "string" and
 +
              s ~= "" and  s ~= "0" and  s ~= "-" then
 +
                local sniffer = "{{REVISIONID}}"
 +
                if lazy then
 +
                    show = ""
 +
                    lazy = false
 +
                end
 +
                if TemplatePar.framing():preprocess( sniffer ) == "" then
 +
                     if s == "1" then
 +
                        show = "*"
 +
                     else
 +
                        show = s
 +
                    end
 +
                    learn = true
 +
                 end
 
             end
 
             end
             i = show:find( "@@@", 1, true )
+
        else
            if i then
+
             opt = { }
                -- No gsub() since r might contain "%3" (e.g. URL)
+
        end
                 r = string.format( "%s%s%s",
+
        if lazy then
                                   show:sub( 1i - 1 ),
+
            if not opt.cat then
                                  r,
+
                 r = string.format( "%s %s",
                                  show:sub( i + 3 ) )
+
                                   submitfactory( "noMSGnoCAT" ) )
            else
 
                r = show
 
 
             end
 
             end
 +
        else
 +
            r = submit
 
         end
 
         end
         if learn and r then
+
         if and not lazy then
             -- r = fatal( r )
+
             local i
        end
+
            if not show  or  show == "*" then
        s = opt.cat
+
                 local e = mw.html.create( "span" )
        if type( s ) == "string" then
+
                                :attr( "class", "error" )
            local link
+
                                :wikitext( "@@@" )
            if opt.errNS then
+
                 if learn then
                 local ns = mw.title.getCurrentTitle().namespace
+
                    local max  = 1000000000
                local st = type( opt.errNS )
+
                    local id  = math.floor( os.clock() * max )
                 if st == "string" then
+
                     local sign = string.format( "error_%d", id )
                     local space  = string.format( ".*%%s%d%%s.*", ns )
+
                     local btn  = mw.html.create( "span" )
                     local spaces = string.format( " %s ", opt.errNS )
+
                    local top  = mw.html.create( "div" )
                     if spaces:match( space ) then
+
                     e:attr( "id", sign )
                        link = true
+
                    btn:css( { ["background"]      = "#FFFF00",
                    end
+
                              ["border"]          = "#FF0000 3px solid",
                elseif st == "table" then
+
                              ["font-weight"]    = "bold",
                    for i = 1, #opt.errNS do
+
                              ["padding"]        = "2px",
                        if opt.errNS[ i ] == ns then
+
                              ["text-decoration"] = "none" } )
                            link = true
+
                      :wikitext( "&gt;&gt;&gt;" )
                            break    -- for i
+
                    sign = string.format( "[[#%s|%s]]",
                        end
+
                                          sign,  tostring( btn ) )
                     end -- for i
+
                    top:wikitext( sign, "&#160;", submit )
 +
                     mw.addWarning( tostring( top ) )
 
                 end
 
                 end
 +
                show = tostring( e )
 +
            end
 +
            i = show:find( "@@@", 1, true )
 +
            if i then
 +
                -- No gsub() since r might contain "%3" (e.g. URL)
 +
                r = string.format( "%s%s%s",
 +
                                  show:sub( 1,  i - 1 ),
 +
                                  r,
 +
                                  show:sub( i + 3 ) )
 
             else
 
             else
                 link = true
+
                 r = show
 
             end
 
             end
            if link then
+
        end
                local cats, i
+
        if learn and r then
                if not r then
+
            -- r = fatal( r )
                  r = ""
+
        end
                end
+
        s = opt.cat
                if s:find( "@@@" ) then
+
        if type( s ) == "string" then
                    if type( opt.template ) == "string" then
+
            local link
                        s = s:gsub( "@@@", opt.template )
+
            if opt.errNS then
 +
                local ns = mw.title.getCurrentTitle().namespace
 +
                local st = type( opt.errNS )
 +
                if st == "string" then
 +
                    local space  = string.format( ".*%%s%d%%s.*", ns )
 +
                    local spaces = string.format( " %s ", opt.errNS )
 +
                    if spaces:match( space ) then
 +
                        link = true
 
                     end
 
                     end
 +
                elseif st == "table" then
 +
                    for i = 1, #opt.errNS do
 +
                        if opt.errNS[ i ] == ns then
 +
                            link = true
 +
                            break    -- for i
 +
                        end
 +
                    end -- for i
 
                 end
 
                 end
                 cats = mw.text.split( s, "%s*#%s*" )
+
            else
                 for i = 1, #cats do
+
                link = true
                     s = mw.text.trim( cats[ i ] )
+
            end
                     if #s > 0 then
+
            if link then
 +
                local cats, i
 +
                if not r then
 +
                  r = ""
 +
                end
 +
                if s:find( "@@@" ) then
 +
                    if type( opt.template ) == "string" then
 +
                        s = s:gsub( "@@@", opt.template )
 +
                    end
 +
                end
 +
                 cats = mw.text.split( s, "%s*#%s*" )
 +
                 for i = 1, #cats do
 +
                     s = mw.text.trim( cats[ i ] )
 +
                     if #s > 0 then
 
                         r = string.format( "%s[[Category:%s]]", r, s )
 
                         r = string.format( "%s[[Category:%s]]", r, s )
 
                     end
 
                     end
Zeile 826: Zeile 1'019:
 
     --    failure()
 
     --    failure()
 
     --    fed()
 
     --    fed()
    local k, v
 
 
     local r = false
 
     local r = false
 +
    local lack
 
     for k, v in pairs( got ) do
 
     for k, v in pairs( got ) do
         if not finder( valid, k ) then
+
         if k == "" then
 +
            lack = true
 +
            break    -- for k, v
 +
        elseif not finder( valid, k ) then
 
             r = fault( r, k )
 
             r = fault( r, k )
 
         end
 
         end
 
     end -- for k, v
 
     end -- for k, v
     if r then
+
     if lack then
 +
        r = failure( "unavailable", false, options )
 +
    elseif r then
 
         r = failure( "unknown",
 
         r = failure( "unknown",
                     string.format( "'%s'", r ),
+
                     string.format( "&quot;%s&quot;", r ),
 
                     options )
 
                     options )
 
     else -- all names valid
 
     else -- all names valid
Zeile 962: Zeile 1'160:
 
     --            false if valid
 
     --            false if valid
 
     -- Uses:
 
     -- Uses:
     --     < TemplatePar.frame
+
     --     TemplatePar.framing()
 
     --    fold()
 
     --    fold()
 
     --    fetch()
 
     --    fetch()
Zeile 969: Zeile 1'167:
 
     local duty, r
 
     local duty, r
 
     if frame then
 
     if frame then
         TemplatePar.frame = frame
+
         TemplatePar.framing( frame )
    else
 
        TemplatePar.frame = mw.getCurrentFrame()
 
 
     end
 
     end
 
     if type( options ) == "table" then
 
     if type( options ) == "table" then
Zeile 1'030: Zeile 1'226:
 
                 show = " <" .. options.min
 
                 show = " <" .. options.min
 
                 if options.say then
 
                 if options.say then
                     show = string.format( "%s '%s'", show, options.say )
+
                     show = string.format( "%s &quot;%s&quot;", show, options.say )
 
                 end
 
                 end
 
                 r = failure( "tooShort", show, options )
 
                 r = failure( "tooShort", show, options )
Zeile 1'043: Zeile 1'239:
 
                 show = " >" .. options.max
 
                 show = " >" .. options.max
 
                 if options.say then
 
                 if options.say then
                     show = string.format( "%s '%s'", show, options.say )
+
                     show = string.format( "%s &quot;%s&quot;", show, options.say )
 
                 end
 
                 end
 
                 r = failure( "tooLong", show, options )
 
                 r = failure( "tooLong", show, options )
Zeile 1'180: Zeile 1'376:
 
         if ( type( append ) == "string" ) then
 
         if ( type( append ) == "string" ) then
 
             if ( append ~= "" ) then
 
             if ( append ~= "" ) then
                 r = string.format( "%s<br />%s", append, r )
+
                 r = string.format( "%s<br /> %s", append, r )
 
             end
 
             end
 
         else
 
         else
Zeile 1'263: Zeile 1'459:
  
  
TemplatePar.failsafe = function ( assert )
+
TemplatePar.valid = function ( access, options )
     -- Retrieve versioning and check for compliance
+
     -- Check validity of one particular template parameter
 
     -- Precondition:
 
     -- Precondition:
     --    assert  -- string, with required version or "wikidata",
+
     --    access  -- id of parameter in template transclusion
     --               or false
+
    --                 string or number
 +
     --     options  -- table or nil; optional details
 
     -- Postcondition:
 
     -- Postcondition:
    --    Returns  string with appropriate version, or false
+
     --    Return string with error message as configured;
    local r
+
     --            false if valid or no answer permitted
    if since == "wikidata" then
 
        local item = TemplatePar.item
 
        since = false
 
        if type( item ) == "number"  and  item > 0 then
 
            local ent = mw.wikibase.getEntity( string.format( "Q%d",
 
                                                              item ) )
 
            if type( ent ) == "table" then
 
                local vsn = ent:formatPropertyValues( "P348" )
 
                if type( vsn ) == "table"  and
 
                  type( vsn.value) == "string"  and
 
                  vsn.value ~= "" then
 
                    r = vsn.value
 
                end
 
            end
 
        end
 
    end
 
    if not r then
 
        if not since  or  since <= TemplatePar.serial then
 
            r = TemplatePar.serial
 
        else
 
            r = false
 
        end
 
    end
 
    return r
 
end -- TemplatePar.failsafe()
 
 
 
 
 
 
 
TemplatePar.valid = function ( access, options )
 
    -- Check validity of one particular template parameter
 
    -- Precondition:
 
    --    access  -- id of parameter in template transclusion
 
    --                string or number
 
    --    options  -- table or nil; optional details
 
    -- Postcondition:
 
     --    Return string with error message as configured;
 
     --            false if valid or no answer permitted
 
 
     -- Uses:
 
     -- Uses:
    --    >< TemplatePar.frame
 
 
     --    mw.text.trim()
 
     --    mw.text.trim()
 
     --    TemplatePar.downcase()
 
     --    TemplatePar.downcase()
 +
    --    TemplatePar.framing()
 
     --    frame:getParent()
 
     --    frame:getParent()
 
     --    formatted()
 
     --    formatted()
Zeile 1'335: Zeile 1'495:
 
             params = TemplatePar.downcase( options )
 
             params = TemplatePar.downcase( options )
 
         else
 
         else
             if not TemplatePar.frame then
+
             params = TemplatePar.framing():getParent()
                TemplatePar.frame = mw.getCurrentFrame()
 
            end
 
            params = TemplatePar.frame:getParent()
 
 
         end
 
         end
 
         r = formatted( params, access, options )
 
         r = formatted( params, access, options )
Zeile 1'344: Zeile 1'501:
 
         r = failure( "noname", false, options )
 
         r = failure( "noname", false, options )
 
     end
 
     end
     return finalize( r, options, frame )
+
     return finalize( r, options )
 
end -- TemplatePar.valid()
 
end -- TemplatePar.valid()
  
Zeile 1'363: Zeile 1'520:
  
  
-- Provide external access
+
TemplatePar.framing = function( frame )
local p = {}
+
     -- Ensure availability of frame object
 
+
     -- Precondition:
 
+
     --    frame  -- object; #invoke environment, or false
 
 
function p.assert( frame )
 
     -- Perform parameter analysis on some single string
 
     -- Precondition:
 
     --    frame  -- object; #invoke environment
 
 
     -- Postcondition:
 
     -- Postcondition:
     --    Return string with error message or ""
+
     --    Return frame object
 
     -- Uses:
 
     -- Uses:
     --    furnish()
+
     --    >< Local.frame
    return furnish( frame, "assert" )
+
    if not Local.frame then
end -- p.assert()
+
        if type( frame ) == "table"  and
 +
          type( frame.args ) == "table"  and
 +
          type( frame.getParent ) == "function"  and
 +
          type( frame:getParent() ) == "table"  and
 +
          type( frame:getParent().getParent ) == "function"  and
 +
          type( frame:getParent():getParent() ) == "nil" then
 +
            Local.frame = frame
 +
        else
 +
            Local.frame = mw.getCurrentFrame()
 +
        end
 +
    end
 +
    return Local.frame
 +
end -- TemplatePar.framing()
  
  
  
function p.check( frame )
+
Failsafe.failsafe = function ( atleast )
     -- Check validity of template parameters
+
     -- Retrieve versioning and check for compliance
 
     -- Precondition:
 
     -- Precondition:
     --    frame -- object; #invoke environment
+
     --    atleast -- string, with required version
 +
    --                        or wikidata|item|~|@ or false
 
     -- Postcondition:
 
     -- Postcondition:
     --    Return string with error message or ""
+
    --    Returns  string  -- with queried version/item, also if problem
     -- Uses:
+
    --              false  -- if appropriate
     --    form()
+
    -- 2020-08-17
     --    fill()
+
    local since = atleast
     local options = { optional  = { "all",
+
    local last    = ( since == "~" )
                                     "opt",
+
    local linked  = ( since == "@" )
                                     "cat",
+
    local link    = ( since == "item" )
                                     "errNS",
+
    local r
                                     "low",
+
    if last  or  link  or  linked  or  since == "wikidata" then
                                     "format",
+
        local item = Failsafe.item
                                     "preview",
+
        since = false
                                     "template" },
+
        if type( item ) == "number"  and  item > 0 then
                       template  = "&#35;invoke:TemplatePar|check|"
+
            local suited = string.format( "Q%d", item )
                     }
+
            if link then
     local r = form( false, options, frame )
+
                r = suited
     if not r then
+
            else
         options = { mandatory = fill( frame.args.all ),
+
                local entity = mw.wikibase.getEntity( suited )
                     optional  = fill( frame.args.opt ),
+
                if type( entity ) == "table" then
                     cat      = frame.args.cat,
+
                    local seek = Failsafe.serialProperty or "P348"
                     errNS    = frame.args.errNS,
+
                    local vsn  = entity:formatPropertyValues( seek )
                     low      = frame.args.low,
+
                    if type( vsn ) == "table"  and
                     format    = frame.args.format,
+
                      type( vsn.value ) == "string"  and
                     preview  = frame.args.preview,
+
                      vsn.value ~= "" then
                     template  = frame.args.template
+
                        if last  and  vsn.value == Failsafe.serial then
                   }
+
                            r = false
         r      = form( true, options, frame )
+
                        elseif linked then
     end
+
                            if mw.title.getCurrentTitle().prefixedText
     return r or ""
+
                              ==  mw.wikibase.getSitelink( suited ) then
end -- p.check()
+
                                r = false
 
+
                            else
 
+
                                r = suited
 
+
                            end
function p.count( frame )
+
                        else
     -- Count number of template parameters
+
                            r = vsn.value
     -- Postcondition:
+
                        end
     --    Return string with digits including "0"
+
                    end
     -- Uses:
+
                end
     --    TemplatePar.count()
+
            end
     return tostring( TemplatePar.count() )
+
        end
end -- p.count()
+
    end
 
+
    if type( r ) == "nil" then
 
+
        if not since  or  since <= Failsafe.serial then
 
+
            r = Failsafe.serial
function p.countNotEmpty( frame )
+
        else
     -- Count number of template parameters which are not empty
+
            r = false
     -- Postcondition:
+
        end
     --    Return string with digits including "0"
+
    end
     -- Uses:
+
    return r
     --    TemplatePar.countNotEmpty()
+
end -- Failsafe.failsafe()
     return tostring( TemplatePar.countNotEmpty() )
+
 
end -- p.countNotEmpty()
+
 
 +
 
 +
-- Provide external access
 +
local p = {}
 +
 
 +
 
 +
 
 +
function p.assert( frame )
 +
    -- Perform parameter analysis on some single string
 +
    -- Precondition:
 +
    --    frame  -- object; #invoke environment
 +
    -- Postcondition:
 +
    --    Return string with error message or ""
 +
    -- Uses:
 +
    --    furnish()
 +
    return furnish( frame, "assert" )
 +
end -- p.assert()
 +
 
 +
 
 +
 
 +
function p.check( frame )
 +
    -- Check validity of template parameters
 +
    -- Precondition:
 +
    --    frame  -- object; #invoke environment
 +
    -- Postcondition:
 +
     --    Return string with error message or ""
 +
     -- Uses:
 +
     --    form()
 +
     --    fill()
 +
     local options = { optional  = { "all",
 +
                                     "opt",
 +
                                     "cat",
 +
                                     "errNS",
 +
                                     "low",
 +
                                     "format",
 +
                                     "preview",
 +
                                     "template" },
 +
                       template  = "&#35;invoke:TemplatePar|check|"
 +
                     }
 +
     local r = form( false, options, frame )
 +
     if not r then
 +
         options = { mandatory = fill( frame.args.all ),
 +
                     optional  = fill( frame.args.opt ),
 +
                     cat      = frame.args.cat,
 +
                     errNS    = frame.args.errNS,
 +
                     low      = frame.args.low,
 +
                     format    = frame.args.format,
 +
                     preview  = frame.args.preview,
 +
                     template  = frame.args.template
 +
                   }
 +
         r      = form( true, options, frame )
 +
     end
 +
     return r or ""
 +
end -- p.check()
 +
 
 +
 
 +
 
 +
function p.count( frame )
 +
     -- Count number of template parameters
 +
     -- Postcondition:
 +
     --    Return string with digits including "0"
 +
     -- Uses:
 +
     --    TemplatePar.count()
 +
     return tostring( TemplatePar.count() )
 +
end -- p.count()
 +
 
 +
 
 +
 
 +
function p.countNotEmpty( frame )
 +
     -- Count number of template parameters which are not empty
 +
     -- Postcondition:
 +
     --    Return string with digits including "0"
 +
     -- Uses:
 +
     --    TemplatePar.countNotEmpty()
 +
     return tostring( TemplatePar.countNotEmpty() )
 +
end -- p.countNotEmpty()
  
  
Zeile 1'447: Zeile 1'687:
 
     --    Return string with error message or ""
 
     --    Return string with error message or ""
 
     -- Uses:
 
     -- Uses:
     --     < TemplatePar.frame
+
     --     TemplatePar.framing()
 
     --    mw.text.trim()
 
     --    mw.text.trim()
 
     --    mw.ustring.lower()
 
     --    mw.ustring.lower()
Zeile 1'467: Zeile 1'707:
 
     local k, v, s
 
     local k, v, s
 
     local params = { }
 
     local params = { }
     TemplatePar.frame = frame
+
     TemplatePar.framing( frame )
 
     for k, v in pairs( frame.args ) do
 
     for k, v in pairs( frame.args ) do
 
         if type( k ) == "number" then
 
         if type( k ) == "number" then
Zeile 1'514: Zeile 1'754:
 
         for k, v in pairs( params ) do
 
         for k, v in pairs( params ) do
 
             options.say = k
 
             options.say = k
            errValue    = false
+
             s           = targs[ k ]
             s = targs[ k ]
 
 
             if s then
 
             if s then
 
                 if s == "" then
 
                 if s == "" then
Zeile 1'532: Zeile 1'771:
 
                     if lack then
 
                     if lack then
 
                         if errMiss then
 
                         if errMiss then
                             errMiss = string.format( "%s, '%s'",
+
                             s      = "%s, &quot;%s&quot;"
                                                    errMiss, k )
+
                            errMiss = string.format( s, errMiss, k )
 
                         else
 
                         else
                             errMiss = string.format( "'%s'", k )
+
                             errMiss = string.format( "&quot;%s&quot;",
 +
                                                    k )
 
                         end
 
                         end
 
                     elseif not errMiss then
 
                     elseif not errMiss then
Zeile 1'573: Zeile 1'813:
  
 
p.failsafe = function ( frame )
 
p.failsafe = function ( frame )
     -- Check or retrieve version information
+
     -- Versioning interface
    -- Precondition:
+
     local s = type( frame )
    --    frame  -- object; #invoke environment
+
     local since
    -- Postcondition:
+
     if s == "table" then
    --    Return string with error message or ""
+
         since = frame.args[ 1 ]
    -- Uses:
+
     elseif s == "string" then
    --    TemplatePar.failsafe()
+
         since = frame
     local s = type( frame )
+
     end
     local since
+
     if since then
     if s == "table" then
+
         since = mw.text.trim( since )
         since = frame.args[ 1 ]
+
         if since == "" then
     elseif s == "string" then
+
             since = false
         since = frame
+
         end
     end
+
     end
     if since then
+
     return Failsafe.failsafe( since )  or  ""
         since = mw.text.trim( since )
+
end -- p.failsafe
         if since == "" then
+
 
             since = false
+
 
         end
+
 
     end
+
function p.TemplatePar()
     return TemplatePar.failsafe( since )  or  ""
+
    -- Retrieve function access for modules
end -- p.failsafe()
+
    -- Postcondition:
 +
    --    Return table with functions
 +
    return TemplatePar
 +
end -- p.TemplatePar()
  
  
  
function p.TemplatePar()
+
setmetatable( p,  { __call = function ( func, ... )
    -- Retrieve function access for modules
+
                                setmetatable( p, nil )
    -- Postcondition:
+
                                return Failsafe
    --    Return table with functions
+
                            end } )
    return TemplatePar
 
end -- p.TemplatePar()
 
 
 
 
 
  
 
return p
 
return p

Version vom 21. März 2023, 16:11 Uhr

Die Dokumentation für dieses Modul kann unter Modul:TemplatePar/Doku erstellt werden

local TemplatePar = { serial  = "2023-03-20",
                      suite   = "TemplatePar",
                      item    = 15393417,
                      globals = { DateTime     = 20652535,
                                  FileMedia    = 24765326,
                                  Multilingual = 47541920,
                                  TemplUtl     = 52364930,
                                  URLutil      = 10859193 } }
--[=[
Template parameter utility
* assert
* check
* count
* countNotEmpty
* downcase()
* duplicates
* match
* valid
* verify()
* TemplatePar()
* failsafe()
]=]


local Local     = { frame = false }
local Failsafe  = TemplatePar
local GlobalMod = Local



-- Module globals
Local.messagePrefix = "lua-module-TemplatePar-"
Local.L10nDef = {}
Local.L10nDef.en = {
    badPattern  = "&#35;invoke:TemplatePar pattern syntax error",
    dupOpt      = "&#35;invoke:TemplatePar repeated optional parameter",
    dupRule     = "&#35;invoke:TemplatePar conflict key/pattern",
    empty       = "Error in template * undefined value for mandatory",
    invalid     = "Error in template * invalid parameter",
    invalidPar  = "&#35;invoke:TemplatePar invalid parameter",
    minmax      = "&#35;invoke:TemplatePar min > max",
    missing     = "&#35;invoke:TemplatePar missing library",
    multiSpell  = "Error in template * multiple spelling of parameter",
    noMSGnoCAT  = "&#35;invoke:TemplatePar neither message nor category",
    noname      = "&#35;invoke:TemplatePar missing parameter name",
    notFound    = "Error in template * missing page",
    tooLong     = "Error in template * parameter too long",
    tooShort    = "Error in template * parameter too short",
    unavailable = "Error in template * parameter name missing",
    undefined   = "Error in template * mandatory parameter missing",
    unknown     = "Error in template * unknown parameter name",
    unknownRule = "&#35;invoke:TemplatePar unknown rule"
}
Local.patterns = {
    [ "ASCII" ]    = "^[ -~]*$",
    [ "ASCII+" ]   = "^[ -~]+$",
    [ "ASCII+1" ]  = "^[!-~]+$",
    [ "n" ]        = "^[%-]?[0-9]*$",
    [ "n>0" ]      = "^[0-9]*[1-9][0-9]*$",
    [ "N+" ]       = "^[%-]?[1-9][0-9]*$",
    [ "N>0" ]      = "^[1-9][0-9]*$",
    [ "x" ]        = "^[0-9A-Fa-f]*$",
    [ "x+" ]       = "^[0-9A-Fa-f]+$",
    [ "X" ]        = "^[0-9A-F]*$",
    [ "X+" ]       = "^[0-9A-F]+$",
    [ "0,0" ]      = "^[%-]?[0-9]*,?[0-9]*$",
    [ "0,0+" ]     = "^[%-]?[0-9]+,[0-9]+$",
    [ "0,0+?" ]    = "^[%-]?[0-9]+,?[0-9]*$",
    [ "0.0" ]      = "^[%-]?[0-9]*[%.]?[0-9]*$",
    [ "0.0+" ]     = "^[%-]?[0-9]+%.[0-9]+$",
    [ "0.0+?" ]    = "^[%-]?[0-9]+[%.]?[0-9]*$",
    [ ".0+" ]      = "^[%-]?[0-9]*[%.]?[0-9]+$",
    [ "ID" ]       = "^[A-Za-z]?[A-Za-z_0-9]*$",
    [ "ID+" ]      = "^[A-Za-z][A-Za-z_0-9]*$",
    [ "ABC" ]      = "^[A-Z]*$",
    [ "ABC+" ]     = "^[A-Z]+$",
    [ "Abc" ]      = "^[A-Z]*[a-z]*$",
    [ "Abc+" ]     = "^[A-Z][a-z]+$",
    [ "abc" ]      = "^[a-z]*$",
    [ "abc+" ]     = "^[a-z]+$",
    [ "aBc+" ]     = "^[a-z]+[A-Z][A-Za-z]*$",
    [ "w" ]        = "^%S*$",
    [ "w+" ]       = "^%S+$",
    [ "base64" ]   = "^[A-Za-z0-9%+/]*$",
    [ "base64+" ]  = "^[A-Za-z0-9%+/]+$",
    [ "aa" ]       = "[%a%a].*[%a%a]",
    [ "pagename" ] = string.format( "^[^#<>%%[%%]|{}%c-%c%c]+$",
                                    1, 31, 127 ),
    [ "ref" ]      = string.format( "%c'%c`UNIQ%s%sref%s%s%sQINU`%c'%c",
                                    127, 34, "%-", "%-", "%-", "%x+",
                                    "%-", 34, 127 ),
    [ "+" ]        = "%S"
}
Local.boolean = { ["1"]     = true,
                  ["true"]  = true,
                  y         = true,
                  yes       = true,
                  on        = true,
                  ["0"]     = true,
                  ["false"] = true,
                  ["-"]     = true,
                  n         = true,
                  no        = true,
                  off       = true }
Local.patternCJK = false



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
    -- 2020-01-01
    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 )
        end
    end
    return r
end -- foreignModule()



local function Foreign( access  )
    -- Access standardized library
    -- Precondition:
    --     access  -- string, with name of base module
    -- Postcondition:
    --     Return library table, or not
    -- Uses:
    local r
    if Local[ access ] then
        r = Local[ access ]
    else
        local bib = foreignModule( access,
                                   true,
                                   false,
                                   TemplatePar.globals[ access ],
                                   false )
        if type( bib ) == "table"   and
           type( bib[ access ] ) == "function" then
            bib = bib[ access ]()
            if type( bib ) == "table" then
                r               = bib
                Local[ access ] = bib
            end
        end
    end
    return r
end -- Foreign()



local function containsCJK( analyse )
    -- Is any CJK character present?
    -- Precondition:
    --     analyse  -- string
    -- Postcondition:
    --     Return false iff no CJK present
    -- Uses:
    --     >< Local.patternCJK
    --     mw.ustring.char()
    --     mw.ustring.match()
    local r = false
    if not Local.patternCJK then
        Local.patternCJK = mw.ustring.char( 91,
                                       13312, 45,  40959,
                                      131072, 45, 178207,
                                      93 )
    end
    if mw.ustring.match( analyse, Local.patternCJK ) then
        r = true
    end
    return r
end -- containsCJK()



local function facility( accept, attempt )
    -- Check string as possible file name or other source page
    -- Precondition:
    --     accept   -- string; requirement
    --                         file
    --                         file+
    --                         file:
    --                         file:+
    --                         image
    --                         image+
    --                         image:
    --                         image:+
    --     attempt  -- string; to be tested
    -- Postcondition:
    --     Return error keyword, or false
    -- Uses:
    --     Module:FileMedia
    --     Foreign()
    --     FileMedia.isFile()
    --     FileMedia.isType()
    local r
    if attempt and attempt ~= "" then
        local FileMedia = Foreign( "FileMedia" )
        if FileMedia  and  type( FileMedia.isFile ) == "function"
                      and  type( FileMedia.isType ) == "function" then
            local s, live = accept:match( "^([a-z]+)(:?)%+?$" )
            if live then
                if FileMedia.isType( attempt, s ) then
                    if FileMedia.isFile( attempt ) then
                        r = false
                    else
                        r = "notFound"
                    end
                else
                    r = "invalid"
                end
            elseif FileMedia.isType( attempt, s ) then
                r = false
            else
                r = "invalid"
            end
        else
            r = "missing"
        end
    elseif accept:match( "%+$" ) then
        r = "empty"
    else
        r = false
    end
    return r
end -- facility()



local function factory( say )
    -- Retrieve localized message string in content language
    -- Precondition:
    --     say  -- string; message ID
    -- Postcondition:
    --     Return some message string
    -- Uses:
    --     >  Local.messagePrefix
    --     >  Local.L10nDef
    --     mw.message.new()
    --     mw.language.getContentLanguage()
    --     Module:Multilingual
    --     Foreign()
    --     TemplatePar.framing()
    --     Multilingual.tabData()
    local m = mw.message.new( Local.messagePrefix .. say )
    local r = false
    if m:isBlank() then
        local c = mw.language.getContentLanguage():getCode()
        local l10n = Local.L10nDef[ c ]
        if l10n then
            r = l10n[ say ]
        else
            local MultiL = Foreign( "Multilingual" )
            if MultiL  and  type( MultiL.tabData ) == "function" then
                local lang
                r, lang = MultiL.tabData( "I18n/Module:TemplatePar",
                                          say,
                                          false,
                                          TemplatePar.framing() )
            end
        end
        if not r then
            r = Local.L10nDef.en[ say ]
        end
    else
        m:inLanguage( c )
        r = m:plain()
    end
    if not r then
        r = string.format( "(((%s)))", say )
    end
    return r
end -- factory()



local function faculty( accept, attempt )
    -- Check string as possible boolean
    -- Precondition:
    --     accept   -- string; requirement
    --                         boolean
    --                         boolean+
    --     attempt  -- string; to be tested
    -- Postcondition:
    --     Return error keyword, or false
    -- Uses:
    --     Module:TemplUtl
    --     Foreign()
    --     TemplUtl.faculty()
    local r
    r = mw.text.trim( attempt ):lower()
    if r == "" then
        if accept == "boolean+" then
            r = "empty"
        else
            r = false
        end
    elseif Local.boolean[ r ]  or   r:match( "^[01%-]+$" ) then
        r = false
    else
        local TemplUtl = Foreign( "TemplUtl" )
        if TemplUtl  and  type( TemplUtl.faculty ) == "function" then
            r = TemplUtl.faculty( r, "-" )
            if r == "-" then
                r = "invalid"
            else
                r = false
            end
        else
            r = "invalid"
        end
    end
    return r
end -- faculty()



local function failure( spec, suspect, options )
    -- Submit localized error message
    -- Precondition:
    --     spec     -- string; message ID
    --     suspect  -- string or nil; additional information
    --     options  -- table or nil; optional details
    --                 options.template
    -- Postcondition:
    --     Return string
    -- Uses:
    --     factory()
    local r = factory( spec )
    if type( options ) == "table" then
        if type( options.template ) == "string" then
            if #options.template > 0 then
                r = string.format( "%s (%s)", r, options.template )
            end
        end
    end
    if suspect then
        r = string.format( "%s: %s", r, suspect )
    end
    return r
end -- failure()



local function fair( story, scan )
    -- Test for match (possibly user-defined with syntax error)
    -- Precondition:
    --     story  -- string; parameter value
    --     scan   -- string; pattern
    -- Postcondition:
    --     Return nil, if not matching, else non-nil
    -- Uses:
    --     mw.ustring.match()
    return  mw.ustring.match( story, scan )
end -- fair()



local function familiar( accept, attempt )
    -- Check string as possible language name or list
    -- Precondition:
    --     accept   -- string; requirement
    --                         lang
    --                         langs
    --                         langW
    --                         langsW
    --                         lang+
    --                         langs+
    --                         langW+
    --                         langsW+
    --     attempt  -- string; to be tested
    -- Postcondition:
    --     Return error keyword, or false
    -- Uses:
    --     Module:Multilingual
    --     Foreign()
    --     Multilingual.isLang()
    local r
    if attempt and attempt ~= "" then
        local MultiL = Foreign( "Multilingual" )
        if MultiL  and  type( MultiL.isLang ) == "function" then
            local lazy = accept:find( "W", 1, true )
            if accept:find( "s", 1, true ) then
                local group = mw.text.split( attempt, "%s+" )
                r = false
                for i = 1, #group do
                    if not MultiL.isLang( group[ i ], lazy ) then
                        r = "invalid"
                        break -- for i
                    end
                end -- for i
            elseif MultiL.isLang( attempt, lazy ) then
                r = false
            else
                r = "invalid"
            end
        else
            r = "missing"
        end
    elseif accept:find( "+", 1, true ) then
        r = "empty"
    else
        r = false
    end
    return r
end -- familiar()



local function far( accept, attempt )
    -- Check string as possible URL
    -- Precondition:
    --     accept   -- string; requirement
    --                         url
    --                         url+
    --     attempt  -- string; to be tested
    -- Postcondition:
    --     Return error keyword, or false
    -- Uses:
    --     Module:URLutil
    --     Foreign()
    --     URLutil.isWebURL()
    local r
    if attempt and attempt ~= "" then
        local URLutil = Foreign( "URLutil" )
        if URLutil  and  type( URLutil.isWebURL ) == "function" then
            if URLutil.isWebURL( attempt ) then
                r = false
            else
                r = "invalid"
            end
        else
            r = "missing"
        end
    elseif accept:find( "+", 1, true ) then
        r = "empty"
    else
        r = false
    end
    return r
end -- far()



local function fast( accept, attempt )
    -- Check string as possible date or time
    -- Precondition:
    --     accept   -- string; requirement
    --                         datetime
    --                         datetime+
    --                         datetime/y
    --                         datetime/y+
    --                         datetime/ym
    --                         datetime/ym+
    --                         datetime/ymd
    --                         datetime/ymd+
    --     attempt  -- string; to be tested
    -- Postcondition:
    --     Return error keyword, or false
    -- Uses:
    --     Module:DateTime
    --     Foreign()
    --     DateTime.DateTime()
    local r
    r = mw.text.trim( attempt )
    if r == "" then
        if accept:find( "+", 1, true ) then
            r = "empty"
        else
            r = false
        end
    else
        local DateTime = Foreign( "DateTime" )
        if type( DateTime ) == "table" then
            local d = DateTime( attempt )
            if type( d ) == "table" then
                if accept:find( "/", 1, true ) then
                    r = "invalid"
                    if accept:sub( 1, 10 ) == "datetime/y" then
                        if d.year then
                            r = false
                            if accept:sub( 1, 11 ) == "datetime/ym" then
                                if d.month then
                                    if accept:sub( 1, 12 )
                                                   == "datetime/ymd" then
                                        if not d.dom then
                                            r = "invalid"
                                        end
                                    end
                                else
                                    r = "invalid"
                                end
                            end
                        end
                    end
                else
                    r = false
                end
            else
                r = "invalid"
            end
        else
            r = "invalid"
        end
    end
    return r
end -- fast()



local function fault( store, key )
    -- Add key to collection string and insert separator
    -- Precondition:
    --     store  -- string or nil or false; collection string
    --     key    -- string or number; to be appended
    -- Postcondition:
    --     Return string; extended
    local r
    local s
    if type( key ) == "number" then
        s = tostring( key )
    else
        s = key
    end
    if store then
        r = string.format( "%s; %s", store, s )
    else
        r = s
    end
    return r
end -- fault()



local function feasible( analyze, options, abbr )
    -- Check content of a value
    -- Precondition:
    --     analyze  -- string to be analyzed
    --     options  -- table or nil; optional details
    --                 options.pattern
    --                 options.key
    --                 options.say
    --     abbr     -- true: abbreviated error message
    -- Postcondition:
    --     Return string with error message as configured;
    --            false if valid or no answer permitted
    -- Uses:
    --     >  Local.patterns
    --     failure()
    --     mw.text.trim()
    --     faculty()
    --     fast()
    --     facility()
    --     familiar()
    --     far()
    --     fair()
    --     containsCJK()
    local r     = false
    local s     = false
    local show  = nil
    local scan  = false
    local stuff = mw.text.trim( analyze )
    if type( options.pattern ) == "string" then
        if options.key then
            r = failure( "dupRule", false, options )
        else
            scan = options.pattern
        end
    else
        if type( options.key ) == "string" then
            s = mw.text.trim( options.key )
        else
            s = "+"
        end
        if s ~= "*" then
            scan = Local.patterns[ s ]
        end
        if type( scan ) == "string" then
            if s == "n" or s == "0,0" or s == "0.0" then
                if not stuff:match( "[0-9]" )  and
                   not stuff:match( "^%s*$" ) then
                    scan = false
                    if options.say then
                        show = string.format( "&quot;%s&quot;", options.say )
                    end
                    if abbr then
                        r = show
                    else
                        r = failure( "invalid", show, options )
                    end
                end
            end
        elseif s ~= "*" then
            local op, n, plus = s:match( "([<!=>]=?)([-0-9][%S]*)(+?)" )
            if op then
                n = tonumber( n )
                if n then
                    local i = tonumber( stuff )
                    if i then
                        if op == "<" then
                            i = ( i < n )
                        elseif op == "<=" then
                            i = ( i <= n )
                        elseif op == ">" then
                            i = ( i > n )
                        elseif op == ">=" then
                            i = ( i >= n )
                        elseif op == "==" then
                            i = ( i == n )
                        elseif op == "!=" then
                            i = ( i ~= n )
                        else
                            n = false
                        end
                    end
                    if not i then
                        r = "invalid"
                    end
                elseif plus then
                    r = "undefined"
                end
            elseif s:match( "^boolean%+?$" ) then
                r = faculty( s, stuff )
                n = true
            elseif s:match( "^datetime/?y?m?d?%+?$" ) then
                r = fast( s, stuff )
                n = true
            elseif s:match( "^image%+?:?$" )  or
                   s:match( "^file%+?:?$" ) then
                r = facility( s, stuff )
                n = true
            elseif s:match( "langs?W?%+?" ) then
                r = familiar( s, stuff )
                n = true
            elseif s:match( "url%+?" ) then
                r = far( s, stuff )
                n = true
            end
-- datetime+
-- iso8631+
-- line+
            if not n and not r then
                r = "unknownRule"
            end
            if r then
                if options.say then
                    show = string.format( "&quot;%s&quot; %s", options.say, s )
                else
                    show = s
                end
                if abbr then
                    r = show
                else
                    r = failure( r, show, options )
                end
            end
        end
    end
    if scan then
        local legal, got = pcall( fair, stuff, scan )
        if legal then
            if not got then
                if s == "aa" then
                    got = containsCJK( stuff )
                end
                if not got then
                    if options.say then
                        show = string.format( "&quot;%s&quot;", options.say )
                    end
                    if abbr then
                        r = show
                    else
                        r = failure( "invalid", show, options )
                    end
                end
            end
        else
            r = failure( "badPattern",
                         string.format( "%s *** %s", scan, got ),
                         options )
        end
    end
    return r
end -- feasible()



local function fed( haystack, needle )
    -- Find needle in haystack map
    -- Precondition:
    --     haystack  -- table; map of key values
    --     needle    -- any; identifier
    -- Postcondition:
    --     Return true iff found
    local k, v, r
    for k, v in pairs( haystack ) do
        if k == needle then
            r = true
        end
    end -- for k, v
    return r or false
end -- fed()



local function fetch( light, options )
    -- Return regular table with all parameters
    -- Precondition:
    --     light    -- true: template transclusion;  false: #invoke
    --     options  -- table; optional details
    --                 options.low
    -- Postcondition:
    --     Return table; whitespace-only values as false
    -- Uses:
    --     TemplatePar.downcase()
    --     TemplatePar.framing()
    --     frame:getParent()
    local g, k, v
    local r = { }
    if options.low then
        g = TemplatePar.downcase( options )
    else
        g = TemplatePar.framing()
        if light then
            g = g:getParent()
        end
        g = g.args
    end
    if type( g ) == "table"  then
        r = { }
        for k, v in pairs( g ) do
            if type( v ) == "string" then
                if v:match( "^%s*$" ) then
                    v = false
                end
            else
                v = false
            end
            if type( k ) == "number" then
                k = tostring( k )
            end
            r[ k ] = v
        end -- for k, v
    else
        r = g
    end
    return r
end -- fetch()



local function figure( append, options )
    -- Extend options by rule from #invoke strings
    -- Precondition:
    --     append   -- string or nil; requested rule
    --     options  --  table; details
    --                  ++ .key
    --                  ++ .pattern
    -- Postcondition:
    --     Return sequence table
    local r = options
    if type( append ) == "string" then
        local story = mw.text.trim( append )
        local sub   = story:match( "^/(.*%S)/$" )
        if type( sub ) == "string" then
            sub             = sub:gsub( "%%!", "|" )
                                 :gsub( "%%%(%(", "{{" )
                                 :gsub( "%%%)%)", "}}" )
                                 :gsub( "\\n", string.char( 10 ) )
            options.pattern = sub
            options.key     = nil
        else
            options.key     = story
            options.pattern = nil
        end
    end
    return r
end -- figure()



local function fill( specified )
    -- Split requirement string separated by '='
    -- Precondition:
    --     specified  -- string or nil; requested parameter set
    -- Postcondition:
    --     Return sequence table
    -- Uses:
    --     mw.text.split()
    local r
    if specified then
        local i, s
        r = mw.text.split( specified, "%s*=%s*" )
        for i = #r, 1, -1 do
            s = r[ i ]
            if #s == 0 then
                table.remove( r, i )
            end
        end -- for i, -1
    else
        r = { }
    end
    return r
end -- fill()



local function finalize( submit, options )
    -- Finalize message
    -- Precondition:
    --     submit   -- string or false or nil; non-empty error message
    --     options  -- table or nil; optional details
    --                 options.format
    --                 options.preview
    --                 options.cat
    --                 options.template
    -- Postcondition:
    --     Return string or false
    -- Uses:
    --     TemplatePar.framing()
    --     factory()
    local r = false
    if submit then
        local lazy  = false
        local learn = false
        local show  = false
        local opt, s
        if type( options ) == "table" then
            opt  = options
            show = opt.format
            lazy = ( show == ""  or  show == "0"  or  show == "-" )
            s    = opt.preview
            if type( s ) == "string"  and
               s ~= ""  and  s ~= "0"  and  s ~= "-" then
                local sniffer = "{{REVISIONID}}"
                if lazy then
                    show = ""
                    lazy = false
                end
                if TemplatePar.framing():preprocess( sniffer ) == "" then
                    if s == "1" then
                        show = "*"
                    else
                        show = s
                    end
                    learn = true
                end
            end
        else
            opt = { }
        end
        if lazy then
            if not opt.cat then
                r = string.format( "%s %s",
                                   submit,  factory( "noMSGnoCAT" ) )
            end
        else
            r = submit
        end
        if r  and  not lazy then
            local i
            if not show  or  show == "*" then
                local e = mw.html.create( "span" )
                                 :attr( "class", "error" )
                                 :wikitext( "@@@" )
                if learn then
                    local max  = 1000000000
                    local id   = math.floor( os.clock() * max )
                    local sign = string.format( "error_%d", id )
                    local btn  = mw.html.create( "span" )
                    local top  = mw.html.create( "div" )
                    e:attr( "id", sign )
                    btn:css( { ["background"]      = "#FFFF00",
                               ["border"]          = "#FF0000 3px solid",
                               ["font-weight"]     = "bold",
                               ["padding"]         = "2px",
                               ["text-decoration"] = "none" } )
                       :wikitext( "&gt;&gt;&gt;" )
                    sign = string.format( "[[#%s|%s]]",
                                          sign,  tostring( btn ) )
                    top:wikitext( sign, "&#160;", submit )
                    mw.addWarning( tostring( top ) )
                end
                show = tostring( e )
            end
            i = show:find( "@@@", 1, true )
            if i then
                -- No gsub() since r might contain "%3" (e.g. URL)
                r = string.format( "%s%s%s",
                                   show:sub( 1,  i - 1 ),
                                   r,
                                   show:sub( i + 3 ) )
            else
                r = show
            end
        end
        if learn and r then
            -- r = fatal( r )
        end
        s = opt.cat
        if type( s ) == "string" then
            local link
            if opt.errNS then
                local ns = mw.title.getCurrentTitle().namespace
                local st = type( opt.errNS )
                if st == "string" then
                    local space  = string.format( ".*%%s%d%%s.*", ns )
                    local spaces = string.format( " %s ", opt.errNS )
                    if spaces:match( space ) then
                        link = true
                    end
                elseif st == "table" then
                    for i = 1, #opt.errNS do
                        if opt.errNS[ i ] == ns then
                            link = true
                            break    -- for i
                        end
                    end -- for i
                end
            else
                link = true
            end
            if link then
                local cats, i
                if not r then
                   r = ""
                end
                if s:find( "@@@" ) then
                    if type( opt.template ) == "string" then
                        s = s:gsub( "@@@", opt.template )
                    end
                end
                cats = mw.text.split( s, "%s*#%s*" )
                for i = 1, #cats do
                    s = mw.text.trim( cats[ i ] )
                    if #s > 0 then
                        r = string.format( "%s[[Category:%s]]", r, s )
                    end
                end -- for i
            end
        end
    end
    return r
end -- finalize()



local function finder( haystack, needle )
    -- Find needle in haystack sequence
    -- Precondition:
    --     haystack  -- table; sequence of key names, downcased if low
    --     needle    -- any; key name
    -- Postcondition:
    --     Return true iff found
    local i
    for i = 1, #haystack do
        if haystack[ i ] == needle then
            return true
        end
    end -- for i
    return false
end -- finder()



local function fix( valid, duty, got, options )
    -- Perform parameter analysis
    -- Precondition:
    --     valid    -- table; unique sequence of known parameters
    --     duty     -- table; sequence of mandatory parameters
    --     got      -- table; sequence of current parameters
    --     options  -- table or nil; optional details
    -- Postcondition:
    --     Return string as configured; empty if valid
    -- Uses:
    --     finder()
    --     fault()
    --     failure()
    --     fed()
    local r = false
    local lack
    for k, v in pairs( got ) do
        if k == "" then
            lack = true
            break    -- for k, v
        elseif not finder( valid, k ) then
            r = fault( r, k )
        end
    end -- for k, v
    if lack then
        r = failure( "unavailable", false, options )
    elseif r then
        r = failure( "unknown",
                     string.format( "&quot;%s&quot;", r ),
                     options )
    else -- all names valid
        local i, s
        for i = 1, #duty do
            s = duty[ i ]
            if not fed( got, s ) then
                r = fault( r, s )
            end
        end -- for i
        if r then
            r = failure( "undefined", r, options )
        else -- all mandatory present
            for i = 1, #duty do
                s = duty[ i ]
                if not got[ s ] then
                    r = fault( r, s )
                end
            end -- for i
            if r then
                r = failure( "empty", r, options )
            end
        end
    end
    return r
end -- fix()



local function flat( collection, options )
    -- Return all table elements with downcased string
    -- Precondition:
    --     collection  -- table; k=v pairs
    --     options     -- table or nil; optional messaging details
    -- Postcondition:
    --     Return table, may be empty; or string with error message.
    -- Uses:
    --     mw.ustring.lower()
    --     fault()
    --     failure()
    local k, v
    local r = { }
    local e = false
    for k, v in pairs( collection ) do
        if type ( k ) == "string" then
            k = mw.ustring.lower( k )
            if r[ k ] then
                e = fault( e, k )
            end
        end
        r[ k ] = v
    end -- for k, v
    if e then
        r = failure( "multiSpell", e, options )
    end
    return r
end -- flat()



local function fold( options )
    -- Merge two tables, create new sequence if both not empty
    -- Precondition:
    --     options  -- table; details
    --                 options.mandatory   sequence to keep unchanged
    --                 options.optional    sequence to be appended
    --                 options.low         downcased expected
    -- Postcondition:
    --     Return merged table, or message string if error
    -- Uses:
    --     finder()
    --     fault()
    --     failure()
    --     flat()
    local i, e, r, s
    local base   = options.mandatory
    local extend = options.optional
    if #base == 0 then
        if #extend == 0 then
            r = { }
        else
            r = extend
        end
    else
        if #extend == 0 then
            r = base
        else
            e = false
            for i = 1, #extend do
                s = extend[ i ]
                if finder( base, s ) then
                    e = fault( e, s )
                end
            end -- for i
            if e then
                r = failure( "dupOpt", e, options )
            else
                r = { }
                for i = 1, #base do
                    table.insert( r, base[ i ] )
                end -- for i
                for i = 1, #extend do
                    table.insert( r, extend[ i ] )
                end -- for i
            end
        end
    end
    if options.low  and  type( r ) == "table" then
        r = flat( r, options )
    end
    return r
end -- fold()



local function form( light, options, frame )
    -- Run parameter analysis on current environment
    -- Precondition:
    --     light    -- true: template transclusion;  false: #invoke
    --     options  -- table or nil; optional details
    --                 options.mandatory
    --                 options.optional
    --     frame    -- object; #invoke environment, or false
    -- Postcondition:
    --     Return string with error message as configured;
    --            false if valid
    -- Uses:
    --     TemplatePar.framing()
    --     fold()
    --     fetch()
    --     fix()
    --     finalize()
    local duty, r
    if frame then
        TemplatePar.framing( frame )
    end
    if type( options ) == "table" then
        if type( options.mandatory ) ~= "table" then
            options.mandatory = { }
        end
        duty = options.mandatory
        if type( options.optional ) ~= "table" then
            options.optional = { }
        end
        r = fold( options )
    else
        options = { }
        duty    = { }
        r       = { }
    end
    if type( r ) == "table" then
        local got = fetch( light, options )
        if type( got ) == "table" then
            r = fix( r, duty, got, options )
        else
            r = got
        end
    end
    return finalize( r, options )
end -- form()



local function format( analyze, options )
    -- Check validity of a value
    -- Precondition:
    --     analyze  -- string to be analyzed
    --     options  -- table or nil; optional details
    --                 options.say
    --                 options.min
    --                 options.max
    -- Postcondition:
    --     Return string with error message as configured;
    --            false if valid or no answer permitted
    -- Uses:
    --     feasible()
    --     failure()
    local r = feasible( analyze, options, false )
    local show
    if options.min  and  not r then
        if type( options.min ) == "number" then
            if type( options.max ) == "number" then
                if options.max < options.min then
                    r = failure( "minmax",
                                 string.format( "%d > %d",
                                                options.min,
                                                options.max ),
                                 options )
                end
            end
            if #analyze < options.min  and  not r then
                show = " <" .. options.min
                if options.say then
                    show = string.format( "%s &quot;%s&quot;", show, options.say )
                end
                r = failure( "tooShort", show, options )
            end
        else
            r = failure( "invalidPar", "min", options )
        end
    end
    if options.max  and  not r then
        if type( options.max ) == "number" then
            if #analyze > options.max then
                show = " >" .. options.max
                if options.say then
                    show = string.format( "%s &quot;%s&quot;", show, options.say )
                end
                r = failure( "tooLong", show, options )
            end
        else
            r = failure( "invalidPar", "max", options )
        end
    end
    return r
end -- format()



local function formatted( assignment, access, options )
    -- Check validity of one particular parameter in a collection
    -- Precondition:
    --     assignment  -- collection
    --     access      -- id of parameter in collection
    --     options     -- table or nil; optional details
    -- Postcondition:
    --     Return string with error message as configured;
    --            false if valid or no answer permitted
    -- Uses:
    --     mw.text.trim()
    --     format()
    --     failure()
    local r = false
    if type( assignment ) == "table" then
        local story = assignment.args[ access ] or ""
        if type( access ) == "number" then
            story = mw.text.trim( story )
        end
        if type( options ) ~= "table" then
            options = { }
        end
        options.say = access
        r = format( story, options )
    end
    return r
end -- formatted()



local function furnish( frame, action )
    -- Prepare #invoke evaluation of .assert() or .valid()
    -- Precondition:
    --     frame    -- object; #invoke environment
    --     action   -- "assert" or "valid"
    -- Postcondition:
    --     Return string with error message or ""
    -- Uses:
    --     form()
    --     failure()
    --     finalize()
    --     TemplatePar.valid()
    --     TemplatePar.assert()
    local options = { mandatory = { "1" },
                      optional  = { "2",
                                    "cat",
                                    "errNS",
                                    "low",
                                    "max",
                                    "min",
                                    "format",
                                    "preview",
                                    "template" },
                      template  = string.format( "&#35;invoke:%s|%s|",
                                                 "TemplatePar",
                                                 action )
                    }
    local r       = form( false, options, frame )
    if not r then
        local s
        options = { cat      = frame.args.cat,
                    errNS    = frame.args.errNS,
                    low      = frame.args.low,
                    format   = frame.args.format,
                    preview  = frame.args.preview,
                    template = frame.args.template
                  }
        options = figure( frame.args[ 2 ], options )
        if type( frame.args.min ) == "string" then
            s = frame.args.min:match( "^%s*([0-9]+)%s*$" )
            if s then
                options.min = tonumber( s )
            else
                r = failure( "invalidPar",
                             "min=" .. frame.args.min,
                             options )
            end
        end
        if type( frame.args.max ) == "string" then
            s = frame.args.max:match( "^%s*([1-9][0-9]*)%s*$" )
            if s then
                options.max = tonumber( s )
            else
                r = failure( "invalidPar",
                             "max=" .. frame.args.max,
                             options )
            end
        end
        if r then
            r = finalize( r, options )
        else
            s = frame.args[ 1 ] or ""
            r = tonumber( s )
            if ( r ) then
                s = r
            end
            if action == "valid" then
                r = TemplatePar.valid( s, options )
            elseif action == "assert" then
                r = TemplatePar.assert( s, "", options )
            end
        end
    end
    return r or ""
end -- furnish()



TemplatePar.assert = function ( analyze, append, options )
    -- Perform parameter analysis on a single string
    -- Precondition:
    --     analyze  -- string to be analyzed
    --     append   -- string: append error message, prepending <br />
    --                 false or nil: throw error with message
    --     options  -- table; optional details
    -- Postcondition:
    --     Return string with error message as configured;
    --            false if valid
    -- Uses:
    --     format()
    local r = format( analyze, options )
    if ( r ) then
        if ( type( append ) == "string" ) then
            if ( append ~= "" ) then
                r = string.format( "%s<br /> %s", append, r )
            end
        else
            error( r, 0 )
        end
    end
    return r
end -- TemplatePar.assert()



TemplatePar.check = function ( options )
    -- Run parameter analysis on current template environment
    -- Precondition:
    --     options  -- table or nil; optional details
    --                 options.mandatory
    --                 options.optional
    -- Postcondition:
    --     Return string with error message as configured;
    --            false if valid
    -- Uses:
    --     form()
    return form( true, options, false )
end -- TemplatePar.check()



TemplatePar.count = function ()
    -- Return number of template parameters
    -- Postcondition:
    --     Return number, starting at 0
    -- Uses:
    --     mw.getCurrentFrame()
    --     frame:getParent()
    local k, v
    local r = 0
    local t = mw.getCurrentFrame():getParent()
    local o = t.args
    for k, v in pairs( o ) do
        r = r + 1
    end -- for k, v
    return r
end -- TemplatePar.count()



TemplatePar.countNotEmpty = function ()
    -- Return number of template parameters with more than whitespace
    -- Postcondition:
    --     Return number, starting at 0
    -- Uses:
    --     mw.getCurrentFrame()
    --     frame:getParent()
    local k, v
    local r = 0
    local t = mw.getCurrentFrame():getParent()
    local o = t.args
    for k, v in pairs( o ) do
        if not v:match( "^%s*$" ) then
            r = r + 1
        end
    end -- for k, v
    return r
end -- TemplatePar.countNotEmpty()



TemplatePar.downcase = function ( options )
    -- Return all template parameters with downcased name
    -- Precondition:
    --     options  -- table or nil; optional messaging details
    -- Postcondition:
    --     Return table, may be empty; or string with error message.
    -- Uses:
    --     mw.getCurrentFrame()
    --     frame:getParent()
    --     flat()
    local t = mw.getCurrentFrame():getParent()
    return flat( t.args, options )
end -- TemplatePar.downcase()



TemplatePar.valid = function ( access, options )
    -- Check validity of one particular template parameter
    -- Precondition:
    --     access   -- id of parameter in template transclusion
    --                 string or number
    --     options  -- table or nil; optional details
    -- Postcondition:
    --     Return string with error message as configured;
    --            false if valid or no answer permitted
    -- Uses:
    --     mw.text.trim()
    --     TemplatePar.downcase()
    --     TemplatePar.framing()
    --     frame:getParent()
    --     formatted()
    --     failure()
    --     finalize()
    local r = type( access )
    if r == "string" then
        r = mw.text.trim( access )
        if #r == 0 then
            r = false
        end
    elseif r == "number" then
        r = access
    else
        r = false
    end
    if r then
        local params
        if type( options ) ~= "table" then
            options = { }
        end
        if options.low then
            params = TemplatePar.downcase( options )
        else
            params = TemplatePar.framing():getParent()
        end
        r = formatted( params, access, options )
    else
        r = failure( "noname", false, options )
    end
    return finalize( r, options )
end -- TemplatePar.valid()



TemplatePar.verify = function ( options )
    -- Perform #invoke parameter analysis
    -- Precondition:
    --     options  -- table or nil; optional details
    -- Postcondition:
    --     Return string with error message as configured;
    --            false if valid
    -- Uses:
    --     form()
    return form( false, options, false )
end -- TemplatePar.verify()



TemplatePar.framing = function( frame )
    -- Ensure availability of frame object
    -- Precondition:
    --     frame  -- object; #invoke environment, or false
    -- Postcondition:
    --     Return frame object
    -- Uses:
    --     >< Local.frame
    if not Local.frame then
        if type( frame ) == "table"  and
           type( frame.args ) == "table"  and
           type( frame.getParent ) == "function"  and
           type( frame:getParent() ) == "table"  and
           type( frame:getParent().getParent ) == "function"  and
           type( frame:getParent():getParent() ) == "nil" then
            Local.frame = frame
        else
            Local.frame = mw.getCurrentFrame()
        end
    end
    return Local.frame
end -- TemplatePar.framing()



Failsafe.failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     atleast  -- string, with required version
    --                         or wikidata|item|~|@ or false
    -- Postcondition:
    --     Returns  string  -- with queried version/item, also if problem
    --              false   -- if appropriate
    -- 2020-08-17
    local since = atleast
    local last    = ( since == "~" )
    local linked  = ( since == "@" )
    local link    = ( since == "item" )
    local r
    if last  or  link  or  linked  or  since == "wikidata" then
        local item = Failsafe.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local suited = string.format( "Q%d", item )
            if link then
                r = suited
            else
                local entity = mw.wikibase.getEntity( suited )
                if type( entity ) == "table" then
                    local seek = Failsafe.serialProperty or "P348"
                    local vsn  = entity:formatPropertyValues( seek )
                    if type( vsn ) == "table"  and
                       type( vsn.value ) == "string"  and
                       vsn.value ~= "" then
                        if last  and  vsn.value == Failsafe.serial then
                            r = false
                        elseif linked then
                            if mw.title.getCurrentTitle().prefixedText
                               ==  mw.wikibase.getSitelink( suited ) then
                                r = false
                            else
                                r = suited
                            end
                        else
                            r = vsn.value
                        end
                    end
                end
            end
        end
    end
    if type( r ) == "nil" then
        if not since  or  since <= Failsafe.serial then
            r = Failsafe.serial
        else
            r = false
        end
    end
    return r
end -- Failsafe.failsafe()



-- Provide external access
local p = {}



function p.assert( frame )
    -- Perform parameter analysis on some single string
    -- Precondition:
    --     frame  -- object; #invoke environment
    -- Postcondition:
    --     Return string with error message or ""
    -- Uses:
    --     furnish()
    return furnish( frame, "assert" )
end -- p.assert()



function p.check( frame )
    -- Check validity of template parameters
    -- Precondition:
    --     frame  -- object; #invoke environment
    -- Postcondition:
    --     Return string with error message or ""
    -- Uses:
    --     form()
    --     fill()
    local options = { optional  = { "all",
                                    "opt",
                                    "cat",
                                    "errNS",
                                    "low",
                                    "format",
                                    "preview",
                                    "template" },
                      template  = "&#35;invoke:TemplatePar|check|"
                    }
    local r = form( false, options, frame )
    if not r then
        options = { mandatory = fill( frame.args.all ),
                    optional  = fill( frame.args.opt ),
                    cat       = frame.args.cat,
                    errNS     = frame.args.errNS,
                    low       = frame.args.low,
                    format    = frame.args.format,
                    preview   = frame.args.preview,
                    template  = frame.args.template
                  }
        r       = form( true, options, frame )
    end
    return r or ""
end -- p.check()



function p.count( frame )
    -- Count number of template parameters
    -- Postcondition:
    --     Return string with digits including "0"
    -- Uses:
    --     TemplatePar.count()
    return tostring( TemplatePar.count() )
end -- p.count()



function p.countNotEmpty( frame )
    -- Count number of template parameters which are not empty
    -- Postcondition:
    --     Return string with digits including "0"
    -- Uses:
    --     TemplatePar.countNotEmpty()
    return tostring( TemplatePar.countNotEmpty() )
end -- p.countNotEmpty()



function p.match( frame )
    -- Combined analysis of parameters and their values
    -- Precondition:
    --     frame  -- object; #invoke environment
    -- Postcondition:
    --     Return string with error message or ""
    -- Uses:
    --     TemplatePar.framing()
    --     mw.text.trim()
    --     mw.ustring.lower()
    --     failure()
    --     form()
    --     TemplatePar.downcase()
    --     figure()
    --     feasible()
    --     fault()
    --     finalize()
    local r = false
    local options = { cat      = frame.args.cat,
                      errNS    = frame.args.errNS,
                      low      = frame.args.low,
                      format   = frame.args.format,
                      preview  = frame.args.preview,
                      template = frame.args.template
                    }
    local k, v, s
    local params = { }
    TemplatePar.framing( frame )
    for k, v in pairs( frame.args ) do
        if type( k ) == "number" then
            s, v = v:match( "^ *([^=]+) *= *(%S.*%S*) *$" )
            if s then
                s = mw.text.trim( s )
                if s == "" then
                    s = false
                end
            end
            if s then
                if options.low then
                    s = mw.ustring.lower( s )
                end
                if params[ s ] then
                    s = params[ s ]
                    s[ #s + 1 ] = v
                else
                    params[ s ] = { v }
                end
            else
                r = failure( "invalidPar",  tostring( k ),  options )
                break -- for k, v
            end
        end
    end -- for k, v
    if not r then
        s = { }
        for k, v in pairs( params ) do
            s[ #s + 1 ] = k
        end -- for k, v
        options.optional = s
        r = form( true, options, frame )
    end
    if not r then
        local errMiss, errValues, lack, rule
        local targs = frame:getParent().args
        options.optional = nil
        if options.low then
            targs = TemplatePar.downcase()
        else
            targs = frame:getParent().args
        end
        errMiss   = false
        errValues = false
        for k, v in pairs( params ) do
            options.say = k
            s           = targs[ k ]
            if s then
                if s == "" then
                    lack = true
                else
                    lack = false
                end
            else
                s    = ""
                lack = true
            end
            for r, rule in pairs( v ) do
                options = figure( rule, options )
                r       = feasible( s, options, true )
                if r then
                    if lack then
                        if errMiss then
                            s       = "%s, &quot;%s&quot;"
                            errMiss = string.format( s, errMiss, k )
                        else
                            errMiss = string.format( "&quot;%s&quot;",
                                                     k )
                        end
                    elseif not errMiss then
                        errValues = fault( errValues, r )
                    end
                    break -- for r, rule
                end
            end -- for s, rule
        end -- for k, v
        r = ( errMiss or errValues )
        if r then
            if errMiss then
                r = failure( "undefined", errMiss, options )
            else
                r = failure( "invalid", errValues, options )
            end
            r = finalize( r, options )
        end
    end
    return r or ""
end -- p.match()



function p.valid( frame )
    -- Check validity of one particular template parameter
    -- Precondition:
    --     frame  -- object; #invoke environment
    -- Postcondition:
    --     Return string with error message or ""
    -- Uses:
    --     furnish()
    return furnish( frame, "valid" )
end -- p.valid()



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



function p.TemplatePar()
    -- Retrieve function access for modules
    -- Postcondition:
    --     Return table with functions
    return TemplatePar
end -- p.TemplatePar()



setmetatable( p,  { __call = function ( func, ... )
                                 setmetatable( p, nil )
                                 return Failsafe
                             end } )

return p