% \iffalse meta-comment % %% File: l3keys.dtx % % Copyright (C) 2006-2020 The LaTeX3 Project % % It may be distributed and/or modified under the conditions of the % LaTeX Project Public License (LPPL), either version 1.3c of this % license or (at your option) any later version. The latest version % of this license is in the file % % https://www.latex-project.org/lppl.txt % % This file is part of the "l3kernel bundle" (The Work in LPPL) % and all files in that bundle must be distributed together. % % ----------------------------------------------------------------------- % % The development version of the bundle can be found at % % https://github.com/latex3/latex3 % % for those people who are interested. % %<*driver> \documentclass[full,kernel]{l3doc} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{^^A % The \pkg{l3keys} package\\ Key--value interfaces^^A % } % % \author{^^A % The \LaTeX3 Project\thanks % {^^A % E-mail: % \href{mailto:latex-team@latex-project.org} % {latex-team@latex-project.org}^^A % }^^A % } % % \date{Released 2020-06-18} % % \maketitle % % \begin{documentation} % % The key--value method is a popular system for creating large numbers % of settings for controlling function or package behaviour. The % system normally results in input of the form % \begin{verbatim} % \MyModuleSetup{ % key-one = value one, % key-two = value two % } % \end{verbatim} % or % \begin{verbatim} % \MyModuleMacro[ % key-one = value one, % key-two = value two % ]{argument} % \end{verbatim} % for the user. % % The high level functions here are intended as a method to create % key--value controls. Keys are themselves created using a key--value % interface, minimising the number of functions and arguments % required. Each key is created by setting one or more \emph{properties} % of the key: % \begin{verbatim} % \keys_define:nn { mymodule } % { % key-one .code:n = code including parameter #1, % key-two .tl_set:N = \l_mymodule_store_tl % } % \end{verbatim} % These values can then be set as with other key--value approaches: % \begin{verbatim} % \keys_set:nn { mymodule } % { % key-one = value one, % key-two = value two % } % \end{verbatim} % % At a document level, \cs{keys_set:nn} is used within a % document function, for example % \begin{verbatim} % \DeclareDocumentCommand \MyModuleSetup { m } % { \keys_set:nn { mymodule } { #1 } } % \DeclareDocumentCommand \MyModuleMacro { o m } % { % \group_begin: % \keys_set:nn { mymodule } { #1 } % % Main code for \MyModuleMacro % \group_end: % } % \end{verbatim} % % Key names may contain any tokens, as they are handled internally % using \cs{tl_to_str:n}. As discussed in % section~\ref{sec:l3keys:subdivision}, it is suggested that the character % |/| is reserved for sub-division of keys into logical % groups. Functions and variables are \emph{not} expanded when creating % key names, and so % \begin{verbatim} % \tl_set:Nn \l_mymodule_tmp_tl { key } % \keys_define:nn { mymodule } % { % \l_mymodule_tmp_tl .code:n = code % } % \end{verbatim} % creates a key called |\l_mymodule_tmp_tl|, and not one called % \texttt{key}. % % \section{Creating keys} % % \begin{function}[updated = 2017-11-14]{\keys_define:nn} % \begin{syntax} % \cs{keys_define:nn} \Arg{module} \Arg{keyval list} % \end{syntax} % Parses the \meta{keyval list} and defines the keys listed there for % \meta{module}. The \meta{module} name is treated as a string. % In practice the % \meta{module} should be chosen to be unique to the module in question % (unless deliberately adding keys to an existing module). % % The \meta{keyval list} should consist of one or more key names along % with an associated key \emph{property}. The properties of a key % determine how it acts. The individual properties are described % in the following text; a typical use of \cs{keys_define:nn} might % read % \begin{verbatim} % \keys_define:nn { mymodule } % { % keyname .code:n = Some~code~using~#1, % keyname .value_required:n = true % } % \end{verbatim} % where the properties of the key begin from the |.| after the key % name. % \end{function} % % The various properties available take either no arguments at % all, or require one or more arguments. This is indicated in the % name of the property using an argument specification. In the following % discussion, each property is illustrated attached to an % arbitrary \meta{key}, which when used may be supplied with a % \meta{value}. All key \emph{definitions} are local. % % Key properties are applied in the reading order and so the ordering % is significant. Key properties which define \enquote{actions}, such % as |.code:n|, |.tl_set:N|, \emph{etc.}, override one another. % Some other properties are mutually exclusive, notably |.value_required:n| % and |.value_forbidden:n|, and so they replace one another. However, % properties covering non-exclusive behaviours may be given in any order. Thus % for example the following definitions are equivalent. % \begin{verbatim} % \keys_define:nn { mymodule } % { % keyname .code:n = Some~code~using~#1, % keyname .value_required:n = true % } % \keys_define:nn { mymodule } % { % keyname .value_required:n = true, % keyname .code:n = Some~code~using~#1 % } % \end{verbatim} % Note that with the exception of the special |.undefine:| property, all % key properties define the key within the current \TeX{} scope. % % \begin{function}[updated = 2013-07-08] % {.bool_set:N, .bool_set:c, .bool_gset:N, .bool_gset:c} % \begin{syntax} % \meta{key} .bool_set:N = \meta{boolean} % \end{syntax} % Defines \meta{key} to set \meta{boolean} to \meta{value} (which % must be either \texttt{true} or \texttt{false}). If the variable % does not exist, it will be created globally at the point that % the key is set up. % \end{function} % % \begin{function}[added = 2011-08-28, updated = 2013-07-08] % { % .bool_set_inverse:N, .bool_set_inverse:c, % .bool_gset_inverse:N, .bool_gset_inverse:c % } % \begin{syntax} % \meta{key} .bool_set_inverse:N = \meta{boolean} % \end{syntax} % Defines \meta{key} to set \meta{boolean} to the logical % inverse of \meta{value} (which must be either \texttt{true} or % \texttt{false}). % If the \meta{boolean} does not exist, it will be created globally % at the point that the key is set up. % \end{function} % % \begin{function}{.choice:} % \begin{syntax} % \meta{key} .choice: % \end{syntax} % Sets \meta{key} to act as a choice key. Each valid choice % for \meta{key} must then be created, as discussed in % section~\ref{sec:l3keys:choice}. % \end{function} % % \begin{function}[added = 2011-08-21, updated = 2013-07-10] % {.choices:nn, .choices:Vn, .choices:on, .choices:xn} % \begin{syntax} % \meta{key} .choices:nn = \Arg{choices} \Arg{code} % \end{syntax} % Sets \meta{key} to act as a choice key, and defines a series \meta{choices} % which are implemented using the \meta{code}. Inside \meta{code}, % \cs{l_keys_choice_tl} will be the name of the choice made, and % \cs{l_keys_choice_int} will be the position of the choice in the list % of \meta{choices} (indexed from~$1$). % Choices are discussed in detail in section~\ref{sec:l3keys:choice}. % \end{function} % % \begin{function}[added = 2011-09-11] % {.clist_set:N, .clist_set:c, .clist_gset:N, .clist_gset:c} % \begin{syntax} % \meta{key} .clist_set:N = \meta{comma list variable} % \end{syntax} % Defines \meta{key} to set \meta{comma list variable} to \meta{value}. % Spaces around commas and empty items will be stripped. % If the variable does not exist, it % is created globally at the point that the key is set up. % \end{function} % % \begin{function}[updated = 2013-07-10]{.code:n} % \begin{syntax} % \meta{key} .code:n = \Arg{code} % \end{syntax} % Stores the \meta{code} for execution when \meta{key} is used. % The \meta{code} can include one parameter (|#1|), which will be the % \meta{value} given for the \meta{key}. % \end{function} % % \begin{function}[added = 2020-01-11] % { % .cs_set:Np, .cs_set:cp, % .cs_set_protected:Np, .cs_set_protected:cp, % .cs_gset:Np, .cs_gset:cp, % .cs_gset_protected:Np, .cs_gset_protected:cp, % } % \begin{syntax} % \meta{key} .cs_set:Np = \meta{control sequence} \meta{arg.~spec.} % \end{syntax} % Defines \meta{key} to set \meta{control sequence} to have \meta{arg.~spec.} % and replacement text \meta{value}. % \end{function} % % \begin{function}[updated = 2013-07-09] % {.default:n, .default:V, .default:o, .default:x} % \begin{syntax} % \meta{key} .default:n = \Arg{default} % \end{syntax} % Creates a \meta{default} value for \meta{key}, which is used if no % value is given. This will be used if only the key name is given, % but not if a blank \meta{value} is given: % \begin{verbatim} % \keys_define:nn { mymodule } % { % key .code:n = Hello~#1, % key .default:n = World % } % \keys_set:nn { mymodule } % { % key = Fred, % Prints 'Hello Fred' % key, % Prints 'Hello World' % key = , % Prints 'Hello ' % } % \end{verbatim} % The default does not affect keys where values are required or % forbidden. Thus a required value cannot be supplied by a default % value, and giving a default value for a key which cannot take a value % does not trigger an error. % \end{function} % % \begin{function}[updated = 2020-01-17] % {.dim_set:N, .dim_set:c, .dim_gset:N, .dim_gset:c} % \begin{syntax} % \meta{key} .dim_set:N = \meta{dimension} % \end{syntax} % Defines \meta{key} to set \meta{dimension} to \meta{value} (which % must a dimension expression). If the variable does not exist, it % is created globally at the point that the key is set up. The key will % require a value at point-of-use unless a default is set. % \end{function} % % \begin{function}[updated = 2020-01-17] % {.fp_set:N, .fp_set:c, .fp_gset:N, .fp_gset:c} % \begin{syntax} % \meta{key} .fp_set:N = \meta{floating point} % \end{syntax} % Defines \meta{key} to set \meta{floating point} to \meta{value} % (which must a floating point expression). If the variable does not exist, % it is created globally at the point that the key is set up. The key will % require a value at point-of-use unless a default is set. % \end{function} % % \begin{function}[added = 2013-07-14] % {.groups:n} % \begin{syntax} % \meta{key} .groups:n = \Arg{groups} % \end{syntax} % Defines \meta{key} as belonging to the \meta{groups} declared. Groups % provide a \enquote{secondary axis} for selectively setting keys, and are % described in Section~\ref{sec:l3keys:selective}. % \end{function} % % \begin{function}[added = 2016-11-22]{.inherit:n} % \begin{syntax} % \meta{key} .inherit:n = \Arg{parents} % \end{syntax} % Specifies that the \meta{key} path should inherit the keys listed % as \meta{parents}. For example, after setting % \begin{verbatim} % \keys_define:nn { foo } { test .code:n = \tl_show:n {#1} } % \keys_define:nn { } { bar .inherit:n = foo } % \end{verbatim} % setting % \begin{verbatim} % \keys_set:nn { bar } { test = a } % \end{verbatim} % will be equivalent to % \begin{verbatim} % \keys_set:nn { foo } { test = a } % \end{verbatim} % \end{function} % % \begin{function}[updated = 2013-07-09] % {.initial:n, .initial:V, .initial:o, .initial:x} % \begin{syntax} % \meta{key} .initial:n = \Arg{value} % \end{syntax} % Initialises the \meta{key} with the \meta{value}, equivalent to % \begin{quote} % \cs{keys_set:nn} \Arg{module} \{ \meta{key} = \meta{value} \} % \end{quote} % \end{function} % % \begin{function}[updated = 2020-01-17] % {.int_set:N, .int_set:c, .int_gset:N, .int_gset:c} % \begin{syntax} % \meta{key} .int_set:N = \meta{integer} % \end{syntax} % Defines \meta{key} to set \meta{integer} to \meta{value} (which % must be an integer expression). If the variable does not exist, it % is created globally at the point that the key is set up. The key will % require a value at point-of-use unless a default is set. % \end{function} % % \begin{function}[updated = 2013-07-10]{.meta:n} % \begin{syntax} % \meta{key} .meta:n = \Arg{keyval list} % \end{syntax} % Makes \meta{key} a meta-key, which will set \meta{keyval list} in % one go. The \meta{keyval list} can refer as |#1| to the value given % at the time the \meta{key} is used (or, if no value is given, the % \meta{key}'s default value). % \end{function} % % \begin{function}[added = 2013-07-10]{.meta:nn} % \begin{syntax} % \meta{key} .meta:nn = \Arg{path} \Arg{keyval list} % \end{syntax} % Makes \meta{key} a meta-key, which will set \meta{keyval list} in % one go using the \meta{path} in place of the current one. The % \meta{keyval list} can refer as |#1| to the value given at the time % the \meta{key} is used (or, if no value is given, the \meta{key}'s % default value). % \end{function} % % \begin{function}[added = 2011-08-21]{.multichoice:} % \begin{syntax} % \meta{key} .multichoice: % \end{syntax} % Sets \meta{key} to act as a multiple choice key. Each valid choice % for \meta{key} must then be created, as discussed in % section~\ref{sec:l3keys:choice}. % \end{function} % % \begin{function}[added = 2011-08-21, updated = 2013-07-10] % {.multichoices:nn, .multichoices:Vn, .multichoices:on, .multichoices:xn} % \begin{syntax} % \meta{key} .multichoices:nn \Arg{choices} \Arg{code} % \end{syntax} % Sets \meta{key} to act as a multiple choice key, and defines a series % \meta{choices} % which are implemented using the \meta{code}. Inside \meta{code}, % \cs{l_keys_choice_tl} will be the name of the choice made, and % \cs{l_keys_choice_int} will be the position of the choice in the list % of \meta{choices} (indexed from~$1$). % Choices are discussed in detail in section~\ref{sec:l3keys:choice}. % \end{function} % % \begin{function}[added = 2019-05-05, updated = 2020-01-17] % {.muskip_set:N, .muskip_set:c, .muskip_gset:N, .muskip_gset:c} % \begin{syntax} % \meta{key} .muskip_set:N = \meta{muskip} % \end{syntax} % Defines \meta{key} to set \meta{muskip} to \meta{value} (which % must be a muskip expression). If the variable does not exist, it % is created globally at the point that the key is set up. The key will % require a value at point-of-use unless a default is set. % \end{function} % % \begin{function}[added = 2019-01-31] % {.prop_put:N, .prop_put:c, .prop_gput:N, .prop_gput:c} % \begin{syntax} % \meta{key} .prop_put:N = \meta{property list} % \end{syntax} % Defines \meta{key} to put the \meta{value} onto the \meta{property list} % stored under the \meta{key}. % If the variable does not exist, it % is created globally at the point that the key is set up. % \end{function} % % \begin{function}[updated = 2020-01-17] % {.skip_set:N, .skip_set:c, .skip_gset:N, .skip_gset:c} % \begin{syntax} % \meta{key} .skip_set:N = \meta{skip} % \end{syntax} % Defines \meta{key} to set \meta{skip} to \meta{value} (which % must be a skip expression). If the variable does not exist, it % is created globally at the point that the key is set up. The key will % require a value at point-of-use unless a default is set. % \end{function} % % \begin{function}{.tl_set:N, .tl_set:c, .tl_gset:N, .tl_gset:c} % \begin{syntax} % \meta{key} .tl_set:N = \meta{token list variable} % \end{syntax} % Defines \meta{key} to set \meta{token list variable} to \meta{value}. % If the variable does not exist, it is created globally % at the point that the key is set up. % \end{function} % % \begin{function}{.tl_set_x:N, .tl_set_x:c, .tl_gset_x:N, .tl_gset_x:c} % \begin{syntax} % \meta{key} .tl_set_x:N = \meta{token list variable} % \end{syntax} % Defines \meta{key} to set \meta{token list variable} to \meta{value}, % which will be subjected to an \texttt{x}-type expansion % (\emph{i.e.}~using \cs{tl_set:Nx}). If the variable does not exist, % it is created globally at the point that the key is set up. % \end{function} % % \begin{function}[added = 2015-07-14]{.undefine:} % \begin{syntax} % \meta{key} .undefine: % \end{syntax} % Removes the definition of the \meta{key} within the current scope. % \end{function} % % \begin{function}[added = 2015-07-14]{.value_forbidden:n} % \begin{syntax} % \meta{key} .value_forbidden:n = \texttt{true\string|false} % \end{syntax} % Specifies that \meta{key} cannot receive a \meta{value} when used. % If a \meta{value} is given then an error will be issued. Setting % the property \texttt{false} cancels the restriction. % \end{function} % % \begin{function}[added = 2015-07-14]{.value_required:n} % \begin{syntax} % \meta{key} .value_required:n = \texttt{true\string|false} % \end{syntax} % Specifies that \meta{key} must receive a \meta{value} when used. % If a \meta{value} is not given then an error will be issued. Setting % the property \texttt{false} cancels the restriction. % \end{function} % % \section{Sub-dividing keys} % \label{sec:l3keys:subdivision} % % When creating large numbers of keys, it may be desirable to divide % them into several sub-groups for a given module. This can be achieved % either by adding a sub-division to the module name: % \begin{verbatim} % \keys_define:nn { mymodule / subgroup } % { key .code:n = code } % \end{verbatim} % or to the key name: % \begin{verbatim} % \keys_define:nn { mymodule } % { subgroup / key .code:n = code } % \end{verbatim} % As illustrated, the best choice of token for sub-dividing keys in % this way is |/|. This is because of the method that is % used to represent keys internally. Both of the above code fragments % set the same key, which has full name \texttt{mymodule/subgroup/key}. % % As illustrated in the next section, this subdivision is % particularly relevant to making multiple choices. % % \section{Choice and multiple choice keys} % \label{sec:l3keys:choice} % % The \pkg{l3keys} system supports two types of choice key, in which a series % of pre-defined input values are linked to varying implementations. Choice % keys are usually created so that the various values are mutually-exclusive: % only one can apply at any one time. \enquote{Multiple} choice keys are also % supported: these allow a selection of values to be chosen at the same time. % % Mutually-exclusive choices are created by setting the \texttt{.choice:} % property: % \begin{verbatim} % \keys_define:nn { mymodule } % { key .choice: } % \end{verbatim} % For keys which are set up as choices, the valid choices are generated % by creating sub-keys of the choice key. This can be carried out in % two ways. % % In many cases, choices execute similar code which is dependant only % on the name of the choice or the position of the choice in the % list of all possibilities. Here, the keys can share the same code, and can % be rapidly created using the \texttt{.choices:nn} property. % \begin{verbatim} % \keys_define:nn { mymodule } % { % key .choices:nn = % { choice-a, choice-b, choice-c } % { % 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. % } % } % \end{verbatim} % The index \cs{l_keys_choice_int} in the list of choices starts at~$1$. % % \begin{variable}{\l_keys_choice_int, \l_keys_choice_tl} % Inside the code block for a choice generated using \texttt{.choices:nn}, % the variables \cs{l_keys_choice_tl} and \cs{l_keys_choice_int} are % available to indicate the name of the current choice, and its position in % the comma list. The position is indexed from~$1$. Note that, as with % standard key code generated using \texttt{.code:n}, the value passed to % the key (i.e.~the choice name) is also available as |#1|. % \end{variable} % % On the other hand, it is sometimes useful to create choices which % use entirely different code from one another. This can be achieved % by setting the \texttt{.choice:} property of a key, then manually % defining sub-keys. % \begin{verbatim} % \keys_define:nn { mymodule } % { % key .choice:, % key / choice-a .code:n = code-a, % key / choice-b .code:n = code-b, % key / choice-c .code:n = code-c, % } % \end{verbatim} % % It is possible to mix the two methods, but manually-created choices % should \emph{not} use \cs{l_keys_choice_tl} or \cs{l_keys_choice_int}. % These variables do not have defined behaviour when used outside of % code created using \texttt{.choices:nn} % (\emph{i.e.}~anything might happen). % % It is possible to allow choice keys to take values which have not previously % been defined by adding code for the special \texttt{unknown} choice. The % general behavior of the \texttt{unknown} key is described in % Section~\ref{sec:l3keys:unknown}. A typical example in the case of a choice % would be to issue a custom error message: % \begin{verbatim} % \keys_define:nn { mymodule } % { % key .choice:, % key / choice-a .code:n = code-a, % key / choice-b .code:n = code-b, % key / choice-c .code:n = code-c, % key / unknown .code:n = % \msg_error:nnxxx { mymodule } { unknown-choice } % { key } % Name of choice key % { choice-a , choice-b , choice-c } % Valid choices % { \exp_not:n {#1} } % Invalid choice given % % % % % } % \end{verbatim} % % Multiple choices are created in a very similar manner to mutually-exclusive % choices, using the properties \texttt{.multichoice:} and % \texttt{.multichoices:nn}. As with mutually exclusive choices, multiple % choices are define as sub-keys. Thus both % \begin{verbatim} % \keys_define:nn { mymodule } % { % key .multichoices:nn = % { choice-a, choice-b, choice-c } % { % 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. % } % } % \end{verbatim} % and % \begin{verbatim} % \keys_define:nn { mymodule } % { % key .multichoice:, % key / choice-a .code:n = code-a, % key / choice-b .code:n = code-b, % key / choice-c .code:n = code-c, % } % \end{verbatim} % are valid. % % When a multiple choice key is set % \begin{verbatim} % \keys_set:nn { mymodule } % { % key = { a , b , c } % 'key' defined as a multiple choice % } % \end{verbatim} % each choice is applied in turn, equivalent to a \texttt{clist} mapping or % to applying each value individually: % \begin{verbatim} % \keys_set:nn { mymodule } % { % key = a , % key = b , % key = c , % } % \end{verbatim} % Thus each separate choice will have passed to it the % \cs{l_keys_choice_tl} and \cs{l_keys_choice_int} in exactly % the same way as described for \texttt{.choices:nn}. % % \section{Setting keys} % % \begin{function}[updated = 2017-11-14] % {\keys_set:nn, \keys_set:nV, \keys_set:nv, \keys_set:no} % \begin{syntax} % \cs{keys_set:nn} \Arg{module} \Arg{keyval list} % \end{syntax} % Parses the \meta{keyval list}, and sets those keys which are defined % for \meta{module}. The behaviour on finding an unknown key can be set % by defining a special \texttt{unknown} key: this is illustrated % later. % \end{function} % % \begin{variable}[updated = 2020-02-08] % {\l_keys_key_str, \l_keys_path_str, \l_keys_value_tl} % For each key processed, information of the full \emph{path} of the % key, the \emph{name} of the key and the \emph{value} of the key is % available within three token list variables. These may be used within % the code of the key. % % The \emph{value} is everything after the \texttt{=}, which may be % empty if no value was given. This is stored in \cs{l_keys_value_tl}, and % is not processed in any way by \cs{keys_set:nn}. % % The \emph{path} of the key is a \enquote{full} description of the key, % and is unique for each key. It consists of the module and full key name, % thus for example % \begin{verbatim} % \keys_set:nn { mymodule } { key-a = some-value } % \end{verbatim} % has path \texttt{mymodule/key-a} while % \begin{verbatim} % \keys_set:nn { mymodule } { subset / key-a = some-value } % \end{verbatim} % has path \texttt{mymodule/subset/key-a}. This information is stored in % \cs{l_keys_path_str}. % % The \emph{name} of the key is the part of the path after the last % \texttt{/}, and thus is not unique. In the preceding examples, both keys % have name \texttt{key-a} despite having different paths. This information % is stored in \cs{l_keys_key_str}. % \end{variable} % % \section{Handling of unknown keys} % \label{sec:l3keys:unknown} % % If a key has not previously been defined (is unknown), \cs{keys_set:nn} % looks for a special \texttt{unknown} key for the same module, and if this is % not defined raises an error indicating that the key name was unknown. This % mechanism can be used for example to issue custom error texts. % \begin{verbatim} % \keys_define:nn { mymodule } % { % unknown .code:n = % You~tried~to~set~key~'\l_keys_key_str'~to~'#1'. % } % \end{verbatim} % % \begin{function}[added = 2011-08-23, updated = 2019-01-29] % { % \keys_set_known:nn, \keys_set_known:nV, % \keys_set_known:nv, \keys_set_known:no, % \keys_set_known:nnN, \keys_set_known:nVN, % \keys_set_known:nvN, \keys_set_known:noN, % \keys_set_known:nnnN, \keys_set_known:nVnN, % \keys_set_known:nvnN, \keys_set_known:nonN % } % \begin{syntax} % \cs{keys_set_known:nn} \Arg{module} \Arg{keyval list} % \cs{keys_set_known:nnN} \Arg{module} \Arg{keyval list} \meta{tl} % \cs{keys_set_known:nnnN} \Arg{module} \Arg{keyval list} \Arg{root} \meta{tl} % \end{syntax} % These functions set keys which are known for the \meta{module}, and % simply ignore other keys. The \cs{keys_set_known:nn} function parses the % \meta{keyval list}, and sets those keys which are defined for % \meta{module}. Any keys which are unknown are not processed further by % the parser. In addition, \cs{keys_set_known:nnN} stores the key--value % pairs in the \meta{tl} in comma-separated form (\emph{i.e.}~an edited % version of the \meta{keyval list}). When a \meta{root} is given % (\cs{keys_set_known:nnnN}), the key--value entries are returned % relative to this point in the key tree. When it is absent, only the % key name and value are provided. The correct list is returned by % nested calls. % \end{function} % % \section{Selective key setting} % \label{sec:l3keys:selective} % % In some cases it may be useful to be able to select only some keys for % setting, even though these keys have the same path. For example, with % a set of keys defined using % \begin{verbatim} % \keys define:nn { mymodule } % { % key-one .code:n = { \my_func:n {#1} } , % key-two .tl_set:N = \l_my_a_tl , % key-three .tl_set:N = \l_my_b_tl , % key-four .fp_set:N = \l_my_a_fp , % } % \end{verbatim} % the use of \cs{keys_set:nn} attempts to set all four keys. However, in % some contexts it may only be sensible to set some keys, or to control the % order of setting. To do this, keys may be assigned to \emph{groups}: % arbitrary sets which are independent of the key tree. Thus modifying the % example to read % \begin{verbatim} % \keys define:nn { mymodule } % { % key-one .code:n = { \my_func:n {#1} } , % key-one .groups:n = { first } , % key-two .tl_set:N = \l_my_a_tl , % key-two .groups:n = { first } , % key-three .tl_set:N = \l_my_b_tl , % key-three .groups:n = { second } , % key-four .fp_set:N = \l_my_a_fp , % } % \end{verbatim} % assigns \texttt{key-one} and \texttt{key-two} to group \texttt{first}, % \texttt{key-three} to group \texttt{second}, while \texttt{key-four} is % not assigned to a group. % % Selective key setting may be achieved either by selecting one or more % groups to be made \enquote{active}, or by marking one or more groups to % be ignored in key setting. % % \begin{function}[added = 2013-07-14, updated = 2019-01-29] % { % \keys_set_filter:nnn, \keys_set_filter:nnV, % \keys_set_filter:nnv, \keys_set_filter:nno, % \keys_set_filter:nnnN, \keys_set_filter:nnVN, % \keys_set_filter:nnvN, \keys_set_filter:nnoN, % \keys_set_filter:nnnnN, \keys_set_filter:nnVnN, % \keys_set_filter:nnvnN, \keys_set_filter:nnonN, % } % \begin{syntax} % \cs{keys_set_filter:nnn} \Arg{module} \Arg{groups} \Arg{keyval list} % \cs{keys_set_filter:nnnN} \Arg{module} \Arg{groups} \Arg{keyval list} \meta{tl} % \cs{keys_set_filter:nnnnN} \Arg{module} \Arg{groups} \Arg{keyval list} \meta{root} \meta{tl} % \end{syntax} % Activates key filtering in an \enquote{opt-out} sense: keys assigned to any % of the \meta{groups} specified are ignored. The \meta{groups} are % given as a comma-separated list. Unknown keys are not assigned to any % group and are thus always set. The key--value pairs for each % key which is filtered out are stored in the \meta{tl} in a % comma-separated form (\emph{i.e.}~an edited version of the \meta{keyval % list}). The \cs{keys_set_filter:nnn} version skips this stage. % % Use of \cs{keys_set_filter:nnnN} can be nested, with the correct residual % \meta{keyval list} returned at each stage. In the version which takes % a \meta{root} argument, the key list is returned relative to that point % in the key tree. In the cases without a \meta{root} argument, only % the key names and values are returned. % \end{function} % % \begin{function}[added = 2013-07-14, updated = 2017-05-27] % { % \keys_set_groups:nnn, \keys_set_groups:nnV, % \keys_set_groups:nnv, \keys_set_groups:nno % } % \begin{syntax} % \cs{keys_set_groups:nnn} \Arg{module} \Arg{groups} \Arg{keyval list} % \end{syntax} % Activates key filtering in an \enquote{opt-in} sense: only keys assigned to % one or more of the \meta{groups} specified are set. The \meta{groups} are % given as a comma-separated list. Unknown keys are not assigned to any % group and are thus never set. % \end{function} % % \section{Utility functions for keys} % % \begin{function}[EXP, pTF, updated = 2017-11-14]{\keys_if_exist:nn} % \begin{syntax} % \cs{keys_if_exist_p:nn} \Arg{module} \Arg{key} \\ % \cs{keys_if_exist:nnTF} \Arg{module} \Arg{key} \Arg{true code} \Arg{false code} % \end{syntax} % Tests if the \meta{key} exists for \meta{module}, \emph{i.e.}~if any code % has been defined for \meta{key}. % \end{function} % % \begin{function}[added = 2011-08-21,EXP,pTF, updated = 2017-11-14] % {\keys_if_choice_exist:nnn} % \begin{syntax} % \cs{keys_if_choice_exist_p:nnn} \Arg{module} \Arg{key} \Arg{choice} \\ % \cs{keys_if_choice_exist:nnnTF} \Arg{module} \Arg{key} \Arg{choice} \Arg{true code} \Arg{false code} % \end{syntax} % Tests if the \meta{choice} is defined for the \meta{key} within the % \meta{module}, \emph{i.e.}~if any code has been defined for % \meta{key}/\meta{choice}. The test is \texttt{false} if the \meta{key} % itself is not defined. % \end{function} % % \begin{function}[updated = 2015-08-09]{\keys_show:nn} % \begin{syntax} % \cs{keys_show:nn} \Arg{module} \Arg{key} % \end{syntax} % Displays in the terminal % the information associated to the \meta{key} for a \meta{module}, % including the function which is used to actually implement it. % \end{function} % % \begin{function}[added = 2014-08-22, updated = 2015-08-09]{\keys_log:nn} % \begin{syntax} % \cs{keys_log:nn} \Arg{module} \Arg{key} % \end{syntax} % Writes in the log file the information associated to the \meta{key} % for a \meta{module}. See also \cs{keys_show:nn} which displays the % result in the terminal. % \end{function} % % \section{Low-level interface for parsing key--val lists} % % To re-cap from earlier, a key--value list is input of the form % \begin{verbatim} % KeyOne = ValueOne , % KeyTwo = ValueTwo , % KeyThree % \end{verbatim} % where each key--value pair is separated by a comma from the rest of % the list, and each key--value pair does not necessarily contain an % equals sign or a value! Processing this type of input correctly % requires a number of careful steps, to correctly account for % braces, spaces and the category codes of separators. % % While the functions described earlier are used as a high-level interface % for processing such input, in special circumstances you may wish to use % a lower-level approach. % The low-level parsing system converts a \meta{key--value list} % into \meta{keys} and associated \meta{values}. After the parsing phase % is completed, the resulting keys and values (or keys alone) are % available for further processing. This processing is not carried out by the % low-level parser itself, and so the parser requires the names of % two functions along with the key--value list. One function is % needed to process key--value pairs (it receives two arguments), % and a second function is required for keys given without any value % (it is called with a single argument). % % The parser does not double |#| tokens or expand any input. Active % tokens |=| and |,| appearing at the outer level of braces are converted % to category \enquote{other} (12) so that the parser does not \enquote{miss} % any due to category code changes. Spaces are removed from the ends % of the keys and values. Keys and values which are given in braces % have exactly one set removed (after space trimming), thus % \begin{verbatim} % key = {value here}, % \end{verbatim} % and % \begin{verbatim} % key = value here, % \end{verbatim} % are treated identically. % % \begin{function}[EXP,updated = 2020-02-20]{\keyval_parse:NNn} % \begin{syntax} % \cs{keyval_parse:NNn} \meta{function_1} \meta{function_2} \Arg{key--value list} % \end{syntax} % Parses the \meta{key--value list} into a series of \meta{keys} and % associated \meta{values}, or keys alone (if no \meta{value} was % given). \meta{function_1} should take one argument, while % \meta{function_2} should absorb two arguments. After % \cs{keyval_parse:NNn} has parsed the \meta{key--value list}, % \meta{function_1} is used to process keys given with no value % and \meta{function_2} is used to process keys given with a % value. The order of the \meta{keys} in the \meta{key--value list} % is preserved. Thus % \begin{verbatim} % \keyval_parse:NNn \function:n \function:nn % { key1 = value1 , key2 = value2, key3 = , key4 } % \end{verbatim} % is converted into an input stream % \begin{verbatim} % \function:nn { key1 } { value1 } % \function:nn { key2 } { value2 } % \function:nn { key3 } { } % \function:n { key4 } % \end{verbatim} % Note that there is a difference between an empty value (an equals % sign followed by nothing) and a missing value (no equals sign at % all). Spaces are trimmed from the ends of the \meta{key} and \meta{value}, % then one \emph{outer} set of braces is removed from the \meta{key} % and \meta{value} as part of the processing. % \begin{texnote} % The result is returned within \cs{exp_not:n}, which means that the % converted input stream does not expand further when appearing in an % \texttt{x}-type or \texttt{e}-type argument expansion. % \end{texnote} % \end{function} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3keys} Implementation} % % \begin{macrocode} %<*initex|package> % \end{macrocode} % % \subsection{Low-level interface} % % The low-level key parser's implementation is based heavily on \pkg{expkv}. % Compared to \pkg{keyval} it adds a number of additional \enquote{safety} % requirements and allows to process the parsed list of key--value pairs in a % variety of ways. The net result is that this code needs around one and a half % the amount of time as \pkg{keyval} to parse the same list of keys. To optimise % speed as far as reasonably practical, a number of lower-level approaches are % taken rather than using the higher-level \pkg{expl3} interfaces. % % \begin{macrocode} %<@@=keyval> % \end{macrocode} % % \begin{variable}{\s_@@_nil,\s_@@_mark,\s_@@_stop,\s_@@_tail} % \begin{macrocode} \scan_new:N \s_@@_nil \scan_new:N \s_@@_mark \scan_new:N \s_@@_stop \scan_new:N \s_@@_tail % \end{macrocode} % \end{variable} % % This temporary macro will be used since some of the definitions will need an % active comma or equals sign. Inside of this macro |#1| will be the active % comma and |#2| will be the active equals sign. % \begin{macrocode} \group_begin: \cs_set_protected:Npn \@@_tmp:NN #1#2 { % \end{macrocode} % % \begin{macro}[EXP]{\keyval_parse:NNn} % The main function starts the first of two loops. The outer loop splits the % key--value list at active commas, the inner loop will do so at other commas. % The use of \cs{s_@@_mark} here prevents loss of braces from the key % argument. % \begin{macrocode} \cs_new:Npn \keyval_parse:NNn ##1 ##2 ##3 { \@@_loop_active:NNw ##1 ##2 \s_@@_mark ##3 #1 \s_@@_tail #1 } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_loop_active:NNw} % First a fast test for the end of the loop is done, it'll gobble everything % up to a \cs{s_@@_tail}. The loop ending macro will gobble everything to the % last \cs{s_@@_mark} in this definition. % If the end isn't reached yet, start the second loop splitting at other % commas, and after that one iterate the current loop. % \begin{macrocode} \cs_new:Npn \@@_loop_active:NNw ##1 ##2 ##3 #1 { \@@_if_recursion_tail:w ##3 \@@_end_loop_active:w \s_@@_tail \@@_loop_other:NNw ##1 ##2 ##3 , \s_@@_tail , \@@_loop_active:NNw ##1 ##2 \s_@@_mark } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_split_other:w, \@@_split_active:w} % These two macros allow to split at the first equals sign of category 12 or % 13. At the same time they also execute branching by inserting the first % token following \cs{s_@@_mark} that followed the equals sign. Hence they % also test for the presence of such an equals sign simultaneously. % \begin{macrocode} \cs_new:Npn \@@_split_other:w ##1 = ##2 \s_@@_mark ##3 ##4 \s_@@_stop { ##3 ##1 \s_@@_stop \s_@@_mark ##2 } \cs_new:Npn \@@_split_active:w ##1 #2 ##2 \s_@@_mark ##3 ##4 \s_@@_stop { ##3 ##1 \s_@@_stop \s_@@_mark ##2 } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_loop_other:NNw} % The second loop uses the same test for its end as the first loop, next it % splits at the first active equals sign using \cs{@@_split_active:w}. The % \cs{s_@@_nil} prevents accidental brace stripping and acts as a delimiter in % the next steps. First testing for an active equals sign will reduce the % number of necessary expansion steps for the expected average use case of % other equals signs and hence perform better on average. % \begin{macrocode} \cs_new:Npn \@@_loop_other:NNw ##1 ##2 ##3 , { \@@_if_recursion_tail:w ##3 \@@_end_loop_other:w \s_@@_tail \@@_split_active:w ##3 \s_@@_nil \s_@@_mark \@@_split_active_auxi:w #2 \s_@@_mark \@@_clean_up_active:w \s_@@_stop ##1 ##2 \@@_loop_other:NNw ##1 ##2 \s_@@_mark } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_split_active_auxi:w} % \begin{macro}[EXP]{\@@_split_active_auxii:w} % \begin{macro}[EXP]{\@@_split_active_auxiii:w} % \begin{macro}[EXP]{\@@_split_active_auxiv:w} % \begin{macro}[EXP]{\@@_split_active_auxv:w} % After \cs{@@_split_active:w} the following will only be called if there was % at least one active equals sign in the current key--value pair. Therefore % this is the execution branch for a key--value pair with an active equals % sign. |##1| will be everything up to the first active equals sign. First it % tests for other equals signs in the key name, which will eventually throw an % error via \cs{@@_misplaced_equal_after_active_error:w}. If none was found we % forward the key to \cs{@@_split_active_auxii:w}. % \begin{macrocode} \cs_new:Npn \@@_split_active_auxi:w ##1 \s_@@_stop { \@@_split_other:w ##1 \s_@@_nil \s_@@_mark \@@_misplaced_equal_after_active_error:w = \s_@@_mark \@@_split_active_auxii:w \s_@@_stop } % \end{macrocode} % \cs{@@_split_active_auxii:w} gets the correct key name with a leading % \cs{s_@@_mark} as |##1|. It has to sanitise the remainder of the previous % test and trims the key name which will be forwarded to % \cs{@@_split_active_auxiii:w}. % \begin{macrocode} \cs_new:Npn \@@_split_active_auxii:w ##1 \s_@@_nil \s_@@_mark \@@_misplaced_equal_after_active_error:w \s_@@_stop \s_@@_mark { \@@_trim:nN { ##1 } \@@_split_active_auxiii:w } % \end{macrocode} % Next we test for a misplaced active equals sign in the value, if none is % found \cs{@@_split_active_auxiv:w} will be called. % \begin{macrocode} \cs_new:Npn \@@_split_active_auxiii:w ##1 ##2 \s_@@_nil { \@@_split_active:w ##2 \s_@@_nil \s_@@_mark \@@_misplaced_equal_in_split_error:w #2 \s_@@_mark \@@_split_active_auxiv:w \s_@@_stop { ##1 } } % \end{macrocode} % This runs the last test after sanitising the remainder of the previous one. % This time test for a misplaced equals sign of category 12 in the value. % Finally the last auxiliary macro will be called. % \begin{macrocode} \cs_new:Npn \@@_split_active_auxiv:w ##1 \s_@@_nil \s_@@_mark \@@_misplaced_equal_in_split_error:w \s_@@_stop \s_@@_mark { \@@_split_other:w ##1 \s_@@_nil \s_@@_mark \@@_misplaced_equal_in_split_error:w = \s_@@_mark \@@_split_active_auxv:w \s_@@_stop } % \end{macrocode} % This last macro in this execution branch sanitises the last test, trims the % value and passes it to \cs{@@_pair:nnNN}. % \begin{macrocode} \cs_new:Npn \@@_split_active_auxv:w ##1 \s_@@_nil \s_@@_mark \@@_misplaced_equal_in_split_error:w \s_@@_stop \s_@@_mark { \@@_trim:nN { ##1 } \@@_pair:nnNN } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\@@_clean_up_active:w} % The following is the branch taken if the key--value pair doesn't contain an % active equals sign. The remainder of that test will be cleaned up by % \cs{@@_clean_up_active:w} which will then split at an equals sign of % category other. % \begin{macrocode} \cs_new:Npn \@@_clean_up_active:w ##1 \s_@@_nil \s_@@_mark \@@_split_active_auxi:w \s_@@_stop \s_@@_mark { \@@_split_other:w ##1 \s_@@_nil \s_@@_mark \@@_split_other_auxi:w = \s_@@_mark \@@_clean_up_other:w \s_@@_stop } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_split_other_auxi:w} % \begin{macro}[EXP]{\@@_split_other_auxii:w} % \begin{macro}[EXP]{\@@_split_other_auxiii:w} % This is executed if the key--value pair doesn't contain an active equals % sign but at least one other. |##1| of \cs{@@_split_other_auxi:w} will % contain the complete key name, which is trimmed and forwarded to the next % auxiliary macro. % \begin{macrocode} \cs_new:Npn \@@_split_other_auxi:w ##1 \s_@@_stop { \@@_trim:nN { ##1 } \@@_split_other_auxii:w } % \end{macrocode} % We know that the value doesn't contain misplaced active equals signs but we % have to test for others. % \begin{macrocode} \cs_new:Npn \@@_split_other_auxii:w ##1 ##2 \s_@@_nil { \@@_split_other:w ##2 \s_@@_nil \s_@@_mark \@@_misplaced_equal_in_split_error:w = \s_@@_mark \@@_split_other_auxiii:w \s_@@_stop { ##1 } } % \end{macrocode} % \cs{@@_split_other_auxiii:w} sanitises the test for other equals signs, % trims the value and forwards it to \cs{@@_pair:nnNN}. % \begin{macrocode} \cs_new:Npn \@@_split_other_auxiii:w ##1 \s_@@_nil \s_@@_mark \@@_misplaced_equal_in_split_error:w \s_@@_stop \s_@@_mark { \@@_trim:nN { ##1 } \@@_pair:nnNN } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\@@_clean_up_other:w} % \cs{@@_clean_up_other:w} is the last branch that might exist. It is called % if no equals sign was found, hence the only possibilities left are a blank % list element, which is to be skipped, or a lonely key. If it's no empty list % element this will trim the key name and forward it to \cs{@@_key:nNN}. % \begin{macrocode} \cs_new:Npn \@@_clean_up_other:w ##1 \s_@@_nil \s_@@_mark \@@_split_other_auxi:w \s_@@_stop \s_@@_mark { \@@_if_blank:w ##1 \s_@@_nil \s_@@_stop \@@_blank_true:w \s_@@_mark \s_@@_stop \use:n { \@@_trim:nN { ##1 } \@@_key:nNN } } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_misplaced_equal_after_active_error:w} % \begin{macro}[EXP]{\@@_misplaced_equal_in_split_error:w} % All these two macros do is gobble the remainder of the current other loop % execution and throw an error. % \begin{macrocode} \cs_new:Npn \@@_misplaced_equal_after_active_error:w \s_@@_mark ##1 \s_@@_stop \s_@@_mark ##2 \s_@@_nil \s_@@_mark ##3 \s_@@_nil ##4 ##5 { \__kernel_msg_expandable_error:nn { kernel } { misplaced-equals-sign } } \cs_new:Npn \@@_misplaced_equal_in_split_error:w \s_@@_mark ##1 \s_@@_stop \s_@@_mark ##2 \s_@@_nil ##3 ##4 ##5 { \__kernel_msg_expandable_error:nn { kernel } { misplaced-equals-sign } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\@@_end_loop_other:w, \@@_end_loop_active:w} % All that's left for the parsing loops are the macros which end the % recursion. Both just gobble the remaining tokens of the respective loop % including the next recursion call. % \begin{macrocode} \cs_new:Npn \@@_end_loop_other:w \s_@@_tail \@@_split_active:w ##1 \s_@@_nil \s_@@_mark \@@_split_active_auxi:w #2 \s_@@_mark \@@_clean_up_active:w \s_@@_stop ##2 ##3 \@@_loop_other:NNw ##4 \s_@@_mark { } \cs_new:Npn \@@_end_loop_active:w \s_@@_tail \@@_loop_other:NNw ##1 , \s_@@_tail , \@@_loop_active:NNw ##2 \s_@@_mark { } % \end{macrocode} % \end{macro} % % The parsing loops are done, so here ends the definition of \cs{@@_tmp:NN}, % which will finally set up the macros. % \begin{macrocode} } \char_set_catcode_active:n { `\, } \char_set_catcode_active:n { `\= } \@@_tmp:NN , = \group_end: % \end{macrocode} % % \begin{macro}[EXP]{\@@_pair:nnNN, \@@_key:nNN} % These macros will be called on the parsed keys and values of the key--value % list. All arguments are completely trimmed. They test for blank key names % and call the functions passed to \cs{keyval_parse:NNn} inside of % \cs{exp_not:n} with the correct arguments. % \begin{macrocode} \cs_new:Npn \@@_pair:nnNN #1 #2 #3 #4 { \@@_if_blank:w \s_@@_mark #2 \s_@@_nil \s_@@_stop \@@_blank_key_error:w \s_@@_mark \s_@@_stop \exp_not:n { #4 { #2 } { #1 } } } \cs_new:Npn \@@_key:nNN #1 #2 #3 { \@@_if_blank:w \s_@@_mark #1 \s_@@_nil \s_@@_stop \@@_blank_key_error:w \s_@@_mark \s_@@_stop \exp_not:n { #2 { #1 } } } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_if_empty:w,\@@_if_blank:w,\@@_if_recursion_tail:w} % All these tests work by gobbling tokens until a certain combination is met, % which makes them pretty fast. The test for a blank argument should be called % with an arbitrary token following the argument. Each of these utilize the % fact that the argument will contain a leading \cs{s_@@_mark}. % \begin{macrocode} \cs_new:Npn \@@_if_empty:w #1 \s_@@_mark \s_@@_stop { } \cs_new:Npn \@@_if_blank:w \s_@@_mark #1 { \@@_if_empty:w \s_@@_mark } \cs_new:Npn \@@_if_recursion_tail:w \s_@@_mark #1 \s_@@_tail { } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_blank_true:w,\@@_blank_key_error:w} % These macros will be called if the tests above didn't gobble them, they % execute the branching. % \begin{macrocode} \cs_new:Npn \@@_blank_true:w \s_@@_mark \s_@@_stop \use:n #1 #2 #3 { } \cs_new:Npn \@@_blank_key_error:w \s_@@_mark \s_@@_stop \exp_not:n #1 { \__kernel_msg_expandable_error:nn { kernel } { blank-key-name } } % \end{macrocode} % \end{macro} % % Two messages for the low level parsing system. % \begin{macrocode} \__kernel_msg_new:nnn { kernel } { misplaced-equals-sign } { Misplaced~equals~sign~in~key-value~input~\msg_line_context: } \__kernel_msg_new:nnn { kernel } { blank-key-name } { Blank~key~name~in~key-value~input~\msg_line_context: } % \end{macrocode} % % \begin{macro}[EXP]{\@@_trim:nN} % \begin{macro}[EXP] % {\@@_trim_auxi:w,\@@_trim_auxii:w,\@@_trim_auxiii:w,\@@_trim_auxiv:w} % And an adapted version of \cs{__tl_trim_spaces:nn} which is a bit faster for % our use case, as it can strip the braces at the end. This is pretty much the % same concept, so I won't comment on it here. The speed gain by using this % instead of \cs{tl_trim_spaces_apply:nN} is about 10\,\% of the total time for % \cs{keyval_parse:NNn} with one key and one key--value pair, so I think it's % worth it. % \begin{macrocode} \group_begin: \cs_set_protected:Npn \@@_tmp:n #1 { \cs_new:Npn \@@_trim:nN ##1 { \@@_trim_auxi:w ##1 \s_@@_nil \s_@@_mark #1 { } \s_@@_mark \@@_trim_auxii:w \@@_trim_auxiii:w #1 \s_@@_nil \@@_trim_auxiv:w \s_@@_stop } \cs_new:Npn \@@_trim_auxi:w ##1 \s_@@_mark #1 ##2 \s_@@_mark ##3 { ##3 \@@_trim_auxi:w \s_@@_mark ##2 \s_@@_mark #1 {##1} } \cs_new:Npn \@@_trim_auxii:w \@@_trim_auxi:w \s_@@_mark \s_@@_mark ##1 { \@@_trim_auxiii:w ##1 } \cs_new:Npn \@@_trim_auxiii:w ##1 #1 \s_@@_nil ##2 { ##2 ##1 \s_@@_nil \@@_trim_auxiii:w } % \end{macrocode} % This is the one macro which differs from the original definition. % \begin{macrocode} \cs_new:Npn \@@_trim_auxiv:w \s_@@_mark ##1 \s_@@_nil ##2 \s_@@_stop ##3 { ##3 { ##1 } } } \@@_tmp:n { ~ } \group_end: % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Constants and variables} % % \begin{macrocode} %<@@=keys> % \end{macrocode} % % \begin{variable} % { % \c_@@_code_root_str , % \c_@@_default_root_str , % \c_@@_groups_root_str , % \c_@@_inherit_root_str , % \c_@@_type_root_str , % \c_@@_validate_root_str % } % Various storage areas for the different data which make up keys. % \begin{macrocode} \str_const:Nn \c_@@_code_root_str { key~code~>~ } \str_const:Nn \c_@@_default_root_str { key~default~>~ } \str_const:Nn \c_@@_groups_root_str { key~groups~>~ } \str_const:Nn \c_@@_inherit_root_str { key~inherit~>~ } \str_const:Nn \c_@@_type_root_str { key~type~>~ } \str_const:Nn \c_@@_validate_root_str { key~validate~>~ } % \end{macrocode} % \end{variable} % % \begin{variable}{\c_@@_props_root_str} % The prefix for storing properties. % \begin{macrocode} \str_const:Nn \c_@@_props_root_str { key~prop~>~ } % \end{macrocode} % \end{variable} % % \begin{variable}{\l_keys_choice_int, \l_keys_choice_tl} % Publicly accessible data on which choice is being used when several % are generated as a set. % \begin{macrocode} \int_new:N \l_keys_choice_int \tl_new:N \l_keys_choice_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_groups_clist} % Used for storing and recovering the list of groups which apply to a key: % set as a comma list but at one point we have to use this for a token % list recovery. % \begin{macrocode} \clist_new:N \l_@@_groups_clist % \end{macrocode} % \end{variable} % % \begin{variable}{\l_keys_key_str, \l_keys_key_tl} % The name of a key itself: needed when setting keys. The |tl| version % is deprecated but has to be handled manually. % \begin{macrocode} \str_new:N \l_keys_key_str \tl_new:N \l_keys_key_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_module_str} % The module for an entire set of keys. % \begin{macrocode} \str_new:N \l_@@_module_str % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_no_value_bool} % A marker is needed internally to show if only a key or a key plus a % value was seen: this is recorded here. % \begin{macrocode} \bool_new:N \l_@@_no_value_bool % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_only_known_bool} % Used to track if only \enquote{known} keys are being set. % \begin{macrocode} \bool_new:N \l_@@_only_known_bool % \end{macrocode} % \end{variable} % % \begin{variable}{\l_keys_path_str, \l_keys_path_tl} % The \enquote{path} of the current key is stored here: this is % available to the programmer and so is public. The older version is % deprecated but has to be handled manually. % \begin{macrocode} \str_new:N \l_keys_path_str \tl_new:N \l_keys_path_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_inherit_str} % \begin{macrocode} \str_new:N \l_@@_inherit_str % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_relative_tl} % The relative path for passing keys back to the user. As this can % be explicitly no-value, it must be a token list. % \begin{macrocode} \tl_new:N \l_@@_relative_tl \tl_set:Nn \l_@@_relative_tl { \q_@@_no_value } % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_property_str} % The \enquote{property} begin set for a key at definition time is % stored here. % \begin{macrocode} \str_new:N \l_@@_property_str % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_selective_bool, \l_@@_filtered_bool} % Two flags for using key groups: one to indicate that \enquote{selective} % setting is active, a second to specify which type (\enquote{opt-in} % or \enquote{opt-out}). % \begin{macrocode} \bool_new:N \l_@@_selective_bool \bool_new:N \l_@@_filtered_bool % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_selective_seq} % The list of key groups being filtered in or out during selective setting. % \begin{macrocode} \seq_new:N \l_@@_selective_seq % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_unused_clist} % Used when setting only some keys to store those left over. % \begin{macrocode} \tl_new:N \l_@@_unused_clist % \end{macrocode} % \end{variable} % % \begin{variable}{\l_keys_value_tl} % The value given for a key: may be empty if no value was given. % \begin{macrocode} \tl_new:N \l_keys_value_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_tmp_bool, \l_@@_tmpa_tl, \l_@@_tmpb_tl} % Scratch space. % \begin{macrocode} \bool_new:N \l_@@_tmp_bool \tl_new:N \l_@@_tmpa_tl \tl_new:N \l_@@_tmpb_tl % \end{macrocode} % \end{variable} % % \subsubsection{Internal auxiliaries} % % \begin{variable}{\s_@@_stop} % Internal scan marks. % \begin{macrocode} \scan_new:N \s_@@_stop % \end{macrocode} % \end{variable} % % \begin{variable}{\q_@@_nil,\q_@@_no_value} % Internal quarks. % \begin{macrocode} \quark_new:N \q_@@_nil \quark_new:N \q_@@_no_value % \end{macrocode} % \end{variable} % % \begin{macro}[pTF]{\@@_quark_if_nil:n} % Branching quark conditional. % \begin{macrocode} \__kernel_quark_new_conditional:Nn \@@_quark_if_nil:n { TF } \__kernel_quark_new_conditional:Nn \@@_quark_if_no_value:N { TF } % \end{macrocode} % \end{macro} % % \begin{variable}{\q_@@_recursion_tail,\q_@@_recursion_stop} % Internal recursion quarks. % \begin{macrocode} \quark_new:N \q_@@_recursion_tail \quark_new:N \q_@@_recursion_stop % \end{macrocode} % \end{variable} % % \begin{macro}[EXP]{\@@_if_recursion_tail_stop:n} % Functions to query recursion quarks. % \begin{macrocode} \__kernel_quark_new_test:N \@@_if_recursion_tail_stop:n % \end{macrocode} % \end{macro} % % \subsection{The key defining mechanism} % % \begin{macro}{\keys_define:nn} % \begin{macro}{\@@_define:nnn, \@@_define:onn} % The public function for definitions is just a wrapper for the lower % level mechanism, more or less. The outer function is designed to % keep a track of the current module, to allow safe nesting. The module is set % removing any leading |/| (which is not needed here). % \begin{macrocode} \cs_new_protected:Npn \keys_define:nn { \@@_define:onn \l_@@_module_str } \cs_new_protected:Npn \@@_define:nnn #1#2#3 { \str_set:Nx \l_@@_module_str { \@@_trim_spaces:n {#2} } \keyval_parse:NNn \@@_define:n \@@_define:nn {#3} \str_set:Nn \l_@@_module_str {#1} } \cs_generate_variant:Nn \@@_define:nnn { o } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_define:n} % \begin{macro}{\@@_define:nn} % \begin{macro}{\@@_define_aux:nn} % The outer functions here record whether a value was given and then % converge on a common internal mechanism. There is first a search for % a property in the current key name, then a check to make sure it is % known before the code hands off to the next step. % \begin{macrocode} \cs_new_protected:Npn \@@_define:n #1 { \bool_set_true:N \l_@@_no_value_bool \@@_define_aux:nn {#1} { } } \cs_new_protected:Npn \@@_define:nn #1#2 { \bool_set_false:N \l_@@_no_value_bool \@@_define_aux:nn {#1} {#2} } \cs_new_protected:Npn \@@_define_aux:nn #1#2 { \@@_property_find:n {#1} \cs_if_exist:cTF { \c_@@_props_root_str \l_@@_property_str } { \@@_define_code:n {#2} } { \str_if_empty:NF \l_@@_property_str { \__kernel_msg_error:nnxx { kernel } { key-property-unknown } { \l_@@_property_str } { \l_keys_path_str } } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_property_find:n} % \begin{macro}{\@@_property_find:w} % Searching for a property means finding the last |.| in the input, % and storing the text before and after it. Everything is turned into % strings, so there is no problem using an \texttt{x}-type expansion. % \begin{macrocode} \cs_new_protected:Npn \@@_property_find:n #1 { \str_set:Nx \l_@@_property_str { \@@_trim_spaces:n {#1} } \exp_after:wN \@@_property_find:w \l_@@_property_str . . \s_@@_stop {#1} } \cs_new_protected:Npn \@@_property_find:w #1 . #2 . #3 \s_@@_stop #4 { \tl_if_blank:nTF {#3} { \str_clear:N \l_@@_property_str \__kernel_msg_error:nnn { kernel } { key-no-property } {#4} } { \str_if_eq:nnTF {#3} { . } { \str_set:Nx \l_keys_path_str { \str_if_empty:NF \l_@@_module_str { \l_@@_module_str / } \tl_trim_spaces:n {#1} } \str_set:Nn \l_@@_property_str { . #2 } } { \str_set:Nx \l_keys_path_str { \l_@@_module_str / #1 . #2 } \@@_property_search:w #3 \s_@@_stop } \tl_set_eq:NN \l_keys_path_tl \l_keys_path_str } } \cs_new_protected:Npn \@@_property_search:w #1 . #2 \s_@@_stop { \str_if_eq:nnTF {#2} { . } { \str_set:Nx \l_keys_path_str { \l_keys_path_str } \str_set:Nn \l_@@_property_str { . #1 } } { \str_set:Nx \l_keys_path_str { \l_keys_path_str . #1 } \@@_property_search:w #2 \s_@@_stop } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_define_code:n} % \begin{macro}[EXP]{\@@_define_code:w} % Two possible cases. If there is a value for the key, then just use % the function. If not, then a check to make sure there is no need for % a value with the property. If there should be one then complain, % otherwise execute it. There is no need to check for a |:| as if it % was missing the earlier tests would have failed. % \begin{macrocode} \cs_new_protected:Npn \@@_define_code:n #1 { \bool_if:NTF \l_@@_no_value_bool { \exp_after:wN \@@_define_code:w \l_@@_property_str \s_@@_stop { \use:c { \c_@@_props_root_str \l_@@_property_str } } { \__kernel_msg_error:nnxx { kernel } { key-property-requires-value } { \l_@@_property_str } { \l_keys_path_str } } } { \use:c { \c_@@_props_root_str \l_@@_property_str } {#1} } } \exp_last_unbraced:NNNNo \cs_new:Npn \@@_define_code:w #1 \c_colon_str #2 \s_@@_stop { \tl_if_empty:nTF {#2} } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Turning properties into actions} % % \begin{macro}{\@@_bool_set:Nn, \@@_bool_set:cn} % Boolean keys are really just choices, but all done by hand. The % second argument here is the scope: either empty or \texttt{ g } for % global. % \begin{macrocode} \cs_new_protected:Npn \@@_bool_set:Nn #1#2 { \bool_if_exist:NF #1 { \bool_new:N #1 } \@@_choice_make: \@@_cmd_set:nx { \l_keys_path_str / true } { \exp_not:c { bool_ #2 set_true:N } \exp_not:N #1 } \@@_cmd_set:nx { \l_keys_path_str / false } { \exp_not:c { bool_ #2 set_false:N } \exp_not:N #1 } \@@_cmd_set:nn { \l_keys_path_str / unknown } { \__kernel_msg_error:nnx { kernel } { boolean-values-only } { \l_keys_key_str } } \@@_default_set:n { true } } \cs_generate_variant:Nn \@@_bool_set:Nn { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_bool_set_inverse:Nn, \@@_bool_set_inverse:cn} % Inverse boolean setting is much the same. % \begin{macrocode} \cs_new_protected:Npn \@@_bool_set_inverse:Nn #1#2 { \bool_if_exist:NF #1 { \bool_new:N #1 } \@@_choice_make: \@@_cmd_set:nx { \l_keys_path_str / true } { \exp_not:c { bool_ #2 set_false:N } \exp_not:N #1 } \@@_cmd_set:nx { \l_keys_path_str / false } { \exp_not:c { bool_ #2 set_true:N } \exp_not:N #1 } \@@_cmd_set:nn { \l_keys_path_str / unknown } { \__kernel_msg_error:nnx { kernel } { boolean-values-only } { \l_keys_key_str } } \@@_default_set:n { true } } \cs_generate_variant:Nn \@@_bool_set_inverse:Nn { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_choice_make:, \@@_multichoice_make:} % \begin{macro}{\@@_choice_make:N} % \begin{macro}{\@@_choice_make_aux:N} % To make a choice from a key, two steps: set the code, and set the % unknown key. As multichoices and choices are essentially the same bar one % function, the code is given together. % \begin{macrocode} \cs_new_protected:Npn \@@_choice_make: { \@@_choice_make:N \@@_choice_find:n } \cs_new_protected:Npn \@@_multichoice_make: { \@@_choice_make:N \@@_multichoice_find:n } \cs_new_protected:Npn \@@_choice_make:N #1 { \cs_if_exist:cTF { \c_@@_type_root_str \@@_parent:o \l_keys_path_str } { \str_if_eq:vnTF { \c_@@_type_root_str \@@_parent:o \l_keys_path_str } { choice } { \__kernel_msg_error:nnxx { kernel } { nested-choice-key } { \l_keys_path_tl } { \@@_parent:o \l_keys_path_str } } { \@@_choice_make_aux:N #1 } } { \@@_choice_make_aux:N #1 } } \cs_new_protected:Npn \@@_choice_make_aux:N #1 { \cs_set_nopar:cpn { \c_@@_type_root_str \l_keys_path_str } { choice } \@@_cmd_set:nn { \l_keys_path_str } { #1 {##1} } \@@_cmd_set:nn { \l_keys_path_str / unknown } { \__kernel_msg_error:nnxx { kernel } { key-choice-unknown } { \l_keys_path_str } {##1} } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_choices_make:nn, \@@_multichoices_make:nn} % \begin{macro}{\@@_choices_make:Nnn} % Auto-generating choices means setting up the root key as a choice, then % defining each choice in turn. % \begin{macrocode} \cs_new_protected:Npn \@@_choices_make:nn { \@@_choices_make:Nnn \@@_choice_make: } \cs_new_protected:Npn \@@_multichoices_make:nn { \@@_choices_make:Nnn \@@_multichoice_make: } \cs_new_protected:Npn \@@_choices_make:Nnn #1#2#3 { #1 \int_zero:N \l_keys_choice_int \clist_map_inline:nn {#2} { \int_incr:N \l_keys_choice_int \@@_cmd_set:nx { \l_keys_path_str / \@@_trim_spaces:n {##1} } { \tl_set:Nn \exp_not:N \l_keys_choice_tl {##1} \int_set:Nn \exp_not:N \l_keys_choice_int { \int_use:N \l_keys_choice_int } \exp_not:n {#3} } } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro} % {\@@_cmd_set:nn, \@@_cmd_set:nx, \@@_cmd_set:Vn, \@@_cmd_set:Vo} % Setting the code for a key first logs if appropriate that we are % defining a new key, then saves the code. % \begin{macrocode} \cs_new_protected:Npn \@@_cmd_set:nn #1#2 { \cs_set_protected:cpn { \c_@@_code_root_str #1 } ##1 {#2} } \cs_generate_variant:Nn \@@_cmd_set:nn { nx , Vn , Vo } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_cs_set:NNpn, \@@_cs_set:Ncpn} % Creating control sequences is a bit more tricky than other cases as % we need to pick up the |p| argument. To make the internals look clearer, % the trailing |n| argument here is just for appearance. % \begin{macrocode} \cs_new_protected:Npn \@@_cs_set:NNpn #1#2#3# { \cs_set_protected:cpx { \c_@@_code_root_str \l_keys_path_str } ##1 { #1 \exp_not:N #2 \exp_not:n {#3} {##1} } \use_none:n } \cs_generate_variant:Nn \@@_cs_set:NNpn { Nc } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_default_set:n} % Setting a default value is easy. These are stored using \cs{cs_set:cpx} as this % avoids any worries about whether a token list exists. % \begin{macrocode} \cs_new_protected:Npn \@@_default_set:n #1 { \tl_if_empty:nTF {#1} { \cs_set_eq:cN { \c_@@_default_root_str \l_keys_path_str } \tex_undefined:D } { \cs_set_nopar:cpx { \c_@@_default_root_str \l_keys_path_str } { \exp_not:n {#1} } \@@_value_requirement:nn { required } { false } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_groups_set:n} % Assigning a key to one or more groups uses comma lists. As the list of % groups only exists if there is anything to do, the setting is done using % a scratch list. For the usual grouping reasons we use the low-level % approach to undefining a list. We also use the low-level approach for % the other case to avoid tripping up the |check-declarations| code. % \begin{macrocode} \cs_new_protected:Npn \@@_groups_set:n #1 { \clist_set:Nn \l_@@_groups_clist {#1} \clist_if_empty:NTF \l_@@_groups_clist { \cs_set_eq:cN { \c_@@_groups_root_str \l_keys_path_str } \tex_undefined:D } { \cs_set_eq:cN { \c_@@_groups_root_str \l_keys_path_str } \l_@@_groups_clist } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_inherit:n} % Inheritance means ignoring anything already said about the key: % zap the lot and set up. % \begin{macrocode} \cs_new_protected:Npn \@@_inherit:n #1 { \@@_undefine: \cs_set_nopar:cpn { \c_@@_inherit_root_str \l_keys_path_str } {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_initialise:n} % A set up for initialisation: just run the code if it exists. % \begin{macrocode} \cs_new_protected:Npn \@@_initialise:n #1 { \cs_if_exist:cTF { \c_@@_inherit_root_str \@@_parent:o \l_keys_path_str } { \@@_execute_inherit: } { \str_clear:N \l_@@_inherit_str \cs_if_exist_use:cT { \c_@@_code_root_str \l_keys_path_str } { {#1} } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_meta_make:n} % \begin{macro}{\@@_meta_make:nn} % To create a meta-key, simply set up to pass data through. % \begin{macrocode} \cs_new_protected:Npn \@@_meta_make:n #1 { \@@_cmd_set:Vo \l_keys_path_str { \exp_after:wN \keys_set:nn \exp_after:wN { \l_@@_module_str } {#1} } } \cs_new_protected:Npn \@@_meta_make:nn #1#2 { \@@_cmd_set:Vn \l_keys_path_str { \keys_set:nn {#1} {#2} } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_prop_put:Nn, \@@_prop_put:cn} % Much the same as other variables, but needs a dedicated auxiliary. % \begin{macrocode} \cs_new_protected:Npn \@@_prop_put:Nn #1#2 { \prop_if_exist:NF #1 { \prop_new:N #1 } \exp_after:wN \@@_find_key_module:NNw \exp_after:wN \l_@@_tmpa_tl \exp_after:wN \l_@@_tmpb_tl \l_keys_path_str / \s_@@_stop \@@_cmd_set:nx { \l_keys_path_str } { \exp_not:c { prop_ #2 put:Nnn } \exp_not:N #1 { \l_@@_tmpb_tl } \exp_not:n { {##1} } } } \cs_generate_variant:Nn \@@_prop_put:Nn { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_undefine:} % Undefining a key has to be done without \cs{cs_undefine:c} as that % function acts globally. % \begin{macrocode} \cs_new_protected:Npn \@@_undefine: { \clist_map_inline:nn { code , default , groups , inherit , type , validate } { \cs_set_eq:cN { \tl_use:c { c_@@_ ##1 _root_str } \l_keys_path_str } \tex_undefined:D } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_value_requirement:nn} % \begin{macro}{\@@_validate_forbidden:, \@@_validate_required:} % \begin{macro}{\@@_validate_cleanup:w} % Validating key input is done using a second function which runs before % the main key code. Setting that up means setting it equal to a generic % stub which does the check. This approach makes the lookup very fast at % the cost of one additional csname per key that needs it. The cleanup here % has to know the structure of the following code. % \begin{macrocode} \cs_new_protected:Npn \@@_value_requirement:nn #1#2 { \str_case:nnF {#2} { { true } { \cs_set_eq:cc { \c_@@_validate_root_str \l_keys_path_str } { @@_validate_ #1 : } } { false } { \cs_if_eq:ccT { \c_@@_validate_root_str \l_keys_path_str } { @@_validate_ #1 : } { \cs_set_eq:cN { \c_@@_validate_root_str \l_keys_path_str } \tex_undefined:D } } } { \__kernel_msg_error:nnx { kernel } { key-property-boolean-values-only } { .value_ #1 :n } } } \cs_new_protected:Npn \@@_validate_forbidden: { \bool_if:NF \l_@@_no_value_bool { \__kernel_msg_error:nnxx { kernel } { value-forbidden } { \l_keys_path_str } { \l_keys_value_tl } \@@_validate_cleanup:w } } \cs_new_protected:Npn \@@_validate_required: { \bool_if:NT \l_@@_no_value_bool { \__kernel_msg_error:nnx { kernel } { value-required } { \l_keys_path_str } \@@_validate_cleanup:w } } \cs_new_protected:Npn \@@_validate_cleanup:w #1 \cs_end: #2#3 { } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_variable_set:NnnN, \@@_variable_set:cnnN} % \begin{macro}{\@@_variable_set_required:NnnN, \@@_variable_set_required:cnnN} % Setting a variable takes the type and scope separately so that % it is easy to make a new variable if needed. % \begin{macrocode} \cs_new_protected:Npn \@@_variable_set:NnnN #1#2#3#4 { \use:c { #2_if_exist:NF } #1 { \use:c { #2 _new:N } #1 } \@@_cmd_set:nx { \l_keys_path_str } { \exp_not:c { #2 _ #3 set:N #4 } \exp_not:N #1 \exp_not:n { {##1} } } } \cs_generate_variant:Nn \@@_variable_set:NnnN { c } \cs_new_protected:Npn \@@_variable_set_required:NnnN #1#2#3#4 { \@@_variable_set:NnnN #1 {#2} {#3} #4 \@@_value_requirement:nn { required } { true } } \cs_generate_variant:Nn \@@_variable_set_required:NnnN { c } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Creating key properties} % % The key property functions are all wrappers for internal functions, % meaning that things stay readable and can also be altered later on. % % Importantly, while key properties have \enquote{normal} argument specs, the % underlying code always supplies one braced argument to these. As such, argument % expansion is handled by hand rather than using the standard tools. This shows % up particularly for the two-argument properties, where things would otherwise % go badly wrong. % % \begin{macro}{.bool_set:N, .bool_set:c} % \begin{macro}{.bool_gset:N, .bool_gset:c} % One function for this. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .bool_set:N } #1 { \@@_bool_set:Nn #1 { } } \cs_new_protected:cpn { \c_@@_props_root_str .bool_set:c } #1 { \@@_bool_set:cn {#1} { } } \cs_new_protected:cpn { \c_@@_props_root_str .bool_gset:N } #1 { \@@_bool_set:Nn #1 { g } } \cs_new_protected:cpn { \c_@@_props_root_str .bool_gset:c } #1 { \@@_bool_set:cn {#1} { g } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{.bool_set_inverse:N, .bool_set_inverse:c} % \begin{macro}{.bool_gset_inverse:N, .bool_gset_inverse:c} % One function for this. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .bool_set_inverse:N } #1 { \@@_bool_set_inverse:Nn #1 { } } \cs_new_protected:cpn { \c_@@_props_root_str .bool_set_inverse:c } #1 { \@@_bool_set_inverse:cn {#1} { } } \cs_new_protected:cpn { \c_@@_props_root_str .bool_gset_inverse:N } #1 { \@@_bool_set_inverse:Nn #1 { g } } \cs_new_protected:cpn { \c_@@_props_root_str .bool_gset_inverse:c } #1 { \@@_bool_set_inverse:cn {#1} { g } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{.choice:} % Making a choice is handled internally, as it is also needed by % \texttt{.generate_choices:n}. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .choice: } { \@@_choice_make: } % \end{macrocode} % \end{macro} % % \begin{macro} % {.choices:nn, .choices:Vn, .choices:on, .choices:xn} % For auto-generation of a series of mutually-exclusive choices. % Here, |#1| consists of two separate % arguments, hence the slightly odd-looking implementation. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .choices:nn } #1 { \@@_choices_make:nn #1 } \cs_new_protected:cpn { \c_@@_props_root_str .choices:Vn } #1 { \exp_args:NV \@@_choices_make:nn #1 } \cs_new_protected:cpn { \c_@@_props_root_str .choices:on } #1 { \exp_args:No \@@_choices_make:nn #1 } \cs_new_protected:cpn { \c_@@_props_root_str .choices:xn } #1 { \exp_args:Nx \@@_choices_make:nn #1 } % \end{macrocode} % \end{macro} % % \begin{macro}{.code:n} % Creating code is simply a case of passing through to the underlying % \texttt{set} function. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .code:n } #1 { \@@_cmd_set:nn { \l_keys_path_str } {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{.clist_set:N, .clist_set:c} % \begin{macro}{.clist_gset:N, .clist_gset:c} % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .clist_set:N } #1 { \@@_variable_set:NnnN #1 { clist } { } n } \cs_new_protected:cpn { \c_@@_props_root_str .clist_set:c } #1 { \@@_variable_set:cnnN {#1} { clist } { } n } \cs_new_protected:cpn { \c_@@_props_root_str .clist_gset:N } #1 { \@@_variable_set:NnnN #1 { clist } { g } n } \cs_new_protected:cpn { \c_@@_props_root_str .clist_gset:c } #1 { \@@_variable_set:cnnN {#1} { clist } { g } n } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro} % {.cs_set:Np, .cs_set:cp, .cs_set_protected:Np, .cs_set_protected:cp} % \begin{macro} % {.cs_gset:Np, .cs_gset:cp, .cs_gset_protected:Np, .cs_gset_protected:cp} % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .cs_set:Np } #1 { \@@_cs_set:NNpn \cs_set:Npn #1 { } } \cs_new_protected:cpn { \c_@@_props_root_str .cs_set:cp } #1 { \@@_cs_set:Ncpn \cs_set:Npn #1 { } } \cs_new_protected:cpn { \c_@@_props_root_str .cs_set_protected:Np } #1 { \@@_cs_set:NNpn \cs_set_protected:Npn #1 { } } \cs_new_protected:cpn { \c_@@_props_root_str .cs_set_protected:cp } #1 { \@@_cs_set:Ncpn \cs_set_protected:Npn #1 { } } \cs_new_protected:cpn { \c_@@_props_root_str .cs_gset:Np } #1 { \@@_cs_set:NNpn \cs_gset:Npn #1 { } } \cs_new_protected:cpn { \c_@@_props_root_str .cs_gset:cp } #1 { \@@_cs_set:Ncpn \cs_gset:Npn #1 { } } \cs_new_protected:cpn { \c_@@_props_root_str .cs_gset_protected:Np } #1 { \@@_cs_set:NNpn \cs_gset_protected:Npn #1 { } } \cs_new_protected:cpn { \c_@@_props_root_str .cs_gset_protected:cp } #1 { \@@_cs_set:Ncpn \cs_gset_protected:Npn #1 { } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{.default:n, .default:V, .default:o, .default:x} % Expansion is left to the internal functions. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .default:n } #1 { \@@_default_set:n {#1} } \cs_new_protected:cpn { \c_@@_props_root_str .default:V } #1 { \exp_args:NV \@@_default_set:n #1 } \cs_new_protected:cpn { \c_@@_props_root_str .default:o } #1 { \exp_args:No \@@_default_set:n {#1} } \cs_new_protected:cpn { \c_@@_props_root_str .default:x } #1 { \exp_args:Nx \@@_default_set:n {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{.dim_set:N, .dim_set:c} % \begin{macro}{.dim_gset:N, .dim_gset:c} % Setting a variable is very easy: just pass the data along. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .dim_set:N } #1 { \@@_variable_set_required:NnnN #1 { dim } { } n } \cs_new_protected:cpn { \c_@@_props_root_str .dim_set:c } #1 { \@@_variable_set_required:cnnN {#1} { dim } { } n } \cs_new_protected:cpn { \c_@@_props_root_str .dim_gset:N } #1 { \@@_variable_set_required:NnnN #1 { dim } { g } n } \cs_new_protected:cpn { \c_@@_props_root_str .dim_gset:c } #1 { \@@_variable_set_required:cnnN {#1} { dim } { g } n } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{.fp_set:N, .fp_set:c} % \begin{macro}{.fp_gset:N, .fp_gset:c} % Setting a variable is very easy: just pass the data along. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .fp_set:N } #1 { \@@_variable_set_required:NnnN #1 { fp } { } n } \cs_new_protected:cpn { \c_@@_props_root_str .fp_set:c } #1 { \@@_variable_set_required:cnnN {#1} { fp } { } n } \cs_new_protected:cpn { \c_@@_props_root_str .fp_gset:N } #1 { \@@_variable_set_required:NnnN #1 { fp } { g } n } \cs_new_protected:cpn { \c_@@_props_root_str .fp_gset:c } #1 { \@@_variable_set_required:cnnN {#1} { fp } { g } n } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{.groups:n} % A single property to create groups of keys. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .groups:n } #1 { \@@_groups_set:n {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{.inherit:n} % Nothing complex: only one variant at the moment! % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .inherit:n } #1 { \@@_inherit:n {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{.initial:n, .initial:V, .initial:o, .initial:x} % The standard hand-off approach. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .initial:n } #1 { \@@_initialise:n {#1} } \cs_new_protected:cpn { \c_@@_props_root_str .initial:V } #1 { \exp_args:NV \@@_initialise:n #1 } \cs_new_protected:cpn { \c_@@_props_root_str .initial:o } #1 { \exp_args:No \@@_initialise:n {#1} } \cs_new_protected:cpn { \c_@@_props_root_str .initial:x } #1 { \exp_args:Nx \@@_initialise:n {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{.int_set:N, .int_set:c} % \begin{macro}{.int_gset:N, .int_gset:c} % Setting a variable is very easy: just pass the data along. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .int_set:N } #1 { \@@_variable_set_required:NnnN #1 { int } { } n } \cs_new_protected:cpn { \c_@@_props_root_str .int_set:c } #1 { \@@_variable_set_required:cnnN {#1} { int } { } n } \cs_new_protected:cpn { \c_@@_props_root_str .int_gset:N } #1 { \@@_variable_set_required:NnnN #1 { int } { g } n } \cs_new_protected:cpn { \c_@@_props_root_str .int_gset:c } #1 { \@@_variable_set_required:cnnN {#1} { int } { g } n } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{.meta:n} % Making a meta is handled internally. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .meta:n } #1 { \@@_meta_make:n {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{.meta:nn} % Meta with path: potentially lots of variants, but for the moment % no so many defined. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .meta:nn } #1 { \@@_meta_make:nn #1 } % \end{macrocode} % \end{macro} % % \begin{macro}{.multichoice:} % \begin{macro} % { % .multichoices:nn, .multichoices:Vn, .multichoices:on, % .multichoices:xn, % } % The same idea as \texttt{.choice:} and \texttt{.choices:nn}, but % where more than one choice is allowed. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .multichoice: } { \@@_multichoice_make: } \cs_new_protected:cpn { \c_@@_props_root_str .multichoices:nn } #1 { \@@_multichoices_make:nn #1 } \cs_new_protected:cpn { \c_@@_props_root_str .multichoices:Vn } #1 { \exp_args:NV \@@_multichoices_make:nn #1 } \cs_new_protected:cpn { \c_@@_props_root_str .multichoices:on } #1 { \exp_args:No \@@_multichoices_make:nn #1 } \cs_new_protected:cpn { \c_@@_props_root_str .multichoices:xn } #1 { \exp_args:Nx \@@_multichoices_make:nn #1 } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{.muskip_set:N, .muskip_set:c, .muskip_gset:N, .muskip_gset:c} % Setting a variable is very easy: just pass the data along. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .muskip_set:N } #1 { \@@_variable_set_required:NnnN #1 { muskip } { } n } \cs_new_protected:cpn { \c_@@_props_root_str .muskip_set:c } #1 { \@@_variable_set_required:cnnN {#1} { muskip } { } n } \cs_new_protected:cpn { \c_@@_props_root_str .muskip_gset:N } #1 { \@@_variable_set_required:NnnN #1 { muskip } { g } n } \cs_new_protected:cpn { \c_@@_props_root_str .muskip_gset:c } #1 { \@@_variable_set_required:cnnN {#1} { muskip } { g } n } % \end{macrocode} % \end{macro} % % \begin{macro}{.prop_put:N, .prop_put:c, .prop_gput:N, .prop_gput:c} % Setting a variable is very easy: just pass the data along. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .prop_put:N } #1 { \@@_prop_put:Nn #1 { } } \cs_new_protected:cpn { \c_@@_props_root_str .prop_put:c } #1 { \@@_prop_put:cn {#1} { } } \cs_new_protected:cpn { \c_@@_props_root_str .prop_gput:N } #1 { \@@_prop_put:Nn #1 { g } } \cs_new_protected:cpn { \c_@@_props_root_str .prop_gput:c } #1 { \@@_prop_put:cn {#1} { g } } % \end{macrocode} % \end{macro} % % \begin{macro}{.skip_set:N, .skip_set:c} % \begin{macro}{.skip_gset:N, .skip_gset:c} % Setting a variable is very easy: just pass the data along. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .skip_set:N } #1 { \@@_variable_set_required:NnnN #1 { skip } { } n } \cs_new_protected:cpn { \c_@@_props_root_str .skip_set:c } #1 { \@@_variable_set_required:cnnN {#1} { skip } { } n } \cs_new_protected:cpn { \c_@@_props_root_str .skip_gset:N } #1 { \@@_variable_set_required:NnnN #1 { skip } { g } n } \cs_new_protected:cpn { \c_@@_props_root_str .skip_gset:c } #1 { \@@_variable_set_required:cnnN {#1} { skip } { g } n } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{.tl_set:N, .tl_set:c} % \begin{macro}{.tl_gset:N, .tl_gset:c} % \begin{macro}{.tl_set_x:N, .tl_set_x:c} % \begin{macro}{.tl_gset_x:N, .tl_gset_x:c} % Setting a variable is very easy: just pass the data along. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .tl_set:N } #1 { \@@_variable_set:NnnN #1 { tl } { } n } \cs_new_protected:cpn { \c_@@_props_root_str .tl_set:c } #1 { \@@_variable_set:cnnN {#1} { tl } { } n } \cs_new_protected:cpn { \c_@@_props_root_str .tl_set_x:N } #1 { \@@_variable_set:NnnN #1 { tl } { } x } \cs_new_protected:cpn { \c_@@_props_root_str .tl_set_x:c } #1 { \@@_variable_set:cnnN {#1} { tl } { } x } \cs_new_protected:cpn { \c_@@_props_root_str .tl_gset:N } #1 { \@@_variable_set:NnnN #1 { tl } { g } n } \cs_new_protected:cpn { \c_@@_props_root_str .tl_gset:c } #1 { \@@_variable_set:cnnN {#1} { tl } { g } n } \cs_new_protected:cpn { \c_@@_props_root_str .tl_gset_x:N } #1 { \@@_variable_set:NnnN #1 { tl } { g } x } \cs_new_protected:cpn { \c_@@_props_root_str .tl_gset_x:c } #1 { \@@_variable_set:cnnN {#1} { tl } { g } x } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{.undefine:} % Another simple wrapper. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .undefine: } { \@@_undefine: } % \end{macrocode} % \end{macro} % % \begin{macro}{.value_forbidden:n} % \begin{macro}{.value_required:n} % These are very similar, so both call the same function. % \begin{macrocode} \cs_new_protected:cpn { \c_@@_props_root_str .value_forbidden:n } #1 { \@@_value_requirement:nn { forbidden } {#1} } \cs_new_protected:cpn { \c_@@_props_root_str .value_required:n } #1 { \@@_value_requirement:nn { required } {#1} } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Setting keys} % % \begin{macro} % { % \keys_set:nn, \keys_set:nV, \keys_set:nv, \keys_set:no, % \@@_set:nn % } % \begin{macro}{\@@_set:nnn} % A simple wrapper allowing for nesting. % \begin{macrocode} \cs_new_protected:Npn \keys_set:nn #1#2 { \use:x { \bool_set_false:N \exp_not:N \l_@@_only_known_bool \bool_set_false:N \exp_not:N \l_@@_filtered_bool \bool_set_false:N \exp_not:N \l_@@_selective_bool \tl_set:Nn \exp_not:N \l_@@_relative_tl { \exp_not:N \q_@@_no_value } \@@_set:nn \exp_not:n { {#1} {#2} } \bool_if:NT \l_@@_only_known_bool { \bool_set_true:N \exp_not:N \l_@@_only_known_bool } \bool_if:NT \l_@@_filtered_bool { \bool_set_true:N \exp_not:N \l_@@_filtered_bool } \bool_if:NT \l_@@_selective_bool { \bool_set_true:N \exp_not:N \l_@@_selective_bool } \tl_set:Nn \exp_not:N \l_@@_relative_tl { \exp_not:o \l_@@_relative_tl } } } \cs_generate_variant:Nn \keys_set:nn { nV , nv , no } \cs_new_protected:Npn \@@_set:nn #1#2 { \exp_args:No \@@_set:nnn \l_@@_module_str {#1} {#2} } \cs_new_protected:Npn \@@_set:nnn #1#2#3 { \str_set:Nx \l_@@_module_str { \@@_trim_spaces:n {#2} } \keyval_parse:NNn \@@_set_keyval:n \@@_set_keyval:nn {#3} \str_set:Nn \l_@@_module_str {#1} } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro} % { % \keys_set_known:nnN, \keys_set_known:nVN, % \keys_set_known:nvN, \keys_set_known:noN % } % \begin{macro} % { % \keys_set_known:nnnN, \keys_set_known:nVnN, % \keys_set_known:nvnN, \keys_set_known:nonN % } % \begin{macro}{\@@_set_known:nnnnN} % \begin{macro} % { % \keys_set_known:nn, \keys_set_known:nV, % \keys_set_known:nv, \keys_set_known:no % } % \begin{macro}{\@@_set_known:nnn} % Setting known keys simply means setting the appropriate flag, then % running the standard code. To allow for nested setting, any existing % value of \cs{l_@@_unused_clist} is saved on the stack and reset % afterwards. Note that for speed/simplicity reasons we use a \texttt{tl} % operation to set the \texttt{clist} here! % \begin{macrocode} \cs_new_protected:Npn \keys_set_known:nnN #1#2#3 { \exp_args:No \@@_set_known:nnnnN \l_@@_unused_clist { \q_@@_no_value } {#1} {#2} #3 } \cs_generate_variant:Nn \keys_set_known:nnN { nV , nv , no } \cs_new_protected:Npn \keys_set_known:nnnN #1#2#3#4 { \exp_args:No \@@_set_known:nnnnN \l_@@_unused_clist {#3} {#1} {#2} #4 } \cs_generate_variant:Nn \keys_set_known:nnnN { nV , nv , no } \cs_new_protected:Npn \@@_set_known:nnnnN #1#2#3#4#5 { \clist_clear:N \l_@@_unused_clist \@@_set_known:nnn {#2} {#3} {#4} \tl_set:Nx #5 { \exp_not:o { \l_@@_unused_clist } } \tl_set:Nn \l_@@_unused_clist {#1} } \cs_new_protected:Npn \keys_set_known:nn #1#2 { \@@_set_known:nnn { \q_@@_no_value } {#1} {#2} } \cs_generate_variant:Nn \keys_set_known:nn { nV , nv , no } \cs_new_protected:Npn \@@_set_known:nnn #1#2#3 { \use:x { \bool_set_true:N \exp_not:N \l_@@_only_known_bool \bool_set_false:N \exp_not:N \l_@@_filtered_bool \bool_set_false:N \exp_not:N \l_@@_selective_bool \tl_set:Nn \exp_not:N \l_@@_relative_tl { \exp_not:n {#1} } \@@_set:nn \exp_not:n { {#2} {#3} } \bool_if:NF \l_@@_only_known_bool { \bool_set_false:N \exp_not:N \l_@@_only_known_bool } \bool_if:NT \l_@@_filtered_bool { \bool_set_true:N \exp_not:N \l_@@_filtered_bool } \bool_if:NT \l_@@_selective_bool { \bool_set_true:N \exp_not:N \l_@@_selective_bool } \tl_set:Nn \exp_not:N \l_@@_relative_tl { \exp_not:o \l_@@_relative_tl } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro} % { % \keys_set_filter:nnnN, \keys_set_filter:nnVN, \keys_set_filter:nnvN, % \keys_set_filter:nnoN % } % \begin{macro} % { % \keys_set_filter:nnnnN, \keys_set_filter:nnVnN, % \keys_set_filter:nnvnN, \keys_set_filter:nnonN % } % \begin{macro}{\@@_set_filter:nnnnnN} % \begin{macro} % { % \keys_set_filter:nnn, \keys_set_filter:nnV, \keys_set_filter:nnv, % \keys_set_filter:nno % } % \begin{macro}{\@@_set_filter:nnnn} % \begin{macro} % { % \keys_set_groups:nnn, \keys_set_groups:nnV, \keys_set_groups:nnv, % \keys_set_groups:nno % } % \begin{macro}{\@@_set_selective:nnn} % \begin{macro}{\@@_set_selective:nnnn} % The idea of setting keys in a selective manner again uses flags % wrapped around the basic code. The comments on \cs{keys_set_known:nnN} % also apply here. We have a bit more shuffling to do to keep everything % nestable. % \begin{macrocode} \cs_new_protected:Npn \keys_set_filter:nnnN #1#2#3#4 { \exp_args:No \@@_set_filter:nnnnnN \l_@@_unused_clist { \q_@@_no_value } {#1} {#2} {#3} #4 } \cs_generate_variant:Nn \keys_set_filter:nnnN { nnV , nnv , nno } \cs_new_protected:Npn \keys_set_filter:nnnnN #1#2#3#4#5 { \exp_args:No \@@_set_filter:nnnnnN \l_@@_unused_clist {#4} {#1} {#2} {#3} #5 } \cs_generate_variant:Nn \keys_set_filter:nnnnN { nnV , nnv , nno } \cs_new_protected:Npn \@@_set_filter:nnnnnN #1#2#3#4#5#6 { \clist_clear:N \l_@@_unused_clist \@@_set_filter:nnnn {#2} {#3} {#4} {#5} \tl_set:Nx #6 { \exp_not:o { \l_@@_unused_clist } } \tl_set:Nn \l_@@_unused_clist {#1} } \cs_new_protected:Npn \keys_set_filter:nnn #1#2#3 {\@@_set_filter:nnnn { \q_@@_no_value } {#1} {#2} {#3} } \cs_generate_variant:Nn \keys_set_filter:nnn { nnV , nnv , nno } \cs_new_protected:Npn \@@_set_filter:nnnn #1#2#3#4 { \use:x { \bool_set_false:N \exp_not:N \l_@@_only_known_bool \bool_set_true:N \exp_not:N \l_@@_filtered_bool \bool_set_true:N \exp_not:N \l_@@_selective_bool \tl_set:Nn \exp_not:N \l_@@_relative_tl { \exp_not:n {#1} } \@@_set_selective:nnn \exp_not:n { {#2} {#3} {#4} } \bool_if:NT \l_@@_only_known_bool { \bool_set_true:N \exp_not:N \l_@@_only_known_bool } \bool_if:NF \l_@@_filtered_bool { \bool_set_false:N \exp_not:N \l_@@_filtered_bool } \bool_if:NF \l_@@_selective_bool { \bool_set_false:N \exp_not:N \l_@@_selective_bool } \tl_set:Nn \exp_not:N \l_@@_relative_tl { \exp_not:o \l_@@_relative_tl } } } \cs_new_protected:Npn \keys_set_groups:nnn #1#2#3 { \use:x { \bool_set_false:N \exp_not:N \l_@@_only_known_bool \bool_set_false:N \exp_not:N \l_@@_filtered_bool \bool_set_true:N \exp_not:N \l_@@_selective_bool \tl_set:Nn \exp_not:N \l_@@_relative_tl { \exp_not:N \q_@@_no_value } \@@_set_selective:nnn \exp_not:n { {#1} {#2} {#3} } \bool_if:NT \l_@@_only_known_bool { \bool_set_true:N \exp_not:N \l_@@_only_known_bool } \bool_if:NF \l_@@_filtered_bool { \bool_set_true:N \exp_not:N \l_@@_filtered_bool } \bool_if:NF \l_@@_selective_bool { \bool_set_false:N \exp_not:N \l_@@_selective_bool } \tl_set:Nn \exp_not:N \l_@@_relative_tl { \exp_not:o \l_@@_relative_tl } } } \cs_generate_variant:Nn \keys_set_groups:nnn { nnV , nnv , nno } \cs_new_protected:Npn \@@_set_selective:nnn { \exp_args:No \@@_set_selective:nnnn \l_@@_selective_seq } \cs_new_protected:Npn \@@_set_selective:nnnn #1#2#3#4 { \seq_set_from_clist:Nn \l_@@_selective_seq {#3} \@@_set:nn {#2} {#4} \tl_set:Nn \l_@@_selective_seq {#1} } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_set_keyval:n, \@@_set_keyval:nn} % \begin{macro}{\@@_set_keyval:nnn, \@@_set_keyval:onn} % \begin{macro}{\@@_find_key_module:NNw} % \begin{macro}{\@@_set_selective:} % A shared system once again. First, set the current path and add a % default if needed. There are then checks to see if the a value is % required or forbidden. If everything passes, move on to execute the % code. % \begin{macrocode} \cs_new_protected:Npn \@@_set_keyval:n #1 { \bool_set_true:N \l_@@_no_value_bool \@@_set_keyval:onn \l_@@_module_str {#1} { } } \cs_new_protected:Npn \@@_set_keyval:nn #1#2 { \bool_set_false:N \l_@@_no_value_bool \@@_set_keyval:onn \l_@@_module_str {#1} {#2} } % \end{macrocode} % The key path here can be fully defined, after which there is a search % for the key and module names: the user may have passed them with part % of what is actually the module (for our purposes) in the key name. As % that happens on a per-key basis, we use the stack approach to restore % the module name without a group. % \begin{macrocode} \cs_new_protected:Npn \@@_set_keyval:nnn #1#2#3 { \tl_set:Nx \l_keys_path_str { \tl_if_blank:nF {#1} { #1 / } \@@_trim_spaces:n {#2} } \str_clear:N \l_@@_module_str \str_clear:N \l_@@_inherit_str \exp_after:wN \@@_find_key_module:NNw \exp_after:wN \l_@@_module_str \exp_after:wN \l_keys_key_str \l_keys_path_str / \s_@@_stop \tl_set_eq:NN \l_keys_key_tl \l_keys_key_str \@@_value_or_default:n {#3} \bool_if:NTF \l_@@_selective_bool { \@@_set_selective: } { \@@_execute: } \str_set:Nn \l_@@_module_str {#1} } \cs_generate_variant:Nn \@@_set_keyval:nnn { o } \cs_new_protected:Npn \@@_find_key_module:NNw #1#2#3 / #4 \s_@@_stop { \tl_if_blank:nTF {#4} { \str_set:Nn #2 {#3} } { \str_put_right:Nx #1 { \str_if_empty:NF #1 { / } #3 } \@@_find_key_module:NNw #1#2 #4 \s_@@_stop } } % \end{macrocode} % If selective setting is active, there are a number of possible sub-cases % to consider. The key name may not be known at all or if it is, it may not % have any groups assigned. There is then the question of whether the % selection is opt-in or opt-out. % \begin{macrocode} \cs_new_protected:Npn \@@_set_selective: { \cs_if_exist:cTF { \c_@@_groups_root_str \l_keys_path_str } { \clist_set_eq:Nc \l_@@_groups_clist { \c_@@_groups_root_str \l_keys_path_str } \@@_check_groups: } { \bool_if:NTF \l_@@_filtered_bool { \@@_execute: } { \@@_store_unused: } } } % \end{macrocode} % In the case where selective setting requires a comparison of the list % of groups which apply to a key with the list of those which have been % set active. That requires two mappings, and again a different outcome % depending on whether opt-in or opt-out is set. % \begin{macrocode} \cs_new_protected:Npn \@@_check_groups: { \bool_set_false:N \l_@@_tmp_bool \seq_map_inline:Nn \l_@@_selective_seq { \clist_map_inline:Nn \l_@@_groups_clist { \str_if_eq:nnT {##1} {####1} { \bool_set_true:N \l_@@_tmp_bool \clist_map_break:n { \seq_map_break: } } } } \bool_if:NTF \l_@@_tmp_bool { \bool_if:NTF \l_@@_filtered_bool { \@@_store_unused: } { \@@_execute: } } { \bool_if:NTF \l_@@_filtered_bool { \@@_execute: } { \@@_store_unused: } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_value_or_default:n} % \begin{macro}{\@@_default_inherit:} % If a value is given, return it as |#1|, otherwise send a default if % available. % \begin{macrocode} \cs_new_protected:Npn \@@_value_or_default:n #1 { \bool_if:NTF \l_@@_no_value_bool { \cs_if_exist:cTF { \c_@@_default_root_str \l_keys_path_str } { \tl_set_eq:Nc \l_keys_value_tl { \c_@@_default_root_str \l_keys_path_str } } { \tl_clear:N \l_keys_value_tl \cs_if_exist:cT { \c_@@_inherit_root_str \@@_parent:o \l_keys_path_str } { \@@_default_inherit: } } } { \tl_set:Nn \l_keys_value_tl {#1} } } \cs_new_protected:Npn \@@_default_inherit: { \clist_map_inline:cn { \c_@@_inherit_root_str \@@_parent:o \l_keys_path_str } { \cs_if_exist:cT { \c_@@_default_root_str ##1 / \l_keys_key_str } { \tl_set_eq:Nc \l_keys_value_tl { \c_@@_default_root_str ##1 / \l_keys_key_str } \clist_map_break: } } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_execute:, \@@_execute_inherit:, \@@_execute_unknown:} % \begin{macro}[EXP]{\@@_execute:nn} % \begin{macro}{\@@_store_unused:,\@@_store_unused_aux:} % Actually executing a key is done in two parts. First, look for the % key itself, then look for the \texttt{unknown} key with the same % path. If both of these fail, complain. What exactly happens if a key % is unknown depends on whether unknown keys are being skipped or if % an error should be raised. % \begin{macrocode} \cs_new_protected:Npn \@@_execute: { \cs_if_exist:cTF { \c_@@_code_root_str \l_keys_path_str } { \cs_if_exist_use:c { \c_@@_validate_root_str \l_keys_path_str } \cs:w \c_@@_code_root_str \l_keys_path_str \exp_after:wN \cs_end: \exp_after:wN { \l_keys_value_tl } } { \cs_if_exist:cTF { \c_@@_inherit_root_str \@@_parent:o \l_keys_path_str } { \@@_execute_inherit: } { \@@_execute_unknown: } } } % \end{macrocode} % To deal with the case where there is no hit, we leave % \cs{@@_execute_unknown:} in the input stream and clean it up using the % break function: that avoids needing a boolean. % \begin{macrocode} \cs_new_protected:Npn \@@_execute_inherit: { \clist_map_inline:cn { \c_@@_inherit_root_str \@@_parent:o \l_keys_path_str } { \cs_if_exist:cT { \c_@@_code_root_str ##1 / \l_keys_key_str } { \str_set:Nn \l_@@_inherit_str {##1} \cs_if_exist_use:c { \c_@@_validate_root_str ##1 / \l_keys_key_str } \cs:w \c_@@_code_root_str ##1 / \l_keys_key_str \exp_after:wN \cs_end: \exp_after:wN { \l_keys_value_tl } \clist_map_break:n { \use_none:n } } } \@@_execute_unknown: } \cs_new_protected:Npn \@@_execute_unknown: { \bool_if:NTF \l_@@_only_known_bool { \@@_store_unused: } { \cs_if_exist:cTF { \c_@@_code_root_str \l_@@_module_str / unknown } { \cs:w \c_@@_code_root_str \l_@@_module_str / unknown \exp_after:wN \cs_end: \exp_after:wN { \l_keys_value_tl } } { \__kernel_msg_error:nnxx { kernel } { key-unknown } { \l_keys_path_str } { \l_@@_module_str } } } } \cs_new:Npn \@@_execute:nn #1#2 { \cs_if_exist:cTF { \c_@@_code_root_str #1 } { \cs:w \c_@@_code_root_str #1 \exp_after:wN \cs_end: \exp_after:wN { \l_keys_value_tl } } {#2} } % \end{macrocode} % When there is no relative path, things here are easy: just save the key % name and value. When we are working with a relative path, first we % need to turn it into a string: that can't happen earlier as we need % to store \cs{q_@@_no_value}. Then, use a standard delimited approach to fish % out the partial path. % \begin{macrocode} \cs_new_protected:Npn \@@_store_unused: { \@@_quark_if_no_value:NTF \l_@@_relative_tl { \clist_put_right:Nx \l_@@_unused_clist { \exp_not:o \l_keys_key_str \bool_if:NF \l_@@_no_value_bool { = { \exp_not:o \l_keys_value_tl } } } } { \tl_if_empty:NTF \l_@@_relative_tl { \clist_put_right:Nx \l_@@_unused_clist { \exp_not:o \l_keys_path_str \bool_if:NF \l_@@_no_value_bool { = { \exp_not:o \l_keys_value_tl } } } } { \@@_store_unused_aux: } } } \cs_new_protected:Npn \@@_store_unused_aux: { \tl_set:Nx \l_@@_relative_tl { \exp_args:No \@@_trim_spaces:n \l_@@_relative_tl } \use:x { \cs_set_protected:Npn \@@_store_unused:w ####1 \l_@@_relative_tl / ####2 \l_@@_relative_tl / ####3 \s_@@_stop } { \tl_if_blank:nF {##1} { \__kernel_msg_error:nnxx { kernel } { bad-relative-key-path } \l_keys_path_str \l_@@_relative_tl } \clist_put_right:Nx \l_@@_unused_clist { \exp_not:n {##2} \bool_if:NF \l_@@_no_value_bool { = { \exp_not:o \l_keys_value_tl } } } } \use:x { \@@_store_unused:w \l_keys_path_str \l_@@_relative_tl / \l_@@_relative_tl / \s_@@_stop } } \cs_new_protected:Npn \@@_store_unused:w { } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\@@_choice_find:n} % \begin{macro}[EXP]{\@@_choice_find:nn} % \begin{macro}[EXP]{\@@_multichoice_find:n} % Executing a choice has two parts. First, try the choice given, then % if that fails call the unknown key. That always exists, as it is created % when a choice is first made. So there is no need for any escape code. % For multiple choices, the same code ends up used in a mapping. % \begin{macrocode} \cs_new:Npn \@@_choice_find:n #1 { \str_if_empty:NTF \l_@@_inherit_str { \@@_choice_find:nn { \l_keys_path_str } {#1} } { \@@_choice_find:nn { \l_@@_inherit_str / \l_keys_key_str } {#1} } } \cs_new:Npn \@@_choice_find:nn #1#2 { \cs_if_exist:cTF { \c_@@_code_root_str #1 / \@@_trim_spaces:n {#2} } { \use:c { \c_@@_code_root_str #1 / \@@_trim_spaces:n {#2} } {#2} } { \use:c { \c_@@_code_root_str #1 / unknown } {#2} } } \cs_new:Npn \@@_multichoice_find:n #1 { \clist_map_function:nN {#1} \@@_choice_find:n } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \subsection{Utilities} % % \begin{macro}[EXP]{\@@_parent:n, \@@_parent:o} % \begin{macro}[EXP]{\@@_parent:w} % Used to strip off the ending part of the key path after the last~|/|. % \begin{macrocode} \cs_new:Npn \@@_parent:n #1 { \@@_parent:w #1 / / \s_@@_stop { } } \cs_generate_variant:Nn \@@_parent:n { o } \cs_new:Npn \@@_parent:w #1 / #2 / #3 \s_@@_stop #4 { \tl_if_blank:nTF {#2} { \tl_if_blank:nF {#4} { \use_none:n #4 } } { \@@_parent:w #2 / #3 \s_@@_stop { #4 / #1 } } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\@@_trim_spaces:n} % \begin{macro}[EXP] % { % \@@_trim_spaces_auxi:w , % \@@_trim_spaces_auxii:w , % \@@_trim_spaces_auxiii:w % } % Space stripping has to allow for the fact that the key here might have % several parts, and spaces need to be stripped from each part. % \begin{macrocode} \cs_new:Npn \@@_trim_spaces:n #1 { \exp_after:wN \@@_trim_spaces_auxi:w \tl_to_str:n {#1} / \q_@@_nil \s_@@_stop } \cs_new:Npn \@@_trim_spaces_auxi:w #1 / #2 \s_@@_stop { \@@_quark_if_nil:nTF {#2} { \tl_trim_spaces:n {#1} } { \@@_trim_spaces_auxii:w #1 / #2 } } \cs_new:Npn \@@_trim_spaces_auxii:w #1 / #2 / \q_@@_nil { \tl_trim_spaces:n {#1} \@@_trim_spaces_auxiii:w #2 / \q_@@_recursion_tail / \q_@@_recursion_stop } \cs_set:Npn \@@_trim_spaces_auxiii:w #1 / { \@@_if_recursion_tail_stop:n {#1} / \tl_trim_spaces:n { #1 } \@@_trim_spaces_auxiii:w } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[EXP,pTF]{\keys_if_exist:nn} % A utility for others to see if a key exists. % \begin{macrocode} \prg_new_conditional:Npnn \keys_if_exist:nn #1#2 { p , T , F , TF } { \cs_if_exist:cTF { \c_@@_code_root_str \@@_trim_spaces:n { #1 / #2 } } { \prg_return_true: } { \prg_return_false: } } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP,pTF]{\keys_if_choice_exist:nnn} % Just an alternative view on \cs{keys_if_exist:nnTF}. % \begin{macrocode} \prg_new_conditional:Npnn \keys_if_choice_exist:nnn #1#2#3 { p , T , F , TF } { \cs_if_exist:cTF { \c_@@_code_root_str \@@_trim_spaces:n { #1 / #2 / #3 } } { \prg_return_true: } { \prg_return_false: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\keys_show:nn, \keys_log:nn, \@@_show:Nnn} % To show a key, show its code using a message. % \begin{macrocode} \cs_new_protected:Npn \keys_show:nn { \@@_show:Nnn \msg_show:nnxxxx } \cs_new_protected:Npn \keys_log:nn { \@@_show:Nnn \msg_log:nnxxxx } \cs_new_protected:Npn \@@_show:Nnn #1#2#3 { #1 { LaTeX / kernel } { show-key } { \@@_trim_spaces:n { #2 / #3 } } { \keys_if_exist:nnT {#2} {#3} { \exp_args:Nnf \msg_show_item_unbraced:nn { code } { \exp_args:Nc \cs_replacement_spec:N { \c_@@_code_root_str \@@_trim_spaces:n { #2 / #3 } } } } } { } { } } % \end{macrocode} % \end{macro} % % \subsection{Messages} % % For when there is a need to complain. % \begin{macrocode} \__kernel_msg_new:nnnn { kernel } { bad-relative-key-path } { The~key~'#1'~is~not~inside~the~'#2'~path. } { The~key~'#1'~cannot~be~expressed~relative~to~path~'#2'. } \__kernel_msg_new:nnnn { kernel } { boolean-values-only } { Key~'#1'~accepts~boolean~values~only. } { The~key~'#1'~only~accepts~the~values~'true'~and~'false'. } \__kernel_msg_new:nnnn { kernel } { key-choice-unknown } { Key~'#1'~accepts~only~a~fixed~set~of~choices. } { The~key~'#1'~only~accepts~predefined~values,~ and~'#2'~is~not~one~of~these. } \__kernel_msg_new:nnnn { kernel } { key-unknown } { The~key~'#1'~is~unknown~and~is~being~ignored. } { The~module~'#2'~does~not~have~a~key~called~'#1'.\\ Check~that~you~have~spelled~the~key~name~correctly. } \__kernel_msg_new:nnnn { kernel } { nested-choice-key } { Attempt~to~define~'#1'~as~a~nested~choice~key. } { The~key~'#1'~cannot~be~defined~as~a~choice~as~the~parent~key~'#2'~is~ itself~a~choice. } \__kernel_msg_new:nnnn { kernel } { value-forbidden } { The~key~'#1'~does~not~take~a~value. } { The~key~'#1'~should~be~given~without~a~value.\\ The~value~'#2'~was~present:~the~key~will~be~ignored. } \__kernel_msg_new:nnnn { kernel } { value-required } { The~key~'#1'~requires~a~value. } { The~key~'#1'~must~have~a~value.\\ No~value~was~present:~the~key~will~be~ignored. } \__kernel_msg_new:nnn { kernel } { show-key } { The~key~#1~ \tl_if_empty:nTF {#2} { is~undefined. } { has~the~properties: #2 . } } % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % %\end{implementation} % %\PrintIndex