# Using a macro evaluating to a set of xkeyval arguments as argument to another command

I am trying to code a macro that adds xkeyval arguments contained in a macro to a list of default xkeyval arguments.

The following minimal example explains better what I want to do.

\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}


Should generate the output:

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


However, the last call \prefilled[\dummy]{b=other, \otherArgs} generates an error as it interpret the whole content of \otherArgs as a key name.

I tried using \expandafter (which I do not really know how to use) in different places in \prefilled[\dummy]{b=other, \otherArgs} without success. {b=other, \otherArgs} is not taken as argument and I get:

c@a = prefilled
c@b = b
c@c = c
b=other, c=another


How do I modify the call \prefilled[\dummy]{b=other, \otherArgs} or the definition of \prefilled for it to work? The real case is more complex than this minimal example (it involves includegraphics and lengths) but hopefully I will be able to adapt a potential solution.

-
I do something similar in adjustbox if \adjustboxset is used: bitbucket.org/martin_scharrer/adjustbox/src/47332dea3c9f/… –  Martin Scharrer May 13 '12 at 10:52
You can pass something like {b=other, \otherArgs} because the = and , inside \otherArgs are then hidden. Expanding everything with \edef like egreg showed is one solution against this, but will prevent to have general code in the key values. You could allow to pass one macro as an extra argument which is the expanded. I have trouble seeing your real use case from your example. Why would someone pass \otherArgs instead of the real key=value pairs? Is this done by the user or internally by some of your macros?? –  Martin Scharrer May 13 '12 at 12:29
I am coding a user macro IMGS formating a set of images (IMGS{f1, f2, ...}). IMGS call includegraphics with a set of predefined (keepasp..., ...) or computed (width, ...) arguments. Optionally the user can give additional arguments for specific imges (IMGS{f1, [angle=90]f2, f3}). I use xstring to parse the argument of IMGS and multido to call includegraphics for every image. I am able to get the file in \f... and the argument in \args. I now want to call (via one more level of indirection thru a macro \img) \includegraphics[keepaspectratio, width=\computedWidth, ..., \args]{\f...} –  Gus May 13 '12 at 13:59
In this case use \expandafter with an auxiliary macro as shown in my answer. –  Martin Scharrer May 13 '12 at 14:07

You have to expand the option list buried in \otherArgs before \setkeys sees that macro: remember that \setkeys doesn't perform expansion.

\newcommand{\prefilled}[2][toMakeItMoreComplex]{%
\begingroup\edef\x{\endgroup\noexpand\dispKeys[#1]{a=prefilled,#2}}\x
}


As Martin remarks, this bears some risks, as the expansion is complete, so the values given to keys should not contain things that don't survive expansion (typical examples are \textbf and such). Alternatively, use \protected@edef instead of \edef.

The \begingroup and \endgroup ensure that the meaning given to \x will be forgotten instantly when \x is expanded.

-
Yes, but this has the drawback that all keys and values are expanded. However, this seems to be the only possibility if the OP wants to allow any form of combinations of direct key=value pairs and ones inside macros. –  Martin Scharrer May 13 '12 at 11:21
Thanks, it works pretty well on the minimal case study. I will now try to adapt it on my real case. Just to make sure, the \expandafter[#1]{a=prefilled,#2} protect the whole block [#1] or only [? Also, what's the role of the \begingroup and \endgroup, why wouldn't it work without them? –  Gus May 13 '12 at 11:43
@egreg: Please what does the \expandafter in \expandafter[#1] do, given the \edef? This solution (with \edef) isn't applicable for general use. My preferred approach is a walk through the key-value pairs and one-step expanding each pair that requires expansion. Of course, the solution will be longer. I can develop and post it only if the OP thinks it will be useful to him. –  Ahmed Musa May 13 '12 at 14:59
@AhmedMusa Thanks for noting: it was a "copy-paste" error. –  egreg May 13 '12 at 15:29

If \otherArgs is actually added by some internal code, not by the user directly, you can expand it first and add the expanded code at the right position using an auxiliary macro as shown by \withotherargs and \@withotherargs below. If the \otherArgs comes from the user instead you can provide a second argument to pass such a macro.

\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}

-

A more universal and robust solution turned out to be cheaper than I had thought. I choose to normalize the key-value set because without first normalizing the list the scheme of pre-expanding each keyname will not always work. This is because the \expandafter may simply act on a blank space. Normalization also deals with active commas and equal signs. Top-level expansion of keynames is safe because it is unlikely that an active character will lead any keyname.

You can use the option expandkeyname in \documentclass or \usepackage to globally turn on 'expand keyname'. Also, in case anyone rightly fears global assignments, it is possible to use the commands \ExpandKeynameInSetkeys and \NoExpandKeynameInSetkeys to locally turn expandkeyname on and off. You can also use the command \expandnamekeys and similar commands to enter, remove or delete keys whose names should be expanded once or twice at \setkeys.

After extracting the file content keyvalx.sty, move it to the right location in your TeX repository.

Aside: I often hear that one of the drawbacks of xkeyval package is that it can't be loaded before \documentclass. The following keyvalx package patches xkeyval so that keyvalx can be loaded before \documentclass. It postpones what xkeyval likes to do with \@classoptionslist until \documentclass is seen/invoked.

\begin{filecontents}{keyvalx.sty}
\NeedsTeXFormat{LaTeX2e}[2011/06/27]
\ProvidesPackage{keyvalx}[2012/05/13 v0.0.1 Patches for xkeyval package (AM)]
\RequirePackage{catoptions}
\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{%
\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%
}
% \XKVtfor<list>\do{<callback>}
\robust@def*\XKVtfor{\cpt@testst\XKV@tfor}
\robust@def*\XKV@tfor#1\do#2{%
\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
}
\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{%
\XKV@checksanitizeb{#1}\XKV@tempa
\ifXKV@st\expandafter\global\fi
}{%
}%
}
% 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{%
\XKV@err{No 'expandname keys' defined for family \XKV@header'}%
}{%
}%
}
% 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{%
\XKV@err{No 'expandname keys' defined for family \XKV@header'}%
}{%
\ifXKV@st\expandafter\global\fi
}%
}
\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
}%
\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{%
\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
\edef\XKV@testclass{\@currname.\@currext}%
\ifxTF\XKV@testclass\XKV@documentclass{%
\let\@unusedoptionlist\XKV@classoptionslist
\ifdefFT{ver@xkvltxp.sty}{}{%
\@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}%
}%
}%
}%
}{}%
}%
\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{ver@xkvltxp.sty}{}{%
\@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}

\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}

-
It seems to be a nice generic solution. I have done a quick fix for now. When I cleanup the macros I will definitely test this solution with the others. Thanks a lot. –  Gus May 13 '12 at 18:18