1

Consider the following 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}

This works fine as long as the only optional arguments passed to \othercommand are either foo or bar. Since these are optional arguments, they may be omitted as well. But what I would like to do now is the following: if there is another optional argument given, not known to the \othercommand, it shall be passed to the \somecommand. Example: if I write

\othercommand[foo, bar=29, zap=12]{baz}

I want the options foo and bar still be processed by the \othercommand. But the zap=12 option is not known, so it shall be passed to \somecommand without modification, such that I can write

\newcommand{\othercommand}[2][]{\setkeys{my}{#1} \somecommand[pass unknown options here]{#2}}

For documentclasses, there is a command like "pass options to class" which passes unknown documentclass options to the underlying document class. I wonder whether such a mechanism can be used here as well.

T. Pluess
  • 1,040
  • Does \somecommand also accept foo and bar as options? – Werner Jan 31 '19 at 20:19
  • keyval has a command \KV@errx that you could redefine to handle unknown keys, but imho it would be better to use a newer keyval package like l3keys from expl3 which has built-in handlers for this case. – Ulrike Fischer Jan 31 '19 at 20:41
  • Werner, the \somecommand only accepts zap as optional argument, but neither foo nor bar. So these should not be passed to the \somecommand. – T. Pluess Feb 02 '19 at 17:38
  • Ulrike, could you show a sample? I have absolutely no idea about this l3 topic. I have never used it before and the syntax seems even more complicated to me than the "old" syntax does. Perhaps I am not smart enough :-) – T. Pluess Feb 02 '19 at 17:39

3 Answers3

3

The l3keys module of LaTeX3 has the function \keys_set_known:nnN which stores every unknown key in a token list.

You could use it in the following way:

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


If you want to only use the optional argument of \somecommand if there were any unknown keys and else stick to its default, you could use:

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

Skillmon
  • 60,462
1

The following is an example how this could be done with expkv. Note that the unknown key isn't forwarded exactly like it was passed in (that information is lost when expkv has parsed the current key=val pair), but in a way in which most key=value packages would parse an equivalent value of pair (I say most because some of the wide spread packages have some issues regarding brace stripping, most notably it is possible that pgfkeys doesn't parse correctly something which is passed in as 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}


If you want to only use the optional argument of \somecommand if there were any unknown keys and else stick to its default, you could use:

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

Skillmon
  • 60,462
1

Starting with version 1.0 of expkv-cs (released on 2021-06-20) this is trivial, though the built in possibilities with expkv-cs are quite limited compared to other key=value implementations (like, e.g., expkv with expkv-def or l3keys or pgfkeys), since expkv-cs almost exclusively works with argument forwarding and can't store arbitrary data in any macro.

However for most simple use cases it is really straight forward to create key=value taking macros. And with the ... primary key specification introduced in v1.0 forwarding unknown keys is as complicated as using #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}

You'll notice that this example also didn't use LaTeX2e's standard mechanism for optional arguments but instead used \ekvoptarg. This has the advantage of being fully expandable (downside: the optional argument must be followed by a mandatory argument; and a mandatory argument {[} can't be distinguished from the beginning of the optional argument).


Another variant which doesn't use flags for 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}


Output of both:

enter image description here

Skillmon
  • 60,462