StrSubstitute for multiple replacements in a loop?

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}

enter image description here


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.

enter image description here


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}