为什么在这些情况下 pgfkeys .initial 不起作用?

为什么在这些情况下 pgfkeys .initial 不起作用?

我想我终于对 有了合理的理解pgfkeys,但使用或不使用处理程序初始化密钥.initial给我带来了麻烦。我确实理解.default和之间的区别.initial,前者在没有为密钥指定值时指定一个值,而后者在其他情况下指定一个值(无论如何,这是我的理解)。 MWE 包含四个示例案例。

第一个似乎可以正常工作,只是尝试使用 初始化paintwhatpaintshade.initial给出有关未定义控制序列的错误。第二个示例似乎可以正常工作,但再次尝试使用 while 初始化units似乎.initial可以.default完美工作。在第三个示例中,我理解 不适.initial用于子密钥,我认为这就是我无法在此示例中使用它的原因。那么,我该如何初始化sunits?最后,在第四个示例中,当我zunits在 LAST 中初始化 时\pgfkeys{...}, 的初始使用\picktheunits按预期工作,但最终使用却不行。

最终,我的问题是:初始化密钥时出现这些困难的原因是什么?是我忽略了一些琐碎的事情,还是一些微妙的事情?

(如果有人好奇,这个带有单位的例子是我测试键和值的典型测试用例。)

以下是 MWE:

% !TEX TS-program = lualatexmk
% !TEX encoding = UTF-8 Unicode

\documentclass{article}
\usepackage{pgfkeys}

\begin{document}

A command about painting things.\par
%\newcommand*{\paintit}{}   % this appears to be optional
%\newcommand*{paintshadw}{} % this appears to be optional
\pgfkeys{%
  /paintthe/.is family, /paintthe,
  paintwhat/.store in=\paintit,
  paintwhat/.default=building, % used when just paintwhat is given
  paintwhat = barn,            % initial value of paintwhat; can be overwritten
  paintshade/.store in=\paintshade,
  paintshade/.default=light,   % used when just paintshade is given
  paintshade = light,          % initial value of paintshade; can be overwritten
}%

%  version with shade
\newcommand*{\paintthecolor}[2][]{%
  \begingroup % make key values local to this instance
    \pgfkeys{/paintthe,#1}
    Paint the \paintit\ \paintshade\ #2.
  \endgroup
}%

\paintthecolor{green}\par
\paintthecolor[paintwhat]{green}\par
\paintthecolor[paintwhat=house,paintshade=dark]{red}\par
\paintthecolor[paintwhat=car,paintshade=medium]{blue}\par
\paintthecolor{gray}\par

% These commands will already be defined, albeit not as they are here.
\newcommand*{\perpusederivedunits}{You'll get derived units.}
\newcommand*{\perpusebaseunits}{You'll get base units.}
\newcommand*{\perpusealternateunits}{You'll get alternate units.}

A command about selecting default units. Seems to work correctly.\par
\pgfkeys{%
  /chooseunits/.is family, /chooseunits,
  units/.store in=\chosenunits,
  units/.default=alternate,
  units=derived, % .initial doesn't work here
}%

\newcommand*{\choosetheunits}[1][]{%
  \begingroup
    \pgfkeys{/chooseunits,#1}
    \csname perpuse\chosenunits units\endcsname % this builds the command that selects the units
  \endgroup
}%
\choosetheunits\par % works correctly
\choosetheunits[units=base]\par
\choosetheunits[units]\par
\choosetheunits[units=derived]\par
\choosetheunits[units=alternate]\par
\choosetheunits[units=derived]\par
\choosetheunits[units]\par
\choosetheunits\par % works correctly

A different approach using \texttt{.is choice} handler. Can't initialize.\par
\pgfkeys{%
  /selectunits/.is family, /selectunits,% /.cd,
  sunits/.is choice,
  sunits/.initial={\perpusederivedunits}, % .initial doesn't work with .is choice
  sunits/.default=alternate,
  sunits/base/.code={\perpusebaseunits},
  sunits/derived/.code={\perpusederivedunits},
  sunits/alternate/.code={\perpusealternateunits},
}%
\newcommand*{\settheunits}[1][]{%
  \begingroup
    \pgfkeys{/selectunits,#1}
  \endgroup
}%
\settheunits\par % doesn't work since .initial doesn't apply to .is choice
\settheunits[sunits=base]\par
\settheunits[sunits]\par
\settheunits[sunits=derived]\par
\settheunits[sunits=alternate]\par
\settheunits[sunits=derived]\par
\settheunits[sunits]\par
\settheunits\par % doesn't work since .initial doesn't apply to .is choice

Another approach to the unit problem. Partially works.\par
\pgfkeys{%
  /pickunits/.is family, /pickunits,
  %zunits=derived, % throws unknown key error when placed here
  zunits/.default=alternate,
  zunits/.code={\csname perpuse#1units\endcsname},
  zunits=derived, % works partially when placed here
}%
\newcommand*{\picktheunits}[1][]{%
  \begingroup
    \pgfkeys{/pickunits,#1}
  \endgroup
}%
\picktheunits\par % works correctly
\picktheunits[zunits=base]\par
\picktheunits[zunits]\par
\picktheunits[zunits=derived]\par
\picktheunits[zunits=alternate]\par
\picktheunits[zunits=derived]\par
\picktheunits[zunits]\par
\picktheunits\par % does not work
\end{document}

答案1

密钥处理程序.initial用于存储值里面的名称空间中的键pgfkey。如果某个键除了该处理程序之外没有其他关联的处理程序.initial,则它将变为相似的到一个.store in键,它将存储新的值里面使用时使用 key。然后可以通过.get处理程序或使用检索存储的值\pgfkeysvalueof

当您还使用.code键时,将继续存储最后存储的值(无论是在添加 之前.code,还是在稍后使用 时.initial),但现在.code当使用键时(没有处理程序) 优先。因此,当您现在执行 时,\pgfkeys{/foo=abc}它不会存储abc,而是.code执行abc

任何(或大多数?)其他处理程序都在内部使用.code,因此一旦您向键添加另一个处理程序,它就会失去在正常使用中将值存储在键本身内部的功能。

但是,就您而言,您从未打算使用 的存储功能pgfkeys,而是使用诸如 之类的处理程序.store in。因此,您不会像使用其他一些 key=value 包(如或)那样使用.initialinside作为初始值。相反,使用诸如或 之类的处理程序为键设置初始值的正确方法是仅使用键,例如,使用。pgfkeysl3keysexpkv-def.choice.store in\pgfkeys{/foo=bar}

顺便说一句,您未定义的控制序列源于.store in未初始化所使用的宏,因此在\pgfkeys{/foo/.store in=\foo}\foo尚未定义后,它只会在您第一次使用该键后被定义。


为通过以下方式定义的宏定义初始值的另一种方法.store in是使用\newcommand*

\newcommand*\foo{bar}
\pgfkeys{/foo/.store in=\foo,/foo/.default=baz}

将添加一个/foo存储在里面的键\foo,具有初始值bar和默认值baz

答案2

经过更多的实验,我终于找到了一个我认为不错的解决方案,即使用\NewDocumentCommand。有两个\pfgkeys{...}块,一个使用.is choice,一个不使用。显然,一次只能使用其中一个,但两者都给出相同的结果,并且新命令的语法完全符合我的预期。这个想法是测试包含键的可选参数是否存在,或者是否存在,并执行相应的代码。我最终希望尽可能\NewDocumentCommand多地使用,所以这似乎是一个不错的解决方案。我想有人会让我知道我是否犯了任何暴行。

编辑:我添加了对键值的健全性检查。

以下是 MWE:

% !TEX TS-program = lualatexmk
% !TEX encoding = UTF-8 Unicode

\documentclass{article}
\usepackage{pgfkeys}

\begin{document}

\newcommand*{\perpusebaseunits}{You'll get base units.}
\newcommand*{\perpusederivedunits}{You'll get derived units.}
\newcommand*{\perpusealternateunits}{You'll get alternate units.}

% This block does not use .is choice
\pgfkeys{%
  /selectunits/.is family, /selectunits,%
  sunits/.initial=alternate,%
  sunits/.default=derived,%
  sunits/.code={%
    \ifcsname perpuse#1units\endcsname
      \csname perpuse#1units\endcsname
    \else
      \GenericError{}
        {\MessageBreak settheunits: Illegal key value}
        {Key 'sunits' can only be one of base, derived, or alternate.}
        {Read the documentation for help.}
    \fi
  }%
  %sunits/.code={\csname perpuse#1units\endcsname},%
}%

% This block uses .is choice
%\pgfkeys{%
%  /selectunits/.is family, /selectunits,%
%  sunits/.is choice,%
%  sunits/.initial=alternate,%
%  sunits/.default=derived,%
%  sunits/base/.code={\perpusebaseunits},%
%  sunits/derived/.code={\perpusederivedunits},%
%  sunits/alternate/.code={\perpusealternateunits},%
%}%

\NewDocumentCommand{\settheunits}{ o }{%
  \IfValueTF {#1}
    { \pgfkeys{/selectunits,#1} }
    { \csname perpuse\pgfkeysvalueof{/selectunits/sunits}units\endcsname }
}%

\settheunits\par
\settheunits[sunits]\par
\settheunits[sunits=base]\par
\settheunits[sunits=derived]\par
\settheunits[sunits=alternate]\par
\settheunits[sunits]\par
\settheunits\par
\settheunits[sunits=blubb]\par % This will halt processing with an error.

\end{document}

相关内容