1

I am using PGF's \foreach on comma-separated list where the items may contain spaces; each item can be enclosed in {...} if it contains space, that works fine. Now, what happens when there is only a single item in the list? Curiously, whitespace has (for me unexpected) influence here.

An explanation, and better yet, a way to avoid this type of input fragility, would be much appreciated.

This is parsed correcrtly:

\fooList{items={{foo, foo}
    }
}

but not this one:

\fooList{items={{foo, foo}
}}

or this one:

\fooList{items={{foo, foo}}}
\documentclass{article}
\usepackage{pgffor}
\pgfkeys{
    /foo/.is family,
    /foo,
    items/.store in=\fooItems,
    items/.initial=\undefined
}
\newcommand{\fooList}[1]{
    \pgfkeys{/foo/.cd,#1}
    \begin{enumerate}
        \foreach [var=\content] in \fooItems{\item\content}
    \end{enumerate}
}
\parindent=0pt
\begin{document}
    Comma-separated list works fine for two items:
    \fooList{
        items={
            {foo, foo},
            {bar, bar}
        }
    }
    Single item is understood when written with trailing newlines
    \fooList{items={{foo, foo}
        }
    }
    It gets incorrectly split when written like this:
    \fooList{items={{foo, foo}
    }}
    And also when fully compacted:
    \fooList{items={{foo, foo}}}
\end{document}

PDF

2
  • Unrelated, but maybe better use \usepackage instead of \include in the preamble. Commented Oct 7, 2022 at 14:39
  • Oops, that was my fingers on autopilot :) Fixed.
    – eudoxos
    Commented Oct 7, 2022 at 15:08

1 Answer 1

3

I'm afraid there is nothing you can do about the bugs in pgfkeys... There are two possibilities no viable solutions if you want to stick to it:

  • always put a space before the opening or after the closing brace (\fooItems{items={{one,two}} } or \fooItems{items= {{one,two}}} -- or both)
  • put an extra brace around the value (\fooItems{items={{{one,two}}}})

EDIT: Unfortunately, this is still not correct because of a second bug in pgfkeys, it strips one set of braces too much if there are no spaces around the value (this would be fixed by both suggestions) and then it might strip a second pair of braces when forwarding the value to the underlying command defined by the .store in handler (ouch!)


But note that there are a few key=value parsers available that don't have that brace bug (and might as well provide a csv-looping macro):

  • expkv
  • kvsetkeys
  • l3keys (part of the LaTeX kernel in the expl3 language)
  • \DeclareKeys/\SetKeys (part of the LaTeX kernel)

The following demonstrates your code using expkv (Disclaimer: I'm the package author) and l3keys (in the expl3 language):

\documentclass{article}

\usepackage{expkv-def}
\ekvdefinekeys{foo}
  {
     store items = \fooItems
    ,initial items = \undefined
  }
\newcommand\fooList[1]
  {%
    \ekvset{foo}{#1}%
    \begin{enumerate}
      % no automatic detection of a variable storing the list, so we need to
      % manually expand it.
      \expandafter\ekvcsvloop\expandafter\item\expandafter{\fooItems}%
    \end{enumerate}
  }

\ExplSyntaxOn
\keys_define:nn { eudoxos / foo }
  {
     items .clist_set:N = \l__eudoxos_items_clist
    ,items .initial:n = \undefined
  }
\NewDocumentCommand \FooList { m }
  {
    \keys_set:nn { eudoxos / foo } {#1}
    \begin{enumerate}
      \clist_map_function:NN \l__eudoxos_items_clist \item
    \end{enumerate}
  }
\ExplSyntaxOff

\parindent=0pt
\begin{document}
\section{Using \texttt{expkv}}
    Comma-separated list works fine for two items:
    \fooList{
        items={
            {foo, foo},
            {bar, bar}
        }
    }
    Single item is understood when written with trailing newlines
    \fooList{items={{foo, foo}
        }
    }
    It gets incorrectly split when written like this:
    \fooList{items={{foo, foo}
    }}
    And also when fully compacted:
    \fooList{items={{foo, foo}}}
    \clearpage

\section{Using \texttt{l3keys}}
    Comma-separated list works fine for two items:
    \FooList{
        items={
            {foo, foo},
            {bar, bar}
        }
    }
    Single item is understood when written with trailing newlines
    \FooList{items={{foo, foo}
        }
    }
    It gets incorrectly split when written like this:
    \FooList{items={{foo, foo}
    }}
    And also when fully compacted:
    \FooList{items={{foo, foo}}}
\end{document}

You must log in to answer this question.

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