6

I am trying to use pgfkeys to store some text for use later. The end goal is to use a key filter to process options first and then apply them to text before it is stored.

In the MWE below, if it try to store text containing \par, I get an error when using pgfkeysfiltered but not when using pgfkeys, which you can see by uncommenting the commented line. I know I am not really using a key filter in the example below, but this is fairly minimal.

\documentclass{article}

\usepackage{pgfkeys}

\newcommand{\storecontent}{}
\newcommand{\usecontent}{\storecontent}

\pgfkeys{%
    /test/temp family/.is family,
    /test/store/.code={\renewcommand{\storecontent}{#1}},
    /test/store/.belongs to family=/test/temp family,
    /pgf/key filters/active families/.install key filter
}

\begin{document}

\pgfkeys{/test/store={first \par test}}

\usecontent

\pgfkeys{/test/temp family/.activate family}
%\pgfkeysfiltered{/test/store={second \par test}}

\usecontent

\end{document}

Is there any way I can resolve this? Or is this intended?

3 Answers 3

7

Some macro used by \pgfkeysfiltered is non \long, so it doesn't accept arguments containing a \par token.

In these cases, assuming a \par makes sense (it's up to you to ensure it), you can use the "almost" synonym \endgraf. Be careful, however, that in some places LaTeX redefines \par but not \endgraf.

A different strategy might be to do

\newcommand{\notpar}{\par}

and using \notpar in the problematic place.


You can try patching the involved commands; I get no error after these incantations (to be issued after \usepackage{pgfkeys}):

\makeatletter

\begingroup
\toks0=\expandafter{\pgfkeysfiltered@@install{#1}{#2}}
\edef\x{\endgroup
  \long\def\noexpand\pgfkeysfiltered@@install##1##2{\the\toks0 }}\x

\begingroup
\toks0=\expandafter{\pgfkeys@install@filter@and@invoke{#1}}
\edef\x{\endgroup
  \long\def\noexpand\pgfkeys@install@filter@and@invoke##1{\the\toks0 }}\x

\makeatother

This makes those macro to accept arguments containing \par.


As suggested in this answer, also the following way, which is more generic, can work:

\def\longpatch#1{\expandafter\getparts\meaning#1\longpatch
  \begingroup\edef#1{\long\def\noexpand#1\the\toks0 {\the\toks2}}%
  \scantokens\expandafter{\expandafter\endgroup#1}}
\def\getparts#1:#2->#3\longpatch{\toks0={#2}\toks2={#3}}

\makeatletter
\longpatch\pgfkeysfiltered@@install
\longpatch\pgfkeys@install@filter@and@invoke
\makeatother

How do you find the macros to patch? Well, the error in the original code is

! Paragraph ended before \pgfkeysfiltered@@install was complete.

So you apply

\makeatletter
\longpatch\pgfkeysfiltered@@install
\makeatother

(after adding the generic code above). Now you get a similar error

! Paragraph ended before \pgfkeys@install@filter@and@invoke was complete.

so you know you have to add

\longpatch\pgfkeys@install@filter@and@invoke

These must go between \makeatother and \makeatletter, because the macro have @ in their name.

7
  • great that was very helpful. i figured something was missing long, but had trouble getting it work work until reading this. my original problem was with pgfkeys@install@filter@and@invoke@, which is in itself a hard command to find in this enormous package. Commented Feb 1, 2013 at 0:04
  • Can you see why \def is such a disaster? There is a good reason why some commands shouldn't be long. Also, it is not advisable for a user to patch an internal command of a package, except in his/her document or in a local scope.
    – Ahmed Musa
    Commented Feb 1, 2013 at 0:12
  • @AhmedMusa This is one of the reasons why (non user level) LaTeX3 functions are \long by default and one has to explicitly specify _nopar if a function must be non \long.
    – egreg
    Commented Feb 1, 2013 at 0:16
  • @AhmedMusa Re your edit: what makes you think that \toks0 and \toks2 are not set locally?
    – egreg
    Commented Feb 1, 2013 at 0:36
  • @egreg: Please see below.
    – Ahmed Musa
    Commented Feb 1, 2013 at 1:47
3

TeX has an old feature that macros can be either defined short/*normal* or long. Only long macros may include paragraph breaks, either explicit ones (\par) or implicit ones (two sequential line breaks). This was implemented to cause an error quickly if a macro argument was not closed properly, because at this time (1970ties etc.) computing time was expensive. Nowadays it has lost its importance and LaTeX's \newcommand defines macros as long by default (the starred version doesn't).

Apparently some internal macro in \pgfkeyfiltered was not defined as long, so a TeX error is thrown if the content contains \par. This is most likely a bug and should be reported to the author of the code. You can workaround this issue by hiding the \par from TeX e.g. by using \csname par\endcsname or a macro which contains \par like \newcommand\mypar{\par}.

2

Here is a variation on one of egreg's solution. Any command patching scheme that invokes \scantokens presumes that the command is scannable. This can't be taken for granted. Hence, command patching should ideally include a test for scannability of the command.

\documentclass{article}
\usepackage{pgfkeys}
\makeatletter
\def\changecmdprefix{\@testopt\ch@ngecmdprefix{}}
\def\ch@ngecmdprefix[#1]#2{%
  \begingroup
  \edef\x{\def\noexpand\getparts####1\detokenize{macro}:####2->####3}%
  \x\changecmdprefix{%
    \scantokens{\endgroup
      \csname @\ifx\@empty#1\@empty first\else second\fi oftwo\endcsname
      {##1}{#1}\def#2##2{##3}%
    }%
  }%
  \expandafter\getparts\meaning#2\changecmdprefix
}
\changecmdprefix[\long]\pgfkeysfiltered@@install
\changecmdprefix[\protected\long]\pgfkeys@install@filter@and@invoke
\makeatother

\begin{document}
x
\end{document}

Key filtering using pgfkeys seems a complex business. Here is an approach via skeyval package:

\documentclass{article}
\usepackage{skeyval,xcolor}
\newcommand{\storecontent}{}
\newcommand{\usecontent}{%
  \ifx\storecontent\@empty
    \@@warning{\noexpand\storecontent is empty}%
  \else
    \storecontent
  \fi
}
\def\savpar{\par}
\def\bigvskip{\par\bigskip}
\def\showfam{Family: \skvcurrentfamily\par}

\directkeys{%
  % Define keys in two families:
  .families={family1,family2},
  .define keys={
    .ord/store//\skvappendtomacro\storecontent{#1},
    .ord/remove//\skvpatchcmd\storecontent{#1}{}{}{
      \@@warning{No token '\detokenize{#1}' in command \string\storecontent}
    }
  }
}    
\begin{document}
\directkeys{%
  % Set keys in only family1:
  .family=family1,
  .set keys={
    store={\showfam first test\bigvskip},
    store={\showfam second test\bigvskip}
  },
  .exec code=\usecontent{\color{red}\savpar\hrule\bigvskip},
  % Set keys in only family2:
  .family=family2,
  .set keys={
    remove={\showfam first test\bigvskip},
    remove={\showfam second test\bigvskip},
    store={\showfam third test\bigvskip},
    store={\showfam fourth test\bigvskip}
  },
  .exec code=\usecontent
}
\end{document} 

enter image description here

Other filter handlers include

.prefix, .change prefix, .family, .change family, .families, .change families, 
.add family, .add families, .ignore family, .ignore families, .restore family,
.restore families, .paths, .change path, .change paths, .add paths, .ignore paths,
.restore paths, .ignore keys, .restore keys

You must log in to answer this question.

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