6

I have the following problem: I am using the etoolbox package to check if a macro (say, \foo) expands to one of several possible strings (say, bar, baz, abc, or other). I need to execute different code depending on what string \foo expands to.

My current solution is

\ifdefstring{\foo}{bar}
{% if \foo is bar
  <do something>
}
{% if \foo is not bar
  \ifdefstring{\foo}{baz}
  {% if \foo is baz
    <do something else>
  }
  {% if \foo is not bar or baz
    \ifdefstring{\foo}{abc}
    {% if \foo is abc
      <do something else>
    }
    {% if \foo is not bar, baz, or abc
      <do something else>
    }
  }
}

Although this works, it is not satisfactory due to the deep nesting, and it doesn't generalize well to longer lists of possible strings. In my real situation, I have up to eight possible strings to check, so this solution creates unreadable code.

My question is, what alternative options (in LaTeX2e) do I have to check if a macro expands to one of several strings?

Ideally, the solution would also work for other etoolbox comparison commands (e.g., \ifstrequal). However, the solution need not use the etoolbox package.

7

If \foo expands to a simple string, which survives a \csname, the following method can be used, which does not need LaTeX3:

\documentclass{article}

\makeatletter
\@namedef{foo@bar}{%
  \typeout{\noexpand\foo is bar}%
  % <do something>
}
\@namedef{foo@baz}{%
  \typeout{\noexpand\foo is baz}%
  % <do something>
}
\@namedef{foo@abc}{%
  \typeout{\noexpand\foo is abc}%
  % <do someting>
}

\newcommand*{\evaluate@foo}{%
  \@ifundefined{foo@\foo}{%
    \errmessage{\noexpand\foo has unknown value}%
  }{%
    \@nameuse{foo@\foo}%
  }%
}
\providecommand*{\foo}{}% initialization

% Test

\def\foo{baz}
\evaluate@foo

\def\foo{abc}
\evaluate@foo

\def\foo{xyz}
\evaluate@foo

\makeatletter

\begin{document}
\end{document}

Result:

\foo is baz
\foo is abc
! \foo has unknown value.
  • This looks like it might do the trick! I'll try it out later today and accept your answer if it does. Thanks! – Artem Mavrin Sep 10 '15 at 23:44
  • This idea ended up working perfectly, thanks again! – Artem Mavrin Sep 11 '15 at 3:40
4

The best solution I can think of is expl3’s \str_case:nnTF. Here I generate a variant to take one token instead of a test string and compares to expansion of that token to the list. If no match is found in the list an appropriate message is output.

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn

\cs_generate_variant:Nn \str_case:nnF { VnF }

\cs_new_protected:Npn \artem_testfoo:N #1
 {
  \texttt{ \cs_to_str:N #1 } ~ 
  \str_case:VnF #1
   {
    { bar } { is ~ bar }
    { baz } { is ~ baz }
    { abc } { is ~ abc }
   }
   { is ~ neither ~ bar, ~ nor ~ baz, ~ nor ~ abc. }
 }

\NewDocumentCommand \testfoo { m }
 {
  \artem_testfoo:N #1
 }

\ExplSyntaxOff
\begin{document}

\def\foo{bar}
\testfoo\foo

\def\foo{baz}
\testfoo\foo

\def\foo{abc}
\testfoo\foo

\def\foo{xyz}
\testfoo\foo

\end{document}

enter image description here

  • 1
    BTW: \cs_generate_variant:Nn \str_case:nnF { V } would suffice :) – clemens Sep 10 '15 at 19:45
  • Thank you for taking time to answer! Unfortunately, I'm not familiar with LaTeX3 at all (I should have specified that in the question). – Artem Mavrin Sep 10 '15 at 23:46
3

It's quite easy with xparse and expl3:

\documentclass{article}

\usepackage{xparse}
\ExplSyntaxOn
\DeclareExpandableDocumentCommand{\xifstring}{mO{}m}
 {
  \str_case:onF { #1 } { #3 } { #2 }
 }
\ExplSyntaxOff

\begin{document}

\newcommand{\foo}{abc}

\xifstring{\foo}[No match]
 {
  {abc}{String is `abc'}
  {def}{String is `def'}
  {ghi}{String is `ghi'}
 }

\renewcommand{\foo}{def}

\xifstring{\foo}[No match]
 {
  {abc}{String is `abc'}
  {def}{String is `def'}
  {ghi}{String is `ghi'}
 }

\renewcommand{\foo}{ghi}

\xifstring{\foo}[No match]
 {
  {abc}{String is `abc'}
  {def}{String is `def'}
  {ghi}{String is `ghi'}
 }

\renewcommand{\foo}{foo}

\xifstring{\foo}[No match]
 {
  {abc}{String is `abc'}
  {def}{String is `def'}
  {ghi}{String is `ghi'}
 }

\end{document}

The optional argument is for what you want when there's no match (default value, do nothing).

enter image description here

  • Thank you for taking time to answer! Unfortunately, I'm not familiar with LaTeX3 at all (I should have specified that in the question). – Artem Mavrin Sep 10 '15 at 23:46
  • Why do you need to be familiar with latex3? Just think of it as a package that you load that provides programming tools. – Will Robertson Sep 11 '15 at 0:58
3

The simple minded method from my other answer avoids nesting. Here we don't have TeX like \if..\fi but suitable wrappers, so we need to modify a bit the definitions.

\long\def\DoThis #1#2\OrThat #3{#1}
\long\def\OrThat #1{#1}% \@firstofone

\ifdefstring{\foo}{bar}{\DoThis{do something}}{}%
\ifdefstring{\foo}{baz}{\DoThis{do something else}}{}%
\ifdefstring{\foo}{bat}{\DoThis{do something different}}{}%
\OrThat {in case all tests fail}%

Remark: once a test is positive all successive ones are skipped, due to the definition of \DoThis macro.

  • the method applies to all possible branching conditional of the type \ifsometest{..}...{YES}{NO} . – user4686 Sep 11 '15 at 11:30

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.