让我们考虑一个形式为 的宏名\first@second
。我想编写一个命令,将宏的名称转换为pgfkey
形式的名称/first/second
,以便以下方法等效且具有相同的效果:
% This can change:
\newcommand{\macrotokey}[1]{% WHAT TO DO HERE? %}
\newcommand{\setviakey}[2]{\pgfkeys{#1 = #2}}
\newcommand{\setviamacro}[2]{\pgfkeys{\macrotokey{#1} = #2}}
% If possible I would like to have this syntax on the user side:
\setviakey{/A/B}{Hello}
\setviamacro{\A@B}{Hello}
我不知道从哪里开始做这样的技巧。
答案1
\macrotokey
如果您希望在的参数中使用\pgfkeys
,那么\macrotokey
需要完全扩展。
Expl3 附带一个完全可扩展的例程\cs_to_str:N
,其中通过触发两个扩展步骤来传递不带前导转义字符(通常是反斜杠)的控制序列标记的名称\cs_to_str:N
。使用此例程,您无需担心诸如值为 32 或负值之类的边缘情况。(32 表示通过诸如或 之类\escapechar
的东西传递空格而不是反斜杠作为转义字符。负值表示根本不传递任何转义字符。)\string
\detokenize
因此,让我们使用 expl3 来定义一个宏,该宏触发两个扩展步骤,然后启动一个递归循环,检查参数(仍然)是否包含类别 12(其他),如果是,则在再次调用自身之前用 替换其中的第一个,如果不是,则传递参数。\macrotokey{⟨argument⟩}
\cs_to_str:N ⟨argument⟩
\__MyStuff_macrotokeyReplaceAtLoop:n
@
@
/
我可能会用\cstokentokey
而不是来命名这个事物,\macrotokey
因为构成 参数的标记的含义\macrotokey
并不相关(除非它不应该是\outer
标记)——参数是控制序列标记就足够了。它甚至不需要定义。
如果取消注释\exp_end:
和\exp:w
,则的结果\macrotokey
是通过触发恰好两个扩展步骤而获得的\macrotokey
。
但对于触发形成密钥本身的标记的扩展\pgfkeys
而言,这并不是必需的。\pgfkeys
\documentclass{article}
\usepackage{pgfkeys}
\ExplSyntaxOn
\cs_new:Npn \MyStuff_GobbleTillAt:w #1@ {}
\cs_new:Npn \MyStuff_ReplaceAt:w #1@ { #1/ }
\cs_new:Nn \__MyStuff_macrotokeyReplaceAtLoop:n
{
\tl_if_empty:oTF {\MyStuff_GobbleTillAt:w #1 @}
{ %\exp_end:
#1
% If the name of the control-sequence-token begins with @, then it denotes an absolute path,
% otherwise the path is relative to what \pgfkeysdefaultpath was defined to via the /.cd-handler.
% If you do /#1 instead of #1, then every path is absolute and the names of
% control-sequence-tokens shall not begin with `@`.
% If you do \pgfkeysdefaultpath #1 instead of #1, then every path is relative and the names of
% control-sequence-tokenss shall not begin with `@`.
}
{
\exp_args:No \__MyStuff_macrotokeyReplaceAtLoop:n
{\MyStuff_ReplaceAt:w #1}
}
}
\cs_new:Npn \macrotokey #1
{ %\exp:w
\exp_args:NNo \exp_args:No
\__MyStuff_macrotokeyReplaceAtLoop:n
{ \cs_to_str:N #1 }
}
\ExplSyntaxOff
\newcommand{\setviamacro}[2]{\pgfkeys{\macrotokey{#1}={#2}}}
\newcommand{\setviakey}[2]{\pgfkeys{#1={#2}}}
\makeatletter
\pgfkeys{%
%---------------------------------------------------------------
% define the code to execute when encountering the key /A/B/C:
%---------------------------------------------------------------
/A/B/C/.code=\message{^^JKey /A/B/C's value in parentheses is: (#1)^^J},
%---------------------------------------------------------------
% Use the key /A/B/C :
%---------------------------------------------------------------
/A/B/C=Test 1: Absolute key provided,
\macrotokey{\@A@B@C} = Test 1: Control sequence token denoting absolute key provided,
/A/.cd,
B/C=Test 2: Relative key provided,
\macrotokey{\B@C} = Test 2: Control sequence token denoting relative key provided,
/A/B/.cd,
C=Test 3: Relative key provided,
\macrotokey{\C} = Test 3: Control sequence token denoting relative key provided,
}
\setviakey{/A/B/C}{Test 4: Absolute key provided}
\setviamacro{\@A@B@C}{Test 4: Control sequence token denoting absolute key provided}
\stop
以下消息传送到控制台:
Key /A/B/C's value in parentheses is: (Test 1: Absolute key provided)
Key /A/B/C's value in parentheses is: (Test 1: Control sequence token denoting
absolute key provided)
Key /A/B/C's value in parentheses is: (Test 2: Relative key provided)
Key /A/B/C's value in parentheses is: (Test 2: Control sequence token denoting
relative key provided)
Key /A/B/C's value in parentheses is: (Test 3: Relative key provided)
Key /A/B/C's value in parentheses is: (Test 3: Control sequence token denoting
relative key provided)
Key /A/B/C's value in parentheses is: (Test 4: Absolute key provided)
Key /A/B/C's value in parentheses is: (Test 4: Control sequence token denoting
absolute key provided)
答案2
这可能不是最强大的版本。
所有宏的名称中都不使用,@
因为这样会导致@
11(@
catcode 为 11,即宏名称中的 catcode)和@
12(由 生成的 catcode \string
)之间发生冲突。
有办法解决这个问题 - 甚至最近有一个关于的问题,但我现在找不到 - 但老实说,无论如何我都不会那样使用它。
您可以只定义\A@B
扩展/A/B
并将其用作,\setviakey{\A@B}{…}
但我猜\A@B
在您的用例中它还有另一个用途。
代码
\documentclass{article}
\usepackage{pgfkeys}
\pgfkeys{/A/B/.code=``#1''}
\begin{document}
\def\gobble#1{}
\def\macrotokeyatstripperstop{!stop!}%
\newcommand*\macrotokey[1]{%
/\expandafter\expandafter\expandafter\macrotokeyatstripper
\expandafter\gobble\string#1@\macrotokeyatstripperstop
}%
\def\macrotokeyatstripper#1@#2{%
#1\ifx\macrotokeyatstripperstop#2\expandafter\gobble\else
/\expandafter\macrotokeyatstripper
\fi#2}%
\newcommand{\setviakey}[2]{\pgfkeys{#1 = #2}}
\newcommand{\setviamacro}[2]{\pgfkeys{\macrotokey{#1} = #2}}
\setviakey{/A/B}{Hello}
\makeatletter
\setviamacro{\A@B}{Hello World}
\makeatother
\end{document}