21

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}
0

2 Answers 2

25
+50

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).

8
  • @RyanReich That would be great to have \pgfkeysvalueof{/peek/a style/a key} so it would be possible to access the value of a key redefined in another style.
    – cjorssen
    Feb 2, 2012 at 12:27
  • Someone remind me what the cutoff number of edits is for the answer to become CW? I'm at 5 right now.
    – Ryan Reich
    Feb 2, 2012 at 21:45
  • I think I'd go for "may not (indeed, probably will not) actually". If this gets CW'd, then I'll be first in the queue for hassling the mods to un-CW it. If that's impossible, I'll set a bounty to make up for the lost rep! Feb 3, 2012 at 9:41
  • @RyanReich: Thanks, but unfortunately your \pgfkeysvaluefromstyle shows the same problems than my initial attempt if applied to the MWE (pdflatex hangs) :-(
    – Daniel
    Feb 3, 2012 at 10:24
  • @Daniel: your problem appears to be that you are trying to get a length into the second argument of \titlespacing*, and the command here does not actually expand into a length (not expandable, remember). The reason your attempt with \Xoutersep worked is that you did the non-expandable part before calling \titlespacing*, so the only thing there ended up being a macro with the number in it. The same thing will work if you use the .peek//peek/outer xsep key combination rather than \pgfkeysvaluefromstyle (BTW: \pgfkeysvalueof is expandable).
    – Ryan Reich
    Feb 3, 2012 at 17:43
6

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]).

enter image description 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}
0

You must log in to answer this question.

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