7

I'm trying to create some templating infrastructure that heavily depends on pgfkeys' styles to easily customise the appearance of how some bits of text are typeset on my document. However, I've run into some problem when trying to append more key-value pairs to a style that, as its job, already defined more codes or styles.

The following example shows the problem I'm talking about:

\documentclass{minimal}
\usepackage{pgfkeys}
\begin{document}

\pgfkeys{
  /my package/.cd,
  fancy/.style = {
    format/.code = \textbf{##1},
  },
  fancy,
  format=hello, % typesets `hello' in boldface
  fancy/.append style = { },
  fancy,
  format=hello, % nothing is typeset
%  format/.show code, % uncomment to see the definition of format
}

\end{document}

Uncommenting the last line in the \pgfkeys command shows that, after trying to append to the fancy style, the code in the format key has been replaced by:

\textbf {\pgfkeysnovalue }

and thus nothing is typeset on the second format=hello.

I (sort of) understand why this happens. The same reason why I had to write ##1 when defining the code within the style. However, is there a fix or workaround to make the style “robust” enough so that it is possible to append to it without breaking it?


Edit 1

Thanks to the comment from Joseph Wright and attempted answer from percusse, I figured out that—at the lower level—the problem to solve is the following: given an already defined macro which takes one argument (this is now codes/styles in pgfkeys are internally defined), patch the replacement text of the macro by appending some more text.

Now, from etoolbox, there is a macro for doing just this: \apptocmd. The following is an example of how a solution using this macro should probably look like:

\documentclass{minimal}
\usepackage{etoolbox}
\tracingpatches
\begin{document}
\ttfamily
\def\fancy#1\pgfeov{\pgfkeysalso{format/.code=\textbf{##1}}}
\meaning\fancy\par
\apptocmd\fancy{\pgfkeysalso{color=red}}{Ok!}{Fail.}\par
\meaning\fancy\par
\apptocmd\fancy{\pgfkeysalso{format/.code=\textit{##1}}}{Ok!}{Fail.}\par
\meaning\fancy\par
\end{document}

The only problem is that the second patching attempt doesn't work, and etoolbox shows the following as debug information:

[debug] tracing \apptocmd on input line 10
[debug] analyzing '\fancy'
[debug] ++ control sequence is defined
[debug] ++ control sequence is a macro
[debug] ++ control sequence is a macro with parameters
[debug] -- nested patching command and parameters in patch
[debug] -> the patching command seems to be nested in the
[debug]    argument to some other command
[debug] -> the patch text seems to contain # characters
[debug] -> either avoid nesting or use # characters with
[debug]    category code 12 in the patch text
[debug] -> simply doubling the # characters will not work

Would it be possible to have an \apptocmd that works even with # characters in the new replacement text?

Disclaimer: I do now have some workaround for my particular case, which only works because my fancy styles in fact do not need to take any arguments (format does, but fancy doesn't). So, instead of storing the code as a style, I store it as a regular plain value, and make .code expand to this value. Later, a simple /.append can be used to patch the “fake” style.

Still, I would very much like a solution to the original problem, so that one can also append to styles containing both arguments and (let's call them) sub-arguments. I don't know, however, if this is technically possible. So, this is my deal: If someone believes that a general solution is feasible, and lets me know this by just posting a comment, I'm willing to open a 300 bounty of my rep for a working solution to the general case. If, however, people believe that this is either just too difficult or technically impossible (and I suspect of this because etoolbox did not implement it) then I'll just be content with my sillier workaround and post a full answer expanding on it.

1
  • 2
    I'd say this looks like a bug in pgfkeys: if you try fancy/.show code you get \pgfkeysalso { format/.code = \textbf {##1}, } to begin with but \pgfkeysalso { format/.code = \textbf {#1}}\pgfkeysalso { } later, with the loss of a #.
    – Joseph Wright
    Apr 11, 2013 at 17:06

1 Answer 1

4

This looks like a bug in pgfkeys. If you do

\pgfkeys{
  /my package/.cd,
  fancy/.style = {
    format/.code = \textbf{##1},
  },
  fancy/.show code,
  fancy/.append style = { },
  fancy/.show code
}

you see

\pgfkeysalso { format/.code = \textbf {##1}, }

the first time but

\pgfkeysalso { format/.code = \textbf {#1}}\pgfkeysalso { }

the second time: note the loss of a #.

The code which underlies .append style is

\pgfkeys{/handlers/.add code/.code 2 args=%
  % Find out, whether with args or not.
  \pgfkeysifdefined{\pgfkeyscurrentpath/.@args}%
  {% Yes, so add to body and reuse args
    \pgfkeysaddvalue{\pgfkeyscurrentpath/.@body}{#1}{#2}%
    % Redefine code
    {%
      \pgfkeysgetvalue{\pgfkeyscurrentpath/.@args}{\pgfkeys@tempargs}%
      \pgfkeysgetvalue{\pgfkeyscurrentpath/.@body}{\pgfkeys@tempbody}%
      \def\pgfkeys@marshal{\expandafter\gdef\expandafter\pgfkeys@global@temp\pgfkeys@tempargs}%
      \expandafter\pgfkeys@marshal\expandafter{\pgfkeys@tempbody}%
    }%
    \pgfkeysifdefined{\pgfkeyscurrentpath/.@@body}{%
      % support for \pgfkeysndefargs:
      \pgfkeyslet{\pgfkeyscurrentpath/.@@body}{\pgfkeys@global@temp}%
    }{%
      % support for \pgfkeysdefargs:
      \pgfkeyslet{\pgfkeyscurrentpath/.@cmd}{\pgfkeys@global@temp}%
    }%
  }%
  {%
    % No, so single argument (simple \pgfkeysdef). Redefine accordingly.
    {%
      \toks0{#1}%
      \pgfkeysifdefined{\pgfkeyscurrentpath/.@cmd}%
      {\pgfkeys@temptoks\expandafter\expandafter\expandafter{\csname pgfk@\pgfkeyscurrentpath/.@cmd\endcsname##1\pgfeov}}%
      {\pgfkeys@temptoks{}}%
      \toks1{#2}%
      \xdef\pgfkeys@global@temp{\the\toks0 \the\pgfkeys@temptoks \the\toks1 }%
    }%
    \expandafter\def\expandafter\pgfkeys@temp\expandafter##\expandafter1\expandafter\pgfeov\expandafter{\pgfkeys@global@temp}%
    \pgfkeyslet{\pgfkeyscurrentpath/.@cmd}\pgfkeys@temp%
  }%
}

The issue is the line toward the end

\expandafter\def\expandafter\pgfkeys@temp\expandafter##\expandafter1\expandafter\pgfeov\expandafter{\pgfkeys@global@temp}%

As this simply expands \pgfkeys@global@temp, TeX will undouble any # it contains. Instead, an \edef plus toks should be used:

\toks0\expandafter{\pgfkeys@global@temp}%
\edef\pgfkeys@temp##1\pgfeov{\the\toks0}%
2

You must log in to answer this question.

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