keyval:将未知选项传递给命令

keyval:将未知选项传递给命令

考虑以下 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}

只要传递给 的可选参数只有 或 ,这种方法就很好\othercommandfoo由于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-csexpkvexpkv-defl3keyspgfkeysexpkv-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}

两者的输出:

在此处输入图片描述

相关内容