在 expl3 中的 keyval 输入中扩展宏的最佳方法是什么

在 expl3 中的 keyval 输入中扩展宏的最佳方法是什么

我认为 LaTeX 软件包很少使用宏作为键名。有些用户希望 keyval 输入中的宏首先被递归扩展。但目前l3keysinexpl3不提供此功能。所以这是软件包编写者的工作。我能想到一种方法来做到这一点。但这可能有点慢。因此我问这个问题,希望找到最好的解决方案。

注意:只有用逗号包围的宏才会在 keyval 输入中扩展。

\documentclass{article}
\ExplSyntaxOn
\keys_define:nn { test }
  {
    key1 .code:n = \tl_to_str:n {#1},
    key2 .code:n = \tl_to_str:n {#1},
    key3 .code:n = \tl_to_str:n {#1},
    key4 .code:n = \tl_to_str:n {#1},
    key5 .code:n = \tl_to_str:n {#1},
    key6 .code:n = \tl_to_str:n {#1},
  }
\NewDocumentCommand \mycommand {m}
  {
    \keys_set:nn { test } { #1 }
  }
\ExplSyntaxOff
\begin{document}

\mycommand{key1=val1,key2=val2,key3=val3,key4=val4,key5=val5,key6=val6}

\def\mymacroa{key3=val3,key4=val4}

\mycommand{key1=val1,key2=val2,\mymacroa,key5=val5,key6=val6} % error

\def\mymacrob{key2=val2,\mymacroa,key5=val5}

\mycommand{key1=val1,\mymacrob,key6=val6} % error

\end{document}

答案1

您可以递归扫描选项列表,扩展一次单个控制序列的项目。

\documentclass{article}

\ExplSyntaxOn

\clist_new:N \l__ljr_keysexp_clist
\bool_new:N \l__ljr_keysexp_bool

\cs_new_protected:Nn \ljr_keysexp_set:nn
 {
  \clist_clear:N \l__ljr_keysexp_set_clist
  % scan the options one by one and put them in a clist,
  % expanding once the control sequences that are found
  \bool_set_false:N \l__ljr_keysexp_bool
  \clist_map_inline:nn { #2 }
   {
    \bool_lazy_and:nnTF { \tl_if_single_p:n { ##1 } } { \token_if_cs_p:N ##1 }
     {
      \clist_put_right:No \l__ljr_keysexp_set_clist { ##1 }
      \bool_set_true:N \l__ljr_keysexp_bool
     }
     {
      \clist_put_right:Nn \l__ljr_keysexp_set_clist { ##1 }
     }
   }
  % we found a macro, let's repeat
  \bool_if:NTF \l__ljr_keysexp_bool
   {% there is a macro
    \ljr_keysexp_set:nV { #1 }\l__ljr_keysexp_set_clist
   }
   {% no macro, set the keys
    \keys_set:nV { #1 } \l__ljr_keysexp_set_clist
   }
 }
\cs_generate_variant:Nn \ljr_keysexp_set:nn { nV }

\keys_define:nn { test }
  {
    key1 .code:n = \tl_to_str:n {#1},
    key2 .code:n = \tl_to_str:n {#1},
    key3 .code:n = \tl_to_str:n {#1},
    key4 .code:n = \tl_to_str:n {#1},
    key5 .code:n = \tl_to_str:n {#1},
    key6 .code:n = \tl_to_str:n {#1},
  }

\NewDocumentCommand \mycommand {m}
  {
    \ljr_keysexp_set:nn { test } { #1 }
  }
\ExplSyntaxOff

\begin{document}

\mycommand{key1=val1,key2=val2,key3=val3,key4=val4,key5=val5,key6=val6}

\def\mymacroa{key3=val3,key4=val4}

\mycommand{key1=val1,key2=val2,\mymacroa,key5=val5,key6=val6} % error

\def\mymacrob{key2=val2,\mymacroa,key5=val5}

\mycommand{key1=val1,\mymacrob,key6=val6} % error

\end{document}

在此处输入图片描述

但这不是键值系统的最佳方法。

答案2

以下评论没有回答您的问题,因此可能不会赞成此“答案”。以下评论不适合评论。它们旨在引起人们对上下文中出现的问题的注意。


为键或值交替收集的标记列表,除了在解析/拆分列表时未定义但在进一步处理拆分(值)组件时定义的标记之外,它可以包含可扩展的标记,仅在未收集值时才进行扩展?

为了收集密钥的标记,可以想象一个尾部递归循环,它将不可扩展的标记作为密钥的一部分进行收集,并扩展可扩展的标记而不是收集它们。当找到逗号或等号或收集列表中的所有标记时,密钥的收集结束。如果由于找到逗号而导致密钥的收集结束,则收集下一个密钥。如果由于找到等号而导致密钥的收集结束,则收集相应的值。

应该如何收集值的标记?您可以说,只收集未展开的标记,直到到达下一个逗号(未嵌套在显式 catcode1/2 字符标记中)或收集了列表中的所有标记。

但是 - 如果这是关于任意用户的任意输入,您必须考虑一切 - 您如何处理用户想到在值部分中使用宏的情况,其扩展包含一个前导逗号来结束值部分?

像这样:

\def\MoreKeyVals{, keyc=valc, keyd=vald,}
\mycommand{keya=vala, keyb=valb \MoreKeyVals \keye=vale}

如果也考虑到这样的事情,那么问题就出现了:用于收集属于某个值的标记的算法应该如何决定是否扩展事物?

此外,还需要关于删除花括号/周围空格的精确规范。例如,应该在扩展之前还是扩展之后删除周围的空格?如果可扩展标记的扩展产生空格标记,这可能会有所不同。

如果在某个阶段键名称的字符串化发挥了作用,人们可能会问一个边缘问题:如何处理类似的事情\mycommand{{keya=vala, }keyb=valb}

答案3

\begin{advertisement}

我的expkv软件包有一个内置的键扩展语法,该语法还支持扩展宏并将其替换用作额外的 key=value 输入。这可以通过在宏前面加上前缀R:␣(其中有一个空格)来实现。一般来说,语法会<expansions>:␣在键的开头扫描,然后执行<expansions>。有关已定义的扩展规则的完整列表,请参阅软件包文档。

警告::␣名称中包含的键会变得更难输入\ekvparse(在中没有问题\ekvset),在这种情况下您需要使用\ekvparse{<code1>}{<code2>}{: key: name: with: colons},并且解析的键名称将是key: name: with: colons

为了允许这种机制,l3keys我们通过传递一次键列表来包装它,\ekvparse以应用我们的扩展语法,并将结果转发给\keys_set:nn。我们需要两次完整的扩展,第一次是\ekvparse为了让辅助宏生效,第二次是为了让辅助宏生效。

\documentclass{article}

\usepackage{expkv}

\ExplSyntaxOn
\cs_new:Npn \ljr_set_keys_expanded:nn #1#2
  {
    \exp_args:Nne \keys_set:ne {#1}
      { \ekvparse \__ljr_set_keys_expanded:n \__ljr_set_keys_expanded:nn {#2} }
  }
\cs_generate_variant:Nn \keys_set:nn { ne }
\cs_new:Npn \__ljr_set_keys_expanded:n #1
  { , { \exp_not:n {#1} } }
\cs_new:Npn \__ljr_set_keys_expanded:nn #1#2
  { , { \exp_not:n {#1} } = { \exp_not:n {#2} } }
\keys_define:nn { test }
  {
    key1 .code:n = \tl_to_str:n {#1},
    key2 .code:n = \tl_to_str:n {#1},
    key3 .code:n = \tl_to_str:n {#1},
    key4 .code:n = \tl_to_str:n {#1},
    key5 .code:n = \tl_to_str:n {#1},
    key6 .code:n = \tl_to_str:n {#1},
  }
\NewDocumentCommand \mycommand {m}
  { \ljr_set_keys_expanded:nn { test } {#1} }
\ExplSyntaxOff

\begin{document}

\mycommand{key1=val1,key2=val2,key3=val3,key4=val4,key5=val5,key6=val6}

\def\mymacroa{key3=val3,key4=val4}

\mycommand{key1=val1,key2=val2,R: \mymacroa,key5=val5,key6=val6} % error

\def\mymacrob{key2=val2,R: \mymacroa,key5=val5}

\mycommand{key1=val1,R: \mymacrob,key6=val6} % error

\end{document}

在此处输入图片描述

\end{advertisement}

答案4

创建一个新的扩展变体\keys_set:nx

\documentclass{article}
\ExplSyntaxOn
\keys_define:nn { test }
  {
    key1 .code:n = \tl_to_str:n {#1},
    key2 .code:n = \tl_to_str:n {#1},
    key3 .code:n = \tl_to_str:n {#1},
    key4 .code:n = \tl_to_str:n {#1},
    key5 .code:n = \tl_to_str:n {#1},
    key6 .code:n = \tl_to_str:n {#1},
  }
\cs_generate_variant:Nn \keys_set:nn { nx }
\NewDocumentCommand \mycommand {m}
  {
    \keys_set:nx { test } { #1 }
  }
\ExplSyntaxOff
\begin{document}

\mycommand{key1=val1,key2=val2,key3=val3,key4=val4,key5=val5,key6=val6}

\def\mymacroa{key3=val3,key4=val4}

\mycommand{key1=val1,key2=val2,\mymacroa,key5=val5,key6=val6}

\def\mymacrob{key2=val2,\mymacroa,key5=val5}

\mycommand{key1=val1,\mymacrob,key6=val6}

\end{document}

相关内容