テキストの周囲の空白をトリミングする(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

上記は、2 つのループによって動作します。1 つは入力の先頭のスペース用 ( \@@trim@spaces@i)、もう 1 つは末尾のスペース用 ( \@@trim@spaces@iii) です。まず、\@@trim@spaces正しいマーカーが配置されるように設定します。先頭のステップでは、 は、に続くスペース (スペース自体は破棄されます) から\@@trim@spaces@iなる引数に一致します。さらにスペースがある場合、と は 空になり、 は残りの入力となり、残りの入力で が再度呼び出されることになります。一方、入力にスペースが残っていない場合、 はによって設定された空の入力に一致します。 は先頭のスペースがすべて削除されたユーザー入力であり、は です。後者はループを停止し、 に渡します(ユーザー入力の前には中括弧が失われるのを防ぐため が残されます。後述)。この 2 番目のループでは、 と 入力末尾のスペースが の直前に表示されます。このパターンは、 の引数に一致します。入力に末尾にスペースがあった場合、はスペースが削除されたユーザー入力 (ただし先頭 はそのまま) であり、となり、ループが発生します。ただし、末尾のスペースがなくなると、は となり、は となります。 は、先頭の がによって取り除かれる前に、 の引数パターンによって削除されます( によりそれ以上の拡張が防止されます)。\q@mark#1#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

上記ではe-TeXを使用して、または同様のもの内でのさらなる拡張を防止していることに注意してください\edef。拡張機能が利用できない場合は、最後の補助を次のように変更します。

  \long\def\@@trim@spaces@iv##1\q@nil##2\q@stop{%
    \@gobble##1%
  }%

ただし、これは、通過する内容に注意する必要があることを意味します。

2 つ目に注目すべき点は、上記には、たとえば\q@nil、マクロ引数パターンに一致するために使用される「特別な」トークンがいくつかあるため、入力に含めることができないことです。これは実際には「テキスト」で問題ありませんが、必要に応じて、\catcode`\Q=3then Q(math shift catcode) などのさらにわかりにくいものを使用することもできます。

\hspace要求された他の項目を削除すると、それらすべてを別々に検索することになります。/の場合、\hskipスペースは有効な単位で指定できると思われるため、次のようなことを心配する前であっても、かなりトリッキーに思えます。

\def\foo{10 pt }
\hskip\foo

ご存知のとおり、グループ トークンの扱いはどんなにうまくいっても難しいため、空のグループを見つけるのも難しい場合があります。(ループを使用する必要があると思います。入力内の各トークンを取得し、空かどうかを確認し、空でない場合は「保持」パイルに追加します。)

さらに、実際の入力では、このような入力はほとんどあり得ないと思います。明示的なスペースをトリミングするのは理にかなっていますが、他の項目については納得できません (他の項目を拾う可能性が高い特別なケースがない限り)。

答え2

明示的なスペースのみが削除され、 や のようなものは削除されませんが、実際の使用例では Joseph の回答を使用することを強くお勧めします\hskip

このようなスペースを右側からトリミングするのは、(ある程度は)簡単です。 、次に がゼロでない\unskip場合は繰り返します。ただし、サイズ のスキップがある場合は、これが誤認される可能性があります。\lastskip0pt

\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}

関連情報