.png)
Wenn Sie in Excel mit Textzeichenfolgen arbeiten, ist es praktisch, denLTRIM-, RTRIM- und TRIM-Funktionenwodurch Leerzeichen um Textzeichenfolgen entfernt werden. Wie könnte man dies effizient in LaTeX duplizieren?
Angenommen, Sie erzeugen programmgesteuert Variablen
\def\firstname{FirstName}% First name
\def\lastname{LastName}% Last name
\edef\fullname{\firstname\ \lastname}% Full name
\fullname% Display full name
sollte aber auch berücksichtigen, wenn\firstname
oder \lastname
können leer sein. Ohne zu testen, ob sie leer sind, so etwas wie
\def\firstname{FirstName}% First name
\def\lastname{}% Last name (none)
\edef\fullname{\firstname\ \lastname}% Full name
\trim{\fullname}% Display full name
würde mich darum kümmern. Mein erster Gedanke war, zu definieren
\def\trim#1{\ignorespaces#1\unskip}
aber das würde in einer allgemeinen Einstellung mit Sicherheit nicht funktionieren, da eine leere Gruppe dabei nicht berücksichtigt wird. Außerdem \unskip
würde nur der letzte Sprung berücksichtigt, von dem es mehr als einen geben kann.
Ist es insbesondere möglich, eine Definition zu erstellen, \trim
die sich um
\hspace
s und\hskip
s?\
'S?- leere Gruppen
{}
und vielleicht nicht druckbare Token wie\relax
?
\documentclass{article}
\begin{document}
Without \verb|\trim|:\par\medskip
\def\firstname{FirstName}\def\lastname{LastName}
\edef\fullname{\firstname\ \lastname}\fbox{\fullname}
\def\firstname{FirstName}\def\lastname{}
\edef\fullname{\firstname\ \lastname}\fbox{\fullname}
\def\firstname{}\def\lastname{LastName}
\edef\fullname{\firstname\ \lastname}\fbox{\fullname}
\bigskip
With \verb|\trim|:\par\medskip
\def\trim#1{\ignorespaces#1\unskip}
\def\firstname{FirstName}\def\lastname{LastName}
\edef\fullname{\firstname\ \lastname}\fbox{\trim{\fullname}}
\def\firstname{FirstName}\def\lastname{}
\edef\fullname{\firstname\ \lastname}\fbox{\trim{\fullname}}
\def\firstname{}\def\lastname{LastName}
\edef\fullname{\firstname\ \lastname}\fbox{\trim{\fullname}}
\end{document}
Antwort1
Das Entfernen aller expliziten Leerzeichen um die Eingabe ist sicherlich machbar. Es gibt mehrere Ansätze für dieses Problem: Ich würde den Ansatz wählen, den Bruno Le Floch für expl3
as geschrieben hat \tl_trim_spaces:n
. Dies kann verwendet werden, indem
\usepackage{expl3}
\ExplSyntaxOn
\cs_new_eq:NN \trimspaces \tl_trim_spaces:n
\ExplSyntaxOff
Alternativ kann die Implementierung auch direkt in den Quellcode eingebunden und so jegliche Abhängigkeit vermieden werden:
\documentclass{article}
\makeatletter
\long\def\trim@spaces#1{%
\@@trim@spaces{\q@mark#1}%
}
\def\@tempa#1{%
\long\def\@@trim@spaces##1{%
\@@trim@spaces@i##1\q@nil\q@mark#1{}\q@mark
\@@trim@spaces@ii
\@@trim@spaces@iii
#1\q@nil
\@@trim@spaces@iv
\q@stop
}%
\long\def\@@trim@spaces@i##1\q@mark#1##2\q@mark##3{%
##3%
\@@trim@spaces@i
\q@mark
##2%
\q@mark#1{##1}%
}%
\long\def\@@trim@spaces@ii\@@trim@spaces@i\q@mark\q@mark##1{%
\@@trim@spaces@iii
##1%
}%
\long\def\@@trim@spaces@iii##1#1\q@nil##2{%
##2%
##1\q@nil
\@@trim@spaces@iii
}%
\long\def\@@trim@spaces@iv##1\q@nil##2\q@stop{%
\unexpanded\expandafter{\@gobble##1}%
}%
}
\@tempa{ }
\def\test{ foo }
\edef\test{\expandafter\trim@spaces\expandafter{\test}}
\show\test
Dadurch werden alle Leerzeichen am Ende der Eingabe entfernt, selbst wenn Sie etwas Schwieriges tun, wie z. B. \edef\test{ \space foo \space}
mit zu beginnen (so dass an beiden Enden mehrere Leerzeichen vorhanden sind). (Wenn Sie sich auf diesen Fall beschränken möchten, dannxparse
bietet den \TrimSpaces
Postprozessor für Argumente mit dieser Methode an.)
Das obige funktioniert so, dass es zwei Schleifen gibt: eine für Leerzeichen am Anfang der Eingabe ( \@@trim@spaces@i
), eine zweite für diejenigen am Ende ( \@@trim@spaces@iii
). Zuerst \@@trim@spaces
wird alles so eingerichtet, dass die richtigen Markierungen vorhanden sind. Im „führenden“ Schritt \@@trim@spaces@i
entspricht einem Argument, das aus \q@mark
gefolgt von einem Leerzeichen besteht (das Leerzeichen selbst wird verworfen). Wenn mehr Leerzeichen vorhanden sind, dann sind #1
und leer und sind die verbleibende Eingabe, was bedeutet, dass mit der verbleibenden Eingabe erneut aufgerufen wird. Wenn andererseits keine Leerzeichen mehr in der Eingabe vorhanden sind, dann entspricht der leeren Eingabe, die durch eingerichtet wurde , ist die Benutzereingabe, bei der alle führenden Leerzeichen entfernt wurden und ist . Letzteres stoppt die Schleife und übergibt an (ein bleibt vor der Benutzereingabe, um den Verlust von Klammern zu verhindern: siehe später). In dieser zweiten Schleife erscheinen und Leerzeichen am Ende der Eingabe direkt vor . Dieses Muster wird durch das Argument mit abgeglichen . Wenn die Eingabe ein nachstehendes Leerzeichen enthielt, dann ist die Benutzereingabe, bei der das Leerzeichen entfernt wurde (aber immer noch mit einem führenden ) und ist , was zu einer Schleife führt. Wenn jedoch alle nachstehenden Leerzeichen erschöpft sind, ist und ist das . Das wird durch das Argumentmuster für entfernt, bevor das führende durch entfernt wird (wobei das weitere Erweitern verhindert).#3
#2
\@@trim@spaces@i
#2
\@@trim@spaces
#1
#3
\@@trim@spaces@ii
\@@trim@spaces@iii
\q@mark
\q@nil
\@@trim@spaces@iii
#1
\q@mark
#2
\@@trim@spaces@iii
#2
\@@trim@spaces@iv
#1
\q@mark <user input>\q@nil\@@trim@spaces@iii
\q@nil\@@trim@spaces@iii
\@@trim@spaces@iv
\q@amrk
\@gobble
\unexpanded
Beachten Sie, dass oben e-TeX verwendet wird, um weitere Erweiterungen innerhalb von an oder ähnlichem zu verhindern \edef
. Wenn die Erweiterungen nicht verfügbar sind, ändern Sie das letzte Hilfsverb in
\long\def\@@trim@spaces@iv##1\q@nil##2\q@stop{%
\@gobble##1%
}%
mit der Maßgabe, dass dies bedeutet, dass Sie vorsichtig sein müssen, was durchgeleitet wird.
Zweitens ist zu beachten, dass es im obigen Beispiel einige „spezielle“ Token gibt \q@nil
, die verwendet werden, um die Makroargumentmuster abzugleichen, und daher nicht in der Eingabe enthalten sein können. Das sollte mit „Text“ eigentlich in Ordnung sein, aber Sie könnten etwas noch Obskureres wie \catcode`\Q=3
then Q
(math shift catcode) verwenden, wenn Sie möchten.
Das Entfernen der anderen angeforderten Elemente würde bedeuten, nach allen einzeln zu suchen. Das klingt im Fall von \hspace
/ ziemlich schwierig \hskip
, da der Abstand vermutlich in allen gültigen Einheiten angegeben werden könnte, noch bevor wir uns um Dinge wie
\def\foo{10 pt }
\hskip\foo
Wie Sie vielleicht wissen, ist der Umgang mit Gruppentokens schon im besten Fall schwierig, daher kann es auch schwierig sein, eine leere Gruppe zu finden. (Ich schätze, Sie müssten eine Schleife verwenden: Greifen Sie jedes Token in der Eingabe, prüfen Sie, ob es leer ist, und fügen Sie es andernfalls dem „Behalten“-Stapel hinzu.)
Darüber hinaus halte ich diese Art von Eingabe bei realen Eingaben für ziemlich unwahrscheinlich. Das Kürzen expliziter Leerzeichen ist sinnvoll, aber von den anderen Elementen bin ich nicht überzeugt (es sei denn, es gibt hier einen besonderen Fall, in dem eine gute Chance besteht, die anderen Elemente zu erfassen).
Antwort2
Ich rate Ihnen unbedingt dazu, in praktischen Anwendungsfällen die Antwort von Joseph zu verwenden, auch wenn hierdurch nur explizite Leerzeichen und keine Dinge wie \
oder entfernt werden \hskip
.
Das Abschneiden solcher Leerzeichen von rechts ist (bis zu einem gewissen Grad) unkompliziert: \unskip
, dann wiederholen, wenn der Wert \lastskip
ungleich Null ist. Dies kann jedoch ausgetrickst werden, wenn ein Sprung der Größe auftritt 0pt
.
Trimmen \hspace
und Co. von links, auch innerhalb von Makros, zwingt uns, alle Makroerweiterungen manuell durchzuführen. Noch schlimmer: Da \hspace
verwendet wird \@ifnextchar
, müssen wir auch Zuweisungen durchführen. Siehe Code unten.
Beachten Sie, dass \hspace*
die Grundelemente von TeX \vrule
und verwendet werden, \penalty
für die ich keine Unterstützung implementiert habe. Sie stoppen sowohl \trimleft
als auch \trimright
. Ich weiß, wie man das für beheben kann \trimleft
(mit hohen Kosten), aber nicht für \trimright
, da TeX kein hat \lastrule
. LuaTeX könnte helfen.
\begingroup
%
% This plain TeX code uses the prefix "tsp", and defines
% \trim, \trimleft, and \trimright.
%
\catcode`@=11
\long\gdef\trim#1{\trimleft{\trimright{#1}}}
%
% Trimming spaces on the right is done by repeatedly calling \unskip
% until \lastskip is zero. We start with \hskip0pt\relax to stop
% \trimright from trimming spaces _before_ #1 in case this only
% contains spaces.
%
\long\gdef\trimright#1{\hskip0pt\relax #1\tsp@right}
\gdef\tsp@right
{\unskip\ifdim0pt=\lastskip\else\expandafter\tsp@right\fi}
%
% Trimming spaces on the left is done by repeatedly using \futurelet
% to test the first token, and dispatching depending on what is found.
% Expandable tokens are expanded; most assignments are performed;
% spaces are ignored; groups are entered. The loop ends when
% encountering \tsp@left@end.
%
\long\gdef\trimleft#1{\tsp@left#1\tsp@left@end}
\global\let\tsp@left@end\relax
\gdef\tsp@left{\expandafter\tsp@left@look}
\gdef\tsp@left@look{\futurelet\tsp@token\tsp@left@test}
\gdef\tsp@left@test
{%
\typeout{\meaning\tsp@token}%
\expandafter\ifx\noexpand\tsp@token\tsp@token
\expandafter\@secondoftwo
\else
\expandafter\@firstoftwo
\fi
{% Expandable token => expand again.
\let\tsp@next\tsp@left
}%
{%
\ifcat\tsp@token\relax
% Non-expandable primitive: build \tsp@<meaning>.
% Note that primitives for which I haven't defined
% \tsp@<meaning> just give \relax, which stops
% trimming cleanly.
\begingroup
\escapechar-1%
\global\expandafter\let\expandafter\tsp@next
\csname tsp@\meaning\tsp@token\endcsname
\endgroup
\else
% Character token.
\ifcat\tsp@token\bgroup % Begin-group: do; continue trimming
\bgroup\let\tsp@next\tsp@gobble@token
\else
\ifcat\tsp@token\egroup % End-group: do; continue trimming
\egroup\let\tsp@next\tsp@gobble@token
\else
\ifcat\tsp@token\space % Space: remove; continue trimming
\let\tsp@next\tsp@gobble@token
\else % Anything else: stop trimming
\let\tsp@next\relax
\fi
\fi
\fi
\fi
}%
\tsp@next
}%
\gdef\tsp@gobble@token{\afterassignment\tsp@left\let\tsp@token= }
%
% Helpers for defining primitives.
%
\long\gdef\tsp@swap#1{#1\tsp@gobble@token}
\gdef\tsp@assignment{\afterassignment\tsp@left}
%
% Various primitives
%
\global \let \tsp@unskip \tsp@gobble@token
\global \expandafter \let \csname tsp@ \endcsname \tsp@gobble@token
\global \let \tsp@begingroup \tsp@swap
\global \let \tsp@endgroup \tsp@swap
\global \let \tsp@def \tsp@assignment
\global \let \tsp@edef \tsp@assignment
\global \let \tsp@gdef \tsp@assignment
\global \let \tsp@xdef \tsp@assignment
\global \let \tsp@let \tsp@assignment
\global \let \tsp@futurelet \tsp@assignment
\global \let \tsp@global \tsp@assignment
\global \let \tsp@long \tsp@assignment
\global \let \tsp@protected \tsp@assignment
\gdef\tsp@hskip#1{\begingroup\afterassignment\tsp@hskip@\skip0= }
\gdef\tsp@hskip@{\endgroup\tsp@left}
%
% We must end when seeing \tsp@left@end (normally \relax)
%
\long\gdef\tsp@relax#1%
{%
\begingroup
\def\tsp@left@end{\tsp@left@end}%
\expandafter
\endgroup
\ifx#1\tsp@left@end
\else
\expandafter\tsp@left
\fi
}
\endgroup
\documentclass{article}
\begin{document}
Without \verb|\trim|:\par\medskip
\def\firstname{FirstName}\def\lastname{LastName}
\edef\fullname{\firstname\ \lastname}\fbox{\fullname}
\def\firstname{FirstName}\def\lastname{}
\edef\fullname{\firstname\ \lastname}\fbox{\fullname}
\def\firstname{}\def\lastname{LastName}
\edef\fullname{\firstname\ \lastname}\fbox{\fullname}
\bigskip
With \verb|\trim|:\par\medskip
\def\firstname{FirstName}\def\lastname{LastName}
\edef\fullname{\firstname\ \lastname}\fbox{\trim{\fullname}}
\def\firstname{FirstName}\def\lastname{}
\edef\fullname{\firstname\ \lastname}\fbox{\trim{\fullname}}
\def\firstname{}\def\lastname{LastName}
\edef\fullname{\firstname\ \lastname}\fbox{\trim{\fullname}}
\end{document}