.png)
При работе в 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, безусловно, выполнима. Существует несколько подходов к этой проблеме: я бы выбрал тот, который написал Бруно Ле Флок для expl3
as \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
, что приводит к циклу. Однако, когда конечные пробелы исчерпаны, #2
is \@@trim@spaces@iv
и #1
is \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=3
then 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}