5

Is there a good way to store the key of an unknown key-val in expl3? I can get \l_keys_key_tl, but it gets overwritten before it's expanded. I suppose I need to expand it when I store it, but I'm having trouble making that work, especially because I don't want to expand the value yet. Also, I expect to get multiple unknowns, and the order is important, so l3prop isn't appropriate.

Here's a MWE:

\documentclass{article}
\usepackage{expl3}

\ExplSyntaxOn

\seq_new:N \l_mymod_seq
\keys_define:nn { mymod } {
  a       .tl_set:N = \tmpa, % some keys are treated special
  % others are grouped with unknowns
  b       .code:n = \seq_put_right:Nn \l_mymod_seq {(B~is~\f{#1})},
  unknown .code:n = \seq_put_right:Nn \l_mymod_seq {(unknown~{\l_keys_key_tl}~is~\f{#1})},
}

\newcommand\setkeys[1]{\keys_set:nn { mymod } { #1 }}
\newcommand\printkeys{\seq_use:Nn \l_mymod_seq {,~}}

\ExplSyntaxOff

\begin{document}
\setkeys{a=5,b=6,c=7,d=8}
\newcommand\f[1]{\textbf{#1}}
A is \f{\tmpa}
\renewcommand\f[1]{\textit{#1}}
\printkeys
\end{document}

MWE output showing the \l_keys_key_tl variable printing the same thing twice

5

You can avoid code duplication by using self-defined functions and variants thereof. Here \mymod_store:Vn is just the same as \mymod_store:nn but the first argument should be a token list variable (other data types could apply as well) whose value is passed to the main macro.

\documentclass{article}
\usepackage{expl3,xparse}

\ExplSyntaxOn

\seq_new:N \l_mymod_keyvalue_seq
\keys_define:nn { mymod }
 {
  % some keys are special
  a       .tl_set:N = \valueofa,
  % others are grouped with unknowns
  b       .code:n = \mymod_store:nn {B} {#1},
  unknown .code:n = \mymod_store:Vn \l_keys_key_tl {#1},
 }
\cs_new_protected:Nn \mymod_store:nn
 {
  \seq_put_right:Nn \l_mymod_keyvalue_seq {(#1~is~\f{#2})}
 }
\cs_generate_variant:Nn \mymod_store:nn { V }

\NewDocumentCommand{\setkeys}{m}
 {
  \keys_set:nn { mymod } { #1 }
 }
\NewExpandableDocumentCommand\printkeys{}
 {
  \seq_use:Nn \l_mymod_keyvalue_seq {,~}
 }

\ExplSyntaxOff

\newcommand\f[1]{\textbf{#1}}

\begin{document}

\setkeys{a=5,b=6,c=7,d=8}

A is \f{\valueofa}

\renewcommand\f[1]{\textit{#1}}

\printkeys

\end{document}

enter image description here

| improve this answer | |
  • (or anyone), can you say why you made \printkeys expandable? For that matter, why the protected \mymod_store:nn? As I understand, expandable commands have constraints that allow them to be used in \edefs, and protected commands don't expand in \edefs, but why did you use them here? The xparse docs have many warnings about using \NewExpandableDocumentCommand so I have thus far avoided them, so why are they necessary here? – goodmami Mar 23 '18 at 18:13
  • 1
    @goodmami You might want to use \printkeys in an expansion context. Since \seq_use:Nn is fully expandable, there's no problem in doing it so. Of course, in this case, \textit is likely to break, but the issue is solvable in a different way. – egreg Mar 23 '18 at 18:23
7

You can use expansion (Nx) to expand the key and then prevent expansion (\exp_not) for the unknown control sequence.

As @egreg mentioned in the comments you might want \exp_not:n { \f{#1} } instead of \exp_not:N, because this does not expand #1 (the curenntly used one does).

expand

\documentclass{article}
\usepackage{expl3}

\ExplSyntaxOn

\seq_new:N \l_mymod_seq
\keys_define:nn { mymod } {
  a       .tl_set:N = \tmpa, % some keys are treated special
  % others are grouped with unknowns
  b       .code:n = \seq_put_right:Nn \l_mymod_seq {(B~is~\f{#1})},
  unknown .code:n = \seq_put_right:Nx \l_mymod_seq {(unknown~\l_keys_key_tl\ is~\exp_not:N \f{#1})},
}

\newcommand\setkeys[1]{\keys_set:nn { mymod } { #1 }}
\newcommand\printkeys{\seq_use:Nn \l_mymod_seq {,~}}

\ExplSyntaxOff

\begin{document}
\setkeys{a=5,b=6,c=7,d=8}
\newcommand\f[1]{\textbf{#1}}
A is \f{\tmpa}
\renewcommand\f[1]{\textit{#1}}
\printkeys
\end{document}
| improve this answer | |
  • 1
    Possibly \exp_not:n { \f{#1} } or the text in #1 would be expanded in the unknown case, unlike the b case. – egreg Mar 23 '18 at 7:24
  • @egreg You're right, I supposed the #1 should be expanded. – TeXnician Mar 23 '18 at 13:17
  • This was useful, and I think I'll use the \exp_not:n solution for a related problem, but I think @egreg's answer is more generally useful to the question as posed. One question for you: why does \l_keys_key_tl\ is insert a space before is but \l_keys_key_tl~is does not? I solved this with braces around the macro, but you must have changed it for a reason. Is there a difference between \ and ~ in expl3 syntax mode? – goodmami Mar 23 '18 at 16:53
  • 1
    @goodmami ~ is the LaTeX3 equivalent for an ordinary space. That means that it will look to TeX like a space after a control word and hence is eaten. For it to be visible I changed it to a forced space \ . And the grouping (braces) is something I dislike at that point (I do not like to introduce unnecessary groups). – TeXnician Mar 23 '18 at 17:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.