1

I'm trying to create a key to pass the rest of the keys to /subgroup based on a boolean condition. That is to say that something like this works:

[key in <module>, rest of keys pass to <module/subgroup>]

Each /subgroup has keys with the same names, my idea is to be able to create a command that works in the following way:

\mycmd[key = name (active condition), pass rest of keys acorde to condition to <module/name>]{...}

with the code I own, I am able to do this, but it is based on a comparison of strings, is quite weak and fails if not placed in the correct order. This is the example file (first attempt):

\documentclass[10pt]{article}
\usepackage{xparse}
\ExplSyntaxOn
\keys_define:nn { mymodule / style-a }
  {
    style-a .bool_set:N = \l_style_a_bool, 
    style-a .initial:n  = false, 
    key-a   .tl_set:N   = \l_style_a_keya_tl, 
    key-b   .tl_set:N   = \l_style_a_keyb_tl
  }

\keys_define:nn { mymodule / style-b }
  {
    style-b .bool_set:N = \l_style_b_bool, 
    style-b .initial:n  = false, 
    key-a   .tl_set:N   = \l_style_b_keya_tl, 
    key-b   .tl_set:N   = \l_style_b_keyb_tl,
  }

\keys_define:nn { mymodule }
  {
    style-a .code:n = \keys_set:nn { mymodule / style-a } { style-a = true },
    style-b .code:n = \keys_set:nn { mymodule / style-b } { style-b = true }
  }

% style-a
\cs_new_protected:Npn \__mymodule_style_a:n #1
  {
    This  ~ is ~ \texttt{"#1"} ~ whit ~ \texttt{[key-a ~ = ~ \use:c{l_style_a_keya_tl}]}  ~ 
    and ~ \texttt{[key-b ~ = ~ \use:c{l_style_a_keyb_tl}]} .
  }
% style-b
\cs_new_protected:Npn \__mymodule_style_b:n #1
  {
    This  ~ is ~ \texttt{"#1"} ~ whit ~ \texttt{[key-a ~ = ~ \use:c{l_style_b_keya_tl}]}  ~ 
    and ~ \texttt{[key-b ~ = ~ \use:c{l_style_b_keyb_tl}]} .
  }

\cs_new_protected:Npn \__mymodule_test_keys:n #1
  {
    \str_case:xnF { \clist_item:nn { #1 } { 1 } }
      {
        { style-a } 
          { 
            \keys_set:nn { mymodule  } { style-a } 
            \keys_set:nn { mymodule / style-a } { #1 }
          }
        { style-b } 
          { 
            \keys_set:nn { mymodule  } { style-b } 
            \keys_set:nn { mymodule / style-b } { #1 }
          }
      }
      {}
  }
\cs_generate_variant:Nn \str_case:nnF { x }

\cs_new_protected:Npn \mycmd:nn #1#2
  {
    \group_begin:
    \__mymodule_test_keys:n { #1 }
    \bool_if:nT
      {
        \l_style_a_bool
      }
      { \__mymodule_style_a:n { #2 } }
    \bool_if:nT
      {
        \l_style_b_bool
      }
      { \__mymodule_style_b:n { #2 } }
    \group_end:
  }

\NewDocumentCommand{\mycmd}{ o m }
  {
    \mycmd:nn{#1}{#2}
  }
\ExplSyntaxOff
\begin{document}
\mycmd[style-a,key-a=value-A,key-b=value-A]{style-a}\par
\mycmd[style-b,key-a=value-B,key-b=value-B]{style-b}
\end{document}

My second attempt solves the problems of my first attempt, but, lose the possibility of generating an error message. This is a second example file:

\documentclass[10pt]{article}
\usepackage{xparse}
\ExplSyntaxOn
% define bools for keys
\bool_new:N \l_style_a_bool 
\bool_new:N \l_style_b_bool

\keys_define:nn { mymodule / style-a }
  { 
    style   .tl_set:N   = \l_tmpa_tl        , 
    key-a   .tl_set:N   = \l_style_a_keya_tl, 
    key-b   .tl_set:N   = \l_style_a_keyb_tl,
  }

\keys_define:nn { mymodule / style-b }
  { 
    style   .tl_set:N   = \l_tmpa_tl        , 
    key-a   .tl_set:N   = \l_style_b_keya_tl, 
    key-b   .tl_set:N   = \l_style_b_keyb_tl,
  }

\keys_define:nn { mymodule } 
  { 
    style           .choice:                                        ,
    style / style-a .code:n  = { \bool_set_true:N \l_style_a_bool } ,
    style / style-b .code:n  = { \bool_set_true:N \l_style_b_bool } ,
    unknown         .code:n  = {} % 
  }

% style-a
\cs_new_protected:Npn \__mymodule_style_a:n #1
  {
    This  ~ is ~ \texttt{"#1"} ~ whit ~ \texttt{[key-a ~ = ~ \use:c{l_style_a_keya_tl}]}  ~ 
    and ~ \texttt{[key-b ~ = ~ \use:c{l_style_a_keyb_tl}]} .
  }
% style-b
\cs_new_protected:Npn \__mymodule_style_b:n #1
  {
    This  ~ is ~ \texttt{"#1"} ~ whit ~ \texttt{[key-a ~ = ~ \use:c{l_style_b_keya_tl}]}  ~ 
    and ~ \texttt{[key-b ~ = ~ \use:c{l_style_b_keyb_tl}]} .
  }

\cs_new_protected:Npn \mycmd:nn #1#2
  {
    \group_begin:
    \keys_set:nn { mymodule  } { #1 } 
    \bool_if:nT
      {
        \l_style_a_bool
      }
      { 
        \keys_set:nn { mymodule / style-a } { #1 } 
        \__mymodule_style_a:n {#2}  
      }
    \bool_if:nT
      {
        \l_style_b_bool
      }
      { 
        \keys_set:nn { mymodule / style-b } { #1 } 
        \__mymodule_style_b:n {#2} 
      }
    \group_end:
  }

\NewDocumentCommand{\mycmd}{ o m }
  {
    \mycmd:nn{#1}{#2}
  }

\ExplSyntaxOff

\begin{document}
\mycmd[style=style-a,key-a=value-A,key-b=value-A]{style-a}\par
\mycmd[style=style-b,key-a=value-B,key-b=value-B]{style-b}
\end{document}

I searched the forum for some adaptive code, but I didn't find any. Is there any way to do this?

Greetings.

  • Which error message are you talking about? There is already one when passing an invalid value for a choice key. In your \keys_define:nn { mymodule } (second example), I would use style / style-a .bool_set:N = ..., style / style-a .initial:n = { false }, otherwise, you are keeping the value the bool had before calling \keys_define:nn, in case the option isn't set in any way (I mean, not passed at all). – frougon Jun 13 at 6:07
  • For the “separation”: you could iterate on a clist created from the first argument of \mycmd:nn and remove every item that starts with style= (i.e., you can create an empty clist and copy there only the items that don't start with style=; I believe you would have to handle optional space tokens on either side of the = to be fully correct, maybe also to the left of style, so a regex could be useful). Regarding the error messages, wouldn't it be enough to use an unknown key in \keys_define:nn { mymodule / style-a } and \keys_define:nn { mymodule / style-b }? – frougon Jun 13 at 6:55
  • @frougon: That's exactly what I need, regarding error handling perhaps your idea is better than mine, could you give an example of the use of cmap/regex? – Pablo González L Jun 13 at 11:36
  • Please see my answer below. – frougon Jun 13 at 12:41
2

You can filter out a given option from a comma list with the help of \clist_map_inline:nn and the regex module. In the following example, we filter out all occurrences of the style option:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\regex_new:N \l__mymodule_option_regex

% Filter-out all occurrences of keyword argument #1 from comma list #2.
% Store the resulting comma list in #3.
\cs_new_protected:Npn \__mymodule_filter_out_option:nnN #1#2#3
  {
    \tl_set:Nn \l_tmpa_tl {#1}
    \regex_set:Nn \l__mymodule_option_regex { ^ \u{l_tmpa_tl} \ * = }
    \clist_clear:N #3

    \clist_map_inline:nn {#2}
      {
        \regex_match:NnF \l__mymodule_option_regex {##1}
          { \clist_put_right:Nn #3 {##1} }
      }
  }

% Filter-out all occurrences of keyword argument #1 from comma list #2.
% Print the remaining items, one per line.
\NewDocumentCommand \filteroutoption { m m }
  {
    \__mymodule_filter_out_option:nnN {#1} {#2} \l_tmpa_clist
    \clist_use:Nn \l_tmpa_clist { \\ }
  }

\ExplSyntaxOff

\begin{document}
  \noindent
  \filteroutoption{style}{style=style-a,key-a=value-A,key-b=value-B,
                          foobarstyle = foobarvalue,
                          style   =   other-style, some-key=value,
                          other key = {other, value}, style=foo-style,
                          final key = {its\ \ \ \ value}}
\end{document}

Screenshot

In order to use this is in your second example, you can replace:

\cs_new_protected:Npn \mycmd:nn #1#2
  {
    \group_begin:
    \keys_set:nn { mymodule  } { #1 } 
    \bool_if:nT
      {
        \l_style_a_bool
      }
      { 
        \keys_set:nn { mymodule / style-a } { #1 } 
        \__mymodule_style_a:n {#2}  
      }
    \bool_if:nT
      {
        \l_style_b_bool
      }
      { 
        \keys_set:nn { mymodule / style-b } { #1 } 
        \__mymodule_style_b:n {#2} 
      }
    \group_end:
  }

\NewDocumentCommand{\mycmd}{ o m }
  {
    \mycmd:nn{#1}{#2}
  }

with:

\clist_new:N \l__mymodule_tmp_clist

\cs_new_protected:Npn \mymodule_mycmd:nn #1#2
  {
    \group_begin:
    \__mymodule_filter_out_option:nnN { style } {#1} \l__mymodule_tmp_clist

    \keys_set:nn { mymodule } {#1}
    \bool_if:NT \l_style_a_bool
      {
        \keys_set:nV { mymodule / style-a } \l__mymodule_tmp_clist
        \__mymodule_style_a:n {#2}
      }
    \bool_if:NT \l_style_b_bool
      {
        \keys_set:nV { mymodule / style-b } \l__mymodule_tmp_clist
        \__mymodule_style_b:n {#2}
      }
    \group_end:
  }

\NewDocumentCommand \mycmd { O{} m }
  {
    \mymodule_mycmd:nn {#1} {#2}
  }

This way, mymodule / style-a and mymodule / style-b will not receive any style key.

Your second example could be changed like this:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

% define bools for keys (by def false)
\bool_new:N \l_style_a_bool
\bool_new:N \l_style_b_bool

\keys_define:nn { mymodule / style-a }
  {
    key-a   .tl_set:N   = \l_style_a_keya_tl,
    key-b   .tl_set:N   = \l_style_a_keyb_tl,
  }

\keys_define:nn { mymodule / style-b }
  {
    key-a   .tl_set:N   = \l_style_b_keya_tl,
    key-b   .tl_set:N   = \l_style_b_keyb_tl,
  }

\keys_define:nn { mymodule }
  {
    style            .choice:                                        ,
    % Remember to set the bools to their default value before using
    % a \keys_set-like function here (alas, .bool_set:N \l_style_*_bool
    % doesn't seem to be accepted here).
    style / style-a  .code:n  = { \bool_set_true:N \l_style_a_bool } ,
    style / style-b  .code:n  = { \bool_set_true:N \l_style_b_bool } ,
  }

% style-a
\cs_new_protected:Npn \__mymodule_style_a:n #1
  {
    This  ~ is ~ \texttt{"#1"} ~ with ~ \texttt{[key-a ~ = ~ \use:c{l_style_a_keya_tl}]}  ~
    and ~ \texttt{[key-b ~ = ~ \use:c{l_style_a_keyb_tl}]} .
  }

% style-b
\cs_new_protected:Npn \__mymodule_style_b:n #1
  {
    This  ~ is ~ \texttt{"#1"} ~ with ~ \texttt{[key-a ~ = ~ \use:c{l_style_b_keya_tl}]}  ~
    and ~ \texttt{[key-b ~ = ~ \use:c{l_style_b_keyb_tl}]} .
  }

\msg_new:nnn { mymodule } { unknown-style }
  {
   The ~ choice ~ passed ~ to ~ key  ~ [style]  ~  is ~ not ~ valid.  ~
   Valid ~ choices ~ are: ~ style-a, ~ style-b.
  }

\tl_new:N \l__mymodule_styles_tl
% Filter-out all occurrences of keyword argument #1 from comma list #2.
% Store the resulting comma list in #3.
\cs_new_protected:Npn \__mymodule_filter_out_option:nnN #1#2#3
  {
    \tl_set:Nn \l_tmpa_tl {#1}
    \regex_set:Nn \l__mymodule_option_regex { ^ \u{l_tmpa_tl} \ * = }
    \clist_clear:N #3

    \clist_map_inline:nn {#2}
      {
        \regex_match:NnF \l__mymodule_option_regex {##1}
          { \clist_put_right:Nn #3 {##1} }
      }
  }

\clist_new:N \l__mymodule_tmp_clist

\cs_new_protected:Npn \mymodule_mycmd:nn #1#2
  {
    \group_begin:

    \bool_set_false:N \l_style_a_bool
    \bool_set_false:N \l_style_b_bool
    \keys_set_known:nn { mymodule } {#1}

    \__mymodule_filter_out_option:nnN { style } {#1} \l__mymodule_tmp_clist

    \bool_if:NT \l_style_a_bool
      {
        \keys_set:nV { mymodule / style-a } \l__mymodule_tmp_clist
        \__mymodule_style_a:n {#2}
      }
    \bool_if:NT \l_style_b_bool
      {
        \keys_set:nV { mymodule / style-b } \l__mymodule_tmp_clist
        \__mymodule_style_b:n {#2}
      }
    \group_end:
  }

\NewDocumentCommand \mycmd { O{} m }
  {
    \mymodule_mycmd:nn {#1} {#2}
  }

\ExplSyntaxOff

\begin{document}
% check with valid keys
\mycmd[style=style-a,key-a=value-A,key-b=value-A]{style-a}\par
\mycmd[key-a=value-B,key-b=value-B,style=style-b]{style-b}
% Uncomment to show error :)
%\mycmd[foobarstyle=style-aandotherthings, style=style-a,key-a=value-A,key-b=value-A]{style-a}\par
%\mycmd[style=style-c,key-a=value-B,key-b=value-B]{style-b}
\end{document}
  • I've made a little correction (added a ^ regex metacharacter) to make sure we don't filter out items such as blablastyle=.... – frougon Jun 13 at 13:11
  • Great, I will try to place it in my second example ... – Pablo González L Jun 13 at 13:14
  • Using \__mymodule_filter_out_option:nnN {style} {〈options〉} \l__your_clist where 〈options〉 contains the user-provided options will store into \l__your_clist exactly what you want to process (〈options〉 with all style= options filtered out). Note: you have to declare \l__your_clist yourself. – frougon Jun 13 at 13:19
  • Please see my last edit. Look carefully: I simplified the \bool_if tests, renamed your \mycmd:nn function and replaced the o option specifier for \mycmd with O{}, in addition to passing \mycmd's first argument to mymodule / style-a and mymodule / style-b with style options filtered out. – frougon Jun 13 at 14:00
  • Ok, I tried it, I commented %style in mymodule / style-a and mymodule / style-b and everything goes OK, but, if I comment unknown in mymodule fails, that is to say, it doesn't go as I would like. It may be necessary to add a second list|regex to save the style= values and pass them to mymodule using \keys_set:nn { mymodule } } { \clist_item: \l_tmpa_clist {1} } ( I don't want style=val to be repeated in the options). I don't know if that's possible. – Pablo González L Jun 13 at 14:53

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.