pgfkeys not parsing equals (=) or comma (,) in assembled string of key/value pairs

This is a somewhat recherche question but you'll have to take my word for it that these toy examples have been extracted from something useful (to me). Everything is explained in the code below, but in brief:

I have a macro (\assignkeywords) that converts a list of key/value pairs from one format into another, namely that required by \pgfkeys. (Why I want to do precisely this is beside the point here.) The macro then passes this list to the macro provided as its second argument (\barr in the example below), which in turn passes the list to \pgfkeys. This works fine.

But if I try to pass a "partially evaluated" version of \barr to \assignkeywords, e.g. \barr keythree=c, the additional key/value pairs passed by \asignkeywords are no longer seen as a key/value pairs but (apparently) as a single string. It appears that = and , are not in this case parsed by pgfkeys in the usual way.

MWE:

\documentclass[11pt,letterpaper]{report}

\usepackage{pgffor}
\usepackage{pgfkeys}

\begin{document}

% The family of key/value pairs.  Note that each key value gets stored in
%  a macro with a matching name.
\pgfkeys{
/fake/.is family,
fake,
default/.style = {
keyone = x,
keytwo = y,
keythree = z,
},
keyone/.store in=\keyone,
keytwo/.store in=\keytwo,
keythree/.store in=\keythree,
}

% A macro for assembling a list of the form "keyone/a,keytwo/b"
%  (argument 1) into the form "keyone=a,keytwo=b" and then
%  passing it to the second argument.
\let\ea\expandafter
\newcommand{\assignkeywords}[2]{%
\def\keywordargslist{}%
\foreach \keyname/\keyvalue in {#1}{%
\ea\ea\ea\ea\ea\ea\ea\def%
\ea\ea\ea\ea\ea\ea\ea\temp%
\ea\ea\ea\ea\ea\ea\ea{%
\ea\ea\ea\keywordargslist%
\ea\keyname%
\ea=%
\keyvalue,}%
\global\let\keywordargslist\temp%
}%
% (((\keywordargslist)))
\ea#2\keywordargslist %
}

% A prototypical second argument for \assignkeywords.  When
%  passed a list like "keyone=a,keytwo=b" it passes these
%  to \pgfkeys, thereby setting these keys (keyone, keytwo)
%  to these values (a,b).
\def\barr#1 {%
\pgfkeys{fake, default, #1}%
\keyone, \keytwo, \keythree
}

\def\printkeys#1 {[[[#1]]]}

% A working example usage of \assignkeywords; produces a, b, z
\assignkeywords{keyone/a,keytwo/b}{\barr}

% A two-step working example usage of \assignkeywords; produces a, b, c
\def\foo#1 {\barr keythree=c,#1 }
\assignkeywords{keyone/a,keytwo/b}{\foo}

% A non-working example usage of \assignkeywords that seems like
%  it ought to behave just like the previous example.  Instead, pgfkeys
%  throws
%       I do not know the key '/fake/keyone=a,keytwo=b,'
%  which seems to suggest that the "=" and "," are not getting parsed
%  as such.
%%% \assignkeywords{keyone/a,keytwo/b}{\barr keythree=c,}

% If we just *print* the keys, they look right...
\assignkeywords{keyone/a,keytwo/b}{\printkeys keythree=c,}

% ...and if we print the keys in the second example, they look
% identical
\def\foo#1 {\printkeys keythree=c,#1 }
\assignkeywords{keyone/a,keytwo/b}{\foo}

\end{document}


• Please note that I don't think delimiting the argument of \barr etc by a single space is a good idea. As soon as any of your keys or values contains a comma, havoc will break loose. Better to forward it as a real argument. Jul 7, 2021 at 19:01

The issue is rather simple, your \ea#2\keywordsargslist doesn't expand the \keywordsargslist if #2 contains more than a single token. What TeX sees is:

\expandafter\barr keythree=c,\keywordsargslist


and \expandafter will hit the k of keythree=c, which doesn't expand any further. So your \barr does forward the following to \pgfkeys:

keythree=c,\keywordsargslist


\pgfkeys will then correctly split at the first comma and assign keythree, but afterwards it'll find no top-level comma, treating \keywordsargslist as a single key=value pair.

The following correctly expands \keywordsargslist no matter what the contents of #2 are. It also simplifies your macro creation using \xdef instead of a bunch of \expandafters. Last but not least, I changed the first \def\keywordsargslist{} to \gdef to play nice on the save stack (if you assign to a variable globally, always assign to it globally, gives better performance). I did not change the questionable syntax of space delimited arguments.

\documentclass[11pt,letterpaper]{report}

\usepackage{pgffor}
\usepackage{pgfkeys}

\begin{document}

% The family of key/value pairs.  Note that each key value gets stored in
%  a macro with a matching name.
\pgfkeys{
/fake/.is family,
fake,
default/.style = {
keyone = x,
keytwo = y,
keythree = z,
},
keyone/.store in=\keyone,
keytwo/.store in=\keytwo,
keythree/.store in=\keythree,
}

% A macro for assembling a list of the form "keyone/a,keytwo/b"
%  (argument 1) into the form "keyone=a,keytwo=b" and then
%  passing it to the second argument.
\newcommand{\assignkeywords}[2]{%
\gdef\keywordargslist{}%
\foreach \keyname/\keyvalue in {#1}{%
\xdef\keywordargslist
{%
\unexpanded\expandafter{\keywordargslist}%
\unexpanded\expandafter{\keyname}=%
{\unexpanded\expandafter{\keyvalue}},%
}%
}%
% (((\keywordargslist)))
\begingroup
\edef\tmp
{\endgroup\unexpanded{#2}\unexpanded\expandafter{\keywordargslist}}%
\tmp
}

% A prototypical second argument for \assignkeywords.  When
%  passed a list like "keyone=a,keytwo=b" it passes these
%  to \pgfkeys, thereby setting these keys (keyone, keytwo)
%  to these values (a,b).
\def\barr#1 {%
\pgfkeys{fake, default, #1}%
\keyone, \keytwo, \keythree
}

\def\printkeys#1 {[[[#1]]]}

% A working example usage of \assignkeywords; produces a, b, z
\assignkeywords{keyone/a,keytwo/b}{\barr}

% A two-step working example usage of \assignkeywords; produces a, b, c
\def\foo#1 {\barr keythree=c,#1 }
\assignkeywords{keyone/a,keytwo/b}{\foo}

% A non-working example usage of \assignkeywords that seems like
%  it ought to behave just like the previous example.  Instead, pgfkeys
%  throws
%       I do not know the key '/fake/keyone=a,keytwo=b,'
%  which seems to suggest that the "=" and "," are not getting parsed
%  as such.
\assignkeywords{keyone/a,keytwo/b}{\barr keythree=c,}

% If we just *print* the keys, they look right...
\assignkeywords{keyone/a,keytwo/b}{\printkeys keythree=c,}

% ...and if we print the keys in the second example, they look
% identical
\def\foo#1 {\printkeys keythree=c,#1 }
\assignkeywords{keyone/a,keytwo/b}{\foo}

\end{document}

• Of course, the failure to expand \keywordsargslist--thanks I take your point about space-delimited arguments--somewhat dangerous but it made some other things simpler. Probably not, in the long run, a very good idea. Jul 8, 2021 at 1:59
• @Quiggin especially considering it works more or less just by chance in your case, because there is no space delimiting the argument of #2 in your macro (the space after \keywordargslist is ignored by TeX). The space which really delimited your argument was the one after the argument of \assignkeywords in your usage. Jul 9, 2021 at 9:25