在 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
s 和\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
。\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
(\q@mark
在用户输入的前面留下 a 以防止括号丢失:请参阅下文)。在第二个循环中, 和 输入末尾的空格将出现在 之前\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
请注意,以上代码使用 e-TeX,以阻止在\edef
或类似代码中进一步扩展。如果扩展不可用,请将最后一个辅助代码更改为
\long\def\@@trim@spaces@iv##1\q@nil##2\q@stop{%
\@gobble##1%
}%
但前提是你必须小心所传递的信息。
需要注意的第二件事是,上面有一些“特殊”标记,例如\q@nil
,用于匹配宏参数模式,因此不能出现在输入中。这实际上应该适用于“文本”,但如果您愿意,您可以使用更晦涩难懂的东西,例如(数学移位 catcode)\catcode`\Q=3
。Q
删除请求的其他项目意味着要单独搜索所有这些项目。对于\hspace
/来说,这听起来相当棘手\hskip
,因为可能可以以任何有效单位给出间距,甚至在我们担心诸如此类的事情之前
\def\foo{10 pt }
\hskip\foo
您可能知道,处理组标记在最好的情况下也很棘手,因此找到一个空组也可能很困难。(我想您需要使用循环:抓取输入中的每个标记,查看它是否为空,如果不是,则将其添加到“保留”堆中。)
此外,我认为这种输入在实际输入中不太可能出现。修剪显式空间是有意义的,但我对其他项目不太确定(除非这里有一些特殊情况,其中很有可能拾取其他项目)。
答案2
我绝对建议您在实际用例中使用约瑟夫的答案,即使它只删除明确的空格,而不是像\
或 之类的东西\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}