可扩展测试

可扩展测试

我有一个宏,既可以在 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参数被声明为非空,尽管其展开为空。

代码(对于PlainLaTeX):

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

它使用\detokenizee-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

相关内容