2

How can I append to a pgfkeys-style comma-separated list? Or, alternatively, how can I define a macro to join an etoolbox list with a delimiter?

For an environment I'm designing, I want to accumulate definitions in the body into a list and output them in the environment's end-code, joined with a delimiter. My method of inserting delimiters is to have a macro that gets inserted before each item (initially it is blank, then it gets reassigned during the loop).

I've tried two methods of storing lists, one using pgfkeys and another using etoolbox. For the pgfkeys version, I have a list-joining macro that seems to work well, but I can't figure out how to append to the list. With etoolbox, I can build up a list incrementally, but my macro to join the list doesn't work. Unfortunately the list representations are not compatible (so I can't just use both methods).

\documentclass{article}

\usepackage{pgfkeys,pgffor}
\usepackage{etoolbox}

\makeatletter
\def\listdelimiter{, }  % default delimiter

% pgfkeys list
\pgfkeys{
  /list/.code={\@delim#1\edef\@delim{\@nextdelim}} % delim reassigned after first item
}
\newcommand\pgflistjoin[2][\listdelimiter]{%
  \edef\@nextdelim{#1}%
  \edef\@delim{}%
  \expandafter\@pgflistjoin\expandafter{#2}
}
\newcommand\@pgflistjoin[1]{%  helper macro for expanding the list
  \pgfkeys{/list/.list={#1}}%
}

% etoolbox list
\newcommand\listjoin[2][\listdelimiter]{%
  \edef\@nextdelim{#1}%
  \edef\@delim{}%
  \begingroup % so #1 below is for \do (?)
  \renewcommand*\do[1]{\@delim#1\edef\@delim{\@nextdelim}}%
  \endgroup%
  \dolistloop{#2}%
}
\makeatother

\begin{document}
\pgflistjoin[;]{1,{2 3},4} % bare list; works

\def\alist{1,{2 3},4}
\pgflistjoin[;]{\alist} % macro list; works

\listadd\blist{a}
\listadd\blist{b c}
\listadd\blist{d}

\listjoin[;]{\blist}  % no delimiters (whole list treated as one item?)

\makeatletter  % try manually
\def\@nextdelim{;}
\def\@delim{}
\renewcommand*\do[1]{\@delim#1\edef\@delim{\@nextdelim}}
\dolistloop{\blist}  % this works
\makeatother

\end{document}

For the etoolbox version, I suspect the problem is around the \begingroup. But without it, the #1 is the delimiter from the outer-macro.

Lastly, I'd rather not bring in other packages besides these two.

  • 1
    PGF does this by collecting into a \newtoks and then redefining it with the added items – percusse Mar 15 '18 at 10:01
  • @percusse thank you, but do you mind adding an answer that defines a macro like \pgflistadd\mylist{item}; or maybe it's built-in already? I tried storing the list in a pgfkeys key then using \pgfkeys{mylist/.append=..} to add to the list, but I couldn't get it to work. – goodmami Mar 15 '18 at 16:36
3

I will work with etoolbox: In your definition of \listjoin there are two problems: Groups can't be used to access the \do #1, for this you have to escape the # by doubling it instead. So you get

...
\renewcommand*\do[1]{\@delim##1\edef\@delim{\@nextdelim}}
...

The \begingroup-\endgroup do have an effect through: The \renewcommand*\do is local to the group, so it is no longer visible after \endgroup. This explains why you don't see any delimiters: Your definition of \do is no longer visible, so the default definition is used instead. This is \noexpand which acts like a noop.

So we do not want a group here. Instead the group can be used around the entire macro to stop other macros from seeing out changes to \do, \delim, ... .

This leads to

\newcommand\listjoin[2][\listdelimiter]{%
  \begingroup%
  \edef\@nextdelim{#1}%
  \edef\@delim{}%
  \renewcommand*\do[1]{\@delim##1\edef\@delim{\@nextdelim}}%
  \dolistloop{#2}%
  \endgroup%
}
  • That's it! I thought the group got me closer to an answer, but it was hiding the problem. I've seen the double ## escapes around but I didn't yet figure out what they're for. Thank you for explaining. – goodmami Mar 15 '18 at 0:23

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.