# How do I set a toggle based on the end of a string

Compiling

\documentclass{article}
\usepackage{etoolbox,xstring}

\newcommand{\strendswithfoo}{\IfEndWith{bar-foo}{foo}{true}{false}}

\newtoggle{strendswithfoo}
\settoggle{strendswithfoo}{\strendswithfoo}

\begin{document}

command \textbackslash strendswithfoo = \strendswithfoo

toggle strendswithfoo = \iftoggle{strendswithfoo}{true}{false}

\end{document}


./test.tex:7: Missing \endcsname inserted.
\let
l.7 \settoggle{strendswithfoo}{\strendswithfoo}

The control sequence marked <to be read again> should
not appear between \csname and \endcsname.

./test.tex:7: Argument of \reserved@a has an extra }.
<inserted text>
\par
l.7 \settoggle{strendswithfoo}{\strendswithfoo}

I've run across a }' that doesn't seem to match anything.
For example, \def\a#1{...}' and \a}' would produce
this error. If you simply proceed now, the \par' that
I've just inserted will cause me to report a runaway
argument that might be the root of the problem. But if
your }' was spurious, just type 2' and it will go away.

Runaway argument?
{\@firstoftwo {\let \@xs@assign \@xs@expand@and@detokenize \@xs@IfEndWith@@ \ETC.
./test.tex:7: Paragraph ended before \reserved@a was complete.
\par
l.7 \settoggle{strendswithfoo}{\strendswithfoo}

I suspect you've forgotten a }', causing me to apply this
control sequence to too much text. How can we recover?
My plan is to forget the whole thing and hope for the best.

./test.tex:7: Package etoolbox Error: Invalid boolean value '\let \reserved@d =*\def \par .

l.7 \settoggle{strendswithfoo}{\strendswithfoo}

Valid boolean values are 'true' and 'false'.


The output document contains:

command \strendswithfoo = true
toggle strendswithfoo = false


I would like the toggle strendswithfoo to be true. Happy to switch away from xstring if there is a better solution.

• As with almost any xstring macro: They are not expandable. Switching to a expl3 based solution is better, in my point of view – user31729 Oct 18 '17 at 16:30

A different approach using listofitems, whose results are expandable. That is to say, the actual \readlist is not expandable, but the array of data it produces is expandable.

In this implementation, I search the test string for all occurrences of "foo", using it as a list separator. The text that isn't "foo" is stored in the \foosep array, \foosep[1], \foosep[2], etc. where the index 1 is the text to the left of the leftmost "foo", the index 2 is the text between the 1st and 2nd "foo", etc.

Negative indices such as [-1] merely starts the counting leftward, from the rightmost "foo". If the string ended in "foo", then the expansion of \foosep[-1], representing the text to the right of the rightmost "foo", will be empty, and so I test its emptiness to determine whether to set the toggle to true or false.

\documentclass{article}
\usepackage{etoolbox,listofitems}

\newtoggle{strendswithfoo}
\def\toggletmp{\settoggle{strendswithfoo}}

\newcommand{\teststrendswithfoo}[1]{%
\setsepchar{foo}%
\edef\strendswithfoo{\if\relax\foosep[-1]\relax{true}\else{false}\fi}%
\expandafter\toggletmp\strendswithfoo
}

\begin{document}
\teststrendswithfoo{bar-foo}
toggle strendswithfoo = \iftoggle{strendswithfoo}{true}{false}

\teststrendswithfoo{foo-bar}
toggle strendswithfoo = \iftoggle{strendswithfoo}{true}{false}
\end{document}


One solution is to make the arguments to \IfEndWith \toggletrue and \togglefalse. I find this rather unsatisfying and a little more like spaghetti code than I'd like but it is short.

\documentclass{article}
\usepackage{etoolbox,xstring}

\newtoggle{strendswithfoo}

\IfEndWith{bar-foo}{foo}{\toggletrue}{\togglefalse}{strendswithfoo}

\begin{document}

toggle strendswithfoo = \iftoggle{strendswithfoo}{true}{false}

\end{document}
`