可扩展的“字符扫描”命令,可保留空格

可扩展的“字符扫描”命令,可保留空格

David Carlisle 最近针对以下问题提供了一个简洁且相当优雅的 TeX 解决方案:可扩展的“字符扫描”命令。不幸的是,David 的解决方案没有保留输入字符串中的空格。因此,给出 David 的代码:

\def\expandloop#1{\xloop#1\relax}
\def\xloop#1{%
  \ifx\relax#1\else[#1]\expandafter\xloop\fi}

无论我们是否提供

\edef\zzzz{\expandloop{TEST1}}

或者

\edef\zzzz{\expandloop{  T ES T    1 }}

为什么会发生这种情况?

是否有一种解决方案,可以将空格字符与输入字符串中的其他字符相同地处理?

答案1

下面显示了三个循环的输出。

  1. 如上所述,原始版本删除了所有空间标记,即 catcode 10 的标记。
  2. 如果空格字符有 catcode 12(其他),那么它会被视为普通字符并且不会被删除。
  3. 变体循环会提前扫描以查找真实空格并使其安全,但正如@Ryan 的回答中所述,TeX 已将相邻的空格字符压缩为单个标记,因此[ ]每次运行空格时您只能获得一个标记。但是,此版本适用于普通的 catcode 10 个字符。
  4. 第四种是第一种的变体,它\string在循环中插入一个扩展,这样括号而不是括号组就会被视为单个标记。(正如 Bruno 所指出的,最初的三个版本并没有这样做)。

哪一个是最可接受的取决于你真正想要的是什么。

$ pdftex xloop2
This is pdfTeX, Version 3.1415926-2.3-1.40.12 (TeX Live 2011/Cygwin)
 restricted \write18 enabled.
entering extended mode
(./xloop2.tex
2:[ ][ ][T][E][S][T][ ][ ][1]
3:[ ][T][E][S][T][ ][1]
3:[ ][T][ES][T][ ][1]
4:[T][{][E][S][}][T][1]
 )
No pages of output.
Transcript written on xloop2.log.

\def\expandloop#1{\xloop#1\relax}

\def\xloop#1{%
  \ifx\relax#1\else[#1]\expandafter\xloop\fi}

%%%%%%%%%%%%%%%%%%%%%%%%%%%

\edef\tmp{\def\noexpand\expandloopb##1{\noexpand\xloopb##1\relax\space \valign}}
\tmp

\def\xloopb#1 #2\valign{%
\ifx\relax#2\relax
\xxloopb#1%
\else
\xloopb#1\xloopb#2\valign
\fi}


\def\xxloopb#1{%
  \ifx\relax#1\else[\ifx\xloopb#1\space\else#1\fi]\expandafter\xxloopb\fi}

%%%%%%%%%%%%%%%%%%%%%%%%%%%

\def\expandloopc#1{\xloopc#1!}

\def\xloopc{\expandafter\xxloopc\string}

\def\xxloopc#1{%
  \ifx!#1\else[#1]\expandafter\xloopc\fi}


%%%%%%%%%%%%%%%%%%%%%

{\catcode`\ =12
\immediate\write20{2:\expandloop{  TEST  1}}}

\immediate\write20{3:\expandloopb{  TEST  1}}


\immediate\write20{3:\expandloopb{  T{ES}T  1}}

\immediate\write20{4:\expandloopc{  T{ES}T  1}}

\bye

答案2

可能有点过头了,但 Bruno Le Floch 在 LaTeX3 代码库中提出了“标记列表操作”的概念。这是一个通用概念,可以应用于不同的场景,例如\MakeUppercase 是否有纯粹可扩展的变体?

我们没有为通用机制提供公共接口,因为不清楚是否需要这样做。因此,我在这里使用标准 TeX 编码重新编码了该方法。由于 LaTeX3 需要\pdfstrcmp或等效功能,我正在加载pdftexmcds包以提供\pdf@strcmp

\RequirePackage{pdftexcmds}
\makeatletter
\long\def\tl@action#1#2#3#4#5{%
  \ifnum\iffalse{\fi`}=\z@\fi
  \tl@action@loop#5\q@action@mark\q@action@stop
  {#4}#1#2#3%
  \tl@action@result{}%
}
\long\def\tl@action@loop#1\q@action@stop{%
  \tl@if@head@N@type{#1}
    {\tl@action@normal}
    {%
      \tl@if@head@grouped{#1}
        {\tl@action@group}
        {\tl@action@space}%
    }%
  #1\q@action@stop
}
\long\def\tl@action@normal#1#2\q@action@stop#3#4{%
  \ifx\q@action@mark#1%
    \expandafter\tl@action@end
  \fi
  #4{#3}#1%
  \tl@action@loop#2\q@action@stop
  {#3}#4%
}
\long\def\tl@action@end#1\tl@action@result#2{%
  \ifnum`{=\z@}\fi
  \z@
  #2%
}
\long\def\tl@action@group#1#2\q@action@stop#3#4#5{%
  #5{#3}{#1}%
  \tl@action@loop#2\q@action@stop
  {#3}#4#5%
}
\expandafter\long\expandafter\def\expandafter
  \tl@action@space\space#1\q@action@stop#2#3#4#5{%
  #5{#2}%
  \tl@action@loop#1\q@action@stop
  {#2}#3#4#5%
}
\long\def\tl@action@output#1#2\tl@action@result#3{%
  #2%
  \tl@action@result{#3#1}%
}
\def\q@action@mark{\q@action@mark}
\long\def\tl@if@head@N@type#1{%
  \ifnum\pdf@strcmp
    {\unexpanded\expandafter{\@firstofone#1{}}}{\unexpanded{#1{}}}=\z@
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi  
}
\long\def\tl@if@head@grouped#1{%
  \ifcat*\expandafter\@gobble\expandafter{\expandafter{\string#1?}}*%
    \expandafter\@secondoftwo
  \else
    \expandafter\@firstoftwo
  \fi
}
\long\def\tl@wrap#1{%
  \unexpanded\expandafter{%
    \romannumeral
    \tl@action
      \tl@wrap@normal
      \tl@wrap@normal
      \tl@wrap@space
      {}
      {#1}%
  }%
}
\long\def\tl@wrap@normal#1#2{%
  \tl@action@output{[#2]}%
}
\long\def\tl@wrap@space#1{%
  \tl@action@output{[ ]}%
}
\makeatother
\documentclass{article}
\begin{document}
\makeatletter
\tl@wrap{abc de  { } f}
\makeatother
\end{document}

在很多方面,仔细扫描空间和群组的想法与 Ryan 的相同,尽管这里的实现显然有点不同。(\tl@action可以用于很多事情,因此有空洞和重复的论点!)


关于“为什么会发生这种情况?”这个问题,有两点需要注意。首先,TeX 会将多个空格读作一个空格。因此,

foo  bar  baz

将被解读为

foo bar baz

除非有类似的东西\obeyspaces在起作用。后者必须在阅读材料之前设置,并且不可扩展。

其次,以下形式的宏

\def\foo#1{<do stuff>}

\foo读取时会跳过后面的空格#1。因此

\foo tokens

将跳过 之后的空格\foo,并将其读取t为第一个标记。为了避免这种情况,需要仔细控制标记,以免发生这种情况。

答案3

在 TeX 中,当宏被展开并收集其参数时,所有中间的空格都会被忽略。因此,\xloop只需从一个字母跳到下一个字母并占用所有空间。我写道一个答案曾经有一个宏,它提供了一个完全可扩展的宏,用于“清理”文本:完全删除控制序列和括号。您可以修改该答案,例如,[]通过进行以下更改来包围每个标记:\SanitizeTokens用以下宏替换那里给出的:

\newcommand\SanitizeTokens[1]{%
 \ifx\SanitizeStop#1%
 \else
  \ifx\SanitizedSpace#1%
   \RealSpace
  \else
   \ifx\ #1%
    \RealSpace
   \else
    \if\relax\noexpand#1%
     [\string#1]%
    \else
     [#1]%
    \fi
   \fi
  \fi
  \expandafter\SanitizeTokens
 \fi
}

这将产生以下效果:

\Sanitize{TEST1} = [T][E][S][T][1]
\Sanitize{ T E S T   1} =  [T] [E] [S] [T] [1]
\Sanitize{T{EST}1} = [T][E][S][T][1]
\Sanitize{TEST\macro} = [T][E][S][T][\macro]

请注意,多个空格会被压缩。这是因为 TeX 本身在解析输入时会将重复的空格合并为一个空格。要解决这个问题,您必须使用 catcode 来解决问题,而且这是不可扩展的。可能的让花括号也包含在括号中,如果这对您来说是个问题。我不知道这是否可取。最后,我选择强制宏名称不扩展;\string如果您愿意,您可以更改其中的行。

注意:您可能想知道,对于这个简单的更改,我编写的极其复杂的宏是否真的有必要。答案是肯定的:所有的复杂性都是为了小心翼翼地绕过宏解析器,以避免丢失空格和被组欺骗。

相关内容