Die Arbeit am allgemeinen Argumentparser xparse begann beim LaTeX-Team in den späten 1990er-Jahren; sein Herzstück \NewDocumentCommand wanderte erst mit der Ausgabe vom 1. Oktober 2020 aus einem experimentellen Paket in den LaTeX-Kernel. Für diese lange Lehrzeit gibt es einen Grund. \newcommand kann nur zählen, wie viele Argumente ein Befehl nimmt; \NewDocumentCommand beschreibt dagegen, welcher Art jedes einzelne ist – als Zeichenkette, die Argumentspezifikation (arg-spec) heißt. Der Schritt vom Zählen zum Beschreiben bringt Sternvarianten, mehrere voneinander unabhängige optionale Argumente und Argumente, die von selbst gewählten Begrenzern eingefasst werden: Eingabesyntaxen also, die sich mit \newcommand überhaupt nicht formulieren lassen. Diese Seite geht durch, was jeder Spezifizierer zusagt, wann \IfNoValueTF und wann \IfBooleanTF zuständig ist – und wann keines von beiden nötig ist.
Was \newcommand nicht ausdrücken kann: ein optionales Argument, nur an erster Stelle
\newcommand erzeugt genau eine Form: höchstens ein optionales Argument in eckigen Klammern, und zwar nur an erster Stelle, gefolgt von beliebig vielen Pflichtargumenten. Alles Reichhaltigere verlangte, so hält es LaTeX News 32 fest, den Abstieg zum TeX-Primitiv \def und zur Low-Level-Makroprogrammierung. Deshalb stecken die Quellen älterer Pakete voller handgeschriebener Vorrichtungen, die das nächste Token belauern: \@ifstar für einen Stern, \@ifnextchar für ein beliebiges Zeichen. Da diese Namen ein @ enthalten, müssen sie in \makeatletter eingefasst werden, sie zerbrechen leicht an Leerzeichen und Schachtelung, und lesbar sind sie ohnehin nicht.
\NewDocumentCommand ersetzt dieses Belauern durch eine deklarative Grammatik. Übergeben wird keine Zahl, sondern eine Buchstabenfolge; der Parser liest die Eingabe und liefert dem Rumpf stets normalisierte Argumente als #1, #2 und so fort. Damit ist die Schnittstelle, die Anwender sehen, vom implementierenden Code getrennt. Im Kernel steckt diese Mechanik im Modul ltcmd, und seit der Ausgabe vom 1. Oktober 2020 ist \usepackage{xparse} überflüssig. Das Paket xparse liegt zwar weiterhin auf CTAN, doch die README des Bündels l3packages, das es ausliefert, trägt inzwischen den Titel „Deprecated“ und hält fest, dass das Material nur noch zur Unterstützung älterer Dateien erhalten bleibt. Ausnahme sind die veralteten Argumenttypen g/G, l und u: Wer einen davon verwendet, erhält Invalid argument type "g" in command "\zzz" (requires xparse). Neuer Code hat praktisch keinen Grund, sie zu brauchen.
\NewDocumentCommand schreiben und der Unterschied zwischen New, Renew, Provide und Declare
Die Grundform nimmt drei Argumente: \NewDocumentCommand{\cmd}{⟨arg-spec⟩}{⟨Rumpf⟩} – Befehlsname, Argumentspezifikation und Rumpf, in dem die Argumente als #1, #2 und so weiter ankommen. Ein Wechsel des einleitenden Verbs ändert das Verhalten gegenüber einem bereits vergebenen Namen. Zielt \NewDocumentCommand auf einen belegten Namen, bricht es mit LaTeX cmd Error: Command "\section" already defined. ab. Das ist ein Sicherheitsnetz und keine Schikane: Zum Ersetzen einer vorhandenen Definition dient \RenewDocumentCommand, zum Definieren nur im Fall, dass noch nichts da ist, \ProvideDocumentCommand.
| Deklaration | Verhalten bei einem bereits vergebenen Namen |
|---|---|
\NewDocumentCommand | Bricht mit Fehler ab, wenn er belegt ist – die Standardwahl |
\RenewDocumentCommand | Fehler, wenn der Name noch frei ist; dient zum Umarbeiten vorhandener Befehle |
\ProvideDocumentCommand | Definiert nur, wenn nichts vorhanden ist; so füllen Pakete Kompatibilitätslücken |
\DeclareDocumentCommand | Überschreibt bedingungslos – die offizielle Dokumentation mahnt zu sparsamem Einsatz |
Befehle aus einer dieser vier Deklarationen bringen eine Eigenschaft mit, um die niemand bitten musste: Sie sind von Anfang an robust. Wendet man \meaning auf einen an, erscheint \protected macro:->… – der \protected-Mechanismus von ε-TeX erledigt die Arbeit auf Engine-Ebene. In einem beweglichen Argument, etwa einer Überschrift oder Bildunterschrift, ist deshalb kein vorangestelltes \protect nötig. Warum eine \newcommand-Definition genau dort zerbricht und was \DeclareRobustCommand dagegen unternahm, steht auf der Seite „Makros definieren“.
Die Argumentspezifizierer: wofür m, o, O{}, s, t, r, d, e, v und b stehen
Eine Argumentspezifikation ist eine Zeichenkette, in der ein Buchstabe genau ein Argument beschreibt; die Spezifizierer zerfallen in zwei Familien. Zur pflichtigen gehören m, r, R, v, b, zur optionalen o, O, d, D, s, t, e, E. Eine Regel durchzieht die ganze Menge: Ein Großbuchstabentyp erlaubt einen selbst gewählten Standardwert, sein Kleinbuchstaben-Gegenstück liefert stattdessen die Spezialmarke -NoValue-. Liest man o gegen O{...}, d gegen D, e gegen E, r gegen R, trifft das Muster jedes Mal zu. Intern, so die Dokumentation, sind o, d und O allesamt nur Abkürzungen für ein passend konstruiertes Argument vom Typ D.
| Spezifizierer | Bedeutung | Ankunft im Rumpf |
|---|---|---|
m | Pflichtargument: Klammergruppe oder einzelnes Token | Gewöhnliches #1, äußere Klammern entfernt |
r | r⟨d1⟩⟨d2⟩ – pflichtig, von selbst gewählten Begrenzern eingefasst | -NoValue- nach einem Fehler, wenn der Öffner fehlt |
R | R⟨d1⟩⟨d2⟩{Standard} – wie r, aber mit eigenem Ersatzwert | Der angegebene Standardwert, wenn es fehlt |
v | Verbatim-Argument wie bei \verb; als Begrenzer sind %, \, #, {, } und Leerzeichen ausgeschlossen | Die wörtlichen Zeichen; im Argument eines anderen Befehls nicht verwendbar |
b | Der Rumpf einer Umgebung; nur in \NewDocumentEnvironment und nur zuletzt | Alles zwischen \begin und \end |
o | Das übliche optionale [...]-Argument | -NoValue-, wenn nichts angegeben wurde |
O | O{Standard} – o mit Standardwert | Der Standardwert bei Abwesenheit; es liegt also stets ein Wert vor |
d | d⟨d1⟩⟨d2⟩ – optional, mit frei gewählten Begrenzern | -NoValue-, wenn nichts angegeben wurde |
D | D⟨d1⟩⟨d2⟩{Standard} – d mit Standardwert | Der Standardwert bei Abwesenheit |
s | Erkennt einen vorangestellten Stern * | Entweder \BooleanTrue oder \BooleanFalse |
t | t⟨Zeichen⟩ – prüft auf ein bestimmtes Zeichen; die Verallgemeinerung von s | Entweder \BooleanTrue oder \BooleanFalse |
e | e{⟨Tokens⟩} – ein Satz Verzierungen wie ^ und _; alle Tokens müssen verschieden sein | Ein Argument je Token, -NoValue- für jedes fehlende |
E | E{⟨Tokens⟩}{⟨Standardwerte⟩} – e mit Standardwerten | Ist die Liste kürzer, fällt der Rest auf -NoValue- zurück |
Die begrenzten Typen (r, R, d, D) bringen Einschränkungen mit, die man kennen sollte. Zum einen taugen TeX’ Gruppierungszeichen { und } nicht als Begrenzer: Wer r{} schreibt, wird mit LaTeX cmd Error: Argument delimiter "" invalid in command "\zzz". abgewiesen. Üblich sind natürlich gepaarte Zeichen wie [], (), <> oder "". Zum anderen merkt sich der Parser bei einem Zeichentoken als Begrenzer dessen Kategoriecode zum Definitionszeitpunkt. Wird < später zum Buchstaben erklärt, erkennt der Befehl genau dieses < nicht mehr als Begrenzer. Ein Kontrollwort als Begrenzer (etwa \x) bleibt davon unberührt, denn es wird über seinen Namen erkannt, unabhängig von der aktuellen Bedeutung.
% t<char> tests for one character; r()...() is a required delimited argument
\NewDocumentCommand{\pt}{t+ r()}{%
\IfBooleanTF{#1}{\mathbf{(#2)}}{(#2)}%
}
$\pt(1,2)$ % -> (1,2)
$\pt+(3,4)$ % -> (3,4) in bold
% e{^} picks up an optional ^ embellishment wherever it appears
\NewDocumentCommand{\deriv}{e{^} m m}{%
\frac{\mathrm{d}\IfNoValueF{#1}{^{#1}}#3}{\mathrm{d}#2\IfNoValueF{#1}{^{#1}}}%
}
$\deriv{x}{f}$ % -> df/dx
$\deriv^{2}{x}{f}$ % -> d^2 f / dx^2Die Modifizierer +, !, > und = vor einem Spezifizierer
+ macht ein Argument lang, es darf also eine Leerzeile und damit einen Absatzwechsel enthalten. Hier lauert die erste Falle für alle, die von \newcommand kommen: Die Voreinstellung ist umgekehrt. \newcommand macht sämtliche Argumente lang; wer sie kurz haben will, schreibt das gesternte \newcommand*. Bei \NewDocumentCommand ist es andersherum – Argumente sind standardmäßig kurz, und + steht vor jedem, das lang sein soll. Ein frisch portierter Befehl, dem ein Text mit Leerzeile übergeben wird, begrüßt einen deshalb mit ! Paragraph ended before \remark was complete. Die Feinsteuerung pro Argument ist dabei gerade der Gewinn: Eine einzige Deklaration kann festhalten, dass die Kurzüberschrift einen Absatz umfasst, der Fließtext dagegen mehrere.
Die übrigen drei sind rasch erklärt. ! verbietet ein Leerzeichen unmittelbar vor einem optionalen Argument und darf nur auf ein abschließendes optionales Argument angewandt werden – vorangestellt liefert es Invalid argument prefix "!" in command "\remark". Gebraucht wird es, wenn die Klammern in \foo{x} [x] als gewöhnlicher Text gelesen werden sollen. > schiebt einen Argumentprozessor dazwischen: Mit >{\SplitArgument{2}{;}} m wird a;b;c in drei Argumente zerlegt, bevor der Rumpf es überhaupt zu sehen bekommt. Der Kernel liefert \SplitArgument, \SplitList, \TrimSpaces, \ProcessList und \ReverseBoolean mit. = ist ein neuerer Modifizierer, der ein optionales Argument als Schlüssel-Wert-Paare deuten lässt; er existiert, damit Befehle mit langer Freitext-Tradition – \caption und die Gliederungsbefehle – eine Keyval-Schnittstelle bekommen können, ohne die alte Syntax zu brechen.
% + makes ONE argument long; ! on a trailing optional argument forbids a space
\NewDocumentCommand{\remark}{+m !o}{\par\textbf{Note.} #1 (#2)\par}
\remark{first paragraph
second paragraph}[tag]
\remark{x} [these brackets stay ordinary text]
% > runs a processor before the body sees the argument
\NewDocumentCommand{\triple}{>{\SplitArgument{2}{;}} m}{\tripleaux#1}
\NewDocumentCommand{\tripleaux}{m m m}{(#1/#2/#3)}
\triple{a;b;c} % -> (a/b/c)\IfNoValueTF gegen \IfBooleanTF und der Unterschied zwischen o und O{}
Es gibt zwei Familien von Tests, und der Spezifizierer entscheidet, welche zuständig ist. Für Typen, die -NoValue- liefern – o, d, e –, dient \IfNoValueTF{#1}{⟨falls fehlend⟩}{⟨falls vorhanden⟩}; für Typen mit Wahrheitswert – s, t – dient \IfBooleanTF{#1}{⟨wahr⟩}{⟨falsch⟩}. Das logisch umgekehrte \IfValueTF gibt es ebenfalls, und beide Familien bringen Einzweigformen mit: \IfNoValueT, \IfNoValueF, \IfValueT, \IfValueF, \IfBooleanT, \IfBooleanF. Warum \IfNoValueTF überhaupt erfunden werden musste, ist der interessante Punkt: Ein weggelassenes optionales Argument ist etwas anderes als ein leer übergebenes. Der Standardwert-Mechanismus von \newcommand kann diesen Unterschied gar nicht ausdrücken – der Standardwert erscheint einfach, und die Tatsache, dass nichts geschrieben wurde, erreicht den Rumpf nie.
-NoValue- ist eine gut gemachte Fälschungssicherung: Es ist so konstruiert, dass es nicht mit dem wörtlichen Text -NoValue- übereinstimmt, weshalb \IfNoValueTF{-NoValue-} logisch falsch ergibt. Ein Zeichenkettenvergleich taugt also nicht als Ersatz – geprüft wird immer mit \IfNoValueTF. Die klassische Falle ist die Verwechslung von o und O{}. Bei o ist ein ausgelassenes Argument tatsächlich -NoValue-, \IfNoValueTF verzweigt also richtig; bei O{} liegt immer ein Wert vor, bei Auslassung eben ein leerer, sodass \IfNoValueTF stets in den Falsch-Zweig läuft. Und wer die Prüfung ganz vergisst, erkennt es meist erst daran, dass im gesetzten PDF wörtlich -NoValue- steht.
Womit prüft man dann, ob ein O{}-Argument leer ist? Genau hier hat sich die offizielle Empfehlung im Juni 2022 geändert. Der Kernel stellt \IfBlankTF bereit (samt \IfBlankT und \IfBlankF); es meldet wahr, wenn das Argument wirklich leer ist oder nur Leerzeichen enthält. Für Entwürfe mit zwei aufeinanderfolgenden optionalen Argumenten empfiehlt die Dokumentation inzwischen O{} zusammen mit \IfBlankTF, statt getrennt auf Leere und auf -NoValue- zu prüfen. Weder \tl_if_blank:nTF aus expl3 noch \ifblank aus etoolbox muss dafür bemüht werden. Eine Feinheit: \IfBlankTF wertet einen Befehl wie \space als echten Inhalt – gedruckt wird ein Leerraum, als Token ist er dennoch Substanz.
% s = optional star, o = optional [..], m = mandatory
\NewDocumentCommand{\heading}{s o m}{%
\IfBooleanTF{#1}
{\section*{#3}}% starred: unnumbered
{\IfNoValueTF{#2}
{\section{#3}}% no short title given
{\section[#2]{#3}}}% short title for the ToC
}
\heading{A Long Introduction} % numbered section
\heading[Intro]{A Long Introduction} % short title in the table of contents
\heading*{Preface} % unnumbered
% with O{} the value is always there, so test for blankness instead
\NewDocumentCommand{\tagged}{O{} m}{\IfBlankTF{#1}{#2}{[#1] #2}}Im Überschriftenbeispiel steckt noch eine Eigenschaft, die \newcommand nicht nachahmen kann: Optionale Argumente aus \NewDocumentCommand lassen sich gefahrlos schachteln. Im Beispiel der Dokumentation wird \foo[\baz[stuff]]{more stuff} korrekt zerlegt, obwohl ein optionales Argument einen Befehl enthält, der selbst eines nimmt. Die Klammern einer \newcommand-Definition greifen naiv alles bis zum nächsten ], dieselbe Eingabe bricht dort also vorzeitig ab. Sobald ein Befehl mit optionalem Argument innerhalb eines anderen optionalen Arguments stehen soll, ist der Wechsel zu \NewDocumentCommand hinreichend begründet.
\NewDocumentEnvironment und der Typ b: den Umgebungsrumpf als Argument nehmen
Umgebungen erhalten dieselbe Mechanik über \NewDocumentEnvironment{⟨env⟩}{⟨arg-spec⟩}{⟨Startcode⟩}{⟨Endcode⟩} (nebst \Renew…, \Provide… und \Declare…). Die Argumente stehen unmittelbar nach \begin{⟨env⟩} und sind sowohl im Start- als auch im Endcode sichtbar. Und hier gibt es einen Spezifizierer, der auf der Befehlsseite kein Gegenstück hat: b, den Rumpf der Umgebung selbst. Steht b am Ende der Argumentspezifikation, kommt alles zwischen \begin und \end als ein einziges Argument an – bereit, umgeformt, zweimal gesetzt oder unter Bedingungen verworfen zu werden.
Mit b kommen drei Gepflogenheiten. Erstens wird der Rumpf standardmäßig an beiden Enden von Leerraum befreit, sodass die Leerzeichen an Zeilenenden niemanden beschäftigen müssen; wer das Trimmen unterbinden will, schreibt !b. Zweitens dient +b für einen Rumpf aus mehreren Absätzen. Drittens – und das gerät leicht in Vergessenheit – wird der Endcode bei b faktisch überflüssig, doch das leere vierte Argument muss trotzdem geschrieben werden; fehlt es, verzählt sich \NewDocumentEnvironment. Umgebungen mit b lassen sich ineinander schachteln. Die Grundlagen von \newenvironment und schlichte Umgebungen ohne b behandelt die Seite „Eigene Umgebungen“.
% b grabs the whole body; + allows several paragraphs; the empty 4th
% argument is still required even though there is no end code left to run
\NewDocumentEnvironment{shout}{O{\bfseries} +b}{#1#2}{}
\begin{shout}[\itshape]
Loud and clear.
\end{shout}Wann \NewExpandableDocumentCommand nötig ist: am Anfang einer Tabellenzelle und in \edef
Dass die Standarddeklaration robuste Befehle liefert – solche, die nicht beiläufig expandieren –, ist fast überall ein Vorteil und an wenigen Stellen ein Hindernis. Das praktischste Beispiel ist der Anfang einer Tabellenzelle: Die Mechanik von tabular verlangt, dass ein Befehl, der \multicolumn umhüllt, expandierbar ist, während ein Befehl aus \NewDocumentCommand die Expansion mit einem Engine-Merkmal absichtlich unterbindet. Dasselbe gilt, wenn der Inhalt in \edef oder \write festgeschrieben werden soll. Dafür existiert \NewExpandableDocumentCommand samt \Renew…, \Provide… und \Declare…. Die offizielle Dokumentation wird dabei deutlich: Diese Möglichkeit sollte nur bei echter Notwendigkeit genutzt werden, denn sie bringt Einschränkungen mit.
- Sind überhaupt Argumente vorhanden, muss das letzte
m,roderRsein – also einer der pflichtigen Typen. - Der Verbatim-Typ
vsteht nicht zur Verfügung, ebenso wenig die Argumentprozessoren mit>und der Keyval-Modifizierer=. \foo[und\foo{[}lassen sich nicht unterscheiden: Beide Klammern gelten als Beginn eines optionalen Arguments, weshalb dessen Erkennung weniger zuverlässig ist als bei der Standardform.- Die booleschen Typen
sundtfunktionieren dagegen sehr wohl.\IfBooleanTFist selbst expandierbar, die Verzweigung löst sich also auch innerhalb von\edefwie erwartet auf.
% a command wrapping \multicolumn must be expandable to work in a cell
\NewExpandableDocumentCommand{\wide}{m}{\multicolumn{3}{c}{#1}}
\begin{tabular}{lcr}
a & b & c \\
\wide{spans three columns} \\
\end{tabular}Was verwenden: \newcommand oder \NewDocumentCommand?
Für eine Abkürzung ohne Argumente oder mit ein, zwei Pflichtargumenten genügt \newcommand vollauf. \newcommand{\R}{\mathbb{R}} als \NewDocumentCommand neu zu schreiben bringt nichts außer zusätzlichen Zeichen. \newcommand ist weder gealtert noch abgekündigt; es bleibt neben der neueren Schnittstelle ein vollwertiges LaTeX-Werkzeug. Die Signale zum Wechsel sind erfreulich klar: wenn eine Sternvariante gewünscht ist, wenn ein zweites optionales Argument gebraucht wird, wenn die Eingabesyntax etwas anderes als [...] sein soll, und wenn ein Befehl mit optionalem Argument innerhalb eines anderen optionalen Arguments stehen muss. Trifft auch nur einer dieser Punkte zu, ist eine Zeile Argumentspezifikation kürzer und ungleich lesbarer als handgeschriebene \@ifstar-Mechanik.
Eine letzte Leitlinie weist in die Gegenrichtung. \NewDocumentCommand ist ein Werkzeug zum Entwerfen von Eingabesyntax, keine Sprache zum Ausformulieren dessen, was der Rumpf tut. Wer nach dem Einsammeln der Argumente Zeichenketten zerlegt, Bedingungen schachtelt oder Schleifen dreht, steht bereits im Gebiet von expl3, der LaTeX3-Programmierschicht – in der ltcmd im Übrigen selbst geschrieben ist. Umgekehrt bleibt \NewDocumentCommand die erste Wahl, wenn in einem Paket oder einer Klasse Befehle für Anwender entworfen werden: Die Argumentspezifikation liest sich zugleich als Spezifikation der Schnittstelle.