# Replacing entries in a csv list

I want to replace entries in a csv list, where the list parser can be arbitrary (eg: ,;:|). An example follows:

\def\x{r;s;t;x;y;z}


In this list I want to efficiently replace r,t,x by a,b,c via, say,

\replaceentries\x{r;s;t}{a;b;c}


But I may also end up with unbalanced pairs, like in

\replaceentries\x{r;s;t}{a;b;c;d;e}


and I don't want TeX to complain.

Please has anyone solved this problem before, or perhaps found a solution in a package? Otherwise, I think I can give it a try, but why reinvent the wheel?

-

The xstring package provides such features :

\documentclass{minimal}
\usepackage[T1]{fontenc}
\usepackage{xstring}
\begin{document}
\def\x{r;s;t;x;y;z}
\StrSubstitute\x{r;s;t}{a;b;c}[\x]
\meaning\x

\def\x{r;s;t;x;y;z}
\StrSubstitute\x{r;s;t}{a;b;c;d;e;f;g}[\x]
\meaning\x
\end{document}


: sorry, Ahmed, I misunderstood what you exactly wanted! I hope this code is better:

\documentclass{minimal}
\usepackage[T1]{fontenc}
\usepackage{xstring}
\newcommand\replaceentries[4][;]{%
\def\listremaining{#3#1}\def\substremaining{#4#1}%
\saveexpandmode\expandarg
\loop
\unless\ifx\empty\listremaining
\StrBefore\listremaining{\noexpand#1}[\currentitem]%
\StrBehind\listremaining{\noexpand#1}[\listremaining]%
\StrBefore\substremaining{\noexpand#1}[\currentsubst]%
\StrBehind\substremaining{\noexpand#1}[\substremaining]%
\StrSubstitute#2\currentitem\currentsubst[#2]%
\repeat
\restoreexpandmode}
\begin{document}
\def\x{r;s;t;x;y;z}
\replaceentries\x{x;y;z}{a;b;c}
\meaning\x

\def\x{r!s!t!x!y!z}
\replaceentries[!]\x{t!x!z}{A!B!C!D!E}
\meaning\x
\end{document}

-
I will look in the xstring package documentation, but wouldn't \StrSubstitute have to be told the list parser/separator first, if the parser can be arbitrary? –  Ahmed Musa Mar 17 '11 at 20:42
Welcome to TeX.SX! Have a look at meta.tex.stackexchange.com/questions/1181/…, would be nice if you could add yourself and your package(s). –  Martin Scharrer Mar 17 '11 at 20:51
I have looked at xstring manual but it doesn't do exactly what I have in mind. It replaces blocks of strings, if found. Also, although I have not looked at the implementation, I guess that it iterates token-wise, instead of element-wise: so it perhaps grabs the list parsers and substitutes them too. –  Ahmed Musa Mar 17 '11 at 20:52
@Martin: Thanks you! You suggest me to add my packages in the list. Although I'm only a newbie here, why not... I'm sorry but I couldn't find where to go, where to click, what to do to add anything! Yes, a newbie i am! ;) –  unbonpetit Mar 17 '11 at 21:24
My apologies, I forgot that you will need 100rep points to be able to edit community wiki posts. –  Martin Scharrer Mar 17 '11 at 21:45

Why not do it without any package? The code is not much more complicated:

\documentclass{minimal}
\usepackage[T1]{fontenc}
\makeatletter
\newcommand\if@instr[2]{%
\def\if@instr@i##1#2##2\@nil{\ifx\if@instr@i##2\expandafter\@secondoftwo\else\expandafter\@firstoftwo\fi}%
\expandafter\if@instr@i#1\@@nil#2\if@instr@i\@nil}
\newcommand\subst@[3]{%
\def\subst@i##1#2##2\@nil{\def#1{##1#3##2}\subst@#1{#2}{#3}}%
\if@instr#1{#2}{\expandafter\subst@i#1\@nil}\relax}
\newcommand\split@at[4]{%
\def\split@at@i##1#2##2\@nil{\def#3{##1}\def#4{##2}}%
\expandafter\split@at@i#1\@nil}
\newcommand\replaceentries[4][;]{%
\def\listremaining{#3#1}\def\substremaining{#4#1}%
\loop
\unless\ifx\empty\listremaining
\split@at\listremaining{#1}\currentitem\listremaining
\split@at\substremaining{#1}\currentsubst\substremaining
\expandafter\expandafter\expandafter\subst@
\expandafter\expandafter\expandafter#2%
\expandafter\expandafter\expandafter{\expandafter\currentitem\expandafter}\expandafter{\currentsubst}%
\repeat}
\makeatother
\begin{document}
\def\x{a;b;c;d;e;f;g;h}
\replaceentries\x{a;c;f}{J;K;L}
\x

\def\x{r!s!t!x!y!z}
\replaceentries[!]\x{t!x!z}{A!B!C!D!E}
\x
\end{document}

-
Nice! –  Ahmed Musa Mar 18 '11 at 16:36
But the lists may originally be non-normalized (with spaces between entries and non-standard catcode for the list parser), thereby making searches fail. –  Ahmed Musa Mar 18 '11 at 16:50
Yes Ahmed, this is a minimal code, no extra feature is provided! But remove spaces is easy with \subst@. For catcode match, a \scantoken\expandafter{#1} should be enough, and easy to code. Best. –  unbonpetit Mar 18 '11 at 19:46

unbonpetit's answer appears short and beautiful (many thanks for your effort!), but that is because xstring is in the background. In the meantime, I obtained a solution based on the API of the catoptions package. It first normalizes the lists. Sorry about the possibly unfamiliar commands.

\robust@def*\replaceoneelement{\cpt@testopt\@replaceoneelement{,}}
\robust@def\@replaceoneelement[#1]#2#3#4{%
\if@checklp\cpt@checklistparser{#1}\relax\fi
\begingroup
\def\@tempa##1{%
\def\@replace####1#1##1#1####2\cpt@nil{\def#2{####1#1#4#1####2}}%
\edef\@tempa{%
{\detokenize{#1##1#1}}{\detokenize{#1}\cptoxdetok{#2}\detokenize{#1}}}%
\expandafter\xifinsetFT\@tempa{}{%
\expandafter\@replace\expandafter#1#2#1\cpt@nil
\trimoneparser{#1}{#2}%
}%
}%
\if@twincall
\edef\@tempb{\unexpanded{#3}}%
\else
\@csvnormalize#1#2%
\edef\@tempb{\xp@despace{#3}}%
\fi
\expandafter\@tempa\expandafter{\@tempb}%
\exitgroupdef#2%
}
\robust@def*\replaceelements{\cpt@testopt\@replaceelements{,}}
\robust@def\@replaceelements[#1]#2#3#4{%
\begingroup
\cpt@csvnormalize#1#2%
\edef\@tempa{\unexpanded{#3}}%
\cpt@csvnormalize#1\@tempa
\edef\@tempb{\unexpanded{#4}}%
\cpt@csvnormalize#1\@tempb
\def\@tempc##1#1##2\cpt@unique##3#1##4\cpt@unique{%
\@replaceoneelement[#1]#2{##1}{##3}%
\ifblankTF{##2}{}{\ifblankTF{##4}{}{\@tempc##2\cpt@unique##4\cpt@unique}}%
}%
\@checklpfalse\@twincalltrue
\cptexpandsecond\@tempc{%
\expandcsonce\@tempa#1\cpt@unique\expandcsonce\@tempb#1\cpt@unique
}%
\exitgroupdef#2%
}

-
Starting a comment with "@unbonpetit" is The Right Thing, but for an answer this is a bit ... unusual. I've taken the liberty of editing your answer, hope that's OK. –  Hendrik Vogt Mar 18 '11 at 8:33
@Hendrik: I think that is some Twitter nonsense. People are referred to with a prepended @. –  TH. Mar 18 '11 at 10:52
@Hendrik: Noted with thanks. –  Ahmed Musa Mar 18 '11 at 16:35

This is a variation on unbonpetit's latest solution:

\usepackage{catoptions}
\makeatletter

\newif\ifsubstall
\newcommand\subst@[3]{%
% Substitute all occurrences of #2 in #1 by #3.
% I didn't need this, but in general it is important.
% There is, however, need for a guard, in case we
% don't need to substitute all the occurrences.
\def\subst@i##1#2##2\@nil{%
\def#1{##1#3##2}%
\ifboolFT{substall}{}{\subst@#1{#2}{#3}}%
}%
\oifinsetFT{#2}{#1}{}{\expandafter\subst@i#1\@nil}%
}
\newcommand\split@at[3]{%
\def\split@at@i##1#3##2\@@nil{\def#2{##1}\def#1{##2}}%
\expandafter\split@at@i#1\@@nil
}
\newcommand\replaceentries[4][;]{%
\def\listremaining{#3#1}\def\substremaining{#4#1}%
\@tempswatrue
% \substremaining may run out before \listremaining. Hence
\loop
\ifx\@empty\listremaining\@tempswafalse\else
\ifx\@empty\substremaining\@tempswafalse\fi\fi
\if@tempswa
\split@at\listremaining\currentitem{#1}%
\split@at\substremaining\currentsubst{#1}%
% We can avoid a chain of \expandafters here:
\cptexpandtwoargsonce{\subst@#2}\currentitem\currentsubst
\repeat
}

\makeatother
\begin{document}
\substalltrue
\def\x{a;b;c;d;e;a;f;g;h}
\replaceentries\x{a;c;f}{A;C;F}
\x

\def\x{r!s!t!x!y!r!z}
\replaceentries[!]\x{r!x!z}{R!X!Z!D!E}
\x
\end{document}

-
If you post code, in particular if you post a 2nd answer, then it is always helpful if you include a few sentences of explanation (as you did in your other answer here). It would be great if you could still provide a bit in this direction. Thanks! –  Hendrik Vogt Mar 19 '11 at 17:12
Thanks, that helps! –  Hendrik Vogt Mar 21 '11 at 8:47