1

I want a package using expl3 to accept a multiple choice list of key=boolean pairs to a parameter where each value is optional and defaults to false if not specified.

I still find generic examples of expl3 too hard to follow. So for a concrete example let's make a package food that has many keys but we'll just consider one called fruit. This key should accept a list of actual fruit, each optionally with a value true or false. When an actual fruit is specified the key processing should create a toggle from etoolbox and set it true unless it explicitly has a false parameter value. Fruits not specified should have a toggle that is false.

I know expl3 has bool but I have expl3 overload and I forgot to buy headache pills because my LaTeX grocery list doesn't work yet. Also I want the toggles easily available in the main document without expl3 for now.

Minimal not working fragment

\RequirePackage{etoolbox} % for toggles

\providetoggle{fruitapple} \providetoggle{fruitbanana}
\providetoggle{fruitcherry} \providetoggle{fruitdurian}
\providetoggle{fruitelderberry} \providetoggle{fruitfig}
% yawn, there must be a way to loop over a list to make the toggles

\keys_define:nn { food }
  {
    fruit .multichoices:nn  =
    { apple, banana, cherry,
      durian, elderberry, fig
    } % ideally reuse the list of allowed fruit that made the toggles
    {
    \iow_term:x { % from l3keys manual
      You~gave~choice~'\tl_use:N \l_keys_choice_tl'~
      which~is~in~position~
      \int_use:N \l_keys_choice_int \c_space_tl
      in~the~list.
    }
    % some missing logic to determine the value of the current key... then
    \toggletrue{fruit\tl_use:N \l_keys_choice_tl}
    }
  }

This works for specifying a vanilla list

\usepackage[fruit={apple, banana, durian}]{food}

You gave choice 'apple' which is in position 1 in the list.
You gave choice 'banana' which is in position 2 in the list.
You gave choice 'durian' which is in position 4 in the list.

\begin{document}
\iftoggle{fruitapple}{Buy apples}{No apples}
\end{document}

Buy apples

but requires clean up in aisle 1 if you add any values

\usepackage[fruit={apple, banana=true, cherry=false}]{food}

You gave choice 'apple' which is in position 1 in the list.
LaTeX Error: Key 'food/fruit' accepts only a fixed set of choices.

As well as searching this site and the wider internet, I grepped the source code of my MikTeX installation for examples. There was nothing similar that I could find. Packages were updated yesterday.

2 Answers 2

3

You don't have a simple key here, the argument is actually another key-value and so if you want to use keys, you have to nest the calls:

\documentclass[12pt]{article}
\RequirePackage{etoolbox} % for toggles
\ExplSyntaxOn
\clist_new:N\l_food_fruits_clist
\clist_set:Nn\l_food_fruits_clist {apple,banana,cherry,durian,elderberry,fig}
\clist_map_inline:Nn \l_food_fruits_clist
 { \providetoggle{fruit#1} }
 
\keys_define:nn { food }
  {
    fruit .code:n = 
     {
       \keys_set:nn { food / fruit}{ #1 } 
     }
  }

\clist_map_inline:Nn  \l_food_fruits_clist
 {
   \keys_define:nn { food/fruit }
    {
      #1 .choice:,
      #1 / true .code:n = 
        { 
          \iow_term:x { you~gave~#1~the~value~##1}
          \toggletrue {fruit#1} 
        },
      #1 / false .code:n =
        { 
         \iow_term:x { you~gave~#1~the~value~##1}
         \togglefalse{fruit#1} 
        },
      #1 .default:n = {true}
    }
 }   
 
\ExplSyntaxOff


\begin{document}


\ExplSyntaxOn 
 \keys_set:nn {food}{fruit={apple, banana=true, cherry=false}} 
\ExplSyntaxOff   

\iftoggle{fruitapple}{Buy apples}{No apples}

\iftoggle{fruitcherry}{Buy cherries}{No cherries}
\end{document}

enter image description here

1
  • Perfect. +1 for showing how to process a list. I'd seen \keys_define could be split in the source of siunitx. Thanks to your answer I now understand having multiple \keys_define makes it multi-stage processing.
    – Doc Octal
    Commented Nov 28, 2022 at 3:24
0

I don't think that using l3keys makes sense here since that's more designed for "code". What you should use instead are nested "property lists" since those are designed to store data.

I'm not entirely sure what sort of interface you are looking for, but you can try this:

\documentclass[12pt]{article}

\ExplSyntaxOn

\prop_new:N \l_food_prop
\cs_generate_variant:Nn \prop_put_from_keyval:Nn { NV, Ne }

\NewDocumentCommand \setfood { m } {
    \prop_set_from_keyval:Nn \l_tmpa_prop { #1 }

    \prop_map_inline:Nn \l_tmpa_prop {
        \prop_get:NnNF \l_tmpb_prop { ##1 } {
            \prop_clear:N \l_tmpb_prop
        }

        \prop_clear:N \l_tmpb_prop
        \clist_set:Nn \l_tmpa_clist { ##2 }

        \clist_map_variable:NNn \l_tmpa_clist \l_tmpa_tl {
            \tl_if_in:NnF \l_tmpa_tl { = } {
                \tl_put_right:Nn \l_tmpa_tl { = false }
            }
            \prop_put_from_keyval:NV \l_tmpb_prop \l_tmpa_tl
        }

        \prop_put_from_keyval:Ne \l_food_prop { ##1 = \l_tmpb_prop }
    }
}

\tl_new:N \l_food_tl
\NewDocumentCommand \iffoodexists { m m m m } {
    \prop_get:NnN \l_food_prop { #1 } \l_tmpa_prop
    \prop_get:NnN \l_tmpa_prop { #2 } \l_food_tl

    \quark_if_no_value:NTF \l_food_tl {
        #4
    } {
        #3
    }
}

\NewDocumentCommand \foodvalue { m m } {
    \iffoodexists { #1 } { #2 } { \l_food_tl } { (empty) }
}

\ExplSyntaxOff


\begin{document}
    \setfood{
        fruit={
            apple,
            banana=true,
        },
        vegetable={
            avocado=false,
        },
    }

    fruit/apple: \foodvalue{fruit}{apple}

    fruit/banana: \foodvalue{fruit}{banana}

    fruit/cherry: \foodvalue{fruit}{cherry}

    vegetable/avocado: \foodvalue{vegetable}{avocado}

    \setfood{fruit={apple=true}, spice={allspice}}

    fruit/apple: \foodvalue{fruit}{apple}

    spice/allspice: \foodvalue{spice}{allspice}

    animal/aardvark: \iffoodexists{animal}{aardvark}{Aardvark!}{That's not a food!}
\end{document}

output demonstration

1
  • 1
    Thanks @Max Chernoff. This is a good suggestion for a command. I prefer package interfaces that use a setup command instead of parameters but in this case it had to be parameters.
    – Doc Octal
    Commented Nov 28, 2022 at 3:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .