
在 Excel 中處理文字字串時,可以方便地使用LTRIM、RTRIM 和 TRIM 功能它刪除文字字串周圍的空白。在 LaTeX 中複製這個的有效方法是什麼?
例如,假設您以程式設計方式產生變數
\def\firstname{FirstName}% First name
\def\lastname{LastName}% Last name
\edef\fullname{\firstname\ \lastname}% Full name
\fullname% Display full name
但也該適應什麼時候\firstname
或者 \lastname
可能為空。沒有測試它們是否為空,例如
\def\firstname{FirstName}% First name
\def\lastname{}% Last name (none)
\edef\fullname{\firstname\ \lastname}% Full name
\trim{\fullname}% Display full name
會處理這個的。我的第一個想法是定義
\def\trim#1{\ignorespaces#1\unskip}
但這在一般環境中絕對行不通,因為這不能處理空組。而且,\unskip
只會處理最後一個跳躍,其中可能有多個。
特別是,是否可以定義\trim
它可以照顧
\hspace
和\hskip
s?\
的?- 空組
{}
,也許還有非印刷標記,例如\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}
答案1
修剪輸入周圍的所有顯式空間當然是可行的。關於這個問題有幾種方法:我會選擇 Bruno Le Floch 為expl3
as寫的方法\tl_trim_spaces:n
。可以透過這樣做來使用
\usepackage{expl3}
\ExplSyntaxOn
\cs_new_eq:NN \trimspaces \tl_trim_spaces:n
\ExplSyntaxOff
或者,可以將實作直接包含在原始程式碼中,從而避免任何依賴性:
\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
這將從輸入的末尾刪除所有空格,即使您做了一些棘手的事情(例如\edef\test{ \space foo \space}
開始時)(因此兩端都有多個空格)。 (如果您願意將自己限制在這種情況下,那麼xparse
使用此方法為參數提供\TrimSpaces
後處理器。
上面的工作方式是有兩個循環:一個循環用於輸入開頭的空格 ( \@@trim@spaces@i
),第二個循環用於結尾的空格 ( \@@trim@spaces@iii
)。首先,\@@trim@spaces
進行設定以使正確的標記就位。在「前導」步驟中,\@@trim@spaces@i
匹配由後跟空格組成的參數\q@mark
(空格本身被丟棄)。如果有更多空格,則#1
和 #3
將為空,並且#2
將是剩餘的輸入,這意味著\@@trim@spaces@i
將使用剩餘的輸入再次呼叫它。另一方面,如果輸入中沒有剩餘空格,則與#2
所設定的空輸入相匹配\@@trim@spaces
,#1
是刪除所有前導空格的使用者輸入,並且#3
是\@@trim@spaces@ii
。後者停止循環並移交\@@trim@spaces@iii
(a\q@mark
留在使用者輸入的前面,以防止丟失大括號:見下文)。在第二個循環中,輸入末尾的空格將出現在 之前\q@nil
。此模式與 的參數相符\@@trim@spaces@iii
。如果輸入中有尾隨空格,則#1
使用者輸入的空格被刪除(但仍帶有前導\q@mark
)和#2
is \@@trim@spaces@iii
,從而導致循環。然而,當尾隨空格用完時,#2
is\@@trim@spaces@iv
和#1
is \q@mark <user input>\q@nil\@@trim@spaces@iii
。在前導被剝離之前,被\q@nil\@@trim@spaces@iii
參數模式 for 刪除(防止進一步擴展)。\@@trim@spaces@iv
\q@amrk
\@gobble
\unexpanded
請注意,上面使用 e-TeX 來防止在 an\edef
或類似的內部進一步擴展。如果擴充功能不可用,請將最後一個輔助變更為
\long\def\@@trim@spaces@iv##1\q@nil##2\q@stop{%
\@gobble##1%
}%
附帶條件是,這意味著您必須對所通過的內容保持謹慎。
第二件事要注意的是,上面有一些「特殊」標記,例如\q@nil
,它們用於匹配巨集參數模式,因此不能出現在輸入中。對於“文字”來說確實應該沒問題,但是如果您願意,您可以使用更晦澀的東西(數學移位貓代碼\catcode`\Q=3
)。Q
刪除請求的其他項目意味著單獨搜尋所有這些項目。在\hspace
/的情況下,這聽起來相當棘手,\hskip
因為即使在我們擔心諸如此類的事情之前,間距也可以以任何有效單位給出
\def\foo{10 pt }
\hskip\foo
如您所知,即使在最好的情況下,處理群組令牌也是很棘手的,因此找到空組也可能很困難。 (我猜你需要使用一個循環:抓取輸入中的每個標記,看看它是否為空,如果不是,則將其添加到「保留」堆中。)
而且,我認為這種輸入在實際輸入中不太可能出現。修剪顯式空間是有道理的,但我不相信其他項目(除非這裡有一些特殊情況,很有可能會拿起其他項目)。
答案2
我絕對建議您在實際用例中使用 Joseph 的答案,即使它只刪除顯式空格,而不是像\
or 之類的東西\hskip
。
從右側修剪此類空間很簡單(在某種程度上):\unskip
,然後如果\lastskip
不為零則重複。然而,如果存在尺寸跳躍,這可能會被愚弄0pt
。
修剪\hspace
和左邊的朋友,也在巨集內迫使我們手動執行所有巨集擴充。更糟的是:由於\hspace
使用了\@ifnextchar
,我們還需要執行分配。請參閱下面的程式碼。
請注意,它\hspace*
使用 TeX 的原語\vrule
,並且\penalty
我沒有實現任何支援。他們將停止\trimleft
和\trimright
。我知道如何解決這個問題\trimleft
(付出巨大的代價),但不知道如何解決這個問題\trimright
,因為 TeX 沒有\lastrule
. LuaTeX 可以提供協助。
\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}