6

I would like to define a function \keys_filter:nnnN which would do the very same thing as \keys_set_filter:nnnN but without the setting. I realized that the setting is realized by a core function \__keys_set_elt_aux: and if I disable it locally, everything works like charm. (The LaTeX3 police is after me. I know.) However, I modify the core, which is frowned upon. Is there a cleaner way how to do it?

Below I show an example of how I use this to separate keys that were filtered for belonging into different key groups.

\documentclass{article}

\usepackage{expl3}[2014/11/25]
\usepackage{xparse}

\ExplSyntaxOn

\cs_new:Nn \my_keys_filter:nnnN {
    \group_begin:
        % Disable the L3 core fucntion responsible for setting the key
        \cs_set_eq:NN \__keys_set_elt_aux: \prg_do_nothing:
        % Use \keys_set_filter
        \keys_set_filter:nnnN { #1 } { #2 } { #3 } \l_my_temp_tl
        % Globalize the token list
        \tl_gset_eq:NN \g_my_temp_tl \l_my_temp_tl
    \group_end:
    % Assign the token list to the output
    \tl_set_eq:NN #4 \g_my_temp_tl
}
\cs_generate_variant:Nn \my_keys_filter:nnnN { nnV }

\keys_define:nn { my } {

    % group A key
    one .code:n = { (ONE:#1) },
    one .groups:n = { grpA },

    % group A+B key
    two .code:n = { (TWO:#1) },
    two .groups:n = { grpAB },

    % group A+B+C key
    three .code:n = { (THREE:#1) },
    three .groups:n = { grpABC },

}

\cs_generate_variant:Nn \msg_error:nnn { nnV }

% Just messages
\msg_new:nnn { my } { keys-only-A } { The~key~'#1'~can~appear~only~in~\token_to_str:N \takesABC . }
\msg_new:nnn { my } { keys-only-AB } { The~key~'#1'~can't~appear~in~\token_to_str:N \takesC . }

% If a key falls in my-only-A(B) key module, we call the error function
\keys_define:nn { my-only-A } { unknown .code:n = { \msg_error:nnV { my } { keys-only-A } \l_keys_key_tl } }
\keys_define:nn { my-only-AB } { unknown .code:n = { \msg_error:nnV { my } { keys-only-AB } \l_keys_key_tl } }

% Takes anything
\DeclareDocumentCommand \takesABC { O{} } {
    \keys_set:nn { my } { #1 }
    ...
}

% Doesn't take A keys
\DeclareDocumentCommand \takesBC { O{} } {
    \keys_set_filter:nnnN { my } { grpA } { #1 } \l_my_filtered_keys_tl
    \keys_set:nV { my-only-A } \l_my_filtered_keys_tl
    ...
}

% Takes only C keys
\DeclareDocumentCommand \takesC { O{} } {
    \keys_set_filter:nnnN { my } { grpA, grpAB } { #1 } \l_my_filtered_keys_tl
    \my_keys_filter:nnVN { my } { grpA } \l_my_filtered_keys_tl \l_my_filtered_B_keys_tl
    \my_keys_filter:nnVN { my } { grpAB } \l_my_filtered_keys_tl \l_my_filtered_C_keys_tl
    \keys_set:nV { my-only-A } \l_my_filtered_B_keys_tl
    \keys_set:nV { my-only-AB } \l_my_filtered_C_keys_tl
    ...
}

\ExplSyntaxOff

\begin{document}

\takesABC[one = 11, two = 22, three = 33]

\takesBC[two = 222, three = 333]

\takesC[three = 3333]

These generate errors:

\takesBC[one = 1, two = 2, three = 3]

\takesC[one = I, two = II, three = III]

\end{document}

What I actually try to do? I have a KV interface to a class, and there are 3 places where keys can be set:

\documentclass[one=1]{myclass}
\mysetup{ two=2 }
\myprocessthings
\mysetup{ three=3 }
\begin{document}
...
\end{document}

And there are keys that are not useful in some "later contexts", for instance, fontsize is fixed when the class is called, and babel is called in \myprocessthings, so the used languages are fixed there. I could make one general error Key '#1' used later than where it is allowed, but I wanna be more precise in what I mean by that, and for this, I need to distinguish the keys. (I'm sorry, a MWE for this would be much longer because it would need a class file, so I decided to make it simpler, probably too much.)

  • We probably need to know the wider context here: what are you actually trying to achieve at an interface level? Also, if you don't want to set the keys why not just carry out the procedure in a group as you have done but leave \__keys_set_elt_aux: alone? Key setting should be a local procedure so there should be no knock-on. – Joseph Wright Mar 2 '15 at 6:46
  • Also, the point of key filtering is to allow some 'ordering' of keys (so for example 'set group A then set group B'). If you have two exclusive sets and a combined situation why not give them submodule prefixes and for the combined situation do two \keys_set_known:nn operations? – Joseph Wright Mar 2 '15 at 6:47
4

Key filtering is intended for rather specialised cases where multiple keys make sense but need additional processing or ordering. In particular, it is not really the correct approach to handling keys where from some fixed point onward they no longer make sense. For example, many packages have preamble-only options which affect an entire document: these keys don't need filtering, they need disabling! This can be handled for example using the \AtBeginDocument hook

\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\keys_define:nn { mymodule }
  {
    key-one .tl_set:N = \l_my_a_tl ,
    key-two .tl_set:N = \l_my_b_tl
  }
\AtBeginDocument
  {
    \keys_define:nn { mymodule }
      {
        key-one .code:n = \msg_error:nnn
          { mymodule } { key-preamble-only } { key-one }
      }
  }
\msg_new:nnn { mymodule } { key-preamble-only }
  { Key~'#1'~only~valid~in~the~preamble! }
\cs_new_eq:NN \SetKeys \keys_set:nn
\ExplSyntaxOff
\SetKeys{mymodule}{key-one = value-a, key-two = value-b}
\begin{document}
\SetKeys{mymodule}{key-one = value-c, key-two = value-d}
\end{document}

If there are two sets of keys with parallel usage in two document commands and common usage in a third common, one could either use key grouping

\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\keys_define:nn { mymodule }
  {
    key-one .tl_set:N = \l_my_a_tl ,
    key-one .groups:n = a,
    key-two .tl_set:N = \l_my_b_tl,
    key-two .groups:n = b
  }
\cs_new_eq:NN \SetKeys      \keys_set:nn
\cs_new_eq:NN \SetGroupKeys \keys_set_groups:nnn
\ExplSyntaxOff
\begin{document}
\SetGroupKeys{mymodule}{a}{key-one = value-a, key-two = value-b}
\SetGroupKeys{mymodule}{b}{key-one = value-c, key-two = value-d}
\SetKeys{mymodule}{key-one = value-e, key-two = value-f}
\end{document}

or use two submodules and a multi-pass approach, building on for example

\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\keys_define:nn { mymodule }
  {
    a / key-one .tl_set:N = \l_my_a_tl ,
    b / key-two .tl_set:N = \l_my_b_tl
  }
\cs_new_eq:NN \SetKeys      \keys_set:nn
\cs_new_eq:NN \SetKnownKeys \keys_set_known:nn
\ExplSyntaxOff
\begin{document}
\SetKnownKeys{mymodule / a}{key-one = value-a, key-two = value-b}
\SetKnownKeys{mymodule / b}{key-one = value-c, key-two = value-d}
\end{document}
  • Just a doubt (I will delete comment after): now it's possible to swap the a / to the mymodule / a? Since when? I asked some time ago and the answer was that those different “paths” weren't interchangeable. – Manuel Mar 2 '15 at 13:21
  • @Manuel From memory this has always been the case: l3keys follows pgfkeys here in treating keys much like a Unix file system. The / token is only really 'special' when it comes to choice keys (where a defined structure is needed), but I think we've documented this is the preferred way to subdivide keys. – Joseph Wright Mar 2 '15 at 14:11
  • I might not have been clear when I asked (may be you remember), but that was exactly what I wanted :) – Manuel Mar 2 '15 at 15:16

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.