4

Summary

I have a caller macro that should pass options to other nested macros within it. However, the key is not found when I pass it from the caller macro, yet it is correctly defined (I think) since it is found when I call the inner macros with it.

Setup

In my following simplification of the original setup, I have a \caller macro that receives the options, stores them into a \nodeopts macro using .store in key handler within its set of keys /caller.

\pgfkeys{
  /caller/.cd,
  opts/.store in=\nodeopts,
  opts/.default=,
}

\newcommand{\caller}[1][]{\pgfkeys{/caller/.cd, opts, #1}\node[\nodeopts]}

Then, I process these parameters in the \node macro that uses its own /node directory for its own keys.

\pgfkeys{
  /node/.cd,
  background color/.store in={\bgcolor},
  background color/.default=none,
}

\newcommand{\node}[1][]{\pgfkeys{/node/.cd, background color=yellow, #1}\nodeh{red}}

And once the keys are set, it calls the final macro that does the actual job:

\newcommand{\nodeh}[1]{\tikz\draw[fill=\bgcolor, draw=#1] (0,0) circle (10pt);}

Problem

If I call \caller[opts={background color=blue}], I get

Package pgfkeys Error: I do not know the key '/node/background color=blue' and I am going to ignore it. Perhaps you misspelled it.

Which is strange since calling \node[background color=blue] works as expected. And even if I print the content of the options inside \nodeopts (for example, \newcommand{\caller}[1][]{\pgfkeys{/caller/.cd, opts, #1}\nodeopts\node[\nodeopts]}) I can see that the options are being passed.

My limited knowledge of pgfkeys is telling me that there is an expansion shenanigan happening in the middle. I tried to find a way to debug the keys and how they are expanded but I couldn't find information about that as well.

Question

How should I declared the parameters or used them to achieve my goal of passing parameters down the macros?

Could explain the expansion mechanism and why the macro calling from the outside doesn't work?

MWE

\documentclass{article}

\usepackage{tikz}

\pgfkeys{
  /node/.cd,
  background color/.store in={\bgcolor},
  background color/.default=none,
  exec/.expand after=#1,
}

\newcommand{\node}[1][]{\pgfkeys{/node/.cd, background color=yellow, #1}\nodeh{red}}
\newcommand{\nodeh}[1]{\tikz\draw[fill=\bgcolor, draw=#1] (0,0) circle (10pt);}

\pgfkeys{
  /caller/.cd,
  opts/.store in=\nodeopts,
  opts/.default=,
}

\newcommand{\caller}[1][]{\pgfkeys{/caller/.cd, opts, #1}%
%\nodeopts% uncomment to see the parameters
\node[\nodeopts]% error with macro expansion
%\node[exec=\nodeopts]% attempt to expand also results in '/node/exec' key not found 
}


\begin{document}
  \node
  \node[background color=blue]
  
  \caller
  % this call gives an error:
  % Package pgfkeys Error: I do not know the key '/node/background color=blue' and I am going to ignore it. Perhaps you misspelled it.
  \caller[opts={background color=blue}]
\end{document}
11
  • Sounds to me, you better should use the key mechanism from expl3. Tikz is based on TeX, with some surprises here and there. While in expl3 you‘d state „I want a value returned here“, which seems to give more predictable results.
    – MS-SPO
    Commented Sep 20 at 7:53
  • 3
    \nodeopts needs to be expanded once to be properly parsed by PGFKeys. Either do \expandafter\node\expandafter[\nodeopts] or /tikz/style/.expand once={#1} (or make your own key that expands #1 once or use any other tool for expansion). Commented Sep 20 at 7:53
  • 3
    By the way, .default doesn't set the initial value for \bgcolor but the default parameter to the key if the key is used without a parameter (i.e. without the =). Commented Sep 20 at 7:56
  • @Qrrbrbirlbel I tried something like that with a key like /node/exec/.expand once=#1 but I got the same error that the key didn't exist. But in that case, I wanted to expand it inside of \node (like \newcommand{\caller}[1][]{\pgfkeys{/caller/.cd, opts, #1}\node[exec=\nodeopts]}). I want to avoid the \expandafter dance since my final setup is more involved and it broke my head to get the proper expansion. How would you go with the .expand once with my own key? Or should I have another layer to just expand it?
    – adn
    Commented Sep 20 at 7:59
  • 2
    \node[exec=\nodeopts] exec isn't in the search path once you do \node. in general, I'd avoid naming paths with standard tikz/pgf names as it will make things horrible to debug. everything pgf/tikz does is contained, local. it goes to enormous trouble not to contaminate the global environment. \pgfkeys{...} may change directory, but after that closing } you are back to normal. so you can say, eg. exec/.expand once=\nodeopts or whatever, but you have to think about the grouping and search paths, too.
    – cfr
    Commented Sep 20 at 8:10

2 Answers 2

5

This answer uses pgfkeys to solve your issue. It basically only contains two very small changes from the code you already got. The first is to change the exec-key to a .style, the second is to use exec/.expand once to forward \nodeopts.

\documentclass{article}

\usepackage{tikz}

\pgfkeys{
  /node/.cd,
  background color/.store in={\bgcolor},
  background color/.default=none,
  exec/.style={#1}
}

\newcommand{\node}[1][]{\pgfkeys{/node/.cd, background color=yellow, #1}\nodeh{red}}
\newcommand{\nodeh}[1]{\tikz\draw[fill=\bgcolor, draw=#1] (0,0) circle (10pt);}

\pgfkeys{
  /caller/.cd,
  opts/.store in=\nodeopts,
  opts/.default=,
}

\newcommand{\caller}[1][]{\pgfkeys{/caller/.cd, opts, #1}%
%\nodeopts% uncomment to see the parameters
\node[exec/.expand once=\nodeopts]%
}


\begin{document}
  \node
  \node[background color=blue]
  
  \caller
  \caller[opts={background color=blue}]
\end{document}
0
4

The following doesn't solve your issue with pgfkeys but instead suggests an implementation using expkv-def to set up your macros and only uses pgfkeys in the TikZ parts.

expkv (and therefore also expkv-def) allows you to specify additional key=value list elements to be picked up from a macro without you needing to go to great lengths with \expandafter/\expanded+\unexpanded and whatnot. Simply use R: \<macro> in your key=value argument to place the contents of \<macro> there.

\documentclass{article}

\usepackage{expkv-def}
\usepackage{tikz}

\ekvdefinekeys{node}
  {
    store background color = \bgcolor
  }
\ekvcompile\nodeDefaults{node}{background color=yellow}
\newcommand\node[1][]{\nodeDefaults\ekvset{node}{#1}\nodeh{red}}
\newcommand\nodeh[1]{\tikz\draw[fill=\bgcolor,draw=#1](0,0)circle[radius=10pt];}

\ekvdefinekeys{caller}
  {
    store opts = \nodeopts
  }
\ekvcompile\callerDefaults{caller}{opts={}}
\newcommand\caller[1][]{\callerDefaults\ekvset{caller}{#1}\node[R: \nodeopts]}


\begin{document}
  \node
  \node[background color=blue]
  
  \caller
  \caller[opts={background color=blue}]
\end{document}

You must log in to answer this question.

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