.png)
Cuando se trabaja en Excel con cadenas de texto, es conveniente utilizar elFunciones LTRIM, RTRIM y TRIMque elimina los espacios en blanco alrededor de la cadena de texto. ¿Cuál sería una forma eficaz de duplicar esto en LaTeX?
Por ejemplo, supongamos que produce variables mediante programación
\def\firstname{FirstName}% First name
\def\lastname{LastName}% Last name
\edef\fullname{\firstname\ \lastname}% Full name
\fullname% Display full name
pero también debe tener en cuenta cuando\firstname
o \lastname
puede estar vacío. Sin probar si están vacíos, algo así como
\def\firstname{FirstName}% First name
\def\lastname{}% Last name (none)
\edef\fullname{\firstname\ \lastname}% Full name
\trim{\fullname}% Display full name
se encargaría de esto. Mi primer pensamiento fue definir
\def\trim#1{\ignorespaces#1\unskip}
pero esto ciertamente no funcionaría en un entorno general, ya que no se ocupa de un grupo vacío. Es más, \unskip
sólo se ocuparía del último salto, del que puede haber más de uno.
En particular, ¿es posible definir \trim
algo que se encargue de
\hspace
s y\hskip
s?\
'¿s?- ¿Grupos vacíos
{}
y quizás tokens que no se imprimen como\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}
Respuesta1
Ciertamente es posible recortar todos los espacios explícitos alrededor de la entrada. Hay varios enfoques sobre este problema: yo elegiría el que escribió Bruno Le Floch expl3
como \tl_trim_spaces:n
. Eso se puede usar haciendo
\usepackage{expl3}
\ExplSyntaxOn
\cs_new_eq:NN \trimspaces \tl_trim_spaces:n
\ExplSyntaxOff
Alternativamente, la implementación se puede incluir directamente en el código fuente y así evitar cualquier dependencia:
\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
Esto eliminará todos los espacios de los extremos de la entrada, incluso si haces algo complicado como \edef\test{ \space foo \space}
empezar (por lo que hay varios espacios en ambos extremos). (Si está dispuesto a limitarse a este caso, entoncesxparse
ofrece el \TrimSpaces
posprocesador para argumentos que utilizan este método).
La forma en que funciona lo anterior es que hay dos bucles: uno para los espacios al inicio de la entrada ( \@@trim@spaces@i
), un segundo para los del final ( \@@trim@spaces@iii
). Primero, \@@trim@spaces
configure las cosas de manera que los marcadores correctos estén en su lugar. En el paso 'principal', \@@trim@spaces@i
coincide con un argumento que consta de \q@mark
seguido de un espacio (el espacio en sí se descarta). Si hay más espacios entonces #1
y #3
estará vacío y #2
será la entrada restante, lo que significa que \@@trim@spaces@i
se llamará nuevamente con la entrada restante. Por otro lado, si no quedan espacios en la entrada, entonces #2
coincide con la entrada vacía configurada por \@@trim@spaces
, #1
es la entrada del usuario sin todos los espacios iniciales y #3
es \@@trim@spaces@ii
. Este último detiene el bucle y pasa la mano \@@trim@spaces@iii
(a \q@mark
se deja al frente de la entrada del usuario para evitar cualquier pérdida de llaves: ver más adelante). En este segundo bucle, aparecerán espacios al final de la entrada justo antes \q@nil
. Este patrón coincide con el argumento de \@@trim@spaces@iii
. Si había un espacio final en la entrada, entonces #1
la entrada del usuario es con el espacio eliminado (pero aún con un \q@mark
) inicial y #2
es \@@trim@spaces@iii
, lo que conduce a un bucle. Sin embargo, cuando se agotan los espacios finales, #2
es \@@trim@spaces@iv
y #1
es el \q@mark <user input>\q@nil\@@trim@spaces@iii
. El argumento \q@nil\@@trim@spaces@iii
se elimina antes de que se elimine \@@trim@spaces@iv
el interlineado (evitando una mayor expansión).\q@amrk
\@gobble
\unexpanded
Tenga en cuenta que lo anterior utiliza e-TeX para permitirle evitar una mayor expansión dentro de un archivo \edef
o similar. Si las extensiones no están disponibles, cambie el último auxiliar a
\long\def\@@trim@spaces@iv##1\q@nil##2\q@stop{%
\@gobble##1%
}%
con la condición de que esto signifique que hay que tener cuidado con lo que se pasa.
Una segunda cosa a tener en cuenta es que hay algunos tokens "especiales" en lo anterior, por ejemplo \q@nil
, que se utilizan para hacer coincidir los patrones de argumentos macro y, por lo tanto, no pueden estar en la entrada. Eso realmente debería estar bien con 'texto', pero podrías usar algo aún más oscuro como \catcode`\Q=3
entonces Q
(código cat de cambio matemático) si quisieras.
Eliminar los demás elementos solicitados significaría buscarlos todos por separado. Eso suena bastante complicado en el caso de \hspace
/ \hskip
ya que presumiblemente el espaciado podría darse en cualquier unidad válida, incluso antes de preocuparnos por cosas como
\def\foo{10 pt }
\hskip\foo
Como sabrás, lidiar con fichas de grupo es complicado en el mejor de los casos, por lo que encontrar un grupo vacío también podría ser difícil. (Supongo que necesitarías usar un bucle: toma cada token en la entrada, mira si está vacío y si no, agrégalo a la pila de "conservar").
Además, creo que este tipo de información es bastante improbable en la información real. Recortar espacios explícitos tiene sentido, pero no estoy convencido de los otros elementos (a menos que haya algún caso particular aquí en el que haya buenas posibilidades de seleccionar los otros elementos).
Respuesta2
Definitivamente le recomiendo que use la respuesta de Joseph en casos de uso práctico, aunque solo elimina espacios explícitos y no cosas como \
o \hskip
.
Recortar dichos espacios desde la derecha es sencillo (hasta cierto punto): \unskip
, luego repita si \lastskip
no es cero. Sin embargo, esto puede engañarse si hay un salto de tamaño 0pt
.
Recortar \hspace
y amigos de la izquierda, también dentro de las macros nos obliga a realizar manualmente todas las ampliaciones de las macros. Peor aún: dado que \hspace
uses \@ifnextchar
, también debemos realizar tareas. Vea el código a continuación.
Tenga en cuenta que \hspace*
utiliza las primitivas de TeX \vrule
y \penalty
para las cuales no he implementado soporte. Detendrán ambos \trimleft
y \trimright
. Veo cómo solucionarlo \trimleft
(a un costo terrible), pero no para \trimright
, ya que TeX no tiene \lastrule
. LuaTeX podría ayudar.
\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}