텍스트 주위의 공백 자르기(예: 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다음 사항을 처리하도록 정의하는 것이 가능합니까?

  • \hspaces와 \hskips?
  • \'에스?
  • 빈 그룹 {}및 아마도 \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

입력 주위의 명시적인 공백을 모두 잘라내는 것은 확실히 가능합니다. 이 문제에 대한 접근 방식은 여러 가지가 있습니다. 저는 Bruno Le Floch가 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올바른 마커가 제자리에 있도록 설정합니다. '선행' 단계에서는 뒤에 공백이 오는 \@@trim@spaces@i것으로 구성된 인수와 일치합니다 \q@mark(공백 자체는 삭제됩니다). 공백이 더 있으면 는 #1비어 #3있고 #2나머지 입력이 됩니다. 즉, \@@trim@spaces@i나머지 입력으로 다시 호출됩니다. 반면, 입력에 공백이 남아 있지 않으면 에 의해 #2설정된 빈 입력과 일치하며 \@@trim@spaces#1모든 선행 공백이 제거된 사용자 입력이고 #3입니다 \@@trim@spaces@ii. 후자는 루프를 중지하고 전달합니다 \@@trim@spaces@iii( \q@mark중괄호 손실을 방지하기 위해 사용자 입력 앞에 a가 남습니다. 나중에 참조). 이 두 번째 루프에서는 입력 끝의 공백이 바로 앞에 나타납니다 \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인수 패턴에 의해 제거 됩니다 .\@@trim@spaces@iv\q@amrk\@gobble\unexpanded

\edef위의 내용은 e-TeX를 사용하여 유사한 내부의 추가 확장을 방지합니다 . 확장을 사용할 수 없는 경우 마지막 보조를 다음으로 변경하십시오.

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

단, 이는 통과되는 내용에 주의해야 함을 의미합니다.

두 번째로 주목해야 할 점은 위의 예에는 \q@nil매크로 인수 패턴을 일치시키는 데 사용되므로 입력에 포함될 수 없는 일부 '특수' 토큰이 있다는 것입니다. '텍스트'에는 정말 괜찮지만, 원한다면 훨씬 더 모호한 것을 사용할 수도 있습니다 \catcode`\Q=3( Q수학 시프트 캣코드).

요청된 다른 항목을 제거한다는 것은 해당 항목을 모두 별도로 검색하는 것을 의미합니다. \hspace/ \hskip아마도 우리가 다음과 같은 것에 대해 걱정하기 전에도 공백이 유효한 단위로 주어질 수 있기 때문에 이는 매우 까다롭게 들립니다 .

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

아시다시피, 그룹 토큰을 처리하는 것은 가장 까다로울 수 있으므로 빈 그룹을 찾는 것도 어려울 수 있습니다. (루프를 사용해야 할 것 같습니다. 입력의 각 토큰을 가져와서 비어 있는지 확인하고 '유지' 더미에 추가하지 않는지 확인하세요.)

게다가 이런 입력은 실제 입력에서는 거의 불가능하다고 생각합니다. 명시적인 공백을 자르는 것은 의미가 있지만 다른 항목에 대해서는 확신이 없습니다(여기서 다른 항목을 선택할 가능성이 높은 특별한 경우가 아닌 한).

답변2

나 같은 것은 제거하지 않고 명시적인 공백만 제거하더라도 실제 사용 사례에서는 Joseph의 답변을 사용하는 것이 좋습니다 \hskip.

오른쪽에서 이러한 공백을 잘라내는 것은 간단합니다(어느 정도). 0이 아닌 \unskip경우 반복합니다 . \lastskip그러나 크기를 건너뛰면 속일 수 있습니다 0pt.

\hspace매크로 내에서도 왼쪽의 트리밍 및 친구를 사용하면 모든 매크로 확장을 수동으로 수행해야 합니다. 더 나쁜 점은 \hspacepresents를 사용하기 때문에 \@ifnextchar할당도 수행해야 한다는 것입니다. 아래 코드를 참조하세요.

\hspace*TeX의 기본 요소를 사용하며 이에 \vrule대한 \penalty지원은 구현하지 않았습니다. \trimleft과 가 모두 중지됩니다 \trimright. \trimleft(엄청난 비용으로) 문제를 해결하는 방법을 알지만 \trimrightTeX에는 \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}

관련 정보