# On testing two fully expanded character strings for equality

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?

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

\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.)

• 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 '12 at 12:13
• I assume by Knuth TeX you mean Plain-TeX, but what exactly does "no Knuth TeX" mean? – Geoff Pointer Nov 9 '13 at 7:39
• @GeoffPointer “Knuth TeX” is “TeX the program” without the e-TeX extensions. – egreg Nov 9 '13 at 10:00

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}

• Résumé: Works! :-) Based on \romannumeral. Active characters are handled reliably, comparison is catcode-agnostic. But, inputenc characters are not handled. – mhp Jun 16 '12 at 11:48
• inputenc should be OK, what example were you trying? – David Carlisle Jun 16 '12 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 '12 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. – David Carlisle Jun 17 '12 at 9:43
• Are you going to update your answer? I’m just too curious :-) – mhp Jul 11 '12 at 14:22

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}

• 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 '12 at 13:17

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}

• 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 '12 at 12:50