# Expand arguments in string substitution macro

Rewritten as minimum non-working example:

\documentclass{article}

\makeatletter
\usepackage{xstring}
\usepackage{xparse}

\newcommand\textfromcase[1]{%
\IfEqCase{#1}{% for the purposes of this example:
{0}{AB} % \textfromcase[0] expands to AB
{1}{CD} % \textfromcase[1] expands to CD
% and many more lines of this kind.
}}

\newcommand{\EscSubstitute}[2]{\expandafter\StrSubstitute\expandafter{\x}{#1}{#2}[\x]}
\newcommand\substitute[1]{{%
\noexpandarg% because I otherwise get weird error messages.
\StrSubstitute{#1}{BC}{CB}[\x]% replacements for example.
\EscSubstitute{PQ}{QP}% many more lines of this kind.
\x}}

\newcommand{\concatenate}[1]{%
\substitute{\@for \el:=#1\do{\textfromcase{\el}}}%
}
\makeatother

\begin{document}
\substitute{ABCD} = ACBD.

\concatenate{1,0} = CDAB.

\concatenate{0,1} $\neq$ ACBD!
\end{document}


\concatenate{0,1} should expand to ACBD but instead expands to ABCD.

• We probably need a bit more detail here. The obvious thing to do is simply fully expand the argument to \substitute but you've deliberately disabled much the same idea for \StrSubstitute using \noexpandarg. That makes me suspect there is more to the arguments here than simple text. Can you give more detail? Also, at present we have to guess what packages you are using: a full minimal working example (MWE) would be useful. Nov 20, 2014 at 9:13
• I'd probably do then entire process here just using expl3, by the way. Nov 20, 2014 at 9:13
• Made a minimum working example (i.e. added the first 4 and last 6 non-empty lines). \noexpandarg is because it gives me weird error messages otherwise. I tried using expl3, but couldn't get it done that way, either. Nov 20, 2014 at 17:19
• There is an important difference between your initial snippets and the full MWE: that \@for part. That explains why you've used \noexpandarg so I can hopefully provide a solution. Nov 20, 2014 at 17:51
• No, it doesn't, the \@for was from my in-between attempts at finding another solution. With the \@for, the error message on leaving out the \noexpandarg is different, but in either case there is an error. Nov 20, 2014 at 18:15

You need to fully expand the argument of \substitute before you use this command. That can be arranged but not with a \@for mapping, which is non-expandable. As you are already using xparse I'd just write this in expl3. See code comments for details:

\documentclass{article}
\usepackage{expl3,xparse}
\ExplSyntaxOn
% User command taking ones comma-list argument
\NewDocumentCommand \concatenate { > { \SplitList { , } } m }
{
% Use an x-type expansion with an expandable argument to force
% expansion here
\user_substitute:x
{
\tl_map_function:nN {#1} \user_text_from_case:n
}
}
% A simple document-level wrapper around a code-level function
\NewDocumentCommand \substitute { m }
{ \user_substitute:n {#1} }
% The code-level substitution command is built to be flexible
\cs_new_protected:Npn \user_substitute:n #1
{
\group_begin:
\tl_set:Nn \l__user_tmp_tl {#1}
\tl_map_inline:nn
{
% A list of pairs for substitution: could be in a separate
% tl or comma list depending on requirements
{ { BC } { CB } }
{ { PQ } { QP } }
}
{ \__user_substitute_aux:nn ##1 }
\exp_last_unbraced:NV \group_end:
\l__user_tmp_tl
}
\cs_generate_variant:Nn \user_substitute:n { x }
\cs_new_protected:Npn \__user_substitute_aux:nn #1#2
{ \tl_replace_all:Nnn \l__user_tmp_tl {#1} {#2} }
\tl_new:N \l__user_tmp_tl
% An expandable case function to do first replacements
% Again, the list itself could be in a separate variable if required
\cs_new:Npn \user_text_from_case:n #1
{
\str_case:nn {#1}
{
{ 0 } { AB }
{ 1 } { CD }
}
}

% For outdated expl3 installations
\cs_if_exist:NF \str_case:nn
{
\cs_new:Npn \str_case:nn #1#2
{ \prg_case_str:nnn {#1} {#2} }
}
\ExplSyntaxOff

\begin{document}
\substitute{ABCD} = ACBD.

\concatenate{1,0} = CDAB.

\concatenate{0,1} $\neq$ ACBD!
\end{document}

• That results in: Undefined control sequence: \user_text_from_case:n #1->\str_case:nn Nov 20, 2014 at 18:12
• @user66554 You have an older version of expl3 installed. We are pretty clear that we only support the latest version, but I'll add a check for the older code. Nov 20, 2014 at 19:30