Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

This is essentially asking how to do a case switch in LaTeX. I want to define a macro so that if the argument passed is one thing, then something happens, and if not then something else happens. I know ways to hack this, but they do feel very hacky. What I currently use is something like:

\newcommand{\my@testcase}{world}
\newcommand{\hello}[1]{%
 \edef\my@temp{#1}%
 \ifx\my@temp\my@testcase
  Greetings%
 \else
  Hello%
 \fi
}

It's a pain to nest (though I suppose one could be clever with loops) and it seems a bit overkill to have to define two macros in order to test if they expand to the same thing. So: what's the cleanest way to do this (for preference in LaTeX2e, though as I'm starting to use a bit of expl3 then I'd be happy to learn about that too, and of course answers explaining how easy this is in other variants are equally welcome)?

(Edit in response to Joseph's comment, part of the problem is that I don't know what all those words mean!): Here's my "real world" example. Using the unicode-math package, I've colour coded lots of letters so that the colour (and style) links to what it is. That's fine, except that sometimes a letter or number plays more than one role. So I have a command that can do a one-time switch. The problem is that these one-time switches appear to be very expensive (my document compilation time went up to about 20minutes on a new machine). So for the most common ones, I've found a way around the one-time switch - the best example being that 0 is sometimes a real number, sometimes a vector, and sometimes a function. But as far as the document is concerned, it's still a one-time switch: that is \vect{0} and \vect{x} both say "Do a one-time switch to typeset 0 (or x) as a vector", but \vect{0} implements the "way round" whilst \vect{x} does the expensive switch.

So all I'm passing to the macro are characters, but I'd like it to be robust enough that if a passed it something longer then it Did The Right Thing without complaining.

(Is that clearer?)

Oh, and I'm using TeXLive 2010.

share|improve this question
    
A few details would be nice. a) Do you want an expandable solution? b) Do you want to compare as tokens or as strings? c) Can we assume a recent engine, so use things like \pdfstrcmp? –  Joseph Wright Sep 20 '10 at 18:37
    
@Joseph: Does the edit help a little? –  Loop Space Sep 20 '10 at 19:11
    
Yes, thanks. I suspect your very long compilation time is due to something else, but without seeing the code it's hard to say what. –  Joseph Wright Sep 20 '10 at 19:31
1  
I don't understand what you mean by "\vect{0} implements the 'way round' whilst \vect{x} does the expensive switch." I don't really know what Right Thing (tm) you want done. –  TH. Sep 20 '10 at 19:35
    
@Joseph: When I compile it without the 'one-time' switches, it takes an okay time but each 'one-time' switch adds about 10s to the compilation time. I tested with a MWE to isolate the problem (happy to send you the code and results if you want). @TH: Here's a simplified example: \vect{0} expands to 0 whilst \vect{anything else} expands to \mathbf{anything else}. The 'Right Thing' means that the "anything else" can truly be "anything else". –  Loop Space Sep 20 '10 at 20:06
show 2 more comments

4 Answers

up vote 10 down vote accepted

As the questions seems to if possible want a solution without using expl3, I'll provide one by taking the code we have there are reworking it in the traditional style. This is of course not the only approach, but seems to form enough material for one answer! (The reason for basing the solution on expl3 is that there are a lot of good TeX programming ideas in it, whether you like the particular approach or not.)

The idea here is to provide two tests:

\CompareMacro\MacroName{%
  \ComparisonMacroOne{Code for case one}%
  \ComparisonMacroTwo{Code for case two}%
  ...
}{Code for case 'else}

and

\CompareString{test-string}{%
  {comparison-string-one}{Code for case one}%
  {comparison-string-two}{Code for case two}%
  ...
}{Code for case 'else}

both of which can be nested. Obviously, for the macro-based comparison you need to store the value to be tested and then each possible value to check.

Comparing as macros means that both the characters ('a', 'b', etc.) and their category code ('letter', 'other', etc.) are checked, and have to agree. On the other hand, testing as strings means that on the characters are compared. (In most other languages only the string test exists: category code is something TeX seems to have to itself!) Comparing as strings (easily) needs a primitive function that was not present in TeX as written by Knuth. pdfTeX calls this \pdfstrcmp, while XeTeX uses just \strcmp. (LuaTeX users will probably use Lua here, but you can simulate \pdfstrcmp, most easily using the pdftexcmds package).

First, let's have the comparison functions themselves. For comparing as macros, the code is:

\newcommand\CompareMacro[2]{% But needs three arguments!
  \compare@macro#1#2\compare@tail?\compare@stop
}
\newcommand\compare@macro[3]{%
  \compare@if@tail@do{#2}{\@firstofone}%
  \ifx#1#2
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
    {\compare@end{#3}}%
    {\compare@macro#1}%
}

while for strings it's

\newcommand\CompareString[2]{% But needs three arguments!
  \compare@string{#1}#2\compare@tail?\compare@stop
}
\newcommand\compare@string[3]{%
  \compare@if@tail@do{#2}{\@firstofone}%
  \ifnum\pdfstrcmp{\unexpanded{#1}}{\unexpanded{#2}}%
    =\z@ % When two strings are equal \pdfstrcmp returns 0
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
    {\compare@end{#3}}%
    {\compare@string{#1}}%
}

The support material is common to both:

\newcommand\compare@if@tail@do[2]{%
  \expandafter\ifx\compare@if@tail@do@aux#1?\@nil\compare@tail\compare@tail
    \expandafter\compare@do@after@stop
  \else
    \expandafter\@gobble
  \fi
  {#2}%
}
\newcommand\compare@if@tail@do@aux{}
\long\def\compare@if@tail@do@aux#1#2\@nil\compare@tail{#1}
\newcommand\compare@do@after@stop{}
\long\def\compare@do@after@stop#1#2\compare@stop{#1}
\newcommand*\compare@tail{\compare@tail}
\newcommand\compare@end{}
\long\def\compare@end#1#2\compare@stop#3{#1}

This works by looping over the second argument to \Compare.... The first test in both cases is to see if we've hit the marker \compare@tail, and if so do find the 'else' case and do it. On the other hand, for any other loop value the comparison is done. If it is successful, all of the other cases are thrown away (using the second marker \compare@stop), and the 'success' code is used. On the other hand, if the test fails then there is a loop.

Implemented in this way the tests can be nested. For example, you can do

\CompareString{Hello}{%
  {Hello}{%
    \CompareString{Joseph}{%
      {Fred}{Nice to see you, Fred}%
      {Jane}{Nice to meet you, Jane}%
      {Joseph}{Oh, it's you!}%
    }{OH NO!}%
  }%
  {Goodbye}{See you}%
}{HELP!}

and the result will be correct. Secondly, the tests are expandable. That may not always be relevant but it's very handy to be able to \edef cases, or put them inside a \write, rather than having to do the test, save the result and then apply it.

(For those people willing to use expl3, I've described the functions \tl_case:Nnn and \str_case:nnn. You can load expl3 and use these without needing to go through all of that detail!)

share|improve this answer
add comment

This can also be done with the xstring package which provides \IfStrEqCase and \IfEqCase, where the latter can be used to test for numerically equivalency.

\documentclass{article}
\usepackage{xstring}

\begin{document}
    \IfStrEqCase{Jane}{%
      {Fred}{Nice to see you, Fred}%
      {Jane}{Nice to meet you, Jane}%
      {Joseph}{Oh, it's you, Joseph!}%
    }[OH NO, I don't know you!]%

    \IfEqCase{2.00}{%
      {1}{Number is same as: One}%
      {2}{Number is same as: Two}%
      {3}{Number is same as: Three}%
    }[OH NO, Unrecognized number.]%
\end{document}

produces:

enter image description here

This can also be nested as in Joseph Wright's example. Here is the equivalent code with xstring:

\IfStrEqCase{Hello}{%
  {Hello}{%
    \IfStrEqCase{Joseph}{%
      {Fred}{Nice to see you, Fred}%
      {Jane}{Nice to meet you, Jane}%
      {Joseph}{Oh, it's you!}%
    }[OH NO!]%
  }%
  {Goodbye}{See you}%
}[HELP!]
share|improve this answer
add comment

ConTeXt provides \processaction and some related macros which are essentially equal to a case statement. Here is the documentation from syst-aux.mkiv

%D \macros
%D   {processaction,
%D    processfirstactioninset,
%D    processallactionsinset}
%D
%D \CONTEXT\ makes extensive use of a sort of case or switch
%D command. Depending of the presence of one or more provided
%D items, some actions is taken. These macros can be nested
%D without problems.
%D
%D \starttyping
%D \processaction           [x]     [a=>\a,b=>\b,c=>\c]
%D \processfirstactioninset [x,y,z] [a=>\a,b=>\b,c=>\c]
%D \processallactionsinset  [x,y,z] [a=>\a,b=>\b,c=>\c]
%D \stoptyping
%D
%D We can supply both a \type{default} action and an action
%D to be undertaken when an \type{unknown} value is met:
%D
%D \starttyping
%D \processallactionsinset
%D   [x,y,z]
%D   [      a=>\a,
%D          b=>\b,
%D          c=>\c,
%D    default=>\default,
%D    unknown=>\unknown{... \commalistelement ...}]
%D \stoptyping
%D
%D When \type{#1} is empty, this macro scans list \type{#2} for
%D the keyword \type{default} and executed the related action
%D if present. When \type{#1} is non empty and not in the list,
%D the action related to \type{unknown} is executed. Both
%D keywords must be at the end of list \type{#2}. Afterwards,
%D the actually found keyword is available in
%D \type{\commalistelement}. An advanced example of the use of
%D this macro can be found in \PPCHTEX, where we completely                                                                                                                                                        
%D rely on \TEX\ for interpreting user supplied keywords like
%D \type{SB}, \type{SB1..6}, \type{SB125} etc.
share|improve this answer
add comment

You could use ifstrequal from etoolbox, but that doesn't help with nesting. Same with \ifthenelse from ifthen.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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