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 Answers2

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}
Marco Daniel
  • 95,681
  • 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. – Karl Ove Hufthammer Nov 07 '11 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. – TakingItCasual Jul 07 '20 at 05:49
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}}
egreg
  • 1,121,712
  • 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. – Karl Ove Hufthammer Nov 07 '11 at 20:48
  • @KarlOveHufthammer See edited answer – egreg Nov 07 '11 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. :) – Karl Ove Hufthammer Nov 07 '11 at 21:24