# StrSubstitute for multiple replacements in a loop?

I am trying to replace multiple keywords using \StrSubstitute inside a loop using foreach - but unable to accumulate all the results. The result of each \StrSubstitute call is getting lost inside the loop.

I am using the method suggested at capturing the result. Sample code is as below:

\documentclass[11pt]{article}
\usepackage{etoolbox}
\usepackage{xstring}
\usepackage{tikz}

\begin{document}

\def\KeyWords{one, two, three}
\def\MyText{This is one, followed by two later by three}
\expandarg

Original Text: \MyText\\

\foreach \keyword in \KeyWords
{
Replacing: \texttt{\keyword}
\StrSubstitute{\MyText}{\keyword}{\textbf{\keyword}}[\MyText]

Now MyText: \MyText  \\ \\
}
Final: \MyText \


In the above sample, in each iteration the particular word one, two or three gets bold correctly - but when it finally comes out of the loop, the \MyText ends up being the same as original.

Even \csgdef did not work (assuming global vs local definitions is the problem).

Tried \renewcommand{\MyText}{\StrSubstitute{\MyText}{\keyword}{\textbf{\keyword}}} to directly assign the substitution result to \MyText but it just hangs saying something like

[Loading MPS to PDF converter (version 2006.09.02).]


(I am using MikTeX 2.9 and pdflatex if that matters )

What is the correct mechanism to achieve this?

Goal is to substitute one or more comma separated supplied keywords with their respective replacement words in a given string.

The problem is that each cycle of \foreach is performed inside a group. So you have to globally save \MyText after the substitution; however a straightforward

\documentclass[11pt]{article}
\usepackage{etoolbox}
\usepackage{xstring}
\usepackage{tikz}

\begin{document}

\def\KeyWords{one, two, three}
\def\MyText{This is one, followed by two later by three}
\expandarg

Original Text: \MyText\\

\foreach \keyword in \KeyWords
{
Replacing: \texttt{\keyword}
\StrSubstitute{\MyText}{\keyword}{\textbf{\keyword}}[\temp]%
\global\let\MyText\temp

Now MyText: \MyText  \\
}
Final: \MyText
\end{document}


will not work, because the keywords are replaced by \textbf{\keyword}, where \keyword doesn't get expanded.

You have to expand \keyword in the substitution string; for instance with

\documentclass[11pt]{article}
\usepackage{etoolbox}
\usepackage{xstring}
\usepackage{tikz}

\begin{document}

\def\KeyWords{one, two, three}
\def\MyText{This is one, followed by two later by three}
\expandarg

Original Text: \MyText

\foreach \keyword in \KeyWords
{
Replacing: \texttt{\keyword}
\begingroup\edef\x{\endgroup
\unexpanded{\StrSubstitute{\MyText}{\keyword}}
{\noexpand\textbf{\keyword}}}\x[\temp]
\global\let\MyText\temp
Now MyText: \MyText  \\
}
Final: \MyText
\end{document}


I's suggest a different approach with l3regex:

\documentclass{article}
\usepackage{xparse,l3regex}

\ExplSyntaxOn
\NewDocumentCommand{\setkeywords}{m}
{
\gopa_set_keywords:n { #1 }
}

\NewDocumentCommand{\changekeywords}{ O{textbf} m}
{
\gopa_change_keywords:nn { #1 } { #2 }
}

\cs_new_protected:Npn \gopa_set_keywords:n #1
{
\seq_set_split:Nnn \l_gopa_keywords_seq { , } { #1 }
\tl_set:Nx \l_gopa_keywords_tl { \seq_use:Nn \l_gopa_keywords_seq { | } }
\tl_put_left:Nn \l_gopa_keywords_tl { \b( }
\tl_put_right:Nn \l_gopa_keywords_tl { ) }
\regex_gset:NV \g_gopa_keywords_regex \l_gopa_keywords_tl
}

\cs_new_protected:Npn \gopa_change_keywords:nn #1 #2
{
\tl_set:Nn \l_gopa_sentence_tl { #2 }
\regex_replace_all:NnN \g_gopa_keywords_regex { \c{#1}\cB\{\1\cE\} } \l_gopa_sentence_tl
\tl_use:N \l_gopa_sentence_tl
}
\tl_new:N \l_gopa_keywords_tl
\tl_new:N \l_gopa_sentence_tl
\seq_new:N \l_gopa_keywords_seq
\regex_new:N \g_gopa_keywords_regex
\cs_generate_variant:Nn \regex_gset:Nn { NV }
\ExplSyntaxOff

\setkeywords{one, two, three}

\begin{document}
\changekeywords{This is one, followed by two later by three}

\changekeywords[textit]{This is one, followed by two later by three}

\end{document}


The \setkeywords command defines the keywords to change; with \changekeywords you specify the text and, optionally, the format to use (only the control sequence name, rather than a command).

How does it work? From the list of keywords, we prepare a regular expression in the form

\b ( one | two | three )


is built. Matches are replaced by \textbf{\1} where \1 represents the matching string.

• Thanks much. The l3regex approach looks elegant - I am sure regular expressions would match and replace faster than explicit loops. But when I copy paste that code as-is and run on pdflatex (miktex 2.9) I am getting errors: ! Undefined control sequence. <argument> \seq_use:Nn \l_gopa_keywords_seq {|} l.39 \setkeywords{one, two, three} ?  Am I missing something? Does latex3 require some special processing? – Gopalakrishna Palem Jul 19 '13 at 0:23
• tested on TexLive on ubuntu - same error. Added expl3 package reference - no change. – Gopalakrishna Palem Jul 19 '13 at 0:45
• @GopalakrishnaPalem You have to update to the last version of all packages. For the moment you can try with \seq_use:Nnnn \l_gopa_keywords_seq {|} {|} {|} – egreg Jul 19 '13 at 7:32

Here's an alternate way, using stringstrings. Downside: its a slow package.

\documentclass[12pt]{article}
\usepackage{stringstrings}
\newcounter{index}
\newcommand\emboldenkeywords[2]{%
\getargs{#1}%
\setcounter{index}{0}%
\edef\thestring{#2}%
\encodetoken[1]{\bfseries}
\encodetoken[2]{\mdseries}
\whiledo{\value{index} < \narg}{%
\stepcounter{index}%
\edef\nextkeyword{\csname arg\roman{index}\endcsname}%
\convertword[e]{\thestring}{\nextkeyword}{\bfseries\nextkeyword\mdseries}%
}%
\retokenize{\thestring}%
\thestring%
\decodetoken[1]{\bfseries}
\decodetoken[2]{\mdseries}
}
\begin{document}
\emboldenkeywords{one two three}{This is one, followed by two later by three}
\end{document}

• Interesting. Was not aware of this package stringstrings` before. Thanks for this approach - I can use some of these concepts. – Gopalakrishna Palem Jul 19 '13 at 0:05