Eine freie Initiative von Menschen bei ![]() ![]() ![]() ![]() mit online Lesekreisen, Übungsgruppen, Vorträgen ... |
![]() |
Use Google Translate for a raw translation of our pages into more than 100 languages. Please note that some mistranslations can occur due to machine translation. |
Modul:TemplatePar
TemplatePar
– Modul mit Hilfsfunktionen für die Vorlagenprogrammierung; namentlich die Analyse der Parameter der umgebenden Vorlageneinbindung.
- check – Gesamt-Einbindung der Vorlage prüfen
- count – Anzahl der Parameter der Vorlage
- countNotEmpty – Anzahl der nicht-leeren Parameter der Vorlage
- match – Vorlageneinbindung mit gefordertem Profil abgleichen
- valid – Einzelnen Parameterwert prüfen
Insbesondere sollen den Anwendern für Standardfälle der Parameteranalyse auf einfache Weise Standard-Fehlermeldungen dargestellt werden.
Funktionen für Vorlagen
Einzelfunktionen
Alle Funktionen wirken hinsichtlich der analysierten Parameter auf die umgebende Vorlageneinbindung; die Parameter von #invoke
spezifizieren die Regeln für die Analyse.
assert
- Überprüfung einer beliebigen Zeichenkette nach den Regeln für das Parameterformat.
- 1
- Zu untersuchende Zeichenkette (Pflichtparameter).
- 2
- Format („Nur Ziffern“, „ASCII“, begrenzter Zeichensatz, Lua-pattern); siehe unten. Optional, aber sinnvollerweise in der Regel anzugeben.
check
- Überprüfung auf vorgesehene und unerwartete Parameter der Vorlageneinbindung; Vollständigkeit von Pflichtangaben. Details siehe unten.
- Eine Fehlermeldung wird mit
class=error
zurückgegeben. - Wenn nichts zurückgegeben wird, scheint alles in Ordnung zu sein.
- Parameter (alle optional):
- all
- Namen der Pflichtparameter; müssen auch mit Wert belegt sein.
- Untereinander durch Gleichheitszeichen
=
getrennt. - Fehlende Angaben lösen eine Standard-Fehlermeldung aus; wenn individuelle Fehlermeldungen gewünscht werden, sind sie unter opt aufzuführen und mittels Vorlagenprogrammierung zu analysieren.
- opt
- Namen der optionalen Parameter.
- Untereinander durch Gleichheitszeichen
=
getrennt. - low
- Ignoriere Groß- und Kleinschreibung.
- →Beispiele und Test.
count
- Anzahl der Parameter der umgebenden Vorlageneinbindung.
- Parameter: Keine (beim #invoke)
- Das Ergebnis ist eine Zahl ab
0
. - →Beispiele und Test.
countNotEmpty
- Anzahl der Parameter der umgebenden Vorlageneinbindung, die nicht leer sind (höchstens Leerzeichen oder Zeilenumbruch enthalten).
- Parameter: Keine (beim
#invoke
) - Das Ergebnis ist eine Zahl ab
0
. - →Beispiele und Test.
match
- Umgebende Vorlageneinbindung mit gefordertem Profil abgleichen.
- 1
- Regel im Format
1=
Parametername=
Spezifikation - Die Spezifikation entspricht der von valid.
- nnn…
- Wie 1 – beliebig viele unbenannte Parameter als Regeln; in beliebiger Reihenfolge und nicht lückenlos.
- Alle zulässigen Parameternamen müssen aufgelistet werden; optionale sind mit der Bedingung
*
anzugeben oder einer geeigneten Spezifikation zu unterwerfen. - Es wird eine Fehlermeldung (
class=error
) zurückgegeben, wenn etwas nicht stimmt; sonst nichts. - Fatale Fehler führen in der folgenden Reihenfolge zum Abbruch: Unbekannter Parametername – fehlender Parameter – ungültiger Parameterwert.
- →Beispiele und Test.
- Mittels der Funktion lässt sich durch einen einzigen Aufruf gleichzeitig für alle Parameter eine Prüfung ausführen auf
- Pflichtparameter vorhanden?
- keine unbekannten Parameternamen (Falschschreibung)?
- gültige Werte für jeden Parameter?
- Für jeden Parameternamen bei der Vorlageneinbindung muss mindestens eine Regel vorhanden sein. Gibt es für einen Vorlagenparameter keine Regel, ist er unbekannt und damit unzulässig.
- Für denselben Parameternamen kann es mehrere Regeln geben. Die Einhaltung der Regeln wird in der Reihenfolge geprüft, in der sie vereinbart werden.
- Die Reihenfolge der Parameternamen ist ohne Bedeutung.
valid
- Überprüfung eines einzelnen Parameterwerts.
- Eine Fehlermeldung wird mit
class=error
zurückgegeben. - Wenn nichts zurückgegeben wird, scheint alles in Ordnung zu sein.
- Parameter (bis auf 1 alle optional):
- 1
- Name des einzelnen Parameters.
- 2
- Format („Nur Ziffern“, „ASCII“, begrenzter Zeichensatz, Lua-pattern); siehe unten.
- min
- Mindestlänge ≥0.
- max
- Maximale Länge >0.
- low
- Ignoriere Groß- und Kleinschreibung.
- Aus der Gruppe 2, min, max sollte sinnvollerweise wenigstens eine angegeben werden.
- →Beispiele und Test.
Handhabung von Fehlern
Alle Funktionen, die auf einen Fehlerfall führen können (check, valid), unterstützen auch die nachstehenden Parameter:
- template
- Titel der für Autoren sichtbaren Vorlage (für Fehlermeldungen).
- Kann auch ein anderes Schlüsselwort sein; vor allem für Untervorlagen vorgesehen.
- Ein Wikilink ist darin ebenfalls möglich; etwa auf eine bestimmte Doku-, Hilfe- oder Projektseite. (Hier wäre – anders als beim Titel – natürlich der Namensraum anzugeben.)
- cat
- Titel einer Wartungskategorie.
- Im Fehlerfall wird diese aktiviert.
- Mit
errNS
lässt sich die Kategorisierung auf bestimmte Namensräume beschränken. - Falls der Titel die Zeichenkette
@@@
enthält undtemplate
gesetzt ist, wird dies durchtemplate
ersetzt. - errNS
- Leerzeichen-getrennte Auflistung von Namensraum-Nummern, auf die
cat
beschränkt bleibt. - format
- Fehlermeldung formatieren oder unterdrücken.
- Standardfall, Standardmeldung mit
class="error"
formatiert:- Parameter
format
nicht angegeben. |format=*|
- Parameter
- Unterdrücken (dann sollte
cat=
gesetzt sein):|format=|
|format=0|
|format=-|
- Fester Text, eigene Formatierung:
|format=<anfang>Fester Text</ende>|
- Freie Formatierung der Standardmeldung:
- Enthält
@@@
als Platzhalter für die unformatierte Standardmeldung. |format=<anfang>Eigener Text; @@@</ende>|
- Enthält
- Standardfall, Standardmeldung mit
- preview
- Unterdrückung der Fehlermeldung im Vorschaumodus unterdrücken, also trotz
|format=0|
usw. immer Standardmeldung anzeigen:|preview=1|
- Fester Text, eigene Formatierung im Vorschaumodus:
|preview=<anfang>Fester Text</ende>|
- Standardmeldung in eigener Formatierung im Vorschaumodus:
|preview=<anfang>Standardmeldung: @@@</ende>|
Parameterprüfung (check)
Anwendungsfall für check am Beispiel der {{Information}}:
{{#invoke:TemplatePar
|check
|all= Beschreibung= Quelle= Urheber=
|opt= Datum= Genehmigung= Andere Versionen= Anmerkungen=
|template=[[Vorlage:Information]]}}
- Weil der Name eines Vorlagenparameters kein Gleichheitszeichen
=
enthalten kann, werden diese zur Abtrennung der Namen benutzt. Leerzeichen vor und nach dem Namen werden ignoriert. Zusätzliche Gleichheitszeichen sind unproblematisch. - Die ersten drei Vorlagenparameter des Beispiels sind Pflichtparameter und müssen auch mit Wert angegeben werden.
- Alle bei der Vorlageneinbindung benutzten Parameter, die weder unter
all=
nochopt=
aufgeführt sind, lösen eine Fehlermeldung aus. - Die unbenannten Vorlagenparameter tragen ihre laufende Nummer als Namen; im Übrigen werden die Namen in der Fehlermeldung bezeichnet.
- Es gibt vier Fehlermeldungen:
- #invoke:TemplatePar * Optionsparameter wiederholt
- Beim Aufruf des Moduls wurde ein Name sowohl unter
all=
als auchopt=
angegeben.
- Beim Aufruf des Moduls wurde ein Name sowohl unter
- Fehler bei Vorlage * Parametername unbekannt
- Bei der Vorlageneinbindung wurde ein Parameter benutzt, der weder unter
all=
nochopt=
aufgeführt ist.
- Bei der Vorlageneinbindung wurde ein Parameter benutzt, der weder unter
- Fehler bei Vorlage * Pflichtparameter fehlt
- Bei der Vorlageneinbindung fehlt ein unter
all=
aufgeführter Parameter.
- Bei der Vorlageneinbindung fehlt ein unter
- Fehler bei Vorlage * Pflichtparameter ohne Wert
- Bei der Vorlageneinbindung ist ein unter
all=
aufgeführter Parameter nur mit Gleichheitszeichen, aber ohne sichtbaren Wert angegeben; oder ein unbenannter Parameter ist leer.
- Bei der Vorlageneinbindung ist ein unter
- #invoke:TemplatePar * Optionsparameter wiederholt
- Die Fehlermeldungen sind hierarchisch gestaffelt. Bei einem Schreibfehler, der zu einem Problem höherer Ordnung führt, könnten die weiteren lediglich Folgefehler sein, die sich mit Korrektur des Grundproblems von selbst erledigen. Es wird daher nur die wichtigste gefundene Fehlerkategorie angezeigt.
Parameterformat (valid)
Für die optionale Bedingung 2 in valid gibt es zwei Grundtypen:
- Schlüsselwort
- Erleichterung für Vorlagenprogrammierer; einige Ausdrücke mit Lua-Patterns nicht möglich.
- Siehe Tabelle.
- Spezielle Datentypen wie URL, DOI, ISBN, Datum sind nicht erwünscht. Hier müssten bei jedem neuen Typ alle Artikel (mehrere Hunderttausend) neu zusammengestellt werden. Hingegen gibt es darauf spezialisierte Funktionen wie in URLutil.
- Erleichterung für Vorlagenprogrammierer; einige Ausdrücke mit Lua-Patterns nicht möglich.
- Lua-Pattern (Ustring)
- Eingeschlossen in Schrägstriche, um sie von einem Schlüsselwort unterscheiden zu können und ggf. führende und schließende Leerzeichen bei Werten unbenannter Vorlagenparameter zu identifizieren.
- Die Zeichenketten
|
sowie{{
und}}
sind in der Vorlagenprogrammierung nicht möglich. Dafür sind zu ersetzen:|
→%!
{{
→%((
}}
→%))
Zur Spalte Implementierung siehe Wikipedia:Hilfe:Lua/Zeichenketten #Pattern
Schlüssel | Bedeutung | Implementierung |
---|---|---|
ASCII
|
ASCII-Zeichen (Codes 32–126), nur innerhalb einer Zeile, oder leer | ^[ -~]*$
|
ASCII+
|
wie vor; nicht leer | ^[ -~]+$
|
ASCII+1
|
in einem Wort; sonst wie vor und nicht leer | ^[!-~]+$
|
n
|
Nur ASCII-Ziffern 0–9, sowie vorangestelltes Vorzeichen ASCII-Minus, oder leer | ^[%-]?[0-9]*$ einzelnes Minus ausschließen |
n>0
|
Nur ASCII-Ziffern 0–9, ohne Vorzeichen, nicht leer und mindestens eine Ziffer nicht Null | ^[0-9]*[1-9][0-9]*$
|
N+
|
Wie n , aber führende Null nicht erlaubt und nicht leer
|
^[%-]?[1-9][0-9]*$
|
N>0
|
Wie n>0 , aber führende Null nicht erlaubt
|
^[1-9][0-9]*$
|
x
|
Hexadezimalzahl; ASCII-Ziffern 0–9 Buchstaben a–f A–F, oder leer | ^[0-9A-Fa-f]*$
|
x+
|
wie vor; nicht leer | ^[0-9A-Fa-f]+$
|
X
|
Hexadezimalzahl; ASCII-Ziffern 0–9 Buchstaben A–F, oder leer | ^[0-9A-F]*$
|
X+
|
wie vor; nicht leer | ^[0-9A-F]+$
|
0,0
|
Beliebige Zahl; auch kleiner Null; kann Komma enthalten; oder leer | ^[%-]?[0-9]*,?[0-9]*$ nicht nur Minus oder Komma |
0,0+
|
Zahl mit Komma und Nachkommastelle; auch kleiner Null | ^[%-]?[0-9]+,[0-9]+$ |
0,0+?
|
Beliebige Zahl; auch kleiner Null; kann Komma und Nachkommastelle enthalten; nicht leer | ^[%-]?[0-9]+,?[0-9]*$
|
0.0
|
Beliebige Zahl; auch kleiner Null; kann Dezimalpunkt enthalten; oder leer | ^[%-]?[0-9]*[%.]?[0-9]*$ nicht nur Minus oder Punkt |
0.0+
|
Zahl mit Dezimalpunkt und Dezimalstelle; auch kleiner Null | ^[%-]?[0-9]+%.[0-9]+$
|
0.0+?
|
Beliebige Zahl; auch kleiner Null; kann Dezimalpunkt und Dezimalstelle enthalten; nicht leer | ^[%-]?[0-9]+[%.]?[0-9]*$
|
.0+
|
Beliebige Zahl; auch kleiner Null; kann Dezimalpunkt und Dezimalstelle enthalten, aber nicht notwendigerweise mit Ziffer davor; nicht leer | ^[%-]?[0-9]*[%.]?[0-9]+$
|
aa
|
Mindestens zwei Buchstaben (nicht nur nebeneinander: N.N.) oder ein CJK | %a.*%a oder CJK |
ID
|
Identifizierer unter Einschränkungen, wie sie für viele Sprachen üblich sind: ASCII, beginnend mit Buchstaben; weiter Ziffern und Unterstreichungsstrich; oder leer. Die nachfolgenden ASCII-Wörter sind ebenfalls unter dem Aspekt solcher Bezeichner zu sehen; etwa als Komponente einer URL. |
^[A-Za-z]?[A-Za-z_0-9]*$
|
ID+
|
wie vor; aber nicht leer. | ^[A-Za-z][A-Za-z_0-9]*$
|
ABC
|
Wort in ASCII-Großbuchstaben; oder leer. | ^[A-Z]*$
|
ABC+
|
wie vor; aber nicht leer. | ^[A-Z]+$
|
Abc
|
Wort in ASCII-Buchstaben mit nur erstem Buchstaben groß; oder leer. | ^[A-Z]*[a-z]*$
|
Abc+
|
wie vor; aber nicht leer. | ^[A-Z][a-z]+$
|
abc
|
Wort in ASCII-Kleinbuchstaben; oder leer. | ^[a-z]*$
|
abc+
|
wie vor; aber nicht leer. | ^[a-z]+$
|
aBc+
|
Wort in ASCII-Buchstaben mit lower camel casing; nicht leer. | ^[a-z]+[A-Z][A-Za-z]*$
|
base64
|
Base64; oder leer. | ^[A-Za-z0-9%+/]*$
|
base64+
|
wie vor; aber nicht leer. | ^[A-Za-z0-9%+/]+$
|
pagename
|
Zulässiger Seitenname; nicht leer. | nicht #<>[]|{} oder 12710 oder 1–3110
|
|
Zulässiger Titel oder Name einer Mediendatei bei
Als | |
|
Bedingung: Arithmetischer Vergleich oder (auch) numerische Ungleichheit | |
*
|
leer oder beliebig | |
+ nichts |
nicht leer | %S
|
In Planung | ||
date
|
Irgendein bekanntes Format für Datum und auch Zeit. | |
Hier eher nicht vorgesehen | ||
|
Beispiele (Testseite)
Eine Testseite illustriert praktische Beispiele.
--[=[ TemplatePar 2015-02-14 Template parameter utility * assert * check * count * countNotEmpty * downcase() * match * valid * verify() * TemplatePar() ]=] -- Module globals local TemplatePar = { } local MessagePrefix = "lua-module-TemplatePar-" local L10nDef = {} L10nDef.en = { badPattern = "#invoke:TemplatePar pattern syntax error", dupOpt = "#invoke:TemplatePar repeated optional parameter", dupRule = "#invoke:TemplatePar conflict key/pattern", empty = "Error in template * undefined value for mandatory", invalid = "Error in template * invalid parameter", invalidPar = "#invoke:TemplatePar invalid parameter", minmax = "#invoke:TemplatePar min > max", missing = "#invoke:TemplatePar missing library", multiSpell = "Error in template * multiple spelling of parameter", noMSGnoCAT = "#invoke:TemplatePar neither message nor category", noname = "#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", undefined = "Error in template * mandatory parameter missing", unknown = "Error in template * unknown parameter name", unknownRule = "#invoke:TemplatePar unknown rule" } L10nDef.de = { badPattern = "#invoke:TemplatePar Syntaxfehler des pattern", dupOpt = "#invoke:TemplatePar Optionsparameter wiederholt", dupRule = "#invoke:TemplatePar Konflikt key/pattern", empty = "Fehler bei Vorlage * Pflichtparameter ohne Wert", invalid = "Fehler bei Vorlage * Parameter ungültig", invalidPar = "#invoke:TemplatePar Ungültiger Parameter", minmax = "#invoke:TemplatePar min > max", multiSpell = "Fehler bei Vorlage * Mehrere Parameter-Schreibweisen", noMSGnoCAT = "#invoke:TemplatePar weder Meldung noch Kategorie", noname = "#invoke:TemplatePar Parameter nicht angegeben", notFound = "Fehler bei Vorlage * Seite fehlt", 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 = "#invoke:TemplatePar Unbekannte Regel" } 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 ), [ "+" ] = "%S" } local patternCJK = false local function containsCJK( s ) -- Is any CJK character present? -- Precondition: -- s -- string -- Postcondition: -- Return false iff no CJK present -- Uses: -- >< patternCJK -- mw.ustring.char() -- mw.ustring.match() local r = false if not patternCJK then patternCJK = mw.ustring.char( 91, 13312, 45, 40959, 131072, 45, 178207, 93 ) end if mw.ustring.match( s, 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 -- FileMedia.isType() local r if attempt and attempt ~= "" then local lucky, FileMedia = pcall( require, "Module:FileMedia" ) if type( FileMedia ) == "table" then FileMedia = FileMedia.FileMedia() 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: -- > MessagePrefix -- > L10nDef -- mw.language.getContentLanguage() -- mw.message.new() 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 = string.format( "(((%s)))", say ) end return r end -- factory() local function failsafe( 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 -- failsafe() 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 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: -- > Patterns -- failure() -- mw.text.trim() -- facility() -- failsafe() -- containsCJK() local r = false local s = false local show = nil local scan = false 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 = Patterns[ s ] end 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 scan = false if options.say then show = string.format( "'%s'", 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( analyze ) 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( "^image%+?:?$" ) or s:match( "^file%+?:?$" ) then r = facility( s, analyze ) n = true elseif s:match( "langW?%+?" ) then n = "lang" -- lang lang+ -- langW langW+ end if not n and not r then r = "unknownRule" 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 if scan then local legal, got = pcall( failsafe, analyze, scan ) if legal then if not got then if s == "aa" then got = containsCJK( analyze ) end if not got then if options.say then show = string.format( "'%s'", 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 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( 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() -- mw.getCurrentFrame() -- frame:getParent() local g, k, v local r = { } if options.low then g = TemplatePar.downcase( options ) else g = mw.getCurrentFrame() 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( "%%!", "|" ) sub = sub:gsub( "%%%(%(", "{{" ) sub = sub:gsub( "%%%)%)", "}}" ) 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, frame ) -- 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 -- frame -- object, or false -- Postcondition: -- Return string or false -- Uses: -- factory() local r = false if submit then local opt, s local lazy = false local show = false 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 if lazy then show = "" lazy = false end if not frame then frame = mw.getCurrentFrame() end if frame:preprocess( "{{REVISIONID}}" ) == "" then if s == "1" then show = "*" else show = s end 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 show = "<span class=\"error\">@@@</span>" 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 s = opt.cat if type( s ) == "string" then 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 opt.errNS = false end elseif st == "table" then for i = 1, #opt.errNS do if opt.errNS[ i ] == ns then opt.errNS = false break -- for i end end -- for i end end if opt.errNS then r = "" else if not r then r = "" end if s:find( "@@@" ) then if type( opt.template ) == "string" then s = s:gsub( "@@@", opt.template ) end end local i local 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 k, v local r = false 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", string.format( "'%s'", 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, or false -- Postcondition: -- Return string with error message as configured; -- false if valid -- Uses: -- fold() -- fetch() -- fix() -- finalize() local duty, r 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, frame ) 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 '%s'", 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 '%s'", 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( "#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, frame ) else s = frame.args[ 1 ] or "" r = tonumber( s ) if ( r ) then s = r end if action == "valid" then r = TemplatePar.valid( s, options, frame ) 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, frame ) -- Check validity of one particular template parameter -- Precondition: -- access -- id of parameter in template transclusion -- string or number -- options -- table or nil; optional details -- frame -- object; #invoke environment -- Postcondition: -- Return string with error message as configured; -- false if valid or no answer permitted -- Uses: -- mw.text.trim() -- TemplatePar.downcase() -- 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 = frame:getParent() end r = formatted( params, access, options ) else r = failure( "noname", false, options ) end return finalize( r, options, frame ) 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() -- 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 -- .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 = "#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 -- .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.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 -- .countNotEmpty() function p.match( frame ) -- Combined analysis of parameters and their values -- Postcondition: -- Return string with error message or "" -- Uses: -- 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 = { } 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 errValue = false 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 errMiss = string.format( "%s, '%s'", errMiss, k ) else errMiss = string.format( "'%s'", 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, frame ) end end return r or "" end -- .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 -- .valid() function p.TemplatePar() -- Retrieve function access for modules -- Postcondition: -- Return table with functions return TemplatePar end -- .TemplatePar() return p