我有一个命令偶尔会产生我不想要的空格。
\def\testa{a}
\def\testb{a}
\newcommand{\mycommand}[1]{\ifx\testa\testb{#1}\fi}
当\testa
和\testb
不同时,我最终会在以下示例中得到不需要的空格:
Testing \mycommand{This} \mycommand{is} \mycommand{my} \mycommand{trial} \mycommand{run.} Testing
我以为我可以做一些聪明的事情
\def\sweetnothing{}
\def\testa{a}
\def\testb{b}
\newcommand{\mycommand}[1]{\ifx\testa\testb{#1}\else\sweetnothing\fi}
我认为这会使上面的测试行等同于以下内容
Testing \sweetnothing \sweetnothing \sweetnothing \sweetnothing Testing
但事实并非如此。
我尝试了一些\expandafter
魔法
\newcommand{\mycommand}[1]{\ifx\testa\testb{#1}\else\expandafter\sweetnothing\fi}
因为我认为那基本上会展开到\sweetnothing
后面有一个空白处。但没那么幸运。
这是一个 M(non)WE:
\documentclass{article}
\makeatletter
%%
\def\testa{a}
\def\testb{b}
\newcommand{\mycommand}[1]{\ifx\testa\testb{#1}\fi}
%%
\def\sweetnothing{}
\newcommand{\mycommandvar}[1]{\ifx\testa\testb{#1}\else\sweetnothing\fi}
%%
\makeatother
\pagestyle{empty}
\begin{document}
\textbf{Line 1:} Testing \mycommand{This} \mycommand{is} \mycommand{my} \mycommand{trial} \mycommand{run.} Testing
\textbf{Line 2:} Testing \mycommandvar{This} \mycommandvar{is} \mycommandvar{my} \mycommandvar{trial} \mycommandvar{run.} Testing
\textbf{Line 3:} Testing {} {} {} {} {} Testing
\textbf{Line 4:} Testing \sweetnothing \sweetnothing \sweetnothing \sweetnothing Testing
\end{document}
我真的希望所有三行看起来都像第 4 行,但实际上\testa
它们\testb
是不同的。
然后我又开始胡思乱想,尝试了类似这样的东西
\documentclass{article}
\makeatletter
\def\testa{a}
\def\testb{b}
\def\eat@white@space#1{\ifcat #1\relax\else#1\fi}
\newcommand{\mycommand}[2]{\ifx\testa\testb{#1#2}\else\expandafter\eat@white@space\fi}
\makeatother
\pagestyle{empty}
\begin{document}
Testing \mycommand{This} \mycommand{is} \mycommand{my} \mycommand{trial} \mycommand{run.} Testing
\end{document}
但结果完全出乎我的意料。我没想到它会成功,但也没料到会失败成这样。
我想法我所做的是将空间的 catcode 与第一个参数传递的 catcode 进行比较。如果它们相同,那么我想丢弃第二个参数(此时应该有一个递归调用---我不会这样做)。如果它们不相同,那么我想保留第一个参数。
有任何想法吗?
答案1
让我们看看会发生什么:如果\testa
和\testb
相等,就没有问题;当\testa
和\testb
不同时,
Test \mycommand{this} is
您将得到“Test••is”(其中 • I 表示输出中的空格)。这与手头的宏无关,但它是规则的结果。事实上,当\mycommand{this}
扩展为零时,输入已经被标记,因此 TeX 不再处于连续空格减少为一个空格标记的状态。
如何删除空格取决于你想做什么。
\newcommand{\mycommand}[1]{%
\ifx\testa\testb
#1%
\else
\ifhmode\unskip\fi
\fi}
和
\newcommand{\mycommand}[1]{%
\ifx\testa\testb
#1%
\else
\ignorespaces
\fi}
是关于删除两个空格中的哪一个;用\unskip
it 表示删除之前的空格,用\ignorespaces
it 表示删除之后的空格。
但是,存在一个概念上的区别:\unskip
如果存在粘连,则仅将其移除,与宏扩展无关;\ignorespaces
相反,它会扩展以下标记,直到找到一个不是空间标记的不可扩展标记,并吞噬它在此过程中找到的所有空间标记。 (这就是为什么在第二个定义中没有必要\expandafter
在它前面使用,因为它将开始扩展\fi
)。
因此,如果您担心扩展可能过早发生,最好使用第一种模式。如果您不需要在仅扩展的环境中使用,则使用\relax
before可能会很有用。\ifx
\mycommand
为了更清楚起见,\unskip
在执行命令时,它在胃中起作用;相反,\ignorespaces
在宏扩展期间起作用,比 TeX 执行命令的阶段早得多。因此,如果最后一个节点是胶合节点,前者可以删除最后一个节点,而后者不能删除间距指令,只能删除空格标记。
答案2
@egreg 已经回答了主要问题,所以我想我应该解释一下
\documentclass{article}
\makeatletter
\def\testa{a}
\def\testb{b}
\def\eat@white@space#1{\ifcat #1\relax\else#1\fi}
\newcommand{\mycommand}[2]{\ifx\testa\testb{#1#2}\else\expandafter\eat@white@space\fi}
\makeatother
\pagestyle{empty}
\begin{document}
Testing \mycommand{This} \mycommand{is} \mycommand{my} \mycommand{trial} \mycommand{run.} Testing
\end{document}
\ifcat
扩展标记,直到找到两个不可扩展的标记,然后比较它们的类别代码(为此,任何非字符标记都假定为 16 的假类别代码)。这两个不可扩展的标记将在测试中使用。如果测试为真,则处理从下一个标记开始,并\else
定义为跳转到匹配的\fi
。如果测试为假,TeX 将跳转到匹配的\else
或\fi
并重新开始处理。
所以鉴于\mycommand{This} \mycommand{is}
这两个参数,#1
和#2
是\mycommand
和This
\mycommand
测试\ifx
结果为假,因此处理跳至,\else
输入流如下所示
\expandafter\eat@white@space\fi{is}
\expandafter
扩展了\fi
所以我们有
\eat@white@space{is}
这样就扩展为
\ifcat is\relax\else is\fi
i
和的 catcodes
匹配,因此扩展为\relax
,并且输入流现在看起来像
\mycommand{my} \mycommand{trial} \mycommand{run.} Testing
处理过程和以前一样,只是这次我们要
\eat@white@space{trail}
扩展为
\ifcat trial\relax\else is\fi
这次比较t
和的 catcode r
,因此它扩展为
ial\relax
并ial
最终按照您展示的方式进行排版。
答案3
你要找的宏是\unskip
。如果你将以下两行
\newcommand{\mycommand}[1]{\ifx\testa\testb{#1}\else\unskip\fi}
\newcommand{\mycommandvar}[1]{\ifx\testa\testb{#1}\else\sweetnothing\unskip\fi}
你得到了想要的结果:
代码:
\documentclass{article}
\makeatletter
%%
\def\testa{a}
\def\testb{b}
\newcommand{\mycommand}[1]{\ifx\testa\testb{#1}\else\unskip\fi}
\def\sweetnothing{}
\newcommand{\mycommandvar}[1]{\ifx\testa\testb{#1}\else\sweetnothing\unskip\fi}
%%
\makeatother
\pagestyle{empty}
\begin{document}
\textbf{Line 1:} Testing \mycommand{This} \mycommand{is} \mycommand{my} \mycommand{trial} \mycommand{run.} Testing
\textbf{Line 2:} Testing \mycommandvar{This} \mycommandvar{is} \mycommandvar{my} \mycommandvar{trial} \mycommandvar{run.} Testing
\textbf{Line 3:} Testing {} {} {} {} {} Testing
\textbf{Line 4:} Testing \sweetnothing \sweetnothing \sweetnothing \sweetnothing Testing
\end{document}