summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/source/latex/l3experimental/l3dt/l3dt.dtx
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2012-02-06 01:22:53 +0000
committerKarl Berry <karl@freefriends.org>2012-02-06 01:22:53 +0000
commita851d6714a00e2a09b93ba3e251d8665290c0ab8 (patch)
tree3ba045c487cc9e5e28e73714cb6312d0e556b924 /Master/texmf-dist/source/latex/l3experimental/l3dt/l3dt.dtx
parenta9533a6794104fad593912e3481304047dcd0025 (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.dtx1351
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