When using commands that allow using key-value pairs, how can one provide an argument value that has commas in it? As an example, say I'm using the \newacronym command from the glossaries package as below:

\newacronym[firstplural = Alphas, Bravos, and Charlies (ABCs), plural = ABCs]{ABC}{ABC}{Alpha, Bravo, Charlie}

Unfortunately, the comma is used to separate key-value pairs so the Bravos, and Charlies (ABCs) portion doesn't get parsed correctly. Is there a way to overcome this?

  • 1
    You need to "hide" the commas with braces: firstplural = {Alphas, Bravos, and Charlies (ABCs)}. – Phelype Oleinik Nov 5 '19 at 21:01
  • @PhelypeOleinik Ah, so that's the way... This would be a simple answer then, although pointing to a source would also help as I found this very difficult to google. – zephyr Nov 5 '19 at 21:03
  • Now that you mention it, it's really hard to find (because the asker usually doesn't find out what happend and asks "why the error?"). The reason is the same as explained here. In short, the comma-list parser will look for each , and splits the argument. It doesn't know which comma means. "hiding" inside braces works because TeX won't grab an argument with unbalanced braces, so it will skip the two commas in Alphas, Bravos, and Charlies and use the final one as delimiter (otherwise the argument would be {Alphas,. – Phelype Oleinik Nov 5 '19 at 21:17
  • @PhelypeOleinik this questiuon has a nice clear title, probably worth posting an answer even though the actual issue must be on the site elsewhere. – David Carlisle Nov 5 '19 at 21:46
  • @DavidCarlisle You're right. I'll write something – Phelype Oleinik Nov 5 '19 at 21:48

This is a quite common problem in LaTeX because TeX doesn't provide good tools to write a sturdy parser that will understand the difference of a , which is supposed to separate two items in a list and the , which is just part of a sentence. In fact, most programming languages don't do that and rely on something else to differentiate those, like wrapping a string in "...".

TeX only has macros, and funny ways of grabbing arguments to those macros. If you define a macro with, say, \def\foo[#1]{<use #1>}, then you must use \foo with \foo[argument], and if you omit either [ or ] something bad will happen (with "bad" I mean either an error or the argument being something unexpected).

A definition: a delimited argument is any argument (#<number>) followed by some sequence of at least one token (\def\bar#1\relax{}, \def\foo[#1]{}, \def\baz#1xyz{} are examples of macros whose argument is delimited by \relax, ], and xyz, respectively).

The delimited argument of a macro will be the shortest brace-balanced list of tokens before the first occurrence of the delimiter. That is, when scanning the delimited argument of a macro, TeX will keep track of how many { and } it finds along the way, and will search for first sequence of tokens which matches the argument delimiter, as long as the number of } matches the number of {.

Furthermore, if a delimited argument starts with a { and ends with a }, the outer layer of braces is dropped. So if you have a macro, say, \def\test[#1]{}, then using as \test[hello] or \test[{hello}] will grab the same hello as argument. However, if the argument happens to have a ], then using \test[he[l]lo] will only grab he[l as argument, while using \test[{he[l]lo}] will grab he[l]lo.

Here's the paragraph from The TeXbook which explains this (take your time to read it; this paragraph in particular is dense):

How does TeX determine where an argument stops, you ask. Answer: There are two cases. A delimited parameter is followed in the <parameter text> by one or more non-parameter tokens, before reaching the end of the parameter text or the next parameter token; in this case the corresponding argument is the shortest (possibly empty) sequence of tokens with properly nested {...} groups that is followed in the input by this particular list of non-parameter tokens. (Category codes and character codes must both match, and control sequence names must be the same.) An undelimited parameter is followed immediately in the <parameter text> by a parameter token, or it occurs at the very end of the parameter text; in this case the corresponding argument is the next nonblank token, unless that token is '{', when the argument will be the entire {...} group that follows. In both cases, if the argument found in this way has the form '{<nested tokens>}', where <nested tokens> stands for any sequence of tokens that is properly nested with respect to braces, the outermost braces enclosing the argument are removed and the <nested tokens> will remain.

(<parameter text> is what's after the macro name and before a { in a definition, as in \def\macro<parameter text>{<replacement text>}, a parameter token is a catcode-6 (usually #) character)

But what does it all have to do with your problem?

Consider this very simple comma-separated list parser:

% Define
% Use

The \csvparse macro is just a user-friendly interface to do:

\csvparseloop one,{two,2},three,\quarkstop,

and the \quarkstop is there so that the code can detect the end of the list.

The \csvparseloop macro is a delimited macro (defined with \def\csvparseloop#1,{...}), whose first (and only) argument is delimited by a ,. So each time it expands, it grabs everything up to the next (brace-balanced) ,.

In the first iteration, \csvparseloop sees the , after one and processes one as argument.
In the second iteration, the , after {two is ignored because the list is {} unbalanced, so TeX grabs the entire {two,2} and drops the braces, so the argument is two,2.
In the third iteration the argument is three, and in the fourth iteration the argument is \quarkstop, so the loop ends.

In your case, the key-val parser will behave more-or less like in the above example and will understand that each key is delimited by a ,, and it can't distinguish the , which is supposed to separate the key-value pais, and the , in the middle of a sentence. That's why you need to wrap the value in braces, so that the parser doesn't get confused.

And of course, this is not David's fault :-)

See also:

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.