3

I am trying to test if a parameter is "empty" in practice rather than just "technically". Consider the following MWE for my meaning:

\documentclass{article}
\usepackage{xifthen}
\begin{document}

\def\ansOne#1{%
    \ifx\fish#1\fish%
    \else%
    correct%
    \fi%
}

1) \ansOne{}% This returns nothing as intended

2) \ansOne{{}}% This returns "correct" when I want it to return nothing

3) \ansOne{{}{}{}}% This returns "correct" when I want it to return nothing

4) \ansOne{correct}% This returns "correct" as intended
\end{document}

I found a great description of various tests to see if a parameter is empty Here which is where I got the above technique. After testing them however all of them seem to have this problem. My guess is that latex is expanding the outermost braces only, and then since the inside has "tokens" it counts as not empty and returns "correct".

To be clear; I need a macro that returns absolutely nothing if the interior is only braces or spaces (I know it will be balanced however, so no unbalanced ifs, braces, etc), and returns "correct" (or whatever) if there is anything else.

Is there a way to do some sort of recursive token expansion to discover if the braces are filled or empty eventually? And will it have a problem with, say, \ansOne{{}{ }}? Or will the whitespace get eaten as it usually does?

EDIT: Added Utility

A nice place to use this particular situation would be if you have a custom command that gives a \item with some formatting options, and takes an optional argument to have an effect. For example;

\newcommand{\choice}[2][]{\item You typed in #2. 
    \ifthenelse{\equal{#1}{correct}}{Your answer is correct! Good work!}{}
    \ifthenelse{\equal{#1}{}}{Your answer is incorrect, try again.}{}
}

\begin{enumerate}
\choice[correct]{This should mark as correct}
\choice[]{This should mark as incorrect}
\choice[Nonsense]{This should just be repeated, not marked correct or incorrect}
\choice[\ansOne{{{}}}]{This should be marked incorrect, ideally}
\choice[\ansOne{{{correct}}{}{}}]{This should be marked as correct, ideally}.
\end{enumerate}

This fails currently because the \ansOne can't expand in time to get processed by the \choice command in some intelligent fashion it seems. Something about the cls file I'm trying to edit has formatting options that make \expandafter before the \choice command fail. In fact, if you compile the above code in my cls type, it returns the "something is missing, possible a \item" error one normally finds when you don't lead with \item. If I can get the \ansOne to immediately expand before \choice I'm fairly certain all these problems go away, but the only thing I know of to try is \expandafter which doesn't seem to be working. I have included the full multipleChoice environment and \choices command code below but it utilizes tex4ht which I don't really understand to hook it into html so read at your own risk :)

%% multiple choice environment
\renewcommand{\theenumi}{\textup{(\alph{enumi})}}
\renewcommand{\labelenumi}{\theenumi}
\renewcommand{\theenumii}{\textup{(\roman{enumii})}}
\renewcommand{\labelenumii}{\theenumii}

%% correct answers for multiple choice
\makeatletter
\define@key{choice}{value}[]{\def\choice@value{#1}}
\define@boolkey{choice}{correct}[true]{\def\choice@correct{#1}}
\setkeys{choice}{correct=false,value=}
\newcommand{\choice}[2][]{%
\setkeys{choice}{#1}%
\item #2\ifthenelse{\boolean{\choice@correct}}{\ifhandout \else \,\checkmark\,\setkeys{choice}{correct=false}\fi}{}}
\makeatother

%% Not really meant to be used outside this package (used in wordChoice)
%% \otherchoice outputs the item if correct and nothing if incorrect.
\makeatletter
\define@key{otherchoice}{value}[]{\def\otherchoice@value{#1}}
\define@boolkey{otherchoice}{correct}[true]{\def\otherchoice@correct{#1}}
\setkeys{otherchoice}{correct=false,value=}
\newcommand{\otherchoice}[2][]{%
  \ignorespaces
  \setkeys{otherchoice}{#1}
  \ifthenelse{\boolean{\otherchoice@correct}}{#2\setkeys{otherchoice}{correct=false}}{}\ignorespaces}
\makeatother
%%%

%setting default values for multipleChoice command key=value pairs
\makeatletter
\define@key{multipleChoice}{id}{\def\mc@id{#1}}
\setkeys{multipleChoice}{id=}

\newenvironment{multipleChoice}[1][]
{\setkeys{multipleChoice}{#1}%
\recordvariable{\mc@id}%
\begin{trivlist}\item[\hskip \labelsep\small\bfseries Multiple Choice:]\hfil\begin{enumerate}}
{\end{enumerate}\end{trivlist}}

%multipleChoice* is for internal use only! (used in wordChoice)
%It displays all choices in a list separated by /
\newenvironment{multipleChoice*}[1][]
{\setkeys{multipleChoice}{#1}%
\ifthenelse{\equal{\mc@id}{}}{}{\immediate\write\idfile{var \mc@id;}}%
\begin{enumerate*}[before={\upshape{(\hspace{-.25em}}}, itemjoin={/\hspace{-.25em}}, after={\upshape{)}}, label={}]}
{\end{enumerate*}}
\makeatother

The full cls file can be found here

1
  • Etoolbox and \ifdefvoid might work
    – daleif
    Jun 20, 2017 at 5:35

2 Answers 2

4

You can use expl3 to iterate over the subgroups and check if they are empty.

\documentclass{article}
\usepackage{xparse}
\begin{document}

\ExplSyntaxOn

\NewDocumentCommand \ansOne { m }
{
  \bool_set_true:N \l_tmpa_bool
  \tl_map_inline:nn { #1 }
  {
    \tl_if_blank:nF { ##1 } { \bool_set_false:N \l_tmpa_bool }
  }
  \bool_if:NF \l_tmpa_bool { correct }
}

\ExplSyntaxOff

\ansOne{} % returns nothing
\ansOne{{}} % returns nothing
\ansOne{{}{}{}} % returns nothing

\ansOne{{}{ }} % returns nothing

\ansOne{{abc}{def}} % returns "correct"

\end{document}

You can do it in Lua as well. The good thing about this is that the level of nesting of empty groups does not matter (the other solution only supports one level) and the macro is fully expandable, i.e. after \edef\x{\ansOne{{abc}{def}}} the content of \x will be correct.

\documentclass{article}
\begin{document}

\newcommand\ifonlybracesandspaces[1]{%
  \ifcase\directlua{%
    local input = "\detokenize{#1}"
    % Remove all {, }, and space from input
    local output = string.gsub(input, "[{} ]", "")
    if output == '' then
        tex.sprint(0)
    else
        tex.sprint(1)
    end
  }\relax
}

\newcommand\ansOne[1]{%
  \ifonlybracesandspaces{#1}%
    % Do nothing
  \else
    correct
  \fi
}

\ansOne{} % returns nothing
\ansOne{{}} % returns nothing
\ansOne{{}{}{}} % returns nothing

\ansOne{{}{ }} % returns nothing

\ansOne{{abc}{def}} % returns "correct"

\end{document}
4
  • If you use \bool_if:fF you can also get \ansOne{\empty} to return nothing.
    – Manuel
    Jun 18, 2017 at 6:28
  • @Manuel You probably mean \tl_if_blank:fF. Also you have to generate that variant. It doesn't exist by default. Jun 18, 2017 at 6:32
  • Yes, that's what I meant. And yes, always generate such variants if needed, there's nothing wrong. I meant that with just a small change the behaviour could be extended, but I really don't know what OP has in mind. I will remove my comments, it was just nitpicking. And the question explicitly says spaces and braces only.
    – Manuel
    Jun 18, 2017 at 6:33
  • This works great for what I needed. Upon using it in my code however, I encountered a situation where I'd like to use this in an itemize setting to pass an optional argument. I've edited the problem with the desired added utility. I'll mark this answer as correct but I'll give it a little time before I do so in case anyone wants to chime in with the other part. Either way, what you've written here is very useful for a bunch of use-cases I needed, so thanks!
    – Jason
    Jun 19, 2017 at 1:32
5

Of course, you can do \setbox0=\hbox{\ignorespaces#1}\ifdim\wd0=0pt but this is not expandable test. The expandable test can be \isXempty{param}\iftrue defined here:

\def\isXempty#1#2{\expandafter\isXemptyA\detokenize{#1}\end}
\def\isXemptyA#1{%
   \ifx\end#1\csname iftrue\expandafter\endcsname
   \else \if\string{#1\expandafter\expandafter\expandafter \isXemptyA
         \else \if\string}#1\isXemptyB 
               \else \isXemptyC
   \fi   \fi   \fi
}
\def\isXemptyB#1\fi\fi\fi{\fi\fi\fi\isXemptyA}
\def\isXemptyC#1\end{\fi\fi\fi\iffalse}

% test:

\isXempty{}\iftrue YES\else NO\fi          % returns YES

\isXempty{{} {{ }}}\iftrue YES\else NO\fi  % returns YES

\isXempty{{x}}\iftrue YES\else NO\fi       % returns NO

\isXempty{{}{}{}}\iftrue YES\else NO\fi    % returns YES

\bye
1
  • Also I like the \expandafter\endcsname trick. +1! Jun 19, 2017 at 21:18

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .