8

I am looking for an example of how to get pgfkeys to split a list of options so that: (1) some of the options are processed now, and (2) the rest of the options are passed to a second macro for further processing.

It seems that key filtering should be the answer to this, but I'm not sure how to go about it.

Let's start assuming that there is some command \theirmacro{...} whose single argument takes a key-value list of options. I know that it understands options optA and optB, but it may also accept other options (also later updates of theirpackage might even define new options of which I am not aware at this point). Furthermore, theirpackage might or might not be implemented using pgfkeys.

Now I want to define some new \mymacro{...} which additionally also understands optC and optD, and passes all other options to \theirmacro.

That is, calling \mymacro{optA=1,optC=2} should: (1) do something with the value of optC, and internally also call \theirmacro{optA=1} to process the rest of the options.

6
  • 2
    If I am not mistaken, then you want to collect all options which are unknown to \mymacro and want to forward them to \theirmacro, right? In particular, you want to do the job within the body of \mymacro? In this case, you might benefit from an /.unknown key handler rather than key filtering (although key filtering might also work). Nov 7, 2012 at 19:37
  • Yes, that is exactly what I want to do! As you say probably an .unknown handler is a better alternative than filtering, but I'm still a bit lost on how to do the “option collecting” bit. I'm also surprised that pgftikz doesn't seem to already have an option implemented for this. Nov 7, 2012 at 22:52
  • Perhaps you do not really need to collect a list. You could call \theirmacro{option i=value i} whenever you encounter an unknown option. I'd say the performance overhead is neglectable compared to a bulk approach. And "option collecting" might turn out to be much more expensiv then you thought. Nov 8, 2012 at 9:55
  • ... "an option implemented for this": the key filtering does such a thing. Clearly, its documentation could be improved and perhaps some simple styles would be adequate. I could help you here, but I'd suggest to try the other option first Nov 8, 2012 at 9:57
  • The \setkeys* and \setrmkeys commands of the xkeyval package are very handy for this type of task, but you're in the world of pgfkeys. \setkeys* will collect unknown ('remaining') keys in the macro \XKV@rm, which you can use directly or pass to another method. \setrmkeys will use the content of \XKV@rm to set the keys that were not recognized in the last run of \setkeys* or \setrmkeys*.
    – Ahmed Musa
    Nov 8, 2012 at 12:35

2 Answers 2

3

Following some of the hints and comments to my question—and also after just finding another quite relevant question on passing unknown keys to other environments—I was able to build the following implementation.

\documentclass{article}
\usepackage{pgfkeys}
\usepackage{etoolbox}

% A mock-up implementation of \theirmacro on `their package`
\pgfkeys{
  /their package/.cd,
  optA/.code = {(A:#1)},
  optB/.code = {(B:#1)}
}

\newcommand\theirmacro[1]{\pgfqkeys{/their package}{#1}}

% my proposed code for unknown option collecting
\pgfkeys{
  /handlers/.collect unknowns/.style = {
    unknown options/.initial = {},
    .unknown/.code = {%
      % Edit by EM:
      \letcs\reserved{pgfk@\pgfkeyscurrentpath/unknown options}%
      \csedef{pgfk@\pgfkeyscurrentpath/unknown options}{%
        \ifx\reserved\empty\else\expandonce\reserved,\fi
        \expandonce\pgfkeyscurrentname
        \ifx\pgfkeysnovalue##1\else=\expandonce\pgfkeyscurrentvalue\fi
      }%
    }
  }
}

% the implementation of \mymacro on `my package`
\pgfkeys{
  /my package/.cd,
  optC/.code = {(C:#1)},
  optD/.code = {(D:#1)},
}

\newcommand\mymacro[1]{%
  I process options:
  \pgfqkeys{/my package}{
    .collect unknowns,%
    #1,
    unknown options/.get = \myunknowns
  }\par
  They process options:
  \expandafter\theirmacro\expandafter{\myunknowns}
}

\begin{document}

\mymacro{optA=3,optC=2,optD=4,optB=4}

\end{document}

This seems to work as I expect it. But I am no whiz on macro expansion hackery (note I had to rely on etoolbox, since otherwise I'm not sure how to achieve these things), so I would like to know if there are any further hints or comments on how to improve this implementation.

On another note I am still puzzled, and curious, on how would one implement a similar thing using pgfkey filters.

1
  • Every call of \mymacro will first emptify pgfk@\pgfkeyscurrentpath/unknown options before repopulating it. Maybe that is what you desire.
    – Ahmed Musa
    Nov 8, 2012 at 23:36
1

Here is another solution. The handler .save unknowns allows us to submit and initialize a store macro for the unknown keys.

\documentclass{article}
\usepackage{pgfkeys}
\makeatletter
\def\emexpandonce#1{\unexpanded\expandafter{#1}}

\pgfqkeys{/their package}{
  optA/.code = {(A:#1)}, optB/.code = {(B:#1)},
  optA2/.code = {(A2-#1)}, optB2/.code = {(B2-#1)}
}
\newcommand*\theirmacro{%
  \begingroup
  \edef\x{\endgroup
    \noexpand\pgfqkeys{/their package}{\emexpandonce\unknownkeys}}\x
}

\pgfqkeys{/my package}{
  optC/.code = {(C:#1)}, optD/.code = {(D:#1)},
  optC2/.code = {(C2-#1)}, optD2/.code = {(D2-#1)}
}
\newcommand*\mymacro[1]{%
  I processed options:
  \pgfqkeys{/my package}{.save unknowns=\unknownkeys,#1}\endgraf
  They processed options:
  \theirmacro
}

\pgfkeys{
  /handlers/.save unknowns/.code={
    \edef\reserved{\expandafter\@car\string#1\@nil}%
    \ifx\reserved\@backslashchar
      \def#1{}%
    \else
      \@latexerr{'\detokenize{#1}' is not escaped; maybe not a macro}\@ehd
    \fi
    \pgfkeysalso{
      \pgfkeyscurrentpath/.unknown/.code={%
        \edef#1{%
          \ifx#1\empty\else\emexpandonce#1,\fi
          \emexpandonce\pgfkeyscurrentname
          \ifx\pgfkeysnovalue##1\else=\emexpandonce\pgfkeyscurrentvalue\fi
        }%
      }
    }
  }
}
\makeatother

\begin{document}
\mymacro{optA=3,optC=2,optD=4,optB=4}
\par
\mymacro{optA2=3,optC2=2,optD2=4,optB2=4}
\end{document}

You must log in to answer this question.

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