pgfkeys strips braces from argument

This is most likely a dupe of pgfkeys overeager in stripping away my braces

If I pass {[[To]], [[From]]} to a macro as the value of an optional argument using pgfkeys, the outermost bracket is stripped. This doesn't happen if I pass it as the single mandatory argument.

I've been using pgfkeys for a while. It generally works well for me, and has the features I need, which may not be the case for other similar key packages. So I was wondering, assuming this is the issue as described in that other question, and not user error, can anyone suggest a workaround? Adding extra spaces to the \normalmacro argument doesn't help. They all get stripped away.

For background, this is for use with LuaTeX, to pass arrays from TeX to Lua, but Lua is not needed to illustrate the issue.

Since an extra pair of braces makes no difference to the printed output here, the difference can only be seen in the \typeout output to the terminal and console.

\documentclass[12pt]{scrartcl}
\usepackage{pgfkeys}

\newcommand\colnames{}

\pgfkeys
{
/foo/.is family, /foo,
default/.style =
{
colnames,
},
colnames/.estore in = \colnames,
colnames/.default = {},
}
\begin{document}

\NewDocumentCommand{\usingpgfkeys}{O{}}
{
\pgfkeys{/foo, default, #1}%
\typeout{USINGPGFKEYS RETURNS \colnames}\colnames
}

\usingpgfkeys[colnames={[[To]], [[From]]}]

\NewDocumentCommand{\normalmacro}{m}{\typeout{NORMALMACRO RETURNS #1}#1}

\normalmacro{{[[To]], [[From]]}}

\end{document}

• Stripping one set of braces is actually correct, in that case you want to input {{[[<stuff>]]}}. The issue is, that will be still wrong with pgfkeys... Jun 14, 2022 at 19:34
• @Skillmon I'm actually unclear why two sets of braces is required here. Can you spell it out, please? Jun 15, 2022 at 16:46
• This could be well suited for another question, but I'll try to fit it into a comment (or two): You need some way to input every possible input. Now a key=value parser has a delimiter ending a key=value pair and a delimiter separating a key from its value. This means there are two tokens with special meaning, but we still like to be able to input them as either keys or values (or parts thereof). In order to do so we have to hide them from the parser (by a set of braces), but now we got a set of braces we might not want. The only reasonable compromise between easy syntax and predictable [...] Jun 15, 2022 at 22:40
• [...] outcome is to strip one set of outer braces (in order to allow the special tokens to be used inside either key or value without unwanted braces being added to either of them), and if we always strip at most one set of outer braces (and only after spaces are stripped -- another peculiarity of key=value input) we actually allow predictable input of outer braces as well: if you need them, add one set more than you want and you're good. Unfortunately not all key=value parsers are that strict -> see my answer for a short list of the ones I know that do the right thing™. Jun 15, 2022 at 22:43
• @Skillmon I could certainly ask it as a separate question. But could you offer guidance as to how the question should be phrased, exactly? Jun 16, 2022 at 8:36

pgfkeys is pretty unpredictable in brace stripping, imho. It depends on the used key handlers (if I remember correctly, been a while since I took a thorough look).

Compare the following (which uses expkv as an example for a parser that predictably strips braces -- of course you could use another one, the ones I'm aware of are: expkv, kvsetkeys, and l3keys).

In case of your pgfkeys usage, you'd have to put four sets of braces around your value so that one survives, but if you put a space in front of the first braces, you need only three, or if you put a space after the first brace, you also need four. And if you're daring and put a space before and after the first brace you can't get the input you want.

\documentclass[12pt]{scrartcl}
\usepackage{pgfkeys}

\newcommand\colnames

\pgfkeys
{
/foo/.is family, /foo,
default/.style =
{
colnames,
},
colnames/.estore in = \colnames,
colnames/.default = {},
}

% more or less identical definition to your pgfkeys using expkv-def
\usepackage{expkv-def}
\ekvdefinekeys{foo}
{
store   colnames = \colnames
,default colnames = {}
,meta    default  = {colnames}
,default default  = {}
}
\NewDocumentCommand \usingexpkv { O{} }
{%
\ekvset{foo}{default, #1}%
\typeout{BAR RETURNS \colnames}\colnames
}
% similar macro definition using expkv-cs
\usepackage{expkv-cs}
\ekvcSplit\usingexpkvcs
{
colnames = {}
}
{\typeout{BARR RETURNS #1}#1}

\begin{document}

\NewDocumentCommand{\usingpgfkeys}{O{}}
{
\pgfkeys{/foo, default, #1}%
\typeout{FEE RETURNS \colnames}\colnames
}

\usingexpkv[colnames={{[[To]], [[From]]}}]

\usingexpkvcs{colnames={{[[To]], [[From]]}}}

\usingpgfkeys[colnames={{{{[[To]], [[From]]}}}}]

\NewDocumentCommand{\normalmacro}{m}{\typeout{BAZ REURNS #1}#1}

\normalmacro{{[[To]], [[From]]}}

\end{document}


Making pgfkeys strict in space trimming and brace stripping for keys and values may need more effort than expected, thus I guess a full fix won't appear in recent future. Perhaps it should start with writing tests.

Here's a trick you can use for keys, but currently restricted to your specific usage (/.estore in). A more powerful hence general scheme is possible I guess.

This trick requires user to add a mark command before normal value, and macro author to trim that command in key definition.

\documentclass[12pt]{scrartcl}
\usepackage{pgfkeys}

\newcommand\colnames{}

\pgfkeys
{
/foo/.is family, /foo,
colnames/.estore in = \colnames,
colnames/.default = {},
}
\begin{document}
\ttfamily

\NewDocumentCommand{\usingpgfkeys}{O{}}
{%
\pgfkeys{/foo, colnames, #1}% colnames is used to init \colnames
\detokenize\expandafter{\colnames}%
}
\newcommand\mymark{}

using pgfkeys:
% use colnames={\mymark<normal value>} to disable further brace stripping
\usingpgfkeys[colnames={\mymark{[[To]], [[From]]}}]

\NewDocumentCommand{\normalmacro}{m}{\detokenize{#1}}

normal macro:
\normalmacro{{[[To]], [[From]]}}
\end{document}