Cortando espaços em branco ao redor do texto (como LTRIM, RTRIM e TRIM)

Cortando espaços em branco ao redor do texto (como LTRIM, RTRIM e TRIM)

Ao trabalhar no Excel com strings de texto, é conveniente usar oFunções LTRIM, RTRIM e TRIMque remove o espaço em branco ao redor da string de texto. Qual seria uma maneira eficiente de duplicar isso no LaTeX?

Por exemplo, digamos que você produza variáveis ​​programaticamente

\def\firstname{FirstName}% First name
\def\lastname{LastName}% Last name
\edef\fullname{\firstname\ \lastname}% Full name
\fullname% Display full name

mas também deve acomodar quando\firstname ou \lastnamepode estar vazio. Sem testar se eles estão vazios, algo como

\def\firstname{FirstName}% First name
\def\lastname{}% Last name (none)
\edef\fullname{\firstname\ \lastname}% Full name
\trim{\fullname}% Display full name

cuidaria disso. Meu primeiro pensamento foi definir

\def\trim#1{\ignorespaces#1\unskip}

mas isto certamente não funcionaria num cenário geral, uma vez que não se aplica a um grupo vazio. Além disso, \unskipcuidaria apenas do último salto, podendo haver mais de um.

Em particular, é possível definir \trimtal que irá cuidar de

  • \hspaces e \hskips?
  • \é?
  • grupos vazios {}e talvez tokens não imprimíveis como \relax?

insira a descrição da imagem aqui

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

Responder1

Aparar todos os espaços explícitos em torno da entrada é certamente possível. Existem várias abordagens sobre esse problema: eu escolheria aquela para a qual Bruno Le Floch escreveu expl3como \tl_trim_spaces:n. Isso pode ser usado fazendo

\usepackage{expl3}
\ExplSyntaxOn
\cs_new_eq:NN \trimspaces \tl_trim_spaces:n
\ExplSyntaxOff

Alternativamente, a implementação pode ser incluída diretamente na fonte e assim evitar qualquer dependência:

\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

Isso removerá todos os espaços das extremidades da entrada, mesmo que você faça algo complicado \edef\test{ \space foo \space}para começar (para que haja vários espaços nas duas extremidades). (Se você estiver feliz em se limitar a este caso, entãoxparseoferece o \TrimSpacespós-processador para argumentos usando este método.)

A forma como o acima funciona é que existem dois loops: um para espaços no início da entrada ( \@@trim@spaces@i), um segundo para aqueles no final ( \@@trim@spaces@iii). Primeiro, \@@trim@spacesconfigure as coisas de forma que os marcadores corretos estejam no lugar. Na etapa 'inicial', \@@trim@spaces@icorresponde a um argumento que consiste em \q@markseguido por um espaço (o próprio espaço é descartado). Se houver mais espaços então #1e #3estará vazio e #2será a entrada restante, o que significa que \@@trim@spaces@iserá chamado novamente com a entrada restante. Por outro lado, se não houver espaços restantes na entrada, então #2corresponde à entrada vazia configurada por \@@trim@spaces, #1é a entrada do usuário com todos os espaços iniciais removidos e #3é \@@trim@spaces@ii. Este último interrompe o loop e passa para \@@trim@spaces@iii(a \q@marké deixado na frente da entrada do usuário para evitar qualquer perda de colchetes: veja mais tarde). Neste segundo loop, os espaços no final da entrada aparecerão logo antes \q@nil. Este padrão é correspondido pelo argumento to \@@trim@spaces@iii. Se houvesse um espaço à direita na entrada, então #1is a entrada do usuário com o espaço removido (mas ainda com um \q@mark) inicial e #2is \@@trim@spaces@iii, levando a um loop. No entanto, quando os espaços finais se esgotam, #2é \@@trim@spaces@ive #1é o \q@mark <user input>\q@nil\@@trim@spaces@iii. O \q@nil\@@trim@spaces@iiié removido pelo padrão do argumento \@@trim@spaces@ivantes que o início \q@amrkseja removido \@gobble(com a \unexpandedprevenção de expansão adicional).

Observe que o acima usa e-TeX para permitir evitar expansão adicional dentro de um \edefou similar. Se as extensões não estiverem disponíveis, altere o último auxiliar para

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

com a ressalva de que isso significará que você deve ter cuidado com o que é transmitido.

Uma segunda coisa a observar é que existem alguns tokens 'especiais' acima, por exemplo \q@nil, que são usados ​​para corresponder aos padrões de argumento da macro e, portanto, não podem estar na entrada. Isso realmente deveria estar bem com 'texto', mas você poderia usar algo ainda mais obscuro como \catcode`\Q=3então Q(código de mudança matemática) se quisesse.

Remover os demais itens solicitados significaria procurar todos eles separadamente. Isso parece bastante complicado no caso de \hspace/ \hskipjá que presumivelmente o espaçamento poderia ser dado em qualquer unidade válida, mesmo antes de nos preocuparmos com coisas como

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

Como você deve saber, lidar com tokens de grupo é complicado na melhor das hipóteses, portanto, encontrar um grupo vazio também pode ser difícil. (Acho que você precisaria usar um loop: pegue cada token na entrada, veja se está vazio e se não estiver, adicione-o à pilha 'manter'.)

Além disso, penso que este tipo de informação é bastante improvável em termos reais. Cortar espaços explícitos faz sentido, mas não estou convencido sobre os outros itens (a menos que haja algum caso específico aqui onde haja uma boa chance de pegar os outros itens).

Responder2

Definitivamente, aconselho você a usar a resposta de Joseph em casos de uso prático, mesmo que ela remova apenas espaços explícitos, e não coisas como ou \hskip.

Cortar esses espaços da direita é simples (até certo ponto): \unskipe repita se for \lastskipdiferente de zero. No entanto, isso pode ser enganado se houver um salto de size 0pt.

Cortar \hspacee amigos da esquerda, também dentro das macros, nos obriga a realizar manualmente todas as expansões das macros. Pior ainda: como \hspaceusa \@ifnextchar, precisamos também realizar atribuições. Veja o código abaixo.

Observe que \hspace*usa primitivas do TeX \vrulee \penaltypara as quais não implementei nenhum suporte. Eles vão parar ambos \trimlefte \trimright. Vejo como consertar isso \trimleft(a um custo terrível), mas não para \trimright, já que o TeX não tem \lastrule. LuaTeX pode ajudar.

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

informação relacionada