回答你的主要问题

回答你的主要问题

考虑到

\pgfkeys{
  A/.style={
    key=value,
    key2=value2,
  }
}

如何从“A”和“键”中检索“值”?

经过一些实验后,下一步似乎至少对于人为的例子是有效的。

是否有意想不到的副作用?

\documentclass{standalone}
\RequirePackage{pgf}
\ExplSyntaxOn
\newcommand \PGFValueForStyleKey [2] { % #1: style, #2: key
  \group_begin:             % local changes only
  \pgfkeys{
    .unknown/.code=,        % do nothing for unknown keys
    #2/.initial,            % default value for key #2
    #1,                     % eventually execute the style #1, possibly overriding #2
    #2/.get = \l_tmpa_tl    % retrieve at least the default value
  }
  \exp_args:NNV
  \group_end:
  \use:n \l_tmpa_tl         % use the value after closing the group
}
\ExplSyntaxOff
\begin{document}
\pgfkeys{
  A/.style={
    key=value,
    key2=value2,
  }
}
\PGFValueForStyleKey{A}{key}/%
\PGFValueForStyleKey{A}{key3}/%
\PGFValueForStyleKey{B}{key}
\end{document}

输出显示“value//”

答案1

回答你的主要问题

是否有意想不到的副作用?

这完全取决于所涉及的键。如果你的键会产生一些输出,例如,如果你有类似

\pgfkeys
  {
     key/.code = Key `key' was used.
  }

您无法可靠地抑制这种情况。此外,具有(半)全局效果的键无法在您使用宏处理的任何样式中使用。并且在终端/日志中打印某些状态信息的键仍会这样做。

所以,是的,这可能会产生意想不到的副作用。这些副作用在很大程度上取决于样式中涉及的键。如果涉及的键只是将一些信息本地存储在一些宏中(例如使用处理程序创建的键等/.store in),您的解决方案就没问题。

最安全的方法是根本不执行任何键代码,而是通过style其他方式解析 key=value 列表。这就是下面的代码所做的。


pgfkeys无需执行按键即可实现

以下是完全可扩展的,并可检索<value>存储<key>pgfkeys样式中的。

您可以将其与赋值一起使用\edef,将结果存储在宏中,该值受到保护,不会进一步扩展。此外,它确实分两步进行扩展。它会进行一些错误检测,并能够抛出一些错误。

定义的宏用 调用\getpgfstylevalue{<path>}[<value>]{<key>},带有<path>样式键的完整路径(包括前导斜杠)、<value>要赋予样式的值(因为样式可以接受参数......;默认为空)以及<key>要检索的值的键。

A/.style总是在宏中具有内部结构,\pgfkeysalso{<keylist>}其定义类似于\expandafter\def\csname pgfk@<path>/.@cmd#1\pgfeov{\pgfkeysalso{<keylist>}}。我们可以利用此结构从 中提取密钥<keylist>

\documentclass[]{article}

\usepackage{expkv}
\usepackage{pgfkeys}

\pgfkeys
  {
     A/.style={key=value, key2=value2, key3=val#1val}
    ,B/.code={}
  }

\makeatletter
\ExplSyntaxOn
\cs_new_eq:NN \getpgfstylevalue@strcmp \str_if_eq:nnT
\ExplSyntaxOff
\newcommand\getpgfstylevalue[1]
  {%
    \unexpanded
    \ekvoptarg{\getpgfstylevalue@{#1}}{}%
  }
\newcommand\getpgfstylevalue@[3]
  {%
    \ifcsname pgfk@#1/.@cmd\endcsname
      \expandafter\@firstoftwo
    \else
      \ekverr{getpgfstylevalue}{Unknown style #1}%
      \expandafter\@gobble
    \fi
      {\expandafter\getpgfstylevalue@a\csname pgfk@#1/.@cmd\endcsname{#2}{#3}}%
      {}%
  }
\newcommand\getpgfstylevalue@a[3]
  {%
    \expandafter\getpgfstylevalue@b#1{#2}\pgfeov{#3}%
    \@secondoftwo
    \getpgfstylevalue@mark
    {\getpgfstylevalue@notfound{#3}{}}%
  }
\newcommand\getpgfstylevalue@b[1]
  {%
    \ifx\pgfkeysalso#1%
      \expandafter\getpgfstylevalue@c
    \else
      \ekverr{getpgfstylevalue}{Not a style.}%
      \expandafter\getpgfstylevalue@cleanup
    \fi
  }
\newcommand\getpgfstylevalue@c[2]
  {%
    \ekvparse\@gobble{\getpgfstylevalue@search{#2}}{#1}%
  }
\newcommand\getpgfstylevalue@search[3]
  {\getpgfstylevalue@strcmp{#1}{#2}{\getpgfstylevalue@output{#3}}}
\newcommand\getpgfstylevalue@notfound[2]
  {\ekverr{getpgfstylevalue}{Key #1 not found}}
\long\def\getpgfstylevalue@cleanup#1\getpgfstylevalue@mark{{}}
\newcommand*\getpgfstylevalue@mark{\getpgfstylevalue@mark}
\long\def\getpgfstylevalue@output#1#2\getpgfstylevalue@mark#3%
  {#2\getpgfstylevalue@mark{{#1}}}
\makeatother

\begin{document}
\getpgfstylevalue{/A}{key}

\edef\mytmp{\getpgfstylevalue{/A}{key2}}\texttt{\meaning\mytmp}

\getpgfstylevalue{/A}[FOO]{key3}

%\getpgfstylevalue{/A}{KEY} % <- throws a key-not-found error

%\getpgfstylevalue{/B}{key2} % <- throws a not-a-style error

%\getpgfstylevalue{/UNDEFINED}{} % <- throws an unknown-style error
\end{document}

在此处输入图片描述


除了上述内容之外,您对代码的其他评论

如果我不得不以不可扩展的方式工作,我会使用略有不同的用户界面。我不会直接输出您的值,而是将其分配给用户指定的变量。我不会使用\newcommand无法扩展的宏,而是定义受保护的宏,例如 via \NewDocumentCommand。所以我会使用类似以下内容:

\NewDocumentCommand \PGFValueForStyleKey { m m m } { % #1: style, #2: key, #3: macro
  \group_begin:             % local changes only
  \pgfkeys{
    .unknown/.code=,        % do nothing for unknown keys
    #2/.initial,            % default value for key #2
    #1,                     % eventually execute the style #1, possibly overriding #2
    #2/.get = \l_tmpa_tl    % retrieve at least the default value
  }
  \exp_args:NNNV
  \group_end:
  \tl_set:Nn #3 \l_tmpa_tl         % use the value after closing the group
}
\pgfkeys{
  A/.style={
    key=value,
    key2=value2,
  }}
\PGFValueForStyleKey{A}{key}\mymacro

expl3执行

expl3实现检测到相同的错误,并且通常表现得与上面的实现相同。但是,它不是精确地扩展两步,而是在 中返回其结果\unexpanded,因此在e- 或x- 类型扩展上下文中是安全的。

\documentclass{standalone}
\RequirePackage{pgfkeys}
\ExplSyntaxOn
\NewExpandableDocumentCommand \PGFValueForStyleKey { m O{} m }
  { \jerome_get_pgfstyle_value:nnn {#1} {#2} {#3} }
\cs_new:Npn \jerome_get_pgfstyle_value:nnn #1#2#3
  {
    \cs_if_exist:cTF { pgfk@#1/.@cmd }
      { \__jerome_get_pgfstyle_value:cnn { pgfk@#1/.@cmd } {#2} {#3} }
      { \msg_expandable_error:nnn { jerome } { no-key } {#1} }
  }
\cs_new:Npn \__jerome_get_pgfstyle_value:Nnn #1#2
  { \__jerome_get_pgfstyle_value:on { #1 {#2} \pgfeov } }
\cs_new:Npn \__jerome_get_pgfstyle_value:nn #1#2
  {
    \tl_if_head_eq_meaning:nNTF {#1} \pgfkeysalso
      { \__jerome_get_pgfstyle_value_aux:on { \use_ii:nn #1 } {#2} }
      { \msg_expandable_error:nn { jerome } { no-style } }
  }
\cs_new:Npn \__jerome_get_pgfstyle_value_aux:nn #1#2
  {
    % expand \keyval_parse:nnn now so that our macro works as expected inside
    % x/e-expansion contexts
    \use:e
      {
        \keyval_parse:nnn
          { \use_none:n }
          { \__jerome_get_pgfstyle_value_search:nnn {#2} }
          {#1}
      }
    \use_ii:nn
    \q_stop
    { \msg_expandable_error:nnn { jerome } { key-not-found } {#2} }
  }
\cs_new:Npn \__jerome_get_pgfstyle_value_search:nnn #1#2#3
  { \str_if_eq:nnT {#1} {#2} { \__jerome_get_pgfstyle_value_output:nw {#3} } }
\cs_new:Npn \__jerome_get_pgfstyle_value_output:nw #1#2 \q_stop #3
  { #2 \q_stop { \exp_not:n {#1} } }
\cs_generate_variant:Nn \__jerome_get_pgfstyle_value:Nnn { c }
\cs_generate_variant:Nn \__jerome_get_pgfstyle_value:nn { o }
\cs_generate_variant:Nn \__jerome_get_pgfstyle_value_aux:nn { o }

\msg_new:nnn { jerome } { no-key } { Key~ `#1'~ doesn't~ exist }
\msg_new:nnn { jerome } { no-style } { Key~ is~ not~ a~ pgfkeys~ style }
\msg_new:nnn { jerome } { key-not-found } { Key~ `#1'~ not~ found~ in~ style }

\pgfkeys{
  A/.style={
    key=value,
    key2=value2,
  }}
\tl_set:Nx \l_tmpa_tl { \PGFValueForStyleKey{/A}{key} }
\ExplSyntaxOff
\begin{document}
\pgfkeys{
  A/.style={
    key=value,
    key2=value2,
  }
  ,key/.code = Key `key' was used.
}
\PGFValueForStyleKey{/A}{key}/%
\edef\mytmp{\PGFValueForStyleKey{/A}{key2}}\texttt{\meaning\mytmp}

%\PGFValueForStyleKey{/A}{key3}/% <- throws an error
%\PGFValueForStyleKey{/B}{key}% <- throws an error
\end{document}

扩展代码

此扩展代码(基于expkv上述实现)还将解析嵌套样式,并对pgfkeys未提供值的样式使用默认值。仍缺少的是实现类似的行为,但可扩展地解析此代码可能会变得非常混乱,从长远来看,以可扩展的方式.search also实现 ' 解析规则可能不值得。无论如何,这是扩展代码版本:pgfkeys

\documentclass[]{article}

\usepackage{expkv}
\usepackage{pgfkeys}

\pgfkeys
  {
     A/.style={key=value, key2=value2, key3=val#1val}
    ,B/.code={}
    ,key3/.code={\nop}
    ,key2/.style={style1}
    ,style1/.style={key=VA#1UE}
    ,style1/.default={L}
  }

\makeatletter
\ExplSyntaxOn
% borrow a string comparison function
\cs_new_eq:NN \getpgfstylevalue@strcmp \str_if_eq:nnTF
\ExplSyntaxOff
\newcommand\getpgfstylevalue[1]
  {%
    % this starts an expansion context similar to expl3's f-expansion, it'll be
    % stopped by the first opening brace.
    \unexpanded
    % detokenize the key path after fully expanding it (pgfkeys would use it as
    % is inside of \csname ...\endcsname which would expand it as well, but some
    % of the following functions assume the key path being a string)
    \expandafter\getpgfstylevalue@\detokenize\expanded{{#1}}\relax
  }
% search for the optional argument containing the key value
\long\def\getpgfstylevalue@#1\relax
  {\ekvoptargTF{\getpgfstylevalue@v{#1}}{\getpgfstylevalue@nv{#1}}}
% no value was used, so we use the .default value if it is present
\long\def\getpgfstylevalue@nv#1%
  {%
    \expanded
      {\unexpanded{\getpgfstylevalue@v{#1}}\getpgfstylevalue@getdefault{#1}}%
  }
\long\def\getpgfstylevalue@v#1#2#3%
  {%
    \unless\ifcsname pgfk@#1/.@cmd\endcsname
      \ekverr{getpgfstylevalue}{Unknown key #1}%
      % only gobble two, the second set of braces will then be the argument of
      % \unexpanded
      \expandafter\@gobbletwo
    \fi
    % gobble the empty set of braces and execute the next step
    \@firstoftwo
      {%
        % build the macro name and split the path
        \expandafter\getpgfstylevalue@a
          \csname pgfk@#1/.@cmd\expandafter\endcsname
          \expanded{\getpgfstylevalue@splitpath{#1}}%
          {#2}{#3}%
      }%
      {}%
  }
\newcommand\getpgfstylevalue@a[5]
  {%
    % expand the style (or .code) macro
    \expanded
      {\unexpanded{\getpgfstylevalue@b{#2}{#3}}\expandafter}#1{#4}\pgfeov{#5}%
    % the mark will be gobbled once the parsing is done, but it necessary for
    % the output to put it here
    \@secondoftwo
    \getpgfstylevalue@mark
    % the output value starts with an error message, the output will remove this
    % error.
    {\ekverr{getpgfstylevalue}{Key `#5' not found in style #2#3}{}}%
  }
\newcommand\getpgfstylevalue@b[3]
  {%
    % we can't use \unless here, else the \ifx couldn't gobble excessive tokens
    % in #1.
    \ifx\pgfkeysalso#3%
    \else
      \ekverr{getpgfstylevalue}{#1#2 is not a style.}%
      \expandafter\getpgfstylevalue@cleanup
    \fi
    \getpgfstylevalue@c{#1}{#2}%
  }
\newcommand\getpgfstylevalue@c[4]
  {%
    \ekvparse
      {\getpgfstylevalue@key{#4}{#1}}%
      {\getpgfstylevalue@search{#4}{#1}}%
      {#3}%
  }
% first check whether the searched key was found, if not check whether this key
% is a nested style.
\newcommand\getpgfstylevalue@search[4]
  {%
    \getpgfstylevalue@strcmp{#1}{#3}
      {\getpgfstylevalue@output{#4}}%
      {\getpgfstylevalue@checknested{#1}{#2}{#3}{#4}}%
  }
% we just search in the current path, this doesn't parse for .search also or
% anything like that.
\newcommand\getpgfstylevalue@checknested[4]
  {%
    \unless\ifcsname pgfk@#2#3/.@cmd\endcsname
      % if this can't be a nested style just do nothing for this key
      \expandafter\@gobbletwo
    \fi
    \@firstofone
    {%
      \expandafter\expandafter\expandafter
      \getpgfstylevalue@checknested@a
        \csname pgfk@#2#3/.@cmd\endcsname{#4}\pgfeov
        {#1}{#2}%
      % place this mark to remove excessive code if this is not a style
      \getpgfstylevalue@mark
    }%
  }
% we need to check whether this is indeed a style
\newcommand\getpgfstylevalue@checknested@a[1]
  {%
    \ifx\pgfkeysalso#1%
    \else
      \expandafter\getpgfstylevalue@gobble
    \fi
    \getpgfstylevalue@checknested@b
  }
\long\def\getpgfstylevalue@checknested@b#1#2#3\getpgfstylevalue@mark
  {%
    % recursively search this style
    \ekvparse
      {\getpgfstylevalue@key{#2}{#3}}%
      {\getpgfstylevalue@search{#2}{#3}}%
      {#1}%
  }
\newcommand\getpgfstylevalue@key[3]
  {%
    % if it is just a key, check whether the names match or not. If they do
    % match use the default value for output, else to see whether this is a
    % nested style.
    \expanded
      {%
        \getpgfstylevalue@strcmp{#1}{#3}
          {\unexpanded{\getpgfstylevalue@output}}%
          {\unexpanded{\getpgfstylevalue@checknested{#1}{#2}{#3}}}%
        \getpgfstylevalue@getdefault{#2#3}%
      }%
  }
% clean up must leave an empty set of braces for \unexpanded
\long\def\getpgfstylevalue@cleanup#1\getpgfstylevalue@mark#2{{}}
% this is a simpler clean up that just gobbles stuff
\long\def\getpgfstylevalue@gobble#1\getpgfstylevalue@mark{}
\newcommand*\getpgfstylevalue@mark{\getpgfstylevalue@mark}
% just remove what's currently stored in the output and place the new output. We
% use a nested set of braces for the \unexpanded output in the end (remember the
% \@secondoftwo to remove the mark)
\long\def\getpgfstylevalue@output#1#2\getpgfstylevalue@mark#3%
  {#2\getpgfstylevalue@mark{{#1}}}

% expandably split a key path into directory and key name, use it inside an
% \expanded-context.
\newcommand\getpgfstylevalue@splitpath[1]
  {{\iffalse}\fi\getpgfstylevalue@splitpath@a#1/\getpgfstylevalue@mark}
\long\def\getpgfstylevalue@splitpath@a#1/#2%
  {%
    \getpgfstylevalue@gobble#2%
      \getpgfstylevalue@splitpath@b\getpgfstylevalue@mark
      #1/%
    \getpgfstylevalue@splitpath@a#2%
  }
\long\def\getpgfstylevalue@splitpath@b
    \getpgfstylevalue@mark#1/#2\getpgfstylevalue@mark
  {\iffalse{\fi}{#1}}

% get the default value of a key (or expand to an empty argument), use it inside
% an \expanded-context, like so:
%   \expandafter\stuff\expanded{\getpgfstylevalue@getdefault{<path>}}.
% this will expand to \stuff{<default>} or, if no default is there to \stuff{}
\long\def\getpgfstylevalue@getdefault#1%
  {%
    {%
      \unless\ifcsname pgfk@#1/.@def\endcsname
        % if no default is defined remove the output below
        \expandafter\@gobbletwo
      \fi
      \@firstofone
        {%
          % output the contents of the default value
          \unexpanded\expandafter\expandafter\expandafter
            {\csname pgfk@#1/.@def\endcsname}%
        }%
    }%
  }
\makeatother

\begin{document}
Test:
\getpgfstylevalue{/A}{key}

\edef\mytmp{\getpgfstylevalue{/A}{key2}}\texttt{\meaning\mytmp}

\getpgfstylevalue{/A}[FOO]{key3}

%\getpgfstylevalue{/A}{KEY} % <- throws a key-not-found error

%\getpgfstylevalue{/B}{key2} % <- throws a not-a-style error

%\getpgfstylevalue{/UNDEFINED}{} % <- throws an unknown-key error
\end{document}

相关内容