我正在尝试编写一个宏,将宏中包含的 xkeyval 参数添加到默认 xkeyval 参数列表中。
以下最小的例子更好地解释了我想要做的事情。
\documentclass{article}
\usepackage{xkeyval}
\makeatletter
\define@cmdkey{f}[c@]{a}[a]{}
\define@cmdkey{f}[c@]{b}[b]{}
\define@cmdkey{f}[c@]{c}[c]{}
\newcommand{\dispKeys}[2][toMakeItMoreComplex]{
\setkeys{f}{a,b,c,#2}
c@a = \c@a{} \\
c@b = \c@b{} \\
c@c = \c@c{} \\
}
\makeatother
\newcommand{\prefilled}[2][toMakeItMoreComplex]{
\dispKeys[#1]{a=prefilled, #2}
}
\begin{document}
\dispKeys{a=test}
\def\dummy{dummy}
\prefilled[\dummy]{}
\prefilled[\dummy]{b=other}
\def\otherArgs{c}
\prefilled[\dummy]{b=other, \otherArgs}
\def\otherArgs{c=another}
\prefilled[\dummy]{b=other, \otherArgs}
\end{document}
应该生成输出:
c@a = test
c@b = b
c@c = c
c@a = prefilled
c@b = b
c@c = c
c@a = prefilled
c@b = other
c@c = c
c@a = prefilled
c@b = other
c@c = c
c@a = prefilled
c@b = other
c@c = another
然而,最后一次调用\prefilled[\dummy]{b=other, \otherArgs}
会产生错误,因为它将整个内容解释\otherArgs
为键名。
我尝试\expandafter
在不同的地方使用(我真的不知道如何使用)但\prefilled[\dummy]{b=other, \otherArgs}
没有成功。{b=other, \otherArgs}
不作为参数,我得到:
c@a = prefilled
c@b = b
c@c = c
b=other, c=another
我该如何修改调用\prefilled[\dummy]{b=other, \otherArgs}
或定义\prefilled
才能使其工作?实际情况比这个最小示例更复杂(它涉及到包括图形和长度),但希望我能够适应一个潜在的解决方案。
答案1
你必须展开隐藏在看到该宏\otherArgs
之前的选项列表\setkeys
:记住\setkeys
没有进行扩展。
\newcommand{\prefilled}[2][toMakeItMoreComplex]{%
\begingroup\edef\x{\endgroup\noexpand\dispKeys[#1]{a=prefilled,#2}}\x
}
正如 Martin 所说,这会带来一些风险,因为扩展已经完成,所以赋予键的值不应包含无法在扩展中生存的内容(典型示例是\textbf
和 等)。或者,使用\protected@edef
而不是\edef
。
并\begingroup
确保\endgroup
所赋予的含义在扩展\x
时会立即被遗忘。\x
答案2
如果\otherArgs
实际上是由某些内部代码添加的,而不是由用户直接添加的,则可以先将其展开,然后使用辅助宏将展开的代码添加到正确的位置,如下图所示\withotherargs
。\@withotherargs
如果是\otherArgs
来自用户,则可以提供第二个参数来传递这样的宏。
\documentclass{article}
\usepackage{xkeyval}
\makeatletter
\define@cmdkey{f}[c@]{a}[a]{}
\define@cmdkey{f}[c@]{b}[b]{}
\define@cmdkey{f}[c@]{c}[c]{}
\newcommand{\dispKeys}[2][toMakeItMoreComplex]{%
\setkeys{f}{a,b,c,#2}%
c@a = \c@a{} \\
c@b = \c@b{} \\
c@c = \c@c{} \\
}
\makeatother
\newcommand{\prefilled}[2][toMakeItMoreComplex]{%
\dispKeys[#1]{a=prefilled, #2}%
}
\makeatletter
\newcommand\withotherargs[2][]{%
\expandafter\@withotherargs\expandafter{\otherArgs}{#1}{#2}%
}
\def\@withotherargs#1#2#3{%
\prefilled[#2]{#3,#1}%
}
\makeatother
\begin{document}
\dispKeys{a=test}
\def\dummy{dummy}
\prefilled[\dummy]{}
\prefilled[\dummy]{b=other}
\def\otherArgs{c}
\prefilled[\dummy]{b=other, \otherArgs}
\def\otherArgs{c=another}
%\prefilled[\dummy]{b=other, \otherArgs}
\withotherargs[\dummy]{b=other}
\end{document}
答案3
事实证明,一个更通用、更强大的解决方案比我想象的更便宜。我选择规范化键值集,因为如果不先规范化列表,预扩展每个键名的方案并不总是有效。这是因为可能\expandafter
只是作用于空格。规范化还处理活动逗号和等号。键名的顶级扩展是安全的,因为活动字符不太可能引导任何键名。
您可以使用或expandkeyname
中的选项全局启用“扩展键名”。此外,如果有人担心全局分配,可以使用命令和本地启用和禁用。您还可以使用命令和类似命令一次或两次输入、移除或删除名称应扩展的键。\documentclass
\usepackage
\ExpandKeynameInSetkeys
\NoExpandKeynameInSetkeys
expandkeyname
\expandnamekeys
\setkeys
提取文件内容后keyvalx.sty,将其移动到 TeX 存储库中的正确位置。
另外:我经常听到有人说键值包的一个缺点是它不能在之前加载\documentclass
。以下键值软件包补丁键值以便键值可以提前加载\documentclass
。它推迟了键值喜欢与...一起做\@classoptionslist
直到\documentclass
被看到/调用。
\begin{filecontents}{keyvalx.sty}
\NeedsTeXFormat{LaTeX2e}[2011/06/27]
\ProvidesPackage{keyvalx}[2012/05/13 v0.0.1 Patches for xkeyval package (AM)]
\RequirePackage{catoptions}
\ifdefTF\XKeyValLoaded{}{\input xkeyval }
\edef\XKV@restorecatcodes{%
\catcode`\noexpand\@\the\catcode`\@\relax
\catcode`\noexpand\=\the\catcode`\=\relax
\catcode`\noexpand\,\the\catcode`\,\relax
\catcode`\noexpand\:\the\catcode`\:\relax
\let\noexpand\XKV@restorecatcodes\relax
}
\catcode`\@11\relax
\catcode`\=12\relax
\catcode`\,12\relax
\catcode`\:12\relax
\let\XKV@doxs\relax
\def\XKV@warn#1{\PackageWarning{xkeyval}{#1}}
\def\XKV@err#1{\PackageError{xkeyval}{#1}\@ehd}
\cptnewvariables{if}[XKV@]{expandkeyname}[false]
\cptnewvariables{if}[XKV]{forbreak}[false]
\new@def*\XKVifcond#1\fi{%
#1\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
}
\newletcs\XKVforbreak\XKVforbreaktrue
% \XKVfor[<parser>]<list>\do{<1-parameter-callback>}
% Star (*) implies <listcmd> in place of <list>
\robust@def*\XKVfor{\cpt@teststopt\XKV@for,}
\robust@def*\XKV@for[#1]#2\do#3{%
\global\advance\XKV@depth by1
\csn@def{XKV@fordo@\romannumeral\XKV@depth}##1#1{%
\XKV@for@a#1\XKVfor{XKV@fordo@\romannumeral\XKV@depth}{##1}{#3}%
}%
\XKVforbreakfalse
\begingroup % for \XKV@tempa
\cpt@stchoose{cpt@st}{#2}\XKV@tempa\XKVfor
\cpt@sttrue\cpt@csvnormalize[#1]\XKV@tempa
\expandafter\endgroup
\csname XKV@fordo@\romannumeral\XKV@depth\expandafter\endcsname
\XKV@tempa#1\XKVfor#1%
\global\advance\XKV@depth by-1
}
% \XKVtfor<list>\do{<callback>}
\robust@def*\XKVtfor{\cpt@testst\XKV@tfor}
\robust@def*\XKV@tfor#1\do#2{%
\global\advance\XKV@depth by1
\csn@def{XKV@tfordo@\romannumeral\XKV@depth}##1{%
\XKV@for@a{}\XKVtfor{XKV@tfordo@\romannumeral\XKV@depth}{##1}{#2}%
}%
\XKVforbreakfalse
\begingroup
\cpt@stchoose{cpt@st}{#1}\XKV@tempa\XKVtfor
\expandafter\endgroup
\csname XKV@tfordo@\romannumeral\XKV@depth\expandafter\endcsname
\XKV@tempa\XKVtfor
\global\advance\XKV@depth by-1
}
\robust@def*\XKV@for@a#1#2#3#4#5{%
\csname i\ifx#2#4\else i\fi ofii\endcsname{%
% In case it was set true by the last item:
\XKVforbreakfalse
}{%
\csname i\ifXKVforbreak\else i\fi ofii\endcsname{%
\XKVforbreakfalse
\begingroup
\defpass\reserved@a##1#2#1{\endgroup
\edef\XKV@remainder{\unexpanded{##1}}%
}%
}{%
#5\relax\csname#3\endcsname
}%
}%
}
% \XKVforwhile[<parser>]<list><holder-cmd><if-cond>\fi{<callback>}
% Star (*) -> <listcmd> in place of <list>
\robust@def*\XKVforwhile{\cpt@teststopt\XKV@forwhile,}
\robust@def\XKV@forwhile[#1]#2#3#4\fi#5{%
\XKVifcond#4\fi{%
\edef#3{\ifboolTF{cpt@st}\expandcsonce\unexpanded{#2}}%
\XKVfor*[#1]#3\do{%
\edef#3{\unexpanded{##1}}%
\XKVifcond#4\fi{#5}\XKVforbreak
}%
}{}%
}
\robust@redef*\XKV@whilist{\XKVforwhile*}
\robust@def*\XKV@normalizelist#1#2{%
\ifcsnullTF#2{}{%
\usename{\ifcase#1csv\else kv\fi @@normalize}*#2%
}%
}
\robust@redef\XKV@sp@deflist#1#2{%
\edef#1{\unexpanded{#2}}%
\XKV@normalizelist{0}#1%
}
\robust@redef*\XKV@checksanitizea#1#2{%
\XKV@ch@cksanitize{#1}#2=%
\edef#2{\unexpanded{#1}}%
\ifboolTF{in@}{}{\XKV@ch@cksanitize{#1}#2,}%
\ifboolTF{in@}{\XKV@normalizelist{1}#2}{}%
}
\robust@redef*\XKV@checksanitizeb#1#2{%
\XKV@ch@cksanitize{#1}#2,%
\edef#2{\unexpanded{#1}}%
\ifboolTF{in@}{\XKV@normalizelist{0}#2}%
}
% Save/add to 'expand name keys':
\robust@def*\expandnamekeys{\XKV@stfalse\XKV@testoptb\XKV@expandnamekeys}
\robust@def*\gexpandnamekeys{\XKV@sttrue\XKV@testoptb\XKV@expandnamekeys}
\robust@def*\XKV@expandnamekeys#1{%
\ifdefFT{XKV@\XKV@header expandname}{%
\XKV@checksanitizeb{#1}\XKV@tempa
\ifXKV@st\expandafter\global\fi
\csn@edef{XKV@\XKV@header expandname}{\expandcsonce\XKV@tempa}%
}{%
\aftercsname\XKV@merge{XKV@\XKV@header expandname}{#1}\XKV@getsg
}%
}
% Remove from 'expand name keys' list:
\robust@def*\remexpandnamekeys{\XKV@stfalse\XKV@testoptb\XKV@remexpandnamekeys}
\robust@def*\gremexpandnamekeys{\XKV@sttrue\XKV@testoptb\XKV@remexpandnamekeys}
\robust@def*\XKV@remexpandnamekeys#1{%
\ifdefFT{XKV@\XKV@header expandname}{%
\XKV@err{No 'expandname keys' defined for family `\XKV@header'}%
}{%
\aftercsname\XKV@delete{XKV@\XKV@header expandname}{#1}\XKV@getsg
}%
}
% Delete entire 'expand name keys' list:
\robust@def*\delexpandnamekeys{\XKV@stfalse\XKV@testoptb\XKV@delexpandnamekeys}
\robust@def*\gdelexpandnamekeys{\XKV@sttrue\XKV@testoptb\XKV@delexpandnamekeys}
\robust@def*\XKV@delexpandnamekeys{%
\ifdefFT{XKV@\XKV@header expandname}{%
\XKV@err{No 'expandname keys' defined for family `\XKV@header'}%
}{%
\ifXKV@st\expandafter\global\fi
\undefcsn{XKV@\XKV@header expandname}%
}%
}
\robust@def*\XKV@checkexpandname#1{%
\ifboolTF{XKV@expandkeyname}{%
\expandafter
}{%
\XKV@ch@ckexpandname{#1}%
}%
}
\robust@def*\XKV@ch@ckexpandname#1{%
\begingroup
\def\XKV@exponce{01}%
\def\XKV@exptwice{01}%
\def\XKV@tempc##1{%
\ifcsndefFT{XKV@\XKV@prefix ##1@expandname}{}{%
\expandcsnonce{XKV@\XKV@prefix ##1@expandname}%
}%
}%
\XKVfor*\XKV@fams\do{%
\expandafter\XKV@g@tkeyname#1=\@nil\XKV@tempa
\xifinsetTF{,\expandcsonce\XKV@tempa,}{,\XKV@tempc{##1},}{%
\def\XKV@exponce{00}%
\XKVforbreak
}{%
\expandafter\expandafter\expandafter
\XKV@g@tkeyname#1=\@nil\XKV@tempa
\xifinsetTF{,\expandcstwice\XKV@tempa,}{,\XKV@tempc{##1},}{%
\def\XKV@exptwice{00}%
\XKVforbreak
}{}%
}%
}%
\ifswitchTF{XKV@exponce}{%
\endgroup\expandafter
}{%
\ifswitchTF{XKV@exptwice}{%
\endgroup\expandafter\expandafter\expandafter
}{%
\endgroup
}%
}%
}
\robust@redef\XKV@setkeys[#1]#2{%
\edef\XKV@userkeys{\unexpanded{#2}}%
\XKV@normalizelist{1}\XKV@userkeys
\let\XKV@naa\@empty
\XKVfor*\XKV@userkeys\do{%
\XKV@checkexpandname{##1}%
\XKV@g@tkeyname##1=\@nil\XKV@tempa
\XKV@addtolist@x\XKV@naa\XKV@tempa
}%
\ifnum\XKV@depth=\z@\let\XKV@rm\@empty\fi
\XKV@usepresetkeys{#1}{preseth}%
\expandafter\XKV@s@tkeys\expandafter{\XKV@userkeys}{#1}%
\XKV@usepresetkeys{#1}{presett}%
\let\CurrentOption\@empty
}
\robust@redef\XKV@s@tkeys#1#2{%
\edef\XKV@na{\unexpanded{#2}}%
\XKV@normalizelist{0}\XKV@na
\XKVfor#1\do{%
\edef\CurrentOption{\unexpanded{##1}}%
\XKV@checkexpandname{##1}%
\XKV@s@tk@ys##1==\@nil
}%
}
\XKVforwhile*\@filelist\XKV@tempa\ifx\XKV@documentclass\@undefined\fi{%
\filename@parse\XKV@tempa
\ifxTF\filename@ext\@clsextension{%
\ifcsndefFT{opt@\filename@area\filename@base.\filename@ext}{}{%
\edef\XKV@documentclass{\filename@area\filename@base.\filename@ext}%
}%
}{}%
}
\robust@def*\XKV@filterclassoptions{%
\XKV@sgfalse
\ifx\@classoptionslist\@undefined\else
\ifx\@classoptionslist\relax\else\XKV@sgtrue\fi
\fi
\ifXKV@sg
\let\XKV@filterclassoptions\relax
\let\XKV@classoptionslist\@classoptionslist
\def\@classoptionslist{}%
\XKVfor*\XKV@classoptionslist\do{%
\ifinsetTF={##1}{}{%
\edef\@classoptionslist{%
\@classoptionslist\ifx\@classoptionslist\@empty\else,\fi
\unexpanded{##1}%
}%
}%
}%
\fi
}
\ifdefTF\XKV@documentclass{%
\XKV@filterclassoptions
}{%
\let\XKV@documentclass\@empty
\let\XKV@classoptionslist\@empty
\edef\@popfilename{\expandcsonce\@popfilename\XKV@filterclassoptions}%
}
\robust@redef*\XKV@testopte#1{%
\XKV@ifstar{\XKV@sttrue\XKV@t@stopte#1}{\XKV@stfalse\XKV@t@stopte#1}%
}
\robust@redef*\XKV@t@stopte#1{\@testopt{\XKV@t@st@pte#1}{KV}}
\robust@redef*\XKV@t@st@pte#1[#2]{%
\XKV@makepf{#2}%
\@ifnextchar<{\XKV@@t@st@pte#1}%
{\XKV@@t@st@pte#1<\@currname.\@currext>}%
}
\robust@redef*\XKV@@t@st@pte#1<#2>{%
\XKV@sp@deflist\XKV@fams{#2}%
\@testopt#1{}%
}
\robust@redef*\DeclareOptionX{%
\let\@fileswith@pti@ns\@badrequireerror
\XKV@ifstar\XKV@dox\XKV@d@x
}
\robust@redef\XKV@dox#1{\XKV@toks{#1}\edef\XKV@doxs{\the\XKV@toks}}
\robust@redef*\XKV@d@x{\@testopt\XKV@@d@x{KV}}
\robust@redef*\XKV@@d@x[#1]{%
\@ifnextchar<{\XKV@@@d@x[#1]}{\XKV@@@d@x[#1]<\@currname.\@currext>}%
}
\robust@redef*\XKV@@@d@x[#1]<#2>#3{\@testopt{\define@key[#1]{#2}{#3}}{}}
\robust@redef*\ExecuteOptionsX{\XKV@stfalse\XKV@plfalse\XKV@t@stopte\XKV@setkeys}
\robust@redef*\ProcessOptionsX{\XKV@plfalse\XKV@testopte\XKV@pox}
\robust@redef*\XKV@pox[#1]{%
\XKV@normalizelist{1}\XKV@classoptionslist
\let\XKV@tempa\@empty
\XKV@inpoxtrue
\let\@fileswith@pti@ns\@badrequireerror
\edef\XKV@testclass{\@currname.\@currext}%
\ifxTF\XKV@testclass\XKV@documentclass{%
\let\@unusedoptionlist\XKV@classoptionslist
\ifdefFT{[email protected]}{}{%
\@onelevel@sanitize\@unusedoptionlist
}%
}{%
\ifboolTF{XKV@st}{%
\ifcsnullTF\XKV@classoptionslist{}{%
\XKVfor*\XKV@classoptionslist\do{%
% When 'xkvltxp' or 'catoptions' is loaded, options aren't expanded:
\XKV@checkexpandname{##1}%
\XKV@g@tkeyname##1=\@nil\CurrentOption
\XKV@key@if@ndefined{\CurrentOption}{}{%
\expandafter\XKV@useoption\expandafter{\CurrentOption}%
\XKV@addtolist@n\XKV@tempa{##1}%
}%
}%
}%
}{}%
}%
\aftercsname{\XKV@addtolist@o\XKV@tempa}{opt@\@currname.\@currext}%
\cptexpandargonce{\XKV@setkeys[#1]}\XKV@tempa
\let\XKV@doxs\relax\let\XKV@rm\@empty
\XKV@inpoxfalse
\let\@fileswith@pti@ns\@@fileswith@pti@ns
\AtEndOfPackage{\let\@unprocessedoptions\relax}%
}
\robust@redef*\XKV@useoption#1{%
\def\XKV@resa{#1}%
\ifdefFT{[email protected]}{}{%
\@onelevel@sanitize\XKV@resa
}%
\@expandtwoargs\@removeelement{\XKV@resa}%
{\@unusedoptionlist}\@unusedoptionlist
}
\define@boolkey[KV]{keyvalx}[XKV@]{expandkeyname}[true]{}
\robust@def*\ExpandKeynameInSetkeys{%
\setkeys[KV]{keyvalx}{expandkeyname=true}%
}
\robust@def*\NoExpandKeynameInSetkeys{%
\setkeys[KV]{keyvalx}{expandkeyname=false}%
}
\DeclareOptionX*{%
\PackageWarning{keyvalx}{Unknown option '\CurrentOption' ignored}%
}
\ExecuteOptionsX[KV]<keyvalx>{expandkeyname=false}
\ProcessOptionsX*[KV]<keyvalx>\relax
\XKV@restorecatcodes
\endinput
\end{filecontents}
% Your example document:
\documentclass%[expandkeyname]
{article}
\usepackage%[expandkeyname]
{keyvalx}
\makeatletter
\define@cmdkey{gus}[c@]{a}[a]{}
\define@cmdkey{gus}[c@]{b}[b]{}
\define@cmdkey{gus}[c@]{c}[c]{}
\newcommand{\dispKeys}[2][toMakeItMoreComplex]{%
\setkeys{gus}{a,b,c,#2}%
c@a = \c@a{} \\
c@b = \c@b{} \\
c@c = \c@c{} \\
}
\makeatother
\newcommand{\prefilled}[2][toMakeItMoreComplex]{%
\dispKeys[#1]{a=prefilled,#2}%
}
\begin{document}
\setkeys{gus}{a,b}
\def\dummy{dummy}
\prefilled[\dummy]{}
\prefilled[\dummy]{b=other}
\def\otherArgs{c}
%\ExpandKeynameInSetkeys
% Enter key 'c' as 'expand name key':
\expandnamekeys{gus}{c}
\prefilled[\dummy]{b=other,\otherArgs}
\def\otherArgs{c=another}
\prefilled[\dummy]{b=other, \otherArgs}
%\NoExpandKeynameInSetkeys
\end{document}