% \iffalse meta-comment % %% File: l3prop.dtx % % Copyright (C) 1990-2019 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{l3prop} package\\ Property lists^^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 2019-05-28} % % \maketitle % % \begin{documentation} % % \LaTeX3 implements a \enquote{property list} data type, which contain % an unordered list of entries each of which consists of a \meta{key} and % an associated \meta{value}. The \meta{key} and \meta{value} may both be % any \meta{balanced text}. It is possible to map functions to property lists % such that the function is applied to every key--value pair within % the list. % % Each entry in a property list must have a unique \meta{key}: if an entry is % added to a property list which already contains the \meta{key} then the new % entry overwrites the existing one. The \meta{keys} are compared on a % string basis, using the same method as \cs{str_if_eq:nn}. % % Property lists are intended for storing key-based information for use within % code. This is in contrast to key--value lists, which are a form of % \emph{input} parsed by the \pkg{keys} module. % % \section{Creating and initialising property lists} % % \begin{function}{\prop_new:N, \prop_new:c} % \begin{syntax} % \cs{prop_new:N} \meta{property list} % \end{syntax} % Creates a new \meta{property list} or raises an error if the name is % already taken. The declaration is global. The \meta{property list} % initially contains no entries. % \end{function} % % \begin{function} % {\prop_clear:N, \prop_clear:c, \prop_gclear:N, \prop_gclear:c} % \begin{syntax} % \cs{prop_clear:N} \meta{property list} % \end{syntax} % Clears all entries from the \meta{property list}. % \end{function} % % \begin{function} % { % \prop_clear_new:N, \prop_clear_new:c, % \prop_gclear_new:N, \prop_gclear_new:c % } % \begin{syntax} % \cs{prop_clear_new:N} \meta{property list} % \end{syntax} % Ensures that the \meta{property list} exists globally by applying % \cs{prop_new:N} if necessary, then applies % \cs[index=prop_clear:N]{prop_(g)clear:N} to leave % the list empty. % \end{function} % % \begin{function} % { % \prop_set_eq:NN, \prop_set_eq:cN, \prop_set_eq:Nc, \prop_set_eq:cc, % \prop_gset_eq:NN, \prop_gset_eq:cN, \prop_gset_eq:Nc, \prop_gset_eq:cc % } % \begin{syntax} % \cs{prop_set_eq:NN} \meta{property list_1} \meta{property list_2} % \end{syntax} % Sets the content of \meta{property list_1} equal to that of % \meta{property list_2}. % \end{function} % % \section{Adding entries to property lists} % % \begin{function}[updated = 2012-07-09] % { % \prop_put:Nnn, \prop_put:NnV, \prop_put:Nno, \prop_put:Nnx, % \prop_put:NVn, \prop_put:NVV, \prop_put:Non, \prop_put:Noo, % \prop_put:cnn, \prop_put:cnV, \prop_put:cno, \prop_put:cnx, % \prop_put:cVn, \prop_put:cVV, \prop_put:con, \prop_put:coo, % \prop_gput:Nnn, \prop_gput:NnV, \prop_gput:Nno, \prop_gput:Nnx, % \prop_gput:NVn, \prop_gput:NVV, \prop_gput:Non, \prop_gput:Noo, % \prop_gput:cnn, \prop_gput:cnV, \prop_gput:cno, \prop_gput:cnx, % \prop_gput:cVn, \prop_gput:cVV, \prop_gput:con, \prop_gput:coo % } % \begin{syntax} % \cs{prop_put:Nnn} \meta{property list} \Arg{key} \Arg{value} % \end{syntax} % Adds an entry to the \meta{property list} which may be accessed % using the \meta{key} and which has \meta{value}. Both the \meta{key} % and \meta{value} may contain any \meta{balanced text}. The \meta{key} % is stored after processing with \cs{tl_to_str:n}, meaning that % category codes are ignored. If the \meta{key} is already present % in the \meta{property list}, the existing entry is overwritten % by the new \meta{value}. % \end{function} % % \begin{function} % { % \prop_put_if_new:Nnn, \prop_put_if_new:cnn, % \prop_gput_if_new:Nnn, \prop_gput_if_new:cnn % } % \begin{syntax} % \cs{prop_put_if_new:Nnn} \meta{property list} \Arg{key} \Arg{value} % \end{syntax} % If the \meta{key} is present in the \meta{property list} then % no action is taken. If the \meta{key} is not present in the % \meta{property list} then a new entry is added. Both the \meta{key} % and \meta{value} may contain any \meta{balanced text}. The \meta{key} % is stored after processing with \cs{tl_to_str:n}, meaning that % category codes are ignored. % \end{function} % % \section{Recovering values from property lists} % % \begin{function}[updated = 2011-08-28] % { % \prop_get:NnN, \prop_get:NVN, \prop_get:NoN, % \prop_get:cnN, \prop_get:cVN, \prop_get:coN, % } % \begin{syntax} % \cs{prop_get:NnN} \meta{property list} \Arg{key} \meta{tl var} % \end{syntax} % Recovers the \meta{value} stored with \meta{key} from the % \meta{property list}, and places this in the \meta{token list % variable}. If the \meta{key} is not found in the % \meta{property list} then the \meta{token list variable} is set % to the special marker \cs{q_no_value}. The \meta{token list % variable} is set within the current \TeX{} group. See also % \cs{prop_get:NnNTF}. % \end{function} % % \begin{function}[updated = 2011-08-18] % {\prop_pop:NnN, \prop_pop:NoN, \prop_pop:cnN, \prop_pop:coN} % \begin{syntax} % \cs{prop_pop:NnN} \meta{property list} \Arg{key} \meta{tl var} % \end{syntax} % Recovers the \meta{value} stored with \meta{key} from the % \meta{property list}, and places this in the \meta{token list % variable}. If the \meta{key} is not found in the % \meta{property list} then the \meta{token list variable} is set % to the special marker \cs{q_no_value}. The \meta{key} and % \meta{value} are then deleted from the property list. Both % assignments are local. See also \cs{prop_pop:NnNTF}. % \end{function} % % \begin{function}[updated = 2011-08-18] % {\prop_gpop:NnN, \prop_gpop:NoN, \prop_gpop:cnN, \prop_gpop:coN} % \begin{syntax} % \cs{prop_gpop:NnN} \meta{property list} \Arg{key} \meta{tl var} % \end{syntax} % Recovers the \meta{value} stored with \meta{key} from the % \meta{property list}, and places this in the \meta{token list % variable}. If the \meta{key} is not found in the % \meta{property list} then the \meta{token list variable} is set % to the special marker \cs{q_no_value}. The \meta{key} and % \meta{value} are then deleted from the property list. % The \meta{property list} is modified globally, while the assignment of % the \meta{token list variable} is local. See also \cs{prop_gpop:NnNTF}. % \end{function} % % \begin{function}[added = 2014-07-17, EXP]{\prop_item:Nn, \prop_item:cn} % \begin{syntax} % \cs{prop_item:Nn} \meta{property list} \Arg{key} % \end{syntax} % Expands to the \meta{value} corresponding to the \meta{key} in % the \meta{property list}. If the \meta{key} is missing, this has % an empty expansion. % \begin{texnote} % This function is slower than the non-expandable analogue % \cs{prop_get:NnN}. % The result is returned within the \tn{unexpanded} % primitive (\cs{exp_not:n}), which means that the \meta{value} % does not expand further when appearing in an \texttt{x}-type % argument expansion. % \end{texnote} % \end{function} % % \begin{function}[EXP]{\prop_count:N, \prop_count:c} % \begin{syntax} % \cs{prop_count:N} \meta{property list} % \end{syntax} % Leaves the number of key--value pairs in the \meta{property list} in % the input stream as an \meta{integer denotation}. % \end{function} % % \section{Modifying property lists} % % \begin{function}[added = 2012-05-12] % { % \prop_remove:Nn, \prop_remove:NV, \prop_remove:cn, \prop_remove:cV, % \prop_gremove:Nn, \prop_gremove:NV, \prop_gremove:cn, \prop_gremove:cV % } % \begin{syntax} % \cs{prop_remove:Nn} \meta{property list} \Arg{key} % \end{syntax} % Removes the entry listed under \meta{key} from the % \meta{property list}. If the \meta{key} is % not found in the \meta{property list} no change occurs, % \emph{i.e}~there is no need to test for the existence of a key before % deleting it. % \end{function} % % \section{Property list conditionals} % % \begin{function}[EXP, pTF, added = 2012-03-03] % {\prop_if_exist:N, \prop_if_exist:c} % \begin{syntax} % \cs{prop_if_exist_p:N} \meta{property list} % \cs{prop_if_exist:NTF} \meta{property list} \Arg{true code} \Arg{false code} % \end{syntax} % Tests whether the \meta{property list} is currently defined. This does not % check that the \meta{property list} really is a property list variable. % \end{function} % % \begin{function}[EXP, pTF]{\prop_if_empty:N, \prop_if_empty:c} % \begin{syntax} % \cs{prop_if_empty_p:N} \meta{property list} % \cs{prop_if_empty:NTF} \meta{property list} \Arg{true code} \Arg{false code} % \end{syntax} % Tests if the \meta{property list} is empty (containing no entries). % \end{function} % % \begin{function}[updated = 2011-09-15, EXP, pTF] % { % \prop_if_in:Nn, \prop_if_in:NV, \prop_if_in:No, % \prop_if_in:cn, \prop_if_in:cV, \prop_if_in:co % } % \begin{syntax} % \cs{prop_if_in:NnTF} \meta{property list} \Arg{key} \Arg{true code} \Arg{false code} % \end{syntax} % Tests if the \meta{key} is present in the \meta{property list}, % making the comparison using the method described by \cs{str_if_eq:nnTF}. % \begin{texnote} % This function iterates through every key--value pair in the % \meta{property list} and is therefore slower than using the % non-expandable \cs{prop_get:NnNTF}. % \end{texnote} % \end{function} % % \section{Recovering values from property lists with branching} % % The functions in this section combine tests for the presence of a key % in a property list with recovery of the associated valued. This makes them % useful for cases where different cases follow dependent on the presence % or absence of a key in a property list. They offer increased readability % and performance over separate testing and recovery phases. % % \begin{function}[updated = 2012-05-19, TF] % { % \prop_get:NnN, \prop_get:NVN, \prop_get:NoN, % \prop_get:cnN, \prop_get:cVN, \prop_get:coN % } % \begin{syntax} % \cs{prop_get:NnNTF} \meta{property list} \Arg{key} \meta{token list variable} \\ % ~~\Arg{true code} \Arg{false code} % \end{syntax} % If the \meta{key} is not present in the \meta{property list}, leaves % the \meta{false code} in the input stream. The value of the % \meta{token list variable} is not defined in this case and should % not be relied upon. If the \meta{key} is present in the % \meta{property list}, stores the corresponding \meta{value} in the % \meta{token list variable} without removing it from the % \meta{property list}, then leaves the \meta{true code} in the input % stream. The \meta{token list variable} is assigned locally. % \end{function} % % \begin{function}[TF, added = 2011-08-18, updated = 2012-05-19] % {\prop_pop:NnN, \prop_pop:cnN} % \begin{syntax} % \cs{prop_pop:NnNTF} \meta{property list} \Arg{key} \meta{token list variable} \Arg{true code} \Arg{false code} % \end{syntax} % If the \meta{key} is not present in the \meta{property list}, leaves % the \meta{false code} in the input stream. The value of the % \meta{token list variable} is not defined in this case and should % not be relied upon. If the \meta{key} is present in % the \meta{property list}, pops the corresponding \meta{value} % in the \meta{token list variable}, \emph{i.e.}~removes the item from % the \meta{property list}. % Both the \meta{property list} and the \meta{token list variable} % are assigned locally. % \end{function} % % \begin{function}[TF, added = 2011-08-18, updated = 2012-05-19] % {\prop_gpop:NnN, \prop_gpop:cnN} % \begin{syntax} % \cs{prop_gpop:NnNTF} \meta{property list} \Arg{key} \meta{token list variable} \Arg{true code} \Arg{false code} % \end{syntax} % If the \meta{key} is not present in the \meta{property list}, leaves % the \meta{false code} in the input stream. The value of the % \meta{token list variable} is not defined in this case and should % not be relied upon. If the \meta{key} is present in % the \meta{property list}, pops the corresponding \meta{value} % in the \meta{token list variable}, \emph{i.e.}~removes the item from % the \meta{property list}. % The \meta{property list} is modified globally, while the % \meta{token list variable} is assigned locally. % \end{function} % % \section{Mapping to property lists} % % \begin{function}[updated = 2013-01-08, rEXP] % {\prop_map_function:NN, \prop_map_function:cN} % \begin{syntax} % \cs{prop_map_function:NN} \meta{property list} \meta{function} % \end{syntax} % Applies \meta{function} to every \meta{entry} stored in the % \meta{property list}. The \meta{function} receives two arguments for % each iteration: the \meta{key} and associated \meta{value}. % The order in which \meta{entries} are returned is not defined and % should not be relied upon. % \end{function} % % \begin{function}[updated = 2013-01-08] % {\prop_map_inline:Nn, \prop_map_inline:cn} % \begin{syntax} % \cs{prop_map_inline:Nn} \meta{property list} \Arg{inline function} % \end{syntax} % Applies \meta{inline function} to every \meta{entry} stored % within the \meta{property list}. The \meta{inline function} should % consist of code which receives the \meta{key} as |#1| and the % \meta{value} as |#2|. % The order in which \meta{entries} are returned is not defined and % should not be relied upon. % \end{function} % % \begin{function}[updated = 2012-06-29, rEXP]{\prop_map_break:} % \begin{syntax} % \cs{prop_map_break:} % \end{syntax} % Used to terminate a \cs[no-index]{prop_map_\ldots} function before all % entries in the \meta{property list} have been processed. This % normally takes place within a conditional statement, for example % \begin{verbatim} % \prop_map_inline:Nn \l_my_prop % { % \str_if_eq:nnTF { #1 } { bingo } % { \prop_map_break: } % { % % Do something useful % } % } % \end{verbatim} % Use outside of a \cs[no-index]{prop_map_\ldots} scenario leads to low % level \TeX{} errors. % \begin{texnote} % When the mapping is broken, additional tokens may be inserted % before further items are taken % from the input stream. This depends on the design of the mapping % function. % \end{texnote} % \end{function} % % \begin{function}[updated = 2012-06-29, rEXP]{\prop_map_break:n} % \begin{syntax} % \cs{prop_map_break:n} \Arg{code} % \end{syntax} % Used to terminate a \cs[no-index]{prop_map_\ldots} function before all % entries in the \meta{property list} have been processed, inserting % the \meta{code} after the mapping has ended. This % normally takes place within a conditional statement, for example % \begin{verbatim} % \prop_map_inline:Nn \l_my_prop % { % \str_if_eq:nnTF { #1 } { bingo } % { \prop_map_break:n { } } % { % % Do something useful % } % } % \end{verbatim} % Use outside of a \cs[no-index]{prop_map_\ldots} scenario leads to low % level \TeX{} errors. % \begin{texnote} % When the mapping is broken, additional tokens may be inserted % before the \meta{code} is % inserted into the input stream. % This depends on the design of the mapping function. % \end{texnote} % \end{function} % % \section{Viewing property lists} % % \begin{function}[updated = 2015-08-01]{\prop_show:N, \prop_show:c} % \begin{syntax} % \cs{prop_show:N} \meta{property list} % \end{syntax} % Displays the entries in the \meta{property list} in the terminal. % \end{function} % % \begin{function}[added = 2014-08-12, updated = 2015-08-01]{\prop_log:N, \prop_log:c} % \begin{syntax} % \cs{prop_log:N} \meta{property list} % \end{syntax} % Writes the entries in the \meta{property list} in the log file. % \end{function} % % \section{Scratch property lists} % % \begin{variable}[added = 2012-06-23]{\l_tmpa_prop, \l_tmpb_prop} % Scratch property lists for local assignment. These are never used by % the kernel code, and so are safe for use with any \LaTeX3-defined % function. However, they may be overwritten by other non-kernel % code and so should only be used for short-term storage. % \end{variable} % % \begin{variable}[added = 2012-06-23]{\g_tmpa_prop, \g_tmpb_prop} % Scratch property lists for global assignment. These are never used by % the kernel code, and so are safe for use with any \LaTeX3-defined % function. However, they may be overwritten by other non-kernel % code and so should only be used for short-term storage. % \end{variable} % % \section{Constants} % % \begin{variable}{\c_empty_prop} % A permanently-empty property list used for internal comparisons. % \end{variable} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3prop} implementation} % % \TestFiles{m3prop001, m3prop002, m3prop003, m3prop004, m3show001} % % \begin{macrocode} %<*initex|package> % \end{macrocode} % % \begin{macrocode} %<@@=prop> % \end{macrocode} % % A property list is a macro whose top-level expansion is of the form % \begin{quote} % \cs{s_@@} % \cs{@@_pair:wn} \meta{key_1} \cs{s_@@} \Arg{value_1} \\ % \ldots{} \\ % \cs{@@_pair:wn} \meta{key_n} \cs{s_@@} \Arg{value_n} \\ % \end{quote} % where \cs{s_@@} is a scan mark % (equal to \cs{scan_stop:}), and \cs{@@_pair:wn} can be used to map % through the property list. % % \begin{variable}{\s_@@} % The internal token used at the beginning of property lists. This is % also used after each \meta{key} (see \cs{@@_pair:wn}). % \end{variable} % % \begin{variable}{\@@_pair:wn} % \begin{syntax} % \cs{@@_pair:wn} \meta{key} \cs{s_@@} \Arg{item} % \end{syntax} % The internal token used to begin each key--value pair in the % property list. If expanded outside of a mapping or manipulation % function, an error is raised. The definition should always be % set globally. % \end{variable} % % \begin{variable}{\l_@@_internal_tl} % Token list used to store new key--value pairs to be inserted by % functions of the \cs{prop_put:Nnn} family. % \end{variable} % % \begin{function}[updated = 2013-01-08]{\@@_split:NnTF} % \begin{syntax} % \cs{@@_split:NnTF} \meta{property list} \Arg{key} \Arg{true code} \Arg{false code} % \end{syntax} % Splits the \meta{property list} at the \meta{key}, giving three % token lists: the \meta{extract} of \meta{property list} before the % \meta{key}, the \meta{value} associated with the \meta{key} and the % \meta{extract} of the \meta{property list} after the \meta{value}. % Both \meta{extracts} retain the internal structure of a property % list, and the concatenation of the two \meta{extracts} is a % property list. % If the \meta{key} is present in the \meta{property list} then the % \meta{true code} is left in the input stream, with |#1|, |#2|, and % |#3| replaced by the first \meta{extract}, the \meta{value}, and the % second extract. % If the \meta{key} is not present in the \meta{property list} then % the \meta{false code} is left in the input stream, with no trailing % material. % Both \meta{true code} and \meta{false code} are used in the % replacement text of a macro defined internally, hence macro % parameter characters should be doubled, except |#1|, |#2|, and |#3| % which stand in the \meta{true code} for the three extracts from the % property list. % The \meta{key} comparison takes place as described for \cs{str_if_eq:nn}. % \end{function} % % \begin{macro}{\s_@@} % A private scan mark is used as a marker after each key, and at the % very beginning of the property list. % \begin{macrocode} \scan_new:N \s_@@ % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_pair:wn} % The delimiter is always defined, but when misused simply triggers an % error and removes its argument. % \begin{macrocode} \cs_new:Npn \@@_pair:wn #1 \s_@@ #2 { \__kernel_msg_expandable_error:nn { kernel } { misused-prop } } % \end{macrocode} % \end{macro} % % \begin{variable}{\l_@@_internal_tl} % Token list used to store the new key--value pair inserted by % \cs{prop_put:Nnn} and friends. % \begin{macrocode} \tl_new:N \l_@@_internal_tl % \end{macrocode} % \end{variable} % % \begin{variable}[tested = m3prop004]{\c_empty_prop} % An empty prop. % \begin{macrocode} \tl_const:Nn \c_empty_prop { \s_@@ } % \end{macrocode} % \end{variable} % % \subsection{Allocation and initialisation} % % \begin{macro}[tested = m3prop001]{\prop_new:N, \prop_new:c} % Property lists are initialized with the value \cs{c_empty_prop}. % \begin{macrocode} \cs_new_protected:Npn \prop_new:N #1 { \__kernel_chk_if_free_cs:N #1 \cs_gset_eq:NN #1 \c_empty_prop } \cs_generate_variant:Nn \prop_new:N { c } % \end{macrocode} % \end{macro} % % \begin{macro}[tested = m3prop001]{\prop_clear:N, \prop_clear:c} % \begin{macro}[tested = m3prop001]{\prop_gclear:N, \prop_gclear:c} % The same idea for clearing. % \begin{macrocode} \cs_new_protected:Npn \prop_clear:N #1 { \prop_set_eq:NN #1 \c_empty_prop } \cs_generate_variant:Nn \prop_clear:N { c } \cs_new_protected:Npn \prop_gclear:N #1 { \prop_gset_eq:NN #1 \c_empty_prop } \cs_generate_variant:Nn \prop_gclear:N { c } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[tested = m3prop001]{\prop_clear_new:N, \prop_clear_new:c} % \begin{macro}[tested = m3prop001]{\prop_gclear_new:N, \prop_gclear_new:c} % Once again a simple variation of the token list functions. % \begin{macrocode} \cs_new_protected:Npn \prop_clear_new:N #1 { \prop_if_exist:NTF #1 { \prop_clear:N #1 } { \prop_new:N #1 } } \cs_generate_variant:Nn \prop_clear_new:N { c } \cs_new_protected:Npn \prop_gclear_new:N #1 { \prop_if_exist:NTF #1 { \prop_gclear:N #1 } { \prop_new:N #1 } } \cs_generate_variant:Nn \prop_gclear_new:N { c } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[tested = m3prop001] % {\prop_set_eq:NN, \prop_set_eq:cN, \prop_set_eq:Nc, \prop_set_eq:cc} % \begin{macro}[tested = m3prop001] % {\prop_gset_eq:NN, \prop_gset_eq:cN, \prop_gset_eq:Nc, \prop_gset_eq:cc} % These are simply copies from the token list functions. % \begin{macrocode} \cs_new_eq:NN \prop_set_eq:NN \tl_set_eq:NN \cs_new_eq:NN \prop_set_eq:Nc \tl_set_eq:Nc \cs_new_eq:NN \prop_set_eq:cN \tl_set_eq:cN \cs_new_eq:NN \prop_set_eq:cc \tl_set_eq:cc \cs_new_eq:NN \prop_gset_eq:NN \tl_gset_eq:NN \cs_new_eq:NN \prop_gset_eq:Nc \tl_gset_eq:Nc \cs_new_eq:NN \prop_gset_eq:cN \tl_gset_eq:cN \cs_new_eq:NN \prop_gset_eq:cc \tl_gset_eq:cc % \end{macrocode} % \end{macro} % \end{macro} % % \begin{variable}[tested = m3prop004]{\l_tmpa_prop, \l_tmpb_prop} % \begin{variable}[tested = m3prop004]{\g_tmpa_prop, \g_tmpb_prop} % We can now initialize the scratch variables. % \begin{macrocode} \prop_new:N \l_tmpa_prop \prop_new:N \l_tmpb_prop \prop_new:N \g_tmpa_prop \prop_new:N \g_tmpb_prop % \end{macrocode} % \end{variable} % \end{variable} % % \begin{macro}{\prop_set_from_keyval:Nn, \prop_set_from_keyval:cn} % \begin{macro}{\prop_gset_from_keyval:Nn, \prop_gset_from_keyval:cn} % \begin{macro}{\prop_const_from_keyval:Nn, \prop_const_from_keyval:cn} % \begin{macro} % { % \@@_from_keyval:n, % \@@_from_keyval_loop:w, % \@@_from_keyval_split:Nw, % \@@_from_keyval_key:n, % \@@_from_keyval_key:w, % \@@_from_keyval_value:n, % \@@_from_keyval_value:w % } % Loop through items separated by commas, with \cs{q_mark} to avoid % losing braces. After checking for termination, split the item at % the first then at the second |=| (which ought to be the first of the % trailing~|=|). At both splits, trim spaces and % call \cs{@@_from_keyval_key:w}, then \cs{@@_from_keyval_value:w}, % followed by the trimmed material, \cs{q_nil}, the subsequent part of % the item, and the trailing |=|'s and \cs{q_stop}. After finding the % \meta{key} just store it after \cs{q_stop}. After finding the % \meta{value} ignore completely empty items (both trailing~|=| were % used as delimiters and all parts are empty); if the remaining % part~|#2| consists exactly of the second trailing~|=| (namely there % was exactly one |=|~in the item) then output one key--value pair for % the property list; otherwise complain about a missing or extra~|=|. % \begin{macrocode} \cs_new_protected:Npn \prop_set_from_keyval:Nn #1#2 { \tl_set:Nx #1 { \@@_from_keyval:n {#2} } } \cs_generate_variant:Nn \prop_set_from_keyval:Nn { c } \cs_new_protected:Npn \prop_gset_from_keyval:Nn #1#2 { \tl_gset:Nx #1 { \@@_from_keyval:n {#2} } } \cs_generate_variant:Nn \prop_gset_from_keyval:Nn { c } \cs_new_protected:Npn \prop_const_from_keyval:Nn #1#2 { \tl_const:Nx #1 { \@@_from_keyval:n {#2} } } \cs_generate_variant:Nn \prop_const_from_keyval:Nn { c } \cs_new:Npn \@@_from_keyval:n #1 { \s_@@ \@@_from_keyval_loop:w \q_mark #1 , \q_recursion_tail , \q_recursion_stop } \cs_new:Npn \@@_from_keyval_loop:w #1 , { \quark_if_recursion_tail_stop:o { \use_none:n #1 } \@@_from_keyval_split:Nw \@@_from_keyval_key:n #1 = = \q_stop { \use_none:n #1 } \@@_from_keyval_loop:w \q_mark } \cs_new:Npn \@@_from_keyval_split:Nw #1#2 = { \tl_trim_spaces_apply:oN { \use_none:n #2 } #1 \q_nil } \cs_new:Npn \@@_from_keyval_key:n #1 { \@@_from_keyval_key:w #1 } \cs_new:Npn \@@_from_keyval_key:w #1 \q_nil #2 \q_stop { \@@_from_keyval_split:Nw \@@_from_keyval_value:n \q_mark #2 \q_stop {#1} } \cs_new:Npn \@@_from_keyval_value:n #1 { \@@_from_keyval_value:w #1 } \cs_new:Npn \@@_from_keyval_value:w #1 \q_nil #2 \q_stop #3#4 { \tl_if_empty:nF { #3 #1 #2 } { \str_if_eq:nnTF {#2} { = } { \exp_not:N \@@_pair:wn \tl_to_str:n {#3} \s_@@ { \exp_not:n {#1} } } { \__kernel_msg_expandable_error:nnf { kernel } { prop-keyval } { \exp_after:wN \exp_stop_f: #4 } } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \subsection{Accessing data in property lists} % % \begin{macro}{\@@_split:NnTF} % \begin{macro}{\@@_split_aux:NnTF} % \begin{macro}[EXP]{\@@_split_aux:w} % This function is used by most of the module, and hence must be fast. % It receives a \meta{property list}, a \meta{key}, a \meta{true code} % and a \meta{false code}. The aim is to split the \meta{property % list} at the given \meta{key} into the \meta{extract_1} before the % key--value pair, the \meta{value} associated with the \meta{key} and % the \meta{extract_2} after the key--value pair. This is done using % a delimited function, whose definition is as follows, where the % \meta{key} is turned into a string. % \begin{quote} % \cs{cs_set:Npn} \cs{@@_split_aux:w} |#1| \\ % \quad \cs{@@_pair:wn} \meta{key} \cs{s_@@} |#2| \\ % \quad |#3| \cs{q_mark} |#4| |#5| \cs{q_stop} \\ % \quad |{| |#4| \Arg{true code} \Arg{false code} |}| % \end{quote} % % If the \meta{key} is present in the property list, % \cs{@@_split_aux:w}'s |#1| is the part before the \meta{key}, |#2| % is the \meta{value}, |#3| is the part after the \meta{key}, |#4| is % \cs{use_i:nn}, and |#5| is additional tokens that we do not care % about. The \meta{true code} is left in the input stream, and can % use the parameters |#1|, |#2|, |#3| for the three parts of the % property list as desired. Namely, the original property list is in % this case |#1| \cs{@@_pair:wn} \meta{key} \cs{s_@@} |{#2}| |#3|. % % If the \meta{key} is not there, then the \meta{function} is % \cs{use_ii:nn}, which keeps the \meta{false code}. % \begin{macrocode} \cs_new_protected:Npn \@@_split:NnTF #1#2 { \exp_args:NNo \@@_split_aux:NnTF #1 { \tl_to_str:n {#2} } } \cs_new_protected:Npn \@@_split_aux:NnTF #1#2#3#4 { \cs_set:Npn \@@_split_aux:w ##1 \@@_pair:wn #2 \s_@@ ##2 ##3 \q_mark ##4 ##5 \q_stop { ##4 {#3} {#4} } \exp_after:wN \@@_split_aux:w #1 \q_mark \use_i:nn \@@_pair:wn #2 \s_@@ { } \q_mark \use_ii:nn \q_stop } \cs_new:Npn \@@_split_aux:w { } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[tested = m3prop002] % {\prop_remove:Nn, \prop_remove:NV, \prop_remove:cn, \prop_remove:cV} % \begin{macro}[tested = m3prop002] % {\prop_gremove:Nn, \prop_gremove:NV, \prop_gremove:cn, \prop_gremove:cV} % Deleting from a property starts by splitting the list. % If the key is present in the property list, the returned value is ignored. % If the key is missing, nothing happens. % \begin{macrocode} \cs_new_protected:Npn \prop_remove:Nn #1#2 { \@@_split:NnTF #1 {#2} { \tl_set:Nn #1 { ##1 ##3 } } { } } \cs_new_protected:Npn \prop_gremove:Nn #1#2 { \@@_split:NnTF #1 {#2} { \tl_gset:Nn #1 { ##1 ##3 } } { } } \cs_generate_variant:Nn \prop_remove:Nn { NV } \cs_generate_variant:Nn \prop_remove:Nn { c , cV } \cs_generate_variant:Nn \prop_gremove:Nn { NV } \cs_generate_variant:Nn \prop_gremove:Nn { c , cV } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[tested = m3prop002] % { % \prop_get:NnN, \prop_get:NVN, \prop_get:NoN, % \prop_get:cnN, \prop_get:cVN, \prop_get:coN % } % Getting an item from a list is very easy: after splitting, % if the key is in the property list, just set the token list variable % to the return value, otherwise to \cs{q_no_value}. % \begin{macrocode} \cs_new_protected:Npn \prop_get:NnN #1#2#3 { \@@_split:NnTF #1 {#2} { \tl_set:Nn #3 {##2} } { \tl_set:Nn #3 { \q_no_value } } } \cs_generate_variant:Nn \prop_get:NnN { NV , No } \cs_generate_variant:Nn \prop_get:NnN { c , cV , co } % \end{macrocode} % \end{macro} % % \begin{macro}[tested = m3prop002] % {\prop_pop:NnN, \prop_pop:NoN, \prop_pop:cnN, \prop_pop:coN} % \begin{macro}[tested = m3prop002] % {\prop_gpop:NnN, \prop_gpop:NoN, \prop_gpop:cnN, \prop_gpop:coN} % Popping a value also starts by doing the split. % If the key is present, save the value in the token list and update the % property list as when deleting. % If the key is missing, save \cs{q_no_value} in the token list. % \begin{macrocode} \cs_new_protected:Npn \prop_pop:NnN #1#2#3 { \@@_split:NnTF #1 {#2} { \tl_set:Nn #3 {##2} \tl_set:Nn #1 { ##1 ##3 } } { \tl_set:Nn #3 { \q_no_value } } } \cs_new_protected:Npn \prop_gpop:NnN #1#2#3 { \@@_split:NnTF #1 {#2} { \tl_set:Nn #3 {##2} \tl_gset:Nn #1 { ##1 ##3 } } { \tl_set:Nn #3 { \q_no_value } } } \cs_generate_variant:Nn \prop_pop:NnN { No } \cs_generate_variant:Nn \prop_pop:NnN { c , co } \cs_generate_variant:Nn \prop_gpop:NnN { No } \cs_generate_variant:Nn \prop_gpop:NnN { c , co } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\prop_item:Nn, \prop_item:cn} % \begin{macro}[EXP]{\@@_item_Nn:nwwn} % Getting the value corresponding to a key in a property list in an % expandable fashion is similar to mapping some tokens. Go through % the property list one \meta{key}--\meta{value} pair at a time: the % arguments of \cs{@@_item_Nn:nwn} are the \meta{key} we are looking % for, a \meta{key} of the property list, and its associated value. % The \meta{keys} are compared (as strings). If they match, the % \meta{value} is returned, within \cs{exp_not:n}. The loop % terminates even if the \meta{key} is missing, and yields an empty % value, because we have appended the appropriate % \meta{key}--\meta{empty value} pair to the property list. % \begin{macrocode} \cs_new:Npn \prop_item:Nn #1#2 { \exp_last_unbraced:Noo \@@_item_Nn:nwwn { \tl_to_str:n {#2} } #1 \@@_pair:wn \tl_to_str:n {#2} \s_@@ { } \prg_break_point: } \cs_new:Npn \@@_item_Nn:nwwn #1#2 \@@_pair:wn #3 \s_@@ #4 { \str_if_eq:eeTF {#1} {#3} { \prg_break:n { \exp_not:n {#4} } } { \@@_item_Nn:nwwn {#1} } } \cs_generate_variant:Nn \prop_item:Nn { c } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\prop_count:N, \prop_count:c} % \begin{macro}[EXP]{\@@_count:nn} % Counting the key--value pairs in a property list is done using the % same approach as for other count functions: turn each entry into a % \texttt{+1} then use integer evaluation to actually do the % mathematics. % \begin{macrocode} \cs_new:Npn \prop_count:N #1 { \int_eval:n { 0 \prop_map_function:NN #1 \@@_count:nn } } \cs_new:Npn \@@_count:nn #1#2 { + 1 } \cs_generate_variant:Nn \prop_count:N { c } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[TF, tested = m3prop004] % {\prop_pop:NnN, \prop_pop:cnN, \prop_gpop:NnN, \prop_gpop:cnN} % Popping an item from a property list, keeping track of whether % the key was present or not, is implemented as a conditional. % If the key was missing, neither the property list, nor the token % list are altered. Otherwise, \cs{prg_return_true:} is used after % the assignments. % \begin{macrocode} \prg_new_protected_conditional:Npnn \prop_pop:NnN #1#2#3 { T , F , TF } { \@@_split:NnTF #1 {#2} { \tl_set:Nn #3 {##2} \tl_set:Nn #1 { ##1 ##3 } \prg_return_true: } { \prg_return_false: } } \prg_new_protected_conditional:Npnn \prop_gpop:NnN #1#2#3 { T , F , TF } { \@@_split:NnTF #1 {#2} { \tl_set:Nn #3 {##2} \tl_gset:Nn #1 { ##1 ##3 } \prg_return_true: } { \prg_return_false: } } \prg_generate_conditional_variant:Nnn \prop_pop:NnN { c } { T , F , TF } \prg_generate_conditional_variant:Nnn \prop_gpop:NnN { c } { T , F , TF } % \end{macrocode} % \end{macro} % % \begin{macro}[tested = m3prop002] % { % \prop_put:Nnn, \prop_put:NnV, \prop_put:Nno, \prop_put:Nnx, % \prop_put:NVn, \prop_put:NVV, \prop_put:Non, \prop_put:Noo, % \prop_put:cnn, \prop_put:cnV, \prop_put:cno, \prop_put:cnx, % \prop_put:cVn, \prop_put:cVV, \prop_put:con, \prop_put:coo % } % \begin{macro}[tested = m3prop002] % { % \prop_gput:Nnn, \prop_gput:NnV, \prop_gput:Nno, \prop_gput:Nnx, % \prop_gput:NVn, \prop_gput:NVV, \prop_gput:Non, \prop_gput:Noo, % \prop_gput:cnn, \prop_gput:cnV, \prop_gput:cno, \prop_gput:cnx, % \prop_gput:cVn, \prop_gput:cVV, \prop_gput:con, \prop_gput:coo % } % \begin{macro}{\@@_put:NNnn} % Since the branches of \cs{@@_split:NnTF} are used as the replacement % text of an internal macro, and since the \meta{key} and new % \meta{value} may contain arbitrary tokens, it is not safe to include % them in the argument of \cs{@@_split:NnTF}. We thus start by % storing in \cs{l_@@_internal_tl} tokens which (after % \texttt{x}-expansion) encode the key--value pair. This variable can % safely be used in \cs{@@_split:NnTF}. If the \meta{key} was absent, % append the new key--value to the list. % Otherwise concatenate the extracts |##1| % and |##3| with the new key--value pair \cs{l_@@_internal_tl}. The % updated entry is placed at the same spot as the original \meta{key} % in the property list, preserving the order of entries. % \begin{macrocode} \cs_new_protected:Npn \prop_put:Nnn { \@@_put:NNnn \tl_set:Nx } \cs_new_protected:Npn \prop_gput:Nnn { \@@_put:NNnn \tl_gset:Nx } \cs_new_protected:Npn \@@_put:NNnn #1#2#3#4 { \tl_set:Nn \l_@@_internal_tl { \exp_not:N \@@_pair:wn \tl_to_str:n {#3} \s_@@ { \exp_not:n {#4} } } \@@_split:NnTF #2 {#3} { #1 #2 { \exp_not:n {##1} \l_@@_internal_tl \exp_not:n {##3} } } { #1 #2 { \exp_not:o {#2} \l_@@_internal_tl } } } \cs_generate_variant:Nn \prop_put:Nnn { NnV , Nno , Nnx , NV , NVV , No , Noo } \cs_generate_variant:Nn \prop_put:Nnn { c , cnV , cno , cnx , cV , cVV , co , coo } \cs_generate_variant:Nn \prop_gput:Nnn { NnV , Nno , Nnx , NV , NVV , No , Noo } \cs_generate_variant:Nn \prop_gput:Nnn { c , cnV , cno , cnx , cV , cVV , co , coo } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[tested = m3prop002] % {\prop_put_if_new:Nnn, \prop_put_if_new:cnn} % \begin{macro}[tested = m3prop002] % {\prop_gput_if_new:Nnn, \prop_gput_if_new:cnn} % \begin{macro}{\@@_put_if_new:NNnn} % Adding conditionally also splits. If the key is already present, % the three brace groups given by \cs{@@_split:NnTF} are removed. % If the key is new, then the value is added, being careful to % convert the key to a string using \cs{tl_to_str:n}. % \begin{macrocode} \cs_new_protected:Npn \prop_put_if_new:Nnn { \@@_put_if_new:NNnn \tl_set:Nx } \cs_new_protected:Npn \prop_gput_if_new:Nnn { \@@_put_if_new:NNnn \tl_gset:Nx } \cs_new_protected:Npn \@@_put_if_new:NNnn #1#2#3#4 { \tl_set:Nn \l_@@_internal_tl { \exp_not:N \@@_pair:wn \tl_to_str:n {#3} \s_@@ \exp_not:n { {#4} } } \@@_split:NnTF #2 {#3} { } { #1 #2 { \exp_not:o {#2} \l_@@_internal_tl } } } \cs_generate_variant:Nn \prop_put_if_new:Nnn { c } \cs_generate_variant:Nn \prop_gput_if_new:Nnn { c } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \subsection{Property list conditionals} % % \begin{macro}[pTF, tested = m3prop004]{\prop_if_exist:N, \prop_if_exist:c} % Copies of the \texttt{cs} functions defined in \pkg{l3basics}. % \begin{macrocode} \prg_new_eq_conditional:NNn \prop_if_exist:N \cs_if_exist:N { TF , T , F , p } \prg_new_eq_conditional:NNn \prop_if_exist:c \cs_if_exist:c { TF , T , F , p } % \end{macrocode} % \end{macro} % % \begin{macro}[pTF, tested = m3prop003]{\prop_if_empty:N, \prop_if_empty:c} % Same test as for token lists. % \begin{macrocode} \prg_new_conditional:Npnn \prop_if_empty:N #1 { p , T , F , TF } { \tl_if_eq:NNTF #1 \c_empty_prop \prg_return_true: \prg_return_false: } \prg_generate_conditional_variant:Nnn \prop_if_empty:N { c } { p , T , F , TF } % \end{macrocode} % \end{macro} % % \begin{macro}[pTF, tested = m3prop003] % { % \prop_if_in:Nn, \prop_if_in:NV, \prop_if_in:No, % \prop_if_in:cn, \prop_if_in:cV, \prop_if_in:co % } % \begin{macro}[EXP]{\@@_if_in:nwwn, \@@_if_in:N} % Testing expandably if a key is in a property list % requires to go through the key--value pairs one by one. % This is rather slow, and a faster test would be % \begin{verbatim} % \prg_new_protected_conditional:Npnn \prop_if_in:Nn #1 #2 % { % \@@_split:NnTF #1 {#2} % { \prg_return_true: } % { \prg_return_false: } % } % \end{verbatim} % but \cs{@@_split:NnTF} is non-expandable. % % Instead, the key is compared to each key in turn using % \cs{str_if_eq:ee}, which is expandable. To terminate the mapping, % we append to the property list the key that is searched for. This % second \cs{tl_to_str:n} is not expanded at the start, but only when % included in the \cs{str_if_eq:ee}. It cannot make the breaking % mechanism choke, because the arbitrary token list material is % enclosed in braces. The second argument of \cs{@@_if_in:nwwn} is % most often empty. When the \meta{key} is found in the list, % \cs{@@_if_in:N} receives \cs{@@_pair:wn}, and if it is % found as the extra item, the function receives % \cs{q_recursion_tail}, easily recognizable. % % Here, \cs{prop_map_function:NN} is not sufficient for the mapping, % since it can only map a single token, and cannot carry the key that % is searched for. % \begin{macrocode} \prg_new_conditional:Npnn \prop_if_in:Nn #1#2 { p , T , F , TF } { \exp_last_unbraced:Noo \@@_if_in:nwwn { \tl_to_str:n {#2} } #1 \@@_pair:wn \tl_to_str:n {#2} \s_@@ { } \q_recursion_tail \prg_break_point: } \cs_new:Npn \@@_if_in:nwwn #1#2 \@@_pair:wn #3 \s_@@ #4 { \str_if_eq:eeTF {#1} {#3} { \@@_if_in:N } { \@@_if_in:nwwn {#1} } } \cs_new:Npn \@@_if_in:N #1 { \if_meaning:w \q_recursion_tail #1 \prg_return_false: \else: \prg_return_true: \fi: \prg_break: } \prg_generate_conditional_variant:Nnn \prop_if_in:Nn { NV , No , c , cV , co } { p , T , F , TF } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Recovering values from property lists with branching} % % \begin{macro}[TF, tested = m3prop004] % { % \prop_get:NnN, \prop_get:NVN, \prop_get:NoN, % \prop_get:cnN, \prop_get:cVN, \prop_get:coN % } % Getting the value corresponding to a key, keeping track of whether % the key was present or not, is implemented as a conditional (with % side effects). If the key was absent, the token list is not altered. % \begin{macrocode} \prg_new_protected_conditional:Npnn \prop_get:NnN #1#2#3 { T , F , TF } { \@@_split:NnTF #1 {#2} { \tl_set:Nn #3 {##2} \prg_return_true: } { \prg_return_false: } } \prg_generate_conditional_variant:Nnn \prop_get:NnN { NV , No , c , cV , co } { T , F , TF } % \end{macrocode} % \end{macro} % % \subsection{Mapping to property lists} % % \begin{macro}[tested = m3prop003] % { % \prop_map_function:NN, \prop_map_function:Nc, % \prop_map_function:cN, \prop_map_function:cc % } % \begin{macro}{\@@_map_function:Nwwn} % The argument delimited by \cs{@@_pair:wn} is empty except at the end % of the loop where it is \cs{prg_break:}. No need for any quark % test. % \begin{macrocode} \cs_new:Npn \prop_map_function:NN #1#2 { \exp_after:wN \use_i_ii:nnn \exp_after:wN \@@_map_function:Nwwn \exp_after:wN #2 #1 \prg_break: \@@_pair:wn \s_@@ { } \prg_break_point: \prg_break_point:Nn \prop_map_break: { } } \cs_new:Npn \@@_map_function:Nwwn #1#2 \@@_pair:wn #3 \s_@@ #4 { #2 #1 {#3} {#4} \@@_map_function:Nwwn #1 } \cs_generate_variant:Nn \prop_map_function:NN { Nc , c , cc } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[tested = m3prop003]{\prop_map_inline:Nn, \prop_map_inline:cn} % Mapping in line requires a nesting level counter. Store the current % definition of \cs{@@_pair:wn}, and define it anew. At the end of % the loop, revert to the earlier definition. Note that besides pairs % of the form \cs{@@_pair:wn} \meta{key} \cs{s_@@} \Arg{value}, there % are a leading and a trailing tokens, but both are equal to % \cs{scan_stop:}, hence have no effect in such inline mapping. % Such \cs{scan_stop:} could have affected ligatures if they appeared % during the mapping. % \begin{macrocode} \cs_new_protected:Npn \prop_map_inline:Nn #1#2 { \cs_gset_eq:cN { @@_map_ \int_use:N \g__kernel_prg_map_int :wn } \@@_pair:wn \int_gincr:N \g__kernel_prg_map_int \cs_gset_protected:Npn \@@_pair:wn ##1 \s_@@ ##2 {#2} #1 \prg_break_point:Nn \prop_map_break: { \int_gdecr:N \g__kernel_prg_map_int \cs_gset_eq:Nc \@@_pair:wn { @@_map_ \int_use:N \g__kernel_prg_map_int :wn } } } \cs_generate_variant:Nn \prop_map_inline:Nn { c } % \end{macrocode} % \end{macro} % % \begin{macro}[tested = m3prop003]{\prop_map_break:} % \begin{macro}[tested = m3prop003]{\prop_map_break:n} % The break statements are based on the general \cs{prg_map_break:Nn}. % \begin{macrocode} \cs_new:Npn \prop_map_break: { \prg_map_break:Nn \prop_map_break: { } } \cs_new:Npn \prop_map_break:n { \prg_map_break:Nn \prop_map_break: } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Viewing property lists} % % \begin{macro}[tested = m3show001] % {\prop_show:N, \prop_show:c, \prop_log:N, \prop_log:c} % Apply the general \cs{__kernel_chk_defined:NT} and % \cs{msg_show:nnnnnn}. Contrarily to sequences and comma lists, % we use \cs{msg_show_item:nn} to format both the key and the value % for each pair. % \begin{macrocode} \cs_new_protected:Npn \prop_show:N { \@@_show:NN \msg_show:nnxxxx } \cs_generate_variant:Nn \prop_show:N { c } \cs_new_protected:Npn \prop_log:N { \@@_show:NN \msg_log:nnxxxx } \cs_generate_variant:Nn \prop_log:N { c } \cs_new_protected:Npn \@@_show:NN #1#2 { \__kernel_chk_defined:NT #2 { #1 { LaTeX/kernel } { show-prop } { \token_to_str:N #2 } { \prop_map_function:NN #2 \msg_show_item:nn } { } { } } } % \end{macrocode} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintIndex