diff options
author | Karl Berry <karl@freefriends.org> | 2012-02-06 01:22:53 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2012-02-06 01:22:53 +0000 |
commit | a851d6714a00e2a09b93ba3e251d8665290c0ab8 (patch) | |
tree | 3ba045c487cc9e5e28e73714cb6312d0e556b924 /Master/texmf-dist/source/latex/l3experimental/l3dt/l3dt.dtx | |
parent | a9533a6794104fad593912e3481304047dcd0025 (diff) |
l3experimental 3287
git-svn-id: svn://tug.org/texlive/trunk@25308 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/source/latex/l3experimental/l3dt/l3dt.dtx')
-rw-r--r-- | Master/texmf-dist/source/latex/l3experimental/l3dt/l3dt.dtx | 1351 |
1 files changed, 1351 insertions, 0 deletions
diff --git a/Master/texmf-dist/source/latex/l3experimental/l3dt/l3dt.dtx b/Master/texmf-dist/source/latex/l3experimental/l3dt/l3dt.dtx new file mode 100644 index 00000000000..e5063618228 --- /dev/null +++ b/Master/texmf-dist/source/latex/l3experimental/l3dt/l3dt.dtx @@ -0,0 +1,1351 @@ +% \iffalse meta-comment +% +%% File l3dt.dtx Copyright (C) 2011,2012 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 +%% +%% http://www.latex-project.org/lppl.txt +%% +%% This file is part of the "l3experimental bundle" (The Work in LPPL) +%% and all files in that bundle must be distributed together. +%% +%% The released version of this bundle is available from CTAN. +%% +%% ----------------------------------------------------------------------- +%% +%% The development version of the bundle can be found at +%% +%% http://www.latex-project.org/svnroot/experimental/trunk/ +%% +%% for those people who are interested. +%% +%%%%%%%%%%% +%% NOTE: %% +%%%%%%%%%%% +%% +%% Snapshots taken from the repository represent work in progress and may +%% not work or may contain conflicting material! We therefore ask +%% people _not_ to put them into distributions, archives, etc. without +%% prior consultation with the LaTeX Project Team. +%% +%% ----------------------------------------------------------------------- +%% +% +%<*driver|package> +\RequirePackage{expl3} +\GetIdInfo$Id: l3dt.dtx 3251 2012-01-28 09:42:44Z joseph $ + {L3 Experimental data tables} +%</driver|package> +%<*driver> +\documentclass[full]{l3doc} +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +%</driver> +% \fi +% +% \title{^^A +% The \pkg{l3dt} package\\ Data tables^^A +% \thanks{This file describes v\ExplFileVersion, +% last revised \ExplFileDate.}^^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 \ExplFileDate} +% +% \maketitle +% +% \begin{documentation} +% +% \LaTeX3 implements a \enquote{data table} variable type, which is made up of +% a series of rows each of which contain a number of key--value pairs. Thus +% a data table is in effect an array of property lists. The rows of the table +% are stored in a fixed order, and are numbered consecutively from zero. +% In the same way, the order of keys (columns) is recorded in a sequence-like +% manner, again indexed from zero. +% +% Within each row in a data table each entry must have a unique \meta{key}: if +% an entry is added to a row within a data table which already contains the +% \meta{key} then the new entry will overwrite the existing one. The +% \meta{keys} are compared on a string basis, using the same method as +% \cs{str_if_eq:nn}. +% +% \section{Creating and initialising data tables} +% +% \begin{function}{\dt_new:N} +% \begin{syntax} +% \cs{dt_new:N} \meta{data table} +% \end{syntax} +% Creates a new \meta{data table} or raises an error if the name is +% already taken. The declaration is global. The \meta{property lists} will +% initially contain no entries. +% \end{function} +% +% \begin{function}{\dt_clear:N, \dt_gclear:N} +% \begin{syntax} +% \cs{dt_clear:N} \meta{data table} +% \end{syntax} +% Clears all entries and keys from the \meta{data table}. +% \end{function} +% +% \begin{function}{\dt_clear_new:N, \dt_gclear_new:N} +% \begin{syntax} +% \cs{dt_clear_new:N} \meta{data table} +% \end{syntax} +% Ensures that the \meta{data table} exists globally by applying +% \cs{dt_new:N} if necessary, then applies \cs{dt_(g)clear:N} to leave +% the table empty. +% \end{function} +% +% \begin{function}{\dt_set_eq:NN, \dt_gset_eq:NN} +% \begin{syntax} +% \cs{dt_set_eq:NN} \meta{data table1} \meta{data table2} +% \end{syntax} +% Sets the content of \meta{data table1} equal to that of +% \meta{data table2}. +% \end{function} +% +% \section{Adding data} +% +% \begin{function}{\dt_add_key:Nn, \dt_gadd_key:Nn} +% \begin{syntax} +% \cs{dt_add_key:Nn} \meta{dt} \Arg{key} +% \end{syntax} +% Adds the \meta{key} to the list of those in the \meta{data table}. The +% \meta{key} will be converted to a string using \cs{tl_to_str:n}, and thus +% category codes in the \meta{key} are ignored. If the \meta{key} is already +% present in the \meta{data table} then no action is taken. +% \end{function} +% +% \begin{function}{\dt_add_row:N, \dt_gadd_row:N} +% \begin{syntax} +% \cs{dt_add_row:N} \meta{dt} +% \end{syntax} +% Adds a new row to the \meta{data table}. This will initially contain +% no entries: all keys will be be blank. +% \end{function} +% +% \begin{function}{\dt_put:Nnn, \dt_gput:Nnn} +% \begin{syntax} +% \cs{dt_put:Nnn} \meta{dt} \Arg{key} \Arg{value} +% \end{syntax} +% Adds an entry to the current row of the \meta{data table} 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 current row of the \meta{data table}, the existing entry is +% overwritten by the new \meta{value}. +% \end{function} +% +% \begin{function}{\dt_put:Nnnn, \dt_gput:Nnnn} +% \begin{syntax} +% \cs{dt_put:Nnnn} \meta{dt} \Arg{row} \Arg{key} \Arg{value} +% \end{syntax} +% Adds an entry to the \meta{row} of the \meta{data table} 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{row} of the \meta{data table}, the existing entry is +% overwritten by the new \meta{value}. The \meta{row} should be given as +% an \meta{integer expression}. +% \end{function} +% +% \section{Removing data} +% +% \begin{function}{\dt_del:Nn, \dt_gdel:Nn} +% \begin{syntax} +% \cs{dt_del:Nn} \meta{dt} \Arg{key} +% \end{syntax} +% Deletes any entry from the current row of the \meta{data table} with +% the \meta{key}. The \meta{key} is compared after processing with +% \cs{tl_to_str:n}, meaning that category codes are ignored. Deleting of +% all entries from a row does not delete the row itself. +% \end{function} +% +% \begin{function}{\dt_del:Nnn, \dt_gdel:Nnn} +% \begin{syntax} +% \cs{dt_del:Nnn} \meta{dt} \Arg{row} \Arg{key} +% \end{syntax} +% Deletes any entry from the \meta{row} of the \meta{data table} with +% the \meta{key}. The \meta{key} is compared after processing with +% \cs{tl_to_str:n}, meaning that category codes are ignored. The \meta{row} +% may be given as an \meta{integer expression}. Deleting of +% all entries from a row does not delete the row itself. +% \end{function} +% +% \begin{function}{\dt_remove_key:Nn, \dt_gremove_key:Nn} +% \begin{syntax} +% \cs{dt_remove_key:N} \meta{data table} \Arg{key} +% \end{syntax} +% Removes the \meta{key} from the \meta{data table} if it is present. +% The \meta{key} and any associated \meta{value} will be removed from any +% row that it is found in. +% \end{function} +% +% \begin{function}{\dt_remove_row:Nn, \dt_gremove_row:Nn} +% \begin{syntax} +% \cs{dt_remove_row:Nn} \meta{data table} \Arg{row} +% \end{syntax} +% Removes the \meta{row} (given as an \meta{integer expressions}) from the +% \meta{data table}. The remaining rows of the table will be renumbered +% such that they are sequential. +% \end{function} +% +% \section{Recovering information} +% +% \begin{function}[EXP]{\dt_keys:N} +% \begin{syntax} +% \cs{dt_keys:N} \meta{dt} +% \end{syntax} +% Leaves the number of keys in the \meta{data table} in the input +% stream as an \meta{integer denotation}. +% \end{function} +% +% \begin{function}[EXP]{\dt_rows:N} +% \begin{syntax} +% \cs{dt_rows:N} \meta{dt} +% \end{syntax} +% Leaves the number of rows in the \meta{data table} in the input +% stream as an \meta{integer denotation}. +% \end{function} +% +% \begin{function}{\dt_get:NnN} +% \begin{syntax} +% \cs{dt_get:NnnN} \meta{dt} \Arg{key} \meta{tl var} +% \end{syntax} +% Recovers the \meta{value} stored with \meta{key} from the current row in +% the \meta{data table}, and places this in the \meta{token list variable}. +% If the \meta{key} is not found in the \meta{row} of the \meta{data table} +% then the \meta{token list variable} will contain the special marker +% \cs{q_no_value}. The \meta{token list variable} is set within the +% current \TeX{} group. The \meta{row} should be given as an +% \meta{integer expression}. See also \cs{dt_get:NnNTF}. +% \end{function} +% +% \begin{function}[TF]{\dt_get:NnN} +% \begin{syntax} +% \cs{dt_get:NnnNTF} \meta{dt} \Arg{key} \meta{tl var} \Arg{true code} \Arg{false code} +% \end{syntax} +% Recovers the \meta{value} stored with \meta{key} from the current row in +% the \meta{data table}, and places this in the \meta{token list variable}. +% If the \meta{key} is not found in the \meta{row} of the \meta{data table} +% then the \meta{token list variable} will contain the special marker +% \cs{q_no_value}. The \meta{token list variable} is set within the +% current \TeX{} group. The \meta{row} should be given as an +% \meta{integer expression}. Once the \meta{token list variable} has been +% assigned either the \meta{true code} or \meta{false code} will be left in +% the input stream, depending on whether the \meta{key} was found. +% See also \cs{dt_get:NnN}. +% \end{function} +% +% \begin{function}{\dt_get:NnnN} +% \begin{syntax} +% \cs{dt_get:NnnN} \meta{dt} \Arg{row} \Arg{key} \meta{tl var} +% \end{syntax} +% Recovers the \meta{value} stored with \meta{key} from \meta{row} in the +% \meta{data table}, and places this in the \meta{token list variable}. If +% the \meta{key} is not found in the \meta{row} of the \meta{data table} +% then the \meta{token list variable} will contain the special marker +% \cs{q_no_value}. The \meta{token list variable} is set within the +% current \TeX{} group. The \meta{row} should be given as an +% \meta{integer expression}. See also \cs{dt_get:NnnNTF}. +% \end{function} +% +% \begin{function}[TF]{\dt_get:NnnN} +% \begin{syntax} +% \cs{dt_get:NnnNTF} \meta{dt} \Arg{row} \Arg{key} \meta{tl var} \Arg{true code} \Arg{false code} +% \end{syntax} +% Recovers the \meta{value} stored with \meta{key} from \meta{row} in the +% \meta{data table}, and places this in the \meta{token list variable}. If +% the \meta{key} is not found in the \meta{row} of the \meta{data table} +% then the \meta{token list variable} will contain the special marker +% \cs{q_no_value}. The \meta{token list variable} is set within the +% current \TeX{} group. The \meta{row} should be given as an +% \meta{integer expression}. Once the \meta{token list variable} has been +% assigned either the \meta{true code} or \meta{false code} will be left in +% the input stream, depending on whether the \meta{key} was found. +% See also \cs{dt_get:NnnN}. +% \end{function} +% +% \section{Mapping to data tables} +% +% \begin{function}{\dt_map_variables:Nnn} +% \begin{syntax} +% \cs{dt_map_variables:Nnn} \meta{data table} \Arg{key--variable mapping} \Arg{code} +% \end{syntax} +% Applies the \meta{code} to each \meta{row} of the \meta{data table}. The +% \meta{keys} of the \meta{data table} are mapped to variables by the +% \meta{key--variable mapping}, which should be a key--value list of the form +% \begin{verbatim} +% key-a = \l_a_tl , +% key-b = \l_b_tl +% ... +% \end{verbatim} +% It is not necessary to map all of the \meta{keys} in a \meta{data table} +% to variables. If there is not \meta{value} for a \meta{key} in a row, +% the variable will contain the marker \cs{q_no_value}. Assignment of the +% \meta{variables} is local to the current \TeX{} group. The mapping +% to rows is ordered. +% \end{function} +% +% \begin{variable}{\g_dt_map_level_int} +% The nesting level of the data table mapping is available as +% \cs{g_dt_map_level_int}. Within a mapping, the \texttt{int} variable +% \cs{l_dt_map_\meta{level}_row_int} is available so that the row +% number being operated on is available. Thus +% \begin{verbatim} +% \int_use:c { l_dt_map_ \int_use:N \g_dt_map_level_int _row_int } +% \end{verbatim} +% will give the current row for the current mapping. +% \end{variable} +% +% \begin{function}[rEXP]{\dt_map_break:, \dt_map_break:n} +% \begin{syntax} +% \cs{dt_map_break:} +% \cs{dt_map_break:n} \Arg{tokens} +% \end{syntax} +% Used to terminate a \cs{dt_map_\ldots} function before all +% entries in the \meta{data table} have been processed. This will +% normally take place within a conditional statement, for example +% \begin{verbatim} +% \dt_map_variables:Nn \l_my_dt { a = \l_my_tl } +% { +% \str_if_eq:VnTF \l_my_tl { bingo } +% { \dt_map_break: } +% { +% % Do something useful +% } +% } +% \end{verbatim} +% The \texttt{:n} variant will insert the \meta{tokens} into the input stream +% after the mapping terminates. Use outside of a \cs{dt_map_\ldots} scenario +% will lead low level \TeX{} errors. +% \end{function} +% +% \section{Data table conditionals} +% +% \begin{function}[EXP, pTF]{\dt_if_empty:N} +% \begin{syntax} +% \cs{dt_if_empty_p:N} \meta{dt} +% \cs{dt_if_empty:NTF} \meta{dt} \Arg{true code} \Arg{false code} +% \end{syntax} +% Tests if the \meta{dt} is empty, containing no keys and no rows. +% \end{function} +% +% \begin{function}[EXP, pTF]{\dt_if_in:Nn} +% \begin{syntax} +% \cs{dt_if_in_p:Nn} \meta{dt} \Arg{key} +% \cs{dt_if_in:NnTF} \meta{dt} \Arg{key} \Arg{true code} \Arg{false code} +% \end{syntax} +% Tests if the \meta{key} is present in the \meta{data table} at all, +% \emph{i.e.} if it is one of the columns of the table. This test will be +% \texttt{true} even if none of the rows contain an entry for the \meta{key}. +% \end{function} +% +% \begin{function}[EXP, pTF]{\dt_if_in_row:Nnn} +% \begin{syntax} +% \cs{dt_if_in_row_p:Nnn} \meta{dt} \Arg{row} \Arg{key} +% \cs{dt_if_in_row:NnnTF} \meta{dt} \Arg{row} \Arg{key} \Arg{true code} \Arg{false code} +% \end{syntax} +% Tests if the \meta{key} is present in the \meta{row} of the +% \meta{data table}. The \meta{row} may be given as an \meta{integer +% expression}. +% \end{function} +% +% \begin{function}[EXP, pTF]{\dt_if_in_row:Nn} +% \begin{syntax} +% \cs{dt_if_in_row_p:Nn} \meta{dt} \Arg{key} +% \cs{dt_if_in_row:NnTF} \meta{dt} \Arg{key} \Arg{true code} \Arg{false code} +% \end{syntax} +% Tests if the \meta{key} is present in the current row of +% \meta{data table}. +% \end{function} +% +% \section{Variables} +% +% \begin{variable}{\c_empty_dt} +% A permanently empty data table. +% \end{variable} +% +% \begin{variable}{\l_tmpa_dt, \l_tmpb_dt, \g_tmpa_dt, \g_tmpb_dt} +% Scratch data tables for general use: these are never used by the kernel. +% \end{variable} +% +% \section{Internal function} +% +% \begin{variable}{\q_dt, \q_dt_header, \q_dt_row} +% Quarks used to construct the data table format. +% \end{variable} +% +% \begin{function}[EXP]{\dt_split_header:NT} +% \begin{syntax} +% \cs{dt_split_header:Nn} \meta{dt} \Arg{code} +% \end{syntax} +% Splits the \meta{data table} into the header part (containing the total +% number of rows present and the key list) and the body (containing the +% rows). The \meta{code} is then inserted, and should absorb the two parts +% of the split table as arguments. +% \end{function} +% +% \begin{function}{\dt_split_key:nnTF} +% \begin{syntax} +% \cs{dt_split_key:nnTF} \Arg{row} \Arg{key} \Arg{true code} \Arg{false code} +% \end{syntax} +% Searches the \meta{row} for the \meta{key}, using the +% comparison method as described for \cs{str_if_eq:nn}. If the \meta{key} is +% present, the \meta{true code} is left in the input stream followed by +% three \meta{balanced text} arguments +% \begin{enumerate} +% \item the partial \meta{row} for all keys before the \meta{key}, +% \item the \meta{value} for the \meta{key} and +% \item the partial \meta{row} for all keys after the \meta{key}. +% \end{enumerate} +% Thus the \meta{true code} must absorb three arguments. +% The two partial tables are structured such they may be recombined directly +% to produce a valid row lacking the entry for the \meta{key}. +% +% If the \meta{key} is not found in the \meta{row}, then the \meta{false code} +% is left in the input stream with no arguments. +% \end{function} +% +% \begin{function}{\dt_split_key_list:NnTF} +% \begin{syntax} +% \cs{dt_split_key_list:NnTF} \meta{dt} \Arg{key} \Arg{true code} \Arg{false code} +% \end{syntax} +% Searches the key list of the \meta{dt} for the \meta{key}, using the +% comparison method as described for \cs{str_if_eq:nn}. If the \meta{key} is +% present, the \meta{true code} is left in the input stream followed by +% five \meta{balanced text} arguments +% \begin{enumerate} +% \item the partial table for all keys before the \meta{key}, +% \item the \meta{id} for the \meta{key}, +% \item the \meta{type} for the \meta{key}, +% \item the \meta{header} for the \meta{key} and +% \item the partial table for all keys after the \meta{key}. +% \end{enumerate} +% Thus the \meta{true code} must absorb five arguments. +% The two partial tables are structured such they may be recombined directly +% to produce a valid key table lacking the entry for the \meta{key}. +% +% If the \meta{key} is not found in the key table, then the \meta{false code} +% is left in the input stream with no arguments. +% \end{function} +% +% \begin{function}{\dt_split_row:NnTF} +% \begin{syntax} +% \cs{dt_split_row:NnTF} \meta{dt} \Arg{row} \Arg{true code} \Arg{false code} +% \end{syntax} +% Searches the key list of the \meta{dt} for the \meta{row} +% (an \meta{integer expression}). If the +% \meta{row} is present, the \meta{true code} is left in the input stream +% followed by three \meta{balanced text} arguments +% \begin{enumerate} +% \item the partial table before the \meta{row}, +% \item the content of the \meta{row}, starting and ending with the +% row number, +% \item the partial table after the \meta{row}. +% \end{enumerate} +% Thus the \meta{true code} must absorb three arguments. +% The two partial tables are structured such they may be recombined directly +% to produce a valid key table lacking the entry for the \meta{row}. +% +% If the \meta{row} is not found in the key table, then the \meta{false code} +% is left in the input stream with no arguments. +% \end{function} +% +% \end{documentation} +% +% \begin{implementation} +% +% \section{\pkg{l3dt} implementation} +% +% \begin{macrocode} +%<*initex|package> +% \end{macrocode} +% +% \begin{macrocode} +%<*package> +\ProvidesExplPackage + {\ExplFileName}{\ExplFileDate}{\ExplFileVersion}{\ExplFileDescription} +\package_check_loaded_expl: +%</package> +% \end{macrocode} +% +% \subsection{Structures} +% +% The structure of a data table must allow each row (record) to contain only +% some of the keys, and for the keys to be removed after the table +% is initialised. It also needs to ensure that a unique match can be made to +% every item in the table. At the same time, it is desirable to keep all of +% the information about the table in a single \TeX{} macro. This can be +% achieved by packing the data into a structure in which each key and row is +% numbered: +% \begin{quote} +% \Arg{rows} \\ +% \cs{q_dt} \meta{key$_0$} \cs{q_dt} \meta{key$_1$} \cs{q_dt} \ldots \\ +% \cs{q_nil} \\ +% \cs{q_dt_header} \\ +% \cs{q_dt_row} \\ +% \meta{row$_0$} \\ +% \cs{q_dt} \meta{key$_0$} \cs{q_dt} \Arg{data$_{\0,\0}$} \\ +% \cs{q_dt} \meta{key$_1$} \cs{q_dt} \Arg{data$_{\0,\1}$} \\ +% \ldots \\ +% \cs{q_dt} \\ +% \cs{q_nil} \\ +% \cs{q_dt_row} \\ +% \meta{row$_2$} \\ +% \cs{q_dt} \meta{key$_0$} \cs{q_dt} \Arg{data$_{\1,\0}$} \\ +% \cs{q_dt} \meta{key$_1$} \cs{q_dt} \Arg{data$_{\1,\1}$} \\ +% \ldots \\ +% \cs{q_dt} \\ +% \cs{q_nil} \\ +% \cs{q_dt_row} \\ +% \ldots \\ +% \cs{q_dt_row} +% \end{quote} +% +% \begin{variable}{\q_dt, \q_dt_row, \q_dt_header} +% The quarks are set up. +% \begin{macrocode} +\quark_new:N \q_dt +\quark_new:N \q_dt_row +\quark_new:N \q_dt_header +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\c_empty_dt} +% A permanently-empty data table, which therefore contains only the minimum +% number of items necessary to comply with the structure above. +% \begin{macrocode} +\tl_const:Nn \c_empty_dt + { + { 0 } + \q_dt + \q_nil + \q_dt_header + \q_dt_row + } +% \end{macrocode} +% \end{variable} +% +% \subsection{Allocation and initialisation} +% +% \begin{macro}{\dt_new:N} +% Internally, data tables are token lists, but an empty dt +% is not an empty tl. +% \begin{macrocode} +\cs_new_protected:Npn \dt_new:N #1 { \cs_new_eq:NN #1 \c_empty_dt } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\dt_clear:N, \dt_gclear:N} +% The same idea for clearing. +% \begin{macrocode} +\cs_new_protected:Npn \dt_clear:N #1 { \cs_set_eq:NN #1 \c_empty_dt } +\cs_new_protected:Npn \dt_gclear:N #1 { \cs_gset_eq:NN #1 \c_empty_dt } +% \end{macrocode} +% \end{macro} +% +% \begin{macro} +% {\dt_clear_new:N, \dt_gclear_new:N} +% Once again a simple copy from the token list functions. +% \begin{macrocode} +\cs_new_protected:Npn \dt_clear_new:N #1 + { \cs_if_exist:NTF #1 { \dt_clear:N #1 } { \dt_new:N #1 } } +\cs_new_protected:Npn \dt_gclear_new:N #1 + { \cs_if_exist:NTF #1 { \dt_gclear:N #1 } { \dt_new:N #1 } } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\dt_set_eq:NN, \dt_gset_eq:NN} +% Once again, these are simply copies from the token list functions. +% \begin{macrocode} +\cs_new_eq:NN \dt_set_eq:NN \tl_set_eq:NN +\cs_new_eq:NN \dt_gset_eq:NN \tl_gset_eq:NN +% \end{macrocode} +% \end{macro} +% +% \begin{variable}{\l_tmpa_dt, \l_tmpb_dt, \g_tmpa_dt, \g_tmpb_dt} +% Scratch tables. +% \begin{macrocode} +\dt_new:N \l_tmpa_dt +\dt_new:N \l_tmpb_dt +\dt_new:N \g_tmpa_dt +\dt_new:N \g_tmpb_dt +% \end{macrocode} +% \end{variable} +% +% \subsection{Splitting functions} +% +% \begin{macro}[aux]{\dt_split_aux:nnnn} +% \begin{macro}[aux]{\dt_split_aux:w} +% Two general auxiliaries. The \texttt{nnnn} function is used to apply the +% \texttt{T} branch if a match is found and the \texttt{F} branch otherwise. +% The \texttt{w} function is general purpose, and is used to define the +% matching parameter set. +% \begin{macrocode} +\cs_new_protected:Npn \dt_split_aux:nnnn #1#2#3#4 { #3 #2 } +\cs_new_protected:Npn \dt_split_aux:w { } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[int,EXP]{\dt_split_header:NT} +% \begin{macro}[aux,EXP]{\dt_split_header_aux:wn} +% Splits the header from the table, inserting the code required to then +% process the split table. The \cs{q_nil} is also removed from the end of the +% header, as it is essentially a distraction here. +% \begin{macrocode} +\cs_new:Npn \dt_split_header:NT #1#2 + { \exp_after:wN \dt_split_header_aux:wn #1 \q_stop {#2} } +\cs_new:Npn \dt_split_header_aux:wn #1 \q_nil \q_dt_header #2 \q_stop #3 + { #3 {#1} { \q_dt_header #2 } } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[int]{\dt_split_key:nnTF} +% \begin{macro}[aux]{\dt_split_key_aux:nnTF} +% Here, the split is made for a partial list within a row. The row is +% basically the same as a property list, so the split here is almost +% identical to that in \cs{prop_split_aux:NnTF}. The row-end data is set up +% such that it will not interfere with this process. +% \begin{macrocode} +\cs_new_protected:Npn \dt_split_key:nnTF #1#2 + { \exp_args:No \dt_split_key_aux:nnTF { \tl_to_str:n {#2} } {#1} } +\cs_new_protected:Npn \dt_split_key_aux:nnTF #1#2 + { + \cs_set_protected:Npn \dt_split_aux:w + ##1 \q_dt #1 \q_dt ##2##3##4 \q_mark ##5 \q_stop + { \dt_split_aux:nnnn ##3 { { ##1 \q_dt } {##2} {##4} } } + \dt_split_aux:w #2 \q_mark + \q_dt #1 \q_dt { } { ? \use_ii:nn { } } \q_mark \q_stop + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[int]{\dt_split_key_list:NnTF} +% \begin{macro}[aux]{\dt_split_key_list_aux:NnTF} +% Finding a key in the header uses a similar approach to finding a key in +% a property list. Here, if the key is found there will always be at least +% one token between \cs{q_dt} and \cs{q_dt_header} due to the \cs{q_nil} +% which is part of a new table. The use of |##1##2| in \cs{dt_split_aux:w} +% here is to deal with the overall number of rows. The set up here means +% that this will always be unbraced then rebraced: simply grabbing |##1| +% to include this and anything before the key of interest will give variable +% results depending on whether the match is to the very first key or not. +% \begin{macrocode} +\cs_new_protected:Npn \dt_split_key_list:NnTF #1#2 + { \exp_args:NNo \dt_split_key_list_aux:NnTF #1 { \tl_to_str:n {#2} } } +\cs_new_protected:Npn \dt_split_key_list_aux:NnTF #1#2 + { + \cs_set_protected:Npn \dt_split_aux:w + ##1##2 \q_dt #2 \q_dt ##3##4 \q_dt_header ##5 \q_mark ##6 \q_stop + { + \dt_split_aux:nnnn ##3 + { { {##1} ##2 \q_dt } { ##3##4 \q_dt_header ##5 } } + } + \exp_after:wN \dt_split_aux:w #1 \q_mark + \q_dt #2 \q_dt { ? \use_ii:nn { } } \q_dt_header \q_mark \q_stop + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[int]{\dt_split_row:NnTF} +% \begin{macro}[aux]{\dt_split_row_aux:NnTF, \dt_split_row_aux:NfTF} +% The usual approach, here using the fact that each row start with row number +% and ends with \cs{q_nil} so there will always be at least one token to be +% absorbed as |##2|. The only odd thing to watch here is that the row +% number is evaluated so that higher-level functions in the main do not +% need to have an \texttt{f}-type variant. +% \begin{macrocode} +\cs_new_protected:Npn \dt_split_row:NnTF #1#2 + { \dt_split_row_aux:NfTF #1 { \int_eval:n {#2} } } +\cs_new_protected:Npn \dt_split_row_aux:NnTF #1#2 + { + \cs_set_protected:Npn \dt_split_aux:w + ##1 \q_dt_row #2 \q_dt ##2##3 \q_dt_row ##4 \q_mark ##5 \q_stop + { + \dt_split_aux:nnnn ##2 + { { ##1 \q_dt_row } { #2 \q_dt ##2##3 } {##4} } + } + \exp_after:wN \dt_split_aux:w #1 \q_mark + \q_dt_row #2 \q_dt { ? \use_ii:nn { } } \q_dt_row \q_mark \q_stop + } +\cs_generate_variant:Nn \dt_split_row_aux:NnTF { Nf } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsection{Adding and removing data} +% +% \begin{macro}{\dt_add_key:Nn, \dt_gadd_key:Nn} +% \begin{macro}[aux]{\dt_add_key_aux:NNn} +% \begin{macro}[aux]{\dt_add_key_aux:NNnnn} +% Here, there are two stages. If the key is already present in the list of +% known keys then no action is taken, and the split list is thrown away. +% On the other hand, if the key is not present then the header and body +% are separated and the key is added to the end of the list of known keys +% (hence keys are ordered). The \cs{dt_split_header:Nn} function will have +% removed the \cs{q_nil} from the header, and so it is put back in here. +% \begin{macrocode} +\cs_new_protected_nopar:Npn \dt_add_key:Nn { \dt_add_key_aux:NNn \tl_set:Nx } +\cs_new_protected_nopar:Npn \dt_gadd_key:Nn { \dt_add_key_aux:NNn \tl_gset:Nx } +\cs_new_protected:Npn \dt_add_key_aux:NNn #1#2#3 + { + \dt_split_key_list:NnTF #2 {#3} + { \use_none:nn } + { + \dt_split_header:NT #2 + { \dt_add_key_aux:NNnnn #1 #2 {#3} } + } + } +\cs_new_protected:Npn \dt_add_key_aux:NNnnn #1#2#3#4#5 + { + #1 #2 + { + \exp_not:n {#4} + \tl_to_str:n {#3} + \exp_not:n { \q_dt \q_nil #5 } + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\dt_add_row:N, \dt_gadd_row:N} +% \begin{macro}[aux]{\dt_add_row_aux:NN} +% \begin{macro}[aux]{\dt_add_row_aux:NnN} +% \begin{macro}[aux,EXP]{\dt_add_row_aux:nw} +% Adding a row means incrementing the total number and adding the structure +% of an empty row. As finding the rows will get slow for large tables, this +% is only done once. +% \begin{macrocode} +\cs_new_protected_nopar:Npn \dt_add_row:N { \dt_add_row_aux:NN \tl_set:Nx } +\cs_new_protected_nopar:Npn \dt_gadd_row:N { \dt_add_row_aux:NN \tl_gset:Nx } +\cs_new_protected:Npn \dt_add_row_aux:NN #1#2 + { \exp_args:NNf \dt_add_row_aux:NnN #1 { \dt_rows:N #2 } #2 } +\cs_new_protected:Npn \dt_add_row_aux:NnN #1#2#3 + { + #1 #3 + { + { \int_eval:n { #2 + \c_one } } + \exp_after:wN \dt_add_row_aux:nw #3 \q_stop + #2 + \exp_not:n { \q_dt \q_nil \q_dt_row } + } + } +\cs_new:Npn \dt_add_row_aux:nw #1#2 \q_stop { \exp_not:n {#2} } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\dt_put:Nnn, \dt_gput:Nnn} +% Adding to the current row is simply a special case of adding to an +% arbitrary row. +% \begin{macrocode} +\cs_new_protected:Npn \dt_put:Nnn #1 + { \dt_put:Nnnn #1 { \dt_rows:N #1 - \c_one } } +\cs_new_protected:Npn \dt_gput:Nnn #1 + { \dt_gput:Nnnn #1 { \dt_rows:N #1 - \c_one } } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\dt_put:Nnnn, \dt_gput:Nnnn} +% \begin{macro}[aux]{\dt_put_aux:NNNnnn} +% \begin{macro}[aux]{\dt_put_aux:NNnnnnn} +% \begin{macro}[aux]{\dt_put_update:NNnnnnnnn} +% \begin{macro}[aux]{\dt_put_add_to_row:NNnnnnn} +% \begin{macro}[aux, EXP]{\dt_put_add_to_row_aux:w} +% Adding to a row is a slightly complex procedure. The lead-off is the +% standard combination across the local and global routes. +% \begin{macrocode} +\cs_new_protected_nopar:Npn \dt_put:Nnnn + { \dt_put_aux:NNNnnn \dt_add_key:Nn \tl_set:Nx } +\cs_new_protected_nopar:Npn \dt_gput:Nnnn + { \dt_put_aux:NNNnnn \dt_gadd_key:Nn \tl_gset:Nx } +% \end{macrocode} +% Add the key to the list those known, if necessary, then check that the +% row requested makes sense. +% \begin{macrocode} +\cs_new_protected:Npn \dt_put_aux:NNNnnn #1#2#3#4#5#6 + { + #1 #3 {#5} + \dt_split_row:NnTF #3 {#4} + { \dt_put_aux:NNnnnnn #2 #3 {#5} {#6} } + { + \msg_kernel_error:nnxxx { dt } { unknown-row } + { \token_to_str:N #3 } { \int_eval:n {#4} } { \dt_rows:N #3 } + } + } +% \end{macrocode} +% At this stage, the arguments are +% \begin{enumerate} +% \item the set function \cs{tl_(g)set:Nx}, +% \item the data table, +% \item the key, +% \item the value, +% \item the data table before the row, +% \item the extracted data table row, +% \item the data table after the row. +% \end{enumerate} +% Splitting on the key will then leave three further items in the input +% stack if the key is already present. So there is some care needed sending +% the parameters forward without running out of \TeX{} arguments. +% \begin{macrocode} +\cs_new_protected:Npn \dt_put_aux:NNnnnnn #1#2#3#4#5#6#7 + { + \dt_split_key:nnTF {#6} {#3} + { \dt_put_update:NNnnnnnnn #1 #2 {#3} {#4} {#5} {#7} } + { \dt_put_add_to_row:NNnnnnn #1 #2 {#3} {#4} {#5} {#6} {#7} } + } +% \end{macrocode} +% The arguments here are +% \begin{enumerate} +% \item the set function \cs{tl_(g)set:Nx}, +% \item the data table, +% \item the key, +% \item the value, +% \item the data table before the row, +% \item the data table after the row, +% \item the row before the key, +% \item the current value for the key +% \item the row after the key. +% \end{enumerate} +% What happens here is a reconstruction of the table: everything except +% |#8| is needed. To try to keep things clear, there are a few more +% \cs{exp_not:n} here than formally required. +% \begin{macrocode} +\cs_new_protected:Npn \dt_put_update:NNnnnnnnn #1#2#3#4#5#6#7#8#9 + { + #1 #2 + { + \exp_not:n { #5 #7 } + \tl_to_str:n {#3} + \exp_not:n { \q_dt {#4} \q_dt #9 \q_dt_row #6 } + } + } +% \end{macrocode} +% A slightly more complex case when adding an item. The arguments here are +% identical to those for \cs{dt_put_aux:NNnnnnnn}. The row has not been +% split, so the \cs{q_nil} there is removed and re-added to come after the +% new content. +% \begin{macrocode} +\cs_new_protected:Npn \dt_put_add_to_row:NNnnnnn #1#2#3#4#5#6#7 + { + #1 #2 + { + \exp_not:n {#5} + \exp_not:o { \dt_put_add_row_aux:w #6 } + \tl_to_str:n {#3} + \exp_not:n { \q_dt {#4} \q_dt \q_nil \q_dt_row #7 } + } + } +\cs_new:Npn \dt_put_add_row_aux:w #1 \q_nil {#1} +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP]{\dt_keys:N} +% \begin{macro}[aux, EXP]{\dt_keys_aux:nn} +% \begin{macro}[aux, EXP]{\dt_keys_aux:wN} +% A quick mapping is needed to count keys. The \cs{use_none:nn} here +% is used to remove the number of rows and initial \cs{q_dt}. This could +% also be handled by starting from $-1$ rather than $0$, but this makes +% the logic hopefully slightly clearer. +% \begin{macrocode} +\cs_new:Npn \dt_keys:N #1 + { \dt_split_header:NT #1 { \dt_keys_aux:nn } } +\cs_new:Npn \dt_keys_aux:nn #1#2 + { + \int_eval:n + { + 0 + \exp_after:wN \dt_keys_aux:wN \use_none:nn #1 \q_recursion_tail \q_dt + \prg_break_point:n { } + } + } +\cs_new:Npn \dt_keys_aux:wN #1 \q_dt + { + \if_meaning:w \q_recursion_tail #1 + \exp_after:wN \prg_map_break: + \fi: + +1 + \dt_keys_aux:wN + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP]{\dt_rows:N} +% The number of rows in a dt is the very first entry. +% \begin{macrocode} +\cs_new:Npn \dt_rows:N #1 + { \exp_after:wN \use_i_delimit_by_q_stop:nw #1 \q_stop } +% \end{macrocode} +% \end{macro} +% +% \subsection{Removing data} +% +% \begin{macro}{\dt_del:Nn,\dt_gdel:Nn} +% Deleting to the current row is simply a special case of deleting to an +% arbitrary row. +% \begin{macrocode} +\cs_new_protected:Npn \dt_del:Nn #1 { \dt_del:Nnn #1 + { \dt_rows:N #1 - \c_one } } +\cs_new_protected:Npn \dt_gdel:Nn #1 { \dt_gdel:Nnn #1 + { \dt_rows:N #1 - \c_one } } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\dt_del:Nnn, \dt_gdel:Nnn} +% \begin{macro}[aux]{\dt_del_aux:NNnn} +% \begin{macro}[aux]{\dt_del_aux:NNnnnn} +% \begin{macro}[aux]{\dt_del_aux:NNnnnnn} +% Deleting a single entry from a single row means first splitting by row, +% then splitting by key, and finally doing the assignment. If the row or the +% key are not present then the entire function does nothing at all. +% \begin{macrocode} +\cs_new_protected_nopar:Npn \dt_del:Nnn { \dt_del_aux:NNnn \tl_set:Nn } +\cs_new_protected_nopar:Npn \dt_gdel:Nnn { \dt_del_aux:NNnn \tl_gset:Nn } +\cs_new_protected:Npn \dt_del_aux:NNnn #1#2#3#4 + { + \dt_split_row:NnTF #2 {#3} + { \dt_del_aux:NNnnnn #1 #2 {#4} } + { } + } +\cs_new_protected:Npn \dt_del_aux:NNnnnn #1#2#3#4#5#6 + { + \dt_split_key:nnTF {#5} {#3} + { \dt_del_aux:NNnnnnn #1 #2 {#4} {#6} } + { } + } +\cs_new_protected:Npn \dt_del_aux:NNnnnnn #1#2#3#4#5#6#7 + { #1 #2 { #3 #5 #7 #4 } } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\dt_remove_key:Nn, \dt_gremove_key:Nn} +% \begin{macro}[aux]{\dt_remove_key_aux:NNn} +% \begin{macro}[aux]{\dt_remove_key_aux:nNNnn} +% \begin{macro}[aux, EXP]{\dt_remove_key_aux:w} +% Deleting a key also removes from the table itself, so that there is no +% need to do any awkward checks when extracting data from the table. (It's +% likely that there will be more cases of accessing data than deleting +% rows). The deletion mapping ignores rows entirely and just pulls out +% matching key--value pairs, as this reduces the number of matches needed +% to a minimum. +% \begin{macrocode} +\cs_new_protected_nopar:Npn \dt_remove_key:Nn + { \dt_remove_key_aux:NNn \tl_set:Nx } +\cs_new_protected_nopar:Npn \dt_gremove_key:Nn + { \dt_remove_key_aux:NNn \tl_gset:Nx } +\cs_new_protected:Npn \dt_remove_key_aux:NNn #1#2#3 + { + \dt_split_key_list:NnTF #2 {#3} + { \exp_args:No \dt_remove_key_aux:nNNnn { \tl_to_str:n {#3} } #1 #2 } + { } + } +\cs_new_protected:Npn \dt_remove_key_aux:nNNnn #1#2#3#4#5 + { + \cs_set:Npn \dt_remove_key_aux:w ##1 \q_dt #1 \q_dt ##2 ##3 + { + \exp_not:n {##1} + \if_meaning:w \q_recursion_tail ##3 + \exp_after:wN \prg_map_break: + \fi: + \dt_remove_key_aux:w ##3 + } + #2 #3 + { + \exp_not:n {#4} + \dt_remove_key_aux:w #5 \q_dt #1 \q_dt { } \q_recursion_tail + \prg_break_point:n { } + } + } +\cs_new:Npn \dt_remove_key_aux:w { } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\dt_remove_row:Nn, \dt_gremove_row:Nn} +% \begin{macro}[aux]{\dt_remove_row_aux:NNn} +% \begin{macro}[aux]{\dt_remove_row_aux:NNnnnn} +% \begin{macro}[aux, EXP]{\dt_remove_row_aux:nw} +% \begin{macro}[aux, EXP]{\dt_remove_row_loop:nw} +% Removing a row is a slightly complex operation as there are two stages. +% The row itself is easy enough to remove, but then all later rows have to +% be renumbers. +% \begin{macrocode} +\cs_new_protected_nopar:Npn \dt_remove_row:Nn + { \dt_remove_row_aux:NNn \tl_set:Nx } +\cs_new_protected_nopar:Npn \dt_gremove_row:Nn + { \dt_remove_row_aux:NNn \tl_gset:Nx } +\cs_new_protected:Npn \dt_remove_row_aux:NNn #1#2#3 + { + \dt_split_row:NnTF #2 {#3} + { \dt_remove_row_aux:NNnnn #1 #2 } + { } + } +% \end{macrocode} +% If the code gets here, then |#3| is the table before the removed row, +% |#4| is the removed row and |#5| is everything afterwards. The first stage +% is to work out the new number of rows, then include all of |#3| except +% the old number of rows. The removed row |#4| is thrown away, and then there +% is a loop to recalculate the row numbers for all of the later rows. +% \begin{macrocode} +\cs_new_protected:Npn \dt_remove_row_aux:NNnnn #1#2#3#4#5 + { + #1 #2 + { + { \int_eval:n { \dt_rows:N #2 - \c_one } } + \dt_remove_row_aux:nw #3 \q_stop + \dt_remove_row_loop:nw #5 \q_recursion_tail \q_dt_row + \prg_break_point:n { } + } + } +\cs_new_eq:NN \dt_remove_row_aux:nw \dt_add_row_aux:nw +\cs_new:Npn \dt_remove_row_loop:nw #1#2 \q_dt_row + { + \if_meaning:w \q_recursion_tail #1 + \exp_after:wN \prg_map_break: + \fi: + \int_eval:n { #1 - \c_one } + \exp_not:n { #2 \q_dt_row } + \dt_remove_row_loop:nw + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \subsection{Accessing data in data tables} +% +% \begin{macro}{\dt_get:NnnN} +% \begin{macro}[aux]{\dt_get_aux:nNnnn} +% \begin{macro}[aux]{\dt_get_aux:nNnnn} +% Recovering a value from a row means doing two splits: first find the row, +% then find the key. Nothing exciting, just a question of tracking the +% returned items. +% \begin{macrocode} +\cs_new_protected:Npn \dt_get:NnnN #1#2#3#4 + { + \dt_split_row:NnTF #1 {#2} + { \dt_get_aux:nNnnn {#3} #4 } + { \tl_set:Nn #4 { \q_no_value } } + } +\cs_new_protected:Npn \dt_get_aux:nNnnn #1#2#3#4#5 + { + \dt_split_key:nnTF {#4} {#1} + { \dt_get_aux:Nnnn #2 } + { \tl_set:Nn #2 { \q_no_value } } + } +\cs_new_protected:Npn \dt_get_aux:Nnnn #1#2#3#4 { \tl_set:Nn #1 {#3} } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}[TF]{\dt_get:NnnN} +% \begin{macro}[aux]{\dt_get_aux_true:nNnnn} +% \begin{macro}[aux]{\dt_get_aux_true:Nnnn} +% The same idea as the standard method, but built as a conditional. +% \begin{macrocode} +\prg_new_protected_conditional:Npnn \dt_get:NnnN #1#2#3#4 { T , F , TF } + { + \dt_split_row:NnTF #1 {#2} + { \dt_get_aux_true:nNnnn {#3} #4 } + { \prg_return_false: } + } +\cs_new_protected:Npn \dt_get_aux_true:nNnnn #1#2#3#4#5 + { + \dt_split_key:nnTF {#4} {#1} + { \dt_get_aux_true:Nnnn #2 } + { \prg_return_false: } + } +\cs_new_protected:Npn \dt_get_aux_true:Nnnn #1#2#3#4 + { + \tl_set:Nn #1 {#3} + \prg_return_true: + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\dt_get:NnN} +% \begin{macro}[TF]{\dt_get:NnN} +% Simple wrappers. +% \begin{macrocode} +\cs_new_protected:Npn \dt_get:NnN #1 { \dt_get:NnnN #1 + { \dt_rows:N #1 - \c_one } } +\cs_new_protected:Npn \dt_get:NnNT #1 { \dt_get:NnnNF #1 + { \dt_rows:N #1 - \c_one } } +\cs_new_protected:Npn \dt_get:NnNF #1 { \dt_get:NnnNF #1 + { \dt_rows:N #1 - \c_one } } +\cs_new_protected:Npn \dt_get:NnNTF #1 { \dt_get:NnnNTF #1 + { \dt_rows:N #1 - \c_one } } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsection{Mapping to data tables} +% +% \begin{variable}{\g_dt_map_level_int} +% Unlike other mappings, the mapping level here has to be available +% and so linked to the module. +% \begin{macrocode} +\int_new:N \g_dt_map_level_int +% \end{macrocode} +% \end{variable} +% +% \begin{macro}{\dt_map_variables:Nnn} +% \begin{macro}[aux]{\dt_map_variables_key:nn} +% \begin{macro}[aux]{\dt_map_variables_aux:nnn} +% \begin{macro}[aux]{\dt_map_variables_aux:nNNw} +% \begin{macro}[aux]{\dt_map_variables_aux:nnw} +% Mapping across a data table is more complex than other cases as there +% are two \enquote{dimensions} to worry about: the rows and the keys. +% The first stage of the mapping is to convert the key--variable mapping +% into a sequence that can be used later. This is done with the assumption +% that any key without a variable can simply be dropped entirely. The +% header of the table is then split from the body. +% \begin{macrocode} +\cs_new_protected:Npn \dt_map_variables:Nnn #1#2#3 + { + \int_gincr:N \g_dt_map_level_int + \seq_gclear_new:c { g_dt_map_ \int_use:N \g_dt_map_level_int _seq } + \keyval_parse:NNn \use_none:n \dt_map_variables_key:nn {#2} + \dt_split_header:NT #1 { \dt_map_variables_aux:nnn {#3} } + } +\cs_new_protected:Npn \dt_map_variables_key:nn #1#2 + { + \seq_gput_right:cn { g_dt_map_ \int_use:N \g_dt_map_level_int _seq } + { {#1} #2 } + } +% \end{macrocode} +% As \cs{dt_split_header:NT} will leave a couple of tokens at the front +% of the body part of the split, there is a quick piece of tidying up +% to remove them. +% \begin{macrocode} +\cs_new_protected:Npn \dt_map_variables_aux:nnn #1#2#3 + { \dt_map_variables_aux:nNNw {#1} #3 \q_stop } +\cs_new_protected:Npn \dt_map_variables_aux:nNNw + #1 \q_dt_header \q_dt_row #2 \q_stop + { + \int_zero_new:c { l_dt_map_ \int_use:N \g_dt_map_level_int _row_int } + \dt_map_variables_aux:nnw {#1} #2 { } \q_recursion_tail \q_dt_row + \prg_break_point:n { \int_gdecr:N \g_dt_map_level_int } + } +\cs_new_protected:Npn \dt_map_variables_aux:nnw #1#2#3#4 \q_dt_row + { + \if_meaning:w \q_recursion_tail #3 + \exp_after:wN \dt_map_break: + \fi: + \seq_map_inline:cn { g_dt_map_ \int_use:N \g_dt_map_level_int _seq } + { \dt_get_aux:nNnnn ##1 { } {#3#4} { } } + #1 + \int_incr:c { l_dt_map_ \int_use:N \g_dt_map_level_int _row_int } + \dt_map_variables_aux:nnw {#1} + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}[rEXP]{\dt_map_break:} +% \begin{macro}[rEXP]{\dt_map_break:n} +% The break statements are simply copies. +% \begin{macrocode} +\cs_new_eq:NN \dt_map_break: \prg_map_break: +\cs_new_eq:NN \dt_map_break:n \prg_map_break:n +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsection{Data table conditionals} +% +% \begin{macro}[pTF, EXP]{\dt_if_empty:N} +% An empty data table has not only no rows but also no keys. (The number of +% rows can be tested using \cs{dt_rows:N} and an \texttt{int} test.) +% \begin{macrocode} +\prg_new_conditional:Npnn \dt_if_empty:N #1 { T , F , TF , p } + { + \if_meaning:w #1 \c_empty_dt + \prg_return_true: + \else: + \prg_return_false: + \fi: + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[pTF, EXP]{\dt_if_in:Nn} +% \begin{macro}[aux, EXP]{\dt_if_in_aux:nnn} +% \begin{macro}[aux, EXP]{\dt_if_in_aux:nwN} +% \begin{macro}[aux, EXP]{\dt_if_in_aux:n} +% Expandably checking for the presence of a key in the table as a whole +% requires a mapping to the header. The idea is the usual recursion set +% up with a string-based comparison only after checking for the end of +% the loop. +% \begin{macrocode} +\prg_new_conditional:Npnn \dt_if_in:Nn #1#2 { p , T , F , TF } + { \dt_split_header:NT #1 { \dt_if_in_aux:nnn {#2} } } +\cs_new:Npn \dt_if_in_aux:nnn #1#2#3 + { + \exp_last_unbraced:Nno \dt_if_in_aux:nwN {#1} { \use_none:nn #2 } + \q_recursion_tail \q_dt + \prg_break_point:n { } + } +\cs_new:Npn \dt_if_in_aux:nwN #1#2 \q_dt + { + \if_meaning:w \q_recursion_tail #2 + \exp_after:wN \prg_map_break:n + \else: + \exp_after:wN \use_none:n + \fi: + { \prg_return_false: } + \str_if_eq:nnTF {#1} {#2} + { \prg_map_break:n { \prg_return_true: } } + { \dt_if_in_aux:nwN {#1} } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}[pTF, EXP]{\dt_if_in_row:Nnn} +% \begin{macro}[aux, EXP]{\dt_if_in_row_aux:nw} +% \begin{macro}[aux, EXP]{\dt_if_in_row_aux:nn} +% \begin{macro}[aux, EXP]{\dt_if_in_row_aux:nwn} +% \begin{macro}[aux, EXP]{\dt_if_in_row_aux:N} +% Finding a key in a single row in an expandable way requires two mappings. +% To start of with, there is a search for the row. This uses for termination +% the fact that each row starts \cs{q_dt_row} and ends \cs{q_nil}, and +% always contains at least the row number as the first \meta{balanced +% text}. That can be replaced by the tail marker to terminate iteration: +% all that is then needed is the correct placement of the clean-up code. +% \begin{macrocode} +\prg_new_conditional:Npnn \dt_if_in_row:Nnn #1#2#3 { p , T , F , TF } + { + \exp_last_unbraced:Nno \dt_if_in_row_aux:nw {#2} #1 + \q_recursion_tail \q_nil + \prg_break_point:n { } + { \tl_to_str:n {#3} } + } +% \end{macrocode} +% The row iteration does a numerical comparison to see if the target row has +% been found. That means that the row argument does not need to be converted +% to a number earlier. +% \begin{macrocode} +\cs_new:Npn \dt_if_in_row_aux:nw #1#2 \q_dt_row #3#4 \q_nil + { + \if_meaning:w \q_recursion_tail #3 + \exp_after:wN \prg_map_break:n + \else: + \exp_after:wN \use_none:n + \fi: + { + \use_i:nn + \prg_return_false: + } + \int_compare:nNnTF {#1} = {#3} + { \prg_map_break:n { \exp_args:Nno \dt_if_in_row_aux:nn {#4} } } + { \dt_if_in_row_aux:nw {#1} } + } +% \end{macrocode} +% The second iteration is along the row. This is basically the same as +% \cs{prop_if_in:NnTF} with the \cs{q_dt} in place of \cs{q_prop}. +% \begin{macrocode} +\cs_new:Npn \dt_if_in_row_aux:nn #1#2 + { + \dt_if_in_row_aux:nwn {#2} #1 {#2} \q_dt { } \q_recursion_tail + \prg_break_point:n { } + } +\cs_new:Npn \dt_if_in_row_aux:nwn #1 \q_dt #2 \q_dt #3 + { + \str_if_eq:xxTF {#1} {#2} + { \dt_if_in_row_aux:N } + { \dt_if_in_row_aux:nwn {#1} } + } +\cs_new:Npn \dt_if_in_row_aux:N #1 + { + \if_meaning:w \q_dt #1 + \prg_return_true: + \else: + \prg_return_false: + \fi: + \prg_map_break: + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}[pTF, EXP]{\dt_if_in_row:Nn} +% Simple wrappers. +% \begin{macrocode} +\cs_new:Npn \dt_if_in_row_p:Nn #1 { \dt_if_in_row_p:Nnn #1 + { \dt_rows:N #1 - \c_one } } +\cs_new:Npn \dt_if_in_row:NnT #1 { \dt_if_in_row:NnnT #1 + { \dt_rows:N #1 - \c_one } } +\cs_new:Npn \dt_if_in_row:NnF #1 { \dt_if_in_row:NnnF #1 + { \dt_rows:N #1 - \c_one } } +\cs_new:Npn \dt_if_in_row:NnTF #1 { \dt_if_in_row:NnnTF #1 + { \dt_rows:N #1 - \c_one } } +% \end{macrocode} +% \end{macro} +% +% \subsection{Messages} +% +% \begin{macrocode} +\msg_kernel_new:nnnn { dt } { unknown-row } + { Data~table~#1~does~not~contain~a~row~'#2'. } + { + Data~table~#1~contains~#3~rows.~These~must~be~accessed~by~number:~row~ + #2~is~not~present~in~the~table. + } +% \end{macrocode} +% +% \begin{macrocode} +%</initex|package> +% \end{macrocode} +% +% \end{implementation} +% +% \PrintIndex
\ No newline at end of file |