修剪文本周围的空白(如 LTRIM、RTRIM 和 TRIM)

修剪文本周围的空白(如 LTRIM、RTRIM 和 TRIM)

在 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这样一种方式来处理

  • \hspaces 和\hskips?
  • \的?
  • 空组{}和也许非打印标记类似\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=3Q

删除请求的其他项目意味着要单独搜索所有这些项目。对于\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}

相关内容