# strip last character from parameter if it is 's'

I have a semantic annotation \monster to annotate mentions of monsters so that they are indexed:

\newcommand{\indexMonster}[1]{\index{#1}}
\newcommand{\monster}[1]{#1\indexMonster{#1}}


Now, most of the monsters mentions are in plural but some are not. I would like the index entries to be in singular. So I am looking for a way to strip the last character of the \monster argument if it is 's'.

This solution uses xstring. Since there are some terrible monsters named albatross which are intesly feared, and you of course will be writing a lot about, I have also made a warning system for when the names of monsters have been changed. You can of course drop this if you feel like. If you want to override the test for monsters ending in s, simply add braces around your monster, like in the example below.

Output

Code

\documentclass[11pt,draft]{article}
\usepackage{xstring}
\usepackage{ifdraft}
\newcommand{\indexMonster}[1]{\index{#1}}
\newcommand{\monster}[1]{%
\IfEndWith{#1}{s}{%
% Do nothing if ends with s.
\StrGobbleRight{#1}{1}[\result]%
\result\indexMonster{\result}%
% Issue a warning on changed monsters
\ifdraft{%
\marginpar{Monster \textbf{#1} was changed to \result.}%
}{}%%
}{%
#1\indexMonster{#1}}%
}
\begin{document}
\monster{Dogs} \monster{Dog} \monster{albatross} \monster{{albatross}}
\end{document}


## Second solution

Here is my attempt at this with expl3. I just did the text-replacement, not the indexing, but that should be trivial.

\documentclass[11pt]{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\monster}{m}
{
\l_wilx_monster:n { #1 }
}

\cs_new_protected:Nn \l_wilx_monster:n
{
% Check if last character is "s"
\str_if_eq_x:nnTF { \str_item:nn {#1} {-1} } {s}
% True: print string expect for last string (in other words, remove s)
{\str_range:nnn {#1} {1} {-2}} %
% False: Print string as usual
{#1}
}
\ExplSyntaxOff

\begin{document}
\monster{dogs}
\end{document}

• I do not think this answers the question. However, looking at xstring, some combination of the macros could probably give me what I want. I am not clear as to how to compute say string length minus 1 expression to cut the string right. – wilx Jun 21 '16 at 6:47
• Oh, right, I'm sorry, I clearly misunderstood here. My bad. See my edit with new code. – Runar Jun 21 '16 at 6:53
• I am accepting this because it does what the question asks for. Unfortunately, it turns out my case is a little bit more complex. I am going to open a new question using your answer as a starting point. – wilx Jun 21 '16 at 7:36
• yes, I guess it is. I think the best course is just the manual one, looking over every one of them by hand in the end. – Runar Jun 21 '16 at 7:44

Here is an example how to strip the last s using TeX primitives. The expandable macro \striplastS is created. \striplastS{texts} expands to text and \striplastS{text} expands to text too.

\def\striplastS#1{\striplastSa{#1}#1\end s\end\eend}
\def\striplastSa#1#2s\end#3\eend{\ifx\end#3\end#1\else#2\fi}

%test:
\message{\striplastS{text} \striplastS{texts}} % prints: text text

• Sorry for my ignorance about TeX, but can you explain how this works? What is the meaning of all those \end and \eend? I can't seem to find any source that explains the \eend command. – Māris Ozols Dec 20 '16 at 22:20

You can use the stringstringspackage by Steven B. Segletes:

\documentclass{article}
\usepackage{stringstrings}
\usepackage{makeidx}\makeindex

\begin{document}

\newcommand{\indexMonster}[1]{%
\testmatchingchar{#1}{$}{s} \ifmatchingchar \substring[q]{#1}{1}{$-1}\index{\thestring}
\else
\index{#1}
\fi}
\newcommand{\monster}[1]{#1\indexMonster{#1}}

\monster{zombies}

\pagebreak

\monster{zombie}

\printindex

\end{document}


You have more problems than you think of.

\documentclass{book}

\usepackage{etoolbox}
\usepackage{imakeidx}
\usepackage{fancyhdr}

\makeindex[name=monsters,title=Monster index]

\newcommand{\indexMonster}[1]{\index[monsters]{\stripfinals{#1}}}
\newrobustcmd{\monster}[1]{#1\indexMonster{#1}}
\makeatletter % Wipet's
\newcommand{\stripfinals}[1]{\strip@final@s{#1}#1\@nnil s\@nnil\@nil}
\def\strip@final@s#1#2s\@nnil#3\@nil{%
\ifx\@nnil#3\@nnil#1\else#2\fi
}

\newcommand{\fixmonster}{\let\monster\@firstofone}
{\let}
{\fixmonster\let}
{}{}
\makeatother

\pagestyle{fancy}
\fancyhf{}
\makeatletter

\newcommand{\WviiSPOT}{\mbox{\monster{*S~P~O~T*}}}

\begin{document}

\frontmatter
\tableofcontents

\mainmatter

\chapter{\monster{zombies} and \WviiSPOT}

\monster{asasas}, \monster{zombies} and \WviiSPOT

\clearpage

\monster{zombie} and \monster{uss}

\printindex[monsters]

\end{document}


lualatex solution

\documentclass{article}
\usepackage{luacode}
\usepackage{makeidx}
\makeindex
\begin{document}

\newcommand\myIndex[1]{\directlua{%
mystr="#1"
len=string.len(mystr)
lastLetter= string.sub(mystr,len,len)

if lastLetter=='s' or lastLetter=='S' then
mystr=string.sub(mystr,1,len-1)
end
tex.sprint(mystr)
}}%
\newcommand{\indexMonster}[1]{\index{\myIndex{#1}}}
\newcommand{\monster}[1]{#1\indexMonster{#1}}

This ends with s \monster{big monesters}
and this is has no s at end \monster{small  monester}
\printindex

\end{document}


then

lualatex foo.tex makeindex foo.tex lualatex foo.tex

gives

Second solution, same as above, but using luacode separate environment.

\documentclass{article}
\usepackage{luacode}
\usepackage{makeidx}
\makeindex

\begin{luacode*}
function myIndex(myStr)
len=string.len(myStr)
lastLetter= string.sub(myStr,len,len)

if lastLetter=='s' or lastLetter=='S' then
myStr=string.sub(myStr,1,len-1)
end
tex.sprint("\\index{"..myStr.."}")
end

\end{luacode*}
\newcommand{\myIndex}[1]{\directlua{myIndex("#1") }}%

\begin{document}

\newcommand{\indexMonster}[1]{\myIndex{#1}}
\newcommand{\monster}[1]{#1\indexMonster{#1}}

This ends with s \monster{big monesters}
and this is has no s at end \monster{small  monester}

\printindex
\end{document}