How to extract the value from a pgfkeys style element

Lets say I have defined some style:

\tikzset{mystyle/.style={inner xsep=2pt, outer xsep=10pt}}


Given the name of the style: How can I later get the (possibly overriden) value of one of its element, such as outer xsep?

My current attempt works like this:

\newcommand{\Xouterxsep}{%
\bgroup\tikzset{mystyle}\xdef\outerxsep{\pgfkeysvalueof{/pgf/outer xsep}}\egroup%
}


So after invoking \Xouterxsep the macro \outerxsep contains the value I am looking for.

However, I would prefer a more elegant solution, that is, use something like \pgfkeysvalueof directly. I have already tried something along:

\def\pgfkeysvaluefromstyle#1#2{\begingroup\tikzset{#1}\pgfkeysvalueof{/pgf/#2}\endgroup}


This does work in simple cases, however, not in my particular case, where I feed the value into the \titlespacing* command from the titlesec package. I guess it is some sort of expansion issue. I have tried to look up in the titlesec source what is actually happening there, but did not get very far; the titlesec code is somewhat opaque.

Complete not-working MWE:

\documentclass{article}
\usepackage{tikz}
\usepackage[explicit]{titlesec}

\tikzset{mystyle/.style={outer xsep=10pt, outer ysep=10pt}}

% this does not work: pdflatex hangs, interrupting it with Ctrl+C yields
% ! Interruption.
%  \pgfkeys@parse ...uturelet \pgfkeys@possiblerelax
%                                                   \pgfkeys@parse@main
%  l.16   \section{Section}
%
\def\pgfkeysvaluefromstyle#1#2{\begingroup\tikzset{#1}\pgfkeysvalueof{/pgf/#2}\endgroup}
\titlespacing*{\section}{\pgfkeysvaluefromstyle{mystyle}{outer ysep}}{0pt}{0pt}

% this does work:
%
% \def\Xouterxsep{\bgroup\tikzset{mystyle}\xdef\outerxsep{\pgfkeysvalueof{/pgf/outer xsep}}\egroup}
% \Xouterxsep
% \titlespacing*{\section}{\outerxsep}{0pt}{0pt}

\begin{document}
\section{Section}
\end{document}

-
Isn't there something like \pgfkeysusevalue? I'm sure I've seen something like that… –  Seamus Feb 1 '12 at 13:19
Hm... I dont think so, but would love to learn about it :-) –  Daniel Feb 1 '12 at 14:55

This turns out to be a good advertisement for handlers (unknown and otherwise) and for the systematic use of key directories. My strategy is that in order to find out what a style /mystyle/.style = {inner xsep = 2pt, outer xsep = 10pt} does, you must "intercept" its execution with a change of directory where the .unknown handler can cause the keys it tries to set instead to save their arguments.

This is actually quite a bit more robust than simply running /my style and trying to look up the values of inner xsep and outer xsep, since those keys may not (almost certainly don't) actually just store values but do some kind of fancy internal processing. (See Andrew Stacey's comment.)

Usage:

• Write \pgfkeys{/mystyle/.peek} to peek inside, and then look at /peek/mystyle/inner xsep and so on to get the values they would be assigned. If /mystyle takes an argument (just one), you can pass it like /mystyle/.peek = <arg> and the results will be saved with the correct substitutions made. However, if you really want <arg> to be braced, you have to double-brace it, because it gets read twice. I can't do anything about that, unfortunately.

• The keys in /mystyle can't contain slashes. If they do, they won't be handled properly in the /peek (well, the /@peek) directory and this won't work. Sorry, no way around it; that's how handlers work. /mystyle itself may or may not contain slashes.

• It is no longer necessary to surround a call to .peek with a group, as the /peek/mystyle directory is automatically reset on each call to the style /mystyle. It is therefore possible to maintain multiple parallel peek directories (cjorssen's idea), each of which is completely updated on each subsequent peek at the same style but is otherwise undisturbed.

• I also provide \pgfkeysvaluefromstyle{<full style key>}{style arg = <value>, key = <key>}, which prints its result in place (Daniel's idea), possibly applying an argument (which must be triple-braced, since it is read three times). Note: not expandable, because \pgfkeys is not expandable. This is of course true of \pgfkeys{/mystyle/.peek} as well, but, importantly, a subsequent call to \pgfkeysvalueof{/peek/mystyle/inner ysep} is expandable.

Here is my code, including the application to your MWE, and a bunch of other boring tests to prove it works as you would expect. Afterwards is a long explanation.

\documentclass{article}
\usepackage{filecontents}

\begin{filecontents}{pgfkeys-peek.sty}
\RequirePackage{pgfkeys,pgffor}

\pgfkeys{
/@peek/.is family, /@peek,
reset/.code = {%
\edef\peekdir{peek\pgfkeysvalueof{/@peek/style}}%
\pgfkeysifdefined{/@\peekdir/contents}{}{\pgfkeyssetvalue{/@\peekdir/contents}{}}%
\pgfkeysgetvalue{/@\peekdir/contents}{\List}%
\foreach \key in \List {%
% \global should work, since \pgfkeyslet expands to \let(etc.)
% \undefined is undefined!
\global\pgfkeyslet{/\peekdir/\key}{\undefined}
}%
\pgfkeyssetvalue{/@\peekdir/contents}{}%
},
.unknown/.code = {%
\edef\peekdir{peek\pgfkeysvalueof{/@peek/style}}%
\edef\next{\noexpand\pgfkeys{
/\peekdir/\pgfkeyscurrentname/.initial = {\unexpanded{#1}},
/@\peekdir/contents/.append = {\pgfkeyscurrentname,},
}}\next
},
style/.initial = {},
% Need double braces around #1 if you want it braced here
/handlers/.peek/.code = {%
\edef\next{\noexpand\pgfkeys{
/@peek,
style=\pgfkeyscurrentpath,
reset,
\pgfkeyscurrentpath = #1
}}\next
},
}

\pgfkeys{
/value from style/.is family, /value from style,
style arg/.initial = {\pgfkeysnovalue},
key/.initial = {},
}
% #1 = full key name of a style
% #2 = the above keys, with key = *relative* key name mandatory
\newcommand\pgfkeysvaluefromstyle[2]{%
% The group is for the /value from style keys
\begingroup
\pgfkeys{/value from style,#2}%
\pgfkeys{#1/.peek/.expand twice = \pgfkeysvalueof{/value from style/style arg}}%
\pgfkeysvalueof{/peek#1/\pgfkeysvalueof{/value from style/key}}%
\endgroup
}
\end{filecontents}

% MWE example

\usepackage{tikz}
\usepackage[explicit]{titlesec}
\usepackage{pgfkeys-peek}

\tikzset{mystyle/.style={outer xsep=10pt, outer ysep=10pt}}

\tikzset{mystyle/.peek}
\titlespacing*{\section}{\pgfkeysvalueof{/peek/tikz/mystyle/outer ysep}}{0pt}{0pt}

\begin{document}
\section{Code examples}% Following line should have no indent
\pgfkeys{/a style/.style = {key 1 = x, key 2 = 6}}
\pgfkeys{/a style/.peek}
key 1: \pgfkeysvalueof{/peek/a style/key 1}\par
key 2: \pgfkeysvalueof{/peek/a style/key 2}\par
key 3: \pgfkeysvalueof{/peek/a style/key 3}\par

\medskip
\pgfkeys{/a style/.style = {key 3 = $\pi$}}
\pgfkeys{/a style/.peek}
key 1: \pgfkeysvalueof{/peek/a style/key 1}\par
key 2: \pgfkeysvalueof{/peek/a style/key 2}\par
key 3: \pgfkeysvalueof{/peek/a style/key 3}\par

\medskip
\pgfkeys{/another style/.style = {key 2 = (#1)}}
\pgfkeys{/another style/.peek = 2}
key 1: \pgfkeysvalueof{/peek/another style/key 1}\par
key 2: \pgfkeysvalueof{/peek/another style/key 2}\par
key 3: \pgfkeysvalueof{/peek/another style/key 3}\par
(/a style)\par
key 1: \pgfkeysvalueof{/peek/a style/key 1}\par
key 2: \pgfkeysvalueof{/peek/a style/key 2}\par
key 3: \pgfkeysvalueof{/peek/a style/key 3}\par

\medskip
\pgfkeysvaluefromstyle{/another style}{style arg = 7, key = key 2}\par
\pgfkeysvaluefromstyle{/another style}{key = key 2}\par
\end{document}


This used to be much more straightforward but it is not quite as much anymore. It is probably easier to understand as the following TeX "pseudocode":

% /STYLE = \pgfkeysvalueof{\@peek/style}
\pgfkeys{/@peek/.is family, /@peek,
reset/.code = {
\foreach \key in \pgfkeysvalueof{/@peek/STYLE/contents} {
\pgfkeyslet{/peek/STYLE/\key}{\undefined}
}
\pgfkeyssetvalue{/@peek/STYLE/contents}{}
},
.unknown/.style = {
/peek/STYLE/\pgfkeyscurrentname/.initial = {#1},
/@peek/STYLE/contents/.append = {\pgfkeyscurrentname}
},
style/.initial = {},
/handlers/.peek/.style = {
/@peek, style = \pgfkeyscurrentpath, reset, \pgfkeyscurrentpath = #1
}
}


The pseudocode, if it actually worked (which it does not due to expansion issues and \pgfkeyscurrentname being redefined, not to mention /STYLE), would work as follows:

• All the keys end up in /peek/STYLE, where /STYLE is the full path of the style at which you peeked.

• There is an unknown handler /@peek/.unknown that sets up these keys when they are "run" in /@peek by the .peek handler itself.

• The mechanics of .peek are stored in the somewhat parallel directory /@peek. The reason for this is that I found that if you checked \pgfkeysvalueof{/peek/STYLE/some other key}, where some other key was not set by /STYLE, then a subsequent peek at /STYLE would not correctly process some other key because it would have been defined (via \csname...\endcsname, which is called internally in \pgfkeysvalueof) to be \relax, and not remain actually undefined. Therefore I needed to separate the .unknown handler from the place it put the keys.

• There is a key /@peek/reset that actually re-undefines all the keys previously set in /peek/STYLE (by \letting them to an undefined control sequence). It does so by consulting a list, /@peek/STYLE/contents, that is maintained by the .unknown handler upon processing each key set by /STYLE.

• The .peek handler itself will change dir to /@peek, set \STYLE, reset the keys in /peek/STYLE, and then run /STYLE itself (with whatever args you passed).

Here is how the pseudocode differs from the real code, and why:

• In /@peek/reset, of course, I can't use /STYLE, so for visual efficiency I store a large common part of the path in a macro \peekdir. I also have to check if /peek/STYLE/contents is defined, since otherwise, the macro \List which is supposed to be \let to it is instead let to \relax, which is what comes out of \csname...\endcsname when you apply it to an unknown control sequence. This screws up \foreach, since \relax is not the same as an empty list. Finally, the \pgfkeyslet has to be \global because a \foreach loop executes its body locally (surprisingly, this works because of how \pgfkeyslet expands).

• /@peek/.unknown: Same thing with /STYLE. Also, I need to fully expand the list of keys to set, because they contain \pgfkeyscurrentname not at the beginning. Every time a handler is called, \pgfkeyscurrentname is redefined, which means that it can effectively be used only in the name of the first key to be called in a .style.

• /handlers/.peek: Same thing with \pgfkeyscurrentpath (which has the same problem as \pgfkeyscurrentname).

-
Your comment about robustness should be underlined, highlighted, and set in neon lights. The power of keys is that you can do just about anything with them, so - as you say - \pgfkeys{some key=some value} might do just about anything with some value and the only way to remember it is to make a note of it before you begin. –  Loop Space Feb 2 '12 at 8:17
How many stars for the neon lights again? –  Ryan Reich Feb 2 '12 at 8:51
@RyanReich Very good answer, much more robust and tikz-way-of-thinking than mine. Thanks! –  cjorssen Feb 2 '12 at 9:05
@RyanReich: Excellent approach, works like a charm. I love pgfkeys! One thing you might want to add to your answer is the application to the MWE, that is, how to define a \pgfkeysvaluefromstyle command that hides the necessary grouping to not pollute the /peek directory. –  Daniel Feb 2 '12 at 9:39
Woah, that's great! Hats off! –  Jake Feb 2 '12 at 10:02

Edit 01 Feb 2012

One idea is to make things expandable. Here is a first attempt.

\tikzset{my style/.style = {minimum width = 2cm,inner xsep=3cm}}

\def\pgfkeysmarkkey#1{%
\expandafter\def\csname pgfkeysvaluefromstyle@ii@#1\endcsname
##1,##2#1##3=##4,##5\pgfeov{##4}}

\def\pgfkeysvaluefromstyle#1{%
\expandafter\expandafter\expandafter\pgfkeysvaluefromstyle@i
\csname pgfk@/tikz/#1/.@cmd\endcsname\pgfeov}

\def\pgfkeysvaluefromstyle@i\pgfkeysalso#1#2{%
\csname pgfkeysvaluefromstyle@ii@#2\endcsname,#1,\pgfeov}

\pgfkeysmarkkey{minimum width}
\pgfkeysvaluefromstyle{my style}{minimum width}


Some comments. When defining a style, the .style handler defines a macro \pgfk@<long path to the key>/.@cmd where <long path to the key> is /tikz/my style for a style defined by \tikzset{my style/.style = ...}.

This macro takes one argument delimited by \pgfeov. In this macro, the whole definition of the style is stored as an argument given to \pgfkeysalso. So if one defines my style with \tikzset{my style/.style = {minimum width = 2cm, inner xsep = 3cm}}, the macro \pgfk@/tikz/my style/.@cmd (called with \pgfk@/tikz/my style/.@cmd<arg>\pgfeov where <arg> is a possibly empty argument) will expand to \pgfkeysalso{minimum width = 2cm, inner xsep = 3cm}.

The idea is to get rid of \pgfkeysalso to get the key = val list of the style and the parse the key = val list to get the value we are looking for.

To make it expandable, I do not see another solution than to "mark" the key we are looking for.

So here is the working example.

\documentclass{article}
\usepackage{tikz}
\usepackage[explicit]{titlesec}

\makeatletter

\def\pgfkeysmarkkey#1{%
\expandafter\def\csname pgfkeysvaluefromstyle@ii@#1\endcsname
##1,##2#1##3=##4,##5\pgfeov{##4}}

\def\pgfkeysvaluefromstyle#1{%
\expandafter\expandafter\expandafter\pgfkeysvaluefromstyle@i
\csname pgfk@/tikz/#1/.@cmd\endcsname\pgfeov}

\def\pgfkeysvaluefromstyle@i\pgfkeysalso#1#2{%
\csname pgfkeysvaluefromstyle@ii@#2\endcsname,#1,\pgfeov}

\makeatother

\tikzset{mystyle/.style={outer xsep=10pt, outer ysep=10pt}}

\pgfkeysmarkkey{outer ysep}

\titlespacing*{\section}{\pgfkeysvaluefromstyle{mystyle}{outer ysep}}{0pt}{0pt}

\begin{document}
\section{Section}
\end{document}


Here is what I suggest (I choose to work with minimal width rather than outer xsep but I hope it is clear it is the same). It may not be the best approach but, right now, I do not have any other ideas.

The idea is to use the key handler .forward to so that any call to mystyle will trigger the key /tikz/save my style minimum width that will exec two things:

• set (locally to the group) the key /tikz/my style minimum width to the value of minimum width defined with the style mystyle.
• set (globally) the macro \mystyleminimumwidthvalue so that it expands to the value of minimum width defined with the style mystyle.

When the style is used once say, in a \draw command, the options are set locally so it is not possible to access the value saved outside of the \draw command (see last \node in the first tikzpicture in the example below). (Note that the trick with \edef\x is required because for some reason \pgfmathparse, called by the key minimum width, does not like \pgfkeysvalueof as an argument [I may be wrong here]).

\documentclass{standalone}
\usepackage{tikz}

\tikzset{%
mystyle/.style = {%
minimum width = 2cm},
mystyle/.forward to = /tikz/save my style minimum width,
save my style minimum width/.code = {%
\pgfkeyssetvalue{/tikz/my style minimum width}{%
\pgfkeysvalueof{/pgf/minimum width}}%
\xdef\mystyleminimumwidthvalue{%
\pgfkeysvalueof{/pgf/minimum width}}},
my style minimum width/.initial = \pgfkeysvalueof{/pgf/minimum width}}

\begin{document}
\begin{tikzpicture}
\node[draw,mystyle] at (0,0) {\pgfkeysvalueof{/pgf/minimum width}};
\node[draw,red,minimum width = 5cm] at (2,0)
{\pgfkeysvalueof{/pgf/minimum width}};
\begingroup
\edef\x{%
\endgroup
\noexpand\node[draw,blue,minimum width = \pgfkeysvalueof{/tikz/my style
minimum width}] at (4,0) {\pgfkeysvalueof{/tikz/my style
minimum width}};}
\x
\end{tikzpicture}
\begin{tikzpicture}[mystyle]
\begingroup
\edef\x{%
\endgroup
\noexpand\node[draw,blue,minimum width = \pgfkeysvalueof{/tikz/my style
minimum width}] at (4,0) {\pgfkeysvalueof{/tikz/my style
minimum width}};}
\x
\end{tikzpicture}
\end{document}

-
+1 for answering. However, this does not yet look that much more elegant than my current attempt :-) –  Daniel Jan 20 '12 at 21:15
@Daniel Ok... So you'd like something like \tikzset{mystyle/.forward value of minimum width \to my style minimum width} ? –  cjorssen Jan 21 '12 at 12:26
What I would like to have is more a \pgfkeysvaluefromstyle{mystyle){minimum width} that expands to whatever was given as value (the token right of the =) in the mystyle style definition. –  Daniel Jan 21 '12 at 15:14
@Daniel Something like \def\pgfkeysvaluefromstyle#1#2{\begingroup\tikzset{#1}\pgfkeysvalueof{/pgf/#2}\‌​endgroup} would do the job? –  cjorssen Jan 23 '12 at 13:02
That is what I tried in the beginning – and ended up with a never halting pdflatex with the \tikzset as the culprit, I guess it is an expansion problem (I need to pass \pgfkeysvaluefromstyle` as macro parameter). –  Daniel Jan 23 '12 at 13:32