10

I have a macro that might work either in LaTeX or in plain TeX and I'd like to test whether one of its parameters, which presumably should be just text, is empty. Currently I do it this way:

\bgroup
\setbox0=\hbox{#1}
\ifdim\wd0=0pt
    it is empty
\else
    it is not empty
\fi
\egroup

And I wonder whether this is “proper” way or whether there is some other, more “best practices like”, one.

Thx.

  • In many cases you have \ifvoid. – Przemysław Scherwentke Aug 29 '15 at 7:07
  • @ Przemysław Scherwentke: Yes, but empty box is not void. – Marcel S. Aug 29 '15 at 7:12
  • The test you've got here is not for #1 being empty but having a typeset width of zero (for example, \llap{Hello} would be true here). Which test are you after? – Joseph Wright Aug 29 '15 at 9:05
  • Also \count0=100 as the argument would return true. – egreg Aug 29 '15 at 9:15
  • a tried-and-true plain tex approach is to \setbox0=\hbox{\ignorespaces#1\unskip} and then test for \ifdim\wd0>0pt. if that's true, then the box is not empty. just slightly different from the suggestion given here. haven't ever used it in cases where there might be color overlays or hyperlinks, but if the argument contains (only) spaces, it will filter those out. – barbara beeton Aug 29 '15 at 18:59
13

The method of the question

\bgroup
\setbox0=\hbox{#1}
\ifdim\wd0=0pt
    it is empty
\else
    it is not empty
\fi
\egroup

has several problems, if used for a general test:

  • \setbox0\hbox{#1} leak color specials, when #1 contains top level \color commands, because the color macros use \aftergroup to reset the color after the current group (\hbox). This can be fixed for both plain TeX and LaTeX by using an additional group:

    \setbox0=\hbox{\begingroup#1\endgroup}
    

    In LaTeX \sbox0{#1} can be used.

  • #1 can contain material, but the overall width is zero, examples:

    \hbox{$\not$}% the width of the glyph `\not` is zero so that `\not=` forms the "not equals" symbols
    \hbox{\rlap{Text}}
    
  • #1 can contain so much material, that the width overflows. TeX does not throw an error, but the width can be zero accidentally again.

  • The width of the \hbox depends on the font. For example, TikZ sets \nullfont in its environments, thus the width would always be zero.

  • \bgroup and \egroup are the macro form for { and }. They have a serious side effect in math, that they form a math subformula, which behaves as math ordinary atom and influences the horizontal spacing. This can be fixed by using \begingroup and \endgroup.

Test based on macro definition

#1 can be put into a macro and then the macro can be tested for emptiness:

\def\param{#1}%
\ifx\param\empty
  The parameter is empty.%
\else
  The parameter is not empty.%
\fi

This is not perfect yet, because #1 might contain #, which is problematic in macro definitions, because they need to be doubled and numbered. This can be avoided by the use of a token register:

\toks0={#1}%
\edef\param{\the\toks0}% No full expansion, the token register is unpacked only
\ifx\param\empty
...

In LaTeX \@empty can be used, but it also provides plain TeX's \empty.

The side effects of setting \toks0 and defining \param can be removed by using a group:

\begingroup
  \toks0={#1}%
  \edef\param{\the\toks0}%
\expandafter\endgroup
\ifx\param\empty
...

This solution works in both plain TeX and LaTeX; e-TeX is not used. Because of the assignment and definition the code is not expandable.

Expandable tests

If e-TeX is available, \detokenize allows a safe expandable method, see also the answer of PhilipPirrip:

\if\relax\detokenize{#1}\relax
  The parameter is empty.%
\else
  The parameter is not empty.%
\fi

Because \detokenize converts is argument to simple characters with category code other (same as digits) and the space (in case of the space), it does not contain any command tokens and other problematic stuff, which could break \if.

Without e-TeX the test should not use \if, but \ifx:

\ifx\relax#1\relax
...

However, a macro with meaning \relax might be present at the start of parameter #1. Therefore \relax should be replaced by something, which is unlikely to be used in #1, examples:

\def\TestEmptyFence{TestEmptyFence}

\ifx\TestEmptyFence#1\TestEmptyFence
...

Or Donald Arseneau (url.sty, ...) often uses a character with unusual catcode:

\begingroup
  \catcode`Q=3 %
  \gdef\MyEmptyTestMacro#1{%
    \ifxQ#1Q%
    ...
   }%
 \endgroup % restore catcode of Q

However, these expandable tests without e-TeX can be broken, e.g., if #1 contains unmatched \if commands. To reduce these problems, see the much more elaborate answer of Ulrich Diez.

I have marked the best solutions, non-expandable without e-TeX and expandable with e-TeX, by adding a quote environment to the code block.

Improvement of the if branches

\def\foobar#1#2#3{%
  \if...
    #2% if true
  \else
    #3% otherwise
  \fi

can be improved, because the code has the limitation, that #2 is followed by \else and #3 is followed by \fi. Thus both #2 and #3 cannot contain macros at the end, which expect following parameters, e.g.:

\foobar{...}{\textit}{\textbf}{Hello}

Instead of Hello, \textit gets \else and \textbf gets\fi` as parameter, breaking the code.

The standard way is finishing the \if construct first and selecting the argument via \@firstoftwo and \@secondoftwo:

\def\foobar#1{%
  \if...
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}

The \expandafter closes the current if branch first. The macros \@firstoftwo and \@secondoftwo are defined in LaTeX:

\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}

Plain TeX does not have an equivalent, thus they need to be defined there.

  • Thanks, I like the toks0… \ifx\…\empty approach the best, I guess. In fact, I had it somewhere in the background of my mind, only I couldn't find it and was looking for \ifempty macro instead of just \empty in Olšák's “TeXbook naruby” (TeXbook inside-out)… :-) – Marcel S. Aug 29 '15 at 11:27
  • @heikooberdiek Why do you need \expandafter in \expandafter\endgroup in your example avoiding side effects with \toks0? – Jonathan Komar Mar 15 '18 at 13:41
  • @JonathanKomar The definition of \param is lost after \endgroup. Therefore, \param is expanded by \expandafter before \endgroup is executed. – Heiko Oberdiek Mar 15 '18 at 19:07
6

Here is an alternative expandable solution:

  • it uses
    • no e-TeX,
    • no TeX conditionals,
    • no \expandafter's,
    • no \string's.
  • it expands in three steps in all cases (with some usual \romannumeral0 wrapper one could get it to expand in two steps), which presumably makes it reasonably fast,
  • it is entirely based on expansion of suitably delimited macros, but the tested argument should not contain at top level \IfNoTokB token (as it appears in the parameter texts of the auxiliary macros).

As with Ulrich Diez's answer, the macro only examines whether the argument has tokens or no token, with #1=\empty the argument is declared non-empty, although its expansion would be empty.

Code (for Plain or 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

Alternative where the only forbidden token in #1 will now be the active backslash (a quite unlikely token, but \IfNoTokB already was):

% 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

Usage:

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

Tests:

\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

Here is the log showing the expansions:

#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
  • The list »no e-TeX, no conditionals, no \expandafter's, no \string's« sounds like they were some kind of advantage?! BTW: \IfNoTokens{\IfNoTokB}{xx}{yy} will cause en error – clemens Aug 29 '15 at 18:26
  • 1
    @clemens do you mean some kind of disadvantage? I wasn't implying that, it was only for description. One could try to make some speed comparisons (I have not and I have no idea how this would compare to the e-TeX \detokenize approach). Yes (as mentioned in my list) \IfNoTokB is a forbidden token, I believe this to be an unavoidable defect of my constraints (expandability and no use of \string nor \detokenize) ; one may make the forbidden token very unlikely in normal contexts, like an active T or ` \ ` for example (the latter would create complications in writing up the macro). – user4686 Aug 29 '15 at 19:48
  • No, I thought you advertised them as advantage :) And I was wondering why »no e-TeX« would be an advantage? After all: the number of people working on a system without e-TeX probably is very small :) Even more wondering why »no \expandafter« would be an advantage… but I probably simply misinterpreted – clemens Aug 29 '15 at 20:00
  • I see now that you meant I was implying using them was an advantage to the macro author ; well it is always advantage to allow oneself more tools... I agree about e-TeX. I knew about Ulrich's macro which impressed me a lot when he taught it to me on a usenet forum. Less \expandafter's means less expansion steps, thus, potentially, perhaps some speed gain. \IfNoTokens expands always in exactly three steps. – user4686 Aug 29 '15 at 20:11
  • 1
    @unbonpetit Thanks. I had in my files another approach which could distinguish #1= nothing or some space tokens from the other cases, and then in the former case one could do \if\relax #1\relax. After seeing the question and Ulrich's answer with its very impressive (non e-TeX) universal solution I decided to make an effort into revamping my earlier method and find a better way to separate in one go empty #1 from the other cases. – user4686 Aug 29 '15 at 21:42
5

EGreg had a nice answer on that, check https://tex.stackexchange.com/a/127506/65222 Here's the relevant part:

A clever example of \if is for testing whether an argument is empty:

  \def\cs#1{%
  \if\relax\detokenize{#1}\relax
    The argument is empty%
  \else
    The argument #1 is non empty%
  \fi
  }

It uses \detokenize which is an e-TeX feature. If the argument is empty, the comparison would be between \relax and \relax, which are equal as far as \if is concerned; otherwise, \detokenize would return a string of characters (of category code 12) and \relax is never equal to a character for \if.

1

Try the ifmtarg package. For instance (less any typos):

...
\usepackage{ifmtarg}
\makeatletter
  \newcommand{\isempty}[1]{%
    \@ifmtarg{#1}{YES}{NO}}
  \newcommand{\isnotempty}[1]{%
    \@ifnotmtarg{#1}{YES}}
\makeatother
...

then some results are:

\isempty{}       --> YES
\isempty{  }     --> YES
\isempty{E}      --> NO
\isempty{ E }    --> NO
\isnotempty{}    -->
\isnotempty{  }  -->
\isnotempty{E}   --> YES
\isnotempty{ E } --> YES
  • Isn't etoolboxs \ifblank better. I think it covers more ( I think) – daleif Aug 29 '15 at 20:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.