考虑以下 MWE:
\documentclass[a4paper]{article}
\usepackage{keyval}
\makeatletter
\define@key{my}{foo}[]{foo is enabled\par}
\define@key{my}{bar}[0]{bar is set to #1\par}
\makeatother
\newcommand{\somecommand}[2][none]{arg 1: #1, arg 2: #2}
\newcommand{\othercommand}[2][]{\setkeys{my}{#1} \somecommand{#2}}
\begin{document}
\othercommand[foo,bar=9]{baz}
\end{document}
只要传递给 的可选参数只有 或 ,这种方法就很好\othercommand
。foo
由于bar
这些是可选参数,因此也可以省略。但我现在想做的是:如果给出了另一个 可选参数, 不知道\othercommand
,则应将其传递给\somecommand
。例如:如果我写
\othercommand[foo, bar=29, zap=12]{baz}
我想要选项foo
,并且bar
仍由 处理\othercommand
。但zap=12
选项是未知的,因此应将其\somecommand
不加修改地传递给 ,这样我就可以写
\newcommand{\othercommand}[2][]{\setkeys{my}{#1} \somecommand[pass unknown options here]{#2}}
对于文档类,有一个类似“将选项传递给类”的命令,它将未知的文档类选项传递给底层文档类。我想知道是否也可以在这里使用这样的机制。
答案1
LaTeX3模块具有将每个未知键存储在标记列表中的l3keys
功能。\keys_set_known:nnN
您可以按照以下方式使用它:
\documentclass[]{article}
% Inside of \ExplSyntaxOn ... \ExplSyntaxOff spaces are ignored and you have to
% use ~ to insert a space instead. Also _ and : can be part of macro names which
% is used to add some structure.
\ExplSyntaxOn
\keys_define:nn { my }
{
foo .code:n = { foo ~ is ~ enabled \par }
,foo .value_forbidden:n = { true }
,bar .code:n = { bar ~ is ~ set ~ to ~ #1\par }
,bar .default:n = { 0 }
}
\tl_new:N \l__my_unknown_keys_tl
\NewDocumentCommand \othercommand { O{} m }
{
\group_begin:
\keys_set_known:nnN { my } {#1} \l__my_unknown_keys_tl
\exp_args:NNo \somecommand [ \l__my_unknown_keys_tl ] {#2}
\group_end:
}
\NewDocumentCommand \somecommand { O{none} m }
{
arg ~ 1: ~ #1, ~ arg ~ 2: ~ #2\par
}
\ExplSyntaxOff
\begin{document}
\othercommand{baz}
\othercommand[foo, bar=9]{baz}
\othercommand[foo, bar=9, zip=12]{baz}
\end{document}
如果您只想使用可选参数(\somecommand
如果有任何未知键,则坚持使用其默认值),则可以使用:
\documentclass[]{article}
% Inside of \ExplSyntaxOn ... \ExplSyntaxOff spaces are ignored and you have to
% use ~ to insert a space instead. Also _ and : can be part of macro names which
% is used to add some structure.
\ExplSyntaxOn
\keys_define:nn { my }
{
foo .code:n = { foo ~ is ~ enabled \par }
,foo .value_forbidden:n = { true }
,bar .code:n = { bar ~ is ~ set ~ to ~ #1\par }
,bar .default:n = { 0 }
}
\tl_new:N \l__my_unknown_keys_tl
\NewDocumentCommand \othercommand { O{} m }
{
\group_begin:
\keys_set_known:nnN { my } {#1} \l__my_unknown_keys_tl
\tl_if_empty:NTF \l__my_unknown_keys_tl
{ \somecommand {#2} }
{ \exp_args:NNo \somecommand [ \l__my_unknown_keys_tl ] {#2} }
\group_end:
}
\NewDocumentCommand \somecommand { O{none} m }
{
arg ~ 1: ~ #1, ~ arg ~ 2: ~ #2\par
}
\ExplSyntaxOff
\begin{document}
\othercommand{baz}
\othercommand[foo, bar=9]{baz}
\othercommand[foo, bar=9, zip=12]{baz}
\end{document}
答案2
以下是如何使用 实现此目的的示例expkv
。请注意,未知密钥的转发方式与传入的方式并不完全相同(expkv
解析当前 key=val 对时,该信息会丢失),而是以最多key=value 包会解析出一个等价的对值(我说最多因为一些广泛使用的软件包在括号剥离方面存在一些问题,最明显的是,它可能pgfkeys
无法正确解析传入的内容key= {value}
)。
\documentclass[]{article}
\usepackage{expkv}
\makeatletter
\ekvdefNoVal{my}{foo}{foo is enabled\par}
\ekvdef{my}{bar}{bar is set to #1\par}
\ekvdefNoVal{my}{bar}{bar is set to 0\par}
\newcommand\pluess@add@to@list[2]
{%
\ifx\@empty#1%
\def#1{#2}%
\else
\edef#1{\unexpanded\expandafter{#1,#2}}%
\fi
}
\ekvdefunknownNoVal{my}{\pluess@add@to@list\pluess@my@unknown@list{#1}}
\ekvdefunknown{my}{\pluess@add@to@list\pluess@my@unknown@list{#2= {#1}}}
\newcommand*\pluess@my@unknown@list{}
\ekvsetdef\my@set{my}
\newcommand\othercommand[2][]
{%
\begingroup
\let\pluess@my@unknown@list\@empty
\my@set{#1}%
\expandafter\somecommand\expandafter
[\expandafter{\pluess@my@unknown@list}]%
{#2}%
\endgroup
}
\newcommand\somecommand[2][none]{arg 1: #1, arg 2: #2\par}
\makeatother
\begin{document}
\othercommand{baz}
\othercommand[foo, bar=9]{baz}
\othercommand[foo, bar=9, zip=12]{baz}
\end{document}
如果您只想使用可选参数(\somecommand
如果有任何未知键,则坚持使用其默认值),则可以使用:
\documentclass[]{article}
\usepackage{expkv}
\makeatletter
\ekvdefNoVal{my}{foo}{foo is enabled\par}
\ekvdef{my}{bar}{bar is set to #1\par}
\ekvdefNoVal{my}{bar}{bar is set to 0\par}
\newcommand\pluess@add@to@list[2]
{%
\ifx\@empty#1%
\def#1{#2}%
\else
\edef#1{\unexpanded\expandafter{#1,#2}}%
\fi
}
\ekvdefunknownNoVal{my}{\pluess@add@to@list\pluess@my@unknown@list{#1}}
\ekvdefunknown{my}{\pluess@add@to@list\pluess@my@unknown@list{#2= {#1}}}
\newcommand*\pluess@my@unknown@list{}
\ekvsetdef\my@set{my}
\newcommand\othercommand[2][]
{%
\begingroup
\let\pluess@my@unknown@list\@empty
\my@set{#1}%
\ifx\pluess@my@unknown@list\@empty
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
{\somecommand}%
{%
\expandafter\somecommand\expandafter
[\expandafter{\pluess@my@unknown@list}]%
}%
{#2}%
\endgroup
}
\newcommand\somecommand[2][none]{arg 1: #1, arg 2: #2\par}
\makeatother
\begin{document}
\othercommand{baz}
\othercommand[foo, bar=9]{baz}
\othercommand[foo, bar=9, zip=12]{baz}
\end{document}
答案3
从 1.0 版(于 2021-06-20 发布)开始,这很简单,尽管与其他 key=value 实现(例如,使用或或)相比expkv-cs
,内置的可能性非常有限,因为几乎只用于参数转发并且不能在任何宏中存储任意数据。expkv-cs
expkv
expkv-def
l3keys
pgfkeys
expkv-cs
但是对于大多数简单的用例来说,创建 key=value 宏其实很简单。并且使用...
v1.0 中引入的主键规范,转发未知键与使用一样复杂#1
:
\documentclass[]{article}
\usepackage{expkv-cs}
\newcommand*\somecommand{\ekvoptarg\somecommandOUT{}}
\newcommand\somecommandOUT[2]
{arg 1: \texttt{\detokenize{#1}}, arg 2: #2\par\medskip}
\newcommand*\fooflag{} % make sure the name isn't yet taken
\ekvcFlagNew\fooflag % define it as a flag
\newcommand*\othercommand
{%
% set the flag to be false
\ekvcFlagSetFalse\fooflag
\ekvoptarg\othercommandKEYS{}%
}
\ekvcSplitAndForward\othercommandKEYS\othercommandOUT
{%
bar = -1 % will be #1 in \othercommandOUT
,... % will be #2 in \othercommandOUT
}
\ekvcSecondaryKeys\othercommandKEYS
{%
% Set up keys to handle foo. It'll not use argument forwarding but instead
% flags. Those are computationally expensive though, an argument forwarding
% mechanism with a bit of manual parsing might be better here
flag-bool foo = \fooflag
,flag-true foo = \fooflag
,default bar = 0
}
\newcommand\othercommandOUT[2]
{%
\ekvcFlagIf\fooflag{foo is enabled\par}{}%
bar is set to #1\par
\somecommand[{#2}]% mandatory argument curried
}
\begin{document}
\othercommand[foo, bar=9]{baz}
\othercommand{baz}
\othercommand[foo, bar, zip=12]{baz}
\end{document}
您会注意到,此示例也没有使用 LaTeX2e 的标准可选参数机制,而是使用了\ekvoptarg
。这样做的优点是完全可扩展(缺点:可选参数后面必须跟一个强制参数;并且{[}
无法区分强制参数与可选参数的开头)。
另一种不使用标志的变体foo
:
\documentclass[]{article}
\usepackage{expkv-cs}
\makeatletter
\newcommand*\somecommand{\ekvoptarg\somecommandOUT{}}
\newcommand\somecommandOUT[2]
{arg 1: \texttt{\detokenize{#1}}, arg 2: #2\par\medskip}
\newcommand*\othercommand{\ekvoptarg\othercommandKEYS{}}
\ekvcSplitAndForward\othercommandKEYS\othercommandOUT
{%
bar = -1 % will be #1 in \othercommandOUT
,... % will be #2 in \othercommandOUT
,foo-internal=\@secondoftwo % will be #3
}
\ekvcSecondaryKeys\othercommandKEYS
{%
nmeta foo = foo-internal=\@firstoftwo
,meta foo = foo-internal=\@firstoftwo
,default bar = 0
}
\newcommand\othercommandOUT[3]
{%
#3{foo is enabled\par}{}%
bar is set to #1\par
\somecommand[{#2}]% mandatory argument curried
}
\makeatother
\begin{document}
\othercommand[foo, bar=9]{baz}
\othercommand{baz}
\othercommand[foo, bar, zip=12]{baz}
\end{document}
两者的输出: