13

I would like to use pgfkeys in a macro but find the documentation a bit hard to get started with. The following code should illustrate the problem I am trying to solve:

\documentclass{article}

\usepackage{pgfkeys}

\newcommand{\mymacro}[2][]{
  Before the elements.
  % if the elements key is set and the list is not empty
  % extract the members and list them separated by commas...
  % else print `No elements passed.'
  #2
}

\begin{document}

\mymacro[elements={a, b, c}]{Foo.}

% The above should expand to:
%
% Before the elements. a, b, c. Foo.

\mymacro{Foo.}

% The above should expand to:
%
% Before the elements. No elements passed. Foo.

\end{document}

2 Answers 2

16

Edit: This is a modified answer based on the comments. I've adopted a bunch of suggestions that Andrew Stacey made.

The PGF key facilities are so varied and powerful that it takes a fair amount of effort to figure out how to use them to do what you want! Let me present my solution in pieces and then in full.

\newcommand{\mymacro}[2][elements/.initial = {No elements passed.}]{
  Before the elements.
  % if the elements key is set and the list is not empty
  % extract the members and list them separated by commas...
  % else print `No elements passed.'
  {\pgfkeys{/mymacro/.cd, #1, print}}
  #2
}

Here is how you should define \mymacro. You must call \pgfkeys on your optional argument, in order that the keys you want to use actually be processed. Note that it's necessary to give your keys a "directory" in the path tree of all PGF keys, and it seems like good form to make your own, which I've called /mymacro (like a UNIX directory).

The way things will work is that elements will process the value it is given and then print will output the result, or the error message if no elements were specified. I handle this by having the default argument for \mymacro simply set the value of elements directly, without any processing.

I am also careful to put the call to \pgfkeys in a group, since the action of elements doesn't initialize its value and subsequent calls of \mymacro could interfere with each other.

\pgfkeys{/mymacro/.cd,
 print/.code = {\pgfkeysvalueof{/mymacro/elements}},
 elements/.code = {%
  \pgfkeys{/mymacro/.cd,
           list/.list = {#1},
           elements/.add={\{}{\}}
  }%
 },
 list/.code = {\pgfkeys{/mymacro/elements/.append = (#1)}}
}

Now we have to define the action of these keys. As I see it, your elements key is a storage bin, and whatever is in it will get printed by print. However, it also processes the value it receives when it is called as elements = {a, b, ...}, using the .code subkey ("handler") I define for it. This thing calls the list key's .list handler (which requires the pgffor package if you are just loading pgfkeys on its own rather than through tikz), which just loops over whatever comma-separated list it is given, applying list/.code to each item. As an uncreative example, I have this just put parentheses around the items. Afterwards, you can also have elements/.code postprocess the result, for example by pre- and app-ending braces.

This trick of having a key act both as storage for a value and code to act on that value is quite useful. At the very least it allows you to write semantically appealing statements like elements = {a,b,c} without worrying whether that is really an assignment, since it can be both code and a subsequent assignment of the processed result.

It is probably possible to set things up more cleverly so that fewer keys are used, but that would be confusing in this example. Here is the full LaTeX file:

\documentclass{article}

\usepackage{pgfkeys,pgffor}

\newcommand{\mymacro}[2][elements/.initial = {No elements passed.}]{
  Before the elements.
  % if the elements key is set and the list is not empty
  % extract the members and list them separated by commas...
  % else print `No elements passed.'
  {\pgfkeys{/mymacro/.cd, #1, print}}
  #2
}

\pgfkeys{/mymacro/.cd,
 print/.code = {\pgfkeysvalueof{/mymacro/elements}},
 elements/.code = {%
  \pgfkeys{/mymacro/.cd,
           list/.list = {#1},
           elements/.add={\{}{\}}
  }%
 },
 list/.code = {\pgfkeys{/mymacro/elements/.append = (#1)}}
}

\begin{document}
\mymacro[elements = {a,b,c}]{Foo}

\mymacro{Bar}

\mymacro[elements = {1,2,3}]{Baz}

\mymacro{Quh}
\end{document}
8
  • If you didn't have the elements key adding the braces then you could shorten that a bit with elements/.initial={No elements passed} and make your macro have its own group (so that any changes to /mymacro/elements is local to the macro. Or you could have the print key with a default option of No elements passed ... as you say, the possibilities are endless! Jun 14, 2011 at 20:10
  • @Andrew: I struggled with the notion of using a default option, but I realized that it might impede generalizing the action of elements. Here is another crazy way using only one key: give \mymacro a default argument of "elements", run just \pgfkeys{/mymacro/.cd, #1}, and have elements/.code print its own value, with a suitable .default. This doesn't generalize, though, without more fiddling than it's worth not to just use several keys.
    – Ryan Reich
    Jun 14, 2011 at 20:22
  • Fair point. In the balance between clarity and number of keys I'd always go for number of keys. But one thing that keeps catching me out is that if one wants to store a value in a key then that key has to be initialised, say via an initial value. That's partly what was on my mind when I suggested an .initial syntax. Jun 14, 2011 at 20:26
  • @Andrew: A useful trick I came up with recently is to use the .unknown handler to void that requirement. Just write, say, /mymacro/.unknown/.code = {\pgfkeyssetvalue{\pgfkeyscurrentpath/\pgfkeyscurrentname}{#1}}, and then the first time you write /mymacro/some key = value, it means what you expect. You can go further and have .unknown also set up a .code for the key, so that the second time you call it something else happens. You can have that action modify some key/.code again...
    – Ryan Reich
    Jun 14, 2011 at 20:35
  • Wow, thanks a lot for the superb explanation. One question still remains though: the value of the elements key should be treated as a list and looped over, not as a string. Possible? Jun 14, 2011 at 20:37
6

The solution depends on what you want to do with elements but an idea is

\documentclass{article}

\usepackage{pgfkeys}
\def\mymacroempty{}

\pgfkeys{/mymacro/.cd,
elements/.code = \def\myelements{#1}}

\newcommand{\mymacro}[2][]{%
\pgfkeys{/mymacro/.cd,elements={}}
\pgfqkeys{/mymacro}{#1}   
\ifx\myelements\mymacroempty 
   No elements passed.
\else 
\myelements.
\fi
  #2
}

\begin{document}

\mymacro[elements={a, b, c}]{Foo.}

\mymacro{Foo.}

\end{document}

enter image description here

You must log in to answer this question.

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