% \iffalse meta-comment % %% File l3dt.dtx Copyright (C) 2011-2013 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 4420 2013-01-08 20:00:04Z joseph $ {L3 Experimental data tables} % %<*driver> \documentclass[full]{l3doc} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \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 one. % In the same way, the order of keys (columns) is recorded in a sequence-like % manner, again indexed from one. % % 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_remove:Nn, \dt_gremove:Nn} % \begin{syntax} % \cs{dt_remove: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_remove:Nnn, \dt_gremove:Nnn} % \begin{syntax} % \cs{dt_remove: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 to 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} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3dt} implementation} % % \begin{macrocode} %<*initex|package> % \end{macrocode} % % \begin{macrocode} %<@@=dt> % \end{macrocode} % % \begin{macrocode} %<*package> \ProvidesExplPackage {\ExplFileName}{\ExplFileDate}{\ExplFileVersion}{\ExplFileDescription} % % \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} \\ % \Arg{columns} \\ % \cs{q_@@} \meta{key_1} \cs{q_@@} \meta{key_2} \cs{q_@@} \ldots \\ % \cs{q_nil} \\ % \cs{q_@@_header} \\ % \cs{q_@@_row} \\ % \meta{row_1} \\ % \cs{q_@@} \meta{key_1} \cs{q_@@} \Arg{data_{1,1}} \\ % \cs{q_@@} \meta{key_2} \cs{q_@@} \Arg{data_{1,2}} \\ % \ldots \\ % \cs{q_@@} \\ % \cs{q_nil} \\ % \cs{q_@@_row} \\ % \meta{row_2} \\ % \cs{q_@@} \meta{key_1} \cs{q_@@} \Arg{data_{2,1}} \\ % \cs{q_@@} \meta{key_2} \cs{q_@@} \Arg{data_{2,2}} \\ % \ldots \\ % \cs{q_@@} \\ % \cs{q_nil} \\ % \cs{q_@@_row} \\ % \ldots \\ % \cs{q_@@_row} % \end{quote} % % \begin{variable}{\q_@@, \q_@@_row, \q_@@_header} % The quarks are set up. % \begin{macrocode} \quark_new:N \q_@@ \quark_new:N \q_@@_row \quark_new:N \q_@@_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 } { 0 } \q_@@ \q_nil \q_@@_header \q_@@_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]{\@@_split:nnnn} % \begin{macro}[aux]{\@@_split: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 \@@_split:nnnn #1#2#3#4 { #3 #2 } \cs_new_protected:Npn \@@_split:w { } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[int,EXP]{\@@_split_header:NT} % \begin{macro}[aux,EXP]{\@@_split_header: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 \@@_split_header:NT #1#2 { \exp_after:wN \@@_split_header:wn #1 \q_stop {#2} } \cs_new:Npn \@@_split_header:wn #1 \q_nil \q_@@_header #2 \q_stop #3 { #3 {#1} { \q_@@_header #2 } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[int]{\@@_split_key:nnTF} % \begin{macro}[aux]{\@@_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 \@@_split_key:nnTF #1#2 { \exp_args:No \@@_split_key_aux:nnTF { \tl_to_str:n {#2} } {#1} } \cs_new_protected:Npn \@@_split_key_aux:nnTF #1#2 { \cs_set_protected:Npn \@@_split:w ##1 \q_@@ #1 \q_@@ ##2##3##4 \q_mark ##5 \q_stop { \@@_split:nnnn ##3 { { ##1 \q_@@ } {##2} {##4} } } \@@_split:w #2 \q_mark \q_@@ #1 \q_@@ { } { ? \use_ii:nn { } } \q_mark \q_stop } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[int]{\@@_split_key_list:NnTF} % \begin{macro}[aux]{\@@_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_@@} and \cs{q_@@_header} due to the \cs{q_nil} % which is part of a new table. The use of |##1##2##3| in \cs{@@_split:w} % here is to deal with the overall number of rows and keys. The set up here % means that these will always be unbraced then rebraced: simply grabbing % |##1##2| 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 \@@_split_key_list:NnTF #1#2 { \exp_args:NNo \@@_split_key_list_aux:NnTF #1 { \tl_to_str:n {#2} } } \cs_new_protected:Npn \@@_split_key_list_aux:NnTF #1#2 { \cs_set_protected:Npn \@@_split:w ##1##2##3 \q_@@ #2 \q_@@ ##4##5 \q_@@_header ##6 \q_mark ##7 \q_stop { \@@_split:nnnn ##4 { { {##1} {##2} ##3 \q_@@ } { ##4##5 \q_@@_header ##6 } } } \exp_after:wN \@@_split:w #1 \q_mark \q_@@ #2 \q_@@ { ? \use_ii:nn { } } \q_@@_header \q_mark \q_stop } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[int]{\@@_split_row:NnTF} % \begin{macro}[aux]{\@@_split_row_aux:NnTF, \@@_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 \@@_split_row:NnTF #1#2 { \@@_split_row_aux:NfTF #1 { \int_eval:n {#2} } } \cs_new_protected:Npn \@@_split_row_aux:NnTF #1#2 { \cs_set_protected:Npn \@@_split:w ##1 \q_@@_row #2 \q_@@ ##2##3 \q_@@_row ##4 \q_mark ##5 \q_stop { \@@_split:nnnn ##2 { { ##1 \q_@@_row } { #2 \q_@@ ##2##3 } {##4} } } \exp_after:wN \@@_split:w #1 \q_mark \q_@@_row #2 \q_@@ { ? \use_ii:nn { } } \q_@@_row \q_mark \q_stop } \cs_generate_variant:Nn \@@_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]{\@@_add_key:NNn} % \begin{macro}[aux]{\@@_add_key:NNnnn} % \begin{macro}[aux]{\@@_add_key: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{@@_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 { \@@_add_key:NNn \tl_set:Nx } \cs_new_protected_nopar:Npn \dt_gadd_key:Nn { \@@_add_key:NNn \tl_gset:Nx } \cs_new_protected:Npn \@@_add_key:NNn #1#2#3 { \@@_split_key_list:NnTF #2 {#3} { \use_none:nn } { \@@_split_header:NT #2 { \@@_add_key:NNnnn #1 #2 {#3} } } } \cs_new_protected:Npn \@@_add_key:NNnnn #1#2#3#4#5 { \@@_add_key:NNnnnwnn #1 #2 #4 \q_stop {#3} {#5} } \cs_new_protected:Npn \@@_add_key:NNnnnwnn #1#2#3#4#5 \q_stop #6#7 { #1 #2 { {#3} { \int_eval:n { #4 + \c_one } } \exp_not:n {#5} \tl_to_str:n {#6} \exp_not:n { \q_@@ \q_nil #7 } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\dt_add_row:N, \dt_gadd_row:N} % \begin{macro}[aux]{\@@_add_row:NN} % \begin{macro}[aux]{\@@_add_row:NnN, \@@_add_row:NfN} % \begin{macro}[aux,EXP]{\@@_add_row: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 { \@@_add_row:NN \tl_set:Nx } \cs_new_protected_nopar:Npn \dt_gadd_row:N { \@@_add_row:NN \tl_gset:Nx } \cs_new_protected:Npn \@@_add_row:NN #1#2 { \@@_add_row:NfN #1 { \int_eval:n { \dt_rows:N #2 + \c_one } } #2 } \cs_new_protected:Npn \@@_add_row:NnN #1#2#3 { #1 #3 { {#2} \exp_after:wN \@@_add_row:nw #3 \q_stop #2 \exp_not:n { \q_@@ \q_nil \q_@@_row } } } \cs_generate_variant:Nn \@@_add_row:NnN { Nf } \cs_new:Npn \@@_add_row: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 } } \cs_new_protected:Npn \dt_gput:Nnn #1 { \dt_gput:Nnnn #1 { \dt_rows:N #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\dt_put:Nnnn, \dt_gput:Nnnn} % \begin{macro}[aux]{\@@_put:NNNnnn} % \begin{macro}[aux]{\@@_put:NNnnnnn} % \begin{macro}[aux]{\@@_put_update:NNnnnnnnn} % \begin{macro}[aux]{\@@_put_add_to_row:NNnnnnn} % \begin{macro}[aux, EXP]{\@@_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 { \@@_put:NNNnnn \dt_add_key:Nn \tl_set:Nx } \cs_new_protected_nopar:Npn \dt_gput:Nnnn { \@@_put: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 \@@_put:NNNnnn #1#2#3#4#5#6 { #1 #3 {#5} \@@_split_row:NnTF #3 {#4} { \@@_put: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 \@@_put:NNnnnnn #1#2#3#4#5#6#7 { \@@_split_key:nnTF {#6} {#3} { \@@_put_update:NNnnnnnnn #1 #2 {#3} {#4} {#5} {#7} } { \@@_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 \@@_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_@@ {#4} \q_@@ #9 \q_@@_row #6 } } } % \end{macrocode} % A slightly more complex case when adding an item. The arguments here are % identical to those for \cs{@@_put: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 \@@_put_add_to_row:NNnnnnn #1#2#3#4#5#6#7 { #1 #2 { \exp_not:n {#5} \exp_not:o { \@@_put_add_to_row_aux:w #6 } \tl_to_str:n {#3} \exp_not:n { \q_@@ {#4} \q_@@ \q_nil \q_@@_row #7 } } } \cs_new:Npn \@@_put_add_to_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}[EXP, aux]{\@@_keys:nnw} % \begin{macro}[EXP]{\dt_rows:N} % The number of rows in a dt is the very first entry. Getting the number of % keys is almost the same, except a custom auxiliary is needed. % \begin{macrocode} \cs_new:Npn \dt_keys:N #1 { \exp_after:wN \@@_keys:nnw #1 \q_stop } \cs_new:Npn \@@_keys:nnw #1#2#3 \q_stop {#2} \cs_new:Npn \dt_rows:N #1 { \exp_after:wN \use_i_delimit_by_q_stop:nw #1 \q_stop } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \subsection{Removing data} % % \begin{macro}{\dt_remove:Nn,\dt_gremove:Nn} % Deleting to the current row is simply a special case of deleting to an % arbitrary row. % \begin{macrocode} \cs_new_protected:Npn \dt_remove:Nn #1 { \dt_remove:Nnn #1 { \dt_rows:N #1 } } \cs_new_protected:Npn \dt_gremove:Nn #1 { \dt_gremove:Nnn #1 { \dt_rows:N #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\dt_remove:Nnn, \dt_gremove:Nnn} % \begin{macro}[aux]{\dt_remove_aux:NNnn} % \begin{macro}[aux]{\dt_remove_aux:NNnnnn} % \begin{macro}[aux]{\dt_remove_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_remove:Nnn { \dt_remove_aux:NNnn \tl_set:Nn } \cs_new_protected_nopar:Npn \dt_gremove:Nnn { \dt_remove_aux:NNnn \tl_gset:Nn } \cs_new_protected:Npn \dt_remove_aux:NNnn #1#2#3#4 { \@@_split_row:NnTF #2 {#3} { \dt_remove_aux:NNnnnn #1 #2 {#4} } { } } \cs_new_protected:Npn \dt_remove_aux:NNnnnn #1#2#3#4#5#6 { \@@_split_key:nnTF {#5} {#3} { \dt_remove_aux:NNnnnnn #1 #2 {#4} {#6} } { } } \cs_new_protected:Npn \dt_remove_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 { \@@_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 { \dt_remove_key_aux:nNNnnwn {#1} #2 #3 #4 \q_stop {#5} } \cs_new_protected:Npn \dt_remove_key_aux:nNNnnwn #1#2#3#4#5#6 \q_stop #7 { \cs_set:Npn \dt_remove_key_aux:w ##1 \q_@@ #1 \q_@@ ##2 ##3 { \exp_not:n {##1} \__quark_if_recursion_tail_break:nN {##3} \dt_map_break: \dt_remove_key_aux:w ##3 } #2 #3 { {#4} { \int_eval:n { #5 - \c_one } } \exp_not:n {#6} \dt_remove_key_aux:w #7 \q_@@ #1 \q_@@ { } \q_recursion_tail \__prg_break_point:Nn \dt_map_break: { } } } \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 { \@@_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_@@_row \__prg_break_point:Nn \dt_map_break: { } } } \cs_new_eq:NN \dt_remove_row_aux:nw \@@_add_row:nw \cs_new:Npn \dt_remove_row_loop:nw #1#2 \q_@@_row { \__quark_if_recursion_tail_break:nN {#1} \dt_map_break: \int_eval:n { #1 - \c_one } \exp_not:n { #2 \q_@@_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 { \@@_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 { \@@_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]{\@@_get_true:nNnnn} % \begin{macro}[aux]{\@@_get_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 } { \@@_split_row:NnTF #1 {#2} { \@@_get_true:nNnnn {#3} #4 } { \prg_return_false: } } \cs_new_protected:Npn \@@_get_true:nNnnn #1#2#3#4#5 { \@@_split_key:nnTF {#4} {#1} { \@@_get_true:Nnnn #2 } { \prg_return_false: } } \cs_new_protected:Npn \@@_get_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 } } \cs_new_protected:Npn \dt_get:NnNT #1 { \dt_get:NnnNF #1 { \dt_rows:N #1 } } \cs_new_protected:Npn \dt_get:NnNF #1 { \dt_get:NnnNF #1 { \dt_rows:N #1 } } \cs_new_protected:Npn \dt_get:NnNTF #1 { \dt_get:NnnNTF #1 { \dt_rows:N #1 } } % \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]{\@@_map_variables_key:nn} % \begin{macro}[aux]{\@@_map_variables:nnn} % \begin{macro}[aux]{\@@_map_variables:nNNw} % \begin{macro}[aux]{\@@_map_variables: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 \@@_map_variables_key:nn {#2} \@@_split_header:NT #1 { \@@_map_variables:nnn {#3} } } \cs_new_protected:Npn \@@_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{@@_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 \@@_map_variables:nnn #1#2#3 { \@@_map_variables:nNNw {#1} #3 \q_stop } \cs_new_protected:Npn \@@_map_variables:nNNw #1 \q_@@_header \q_@@_row #2 \q_stop { \int_zero_new:c { l_dt_map_ \int_use:N \g_dt_map_level_int _row_int } \@@_map_variables:nnw {#1} #2 { } \q_recursion_tail \q_@@_row \__prg_break_point:Nn \dt_map_break: { \int_gdecr:N \g_dt_map_level_int } } \cs_new_protected:Npn \@@_map_variables:nnw #1#2#3#4 \q_@@_row { \__quark_if_recursion_tail_break:nN {#3} \dt_map_break: \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 } \@@_map_variables: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 use the general \cs{__prg_map_break:Nn}. % \begin{macrocode} \cs_new_nopar:Npn \dt_map_break: { \__prg_map_break:Nn \dt_map_break: { } } \cs_new_nopar:Npn \dt_map_break:n { \__prg_map_break:Nn \dt_map_break: } % \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]{\@@_if_in:nnn} % \begin{macro}[aux, EXP]{\@@_if_in:nwN} % \begin{macro}[aux, EXP]{\@@_if_in: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 } { \@@_split_header:NT #1 { \@@_if_in:nnn {#2} } } \cs_new:Npn \@@_if_in:nnn #1#2#3 { \exp_last_unbraced:Nno \@@_if_in:nwN {#1} { \use_none:nn #2 } \q_recursion_tail \q_@@ \__prg_break_point: } \cs_new:Npn \@@_if_in:nwN #1#2 \q_@@ { \if_meaning:w \q_recursion_tail #2 \exp_after:wN \__prg_break:n \else: \exp_after:wN \use_none:n \fi: { \prg_return_false: } \str_if_eq:nnTF {#1} {#2} { \__prg_break:n { \prg_return_true: } } { \@@_if_in: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]{\@@_if_in_row:nw} % \begin{macro}[aux, EXP]{\@@_if_in_row:nn} % \begin{macro}[aux, EXP]{\@@_if_in_row:nwn} % \begin{macro}[aux, EXP]{\@@_if_in_row: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_@@_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 \@@_if_in_row:nw {#2} #1 \q_recursion_tail \q_nil \__prg_break_point: { \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 \@@_if_in_row:nw #1#2 \q_@@_row #3#4 \q_nil { \if_meaning:w \q_recursion_tail #3 \exp_after:wN \__prg_break:n \else: \exp_after:wN \use_none:n \fi: { \use_i:nn \prg_return_false: } \int_compare:nNnTF {#1} = {#3} { \__prg_break:n { \exp_args:Nno \@@_if_in_row:nn {#4} } } { \@@_if_in_row: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_@@} in place of \cs{q__prop}. % \begin{macrocode} \cs_new:Npn \@@_if_in_row:nn #1#2 { \@@_if_in_row:nwn {#2} #1 {#2} \q_@@ { } \q_recursion_tail \__prg_break_point: } \cs_new:Npn \@@_if_in_row:nwn #1 \q_@@ #2 \q_@@ #3 { \str_if_eq_x:nnTF {#1} {#2} { \@@_if_in_row:N } { \@@_if_in_row:nwn {#1} } } \cs_new:Npn \@@_if_in_row:N #1 { \if_meaning:w \q_@@ #1 \prg_return_true: \else: \prg_return_false: \fi: \__prg_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 } } \cs_new:Npn \dt_if_in_row:NnT #1 { \dt_if_in_row:NnnT #1 { \dt_rows:N #1 } } \cs_new:Npn \dt_if_in_row:NnF #1 { \dt_if_in_row:NnnF #1 { \dt_rows:N #1 } } \cs_new:Npn \dt_if_in_row:NnTF #1 { \dt_if_in_row:NnnTF #1 { \dt_rows:N #1 } } % \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} % % \end{macrocode} % % \end{implementation} % % \PrintIndex