我认为 LaTeX 软件包很少使用宏作为键名。有些用户希望 keyval 输入中的宏首先被递归扩展。但目前l3keys
inexpl3
不提供此功能。所以这是软件包编写者的工作。我能想到一种方法来做到这一点。但这可能有点慢。因此我问这个问题,希望找到最好的解决方案。
注意:只有用逗号包围的宏才会在 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}