27

The simple answer to this question appears to be "No"; quoting from the pgf manual:

The setting of a key is always local to the current TeX group.

However, this is very annoying because it means that you can't do something like:

\foreach \mykey/\myvalue in {
  long/list,
  of/keys%
} {
  \pgfkeyssetvalue{\mykey}{\myvalue}
}

So my next best shot is:

\makeatletter
\pgfkeys{/applicant/.code 2 args={\expandafter\global\expandafter\def\csname applicant@#1\expandafter\endcsname\expandafter{#2}}}
\foreach \mykey/\myvalue in {
  long/list,
  of/keys%
} {
  \pgfkeys{/applicant={\mykey}{\myvalue}}
}
\makeatletter

but that's not very transparent since to get at the "value", I have to use the macro \csname applicant@whatever the key name was\endcsname. (Also, I'm not very au fait with pgfkeys, this being my first foray, so I should probably have used something like /.expand once or /.store in, but that's by the by.)

Is there a better way to circumvent this restriction? I'd be happy with a new macro for defining the key, so \pgfglobalkeys would be okay (though in the spirit of \pgfkeys it feels more like it ought to be \pgfkeys{key name/.global=...} but let's not quibble), but when using the key's value then it ought be be accessible via the traditional means, say pgfkeysgetvalue.

Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751
  • I think that all of the key-value solutions take the view that keys are local objects. – Joseph Wright Apr 07 '11 at 08:18
  • @Joseph: I know that, but it can be annoying (since, as I said, \foreach loops are their own little scope) so I'd like to easily circumvent that! – Andrew Stacey Apr 07 '11 at 08:24
  • I guess my point is that this is a problem with \foreach, not with the keys side of things! – Joseph Wright Apr 07 '11 at 08:26
  • @Joseph: So maybe I should have a \globalforeach which saves up the contents of the \foreach and then puts them in an \aftergroup. Hmm, interesting idea. – Andrew Stacey Apr 07 '11 at 08:33
  • I tried this once by patching the pgfkeys source code inserting \global everywhere I could find an assignment. Didn't work...so I hope you find a solution! – Matthew Leingang Apr 07 '11 at 09:56
  • The for loop given by \pgfplotsforeachungrouped from the pgfplots package might also be useful here. Its syntax is basically the same as the pgf \foreach loop, but the interior is at the same grouping level. Thus, assignments inside need not be global to affect what goes on outside. – Charles Staats Mar 08 '14 at 18:49
  • Presumably now you'd use remember .... – cfr Feb 08 '17 at 04:35

5 Answers5

22

Edit: I just got my copy of TeX by Topic from Lulu, which of course means that my work day ended early :-). I stumpled upon \globaldefs, which allows one to answer the question actually asked with a yes:

\documentclass{article}
\usepackage{tikz}
\begin{document}
\pgfkeys{/tmp/.cd, foo/.initial = a, bar/.initial = z}
\def\showstatus{%
  (level: \the\currentgrouplevel\ -- 
   globaldefs: \the\globaldefs\ --
   foo: \pgfkeysvalueof{/tmp/foo} --
   bar: \pgfkeysvalueof{/tmp/bar})}

\showstatus

{\globaldefs=1\relax
  \foreach \k/\v in {{/tmp/foo}/bb,{/tmp/bar}/yy} { %
    \pgfkeyssetvalue{\k}{\v}
  }
  \showstatus
}

\showstatus

\end{document}

Here I implicitly use that \globaldefs is 0 (or non-positive), so that the assignment \globaldefs=1 is local. If \globaldefs is already positive in the current scope, we don't need to set it; and in fact it would be wrong to do so (since its value might be local to some surrounding group; assigning it a positive value again would set it globally). Correcting this is left as an exercise.

Original answer Andrew, your comment about using \aftergroup led me to investigate. It turns out that the body of \foreach is actually performed two levels down. Assuming that what you really want is a way for \foreach to be able to set keys at the current scope (as opposed to actually setting them globally), this seems to work:

\documentclass{article}

\usepackage{tikz}
\usepackage{etoolbox}

\begin{document}

\pgfkeys{/tmp/.cd, foo/.initial = a, bar/.initial = z}

\def\showstatus{%
(\the\currentgrouplevel\ -- 
\pgfkeysvalueof{/tmp/foo} --
\pgfkeysvalueof{/tmp/bar})}

\makeatletter

\csdef{my@count}{0}
\newcommand*{\@csgincr}[1]{\csnumgdef{#1}{\csuse{#1} + 1}}

The initial status: \showstatus

\foreach \k/\v in {{/tmp/foo}/bb,{/tmp/bar}/yy} { %
  \@csgincr{my@count}%
%  \showstatus (\k, \v, \my@count)
  % Define a global macro which does the keyval-setting (locally), and
  % which then undefines itself
  \csxdef{@tmp@setkeyval@\my@count}{\noexpand\pgfkeyssetvalue{\k}{\v}%
    \noexpand\global\noexpand\csundef{@tmp@setkeyval@\my@count}}%
  % Define a global macro which when called, places the above
  % \aftergroup, and then undefines itself
  \csxdef{@tmp@export@\my@count}{\noexpand\aftergroup%
    \expandafter\noexpand\csname @tmp@setkeyval@\my@count\endcsname%
    \noexpand\global\noexpand\csundef{@tmp@export@\my@count}}%
  % Place the above \aftergroup
  \expandafter\aftergroup\csname @tmp@export@\my@count\endcsname%
%  \aftergroup\par
}

Now we have: \showstatus

{
Now we are on a level 1 group \showstatus
\foreach \k/\v in {{/tmp/foo}/ccc,{/tmp/bar}/xxx} { %
  \@csgincr{my@count}%
%  \showstatus (\k, \v, \my@count)
  \csxdef{@tmp@setkeyval@\my@count}{\noexpand\pgfkeyssetvalue{\k}{\v}%
    \noexpand\global\noexpand\csundef{@tmp@setkeyval@\my@count}}%
  \csxdef{@tmp@export@\my@count}{\noexpand\aftergroup%
    \expandafter\noexpand\csname @tmp@setkeyval@\my@count\endcsname%
    \noexpand\global\noexpand\csundef{@tmp@export@\my@count}}%
  %
  \expandafter\aftergroup\csname @tmp@export@\my@count\endcsname%
%  \aftergroup\par
}
The keys have been updated \showstatus
}

but only inside the group \showstatus

\makeatother

\end{document}
Villemoes
  • 4,131
  • 1
    I think you should get a detective badge for this! – Andrew Stacey Apr 08 '11 at 06:59
  • @Andrew: Thanks, and please see the updated answer. – Villemoes Apr 08 '11 at 19:39
  • That is fantastic. I think that both your solutions are useful, so I'm glad you left the earlier one in there. And with the added bonus that I now understand why one might want to mess with \globaldefs! (And as if that weren't enough, I'm left with the image of Lulu sending you a book. If you're confused, take a look at http://en.wikipedia.org/wiki/Lulu_%28singer%29 ) – Andrew Stacey Apr 08 '11 at 20:09
  • It is probably safer to have the \globaldefs inside the loop, so that the the looping code is not affected. Or even better make a wrapper of \pgfkeys that locally sets \globaldefs. – Caramdir Apr 08 '11 at 20:58
  • @Caramdir: That's right. Would something as simple as \def\pgfglobalkeys#1{\begingroup \ifnum\the\globaldefs>0\relax \else \globaldefs=1\fi \pgfkeys{#1}\endgroup} work? (Provided of course that none of the key-setting business messes with \globaldefs.) – Villemoes Apr 08 '11 at 21:52
  • @Andrew: Yes, I thought that the original solution might have its uses, but it's not so much a "solution" as an example of a technique which can be implemented in a situation where one wants to set keys on level X (>0), but for some reason the stuff which does the key setting (or computes which keys to set to which values) happens on level Y > X. If Y = X+1 it would be much simpler; if Y > X+2 I won't try to do this :-) Btw, since the export mechanism heavily relies on being able to define helper macros on a global level, one might have to ensure that \global is not ignored. (tbc) – Villemoes Apr 08 '11 at 22:02
  • (continued) This basically amounts to wrapping each \global assignment in a group within which we make sure \globaldefs is not negative (while also making sure that \globaldefs retains its value immediately after the assignment). – Villemoes Apr 08 '11 at 22:06
  • etextools has a \AfterGroup that might be useful. It seems that you could condense a lot of that stuff down to \AfterGroup\AfterGroup{*code*} – kahen Oct 22 '11 at 23:37
  • A little late, but: I have always wondered how to do this. Thanks! A little note: if you use the handler .list to import the \foreach facilities into pgfkeys, then in fact the key definitions are global, as you can see from perusing the code. So it looks like Till Tantau doesn't know how to do this either. – Ryan Reich Nov 11 '11 at 00:42
12

I don't think pgfkeys allows for global assignments out-of-the-box. However, you can define your own handler to define global values. The problem is that it wont work the same for all macros like \pgfkeyssetvalue keys like .code, .store in, .style, etc. You would have to look up their definitions and define global versions of these handlers, like \pgfkeysgsetvalue .gcode, .gstore in, .gstyle.

Here the implementations of .gcode and \pgfkeysgsetvalue:

\documentclass{article}
\usepackage{pgfkeys}

\makeatletter
\pgfkeysdef{/handlers/.gcode}{%
    \long\def\pgfkeys@temp ##1\pgfeov{#1}%
    \global\pgfkeyslet{\pgfkeyscurrentpath/.@cmd}{\pgfkeys@temp}%
}
\newcommand{\pgfkeysgsetvalue}[2]{%
    \pgfkeys@temptoks{#2}%
    \expandafter\xdef\csname pgfk@#1\endcsname{\the\pgfkeys@temptoks}%
}
\makeatother
\begin{document}

\pgfkeys{test/.code={\message{NOOO}}}%
\pgfkeyssetvalue{test2}{\message{NOOO}}%
{%
\pgfkeys{test/.gcode={\message{WORKS}}}%
\pgfkeysgsetvalue{test2}{\message{WORKS AS WELL}}%
}
\pgfkeys{test}
\pgfkeysgetvalue{test2}{\test}\test

\end{document}
Martin Scharrer
  • 262,582
6

First, you want to use \pgfkeyslet instead of \pgfkeyssetvalue, since you want to use the key outside, where \myvalue is no longer defined, so you have to set the key to the contents of \myvalue instead of to \myvalue itself. Second, the definition of \pgfkeyslet starts with \expandafter\let. As it happens, prefixing \pgfkeyslet with \global still affects the \let after the expandafter, so that

\foreach \mykey/\myvalue in {long/list, of/keys}
  {\global\pgfkeyslet{\mykey}{\myvalue}}

does what you want. This solution is a bit dirty since it depends on the internal definition of \pgfkeyslet.

Another solution is to forget about \foreach if it doesn't fit the bill and to use another method for iterating about a comma separated list. If you are free to choose the representation of the list that you want to iterate over, the simplest approach is the one proposed by Knuth in Appendix D of The TeXbook. Define the list as

\newcommand\mylist{\li{long}{list}\li{of}{keys}}

Now you can process the list items repeatedly by \leting \li to a command that implements the loop body. In your example this would just be

\let\li\pgfkeyssetvalue

after which

\mylist

processes the key-value pairs. (Knuth proposed to use \\ instead of \li, which might be dangerous in LaTeX.)

gernot
  • 49,614
2

For my purposes, it was most convenient to leave the pgfkeys code alone and save whatever changes had been made to the keys using the following macro:

\def\savekeycode#1{%
    \edef\temp{%
        \global\let\expandafter\noexpand\csname pgfk@#1/.@cmd\endcsname %
                   \expandafter\noexpand\csname pgfk@#1/.@cmd\endcsname %
        \global\let\expandafter\noexpand\csname pgfk@#1/.@body\endcsname %
                   \expandafter\noexpand\csname pgfk@#1/.@body\endcsname %
    }\temp%
}

This only works for keys that code for commands, which is fine with me, but you can make the following variant to also handle the case where the key contains a value:

\def\savekey#1{%
    \@ifundefined{pgfk@#1}{%
        \edef\temp{%
            \global\let\expandafter\noexpand\csname pgfk@#1/.@cmd\endcsname %
                       \expandafter\noexpand\csname pgfk@#1/.@cmd\endcsname %
            \global\let\expandafter\noexpand\csname pgfk@#1/.@body\endcsname %
                       \expandafter\noexpand\csname pgfk@#1/.@body\endcsname %
        }\temp %
    }{%
        \edef\temp{%
            \global\let\expandafter\noexpand\csname pgfk@#1\endcsname %
                       \expandafter\noexpand\csname pgfk@#1\endcsname %
        }\temp %
    }%
}

Then you can install this as a handler:

\pgfkeys{/handlers/.save key/.code={\savekey{\pgfkeyscurrentpath}}}
Hood Chatham
  • 5,467
2

In the spirit of pgfkeys: the .list handler

Assuming that what you really want is a looping mechanism to set keys at the current grouping level (as opposed to actually setting them globally), I guess the .list handler is what you are looking for.

Using it to handle a key such asset key value pair/.style 2 args={#1={#2}} does the trick. You could tune the argument pattern to your taste (in any case, the separator / of the foreach statement could be confused with a key path symbol for the first argument).

Although the manual entry for the .list handler says

The list of values is processed using the foreach statement, so you can use the ... notation,

it seems this handler does not put each execution of its commands in a TEX group, unlike the foreach statement.

\documentclass{article}
\usepackage{tikz}
\begin{document}

\pgfkeys{/mykeyspath/.cd, foo/.initial = a, bar/.initial = x}
\def\showstatus{%
[group level=\the\currentgrouplevel , % 
foo=\pgfkeysvalueof{/mykeyspath/foo}, % 
bar=\pgfkeysvalueof{/mykeyspath/bar}]}
Initial status of two value storing keys: \showstatus .

The problem.
The status during the foreach loop shows that 
each execution is actually performed two grouping levels down, 
and the effect of the pgfkeys commands only lasts one iteration:%
\foreach \mykey/\myvalue in {foo/b,bar/y} { 
\pgfkeyssetvalue{/mykeyspath/\mykey}{\myvalue}\showstatus }.

Indeed, after completion of the foreach loop, 
the status is back to initial: \showstatus . 

\pgfkeys{set key value pair/.style 2 args={#1={#2}}}% preambule matter 
\pgfkeys{% 
  /mykeyspath/.cd,% sets the default path to the common part of listed keys
  /set key value pair/.list={{foo}{b},{bar}{y}}% calls a loop on this list
}
The remedy.
After using the .list handler on a suitable key, 
the status is modified as expected: \showstatus . 
\end{document}