4

This is actually a follow-up question to Replacing a substring from 6 years ago.

I want to replace a substring in a long string. With \ReplaceStr from the xstring-package this works perfectly fine. However, if I want to nest multiple of such functions, I get the error Use of \@xs@StrSubstitute@@ doesn't match its definition. \ReplaceStrB{\ReplaceStr{aXYbXYc}}

Any remarks and workarounds are warmly appreciated. I know how to substitute strings outside of LaTeX. However, in this particular case I need a LaTeX-only solution.

\documentclass{article}
\usepackage{xstring}
\newcommand{\ReplaceStrX}[1]{\StrSubstitute{#1}{X}{Y}}
\newcommand{\ReplaceStrB}[1]{\StrSubstitute{#1}{b}{B}}

\begin{document}
\ReplaceStrX{aXbXc}                    % produces aYbYc
\ReplaceStrB{aXbXc}                    % produces aXBXc
\ReplaceStrB{\ReplaceStrX{aXbXc}}      % should produce aYBYc
\end{document}

EDIT 1: The simple solution

Simply add \noexpandarg\exploregroups as suggested by Manuel. This worked for my problem. In other cases, also check out Manuel's macro-solution or cfr's 13regex-solution.

EDIT 2: A related question

The following successfully produces aXBXc

\ReplaceStrB{aXbXc}

However this does not work.

\def\tempvar{aXbXc}
\ReplaceStrB{\tempvar}

Any idea, how to safe \tempvar as a string?

3 Answers3

5

A version using the new l3regex package (which is currently experimental, I believe).

\documentclass{article}
\usepackage{l3regex,xparse}
\ExplSyntaxOn
\tl_new:N \l_lab_string_tl
\cs_new_protected:Nn \lab_replace_me:nnn
{
  \tl_set:Nn \l_lab_string_tl { #3 }
  \regex_replace_all:nnN { #1 } { #2 } \l_lab_string_tl
  \l_lab_string_tl
}
\NewDocumentCommand \ReplaceStrX { +m }
{
  \group_begin:
  \lab_replace_me:nnn { X } { Y } { #1 }
  \group_end:
}
\NewDocumentCommand \ReplaceStrB { +m }
{
  \group_begin:
  \lab_replace_me:nnn { b } { B } { #1 }
  \group_end:
}
\ExplSyntaxOff

\begin{document}
\ReplaceStrX{aXbXc}                    % produces aYbYc
\ReplaceStrB{aXbXc}                    % produces aXBXc
\ReplaceStrB{\ReplaceStrX{aXbXc}}      % should produce aYBYc
\end{document}

string replacements

cfr
  • 198,882
5

From my answer here, you can do this with those macros. (I will use the second version because the namespace is already freplace rather than hmenke, but the first one works as well.)

\documentclass{scrartcl}

\usepackage{xparse}

\ExplSyntaxOn

\cs_generate_variant:Nn \cs_generate_variant:Nn { c }

\NewDocumentCommand \setfreplace { +m +m }
 {
  \freplace_set:nn { #1 } { #2 }
 }
\DeclareExpandableDocumentCommand \freplace { +m +m +m }
 {
  \freplace:nnn { #1 } { #2 } { #3 }
 }
\quark_new:N \q_freplace
\quark_new:N \q_freplacestop
\cs_new:Npn \freplace:nnn #1 #2 #3
 {
  \exp_not:f { \use:c { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } ):n } { #3 } }
 }
\cs_new_protected:Npn \freplace_set:nn #1 #2
 {
  \cs_set:cpx { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } ):n } ##1
   {
    \exp_not:c { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_auxi:nw } { } ##1 { \exp_not:N \q_freplace }
   }
  \cs_set:cpx { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_auxi:nw } ##1 ##2 ##
   {
    \exp_not:c { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_nobraces:nfn }
     { ##1 } { \exp_not:c { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_do:n } { ##2 } }
   }
  \cs_set:cpx { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_nobraces:nnn } ##1 ##2
   {
    \exp_not:c { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_auxii:nn } { ##1 ##2 }
   }
  \cs_generate_variant:cn { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_nobraces:nnn } { nf }
  \cs_set:cpx { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_auxii:nn } ##1 ##2
   {
    \exp_not:N \str_if_eq:nnTF { \exp_not:N \q_freplace } { ##2 }
     { \exp_stop_f: ##1 }
     {
      \exp_not:c { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_addbraces:nfw }
       { ##1 } { \exp_not:c { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } ):n } { ##2 } }
     }
   }
  \cs_set:cpx { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_addbraces:nnw } ##1 ##2
   {
    \exp_not:c { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_auxi:nw } { ##1 { ##2 } }
   }
  \cs_generate_variant:cn { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_addbraces:nnw } { nf }
  \cs_set:cpx { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_do:n } ##1
   {
    \exp_not:N \tl_if_empty:nTF { ##1 }
     { \exp_stop_f: }
     {
      \exp_not:c { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_auxiii:nww }
       { } ##1 \exp_not:n { #1 \q_freplace \q_freplacestop }
     }
   }
  \cs_set:cpx { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_auxiii:nww } ##1 ##2 #1 ##3 \q_freplacestop
   {
    \exp_not:N \str_if_eq:nnTF { \exp_not:N \q_freplace } { ##3 }
     { \exp_stop_f: ##1 ##2 }
     {
      \exp_not:c { freplace_( \tl_to_str:n { #1 } )_( \tl_to_str:n { #2 } )_auxiii:nww }
       { ##1 ##2 \exp_not:n { #2 } } ##3 \exp_not:N \q_freplacestop
     }
   }
 }

\cs_generate_variant:Nn \freplace:nnn { nnV }
\cs_new_protected:Npn \tl_replace_nested:Nnn #1 #2 #3
 {
  \freplace_set:nn { #2 } { #3 }
  \tl_set:Nx #1 { \freplace:nnV { #2 } { #3 } #1 }
 }

\ExplSyntaxOff

\setfreplace{X}{Y} % these two lines are mandatory, for each \freplace you use
\setfreplace{b}{B} % in the document, you have to first declare it with \setfreplace

\newcommand\ReplaceStrX[1]{\freplace{X}{Y}{#1}}
\newcommand\ReplaceStrB[1]{\freplace{b}{B}{#1}}

\begin{document}

\ReplaceStrX{aXbXc} produces aYbYc\par
\ReplaceStrB{aXbXc} produces aXBXc\par
\ReplaceStrB{\ReplaceStrX{aXbXc}} should produce aYBYc\par

\end{document}

This does not only work in nested braces, but it's indeed an expandable solution. This version uses the syntax \freplace{search}{replace}{token list where you search}, and requires you to issue a single \setfreplace{search}{replace} for each different search & replace you use (to create the needed macros).

The other version in the answer I linked uses the syntax \freplace{name}{token list to replace} where what you have to write before is \setfreplace{name}{search}{replace}, i.e., give the pair a symbolic name. If you prefer that syntax, copy the code from the first part of the answer.


In any case, this code seems to compile

\documentclass{scrartcl}

\usepackage{xstring}

\noexpandarg\exploregroups
\newcommand\ReplaceStrX[1]{\StrSubstitute{#1}{X}{Y}}
\newcommand\ReplaceStrB[1]{\StrSubstitute{#1}{b}{B}}

\begin{document}

\ReplaceStrX{aXbXc}\ produces aYbYc\par
\ReplaceStrB{aXbXc}\ produces aXBXc\par
\ReplaceStrB{\ReplaceStrX{aXbXc}}\ should produce aYBYc\par

\end{document}
Manuel
  • 27,118
  • By the way, if you actually want to expand the argument inside the command before doing the “first” replacement, that can be done easily, e.g., \freplace{search}{replace}{\freplace{X}{ch}{searX}} should produce replace rather than search (this means that X in converted to ch before search is converted to replace, rather than the other way around). – Manuel Mar 29 '17 at 23:45
  • Thanks Manuel and @cfr for your detailed answers. For my purpose they all work the same, so I prefer the very short answer of Manuel, which uses \noexpandarg\exploreroups. Please tell me, which one I should mark as the accepted answer, especially since 13regex is still experimental. – LABclimate Mar 30 '17 at 11:37
  • Still, I don't understand the order in which nested functions are executed. I tried with all three solutions presented here, and they all execute the outer function before the inner function... So \freplace{search}{replace}{\freplace{X}{ch}{searX}} in fact returns searX when I test it...

    Or if I replace w with 1 and ww with 2 I get: \ReplaceStrW{\ReplaceStrWW{w-ww-www}} \par % returns 1-11-111 and \ReplaceStrWW{\ReplaceStrW{w-ww-www}} \par % returns 1-2-21

    – LABclimate Mar 30 '17 at 11:37
  • So you actually want to execute before the inner function? In that case it's easy to tweak. Just add \ExplSyntaxOn \DeclareExpandableDocumentCommand \freplace { +m +m +m } { \freplace:nnf { #1 } { #2 } { #3 } } \cs_generate_variant:Nn \freplace:nnn { nnf } \ExplSyntaxOff. If that works for you I can add that to the answer. – Manuel Mar 30 '17 at 12:15
  • I just found out, that \ReplaceStrW\ReplaceStrWW{w-ww-www} (omitting the curly brackets) does the trick as well. Comming from phython I am just very much used to the inner-then-outer syntax. To me it is counter-intuitive, that \ReplaceStrW{\ReplaceStrWW{w-ww-www}} produces something different than \ReplaceStrW\ReplaceStrWW{w-ww-www}.

    If you are willing to edit your answer, maybe move the solution with \noexpandarg\exploregroups to the beginning, since this is the part, I accept most. For my purpose there's no need for macros and many lines. What could such reasons be?

    – LABclimate Mar 30 '17 at 13:02
2

The StrSubstitute command actually has a final optional argument to deal with precisely this. This argument allows you to supply a name to give to the result of the substitution. The result will then be stored in a command with this name, which can be passed to the next StrSubstitute you wish to carry out (or other similar xstring commands).

For example, your double substitution can be carried out like this:

\StrSubstitute{aXbXc}{X}{Y}[\afterXtoY]\StrSubstitute{\afterXtoY}{b}{B}

If you want to create new commands that perform each substitution, and don't mind making a third command to perform first the X to Y and then the b to B substitution rather than having just two commands, then this would work:

\documentclass{article}

\usepackage{xstring} \newcommand{\ReplaceStrX}[1]{\StrSubstitute{#1}{X}{Y}} \newcommand{\ReplaceStrB}[1]{\StrSubstitute{#1}{b}{B}} \newcommand{\ReplaceStrBX}[1]{\StrSubstitute{#1}{X}{Y}[\afterReplaceStrX]\StrSubstitute{\afterReplaceStrX}{b}{B}}

\begin{document}

\ReplaceStrX{aXbXc} % produces aYbYc \ReplaceStrB{aXbXc} % produces aXBXc \ReplaceStrBX{aXbXc} % produces aYBYc

\end{document}

If you want only two commands, this works (although you do now need to type an extra {} when using the \ReplaceStrX command by itself):

\documentclass{article}

\usepackage{xstring} \newcommand{\ReplaceStrX}[2]{\StrSubstitute{#1}{X}{Y}[#2]} \newcommand{\ReplaceStrB}[1]{\StrSubstitute{#1}{b}{B}}

\begin{document} \ReplaceStrX{aXbXc}{} % produces aYbYc \ReplaceStrB{aXbXc} % produces aXBXc

\ReplaceStrX{aXbXc}{\afterReplaceStrX}\ReplaceStrB{\afterReplaceStrX} % produces aYBYc

\end{document}

Of course, you could do the same for \ReplaceStrB to get a pair of commands usable together in either order (which in your example gives the same result but with the different substitution commands in this example does not):

\documentclass{article}
\usepackage{xstring}
\newcommand{\ReplaceStrX}[2]{\StrSubstitute{#1}{X}{bd}[#2]}
\newcommand{\ReplaceStrB}[2]{\StrSubstitute{#1}{b}{Xb}[#2]}
%\newcommand{\ReplaceStrBX}[1]{\StrSubstitute{#1}{X}{Y}[\afterReplaceStrX]\StrSubstitute{\afterReplaceStrX}{b}{B}}

\begin{document}

\ReplaceStrX{aXbc}{} % produces abdbc \ReplaceStrB{aXbc}{} % produces aXXbc

\ReplaceStrX{aXbc}{\afterReplaceStrX}\ReplaceStrB{\afterReplaceStrX}{} % produces aXbdXbc \ReplaceStrB{aXbc}{\afterReplaceStrB}\ReplaceStrX{\afterReplaceStrB}{} % produces abdbdbc

\end{document}

Neremanth
  • 121