Modul:TemplatePar: Unterschied zwischen den Versionen

Aus FreeWiki
Zur Navigation springen Zur Suche springen
te>Leyo
K (Schützte „Modul:TemplatePar“: Häufig eingebundenes Modul ([Bearbeiten=Nur angemeldete, nicht neue Benutzer] (unbeschränkt) [Verschieben=Nur Administratoren] (unbeschränkt)))
te>PerfektesChaos
(update)
Zeile 1: Zeile 1:
--[=[ TemplatePar 2013-04-28
+
--[=[ TemplatePar 2013-05-03
 
Template parameter utility
 
Template parameter utility
 
* check
 
* check
 
* count
 
* count
 +
* valid
 +
* TemplatePar()
 
]=]
 
]=]
  
Zeile 8: Zeile 10:
  
 
-- Module globals
 
-- Module globals
local invokeFrame
+
local TemplatePar = { }
 +
local messagePrefix = "lua-module-TemplatePar-"
 
local l10nDef = {}
 
local l10nDef = {}
 
l10nDef[ "en" ] = {
 
l10nDef[ "en" ] = {
     dupOpt   = "TemplatePar#invoke: repeated optional parameter",
+
     dupOpt     = "TemplatePar#invoke: repeated optional parameter",
     empty     = "Error in template: undefined value for mandatory",
+
     empty       = "Error in template: undefined value for mandatory",
     undefined = "Error in template: mandatory parameter missing",
+
    invalid    = "Error in template: invalid parameter",
     unknown   = "Error in template: unknown parameter name"
+
    invalidPar  = "TemplatePar#invoke: invalid parameter",
 +
    noname      = "TemplatePar#invoke: missing parameter name",
 +
    tooLong    = "Error in template: parameter too long",
 +
    tooShort    = "Error in template: parameter too short",
 +
     undefined   = "Error in template: mandatory parameter missing",
 +
     unknown     = "Error in template: unknown parameter name",
 +
    unknownRule = "TemplatePar#invoke: unknown rule"
 
}
 
}
l10nDef[ "de" ] = {
+
l10nDef[ "de" ] = {
     dupOpt   = "TemplatePar#invoke: Wiederholter Optionsparameter",
+
     dupOpt     = "TemplatePar#invoke: Optionsparameter wiederholt",
     empty     = "Fehler bei Vorlage: Pflichtparameter ohne Wert",
+
     empty       = "Fehler bei Vorlage: Pflichtparameter ohne Wert",
     undefined = "Fehler bei Vorlage: fehlender Pflichtparameter",
+
    invalid    = "Fehler bei Vorlage: Parameter ungültig",
     unknown   = "Fehler bei Vorlage: Unbekannter Parametername"
+
    invalidPar  = "TemplatePar#invoke: Ungültiger Parameter",
 +
    noname      = "TemplatePar#invoke: Parametername nicht angegeben",
 +
    tooLong    = "Fehler bei Vorlage: Parameter zu lang",
 +
    tooShort    = "Fehler bei Vorlage: Parameter zu kurz",
 +
     undefined   = "Fehler bei Vorlage: Pflichtparameter fehlt",
 +
     unknown     = "Fehler bei Vorlage: Parametername unbekannt",
 +
    unknownRule = "TemplatePar#invoke: Unbekannte Regel"
 
}
 
}
 +
local Patterns = {
 +
    [ "ASCII" ]  = "^[ -~]*$",
 +
    [ "ASCII+" ]  = "^[ -~]+$",
 +
    [ "ASCII+1" ] = "^[!-~]+$",
 +
    [ "n" ]      = "^%-?[0-9]*$",        -- einzelnes Minus ausschließen
 +
    [ "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]+$",
 +
    [ "+" ]      = "%S"
 +
}
 +
 +
 +
 +
function trim( s )
 +
    -- Trim string
 +
    -- Precondition:
 +
    --    s  -- string or nil; to be trimmed
 +
    -- Postcondition:
 +
    --    Return trimmed string or nil
 +
    local r = s
 +
    if type( s ) == "string" then
 +
        if s:match( "^%s*$" ) then
 +
            r = ""
 +
        else
 +
            r = s:match( "^%s*(%S.*)$" )
 +
            r = r:match( "^(.*%S)%s*$" )
 +
        end
 +
    end
 +
    return r
 +
end -- trim()
 +
 +
 +
 +
local function factory( say )
 +
    -- Retrieve localized message string in content language
 +
    -- Precondition:
 +
    --    say  -- string; message ID
 +
    -- Postcondition:
 +
    --    Return some message string
 +
    -- Uses:
 +
    --    >  messagePrefix
 +
    --    >  l10nDef
 +
    local c = mw.language.getContentLanguage():getCode()
 +
    local m = mw.message.new( messagePrefix .. say )
 +
    local r = false
 +
    if m:isBlank() then
 +
        local l10n = l10nDef[ c ]
 +
        if not l10n then
 +
            l10n = l10nDef[ "en" ]
 +
        end
 +
        r = l10n[ say ]
 +
    else
 +
        m:inLanguage( c )
 +
        r = m:plain()
 +
    end
 +
    if not r then
 +
        r = "(((".. say .. ")))"
 +
    end
 +
    return r
 +
end -- factory()
  
  
  
local function failed( spec, suspect )
+
local function failure( spec, suspect, options )
     -- Submit error message
+
     -- Submit localized error message
 
     -- Precondition:
 
     -- Precondition:
 
     --    spec    -- string; message ID
 
     --    spec    -- string; message ID
 
     --    suspect  -- string or nil; additional information
 
     --    suspect  -- string or nil; additional information
 +
    --    options  -- table or nil; optional details
 +
    --                options.template
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Return string
 
     --    Return string
 
     -- Uses:
 
     -- Uses:
     --    >  invokeFrame
+
     --    factory()
    --    >  l10nDef
+
     local r = factory( spec )
     local r
+
     if type( options ) == "table" then
    local show = invokeFrame.args[ "template" ]
+
         if type( options.template ) == "string" then
    local l10n = mw.language.getContentLanguage()
+
            if #options.template > 0 then
     l10n = l10nDef[ l10n:getCode() ]
+
                r = r .. " (" .. options.template .. ")"
    if not l10n then
+
            end
         l10n = l10nDef[ "en" ]
+
        end
    end
 
    r = l10n[ spec ]
 
    if show then
 
        r = r .. " (" .. show .. ")"
 
 
     end
 
     end
 
     if suspect then
 
     if suspect then
Zeile 50: Zeile 127:
 
     end
 
     end
 
     return r
 
     return r
end -- failed()
+
end -- failure()
  
  
Zeile 97: Zeile 174:
  
 
local function fetch()
 
local function fetch()
     -- Return regular table with template parameters
+
     -- Return regular table with all template transclusion parameters
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Return table; whitespace-only values as false
 
     --    Return table; whitespace-only values as false
 
     -- Uses:
 
     -- Uses:
     --    >  invokeFrame
+
     --    mw.getCurrentFrame()
 
     --    frame:getParent()
 
     --    frame:getParent()
 
     local k, v
 
     local k, v
 
     local r = { }
 
     local r = { }
     local t = invokeFrame:getParent()
+
     local t = mw.getCurrentFrame():getParent()
 
     local o = t.args
 
     local o = t.args
 
     for k, v in pairs( o ) do
 
     for k, v in pairs( o ) do
Zeile 122: Zeile 199:
 
     return r
 
     return r
 
end -- fetch()
 
end -- fetch()
 
 
 
local function figure()
 
    -- Return number of template parameters
 
    -- Postcondition:
 
    --    Return number, starting at 0
 
    -- Uses:
 
    --    >  invokeFrame
 
    --    frame:getParent()
 
    local k, v
 
    local r = 0
 
    local t = invokeFrame:getParent()
 
    local o = t.args
 
    for k, v in pairs( o ) do
 
        r = r + 1
 
    end -- for k, v
 
    return r
 
end -- figure()
 
  
  
Zeile 165: Zeile 223:
 
     return r
 
     return r
 
end -- fill()
 
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.noError
 +
    --                options.cat
 +
    --                options.template
 +
    -- Postcondition:
 +
    --    Return string or false
 +
    local r = false
 +
    if submit then
 +
        local opt, s
 +
        if type( options ) == "table" then
 +
            opt = options
 +
        else
 +
            opt = { }
 +
        end
 +
        if opt.noError then
 +
            r = false
 +
        else
 +
            r = "<span class='error'>" .. submit .. "</span>"
 +
        end
 +
        s = opt.cat
 +
        if type( s ) == "string" then
 +
            if not r then
 +
              r = ""
 +
            end
 +
            if s:find( "@@@" ) then
 +
                if type( opt.template ) == "string" then
 +
                    s = s:gsub( "@@@", opt.template )
 +
                end
 +
            end
 +
            r = r .. "[[Category:" .. s .. "]]"
 +
        end
 +
    end
 +
    return r
 +
end -- finalize()
  
  
Zeile 231: Zeile 330:
  
  
local function fix( valid, duty )
+
local function fix( valid, duty, options )
     -- Perform parameter analysis
+
     -- Perform transclusion parameter analysis
 
     -- Precondition:
 
     -- Precondition:
     --    valid -- table; unique sequence of known parameters
+
     --    valid   -- table; unique sequence of known parameters
     --    duty   -- table; sequence of mandatory parameters
+
     --    duty     -- table; sequence of mandatory parameters
 +
    --    options  -- table or nil; optional details
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Return string as configured; empty if valid
 
     --    Return string as configured; empty if valid
 
     -- Uses:
 
     -- Uses:
    --    >  invokeFrame
 
 
     --    fetch()
 
     --    fetch()
 
     --    finder()
 
     --    finder()
 
     --    fault()
 
     --    fault()
     --    failed()
+
     --    failure()
 
     --    fed()
 
     --    fed()
 
     local k, v
 
     local k, v
Zeile 254: Zeile 353:
 
     end -- for k, v
 
     end -- for k, v
 
     if r then
 
     if r then
         r = failed( "unknown", r )
+
         r = failure( "unknown", r, options )
 
     else -- all names valid
 
     else -- all names valid
        -- avoid confusing consecutive error messages
 
 
         local i, s
 
         local i, s
 
         for i = 1, #duty do
 
         for i = 1, #duty do
Zeile 265: Zeile 363:
 
         end -- for i
 
         end -- for i
 
         if r then
 
         if r then
             r = failed( "undefined", r )
+
             r = failure( "undefined", r, options )
         else
+
         else -- all mandatory present
 
             for i = 1, #duty do
 
             for i = 1, #duty do
 
                 s = duty[ i ]
 
                 s = duty[ i ]
Zeile 274: Zeile 372:
 
             end -- for i
 
             end -- for i
 
             if r then
 
             if r then
                 r = failed( "empty", r )
+
                 r = failure( "empty", r, options )
 
             end
 
             end
 
         end
 
         end
 
     end
 
     end
     if r then
+
     return r
        if invokeFrame.args[ "noError" ] then
+
end -- fix()
             r = ""
+
 
 +
 
 +
 
 +
local function format( seek, options )
 +
    -- Check validity of one particular template parameter
 +
    -- Precondition:
 +
    --    seek    -- string non-empty; name of template parameter
 +
    --    options  -- table or nil; optional details
 +
    --                options.pattern
 +
    --                options.key
 +
    --                options.min
 +
    --                options.max
 +
    -- Postcondition:
 +
    --    Return string with error message as configured;
 +
    --            false if valid or no answer permitted
 +
    -- Uses:
 +
    --    >  Patterns
 +
    --    failure()
 +
    --    mw.ustring.match()
 +
    --    frame:getParent()
 +
    local r = false
 +
    local s
 +
    local scan  = false
 +
    local story = mw.getCurrentFrame():getParent()
 +
    story = ( story.args[ seek ] or "" )
 +
    if type( options.pattern ) == "string" then
 +
        if options.key then
 +
             r = failure( "duplicatedRule", false, options )
 +
        else
 +
            scan = options.pattern
 +
        end
 +
    else
 +
        if type( options.key ) == "string" then
 +
            s = trim( options.key )
 
         else
 
         else
             r = "<span class='error'>" .. r .. "</span>"
+
             s = "+"
 
         end
 
         end
         k = invokeFrame.args[ "cat" ]
+
         scan = Patterns[ s ]
         if k then
+
         if type( scan ) == "string" then
             if k:find( "@@@" ) then
+
             if s == "n" then
                 v = invokeFrame.args[ "template" ]
+
                 if trim( story ) == "-" then
                if v then
+
                    scan = false
                     k = k:gsub( "@@@", v )
+
                     r    = failure( "invalid",
 +
                                    "'" .. seek .. "'",
 +
                                    options )
 
                 end
 
                 end
 
             end
 
             end
             r = r .. "[[Category:" .. k .. "]]"
+
        else
 +
             r = failure( "unknownRule", s, options )
 +
        end
 +
    end
 +
    if scan then
 +
        if not mw.ustring.match( story, scan ) then
 +
            r = failure( "invalid",  "'" .. seek .. "'",  options )
 +
        end
 +
    end
 +
    if options.min  and  not r then
 +
        if type( options.min ) == "number" then
 +
            if #story < options.min then
 +
                r = failure( "tooShort",
 +
                            " <" .. options.min .. " '" .. seek .. "'",
 +
                            options )
 +
            end
 +
        else
 +
            r = failure( "invalidPar", "min", options )
 +
        end
 +
    end
 +
    if options.max  and  not r then
 +
        if type( options.max ) == "number" then
 +
            if #story > options.max then
 +
                r = failure( "tooLong",
 +
                            " >" .. options.max .. " '" .. seek .. "'",
 +
                            options )
 +
            end
 +
        else
 +
            r = failure( "invalidPar", "max", options )
 
         end
 
         end
    else
 
        r = ""
 
 
     end
 
     end
 
     return r
 
     return r
end -- fix()
+
end -- format()
  
  
  
local function force()
+
TemplatePar.check = function ( options )
     -- Initialize parameter analysis
+
     -- Run parameter analysis
 +
    -- Precondition:
 +
    --    options  -- table or nil; optional details
 +
    --                options.mandatory
 +
    --                options.optional
 
     -- Postcondition:
 
     -- Postcondition:
     --    Return string as configured; empty if valid
+
     --    Return string with error message as configured;
 +
    --            false if valid or no answer permitted
 
     -- Uses:
 
     -- Uses:
    --    >  invokeFrame
 
    --    fill()
 
 
     --    fit()
 
     --    fit()
     --    failed()
+
     --    failure()
 
     --    fix()
 
     --    fix()
     local duty   = fill( invokeFrame.args[ 1 ] )
+
    --    finalize()
    local options = fill( invokeFrame.args[ 2 ] )
+
     local duty, r
    local r       = fit( duty, options )
+
    if type( options ) == "table" then
 +
        if type( options.mandatory ) == "table" then
 +
            duty = options.mandatory
 +
        else
 +
            duty = { }
 +
        end
 +
        if type( options.optional ) ~= "table" then
 +
            options.optional = { }
 +
        end
 +
        r = fit( duty, options.optional )
 +
    else
 +
        duty = { }
 +
        r    = { }
 +
    end
 
     if type( r ) == "string" then
 
     if type( r ) == "string" then
         r = failed( "dupOpt", r )
+
         r = failure( "dupOpt", r, options )
 
     else
 
     else
         r = fix( r, duty )
+
         r = fix( r, duty, options )
 
     end
 
     end
 +
    return finalize( r, options )
 +
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
 
     return r
end -- force()
+
end -- TemplatePar.count()
 +
 
 +
 
  
 +
TemplatePar.valid = function ( seek, options )
 +
    -- Check validity of one particular template parameter
 +
    -- Precondition:
 +
    --    seek    -- string; name of template parameter
 +
    --    options  -- table or nil; optional details
 +
    -- Postcondition:
 +
    --    Return string with error message as configured;
 +
    --            false if valid or no answer permitted
 +
    -- Uses:
 +
    --    trim()
 +
    --    format()
 +
    --    failure()
 +
    --    finalize()
 +
    local r
 +
    if type( seek ) == "string" then
 +
        r = trim( seek )
 +
        if #r == 0 then
 +
            r = false
 +
        end
 +
    end
 +
    if r then
 +
        r = format( seek, options )
 +
    else
 +
        r = failure( "noname", false, options )
 +
    end
 +
    return finalize( r, options )
 +
end -- TemplatePar.valid()
  
  
-- Provide template access
 
  
 +
-- Provide external access
 
local p = {}
 
local p = {}
 +
 +
  
 
function p.check( frame )
 
function p.check( frame )
Zeile 336: Zeile 561:
 
     --    Return string with error message or ""
 
     --    Return string with error message or ""
 
     -- Uses:
 
     -- Uses:
     --    force()
+
     --    fill()
     --     < invokeFrame
+
     --     TemplatePar.check()
     invokeFrame = frame
+
     local options = { mandatory = fill( frame.args[ 1 ] ),
     return force()
+
                      optional  = fill( frame.args[ 2 ] ),
 +
                      cat      = frame.args.cat,
 +
                      noError  = frame.args.noError,
 +
                      template  = frame.args.template
 +
                    }
 +
     return TemplatePar.check( options ) or ""
 
end -- .check()
 
end -- .check()
 +
 +
  
 
function p.count( frame )
 
function p.count( frame )
 
     -- Count number of template parameters
 
     -- Count number of template parameters
    -- Precondition:
 
    --    frame  -- object; #invoke environment
 
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Return string with digits including "0"
 
     --    Return string with digits including "0"
 
     -- Uses:
 
     -- Uses:
     --    figure()
+
     --    TemplatePar.count()
    --      < invokeFrame
+
     return tostring( TemplatePar.count() )
    invokeFrame = frame
 
     return tostring( figure() )
 
 
end -- .count()
 
end -- .count()
 +
 +
  
 
function p.valid( frame )
 
function p.valid( frame )
     -- Check validity of one template parameter
+
     -- Check validity of one particular template parameter
 
     -- Precondition:
 
     -- Precondition:
 
     --    frame  -- object; #invoke environment
 
     --    frame  -- object; #invoke environment
Zeile 362: Zeile 592:
 
     --    Return string with error message or ""
 
     --    Return string with error message or ""
 
     -- Uses:
 
     -- Uses:
     --      < invokeFrame
+
     --     trim()
     invokeFrame = frame
+
    --    TemplatePar.valid()
     return "#invoke:TemplatePar|valid| Not yet available"
+
    local r = false
 +
    local s
 +
    local options = { cat     = frame.args.cat,
 +
                      noError  = frame.args.noError,
 +
                      template = frame.args.template
 +
                    }
 +
     s = trim( frame.args[ 2 ] )
 +
    if type( s ) == "string" then
 +
        local sub = s:match( "^/(.*%S)/$" )
 +
        if type( sub ) == "string" then
 +
            options.pattern = sub
 +
        else
 +
            options.key = s
 +
        end
 +
    end
 +
     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 = TemplatePar.valid( s, options ) or ""
 +
    end
 +
    return r
 
end -- .valid()
 
end -- .valid()
 +
 +
 +
 +
function p.TemplatePar()
 +
    -- Retrieve function access for modules
 +
    -- Postcondition:
 +
    --    Return table with functions
 +
    return TemplatePar
 +
end -- .TemplatePar()
 +
 +
  
 
return p
 
return p

Version vom 4. Mai 2013, 10:10 Uhr

--[=[ TemplatePar 2013-05-03 Template parameter utility

  • check
  • count
  • valid
  • TemplatePar()

]=]


-- Module globals local TemplatePar = { } local messagePrefix = "lua-module-TemplatePar-" local l10nDef = {} l10nDef[ "en" ] = {

   dupOpt      = "TemplatePar#invoke: repeated optional parameter",
   empty       = "Error in template: undefined value for mandatory",
   invalid     = "Error in template: invalid parameter",
   invalidPar  = "TemplatePar#invoke: invalid parameter",
   noname      = "TemplatePar#invoke: missing parameter name",
   tooLong     = "Error in template: parameter too long",
   tooShort    = "Error in template: parameter too short",
   undefined   = "Error in template: mandatory parameter missing",
   unknown     = "Error in template: unknown parameter name",
   unknownRule = "TemplatePar#invoke: unknown rule"

} l10nDef[ "de" ] = {

   dupOpt      = "TemplatePar#invoke: Optionsparameter wiederholt",
   empty       = "Fehler bei Vorlage: Pflichtparameter ohne Wert",
   invalid     = "Fehler bei Vorlage: Parameter ungültig",
   invalidPar  = "TemplatePar#invoke: Ungültiger Parameter",
   noname      = "TemplatePar#invoke: Parametername nicht angegeben",
   tooLong     = "Fehler bei Vorlage: Parameter zu lang",
   tooShort    = "Fehler bei Vorlage: Parameter zu kurz",
   undefined   = "Fehler bei Vorlage: Pflichtparameter fehlt",
   unknown     = "Fehler bei Vorlage: Parametername unbekannt",
   unknownRule = "TemplatePar#invoke: Unbekannte Regel"

} local Patterns = {

   [ "ASCII" ]   = "^[ -~]*$",
   [ "ASCII+" ]  = "^[ -~]+$",
   [ "ASCII+1" ] = "^[!-~]+$",
   [ "n" ]       = "^%-?[0-9]*$",        -- einzelnes Minus ausschließen
   [ "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]+$",
   [ "+" ]       = "%S"

}


function trim( s )

   -- Trim string
   -- Precondition:
   --     s  -- string or nil; to be trimmed
   -- Postcondition:
   --     Return trimmed string or nil
   local r = s
   if type( s ) == "string" then
       if s:match( "^%s*$" ) then
           r = ""
       else
           r = s:match( "^%s*(%S.*)$" )
           r = r:match( "^(.*%S)%s*$" )
       end
   end
   return r

end -- trim()


local function factory( say )

   -- Retrieve localized message string in content language
   -- Precondition:
   --     say  -- string; message ID
   -- Postcondition:
   --     Return some message string
   -- Uses:
   --     >  messagePrefix
   --     >  l10nDef
   local c = mw.language.getContentLanguage():getCode()
   local m = mw.message.new( messagePrefix .. say )
   local r = false
   if m:isBlank() then
       local l10n = l10nDef[ c ]
       if not l10n then
           l10n = l10nDef[ "en" ]
       end
       r = l10n[ say ]
   else
       m:inLanguage( c )
       r = m:plain()
   end
   if not r then
       r = "(((".. say .. ")))"
   end
   return r

end -- factory()


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 = r .. " (" .. options.template .. ")"
           end
       end
   end
   if suspect then
       r = r .. " " .. suspect
   end
   return r

end -- failure()


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 = store .. "; " .. s
   else
       r = s
   end
   return r

end -- fault()


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
   for k, v in pairs( haystack ) do
       if k == needle then
           return true
       end
   end -- for k, v
   return false

end -- fed()


local function fetch()

   -- Return regular table with all template transclusion parameters
   -- Postcondition:
   --    Return table; whitespace-only values as false
   -- Uses:
   --     mw.getCurrentFrame()
   --     frame:getParent()
   local k, v
   local r = { }
   local t = mw.getCurrentFrame():getParent()
   local o = t.args
   for k, v in pairs( o ) 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
   return r

end -- fetch()


local function fill( specified )

   -- Split requirement string separated by '='
   -- Precondition:
   --     specified  -- string or nil; requested parameter set
   -- Postcondition:
   --    Return sequence table
   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.noError
   --                 options.cat
   --                 options.template
   -- Postcondition:
   --     Return string or false
   local r = false
   if submit then
       local opt, s
       if type( options ) == "table" then
           opt = options
       else
           opt = { }
       end
       if opt.noError then
           r = false
       else
           r = "" .. submit .. ""
       end
       s = opt.cat
       if type( s ) == "string" then
           if not r then
              r = ""
           end
           if s:find( "@@@" ) then
               if type( opt.template ) == "string" then
                   s = s:gsub( "@@@", opt.template )
               end
           end
           r = r .. ""
       end
   end
   return r

end -- finalize()


local function finder( haystack, needle )

   -- Find needle in haystack sequence
   -- Precondition:
   --     haystack  -- table; sequence of key names
   --     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 fit( base, extend )

   -- Merge two tables, create new sequence if both not empty
   -- Precondition:
   --     base    -- table; sequence kept unchanged
   --     extend  -- table; sequence to be appended
   -- Postcondition:
   --     Return merged table, or message string if duplicated entries
   -- Uses:
   --     finder()
   --     fault()
   local r
   if #base == 0 then
       if #extend == 0 then
           r = { }
       else
           r = extend
       end
   else
       if #extend == 0 then
           r = base
       else
           local i, s
           r = false
           for i = 1, #extend do
               s = extend[ i ]
               if finder( base, s ) then
                   r = fault( r, s )
               end
           end -- for i
           if not r then
               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
   return r

end -- fit()


local function fix( valid, duty, options )

   -- Perform transclusion parameter analysis
   -- Precondition:
   --     valid    -- table; unique sequence of known parameters
   --     duty     -- table; sequence of mandatory parameters
   --     options  -- table or nil; optional details
   -- Postcondition:
   --     Return string as configured; empty if valid
   -- Uses:
   --     fetch()
   --     finder()
   --     fault()
   --     failure()
   --     fed()
   local k, v
   local r   = false
   local got = fetch()
   for k, v in pairs( got ) do
       if not finder( valid, k ) then
           r = fault( r, k )
       end
   end -- for k, v
   if r then
       r = failure( "unknown", 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 format( seek, options )

   -- Check validity of one particular template parameter
   -- Precondition:
   --     seek     -- string non-empty; name of template parameter
   --     options  -- table or nil; optional details
   --                 options.pattern
   --                 options.key
   --                 options.min
   --                 options.max
   -- Postcondition:
   --     Return string with error message as configured;
   --            false if valid or no answer permitted
   -- Uses:
   --     >  Patterns
   --     failure()
   --     mw.ustring.match()
   --     frame:getParent()
   local r = false
   local s
   local scan  = false
   local story = mw.getCurrentFrame():getParent()
   story = ( story.args[ seek ] or "" )
   if type( options.pattern ) == "string" then
       if options.key then
           r = failure( "duplicatedRule", false, options )
       else
           scan = options.pattern
       end
   else
       if type( options.key ) == "string" then
           s = trim( options.key )
       else
           s = "+"
       end
       scan = Patterns[ s ]
       if type( scan ) == "string" then
           if s == "n" then
               if trim( story ) == "-" then
                   scan = false
                   r    = failure( "invalid",
                                   "'" .. seek .. "'",
                                   options )
               end
           end
       else
           r = failure( "unknownRule", s, options )
       end
   end
   if scan then
       if not mw.ustring.match( story, scan ) then
           r = failure( "invalid",  "'" .. seek .. "'",  options )
       end
   end
   if options.min  and  not r then
       if type( options.min ) == "number" then
           if #story < options.min then
               r = failure( "tooShort",
                            " <" .. options.min .. " '" .. seek .. "'",
                            options )
           end
       else
           r = failure( "invalidPar", "min", options )
       end
   end
   if options.max  and  not r then
       if type( options.max ) == "number" then
           if #story > options.max then
               r = failure( "tooLong",
                            " >" .. options.max .. " '" .. seek .. "'",
                            options )
           end
       else
           r = failure( "invalidPar", "max", options )
       end
   end
   return r

end -- format()


TemplatePar.check = function ( options )

   -- Run parameter analysis
   -- Precondition:
   --     options  -- table or nil; optional details
   --                 options.mandatory
   --                 options.optional
   -- Postcondition:
   --     Return string with error message as configured;
   --            false if valid or no answer permitted
   -- Uses:
   --     fit()
   --     failure()
   --     fix()
   --     finalize()
   local duty, r
   if type( options ) == "table" then
       if type( options.mandatory ) == "table" then
           duty = options.mandatory
       else
           duty = { }
       end
       if type( options.optional ) ~= "table" then
           options.optional = { }
       end
       r = fit( duty, options.optional )
   else
       duty = { }
       r    = { }
   end
   if type( r ) == "string" then
       r = failure( "dupOpt", r, options )
   else
       r = fix( r, duty, options )
   end
   return finalize( r, options )

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.valid = function ( seek, options )

   -- Check validity of one particular template parameter
   -- Precondition:
   --     seek     -- string; name of template parameter
   --     options  -- table or nil; optional details
   -- Postcondition:
   --     Return string with error message as configured;
   --            false if valid or no answer permitted
   -- Uses:
   --     trim()
   --     format()
   --     failure()
   --     finalize()
   local r
   if type( seek ) == "string" then
       r = trim( seek )
       if #r == 0 then
           r = false
       end
   end
   if r then
       r = format( seek, options )
   else
       r = failure( "noname", false, options )
   end
   return finalize( r, options )

end -- TemplatePar.valid()


-- Provide external access local p = {}


function p.check( frame )

   -- Check validity of template parameters
   -- Precondition:
   --     frame  -- object; #invoke environment
   -- Postcondition:
   --     Return string with error message or ""
   -- Uses:
   --     fill()
   --     TemplatePar.check()
   local options = { mandatory = fill( frame.args[ 1 ] ),
                     optional  = fill( frame.args[ 2 ] ),
                     cat       = frame.args.cat,
                     noError   = frame.args.noError,
                     template  = frame.args.template
                   }
   return TemplatePar.check( options ) or ""

end -- .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 -- .count()


function p.valid( frame )

   -- Check validity of one particular template parameter
   -- Precondition:
   --     frame  -- object; #invoke environment
   -- Postcondition:
   --     Return string with error message or ""
   -- Uses:
   --     trim()
   --     TemplatePar.valid()
   local r = false
   local s
   local options = { cat      = frame.args.cat,
                     noError  = frame.args.noError,
                     template = frame.args.template
                   }
   s = trim( frame.args[ 2 ] )
   if type( s ) == "string" then
       local sub = s:match( "^/(.*%S)/$" )
       if type( sub ) == "string" then
           options.pattern = sub
       else
           options.key = s
       end
   end
   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 = TemplatePar.valid( s, options ) or ""
   end
   return r

end -- .valid()


function p.TemplatePar()

   -- Retrieve function access for modules
   -- Postcondition:
   --     Return table with functions
   return TemplatePar

end -- .TemplatePar()


return p