考虑到
\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}