[Tex/LaTex] Comma-separated list

listsmacros

I would like to produce comma-separated lists, like "a, b, c, d" or "a, b, c, and d".

Requirements:

  • In the Latex source code, each line of the source code should contain one list element, and as little additional markup as possible.

  • It should be easy to re-order the entries by changing the order of source code lines.

For example, I could simply write the list elements like this:

a,
b,
c, and
d.

This would (almost) satisfy the first requirement, but it would fail on the second requirement. I would have to remember to fix the punctuation whenever I re-order the entries (or simply add a new entry). It sounds trivial, but it is surprisingly easy to forget to replace the full stop with a comma, and not that easy to spot the mistake. And I have a longish Latex document that mixes other content and such lists, so it would not be convenient to use an external script to generate the appropriate Latex code – I am really looking for something that is as easy as possible to maintain in the long term.

I do not really know what would be an appropriate interface; hence this can also be seen as an interface-design challenge. Perhaps something like this:

xxx\begin{commasep}[and]
    \item a
    \item b
    \item c
    \item d
\end{commasep}yyy

would produce:

xxxa, b, c, and dyyy

(Note: no whitespace after "d", so that I can add appropriate punctuation right after the list.)

I wonder if, e.g., paralist can be tweaked to produce what I want?

Best Answer

Here some macro which reads the lines as macro arguments. It got inspired by this question. It would be possible to change the "interface" to LaTeX environments instead, but the TeX syntax (without \begin and \end) is easier.

\documentclass{article}

\makeatletter
\newcommand*\commasep[2][, ]{%
   \begingroup
   \def\commasepsep{#1\space}%  store separator e.g. ','
   \def\commasependsep{#2\space}%  store last separator e.g. 'and'
   \@commasep
}
\newcommand*\@commasep[1][.]{%
   \def\commasepend{#1}%  store end marker e.g. '.'
   \@ifnextchar\endcommasep{}{%  check for empty list
     \catcode\endlinechar\active%  make end-of-line active
     \commasepfirstelement%  read first element
   }%
}%
\begingroup
\catcode\endlinechar\active%
% The `~` character represents the end-of-line character in the
% code below:
\lccode`\~=\endlinechar%
\lowercase{%
% Read first element
\gdef\commasepfirstelement#1~}{%
    #1%
    \@ifnextchar\endcommasep{%
      \commasepend% remove this if you don't want one with only one element
    }{%
      \commasepelement% no comma here!
    }%
}%
\lowercase{%
\endgroup%
% Read all other elements
\gdef\commasepelement#1~}{%
    \@ifnextchar\endcommasep{% Stop at `\endcommasep`
      \commasependsep#1\commasepend%
    }{%
      \commasepsep#1\commasepelement%
    }%
}%
\def\endcommasep{%
  \@gobble{endcommasep}% be unique (for `\@ifnextchar`) 
  \endgroup
}%
\makeatother

\begin{document}

% Usage: \commasep[<separator>]{<last separator>}[<end marker>]
xxx\commasep[,]{and}[.]
a
b
c
d
\endcommasep yyy

xxx\commasep{and}
a
\endcommasep yyy

xxx\commasep{and}
\endcommasep yyy

xxx\commasep{or}
a
b
\endcommasep yyy

\end{document}

Please also note that there is a parselines package which might be used. However, it doesn't support handling the last entry different. Nevertheless, its code might be a good read for people interested in this kind of parsing.