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 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?

share|improve this question
2  
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 '11 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. –  Jukka Suomela Feb 20 '11 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? –  Martin Scharrer Feb 20 '11 at 18:43
    
@Martin: Yes, it would be ok in my application. –  Jukka Suomela Feb 20 '11 at 18:48
    
Very pleased to see the Oxford comma in use: "a, b, c, and d". –  Andrew Stacey Feb 21 '11 at 14:35
add comment

2 Answers

up vote 14 down vote accepted

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.

share|improve this answer
    
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 '11 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. –  Martin Scharrer Feb 20 '11 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. –  Martin Scharrer Feb 20 '11 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 '11 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. –  Martin Scharrer Feb 20 '11 at 21:08
show 2 more comments

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}
share|improve this answer
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.