Recortar espacios en blanco alrededor del texto (como LTRIM, RTRIM y TRIM)

Recortar espacios en blanco alrededor del texto (como LTRIM, RTRIM y TRIM)

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 \lastnamepuede 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, \unskipsólo se ocuparía del último salto, del que puede haber más de uno.

En particular, ¿es posible definir \trimalgo que se encargue de

  • \hspaces y \hskips?
  • \'¿s?
  • ¿Grupos vacíos {}y quizás tokens que no se imprimen como \relax?

ingrese la descripción de la imagen aquí

\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 expl3como \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, entoncesxparseofrece el \TrimSpacesposprocesador 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@spacesconfigure las cosas de manera que los marcadores correctos estén en su lugar. En el paso 'principal', \@@trim@spaces@icoincide con un argumento que consta de \q@markseguido de un espacio (el espacio en sí se descarta). Si hay más espacios entonces #1y #3estará vacío y #2será la entrada restante, lo que significa que \@@trim@spaces@ise llamará nuevamente con la entrada restante. Por otro lado, si no quedan espacios en la entrada, entonces #2coincide con la entrada vacía configurada por \@@trim@spaces, #1es la entrada del usuario sin todos los espacios iniciales y #3es \@@trim@spaces@ii. Este último detiene el bucle y pasa la mano \@@trim@spaces@iii(a \q@markse 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 #1la entrada del usuario es con el espacio eliminado (pero aún con un \q@mark) inicial y #2es \@@trim@spaces@iii, lo que conduce a un bucle. Sin embargo, cuando se agotan los espacios finales, #2es \@@trim@spaces@ivy #1es el \q@mark <user input>\q@nil\@@trim@spaces@iii. El argumento \q@nil\@@trim@spaces@iiise elimina antes de que se elimine \@@trim@spaces@ivel 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 \edefo 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=3entonces 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/ \hskipya 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 \lastskipno es cero. Sin embargo, esto puede engañarse si hay un salto de tamaño 0pt.

Recortar \hspacey 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 \hspaceuses \@ifnextchar, también debemos realizar tareas. Vea el código a continuación.

Tenga en cuenta que \hspace*utiliza las primitivas de TeX \vruley \penaltypara las cuales no he implementado soporte. Detendrán ambos \trimlefty \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}

información relacionada