Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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?

share|improve this question
add comment

4 Answers

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}

[edit]: 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}
share|improve this answer
    
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
add comment

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}
share|improve this answer
    
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
add comment

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%
}
share|improve this answer
    
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
add comment

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}
share|improve this answer
    
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
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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