pgf-tikz / pgf

A Portable Graphic Format for TeX
https://pgf-tikz.github.io/
1.1k stars 105 forks source link

/handlers/.unknown handler is not called when /.search also fails #671

Closed schtandard closed 3 years ago

schtandard commented 5 years ago

The following example can be found on page 969 of the manual:

\documentclass{article}

\usepackage{pgfkeys}

\begin{document}

% define a key:
\pgfkeys{/secondary path/option/.code={Invoking /secondary path/option with ‘#1’}}

% set up a search path:
\pgfkeys{/main path/.search also={/secondary path}}

% try searching for ‘option=value’ in ’/main path’:
% -> this finds ‘/secondary path/option’!
\pgfkeys{/main path/.cd,option=value}

% negative example:
% try searching for fully qualified key /main path/option.
% This won’t be handled by .search also.
\pgfkeys{/handlers/.unknown/.code={Found unknown option \pgfkeyscurrentkeyRAW={#1}!}}%
\pgfkeys{/main path/.cd,/main path/option=value}

\end{document} 

The last \pgfkeys call should trigger /handlers/.unknown. Instead, nothing happens, there isn't even an error. Apparently, when all the elements in the /.search also path list have been checked, the key is just thrown away.

ilayn commented 5 years ago

Hi @schtandard. When you write

\pgfkeys{/main path/.cd,/main path/option=value}

you are actually creating a key with full path /main path/main path/option=value because of the first /main path/.cd. If you instead use

\pgfkeys{/main path/.cd,bla=value}

then the handler should work. This is also mixed up with the fact that you can directly set a key with a value regardless of its existence. Hence you have to invoke the right key family for the search.

hmenke commented 5 years ago

No, when \tracingall the log shows that the key /main path/option is being set. Unfortuantely I don't know how to fix it yet. The code is a real mess.

https://github.com/pgf-tikz/pgf/blob/6b796ef15b4c6a1199dcff14d1db040d8fec4877/tex/generic/pgf/utilities/pgfkeys.code.tex#L891-L949

ilayn commented 5 years ago

Ah I missed the initial /. But setting value behavior change means breaking a lot of code so I don't think you have too many options.

muzimuzhi commented 3 years ago

This problem was introduced by https://github.com/pgf-tikz/pgf/commit/96e02db761b56e7ceb2d827a93b9100cbafd1855#diff-066476085e65c7869b9a0620c8c56f1a in 2013, using the code from patch #19 on sourceforge. That patch aimed to allow unbalanced \if in key values.

The key is the following change made by that commit, in definition of \pgfkeys@searchalso@prepare@unknown@handler,

+   \toks1={\def\pgfutilnext{\pgfkeysvalueof{/handlers/.unknown/.@cmd}##1\pgfeov}\pgfutilnext}%
-   \toks1={\pgfkeysalso{/handlers/.unknown/.@cmd/.expand once=\pgfkeys@searchalso@temp@value}}%
  1. Currently, #1 of \pgfkeys@searchalso@prepare@unknown@handler is stored in \pgfkeys@searchalso@temp@value.
  2. Currently, \pgfkeysalso{/handlers/.unknown/.@cmd/.expand once=\pgfkeys@searchalso@temp@value} is used when unknown full key is encountered. But that code actually redefines /handlers/.unknown/.@cmd. This leads to the nothing-happened problem. Removing /.@cmd solves the problem.
  3. There are problems remained in \pgfkeysalso{/handlers/.unknown/.expand once=\pgfkeys@searchalso@temp@value}.
    • \pgfkeysalso parses its key-list argument and updates all current-key macros, for example \pgfkeyscurrentkeyRAW. This updating makes \pgfkeys{/main path/.cd,/main path/option=value} produce text Found unknown option /handlers/.unknown=value!, while the expected output is Found unknown option /main path/option=value!.
    • To avoid updating current-key macros, considering \toks1 is used in \noexpand\else\the\toks1 \noexpand\fi, I use some tricks to expand the trailing \fi first. See the following full example.

Full example

\documentclass{article}
\usepackage{pgfkeys}
\usepackage{xpatch}

\makeatletter
\xpatchcmd\pgfkeys@searchalso@prepare@unknown@handler
  {\toks1={\pgfkeysalso{/handlers/.unknown/.@cmd/.expand once=\pgfkeys@searchalso@temp@value}}}
  {%
    \toks1={%
      \pgfkeysgetvalue{/handlers/.unknown/.@cmd}{\pgfkeys@code}%
      % trick:
      % to allow unbalanced \if values (stored in \pgfkeys@searchalso@temp@value),
      % expand the \fi right after "\the\toks1 " first, see below
      \expandafter\expandafter\expandafter\pgfkeys@code\expandafter\pgfkeys@searchalso@temp@value\expandafter\pgfeov
    }%
  }
  {}{\fail}
\makeatother

\begin{document}
\parindent=0pt

\subsection*{Test issue \#671, \texttt{.search also} with unknown full key}
% define a key:
\pgfkeys{/secondary path/option/.code={Invoking /secondary path/option with ‘#1’}}

% set up a search path:
\pgfkeys{/main path/.search also={/secondary path}}

% try searching for ‘option=value’ in ’/main path’:
% -> this finds ‘/secondary path/option’!
\pgfkeys{/main path/.cd, option=value}

% negative example:
% try searching for fully qualified key /main path/option.
% This won’t be handled by .search also.
\pgfkeys{/handlers/.unknown/.code={Found unknown option \pgfkeyscurrentkeyRAW={#1}!}}%
\pgfkeys{/main path/.cd, /main path/option=value}

\subsection*{Test patch \#19, unbalanced \texttt{\char`\\if} values}

% example is from https://sourceforge.net/p/pgf/patches/19/
\pgfkeys{
  rules/.cd,
  define rule/.code={DEFINING RULE: \detokenize{#1}\par},
  special/.search also=/rules,
  % We get an error if the following line is uncommented.
  special/.cd,
  define rule={\ifnum\answer=42\relax}, % `mild' error: "(\end occurred when \ifx on line 12 was incomplete)"
  define rule={\fi}, % fatal
}
\end{document} 

image