我有一个宏,既可以在 LaTeX 中工作,也可以在纯 TeX 中工作,我想测试它的一个参数(可能只是文本)是否为空。目前我这样做:
\bgroup
\setbox0=\hbox{#1}
\ifdim\wd0=0pt
it is empty
\else
it is not empty
\fi
\egroup
我想知道这是否是“正确”的方法,或者是否存在其他更“类似的最佳实践”。
谢谢。
答案1
提问方法
\bgroup
\setbox0=\hbox{#1}
\ifdim\wd0=0pt
it is empty
\else
it is not empty
\fi
\egroup
如果用于一般测试,存在几个问题:
\setbox0\hbox{#1}
#1
当包含顶级 命令时,会泄漏颜色特效\color
,因为颜色宏用于\aftergroup
在当前组 ( ) 之后重置颜色\hbox
。可以使用附加组为纯 TeX 和 LaTeX 修复此问题:\setbox0=\hbox{\begingroup#1\endgroup}
在LaTeX中
\sbox0{#1}
可以使用。#1
可以包含材料,但整体宽度为零,示例:\hbox{$\not$}% the width of the glyph `\not` is zero so that `\not=` forms the "not equals" symbols \hbox{\rlap{Text}}
#1
可以包含太多材料,以致宽度溢出。TeX 不会抛出错误,但宽度可能会意外再次为零。的宽度
\hbox
取决于字体。例如,TikZ\nullfont
在其环境中设置,因此宽度始终为零。\bgroup
和是和\egroup
的宏形式。它们在数学中有一个严重的副作用,即它们形成一个数学子公式,其行为如同数学普通原子并影响水平间距。这可以通过使用和来修复。{
}
\begingroup
\endgroup
基于宏定义测试
#1
可以放入宏中,然后测试宏是否为空:
\def\param{#1}%
\ifx\param\empty
The parameter is empty.%
\else
The parameter is not empty.%
\fi
这还不完美,因为#1
可能包含#
,这在宏定义中是有问题的,因为它们需要加倍和编号。这可以通过使用令牌寄存器来避免:
\toks0={#1}%
\edef\param{\the\toks0}% No full expansion, the token register is unpacked only
\ifx\param\empty
...
在 LaTeX 中\@empty
可以使用,但是它也提供了纯 TeX 的\empty
。
可以使用组来消除设置\toks0
和定义的副作用:\param
\begingroup \toks0={#1}% \edef\param{\the\toks0}% \expandafter\endgroup \ifx\param\empty ...
此解决方案适用于纯 TeX 和 LaTeX;不使用 e-TeX。由于分配和定义,代码不可扩展。
可扩展测试
如果 e-TeX 可用,则\detokenize
允许使用安全的可扩展方法,另请参阅回答PhilipPirrip 的评价:
\if\relax\detokenize{#1}\relax The parameter is empty.% \else The parameter is not empty.% \fi
因为\detokenize
将参数转换为具有类别代码其他(与数字相同)和空格(如果是空格)的简单字符,它不包含任何命令标记和其他可能会中断的有问题的内容
\if
。
如果没有 e-TeX,测试就不应该使用\if
,但是\ifx
:
\ifx\relax#1\relax
...
\relax
但是,在参数 的开头可能存在 一个有意义的宏#1
。因此\relax
应该用不太可能使用的内容替换#1
,例如:
\def\TestEmptyFence{TestEmptyFence}
\ifx\TestEmptyFence#1\TestEmptyFence
...
或者 Donald Arseneau ( url.sty
, ...) 经常使用具有不寻常 catcode 的字符:
\begingroup
\catcode`Q=3 %
\gdef\MyEmptyTestMacro#1{%
\ifxQ#1Q%
...
}%
\endgroup % restore catcode of Q
但是,这些没有 e-TeX 的可扩展测试可能会被破坏,例如,如果#1
包含不匹配的\if
命令。为了减少这些问题,请参阅更详细的回答乌尔里希·迪茨 (Ulrich Diez) 著。
我通过在代码块中添加引号环境,标记了最佳解决方案,即不使用 e-TeX 不可扩展和使用 e-TeX 可扩展。
改进 if 分支
\def\foobar#1#2#3{%
\if...
#2% if true
\else
#3% otherwise
\fi
可以改进,因为代码有限制,即#2
后面跟着\else
并且#3
后面跟着\fi
。因此#2
和#3
后面都不能包含需要以下参数的宏,例如:
\foobar{...}{\textit}{\textbf}{Hello}
而不是Hello
,\textit
获取\else
并\textbf
获取\fi` 作为参数,从而破坏代码。
标准方法是先完成构造然后通过和\if
选择参数:\@firstoftwo
\@secondoftwo
\def\foobar#1{%
\if...
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
首先关闭\expandafter
当前 if 分支。宏\@firstoftwo
和\@secondoftwo
在 LaTeX 中定义:
\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}
Plain TeX 没有等效项,因此需要在那里定义它们。
答案2
以下是一个可替代的可扩展解决方案:
- 它用
- 不
e-TeX
, - 没有 TeX 条件,
- 没有
\expandafter
, - 没有
\string
的。
- 不
- 在所有情况下,它都分三步展开(使用一些常见的
\romannumeral0
包装器,可以分两步展开),这可能使其速度相当快, - 它完全基于适当分隔的宏的扩展,但测试的参数不应包含顶级
\IfNoTokB
标记(因为它出现在辅助宏的参数文本中)。
正如 Ulrich Diez 的回答,宏仅检查参数是否有标记或没有标记,并且#1=\empty
参数被声明为非空,尽管其展开为空。
代码(对于Plain
或LaTeX
):
% auxiliary macros
\long\def\IfNoTokA #1\IfNoTokB \IfNoTokB {}
\long\def\IfNoTokB \IfNoTokC #1#2{#1}
\long\def\IfNoTokC #1#2{#2}% this is \@secondoftwo in LaTeX
\long\def\IfNoTokens #1{\IfNoTokA\IfNoTokB #1\IfNoTokB\IfNoTokB\IfNoTokC }
%% The definition could be simplified if it was not asked to
%% distinguish #1 = space tokens from #1 = truly no token
替代方案是,现在唯一被禁止的令牌#1
将是积极的反斜杠(一个不太可能的标记,但\IfNoTokB
已经是了):
% active \ replaces \IfNoTokB
\begingroup
\catcode`\|=0
\catcode`\\=13
|long|gdef|IfNoTokA #1\\{}
|long|gdef\|IfNoTokC #1#2{#1}
|long|gdef|IfNoTokC #1#2{#2}% = \@secondoftwo
|long|gdef|IfNoTokens #1{|IfNoTokA\#1\\|IfNoTokC }
|endgroup
用法:
%% \IfNoTokens
%% {<The macro argument which is to be checked>}%
%% {<Tokens to be delivered in case of empty argument>}%
%% {<Tokens to be delivered in case of non emty argument>}%
%%
测试:
\tracingmacros1
empty? (yes): \IfNoTokens {}{Yes}{No}
empty? (no): \IfNoTokens { }{Yes}{No}
empty? (no): \IfNoTokens {{}}{Yes}{No}
empty? (no): \IfNoTokens {\if}{Yes}{No}
empty? (no): \IfNoTokens {#}{Yes}{No}
empty? (no): \IfNoTokens {\A}{Yes}{No}
\bye
以下是显示扩展的日志:
#1=empty
:
\IfNoTokens #1->\IfNoTokA \IfNoTokB #1\IfNoTokB \IfNoTokB \IfNoTokC
#1<-
\IfNoTokA #1\IfNoTokB \IfNoTokB ->
#1<-
\IfNoTokB \IfNoTokC #1#2->#1
#1<-Yes
#2<-No
#1=space token
:
\IfNoTokens #1->\IfNoTokA \IfNoTokB #1\IfNoTokB \IfNoTokB \IfNoTokC
#1<-
\IfNoTokA #1\IfNoTokB \IfNoTokB ->
#1<-\IfNoTokB
\IfNoTokC #1#2->#2
#1<-Yes
#2<-No
#1 = {}
:
\IfNoTokens #1->\IfNoTokA \IfNoTokB #1\IfNoTokB \IfNoTokB \IfNoTokC
#1<-{}
\IfNoTokA #1\IfNoTokB \IfNoTokB ->
#1<-\IfNoTokB {}
\IfNoTokC #1#2->#2
#1<-Yes
#2<-No
#1=\if
:
\IfNoTokens #1->\IfNoTokA \IfNoTokB #1\IfNoTokB \IfNoTokB \IfNoTokC
#1<-\if
\IfNoTokA #1\IfNoTokB \IfNoTokB ->
#1<-\IfNoTokB \if
\IfNoTokC #1#2->#2
#1<-Yes
#2<-No
#1=#
:
\IfNoTokens #1->\IfNoTokA \IfNoTokB #1\IfNoTokB \IfNoTokB \IfNoTokC
#1<-##
\IfNoTokA #1\IfNoTokB \IfNoTokB ->
#1<-\IfNoTokB ##
\IfNoTokC #1#2->#2
#1<-Yes
#2<-No
#1=\A
:
\IfNoTokens #1->\IfNoTokA \IfNoTokB #1\IfNoTokB \IfNoTokB \IfNoTokC
#1<-\A
\IfNoTokA #1\IfNoTokB \IfNoTokB ->
#1<-\IfNoTokB \A
\IfNoTokC #1#2->#2
#1<-Yes
#2<-No
答案3
EGreg 对此有一个很好的答案,请查看 https://tex.stackexchange.com/a/127506/65222 以下是相关部分:
一个巧妙的例子\if
是测试一个参数是否为空:
\def\cs#1{%
\if\relax\detokenize{#1}\relax
The argument is empty%
\else
The argument #1 is non empty%
\fi
}
它使用\detokenize
e-TeX 特性。如果参数为空,则比较将在\relax
和之间进行\relax
,就其而言,它们是相等的\if
;否则,\detokenize
将返回一个字符串(类别代码为 12),并且\relax
永远不会等于 的字符\if
。
答案4
尝试一下这个ifmtarg
包。例如(尽量不要出现拼写错误):
...
\usepackage{ifmtarg}
\makeatletter
\newcommand{\isempty}[1]{%
\@ifmtarg{#1}{YES}{NO}}
\newcommand{\isnotempty}[1]{%
\@ifnotmtarg{#1}{YES}}
\makeatother
...
那么结果是:
\isempty{} --> YES
\isempty{ } --> YES
\isempty{E} --> NO
\isempty{ E } --> NO
\isnotempty{} -->
\isnotempty{ } -->
\isnotempty{E} --> YES
\isnotempty{ E } --> YES