30

I know three macros that test two fully expanded character strings for equality:

  1. \ifthenelse{\equal{<op1>}{<op2>}}{<true>}{<false>} (ifthen package)
  2. \IfStrEq{<op1>}{<op2>}{<true>}{<false>} (xstring package)
  3. \ifstr{<op1>}{<op2>}{<true>}{<false>} (scrbase package)

Interestingly, they share one drawback: They are fragile, which means that

  • we have to \protect them in moving arguments (e.g. in the argument of \section).
  • they don’t properly work in the context of PDF string expansion (e.g. for PDF outlines) as implemented by the hyperref package.

Minimal example:

\documentclass{article}

\usepackage{xstring}

\usepackage{hyperref}

\newcommand*{\juhu}{%
  juhu%
}

\newcommand*{\test}[1]{%
  \IfStrEq{#1}{juhu}{Juhu!}{Oje!}%
}

\begin{document}

\tableofcontents

\section{\test{\juhu}}
%% \section{\protect\test{\juhu}}

\end{document}

Hence my question: Is it possible to implement an equality test that fully expands its operands, but removes the restrictions listed above?

2
  • I think the answer is "no" Jun 12, 2012 at 12:54
  • @DavidCarlisle: This was my guess. But, there’s always hope ;-)
    – mhp
    Jun 12, 2012 at 13:00

4 Answers 4

22
\documentclass{article}

\usepackage{pdftexcmds}

\usepackage{hyperref}

\newcommand*{\juhu}{juhu}
\newcommand*{\nojuhu}{nojuhu}

\makeatletter
\newcommand*{\test}[1]{%
  \ifnum\pdf@strcmp{#1}{juhu}=\z@ Juhu!\else Oje!\fi
}
\makeatother

\begin{document}

\tableofcontents

\section{\test{\juhu}}

\section{\test{\nojuhu}}

\end{document}

The use of \pdf@strcmp from pdftexcmds is for allowing the test also with XeLaTeX and LuaLaTeX. With pdflatex it suffices to use \pdfstrcmp.

\pdf@strcmp{A}{B} returns 0 if the strings are, after full expansion to unexpandable tokens, equal; -1 if A precedes B in lexicographic order (based on ASCII code of the final detokenized list) and 1 if A follows B.

(Of course, e-TeX is required, so no Knuth TeX.)

3
  • 1
    Résumé: Works! :-) Based on the \pdfstrcmp primitive (pdfTeX), the \strcmp primitive (XeTeX) and a Lua function (LuaTeX), respectively. Active and inputenc characters are handled reliably, comparison is catcode-agnostic. But, as stated, you need the ε-TeX extensions.
    – mhp
    Jun 16, 2012 at 12:13
  • 1
    I assume by Knuth TeX you mean Plain-TeX, but what exactly does "no Knuth TeX" mean? Nov 9, 2013 at 7:39
  • 1
    @GeoffPointer “Knuth TeX” is “TeX the program” without the e-TeX extensions.
    – egreg
    Nov 9, 2013 at 10:00
16

This doesn't have those restrictions, but it has others. Buyer beware:-)

\documentclass{article}

\usepackage{hyperref}

\newcommand*{\juhu}{%
  juhu%
}

\makeatletter

\def\test#1{%
   \expandafter\xtest\romannumeral`\Q#1\muskipdef\valign juhu\muskipdef\voffset{yes}{no}%
}

\def\xtest#1#2#3#4#5{\xxtest}

\def\xxtest#1#2\valign#3#4\voffset{%
  \ifx\muskipdef#1%
    \ifx\muskipdef#3%
      \expandafter\expandafter\expandafter\@firstoftwo
    \else
      \expandafter\expandafter\expandafter\@secondoftwo
    \fi
  \else
    \ifx\muskipdef#3%
      \expandafter\expandafter\expandafter\@secondoftwo
    \else
      \xiterate{#2}{#4}%
    \fi
  \fi}


\def\xiterate#1#2\fi\fi{\fi\fi\expandafter\xtest\romannumeral`\Q#1\valign#2\voffset}

\typeout{[[\test{\juhu}]]}
\typeout{[[\test{x\juhu}]]}
\typeout{[[\test{\juhu\empty}]]}


\begin{document}



\tableofcontents

\section{\test{\juhu}}


\end{document}
6
  • Résumé: Works! :-) Based on \romannumeral. Active characters are handled reliably, comparison is catcode-agnostic. But, inputenc characters are not handled.
    – mhp
    Jun 16, 2012 at 11:48
  • inputenc should be OK, what example were you trying? Jun 16, 2012 at 11:49
  • I’ve tried \usepackage[utf8]{inputenc} with juhä instead of juhu in \juhu and \test. Interestingly, it does work in expansion-only contexts (\typeout, \write to TOC).
    – mhp
    Jun 16, 2012 at 12:11
  • The code assumes that the test string is already fully expanded and tests the expansion of the supplied string against that. this doesn't work for the inputenc characters in the fixed text string. It's probably more natural to have an equality test for two strings and treats them equally, fully expanding both. I'll update my answer later. Jun 17, 2012 at 9:43
  • Are you going to update your answer? I’m just too curious :-)
    – mhp
    Jul 11, 2012 at 14:22
14

Here a macro which compares both strings character for character and is fully expandable, therefore robust. It works fine for you test case. Note that it might fail if the input does not contain only strings. Included races for example would cause a lot of issues.

The \romannumeral-`0 trick is used to fully expand the input until at least the first character. This character is then separated and the second input is expanded and split in the same way. Both are compared and if equal the whole thing is repeated until two different characters are found or the end of both strings is reached.

\documentclass{article}

\usepackage{hyperref}

\newcommand*{\juhu}{%
  juhu%
}


\makeatletter
\newcommand\ifstreq[2]{%
    \expandafter\@ifstreq\romannumeral-`0#1\relax\@nnil\romannumeral-`0#2\relax\@nnil
}

\def\@ifstreq#1#2\@nnil#3\@nnil{%
    \expandafter\@@ifstreq#3\@nnil{#1}{#2}
}

\long\def\@afterfi#1#2\fi{\fi#1}%

\def\@@ifstreq#1#2\@nnil#3#4{%
    \ifcase0%
        \ifx\relax#1\empty
            \ifx\relax#3\empty 2\else 1\fi
        \else
        \if#3#1\else 1\fi\fi
    \space
        \@afterfi{%
        \expandafter\@ifstreq\romannumeral-`0#4\@nnil\romannumeral-`0#2\@nnil}%
    \or
        \expandafter\@secondoftwo
    \else
        \expandafter\@firstoftwo
    \fi
}

\makeatother


\newcommand*{\test}[1]{%
  \ifstreq{#1}{juhu}{Juhu!}{Oje!}%
}

\begin{document}

\tableofcontents

\section{\test{\juhu}}

\end{document}
1
  • Résumé: Works! :-) Based on \romannumeral. Simple active characters are handled (~, for instance, not in PDF strings), comparison is catcode-agnostic. But, inputenc characters are not handled.
    – mhp
    Jun 16, 2012 at 13:17
9

Here another fully-expandable macro which compares both strings character for character. However, here \csname is used to expand the strings fully and ensure that both have the same catcode. This is important if you compare a string against internal values like \jobname where the characters aren't officially letters.

Note that this approach will cause an error if any input is not fully expandable down to characters. This also excludes the use of many of not all non-English letters like German umlauts which are implemented as macros.

\documentclass{article}

\usepackage{hyperref}

\newcommand*{\juhu}{%
  juhu%
}


\makeatletter
\newcommand\ifstreq[2]{%
    \expandafter\expandafter\expandafter
    \@ifstreq\expandafter\string\csname#1\expandafter
    \expandafter\expandafter\endcsname
    \expandafter\expandafter\expandafter\relax
    \expandafter\expandafter\expandafter\@nnil
    \expandafter\string\csname#2\endcsname\relax\@nnil
}

\long\def\@afterfi#1#2\fi{\fi#1}

\def\@ifstreq#1#2\@nnil#3#4\@nnil{%
    \ifcase0%
        \ifx\relax#1\empty
            \ifx\relax#3\empty 2\else 1\fi
        \else
        \if#3#1\else 1\fi\fi
    \space
        \@afterfi{\@ifstreq#2\@nnil#4\@nnil}%
    \or
        \expandafter\@secondoftwo
    \else
        \expandafter\@firstoftwo
    \fi
}

\makeatother


\newcommand*{\test}[1]{%
  \ifstreq{#1}{juhu}{Juhu!}{Oje!}%
}

\begin{document}

\tableofcontents

\section{\test{\juhu}}

\end{document}
1
  • Résumé: Works! :-) Based on \csname ... \endcsname. Simple active characters are handled (~, for instance, not), comparison is catcode-agnostic. But, inputenc characters are not handled.
    – mhp
    Jun 16, 2012 at 12:50

You must log in to answer this question.

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