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 cycle over several sentences using a foreach loop. Now sentences will prpbably contain commas, which are used by foreach as item separators. So I would like to change the item separator to something that does not appear in my sentences, like e.g. |

So instead of

\foreach \x in {sentenceA,sentenceB,sentenceC}

I would like to use

\foreach \x in {sentenceA|sentenceB|sentenceC}

Is this possible?

share|improve this question
2  
How about putting your sentences in braces? –  Andrew Stacey Sep 19 '12 at 10:42
    
I thought about that, but that wouldn't be easy to automate, as sentences may also contain dots (like in e.g.) where I don't want a separation. Manually putting .| at the end of a sentence will be much easier (or !| or ?|) –  Tom Bombadil Sep 19 '12 at 10:53
add comment

4 Answers

up vote 7 down vote accepted

Here's an expl3 implementation:

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{xparse,pgffor}
\ExplSyntaxOn

\NewDocumentCommand{\Foreach}{ m O{,} m}
 {
  \__foreach_main:nn { #1 } { #2 } { #3 }
 }

\seq_new:N \l__foreach_arg_seq
\clist_new:N \l__foreach_braced_clist

\cs_new_protected:Npn \__foreach_main:nn #1 #2 #3
 {
  \seq_set_split:Nnn \l__foreach_arg_seq { #2 } { #3 }
  \clist_clear:N \l__foreach_braced_clist
  \seq_map_inline:Nn \l__foreach_arg_seq
   {
    \clist_put_right:Nn \l__foreach_braced_clist { { ##1 } }
   }
  \foreach #1 in~\l__foreach_braced_clist
 }
\ExplSyntaxOff

\begin{document}

\Foreach{\x}[|]{sentenceA|se,nt,en,ce,B| sentenceC }{[\x]}

\Foreach{\x}{sentenceA|se,nt,en,ce,B| sentenceC }{[\x]}

\end{document}

The syntax is a bit different than \foreach \x in {...}, but this shouldn't be a big problem. The default separator is the comma, a different one can be specified in the optional argument after the variable.

enter image description here

share|improve this answer
add comment

The comma is hardcoded in pgffor's \foreach. You could convert the list before and store it in a macro. The following example puts the entries in curly braces to protect the commas. Then initial and final spaces are also kept. In the example only an initial space is removed.

\documentclass{article}
\usepackage{pgffor}

\makeatletter
\newcommand{\tocommalist}[3]{%
  \begingroup
    \toks@{}
    \let\@tmp\relax
    \long\def\@tocommalist##1#2##2\@nil{%
      \ifx\@tmp\relax
        % \toks@{{##1}}% without initial trimming
        \toks@\expandafter{\expandafter{\romannumeral-`\x##1}}%
      \else
        % \toks@\expandafter{\the\toks@,{##1}}% without initial trimming
        \toks@\expandafter{%
          \the\expandafter\toks@\expandafter,%
          \expandafter{\romannumeral-`\x##1}%
        }%
      \fi
      \def\@tmp{##2}%
      \ifx\@tmp\@empty
        \expandafter\@gobbletwo
      \fi
      \@tocommalist##2\@nil
    }%
    \@tocommalist#3#2\@nil
  \expandafter\endgroup
  \expandafter\def\expandafter#1\expandafter{\the\toks@}%
}
\makeatother

\tocommalist\mylist{|}{sentenceA|se,nt,en,ce,B| sentenceC }
\foreach \x in \mylist {%
  \typeout{[\meaning\x]}%
}

\begin{document}
\foreach \x in \mylist {%
  [\x]\par
}
\end{document}

Result


Another example using package etoolbox. It also only trims the beginning of the entry and it removed empty entries:

\documentclass{article}

\usepackage{pgffor}
\usepackage{etoolbox}
\usepackage{etexcmds}
\DeclareListParser*{\forvertlist}{|}

\makeatletter
\newcommand{\vertocomma}[2]{%
  \let#1\@empty
  \forvertlist{\@verttocomma{#1}}{#2}%
}
\newcommand{\@verttocomma}[2]{%
  \edef#1{%
    \ifx#1\@empty
    \else
      \etex@unexpanded\expandafter{#1},%
    \fi
    {\etex@unexpanded{#2}}%
  }%
}
\makeatother

\vertocomma\mylist{sentenceA|| |se,nt,en,ce,B| sentenceC |}
\foreach \x in \mylist {%
  \typeout{[\meaning\x]}%
}

\begin{document}
\foreach \x in \mylist {%
  [\x]\par
}
\end{document}

Result

share|improve this answer
add comment

When no auto-list-completion is required, catoptions package's \indrisloop can handle any list separator. The \newforeach macro can naturally accept any type of list separator and it can do list completion, but it is not yet on CTAN. Here is a \indrisloop solution. This should work even when the list separator was active. As you will notice in the result of the example, spurious spaces between the elements of the list are removed.

\documentclass{article}
\usepackage{catoptions}

\begin{document}

\def\bombadil#1{%
  \typeout{Doing item: #1}%
  \edef\transformedlist{%
    \ifdefFT\transformedlist{}{\expandcsonce\transformedlist}%
    \unexpanded{\\{#1}}%
  }%
}

% \indrisloop[<separator>]{<list>}{<callback>}

\indrisloop[|]{%
  David Carlisle, item 1 |
  Heiko Oberdiek, item 2 |
  David Salomon, item 3 |
  Frank Mittelbach, item 4 |
  Donald Arseneau; item 5
}\bombadil

\show\transformedlist
\end{document}

{\show}
> \transformedlist=macro:

\\{David Carlisle, item 1}\\{Heiko Oberdiek, item 2}\\{David Salomon, item 3}
\\{Frank Mittelbach, item 4}\\{Donald Arseneau; item 5}.

l.21 \show\transformedlist

D.E. Knuth liked to use \\ as a list separator.

You can transform an arbitrary list into a comma-separated list using the following \makeintocommalist.

% \makeintocommalist{<original.parser>}{<orig.list.cmd>}{<transformed.list.cmd>}

\def\makeintocommalist#1#2#3{%
  \def#3{}%
  \def\bombadil##1{%
    \edef#3{%
      \expandcsonce#3{\unexpanded{##1}}%
      \iflastindris\else,\fi
    }%
  }%
  \indrisloop*[#1]{#2}\bombadil
}

Example:

\def\alist{%
  David Carlisle, item 1 |
  Heiko Oberdiek, item 2 |
  David Salomon, item 3 |
  Frank Mittelbach, item 4 |
  Donald Arseneau; item 5 |
  Enrico Gregorio/another great |
  Ulrich Diez, yes! |
  Joseph Wright, also/+
}

\makeintocommalist{|}\alist\blist
\show\blist

> \blist=macro:->

{David Carlisle, item 1},{Heiko Oberdiek, item 2},{David Salomon, item 3},
{Frank Mittelbach, item 4},{Donald Arseneau; item 5},{Enrico Gregorio/another great},
{Ulrich Diez, yes!},{Joseph Wright, also/+}.

l.53 \show\blist

EDIT

Now I remember that the catoptions package has a \cptforeach command that does exactly what you wanted. But it doesn't do auto-completion of lists. See it in action below. It is happy with arbitrary (even weird) list item separators.

% \cptforeach[<optional.sep>] <holders> \in <list> \do {<callback>}

% Note the list separators in these loops:

\tikz[shading=ball]
  \cptforeach[:] \x+\cola \in 0+red : 1+green : 2+blue : 3+yellow \do {%
    \cptforeach[|] \y;\colb \in {0;red | 1;green | 2;blue | 3;yellow} \do {%
      \shade[ball color=\cola!50!\colb] (\x,\y) circle (0.4cm);
  }%
};

enter image description here

The above is equivalent to

\tikz[shading=ball]
  \cptforeach \x/\cola \in 0/red, 1/green, 2/blue, 3/yellow \do {%
    \cptforeach \y/\colb \in {0/red, 1/green, 2/blue, 3/yellow} \do {%
      \shade[ball color=\cola!50!\colb] (\x,\y) circle (0.4cm);
  }%
};
share|improve this answer
add comment

uses parentheses { aaa,nnn!} to separate items

\documentclass{article}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}

\foreach \nn in{{0,1},{1,0},{0,-1},{-1,0}}{
\draw (0,0) -- (\nn);
}
\end{tikzpicture}

\end{document}

enter image description here

share|improve this answer
1  
I know that it can be done like this, but that's just what I wanted to avoid (see Andrew Stacey's comment on my question). –  Tom Bombadil Sep 19 '12 at 11:34
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.