Philipp Lehman ist vor allem für biblatex und csquotes bekannt, doch das Stück von ihm, das in den meisten Präambeln still seine Arbeit tut, dürfte das dritte sein: etoolbox. Der Grund lässt sich fast auf einen einzigen Befehl zusammenziehen: \patchcmd, das von einem fremden Makro nur einen Teil ersetzt, statt es ganz neu zu definieren. Es gibt allerdings einen Haken. Wird der Suchtext nicht gefunden, tut \patchcmd schlicht gar nichts – kein Fehler, keine Warnung. Genau das steckt meist hinter einer Präambelanpassung, die am Tag nach einem Paketupdate rätselhaft „nicht mehr wirkt“. Diese Seite behandelt Tests, Schalter, Hooks und Patches aus etoolbox, danach pgfkeys, die Maschine, mit der LaTeX-Pakete key=value-Schnittstellen bauen, und zuletzt \fpeval für Rechnungen mit reellen Zahlen.
Was etoolbox ist: ein e-TeX-Werkzeugkasten mit LaTeX-Gesicht
etoolbox ist ein Programmierwerkzeugkasten für alle, die Klassen und Pakete schreiben. Es verpackt die von e-TeX ergänzten Low-Level-Primitive so, dass sie sich wie LaTeX2e anfühlen, und legt eine große Portion allgemein nützlicher Hilfsmittel darauf. In TeX Live 2024 steckt Version 2.5k vom 5. Oktober 2020; der Copyright-Vermerk führt zwei Namen: Philipp Lehman (2007–2011) und Joseph Wright (2015–2020). Jede moderne TeX-Engine enthält e-TeX, also genügt \usepackage{etoolbox}. Auch jetzt, da expl3, die Programmierschicht von LaTeX3, verbreitet ist, überlebt etoolbox, weil es sich nahtlos in die LaTeX2e-Welt fügt: Argumente sind schlicht #1, Verzweigungen das vertraute Paar {wahr}{falsch}, und vor allem gibt es \patchcmd, um fremde Pakete nachträglich zurechtzurücken. Für echte Präambelarbeit ist diese Kombination schwer zu schlagen.
Tests schreiben: \ifdef, \ifdefempty, \ifstrequal
Jeder Test in etoolbox hat dieselbe Gestalt: am Ende ein Paar {⟨Code bei wahr⟩}{⟨Code bei falsch⟩}. Kein \fi, an das man denken müsste, kein Grübeln über die Stellung von \else. „Ist dieser Befehl schon definiert?“ lautet \ifdef{\cmd}{wahr}{falsch}, oder \ifcsdef{name}{wahr}{falsch}, wenn der Name als Zeichenkette vorliegt (mit \ifundef und \ifcsundef als Gegenstücken). Für Zeichenketten gibt es \ifblank für „besteht das nur aus Leerzeichen?“, dessen Verneinung \notblank, \ifstrequal{Zeichenkette}{Zeichenkette}{wahr}{falsch} für die Gleichheit zweier Zeichenketten \ifdefempty{\cmd}{wahr}{falsch} für „ist der Rumpf dieses Makros leer?“ und \ifstrempty{Zeichenkette}{wahr}{falsch} für „ist die Zeichenkette selbst leer?“. Nicht zu verwechseln mit dem ähnlich klingenden \ifdefined, einem e-TeX-Primitiv, das keine etoolbox-Zweiwegverzweigung ist.
\usepackage{etoolbox}
% provide a command only if nobody defined it yet
\ifdef{\highlight}
{} % already there: leave it alone
{\newcommand{\highlight}[1]{\textbf{#1}}}
% behave differently on an empty argument
\newcommand{\field}[1]{\ifblank{#1}{(none)}{#1}}
% numeric tests, same two-way shape
\ifnumcomp{\value{page}}{>}{10}{late}{early}
\ifnumodd{\value{page}}{recto}{verso}Hier steckt ein Unterschied, den selbst die Dokumentation leicht übersehen lässt: \ifstrequal und \ifdefstring sind nicht expandierbar. Ein Blick in die Quelle von etoolbox zeigt beide mit \newrobustcmd definiert – also mit dem \protected-Präfix von e-TeX –, weshalb sie sich in \edef, \typeout oder \csname nicht wie erwartet verhalten. Schreibt man \typeout{\ifstrequal{abc}{abc}{SAME}{DIFF}}, steht im Log nicht SAME, sondern wörtlich \ifstrequal {abc}{abc}{SAME}{DIFF}. \ifdefempty dagegen ist expandierbar und hinterlässt in einem \edef nur das Ergebnis. Im Dokumenttext verzweigen alle korrekt; der Unterschied zeigt sich erst in einem \edef – wer diese Linie kennt, verliert keinen Tag mit der Jagd nach einem Test, der „nicht funktioniert“.
Boolesche Schalter: \newtoggle oder \newbool?
Die Vorgabe sollte \newtoggle sein, und der Grund ist der Namensraum: Ein Toggle lebt in einem eigenen Namensraum und kann daher nie mit einem bestehenden Befehl kollidieren. Deklariert wird mit \newtoggle{draft}, umgeschaltet mit \toggletrue{draft} / \togglefalse{draft} (oder \settoggle{draft}{true}), verzweigt mit \iftoggle{draft}{⟨wahr⟩}{⟨falsch⟩}, verneint mit \nottoggle. Die andere Familie, der bool, bietet dieselbe Gestalt – \newbool{draft}, \setbool{draft}{true}, \booltrue, \boolfalse, \ifbool{draft}{⟨wahr⟩}{⟨falsch⟩} –, nutzt intern aber dieselbe Mechanik wie LaTeXs \newif und verbraucht damit einen Befehlsnamen, \ifdraft. Das ist der Ausschlag: einen bool wählt man, wenn Zusammenspiel mit vorhandenem \newif-Code nötig ist, sonst genügt ein Toggle.
| Befehl | Bedeutung | Hinweis |
|---|---|---|
\newtoggle{f} | Deklariert Schalter f, anfangs falsch | Eigener Namensraum; verbraucht keinen Befehlsnamen |
\settoggle{f}{v} | Setzt f auf v (true / false) | Gleichbedeutend mit \toggletrue / \togglefalse |
\iftoggle{f}{T}{F} | T bei wahr, F bei falsch | Drei Argumente; kein \fi nötig |
\newbool{f} | Die bool-Variante eines Schalters | Dieselbe Mechanik wie \newif; belegt einen Befehlsnamen |
\ifbool{f}{T}{F} | Die bool-Variante der Verzweigung | Verträgt sich mit vorhandenem \newif-Code |
\newrobustcmd und \robustify: ein Makro, das nicht zerbricht
\newrobustcmd schreibt sich genau wie \newcommand, erzeugt aber einen robusten Befehl. Der Unterschied zeigt sich sofort unter \meaning: Ein mit \newcommand erzeugter Befehl meldet \long macro:->…, ein mit \newrobustcmd erzeugter dagegen \protected\long macro:->…. Es umgeht also den traditionellen Zweischritt mit \protect und nutzt das \protected-Präfix von e-TeX direkt. Deshalb darf es in einem beweglichen Argument stehen – einer Überschrift, einer Bildunterschrift –, ohne auf dem Weg in die Inhaltsverzeichnisdatei expandiert und zerstört zu werden. Für einen fragilen Befehl, den jemand anderes bereits definiert hat, härtet \robustify{\cmd} die vorhandene Definition an Ort und Stelle.
\patchcmd: nur einen Teil eines fremden Makros ersetzen
\patchcmd sucht eine Zeichenkette im Rumpf eines bereits definierten Makros und ersetzt genau diese Stelle. Es nimmt fünf Argumente: \patchcmd{\cmd}{⟨Suche⟩}{⟨Ersatz⟩}{⟨bei Erfolg⟩}{⟨bei Misserfolg⟩}. Wird der Suchtext gefunden, ersetzt es und führt das vierte Argument aus; andernfalls lässt es das Makro unberührt und führt das fünfte aus. Ersetzt wird nur das erste Vorkommen – bei zwei \small im Rumpf ändert sich nur das vordere. Ein wirklich nützliches Beispiel: Die Umgebung thebibliography der Klasse article beginnt mit \section*{\refname}; ersetzt man dieses \section* durch \section, wird das Literaturverzeichnis zu einem nummerierten Abschnitt, der auch im Inhaltsverzeichnis erscheint. Gemessen erhielt die .toc-Datei ordnungsgemäß \contentsline {section}{\numberline {2}References}, und der Patch tat genau das Versprochene.
\usepackage{etoolbox}
\makeatletter % the target usually contains @
\patchcmd{\thebibliography}
{\section*} % search
{\section} % replace
{\typeout{bibliography patch applied}} % on success
{\PackageWarning{mypkg}{bibliography patch failed}} % on failure
\makeatother
% result: "References" becomes a numbered section and enters the ToC
% .toc -> \contentsline {section}{\numberline {2}References}{1}{}Wenn ein Patch stillschweigend nichts tut: \tracingpatches und xpatch
Ein fehlgeschlagenes \patchcmd ist vollkommen stumm. Gemessen: Gibt man ihm ein Muster, das nicht passt, und lässt beide Zweige leer, endet der Lauf mit null Fehlern und null Warnungen und hinterlässt keine Spur im Log. Daher die eherne Regel – den Fehlerzweig niemals leer lassen, sondern ein \PackageWarning hineinsetzen. Dann erscheint Package mypkg Warning: bibliography patch failed on input line 5., und man merkt es am Tag nach dem Update statt Monate später. Um die Ursache zu finden, gehört \tracingpatches in die Präambel: etoolbox.def wird geladen und zu jedem Patch eine Diagnose ins Log geschrieben.
[debug] tracing \patchcmd on input line 5
[debug] analyzing '\thebibliography'
[debug] ++ control sequence is defined
[debug] ++ control sequence is a macro
[debug] ++ macro can be retokenized cleanly
[debug] -- search pattern not found in replacement text
[debug] analyzing '\nosuchcommand'
[debug] -- control sequence is undefined or \relax
[debug] analyzing '\LaTeX'
[debug] -- macro cannot be retokenized cleanly
[debug] -> the macro may have been defined under a category
[debug] code regime different from the current oneDie Diagnosen fallen in drei Arten. „Das Suchmuster steht nicht im Rumpf“ (-- search pattern not found in replacement text) ist das klassische Zeichen, dass ein Paketupdate die Definition geändert hat; die neue mit \show ansehen und den Suchtext neu schreiben. „Der Befehl ist undefiniert“ (-- control sequence is undefined or \relax) heißt, dass zu früh gepatcht wird – den Patch nach hinten verlegen, etwa in \AtBeginDocument. Die dritte, „lässt sich nicht sauber neu tokenisieren“ (-- macro cannot be retokenized cleanly), ist ein Kategoriecode-Problem: Das Makro wurde unter einem anderen catcode-Regime definiert als dem aktuellen – also prüfen, ob innerhalb von \makeatletter gepatcht wird.
Und es gibt einen Fehlschlag, der nicht einmal in der Diagnose auftaucht: \patchcmd wirkt nicht auf Befehle mit optionalem Argument. Fragt man \meaning nach einem als \newcommand{\opt}[2][X]{...} definierten \opt, erhält man \@protected@testopt \opt \\opt {X} – \opt ist nur eine verteilende Eingangstür, der eigentliche Rumpf steckt in einem separaten Befehl namens \\opt. \patchcmd{\opt}{small}{LARGE} durchsucht also die Eingangstür und scheitert. Für diesen Fall nimmt man \xpatchcmd aus dem Paket xpatch, das etoolbox erweitert: Gemessen gelangen dieselben Argumente, und das innere Makro lautete danach \long macro:[#1]#2-><#1|#2|LARGE>. xpatch liefert außerdem die passenden Befehle für Umgebungen.
Hooks, Anhängen und Listen: eigenen Code einschleusen
Lässt sich das Umschreiben eines Makrorumpfs vermeiden, sollte man es vermeiden. etoolbox bietet reichlich Hooks nach dem Muster „führe diesen Code in jenem Moment aus“. Anfang und Ende des Dokuments gehören dem LaTeX-Kernel mit \AtBeginDocument und \AtEndDocument, doch etoolbox ergänzt \AtEndPreamble (das äußerste Ende der Präambel), \AfterEndDocument (wirklich zuletzt) sowie Hooks um eine bestimmte Umgebung herum: \AtBeginEnvironment{⟨env⟩}{⟨Code⟩}, \AtEndEnvironment, \BeforeBeginEnvironment und \AfterEndEnvironment. Um einem vorhandenen Makro oder Hook nachträglich etwas hinzuzufügen, dienen \appto{\cmd}{⟨Code⟩} (ans Ende) und \preto{\cmd}{⟨Code⟩} (an den Anfang); \gappto ist die globale Variante, \eappto expandiert den ergänzten Code zuvor. Bei Makros mit Argumenten nimmt man \apptocmd / \pretocmd mit Erfolgs- und Fehlerzweig – auch diese führen bei einem undefinierten Befehl nur den Fehlerzweig aus, ohne einen Fehler zu melden; dieselbe Vorsicht wie bei \patchcmd ist geboten.
\usepackage{etoolbox}
% run code every time an environment starts -- no patching required
\AtBeginEnvironment{quote}{\itshape}
\AtBeginEnvironment{itemize}{\setlength{\itemsep}{2pt}}
% append to a macro that takes an argument (note the two branches)
\newcommand{\greet}[1]{Hello #1}
\apptocmd{\greet}{!}{}{\PackageWarning{mypkg}{could not extend \string\greet}}
% \greet is now \long macro:#1->Hello #1!
% lightweight lists and loops
\listadd{\mylist}{alpha}\listadd{\mylist}{beta}
\newcommand{\asitem}[1]{\item #1}
\begin{itemize}\forlistloop{\asitem}{\mylist}\end{itemize}
\begin{itemize}\forcsvlist{\asitem}{apples, pears, plums}\end{itemize}Auch die Listenseite ist abgedeckt. \listadd{\mylist}{⟨Element⟩} hängt an eine interne Liste an, und \forlistloop{⟨Handler⟩}{\mylist} wendet einen einargumentigen Handler auf jedes Element an. Liegt bereits eine kommagetrennte Zeichenkette vor, sind \docsvlist{a,b,c} und \forcsvlist{⟨Handler⟩}{a,b,c} die schnellen Wege; \DeclareListParser baut einen Parser für ein selbst gewähltes Trennzeichen. In der Praxis ist die häufigste Anwendung, eine Paketoption entgegenzunehmen und als Liste zu durchlaufen.
pgfkeys: dem eigenen Werkzeug eine key=value-Schnittstelle geben
pgfkeys ist die key=value-Maschine, die mit PGF/TikZ ausgeliefert wird. Die von TikZ vertraute Schreibweise [draw, thick, fill=blue] und die \…setup{...}-artigen Schnittstellen vieler Pakete beruhen weitgehend darauf (in TeX Live 2024 liegt PGF in Version 3.1.10 vor, Copyright Till Tantau). Im Zentrum steht ein einziger Befehl, \pgfkeys{/my/key=value}. Schlüssel werden über /-getrennte Pfade (Familien) in Namensräume aufgeteilt, und jedem Schlüssel wird ein Handler zugewiesen, der bestimmt, was beim Aufruf geschieht. Einen Schlüssel zu definieren heißt kurz gesagt, einen Handler zu wählen.
.store in, .code, .is choice: den richtigen Handler wählen
Drei Handler decken die meiste echte Arbeit ab: .store in=\macro, um den Wert wörtlich zu verwahren, .code={... #1 ...}, um mit dem Wert Code auszuführen (der Wert kommt als #1 an), und .is choice, um eine feste Auswahl aufzuzählen. Dazu liefert .default=Wert den Wert für einen Aufruf ohne =Wert, und .initial=Wert gibt dem Schlüssel einen Anfangswert (auslesbar mit \pgfkeysvalueof{/path/key}). Bietet das eigene Paket einen Einstiegspunkt wie \mypkgsetup{...}, lautet die Redewendung \pgfqkeys{/mypkg}{⟨Schlüsselliste⟩} – das „q“ steht für quick und ist die Kurzform von \pgfkeys{/mypkg/.cd, ⟨Schlüsselliste⟩}. In einen einzeiligen Wrapper gepackt, konfigurieren die Nutzenden alles allein über kurze Schlüsselnamen.
\usepackage{pgfkeys}
\pgfkeys{
/book/title/.store in = \bookTitle,
/book/edition/.store in = \bookEd,
/book/edition/.default = 1, % value used when called bare
/book/pages/.initial = 100, % starting value
/book/layout/.is choice, % a fixed set of options
/book/layout/wide/.code = {\def\bookLayout{WIDE}},
/book/layout/narrow/.code = {\def\bookLayout{NARROW}},
/book/note/.code = {\def\bookNote{<<#1>>}}, % #1 is the value passed in
}
\pgfkeys{/book/title=TeX by Topic, /book/edition, /book/layout=wide}
\pgfkeysvalueof{/book/pages} % -> 100
% a one-line entry point for your users
\newcommand{\mypkgsetup}[1]{\pgfqkeys{/book}{#1}}
\mypkgsetup{title = My Report, edition = 2}Die Fehlermeldungen von pgfkeys sind hilfreich konkret und obendrein gute Suchbegriffe. Ein nie definierter Schlüssel ergibt ! Package pgfkeys Error: I do not know the key '/book/nosuchkey', to which you passed '1', and I am going to ignore it. Perhaps you misspelled it. Eine Auswahl, die in einer .is choice-Liste fehlt, ergibt ! Package pgfkeys Error: Choice 'sideways' unknown in choice key '/book/layout'. I am going to ignore this key. Beide ignorieren das Problem und machen weiter – der Satz bricht nicht ab, ein falsch geschriebener Schlüssel bleibt also unbemerkt, wenn man das Log nicht liest. Auf der LaTeX3-Seite gibt es die Entsprechung l3keys (\keys_define:nn und Verwandte). Eine sinnvolle Aufteilung: l3keys für ein neues Paket in expl3, pgfkeys, wenn man sich an TikZ-nahen Code oder vorhandenen Bestand anpasst.
Rechnen mit reellen Zahlen: \fpeval braucht xfp nicht mehr
TeXs Ganzzahlarithmetik gerät an ihre Grenzen, sobald Dezimalzahlen ins Spiel kommen – etwa weil die Division in \numexpr rundet. Dafür gibt es \fpeval: \fpeval{1/3} ergibt 0.3333333333333333, \fpeval{sqrt(2)} ergibt 1.414213562373095, \fpeval{sind(30)} ergibt 0.5 und \fpeval{round(2/3, 4)} ergibt 0.6667. Zur Kombination mit einer Länge hängt man einfach die Einheit an: \setlength{\x}{\fpeval{345/7}pt}. Eine Aussage sei ausdrücklich datiert: Im mit TeX Live 2024 ausgelieferten LaTeX2e (Fassung 2023-11-01) stecken \fpeval, \inteval und \dimeval im Kernel; \usepackage{xfp} ist nicht erforderlich. xfp selbst definiert sie inzwischen mit \ProvideExpandableDocumentCommand – „stelle sie bereit, falls sie fehlen“ –, sodass ein Laden nicht schadet und die sichere Wahl bleibt, wenn auch ältere Installationen bedient werden müssen.
Zum Schluss eine grobe Richtschnur für das Zusammenspiel der drei. Um fremdes Verhalten aus der Präambel heraus leicht zu ändern, greift man zu etoolbox – und setzt stets eine Warnung in den Fehlerzweig. Um dem eigenen Paket eine Konfigurationsschnittstelle zu geben, greift man zu pgfkeys oder l3keys. Um ein Maß oder ein Verhältnis zu berechnen, greift man zu \fpeval. Und die erste Frage lautet stets, ob sich Patchen ganz vermeiden lässt: erst \renewcommand auf einen öffentlichen Befehl, dann ein Hook wie \AtBeginEnvironment, dann eine ordentliche Paketoption – und erst wenn nichts davon trägt, zieht man \patchcmd. Ein Patch mag heute funktionieren, garantiert ist er nur bis zum Paketupdate von morgen.