修剪文字周圍的空白(如 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它可以照顧

  • \hspace\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 為expl3as寫的方法\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)和#2is \@@trim@spaces@iii,從而導致循環。然而,當尾隨空格用完時,#2is\@@trim@spaces@iv#1is \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}

相關內容