4

I have some text that I would like to preprocess using xstring's \StrSubstitute method (basically replacing certain keywords by simple commands such as foobar by \emph{foobar}). There are two side conditions which make this tough (at least I haven't been able to figure out how to do this). On the one hand, I need to run \StrSubstitute in noexpandarg mode, on the other I would like to use active spaces in order to print out the input obeying spaces. I know there are environments such as listings or verbatim but these won't work for what I have in mind: in the very end the thus preprocessed string should go into an (amsmath) align environment.

So here is what I have

\documentclass{article}

\usepackage{xstring}

\begingroup
\catcode`\@=\active
\lccode`\@=`\ % At-sign is a space
\lowercase{%
  \endgroup
  \newenvironment{withspaces}{%
    \catcode`\ =\active % Space active
    \let@=~%
   }{ 
}}

\begin{document}

\def\foo{foo}
\begin{withspaces}
There is space before        \foo foo
\end{withspaces}

\begin{withspaces}
\StrSubstitute{There is space before        \foo foo}{foo}{bar}
\end{withspaces}

\noexpandarg % in order to first run the string substitution and only then expand \foo
\StrSubstitute{There is space before        \foo foo}{foo}{bar}

% The following breaks down to the noexpandarg flag
\begin{withspaces}
%\StrSubstitute{There is space before        \foo foo}{foo}{bar}
\end{withspaces}


\end{document}

The withspaces environment (adapted from Joseph's answer Environment that obeys spaces) seems to work fine both in text mode and in math mode. The first use of \StrSubstituteworks as it is in fullyexpandmode. The result then, however, is that first \foo is expanded and then replaced by \StrSubstitute which is why I think I need to use noexpandarg: \StrSubstitute should do the substitution without expanding \foo.

This also works fine in isolation. That is,

\noexpandarg % in order to first run the string substitution and only then expand \foo
\StrSubstitute{There is space before        \foo foo}{foo}{bar}

yields the desired result but without active spaces. Once I try to combine the two (uncommenting out the last \StrSubstitute in the MWE) I am stuck at

Argument of \@xs@behindspace has an extra }.
<inserted text> 
                \par 
l.33 \end
         {withspaces}

And here is where I am lost.

  • Why not adding \noexpandarg to the definition of withspaces? – egreg Mar 26 '15 at 9:05
  • @egreg in the example I wanted to emphasize that it works without \noexpandarg (i.e. full expansion mode) but not with it. If I add it to the withspaces environment then already the first StrSubstitute fails. – Arno Mittelbach Mar 26 '15 at 9:21
  • Would a solution without xstring be acceptable? – egreg Mar 26 '15 at 9:34
  • Yes. What I need is the capability of parsing the input and replace keywords by simple macros, i.e., replace foobar by \highlight{foobar}. Currently I am implementing this with a sequence of StrSubstitutes but anything that provides the functionality (i.e. str replacement without expansion + spacing) is fine. – Arno Mittelbach Mar 26 '15 at 9:39
  • To be more exact on what I am trying to do. I'd like to add the capability of automatic indentation for pseudocode generated with the cryptocode package (ctan.org/pkg/cryptocode). Cryptocode is a package I've developed over the last weeks to help typeset cryptographyic papers. – Arno Mittelbach Mar 26 '15 at 9:49
4

You may want to look at an implementation using l3regex:

\documentclass{article}

\usepackage{xstring}
\usepackage{xparse}
% \usepackage{l3tl-analysis} % uncomment for debugging

\newenvironment{withspaces}
 {\obeyspaces\begingroup\lccode`~=` \lowercase{\endgroup\let~}~}
 {}

\ExplSyntaxOn
\tl_new:N \l_arno_strsub_input_tl
\tl_new:N \l_arno_strsub_search_tl
\tl_new:N \l_arno_strsub_replace_tl

\NewDocumentCommand{\strsub}{mmm}
 {
  \tl_set:Nn \l_arno_strsub_input_tl { #1 }
  \tl_set:Nn \l_arno_strsub_search_tl { #2 }
  \tl_set:Nn \l_arno_strsub_replace_tl { #3 }
  \regex_replace_all:nnN
   { \u{l_arno_strsub_search_tl} }
   { \u{l_arno_strsub_replace_tl} }
   \l_arno_strsub_input_tl
  %\tl_show_analysis:N  \l_arno_strsub_input_tl % uncomment for debugging
  \tl_use:N \l_arno_strsub_input_tl
 }
\ExplSyntaxOff

\begin{document}

\def\foo{foo}
\begin{withspaces}
There is space before        \foo foo
\end{withspaces}

\begin{withspaces}
\strsub{There is space before        \foo foo}{foo}{bar}
\end{withspaces}

\strsub{There is space before        \foo foo}{foo}{bar}

\begin{withspaces}
\strsub{Some  spaces  \foo foo}{foo}{\textbf{foo}}
\end{withspaces}

\end{document}

The search and replacement strings are stored into token lists, so they can be used “as is” for the regex business.

As you see in the last example, only the explicit appearance of foo has been replaced.

If you uncomment the lines marked “for debugging” you'll see in the terminal a representation of the strings after the regex replacement.

(For releases before TeX Live 2017, you will need \usepackage{l3tl-analysis} and \usepackage{l3regex} in addition to \usepackage{xparse} for this to work.)

enter image description here

2

You can define \dospaces as a prefix for \StrSubstitute macro:

\def\dospaces\StrSubstitute{\bgroup\catcode`\ =13 \let\protect=\noexpand \dospacesA}
\def\dospacesA#1{\xdef\tmp{#1}\egroup \expandafter\StrSubstitute\expandafter{\tmp}}

%% usage:

\dospaces\StrSubstitute{There is space before        \foo foo}{foo}{bar}
  • That is an interesting construct (which I not fully understand). However, it seems that now first \foo is expanded and then the substitution occurs. That is the result is There is space before [space] bar bar instead of There is space before [space] foo bar. My guess would be that the \xdef does a full expansion. If I put a \noexpand\foo then the result is correct. Can this expansion be somehow prohibited automatically? – Arno Mittelbach Mar 26 '15 at 9:27
  • @ArnoMittelbach I've added \let\protect=\noexpand into my code. Now, you can use \DeclareRobustCommand\foo{foo} and such macro will be not expanded by \xdef. – wipet Mar 26 '15 at 10:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.