8

Is there a simple way of handling the first and/or the last item in an etoolbox-based list in a special way? Here’s a simple example:

\documentclass{article}

\usepackage{etoolbox}

\begin{document}

\forcsvlist{\listadd\namelist}{Tom, Dick, Harry, Jack}

\renewcommand*{\do}[1]{; #1}
\dolistloop{\namelist}

\renewcommand*{\do}[1]{#1, }
\dolistloop{\namelist}

\end{document}

The output is

; Tom; Dick; Harry; Jack

Tom, Dick, Harry, Jack,

How would I go about changing the above code to create the output

Tom; Dick; Harry; Jack

Tom, Dick, Harry and Jack

The first output would be easy to achieve if one could define the first item’s formatting to be simply #1 while the rest are formatted as ; #1. The second output would be easy to achieve if one could define all items to be formatted as , #1 except the first one, which should be #1 and the last one, which should be and #1.

Note that I’m looking for a solution for etoolbox lists, not other list structures.

2 Answers 2

7

For the first problem, I'd define \do to precede the entry with \process, whose initial meaning is just to redefine itself to the desired separator:

\newcommand{\semicolonlist}[1]{%
  \def\process{\def\process{; }}%
  \def\do##1{\process##1}%
  \dolistloop{#1}}

\semicolonlist\namelist

For the second problem, one can process the list once to get the number of elements and then use this information in a second pass:

\newcommand{\commaorandlist}[1]{%
  \count255=0
  \def\do##1{\advance\count255 1 \chardef\finalitem=\count255 }%
  \dolistloop{#1}%
  \count255=0
  \def\do##1{\advance\count255 1
    \ifnum\count255=\finalitem
      \space and\space
    \else
      \ifnum\count255=1
      \else
    ,\space
      \fi
    \fi##1}
  \dolistloop{#1}}

\commaorandlist\namelist    

These commands print the list; if you need to use them in an expandable context you can do, say for \commaorandlist,

\newcommand{\xcommaorandlist}[2]{%
  \count255=0
  \def\do##1{\advance\count255 1 \chardef\finalitem=\count255 }%
  \dolistloop{#1}%
  \count255=0
  \toks0={}% 
  \def\do##1{\advance\count255 1
    \edef\next{%
      \ifnum\count255=\finalitem
    \space and\space
      \else
        \ifnum\count255=1
        \else
          ,\space
        \fi
      \fi
    }
    \toks2={##1}%

    \edef\next{\the\toks0 \next \the\toks2}%
    \toks0=\expandafter{\next}%
  }%
  \dolistloop{#1}%
  \edef#2{\the\toks0 }%
}

and then call

\xcommaorandlist\namelist\finallist
\hypersetup{pdfauthor={\finallist}}
3
  • Thanks. It seems to work perfectly. This might really be a new question, but is there an easy way to also define the result as a fully expanded macro? I’m trying to use it to set the pdfauthor in \hypersetup, which doesn’t work. Nov 7, 2011 at 20:48
  • @KarlOveHufthammer See edited answer
    – egreg
    Nov 7, 2011 at 21:06
  • Wow! That looks complicated, but works perfectly. Thanks! I’m currently reading the TeXbook and TeX by Topic, hoping to some day being able to understand that code. :) Nov 7, 2011 at 21:24
9

here a simple solution which also counts the lists and the counter is used to locate the last element inside the list:

\documentclass{article}

\usepackage{etoolbox}

\begin{document}
\newcounter{elementcount}
\forcsvlist{\stepcounter{elementcount}\listadd\namelist}{Tom, Dick, Harry, Jack}

\number\value{elementcount}:
\def\do#1{%
 \ifnumequal{\value{elementcount}}{1}{%
  #1}{\addtocounter{elementcount}{-1}#1,~}%   
}
\dolistloop{\namelist}

\end{document}
2
  • I accepted egreg’s answer, but I also like this one very much, as it’s easy to see how to modify it to handle special-casing of different items than the first or last one. Nov 7, 2011 at 20:51
  • A second counter is needed if you want to use the loop multiple times. elementcount should be copied to the second counter before the loop, and the second counter is decremented instead. Jul 7, 2020 at 5:49

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .