18

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?

5
  • 3
    I can't provide an answer, but if you think about tweaking paralist, have a look at the alpha version of enumitem 3.0 (announcement), which (finally) includes run-in lists.
    – lockstep
    Feb 20, 2011 at 18:27
  • @lockstep: Thanks, that is great news indeed! I am using enumitem almost all the time, and I have tried to avoid the headache of mixing enumitem + paralist. Feb 20, 2011 at 18:32
  • I could image something based on changing the catcode of the end-of-line character. This then doesn't work when the list is part of a macro argument. Would this be ok? Feb 20, 2011 at 18:43
  • @Martin: Yes, it would be ok in my application. Feb 20, 2011 at 18:48
  • 1
    Very pleased to see the Oxford comma in use: "a, b, c, and d". Feb 21, 2011 at 14:35

4 Answers 4

15

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.

7
  • Very nice. Is there a reason for not adding the spaces in the definition of \commasependsep? That would make user input a lot more intuitive, rather than having to type { and }. (And in fact, I would suggest an optional argument) [,] which would put in a comma before the 'and' if desired. (It might also be useful to let the user specify the delimeter (',' or ';' for example) for the whole list too.
    – Alan Munn
    Feb 20, 2011 at 20:11
  • @Alan Munn: Good argument. I didn't added the spaces to allow for freedom to the user. He/she might want to use {,} instead of { and } or use the funny American style {, and }. It's not a problem to add an optional argument, but this wasn't requested, so I didn't added it. Feb 20, 2011 at 20:41
  • @Alan Munn: I added now optional arguments for the separator as well as the end marker: \commasep[, ]{ and }[.]. I kept the spaces as part of these arguments because of the above mentioned reason of flexibility. Feb 20, 2011 at 20:48
  • @Martin While I see the need for flexibility, the extra spaces are very unintuitive as input. For example, would anyone want no trailing space after the delimiter? (I'm making these comments because Jukka's question was also about what a good interface would be like; I'm not complaining about your solution!)
    – Alan Munn
    Feb 20, 2011 at 21:03
  • 1
    @Alan Munn: Ok, I added the spaces (\space) now, because it is simpler to remove them if they are not wanted than to add them if they are. Feb 20, 2011 at 21:08
6

Items are delimited by ,,, and the list is ended with ... In fact, each ,, should be followed by a space or the end of a line (without %). For very long lists (a few paragraphs?), this might get slow, since we grab the whole list as an argument at every step.

(Note that I "declare" each command with \newcommand\command{} to make sure they don't exist before I define them using \def.)

The whole construction relies on a generic macro to insert the desired separators between list elements, with some configuration possible. See e.g., \commasepitem below, which writes the list in an itemize environment.

\documentclass{article}    

\makeatletter
% #1 at the beginning
% #2 between each element
% #3 between the last two
% #4 after the last
% #5 is the first item
\newcommand*\commasep@generic{}
\def\commasep@generic#1#2#3#4 #5,, {#1#5\commasep@next{#2}{#3}{#4}}

\newcommand*\commasep@next{}
\def\commasep@next#1#2#3#4,, #5..{%
  % #1 is the list separator,
  % #2 is the last list separator,
  % #3 is the end text
  % #4 the item, 
  % #5 the rest.
  \expandafter\ifx\expandafter a\detokenize{#5}a% test if #3 is empty.
  \expandafter\@firstoftwo
  \else
  \expandafter\@secondoftwo
  \fi
  {#2#4#3}%
  {#1#4\commasep@next{#1}{#2}{#3}#5..}%
}%

\newcommand*\commasep[1]{\commasep@generic{}{, }{, #1 }{\ignorespaces}}
\newcommand*\commasepitem{%
  \commasep@generic{\begin{itemize}\item}{\item}{\item[(last)]}{\end{itemize}} }
\makeatother



\begin{document}

xx\commasep{and}
    a,,
    b,,
    c,,
    d,,
    ..
yyyy

xxx\commasepitem
    ``London bridge is falling down'' is much more frightening!,,
    c,,
    ``Mary had a little lamb, little lamb, little lamb'' is a nice
    little song that my mom used to sing,,
    d,,
    ..
yyyy

\end{document}
6

With the help of enumitem it is possible.

\usepackage[inline]{enumitem}

\begin{itemize*}[label={}, itemjoin={,}, itemjoin*={, and}]
  \item one
  \item two
  \item three
\end{itemize*}

Output:

outut

1

With a flexible interface for setting the separators:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\commasep}{O{}m}
 {
  \group_begin:
  \jukka_commasep:nn { #1 } { #2 }
  \group_end:
  \ignorespaces
 }

\NewDocumentCommand{\commasepsetup}{m}
 {
  \keys_set:nn { jukka/commasep } { #1 }
 }

\cs_new_protected:Nn \jukka_commasep:nn
 {
  \keys_set:nn { jukka/commasep } { #1 }
  \seq_set_split:Nnn \l__jukka_commasep_seq { \\ } { #2 }
  \str_if_eq_x:nnT { } { \seq_item:Nn \l__jukka_commasep_seq { -1 } }
   {
    \seq_pop_right:NN \l__jukka_commasep_seq \l_tmpa_tl
   }
  \seq_use:NVVV \l__jukka_commasep_seq
   \l__jukka_commasep_two_tl
   \l__jukka_commasep_more_tl
   \l__jukka_commasep_last_tl
 }

\cs_generate_variant:Nn \seq_use:Nnnn { NVVV }

\keys_define:nn { jukka/commasep }
 {
  two  .tl_set:N = \l__jukka_commasep_two_tl,
  more .tl_set:N = \l__jukka_commasep_more_tl,
  last .tl_set:N = \l__jukka_commasep_last_tl,
  two  .initial:n = { ~and~ },
  more .initial:n = { ,~ },
  last .initial:n = { ,~and~ },
 }

\ExplSyntaxOff

\begin{document}

xxx\commasep{
 a \\
}yyy

xxx\commasep{
 a \\
 b \\
}yyy

xxx\commasep{
 a \\
 b \\
 c \\
 d \\
}yyy

\commasepsetup{
  two=+,
  more=-,
  last=-,
}

xxx\commasep{
 a \\
}yyy

xxx\commasep{
 a \\
 b \\
}yyy

xxx\commasep{
 a \\
 b \\
 c \\
 d \\
}yyy

\end{document}

enter image description here

You must log in to answer this question.

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