Обрезка пробелов вокруг текста (например, 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с и \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

Обрезка всех явных пробелов вокруг input, безусловно, выполнима. Существует несколько подходов к этой проблеме: я бы выбрал тот, который написал Бруно Ле Флок для 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настраивает все так, чтобы правильные маркеры были на месте. На этапе «leading» \@@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является \@@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, чтобы предотвратить дальнейшее расширение внутри \edefили аналогичного. Если расширения недоступны, измените последнее вспомогательное на

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

при условии, что это будет означать, что вам придется быть осторожным с тем, что вам передается.

Второе, что следует отметить, это то, что в приведенном выше примере есть некоторые «специальные» токены, например \q@nil, которые используются для сопоставления шаблонов аргументов макросов и поэтому не могут быть во входных данных. Это действительно должно быть нормально с «текстом», но вы могли бы использовать что-то еще более непонятное, например \catcode`\Q=3then Q(math shift catcode), если бы захотели.

Удаление других запрошенных элементов означало бы поиск всех из них по отдельности. Это звучит довольно сложно в случае с \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}

Связанный контент