%%
%% This is file `datatool.sty',
%% generated with the docstrip utility.
%%
%% The original source files were:
%%
%% datatool.dtx  (with options: `datatool.sty,package')
%% 
%%  datatool.dtx
%%  Copyright 2025 Nicola Talbot
%% 
%%  This work may be distributed and/or modified under the
%%  conditions of the LaTeX Project Public License, either version 1.3
%%  of this license or (at your option) any later version.
%%  The latest version of this license is in
%%    http://www.latex-project.org/lppl.txt
%%  and version 1.3 or later is part of all distributions of LaTeX
%%  version 2005/12/01 or later.
%% 
%%  This work has the LPPL maintenance status `maintained'.
%% 
%%  The Current Maintainer of this work is Nicola Talbot.
%% 
%%  This work consists of the files datatool.dtx and datatool.ins and the derived files datatool-base.sty, datatool-undetermined.ldf, datatool-latin1.ldf, datatool-utf8.ldf, datatool-l3fp.def, datatool-lua.def, datatool-fp.def, datatool-fp.sty, datatool-pgfmath.def, datatool-pgfmath.sty, datatool.sty, datagidx.sty, databib.sty, databar.sty, datapie.sty, dataplot.sty, person.sty, databib.bst, databar-2019-09-27.sty, databib-2019-09-27.sty, datagidx-2019-09-27.sty, datapie-2019-09-27.sty, dataplot-2019-09-27.sty, datatool-2019-09-27.sty, datatool-base-2019-09-27.sty, datatool-fp-2019-09-27.sty, datatool-pgfmath-2019-09-27.sty, person-2019-09-27.sty.
%% 
%% \CharacterTable
%%  {Upper-case    \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
%%   Lower-case    \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
%%   Digits        \0\1\2\3\4\5\6\7\8\9
%%   Exclamation   \!     Double quote  \"     Hash (number) \#
%%   Dollar        \$     Percent       \%     Ampersand     \&
%%   Acute accent  \'     Left paren    \(     Right paren   \)
%%   Asterisk      \*     Plus          \+     Comma         \,
%%   Minus         \-     Point         \.     Solidus       \/
%%   Colon         \:     Semicolon     \;     Less than     \<
%%   Equals        \=     Greater than  \>     Question mark \?
%%   Commercial at \@     Left bracket  \[     Backslash     \\
%%   Right bracket \]     Circumflex    \^     Underscore    \_
%%   Grave accent  \`     Left brace    \{     Vertical bar  \|
%%   Right brace   \}     Tilde         \~}
\NeedsTeXFormat{LaTeX2e}
\DeclareRelease{v2.32}{2019-09-27}{datatool-2019-09-27.sty}
\DeclareCurrentRelease{v3.4.1}{2025-04-25}
\ProvidesPackage{datatool}[2025/04/25 v3.4.1 (NLCT)]
\RequirePackage{ifthen}
\RequirePackage{xfor}
\RequirePackage{etoolbox}
\RequirePackage{tracklang}
\ExplSyntaxOn
\cs_new:Nn \__datatool_char_to_hex:nN
 {
   \tl_set:Nx #2 { \int_to_hex:n { `#1 } }
 }
\newcommand*{\@dtl@separator}{,}
\NewDocumentCommand \DTLsetseparator { m }
{
  \tl_if_single_token:nTF { #1 }
  {
    \tl_set:Nx \@dtl@separator { \tl_to_str:n { #1 } }
    \__datatool_char_to_hex:nN { #1 } \l__datatool_tmpa_tl
    \exp_args:NNx \regex_set:Nn \l__datatool_blank_row_regex
      {
        \exp_not:N \A
        \exp_not:N \cO (?: \exp_not:N \x { \l__datatool_tmpa_tl } ) *
        \exp_not:N \Z
      }
  }
  {
    \PackageError{datatool}{Separator ~ must ~ be ~ a ~ single ~
    token ~ (found ~ \tl_count_tokens:n { #1 } ~ tokens) }{}
  }
}
\regex_new:N \l__datatool_blank_row_regex
\DTLsetseparator{,}
\ExplSyntaxOff
\begingroup
\catcode`\^^I12
\gdef\DTLsettabseparator{%
  \catcode`\^^I12
  \DTLsetseparator{^^I}%
}
\gdef\DTLmaketabspace{%
  \catcode`\^^I10\relax
}
\endgroup
\newcommand{\@dtl@delimiter}{"}
\ExplSyntaxOn
\tl_clear_new:N \l__datatool_tmpa_tl
\NewDocumentCommand \DTLsetdelimiter { m }
{
  \tl_if_single_token:nTF { #1 }
  {
    \tl_set:Nx \@dtl@delimiter { \tl_to_str:n { #1 } }
    \__datatool_char_to_hex:nN { #1 } \l__datatool_tmpa_tl
    \exp_args:NNx \regex_set:Nn \l__datatool_delim_left_regex
      {
        \exp_not:N \A \exp_not:N \s *
        \exp_not:N \cO (?: \exp_not:N \x { \l__datatool_tmpa_tl } )
      }
    \exp_args:NNx \regex_set:Nn \l__datatool_delim_both_regex
      {
        \exp_not:N \A \exp_not:N \s *
        \exp_not:N \cO (?: \exp_not:N \x { \l__datatool_tmpa_tl } )
        ( .* )
        \exp_not:N \cO (?: \exp_not:N \x { \l__datatool_tmpa_tl } )
        \exp_not:N \s * \exp_not:N \Z
      }
    \exp_args:NNx \regex_set:Nn \l__datatool_delim_right_regex
      {
        \exp_not:N \cO (?: \exp_not:N \x { \l__datatool_tmpa_tl } )
        \exp_not:N \s * \exp_not:N \Z
      }
    \exp_args:NNx \regex_set:Nn \l__datatool_escape_delim_bksl_regex
      {
        ( \exp_not:N \\ | \exp_not:N \cO ( \exp_not:N \x { \l__datatool_tmpa_tl } ) )
      }
    \exp_args:NNx \regex_set:Nn \l__datatool_escape_delim_regex
      {
        ( \exp_not:N \cO ( \exp_not:N \x { \l__datatool_tmpa_tl } ) )
      }
    \exp_args:NNx \regex_set:Nn \l__datatool_unescape_str_delim_bksl_regex
      {
         \exp_not:N \cO (?: \exp_not:N \x { 5c } )
         ( \exp_not:N \cO
           ( \exp_not:N \x { \l__datatool_tmpa_tl } | \exp_not:N \x { 5c } )
         )
      }
    \exp_args:NNx \regex_set:Nn \l__datatool_unescape_str_double_delim_regex
      {
          \exp_not:N \cO (?: \exp_not:N \x { \l__datatool_tmpa_tl } )
         ( \exp_not:N \cO ( \exp_not:N \x { \l__datatool_tmpa_tl } ) )
      }
    \exp_args:NNx \regex_set:Nn \l__datatool_unescape_cs_double_delim_regex
      {
         (
           \exp_not:N \c
           {
             ( \exp_not:N \x { \l__datatool_tmpa_tl } )
           }
         )
         \exp_not:N \x { \l__datatool_tmpa_tl }
      }
    \exp_args:NNx \regex_set:Nn \l__datatool_unescape_str_delim_regex
      {
         \exp_not:N \cO (?: \exp_not:N \x { 5c } )
         ( \exp_not:N \cO ( \exp_not:N \x { \l__datatool_tmpa_tl } ))
      }
    \exp_args:NNx \regex_set:Nn \l__datatool_unescape_cs_delim_regex
      {
         \exp_not:N \c
         {
           ( \exp_not:N \x { \l__datatool_tmpa_tl } )
         }
      }
  }
  {
    \PackageError{datatool}{Delimiter ~ must ~ be ~ a ~ single ~
    token ~ (found ~ \tl_count_tokens:n { #1 } ~ tokens) }{}
  }
}
\regex_new:N \l__datatool_delim_left_regex
\regex_new:N \l__datatool_delim_right_regex
\regex_new:N \l__datatool_delim_both_regex
\regex_new:N \l__datatool_escape_delim_regex
\regex_new:N \l__datatool_escape_delim_bksl_regex
\regex_new:N \l__datatool_unescape_str_delim_regex
\regex_new:N \l__datatool_unescape_str_delim_bksl_regex
\regex_new:N \l__datatool_unescape_cs_delim_regex
\regex_new:N \l__datatool_unescape_str_double_delim_regex
\regex_new:N \l__datatool_unescape_cs_double_delim_regex

\DTLsetdelimiter{"}
\tl_new:N \l__datatool_default_dbname_tl
\tl_set:Nn \l__datatool_default_dbname_tl { untitled }
\bool_new:N \l__datatool_db_global_bool
\bool_set_true:N \l__datatool_db_global_bool
\bool_new:N \l__datatool_new_element_trim_bool
\bool_set_true:N \l__datatool_new_element_trim_bool
\bool_new:N \l__datatool_new_element_expand_bool
\newcommand*{\dtlexpandnewvalue}{
  \bool_set_true:N \l__datatool_new_element_expand_bool
  \def\@dtl@setnewvalue##1{
    \protected@edef\@dtl@tmp{ ##1 }
    \bool_if:NT\l__datatool_new_element_trim_bool
    {
      \tl_trim_spaces:N \@dtl@tmp
    }
    \expandafter\@dtl@toks\expandafter{\@dtl@tmp}
  }
}
\newcommand*{\dtlnoexpandnewvalue}{%
  \bool_set_false:N \l__datatool_new_element_expand_bool
  \def\@dtl@setnewvalue##1{
    \bool_if:NTF\l__datatool_new_element_trim_bool
    {
      \tl_set:Nn \l__datatool_item_value_tl { ##1 }
      \tl_trim_spaces:N \l__datatool_item_value_tl
      \exp_args:NV \@dtl@toks \l__datatool_item_value_tl
    }
    {
      \@dtl@toks{##1}
    }
  }
}
\dtlnoexpandnewvalue
\bool_new:N \l__datatool_db_store_datum_bool
\keys_define:nn { datatool }
{
   default-name .str_set_x:N = \l__datatool_default_dbname_tl,
   global .bool_set:N = \l__datatool_db_global_bool,
   store-datum .bool_set:N = \l__datatool_db_store_datum_bool,
   separator .code:n = { \DTLsetseparator { #1 } },
   delimiter .code:n = { \DTLsetdelimiter { #1 } },
   new-value-trim .bool_set:N = \l__datatool_new_element_trim_bool,
   new-value-expand .choice:,
   new-value-expand / true .code:n = { \dtlexpandnewvalue },
   new-value-expand / false .code:n = { \dtlnoexpandnewvalue },
   new-value-expand .default:n = true,
}
\ExplSyntaxOff
\IfPackageLoadedTF{datatool-base}
{
  \ProcessKeyOptions[datatool]
}
{
 \DeclareOption*{\PassOptionsToPackage{\CurrentOption}{datatool-base}}
 \ProcessOptions
  \RequirePackage{datatool-base}
}
\ExplSyntaxOn
\tl_new:N \l__datatool_item_key_tl
\tl_new:N \l__datatool_item_type_tl
\tl_new:N \l__datatool_item_head_tl
\tl_new:N \l__datatool_item_value_tl
\tl_new:N \l__datatool_item_currency_tl
\tl_new:N \l__datatool_item_currency_ii_tl
\tl_new:N \l__datatool_row_tl
\tl_new:N \l__datatool_keydata_tl
\tl_new:N \l__datatool_content_tl
\tl_new:N \l__datatool_align_tl
\tl_new:N \l__datatool_before_tl
\tl_new:N \l__datatool_after_tl
\tl_new:N \l__datatool_cs_builder_tl
\tl_new:N \l__datatool_item_col_tl
\tl_new:N \l__datatool_row_idx_tl
\int_new:N \l__datatool_max_cols_int
\int_new:N \l__datatool_col_idx_int
\int_new:N \l__datatool_row_idx_int
\int_new:N \l__datatool_item_type_int
\seq_new:N \l__datatool_column_indexes_seq
\seq_new:N \l__datatool_column_keys_seq
\fp_new:N \l__datatool_min_fp
\fp_new:N \l__datatool_min_ii_fp
\fp_new:N \l__datatool_max_fp
\fp_new:N \l__datatool_max_ii_fp
\fp_new:N \l__datatool_total_ii_fp
\clist_new:N \l__datatool_tmpa_clist
\newrobustcmd{\DTLpar}{\par}
\NewDocumentCommand \DTLaction { o m }
{
  \int_zero:N \l__datatool_action_column_int
  \int_zero:N \l__datatool_action_column_ii_int
  \tl_clear:N \l__datatool_action_key_tl
  \tl_clear:N \l__datatool_action_key_ii_tl
  \int_set_eq:NN
    \l__datatool_action_type_int
    \c_datatool_unknown_int
  \int_zero:N \l__datatool_action_row_int
  \int_zero:N \l__datatool_action_row_ii_int
  \tl_set:Nn \l__datatool_action_value_tl { \q_no_value }
  \clist_clear:N \l__datatool_action_options_clist
  \clist_clear:N \l__datatool_action_assign_clist
  \clist_clear:N \l__datatool_action_keys_clist
  \clist_clear:N \l__datatool_action_columns_clist
  \seq_clear:N \l__datatool_action_columns_seq
  \seq_clear:N \l__datatool_action_names_seq
  \bool_set_false:N \l__datatool_action_datum_bool
  \int_set:Nn \l__datatool_action_datum_round_int { -1 }
  \bool_set_true:N \l__datatool_action_datum_locale_decimal_bool
  \bool_set_false:N \l__datatool_action_datum_locale_integer_bool
  \tl_set:Nn \l__datatool_action_datum_currency_tl
   {  \l__datatool_item_currency_tl }
  \clist_clear:N \l__datatool_action_return_clist
  \tl_set_eq:NN \l__datatool_action_return_tl \dtlnovalue
  \prop_clear:N \l__datatool_action_return_prop
  \IfValueT { #1 }
    { \keys_set:nn { datatool/action } { #1 } }
  \seq_if_empty:NTF \l__datatool_action_names_seq
   {
     \tl_set_eq:NN
       \l__datatool_action_name_tl
       \l__datatool_default_dbname_tl
   }
   {
     \tl_set:Nx \l__datatool_action_name_tl
      {
        \seq_item:Nn \l__datatool_action_names_seq
          { \c_one_int }
      }
   }
  \tl_set:Nx \l__datatool_action_tl { \tl_trim_spaces:n { #2 } }
  \cs_if_exist_use:cF { __datatool_action_ \l__datatool_action_tl : }
  {
    \PackageError { datatool }
    { Unknown ~ action ~ ` \l__datatool_action_tl ' }
    { The ~ action ~ in ~ the ~ mandatory ~ argument ~ of ~
      \token_to_str:N \DTLaction \c_space_tl ~ is ~ not ~ recognised }
  }
  \clist_if_empty:NF \l__datatool_action_return_clist
   {
     \keyval_parse:NNV
       \__datatool_cskey_missing_val:n
       \__datatool_action_get:nn
       \l__datatool_action_return_clist
   }
}
\tl_new:N \l__datatool_action_tl
\tl_new:N \l__datatool_action_name_tl
\tl_new:N \l__datatool_action_key_tl
\tl_new:N \l__datatool_action_key_ii_tl
\int_new:N \l__datatool_action_column_int
\int_new:N \l__datatool_action_column_ii_int
\int_new:N \l__datatool_action_row_int
\int_new:N \l__datatool_action_row_ii_int
\int_new:N \l__datatool_action_type_int
\int_set_eq:NN \l__datatool_action_type_int \c_datatool_unknown_int
\tl_new:N \l__datatool_action_value_tl
\tl_set:Nn \l__datatool_action_value_tl { \q_no_value }
\bool_new:N \l__datatool_action_datum_bool
\seq_new:N \l__datatool_action_names_seq
\clist_new:N \l__datatool_action_options_clist
\clist_new:N \l__datatool_action_assign_clist
\clist_new:N \l__datatool_action_return_clist
\clist_new:N \l__datatool_action_keys_clist
\clist_new:N \l__datatool_action_columns_clist
\seq_new:N \l__datatool_action_columns_seq
\tl_new:N \l__datatool_action_tmpa_tl
\tl_new:N \l__datatool_action_tmpb_tl
\seq_new:N \l__datatool_action_tmp_options_seq
\seq_new:N \l__datatool_action_tmp_data_seq
\keys_define:nn { datatool/action }
{
  name .code:n =
   {
     \exp_args:NNx \seq_set_from_clist:Nn
       \l__datatool_action_names_seq { #1 }
   },
  name .value_required:n = true,
  key .str_set_x:N = \l__datatool_action_key_tl,
  key .value_required:n = true,
  key2 .str_set_x:N = \l__datatool_action_key_ii_tl,
  key2 .value_required:n = true,
  column .int_set:N = \l__datatool_action_column_int,
  column .value_required:n = true,
  column2 .int_set:N = \l__datatool_action_column_ii_int,
  column2 .value_required:n = true,
  row .int_set:N = \l__datatool_action_row_int,
  row .value_required:n = true,
  row2 .int_set:N = \l__datatool_action_row_ii_int,
  row2 .value_required:n = true,
  value .tl_set:N = \l__datatool_action_value_tl,
  value .value_required:n = true,
  expand-value .tl_set_x:N = \l__datatool_action_value_tl,
  expand-value .value_required:n = true,
  expand-once-value .code:n =
   { \tl_set:No \l__datatool_action_value_tl { #1 } },
  expand-once-value .value_required:n = true,
  options .clist_set:N = \l__datatool_action_options_clist,
  options .value_required:n = true,
  assign .clist_set:N = \l__datatool_action_assign_clist,
  assign .value_required:n = true,
  keys .clist_set:N = \l__datatool_action_keys_clist,
  keys .value_required:n = true,
  columns .clist_set:N = \l__datatool_action_columns_clist,
  columns .value_required:n = true,
  return .clist_set:N = \l__datatool_action_return_clist,
  return .value_required:n = true,
  type .choice:,
  type .value_required:n = true,
  type / string .code:n =
    {
      \int_set_eq:NN \l__datatool_action_type_int \c_datatool_string_int
    },
  type / integer .code:n =
    {
      \int_set_eq:NN \l__datatool_action_type_int \c_datatool_integer_int
    },
  type / int .code:n =
    {
      \int_set_eq:NN \l__datatool_action_type_int \c_datatool_integer_int
    },
  type / decimal .code:n =
    {
      \int_set_eq:NN \l__datatool_action_type_int \c_datatool_decimal_int
    },
  type / real .code:n =
    {
      \int_set_eq:NN \l__datatool_action_type_int \c_datatool_real_int
    },
  type / currency .code:n =
    {
      \int_set_eq:NN \l__datatool_action_type_int \c_datatool_currency_int
    },
  type / datetime .code:n =
    {
      \int_set_eq:NN \l__datatool_action_type_int \c_datatool_datetime_int
    },
  type / timestamp .code:n =
    {
      \int_set_eq:NN \l__datatool_action_type_int \c_datatool_datetime_int
    },
  type / date .code:n =
    {
      \int_set_eq:NN \l__datatool_action_type_int \c_datatool_date_int
    },
  type / time .code:n =
    {
      \int_set_eq:NN \l__datatool_action_type_int \c_datatool_time_int
    },
  datum .code:n =
   {
     \tl_if_eq:nnTF { #1 } { false }
      {
        \bool_set_false:N \l__datatool_action_datum_bool
      }
      {
        \bool_set_true:N \l__datatool_action_datum_bool
        \tl_if_eq:nnF { #1 } { true }
        {
           \keys_set:nn { datatool/action/datum } { #1 }
        }
      }
   },
  datum .default:n = true ,
}
\int_new:N \l__datatool_action_datum_round_int
\int_set:Nn \l__datatool_action_datum_round_int { -1 }
\bool_new:N \l__datatool_action_datum_locale_decimal_bool
\bool_set_true:N \l__datatool_action_datum_locale_decimal_bool
\bool_new:N \l__datatool_action_datum_locale_integer_bool
\tl_new:N \l__datatool_action_datum_currency_tl
\tl_set:Nn \l__datatool_action_datum_currency_tl
 {  \l__datatool_item_currency_tl }
\keys_define:nn { datatool/action/datum }
{
  round .code:n =
   {
     \tl_if_eq:nnTF { #1 } { false }
      {
        \int_set:Nn \l__datatool_action_datum_round_int { -1 }
      }
      {
        \int_set:Nn \l__datatool_action_datum_round_int { #1 }
      }
   } ,
  round .default:n = 0 ,
  locale-decimal .bool_set:N =
    \l__datatool_action_datum_locale_decimal_bool ,
  locale-integer .bool_set:N =
    \l__datatool_action_datum_locale_integer_bool ,
  currency .choice: ,
  currency / false .code:n =
   {
     \tl_clear:N \l__datatool_action_datum_currency_tl
   } ,
  currency / match .code:n =
   {
     \tl_set:Nn
       \l__datatool_action_datum_currency_tl
       { \l__datatool_item_currency_tl }
   } ,
  currency / default .code:n =
   {
     \tl_set:Nn
       \l__datatool_action_datum_currency_tl
       { \@dtl@currency }
   } ,
  currency / unknown .code:n =
   {
     \tl_set:Nn
       \l__datatool_action_datum_currency_tl
       { #1 }
   } ,
  currency .default:n = default ,
}
\tl_new:N \l__datatool_action_return_tl
\tl_set_eq:NN \l__datatool_action_return_tl \dtlnovalue
\tl_new:N \l__datatool_action_secondary_tl
\prop_new:N \l__datatool_action_return_prop
\cs_new:Nn \__datatool_put_return_action_string:nn
{
  \tl_if_head_eq_meaning:nNTF
    { #2 } \__datatool_datum:w
  {
    \prop_put:Nnx \l__datatool_action_return_prop
     { #1 } { #2 }
  }
  {
    \tl_if_head_eq_meaning:nNTF
      { #2 }  \__datatool_datum:nnnn
    {
       \prop_put:Nnn \l__datatool_action_return_prop
        { #1 } { #2 }
    }
    {
       \bool_if:NTF \l__datatool_action_datum_bool
        {
          \prop_put:Nnn \l__datatool_action_return_prop
           { #1 }
           {
             \__datatool_datum:nnnn
               { #2 } { } { } { \c_datatool_string_int }
           }
        }
        {
          \prop_put:Nnn \l__datatool_action_return_prop
           { #1 } { #2 }
        }
     }
   }
}
\cs_generate_variant:Nn \__datatool_put_return_action_string:nn
 { nV, nv, nx, xV }
\cs_new:Nn \__datatool_put_return_action_int:nn
{
  \bool_if:NTF \l__datatool_action_datum_bool
   {
     \bool_if:NTF \l__datatool_action_datum_locale_integer_bool
      {
        \DTLdecimaltolocale { #2 }
          \l__datatool_action_secondary_tl
        \prop_put:Nnx \l__datatool_action_return_prop
         { #1 }
         {
           \exp_not:N \__datatool_datum:nnnn
             { \l__datatool_action_secondary_tl }
             { #2 }
             { }
             { \exp_not:N \c_datatool_integer_int }
         }
      }
      {
        \tl_if_head_eq_meaning:nNTF
          { #2 } \__datatool_datum:w
        {
          \prop_put:Nnx \l__datatool_action_return_prop
           { #1 } { #2 }
        }
        {
          \tl_if_head_eq_meaning:nNTF
            { #2 } \__datatool_datum:nnnn
           {
             \prop_put:Nnn \l__datatool_action_return_prop
              { #1 } { #2 }
           }
           {
             \prop_put:Nnn \l__datatool_action_return_prop
              { #1 }
              {
                \__datatool_datum:nnnn
                  { #2 } { #2 } { } { \c_datatool_integer_int }
              }
           }
         }
      }
   }
   {
     \prop_put:Nnn \l__datatool_action_return_prop
      { #1 } { #2 }
   }
}
\cs_generate_variant:Nn \__datatool_put_return_action_int:nn
 { nV, nv, nx }
\cs_new:Nn \__datatool_put_return_action_decimal:nn
{
  \tl_set:Nn \l__datatool_action_secondary_tl { #2 }
  \__datatool_rm_weird_datum:N
    \l__datatool_action_secondary_tl
  \exp_args:NV \tl_if_head_eq_meaning:nNTF
    \l__datatool_action_secondary_tl
    \__datatool_datum:nnnn
  {
    \tl_set:Nx \l__datatool_action_secondary_tl
     { \DTLdatumvalue { \l__datatool_action_secondary_tl } }
  }
  \bool_if:NTF \l__datatool_action_datum_bool
   {
     \int_compare:nNnT
       { \l__datatool_action_datum_round_int } > { -1 }
     {
       \dtlround
         \l__datatool_action_secondary_tl
         \l__datatool_action_secondary_tl
        { \int_use:N \l__datatool_action_datum_round_int }
     }
     \bool_if:NT \l__datatool_action_datum_locale_decimal_bool
      {
        \exp_args:NV \DTLdecimaltolocale
         \l__datatool_action_secondary_tl
         \l__datatool_action_secondary_tl
      }
     \tl_if_empty:NF \l__datatool_action_datum_currency_tl
      {
        \tl_if_eq:NnTF
         \l__datatool_action_datum_currency_tl
         { \l__datatool_item_currency_tl }
        {
          \tl_if_empty:NF \l__datatool_item_currency_tl
           {
            \tl_set:Nx \l__datatool_action_secondary_tl
             {
               \exp_not:N \DTLfmtcurrency
               { \exp_not:V \l__datatool_item_currency_tl }
               { \exp_not:V \l__datatool_action_secondary_tl }
             }
           }
        }
        {
          \tl_if_eq:NnTF
           \l__datatool_action_datum_currency_tl
           { \@dtl@currency }
          {
            \tl_set:Nx \l__datatool_action_secondary_tl
             {
               \exp_not:N \DTLfmtcurrency
               { \exp_not:V \@dtl@currency }
               { \exp_not:V \l__datatool_action_secondary_tl }
             }
          }
          {
            \tl_set:Nx \l__datatool_action_secondary_tl
             {
               \exp_not:N \DTLfmtcurrency
               { \exp_not:V \l__datatool_action_datum_currency_tl }
               { \exp_not:V \l__datatool_action_secondary_tl }
             }
          }
        }
      }
     \prop_put:Nnx \l__datatool_action_return_prop
      { #1 }
      {
        \exp_not:N \__datatool_datum:nnnn
          { \exp_not:V \l__datatool_action_secondary_tl }
          { #2 } { }
          { \exp_not:N \c_datatool_decimal_int }
      }
   }
   {
     \prop_put:NnV \l__datatool_action_return_prop
      { #1 } \l__datatool_action_secondary_tl
   }
}
\cs_generate_variant:Nn \__datatool_put_return_action_decimal:nn
 { nV, nv, nx }
\cs_new:Nn \__datatool_put_return_action_parse:nn
{
  \bool_if:NTF \l__datatool_action_datum_bool
   {
     \__datatool_parse:Nn
       \l__datatool_action_secondary_tl { #2 }
   }
   {
     \tl_set:Nn \l__datatool_action_secondary_tl { #2 }
     \__datatool_rm_weird_datum:N
       \l__datatool_action_secondary_tl
   }
  \prop_put:NnV \l__datatool_action_return_prop
    { #1 }  \l__datatool_action_secondary_tl
}
\cs_generate_variant:Nn \__datatool_put_return_action_parse:nn
 { VV, xV }
\cs_new:Nn \__datatool_set_return_from_row:nn
{
  \int_step_inline:nn
     { \DTLcolumncount { #2 } }
   {
     \__datatool_get_entry_from_row:Nnn \l__datatool_item_value_tl
      { ##1 } { #1 }
     \datatool_if_null:NF \l__datatool_item_value_tl
      {
        \@dtl@getkeyforcolumn
          \l__datatool_item_key_tl
          { #2 }
          { ##1 }
        \__datatool_put_return_action_parse:VV
          \l__datatool_item_key_tl
          \l__datatool_item_value_tl
      }
   }
}
\cs_generate_variant:Nn \__datatool_set_return_from_row:nn
 { Vn }
\cs_new:Nn \__datatool_add_action_option:n
{
  \seq_if_in:NnF
    \l__datatool_action_tmp_options_seq { #1 }
   {
     \seq_put_right:Nn
       \l__datatool_action_tmp_options_seq { #1 }
   }
}
\cs_new:Nn \__datatool_if_action_option:nTF
{
  \seq_if_in:NnTF
   \l__datatool_action_tmp_options_seq { #1 }
   { #2 } { #3 }
}
\cs_new:Nn \__datatool_if_action_option:nT
{
  \seq_if_in:NnT
   \l__datatool_action_tmp_options_seq { #1 }
   { #2 }
}
\newcommand{\DTLifaction}[3]{
 \tl_if_empty:nTF { #1 }
  {
    \tl_if_eq:NNTF \l__datatool_action_return_tl \dtlnovalue
     { #3 } { #2 }
  }
  {
    \prop_if_in:NnTF \l__datatool_action_return_prop { #1 }
     { #2 } { #3 }
  }
}
\NewDocumentCommand \DTLget { O{} m }
{
  \tl_if_blank:nTF { #1 }
  {
    \tl_set_eq:NN #2 \l__datatool_action_return_tl
  }
  {
    \__datatool_action_get:nn { #2 } { #1 }
  }
}
\cs_new:Nn \__datatool_action_get:nn
{
  \tl_if_single:nTF { #1 }
  {
    \prop_get:NnN \l__datatool_action_return_prop { #2 }
      #1
    \quark_if_no_value:NT #1
     {
       \tl_set_eq:NN #1 \dtlnovalue
     }
  }
  {
    \PackageError { datatool }
    {
      Control ~ sequence ~ expected ~ (to ~ store ~ return ~
      value): ~ found ~ \tl_to_str:n { #1 }
    }
    {
      To ~ fetch ~ an ~ action ~ return ~ value ~ you ~ need ~
      to ~ provide ~ a ~ command ~ to ~ be ~ defined ~ to ~
      the ~ returned ~ value
    }
  }
}
\cs_new:Nn \__datatool_cskey_missing_val:n
{
  \PackageError { datatool}
  {
     Invalid ~ cs=key ~ assignment ~ syntax ~ in ~
     `\tl_to_str:n { #1 }' : ~
     missing ~ key ~ or ~ property ~ name
  }
  {
    Column ~ or ~ property ~ assignment ~ lists ~ need ~ to ~
    have ~ each ~ element ~ in ~ the ~ list ~ in ~ the ~
    form ~ <cs>=<label> ~ where ~ <cs> ~ is ~ a ~ control ~
    sequence ~ (placeholder ~ command) ~ and ~ <label> ~
    is ~ the ~ label ~ identifying ~ the ~ required ~ column ~ or ~ property
  }
}
\newcommand* \DTLuse [ 1 ]
{
  \tl_if_empty:nTF { #1 }
  { \l__datatool_action_return_tl }
  { \@dtl@useresult { #1 } }
}
\newrobustcmd* \@dtl@useresult [ 1 ]
{
  \tl_if_blank:nTF { #1 }
  {
    \l__datatool_action_return_tl
  }
  {
    \prop_get:NnN \l__datatool_action_return_prop { #1 }
      \l__datatool_tmpb_tl
    \quark_if_no_value:NTF \l__datatool_tmpb_tl
     {
       \dtlnovalue
     }
     {
       \l__datatool_tmpb_tl
     }
  }
}
\cs_new:Nn \__datatool_action_noop:Nn
{
  \PackageError {datatool}
  {
    Action ~ `#2' ~ may ~ only ~ be ~ used ~ within ~ \token_to_str:N #1
  }
  {}
}
\cs_new:Nn \__datatool_action_error:nnn
{
  \PackageError { #1 }
   {
     Action ~ ` \l__datatool_action_tl ' : ~ #2
   }
   { #3 }
}
\cs_new:Nn \__datatool_action_error:nn
{
  \__datatool_action_error:nnn { datatool } { #1 } { #2 }
}
\cs_new:Nn \__datatool_action_error:n
{
  \__datatool_action_error:nn { #1 } { }
}
\cs_new:Nn \__datatool_require_database:TF
{
  \DTLifdbexists { \l__datatool_action_name_tl }
   {
     #1
   }
   {
     \__datatool_action_error:nn
      {
        Database ~ `\l__datatool_action_name_tl' ~ doesn't ~ exist
      }
      {
        Check ~ you ~ have ~ spelt ~ the ~ database ~ name ~
        correctly ~ in ~
        \token_to_str:N \DTLaction [name={ \l__datatool_action_name_tl }]
        { \l__datatool_action_tl } ~ or ~ check ~ the ~ default-name ~
        option ~ in ~ \token_to_str:N \DTLsetup
      }
      #2
   }
}
\cs_new:Nn \__datatool_require_database:T
{
  \__datatool_require_database:TF { #1 } { }
}
\cs_new:Nn \__datatool_requires_dbname:T
{
  \tl_if_empty:NTF \dtldbname
  {
    \__datatool_action_error:nn
     {
       no ~ current ~ database ~ selected ~
       or ~ \token_to_str:N \dtldbname \c_space_tl ~
       has ~ unexpectedly ~ been ~ cleared
     }
     {
       The ~ action ~ `\l__datatool_action_tl' ~
       is ~ for ~ use ~ within ~ certain ~ types ~
       of ~ loops ~ or ~ after ~ a ~ row ~ has ~
       been ~ identified ~ as ~ the ~ current ~ row ~
       or ~ other ~ type ~ of ~ command ~ that ~ sets ~
       the ~ placeholder ~ \token_to_str:N \dtldbname
     }
  }
  {
    \tl_if_eq:NNF
       \l__datatool_action_name_tl
       \l__datatool_default_dbname_tl
     {
       \tl_if_eq:NNF
         \l__datatool_action_name_tl
         \dtldbname
       {
         \PackageWarning { datatool }
         {
           Ignoring ~ name = { \l__datatool_action_name_tl } ~
           for ~ action ~ ` \l__datatool_action_tl '
         }
       }
     }
    \tl_set_eq:NN
      \l__datatool_action_name_tl
      \dtldbname
    \DTLifdbexists { \dtldbname }
     { #1 }
     {
       \__datatool_action_error:nn
        {
          No ~ current ~ row ~ selected ~ or ~
          \token_to_str:N \dtldbname \c_space_tl ~
          has ~ been ~ changed
        }
        { }
     }
  }
}
\cs_new:Nn \__datatool_get_placeholder_assign:
{
  \seq_clear:N \l__datatool_action_tmp_data_seq
  \keyval_parse:nnV
   { \__datatool_cskey_missing_val:n }
   { \__datatool_action_placeholder_cskey:nn }
   \l__datatool_action_assign_clist
}
\cs_new:Nn \__datatool_action_placeholder_cskey:nn
{
  \tl_if_single_token:nTF { #1 }
   {
     \tl_set:Nn #1 { \c_datatool_nullvalue_tl }
     \tl_if_exist:cTF { dtl@ci@ \l__datatool_action_name_tl @ #2 }
      {
        \__datatool_get_col_type:vv
          { dtlkeys@  \l__datatool_action_name_tl }
          { dtl@ci@ \l__datatool_action_name_tl @ #2 }
        \seq_put_right:Ne \l__datatool_action_tmp_data_seq
         {
           { \exp_not:N #1 } { #2 }
           { \tl_use:c { dtl@ci@ \l__datatool_action_name_tl @ #2 } }
           { \int_use:N \l__datatool_item_type_int }
         }
      }
      {
        \__datatool_action_error:nn
         {
           Unknown ~ column ~ ` #2 '
         }
         {
           Database ~ ` \l__datatool_action_name_tl ' ~ does ~ not ~
           have ~ a ~ column ~ labelled ~ ` #2 '
         }
      }
   }
   {
     \__datatool_action_error:nn
      {
        Single ~ placeholder ~ command ~ required ~ in ~ assignment, ~ found ~
        ` \tl_to_str:n { #1 } '
      }
      {
        The ~ `assign' ~ option ~ requires ~ a ~ list ~ with ~
        elements ~ in ~ the ~ form ~ <tl-var>=<column-key>
      }
   }
}
\cs_new:Nn \__datatool_do_action_assignments:n
{
  \seq_map_inline:Nn \l__datatool_action_tmp_data_seq
   {
     \__datatool_do_action_row_assign:nNnnn { #1 } ##1
   }
}
\cs_new:Nn \__datatool_do_action_row_assign:nNnnn
{
  \__datatool_get_entry_from_row:Nnn #2 { #4 } { #1 }
  \tl_if_eq:NnT #2 { \c_datatool_nullvalue_tl }
   {
     \datatool_if_numeric_datum_type:nTF { #5 }
      {
        \tl_set_eq:NN #2 \DTLnumbernull
      }
      {
        \tl_set_eq:NN #2 \DTLstringnull
      }
   }
}
\cs_new:Nn \__datatool_forbid_key_and_column:n
{
 \int_if_zero:nF { \l__datatool_action_column_int }
  {
    \__datatool_action_error:nn
     {
       `column' ~ not ~ permitted
     }
     { #1 }
  }
 \tl_if_empty:NF \l__datatool_action_key_tl
  {
    \__datatool_action_error:nn
     {
       `key' ~ not ~ permitted
     }
     { #1 }
  }
}
\cs_new:Nn \__datatool_require_value:TF
{
  \quark_if_no_value:NTF \l__datatool_action_value_tl
   { #2 }
   { #1 }
}
\cs_new:Nn \__datatool_require_value:T
{
  \__datatool_require_value:TF
   { #1 }
   {
     \__datatool_action_error:nn
      { missing ~ value }
      {
        You ~ need ~ to ~ set ~ `value' ~ in ~ the ~ optional ~ argument ~
        of ~ \token_to_str:N \DTLaction[...]
        { \l__datatool_action_tl }. ~
        If ~ an ~ empty ~ value ~ is ~ required, ~ do ~
        value={}
      }
   }
}
\cs_new:Nn \__datatool_optional_row:NnTF
{
  \int_if_zero:nTF { #1 }
   { #2 }
   {
    \bool_lazy_or:nnTF
       {
        \int_compare_p:nNn { #1 } < { \c_zero_int }
       }
       {
        \int_compare_p:nNn
         { #1 }
          >
         { \DTLrowcount { \l__datatool_action_name_tl } }
       }
     {
      \__datatool_action_error:nn
       {
         Invalid ~ row ~ index ~ \int_use:N #1
       }
       {
         Database ~ `\l__datatool_action_name_tl' ~ row ~ index ~
         must ~ be ~ in ~ the ~ range ~
         [ 1 , ~  \DTLrowcount { \l__datatool_action_name_tl } ]
       }
       #4
     }
     { #3 }
   }
}
\cs_new:Nn \__datatool_optional_row:NTF
{
  \__datatool_optional_row:NnTF #1 { #3 } { #2 } { #3 }
}
\cs_new:Nn \__datatool_optional_row:NF
{
  \__datatool_optional_row:NTF #1 { } { #2 }
}
\cs_new:Nn \__datatool_optional_key_xor_column:nNnNnTF
{
  \int_compare:nNnTF
    { #4 }
     <
    { \c_zero_int }
  {
   \__datatool_action_error:nn
    {
      Negative ~ `#3' ~ value ~
      \int_use:N #4 \c_space_tl
      not ~ permitted
    }
    {
      You ~ need ~ to ~ correct ~ the ~ value ~ in ~ the ~
      `#3' ~ setting ~ in ~
      \token_to_str:N \DTLaction
      [#3=\int_use:N #4 \c_space_tl,...]
      { \l__datatool_action_tl } ~
      (or ~ use ~ `#1' ~ instead)
    }
   \int_zero:N #4
   #7
  }
  {
    \tl_if_empty:NTF #2
    {
       \int_if_zero:nTF #4
        {
         #5
        }
        {
          #6
        }
      }
    {
       \int_if_zero:nTF #4
        {
          #6
        }
        {
         \__datatool_action_error:nn
          {
            can't ~ have ~ both ~ `#1' ~ and ~ `#3' ~ set
          }
          {
           either ~ have ~ `#1' ~ or `#3' ~ in ~ the ~ optional ~
           argument ~ of ~ \token_to_str:N \DTLaction [...]
           { \l__datatool_action_tl } ~ but ~ not ~ both
          }
          #7
        }
    }
  }
}
\cs_new:Nn \__datatool_optional_key_xor_column:nTF
{
  \__datatool_optional_key_xor_column:nNnNnTF
   { key } \l__datatool_action_key_tl
   { column } \l__datatool_action_column_int
   { #1 } { #2 } { #3 }
}
\cs_new:Nn \__datatool_optional_key_ii_xor_column_ii:nTF
{
  \__datatool_optional_key_xor_column:nNnNnTF
   { key2 } \l__datatool_action_key_ii_tl
   { column2 } \l__datatool_action_column_ii_int
   { #1 } { #2 } { #3 }
}
\cs_new:Nn \__datatool_optional_key_xor_column_get_column:nNnNnnTF
{
  \__datatool_optional_key_xor_column:nNnNnTF
   { #1 } { #2 } { #3 } { #4 }
   {
    #5
   }
   {
     \int_if_zero:nTF #4
      {
        \tl_if_exist:cTF
           {
             dtl @ ci
             @ \l__datatool_action_name_tl
             @ #2
           }
         {
           \exp_args:NNv \int_set:Nn
             #4
            {
              dtl @ ci
              @ \l__datatool_action_name_tl
              @ #2
            }
           #7
         }
         {
           #6
         }
      }
      {
        #7
      }
   }
   {
     #8
   }
}
\cs_new:Nn \__datatool_optional_key_xor_column_get_column:nnTF
{
  \__datatool_optional_key_xor_column_get_column:nNnNnnTF
   { key } \l__datatool_action_key_tl
   { column } \l__datatool_action_column_int
   { #1 } { #2 } { #3 } { #4 }
}
\cs_new:Nn \__datatool_optional_key_xor_column:TF
{
  \__datatool_optional_key_xor_column:nTF { #1 } { #1 } { #2 }
}
\cs_new:Nn \__datatool_optional_key_ii_xor_column_ii_get_column:nnTF
{
  \__datatool_optional_key_xor_column_get_column:nNnNnnTF
   { key2 } \l__datatool_action_key_ii_tl
   { column2 } \l__datatool_action_column_ii_int
   { #1 } { #2 } { #3 } { #4 }
}
\cs_new:Nn \__datatool_optional_key_ii_xor_column_ii:TF
{
  \__datatool_optional_key_ii_xor_column_ii:nTF { #1 } { #1 } { #2 }
}
\cs_new:Nn \__datatool_optional_key_xor_column_get_column:nNnNnTF
{
  \__datatool_optional_key_xor_column_get_column:nNnNnnTF
   { #1 } { #2 } { #3 } { #4 }
   { #5 }
   {
     \__datatool_action_error:nn
      {
        no ~ column ~ found ~ with ~ key ~
        ` #2 ' ~
        in ~ database ~ ` \l__datatool_action_name_tl '
      }
      {
        Check ~ that ~ you ~ have ~ correctly ~ spelt ~
        the ~ column ~ key  ~ in ~ the ~ optional ~
        argument ~ of ~ \token_to_str:N \DTLaction [...]
        { \l__datatool_action_tl } ~ and ~ that ~
        the ~ database ~ name ~ is ~ correct
      }
     #7
   }
   {
     \int_compare:nNnTF
      { #4 }
       >
      {
        \int_use:c
          { dtlcols@ \l__datatool_action_name_tl }
      }
     {
       \__datatool_action_error:nn
        {
          `#3' ~ value ~
          \int_use:N #4 \c_space_tl ~
          out ~ of ~ range ~ for ~ database ~
         ` \l__datatool_action_name_tl '
        }
        {
          The ~ database ~ ` \l__datatool_action_name_tl ' ~
          only ~ has ~
          \int_use:c { dtlcols@ \l__datatool_action_name_tl }
          \c_space_tl ~ column(s)
        }
       #7
     }
     {
       #6
     }
   }
   { #7 }
}
\cs_new:Nn \__datatool_optional_key_xor_column_get_column:nTF
{
  \__datatool_optional_key_xor_column_get_column:nNnNnTF
   { key } \l__datatool_action_key_tl
   { column } \l__datatool_action_column_int
   { #1 } { #2 } { #3 }
}
\cs_new:Nn \__datatool_optional_key_ii_xor_column_ii_get_column:nTF
{
  \__datatool_optional_key_xor_column_get_column:nNnNnTF
   { key2 } \l__datatool_action_key_ii_tl
   { column2 } \l__datatool_action_column_ii_int
   { #1 } { #2 } { #3 }
}
\cs_new:Nn
 \__datatool_optional_key_xor_column_get_key:nNnNnTF
{
  \__datatool_optional_key_xor_column:nNnNnTF
   { #1 } { #2 } { #3 } { #4 }
   {
    #5
   }
   {
     \tl_if_empty:NTF #2
      {
       \int_compare:nNnTF
        { #4 }
         >
        { \DTLcolumncount { \l__datatool_action_name_tl } }
        {
          \__datatool_action_error:nn
           {
             `#3' ~ index ~
             \int_use:N #4 \c_space_tl ~
             out ~ of ~ range ~ for ~ database ~
             ` \l__datatool_action_name_tl '
           }
           {
             The ~ database ~ ` \l__datatool_action_name_tl ' ~
             only ~ has ~
             \int_use:c { dtlcols@ \l__datatool_action_name_tl }
             \c_space_tl ~ column(s)
           }
           #7
        }
        {
          \__datatool_get_col_key:vV
            { dtlkeys @ \l__datatool_action_name_tl }
            \l__datatool_action_column_int
          \quark_if_no_value:NTF \l__datatool_item_key_tl
          {
            \__datatool_action_error:nn
             {
               No ~ key ~ can ~ be ~ found ~ for ~
               column ~ index ~
               \int_use:N #4 \c_space_tl ~ in ~
               ` \l__datatool_action_name_tl '
             }
             {
               Something ~ seems ~ to ~ have ~ gone ~
               wrong ~ with ~ the ~ column ~ metadata
             }
             #7
          }
          {
            \tl_set_eq:NN #2 \l__datatool_item_key_tl
            #6
          }
        }
      }
      {
        #6
      }
   }
   {
     #7
   }
}
\cs_new:Nn
 \__datatool_optional_key_xor_column_get_key:nTF
{
  \__datatool_optional_key_xor_column_get_key:nNnNnTF
   { key } \l__datatool_action_key_tl
   { column } \l__datatool_action_column_int
   { #1 } { #2 } { #3 }
}
\cs_new:Nn
 \__datatool_optional_key_ii_xor_column_ii_get_key:nTF
{
  \__datatool_optional_key_xor_column_get_key:nNnNnTF
   { key2 } \l__datatool_action_key_ii_tl
   { column2 } \l__datatool_action_column_ii_int
   { #1 } { #2 } { #3 }
}
\cs_new:Nn
  \__datatool_requires_key_xor_column:nNnNTF
{
  \__datatool_optional_key_xor_column:nNnNnTF
   { #1 } #2 { #3 } #4
   {
     \__datatool_action_error:nn
      { missing ~ `#1' ~ or ~ `#3' }
      {
       You ~ need ~ to ~ set ~ `#1' ~ or `#3' ~ in ~ the ~ optional ~
       argument ~ of ~ \token_to_str:N \DTLaction [...]
       { \l__datatool_action_tl } ~ to ~
       a ~ non-empty ~ value ~ (#1) ~ or ~ positive ~ integer ~ (#3) ~
       identifying ~ the ~ required ~ column
      }
      #6
   }
   {
    #5
   }
   {
     #6
   }
}
\cs_new:Nn \__datatool_requires_key_xor_column:TF
{
  \__datatool_requires_key_xor_column:nNnNTF
   { key } \l__datatool_action_key_tl
   { column } \l__datatool_action_column_int
   { #1 } { #2 }
}
\cs_new:Nn \__datatool_requires_key_xor_column:T
{
  \__datatool_requires_key_xor_column:TF { #1 } { }
}
\cs_new:Nn \__datatool_requires_key_ii_xor_column_ii:TF
{
  \__datatool_requires_key_xor_column:nNnNTF
   { key2 } \l__datatool_action_key_ii_tl
   { column2 } \l__datatool_action_column_ii_int
   { #1 } { #2 }
}
\cs_new:Nn \__datatool_requires_key_ii_xor_column_ii:T
{
  \__datatool_requires_key_ii_xor_column_ii:TF { #1 } { }
}
\cs_new:Nn
   \__datatool_requires_key_xor_column_get_column:nNnNnTF
{
  \__datatool_requires_key_xor_column:TF
   {
     \int_if_zero:nTF #4
      {
        \tl_if_exist:cTF
           {
             dtl @ ci
             @ \l__datatool_action_name_tl
             @ #2
           }
         {
           \exp_args:NNv \int_set:Nn
             #4
            {
              dtl @ ci
              @ \l__datatool_action_name_tl
              @ #2
            }
           #6
         }
         {
           #5
         }
      }
      {
       #6
      }
   }
   {
     #7
   }
}
\cs_new:Nn
   \__datatool_requires_key_xor_column_get_column:nTF
{
  \__datatool_requires_key_xor_column_get_column:nNnNnTF
   { key } \l__datatool_action_key_tl
   { column } \l__datatool_action_column_int
   { #1 } { #2 } { #3 }
}
\cs_new:Nn
   \__datatool_requires_key_ii_xor_column_ii_get_column:nTF
{
  \__datatool_requires_key_ii_xor_column_ii_get_column:nNnNnTF
   { key2 } \l__datatool_action_key_ii_tl
   { column2 } \l__datatool_action_column_ii_int
   { #1 } { #2 } { #3 }
}
\cs_new:Nn
  \__datatool_requires_key_xor_column_get_column:nNnNTF
{
  \__datatool_requires_key_xor_column_get_column:nTF
   {
     \__datatool_action_error:nn
      {
        invalid ~ `#1' ~ value. ~
        No ~ column ~ found ~ with ~ key ~ `#2' ~
        in ~ database ~ ` \l__datatool_action_name_tl '
      }
      {
        Check ~ that ~ you ~ have ~ correctly ~ spelt ~
        the ~ value ~ of ~ `#1' ~ in ~ the ~ optional ~
        argument ~ of ~ \token_to_str:N \DTLaction [...]
        { \l__datatool_action_tl } ~ and ~ that ~
        the ~ database ~ name ~ is ~ correct
      }
     #6
   }
   {
     \int_compare:nNnTF
      { #4 }
       >
      {
        \int_use:c
          { dtlcols@ \l__datatool_action_name_tl }
      }
     {
       \__datatool_action_error:nn
        {
          invalid ~ `#3' ~ value.
          Column ~ index ~
          \int_use:N #4 \c_space_tl ~
          out ~ of ~ range ~ for ~ database ~
         ` \l__datatool_action_name_tl '
        }
        {
          The ~ database ~ ` \l__datatool_action_name_tl ' ~
          only ~ has ~
          \int_use:c { dtlcols@ \l__datatool_action_name_tl }
          \c_space_tl ~ columns.
          Check ~ that ~ you ~ have ~ correctly ~ spelt ~
          the ~ value ~ of ~ `#3' ~ in ~ the ~ optional ~
          argument ~ of ~ \token_to_str:N \DTLaction [...]
          { \l__datatool_action_tl } ~ and ~ that ~
          the ~ database ~ name ~ is ~ correct
        }
       #6
     }
     {
       #5
     }
   }
   { #6 }
}
\cs_new:Nn
   \__datatool_requires_key_xor_column_get_column:TF
{
   \__datatool_requires_key_xor_column_get_column:nNnNTF
   { key } \l__datatool_action_key_tl
   { column } \l__datatool_action_column_int
   { #1 } { #2 }
}
\cs_new:Nn \__datatool_requires_key_xor_column_get_column:T
{
  \__datatool_requires_key_xor_column_get_column:TF { #1 } { }
}
\cs_new:Nn
  \__datatool_requires_key_ii_xor_column_ii_get_column:TF
{
   \__datatool_requires_key_xor_column_get_column:nNnNTF
   { key2 } \l__datatool_action_key_ii_tl
   { column2 } \l__datatool_action_column_ii_int
   { #1 } { #2 }
}
\cs_new:Nn
  \__datatool_requires_key_ii_xor_column_ii_get_column:T
{
  \__datatool_requires_key_ii_xor_column_ii_get_column:TF
    { #1 } { }
}
\cs_new:Nn
  \__datatool_optional_ii_key_xor_column:nnTF
{
  \__datatool_optional_key_xor_column_get_column:nTF
   { #1 }
   {
     \__datatool_optional_key_ii_xor_column_ii_get_column:nTF
      { #2 } { #3 } { #4 }
   }
   { #4 }
}
\cs_new:Nn \__datatool_optional_ii_key_xor_column:TF
{
  \__datatool_optional_ii_key_xor_column:nnTF
  { } { } { #1 } { #2 }
}
\cs_new:Nn \__datatool_optional_ii_key_xor_column:
{
  \__datatool_optional_ii_key_xor_column:nnTF
  { } { } { } { }
}
\cs_new:Nn \__datatool_requires_i_optional_ii_key_xor_column:nTF
{
  \__datatool_requires_key_xor_column_get_column:TF
   {
     \__datatool_optional_key_ii_xor_column_ii_get_column:nTF
      { #1 } { #2 } { #3 }
   }
   { #3 }
}
\cs_new:Nn \__datatool_optional_columns:
{
  \clist_map_inline:Nn
   \l__datatool_action_columns_clist
  {
    \__datatool_optional_columns:w ##1 - \q_nil \q_stop
  }
  \clist_map_inline:Nn
   \l__datatool_action_keys_clist
  {
    \__datatool_optional_keys:w ##1 - \q_nil \q_stop
  }
  \tl_if_empty:NF \l__datatool_action_key_tl
   {
    \clist_if_empty:NTF \l__datatool_action_keys_clist
     {
       \PackageWarning { datatool }
       {
          Ignoring ~ key={\l__datatool_action_key_tl} ~
          for ~ action ~ `\l__datatool_action_tl'. ~
          Did ~ you ~ mean ~
          keys = { \l__datatool_action_key_tl } ?
       }
     }
     {
       \PackageWarning { datatool }
       {
          Ignoring ~ key={\l__datatool_action_key_tl} ~
          for ~ action ~ `\l__datatool_action_tl'. ~
          (Append ~ to ~ the ~ `keys' ~ list ~ if ~
           required.)
       }
     }
   }
  \tl_if_empty:NF \l__datatool_action_key_ii_tl
   {
    \clist_if_empty:NTF \l__datatool_action_keys_clist
     {
       \PackageWarning { datatool }
       {
          Ignoring ~ key2={\l__datatool_action_key_ii_tl} ~
          for ~ action ~ `\l__datatool_action_tl'. ~
          Did ~ you ~ mean ~
          keys = { \l__datatool_action_key_ii_tl } ?
       }
     }
     {
       \PackageWarning { datatool }
       {
          Ignoring ~ key2={\l__datatool_action_key_ii_tl} ~
          for ~ action ~ `\l__datatool_action_tl'. ~
          (Append ~ to ~ the ~ `keys' ~ list ~ if ~
           required.)
       }
     }
   }
  \int_if_zero:nF \l__datatool_action_column_int
   {
    \clist_if_empty:NTF \l__datatool_action_columns_clist
     {
       \PackageWarning { datatool }
       {
          Ignoring ~ column=
          { \int_use:N \l__datatool_action_column_int } ~
          for ~ action ~ `\l__datatool_action_tl'. ~
          Did ~ you ~ mean ~
          columns =
          { \int_use:N \l__datatool_action_column_int } ?
       }
     }
     {
       \PackageWarning { datatool }
       {
          Ignoring ~ column=
          { \int_use:N \l__datatool_action_column_int } ~
          for ~ action ~ `\l__datatool_action_tl'. ~
          (Append ~ to ~ the ~ `columns' ~ list ~ if ~
           required.)
       }
     }
   }
  \int_if_zero:nF \l__datatool_action_column_ii_int
   {
    \clist_if_empty:NTF \l__datatool_action_columns_clist
     {
       \PackageWarning { datatool }
       {
          Ignoring ~ column2=
          { \int_use:N \l__datatool_action_column_ii_int } ~
          for ~ action ~ `\l__datatool_action_tl'. ~
          Did ~ you ~ mean ~
          columns =
          { \int_use:N \l__datatool_action_column_ii_int } ?
       }
     }
     {
       \PackageWarning { datatool }
       {
          Ignoring ~ column2=
          { \int_use:N \l__datatool_action_column_ii_int } ~
          for ~ action ~ `\l__datatool_action_tl'. ~
          (Append ~ to ~ the ~ `columns' ~ list ~ if ~
           required.)
       }
     }
   }
}
\cs_new:Npn
  \__datatool_optional_columns:w #1 - #2 \q_stop
{
  \quark_if_nil:nTF { #2 }
   {
     \__datatool_if_column_index_valid:nT { #1 }
      {
        \__datatool_optional_columns_add:n { #1 }
      }
   }
   {
     \__datatool_optional_columns_add_range:w #1 - #2 \q_stop
   }
}
\cs_new:Npn
  \__datatool_optional_columns_add_range:w
   #1 - #2 - \q_nil \q_stop
{
  \tl_if_empty:nTF { #1 }
  {
    \tl_if_empty:nTF { #2 }
    {
      \__datatool_optional_columns_add_range:nn
       { 1 }
       { \DTLcolumncount { \l__datatool_action_name_tl } }
    }
    {
      \__datatool_if_column_index_valid:nT { #2 }
       { #2 }
       {
        \__datatool_optional_columns_add_range:nn
         { 1 } { #2 }
       }
    }
  }
  {
    \tl_if_empty:nTF { #2 }
    {
      \__datatool_if_column_index_valid:nT { #1 }
       {
        \__datatool_optional_columns_add_range:nn
         { #1 }
         { \DTLcolumncount { \l__datatool_action_name_tl } }
       }
    }
    {
      \__datatool_if_column_index_valid:nT { #1 }
       {
         \__datatool_if_column_index_valid:nT { #2 }
          {
            \int_compare:nNnTF
             { #2 } > { #1 }
             {
               \__datatool_action_error:nn
                {
                  Invalid ~ column ~ range ~ `#1-#2'. ~
                  Start ~ of ~ range ~ can't ~ be ~
                  greater ~ than ~ end ~ of ~ range ~
                }
                {
                  Check ~ the ~ syntax ~ of ~ the ~ `columns' ~
                  setting ~ in ~ the ~ optional ~
                  argument ~ of ~ \token_to_str:N \DTLaction [...]
                  { \l__datatool_action_tl }
                }
             }
             {
              \__datatool_optional_columns_add_range:nn
               { #1 } { #2 }
             }
          }
       }
    }
  }
}
\cs_new:Nn
  \__datatool_optional_columns_add_range:nn
{
  \int_step_inline:nnn
   { #1 } { #2 }
   {
     \__datatool_optional_columns_add:n { ##1 }
   }
}
\cs_generate_variant:Nn
  \__datatool_optional_columns_add_range:nn
  { vn, nv, vv }
\cs_new:Nn \__datatool_if_column_index_valid:nT
{
 \int_compare:nNnTF
  { #1 } < { \c_one_int }
 {
   \__datatool_action_error:nn
    {
      Invalid ~ column ~ index ~ `#1'
    }
    {
      Check ~ the ~ syntax ~ of ~ the ~ `columns' ~
      setting ~ in ~ the ~ optional ~
      argument ~ of ~ \token_to_str:N \DTLaction [...]
      { \l__datatool_action_tl }
    }
 }
 {
   \int_compare:nNnTF
     { #1 }
      >
     { \DTLcolumncount { \l__datatool_action_name_tl } }
    {
      \__datatool_action_error:nn
       {
        Invalid ~ column ~ index ~ `#1' ~
        (exceeds ~ column ~ count ~ of ~ database ~
        ` \l__datatool_action_name_tl ')
       }
     {
       Check ~ the ~ syntax ~ of ~ the ~ `columns' ~
       setting ~ in ~ the ~ optional ~
       argument ~ of ~ \token_to_str:N \DTLaction [...]
       { \l__datatool_action_tl } ~ and ~ that ~
       the ~ database ~ name ~ is ~ correct
     }
    }
    { #2 }
  }
}
\cs_new:Nn \__datatool_optional_columns_add:n
 {
   \seq_if_in:NnF
    \l__datatool_action_columns_seq { #1 }
   {
     \seq_put_right:Nn
      \l__datatool_action_columns_seq { #1 }
   }
 }
\cs_generate_variant:Nn
  \__datatool_optional_columns_add:n { x , v }
\cs_new:Npn
  \__datatool_optional_keys:w #1 - #2 \q_stop
{
  \quark_if_nil:nTF { #2 }
   {
     \__datatool_if_column_key_valid:nT { #1 }
      {
        \__datatool_optional_columns_add:v
         {
           dtl@ci
           @ \l__datatool_action_name_tl
           @ #1
         }
      }
   }
   {
     \__datatool_optional_keys_add_range:w #1 - #2 \q_stop
   }
}
\cs_new:Npn
  \__datatool_optional_keys_add_range:w
   #1 - #2 - \q_nil \q_stop
{
  \tl_if_empty:nTF { #1 }
  {
    \tl_if_empty:nTF { #2 }
    {
      \__datatool_optional_columns_add_range:nn
       { 1 }
       { \DTLcolumncount { \l__datatool_action_name_tl } }
    }
    {
      \__datatool_if_column_key_valid:nT { #2 }
       {
        \__datatool_optional_columns_add_range:nv
         { 1 }
         {
           dtl@ci
           @ \l__datatool_action_name_tl
           @ #2
         }
       }
    }
  }
  {
    \tl_if_empty:nTF { #2 }
    {
      \__datatool_if_column_key_valid:nT { #1 }
       {
        \__datatool_optional_columns_add_range:vn
         {
           dtl@ci
           @ \l__datatool_action_name_tl
           @ #1
         }
         { \DTLcolumncount { \l__datatool_action_name_tl } }
       }
    }
    {
      \__datatool_if_column_key_valid:nT { #1 }
       {
         \__datatool_if_column_key_valid:nT { #2 }
          {
            \int_compare:nNnTF
               {
                 \tl_use:c
                  {
                    dtl@ci
                    @ \l__datatool_action_name_tl
                    @ #2
                  }
               }
                 >
               {
                 \tl_use:c
                  {
                    dtl@ci
                    @ \l__datatool_action_name_tl
                    @ #1
                  }
               }
             {
              \__datatool_optional_columns_add_range:vv
               {
                 dtl@ci
                 @ \l__datatool_action_name_tl
                 @ #1
               }
               {
                 dtl@ci
                 @ \l__datatool_action_name_tl
                 @ #2
               }
             }
             {
              \__datatool_optional_columns_add_range:vv
               {
                 dtl@ci
                 @ \l__datatool_action_name_tl
                 @ #2
               }
               {
                 dtl@ci
                 @ \l__datatool_action_name_tl
                 @ #1
               }
             }
          }
       }
    }
  }
}
\cs_new:Nn \__datatool_if_column_key_valid:nT
{
  \tl_if_exist:cTF
   {
     dtl@ci
     @ \l__datatool_action_name_tl
     @ #1
   }
   { #2 }
 {
   \__datatool_action_error:nn
    {
      Unknown ~ column ~ key ~ `#1' ~
      in ~ database ~ `\l__datatool_action_name_tl'
    }
    {
      Check ~ the ~ `keys' ~
      setting ~ in ~ the ~ optional ~
      argument ~ of ~ \token_to_str:N \DTLaction [...]
      { \l__datatool_action_tl }
    }
 }
}
\cs_new:cn { __datatool_action_new: }
{
  \DTLifdbexists
    { \l__datatool_action_name_tl }
  {
    \__datatool_action_error:nn
     {
       Database ~ `\l__datatool_action_name_tl' ~
       already ~ exists
     }
     {
       Did ~ you ~ forget ~ to ~ specify ~ the ~
       database ~ name ~ in ~ the ~ options ~ for ~
       \token_to_str:N \DTLaction [ ... ]
       { \l__datatool_action_tl } ~
       or ~ change ~ the ~ default ~ name ~ in ~
       \token_to_str:N \DTLsetup?
     }
  }
  {
    \__datatool_new_db:n { \l__datatool_action_name_tl }
    \tl_set_eq:NN \l__datatool_action_return_tl \l__datatool_action_name_tl
    \__datatool_put_return_action_string:nV
      { name } \l__datatool_action_name_tl
  }
}
\cs_new:cn { __datatool_action_delete: }
{
  \__datatool_require_database:T
  {
    \int_if_exist:cT { dtlrows@ \l__datatool_action_name_tl }
    {
      \__datatool_put_return_action_int:nv
       { rows }
       { dtlrows@ \l__datatool_action_name_tl }
    }
    \int_if_exist:cT { dtlcols@ \l__datatool_action_name_tl }
    {
      \__datatool_put_return_action_int:nv
       { columns }
       { dtlcols@ \l__datatool_action_name_tl }
    }
    \DTLdeletedb { \l__datatool_action_name_tl }
    \tl_set_eq:NN
      \l__datatool_action_return_tl
      \l__datatool_action_name_tl
    \__datatool_put_return_action_string:nV
     { name } \l__datatool_action_name_tl
  }
}
\cs_new:cn { __datatool_action_clear: }
{
  \__datatool_require_database:T
  {
    \int_if_exist:cT { dtlrows@ \l__datatool_action_name_tl }
    {
      \__datatool_put_return_action_int:nv
       { rows }
       { dtlrows@ \l__datatool_action_name_tl }
    }
    \int_if_exist:cT { dtlcols@ \l__datatool_action_name_tl }
    {
      \__datatool_put_return_action_int:nv
       { columns }
       { dtlcols@ \l__datatool_action_name_tl }
    }
    \DTLcleardb { \l__datatool_action_name_tl }
    \tl_set_eq:NN
      \l__datatool_action_return_tl
      \l__datatool_action_name_tl
  }
  \__datatool_put_return_action_string:nV
   { name } \l__datatool_action_name_tl
}
\cs_new:cn { __datatool_action_new ~ row: }
{
 \__datatool_require_database:T
  {
    \tl_clear:N \l__datatool_action_value_tl
    \__datatool_forbid_key_and_column:n
     {
       The ~ action ~ ` \l__datatool_action_tl ' ~ should ~ have ~
       column-key = value ~ comma ~ separated ~ list ~ in ~ the ~
      `assign' ~ setting ~ to ~ add ~ entries ~ to ~ the ~
       new ~ row
     }
    \clist_if_empty:NF  \l__datatool_action_options_clist
    {
      \PackageWarning { datatool }
       {
         `options' ~ setting ~ ignored ~ in ~ action ~
          ` \l__datatool_action_tl ' ~
         (did ~ you ~ mean ~ `assign'?)
       }
    }
    \keyval_parse:NNV
      \__datatool_new_entry:n
      \__datatool_new_entry:nn
      \l__datatool_action_assign_clist
    \bool_if:NTF \l__datatool_db_global_bool
     {
      \int_gincr:c { dtlrows@ \l__datatool_action_name_tl }
     }
     {
      \int_incr:c { dtlrows@ \l__datatool_action_name_tl }
     }
    \__datatool_dtldb_put_right:Vx
      \l__datatool_action_name_tl
     {
       \__datatool_row_markup:vV
         { dtlrows@ \l__datatool_action_name_tl }
         \l__datatool_action_value_tl
     }
    \tl_set:Nv \l__datatool_action_return_tl
      { dtlrows@ \l__datatool_action_name_tl }
  }
  \__datatool_put_return_action_string:nV
   { name } \l__datatool_action_name_tl
  \__datatool_put_return_action_int:nx
   { row }
   { \DTLrowcount { \l__datatool_action_name_tl } }
}
\cs_new:Nn \__datatool_new_entry:nn
{
  \tl_set:Nx \l__datatool_item_key_tl { #1 }
  \__datatool_process_new_value:n { #2 }
  \tl_if_exist:cTF
   {
     dtl@ci@
     \l__datatool_action_name_tl
     @ \l__datatool_item_key_tl
   }
  {
    \exp_args:NNc \int_set:Nn \l__datatool_action_column_int
      {
         dtl@ci@
          \l__datatool_action_name_tl
        @ \l__datatool_item_key_tl
      }
    \__datatool_get_col_type:vV
      { dtlkeys @ \l__datatool_action_name_tl }
      \l__datatool_action_column_int
    \int_compare:nNnT
       { \@dtl@datatype } > { \l__datatool_item_type_int }
      {
        \int_set_eq:NN
          \l__datatool_item_type_int
          \@dtl@datatype
        \__datatool_get_props:NNNNNvV
          \l__datatool_item_key_tl
          \l__datatool_item_type_tl
          \l__datatool_item_head_tl
          \l__datatool_before_tl
          \l__datatool_after_tl
          { dtlkeys@ \l__datatool_action_name_tl }
          \l__datatool_action_column_int
        \__datatool_dtlkeys_set:Vx
          \l__datatool_action_name_tl
          {
            \exp_not:V \l__datatool_before_tl
            \__datatool_column_markup:VVVV
              \l__datatool_action_column_int
              \l__datatool_item_key_tl
              \l__datatool_item_type_int
              \l__datatool_item_head_tl
            \exp_not:V \l__datatool_after_tl
          }
      }
  }
  {
    \int_set:Nn \l__datatool_action_column_int
     { \DTLcolumncount { \l__datatool_action_name_tl } + 1 }
    \__datatool_dtlkeys_put_right:Vx
      \l__datatool_action_name_tl
     {
       \__datatool_column_markup:VVVV
         \l__datatool_action_column_int
         \l__datatool_item_key_tl
         \@dtl@datatype
         \l__datatool_item_key_tl
     }
    \bool_if:NTF \l__datatool_db_global_bool
     {
       \tl_gset:cV
         {
          dtl@ci@
          \l__datatool_action_name_tl
          @ \l__datatool_item_key_tl
         }
        \l__datatool_action_column_int
        \int_gset_eq:cN
          { dtlcols@ \l__datatool_action_name_tl }
         \l__datatool_action_column_int
     }
     {
       \tl_set:cV
         {
          dtl@ci@
          \l__datatool_action_name_tl
          @ \l__datatool_item_key_tl
         }
        \l__datatool_action_column_int
        \int_set_eq:cN
         { dtlcols@ \l__datatool_action_name_tl }
         \l__datatool_action_column_int
     }
  }
  \tl_put_right:Nx \l__datatool_action_value_tl
   {
      \__datatool_row_element_markup:VV
        \l__datatool_action_column_int
        \l__datatool_item_value_tl
   }
}
\cs_new:Nn \__datatool_new_entry:n
{
 \__datatool_action_error:nn
  {
    Invalid ~ option ~ setting ~ ` \tl_to_str:n { #1 } '
  }
  {
    The ~ action ~ ` \l__datatool_action_tl ' ~ should ~ have ~
    column-key = {value} ~ comma ~ separated ~ list ~ in ~ the ~
    `assign' ~ setting ~ to ~ add ~ entries ~ to ~ the ~ new ~
    row. ~ If ~ you ~ want ~ an ~
    empty ~ value ~ do ~ \token_to_str:N \DTLaction
     [ assign = { column-key = { ... }, ... } ] { \l__datatool_action_tl }
  }
}
\cs_new:cn { __datatool_action_new ~ entry: }
{
 \__datatool_require_database:T
 {
   \int_set_eq:NN \@dtl@datatype \c_datatool_unknown_int
   \__datatool_require_value:T
    {
      \__datatool_requires_key_xor_column:T
      {
        \tl_if_empty:NTF \l__datatool_action_key_tl
        {
          \int_set_eq:NN
            \dtlcolumnnum
            \l__datatool_action_column_int
          \__datatool_process_new_value:V
            \l__datatool_action_value_tl
          \__datatool_get_col_type_key:vV
            { dtlkeys @ \l__datatool_action_name_tl }
            \l__datatool_action_column_int
          \quark_if_no_value:NTF \l__datatool_item_key_tl
          {
            \tl_set:Nx \l__datatool_item_key_tl
              {
                \dtldefaultkey
                \int_use:N \l__datatool_action_column_int
              }
            \__datatool_dtlkeys_put_right:Vx
             \l__datatool_action_name_tl
              {
                \__datatool_column_markup:VVVV
                  \l__datatool_action_column_int
                  \l__datatool_item_key_tl
                  \@dtl@datatype
                  \l__datatool_item_key_tl
              }
            \bool_if:NTF \l__datatool_db_global_bool
            {
              \tl_gset:cV
                {
                   dtl@ci
                   @ \l__datatool_action_name_tl
                   @ \l__datatool_item_key_tl
                }
                \l__datatool_action_column_int
              \int_compare:nNnT
                 { \l__datatool_action_column_int }
                  >
                 { \int_use:c { dtlcols@ \l__datatool_action_name_tl } }
              {
                \int_gset_eq:cN
                  { dtlcols@ \l__datatool_action_name_tl }
                  \l__datatool_action_column_int
              }
            }
            {
              \tl_set:cV
                {
                  dtl@ci
                  @ \l__datatool_action_name_tl
                  @ \l__datatool_item_key_tl
                }
                \l__datatool_action_column_int
              \int_compare:nNnT
                 { \l__datatool_action_column_int }
                  >
                 {
                   \int_use:c
                     { dtlcols@ \l__datatool_action_name_tl }
                 }
              {
                \int_set_eq:cN
                  { dtlcols@ \l__datatool_action_name_tl }
                  \l__datatool_action_column_int
              }
            }
          }
          {
            \int_set_eq:NN \l__datatool_item_type_int
               \c_datatool_unknown_int
            \tl_if_empty:NTF \l__datatool_item_type_tl
            {
              \int_compare:nNnF
                { \@dtl@datatype } = { \c_datatool_unknown_int }
              {
                \int_set_eq:NN
                  \l__datatool_item_type_int
                  \@dtl@datatype
              }
            }
            {
              \int_compare:nNnT
               { \@dtl@datatype } > { \l__datatool_item_type_tl}
              {
                \int_set_eq:NN
                  \l__datatool_item_type_int
                  \@dtl@datatype
              }
            }
            \int_compare:nNnT
              { \l__datatool_item_type_int } > { \c_datatool_unknown_int }
            {
              \__datatool_get_props:NNNNNvV
                \l__datatool_item_key_tl
                \l__datatool_item_type_tl
                \l__datatool_item_head_tl
                \l__datatool_before_tl
                \l__datatool_after_tl
                { dtlkeys@ \l__datatool_action_name_tl }
                \dtlcolumnnum
              \__datatool_dtlkeys_set:Vx
                 \l__datatool_action_name_tl
                {
                  \exp_not:V \l__datatool_before_tl
                  \__datatool_column_markup:VVVV
                    \dtlcolumnnum
                    \l__datatool_item_key_tl
                    \l__datatool_item_type_int
                    \l__datatool_item_head_tl
                  \exp_not:V \l__datatool_after_tl
                }
            }
          }
          \@s@DTLnewdbentry \l__datatool_action_name_tl
        }
        {
          \int_zero:N \dtlcolumnnum
          \exp_args:NNVV \DTLnewdbentry
              \l__datatool_action_name_tl
              \l__datatool_action_key_tl
              \l__datatool_action_value_tl
          \int_set_eq:NN \l__datatool_action_column_int \dtlcolumnnum
        }
        \tl_set:NV \l__datatool_action_return_tl
           \l__datatool_action_column_int
       \__datatool_put_return_action_int:nV
        { column } \l__datatool_action_column_int
       \__datatool_put_return_action_string:nV
        { key } \l__datatool_action_key_tl
       \__datatool_put_return_action_int:nV
        { type } \@dtl@datatype
      }
    }
    \__datatool_put_return_action_int:nv
     { row }
     { dtlrows@ \l__datatool_action_name_tl }
  }
  \__datatool_put_return_action_string:nV
   { name } \l__datatool_action_name_tl
}
\cs_new:cn { __datatool_action_column ~ index: }
{
  \tl_if_empty:NTF \l__datatool_action_key_tl
  {
    \__datatool_action_error:nn
     { Missing ~ `key' }
     {
        \token_to_str:N \DTLaction [...]{ \l__datatool_action_tl } ~
        needs ~ the ~ `key' ~ setting ~ in ~ the ~ optional ~
        argument ~ to ~ identify ~ the ~ required ~ column
     }
  }
  {
    \__datatool_require_database:T
    {
      \exp_args:NVV \@sDTLifhaskey
        \l__datatool_action_name_tl
        \l__datatool_action_key_tl
      {
        \@sdtl@getcolumnindex
          { \l__datatool_action_return_tl }
          { \l__datatool_action_name_tl }
          { \l__datatool_action_key_tl }
        \__datatool_put_return_action_int:nV
         { column } \l__datatool_action_return_tl
      }
      { }
    }
  }
  \__datatool_put_return_action_string:nV
   { name } \l__datatool_action_name_tl
  \__datatool_put_return_action_string:nV
   { key } \l__datatool_action_key_tl
}
\cs_new:cn { __datatool_action_column ~ data: }
{
  \__datatool_require_database:T
   {
    \tl_set:Nn \l__datatool_item_head_tl
      { \q_no_value }
    \int_set_eq:NN
     \l__datatool_item_type_int
     \c_datatool_unknown_int
    \__datatool_requires_key_xor_column_get_column:T
     {
       \__datatool_get_col_data:vV
         { dtlkeys@ \l__datatool_action_name_tl }
         \l__datatool_action_column_int
       \quark_if_no_value:NF \l__datatool_item_key_tl
        {
          \__datatool_put_return_action_string:nV
           { key } \l__datatool_item_key_tl
          \tl_set_eq:NN
            \l__datatool_action_return_tl
            \l__datatool_item_key_tl
          \__datatool_put_return_action_string:nV
           { key } \l__datatool_item_key_tl
        }
       \quark_if_no_value:NF \l__datatool_item_head_tl
        {
          \__datatool_put_return_action_string:nV
          { header } \l__datatool_item_head_tl
        }
      \__datatool_put_return_action_int:nV
       { type } \l__datatool_item_type_int
      \int_if_zero:nF \l__datatool_action_column_int
       {
         \__datatool_put_return_action_int:nV
          { column } \l__datatool_action_column_int
       }
      }
    \__datatool_put_return_action_string:nV
     { name } \l__datatool_action_name_tl
  }
}
\cs_new:cn { __datatool_action_display: }
{
  \__datatool_require_database:T
   {
    \group_begin:
      \clist_if_empty:NF \l__datatool_action_options_clist
      {
        \keys_set_filter:nnVN
          { datatool/display } { longtable }
          \l__datatool_action_options_clist
          \l__datatool_action_tmpb_tl
        \tl_if_empty:NF \l__datatool_action_tmpb_tl
        {
          \PackageWarning { datatool }
          {
            Ignoring ~ unsupported ~
            \token_to_str:N \DTLaction
            { \l__datatool_action_tl} ~
            option(s): ~ \exp_not:o { \l__datatool_action_tmpb_tl }
          }
        }
      }
      \tl_set_eq:NN \dtldbname \l__datatool_action_name_tl
      \__datatool_display_db:
      \tl_set:Nx \l__datatool_action_tmpb_tl
        {
          \exp_not:N \group_end:
          \exp_not:N \__datatool_put_return_action_int:nn
            { columns }
            { \int_use:N \dtlcolumnnum }
          \exp_not:N \__datatool_put_return_action_int:nn
            { rows }
            { \int_use:N \dtlrownum }
        }
    \l__datatool_action_tmpb_tl
   }
  \__datatool_put_return_action_string:nV
   { name } \l__datatool_action_name_tl
}
\cs_new:cn { __datatool_action_display ~ long: }
{
  \__datatool_require_database:T
   {
    \group_begin:
      \clist_if_empty:NF \l__datatool_action_options_clist
      {
        \keys_set_filter:nnVN
          { datatool/display } { tabular }
          \l__datatool_action_options_clist
          \l__datatool_action_tmpb_tl
        \tl_if_empty:NF \l__datatool_action_tmpb_tl
        {
          \PackageWarning { datatool }
          {
            Ignoring ~ unsupported ~
            \token_to_str:N \DTLaction
            { \l__datatool_action_tl} ~
            option(s): ~ \exp_not:o { \l__datatool_action_tmpb_tl }
          }
        }
      }
      \tl_set_eq:NN \dtldbname \l__datatool_action_name_tl
      \__datatool_display_long_db:
      \tl_set:Nx \l__datatool_action_tmpb_tl
        {
          \exp_not:N \group_end:
          \exp_not:N \__datatool_put_return_action_int:nn
            { columns }
            { \int_use:N \dtlcolumnnum }
          \exp_not:N \__datatool_put_return_action_int:nn
            { rows }
            { \int_use:N \dtlrownum }
        }
    \l__datatool_action_tmpb_tl
   }
  \__datatool_put_return_action_string:nV
   { name } \l__datatool_action_name_tl
}
\cs_new:cn { __datatool_action_add ~ column: }
{
 \__datatool_require_database:T
  {
    \int_if_zero:nF { \l__datatool_action_column_int }
    {
     \__datatool_action_error:nn
      {
       `column' ~ setting ~ not ~ supported. ~ Ignoring
      }
      {
        You ~ need ~ to ~ use ~ `key' ~ not ~ `column' ~ to ~
        reference ~ a ~ column ~ with ~ action ~ ` \l__datatool_action_tl '
      }
    }
    \int_set:Nn \l__datatool_action_column_int
      { \DTLcolumncount \l__datatool_action_name_tl + 1 }
    \tl_if_empty:NT \l__datatool_action_key_tl
     {
       \tl_set:Nx \l__datatool_action_key_tl
        {
          \dtldefaultkey
          \int_use:N \l__datatool_action_column_int
        }
     }
    \quark_if_no_value:NTF \l__datatool_action_value_tl
     {
       \tl_set_eq:NN
         \l__datatool_action_value_tl
         \l__datatool_action_key_tl
     }
    \exp_args:NVV \@sDTLifhaskey
       \l__datatool_action_name_tl
       \l__datatool_action_key_tl
    {
     \__datatool_action_error:nn
      {
        Can't ~ add ~ new ~ column ~ ` \l__datatool_action_key_tl ' ~
        to ~ database ~ ` \l__datatool_action_name_tl ': ~
        column ~ with ~ that ~ key ~ already ~ exists
      }
      {
        Check ~ the ~ key ~ setting ~ in ~ the ~ optional ~
        argument ~ of ~ \token_to_str:N \DTLaction [...]
        { \l__datatool_action_tl }
      }
    }
    {
      \__datatool_add_column_with_header:VVVV
        \l__datatool_action_name_tl
        \l__datatool_action_key_tl
        \l__datatool_action_type_int
        \l__datatool_action_value_tl
       \tl_set:NV \l__datatool_action_return_tl
         \l__datatool_action_column_int
      \__datatool_put_return_action_int:nV
       { column } \l__datatool_action_column_int
    }
    \__datatool_put_return_action_string:nV
     { key } \l__datatool_action_key_tl
    \__datatool_put_return_action_int:nV
     { type } \l__datatool_action_type_int
    \quark_if_no_value:NF \l__datatool_action_value_tl
    {
      \__datatool_put_return_action_string:nV
       { header } \l__datatool_action_value_tl
    }
  }
 \__datatool_put_return_action_string:nV
   { name } \l__datatool_action_name_tl
}
\cs_new:cn { __datatool_action_aggregate: }
{
  \__datatool_require_database:T
  {
    \fp_set_eq:NN \l__datatool_min_fp \c_inf_fp
    \fp_set_eq:NN \l__datatool_min_ii_fp \c_inf_fp
    \fp_set_eq:NN \l__datatool_max_fp \c_minus_inf_fp
    \fp_set_eq:NN \l__datatool_max_ii_fp \c_minus_inf_fp
    \fp_zero:N \l__datatool_total_fp
    \fp_zero:N \l__datatool_total_ii_fp
    \tl_clear:N \l__datatool_item_currency_tl
    \tl_clear:N \l__datatool_item_currency_ii_tl
    \seq_clear:N \l__datatool_tmp_seq
    \clist_if_empty:NF \l__datatool_action_options_clist
    {
      \clist_map_inline:Nn
       \l__datatool_action_options_clist
      {
        \clist_if_in:nnTF
          { sum , mean , variance, sd , min , max } { ##1 }
         {
           \seq_put_right:Nn \l__datatool_tmp_seq { ##1 }
         }
         {
           \PackageWarning { datatool }
            {
              Unknown ~ aggregate ~ function ~ `##1' ~ in ~
              \token_to_str:N \DTLaction
               [ options={\l__datatool_action_options_clist}, ... ]
               {` \l__datatool_action_tl ' }
            }
         }
      }
    }
    \seq_if_in:NnT
      \l__datatool_tmp_seq { sd }
     {
       \seq_if_in:NnF
         \l__datatool_tmp_seq { variance }
        {
          \seq_put_right:Nn \l__datatool_tmp_seq { variance }
        }
     }
    \seq_if_in:NnT
      \l__datatool_tmp_seq { variance }
     {
       \seq_if_in:NnF
         \l__datatool_tmp_seq { mean }
        {
          \seq_put_right:Nn \l__datatool_tmp_seq { mean }
        }
     }
    \seq_if_in:NnT
      \l__datatool_tmp_seq { mean }
     {
       \seq_if_in:NnF
         \l__datatool_tmp_seq { sum }
        {
          \seq_put_right:Nn \l__datatool_tmp_seq { sum }
        }
     }
    \seq_clear:N \l__datatool_tmpa_seq
    \seq_clear:N \l__datatool_tmpb_seq
    \__datatool_optional_ii_key_xor_column:
    \DTLmapdata [ name = { \l__datatool_action_name_tl }, read-only ]
    {
      \exp_args:NNVV
         \dtl@getentryfromrow
         \l__datatool_item_value_tl
         \l__datatool_action_column_int
         \l__datatool_map_data_row_tl
      \datatool_if_null:NF \l__datatool_item_value_tl
       {
         \__datatool_parse:N \l__datatool_item_value_tl
          \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
          {
            \int_compare:nNnT
              { \@dtl@datatype } = { \c_datatool_currency_int }
            {
              \tl_if_empty:NT \l__datatool_item_currency_tl
               {
                 \tl_set_eq:NN
                   \l__datatool_item_currency_tl
                   \l__datatool_datum_currency_tl
               }
            }
            \fp_set:Nn \l__datatool_tmpa_fp
             { \DTLdatumvalue { \l__datatool_item_value_tl } }
            \seq_put_right:Nx \l__datatool_tmpa_seq
             { \fp_to_decimal:N \l__datatool_tmpa_fp }
            \seq_if_in:NnT \l__datatool_tmp_seq { sum }
            {
              \fp_add:Nn \l__datatool_total_fp
                \l__datatool_tmpa_fp
            }
            \seq_if_in:NnT \l__datatool_tmp_seq { min }
            {
              \fp_compare:nNnT
                { \l__datatool_min_fp }
                 >
                { \l__datatool_tmpa_fp }
              {
                \fp_set_eq:NN
                  \l__datatool_min_fp
                  \l__datatool_tmpa_fp
              }
            }
            \seq_if_in:NnT \l__datatool_tmp_seq { max }
            {
              \fp_compare:nNnT
                { \l__datatool_max_fp }
                 <
                { \l__datatool_tmpa_fp }
              {
                \fp_set_eq:NN
                  \l__datatool_max_fp
                  \l__datatool_tmpa_fp
              }
            }
          }
       }
      \exp_args:NNVV
         \dtl@getentryfromrow
         \l__datatool_item_value_tl
         \l__datatool_action_column_ii_int
         \l__datatool_map_data_row_tl
      \datatool_if_null:NF \l__datatool_item_value_tl
       {
         \__datatool_parse:N
           \l__datatool_item_value_tl
          \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
          {
            \int_compare:nNnT
              { \@dtl@datatype } = { \c_datatool_currency_int }
            {
              \tl_if_empty:NT \l__datatool_item_currency_ii_tl
               {
                 \tl_set_eq:NN
                   \l__datatool_item_currency_ii_tl
                   \l__datatool_datum_currency_tl
               }
            }
            \fp_set:Nn \l__datatool_tmpb_fp
             { \DTLdatumvalue { \l__datatool_item_value_tl } }
            \seq_put_right:Nx \l__datatool_tmpb_seq
             { \fp_to_decimal:N \l__datatool_tmpb_fp }
            \seq_if_in:NnT \l__datatool_tmp_seq { sum }
            {
              \fp_add:Nn \l__datatool_total_ii_fp
                \l__datatool_tmpb_fp
            }
            \seq_if_in:NnT \l__datatool_tmp_seq { min }
            {
              \fp_compare:nNnT
                { \l__datatool_min_ii_fp }
                 >
                { \l__datatool_tmpb_fp }
              {
                \fp_set_eq:NN
                  \l__datatool_min_ii_fp
                  \l__datatool_tmpb_fp
              }
            }
            \seq_if_in:NnT \l__datatool_tmp_seq { max }
            {
              \fp_compare:nNnT
                { \l__datatool_max_ii_fp }
                 <
                { \l__datatool_tmpb_fp }
              {
                \fp_set_eq:NN
                  \l__datatool_max_ii_fp
                  \l__datatool_tmpb_fp
              }
            }
          }
       }
    }
    \int_set:Nn
      \l__datatool_count_int
      { \seq_count:N \l__datatool_tmpa_seq }
    \tl_set:NV
      \l__datatool_action_return_tl
      \l__datatool_count_int
    \__datatool_put_return_action_int:nV
     { count } \l__datatool_count_int
    \__datatool_put_return_action_int:nV
     { column } \l__datatool_action_column_int
    \prop_put:NnV \l__datatool_action_return_prop
     { seq } \l__datatool_tmpa_seq
    \int_if_zero:nF { \l__datatool_count_int }
     {
      \seq_if_in:NnT \l__datatool_tmp_seq { min }
       {
         \fp_compare:nNnT
            { \l__datatool_min_fp }
            <
            { \c_inf_fp }
          {
            \__datatool_put_return_action_decimal:nx
             { min }
             { \fp_to_decimal:N \l__datatool_min_fp }
          }
       }
      \seq_if_in:NnT \l__datatool_tmp_seq { max }
       {
         \fp_compare:nNnT
            { \l__datatool_max_fp }
            >
            { \c_minus_inf_fp }
          {
            \__datatool_put_return_action_decimal:nx
             { max }
             { \fp_to_decimal:N \l__datatool_max_fp }
          }
       }
      \seq_if_in:NnT \l__datatool_tmp_seq { sum }
       {
         \__datatool_put_return_action_decimal:nx
          { sum }
          { \fp_to_decimal:N \l__datatool_total_fp }
         \seq_if_in:NnT \l__datatool_tmp_seq { mean }
         {
           \fp_set:Nn \l__datatool_mean_fp
            { \l__datatool_total_fp / \l__datatool_count_int }
           \__datatool_put_return_action_decimal:nx
            { mean }
            { \fp_to_decimal:N \l__datatool_mean_fp }
           \seq_if_in:NnT \l__datatool_tmp_seq { variance }
           {
            \fp_zero:N \l__datatool_tmpa_fp
            \seq_map_inline:Nn \l__datatool_tmpa_seq
             {
               \fp_set:Nn \l__datatool_tmpb_fp
                {
                  ##1 - \l__datatool_mean_fp
                }
               \fp_add:Nn \l__datatool_tmpa_fp
                {
                   \l__datatool_tmpb_fp * \l__datatool_tmpb_fp
                }
             }
            \fp_set:Nn \l__datatool_tmpa_fp
             { \l__datatool_tmpa_fp / \l__datatool_count_int }
            \__datatool_put_return_action_decimal:nx
             { variance }
             { \fp_to_decimal:N \l__datatool_tmpa_fp }
             \seq_if_in:NnT \l__datatool_tmp_seq { sd }
             {
               \fp_set:Nn \l__datatool_tmpa_fp
                { sqrt ( \l__datatool_tmpa_fp ) }
               \__datatool_put_return_action_decimal:nx
                { sd }
                { \fp_to_decimal:N \l__datatool_tmpa_fp }
             }
           }
         }
       }
     }
    \int_if_zero:nF { \l__datatool_action_column_ii_int }
     {
       \tl_if_empty:NF \l__datatool_item_currency_ii_tl
        {
          \tl_set_eq:NN
            \l__datatool_item_currency_tl
            \l__datatool_item_currency_ii_tl
        }
       \__datatool_put_return_action_int:nV
        { column2 } \l__datatool_action_column_ii_int
       \int_set:Nn
        \l__datatool_count_int
        { \seq_count:N \l__datatool_tmpb_seq }
       \__datatool_put_return_action_int:nV
        { count2 } \l__datatool_count_int
       \prop_put:NnV \l__datatool_action_return_prop
        { seq2 } \l__datatool_tmpb_seq
       \int_if_zero:nF { \l__datatool_count_int }
        {
         \seq_if_in:NnT \l__datatool_tmp_seq { min }
          {
            \fp_compare:nNnT
               { \l__datatool_min_ii_fp }
               <
               { \c_inf_fp }
             {
               \__datatool_put_return_action_decimal:nx
                { min2 }
                { \fp_to_decimal:N \l__datatool_min_ii_fp }
             }
          }
         \seq_if_in:NnT \l__datatool_tmp_seq { max }
          {
            \fp_compare:nNnT
               { \l__datatool_max_ii_fp }
               >
               { \c_minus_inf_fp }
             {
               \__datatool_put_return_action_decimal:nx
                { max2 }
                { \fp_to_decimal:N \l__datatool_max_ii_fp }
             }
          }
         \seq_if_in:NnT \l__datatool_tmp_seq { sum }
          {
            \__datatool_put_return_action_decimal:nx
             { sum2 }
             { \fp_to_decimal:N \l__datatool_total_ii_fp }
            \seq_if_in:NnT \l__datatool_tmp_seq { mean }
            {
              \fp_set:Nn \l__datatool_mean_fp
               { \l__datatool_total_fp / \l__datatool_count_int }
              \__datatool_put_return_action_decimal:nx
               { mean2 }
               { \fp_to_decimal:N \l__datatool_mean_fp }
              \seq_if_in:NnT \l__datatool_tmp_seq { variance }
              {
               \fp_zero:N \l__datatool_tmpa_fp
               \seq_map_inline:Nn \l__datatool_tmpb_seq
                {
                  \fp_set:Nn \l__datatool_tmpb_fp
                   {
                     ##1 - \l__datatool_mean_fp
                   }
                  \fp_add:Nn \l__datatool_tmpa_fp
                   {
                      \l__datatool_tmpb_fp * \l__datatool_tmpb_fp
                   }
                }
               \fp_set:Nn \l__datatool_tmpa_fp
                { \l__datatool_tmpa_fp / \l__datatool_count_int }
               \__datatool_put_return_action_decimal:nx
                { variance2 }
                { \fp_to_decimal:N \l__datatool_tmpa_fp }
                \seq_if_in:NnT \l__datatool_tmp_seq { sd }
                {
                  \fp_set:Nn \l__datatool_tmpa_fp
                   { sqrt ( \l__datatool_tmpa_fp ) }
                  \__datatool_put_return_action_decimal:nx
                   { sd2 }
                   { \fp_to_decimal:N \l__datatool_tmpa_fp }
                }
              }
            }
          }
        }
     }
  }
  \__datatool_put_return_action_string:nV
   { name } \l__datatool_action_name_tl
}
\cs_new:cn { __datatool_action_row ~ aggregate: }
{
  \__datatool_action_noop:Nn
    \DTLmapdata
    { row ~ aggregate: }
}
\cs_new:Nn \__datatool_action_row_aggregate_op:
{
  \exp_args:NV \__datatool_action_row_aggregate:n
   \l__datatool_map_data_row_tl
}
\cs_new:cn { __datatool_action_current ~ row ~ aggregate: }
{
  \exp_args:NV \__datatool_action_row_aggregate:n
    \dtlcurrentrow
}
\cs_new:Nn \__datatool_action_row_aggregate:n
{
 \__datatool_requires_dbname:T
 {
  \tl_if_empty:nT { #1 }
   {
     \PackageWarning { datatool }
      {
         Action ~ ` \l__datatool_action_tl ': ~ empty ~ row
      }
   }
  \fp_set_eq:NN \l__datatool_min_fp \c_inf_fp
  \fp_set_eq:NN \l__datatool_max_fp \c_minus_inf_fp
  \fp_zero:N \l__datatool_total_fp
  \tl_clear:N \l__datatool_item_currency_tl
  \seq_clear:N \l__datatool_action_tmp_options_seq
  \clist_if_empty:NF \l__datatool_action_options_clist
  {
    \clist_map_inline:Nn
     \l__datatool_action_options_clist
    {
      \clist_if_in:nnTF
        { sum , mean , variance, sd , min , max } { ##1 }
       {
         \__datatool_add_action_option:n { ##1 }
       }
       {
         \PackageWarning { datatool }
          {
            Unknown ~ aggregate ~ function ~ `##1' ~ in ~
            \token_to_str:N \DTLaction
             [ options={\l__datatool_action_options_clist}, ... ]
             {` \l__datatool_action_tl ' }
          }
       }
    }
  }
  \seq_if_in:NnT
    \l__datatool_action_tmp_options_seq { sd }
   {
     \__datatool_add_action_option:n { variance }
   }
  \seq_if_in:NnT
    \l__datatool_action_tmp_options_seq { variance }
   {
     \__datatool_add_action_option:n { mean }
   }
  \seq_if_in:NnT
    \l__datatool_action_tmp_options_seq { mean }
   {
     \__datatool_add_action_option:n { sum }
   }
  \seq_clear:N \l__datatool_action_tmp_data_seq
   \__datatool_optional_columns:
  \seq_if_empty:NTF \l__datatool_action_columns_seq
   {
     \int_step_inline:nnn
      { 1 }
      { \DTLcolumncount { \l__datatool_action_name_tl } }
      {
        \__datatool_action_row_aggregate_col:nn { ##1 } { #1 }
      }
   }
   {
    \seq_map_inline:Nn \l__datatool_action_columns_seq
     {
       \__datatool_action_row_aggregate_col:nn { ##1 } { #1 }
     }
   }
  \int_set:Nn
    \l__datatool_count_int
    { \seq_count:N \l__datatool_action_tmp_data_seq }
  \tl_set:NV
    \l__datatool_action_return_tl
    \l__datatool_count_int
  \__datatool_put_return_action_int:nV
   { count } \l__datatool_count_int
  \__datatool_put_return_action_string:nx
   { columns }
   { \seq_use:Nn \l__datatool_action_columns_seq { , } }
  \prop_put:NnV \l__datatool_action_return_prop
   { seq } \l__datatool_action_tmp_data_seq
  \int_if_zero:nF { \l__datatool_count_int }
   {
    \__datatool_if_action_option:nT { min }
     {
       \fp_compare:nNnT
          { \l__datatool_min_fp }
          <
          { \c_inf_fp }
        {
          \__datatool_put_return_action_decimal:nx
           { min }
           { \fp_to_decimal:N \l__datatool_min_fp }
        }
     }
    \__datatool_if_action_option:nT { max }
     {
       \fp_compare:nNnT
          { \l__datatool_max_fp }
          >
          { \c_minus_inf_fp }
        {
          \__datatool_put_return_action_decimal:nx
           { max }
           { \fp_to_decimal:N \l__datatool_max_fp }
        }
     }
    \__datatool_if_action_option:nT { sum }
     {
       \__datatool_put_return_action_decimal:nx
        { sum }
        { \fp_to_decimal:N \l__datatool_total_fp }
       \__datatool_if_action_option:nT { mean }
       {
         \fp_set:Nn \l__datatool_mean_fp
          { \l__datatool_total_fp / \l__datatool_count_int }
         \__datatool_put_return_action_decimal:nx
          { mean }
          { \fp_to_decimal:N \l__datatool_mean_fp }
         \__datatool_if_action_option:nT { variance }
         {
          \fp_zero:N \l__datatool_tmpa_fp
          \seq_map_inline:Nn
           \l__datatool_action_tmp_data_seq
           {
             \fp_set:Nn \l__datatool_tmpb_fp
              {
                ##1 - \l__datatool_mean_fp
              }
             \fp_add:Nn \l__datatool_tmpa_fp
              {
                 \l__datatool_tmpb_fp * \l__datatool_tmpb_fp
              }
           }
          \fp_set:Nn \l__datatool_tmpa_fp
           { \l__datatool_tmpa_fp / \l__datatool_count_int }
          \__datatool_put_return_action_decimal:nx
           { variance }
           { \fp_to_decimal:N \l__datatool_tmpa_fp }
           \__datatool_if_action_option:nT { sd }
           {
             \fp_set:Nn \l__datatool_tmpa_fp
              { sqrt ( \l__datatool_tmpa_fp ) }
             \__datatool_put_return_action_decimal:nx
              { sd }
              { \fp_to_decimal:N \l__datatool_tmpa_fp }
           }
         }
       }
     }
   }
  \__datatool_put_return_action_string:nV
   { name } \dtldbname
  \__datatool_put_return_action_int:nV
   { row } \dtlrownum
 }
}
\cs_new:Nn \__datatool_action_row_aggregate_col:nn
{
  \dtl@getentryfromrow
    \l__datatool_item_value_tl { #1 } { #2 }
  \datatool_if_null:NF \l__datatool_item_value_tl
   {
     \__datatool_parse:N \l__datatool_item_value_tl
      \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
      {
        \int_compare:nNnT
          { \@dtl@datatype } = { \c_datatool_currency_int }
        {
          \tl_if_empty:NT \l__datatool_item_currency_tl
           {
             \tl_set_eq:NN
               \l__datatool_item_currency_tl
               \l__datatool_datum_currency_tl
           }
        }
        \fp_set:Nn \l__datatool_tmpa_fp
         { \DTLdatumvalue { \l__datatool_item_value_tl } }
        \seq_put_right:Nx \l__datatool_action_tmp_data_seq
         { \fp_to_decimal:N \l__datatool_tmpa_fp }
        \__datatool_if_action_option:nT { sum }
        {
          \fp_add:Nn \l__datatool_total_fp
            \l__datatool_tmpa_fp
        }
        \__datatool_if_action_option:nT { min }
        {
          \fp_compare:nNnT
            { \l__datatool_min_fp }
             >
            { \l__datatool_tmpa_fp }
          {
            \fp_set_eq:NN
              \l__datatool_min_fp
              \l__datatool_tmpa_fp
          }
        }
        \__datatool_if_action_option:nT { max }
        {
          \fp_compare:nNnT
            { \l__datatool_max_fp }
             <
            { \l__datatool_tmpa_fp }
          {
            \fp_set_eq:NN
              \l__datatool_max_fp
              \l__datatool_tmpa_fp
          }
        }
      }
   }
}
\cs_new:cn { __datatool_action_select ~ row: }
{
  \int_zero:N \dtlrownum
  \int_zero:N \dtlcolumnnum
  \__datatool_require_database:T
  {
    \int_if_zero:nTF \l__datatool_action_row_int
    {
     \__datatool_optional_key_xor_column_get_column:nTF
      {
        \__datatool_action_error:nn
         {
            Missing ~ row ~ or ~ column ~ identifier ~
            ( `row' ~ or ~ `key' ~ or `column' ~ required )
         }
         {
            If ~ the ~ row ~ index ~ isn't ~ provided, ~
            \token_to_str:N \DTLaction [...]{ \l__datatool_action_tl } ~
            needs ~ the ~ `key' ~ or `column' ~ setting ~ in ~ the ~ optional ~
            argument ~ to ~ identify ~ the ~ required ~ column ~
            to ~ match on
         }
      }
      {
        \__datatool_get_row_for_value:VVVT
         \l__datatool_action_name_tl
         \l__datatool_action_column_int
         \l__datatool_action_value_tl
        {
          \int_set_eq:NN
            \dtlcolumnnum
            \l__datatool_action_column_int
        }
      }
      {
      }
    }
    {
      \int_set_eq:NN \dtlrownum \l__datatool_action_row_int
      \tl_if_empty:NF \l__datatool_action_key_tl
      {
       \__datatool_action_error:nn
        {
            can't ~ use ~ both ~ `key' ~ and ~ `row'. ~ Ignoring ~
            key = \l__datatool_action_key_tl
        }
        {
            Either ~ use ~ `key' ~ or ~ `row' ~ but ~ not ~ both ~
            in the optional argument of \token_to_str:N \DTLaction
            [...] { \l__datatool_action_tl }
        }
      }
      \int_if_zero:nF \l__datatool_action_column_int
      {
       \__datatool_action_error:nn
        {
            can't ~ use ~ both ~ `column' ~ and ~ `row'. ~ Ignoring ~
            column = \int_use:N \l__datatool_action_column_int
        }
        {
            Either ~ use ~ `column' ~ or ~ `row' ~ but ~ not ~ both ~
            in the optional argument of \token_to_str:N \DTLaction
            [...] { \l__datatool_action_tl }
        }
        \int_zero:N \l__datatool_action_column_int
      }
      \exp_args:NNV \dtlgetrow
       \l__datatool_action_name_tl
       \l__datatool_action_row_int
    }
    \int_if_zero:nF \dtlrownum
     {
       \tl_set:NV
         \l__datatool_action_return_tl
         \dtlrownum
       \__datatool_map_current_row:Nn
        \l__datatool_item_value_tl
        {
          \exp_args:NNVV \@dtl@getkeyforcolumn
           \l__datatool_item_key_tl
           \l__datatool_action_name_tl
           \dtlcolumnnum
          \__datatool_put_return_action_parse:VV
            \l__datatool_item_key_tl
            \l__datatool_item_value_tl
        }
        \int_set_eq:NN
          \dtlcolumnnum
          \l__datatool_action_column_int
     }
  }
}
\cs_new:cn { __datatool_action_current ~ row ~ values: }
{
  \__datatool_requires_dbname:T
  {
    \clist_if_empty:NTF
      \l__datatool_action_options_clist
    {
      \seq_clear:N \l__datatool_column_keys_seq
    }
    {
      \seq_set_from_clist:NN \l__datatool_column_keys_seq
        \l__datatool_action_options_clist
    }
   \__datatool_optional_columns:
   \seq_if_empty:NTF \l__datatool_action_columns_seq
    {
      \int_step_inline:nn { \DTLcolumncount { \dtldbname } }
       {
         \dtlgetentryfromcurrentrow
           \l__datatool_item_value_tl
           { ##1 }
         \datatool_if_null:NF \l__datatool_item_value_tl
          {
            \@dtl@getkeyforcolumn
              \l__datatool_item_key_tl
              \dtldbname
              { ##1 }
            \__datatool_put_return_action_parse:VV
              \l__datatool_item_key_tl
              \l__datatool_item_value_tl
          }
       }
    }
    {
      \seq_map_inline:Nn \l__datatool_action_columns_seq
       {
         \dtlgetentryfromcurrentrow
           \l__datatool_item_value_tl
           { ##1 }
         \datatool_if_null:NF
           \l__datatool_item_value_tl
          {
            \@dtl@getkeyforcolumn
              \l__datatool_item_key_tl
              \dtldbname
              { ##1 }
            \__datatool_put_return_action_parse:VV
              \l__datatool_item_key_tl
              \l__datatool_item_value_tl
          }
       }
    }
  }
  \tl_set:Nx
    \l__datatool_action_return_tl
    { \prop_count:N \l__datatool_action_return_prop }
}
\cs_new:Nn \__datatool_action_find_if:T { #1 }
\bool_new:N \__datatool_action_find_asc_bool
\bool_new:N \__datatool_action_find_select_bool
\keys_define:nn { datatool / action / find }
{
  select .bool_set:N = \__datatool_action_find_select_bool ,
  direction .choice: ,
  direction / ascending .code:n =
   {
      \bool_set_true:N \__datatool_action_find_asc_bool
   } ,
  direction / asc .code:n =
   {
      \bool_set_true:N \__datatool_action_find_asc_bool
   } ,
  direction / descending .code:n =
   {
      \bool_set_false:N \__datatool_action_find_asc_bool
   } ,
  direction / desc .code:n =
   {
      \bool_set_false:N \__datatool_action_find_asc_bool
   } ,
  direction .value_required:n = true ,
  inline .cs_set:Np = \__datatool_action_find_if:T #1 ,
  inline .value_required:n = true ,
  function .code =
   {
     \cs_set_eq:NN \__datatool_action_find_if:T #1
   },
  function .value_required:n = true ,
}
\cs_new:cn { __datatool_action_find: }
{
  \__datatool_require_database:T
  {
    \__datatool_forbid_key_and_column:n
     {
       The ~ action ~ ` \l__datatool_action_tl ' ~ should ~ have ~
       cs = column-key ~ comma ~ separated ~ list ~ in ~ the ~
      `assign' ~ setting ~ to ~ reference ~ column ~ values
     }
    \@DTLifdbempty { \l__datatool_action_name_tl }
     { }
     {
      \__datatool_optional_row:NF \l__datatool_action_row_int
       {
         \int_set_eq:NN \l__datatool_action_row_int \c_one_int
       }
      \__datatool_optional_row:NF \l__datatool_action_row_ii_int
       {
         \int_set:Nn \l__datatool_action_row_ii_int
          { \DTLrowcount { \l__datatool_action_name_tl } }
       }
      \cs_set_eq:NN \__datatool_action_find_if:T \use:n
      \bool_set_true:N \__datatool_action_find_asc_bool
      \bool_set_false:N \__datatool_action_find_select_bool
      \keys_set:nV { datatool / action / find }
        \l__datatool_action_options_clist
      \__datatool_get_placeholder_assign:
      \bool_if:NTF \__datatool_action_find_asc_bool
       {
         \int_compare:nNnT
           { \l__datatool_action_row_ii_int }
            <
           { \l__datatool_action_row_int }
          {
            \datatool_swap_ints:NN
              \l__datatool_action_row_int
              \l__datatool_action_row_ii_int
          }
        \int_until_do:nn
          {
            \l__datatool_action_row_int
              >
            \l__datatool_action_row_ii_int
          }
         {
           \__datatool_action_find_loop_body:
           \int_incr:N \l__datatool_action_row_int
         }
       }
       {
         \int_compare:nNnT
           { \l__datatool_action_row_ii_int }
            >
           { \l__datatool_action_row_int }
          {
            \datatool_swap_ints:NN
              \l__datatool_action_row_int
              \l__datatool_action_row_ii_int
          }
        \int_until_do:nn
          {
            \l__datatool_action_row_int
              <
            \l__datatool_action_row_ii_int
          }
         {
           \__datatool_action_find_loop_body:
           \int_decr:N \l__datatool_action_row_int
         }
       }
     }
  }
}
\cs_new:Nn \__datatool_action_find_loop_body:
{
  \__datatool_get_row:vVN
    { dtldb@ \l__datatool_action_name_tl }
    \l__datatool_action_row_int
   \l__datatool_action_tmpa_tl
  \exp_args:NV \__datatool_do_action_assignments:n
   \l__datatool_action_tmpa_tl
  \__datatool_action_find_if:T
   {
     \tl_set:Nx
       \l__datatool_action_return_tl
       { \int_use:N \l__datatool_action_row_int }
      \__datatool_set_return_from_row:Vn
       \l__datatool_action_tmpa_tl
       { \l__datatool_action_name_tl }
      \bool_if:NT \__datatool_action_find_select_bool
       {
         \exp_args:NNV \dtlgetrow
          \l__datatool_action_name_tl
          \l__datatool_action_row_int
       }
     \int_set_eq:NN
       \l__datatool_action_row_int
       \l__datatool_action_row_ii_int
   }
}
\cs_new:cn { __datatool_action_sort: }
{
  \__datatool_require_database:T
   {
     \clist_if_empty:NF
        \l__datatool_action_columns_clist
      {
        \__datatool_action_error:n
         {
           `columns' ~ action ~ setting ~ not ~ available. ~ Ignoring
         }
      }
     \clist_if_empty:NF
        \l__datatool_action_keys_clist
      {
        \__datatool_action_error:n
         {
           `keys' ~ action ~ setting ~ not ~ available. ~ Ignoring
         }
      }
     \clist_if_empty:NTF
        \l__datatool_action_assign_clist
      {
        \__datatool_action_error:nn
         {
           Missing ~ `assign' ~ setting. ~
           Unable ~ to ~ sort ~ without ~ any ~
           sort ~ criteria!
         }
         {
           The ~ `assign' ~ setting ~ corresponds ~ to ~
           the ~ final ~ `criteria' ~ argument ~ of ~
           \token_to_str:N \DTLsortdata
         }
      }
      {
        \seq_clear:N \l__datatool_wordlist_seq
        \__datatool_db_sort:VVV
          \l__datatool_action_options_clist
          \l__datatool_action_name_tl
          \l__datatool_action_assign_clist
        \seq_if_empty:NF \l__datatool_wordlist_seq
         {
           \tl_set:Nx \l__datatool_action_return_tl
            { \seq_count:N \l__datatool_wordlist_seq }
           \__datatool_put_return_action_int:nx
            { rows }
            { \DTLrowcount { \l__datatool_action_name_tl } }
           \__datatool_put_return_action_int:nx
            { columns }
            { \DTLcolumncount { \l__datatool_action_name_tl } }
         }
      }
   }
  \__datatool_put_return_action_string:nV
   { name } \l__datatool_action_name_tl
}
\cs_new:Nn \__datatool_column_markup:nnnn
{
  \__datatool_column_markup_full:nnnn
   { #1 } { #2 } { #3 } { \exp_not:n { #4 } }
}
\cs_generate_variant:Nn
  \__datatool_column_markup:nnnn
  { VVVV, VVVn, VnVn, VnVV, Vnnn, nVVV, nVnV, VnnV }
\cs_new:Nn \__datatool_column_markup_full:nnnn
{
  \exp_not:N \db@plist@elt@w
    \exp_not:N \db@col@id@w
      #1
    \exp_not:N \db@col@id@end@
    \exp_not:N \db@key@id@w
      #2
    \exp_not:N \db@key@id@end@
    \exp_not:N \db@type@id@w
      #3
    \exp_not:N \db@type@id@end@
    \exp_not:N \db@header@id@w
      #4
    \exp_not:N \db@header@id@end@
    \exp_not:N \db@col@id@w
      #1
    \exp_not:N \db@col@id@end@
  \exp_not:N \db@plist@elt@end@
}
\cs_new:Nn \__datatool_row_markup:nn
{
  \__datatool_row_markup_full:nn
   { #1 } { \exp_not:n { #2 } }
}
\cs_generate_variant:Nn
  \__datatool_row_markup:nn
  { vn , Vn , vV, VV, vv , nV }
\cs_new:Nn \__datatool_row_markup_full:nn
{
 \exp_not:N \db@row@elt@w
   \exp_not:N \db@row@id@w
     #1 % row ID
   \exp_not:N \db@row@id@end@
       #2
   \exp_not:N \db@row@id@w
     #1  % row ID
   \exp_not:N \db@row@id@end@
 \exp_not:N \db@row@elt@end@
}
\cs_generate_variant:Nn
 \__datatool_row_markup_full:nn
 { vn }
\cs_new:Nn \__datatool_row_element_markup:nn
{
  \__datatool_row_element_markup_full:nn
   { #1 } { \exp_not:n { #2 } }
}
\cs_generate_variant:Nn
  \__datatool_row_element_markup:nn
  { vn, Vn , vV , VV, vv, nV }
\cs_new:Nn \__datatool_row_element_markup_full:nn
{
  \exp_not:N \db@col@id@w
    #1
  \exp_not:N \db@col@id@end@
  \exp_not:N \db@col@elt@w
    #2
  \exp_not:N \db@col@elt@end@
  \exp_not:N \db@col@id@w
    #1
  \exp_not:N \db@col@id@end@
}
\NewDocumentCommand \DTLnewdb { m }
{
  \DTLifdbexists{#1}
  {
     \PackageError{datatool}{Database ~ `#1' ~ already ~ exists}{}
  }
  {
    \__datatool_new_db:n { #1 }
  }
}
\cs_new:Nn \__datatool_new_db:n
{%
  \dtl@message {Creating ~ database ~ `#1'}
  \exp_args:Nc \newtoks { dtldb @ #1 }
  \exp_args:Nc \newtoks { dtlkeys @ #1 }
  \int_new:c { dtlrows @ #1 }
  \int_new:c { dtlcols @ #1 }
}
\NewDocumentCommand \DTLcleardb { m }
{
  \bool_if:NTF \l__datatool_db_global_bool
  {
    \DTLgcleardb { #1 }
  }
  {
    \DTLifdbexists { #1 }
    {
      \dtlforeachkey(\@dtl@key,\@dtl@col,\@dtl@type,\@dtl@head)\in{#1}\do
      {
        \tl_set_eq:cN { dtl@ci@ #1 @ \@dtl@key } \undefined
      }
      \__datatool_token_register_set:cn { dtldb@ #1 } { }
      \__datatool_token_register_set:cn { dtlkeys@ #1 } { }
      \int_zero:c { dtlrows@ #1 }
      \int_zero:c { dtlcols@ #1 }
    }
    {
      \PackageError { datatool }
      {
         Can't ~ clear ~ database ~ `#1': ~
         database ~ doesn't ~ exist
      }
      { }
    }
  }
}
\NewDocumentCommand \DTLdeletedb { m }
{
  \bool_if:NTF \l__datatool_db_global_bool
  {
    \DTLgdeletedb { #1 }
  }
  {
    \DTLifdbexists {#1}
    {
      \dtlforeachkey(\@dtl@key,\@dtl@col,\@dtl@type,\@dtl@head)\in{#1}\do
      {
        \expandafter\let\csname dtl@ci@#1@\@dtl@key\endcsname\undefined
      }
      \expandafter\let\csname dtldb@#1\endcsname\undefined
      \expandafter\let\csname dtlkeys@#1\endcsname\undefined
      \expandafter\let\csname dtlrows@#1\endcsname\undefined
      \expandafter\let\csname dtlcols@#1\endcsname\undefined
    }
    {
      \PackageError{datatool}{Can't ~ delete ~ database ~ `#1': ~
         database ~ doesn't ~ exist}{}%
    }
  }
}
\newcommand\DTLgnewdb{\DTLnewdb}
\NewDocumentCommand \DTLgdeletedb { m } {
  \DTLifdbexists { #1 }
  {
    \dtlforeachkey(\@dtl@key,\@dtl@col,\@dtl@type,\@dtl@head)\in{#1}\do
    {
      \cs_undefine:c { dtl@ci@ #1 @ \@dtl@key }
    }
    \cs_undefine:c { dtldb@#1 }
    \cs_undefine:c { dtlkeys@#1 }
    \cs_undefine:c { dtlrows@#1 }
    \cs_undefine:c { dtlcols@#1 }
  }
  {
    \PackageError {datatool}
     {
       Can't ~ delete ~ database ~ `#1': ~
       database ~ doesn't ~ exist
     }
     { }
  }
}
\NewDocumentCommand \DTLgcleardb { m }
{
  \DTLifdbexists { #1 }
  {
    \dtlforeachkey(\@dtl@key,\@dtl@col,\@dtl@type,\@dtl@head)\in{#1}\do
    {
      \cs_undefine:c { dtl@ci@ #1 @ \@dtl@key }
    }
    \__datatool_token_register_gset:cn { dtldb@ #1 } { }
    \__datatool_token_register_gset:cn { dtlkeys@ #1 } { }
    \int_gzero:c { dtlrows@ #1 }
    \int_gzero:c { dtlcols@ #1 }
  }
  {
    \PackageError {datatool}
    {
      Can't ~ clear ~ database ~ `#1': ~
       database ~ doesn't ~ exist
    }
    { }
  }
}
\newcommand*{\DTLrowcount}[1]{
 \int_use:c { dtlrows@ #1 }
}
\newcommand*{\DTLcolumncount}[1]{
  \int_use:c { dtlcols@ #1 }
}
\cs_new:Nn \datatool_db_state:nnnn
{
  \DTLifdbexists { #1 }
  { \@DTLifdbempty { #1 } { #3 } { #2 } }
  { #4 }
}
\NewDocumentCommand \DTLifdbempty { m +m +m }
{
  \DTLifdbexists { #1 }
  { \@DTLifdbempty { #1 } { #2 } { #3 } }
  {
    \PackageError {datatool}
     {
       \token_to_str:N \DTLifdbempty : ~
       Can't ~ check ~ if ~ database ~ `#1' ~ is ~ empty: ~
       database ~ doesn't ~ exist
     }
     { }
  }
}
\newcommand{\@DTLifdbempty}[3]{%
  \int_if_zero:nTF
    { \int_use:c { dtlrows@ #1 } }
   { #2 } { #3 }
}
\NewDocumentCommand \DTLnewrow { s m }
{
  \IfBooleanTF { #1 }
   { \@sDTLnewrow { #2 } }
   { \@DTLnewrow { #2 } }
}
\newcommand*{\@DTLnewrow}[1]{
 \DTLifdbexists { #1 }
  { \@sDTLnewrow { #1 } }
  {
    \PackageError { datatool }
    {
      \token_to_str:N \DTLnewrow : ~
      Can't ~ add ~ new ~ row ~ to ~ database ~ `#1': ~
      database ~ doesn't ~ exist
    }
    { }
  }
}
\newcommand*{\@sDTLnewrow}[1]{%
  \bool_if:NTF \l__datatool_db_global_bool
  {
    \int_gincr:c { dtlrows@#1 }
  }
  {
    \int_incr:c { dtlrows@#1 }
  }
  \__datatool_dtldb_put_right:nx
    { #1 }
   {
     \__datatool_row_markup:vn
      { dtlrows@#1 } % row count
      { }
   }
  \dtl@message
    { New ~ row ~ added ~ to ~ database ~ `#1' }
}
\newcount\dtlcolumnnum
\newcount\dtlrownum
\NewDocumentCommand \DTLifhaskey { s m m m m }
{
  \IfBooleanTF { #1 }
  {
    \@sDTLifhaskey { #2 } { #3 } { #4 } { #5 }
  }
  {
    \@DTLifhaskey { #2 } { #3 } { #4 } { #5 }
  }
}
\newcommand{\@DTLifhaskey} [4]
{
  \DTLifdbexists { #1 }
  {
    \@sDTLifhaskey { #1 } { #2 } { #3 } { #4 }
  }
  {
    \PackageError { datatool }
    { Database ~ `#1' ~ doesn't ~ exist }
    { }
  }
}
\newcommand{\@sDTLifhaskey}{ \datatool_if_has_key:nnTF }
\prg_new_conditional:Npnn \datatool_if_has_key:nn #1 #2 { p, T, F, TF }
{
  \tl_if_exist:cTF { dtl@ci@ #1 @ #2 }
  {
    \prg_return_true:
  }
  {
    \prg_return_false:
  }
}
\NewDocumentCommand \DTLgetcolumnindex { s m m }
{
  \IfBooleanTF { #1 }
  { \@sdtl@getcolumnindex { #2 } { #3 } }
  { \@dtl@getcolumnindex { #2 } { #3 } }
}
\newcommand*{\@dtl@getcolumnindex} [3]
{
  \DTLifdbexists { #2 }
  {
    \@sDTLifhaskey { #2 } { #3 }
    {
      \@sdtl@getcolumnindex { #1 } { #2 } { #3 }
    }
    {
      \PackageError { datatool }
      { Database ~ `#2' ~ doesn't ~ contain ~ key ~ `#3' }
      { }
    }
  }
  {
    \PackageError { datatool }
    { Database ~ `#2' ~ doesn't ~ exist }
    { }
  }
}
\newcommand*{\@sdtl@getcolumnindex}[3]{%
  \tl_set_eq:Nc #1 { dtl@ci@#2@#3 }
}
\newcommand*{\dtlcolumnindex}[2]{%
  \tl_if_exist:cTF { dtl@ci@#1@#2 }
   { \tl_use:c { dtl@ci@#1@#2 } }
   { \c_zero_int }
}
\ExplSyntaxOff
\newrobustcmd*{\DTLgetkeyforcolumn}{%
  \@ifstar\@sdtlgetkeyforcolumn\@dtlgetkeyforcolumn}
\newcommand*{\@dtlgetkeyforcolumn}[3]{%
  \DTLifdbexists{#2}%
  {%
    \ifnum#3<1\relax
      \PackageError{datatool}{Invalid column index \number#3}{%
      Column indices start at 1}%
    \else
      \expandafter\ifnum\csname dtlcols@#2\endcsname<#3\relax
        \PackageError{datatool}{Index \number#3\space out of
        range for database `#2'}{Database `#2' only has
        \expandafter\number\csname dtlcols@#2\endcsname\space
        columns}%
      \else
        \@sdtlgetkeyforcolumn{#1}{#2}{#3}%
      \fi
    \fi
  }%
  {%
    \PackageError{datatool}{Database `#2' doesn't exists}{}%
  }%
}
\newcommand*{\@sdtlgetkeyforcolumn}[3]{%
  \edef\@dtl@dogetkeyforcolumn{\noexpand\@dtl@getkeyforcolumn
    {\noexpand#1}{#2}{\number#3}}%
  \@dtl@dogetkeyforcolumn
}
\newcommand*{\@dtl@getkeyforcolumn}[3]{%
  \def\@dtl@get@keyforcolumn##1% before stuff
    \db@plist@elt@w% start of block
    \db@col@id@w #3\db@col@id@end@% index
    \db@key@id@w ##2\db@key@id@end@% key
    \db@type@id@w ##3\db@type@id@end@% data type
    \db@header@id@w ##4\db@header@id@end@% header
    \db@col@id@w #3\db@col@id@end@% index
    \db@plist@elt@end@% end of block
    ##5\q@nil{\def#1{##2}}%
  \edef\@dtl@tmp{\expandafter\the\csname dtlkeys@#2\endcsname}%
  \expandafter\@dtl@get@keyforcolumn\@dtl@tmp
    \db@plist@elt@w% start of block
    \db@col@id@w #3\db@col@id@end@ %index
    \db@key@id@w \@nil\db@key@id@end@% key
    \db@type@id@w \db@type@id@end@% data type
    \db@header@id@w \db@header@id@end@% header
    \db@col@id@w #3\db@col@id@end@% index
    \db@plist@elt@end@% end of block
    \q@nil
}
\newcommand*\DTLunsettype{}
\newcommand*\DTLstringtype{0}
\newcommand*\DTLinttype{1}
\newcommand*\DTLrealtype{2}
\newcommand*\DTLcurrencytype{3}
\newrobustcmd*{\DTLgetdatatype}{%
  \@ifstar\@sdtlgetdatatype\@dtlgetdatatype
}
\newcommand*{\@dtlgetdatatype}[3]{%
  \DTLifdbexists{#2}%
  {%
    \@sDTLifhaskey{#2}{#3}%
    {%
      \@sdtlgetdatatype{#1}{#2}{#3}%
    }%
    {%
      \PackageError{datatool}{Key `#3' undefined in database `#2'}{}%
    }%
  }%
  {%
    \PackageError{datatool}{Database `#2' doesn't exist}{}%
  }%
}
\newcommand*{\@sdtlgetdatatype}[3]{%
  \edef\@dtl@dogetdata{\noexpand\@dtl@getdatatype{\noexpand#1}%
   {\expandafter\the\csname dtlkeys@#2\endcsname}%
   {\dtlcolumnindex{#2}{#3}}}%
  \@dtl@dogetdata
}
\newcommand*{\@dtl@getdatatype}[3]{%
  \def\@dtl@get@keydata##1% stuff before
    \db@plist@elt@w% start of key block
     \db@col@id@w #3\db@col@id@end@% column index
      \db@key@id@w ##2\db@key@id@end@% key id
      \db@type@id@w ##3\db@type@id@end@% data type
      \db@header@id@w ##4\db@header@id@end@% header
     \db@col@id@w #3\db@col@id@end@% column index
    \db@plist@elt@end@% end of key block
    ##5% stuff afterwards
    \q@nil{\def#1{##3}}%
  \@dtl@get@keydata#2\q@nil
}
\ExplSyntaxOn
\newcommand*{\@dtl@getprops}[7]{%
  \__datatool_get_keydata:NNNNNnn #1 #2 #3 #4 #5 { #6 } { #7 }
}
\cs_new:Nn \__datatool_get_keydata:NNNNNnn
{
  \cs_set:Npn \__datatool_get_keydata:w
     ##1 % stuff before
    \db@plist@elt@w % start of key block
     \db@col@id@w #7\db@col@id@end@ % column index
      \db@key@id@w ##2\db@key@id@end@ % key id
      \db@type@id@w ##3\db@type@id@end@ % data type
      \db@header@id@w ##4\db@header@id@end@ % header
     \db@col@id@w #7\db@col@id@end@ % column index
    \db@plist@elt@end@ % end of key block
    ##5% stuff afterwards
    \q_nil
    {
      \tl_set:Nn #1 { ##2 } % key
      \tl_set:Nn #2 { ##3 } % data type
      \__datatool_token_register_set:Nn #3 { ##4 } % header
      \__datatool_token_register_set:Nn #4 { ##1 } % before stuff
      \__datatool_token_register_set:Nn #5 { ##5 } % after stuff
    }
  \__datatool_get_keydata:w #6 \q_nil
}
\cs_generate_variant:Nn  \__datatool_get_keydata:NNNNNnn
 { NNNNNvV , NNNNNve }
\cs_new:Nn \__datatool_get_props:NNNNNnn
{
  \cs_set:Npn \__datatool_get_keydata:w
     ##1 % stuff before
    \db@plist@elt@w % start of key block
     \db@col@id@w #7\db@col@id@end@ % column index
      \db@key@id@w ##2\db@key@id@end@ % key id
      \db@type@id@w ##3\db@type@id@end@ % data type
      \db@header@id@w ##4\db@header@id@end@ % header
     \db@col@id@w #7\db@col@id@end@ % column index
    \db@plist@elt@end@ % end of key block
    ##5 % stuff afterwards
    \q_nil
    {
      \tl_set:Nn #1 { ##2 } % key
      \tl_set:Nn #2 { ##3 } % data type
      \tl_set:Nn #3 { ##4 } % header
      \tl_set:Nn #4 { ##1 } % before stuff
      \tl_set:Nn #5 { ##5 } % after stuff
    }
  \__datatool_get_keydata:w #6 \q_nil
}
\cs_generate_variant:Nn \__datatool_get_props:NNNNNnn
 { NNNNNvx, NNNNNvV, NNNNNvn }
\cs_new:Nn \__datatool_get_col_data:nn
{
  \tl_if_empty:nTF { #1 }
  {
    \tl_set:Nn \l__datatool_item_head_tl { \q_no_value }
    \int_set_eq:NN \l__datatool_item_type_int \c_datatool_unknown_int
    \tl_set:Nn \l__datatool_item_key_tl { \q_no_value }
  }
  {
    \cs_set:Npn \__datatool_get_col_data:w ##1% stuff before
      \db@plist@elt@w% start of key block
       \db@col@id@w #2\db@col@id@end@% column index
        \db@key@id@w ##2\db@key@id@end@% key id
        \db@type@id@w ##3\db@type@id@end@% data type
        \db@header@id@w ##4\db@header@id@end@% header
       \db@col@id@w #2\db@col@id@end@% column index
      \db@plist@elt@end@% end of key block
      ##5% stuff afterwards
      \q_nil
      {
        \tl_set:Nn \l__datatool_item_key_tl { ##2 }
        \tl_set:Nn \l__datatool_item_head_tl { ##4 }
        \tl_if_empty:nTF { ##3 }
        {
          \int_set_eq:NN \l__datatool_item_type_int \c_datatool_unknown_int
        }
        {
          \int_set:Nn \l__datatool_item_type_int { ##3 }
        }
      }
    \__datatool_get_col_data:w #1
      \db@plist@elt@w% start of key block
       \db@col@id@w #2\db@col@id@end@% column index
        \db@key@id@w \q_no_value \db@key@id@end@% key id
        \db@type@id@w \db@type@id@end@% data type
        \db@header@id@w \q_no_value \db@header@id@end@% header
       \db@col@id@w #2\db@col@id@end@% column index
      \db@plist@elt@end@% end of key block
    \q_nil
  }
}
\cs_generate_variant:Nn \__datatool_get_col_data:nn { vV, vn }
\cs_new:Nn \__datatool_get_col_type:nn
{
  \tl_if_empty:nTF { #1 }
  {
    \int_set_eq:NN \l__datatool_item_type_int \c_datatool_unknown_int
  }
  {
    \cs_set:Npn \__datatool_get_col_type:w ##1% stuff before
      \db@plist@elt@w% start of key block
       \db@col@id@w #2\db@col@id@end@% column index
        \db@key@id@w ##2\db@key@id@end@% key id
        \db@type@id@w ##3\db@type@id@end@% data type
        \db@header@id@w ##4\db@header@id@end@% header
       \db@col@id@w #2\db@col@id@end@% column index
      \db@plist@elt@end@% end of key block
      ##5% stuff afterwards
      \q_nil
      {
        \tl_if_empty:nTF { ##3 }
        {
          \int_set_eq:NN \l__datatool_item_type_int \c_datatool_unknown_int
        }
        {
          \int_set:Nn \l__datatool_item_type_int { ##3 }
        }
      }
    \__datatool_get_col_type:w #1
      \db@plist@elt@w% start of key block
       \db@col@id@w #2\db@col@id@end@% column index
        \db@key@id@w \q_no_value \db@key@id@end@% key id
        \db@type@id@w \db@type@id@end@% data type
        \db@header@id@w \q_no_value \db@header@id@end@% header
       \db@col@id@w #2\db@col@id@end@% column index
      \db@plist@elt@end@% end of key block
    \q_nil
  }
}
\cs_generate_variant:Nn \__datatool_get_col_type:nn { vn, vV, vv }
\cs_new:Nn \__datatool_get_col_type_header:nn
{
  \tl_if_empty:nTF { #1 }
  {
    \tl_set:Nn \l__datatool_item_head_tl { \q_no_value }
    \int_set_eq:NN \l__datatool_item_type_int \c_datatool_unknown_int
  }
  {
    \cs_set:Npn \__datatool_get_col_type_header:w ##1% stuff before
      \db@plist@elt@w% start of key block
       \db@col@id@w #2\db@col@id@end@% column index
        \db@key@id@w ##2\db@key@id@end@% key id
        \db@type@id@w ##3\db@type@id@end@% data type
        \db@header@id@w ##4\db@header@id@end@% header
       \db@col@id@w #2\db@col@id@end@% column index
      \db@plist@elt@end@% end of key block
      ##5% stuff afterwards
      \q_nil
      {
        \tl_set:Nn \l__datatool_item_head_tl { ##4 }
        \tl_if_empty:nTF { ##3 }
        {
          \int_set_eq:NN \l__datatool_item_type_int \c_datatool_unknown_int
        }
        {
          \int_set:Nn \l__datatool_item_type_int { ##3 }
        }
      }
    \__datatool_get_col_type_header:w #1
      \db@plist@elt@w% start of key block
       \db@col@id@w #2\db@col@id@end@% column index
        \db@key@id@w \q_no_value \db@key@id@end@% key id
        \db@type@id@w \db@type@id@end@% data type
        \db@header@id@w \q_no_value \db@header@id@end@% header
       \db@col@id@w #2\db@col@id@end@% column index
      \db@plist@elt@end@% end of key block
    \q_nil
  }
}
\cs_new:Nn \__datatool_get_col_type_key:nn
{
  \tl_if_empty:nTF { #1 }
  {
    \tl_set:Nn \l__datatool_item_key_tl { \q_no_value }
    \int_set_eq:NN \l__datatool_item_type_int \c_datatool_unknown_int
  }
  {
    \cs_set:Npn \__datatool_get_col_type_header:w ##1% stuff before
      \db@plist@elt@w% start of key block
       \db@col@id@w #2\db@col@id@end@% column index
        \db@key@id@w ##2\db@key@id@end@% key id
        \db@type@id@w ##3\db@type@id@end@% data type
        \db@header@id@w ##4\db@header@id@end@% header
       \db@col@id@w #2\db@col@id@end@% column index
      \db@plist@elt@end@% end of key block
      ##5% stuff afterwards
      \q_nil
      {
        \tl_set:Nn \l__datatool_item_key_tl { ##2 }
        \tl_if_empty:nTF { ##3 }
        {
          \int_set_eq:NN \l__datatool_item_type_int \c_datatool_unknown_int
        }
        {
          \int_set:Nn \l__datatool_item_type_int { ##3 }
        }
      }
    \__datatool_get_col_type_header:w #1
      \db@plist@elt@w% start of key block
       \db@col@id@w #2\db@col@id@end@% column index
        \db@key@id@w \q_no_value \db@key@id@end@% key id
        \db@type@id@w \db@type@id@end@% data type
        \db@header@id@w \db@header@id@end@% header
       \db@col@id@w #2\db@col@id@end@% column index
      \db@plist@elt@end@% end of key block
    \q_nil
  }
}
\cs_generate_variant:Nn \__datatool_get_col_type_key:nn { vV, VV }
\cs_new:Nn \__datatool_get_col_key:nn
{
  \tl_if_empty:nTF { #1 }
  {
    \tl_set:Nn \l__datatool_item_key_tl { \q_no_value }
  }
  {
    \cs_set:Npn \__datatool_get_col_type_header:w ##1% stuff before
      \db@plist@elt@w% start of key block
       \db@col@id@w #2\db@col@id@end@% column index
        \db@key@id@w ##2\db@key@id@end@% key id
        \db@type@id@w ##3\db@type@id@end@% data type
        \db@header@id@w ##4\db@header@id@end@% header
       \db@col@id@w #2\db@col@id@end@% column index
      \db@plist@elt@end@% end of key block
      ##5% stuff afterwards
      \q_nil
      {
        \tl_set:Nn \l__datatool_item_key_tl { ##2 }
      }
    \__datatool_get_col_type_header:w #1
      \db@plist@elt@w% start of key block
       \db@col@id@w #2\db@col@id@end@% column index
        \db@key@id@w \q_no_value \db@key@id@end@% key id
        \db@type@id@w \db@type@id@end@% data type
        \db@header@id@w \db@header@id@end@% header
       \db@col@id@w #2\db@col@id@end@% column index
      \db@plist@elt@end@% end of key block
    \q_nil
  }
}
\cs_generate_variant:Nn \__datatool_get_col_key:nn { vV, VV }
\ExplSyntaxOff
\newtoks\@dtl@before
\newtoks\@dtl@after
\newtoks\@dtl@colhead
\ExplSyntaxOn
\cs_new:Nn \__datatool_dtlkeys_set:nn
{
  \bool_if:NTF \l__datatool_db_global_bool
   {
     \__datatool_token_register_gset:cn
       { dtlkeys@ #1 } { #2 }
   }
   {
     \__datatool_token_register_set:cn
       { dtlkeys@ #1 } { #2 }
   }
}
\cs_generate_variant:Nn \__datatool_dtlkeys_set:nn
 { nx, Vx, VV }
\cs_new:Nn \__datatool_dtlkeys_put_right:nn
{
  \bool_if:NTF \l__datatool_db_global_bool
   {
     \__datatool_token_register_gput_right:cn
       { dtlkeys@ #1 } { #2 }
   }
   {
     \__datatool_token_register_put_right:cn
       { dtlkeys@ #1 } { #2 }
   }
}
\cs_generate_variant:Nn \__datatool_dtlkeys_put_right:nn
 { nx, Vx }
\cs_new:Nn \__datatool_dtldb_set:nn
{
  \bool_if:NTF \l__datatool_db_global_bool
   {
     \__datatool_token_register_gset:cn
       { dtldb@ #1 } { #2 }
   }
   {
     \__datatool_token_register_set:cn
       { dtldb@ #1 } { #2 }
   }
}
\cs_generate_variant:Nn \__datatool_dtldb_set:nn
 { nx, Vx, nV, VV }
\cs_new:Nn \__datatool_dtldb_put_right:nn
{
  \bool_if:NTF \l__datatool_db_global_bool
   {
     \__datatool_token_register_gput_right:cn
       { dtldb@ #1 } { #2 }
   }
   {
     \__datatool_token_register_put_right:cn
       { dtldb@ #1 } { #2 }
   }
}
\cs_generate_variant:Nn \__datatool_dtldb_put_right:nn
 { nx, Vx }
\cs_new:Nn \__datatool_dtldb_concat:nNnN
{
  \bool_if:NTF \l__datatool_db_global_bool
   {
     \__datatool_token_register_gconcat_middle:cNnN
       { dtldb@#1 } #2 { #3 } #4
   }
   {
     \__datatool_token_register_concat_middle:cNnN
       { dtldb@#1 } #2 { #3 } #4
   }
}
\cs_generate_variant:Nn \__datatool_dtldb_concat:nNnN
 { nNxN , VNxN }
\cs_new:Nn \__datatool_use_datatype:n
{
  \tl_if_empty:nTF { #1 }
  { -1 }
  {
    \tl_if_single:nTF { #1 }
    {
      \tl_if_empty:NTF #1 { -1 } { \int_eval:n { #1 } }
    }
    { \int_eval:n { #1 } }
  }
}
\newrobustcmd*{\DTLaddcolumn}{%
   \@ifstar\@sDTLaddcolumn\@DTLaddcolumn
}
\newcommand{\@DTLaddcolumn}[2]{
  \DTLifdbexists { #1 }
  {
    \@sDTLifhaskey { #1 } { #2 }
    {
      \PackageError {datatool}
      {
        Can't ~ add ~ new ~ column ~ `#2' ~ to ~ database ~ `#1': ~
        column ~ with ~ that ~ key ~ already ~ exists
      }
      { }
    }
    {
      \s@DTLaddcolumn { #1 } { #2 }
    }
  }
  {
    \PackageError {datatool}
    {
      Can't ~ add ~ new ~ column ~ to ~ database ~ `#1': ~
      database ~ doesn't ~ exist
    }
    { }
  }
}
\newcommand{\s@DTLaddcolumn}[2]{%
  \__datatool_add_column_with_header:nxnn { #1 } { #2 } { -1 } { #2 }
}
\NewDocumentCommand \DTLaddcolumnwithheader { s m m m }
{
  \IfBooleanTF { #1 }
  {
    \__datatool_add_column_with_header:nxnn { #2 } { #3 } { -1 } { #4 }
  }
  {
    \@DTLaddcolumnwithheader { #2 } { #3 } { #4 }
  }
}
\newcommand \@DTLaddcolumnwithheader [3]
{
  \DTLifdbexists { #1 }
  {
    \@sDTLifhaskey {#1} {#2}
    {
      \PackageError {datatool}
      {
        Can't ~ add ~ new ~ column ~ `#2' ~ to ~ database ~ `#1': ~
        column ~ with ~ that ~ key ~ already ~ exists
      }
      {}
    }
    {
      \__datatool_add_column_with_header:nxnn { #1 } { #2 } { -1 } { #3 }
    }
  }
  {
    \PackageError {datatool}
    {
      Can't ~ add ~ new ~ column ~ to ~ database ~ `#1': ~
      database ~ doesn't ~ exist
    }
    {}
  }
}
\cs_new:Nn \__datatool_add_column_with_header:nnnn
{
  \bool_if:NTF \l__datatool_db_global_bool
  {
    \int_gincr:c { dtlcols@#1 }
    \int_set_eq:Nc \dtlcolumnnum { dtlcols@#1 }
    \csxdef{dtl@ci@#1@#2}{\number\dtlcolumnnum}%
    \__datatool_token_register_gput_right:cx
      { dtlkeys@#1 }
    {
      \__datatool_column_markup:Vnnn
         \dtlcolumnnum { #2 } { #3 } { #4 }
    }
  }
  {
    \int_incr:c { dtlcols@#1 }
    \int_set_eq:Nc \dtlcolumnnum { dtlcols@#1 }
    \csedef{dtl@ci@#1@#2}{\number\dtlcolumnnum}%
    \__datatool_token_register_put_right:cx
      { dtlkeys@#1 }
    {
      \__datatool_column_markup:Vnnn
         \dtlcolumnnum { #2 } { #3 } { #4 }
    }
  }
}
\cs_generate_variant:Nn \__datatool_add_column_with_header:nnnn
 { nxxn, nxnn, nxxx, VVVV, VVnV }
\newcommand*{\@dtl@updatekeys}[3]{%
  \@sDTLifhaskey{#1}{#2}%
  {%
    \int_set:Nn \dtlcolumnnum { \dtlcolumnindex { #1 } { #2 } }
    \__datatool_get_keydata:NNNNNvV
      \@dtl@key \@dtl@type \@dtl@colhead \@dtl@before \@dtl@after
      { dtlkeys@#1 } \dtlcolumnnum
    \ifstrempty{#3}%
    {%
    }%
    {%
      \let\@dtl@oldtype\@dtl@type
      \@dtl@checknumerical{#3}%
      \ifnum \@dtl@datatype = \c_datatool_unknown_int
      \else
        \ifdefempty{\@dtl@type}%
        {%
          \edef\@dtl@type{\number\@dtl@datatype}%
        }%
        {%
          \ifcase\@dtl@datatype % string
            \def\@dtl@type{0}%
          \or % int
          \or % real
            \ifnum\@dtl@type=1\relax
              \def\@dtl@type{2}%
            \fi
          \or % currency
            \ifnum\@dtl@type>0\relax
              \def\@dtl@type{3}%
            \fi
          \fi
        }%
      \fi
      \ifx\@dtl@oldtype\@dtl@type
      \else
        \tl_if_empty:NTF \@dtl@type
         {
           \int_set:Nn \@dtl@datatype { \c_datatool_unknown_int }
         }
         {
           \int_set:Nn \@dtl@datatype { \@dtl@type }
         }
        \bool_if:NTF \l__datatool_db_global_bool
        {
          \__datatool_token_register_gconcat_middle:cNxN
           { dtlkeys@#1 }
          \@dtl@before
          {
            \__datatool_column_markup:VnVV
              \dtlcolumnnum
              { #2 }
              \@dtl@datatype
              \@dtl@colhead
          }
          \@dtl@after
        }
        {
          \__datatool_token_register_concat_middle:cNxN
           { dtlkeys@#1 }
          \@dtl@before
          {
            \__datatool_column_markup:VnVV
              \dtlcolumnnum
              { #2 }
              \@dtl@datatype
              \@dtl@colhead
          }
          \@dtl@after
        }
      \fi
    }%
  }%
  {%
    \bool_if:NTF \l__datatool_db_global_bool
    {
      \int_gincr:c { dtlcols @ #1}
      \int_set_eq:Nc \dtlcolumnnum { dtlcols @ #1 }
      \tl_gset:cV { dtl@ci@#1@#2 } \dtlcolumnnum
    }
    {
      \int_incr:c { dtlcols @ #1}
      \int_set_eq:Nc \dtlcolumnnum { dtlcols @ #1 }
      \tl_set:cV { dtl@ci@#1@#2 } \dtlcolumnnum
    }
    \tl_if_empty:nTF { #3 }
    {
      \tl_clear:N \@dtl@type % don't know data type yet
      \int_set_eq:NN \@dtl@datatype \c_datatool_unknown_int
    }
    {
      \@dtl@checknumerical { #3 }
      \int_compare:nNnTF
        { \@dtl@datatype } = { \c_datatool_unknown_int }
      {
        \tl_clear:N \@dtl@type
      }
      {
        \tl_set:NV \@dtl@type \@dtl@datatype
      }
    }
    \bool_if:NTF \l__datatool_db_global_bool
    {
      \__datatool_token_register_gput_right:cx
        { dtlkeys @ #1 }
      {
        \__datatool_column_markup:VnVn
          \dtlcolumnnum
          { #2 }
          \@dtl@datatype
          { #2 }
      }
    }
    {
      \__datatool_token_register_put_right:cx
       { dtlkeys @ #1 }
      {
        \__datatool_column_markup:VnVn
          \dtlcolumnnum
          { #2 }
          \@dtl@datatype
          { #2 }
      }
    }
  }
}
\cs_new:Nn \__datatool_update_meta_data_with_type:nnn
{
  \@sDTLifhaskey { #1 } { #2 }
  {
    \int_set:Nn \dtlcolumnnum
       { \dtlcolumnindex { #1 } { #2 } }
    \__datatool_get_props:NNNNNvV
       \l__datatool_item_key_tl
       \l__datatool_item_type_tl
       \l__datatool_item_head_tl
       \l__datatool_before_tl
       \l__datatool_after_tl
       { dtlkeys@ #1 } \dtlcolumnnum
    \tl_if_empty:NT \l__datatool_item_type_tl
     {
       \tl_set:Nn \l__datatool_item_type_tl
         { \c_datatool_unknown_int }
     }
   \int_set:Nn \l__datatool_tmp_datatype_int
     { \l__datatool_item_type_tl }
   \int_set:Nn \@dtl@datatype { #3 }
   \__datatool_update_datatype:
   \int_compare:nNnF { \@dtl@datatype } = { \l__datatool_tmp_datatype_int }
    {
     \__datatool_dtlkeys_set:nx { #1 }
      {
        \exp_not:V \l__datatool_before_tl
        \__datatool_column_markup:VnVV
          \dtlcolumnnum
          { #2 }
          \@dtl@datatype
          \l__datatool_item_head_tl
        \exp_not:V \l__datatool_after_tl
      }
    }
  }
  {
    \bool_if:NTF \l__datatool_db_global_bool
    {
      \int_gincr:c { dtlcols @ #1}
      \int_set_eq:Nc \dtlcolumnnum { dtlcols @ #1 }
      \tl_gset:cV { dtl@ci@#1@#2 } \dtlcolumnnum
    }
    {
      \int_incr:c { dtlcols @ #1}
      \int_set_eq:Nc \dtlcolumnnum { dtlcols @ #1 }
      \tl_set:cV { dtl@ci@#1@#2 } \dtlcolumnnum
    }
    \bool_if:NTF \l__datatool_db_global_bool
    {
      \__datatool_token_register_gput_right:cx
        { dtlkeys @ #1 }
      {
        \__datatool_column_markup:Vnnn
          \dtlcolumnnum
          { #2 } { #3 } { #2 }
      }
    }
    {
      \__datatool_token_register_put_right:cx
       { dtlkeys @ #1 }
      {
        \__datatool_column_markup:Vnnn
          \dtlcolumnnum
          { #2 } { #3 } { #2 }
      }
    }
  }
}
\cs_generate_variant:Nn
 \__datatool_update_meta_data_with_type:nnn
 { nnV, VVV }
\cs_new:Nn \__datatool_update_meta_data_col_index_with_type:nnn
{
  \int_compare:nNnTF
    { #2 } > { \DTLcolumncount { #1 } }
   {
     \int_compare:nNnTF
       { #2 } = { \DTLcolumncount { #1 } + \c_one_int }
      {
        \tl_set:Nn \l__datatool_item_key_tl { \dtldefaultkey #2 }
        \int_gincr:c { dtlcols @ #1}
        \bool_if:NTF \l__datatool_db_global_bool
        {
          \__datatool_token_register_gput_right:cx
            { dtlkeys @ #1 }
          {
            \__datatool_column_markup:nVnV
              { #2 }
              \l__datatool_item_key_tl
              { #3 }
              \l__datatool_item_key_tl
          }
        }
        {
          \__datatool_token_register_put_right:cx
           { dtlkeys @ #1 }
          {
            \__datatool_column_markup:nVnV
              { #2 }
              \l__datatool_item_key_tl
              { #3 }
              \l__datatool_item_key_tl
          }
        }
      }
      {
        \PackageError { datatool }
         {
           Invalid ~ column ~ index ~ #2 . ~ A ~ new ~ column ~
           requires ~ the ~ index ~ to ~ be ~ one ~ more ~ than ~
           the ~ current ~ column ~ count
         }
         {
           The ~ database ~ ` #1 ' ~ only ~ has ~
           \DTLcolumncount { #1 } ~ column(s). ~ The ~ index ~
           #2 ~ is ~ too ~ large
         }
      }
   }
   {
     \int_compare:nNnTF { #2 } < { \c_one_int }
      {
        \PackageError { datatool }
         {
           Invalid ~ column ~ index ~ #2 . ~ The ~ column ~
           index ~ must ~ start ~ at ~ 1
         }
         {
           The ~ supplied ~ column ~ index ~ to ~ update ~
           column ~ meta ~ data ~ must ~ be ~ from ~ 1 ~
           to ~ one ~ more ~ than ~ the ~ total ~ number ~
           of ~ columns ~ (\DTLcolumncount { #1 }+1 ~ for ~
           database ~ `#1' )
         }
      }
      {
        \__datatool_get_props:NNNNNvn
           \l__datatool_item_key_tl
           \l__datatool_item_type_tl
           \l__datatool_item_head_tl
           \l__datatool_before_tl
           \l__datatool_after_tl
           { dtlkeys@ #1 } { #2 }
        \tl_if_empty:NT
           \l__datatool_item_type_tl
         {
           \tl_set:Nn
             \l__datatool_item_type_tl
             { \c_datatool_unknown_int }
         }
        \int_compare:nNnF
           { \l__datatool_item_type_tl }
            =
           { \c_datatool_string_int }
         {
           \int_compare:nNnT
               { #3 } > { \l__datatool_item_type_tl }
            {
              \bool_if:NTF \l__datatool_db_global_bool
              {
                \__datatool_token_register_gconcat_middle:cNxN
                 { dtlkeys@ #1 }
                \l__datatool_before_tl
                 {
                  \__datatool_column_markup:nVnV
                     { #2 }
                    \l__datatool_item_key_tl
                     { #3 }
                    \l__datatool_item_head_tl
                 }
                \l__datatool_after_tl
              }
              {
                \__datatool_token_register_concat_middle:cNxN
                 { dtlkeys@#1 }
                \l__datatool_before_tl
                 {
                  \__datatool_column_markup:nVnV
                     { #2 }
                    \l__datatool_item_key_tl
                     { #3 }
                    \l__datatool_item_head_tl
                 }
                \l__datatool_after_tl
              }
            }
         }
      }
   }
}
\cs_generate_variant:Nn
\__datatool_update_meta_data_col_index_with_type:nnn
 { nnV, VVV, VnV }
\newrobustcmd*{\DTLsetheader}{\@ifstar\@sDTLsetheader\@DTLsetheader}
\newcommand*{\@DTLsetheader}[3]{%
  \DTLifdbexists{#1}%
  {%
    \@sDTLifhaskey{#1}{#2}%
    {%
      \@sDTLsetheader{#1}{#2}{#3}%
    }%
    {%
      \PackageError{datatool}{Database ~ `#1' ~ doesn't ~ contain ~ key ~
      `#2'}{}%
    }%
  }%
  {%
    \PackageError{datatool}{Database ~ `#1' ~ doesn't ~ exist}{}%
  }%
}
\newcommand*{\@sDTLsetheader}[3]{%
  \expandafter\dtlcolumnnum\expandafter
    =\dtlcolumnindex{#1}{#2}\relax
  \@dtl@setheaderforindex{#1}{\dtlcolumnnum}{#3}%
}
\newcommand*{\@dtl@setheaderforindex}[3]{%
  \__datatool_get_keydata:NNNNNve
    \@dtl@key \@dtl@type \@dtl@colhead \@dtl@before \@dtl@after
    { dtlkeys@#1 } { \int_eval:n { #2 } }
  \tl_if_empty:NTF \@dtl@type
   {
     \int_set_eq:NN \@dtl@datatype \c_datatool_unknown_int
   }
   {
     \int_set:Nn \@dtl@datatype { \@dtl@type }
   }
  \@dtl@colhead={#3}%
  \tl_set:Nx \@dtl@colnum { \int_eval:n { #2 } }
  \bool_if:NTF \l__datatool_db_global_bool
  {
    \__datatool_token_register_gconcat_middle:cNxN
     { dtlkeys@#1 }
     \@dtl@before
    {
      \__datatool_column_markup:VVVV
        \@dtl@colnum
        \@dtl@key
        \@dtl@datatype
        \@dtl@colhead
    }
    \@dtl@after
  }
  {
    \__datatool_token_register_concat_middle:cNxN
     { dtlkeys@#1 }
    \@dtl@before
    {
      \__datatool_column_markup:VVVV
        \@dtl@colnum
        \@dtl@key
        \@dtl@datatype
        \@dtl@colhead
    }
    \@dtl@after
  }
}
\cs_new:Nn \__datatool_process_new_value:n
{
  \tl_set:Nn \l__datatool_item_value_tl { #1 }
  \datatool_if_null:NTF
    \l__datatool_item_value_tl
  {
    \int_set_eq:NN \@dtl@datatype \c_datatool_unknown_int
  }
  {
   \tl_if_head_eq_meaning:nNTF
      { #1 } \dtlspecialvalue
    {
      \group_begin:
        \exp_args:Nx \__datatool_parse_datum:n { #1 }
       \exp_args:NNNV
      \group_end:
       \int_set:Nn \@dtl@datatype \@dtl@datatype
      \@dtl@toks { #1 }
      \tl_set:NV \l__datatool_item_value_tl \@dtl@toks
    }
    {
      \@dtl@setnewvalue { #1 }
      \tl_set:NV \l__datatool_item_value_tl \@dtl@toks
      \bool_if:NTF \l__datatool_db_store_datum_bool
       {
         \__datatool_parse:N \l__datatool_item_value_tl
       }
       {
         \exp_args:NV \__datatool_parse_datum:n
           \l__datatool_item_value_tl
       }
      \__datatool_to_weird_datum_no_parse:N
        \l__datatool_item_value_tl
      \datatool_if_null:NF
        \l__datatool_item_value_tl
       {
         \__datatool_token_register_set:NV
           \@dtl@toks
           \l__datatool_item_value_tl
       }
    }
  }
}
\cs_generate_variant:Nn
  \__datatool_process_new_value:n
  { V }
\newcommand*{\@dtl@storeandupdate}[3]{%
  \__datatool_process_new_value:n { #3 }
  \__datatool_update_meta_data_with_type:nnV
     { #1 } { #2 } \@dtl@datatype
}
\NewDocumentCommand \DTLnewdbentry { s }
{
  \int_zero:N \dtlcolumnnum
  \IfBooleanTF { #1 } { \@sDTLnewdbentry } { \@DTLnewdbentry }
}
\newcommand{\@DTLnewdbentry}[3]{%
  \DTLifdbexists { #1 }
  {
    \exp_args:Nv \int_if_zero:nTF { dtlrows@#1 }
    {
      \PackageError { datatool }
      {
        Can't ~ add ~ an ~ entry ~ to ~ the ~ last ~ row ~ of ~ `#1': ~
        database ~ has ~ no ~ rows
      }
      {
        You ~ need ~ to ~ create ~ a ~ new ~ row ~ before ~ you ~
        can ~ add ~ an ~ entry ~ to ~ it ~ with ~
       \token_to_str:N \DTLnewrow {#1} ~ or ~
       \token_to_str:N \DTLaction [name={#1}] { new ~ row }
      }
    }
    {
      \@sDTLnewdbentry { #1 } { #2 } { #3 }
    }
  }
  {
    \PackageError { datatool }
     {
       Can't ~ add ~ new ~ entry ~ to ~ database ~ `#1': ~
       database ~ doesn't ~ exist
     }
     {
       You ~ need ~ to ~ define ~ the ~ database ~ first ~ with ~
       \token_to_str:N \DTLnewdb {#1} ~ or ~
       \token_to_str:N \DTLaction [name={#1}] { new }
     }
  }
}
\newcommand*{\@sDTLnewdbentry}[3]{%
  \@dtl@storeandupdate { #1 } { #2 } { #3 }
  \int_set:Nn \dtlcolumnnum
    { \dtlcolumnindex { #1 } { #2 } }
  \tl_set:Nn \l__datatool_item_key_tl { #2 }
  \@s@DTLnewdbentry { #1 }
}
\newcommand*{\@s@DTLnewdbentry}[1]{%
  \dtlgetrow { #1 } { \int_use:c { dtlrows@#1} }
  \exp_args:NNV
    \dtlgetentryfromcurrentrow
    \dtl@entry
    \dtlcolumnnum
  \datatool_if_null:NTF \dtl@entry
  {
    \__datatool_token_register_put_right:Nx
     \dtlcurrentrow
      {
        \__datatool_row_element_markup:VV
          \dtlcolumnnum
          \@dtl@toks
      }
    \__datatool_dtldb_concat:nNxN
       { #1 }
      \dtlbeforerow
       {
         \__datatool_row_markup:vV
           { dtlrows@#1 }
          \dtlcurrentrow
       }
      \dtlafterrow
    \dtl@message
    {
     Added ~ \l__datatool_item_key_tl \c_space_tl ~
      -> ~ \the\@dtl@toks \c_space_tl ~ to ~ database ~ `#1'
    }
  }
  {
    \PackageError { datatool }
    {
      Can't ~ add ~ entry ~ with ~ ID ~
      `\l__datatool_item_key_tl' ~ to ~
      current ~ row ~ of ~ database ~ `#1' ~ in ~ column ~
      \int_use:N \dtlcolumnnum
    }
    {
      There ~ is ~ already ~ an ~ entry ~ in ~ this ~
      column ~ in ~ the ~ current ~ row
    }
  }
}
\newcommand{\DTLifdbexists}[3]{
  \ifcsdef { dtldb@#1 } { #2 } { #3 }
}
\NewDocumentCommand \DTLdbNewRow {  }
 {
   \@sDTLnewrow { \l__datatool_default_dbname_tl }
 }
\NewDocumentCommand \DTLdbNewEntry { m m }
 {
  \@sDTLnewdbentry { \l__datatool_default_dbname_tl } { #1 } { #2 }
 }
\NewDocumentCommand \DTLdbSetHeader { m m }
 {
  \@sDTLsetheader { \l__datatool_default_dbname_tl } { #1 } { #2 }
 }
\NewDocumentCommand \DTLassign { m m m }
{
  \DTLifdbexists { #1 }
  {
    {
      \dtlgetrow { #1 } { #2 }
      \@dtl@assign { #3 } { #1 }
    }
  }
  {
    \PackageError {datatool}
    {
      \token_to_str:N \DTLassign : ~
       Database ~ `#1' ~ doesn't ~ exist
    }
    {
      Check ~ that ~ you ~ have ~ spelt ~ the ~
      database ~ name ~ correctly
    }
  }
}
\NewDocumentCommand \DTLassignfirstmatch { m m m m }
{
  \dtl@assignfirstmatch { #3 } { #1 } { #2 } { #4 }
}
\NewDocumentCommand \xDTLassignfirstmatch { m m m m }
{
  \exp_args:Nx
   \dtl@assignfirstmatch
     { \expandonce { #3 } }
     { #1 } { #2 } { #4 }
}
\newcommand*{\dtl@assignfirstmatch}[4]{
  \DTLifdbexists { #2 }
  {%
    {
      \dtlgetrowindex \dtl@asg@rowidx
        { #2 }
        { \dtlcolumnindex { #2 } { #3 } }
        { #1 }
      \datatool_if_null:NTF \dtl@asg@rowidx
      {
        \PackageError {datatool}
        {
          No ~ match ~ found ~ for ~
          \token_to_str:N \DTLassignfirstmatch
            \tl_to_str:n { { #2 } { #3 } { #1 } { #4 } }
        }
        {
          The ~ database ~ `#2' ~ doesn't ~ contain ~
          an ~ entry ~ that ~ exactly ~ matches ~
          `\tl_to_str:n { #1 } ' ~ for ~ column ~
          `#3'
        }
      }
      {
        \dtlgetrow { #2 } \dtl@asg@rowidx
        \@dtl@assign { #4 } { #2 }
      }
    }
  }
  {
    \PackageError {datatool}
    {
      Can't ~ assign ~ first ~ match : ~ database ~ `#2' ~
      doesn't ~ exist
    }
    {
      Check ~ that ~ you ~ have ~ correctly ~ spelt ~
      the ~ database ~ name
    }
  }
}
\NewDocumentCommand \DTLassignfromcurrentrow { m }
{
  \tl_if_empty:NTF \dtldbname
   {
     \PackageError { datatool }
     {
       Can't ~ assign ~ from ~ current ~ row: ~
       current ~ row ~ not ~ assigned ~ or ~ placeholders ~
       have ~ been ~ changed
     }
     {
       You ~ need ~ to ~ select ~ the ~ current ~ row ~
       (for ~ example ~ with ~ \token_to_str:N \dtlgetrow ) ~
       before ~ you ~ can ~ query ~ the ~ current ~ row
     }
   }
   {
     \DTLifdbexists { \dtldbname }
     {
       \__datatool_assign_cskey_list:nnn { #1 }
         { \dtldbname } { \c_false_bool }
     }
     {
       \PackageError { datatool }
       {
         Can't ~ assign ~ from ~ current ~ row: ~
         database ~ `\dtldbname' ~ doesn't ~ exist
       }
       {
         It ~ seems ~ that ~ a ~ placeholder ~ normally ~ assigned ~
         when ~ a ~ row ~ is ~ selected ~ as ~ the ~ current ~ row ~
         doesn't ~ match ~ a ~ defined ~ database
       }
     }
   }
}
\newcommand*{\@dtl@assign}[2]{%
  \__datatool_assign_cskey_list:nnn { #1 } { #2 } { \c_true_bool }
}
\cs_new:Nn \__datatool_assign_cskey_list:nnn
{
  \keyval_parse:nnn
    { \__datatool_cskey_missing_val:n }
    { \__datatool_assign_parsed_cskey:nnnn { #2 } { #3 } }
    { #1 }
}
\cs_new:Nn \__datatool_assign_parsed_cskey:nnnn
{
  \__datatool_assign_cskey:Nnnn { #3 } { #4 } { #1 } { #2 }
}
\cs_new:Nn \__datatool_assign_cskey:Nnn
{
  \__datatool_assign_cskey:Nnnn { #1 } { #2 } { #3 } { \c_false_bool }
}
\cs_new:Nn \__datatool_gassign_cskey:Nnn
{
  \__datatool_assign_cskey:Nnnn { #1 } { #2 } { #3 } { \c_false_bool }
}
\cs_new:Nn \__datatool_assign_cskey:Nnnn
{
  \tl_if_single:nTF { #1 }
  {
    \tl_gset:Nx \@dtl@dbname { #3 }
    \@sDTLifhaskey { #3 } { #2 }
    {
      \exp_args:NNx \dtlgetentryfromcurrentrow
        #1
        { \dtlcolumnindex { #3 } { #2 } }
      \datatool_if_null:NT #1
        {
          \__datatool_set_null:Nnn { #1 } { #2 } { \@dtl@dbname }
        }
      \bool_if:nT { #4 }
      {
        \tl_gset_eq:NN #1 #1
      }
    }
    {
      \PackageError { datatool }
      {
         Can't ~ assign ~ `\tl_to_str:n { #1 = #2 }': ~ there ~
         is ~ no ~ key ~ `#2' ~ in ~ database ~ `#3'
      }
      {
        Check ~ that ~ you ~ have ~ correctly ~ spelt ~ the ~
        column ~ key
      }
      \bool_if:nTF { #4 }
      {
        \tl_gset_eq:NN #1 \DTLstringnull
      }
      {
        \tl_set_eq:NN #1 \DTLstringnull
      }
    }
  }
  {
    \PackageError { datatool}
    {
       Invalid ~ cs=key ~ assignment ~ syntax ~ in ~
       `\tl_to_str:n { #1 = #2 }': ~
       single ~ control ~ sequence ~ required ~ before ~ `='
    }
    {
      Check ~ that ~ you ~ have ~ the ~ assignment ~ the ~ correct ~
      way ~ round
    }
  }
}
\newcommand*{\@dtl@setnull}[2]{
  \@sDTLifhaskey { \@dtl@dbname } { #2 }
  {
    \@@dtl@setnull #1 {#2}
  }
  {
    \tl_gset_eq:NN #1 \DTLstringnull
  }
}
\newcommand*{\@@dtl@setnull}[2]{%
 \__datatool_gset_null:Nnn #1 { #2 } { \@dtl@dbname }
}
\cs_new:Nn \__datatool_gset_null:Nnn
{
  \@sdtlgetdatatype \@dtl@type { #3 } { #2 }
   \bool_lazy_or:nnTF
     { \tl_if_empty_p:N \@dtl@type }
     { \int_compare_p:n { \@dtl@type < 1 } }
   {
     \tl_gset_eq:NN #1 \DTLstringnull
   }
   {
     \tl_gset_eq:NN #1 \DTLnumbernull
   }
}
\cs_new:Nn \__datatool_set_null:Nnn
{
  \@sdtlgetdatatype \@dtl@type { #3 } { #2 }
   \bool_lazy_or:nnTF
     { \tl_if_empty_p:N \@dtl@type }
     { \int_compare_p:n { \@dtl@type < 1 } }
   {
     \tl_set_eq:NN #1 \DTLstringnull
   }
   {
     \tl_set_eq:NN #1 \DTLnumbernull
   }
}
\newcommand*{\DTLifnull}[3]{%
  \datatool_if_null:nTF { #1 } { #2 } { #3 }
}
\newcommand*{\DTLifnullorempty}[3]{%
  \datatool_if_null_or_empty:nTF { #1 } { #2 } { #3 }
}
\newrobustcmd*{\DTLgetkeydata}{%
  \@ifstar\@sdtlgetkeydata\@dtlgetkeydata
}
\newcommand*{\@dtlgetkeydata}[5]{
  \DTLifdbexists { #2 }
  {
    \@sDTLifhaskey { #2 } { #1 }
    {
      \@sdtlgetkeydata { #1 } { #2 } #3 #4 #5
    }
    {
      \PackageError {datatool}
      {
        Can't ~ get ~ data ~ for ~ key ~ `#1' :
        key ~ `#1' ~ not ~ defined ~ in ~ database ~ `#2'
      }
      {
        Check ~ that ~ you ~ have ~ correctly ~ spelt ~
        the ~ column ~ key ~ and ~ the ~ database ~ name
      }
    }
  }
  {
    \PackageError {datatool}
    {
      Can't ~ get ~ data ~ for ~ key ~ `#1' :
      database ~ `#2' ~ doesn't ~ exist
    }
    {
      Check ~ that ~ you ~ have ~ correctly ~ spelt ~
      the ~ database ~ name
    }
  }
}
\newcommand*{\@sdtlgetkeydata}[5]{
  \@sdtl@getcolumnindex #3 { #2 } { #1 }
  \__datatool_get_props:NNNNNvx
    \@dtl@key #4 #5
    \l__datatool_before_tl
    \l__datatool_after_tl
    { dtlkeys@#2 }
    { #3 }
   \__datatool_token_register_set:NV
     \@dtl@colhead
     \l__datatool_item_head_tl
   \__datatool_token_register_set:NV
     \@dtl@before
     \l__datatool_before_tl
   \__datatool_token_register_set:NV
     \@dtl@after
     \l__datatool_after_tl
}
\newcommand{\dtl@gathervalues}[3][key]{
  \dtlforeachkey
    ( \@dtl@key , \@dtl@col , \@dtl@type , \@dtl@head )
    \in { #2 }
   \do
   {
     \dtlgetentryfromrow \@dtl@tmp { \@dtl@col } #3
     \datatool_if_null:NT \@dtl@tmp
      {
        \@dtl@setnull \@dtl@tmp \@dtl@key
      }
      \tl_set_eq:cN { @dtl@#1@ \@dtl@key } \@dtl@tmp
   }
}
\newcommand{\dtl@g@gathervalues}[3][key]{%
  \dtlforeachkey
    ( \@dtl@key , \@dtl@col , \@dtl@type , \@dtl@head )
    \in { #2 }
   \do
   {
     \dtlgetentryfromrow \@dtl@tmp { \@dtl@col } #3
     \datatool_if_null:NT \@dtl@tmp
      {
        \@dtl@setnull \@dtl@tmp \@dtl@key
      }
      \tl_gset_eq:cN { @dtl@#1@ \@dtl@key } \@dtl@tmp
   }
}
\newtoks\dtlcurrentrow
\newtoks\dtlbeforerow
\newtoks\dtlafterrow
\NewDocumentCommand \dtlgetrow { m m }
{
  \datatool_get_row:nnTF { #1 } { #2 } { } { }
}
\cs_new:Nn \datatool_get_row:nnTF
{
  \int_compare:nNnTF
   { #2 } < { \c_one_int }
   {
     \PackageError { datatool }
      {
        \token_to_str:N \dtlgetrow : ~ invalid ~ row ~
        index ~ \int_eval:n { #1 } ~ for ~ database ~
        ` #1 '
      }
      {
        The ~ row ~ index ~ must ~ be ~ between ~ 1 ~
        and ~ the ~ total ~ number ~ of ~ rows ~ in ~ database
      }
      #4
   }
   {
     \int_compare:nNnTF
      { #2 } > { \DTLrowcount { #1 } }
     {
       \PackageError { datatool }
        {
          \token_to_str:N \dtlgetrow : ~ row ~
          index ~ \int_eval:n { #1 } ~ out ~ of ~ range
           ~ for ~ database ~ ` #1 '
        }
        {
           Database ~ `#1' ~ only ~ has ~ \DTLrowcount{#1} ~ row(s)
        }
        #4
     }
     {
       \int_set:Nn \dtlrownum { #2 }
       \tl_set:Nx \dtldbname { #1 }
       \__datatool_get_row:vx
         { dtldb@#1 } { \int_eval:n { #2 } }
       \int_compare:nNnTF { \l__datatool_row_idx_int } = { -1 }
       {
         \PackageError { datatool }
         {
           \token_to_str:N \dtlgetrow :  ~
           Something's ~ gone ~ wrong. ~ ~
           No ~ such ~ row ~ \int_eval:n { #2 } ~ \c_space_tl ~
           found ~ in ~ database ~ `#1'
         }
         {
           Database ~ `#1' ~ seems ~ to ~ be ~ missing ~ row ~
           \int_eval:n { #2 }
         }
        #4
       }
       { #3 }
     }
   }
}
\cs_new:Nn \datatool_get_row:nnT
{
  \datatool_get_row:nnTF { #1 } { #2 } { #3 } { }
}
\newrobustcmd{\edtlgetrowforvalue}[3]{%
  \__datatool_get_row_for_value:xxx
    { #1 } { \int_eval:n { #2 } } { #3 }
}
\NewDocumentCommand \DTLfetch { m m m m }
{
  \edtlgetrowforvalue
    { #1 } { \dtlcolumnindex { #1 } { #2 } } { #3 }
  \dtlgetentryfromcurrentrow
   \dtlcurrentvalue { \dtlcolumnindex { #1 } { #4 } }
  \dtlcurrentvalue
}
\NewDocumentCommand \dtlgetrowforvalue { m m m }
{
  \__datatool_get_row_for_value:xxn
    { #1 } { \int_eval:n { #2 } } { #3 }
}
\cs_new:Nn \__datatool_get_row_for_value:nnn
{
  \__datatool_get_row_for_value:nnnF { #1 } { #2 } { #3 }
  {
    \PackageError {datatool}
    {
      No ~ row ~ found ~ in ~ database ~ `#1' ~ for ~
      column ~ `\int_eval:n { #2 } ' ~ matching ~
      `\tl_to_str:n { #3 }'
    }
    { }
  }
}
\cs_generate_variant:Nn
 \__datatool_get_row_for_value:nnn
 { xxn , xvn , xxx }
\cs_new:Nn \__datatool_get_row_for_value:nnnTF
{
  \__datatool_get_row_index:Nnnn
    \l__datatool_row_idx_tl { #1 } { #2 } { #3 }
  \datatool_if_null:NTF \l__datatool_row_idx_tl
   { #5 }
   {
     \int_set:Nn \dtlrownum { \l__datatool_row_idx_tl }
     \tl_set:Nx \dtldbname { #1 }
     \__datatool_get_row:vx
      { dtldb@#1 } { \l__datatool_row_idx_tl }
     #4
   }
}
\cs_generate_variant:Nn
 \__datatool_get_row_for_value:nnnTF
 { xxxTF }
\cs_new:Nn \__datatool_get_row_for_value:nnnT
{
   \__datatool_get_row_for_value:nnnTF
    { #1 } { #2 } { #3 } { #4 } { }
}
\cs_generate_variant:Nn
 \__datatool_get_row_for_value:nnnT
 { xxnT , xvnT , xxxT, VVVT }
\cs_new:Nn \__datatool_get_row_for_value:nnnF
{
   \__datatool_get_row_for_value:nnnTF
    { #1 } { #2 } { #3 } { } { #4 }
}
\cs_generate_variant:Nn
 \__datatool_get_row_for_value:nnnF
 { xxnF , xvnF , xxxF }
\newcommand*{\@dtlgetrow}[2]{%
 \__datatool_get_row:nn { #1 } { #2 }
}
\cs_new:Nn \__datatool_get_row:nn
{
 \tl_if_in:nnTF { #1 } { \db@row@id@w #2\db@row@id@end@ }
 {
   \cs_set:Npn \__datatool_get_row:w ##1% before stuff
      \db@row@elt@w% start of the row
        \db@row@id@w #2\db@row@id@end@% row id
          ##2%
        \db@row@id@w #2\db@row@id@end@% row id
      \db@row@elt@end@% end of the row
          ##3% after stuff
      \q_nil
    {
      \dtlbeforerow = {##1}
      \dtlcurrentrow = {##2}
      \dtlafterrow = {##3}
      \int_set:Nn \l__datatool_row_idx_int { #2 }
    }
    \__datatool_get_row:w #1 \q_nil
  }
  {
    \dtlbeforerow = { }
    \dtlcurrentrow = { }
    \dtlafterrow = { }
    \int_set:Nn \l__datatool_row_idx_int { -1 }
  }
}
\cs_generate_variant:Nn \__datatool_get_row:nn
  { vn , vV, vx }
\cs_new:Nn \__datatool_get_row:nnN
{
 \tl_if_in:nnTF { #1 } { \db@row@id@w #2\db@row@id@end@ }
 {
   \cs_set:Npn \__datatool_get_row:w ##1% before stuff
      \db@row@elt@w% start of the row
        \db@row@id@w #2\db@row@id@end@% row id
          ##2%
        \db@row@id@w #2\db@row@id@end@% row id
      \db@row@elt@end@% end of the row
          ##3% after stuff
      \q_nil
    {
      \tl_set:Nn #3 { ##2 }
    }
    \__datatool_get_row:w #1 \q_nil
  }
  {
    \tl_clear:N #3
  }
}
\cs_generate_variant:Nn \__datatool_get_row:nnN
 { vnN, vVN }
\newrobustcmd* \dtlrecombine
{
  \__datatool_dtldb_concat:VNxN
    \dtldbname
    \dtlbeforerow
      {
        \__datatool_row_markup:VV
          \dtlrownum
          \dtlcurrentrow
      }
    \dtlafterrow
}
\newrobustcmd* \dtlrecombineomitcurrent
{
  \dtl@decrementrows{\dtlafterrow}{\dtlrownum}
  \__datatool_dtldb_set:Vx
    \dtldbname
    {
      \exp_not:V \dtlbeforerow
      \exp_not:V \dtlafterrow
    }
  \bool_if:NTF \l__datatool_db_global_bool
   {
     \int_gdecr:c { dtlrows@ \dtldbname }
   }
   {
     \int_decr:c { dtlrows@ \dtldbname }
   }
  \dtl@message
   {
     Removed ~ row ~ \int_use:N \dtlrownum \c_space_tl ~
     from ~ database ~ ` \dtldbname '
   }
}
\NewDocumentCommand \dtlsplitrow { m m m m }
{
  \exp_args:Nnx
    \__datatool_split_row:nnNN
    { #1 } { \int_eval:n { #2 } } #3 #4
}
\cs_new:Nn \__datatool_split_row:nnNN
{
  \__datatool_if_split_row:nnNNF
   { #1 } { #2 } #3 #4
  {
    \PackageWarning { datatool }
     {
       \token_to_str:N \dtlsplitrow : ~
       no ~ column ~ with ~ index ~ \tl_to_str:n { #2 } ~
       in ~ current ~ row
     }
  }
}
\cs_generate_variant:Nn \__datatool_split_row:nnNN
  { VvNN , VVNN, VxNN, VnNN }
\cs_new:Nn \__datatool_if_split_row:nnNNTF
{
 \tl_if_in:nnTF
   { #1 } { \db@col@id@w #2 \db@col@id@end@ }
  {
    \cs_set:Npn \__datatool_split_row:w
      ##1 % before stuff
      \db@col@id@w #2\db@col@id@end@ % column id
        ##2 % unwanted stuff
      \db@col@id@w #2\db@col@id@end@ % column id
      ##3 % after stuff
      \q_nil
      {
         \tl_set:Nn #3 { ##1 }
         \tl_set:Nn #4 { ##3 }
         #5
      }
    \__datatool_split_row:w #1 \q_nil
  }
  {
    \tl_set:Nn #3 { #1 }
    \tl_clear:N #4
    #6
  }
}
\cs_new:Nn \__datatool_if_split_row:nnNNT
{
  \__datatool_if_split_row:nnNNTF
    { #1 } { #2 } #3 #4 { #5 } { }
}
\cs_new:Nn \__datatool_if_split_row:nnNNF
{
  \__datatool_if_split_row:nnNNTF
    { #1 } { #2 } #3 #4 { } { #5 }
}
\NewDocumentCommand \dtlreplaceentryincurrentrow { m m }
{
  \__datatool_replace_in_current:nx
   { #1 } { \int_eval:n { #2 } }
}
\cs_new:Nn \__datatool_replace_in_current:nn
{
  \__datatool_split_row:VnNN
    \dtlcurrentrow
     { #2 }
    \l__datatool_before_tl
    \l__datatool_after_tl
  \__datatool_process_new_value:n { #1 }
  \__datatool_token_register_set:Nx
   \dtlcurrentrow
    {
      \exp_not:V \l__datatool_before_tl
      \__datatool_row_element_markup:nV
        { #2 } \l__datatool_item_value_tl
      \exp_not:V \l__datatool_after_tl
    }
  \__datatool_update_meta_data_col_index_with_type:VnV
    \dtldbname { #2 } \@dtl@datatype
  \dtl@message
   {
     Updated ~ \l__datatool_item_key_tl \c_space_tl ~ -> ~
     \exp_not:n { #1 } ~ in ~ database ~ ` \dtldbname '
   }
}
\cs_generate_variant:Nn
  \__datatool_replace_in_current:nn
  { nx }
\NewDocumentCommand \dtlremoveentryincurrentrow { m }
{
  \__datatool_split_row:VxNN
    \dtlcurrentrow
     { \int_eval:n { #1 } }
    \l__datatool_before_tl
    \l__datatool_after_tl
  \__datatool_token_register_set:Nx
    \dtlcurrentrow
   {
     \exp_not:V \l__datatool_before_tl
     \exp_not:V \l__datatool_after_tl
   }
  \dtl@message
  {
    Removed ~ entry ~ from ~ column ~ \int_eval:n { #1 } \c_space_tl ~
     in ~ database ~ `\dtldbname'
  }
}
\NewDocumentCommand \dtlswapentriesincurrentrow { m m }
{
  \dtlgetentryfromcurrentrow \@dtl@entryI { #1 }
  \dtlgetentryfromcurrentrow \@dtl@entryII { #2 }
  \exp_args:NV \dtlreplaceentryincurrentrow
   \@dtl@entryII { #1 }
  \exp_args:NV \dtlreplaceentryincurrentrow
   \@dtl@entryI { #2 }
}
\NewDocumentCommand \dtlgetentryfromcurrentrow { m m }
{
  \dtlgetentryfromrow { #1 } { #2 } \dtlcurrentrow
}
\NewDocumentCommand \dtlgetentryfromrow { m m m }
{
  \__datatool_get_entry_from_row:NxV
    #1 { \int_eval:n { #2 } } #3
}
\newcommand*{\dtl@getentryfromrow}[3]{%
  \__datatool_get_entry_from_row:Nnn #1 { #2 } { #3 }
}
\cs_new:Nn \__datatool_get_entry_from_row:Nnn
{
  \cs_set:Npn \__datatool_get_entry_from_row:w
     ##1 % before stuff
    \db@col@id@w #2\db@col@id@end@ % Column id
      \db@col@elt@w ##2\db@col@elt@end@ % Value
    \db@col@id@w #2\db@col@id@end@ % Column id
    ##3 % Remaining stuff
    \q_nil { \tl_set:Nn #1 { ##2 } }
  \__datatool_get_entry_from_row:w
    #3
    \db@col@id@w #2 \db@col@id@end@
      \db@col@elt@w \c_datatool_nullvalue_tl \db@col@elt@end@
    \db@col@id@w #2 \db@col@id@end@
    \q_nil
  \__datatool_rm_weird_datum:N #1
}
\cs_generate_variant:Nn
 \__datatool_get_entry_from_row:Nnn
 { NVV, NxV, NVn }
\NewDocumentCommand \dtlappendentrytocurrentrow { m m }
{
  \__datatool_process_new_value:n { #2 }
  \__datatool_update_meta_data_with_type:nnV
    { \dtldbname } { #1 } \@dtl@datatype
  \int_set:Nn \dtlcolumnnum
    { \dtlcolumnindex { \dtldbname } { #1 } }
  \exp_args:NNV \dtlgetentryfromcurrentrow
     \dtl@entry \dtlcolumnnum
  \datatool_if_null:NTF \dtl@entry
    {
        \bool_if:NTF \l__datatool_db_global_bool
        {
          \__datatool_token_register_gput_right:Nx
            \dtlcurrentrow
          {
            \__datatool_row_element_markup:VV
              \dtlcolumnnum
              \l__datatool_item_value_tl
          }
        }
        {
          \__datatool_token_register_put_right:Nx
            \dtlcurrentrow
          {
            \__datatool_row_element_markup:VV
              \dtlcolumnnum
              \l__datatool_item_value_tl
          }
        }
         \dtl@message
           {
             Appended ~ #1 \c_space_tl ->
             \exp_not:V \l__datatool_item_value_tl \c_space_tl ~
             to ~ current ~ row ~ of ~ database ~ `\dtldbname'
           }
       }
       {
        \PackageError {datatool}
         {
           Can't ~ append ~ entry ~ to ~ row: ~ there ~ is ~
           already ~ an ~ entry ~ for ~ key ~ `#1' ~ in ~
           the ~ current ~ row
         }
         {
           Use ~ \token_to_str:N \dtlupdateentryincurrentrow
           \c_space_tl ~ to ~ replace ~ an ~ existing ~ entry
         }
       }
}
\NewDocumentCommand \dtlupdateentryincurrentrow { m m }
{
  \__datatool_process_new_value:n { #2 }
  \__datatool_update_meta_data_with_type:nnV
    { \dtldbname } { #1 } \@dtl@datatype
  \int_set:Nn \dtlcolumnnum
    { \dtlcolumnindex { \dtldbname } { #1 } }
  \exp_args:NNV \dtlgetentryfromcurrentrow
     \dtl@entry \dtlcolumnnum
  \datatool_if_null:NTF \dtl@entry
   {
     \bool_if:NTF \l__datatool_db_global_bool
      {
        \__datatool_token_register_gput_right:Nx
          \dtlcurrentrow
        {
          \__datatool_row_element_markup:VV
            \dtlcolumnnum
            \l__datatool_item_value_tl
        }
      }
      {
        \__datatool_token_register_put_right:Nx
          \dtlcurrentrow
        {
          \__datatool_row_element_markup:VV
            \dtlcolumnnum
            \l__datatool_item_value_tl
        }
      }
     \dtl@message
       {
          Appended ~ #1 \c_space_tl -> ~
          \exp_not:V \l__datatool_item_value_tl
          \c_space_tl ~ to ~ the ~ current ~ row ~ of ~
          database ~ `\dtldbname'
       }
   }
   {
     \__datatool_split_row:VVNN
       \dtlcurrentrow
       \dtlcolumnnum
       \l__datatool_before_tl
       \l__datatool_after_tl
     \__datatool_token_register_set:Nx
       \dtlcurrentrow
        {
          \exp_not:V \l__datatool_before_tl
          \__datatool_row_element_markup:VV
            \dtlcolumnnum \l__datatool_item_value_tl
          \exp_not:V \l__datatool_after_tl
        }
      \dtl@message
       {
         Updated ~ #1 \c_space_tl ~ -> ~
         \exp_not:V \l__datatool_item_value_tl \c_space_tl ~
          in ~ database ~ ` \dtldbname '
       }
   }
}
\NewDocumentCommand \DTLgetvalue { m m m m }
{
  \__datatool_get_value:Nnxx #1 { #2 }
    { \int_eval:n { #3 } }
    { \int_eval:n { #4 } }
  \datatool_if_null:NT #1
   {
     \PackageError { datatool }
      {
        There ~ is ~ no ~ element ~ at ~ (row=#3, ~ column=#4) ~
        in ~ database ~ `#2'
      }
      { }
   }
}
\newcommand*{\dtl@getvalue}[4]{%
  \__datatool_get_value:Nnnn #1 { #2 } { #3 } { #4 }
  \datatool_if_null:NT #1
   {
     \PackageError { datatool }
      {
        There ~ is ~ no ~ element ~ at ~ (row=#3, ~ column=#4) ~
        in ~ database ~ `#2'
      }
      { }
   }
}
\cs_new:Nn \__datatool_get_value:Nnnn
{
  \cs_set:Npn \__datatool_get_value:w ##1 % stuff before row <r>
     \db@row@id@w #3\db@row@id@end@ % row <r> id
        ##2 % stuff in row <r> before column <c>
     \db@col@id@w #4\db@col@id@end@ % column <c> id
       \db@col@elt@w ##3\db@col@elt@end@ % value
     \db@col@id@w #4\db@col@id@end@ % column <c> id
     ##4 % stuff after value
     \q_nil
      {
        \tl_set:Nn #1 { ##3 }
      }
  \exp_last_unbraced:Nv \__datatool_get_value:w
     { dtldb@ #2 }
     \db@row@id@w #3 \db@row@id@end@
       \db@col@id@w #4 \db@col@id@end@
       \db@col@elt@w
         \c_datatool_nullvalue_tl % undefined value
       \db@col@elt@end@
       \db@col@id@w #4 \db@col@id@end@
     \q_nil
}
\cs_generate_variant:Nn \__datatool_get_value:Nnnn
  { NnVV , Nnxx }
\NewDocumentCommand \DTLgetlocation { m m m m }
{
  \__datatool_get_location:NNnn #1 #2 { #3 } { #4 }
  \datatool_if_null:NT #1
   {
    \PackageError { datatool }
     {
       There ~ is ~ no ~ element ~
       ` \exp_not:n { #4 } ' ~ in ~ database ~ `#3'
     }
     { }
   }
}
\cs_new:Nn \__datatool_get_location:NNnn
{
  \tl_set_eq:NN #1 \dtlnovalue
  \tl_if_head_eq_meaning:nNT
      { #4 } \__datatool_datum:nnnn
   {
     \__datatool_get_location:NnnnnwNNn
      #4 \q_stop #1 #2 { #3 }
   }
  \tl_if_eq:NNT #1 \dtlnovalue
   {
     \cs_set:Npn \__datatool_get_location:w
       ##1 % stuff before value
       \db@col@elt@w #4\db@col@elt@end@ % value
       \db@col@id@w ##2\db@col@id@end@ % column id
       ##3 % stuff after this column
       \db@row@id@w ##4\db@row@id@end@ % row id
       ##5 % stuff after row
       \q_nil
        {
          \tl_set:Nn #1 { ##4 }
          \tl_set:Nn #2 { ##2 }
        }
     \exp_last_unbraced:Nv \__datatool_get_location:w
       { dtldb@#3 } % contents of data base
       \db@col@elt@w
       #4 % value
       \db@col@elt@end@
       \db@col@id@w
          \c_datatool_nullvalue_tl % undefined column id
       \db@col@id@end@
       \db@row@id@w
          \c_datatool_nullvalue_tl % undefined row id
       \db@row@id@end@
       \q_nil
   }
  \tl_if_eq:NNT #1 \dtlnovalue
   {
     \tl_set:Nn \l__datatool_cs_builder_tl
      {
       \cs_set:Npn \__datatool_get_location:w
         ##1 % stuff before value
         \db@col@elt@w
        \__datatool_datum:w
      }
     \tl_put_right:Nx \l__datatool_cs_builder_tl
      {
        \c__datatool_datum_weird_marker_tl
        \exp_not:n { #4 } % string value
        \c__datatool_datum_weird_marker_tl
      }
     \tl_put_right:Nn \l__datatool_cs_builder_tl
      { ##2 } % numeric value
     \tl_put_right:Nx \l__datatool_cs_builder_tl
      {
        \c__datatool_datum_weird_marker_tl
      }
     \tl_put_right:Nn \l__datatool_cs_builder_tl
      { ##3 } % currency symbol
     \tl_put_right:Nx \l__datatool_cs_builder_tl
      {
        \c__datatool_datum_weird_marker_tl
      }
     \tl_put_right:Nn \l__datatool_cs_builder_tl
      { ##4 } % data type
     \tl_put_right:Nx \l__datatool_cs_builder_tl
      {
        \c__datatool_datum_weird_marker_tl
      }
     \tl_put_right:Nn \l__datatool_cs_builder_tl
      {
         \db@col@elt@end@
         \db@col@id@w ##5\db@col@id@end@ % column id
         ##6 % stuff after this column
         \db@row@id@w ##7\db@row@id@end@ % row id
         ##8 % stuff after row
         \q_nil
          {
            \tl_set:Nn #1 { ##7 } % row
            \tl_set:Nn #2 { ##5 } % column
          }
       \exp_last_unbraced:Nv \__datatool_get_location:w
         { dtldb@#3 } % contents of data base
         \db@col@elt@w
      }
     \tl_put_right:Nx \l__datatool_cs_builder_tl
      {
         \__datatool_weird_datum:nnnn
          { #4 } { } { } { }
      }
     \tl_put_right:Nn \l__datatool_cs_builder_tl
      {
         \db@col@elt@end@
         \db@col@id@w
            \c_datatool_nullvalue_tl % undefined column id
         \db@col@id@end@
         \db@row@id@w
            \c_datatool_nullvalue_tl % undefined row id
         \db@row@id@end@
         \q_nil
      }
     \l__datatool_cs_builder_tl
   }
}
\cs_generate_variant:Nn
 \__datatool_get_location:NNnn
 { NNnx , NNnV }
\cs_new:Npn \__datatool_get_location:NnnnnwNNn
  #1 #2 #3 #4 #5 #6 \q_stop #7 #8 #9
{
 \tl_if_empty:nT { #6 }
  {
    \__datatool_get_location:NNnn #7 #8 { #9 } { #2 }
  }
}
\NewDocumentCommand \DTLgetrowindex { s m m m m }
{
  \IfBooleanTF { #1 }
  { \dtlgetrowindex { #2 } { #3 } { #4 } { #5 } }
  { \@DTLgetrowindex { #2 } { #3 } { #4 } { #5 } }
}
\newcommand{\@DTLgetrowindex}[4]{%
  \__datatool_get_row_index:Nnxn #1
   { #2 } { \int_eval:n { #3 } } { #4 }
  \datatool_if_null:NT #1
   {
    \PackageError {datatool}
    {
      There ~ is ~ no ~ element ~ `#4' ~ for ~ column ~
      \int_eval:n { #3 } ~ in ~ database ~ `#2'
    }
    { }
  }
}
\newrobustcmd*{\dtlgetrowindex}[4]{%
  \__datatool_get_row_index:Nnxn #1
   { #2 } { \int_eval:n { #3 } } { #4 }
}
\newcommand*{\xdtlgetrowindex}[4]{%
  \__datatool_get_row_index:Nnxx #1
   { #2 } { \int_eval:n { #3 } } { #4 }
}
\newcommand*{\@dtlgetrowindex}[4]{
  \__datatool_get_row_index:Nnnn #1 { #2 } { #3 } { #4 }
}
\cs_new:Nn \__datatool_get_row_index:Nnnn
{
  \tl_set_eq:NN #1 \dtlnovalue
  \tl_if_head_eq_meaning:nNT { #4 } \__datatool_datum:nnnn
   {
     \__datatool_get_row_index:NnnnnwNnn
      #4 \q_stop #1 { #2 } { #3 }
   }
  \tl_if_eq:NNT #1 \dtlnovalue
   {
     \cs_set:Npn \__datatool_get_row_index:w
        ##1 % before
       \db@col@id@w #3\db@col@id@end@ % column id
       \db@col@elt@w #4\db@col@elt@end@ % value
       \db@col@id@w #3\db@col@id@end@ % column id
        ##2 % remainder of row
       \db@row@id@w ##3\db@row@id@end@ % row id
        ##4 % after row
       \q_nil
        {
          \tl_set:Nn #1 { ##3 }
        }
     \exp_last_unbraced:Nv \__datatool_get_row_index:w
       { dtldb@#2 }
       \db@col@id@w #3\db@col@id@end@% column id
        \db@col@elt@w
          #4
        \db@col@elt@end@
       \db@col@id@w #3\db@col@id@end@% column id
       \db@row@id@w
         \c_datatool_nullvalue_tl % undefined row id
       \db@row@id@end@
       \q_nil
   }
  \tl_if_eq:NNT #1 \dtlnovalue
   {
     \tl_set:Nn \l__datatool_cs_builder_tl
      {
        \cs_set:Npn \__datatool_get_row_index:w
         ##1 % before
        \db@col@id@w #3\db@col@id@end@ % column id
        \db@col@elt@w
        \__datatool_datum:w
      }
     \tl_put_right:Nx \l__datatool_cs_builder_tl
      {
        \c__datatool_datum_weird_marker_tl
        \exp_not:n { #4 }
        \c__datatool_datum_weird_marker_tl
      }
     \tl_put_right:Nn \l__datatool_cs_builder_tl
      { ##2 }
     \tl_put_right:Nx \l__datatool_cs_builder_tl
      {
        \c__datatool_datum_weird_marker_tl
      }
     \tl_put_right:Nn \l__datatool_cs_builder_tl
      { ##3 }
     \tl_put_right:Nx \l__datatool_cs_builder_tl
      {
        \c__datatool_datum_weird_marker_tl
      }
     \tl_put_right:Nn \l__datatool_cs_builder_tl
      { ##4 }
     \tl_put_right:Nx \l__datatool_cs_builder_tl
      {
        \c__datatool_datum_weird_marker_tl
      }
     \tl_put_right:Nn \l__datatool_cs_builder_tl
      {
        \db@col@elt@end@
        \db@col@id@w #3\db@col@id@end@ % column id
         ##5 % remainder of row
        \db@row@id@w ##6\db@row@id@end@ % row id
         ##7 % after row
        \q_nil
         {
           \tl_set:Nn #1 { ##6 }
         }
        \exp_last_unbraced:Nv \__datatool_get_row_index:w
          { dtldb@#2 }
         \db@col@id@w #3\db@col@id@end@% column id
          \db@col@elt@w
      }
     \tl_put_right:Nx \l__datatool_cs_builder_tl
      {
         \__datatool_weird_datum:nnnn
          { #4 } { } { } { }
      }
     \tl_put_right:Nn \l__datatool_cs_builder_tl
      {
          \db@col@elt@end@
         \db@col@id@w #3\db@col@id@end@% column id
         \db@row@id@w
           \c_datatool_nullvalue_tl % undefined row id
         \db@row@id@end@
         \q_nil
      }
    \l__datatool_cs_builder_tl
   }
}
\cs_generate_variant:Nn
 \__datatool_get_row_index:Nnnn
 { Nnnx , Nxxx, Nxvn, Nnxn }
\cs_new:Npn \__datatool_get_row_index:NnnnnwNnn
  #1 #2 #3 #4 #5 #6 \q_stop #7 #8 #9
{
 \tl_set_eq:NN #7 \dtlnovalue
 \tl_if_empty:nT { #6 }
  {
  \tl_set:Nn \l__datatool_cs_builder_tl
   {
     \cs_set:Npn \__datatool_get_row_index:w
      ##1 % before
     \db@col@id@w #9\db@col@id@end@ % column id
     \db@col@elt@w
   }
  \tl_put_right:Nx \l__datatool_cs_builder_tl
   {
      \__datatool_weird_datum:nnnn
       { #2 } { #3 } { #4 } { #5 }
   }
  \tl_put_right:Nn \l__datatool_cs_builder_tl
   {
     \db@col@elt@end@
     \db@col@id@w #9\db@col@id@end@ % column id
      ##2 % remainder of row
     \db@row@id@w ##3\db@row@id@end@ % row id
      ##4 % after row
     \q_nil
      {
        \tl_set:Nn #7 { ##3 }
      }
     \exp_last_unbraced:Nv \__datatool_get_row_index:w
       { dtldb@#8 }
      \db@col@id@w #9\db@col@id@end@% column id
       \db@col@elt@w
   }
  \tl_put_right:Nx \l__datatool_cs_builder_tl
   {
      \__datatool_weird_datum:nnnn
       { #2 } { #3 } { #4 } { #5 }
   }
  \tl_put_right:Nn \l__datatool_cs_builder_tl
   {
      \db@col@elt@end@
      \db@col@id@w #9\db@col@id@end@% column id
      \db@row@id@w
        \c_datatool_nullvalue_tl % undefined row id
      \db@row@id@end@
      \q_nil
   }
  \l__datatool_cs_builder_tl
  \tl_if_eq:NNT #7 \dtlnovalue
   {
     \tl_set:Nn \l__datatool_cs_builder_tl
      {
        \cs_set:Npn \__datatool_get_row_index:w
         ##1 % before
        \db@col@id@w #9\db@col@id@end@ % column id
        \db@col@elt@w
      }
     \tl_put_right:Nx \l__datatool_cs_builder_tl
      {
         \__datatool_weird_datum:nnnn
          { #2 } { ##2 } { ##3 } { ##4 }
      }
     \tl_put_right:Nn \l__datatool_cs_builder_tl
      {
        \db@col@elt@end@
        \db@col@id@w #9\db@col@id@end@ % column id
         ##5 % remainder of row
        \db@row@id@w ##6\db@row@id@end@ % row id
         ##7 % after row
        \q_nil
         {
           \tl_set:Nn #7 { ##6 }
         }
        \exp_last_unbraced:Nv \__datatool_get_row_index:w
          { dtldb@#8 }
         \db@col@id@w #9\db@col@id@end@% column id
          \db@col@elt@w
      }
     \tl_put_right:Nx \l__datatool_cs_builder_tl
      {
         \__datatool_weird_datum:nnnn
          { #2 } { } { } { }
      }
     \tl_put_right:Nn \l__datatool_cs_builder_tl
      {
         \db@col@elt@end@
         \db@col@id@w #9\db@col@id@end@% column id
         \db@row@id@w
           \c_datatool_nullvalue_tl % undefined row id
         \db@row@id@end@
         \q_nil
      }
    \l__datatool_cs_builder_tl
   }
  }
}
\tl_new:N \l__datatool_map_data_tl
\tl_new:N \l__datatool_map_data_loop_body_tl
\tl_new:N \l__datatool_map_data_row_tl
\tl_new:N \l__datatool_map_data_pending_tl
\bool_new:N \l__datatool_map_data_readonly_bool
\bool_new:N \l__datatool_map_data_update_pending_bool
\int_new:N \l__datatool_map_data_max_cols_int
\prop_new:N \l__datatool_map_data_col_type_prop
\keys_define:nn { datatool/mapdata }
{
  name .str_set_x:N = \dtldbname,
  read-only .bool_set:N = \l__datatool_map_data_readonly_bool,
  allow-edits .bool_set_inverse:N = \l__datatool_map_data_readonly_bool,
}
\NewDocumentCommand \DTLmapdata
 { O{} +m }
 {
   \tl_set_eq:NN
      \dtldbname \l__datatool_default_dbname_tl
   \bool_set_true:N \l__datatool_map_data_readonly_bool
   \keys_set:nn { datatool/mapdata } { #1 }
   \DTLifdbexists { \dtldbname }
   {
     \__datatool_map_data_enable:
     \tl_set:Nv \l__datatool_map_data_tl { dtldb@\dtldbname }
     \tl_set:Nn \l__datatool_map_data_loop_body_tl { #2 }
     \int_zero:N \dtlrownum
     \exp_after:wN \__datatool_map_data:w
       \l__datatool_map_data_tl
       \db@row@elt@w
        \db@row@id@w \q_recursion_tail \db@row@id@end@
        \db@row@id@w \q_recursion_tail \db@row@id@end@
       \db@row@elt@end@
     \q_recursion_stop
     \prg_break_point:Nn \__datatool_map_data_break: { }
     \bool_if:NF \l__datatool_map_data_readonly_bool
     {
       \__datatool_append_map_data_pending:
       \int_step_inline:nnn
         { \dtlrownum + 1 } { \DTLrowcount { \dtldbname } }
       {
         \exp_args:Nv \@dtlgetrow { dtldb@\dtldbname } { ##1 }
         \int_incr:N \l__datatool_row_idx_int
         \tl_put_right:Nx \l__datatool_map_data_pending_tl
          {
            \__datatool_row_markup:VV
              \l__datatool_row_idx_int
              \dtlcurrentrow
         }
       }
       \tl_clear:N \l__datatool_tmpa_tl
       \prop_if_empty:NF
            \l__datatool_map_data_col_type_prop
       {
         \int_step_inline:nn
           { \l__datatool_map_data_max_cols_int }
         {
           \__datatool_get_col_data:vn
             { dtlkeys@ \dtldbname }
             { ##1 }
           \__datatool_map_data_get_col_type_prop:n { ##1 }
           \tl_if_empty:NTF \l__datatool_item_type_tl
            {
              \int_set_eq:NN
                \l__datatool_item_type_int
                \c_datatool_unknown_int
            }
            {
              \int_set:Nn \l__datatool_item_type_int
               { \l__datatool_item_type_tl }
            }
           \tl_put_right:Nx \l__datatool_tmpa_tl
           {
              \__datatool_column_markup:nVVV
               { ##1 }
               \l__datatool_item_key_tl
               \l__datatool_item_type_int
               \l__datatool_item_head_tl
           }
           \tl_if_exist:cF
            {
              dtl@ci@ \dtldbname
              @  \l__datatool_item_key_tl
            }
            {
              \bool_if:NTF \l__datatool_db_global_bool
               {
                 \tl_gset:cn
                  {
                    dtl@ci@ \dtldbname
                    @ \l__datatool_item_key_tl
                  }
                  { ##1 }
               }
               {
                 \tl_set:cn
                  {
                    dtl@ci@ \dtldbname
                    @ \l__datatool_item_key_tl
                  }
                  { ##1 }
               }
            }
         }
       }
       \__datatool_dtldb_set:VV
         \dtldbname
         \l__datatool_map_data_pending_tl
       \tl_if_empty:NF \l__datatool_tmpa_tl
        {
          \__datatool_dtlkeys_set:VV
            \dtldbname
            \l__datatool_tmpa_tl
        }
       \bool_if:NTF \l__datatool_db_global_bool
        {
         \int_gset_eq:cN { dtlrows@\dtldbname }
          \l__datatool_row_idx_int
         \int_gset_eq:cN
           { dtlcols@\dtldbname }
           \l__datatool_map_data_max_cols_int
        }
        {
         \int_set_eq:cN { dtlrows@\dtldbname }
          \l__datatool_row_idx_int
         \int_set_eq:cN
           { dtlcols@\dtldbname }
           \l__datatool_map_data_max_cols_int
        }
     }
     \__datatool_map_data_disable:
   }
   {
     \PackageError { datatool }
     { database ~ `\dtldbname' ~ does ~ not ~ exist }
     { }
   }
 }
\cs_new:Nn \__datatool_map_data_break:
 { \prg_map_break:Nn \__datatool_map_data_break: { } }
\NewDocumentCommand \DTLmapdatabreak { s }
 {
   \IfBooleanT { #1 }
   {
     \bool_set_true:N \l__datatool_map_data_readonly_bool
   }
   \__datatool_map_data_break:
 }
\NewDocumentEnvironment { DTLenvmapdata }
 { O{} +b }
 { \DTLmapdata [ #1 ] { #2 } }
 { }
\cs_new:Npn \__datatool_map_data:w
  \db@row@elt@w
    \db@row@id@w #1 \db@row@id@end@
     #2
    \db@row@id@w #3 \db@row@id@end@
  \db@row@elt@end@
{
  \quark_if_recursion_tail_stop:n { #1 }
  \int_set:Nn \dtlrownum { #1 }
  \bool_if:NF \l__datatool_map_data_readonly_bool
  {
    \bool_set_true:N \l__datatool_map_data_update_pending_bool
  }
  \tl_set:Nn \l__datatool_map_data_row_tl { #2 }
  \l__datatool_map_data_loop_body_tl
  \bool_if:NF \l__datatool_map_data_readonly_bool
  {
    \__datatool_append_map_data_pending:
  }
  \__datatool_map_data:w
}
\cs_new:Nn \__datatool_append_map_data_pending:
{
  \bool_if:NT \l__datatool_map_data_update_pending_bool
  {
    \tl_if_empty:NF \l__datatool_map_data_row_tl
    {
      \int_incr:N \l__datatool_row_idx_int
      \tl_put_right:Nx \l__datatool_map_data_pending_tl
       {
         \__datatool_row_markup:VV
           \l__datatool_row_idx_int
           \l__datatool_map_data_row_tl
      }
    }
    \bool_set_false:N \l__datatool_map_data_update_pending_bool
  }
}
\cs_new:Nn \__datatool_map_data_enable:
{
  \cs_set_eq:NN
    \__datatool_map_get:n
    \__datatool_map_get_op:n
  \cs_set_eq:NN
    \__datatool_map_get_values:n
    \__datatool_map_get_values_op:n
  \cs_set_eq:NN
    \__datatool_map_get_values_noerr:n
    \__datatool_map_get_values_noerr_op:n
  \cs_set_eq:NN
    \__datatool_map_row:Nn
    \__datatool_map_row_op:Nn
  \cs_set_eq:cN
    { __datatool_action_row ~ aggregate: }
    \__datatool_action_row_aggregate_op:
  \bool_if:NTF \l__datatool_map_data_readonly_bool
  {
    \RenewDocumentCommand \DTLrmrow { }
     {
       \__datatool_map_data_cs_noedit:N \DTLrmrow
     }
    \RenewDocumentCommand \DTLsetentry { m }
     {
       \__datatool_map_data_cs_noedit:N \DTLsetentry
     }
    \RenewDocumentCommand \DTLrmentry { m }
     {
       \__datatool_map_data_cs_noedit:N \DTLrmentry
     }
  }
  {
    \tl_clear:N \l__datatool_map_data_pending_tl
    \int_zero:N \l__datatool_row_idx_int
    \prop_clear:N \l__datatool_map_data_col_type_prop
    \int_set:Nn \l__datatool_map_data_max_cols_int
     { \DTLcolumncount { \dtldbname } }
    \RenewDocumentCommand \DTLrmrow { }
     {
       \__datatool_map_data_rm_row:
     }
    \RenewDocumentCommand \DTLsetentry { m }
     {
       \__datatool_map_data_set_column:n { ##1 }
     }
    \RenewDocumentCommand \DTLrmentry { m }
     {
       \__datatool_map_data_rm_column:n { ##1 }
     }
  }
}
\cs_new:Nn \__datatool_map_data_disable:
{
  \cs_set_eq:NN
    \__datatool_map_get:n
    \__datatool_map_get_noop:n
  \cs_set_eq:NN
    \__datatool_map_get_values:n
    \__datatool_map_get_values_noop:n
  \cs_set_eq:NN
    \__datatool_map_get_values_noerr:n
    \__datatool_map_get_values_noop:n
  \cs_set_eq:NN
    \__datatool_map_row:Nn
    \__datatool_map_row_noop:Nn
  \RenewDocumentCommand \DTLrmrow { }
  {
    \__datatool_map_data_cs_noop:N \DTLrmrow
  }
  \RenewDocumentCommand \DTLrmentry { m }
  {
    \__datatool_map_data_cs_noop:N \DTLrmentry
  }
  \RenewDocumentCommand \DTLsetentry { m }
  {
    \__datatool_map_data_cs_noop:N \DTLsetentry
  }
  \tl_clear:N \l__datatool_map_data_tl
  \tl_clear:N \l__datatool_map_data_loop_body_tl
  \tl_clear:N \l__datatool_map_data_row_tl
  \cs_set:cn { __datatool_action_row ~ aggregate: }
  {
    \__datatool_action_noop:Nn
      \DTLmapdata
      { row ~ aggregate: }
  }
}
\cs_new:Nn \__datatool_map_data_cs_noop:N
{
  \PackageError { datatool }
  {
    \token_to_str:N #1 \c_space_tl ~ may ~ only ~ be ~ used ~
    in ~ the ~ loop ~ body ~ of ~ \token_to_str:N \DTLmapdata
  }
  { }
}
\cs_new:Nn \__datatool_map_data_cs_noedit:N
{
  \PackageError { datatool }
  {
    \token_to_str:N #1 \c_space_tl ~ requires ~ read-only=false ~
    setting ~ in ~ \token_to_str:N \DTLmapdata
  }
  {
    Use ~ \token_to_str:N \DTLmapdata [ read-only=false ] { ... } ~
    or ~  \token_to_str:N \DTLmapdata [ allow-edits=true ] { ... }
  }
}
\int_new:N \l__datatool_map_data_edit_column_int
\tl_new:N \l__datatool_map_data_edit_key_tl
\tl_new:N \l__datatool_map_data_edit_value_tl
\keys_define:nn { datatool/mapdata/edit }
{
  value .tl_set:N = \l__datatool_map_data_edit_value_tl,
  expand-value .tl_set_x:N = \l__datatool_map_data_edit_value_tl,
  expand-once-value .code =
   {
     \tl_set:No \l__datatool_map_data_edit_value_tl { #1 }
   },
  column .code:n =
   {
     \int_compare:nNnTF { #1 } > { 0 }
     {
       \int_set:Nn \l__datatool_map_data_edit_column_int { #1 }
     }
     {
       \PackageError { datatool }
       {
          Column ~ index ~ must ~ be ~ greater ~ than ~ 0
       }
       { }
     }
   },
  key .str_set_x:N = \l__datatool_map_data_edit_key_tl,
}
\cs_new:Nn \__datatool_if_init_mapdata_edit_options:NnT
{
  \int_zero:N \l__datatool_map_data_edit_column_int
  \tl_clear:N \l__datatool_map_data_edit_key_tl
  \tl_set:Nn \l__datatool_map_data_edit_value_tl { \c_novalue_tl }
  \keys_set:nn { datatool/mapdata/edit } { #2 }
  \int_if_zero:nTF \l__datatool_map_data_edit_column_int
  {
    \tl_if_empty:NTF \l__datatool_map_data_edit_key_tl
    {
      \PackageError { datatool }
      {
        Missing ~ column ~ identifier ~ in ~ \token_to_str:N #1
      }
      {
        You ~ need ~ to ~ use ~ `column' ~ or ~ `key' ~ to ~
        identify ~ the ~ required ~ column
      }
    }
    {
      \tl_if_exist:cTF
        { dtl@ci@\dtldbname @ \l__datatool_map_data_edit_key_tl }
      {
        \int_set:Nn \l__datatool_map_data_edit_column_int
         { \tl_use:c { dtl@ci@\dtldbname @ \l__datatool_map_data_edit_key_tl } }
        #3
      }
      {
        \PackageError { datatool }
        {
           No ~ such ~ column ~ `\l__datatool_map_data_edit_key_tl' ~
           in ~ database ~ `\dtldbname' ~ referenced ~ in ~
           \token_to_str:N #1
        }
        { }
      }
    }
  }
  {
    \tl_if_empty:NF \l__datatool_map_data_edit_key_tl
    {
      \tl_if_exist:cT
        { dtl@ci@\dtldbname @ \l__datatool_map_data_edit_key_tl }
      {
        \int_compare:nNnF
          { \l__datatool_map_data_edit_column_int }
           =
          { \tl_use:c { dtl@ci@\dtldbname @ \l__datatool_map_data_edit_key_tl } }
        {
          \PackageWarning { datatool }
          {
            Incompatible ~ options ~
            column=\l__datatool_map_data_edit_column_int ,
            key={ \l__datatool_map_data_edit_key_tl } ~
            (`\l__datatool_map_data_edit_key_tl' ~ maps ~ to ~
             column ~ index ~
             \tl_use:c { dtl@ci@\dtldbname @ \l__datatool_map_data_edit_key_tl }).
             Ignoring `key'
          }
        }
      }
    }
    #3
  }
}
\NewDocumentCommand \DTLsetentry { m }
{
  \__datatool_map_data_cs_noop:N \DTLsetentry
}
\cs_new:Nn \__datatool_map_data_set_column:n
{
  \__datatool_if_init_mapdata_edit_options:NnT \DTLsetentry { #1 }
  {
    \exp_args:No
     \tl_if_novalue:nTF { \l__datatool_map_data_edit_value_tl }
    {
      \PackageError { datatool }
      {
        Missing ~ value ~ in ~ \token_to_str:N \DTLsetentry
      }
      {
         Use ~ value={} ~ to ~ set ~ an ~ empty ~ value ~
         or ~ use ~ \token_to_str:N \DTLrmentry \c_space_tl ~ to ~
         remove ~ a ~ column
      }
    }
    {
      \__datatool_process_new_value:V
        \l__datatool_map_data_edit_value_tl
      \exp_args:NNx \tl_if_in:NnTF
         \l__datatool_map_data_row_tl
         {
            \exp_not:N \db@col@id@w
              \int_use:N \l__datatool_map_data_edit_column_int
            \exp_not:N \db@col@id@end@
         }
      {
        \exp_args:NVV \dtlsplitrow
           \l__datatool_map_data_row_tl
           \l__datatool_map_data_edit_column_int
           { \l__datatool_map_data_edit_before_tl }
           { \l__datatool_map_data_edit_after_tl }
        \tl_set_eq:NN
           \l__datatool_map_data_row_tl
           \l__datatool_map_data_edit_before_tl
        \tl_put_right:Nx
           \l__datatool_map_data_row_tl
           {
             \__datatool_row_element_markup:VV
               \l__datatool_map_data_edit_column_int
               \l__datatool_item_value_tl
           }
        \tl_put_right:NV
           \l__datatool_map_data_row_tl
           \l__datatool_map_data_edit_after_tl
      }
      {
        \tl_put_right:Nx
           \l__datatool_map_data_row_tl
           {
             \__datatool_row_element_markup:VV
               \l__datatool_map_data_edit_column_int
               \l__datatool_item_value_tl
           }
        \int_compare:nNnT
           { \l__datatool_map_data_edit_column_int }
            >
         { \l__datatool_map_data_max_cols_int }
        {
          \int_set_eq:NN
            \l__datatool_map_data_max_cols_int
            \l__datatool_map_data_edit_column_int
        }
      }
      \tl_if_empty:NTF \l__datatool_map_data_edit_key_tl
      {
        \tl_clear:N \l__datatool_item_key_tl
      }
      {
        \tl_set_eq:NN
          \l__datatool_item_key_tl
          \l__datatool_map_data_edit_key_tl
      }
      \int_set_eq:NN \l__datatool_item_type_int \@dtl@datatype
      \tl_clear:N \l__datatool_item_head_tl
      \__datatool_map_data_get_col_type_prop:V
        \l__datatool_map_data_edit_column_int
      \__datatool_map_data_put_col_type_prop:VVV
        \l__datatool_map_data_edit_column_int
        \l__datatool_item_type_tl
        \l__datatool_item_key_tl
    }
  }
}
\cs_new:Nn \__datatool_map_data_put_col_type_prop:nnn
{
  \prop_put:Nnn \l__datatool_map_data_col_type_prop
  { #1 } { { #2 } { #3 } }
}
\cs_generate_variant:Nn
  \__datatool_map_data_put_col_type_prop:nnn
 { VVV, Vxx, xxx }
\cs_new:Nn \__datatool_map_data_get_col_type_prop:n
{
  \tl_clear:N \l__datatool_item_type_tl
  \prop_get:NnNT
    \l__datatool_map_data_col_type_prop
    { #1 }
    \l__datatool_tmpb_tl
  {
    \tl_set:Nx \l__datatool_item_type_tl
      { \exp_after:wN \use_i:nn \l__datatool_tmpb_tl }
    \tl_set:Nx \l__datatool_tmpb_tl
       { \exp_after:wN \use_ii:nn \l__datatool_tmpb_tl }
    \tl_if_empty:NF \l__datatool_tmpb_tl
    {
      \tl_set_eq:NN \l__datatool_item_key_tl \l__datatool_tmpb_tl
    }
  }
  \exp_args:No \quark_if_no_value:nT { \l__datatool_item_key_tl }
  {
    \tl_set:Nx \l__datatool_item_key_tl { \dtldefaultkey #1 }
  }
  \exp_args:No \quark_if_no_value:nT { \l__datatool_item_head_tl }
  {
    \tl_set_eq:NN \l__datatool_item_head_tl \l__datatool_item_key_tl
  }
  \tl_if_empty:NTF \l__datatool_item_type_tl
  {
    \tl_set:Nx \l__datatool_item_type_tl
      { \int_use:N \l__datatool_item_type_int }
  }
  {
    \int_compare:nNnT
      { \l__datatool_item_type_tl } > { \c_datatool_string_int }
    {
      \int_compare:nNnT
        { \l__datatool_item_type_tl } < { \l__datatool_item_type_int }
      {
        \tl_set:Nx \l__datatool_item_type_tl
         { \int_use:N \l__datatool_item_type_int }
      }
    }
  }
}
\cs_generate_variant:Nn \__datatool_map_data_get_col_type_prop:n
 { V, x }
\NewDocumentCommand \DTLrmentry { m }
{
  \__datatool_map_data_cs_noop:N \DTLrmentry
}
\cs_new:Nn \__datatool_map_data_rm_column:n
{
  \int_zero:N \l__datatool_map_data_edit_column_int
  \keys_set:nn { datatool/mapdata/edit } { #1 }
  \int_if_zero:nTF \l__datatool_map_data_edit_column_int
  {
    \PackageError { datatool }
    { Missing ~ column ~ in ~ \token_to_str:N \DTLrmentry}
    { }
  }
  {
    \exp_args:NNx \tl_if_in:NnT
       \l__datatool_map_data_row_tl
       {
          \exp_not:N \db@col@id@w
            \int_use:N \l__datatool_map_data_edit_column_int
          \exp_not:N \db@col@id@end@
       }
    {
      \exp_args:NVV \dtlsplitrow
         \l__datatool_map_data_row_tl
         \l__datatool_map_data_edit_column_int
         { \l__datatool_map_data_edit_before_tl }
         { \l__datatool_map_data_edit_after_tl }
      \tl_set_eq:NN
         \l__datatool_map_data_row_tl
         \l__datatool_map_data_edit_before_tl
      \tl_put_right:NV
         \l__datatool_map_data_row_tl
         \l__datatool_map_data_edit_after_tl
    }
  }
}
\NewDocumentCommand \DTLrmrow { }
{
  \__datatool_map_data_cs_noop:N \DTLrmrow
}
\cs_new:Nn \__datatool_map_data_rm_row:
{
  \tl_clear:N \l__datatool_map_data_row_tl
}
\NewDocumentCommand \DTLmaprow { m m }
{
  \__datatool_map_row:Nn #1 { #2 }
}
\cs_new:Nn \__datatool_map_row_noop:Nn
{
  \__datatool_map_data_cs_noop:N \DTLmaprow
}
\cs_set_eq:NN \__datatool_map_row:Nn \__datatool_map_row_noop:Nn
\cs_new:Nn \__datatool_map_row_op:Nn
{
  \cs_set:Nn \__datatool_map_row_loop_body:n
   {
     \tl_set:Nn #1 { ##1 }
     #2
   }
  \exp_after:wN \__datatool_map_row:w
    \l__datatool_map_data_row_tl
    \db@col@id@w \q_recursion_tail \db@col@id@end@
    \db@col@elt@w \db@col@elt@end@
    \db@col@id@w \q_recursion_tail \db@col@id@end@
  \q_recursion_stop
  \prg_break_point:Nn \__datatool_map_row_break: { }
}
\cs_new:Nn \__datatool_map_row_break:
 { \prg_map_break:Nn \__datatool_map_row_break: { } }
\newcommand{\DTLmaprowbreak}{ \__datatool_map_row_break: }
\cs_new:Nn \__datatool_map_row_loop_body:n { }
\cs_new:Npn \__datatool_map_row:w
  \db@col@id@w #1 \db@col@id@end@
  \db@col@elt@w #2 \db@col@elt@end@
  \db@col@id@w #3 \db@col@id@end@
{
  \quark_if_recursion_tail_stop:n { #1 }
  \int_set:Nn \dtlcolumnnum { #1 }
  \__datatool_map_row_loop_body:n { #2 }
  \__datatool_map_row:w
}
\NewDocumentCommand \DTLmapget { m }
{
  \__datatool_map_get:n { #1 }
}
\cs_new:Nn \__datatool_map_get_noop:n
{
  \PackageError { datatool }
  {
    \token_to_str:N \DTLmapget \c_space_tl ~ is ~ only ~
    available ~ within ~ \token_to_str:N \DTLmapdata
  }
  { }
}
\cs_set_eq:NN
  \__datatool_map_get:n \__datatool_map_get_noop:n
\tl_new:N \l__datatool_map_get_return_tl
\keys_define:nn { datatool/mapdata }
{
   key .code:n =
     {
       \cs_set:Nn
         \__datatool_map_get_action:
         {
           \exp_args:Nx
             \__datatool_map_get_from_key:n { #1 }
         }
     },
   key .value_required:n = true,
   column .code:n =
     {
       \cs_set:Nn
         \__datatool_map_get_action:
         {
           \exp_args:Nx
             \__datatool_map_get_from_idx:n { #1 }
         }
     },
   column .value_required:n = true,
   return .code:n =
     {
       \tl_if_empty:nTF { #1 }
       {
         \cs_set:Nn \__datatool_map_get_action_result:
          { \l__datatool_map_get_return_tl }
       }
       {
         \cs_set:Nn \__datatool_map_get_action_result:
          { \tl_set_eq:NN #1 \l__datatool_map_get_return_tl }
       }
     },
   return .default:n = { },
}
\cs_new:Nn \__datatool_map_get_op:n
{
  \cs_set_eq:NN
    \__datatool_map_get_action:
    \__datatool_map_get_op_missing:
  \cs_set:Nn \__datatool_map_get_action_result:
    { \l__datatool_map_get_return_tl }
  \keys_set:nn { datatool/mapdata } { #1 }
  \__datatool_map_get_action:
  \__datatool_map_get_action_result:
}
\cs_new:Nn \__datatool_map_get_op_missing:
{
   \PackageError { datatool }
   {
      Missing ~ `key' ~ or ~ `column' ~ option ~ in ~ \DTLmapget
   }
   { }
   \tl_set_eq:NN \l__datatool_map_get_return_tl \dtlnovalue
}
\cs_new:Nn \__datatool_map_get_from_idx:n
{
  \tl_if_empty:NTF \l__datatool_map_data_row_tl
  {
    \tl_set_eq:NN \l__datatool_map_get_return_tl \dtlnovalue
  }
  {
     \exp_args:NNno
       \dtl@getentryfromrow
       \l__datatool_map_get_return_tl
       { #1 }
       \l__datatool_map_data_row_tl
     \tl_if_eq:NNT \l__datatool_map_get_return_tl \dtlnovalue
     {
       \__datatool_get_col_type:vn { dtlkeys@ \dtldbname } { #1 }
       \int_case:nnF { \l__datatool_item_type_int }
       {
         { \c_datatool_string_int }
           { \tl_set_eq:NN \l__datatool_map_get_return_tl \DTLstringnull }
         { \c_datatool_unknown_int } { }
       }
       {
         \tl_set_eq:NN \l__datatool_map_get_return_tl \DTLnumbernull
       }
     }
  }
}
\cs_new:Nn \__datatool_map_get_from_key:n
{
  \cs_if_exist:cTF { dtl@ci@ \dtldbname @ #1 }
  {
     \exp_args:Nx
     \__datatool_map_get_from_idx:n
     { \use:c { dtl@ci@ \dtldbname @ #1 } }
  }
  {
    \tl_set_eq:NN \l__datatool_map_get_return_tl \dtlnovalue
  }
}
\NewDocumentCommand \DTLmapgetvalues { s m }
{
  \IfBooleanTF { #1 }
   {
     \__datatool_map_get_values_noerr:n { #2 }
   }
   {
     \__datatool_map_get_values:n { #2 }
   }
}
\cs_new:Nn \__datatool_map_get_values_noop:n
{
  \PackageError { datatool }
  {
    \token_to_str:N \DTLmapgetvalues \c_space_tl ~ is ~ only ~
    available ~ within ~ \token_to_str:N \DTLmapdata
  }
  { }
}
\cs_set_eq:NN
  \__datatool_map_get_values:n \__datatool_map_get_values_noop:n
\tl_new:N \l__datatool_assign_tl
\cs_new:Nn \__datatool_map_get_values_op:n
{
  \keyval_parse:nnn
    { \__datatool_cskey_missing_val:n }
    { \__datatool_map_assign_cskey:nn }
    { #1 }
}
\cs_new:Nn \__datatool_map_get_values_noerr_op:n
{
  \keyval_parse:nnn
    { \__datatool_cskey_missing_val:n }
    { \__datatool_map_assign_cskey_noerr:nn }
    { #1 }
}
\cs_new:Nn \__datatool_map_assign_cskey:nn
{
  \tl_if_exist:cTF { dtl@ci@\dtldbname @ #2 }
   {
     \int_set:Nn \l__datatool_col_idx_int
      { \use:c { dtl@ci@\dtldbname @ #2 } }
     \exp_args:NV
      \__datatool_map_get_from_idx:n
      \l__datatool_col_idx_int
     \tl_set_eq:NN #1 \l__datatool_map_get_return_tl
   }
   {
     \PackageError { datatool }
      {
        Unknown ~ column ~ key ~ `#2' ~ for ~ database ~ \dtldbname
      }
      {
        Check ~ the ~ spelling ~ of ~ the ~ column ~ label ~
        in ~ your ~ assignment ~ \tl_to_str:n { #1 } = #2
      }
     \tl_set_eq:NN #1 \dtlnovalue
   }
}
\cs_new:Nn \__datatool_map_assign_cskey_noerr:nn
{
  \tl_if_exist:cTF { dtl@ci@\dtldbname @ #2 }
   {
     \int_set:Nn \l__datatool_col_idx_int
      { \use:c { dtl@ci@\dtldbname @ #2 } }
     \exp_args:NV
      \__datatool_map_get_from_idx:n
      \l__datatool_col_idx_int
     \tl_set_eq:NN #1 \l__datatool_map_get_return_tl
   }
   {
     \tl_set_eq:NN #1 \dtlnovalue
   }
}
\ExplSyntaxOff
\long\def\@dtlforeachrow(#1,#2)\in#3\do#4{%
  \edef\dtl@tmp{\expandafter\the\csname dtldb@#3\endcsname}%
  \expandafter\@dtl@foreachrow\dtl@tmp
    \db@row@elt@w%
      \db@row@id@w \@nil\db@row@id@end@%
      \db@row@id@w \@nil\db@row@id@end@%
    \db@row@elt@end@%
    \@@{#1}{#2}{#4}\q@nil
}
\long\def\@dtl@foreachrow\db@row@elt@w%
\db@row@id@w #1\db@row@id@end@%
#2\db@row@id@w #3\db@row@id@end@%
\db@row@elt@end@#4\@@#5#6#7\q@nil{%
  \gdef#5{#1}%
  \gdef\@dtl@loopbody{#7}%
  \global\advance\@dtl@foreach@level by 1\relax
  \ifx#5\@nnil
    \expandafter\global\expandafter
      \let\csname @dtl@foreachnext\the\@dtl@foreach@level\endcsname
        =\@dtl@foreachnoop
  \else
    \gdef#6{#2}%
    \expandafter\let
      \csname @dtl@break@\the\@dtl@foreach@level\endcsname
      \dtlbreak
    \gdef\dtlbreak{\expandafter\global\expandafter
      \let\csname @dtl@foreachnext\the\@dtl@foreach@level\endcsname
        =\@dtl@foreachnoop}%
    \expandafter\global\expandafter
      \let\csname @dtl@foreachnext\the\@dtl@foreach@level\endcsname
        =\@dtl@foreachrow
    \@dtl@loopbody
    \expandafter\let\expandafter\dtlbreak
      \csname @dtl@break@\the\@dtl@foreach@level\endcsname
  \fi
  \expandafter\let\expandafter\@dtl@foreachnext
    \csname @dtl@foreachnext\the\@dtl@foreach@level\endcsname
  \global\advance\@dtl@foreach@level by -1\relax
  \@dtl@foreachnext#4\@@{#5}{#6}{#7}\q@nil
}
\long\def\@dtl@foreachnoop#1\@@#2\q@nil{}
\ExplSyntaxOn
\cs_new:Nn \datatool_map_keys_inline:nn
{
  \cs_set:Nn \__datatool_map_keys_fn:nnnn { #2 }
  \tl_set:Nv \l__datatool_keydata_tl { dtlkeys@#1 }
  \tl_if_empty:NF \l__datatool_keydata_tl
  {
    \exp_after:wN \__datatool_map_keys:w
    \l__datatool_keydata_tl
    \db@plist@elt@w
    \db@col@id@w \q_recursion_tail \db@col@id@end@
    \db@key@id@w \db@key@id@end@
    \db@type@id@w \db@type@id@end@
    \db@header@id@w \db@header@id@end@
    \db@col@id@w \q_recursion_tail \db@col@id@end@
    \db@plist@elt@end@
    \q_recursion_stop
  }
}
\cs_new:Nn \datatool_map_keys_function:nN
{
  \cs_set_eq:NN \__datatool_map_keys_fn:nnnn #2
  \tl_set:Nv \l__datatool_keydata_tl { dtlkeys@#1 }
  \tl_if_empty:NF \l__datatool_keydata_tl
  {
    \exp_after:wN \__datatool_map_keys:w
    \l__datatool_keydata_tl
    \db@plist@elt@w
    \db@col@id@w \q_recursion_tail \db@col@id@end@
    \db@key@id@w \db@key@id@end@
    \db@type@id@w \db@type@id@end@
    \db@header@id@w \db@header@id@end@
    \db@col@id@w \q_recursion_tail \db@col@id@end@
    \db@plist@elt@end@
    \q_recursion_stop
  }
}
\cs_new:Npn \__datatool_map_keys:w
    \db@plist@elt@w
    \db@col@id@w #1 \db@col@id@end@
    \db@key@id@w #2 \db@key@id@end@
    \db@type@id@w #3 \db@type@id@end@
    \db@header@id@w #4 \db@header@id@end@
    \db@col@id@w #5 \db@col@id@end@
    \db@plist@elt@end@
{
  \quark_if_recursion_tail_stop:n { #1 }
  \tl_if_empty:nTF { #3 }
  {
    \__datatool_map_keys_fn:nnnn { #2 } { #1 } { \c_datatool_unknown_int } { #4 }
  }
  {
    \__datatool_map_keys_fn:nnnn { #2 } { #1 } { #3 } { #4 }
  }
  \__datatool_map_keys:w
}
\ExplSyntaxOff
\long\def\dtlforeachkey(#1,#2,#3,#4)\in#5\do#6{%
  \gdef\@dtl@loopbody{#6}%
  \edef\@dtl@keys{\expandafter\the\csname dtlkeys@#5\endcsname}%
  \expandafter\@dtl@foreachkey\@dtl@keys
    \db@plist@elt@w%
    \db@col@id@w -1\db@col@id@end@%
    \db@key@id@w \db@key@id@end@%
    \db@type@id@w \db@type@id@end@%
    \db@header@id@w \db@header@id@end@%
    \db@col@id@w -1\db@col@id@end@%
    \db@plist@elt@end@%
    \@@{\@dtl@updatefkcs{#1}{#2}{#3}{#4}}\q@nil
}
\newcommand*{\@dtl@updatefkcs}[8]{%
  \gdef#1{#5}%
  \gdef#2{#6}%
  \gdef#3{#7}%
  \gdef#4{#8}%
}
\long\def\@dtl@foreachkey\db@plist@elt@w%
\db@col@id@w #1\db@col@id@end@%
\db@key@id@w #2\db@key@id@end@%
\db@type@id@w #3\db@type@id@end@%
\db@header@id@w #4\db@header@id@end@%
\db@col@id@w #5\db@col@id@end@%
\db@plist@elt@end@#6\@@#7\q@nil{%
  \ifnum#1=-1\relax
    \let\@dtl@foreachnext\@dtl@foreachnoop
  \else
    #7{#2}{#1}{#3}{#4}%
    \global\advance\@dtl@foreach@level by 1\relax
    \expandafter\let
      \csname @dtl@break@\the\@dtl@foreach@level\endcsname
      \dtlbreak
    \gdef\dtlbreak{\expandafter\global\expandafter
      \let\csname @dtl@foreachnext\the\@dtl@foreach@level\endcsname
        =\@dtl@foreachnoop}%
    \expandafter\global\expandafter
      \let\csname @dtl@foreachnext\the\@dtl@foreach@level\endcsname
        =\@dtl@foreachkey
    \@dtl@loopbody
    \expandafter\let\expandafter\@dtl@foreachnext
      \csname @dtl@foreachnext\the\@dtl@foreach@level\endcsname
    \expandafter\let\expandafter\dtlbreak
      \csname @dtl@break@\the\@dtl@foreach@level\endcsname
    \global\advance\@dtl@foreach@level by -1\relax
  \fi
  \@dtl@foreachnext#6\@@{#7}\q@nil
}
\ExplSyntaxOn
\NewDocumentCommand \dtlforcolumn { s }
{
 \IfBooleanTF { #1 } { \@sdtlforcolumn } { \@dtlforcolumn }
}
\newcommand{\@dtlforcolumn}[4]{
  \DTLifdbexists { #2 }
  {
    \@DTLifhaskey { #2 } { #3 }
    {
      \@sdtlforcolumn { #1 } { #2 } { #3 } { #4 }
    }
    {
      \PackageError { datatool }
       {
         Database ~ `#2' ~ doesn't ~ contain ~ key ~ `#3'
       }
       { }
    }
  }
  {
    \PackageError { datatool }
    { Database ~ `#2' ~ doesn't ~ exist}
    { }
  }
}
\cs_new:Nn \__datatool_for_column:Nnnn
{
  \dtl@forcolumn #1 { #2 } { #3 } { #4 }
}
\cs_generate_variant:Nn  \__datatool_for_column:Nnnn
{ Nven }
\newcommand{\@sdtlforcolumn}[4]{%
 \__datatool_for_column:Nven #1 { dtldb@#2 }
   { \dtlcolumnindex { #2 } { #3 } } { #4 }
}
\NewDocumentCommand \dtlforcolumnidx { s }
{
  \IfBooleanTF { #1 } { \@sdtlforcolumnidx } { \@dtlforcolumnidx }
}
\newcommand{\@sdtlforcolumnidx}[4]{%
 \__datatool_for_column:Nven #1 { dtldb@#2 }
   { \int_eval:n { #3 } } { #4 }
}
\ExplSyntaxOff
\newcommand{\@dtlforcolumnidx}[4]{%
  \DTLifdbexists{#2}%
  {%
    \expandafter\ifnum\csname dtlcols@#2\endcsname<#3\relax
      \PackageError{datatool}{Column index \number#3\space out of
        bounds for database `#2'}{Database `#2' only has
        \expandafter\number\csname dtlcols@#2\endcsname\space
        columns}%
    \else
      \ifnum#3<1\relax
       \PackageError{datatool}{Column index \number#3\space out of
        bounds for database `#2'}{Indices start from 1}%
      \else
        \@sdtlforcolumnidx{#1}{#2}{#3}{#4}%
      \fi
    \fi
  }%
  {%
    \PackageError{datatool}{Database `#2' doesn't exist}{}%
  }%
}
\newcommand{\dtl@forcolumn}[4]{%
  \let\@dtl@oldbreak\dtlbreak
  \def\dtlbreak{\let\@dtl@forcolnext=\@dtl@forcolnoop}%
  \def\@dtl@forcolumn##1% before stuff
    \db@col@id@w #3\db@col@id@end@% column index
      \db@col@elt@w ##2\db@col@elt@end@% entry
    \db@col@id@w #3\db@col@id@end@% column index
    ##3% after stuff
    \q@nil{%
      \def#1{##2}% assign value to <cs>
      \ifx#1\@nnil
        \let\@dtl@forcolnext=\@dtl@forcolnoop
      \else
        #4%
        \let\@dtl@forcolnext=\@dtl@forcolumn
      \fi
      \@dtl@forcolnext##3\q@nil
    }%
  \@dtl@forcolumn#2%
   \db@col@id@w #3\db@col@id@end@%
     \db@col@elt@w \@nil\db@col@elt@end@%
   \db@col@id@w #3\db@col@id@end@\q@nil
  \let\dtlbreak\@dtl@oldbreak
}
\def\@dtl@forcolnoop#1\q@nil{}

\newcount\dtlforeachlevel
\newcounter{DTLrow}
\newcounter{DTLrowi}
\newcounter{DTLrowii}
\newcounter{DTLrowiii}
\def\theHDTLrow{\arabic{DTLrow}}
\def\theHDTLrowi{\theHDTLrow.\arabic{DTLrowi}}
\def\theHDTLrowii{\theHDTLrowi.\arabic{DTLrowii}}
\def\theHDTLrowiii{\theHDTLrowii.\arabic{DTLrowiii}}
\NewDocumentCommand\DTLrowreset{}{%
 \ifnum\dtlforeachlevel > 2
   \PackageError{datatool}{DTLrow counter level too deep}%
   {The current counter level is \number\dtlforeachlevel}%
 \else
   \refstepcounter{DTLrow}%
   \setcounter{DTLrow\romannumeral\numexpr\dtlforeachlevel+\@ne}{0}%
 \fi
}
\NewDocumentCommand\DTLrowincr{}{%
 \ifnum\dtlforeachlevel > 2
   \PackageError{datatool}{DTLrow counter level too deep}%
   {The current counter level is \number\dtlforeachlevel}%
 \else
   \refstepcounter{DTLrow\romannumeral\numexpr\dtlforeachlevel+\@ne}%
 \fi
}
\NewDocumentCommand\DTLtherow{}{%
 \ifnum\dtlforeachlevel > 2
   \PackageError{datatool}{DTLrow counter level too deep}%
   {The current counter level is \number\dtlforeachlevel}%
 \else
   \csuse{theDTLrow\romannumeral\numexpr\dtlforeachlevel+\@ne}%
 \fi
}
\newcount\dtl@rowi
\newcount\dtl@rowii
\newcount\dtl@rowiii
\ExplSyntaxOn
\int_new:N \g__filtered_row_i_int
\int_new:N \g__filtered_row_ii_int
\int_new:N \g__filtered_row_iii_int
\ExplSyntaxOff
\newtoks\@dtl@curi
\newtoks\@dtl@previ
\newtoks\@dtl@nexti
\newtoks\@dtl@curii
\newtoks\@dtl@previi
\newtoks\@dtl@nextii
\newtoks\@dtl@curiii
\newtoks\@dtl@previii
\newtoks\@dtl@nextiii
\newcommand*{\DTLsavelastrowcount}[1]{%
\ifnum\dtlforeachlevel>2\relax
  \def#1{0}%
\else
  \ifnum\dtlforeachlevel<0\relax
    \def#1{0}%
  \else
    \@dtl@tmpcount=\dtlforeachlevel
    \advance\@dtl@tmpcount by 1\relax
    \edef#1{\expandafter\number
      \csname c@DTLrow\romannumeral\@dtl@tmpcount\endcsname}%
  \fi
\fi}
\NewDocumentEnvironment{DTLenvforeach}{O{\boolean{true}}mm+b}
{%
  \@DTLforeach[#1]{#2}{#3}{#4}%
}
{}
\NewDocumentEnvironment{DTLenvforeach*}{O{\boolean{true}}mm+b}
{%
  \@sDTLforeach[#1]{#2}{#3}{#4}%
}
{}
\newrobustcmd*{\DTLforeach}{\@ifstar\@sDTLforeach\@DTLforeach}
\ExplSyntaxOn
\cs_new:Nn \__datatool_for_each:nnnnN
{
  \DTLifdbexists { #2 }
  {
    \refstepcounter {DTLrow}
    \tl_gset:Nx \@dtl@dbname { #2 }
    \int_gincr:N \dtlforeachlevel
    \int_compare:nNnTF
       { \dtlforeachlevel } > { 3 }
    {
      \PackageError{datatool}
        {
          \token_to_str:N \DTLforeach \c_space_tl ~ nested ~
          too ~ deeply
        }
        {
          Only ~ 3 ~ levels ~ are ~ allowed
        }
    }
    {
      \@DTLifdbempty { #2 }
      { }
      {
        \int_gzero:c
         { c@DTLrow\romannumeral\dtlforeachlevel }
        \int_gzero:c
         {
            g__filtered_row_
             \romannumeral \dtlforeachlevel
            _int
         }
        \cs_gset_eq:cN
          { @dtl@iffirstrow\the\dtlforeachlevel }
          \DTLiffirstrow
        \cs_gset_eq:NN
          \DTLiffirstrow
          \__datatool_foreach_if_first_row:TF
        \cs_gset_eq:cN
          { @dtl@iflastrow\the\dtlforeachlevel }
          \DTLiflastrow
        \cs_gset_eq:NN
          \DTLiflastrow
          \__datatool_foreach_if_last_row:TF
        \cs_gset_eq:cN
          { @dtl@ifoddrow\the\dtlforeachlevel }
          \DTLifoddrow
        \cs_gset_eq:NN
         \DTLifoddrow
         \__datatool_foreach_if_odd_row:TF
        \tl_gset_eq:cN
          { @dtl@dbname@\romannumeral\dtlforeachlevel }
            \@dtl@dbname
        \bool_if:NTF #5
         {
           \tl_gset:cn
             { @dtl@ro@\romannumeral\dtlforeachlevel }
             { \c_one_int }
         }
         {
           \tl_gset:cn
             { @dtl@ro@\romannumeral\dtlforeachlevel }
             { \c_zero_int }
         }
        \exp_args:Nc \dtlgforint
          { dtl@row\romannumeral\dtlforeachlevel }
          =1\to\csname dtlrows@#2\endcsname\step1\do
        {
          \exp_args:Nnx
            \dtlgetrow { #2 }
            { \int_use:c { dtl@row\romannumeral\dtlforeachlevel } }
          \__datatool_token_register_gset_eq:cN
            { @dtl@cur\romannumeral\dtlforeachlevel }
            \dtlcurrentrow
           \bool_if:NF #5
            {
              \__datatool_token_register_gset_eq:cN
                { @dtl@prev\romannumeral\dtlforeachlevel }
                 \dtlbeforerow
              \__datatool_token_register_gset_eq:cN
                { @dtl@next\romannumeral\dtlforeachlevel }
                 \dtlafterrow
            }
          \ifblank{#3}{}{\@dtl@assign{#3}{#2}}
          \ifthenelse { #1 }
          {
            \refstepcounter
              { DTLrow \romannumeral \dtlforeachlevel }
            \tl_set:Nx
               \DTLcurrentindex
              {
                \arabic { DTLrow\romannumeral\dtlforeachlevel }
              }
            #4
           \bool_if:NF #5
            {
              \exp_args:Nx \tl_if_eq:nnTF
                {
                  \__datatool_token_register_use:c
                   { @dtl@cur\romannumeral \dtlforeachlevel }
                }
                { \@nil }
              {
                \exp_args:Ncc
                 \dtl@decrementrows
                   {
                     @dtl@prev
                     \romannumeral \dtlforeachlevel
                   }
                   {
                     dtl@row
                     \romannumeral \dtlforeachlevel
                   }
                \exp_args:Ncc
                 \dtl@decrementrows
                   {
                     @dtl@next
                      \romannumeral \dtlforeachlevel
                   }
                   {
                     dtl@row
                     \romannumeral \dtlforeachlevel
                   }
                \__datatool_token_register_gset:cx
                 { dtldb@#2 }
                 {
                   \__datatool_token_register_use:c
                   {
                      @dtl@prev
                      \romannumeral \dtlforeachlevel
                   }
                   \__datatool_token_register_use:c
                   {
                      @dtl@next
                      \romannumeral \dtlforeachlevel
                   }
                 }
                \int_gdecr:c { dtlrows@#2 }
                \int_gdecr:c
                 { dtl@row\romannumeral \dtlforeachlevel }
            }
            {
               \__datatool_token_register_set_eq:Nc
                 \@dtl@before
                 { @dtl@prev\romannumeral \dtlforeachlevel }
               \__datatool_token_register_set_eq:Nc
                 \@dtl@after
                 {  @dtl@next\romannumeral \dtlforeachlevel }
                \__datatool_token_register_gconcat_middle:cNxN
                 { dtldb@#2 }
                 \@dtl@before
                {
                  \__datatool_row_markup:vv
                   { dtl@row\romannumeral \dtlforeachlevel }
                   { @dtl@cur\romannumeral \dtlforeachlevel }
                  }
                 \@dtl@after
              }
            }
          }
          {
            \int_gincr:c
             {
                g__filtered_row_
                 \romannumeral \dtlforeachlevel
                _int
             }
          }
        }
       \cs_gset_eq:Nc
         \DTLiffirstrow
         { @dtl@iffirstrow\the\dtlforeachlevel }
       \cs_gset_eq:Nc
          \DTLiflastrow
          { @dtl@iflastrow\the\dtlforeachlevel }
       \cs_gset_eq:Nc
          \DTLifoddrow
          { @dtl@ifoddrow\the\dtlforeachlevel }
      }
    }
    \int_gdecr:N \dtlforeachlevel
  }
  {
    \PackageError {datatool}
     { Database ~ `#2' ~ doesn't ~ exist }
     {}
  }
}
\newcommand{\@DTLforeach}[4][\boolean{true}]{%
 \__datatool_for_each:nnnnN { #1 } { #2 } { #3 } { #4 }
   \c_false_bool
}
\newcommand{\@sDTLforeach}[4][\boolean{true}]{
 \__datatool_for_each:nnnnN { #1 } { #2 } { #3 } { #4 }
   \c_true_bool
}
\ExplSyntaxOff
\newcommand*{\@dtlifreadonly}[2]{%
  \expandafter\ifx
    \csname @dtl@ro@\romannumeral\dtlforeachlevel\endcsname1\relax
    #1%
  \else
    #2%
  \fi
}
\ExplSyntaxOn
\newrobustcmd*{\DTLappendtorow}[2]{%
  \int_if_zero:nTF { \dtlforeachlevel }
  {
    \PackageError {datatool}
    {
      \token_to_str:N \DTLappendrow \c_space_tl ~ can ~ only ~ be ~
      used ~ inside ~ \token_to_str:N \DTLforeach
    }
    { }
  }
  {
    \tl_set_eq:Nc \@dtl@thisdb
      { @dtl@dbname@\romannumeral\dtlforeachlevel }
    \@dtlifreadonly
    {%
      \PackageError {datatool}
       {
         \token_to_str:N \DTLappendtorow \c_space_tl ~ can't ~
         be ~ used ~ inside ~ \token_to_str:N \DTLforeach*
       }
       {
         The ~ starred ~ version ~ of ~
         \token_to_str:N \DTLforeach \c_space_tl ~ is ~ read ~ only}
    }%
    {%
      \int_set_eq:Nc \dtlrownum
        { dtl@row\romannumeral\dtlforeachlevel }
      \@dtl@updatekeys{\@dtl@thisdb}{#1}{#2}%
      \int_set:Nn \dtlcolumnnum
        { \dtlcolumnindex{\@dtl@thisdb}{#1} }
      \dtlcurrentrow =
        \csname @dtl@cur\romannumeral\dtlforeachlevel\endcsname
      \exp_args:NNV
       \dtlgetentryfromcurrentrow
       \dtl@entry
       \dtlcolumnnum
      \datatool_if_null:NTF \dtl@entry
       {
        \protected@edef\@dtl@tmp{#2}%
        \expandafter\@dtl@toks\expandafter{\@dtl@tmp}%
        \__datatool_token_register_gput_right:cx
          { @dtl@cur\romannumeral\dtlforeachlevel }
        {
          \__datatool_row_element_markup:VV
            \dtlcolumnnum
            \@dtl@toks
        }
         \dtl@message{Appended ~ #1\space -> ~ \the\@dtl@toks\space to ~
         database ~ `\@dtl@thisdb'}%
       }
       {
        \PackageError {datatool}
         {
           \token_to_str:N \DTLappendtorow \c_space_tl ~
           Can't ~ append ~ entry ~ `\exp_not:n { #2 }' ~ to ~ row: ~
           there ~ is ~ already ~ an ~ entry ~ for ~ key ~
            `#1' ~ in ~ this ~ row
          }
         { }
       }
    }
  }
}
\newrobustcmd*{\DTLremoveentryfromrow}[1]{%
  \int_if_zero:nTF { \dtlforeachlevel }
  {
    \PackageError {datatool}
    {
      \token_to_str:N \DTLremoventryfromrow \c_space_tl ~
      can ~ only ~ be ~ used ~ inside ~ \token_to_str:N \DTLforeach
    }
    {}%
  }
  {
    \tl_set_eq:Nv
      \@dtl@thisdb
      { @dtl@dbname@\romannumeral\dtlforeachlevel }
    \@dtlifreadonly
    {%
      \PackageError {datatool}
      {
        \token_to_str:N \DTLremoveentryfromrow \c_space_tl
        can't ~ be ~ used ~ inside ~ \token_to_str:N \DTLforeach*
      }
      {
        The ~ starred ~ version ~ of ~
        \token_to_str:N \DTLforeach \c_space_tl ~ is ~ read ~ only
      }%
    }%
    {%
      \int_set_eq:Nc \dtlrownum
        { dtl@row\romannumeral\dtlforeachlevel }
      \@DTLifhaskey{\@dtl@thisdb}{#1}%
      {%
        \@dtl@getcolumnindex{\thiscol}{\@dtl@thisdb}{#1}\relax
        \dtlcolumnnum=\thiscol\relax
        \dtlcurrentrow =
          \csname @dtl@cur\romannumeral\dtlforeachlevel\endcsname
        \exp_args:NNV
         \dtlgetentryfromcurrentrow
         \dtl@entry
         \dtlcolumnnum
        \datatool_if_null:NTF \dtl@entry
         {
          \PackageError {datatool}
           {
             Can't ~ remove ~ entry ~ given ~ by ~ `#1' ~
             from ~ current ~ row ~ in ~ database ~
             `\@dtl@thisdb': ~ no ~ such ~ entry
            }
            {
             The ~ current ~ row ~ doesn't ~ contain ~ an ~ entry ~
             for ~ key ~ `#1'
            }%
         }
         {
          \edef\@dtl@dosplitrow{%
            \noexpand\dtlsplitrow{\the\dtlcurrentrow}%
              {\number\dtlcolumnnum}{\noexpand\dtl@pre}%
              {\noexpand\dtl@post}%
          }%
          \@dtl@dosplitrow
          \expandafter\@dtl@toks\expandafter{\dtl@pre}%
          \expandafter\toks@\expandafter{\dtl@post}%
          \edef\@dtl@tmp{\the\@dtl@toks \the\toks@}%
          \dtlcurrentrow=\expandafter{\@dtl@tmp}%
          \expandafter\global
            \csname @dtl@cur\romannumeral\dtlforeachlevel\endcsname
              = \dtlcurrentrow
         \dtl@message{Removed ~ entry ~ given ~ by ~ #1\space
           from ~ current ~ row ~ of ~ database ~ `\@dtl@thisdb'}%
         }
      }
      {
        \PackageError {datatool}
         {
           Can't ~ remove ~ entry ~ given ~ by ~
          `#1' ~ - ~ no ~ such ~ key ~ exists
         }
         {}
      }
    }
  }
}
\newcommand*{\DTLreplaceentryforrow}[2]{%
  \int_if_zero:nTF { \dtlforeachlevel }
  {
    \PackageError {datatool}
    {
      \token_to_str:N \DTLreplaceentryforrow \c_space_tl ~
      can ~ only ~ be ~ used ~ inside ~ \token_to_str:N \DTLforeach
    }
    {}%
  }
  {
    \tl_set_eq:Nc
      \@dtl@thisdb
      { @dtl@dbname@\romannumeral\dtlforeachlevel }
    \@dtlifreadonly
    {%
      \PackageError {datatool}
       {
         \token_to_str:N \DTLreplaceentryforrow \c_space_tl ~
         can't ~ be ~ used ~ inside ~ \token_to_str:N \DTLforeach*
       }
       {
         The ~ starred ~ version ~
         of ~ \token_to_str:N \DTLforeach \c_space_tl is ~ read ~ only
       }%
    }%
    {%
      \int_set_eq:Nc \dtlrownum
        { dtl@row\romannumeral\dtlforeachlevel }
      \@DTLifhaskey{\@dtl@thisdb}{#1}%
      {%
        \@dtl@getcolumnindex{\thiscol}{\@dtl@thisdb}{#1}
        \int_set_eq:NN \dtlcolumnnum \thiscol
        \dtlcurrentrow =
          \csname @dtl@cur\romannumeral\dtlforeachlevel\endcsname
        \exp_args:NNV
         \dtlgetentryfromcurrentrow
         \dtl@entry
         \dtlcolumnnum
        \datatool_if_null:NTF \dtl@entry
         {
          \PackageError {datatool}
           {
             Can't ~ replace ~ entry ~ given ~ by ~ `#1' ~
             from ~ current ~ row ~ in ~ database ~ `\@dtl@thisdb': ~
             no ~ such ~ entry
           }
           {
             The ~ current ~ row ~ doesn't ~ contain ~ an ~ entry ~
             for ~ key ~ `#1'
           }%
         }
         {
          \edef\@dtl@dosplitrow{%
            \noexpand\dtlsplitrow{\the\dtlcurrentrow}%
              {\number\dtlcolumnnum}{\noexpand\dtl@pre}%
              {\noexpand\dtl@post}%
          }%
          \@dtl@dosplitrow
          \protected@edef\@dtl@tmp{#2}%
          \expandafter\@dtl@toks\expandafter{\@dtl@tmp}% new value
          \expandafter\@dtl@before\expandafter{\dtl@pre}%
          \expandafter\@dtl@after\expandafter{\dtl@post}%
          \__datatool_token_register_gconcat_middle:cNxN
            { @dtl@cur\romannumeral\dtlforeachlevel }
            \@dtl@before
            {
              \__datatool_row_element_markup:VV
                \dtlcolumnnum
                \@dtl@toks
            }
            \@dtl@after
         \dtl@message{Updated ~ #1\space -> \the\@dtl@toks\space
           in ~ database ~ `\@dtl@thisdb'}%
         }
      }%
      {%
         \PackageError {datatool}
          {
           Can't ~ replace ~ key ~ `#1' ~ - ~ no ~ such ~
           key ~ in ~ database ~ `\@dtl@thisdb'
          }
          {}%
      }%
    }%
  }
}
\newcommand*{\DTLremovecurrentrow}{%
  \int_if_zero:nTF { \dtlforeachlevel }
  {
    \PackageError {datatool}
    {
      \token_to_str:N \DTLremovecurrentrow\c_space_tl ~
      can ~ only ~ be ~ used ~ inside ~ \token_to_str:N \DTLforeach
    }
    { }
  }
  {
    \tl_set_eq:Nc
      \@dtl@thisdb
      { @dtl@dbname@\romannumeral\dtlforeachlevel }
    \@dtlifreadonly
    {
      \PackageError {datatool}
      {
        \token_to_str:N \DTLreplaceentryforrow\c_space_tl
        can't ~ be ~ used ~ inside ~ \token_to_str:N \DTLforeach*
      }
      {
        The ~ starred ~ version ~ of ~
        \token_to_str:N \DTLforeach \c_space_tl ~ is ~ read ~ only
      }
    }
    {
      \__datatool_token_register_gset:cn
        { @dtl@cur\romannumeral\dtlforeachlevel }
        { \@nil }
    }
  }
}
\ExplSyntaxOff
\newcommand{\DTLaddentryforrow}[5]{%
  \DTLifdbexists{#1}%
  {%
    \def\@dtl@notdone{\PackageError{datatool}{Unable to add entry
      given by key `#4': condition not met for any row in database
      `#1'}{}}%
    \DTLforeach[#3]{#1}{#2}%
    {%
      \DTLappendtorow{#4}{#5}%
      \let\@dtl@notdone\relax
      \dtlbreak
    }%
    \@dtl@notdone
  }%
  {%
    \PackageError{datatool}{Unable to add entry given by key `#4':
      database `#1' doesn't exist}{}%
  }%
}
\newcommand*{\DTLforeachkeyinrow}[2]{%
  \ifnum\dtlforeachlevel=0\relax
    \PackageError{datatool}{\string\DTLforeachkeyinrow\space can only
     be used inside \string\DTLforeach}{}%
  \else
    \expandafter\let\expandafter\@dtl@thisdb
      \csname @dtl@dbname@\romannumeral\dtlforeachlevel\endcsname
    \dtlforeachkey(\dtlkey,\dtlcol,\dtltype,\dtlheader)\in
      \@dtl@thisdb\do{%
      \dtlcurrentrow =
        \csname @dtl@cur\romannumeral\dtlforeachlevel\endcsname
      \edef\dtl@dogetentry{\noexpand\dtlgetentryfromcurrentrow
        {\noexpand#1}{\dtlcol}}%
      \dtl@dogetentry
      \ifx#1\dtlnovalue
        \ifnum0\dtltype=0\relax
          \let#1=\@dtlstringnull
        \else
          \let#1=\@dtlnumbernull
        \fi
      \fi
      \global\let#1#1%
      \def\@dtl@loop@body{#2}%
      \@dtl@loop@body
    }%
  \fi
}
\ExplSyntaxOn
\newcommand{\DTLiffirstrow}[2]{
  \PackageError {datatool}
  {
    \token_to_str:N \DTLiffirstrow\c_space_tl ~ can ~ only ~
    be ~ used ~ inside ~ \token_to_str:N \DTLforeach
  }
  {}
}
\cs_new:Nn \__datatool_foreach_if_first_row:TF
{
  \int_compare:nNnTF
    { \int_use:c { c@DTLrow\romannumeral\dtlforeachlevel } }
     =
    { \c_one_int }
  { #1 } { #2 }
}
\newcommand{\DTLiflastrow}[2]{
  \PackageError {datatool}
  {
   \token_to_str:N \DTLiflastrow\c_space_tl ~ can ~ only ~
   be ~ used ~ inside ~ \token_to_str:N \DTLforeach
  }
 { }
}
\cs_new:Nn \__datatool_foreach_if_last_row:TF
{
  \int_compare:nNnTF
   { \int_use:c { c@DTLrow\romannumeral\dtlforeachlevel } }
    =
   {
     \int_use:c { dtlrows@ \@dtl@dbname }
     -
     \int_use:c
      {
        g__filtered_row_
         \romannumeral \dtlforeachlevel
        _int
      }
   }
  { #1 } { #2 }
}
\newcommand{\DTLifoddrow}[2]{%
  \PackageError
   {datatool}
   {
     \token_to_str:N \DTLifoddrow \c_space_tl ~ can ~ only ~
     be ~ used ~ inside ~ \token_to_str:N \DTLforeach
   }
   { }
}
\cs_new:Nn \__datatool_foreach_if_odd_row:TF
{
  \int_if_odd:nTF
   { \int_use:c { c@DTLrow\romannumeral\dtlforeachlevel } }
   { #1 } { #2 }
}
\ExplSyntaxOff
\newcommand*{\dtlbetweencols}{}
\newcommand*{\dtlbeforecols}{}
\newcommand*{\dtlaftercols}{}
\newcommand*{\dtlstringalign}{l}
\newcommand*{\dtlintalign}{r}
\newcommand*{\dtlrealalign}{r}
\newcommand*{\dtlcurrencyalign}{r}
\newcommand*{\dtldatetimealign}{l}
\newcommand*{\dtldatealign}{l}
\newcommand*{\dtltimealign}{l}
\ExplSyntaxOn
\NewDocumentCommand \dtladdalign { m m m m }
{
  \int_compare:nNnTF { #3 } = { 1 }
  {
    \tl_set_eq:NN #1 \dtlbeforecols
  }
  {
    \tl_put_right:NV #1 \dtlbetweencols
  }
  \exp_args:Nx \tl_if_empty:nTF { #2 }
   {
     \int_set_eq:NN
        \l__datatool_tmp_datatype_int \c_datatool_unknown_int
   }
   {
     \int_set:Nn \l__datatool_tmp_datatype_int { #2 }
   }
  \int_case:nnF { \l__datatool_tmp_datatype_int }
  {
    { \c_datatool_string_int }
      {
        \tl_put_right:NV #1 \dtlstringalign
      }
    { \c_datatool_integer_int }
      {
        \tl_put_right:NV #1 \dtlintalign
      }
    { \c_datatool_decimal_int }
      {
        \tl_put_right:NV #1 \dtlrealalign
      }
    { \c_datatool_currency_int }
      {
        \tl_put_right:NV #1 \dtlcurrencyalign
      }
    { \c_datatool_datetime_int }
      {
        \tl_put_right:NV #1 \dtldatetimealign
      }
    { \c_datatool_date_int }
      {
        \tl_put_right:NV #1 \dtldatealign
      }
    { \c_datatool_time_int }
      {
        \tl_put_right:NV #1 \dtltimealign
      }
    { \c_datatool_unknown_int }
      {
        \tl_put_right:NV #1 \dtlstringalign
        \PackageWarning { datatool }
        {
          Column ~ \int_eval:n { #3 } ~ has ~ no ~
          associated ~ datatype. ~ Assuming ~
          string, ~ but ~ column ~ may ~ be ~ empty
         }
      }
  }
  {
    \tl_put_right:NV #1 \dtlstringalign
    \PackageError { datatool }
      {
         Invalid ~ data ~ type ~ `\int_eval:n{ #2 } ' ~
        for ~ column ~ \int_eval:n { #3 } }
      { }
  }
  \int_compare:nNnT { #3 } = { #4 }
  {
    \tl_put_right:NV #1 \dtlaftercols
  }
}
\regex_new:N \l_datatool_colalign_regex
\regex_set:Nn \l_datatool_colalign_regex { [ m p r c l ] }
\NewDocumentCommand \dtladdheaderalign { m m m m }
{
  \int_compare:nNnTF { #3 } = { 1 }
  {
    \regex_match:NVF \l_datatool_colalign_regex \dtlbeforecols
     {
       \tl_set_eq:NN #1 \dtlbeforecols
     }
  }
  {
    \tl_put_right:NV #1 \dtlbetweencols
  }
  \tl_put_right:Nn #1 { c }
  \int_compare:nNnT { #3 } = { #4 }
  {
    \regex_match:NVF \l_datatool_colalign_regex \dtlaftercols
     {
       \tl_put_right:NV #1 \dtlaftercols
     }
  }
}
\newcommand*{\dtlheaderformat}[1]{\textbf{#1}}
\newcommand \dtlcolumnheader [2]
{
   \multicolumn { 1 } { #1 } { \dtlheaderformat{ #2 } }
}
\newcommand*{\dtlstringformat}[1]{#1}
\newcommand*{\dtlnumericformat}[1]{#1}
\newcommand*{\dtlintformat}[1]{\dtlnumericformat{#1}}
\newcommand*{\dtlrealformat}[1]{\dtlnumericformat{#1}}
\newcommand*{\dtlcurrencyformat}[1]{\dtlnumericformat{#1}}
\newcommand*{\dtldatetimeformat}[1]{#1}
\newcommand*{\dtldateformat}[1]{#1}
\newcommand*{\dtltimeformat}[1]{#1}
\newcommand*{\dtldisplaystarttab}{}
\newcommand*{\dtldisplayendtab}{}
\newcommand*{\dtldisplayafterhead}{}
\newcommand*{\dtldisplayvalign}{c}
\newcommand*{\dtldisplaystartrow}{}
\int_new:N \l_datatool_display_per_row_int
\int_set_eq:NN \l_datatool_display_per_row_int \c_one_int
\int_new:N \l_datatool_display_tab_rows_int
\prg_new_conditional:Npnn \datatool_if_row_start:nn #1 #2
  { p, T, F, TF }
{
  \int_compare:nNnTF
    { \l_datatool_display_per_row_int } > { \c_one_int }
   {
     \bool_lazy_and:nnTF
       { \int_compare_p:nNn { #2 } = { \c_one_int } }
       {
         \int_compare_p:nNn
           { \int_mod:nn { #1 } { \l_datatool_display_per_row_int } }
           = { \c_one_int }
       }
      { \prg_return_true: }
      { \prg_return_false: }
   }
   {
     \int_compare:nNnTF { #2 } = { \c_one_int }
      { \prg_return_true: }
      { \prg_return_false: }
   }
}
\cs_new:Nn \__datatool_display_row_idx:n { #1 }
\newcommand \DTLdisplayTBrowidxmap [1]
{
  \int_mod:nn
    { ( #1 - \c_one_int ) }
    { \l_datatool_display_per_row_int }
   * \l_datatool_display_tab_rows_int
   + \int_div_truncate:nn
      { #1 - \c_one_int }
      { \l_datatool_display_per_row_int } + \c_one_int
}
\newcommand{\dtldisplaycr}{\tabularnewline}
\newcommand{\dtldisplaydbenv}{tabular}
\NewDocumentCommand \DTLdisplaydbAddBegin { m m m }
{
  \tl_put_right:Nx #1 { \exp_not:N \begin { \dtldisplaydbenv } }
  \tl_if_empty:NF \dtldisplayvalign
  {
    \tl_put_right:Nx #1
     {
       [ \dtldisplayvalign ]
     }
  }
  \tl_put_right:Nn #1 {  { #2 }  }
  \tl_put_right:NV #1 \dtldisplaystarttab
  \tl_put_right:Nn #1 { #3 }
  \tl_put_right:NV #1 \l_datatool_post_head_tl
  \tl_put_right:NV #1 \dtldisplaycr
  \tl_put_right:NV #1 \dtldisplayafterhead
}
\NewDocumentCommand \DTLdisplaydbAddEnd { m }
{
  \tl_if_eq:NnF \l_datatool_foot_tl { \c_novalue_tl }
  {
    \tl_put_right:NV #1 \dtldisplaycr
    \tl_put_right:NV #1 \l_datatool_foot_tl
  }
  \tl_put_right:NV #1 \dtldisplayendtab
  \tl_put_right:Nx #1 { \exp_not:N \end { \dtldisplaydbenv } }
}
\seq_new:N \l__datatool_omit_columns_seq
\seq_new:N \l__datatool_only_columns_seq
\seq_new:N \l__datatool_omit_keys_seq
\seq_new:N \l__datatool_only_keys_seq
\cs_new:Nn \__datatool_if_display_row:NnT { #3 }
\cs_new:Nn \__datatool_display_post_row:Nn { }
\tl_new:N \l__datatool_pre_display_tl
\tl_new:N \l__datatool_init_display_tl
\tl_new:N \l__datatool_user_align_tl
\tl_new:N \l__datatool_user_header_tl
\bool_new:N \l_datatool_include_header_bool
\bool_set_true:N \l_datatool_include_header_bool
\tl_new:N \l_datatool_caption_tl
\tl_set:Nn \l_datatool_caption_tl { \c_novalue_tl }
\tl_new:N \l_datatool_short_caption_tl
\tl_set:Nn \l_datatool_short_caption_tl { \c_novalue_tl }
\tl_new:N \l_datatool_cont_caption_tl
\tl_set:Nn \l_datatool_cont_caption_tl { \c_novalue_tl }
\tl_new:N \l_datatool_label_tl
\tl_set:Nn \l_datatool_label_tl { \c_novalue_tl }
\tl_new:N \l_datatool_foot_tl
\tl_set:Nn \l_datatool_foot_tl { \c_novalue_tl }
\tl_new:N \l_datatool_last_foot_tl
\tl_set:Nn \l_datatool_last_foot_tl { \c_novalue_tl }
\tl_new:N \l_datatool_post_head_tl
\keys_define:nn { datatool/display }
{
  omit-columns .code:n =
    {
      \seq_set_from_clist:Nn \l__datatool_omit_columns_seq { #1 }
      \seq_if_empty:NF \l__datatool_omit_columns_seq
       {
         \seq_clear:N \l__datatool_only_columns_seq
         \seq_clear:N \l__datatool_only_keys_seq
         \seq_clear:N \l__datatool_omit_keys_seq
       }
    },
  only-columns .code:n =
    {
      \seq_set_from_clist:Nn \l__datatool_only_columns_seq { #1 }
      \seq_remove_duplicates:N \l__datatool_only_columns_seq
      \seq_if_empty:NF  \l__datatool_only_columns_seq
       {
         \seq_clear:N \l__datatool_omit_columns_seq
         \seq_clear:N \l__datatool_only_keys_seq
         \seq_clear:N \l__datatool_omit_keys_seq
       }
    },
  omit-keys .code:n =
    {
      \seq_set_from_clist:Nn \l__datatool_omit_keys_seq { #1 }
      \seq_if_empty:NF \l__datatool_omit_keys_seq
       {
         \seq_clear:N \l__datatool_only_columns_seq
         \seq_clear:N \l__datatool_omit_columns_seq
         \seq_clear:N \l__datatool_only_keys_seq
       }
    },
  only-keys .code:n =
    {
      \seq_set_from_clist:Nn \l__datatool_only_keys_seq { #1 }
      \seq_remove_duplicates:N \l__datatool_only_keys_seq
      \seq_if_empty:NF \l__datatool_only_keys_seq
       {
        \seq_clear:N \l__datatool_only_columns_seq
         \seq_clear:N \l__datatool_omit_columns_seq
         \seq_clear:N \l__datatool_omit_keys_seq
       }
    },
  row-condition-inline .cs_set:Np =
      \__datatool_if_display_row:NnT #1 #2 #3 ,
  row-condition-function .code:n =
    {
      \cs_set_eq:NN \__datatool_if_display_row:NnT #1
    },
  post-row-inline .cs_set:Np =
      \__datatool_display_post_row:Nn #1 #2 ,
  post-row-function .code:n =
    {
      \cs_set_eq:NN \__datatool_display_post_row:Nn #1
    } ,
  row-idx-map-inline .cs_set:Np =
     \__datatool_display_row_idx:n #1 ,
  row-idx-map-function .code:n =
    {
      \cs_set_eq:NN \__datatool_display_row_idx:n #1
    },
  init .tl_set:N = \l__datatool_init_display_tl ,
  pre-content .tl_set:N = \l__datatool_pre_display_tl ,
  pre-head .tl_set:N = \dtldisplaystarttab ,
  post-head .tl_set:N = \l_datatool_post_head_tl ,
  after-head .tl_set:N = \dtldisplayafterhead ,
  align-specs .tl_set:N = \l__datatool_user_align_tl ,
  header-row .tl_set:N = \l__datatool_user_header_tl ,
  no-header .bool_set_inverse:N = \l_datatool_include_header_bool ,
  string-align .tl_set:N = \dtlstringalign,
  int-align .tl_set:N = \dtlintalign,
  integer-align .tl_set:N = \dtlintalign,
  real-align .tl_set:N = \dtlrealalign,
  decimal-align .tl_set:N = \dtlrealalign,
  currency-align .tl_set:N = \dtlcurrencyalign,
  inter-col .tl_set:N = \dtlbetweencols,
  pre-col .tl_set:N = \dtlbeforecols,
  post-col .tl_set:N = \dtlaftercols,
  tabular-env .choice:,
  tabular-env / tabular .code:n =
   {
     \tl_set:Nn \dtldisplaydbenv { tabular }
     \tl_if_eq:NnF \dtldisplayvalign { t }
     {
       \tl_if_eq:NnF \dtldisplayvalign { b }
       {
         \tl_set:Nn \dtldisplayvalign { c }
       }
     }
   },
  tabular-env / array .code:n =
   {
     \tl_set:Nn \dtldisplaydbenv { array }
     \tl_if_eq:NnF \dtldisplayvalign { t }
     {
       \tl_if_eq:NnF \dtldisplayvalign { b }
       {
         \tl_set:Nn \dtldisplayvalign { c }
       }
     }
   },
  tabular-env / unknown .code:n =
   {
     \cs_if_exist:cTF { #1 }
     {
       \tl_set:Nn \dtldisplaydbenv { #1 }
       \tl_clear:N \dtldisplayvalign
     }
     {
       \PackageError { datatool }
       {
         Unknown ~ environment ~ `#1' ~ specified ~ in ~
         option ~ `tabular-env'
       }
       {
         Check ~ that ~ you ~ have ~ spelt ~ the ~ environment ~
         name ~ correctly ~ and ~ have ~ loaded ~ any ~
         relevant ~ package
       }
     }
   },
  tabular-env .default:n = { tabular },
  tabular-env .groups:n = { tabular },
  longtable-env .code:n =
   {
     \cs_if_exist:cTF { #1 }
     {
       \tl_set:Nn \dtldisplaylongdbenv { #1 }
     }
     {
       \PackageError { datatool }
       {
         Unknown ~ environment ~ `#1' ~ specified ~ in ~
         option ~ `longtable-env'
       }
       {
         Check ~ that ~ you ~ have ~ spelt ~ the ~ environment ~
         name ~ correctly ~ and ~ have ~ loaded ~ any ~
         relevant ~ package
       }
     }
   },
  longtable-env .default:n = { longtable },
  longtable-env .groups:n = { longtable },
  label .tl_set:N = \l_datatool_label_tl ,
  label .groups:n = { longtable },
  caption .tl_set:N = \l_datatool_caption_tl ,
  caption .groups:n = { longtable },
  cont-caption .tl_set:N = \l_datatool_cont_caption_tl ,
  cont-caption .groups:n = { longtable },
  short-caption .tl_set:N = \l_datatool_short_caption_tl ,
  short-caption .groups:n = { longtable },
  foot .tl_set:N = \l_datatool_foot_tl ,
  last-foot .tl_set:N = \l_datatool_last_foot_tl ,
  last-foot .groups:n = { longtable },
  per-row .int_set:N = \l_datatool_display_per_row_int ,
  contcaption .tl_set:N = \l_datatool_cont_caption_tl ,
  contcaption .groups:n = { longtable },
  shortcaption .tl_set:N = \l_datatool_short_caption_tl ,
  shortcaption .groups:n = { longtable },
  lastfoot .tl_set:N = \l_datatool_last_foot_tl ,
  lastfoot .groups:n = { longtable },
  omit .code:n =
    {
      \seq_set_from_clist:Nn \l__datatool_omit_keys_seq { #1 }
      \seq_if_empty:NF \l__datatool_omit_keys_seq
       {
         \seq_clear:N \l__datatool_only_columns_seq
         \seq_clear:N \l__datatool_omit_columns_seq
         \seq_clear:N \l__datatool_only_keys_seq
       }
    },
}
\NewDocumentCommand \DTLdisplaydb { s o m }
{
  \DTLifdbexists { #3 }
  {
    \group_begin:
    \IfValueT { #2 }
    {
      \IfBooleanTF { #1 }
      {
        \keys_set_filter:nnnN { datatool/display } { longtable } { #2 }
          \l__datatool_tmpb_tl
        \tl_if_empty:NF \l__datatool_tmpb_tl
        {
          \PackageWarning { datatool }
          {
            Ignoring ~ unsupported ~ \token_to_str:N\DTLdisplaydb* ~
            option(s): ~ \exp_not:o { \l__datatool_tmpb_tl }
          }
        }
      }
      {
        \keys_set:nn { datatool/display } { omit-keys = { #2 } }
      }
    }
    \tl_set:Nx \dtldbname { #3 }
    \__datatool_display_db:
    \group_end:
  }
  {
    \PackageError { datatool }
    { Database ~ `#3' ~ doesn't ~ exist }
    { }
  }
}
\cs_new:Nn \__datatool_display_db:
{
  \__datatool_if_display_init:T
  {
    \l__datatool_init_display_tl
    \exp_args:NNVV \DTLdisplaydbAddBegin \l__datatool_content_tl
      \l__datatool_align_tl \l__datatool_row_tl
    \int_zero:N \dtlrownum
    \int_zero:N \l__datatool_row_idx_int
    \__datatool_display_loop:
    \DTLdisplaydbAddEnd \l__datatool_content_tl
    \l__datatool_pre_display_tl
    \l__datatool_content_tl
  }
}
\cs_new:Nn \__datatool_display_loop:
{
  \int_step_inline:nn
   { \DTLrowcount { \dtldbname } }
   {
     \exp_args:Nx \__datatool_display_db_row_fn:n
      {
        \int_eval:n { \__datatool_display_row_idx:n { ##1 } }
      }
   }
}
\cs_new:Nn  \__datatool_if_display_init:T
{
  \tl_clear:N \l__datatool_content_tl
  \tl_clear:N \l__datatool_align_tl
  \tl_clear:N \l__datatool_row_tl
  \tl_set:Nv \l__datatool_keydata_tl { dtlkeys@\dtldbname }
  \seq_clear:N \l__datatool_column_indexes_seq
  \seq_if_empty:NF \l__datatool_only_keys_seq
  {
    \seq_map_inline:Nn \l__datatool_only_keys_seq
     {
       \tl_if_exist:cTF { dtl@ci@\dtldbname @ ##1 }
       {
         \seq_put_right:Nv \l__datatool_only_columns_seq
           { dtl@ci@\dtldbname @ ##1 }
       }
       {
         \PackageWarning { datatool }
         { Ignoring ~ key ~ `##1' ~ in ~ only-keys ~ option: ~
           no ~ such ~ key ~ in ~ database ~ `\dtldbname'
         }
       }
     }
  }
  \seq_if_empty:NF \l__datatool_omit_keys_seq
  {
    \seq_map_inline:Nn \l__datatool_omit_keys_seq
     {
       \tl_if_exist:cTF { dtl@ci@\dtldbname @ ##1 }
       {
         \seq_put_right:Nv \l__datatool_omit_columns_seq
           { dtl@ci@\dtldbname @ ##1 }
       }
       {
         \PackageWarning { datatool }
         { Ignoring ~ key ~ `##1' ~ in ~ omit-keys ~ option: ~
           no ~ such ~ key ~ in ~ database ~ `\dtldbname'
         }
       }
     }
  }
  \seq_if_empty:NTF \l__datatool_only_columns_seq
  {
    \exp_args:NNv \int_set:Nn \l__datatool_max_cols_int { dtlcols@\dtldbname }
    \int_step_inline:nn { \l__datatool_max_cols_int }
     {
       \seq_if_in:NnF \l__datatool_omit_columns_seq { ##1 }
       {
         \seq_put_right:Nn \l__datatool_column_indexes_seq { ##1 }
       }
     }
  }
  {
      \seq_set_eq:NN \l__datatool_column_indexes_seq
        \l__datatool_only_columns_seq
  }
  \int_set:Nn \l__datatool_max_cols_int
     { \seq_count:N \l__datatool_column_indexes_seq }
  \int_compare:nNnTF { \l__datatool_max_cols_int } > { \c_zero_int }
  {
    \int_zero:N \dtlcolumnnum
    \tl_if_empty:NF \l__datatool_user_align_tl
    {
      \tl_set_eq:NN \l__datatool_align_tl \l__datatool_user_align_tl
    }
    \tl_if_empty:NTF \l__datatool_user_header_tl
    {
      \bool_if:NF \l_datatool_include_header_bool
      {
        \tl_set:Nn \l__datatool_user_header_tl { \c_empty_tl }
      }
    }
    {
      \tl_set_eq:NN \l__datatool_row_tl \l__datatool_user_header_tl
    }
    \bool_lazy_or:nnT
      { \tl_if_empty_p:N \l__datatool_user_align_tl }
      { \tl_if_empty_p:N \l__datatool_user_header_tl }
    {
      \seq_map_function:NN \l__datatool_column_indexes_seq
        \__datatool_display_db_metadata_fn:n
    }
    \int_compare:nNnT
      { \l_datatool_display_per_row_int } > { \c_one_int }
    {
      \bool_if:NT \l_datatool_include_header_bool
       {
         \tl_set:Nx \l__datatool_row_tl
          {
            \exp_args:Ne \tl_tail:n
             {
               \prg_replicate:nn { \l_datatool_display_per_row_int }
                { & \exp_not:V \l__datatool_row_tl }
             }
          }
       }
      \tl_set:Nx \l__datatool_align_tl
       {
         \prg_replicate:nn { \l_datatool_display_per_row_int }
          { \exp_not:V \l__datatool_align_tl }
       }
    }
    \int_compare:nNnTF
       { \l_datatool_display_per_row_int } > { \c_one_int }
     {
       \int_set:Nn \l_datatool_display_tab_rows_int
         {
           \int_div_truncate:nn
            { \DTLrowcount { \dtldbname } }
            { \l_datatool_display_per_row_int }
         }
       \int_if_zero:nF
         {
           \int_mod:nn
             { \DTLrowcount { \dtldbname } }
             { \l_datatool_display_per_row_int }
         }
        {
          \int_incr:N \l_datatool_display_tab_rows_int
        }
     }
     {
       \int_set:Nn \l_datatool_display_tab_rows_int
         { \DTLrowcount { \dtldbname } }
     }
    #1
  }
  {
    \@DTLifdbempty { \dtldbname }
     {
       \PackageWarning { datatool }
       { Can't ~ display ~ database ~ `\dtldbname': ~ database ~ empty }
     }
     {
       \PackageError { datatool }
       { Can't ~ display ~ database ~ `\dtldbname': ~ no ~ columns ~ available }
       {
         Either ~ the ~ database ~ is ~ empty ~ or ~ all ~ columns ~
         are ~ in ~ the ~ omit ~ list
       }
     }
  }
}
\cs_new:Nn \__datatool_display_db_metadata_fn:n
{
  \int_incr:N \dtlcolumnnum
  \exp_args:NV \__datatool_get_col_type_header:nn \l__datatool_keydata_tl
   { #1 }
  \tl_if_empty:NT \l__datatool_user_align_tl
  {
    \dtladdalign \l__datatool_align_tl
     { \l__datatool_item_type_int }
     { \dtlcolumnnum }
     { \l__datatool_max_cols_int }
  }
  \tl_if_empty:NT \l__datatool_user_header_tl
  {
    \tl_if_empty:NF \l__datatool_row_tl
    {
      \tl_put_right:Nn \l__datatool_row_tl { & }
    }
    \tl_clear:N \l__datatool_tmpb_tl
    \dtladdheaderalign \l__datatool_tmpb_tl
     { \l__datatool_item_type_int }
     { \dtlcolumnnum }
     { \l__datatool_max_cols_int }
    \tl_put_right:Nn \l__datatool_row_tl { \dtlcolumnheader }
    \tl_put_right:Nx \l__datatool_row_tl
     { { \exp_not:V \l__datatool_tmpb_tl } }
    \tl_put_right:Nx \l__datatool_row_tl
      { { \exp_not:o { \l__datatool_item_head_tl } } }
  }
}
\cs_new:Nn \__datatool_display_db_row_fn:n
{
  \exp_args:Nv \@dtlgetrow { dtldb@\dtldbname } { #1 }
  \__datatool_if_display_row:NnT \l__datatool_content_tl { #1 }
  {
    \__datatool_display_db_row:Nn \l__datatool_content_tl { #1 }
  }
}
\cs_new:Nn \__datatool_display_db_row:Nn
{
  \int_compare:nNnTF { \l__datatool_row_idx_int } = { -1 }
  {
    \PackageWarning { datatool }
    { Omitting ~ row ~ #2: ~ not ~ found ~ in ~ database ~ `\dtldbname' }
  }
  {
    \int_incr:N \dtlrownum
    \int_compare:nNnT { \dtlrownum } > { \c_one_int }
     {
       \int_compare:nNnTF
         { \l_datatool_display_per_row_int } > { \c_one_int }
       {
         \int_compare:nNnTF
           { \int_mod:nn { \dtlrownum } { \l_datatool_display_per_row_int } }
            = { \c_one_int }
          {
            \tl_put_right:NV #1 \dtldisplaycr
            \tl_put_right:NV #1 \dtldisplaystartrow
          }
          {
            \tl_put_right:Nn #1 { & }
          }
       }
       {
         \tl_put_right:NV #1 \dtldisplaycr
         \tl_put_right:NV #1 \dtldisplaystartrow
       }
     }
    \int_zero:N \l__datatool_col_idx_int
    \seq_map_function:NN \l__datatool_column_indexes_seq
      \__datatool_display_db_col_fn:n
    \__datatool_display_post_row:Nn \l__datatool_content_tl { #2 }
  }
}
\cs_new:Nn \__datatool_display_db_col_fn:n
{
  \int_compare:nNnT { \l__datatool_col_idx_int } > { 0 }
  {
     \tl_put_right:Nn \l__datatool_content_tl { & }
  }
  \int_incr:N \l__datatool_col_idx_int
  \dtlgetentryfromcurrentrow { \l__datatool_item_value_tl } { #1 }
  \exp_args:NV \__datatool_get_col_type:nn \l__datatool_keydata_tl
   { #1 }
  \int_case:nnF { \l__datatool_item_type_int }
  {
    { \c_datatool_string_int }
      {
        \tl_set:Nn \l__datatool_tmpb_tl { \dtlstringformat }
        \datatool_if_null:NT \l__datatool_item_value_tl
         {
           \tl_set_eq:NN \l__datatool_item_value_tl \DTLstringnull
         }
      }
    { \c_datatool_integer_int }
      {
        \tl_set:Nn \l__datatool_tmpb_tl { \dtlintformat }
        \datatool_if_null:NT \l__datatool_item_value_tl
         {
           \tl_set_eq:NN \l__datatool_item_value_tl \DTLnumbernull
         }
      }
    { \c_datatool_decimal_int }
      {
        \tl_set:Nn \l__datatool_tmpb_tl { \dtlrealformat }
        \datatool_if_null:NT \l__datatool_item_value_tl
         {
           \tl_set_eq:NN \l__datatool_item_value_tl \DTLnumbernull
         }
      }
    { \c_datatool_currency_int }
      {
        \tl_set:Nn \l__datatool_tmpb_tl { \dtlcurrencyformat }
        \datatool_if_null:NT \l__datatool_item_value_tl
         {
           \tl_set_eq:NN \l__datatool_item_value_tl \DTLnumbernull
         }
      }
    { \c_datatool_datetime_int }
      {
        \tl_set:Nn \l__datatool_tmpb_tl { \dtldatetimeformat }
        \datatool_if_null:NT \l__datatool_item_value_tl
         {
           \tl_set_eq:NN \l__datatool_item_value_tl \DTLnumbernull
         }
      }
    { \c_datatool_date_int }
      {
        \tl_set:Nn \l__datatool_tmpb_tl { \dtldateformat }
        \datatool_if_null:NT \l__datatool_item_value_tl
         {
           \tl_set_eq:NN \l__datatool_item_value_tl \DTLnumbernull
         }
      }
    { \c_datatool_time_int }
      {
        \tl_set:Nn \l__datatool_tmpb_tl { \dtltimeformat }
        \datatool_if_null:NT \l__datatool_item_value_tl
         {
           \tl_set_eq:NN \l__datatool_item_value_tl \DTLnumbernull
         }
      }
  }
  {
    \tl_set:Nn \l__datatool_tmpb_tl { \dtlstringformat }
    \datatool_if_null:NT \l__datatool_item_value_tl
     {
       \tl_set_eq:NN \l__datatool_item_value_tl \DTLstringnull
     }
  }
  \__datatool_display_add_item:NVVVVVVn
    \l__datatool_content_tl
    \l__datatool_item_value_tl  % item
    \l__datatool_tmpb_tl % fmt-cs
    \l__datatool_item_type_int % type
    \dtlrownum  % row num
    \l__datatool_row_idx_int % row idx
    \l__datatool_col_idx_int % col num
    { #1 } % col idx
}
\NewDocumentCommand \DTLdisplaydbAddItem { m m m m m m m m }
{
  \tl_put_right:Nn #1 { #3 { #2 } }
}
\cs_new:Nn \__datatool_display_add_item:Nnnnnnnn
{
  \DTLdisplaydbAddItem #1 { #2 } { #3 } { #4 } { #5 } { #6 } { #7 } { #8 }
}
\cs_generate_variant:Nn \__datatool_display_add_item:Nnnnnnnn
 { NVVVVVVn }
\newcommand{\dtldisplaylongdbenv}{longtable}
\NewDocumentCommand \DTLdisplaylongdbAddBegin { m m m }
{
  \tl_put_right:Nx #1
   {
     \exp_not:N \begin { \dtldisplaylongdbenv }
   }
  \tl_put_right:Nn #1
   {
     { #2 }
   }
  \tl_if_eq:NnTF \l_datatool_caption_tl { \c_novalue_tl }
  {
    \bool_if:NT \l_datatool_include_header_bool
    {
      \tl_put_right:NV #1 \dtldisplaystarttab
      \tl_put_right:Nn #1 { #3 }
      \tl_put_right:NV #1 \l_datatool_post_head_tl
      \tl_if_empty:NF \dtldisplayafterhead
      {
        \tl_put_right:NV #1 \dtldisplaycr
        \tl_put_right:NV #1 \dtldisplayafterhead
      }
      \tl_put_right:Nn #1 { \endhead }
    }
  }
  {
    \tl_put_right:Nn #1 { \caption }
    \tl_if_eq:NnF \l_datatool_short_caption_tl { \c_novalue_tl }
    {
      \tl_put_right:Nn #1 { [ }
      \tl_put_right:NV #1 \l_datatool_short_caption_tl
      \tl_put_right:Nn #1 { ] }
    }
    \tl_put_right:Nx #1 { { \exp_not:o { \l_datatool_caption_tl } } }
    \tl_if_eq:NnF \l_datatool_label_tl { \c_novalue_tl }
    {
      \tl_put_right:Nx #1
       {
         \exp_not:N \label { \l_datatool_label_tl }
       }
    }
    \bool_if:NT \l_datatool_include_header_bool
    {
      \tl_put_right:NV #1 \dtldisplaycr
      \tl_put_right:NV #1 \dtldisplaystarttab
      \tl_put_right:Nn #1 { #3 }
      \tl_put_right:NV #1 \l_datatool_post_head_tl
      \tl_if_empty:NF \dtldisplayafterhead
      {
        \tl_put_right:NV #1 \dtldisplaycr
        \tl_put_right:NV #1 \dtldisplayafterhead
      }
    }
    \tl_put_right:Nn #1 { \endfirsthead }
    \tl_put_right:Nn #1 { \caption [] }
    \tl_if_eq:NnTF \l_datatool_cont_caption_tl { \c_novalue_tl }
    {
      \tl_put_right:Nx #1 { { \exp_not:o { \l_datatool_caption_tl } } }
    }
    {
      \tl_put_right:Nx #1 { { \exp_not:o { \l_datatool_cont_caption_tl } } }
    }
    \bool_if:NT \l_datatool_include_header_bool
    {
      \tl_put_right:NV #1 \dtldisplaycr
      \tl_put_right:NV #1 \dtldisplaystarttab
      \tl_put_right:Nn #1 { #3 }
      \tl_put_right:NV #1 \l_datatool_post_head_tl
      \tl_if_empty:NF \dtldisplayafterhead
      {
        \tl_put_right:NV #1 \dtldisplaycr
        \tl_put_right:NV #1 \dtldisplayafterhead
      }
    }
    \tl_put_right:Nn #1 { \endhead }
  }
  \tl_if_eq:NnF \l_datatool_foot_tl { \c_novalue_tl }
  {
    \tl_put_right:NV #1 \l_datatool_foot_tl
    \tl_put_right:Nn #1 { \endfoot }
  }
  \tl_if_eq:NnF \l_datatool_last_foot_tl { \c_novalue_tl }
  {
    \tl_put_right:NV #1 \l_datatool_last_foot_tl
    \tl_put_right:Nn #1 { \endlastfoot }
  }
}
\NewDocumentCommand \DTLdisplaylongdbAddEnd { m }
{
  \tl_put_right:NV #1 \dtldisplayendtab
  \tl_put_right:Nx #1 { \exp_not:N \end { \dtldisplaylongdbenv } }
}
\NewDocumentCommand \DTLdisplaylongdb { o m }
{
  \DTLifdbexists { #2 }
  {
    \group_begin:
    \IfValueT { #1 }
    {
      \keys_set_filter:nnnN { datatool/display } { tabular } { #1 }
        \l__datatool_tmpb_tl
      \tl_if_empty:NF \l__datatool_tmpb_tl
      {
        \PackageWarning { datatool }
        {
          Ignoring ~ unsupported ~ \token_to_str:N\DTLdisplaylongdb
          \c_space_tl option(s): ~ \exp_not:o { \l__datatool_tmpb_tl }
        }
      }
    }
    \tl_set:Nx \dtldbname { #2 }
    \__datatool_display_long_db:
    \group_end:
  }
  {
    \PackageError { datatool }
    { Database ~ `#2' ~ doesn't ~ exist }
    { }
  }
}
\cs_new:Nn \__datatool_display_long_db:
{
  \__datatool_if_display_init:T
  {
    \l__datatool_init_display_tl
    \exp_args:NNVV \DTLdisplaylongdbAddBegin \l__datatool_content_tl
      \l__datatool_align_tl \l__datatool_row_tl
    \int_zero:N \dtlrownum
    \int_zero:N \l__datatool_row_idx_int
    \__datatool_display_loop:
    \DTLdisplaylongdbAddEnd \l__datatool_content_tl
    \l__datatool_pre_display_tl
    \l__datatool_content_tl
  }
}
\newtoks\@dtl@toksA
\newtoks\@dtl@toksB
\newrobustcmd*{\dtlswaprows}[3]{%
  \ifnum#2=#3\relax
  \else
    \ifnum#2<#3\relax
      \edef\@dtl@rowAidx{\number#2}%
      \edef\@dtl@rowBidx{\number#3}%
    \else
      \edef\@dtl@rowAidx{\number#3}%
      \edef\@dtl@rowBidx{\number#2}%
    \fi
    \edef\@dtl@dosplit{\noexpand\dtlgetrow{#1}{\@dtl@rowAidx}}%
    \@dtl@dosplit
    \expandafter\def\expandafter\@dtl@firstpart\expandafter
      {\the\dtlbeforerow}%
    \@dtl@toksA=\dtlcurrentrow
    \edef\@dtl@dosplit{\noexpand\@dtlgetrow
      {\the\dtlafterrow}{\@dtl@rowBidx}}%
    \@dtl@dosplit
    \expandafter\def\expandafter\@dtl@secondpart\expandafter
      {\the\dtlbeforerow}%
    \@dtl@toksB=\dtlcurrentrow
    \expandafter\def\expandafter\@dtl@thirdpart\expandafter
      {\the\dtlafterrow}%
    \toks@=\expandafter{\@dtl@firstpart}%
    \@dtl@toks=\expandafter{\@dtl@secondpart}%
    \edef\@dtl@tmp{\the\toks@
      \__datatool_row_markup:VV
         \@dtl@rowAidx
         \@dtl@toksB
     \the\@dtl@toks}%
    \toks@=\expandafter{\@dtl@tmp}%
    \@dtl@toks=\expandafter{\@dtl@thirdpart}%
    \edef\@dtl@tmp{\the\toks@
     \__datatool_row_markup:VV
       \@dtl@rowBidx
       \@dtl@toksA
     \the\@dtl@toks
    }%
    \__datatool_dtldb_set:nV { #1 } \@dtl@tmp
  \fi
}
\newcommand*{\dtl@decrementrows}[2]{%
  \def\@dtl@newlist{}%
  \edef\@dtl@min{\number#2}%
  \expandafter\@dtl@decrementrows\the#1%
    \db@row@elt@w%
      \db@row@id@w \@nil\db@row@id@end@%
      \db@row@id@w \@nil\db@row@id@end@%
    \db@row@elt@end@%
    \@nil
  #1=\expandafter{\@dtl@newlist}%
}
\def\@dtl@decrementrows\db@row@elt@w\db@row@id@w #1\db@row@id@end@%
#2\db@row@id@w #3\db@row@id@end@\db@row@elt@end@#4\@nil{%
  \def\@dtl@thisrow{#1}%
  \ifx\@dtl@thisrow\@nnil
    \let\@dtl@donextdec=\@dtl@gobbletonil
  \else
    \ifnum\@dtl@thisrow>\@dtl@min
      \@dtl@tmpcount=\@dtl@thisrow\relax
      \int_decr:N \@dtl@tmpcount
      \toks@{#2}%
      \@dtl@toks=\expandafter{\@dtl@newlist}%
      \edef\@dtl@newlist{\the\@dtl@toks
        \__datatool_row_markup:VV
          \@dtl@tmpcount
          \toks@
      }%
    \else
      \toks@{#2}%
      \@dtl@toks=\expandafter{\@dtl@newlist}%
      \edef\@dtl@newlist{\the\@dtl@toks
        \__datatool_row_markup:nV
        { #1 }
        \toks@
      }%
    \fi
    \let\@dtl@donextdec=\@dtl@decrementrows
  \fi
  \@dtl@donextdec#4\@nil
}
\ExplSyntaxOff
\newcommand*{\DTLremoverow}[2]{%
  \DTLifdbexists{#1}%
  {%
    \ifnum#2>0\relax
      \expandafter\ifnum\csname dtlrows@#1\endcsname<#2\relax
        \expandafter\ifnum\csname dtlrows@#1\endcsname=1\relax
          \PackageError{datatool}{Can't remove row `\number#2' from
            database `#1': no such row}{Database `#1' only has
            1 row}%
        \else
          \PackageError{datatool}{Can't remove row `\number#2' from
            database `#1': no such row}{Database `#1' only has
            \expandafter\number\csname dtlrows@#1\endcsname\space
            rows}%
        \fi
      \else
        \@DTLremoverow{#1}{#2}%
      \fi
    \else
      \PackageError{datatool}{Can't remove row \number#2: index
        out of bounds}{Row indices start at 1}%
    \fi
  }%
  {%
    \PackageError{datatool}{Can't remove row: database `#1' doesn't
      exist}{}%
  }%
}
\ExplSyntaxOn
\newcommand*{\@DTLremoverow}[2]{%
      \edef\dtl@dogetrow{\noexpand\dtlgetrow{#1}{\number#2}}%
      \dtl@dogetrow
      \expandafter\dtl@decrementrows\expandafter
        {\dtlbeforerow}{#2}%
      \expandafter\dtl@decrementrows\expandafter
        {\dtlafterrow}{#2}%
  \__datatool_dtldb_set:nx { #1 }
   {
     \exp_not:V \dtlbeforerow
     \exp_not:V \dtlafterrow
   }
  \bool_if:NTF \l__datatool_db_global_bool
   {
     \int_gdecr:c { dtlrows@ #1 }
   }
   {
     \int_decr:c { dtlrows@ #1 }
   }
}
\NewDocumentCommand \DTLsumforkeys { o o m m m }
{
  \group_begin:
  \fp_zero:N \l__datatool_total_fp
  \int_set_eq:NN
     \@dtl@datatype
     \c_datatool_unknown_int
  \tl_clear:N \l__datatool_datum_currency_tl
  \IfValueTF { #1 }
   {
     \cs_set:Nn \__datatool_filter:n
      {
        \ifthenelse { #1 } { ##1 } { }
      }
   }
   {
     \cs_set_eq:NN \__datatool_filter:n \use:n
   }
  \clist_map_inline:nn { #3 }
   {
     \DTLmapdata [ name = { ##1 }, read-only ]
     {
       \IfValueT { #2 }
        {
          \DTLmapgetvalues { #2 }
        }
       \__datatool_filter:n
        {
          \clist_map_variable:nNn
           { #4 }
           \l__datatool_item_key_tl
          {
            \DTLmapget
             {
               key = \l__datatool_item_key_tl,
               return = \l__datatool_item_value_tl
             }
            \datatool_if_null:NF \l__datatool_item_value_tl
             {
               \__datatool_parse:N
                 \l__datatool_item_value_tl
               \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
               {
                 \tl_set_eq:NN
                   \l__datatool_tmp_currency_tl
                   \l__datatool_datum_currency_tl
                 \int_set_eq:NN
                   \l__datatool_tmp_datatype_int
                   \@dtl@datatype
                 \datatool_set_fp:NV \l__datatool_datum_value_fp
                   \l__datatool_item_value_tl
                 \fp_add:Nn \l__datatool_total_fp
                   { \l__datatool_datum_value_fp }
                 \__datatool_update_datatype:
               }
             }
          }
        }
     }
   }
  \tl_set:Ne \l__datatool_result_tl
   { \fp_to_decimal:N \l__datatool_total_fp }
  \__datatool_assign_result:N #5
  \bool_if:NF \l__datatool_db_store_datum_bool
   { \tl_set:Nx #5 { #5 } }
  \exp_args:NNNV
   \group_end: \tl_set:Nn #5 #5
}
\NewDocumentCommand \DTLsumcolumn { m m m }
{
  \group_begin:
  \fp_zero:N \l__datatool_total_fp
  \int_set_eq:NN
     \@dtl@datatype
     \c_datatool_unknown_int
  \tl_clear:N \l__datatool_datum_currency_tl
  \DTLmapdata [ name = { #1 }, read-only ]
   {
     \DTLmapget
      {
        key = { #2 },
        return = \l__datatool_item_value_tl
      }
     \datatool_if_null:NF \l__datatool_item_value_tl
      {
        \__datatool_parse:N
          \l__datatool_item_value_tl
        \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
        {
           \tl_set_eq:NN
             \l__datatool_tmp_currency_tl
             \l__datatool_datum_currency_tl
           \int_set_eq:NN
             \l__datatool_tmp_datatype_int
             \@dtl@datatype
           \datatool_set_fp:NV \l__datatool_datum_value_fp
             \l__datatool_item_value_tl
           \fp_add:Nn \l__datatool_total_fp
             { \l__datatool_datum_value_fp }
           \__datatool_update_datatype:
        }
      }
   }
  \tl_set:Ne \l__datatool_result_tl
   { \fp_to_decimal:N \l__datatool_total_fp }
  \__datatool_assign_result:N #3
  \bool_if:NF \l__datatool_db_store_datum_bool
   { \tl_set:Nx #3 { #3 } }
  \exp_args:NNNV
   \group_end: \tl_set:Nn #3 #3
}
\newcount\@dtl@elements
\NewDocumentCommand \DTLmeanforkeys { o o m m m }
{%
  \group_begin:
  \int_zero:N \l__datatool_count_int
  \fp_zero:N \l__datatool_total_fp
  \int_set_eq:NN
     \@dtl@datatype
     \c_datatool_unknown_int
  \tl_clear:N \l__datatool_datum_currency_tl
  \seq_clear:N \l__datatool_tmpb_seq
  \IfValueTF { #1 }
   {
     \cs_set:Nn \__datatool_filter:n
      {
        \ifthenelse { #1 } { ##1 } { }
      }
   }
   {
     \cs_set_eq:NN \__datatool_filter:n \use:n
   }
   \__datatool_filtered_calc_mean:nnn { #2 } { #3 } { #4 }
  \tl_set:Ne \l__datatool_result_tl
   { \fp_to_decimal:N \l__datatool_mean_fp }
  \__datatool_assign_result:N #5
  \bool_if:NF \l__datatool_db_store_datum_bool
   { \tl_set:Nx #5 { #5 } }
  \exp_args:NNNV
   \group_end: \tl_set:Nn #5 #5
}
\cs_new:Nn \__datatool_filtered_calc_mean:nnn
{
  \clist_map_inline:nn { #2 }
   {
     \DTLmapdata [ name = { ##1 }, read-only ]
     {
       \IfValueT { #1 }
        {
          \DTLmapgetvalues { #1 }
        }
       \__datatool_filter:n
        {
          \clist_map_variable:nNn
           { #3 }
           \l__datatool_item_key_tl
          {
            \DTLmapget
             {
               key = \l__datatool_item_key_tl,
               return = \l__datatool_item_value_tl
             }
            \datatool_if_null:NF \l__datatool_item_value_tl
             {
               \__datatool_parse:N
                 \l__datatool_item_value_tl
               \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
               {
                 \tl_set_eq:NN
                   \l__datatool_tmp_currency_tl
                   \l__datatool_datum_currency_tl
                 \int_set_eq:NN
                   \l__datatool_tmp_datatype_int
                   \@dtl@datatype
                 \int_incr:N \l__datatool_count_int
                 \datatool_set_fp:NV \l__datatool_datum_value_fp
                   \l__datatool_item_value_tl
                 \fp_add:Nn \l__datatool_total_fp
                   { \l__datatool_datum_value_fp }
                 \seq_put_right:Nx \l__datatool_tmpb_seq
                   { \DTLdatumvalue { \l__datatool_item_value_tl } }
                 \__datatool_update_datatype:
               }
             }
          }
        }
     }
   }
  \int_if_zero:nTF { \l__datatool_count_int }
  {
    \fp_zero:N \l__datatool_mean_fp
    \PackageError
    { datatool }
    {
      no ~ numeric ~ data ~ found ~ in ~ column ~ key ~ set ~ `#3' ~
      for ~ database ~ set ~ `#2'
    }
    { }
  }
  {
    \fp_set:Nn
      \l__datatool_mean_fp
      { \l__datatool_total_fp / \l__datatool_count_int }
  }
}
\NewDocumentCommand \DTLmeanforcolumn { m m m }
{
  \DTLifdbexists { #1 }
  {
    \group_begin:
    \int_zero:N \l__datatool_count_int
    \fp_zero:N \l__datatool_total_fp
    \int_set_eq:NN
       \@dtl@datatype
       \c_datatool_unknown_int
    \tl_clear:N \l__datatool_datum_currency_tl
    \seq_clear:N \l__datatool_tmpb_seq
    \__datatool_column_calc_mean:nn { #1 } { #2 }
    \tl_set:Ne \l__datatool_result_tl
     { \fp_to_decimal:N \l__datatool_mean_fp }
    \__datatool_assign_result:N #3
    \bool_if:NF \l__datatool_db_store_datum_bool
     { \tl_set:Nx #3 { #3 } }
    \exp_args:NNNV
     \group_end: \tl_set:Nn #3 #3
  }
  {
    \PackageError { datatool }
    {
      Database ~ `#1' ~ isn't ~ defined
    }
    { }
  }
}
\cs_new:Nn \__datatool_column_calc_mean:nn
{
  \DTLmapdata [ name = { #1 }, read-only ]
  {
    \DTLmapget
     {
       key = { #2 },
       return = \l__datatool_item_value_tl
     }
    \datatool_if_null:NF \l__datatool_item_value_tl
     {
       \__datatool_parse:N
         \l__datatool_item_value_tl
       \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
       {
         \tl_set_eq:NN
           \l__datatool_tmp_currency_tl
           \l__datatool_datum_currency_tl
         \int_set_eq:NN
           \l__datatool_tmp_datatype_int
           \@dtl@datatype
         \int_incr:N \l__datatool_count_int
         \datatool_set_fp:NV \l__datatool_datum_value_fp
           \l__datatool_item_value_tl
         \fp_add:Nn \l__datatool_total_fp
           { \l__datatool_datum_value_fp }
         \seq_put_right:Nx \l__datatool_tmpb_seq
           { \DTLdatumvalue { \l__datatool_item_value_tl } }
         \__datatool_update_datatype:
       }
     }
  }
  \int_if_zero:nTF { \l__datatool_count_int }
  {
    \PackageError
    { datatool }
    {
      no ~ numeric ~ data ~ found ~ in ~ column ~ labelled ~ `#2' ~
      in ~ database ~ `#1'
    }
    { }
    \fp_zero:N \l__datatool_mean_fp
  }
  {
    \fp_set:Nn
      \l__datatool_mean_fp
      { \l__datatool_total_fp / \l__datatool_count_int }
  }
}
\NewDocumentCommand \DTLvarianceforkeys
  { o o m m m }
{
  \group_begin:
  \int_zero:N \l__datatool_count_int
  \fp_zero:N \l__datatool_total_fp
  \int_set_eq:NN
     \@dtl@datatype
     \c_datatool_unknown_int
  \tl_clear:N \l__datatool_datum_currency_tl
  \seq_clear:N \l__datatool_tmpb_seq
  \IfValueTF { #1 }
   {
     \cs_set:Nn \__datatool_filter:n
      {
        \ifthenelse { #1 } { ##1 } { }
      }
   }
   {
     \cs_set_eq:NN \__datatool_filter:n \use:n
   }
   \__datatool_filtered_calc_mean:nnn { #2 } { #3 } { #4 }
  \int_if_zero:nF { \l__datatool_count_int }
  {
    \fp_zero:N \l__datatool_tmpa_fp
    \seq_map_inline:Nn \l__datatool_tmpb_seq
     {
       \fp_set:Nn \l__datatool_tmpb_fp
        {
          ##1 - \l__datatool_mean_fp
        }
       \fp_add:Nn \l__datatool_tmpa_fp
        {
           \l__datatool_tmpb_fp * \l__datatool_tmpb_fp
        }
     }
    \fp_set:Nn \l__datatool_tmpa_fp
       { \l__datatool_tmpa_fp / \l__datatool_count_int }
    \tl_set:Ne \l__datatool_result_tl
     { \fp_to_decimal:N \l__datatool_tmpa_fp }
    \__datatool_assign_result:N #5
    \bool_if:NF \l__datatool_db_store_datum_bool
     { \tl_set:Nx #5 { #5 } }
  }
  \exp_args:NNNV
   \group_end: \tl_set:Nn #5 #5
}
\NewDocumentCommand \DTLvarianceforcolumn { m m m }
{
  \DTLifdbexists { #1 }
  {
    \group_begin:
    \int_zero:N \l__datatool_count_int
    \fp_zero:N \l__datatool_total_fp
    \int_set_eq:NN
       \@dtl@datatype
       \c_datatool_unknown_int
    \tl_clear:N \l__datatool_datum_currency_tl
    \__datatool_column_calc_mean:nn { #1 } { #2 }
    \int_if_zero:nF { \l__datatool_count_int }
    {
      \fp_zero:N \l__datatool_tmpa_fp
      \seq_map_inline:Nn \l__datatool_tmpb_seq
       {
         \fp_set:Nn \l__datatool_tmpb_fp
          {
            ##1 - \l__datatool_mean_fp
          }
         \fp_add:Nn \l__datatool_tmpa_fp
          {
             \l__datatool_tmpb_fp * \l__datatool_tmpb_fp
          }
       }
      \fp_set:Nn \l__datatool_tmpa_fp
       { \l__datatool_tmpa_fp / \l__datatool_count_int }
      \tl_set:Ne \l__datatool_result_tl
       { \fp_to_decimal:N \l__datatool_tmpa_fp }
      \__datatool_assign_result:N #3
      \bool_if:NF \l__datatool_db_store_datum_bool
       { \tl_set:Nx #3 { #3 } }
    }
    \exp_args:NNNV
     \group_end: \tl_set:Nn #3 #3
  }
  {
    \PackageError { datatool }
    {
      Database ~ `#1' ~ isn't ~ defined
    }
    { }
  }
}
\NewDocumentCommand \DTLsdforkeys
  { o o m m m }
{
  \group_begin:
  \int_zero:N \l__datatool_count_int
  \fp_zero:N \l__datatool_total_fp
  \int_set_eq:NN
     \@dtl@datatype
     \c_datatool_unknown_int
  \tl_clear:N \l__datatool_datum_currency_tl
  \seq_clear:N \l__datatool_tmpb_seq
  \IfValueTF { #1 }
   {
     \cs_set:Nn \__datatool_filter:n
      {
        \ifthenelse { #1 } { ##1 } { }
      }
   }
   {
     \cs_set_eq:NN \__datatool_filter:n \use:n
   }
   \__datatool_filtered_calc_mean:nnn { #2 } { #3 } { #4 }
  \int_if_zero:nF { \l__datatool_count_int }
  {
    \fp_zero:N \l__datatool_tmpa_fp
    \seq_map_inline:Nn \l__datatool_tmpb_seq
     {
       \fp_set:Nn \l__datatool_tmpb_fp
        {
          ##1 - \l__datatool_mean_fp
        }
       \fp_add:Nn \l__datatool_tmpa_fp
        {
           \l__datatool_tmpb_fp * \l__datatool_tmpb_fp
        }
     }
    \fp_set:Nn \l__datatool_tmpa_fp
       { sqrt ( \l__datatool_tmpa_fp / \l__datatool_count_int ) }
    \tl_set:Ne \l__datatool_result_tl
     { \fp_to_decimal:N \l__datatool_tmpa_fp }
    \__datatool_assign_result:N #5
    \bool_if:NF \l__datatool_db_store_datum_bool
     { \tl_set:Nx #5 { #5 } }
  }
  \exp_args:NNNV
   \group_end: \tl_set:Nn #5 #5
}
\NewDocumentCommand \DTLsdforcolumn { m m m }
{
  \DTLifdbexists { #1 }
  {
    \group_begin:
    \int_zero:N \l__datatool_count_int
    \fp_zero:N \l__datatool_total_fp
    \int_set_eq:NN
       \@dtl@datatype
       \c_datatool_unknown_int
    \tl_clear:N \l__datatool_datum_currency_tl
    \__datatool_column_calc_mean:nn { #1 } { #2 }
    \int_if_zero:nF { \l__datatool_count_int }
    {
      \fp_zero:N \l__datatool_tmpa_fp
      \seq_map_inline:Nn \l__datatool_tmpb_seq
       {
         \fp_set:Nn \l__datatool_tmpb_fp
          {
            ##1 - \l__datatool_mean_fp
          }
         \fp_add:Nn \l__datatool_tmpa_fp
          {
             \l__datatool_tmpb_fp * \l__datatool_tmpb_fp
          }
       }
      \fp_set:Nn \l__datatool_tmpa_fp
       { sqrt ( \l__datatool_tmpa_fp / \l__datatool_count_int ) }
      \tl_set:Ne \l__datatool_result_tl
       { \fp_to_decimal:N \l__datatool_tmpa_fp }
      \__datatool_assign_result:N #3
      \bool_if:NF \l__datatool_db_store_datum_bool
       { \tl_set:Nx #3 { #3 } }
    }
    \exp_args:NNNV
     \group_end: \tl_set:Nn #3 #3
  }
  {
    \PackageError { datatool }
    {
      Database ~ `#1' ~ isn't ~ defined
    }
    { }
  }
}
\NewDocumentCommand \DTLminforkeys { o o m m m }
{
  \group_begin:
  \fp_set_eq:NN \l__datatool_min_fp \c_inf_fp
  \tl_clear:N #5
  \IfValueTF { #1 }
   {
     \cs_set:Nn \__datatool_filter:n
      {
        \ifthenelse { #1 } { ##1 } { }
      }
   }
   {
     \cs_set_eq:NN \__datatool_filter:n \use:n
   }
  \clist_map_inline:nn { #3 }
   {
     \DTLmapdata [ name = { ##1 }, read-only ]
     {
       \IfValueT { #2 }
        {
          \DTLmapgetvalues { #2 }
        }
       \__datatool_filter:n
        {
          \clist_map_variable:nNn
           { #4 }
           \l__datatool_item_key_tl
          {
            \DTLmapget
             {
               key = \l__datatool_item_key_tl,
               return = \l__datatool_item_value_tl
             }
            \datatool_if_null:NF \l__datatool_item_value_tl
             {
               \__datatool_parse:N
                 \l__datatool_item_value_tl
               \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
               {
                 \datatool_set_fp:NV \l__datatool_datum_value_fp
                   \l__datatool_item_value_tl
                 \fp_compare:nNnT
                   { \l__datatool_min_fp }
                    >
                   { \l__datatool_datum_value_fp }
                 {
                   \fp_set_eq:NN
                     \l__datatool_min_fp
                     \l__datatool_datum_value_fp
                   \tl_set_eq:NN #5 \l__datatool_item_value_tl
                 }
               }
             }
          }
        }
     }
   }
  \tl_if_empty:NTF #5
   {
     \PackageError { datatool }
     {
       no ~ numeric ~ data ~ found ~ in ~ column ~ key ~ set ~ `#3' ~
      for ~ database ~ set ~ `#2'
     }
     { }
   }
   {
     \bool_if:NF \l__datatool_db_store_datum_bool
      {
        \tl_set:Nx #5 { #5 }
      }
   }
  \exp_args:NNNV
   \group_end: \tl_set:Nn #5 #5
}
\NewDocumentCommand \DTLminforcolumn { m m m }
{
  \DTLifdbexists{#1}%
  {
  \group_begin:
  \fp_set_eq:NN \l__datatool_min_fp \c_inf_fp
  \tl_clear:N #3
  \DTLmapdata [ name = { #1 }, read-only ]
   {
     \DTLmapget
      {
        key = { #2 },
        return = \l__datatool_item_value_tl
      }
     \datatool_if_null:NF \l__datatool_item_value_tl
      {
        \__datatool_parse:N
          \l__datatool_item_value_tl
        \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
        {
          \datatool_set_fp:NV \l__datatool_datum_value_fp
            \l__datatool_item_value_tl
          \fp_compare:nNnT
            { \l__datatool_min_fp }
             >
            { \l__datatool_datum_value_fp }
          {
            \fp_set_eq:NN
              \l__datatool_min_fp
              \l__datatool_datum_value_fp
            \tl_set_eq:NN #3 \l__datatool_item_value_tl
          }
        }
      }
   }
  \tl_if_empty:NTF #3
   {
     \PackageError { datatool }
     {
       no ~ numeric ~ data ~ found ~ in ~ column ~ `#2' ~
      for ~ database ~ `#1'
     }
     { }
   }
   {
     \bool_if:NF \l__datatool_db_store_datum_bool
      {
        \tl_set:Nx #3 { #3 }
      }
   }
  \exp_args:NNNV
   \group_end: \tl_set:Nn #3 #3
  }
  {
    \PackageError {datatool}
    { Database ~ `#1' ~ doesn't ~ exist}
    {}
  }
}
\NewDocumentCommand \DTLmaxforkeys { o o m m m }
{
  \group_begin:
  \fp_set_eq:NN \l__datatool_max_fp \c_minus_inf_fp
  \tl_clear:N #5
  \IfValueTF { #1 }
   {
     \cs_set:Nn \__datatool_filter:n
      {
        \ifthenelse { #1 } { ##1 } { }
      }
   }
   {
     \cs_set_eq:NN \__datatool_filter:n \use:n
   }
  \clist_map_inline:nn { #3 }
   {
     \DTLmapdata [ name = { ##1 }, read-only ]
     {
       \IfValueT { #2 }
        {
          \DTLmapgetvalues { #2 }
        }
       \__datatool_filter:n
        {
          \clist_map_variable:nNn
           { #4 }
           \l__datatool_item_key_tl
          {
            \DTLmapget
             {
               key = \l__datatool_item_key_tl,
               return = \l__datatool_item_value_tl
             }
            \datatool_if_null:NF \l__datatool_item_value_tl
             {
               \__datatool_parse:N
                 \l__datatool_item_value_tl
               \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
               {
                 \datatool_set_fp:NV \l__datatool_datum_value_fp
                   \l__datatool_item_value_tl
                 \fp_compare:nNnT
                   { \l__datatool_max_fp }
                    <
                   { \l__datatool_datum_value_fp }
                 {
                   \fp_set_eq:NN
                     \l__datatool_max_fp
                     \l__datatool_datum_value_fp
                   \tl_set_eq:NN #5 \l__datatool_item_value_tl
                 }
               }
             }
          }
        }
     }
   }
  \tl_if_empty:NTF #5
   {
     \PackageError { datatool }
     {
       no ~ numeric ~ data ~ found ~ in ~ column ~ key ~ set ~ `#3' ~
      for ~ database ~ set ~ `#2'
     }
     { }
   }
   {
     \bool_if:NF \l__datatool_db_store_datum_bool
      {
        \tl_set:Nx #5 { #5 }
      }
   }
  \exp_args:NNNV
   \group_end: \tl_set:Nn #5 #5
}
\NewDocumentCommand \DTLmaxforcolumn { m m m }
{
  \DTLifdbexists{#1}%
  {
  \group_begin:
  \fp_set_eq:NN \l__datatool_max_fp \c_minus_inf_fp
  \tl_clear:N #3
  \DTLmapdata [ name = { #1 }, read-only ]
   {
     \DTLmapget
      {
        key = { #2 },
        return = \l__datatool_item_value_tl
      }
     \datatool_if_null:NF \l__datatool_item_value_tl
      {
        \__datatool_parse:N
          \l__datatool_item_value_tl
        \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
        {
          \datatool_set_fp:NV \l__datatool_datum_value_fp
            \l__datatool_item_value_tl
          \fp_compare:nNnT
            { \l__datatool_max_fp }
             <
            { \l__datatool_datum_value_fp }
          {
            \fp_set_eq:NN
              \l__datatool_max_fp
              \l__datatool_datum_value_fp
            \tl_set_eq:NN #3 \l__datatool_item_value_tl
          }
        }
      }
   }
  \tl_if_empty:NTF #3
   {
     \PackageError { datatool }
     {
       no ~ numeric ~ data ~ found ~ in ~ column ~ `#2' ~
      for ~ database ~ `#1'
     }
     { }
   }
   {
     \bool_if:NF \l__datatool_db_store_datum_bool
      {
        \tl_set:Nx #3 { #3 }
      }
   }
  \exp_args:NNNV
   \group_end: \tl_set:Nn #3 #3
  }
  {
    \PackageError {datatool}
    { Database ~ `#1' ~ doesn't ~ exist}
    {}
  }
}
\NewDocumentCommand \DTLcomputebounds { o m m m m m m m }
{
  \group_begin:
  \fp_set_eq:NN \l__datatool_min_fp \c_inf_fp
  \fp_set_eq:NN \l__datatool_min_ii_fp \c_inf_fp
  \fp_set_eq:NN \l__datatool_max_fp \c_minus_inf_fp
  \fp_set_eq:NN \l__datatool_max_ii_fp \c_minus_inf_fp
  \tl_clear:N #5
  \tl_clear:N #6
  \tl_clear:N #7
  \tl_clear:N #8
  \IfValueTF { #1 }
   {
     \cs_set:Nn \__datatool_filter:n
      {
        \ifthenelse { #1 } { ##1 } { }
      }
   }
   {
     \cs_set_eq:NN \__datatool_filter:n \use:n
   }
  \clist_map_inline:nn { #2 }
   {
     \DTLmapdata [ name = { ##1 }, read-only ]
     {
       \DTLmapgetvalues
        {
          \DTLthisX = #3, \DTLthisY = #4
        }
       \__datatool_filter:n
        {
          \tl_set_eq:NN
            \l__datatool_item_value_tl \DTLthisX
          \datatool_if_null:NF \l__datatool_item_value_tl
           {
             \__datatool_parse:N
               \l__datatool_item_value_tl
             \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
             {
               \datatool_set_fp:NV \l__datatool_datum_value_fp
                 \l__datatool_item_value_tl
               \fp_compare:nNnT
                 { \l__datatool_min_fp }
                  >
                 { \l__datatool_datum_value_fp }
               {
                 \fp_set_eq:NN
                   \l__datatool_min_fp
                   \l__datatool_datum_value_fp
                 \tl_set_eq:Nx #5
                 { \DTLdatumvalue { \l__datatool_item_value_tl } }
               }
               \fp_compare:nNnT
                 { \l__datatool_max_fp }
                  <
                 { \l__datatool_datum_value_fp }
               {
                 \fp_set_eq:NN
                   \l__datatool_max_fp
                   \l__datatool_datum_value_fp
                 \tl_set_eq:Nx #7
                 { \DTLdatumvalue { \l__datatool_item_value_tl } }
               }
             }
           }
          \tl_set_eq:NN
            \l__datatool_item_value_tl \DTLthisY
          \datatool_if_null:NF \l__datatool_item_value_tl
           {
             \__datatool_parse:N
               \l__datatool_item_value_tl
             \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
             {
               \datatool_set_fp:NV \l__datatool_datum_value_fp
                 \l__datatool_item_value_tl
               \fp_compare:nNnT
                 { \l__datatool_min_ii_fp }
                  >
                 { \l__datatool_datum_value_fp }
               {
                 \fp_set_eq:NN
                   \l__datatool_min_ii_fp
                   \l__datatool_datum_value_fp
                 \tl_set_eq:Nx #6
                 { \DTLdatumvalue { \l__datatool_item_value_tl } }
               }
               \fp_compare:nNnT
                 { \l__datatool_max_ii_fp }
                  <
                 { \l__datatool_datum_value_fp }
               {
                 \fp_set_eq:NN
                   \l__datatool_max_ii_fp
                   \l__datatool_datum_value_fp
                 \tl_set_eq:Nx #8
                 { \DTLdatumvalue { \l__datatool_item_value_tl } }
               }
             }
           }
        }
     }
   }
  \tl_if_empty:NTF #5
   {
     \PackageError { datatool }
     {
       no ~ numeric ~ data ~ found ~ in ~ column ~ `#3' ~
      for ~ database ~ set ~ `#2'
     }
     { }
   }
  \tl_set:Nx \l__datatool_tmpa_tl
    {
      \exp_not:N \group_end:
      \exp_not:N \tl_set:Nn \exp_not:N #5 { #5 }
      \exp_not:N \tl_set:Nn \exp_not:N #6 { #6 }
      \exp_not:N \tl_set:Nn \exp_not:N #7 { #7 }
      \exp_not:N \tl_set:Nn \exp_not:N #8 { #8 }
    }
  \l__datatool_tmpa_tl
}
\NewDocumentCommand \dtlmapcurrentrow { m m }
{
  \tl_if_empty:VTF \dtlcurrentrow
  {
    \PackageError { datatool }
    {
      Unable ~ to ~ map ~ current ~ row ~ (missing or empty)
    }
    {}
  }
  {
    \__datatool_map_current_row:Nn #1 { #2 }
  }
}
\cs_new:Nn \__datatool_map_current_row:Nn
{
  \cs_set:Nn \__datatool_map_row_loop_body:n
   {
     \tl_set:Nn #1 { ##1 }
     \exp_args:NV \tl_if_head_eq_meaning:nNT
        #1 \__datatool_datum:w
      {
        \__datatool_to_datum:N #1
      }
     #2
   }
  \exp_last_unbraced:NV \__datatool_map_row:w
    \dtlcurrentrow
    \db@col@id@w \q_recursion_tail \db@col@id@end@
    \db@col@elt@w \db@col@elt@end@
    \db@col@id@w \q_recursion_tail \db@col@id@end@
  \q_recursion_stop
  \prg_break_point:Nn \__datatool_map_row_break: { }
}
\ExplSyntaxOff
\newcommand*{\DTLgetvalueforkey}[5]{%
  \DTLgetrowforkey{\@dtl@row}{#3}{#4}{#5}%
  \@sdtl@getcolumnindex{\@dtl@col}{#3}{#2}%
  {%
    \dtlcurrentrow=\expandafter{\@dtl@row}%
    \edef\@dtl@dogetval{\noexpand\dtlgetentryfromcurrentrow
      {\noexpand\@dtl@val}{\@dtl@col}}%
    \@dtl@dogetval
    \global\let#1=\@dtl@val
  }%
}
\newcommand*{\DTLgetrowforkey}[4]{%
  \global\let#1=\@empty
  \@sDTLforeach{#2}{\dtl@refvalue=#3}{%
    \DTLifnull{\dtl@refvalue}%
    {}%
    {%
      \ifthenelse{\equal{\dtl@refvalue}{#4}}%
      {%
        \xdef#1{\the\dtlcurrentrow}%
        \dtlbreak
      }%
      {}%
    }%
  }%
}
\ExplSyntaxOn
\seq_new:N \l__datatool_sort_order_seq
\seq_new:N \l__datatool_sort_numeric_seq
\seq_new:N \l__datatool_replacement_indexes_seq
\bool_new:N \l__datatool_sort_order_bool
\clist_new:N \l__datatool_sort_replacements_clist
\keys_define:nn { datatool / sortdata / criteria }
{
  ascending .bool_set:N = \l__datatool_sort_order_bool ,
  descending .bool_set_inverse:N = \l__datatool_sort_order_bool ,
  asc .code:n =
   {
     \bool_set_true:N \l__datatool_sort_order_bool
   } ,
  asc .value_forbidden:n = true ,
  desc .code:n =
   {
     \bool_set_false:N \l__datatool_sort_order_bool
   } ,
  desc .value_forbidden:n = true ,
  replacements .code:n =
   {
     \clist_set:No
       \l__datatool_sort_replacements_clist
        { #1 }
   } ,
}
\cs_new:Nn \__datatool_replacement_handler:n
{
 \tl_if_exist:cTF
  {
    dtl@ci
    @ \l__datatool_default_dbname_tl
    @ #1
  }
  {
    \clist_put_right:Nv \l__datatool_tmpa_clist
     {
       dtl@ci
       @ \l__datatool_default_dbname_tl
       @ #1
     }
  }
  {
   \__datatool_sortdata_missing_column:nn
    {
      \token_to_str:N \DTLsortdata : ~ No ~ column ~ with ~ key ~
      ` #1 ' ~ in ~ database ~
      ` \l__datatool_default_dbname_tl ' ~
      in ~ replacement ~ list ~ for ~
      column ~ key ` #1 '. ~
      Ignoring ~ ` \l__datatool_item_key_tl '
    }
    {
      Check ~ that ~ you ~ have ~ correctly ~ spelt ~ the ~
      column ~ key ~ and ~ database ~ name ~
    }
  }
}
\cs_new:Nn \__datatool_parse_sort_criteria_list:n
{
  \seq_clear:N \l__datatool_sort_order_seq
  \seq_clear:N \l__datatool_sort_numeric_seq
  \seq_clear:N \l__datatool_column_indexes_seq
  \seq_clear:N \l__datatool_replacement_indexes_seq
  \clist_map_inline:nn { #1 }
   {
     \keyval_parse:NNn
       \__datatool_parse_sort_criteria:n
       \__datatool_parse_sort_criteria:nn
       { ##1 }
     \tl_if_exist:cTF
      {
        dtl@ci
         @ \l__datatool_default_dbname_tl
         @ \l__datatool_item_key_tl
      }
      {
        \exp_args:NNc
          \int_set:Nn \l__datatool_col_idx_int
           {
              dtl@ci
              @ \l__datatool_default_dbname_tl
              @ \l__datatool_item_key_tl
           }
       \seq_put_right:Nx \l__datatool_column_indexes_seq
         { \int_use:N \l__datatool_col_idx_int }
       \bool_if:NTF \l__datatool_sort_order_bool
        {
          \seq_put_right:Nn \l__datatool_sort_order_seq
            { \c_true_bool }
        }
        {
          \seq_put_right:Nn \l__datatool_sort_order_seq
            { \c_false_bool }
        }
       \__datatool_get_col_type:vV
         { dtlkeys @ \l__datatool_default_dbname_tl }
          \l__datatool_col_idx_int
       \datatool_if_numeric_datum_type:nTF
          { \l__datatool_item_type_int }
        {
         \seq_put_right:Nn \l__datatool_sort_numeric_seq
           { \c_true_bool }
        }
        {
         \seq_put_right:Nn \l__datatool_sort_numeric_seq
           { \c_false_bool }
        }
       \clist_clear:N \l__datatool_tmpa_clist
       \clist_map_function:NN
          \l__datatool_sort_replacements_clist
          \__datatool_replacement_handler:n
       \seq_put_right:Nx \l__datatool_replacement_indexes_seq
         { \l__datatool_tmpa_clist }
      }
      {
        \__datatool_sortdata_missing_column:nn
         {
           \token_to_str:N \DTLsortdata : ~ No ~ column ~ with ~ key ~
           ` \l__datatool_item_key_tl ' ~ in ~ database ~
           ` \l__datatool_default_dbname_tl ' . ~
           Ignoring ~ ` \l__datatool_item_key_tl '
         }
         {
           Check ~ that ~ you ~ have ~ correctly ~ spelt ~ the ~
           column ~ key ~ and ~ database ~ name ~
         }
      }
   }
}
\cs_new:Nn \__datatool_parse_sort_criteria:n
{
  \bool_set_true:N \l__datatool_sort_order_bool
  \clist_clear:N \l__datatool_sort_replacements_clist
  \tl_set:Nn \l__datatool_item_key_tl { #1 }
}
\cs_new:Nn \__datatool_parse_sort_criteria:nn
{
  \__datatool_parse_sort_criteria:n { #1 }
  \keys_set:nn { datatool / sortdata / criteria } { #2 }
}
\cs_new:Nn \__datatool_set_sort_value:NNnnN
{
  \__datatool_get_entry_from_row:Nnn
    \l__datatool_item_value_tl { #3 } { #4 }
  \__datatool_if_replace:NTF \l__datatool_item_value_tl
   {
      \tl_set_eq:NN \l__datatool_content_tl \dtlnovalue
      \clist_if_empty:NF #5
       {
         \clist_pop:NN #5 \l__datatool_item_col_tl
         \__datatool_set_sort_value:NNVnN
            #1 #2 \l__datatool_item_col_tl
            { #4 } #5
       }
   }
   {
     \cs_if_eq:NNF \__datatool_encap_sort:nnn \use_i:nnn
      {
        \tl_set:Ne \l__datatool_item_value_tl
          {
            \__datatool_encap_sort:onn
              { \l__datatool_item_value_tl }
              { #3 } { \l__datatool_default_dbname_tl }
          }
      }
     \__datatool_parse:N \l__datatool_item_value_tl
     \bool_if:NTF #2
      {
       \datatool_if_numeric_datum_type:nTF { \@dtl@datatype }
        {
          \tl_set_eq:NN \l__datatool_content_tl
            \l__datatool_datum_value_tl
        }
        {
          \tl_set:Nx \l__datatool_item_value_tl
            { \l__datatool_datum_original_value_tl }
          \__datatool_parse:N \l__datatool_item_value_tl
          \datatool_if_numeric_datum_type:nTF { \@dtl@datatype }
          {
            \tl_set_eq:NN \l__datatool_content_tl
              \l__datatool_datum_value_tl
          }
          {
            \tl_set:Nn \l__datatool_content_tl { 0 }
            \tl_set:Nx \l__datatool_item_value_tl
             {
               \exp_not:N \__datatool_datum:nnnn
                { \l__datatool_item_value_tl }
                { 0 } { }
                { \c_datatool_integer_int }
             }
          }
        }
      }
      {
       \exp_args:NV #1
         \l__datatool_datum_original_value_tl
         \l__datatool_content_tl
       \tl_set:Nx \l__datatool_item_value_tl
         {
           \exp_not:N \__datatool_datum:nnnn
            { \l__datatool_item_value_tl }
            {  } { }
            { \c_datatool_string_int }
         }
      }
   }
  \datatool_if_null:NT \l__datatool_content_tl
   {
     \bool_if:NTF #2
      {
        \tl_set:Nn \l__datatool_content_tl { 0 }
      }
      {
        \tl_clear:N \l__datatool_content_tl
      }
     \tl_set_eq:NN \l__datatool_item_value_tl \dtlnovalue
   }
}
\cs_generate_variant:Nn
  \__datatool_set_sort_value:NNnnN
  { NNnVN , NNVnN }
\cs_new:Nn \__datatool_default_sort_data_fn:nN
{
  \DTLsortwordhandler { #1 } #2
}
\cs_set_eq:NN
  \__datatool_sort_data_fn:nN
  \__datatool_default_sort_data_fn:nN
\int_new:N \__datatool_sort_data_sortcol_int
\tl_new:N \__datatool_sort_data_sortcol_tl
\int_new:N \__datatool_sort_data_grpcol_int
\tl_new:N \__datatool_sort_data_grpcol_tl
\cs_set_eq:NN
  \__datatool_if_replace:NTF
  \datatool_if_null_or_empty:NTF
\cs_new:Nn \__datatool_encap_sort:nnn { #1 }
\cs_generate_variant:Nn \__datatool_encap_sort:nnn { onn }
\cs_new:Nn \__datatool_sortdata_missing_column_err:nn
{
  \PackageError { datatool } { #1 } { #2 }
}
\cs_new:Nn \__datatool_sortdata_missing_column_warn:nn
{
  \PackageWarning { datatool } { #1 }
}
\cs_set_eq:NN
 \__datatool_sortdata_missing_column:nn
 \__datatool_sortdata_missing_column_err:nn
\keys_define:nn { datatool / sortdata }
{
  missing-column-action .choice: ,
  missing-column-action / error .code:n =
   {
     \cs_set_eq:NN
      \__datatool_sortdata_missing_column:nn
      \__datatool_sortdata_missing_column_err:nn
   } ,
  missing-column-action / warn .code:n =
   {
     \cs_set_eq:NN
      \__datatool_sortdata_missing_column:nn
      \__datatool_sortdata_missing_column_warn:nn
   } ,
  missing-column-action / ignore .code:n =
   {
     \cs_set_eq:NN
      \__datatool_sortdata_missing_column:nn
      \use_none:nn
   } ,
  function .code:n =
   {
     \cs_set_eq:NN \__datatool_sort_data_fn:nN #1
   } ,
  encap .code:n =
   {
     \tl_if_empty:nTF { #1 }
      {
        \cs_set_eq:NN \__datatool_encap_sort:nnn \use_i:nnn
      }
      {
        \cs_set:Nn \__datatool_encap_sort:nnn
         { #1 { ##1 } { ##2 } { ##3 } }
      }
   },
  replace .choice: ,
  replace / null .code:n =
   {
     \cs_set_eq:NN
        \__datatool_if_replace:NTF
         \datatool_if_null:NTF
   } ,
  replace / null ~ or ~ empty .code:n =
   {
     \cs_set_eq:NN
        \__datatool_if_replace:NTF
         \datatool_if_null_or_empty:NTF
   } ,
  save-sort .code:n =
   {
     \tl_set:Nn \__datatool_sort_data_sortcol_tl { sort }
     \int_zero:N \__datatool_sort_data_sortcol_int
   } ,
  save-sort .value_forbidden:n = true ,
  save-group .code:n =
   {
     \tl_set:Nn \__datatool_sort_data_grpcol_tl { group }
     \int_zero:N \__datatool_sort_data_grpcol_int
   } ,
  save-group .value_forbidden:n = true ,
  save-sort-column .code:n =
   {
     \int_set:Nn \__datatool_sort_data_sortcol_int { #1 }
     \tl_clear:N \__datatool_sort_data_sortcol_tl
   },
  save-sort-column .value_required:n = true ,
  save-sort-key .code:n =
   {
     \tl_set:Nn \__datatool_sort_data_sortcol_tl { #1 }
     \int_zero:N \__datatool_sort_data_sortcol_int
   },
  save-sort-key .value_required:n = true ,
  save-group-column .code:n =
   {
     \int_set:Nn \__datatool_sort_data_grpcol_int { #1 }
     \tl_clear:N \__datatool_sort_data_grpcol_tl
   },
  save-group-column .value_required:n = true ,
  save-group-key .code:n =
   {
     \tl_set:Nn \__datatool_sort_data_grpcol_tl { #1 }
     \int_zero:N \__datatool_sort_data_grpcol_int
   },
  save-group-key .value_required:n = true ,
}
\NewDocumentCommand \DTLsortdata
  { o m m }
{
  \__datatool_db_sort:nnn { #1 } { #2 } { #3 }
}
\cs_new:Nn \__datatool_db_sort:nnn
{
  \cs_set_eq:NN
    \__datatool_sort_data_fn:nN
    \__datatool_default_sort_data_fn:nN
  \cs_set_eq:NN
    \__datatool_if_replace:NTF
    \datatool_if_null_or_empty:NTF
  \cs_set_eq:NN \__datatool_encap_sort:nnn \use_i:nnn
  \int_zero:N \__datatool_sort_data_sortcol_int
  \tl_clear:N \__datatool_sort_data_sortcol_tl
  \int_zero:N \__datatool_sort_data_grpcol_int
  \tl_clear:N \__datatool_sort_data_grpcol_tl
  \IfValueT { #1 }
   {
     \keys_set:nn { datatool / sortdata } { #1 }
   }
  \tl_if_empty:nTF { #2 }
  {
    \exp_args:No \__datatool_db_sort:nn
     { \l__datatool_default_dbname_tl }
     { #3 }
  }
  {
    \exp_args:Nx \__datatool_db_sort:nn
     { \text_purify:n { #2 } } { #3 }
  }
}
\cs_generate_variant:Nn \__datatool_db_sort:nnn
 { VVV }
\cs_new:Nn \__datatool_db_sort:nn
{
 \datatool_db_state:nnnn { #1 }
 {
  \__datatool_sort_db_check_opts:n { #1 }
  \group_begin:
  \tl_set:Nx \l__datatool_default_dbname_tl { #1 }
  \int_compare:nNnTF
     { \DTLrowcount {  \l__datatool_default_dbname_tl } }
      >
     { 200 }
   {
     \typeout
      {
        Sorting ~ database ~
       ` \l__datatool_default_dbname_tl' ~ - ~
       this ~ may ~ take ~ a ~ while.
      }
   }
   {
     \dtl@message { Sorting ~ database ~ ` \l__datatool_default_dbname_tl ' }
   }
  \dtl@SortWordCommands@hook
  \seq_clear:N \l__datatool_wordlist_seq
  \__datatool_parse_sort_criteria_list:n { #2 }
  \seq_if_empty:NTF \l__datatool_column_indexes_seq
  {
    \PackageError { datatool }
     {
       \token_to_str:N \DTLsortdata : ~ missing ~ sort ~
       criteria
     }
     {
       The ~ final ~ argument ~ of ~ \token_to_str:N \DTLsortdata
       \c_space_tl ~ must ~ have ~ at ~ least ~ one ~ column ~ key
     }
  }
  {
    \bool_set_true:N \l__datatool_sort_datum_bool
    \DTLmapdata [ read-only ]
    {
      \seq_clear:N \l__datatool_sorta_seq
      \seq_map_indexed_inline:Nn
        \l__datatool_column_indexes_seq
       {
         \exp_args:NNx
           \bool_set:Nn \l__datatool_sort_numeric_bool
            {
              \seq_item:Nn
                \l__datatool_sort_numeric_seq { ##1 }
            }
         \tl_set:Nx
           \l__datatool_sort_replacements_clist
            {
              \seq_item:Nn
                \l__datatool_replacement_indexes_seq { ##1 }
            }
         \__datatool_set_sort_value:NNnVN
           \__datatool_sort_data_fn:nN
            \l__datatool_sort_numeric_bool
            { ##2 }
            \l__datatool_map_data_row_tl
           \l__datatool_sort_replacements_clist
        \bool_if:NTF \l__datatool_sort_numeric_bool
        {
          \dtl@message{ Row ~ \int_use:N \dtlrownum \c_space_tl ~
            numeric ~ sort ~ value ~ for ~ column ~ ##2 : ~
            \l__datatool_content_tl }
        }
        {
          \dtl@message{ Row ~ \int_use:N \dtlrownum \c_space_tl ~
            byte ~ sort ~ value ~ for ~ column ~ ##2 : ~
            \l__datatool_content_tl }
        }
         \seq_put_right:Nx
           \l__datatool_sorta_seq
            {
              { \exp_not:V \l__datatool_content_tl }
              { \exp_not:V \l__datatool_item_value_tl }
            }
       }
      \exp_args:Noo \__datatool_sortword_append:nn
         {\l__datatool_sorta_seq }
         { \l__datatool_map_data_row_tl }
    }
  }
  \exp_args:NVV
    \__datatool_start_datasortword_list:nn
       \l__datatool_wordlist_seq
       \l__datatool_sort_order_seq
  \__datatool_finish_sort_db:n { #1 }
 }
 { }% empty
 {
   \PackageError { datatool }
   {
     \token_to_str:N \DTLsortdata : ~ database ~
     `#1' ~ doesn't ~ exist
   }
   {
     Check ~ that ~ you ~ have ~ correctly ~ spelt ~
     the ~ database ~ name ~ or ~ set ~ the ~ default ~
     name, ~ as ~ applicable
   }
 }
}
\cs_new:Nn \__datatool_start_datasortword_list:nn
{
   \group_end:
   \tl_set:Nn \l__datatool_wordlist_seq { #1 }
   \tl_set:Nn \l__datatool_sort_order_seq { #2 }
   \cs_set_eq:NN
    \__datatool_compare_sortitem:w
    \__datatool_compare_datasortitem:w
   \seq_sort:Nn \l__datatool_wordlist_seq
   {
     \__datatool_compare_sortitem:w ##1 ##2
   }
}
\seq_new:N \l__datatool_sorta_seq
\seq_new:N \l__datatool_sortb_seq
\cs_new:Npn \__datatool_compare_datasortitem:w
 #1\q_mark#2\q_stop#3\q_mark#4\q_stop
 {
   \tl_set:Nn \l__datatool_sorta_seq { #1 }
   \tl_set:Nn \l__datatool_sortb_seq { #3 }
   \seq_set_eq:NN
     \l__datatool_tmp_seq
     \l__datatool_sort_order_seq
   \__datatool_compare_datasortitem:
   \int_compare:nNnTF
       { \dtl@sortresult } < { \c_zero_int }
    {
      \sort_return_same:
    }
    {
      \int_compare:nNnTF
         { \dtl@sortresult } > { \c_zero_int }
       {
         \sort_return_swapped:
       }
       {
         \bool_lazy_or:nnTF
           { \seq_if_empty_p:N \l__datatool_sorta_seq }
           { \seq_if_empty_p:N \l__datatool_sortb_seq }
         {
           \sort_return_same:
         }
         {
           \tl_set:Nn \l__datatool_sorta_seq { #1 }
           \tl_set:Nn \l__datatool_sortb_seq { #3 }
           \seq_get_left:NN
              \l__datatool_sorta_seq
              \l__datatool_tmpa_tl
           \seq_get_left:NN
              \l__datatool_sortb_seq
              \l__datatool_tmpb_tl
         \tl_set:Nx \l__datatool_tmpa_tl
           {
             \exp_last_unbraced:NV
               \use_ii:nn \l__datatool_tmpa_tl
           }
         \tl_set:Nx \l__datatool_tmpb_tl
           {
             \exp_last_unbraced:NV
               \use_ii:nn \l__datatool_tmpb_tl
           }
         \bool_if:NTF \l__datatool_sort_reverse_bool
          {
            \exp_args:NVV
            \__datatool_fallback_action:nnnn
              \l__datatool_tmpb_tl
              \l__datatool_tmpa_tl
             { \sort_return_swapped: }
             { \sort_return_same: }
          }
          {
            \exp_args:NVV
            \__datatool_fallback_action:nnnn
              \l__datatool_tmpa_tl
              \l__datatool_tmpb_tl
             { \sort_return_swapped: }
             { \sort_return_same: }
          }
         }
       }
    }
 }
\cs_new:Nn \__datatool_compare_datasortitem:
{
   \bool_lazy_or:nnTF
     { \seq_if_empty_p:N \l__datatool_sorta_seq }
     { \seq_if_empty_p:N \l__datatool_sortb_seq }
   {
     \int_zero:N \dtl@sortresult
   }
   {
     \seq_if_empty:NTF \l__datatool_tmp_seq
      {
        \bool_set_false:N \l__datatool_sort_reverse_bool
      }
      {
        \seq_pop_left:NN
          \l__datatool_tmp_seq
          \l__datatool_sort_reverse_bool
        \bool_set_inverse:N \l__datatool_sort_reverse_bool
      }
     \seq_pop_left:NN
        \l__datatool_sorta_seq
        \l__datatool_tmpa_tl
     \seq_pop_left:NN
        \l__datatool_sortb_seq
        \l__datatool_tmpb_tl
     \exp_args:NVV
       \__datatool_compare_sortitem:nn
         \l__datatool_tmpa_tl
         \l__datatool_tmpb_tl
       \int_if_zero:nT { \dtl@sortresult }
        {
          \__datatool_compare_datasortitem:
        }
    }
}
\cs_new:Nn \__datatool_compare_sortitem:nn
{
  \__datatool_compare_sortitem:nnnn #1 #2
}
\cs_new:Nn \__datatool_sort_db_check_opts:n
{
  \tl_if_empty:NF \__datatool_sort_data_sortcol_tl
   {
     \tl_if_exist:cTF
       { dtl@ci@ #1 @ \__datatool_sort_data_sortcol_tl }
      {
        \int_set:Nn \__datatool_sort_data_sortcol_int
         {
           \use:c { dtl@ci@ #1 @ \__datatool_sort_data_sortcol_tl }
         }
      }
      {
        \__datatool_add_column_with_header:nxxx
          { #1 }
          { \__datatool_sort_data_sortcol_tl }
          { \int_use:N \c_datatool_string_int }
          { \__datatool_sort_data_sortcol_tl }
        \int_set:Nn \__datatool_sort_data_sortcol_int
          { \DTLcolumncount { #1 } }
      }
   }
  \tl_if_empty:NF \__datatool_sort_data_grpcol_tl
   {
     \tl_if_exist:cTF
       { dtl@ci@ #1 @ \__datatool_sort_data_grpcol_tl }
      {
        \int_set:Nn \__datatool_sort_data_grpcol_int
         {
           \use:c { dtl@ci@ #1 @ \__datatool_sort_data_grpcol_tl }
         }
      }
      {
        \__datatool_add_column_with_header:nxxx
          { #1 }
          { \__datatool_sort_data_grpcol_tl }
          { \int_use:N \c_datatool_string_int }
          { \__datatool_sort_data_grpcol_tl }
        \int_set:Nn \__datatool_sort_data_grpcol_int
          { \DTLcolumncount { #1 } }
      }
   }
   \int_compare:nNnT
    { \__datatool_sort_data_sortcol_int } > { \DTLcolumncount { #1 } }
   {
     \PackageError { datatool }
      {
        save-sort-column ~ value ~
        \int_use:N \__datatool_sort_data_sortcol_int
        \c_space_tl ~ out ~ of ~ range. ~ Ignoring
      }
      {
        Database ~ ` #1 ' ~ only ~ has ~ \DTLcolumncount { #1 } ~
        columns. ~ Use ~ save-sort-key ~ instead ~ if ~ you ~
        want ~ to ~ create ~ a ~ new ~ column
      }
     \int_zero:N \__datatool_sort_data_sortcol_int
   }
   \int_compare:nNnT
    { \__datatool_sort_data_grpcol_int } > { \DTLcolumncount { #1 } }
   {
     \PackageError { datatool }
      {
        save-group-column ~ value ~
        \int_use:N \__datatool_sort_data_grpcol_int
        \c_space_tl ~ out ~ of ~ range. ~ Ignoring
      }
      {
        Database ~ ` #1 ' ~ only ~ has ~ \DTLcolumncount { #1 } ~
        columns. ~ Use ~ save-group-key ~ instead ~ if ~ you ~
        want ~ to ~ create ~ a ~ new ~ column
      }
     \int_zero:N \__datatool_sort_data_grpcol_int
   }
}
\cs_new:Nn \__datatool_finish_sort_db:n
{
   \tl_clear:N \l__datatool_content_tl
   \seq_map_indexed_inline:Nn \l__datatool_wordlist_seq
   {
     \__datatool_finish_sort_db_parse:w ##2 { ##1 }
   }
   \bool_if:NTF \l__datatool_db_global_bool
    {
     \__datatool_token_register_gset:cV
      { dtldb @ #1 }
      \l__datatool_content_tl
    }
    {
     \__datatool_token_register_set:cV
      { dtldb @ #1 }
      \l__datatool_content_tl
    }
}
\newcommand{\dtlsortdatavalue}[2]{#2}
\cs_new:Npn \__datatool_finish_sort_db_parse:w
 #1 \q_mark #2 \q_stop #3
{
  \tl_set:Nn \l__datatool_tmp_seq { #1 }
  \tl_set:Nn \l__datatool_row_tl
   { #2 }
  \int_compare:nNnT
    { \__datatool_sort_data_sortcol_int } > { \c_zero_int }
   {
     \tl_clear:N \l__datatool_tmpa_tl
     \seq_map_inline:Nn \l__datatool_tmp_seq
     {
       \tl_if_eq:nnF
         { ##1 } { { } { \c_datatool_nullvalue_tl } }
        {
          \tl_set:Nn \l__datatool_tmpa_tl { ##1 }
        }
      \tl_if_empty:NF \l__datatool_tmpa_tl
        {
          \seq_map_break:
        }
     }
     \tl_if_empty:NF \l__datatool_tmpa_tl
     {
       \tl_put_left:Nn \l__datatool_tmpa_tl
         { \dtlsortdatavalue }
       \exp_args:NVV
        \__datatool_if_split_row:nnNNTF
        \l__datatool_row_tl
        \__datatool_sort_data_sortcol_int
         \l__datatool_map_data_edit_before_tl
         \l__datatool_map_data_edit_after_tl
        {
          \tl_set:Nx \l__datatool_row_tl
           {
             \exp_not:V \l__datatool_map_data_edit_before_tl
             \__datatool_row_element_markup:VV
                \__datatool_sort_data_sortcol_int
                \l__datatool_tmpa_tl
             \exp_not:V \l__datatool_map_data_edit_after_tl
           }
        }
        {
          \tl_put_right:Nx \l__datatool_row_tl
           {
             \__datatool_row_element_markup:VV
                \__datatool_sort_data_sortcol_int
                \l__datatool_tmpa_tl
           }
        }
      }
   }
  \int_compare:nNnT
   { \__datatool_sort_data_grpcol_int } > { \c_zero_int }
   {
     \tl_clear:N \l__datatool_tmpa_tl
     \seq_map_inline:Nn \l__datatool_tmp_seq
     {
       \tl_if_eq:nnF
         { ##1 } { { } { \c_datatool_nullvalue_tl } }
        {
          \tl_set:Nn \l__datatool_tmpa_tl { ##1 }
        }
      \tl_if_empty:NF \l__datatool_tmpa_tl
        {
          \seq_map_break:
        }
     }
     \__datatool_sort_db_fetch_grp:N \l__datatool_tmpa_tl
     \datatool_post_process_lettergroup:N
       \l__datatool_tmpa_tl
     \exp_args:NVV
      \__datatool_if_split_row:nnNNTF
      \l__datatool_row_tl
      \__datatool_sort_data_grpcol_int
       \l__datatool_map_data_edit_before_tl
       \l__datatool_map_data_edit_after_tl
      {
        \tl_set:Nx \l__datatool_row_tl
         {
           \exp_not:V \l__datatool_map_data_edit_before_tl
           \__datatool_row_element_markup:VV
              \__datatool_sort_data_grpcol_int
              \l__datatool_tmpa_tl
           \exp_not:V \l__datatool_map_data_edit_after_tl
         }
      }
      {
        \tl_put_right:Nx \l__datatool_row_tl
         {
           \__datatool_row_element_markup:VV
              \__datatool_sort_data_grpcol_int
              \l__datatool_tmpa_tl
         }
      }
   }
  \tl_put_right:Nx \l__datatool_content_tl
   {
     \__datatool_row_markup:nV
       { #3 } \l__datatool_row_tl
   }
}
\cs_new:Nn \__datatool_sort_db_fetch_grp:N
{
  \int_compare:nNnTF
    { \tl_count:N #1 } = { 2 }
   {
     \exp_after:wN \__datatool_sort_db_fetch_grp:nnN #1 #1
   }
   {
     \tl_clear:N #1
   }
}
\cs_new:Nn \__datatool_sort_db_fetch_grp:nnN
{
  \tl_clear:N #3
  \DTLassignlettergroup { #2 } { #1 } #3
}
\cs_new:Nn \datatool_post_process_lettergroup:N
{
}
\newcommand*{\DTLsort}{\@ifstar\@sDTLsort\@DTLsort}
\newcommand{\@DTLsort}[3][]{%
  \dtlsort[#1]{#2}{#3}{\dtlcompare}%
}
\newcommand*{\@sDTLsort}[3][]{%
  \dtlsort[#1]{#2}{#3}{\dtlicompare}%
}
\seq_new:N \l__datatool_dtlsort_rows_seq
\tl_new:N \l__datatool_sorta_tl
\tl_new:N \l__datatool_sortb_tl
\NewDocumentCommand \dtlsort { o m m m }
{
  \DTLifdbexists { #3 }
  {
    \group_begin:
    \tl_set:Nx \dtldbname { #3 }
    \seq_clear:N \l__datatool_replacement_indexes_seq
    \IfValueT { #1 }
     {
       \clist_map_inline:nn { #1 }
        {
          \datatool_if_has_key:nnTF { #3 } { ##1 }
           {
             \seq_put_right:Nx \l__datatool_replacement_indexes_seq
              { \dtlcolumnindex { #3 } { ##1 } }
           }
           {
             \PackageWarning { datatool }
              { Ignoring ~ unknown ~ replacement ~ column ~ ##1 }
           }
        }
     }
    \seq_clear:N \l__datatool_column_indexes_seq
    \seq_clear:N \l__datatool_sort_order_seq
    \seq_clear:N \l__datatool_sort_numeric_seq
    \keyval_parse:NNn
      \__datatool_dtlsort_col:n
      \__datatool_dtlsort_col:nn
      { #2 }
    \seq_if_empty:NTF \l__datatool_column_indexes_seq
     {
       \PackageError { datatool }
        {
          \token_to_str:N \dtlsort : ~ No ~ sort ~ columns
        }
        {
          Either ~ the ~ criteria ~ argument ~ is ~ empty ~ or ~
          all ~ listed ~ column ~ keys ~ are ~ undefined
        }
     }
     {
       \seq_clear:N \l__datatool_dtlsort_rows_seq
       \int_step_inline:nn { \DTLrowcount { #3 } }
        {
          \__datatool_get_row:vnN
           { dtldb@ #3 } { ##1 } \l__datatool_sorta_tl
          \seq_put_right:NV \l__datatool_dtlsort_rows_seq
            \l__datatool_sorta_tl
        }
      \seq_sort:Nn \l__datatool_dtlsort_rows_seq
       {
         \__datatool_dtlsort_compare:Nnn #4 { ##1 } { ##2 }
       }
     }
    \exp_args:NNV
    \group_end:
     \__datatool_dtlsort_reconstruct:nn
       \l__datatool_dtlsort_rows_seq { #3 }
  }
  {
    \PackageError { datatool }
     { Database ~ ` #3 ' ~ doesn't ~ exist} { }
  }
}
\cs_new:Nn \__datatool_dtlsort_reconstruct:nn
{
  \tl_set:Nn \l__datatool_dtlsort_rows_seq { #1 }
  \__datatool_token_register_set:cn { dtldb@ #2 } { }
  \seq_map_indexed_inline:Nn \l__datatool_dtlsort_rows_seq
   {
     \bool_if:NTF \l__datatool_db_global_bool
      {
        \__datatool_token_register_gput_right:cx
         { dtldb@ #2 } { \__datatool_row_markup:nn { ##1 } { ##2 } }
      }
      {
        \__datatool_token_register_put_right:cx
         { dtldb@ #2 } { \__datatool_row_markup:nn { ##1 } { ##2 } }
      }
   }
}
\cs_new:Nn \__datatool_dtlsort_compare:Nnn
{
  \seq_map_indexed_inline:Nn \l__datatool_column_indexes_seq
   {
     \bool_set:Nn \l__datatool_sort_order_bool
      {
        \seq_item:Nn \l__datatool_sort_order_seq { ##1 }
      }
     \exp_args:NNx
       \bool_set:Nn \l__datatool_sort_numeric_bool
        {
          \seq_item:Nn
            \l__datatool_sort_numeric_seq { ##1 }
        }
     \__datatool_dtlsort_get_value:Nnn
       \l__datatool_sorta_tl { ##2 } { #2 }
     \__datatool_dtlsort_get_value:Nnn
       \l__datatool_sortb_tl { ##2 } { #3 }
     \bool_if:NTF \l__datatool_sort_numeric_bool
      {
        \DTLnumcompare \dtl@sortresult { \l__datatool_sorta_tl } { \l__datatool_sortb_tl }
        \int_if_zero:nT { \dtl@sortresult }
         {
           \exp_args:NNVV #1 \dtl@sortresult \l__datatool_sorta_tl \l__datatool_sortb_tl
         }
      }
      {
        \exp_args:NNVV #1 \dtl@sortresult \l__datatool_sorta_tl \l__datatool_sortb_tl
      }
     \ifdtlverbose
       \dtl@message
        {
          `\exp_not:V \l__datatool_sorta_tl' ~
          <=> ~ `\exp_not:V \l__datatool_sortb_tl' ~ = ~ \int_use:N \dtl@sortresult
        }
     \fi
     \bool_if:NF \l__datatool_sort_order_bool
      {
        \int_set:Nn \dtl@sortresult { - \dtl@sortresult }
      }
     \int_if_zero:nTF { \dtl@sortresult }
      {
        \int_compare:nNnT
           { ##1 } = { \seq_count:N \l__datatool_column_indexes_seq }
         {
           \sort_return_same:
         }
      }
      {
        \int_compare:nNnTF { \dtl@sortresult } > { \c_zero_int }
         { \seq_map_break:n { \sort_return_swapped: } }
         { \seq_map_break:n { \sort_return_same: } }
      }
   }
}
\cs_new:Nn \__datatool_dtlsort_get_value:Nnn
{
  \__datatool_get_entry_from_row:Nnn
    #1 { #2 } { #3 }
  \bool_lazy_and:nnT
     { \tl_if_eq_p:NN #1 \dtlnovalue }
     { \bool_not_p:n { \seq_if_empty_p:N \l__datatool_replacement_indexes_seq } }
   {
     \seq_map_inline:Nn \l__datatool_replacement_indexes_seq
      {
        \__datatool_get_entry_from_row:Nnn
        #1 { ##1 } { #3 }
       \tl_if_eq:NNF #1 \dtlnovalue
        { \seq_map_break: }
     }
   }
  \bool_if:NT \l__datatool_sort_numeric_bool
   {
     \tl_if_eq:NNTF #1 \dtlnovalue
      {
        \DTLsetintegerdatum #1 { \c_datatool_nullvalue_tl } { 0 }
      }
      {
        \__datatool_parse:N #1
        \int_compare:nNnTF
           { \@dtl@datatype }
             <
           { \c_datatool_integer_int }
         {
           \bool_set_false:N \l__datatool_sort_numeric_bool
         }
      }
   }
}
\cs_new:Nn \__datatool_dtlsort_col:nN
{
  \datatool_if_has_key:nnTF { \dtldbname } { #1 }
   {
     \int_set:Nn \l__datatool_col_idx_int
      { \dtlcolumnindex { \dtldbname } { #1 } }
     \seq_put_right:NV \l__datatool_column_indexes_seq
      \l__datatool_col_idx_int
     \seq_put_right:Nn \l__datatool_sort_order_seq { #2 }
     \__datatool_get_col_type:vV
       { dtlkeys @ \dtldbname }
        \l__datatool_col_idx_int
     \datatool_if_numeric_datum_type:nTF
       { \l__datatool_item_type_int }
      {
       \seq_put_right:Nn \l__datatool_sort_numeric_seq
         { \c_true_bool }
      }
      {
       \seq_put_right:Nn \l__datatool_sort_numeric_seq
         { \c_false_bool }
      }
   }
   {
     \PackageWarning { datatool }
       { Ignoring ~ unknown ~ sort ~ column ~ #1 }
   }
}
\cs_new:Nn \__datatool_dtlsort_col:n
{
  \__datatool_dtlsort_col:nN { #1 } \c_true_bool
}
\cs_new:Nn \__datatool_dtlsort_col:nn
{
  \tl_if_empty:nTF { #2 }
   {
     \__datatool_dtlsort_col:nN { #1 } \c_true_bool
   }
   {
     \tl_if_eq:nnTF { #2 } { descending }
      {
        \__datatool_dtlsort_col:nN { #1 } \c_false_bool
      }
      {
        \tl_if_eq:nnTF { #2 } { ascending }
         {
           \__datatool_dtlsort_col:nN { #1 } \c_true_bool
         }
         {
           \PackageError { datatool }
            {
              \token_to_str:N \dtlsort : ~ Invalid ~ sort ~ order ~ `#2' ~
              for ~ column ~ `#1'
            }
            {
              The ~ sort ~ order ~ may ~ be ~ either ~ `ascending' ~
              or ~ `descending'. ~ You ~ may ~ omit ~ the ~ order ~
              for ~ the ~ default ~ `ascending'
            }
         }
      }
   }
}
\ior_new:N \g__datatool_in_stream
\iow_new:N \g__datatool_out_stream
\cs_new:Nn \__datatool_data:n { \exp_not:n { #1 } }
\newcommand{\dtlspecialvalue}[1]{#1}
\cs_new:Nn \__datatool_data_value:n
{
  \tl_if_head_eq_meaning:nNTF { #1 } \dtlspecialvalue
   { #1 }
   { \__datatool_data:n { #1 } }
}
\cs_new:Nn \__datatool_save_data_no_expand:Nn
 {
   \tl_set:Nx #1 { \tl_to_str:n { #2 } }
   \__datatool_save_data_postprocess:N #1
 }
\cs_new:Nn \__datatool_save_data_protected_expand:Nn
 {
   \protected@edef #1 { #2 }
   \@onelevel@sanitize #1
   \__datatool_save_data_postprocess:N #1
 }
\cs_new:Nn \__datatool_save_data_full_expand:Nn
 {
   \exp_args:NNx \__datatool_save_data_no_expand:Nn #1 { #2 }
 }
\cs_set_eq:NN \__datatool_save_data:Nn \__datatool_save_data_no_expand:Nn
\cs_new:Nn \__datatool_save_escape_delim_bksl:N
 {
   \regex_replace_all:NnN \l__datatool_escape_delim_bksl_regex { \\\1 } #1
 }
\cs_new:Nn \__datatool_save_escape_delim:N
 {
   \regex_replace_all:NnN \l__datatool_escape_delim_regex { \\\1 } #1
 }
\cs_new:Nn \__datatool_save_double_delim:N
 {
   \regex_replace_all:NnN \l__datatool_escape_delim_regex { \1\1 } #1
 }
\cs_set_eq:NN
  \__datatool_save_data_postprocess:N
  \__datatool_save_double_delim:N
\cs_new:Nn \__datatool_load_unescape_delim_bksl:N
 {
   \bool_if:NTF \l__datatool_csv_literal_content_bool
   {
     \regex_replace_all:NnN \l__datatool_unescape_str_delim_bksl_regex { \1 } #1
   }
   {
     \regex_replace_all:NnN \l__datatool_unescape_cs_delim_regex
        { \u{@dtl@delimiter} } #1
     \regex_replace_all:nnN
        { \c{ \x{5c} } ( [[:^alpha:]] | [a-zA-Z]+ ) }
        { \c{ \1 } }
        #1
   }
 }
\cs_new:Nn \__datatool_load_unescape_delim:N
 {
   \bool_if:NTF \l__datatool_csv_literal_content_bool
   {
     \regex_replace_all:NnN \l__datatool_unescape_str_delim_regex { \1 } #1
   }
   {
     \regex_replace_all:NnN \l__datatool_unescape_cs_delim_regex
        { \u{@dtl@delimiter} } #1
   }
 }
\cs_new:Nn \__datatool_load_unescape_double_delim:N
 {
   \bool_if:NTF \l__datatool_csv_literal_content_bool
   {
     \regex_replace_all:NnN \l__datatool_unescape_str_double_delim_regex { \1 } #1
   }
   {
     \regex_replace_all:NnN \l__datatool_unescape_cs_double_delim_regex
        { \1 } #1
   }
 }
\cs_set_eq:NN
 \__datatool_load_data_postprocess:N
 \__datatool_load_unescape_double_delim:N
\cs_new:Nn \__datatool_save:n { \__datatool_save_csv:n { #1 } }
\tl_new:N \l__datatool_default_ext_tl
\tl_set:Nn \l__datatool_default_ext_tl { .csv }
\cs_new:Nn \__datatool_load: { \__datatool_load_csv: }
\cs_new:Nn \__datatool_save_error_overwrite:nn
 {
   \PackageError { datatool }
   { Can't ~ save ~ to ~ file ~ `#1': ~ overwrite ~ forbidden}
   {
     The ~ current ~ overwrite ~ setting ~ doesn't ~ allow ~ an ~
     existing ~ file ~ to ~ be ~ overwritten. ~ Use ~ `overwrite=allow' ~
     option ~ to ~ allow ~ overwrite.
   }
 }
\cs_new:Nn \__datatool_save_warn_overwrite:nn
 {
   \PackageWarning { datatool }
   { File ~ `#1' ~ already ~ exists. ~ Skipping ~ save}
 }
\cs_new:Nn \__datatool_save_allow_overwrite:nn
 {
   \dtl@message { Overwriting ~ `#1' }
   #2
 }
\cs_set_eq:NN \__datatool_overwrite_action:nn
  \__datatool_save_error_overwrite:nn
\tl_new:N \l__datatool_io_csv_init_tl
\newif\ifdtlnoheader
\newif\ifdtlautokeys
\dtlautokeysfalse
\newcommand*{\dtldefaultkey}{Column}
\newif\ifDTLnewdbonload
\DTLnewdbonloadtrue
\bool_new:N \l__datatool_append_allowed_bool
\bool_set_true:N  \l__datatool_append_allowed_bool
\cs_new:Nn \__datatool_load_init_old_style:n
 {
   \ifDTLnewdbonload
     \__datatool_load_init_create:n { #1 }
   \else
     \__datatool_load_init_append:n { #1 }
   \fi
 }
\cs_set_eq:NN \__datatool_load_init:n
  \__datatool_load_init_old_style:n
\cs_new:Nn \__datatool_load_db_exists_error:n
{
  \PackageError { datatool }
   {
     \token_to_str:N \DTLread : ~ Database ~ `#1' ~
     already ~ exists
   }
   {
     Check ~ the ~ `name' ~ setting ~ in ~ the ~ optional ~
     argument ~ of ~ \token_to_str:N \DTLread . ~
     DBTEX ~ v2.0 ~ and ~ DTLTEX ~ v2.0 ~ formats ~ don't ~ allow ~ the ~
     database ~ name ~ to ~ be ~ changed ~ if ~ it's ~
     hard-coded ~ in ~ the ~ file. ~ DBTEX ~ v3.0 ~ and ~
     DTLTEX ~ v3.0 ~ require ~ the ~ new ~ name ~ to ~
     be ~ provided ~ with ~ the ~ `name' ~ option, ~ not ~
     with ~ the ~ `default-name' ~ package-wide ~ setting
   }
}
\cs_new:Nn \__datatool_load_init_create:n
 {
   \cs_set_eq:NN \__datatool_csv_check_key:
     \__datatool_csv_check_key_create:
   \cs_set:Nn \__datatool_csv_check_col_idx: { }
   \cs_set_eq:NN \__datatool_csv_check_col_count:
      \__datatool_csv_check_col_count_create:
   \DTLifdbexists { #1 }
   {
     \__datatool_load_db_exists_error:n { #1 }
   }
   {
    \__datatool_new_db:n { #1 }
   }
 }
\cs_new:Nn \__datatool_load_init_append:n
 {
   \bool_if:NTF \l__datatool_append_allowed_bool
   {
     \cs_set_eq:NN \__datatool_csv_check_key:
       \__datatool_csv_check_key_append:
     \cs_set_eq:NN \__datatool_csv_check_col_idx:
       \__datatool_csv_check_col_idx_append:
     \cs_set:Nn \__datatool_csv_check_col_count: { }
     \dtl@message { Appending ~ to ~  `#1' }
     \int_set:Nn \dtlrownum { \DTLrowcount { #1 } }
   }
   {
     \PackageError {datatool}
     { Appending ~ not ~ supported ~ for ~ the ~ given ~ format. ~
       Unexpected ~ results ~ may ~ occur}
     {}
   }
 }
\cs_new:Nn \__datatool_load_init_overwrite:n
 {
   \cs_set_eq:NN \__datatool_csv_check_key:
     \__datatool_csv_check_key_create:
   \cs_set:Nn \__datatool_csv_check_col_idx: { }
   \cs_set_eq:NN \__datatool_csv_check_col_count:
      \__datatool_csv_check_col_count_create:
   \DTLifdbexists { #1 }
   {
     \DTLgcleardb { #1 }
   }
   { \DTLnewdb { #1 } }
 }
\cs_new:Nn \__datatool_load_init_append_or_create:n
 {
   \DTLifdbexists { #1 }
   {
      \__datatool_load_init_append:n { #1 }
   }
   {
      \__datatool_load_init_create:n { #1 }
   }
 }
\prop_new:N \l__datatool_csv_colnew_to_colold_prop
\cs_new:Nn \__datatool_csv_check_key_create:
 {
   \@sDTLifhaskey \l__datatool_io_name_tl \l__datatool_item_key_tl
   {
     \PackageError { datatool }
     { Duplicate ~ key ~ `\l__datatool_item_key_tl' ~ found ~ found }
     { Did ~ you ~ want ~ to ~ append ~ to ~ an ~ existing ~ database? }
   }
   { }
 }
\cs_new:Nn \__datatool_csv_check_key_append:
 {
   \@sDTLifhaskey \l__datatool_io_name_tl \l__datatool_item_key_tl
   {
     \int_set:Nn \l__datatool_tmpa_int
         { \dtlcolumnindex \l__datatool_io_name_tl \l__datatool_item_key_tl }
     \prop_put:NVV
        \l__datatool_csv_colnew_to_colold_prop
        \l__datatool_item_col_tl
        \l__datatool_tmpa_int
   }
   {
      \int_gincr:c { dtlcols@ \l__datatool_io_name_tl }
      \prop_put:NVx
        \l__datatool_csv_colnew_to_colold_prop
        \l__datatool_item_col_tl
        { \int_use:c { dtlcols@ \l__datatool_io_name_tl } }
   }
 }
\cs_new:Nn \__datatool_csv_check_col_count_create:
{
  \int_compare:nNnT
    { \int_use:c { dtlcols@ \l__datatool_io_name_tl } } < { \dtlcolumnnum}
  {
    \int_gset_eq:cN
       { dtlcols@ \l__datatool_io_name_tl }
       \dtlcolumnnum
  }
}
\cs_new:Nn \__datatool_csv_check_col_idx_append:
{
  \prop_get:NVN
    \l__datatool_csv_colnew_to_colold_prop
    \l__datatool_item_col_tl
    \l__datatool_tmpb_tl
  \quark_if_no_value:NTF \l__datatool_tmpb_tl
  {
    \int_gincr:c { dtlcols@ \l__datatool_io_name_tl }
    \prop_put:NVx
      \l__datatool_csv_colnew_to_colold_prop
      \l__datatool_item_col_tl
      { \int_use:c { dtlcols@ \l__datatool_io_name_tl } }
    \tl_set:Nv \l__datatool_item_col_tl
      { \int_use:c { dtlcols@ \l__datatool_io_name_tl } }
  }
  {
    \tl_set_eq:NN \l__datatool_item_col_tl \l__datatool_tmpb_tl
  }
}
\cs_set_eq:NN \__datatool_csv_check_key: \__datatool_csv_check_key_create:
\cs_set_eq:NN \__datatool_csv_check_col_count:
  \__datatool_csv_check_col_count_create:
\cs_new:Nn \__datatool_csv_check_col_idx: { }
\tl_new:N \l__datatool_io_name_tl
\tl_new:N \dtllastloadedeb
\bool_new:N \l__datatool_csv_literal_content_bool
\bool_set_true:N \l__datatool_csv_literal_content_bool
\bool_new:N \l__datatool_csv_plain_content_bool
\bool_set_false:N \l__datatool_csv_plain_content_bool
\prop_new:N \l__datatool_csv_keys_prop
\prop_new:N \l__datatool_csv_headers_prop
\prop_new:N \l__datatool_csv_types_prop
\seq_new:N \l__datatool_no_parse_cols_seq
\bool_new:N \l__datatool_csv_convert_bool
\bool_set_false:N \l__datatool_csv_convert_bool
\newcount{\dtl@omitlines}
\seq_new:N \l__datatool_csv_only_auto_reformat_seq
\cs_new:Nn \__datatool_csv_set_auto_reformat:
 {
  \seq_if_empty:NF \l__datatool_csv_only_auto_reformat_seq
   {
     \seq_if_in:NVTF
       \l__datatool_csv_only_auto_reformat_seq
       \l__datatool_item_col_tl
      {
        \bool_set_true:N \l__datatool_reformat_numeric_bool
        \bool_set_true:N \l__datatool_reformat_datetime_bool
      }
      {
        \bool_set_false:N \l__datatool_reformat_numeric_bool
        \bool_set_false:N \l__datatool_reformat_datetime_bool
      }
   }
 }
\keys_define:nn { datatool/io }
 {
   keys .code:n =
      {
        \prop_clear_new:N \l__datatool_csv_keys_prop
        \clist_set:Nn \l__datatool_tmpa_clist { #1 }
        \int_zero:N \l__datatool_tmpa_int
        \clist_map_inline:Nn \l__datatool_tmpa_clist
         {
           \int_incr:N \l__datatool_tmpa_int
           \tl_if_empty:nF { ##1 }
           {
             \prop_put:NVn \l__datatool_csv_keys_prop
                \l__datatool_tmpa_int  { ##1 }
           }
         }
        \prop_if_empty:NF \l__datatool_csv_keys_prop
         {
           \dtlautokeysfalse
         }
      },
   headers .code:n =
      {
        \prop_clear_new:N \l__datatool_csv_headers_prop
        \clist_set:Nn \l__datatool_tmpa_clist { #1 }
        \int_zero:N \l__datatool_tmpa_int
        \clist_map_inline:Nn \l__datatool_tmpa_clist
         {
           \int_incr:N \l__datatool_tmpa_int
           \prop_put:NVn \l__datatool_csv_headers_prop
              \l__datatool_tmpa_int  { ##1 }
         }
      },
   data-types .code:n =
      {
        \seq_clear:N \l__datatool_no_parse_cols_seq
        \prop_clear_new:N \l__datatool_csv_types_prop
        \clist_set:Nn \l__datatool_tmpa_clist { #1 }
        \int_zero:N \l__datatool_tmpa_int
        \clist_map_inline:Nn \l__datatool_tmpa_clist
         {
           \int_incr:N \l__datatool_tmpa_int
           \int_if_exist:cTF { c_datatool_ ##1 _int }
            {
              \prop_put:NVv \l__datatool_csv_types_prop
               \l__datatool_tmpa_int  { c_datatool_ ##1 _int }
              \int_compare:nNnT
                { \int_use:c { c_datatool_ ##1 _int } } = { \c_datatool_string_int }
               {
                 \seq_put_right:NV \l__datatool_no_parse_cols_seq
                   \l__datatool_tmpa_int
               }
            }
            {
              \PackageError { datatool }
               {
                  unknown ~ data ~ type ~ identifier ~ ` ##1 '
               }
               {
                  data-types ~ value ~ should ~ be ~ a ~
                  comma-separated ~ list ~ of ~ data ~ type ~ identifiers ~
                  such ~ as ~ `string' ~ for ~ strings or
                  `decimal` for decimals
               }
            }
         }
      },
   only-reformat-columns .code:n =
     {
      \seq_set_from_clist:Nn
        \l__datatool_csv_only_auto_reformat_seq { #1 }
     } ,
   separator .code:n =
     {
       \DTLsetseparator { #1 }
       \tl_clear:N \l__datatool_io_csv_init_tl
     },
   separator .value_required:n = true,
   delimiter .code:n =
     {
        \DTLsetdelimiter { #1 }
     },
   delimiter .value_required:n = true,
   convert-numbers .bool_set:N = \l__datatool_csv_convert_bool ,
   trim .bool_set:N = \l__datatool_new_element_trim_bool ,
   strict-quotes .choice:,
   strict-quotes / true .code:n =
     {
        \PackageWarning { datatool }
         {
           ignoring ~ option ~ `strict-quotes'. ~ This ~ setting ~
           is ~ only ~ supported ~ with ~ datatooltk ~ not ~ with ~
           datatool.sty
         }
     },
   strict-quotes / false .code:n = { },
   strict-quotes .default:n = true ,
   expand .choice:,
   expand .default:n = protected,
   expand / none .code:n =
      {
        \cs_set_eq:NN
          \__datatool_save_data:Nn
          \__datatool_save_data_no_expand:Nn
        \cs_set_eq:NN \__datatool_data:n \tl_to_str:n
        \dtlnoexpandnewvalue
      },
   expand / protected .code:n =
      {
        \cs_set_eq:NN
          \__datatool_save_data:Nn
          \__datatool_save_data_protected_expand:Nn
        \cs_set_eq:NN \__datatool_data:n \tl_to_str:n
        \dtlexpandnewvalue
      },
   expand / full .code:n =
      {
        \cs_set_eq:NN
          \__datatool_save_data:Nn
          \__datatool_save_data_full_expand:Nn
        \cs_set_eq:NN \__datatool_data:n \use:n
        \dtlexpandnewvalue
      },
   format .choice:,
   format .value_required:n = true,
   format / csv .code:n =
      {
        \tl_clear:N \l__datatool_io_csv_init_tl
        \cs_set_eq:NN \__datatool_save:n \__datatool_save_csv:n
        \cs_set_eq:NN \__datatool_load: \__datatool_load_csv:
        \tl_set:Nn \l__datatool_default_ext_tl { .csv }
        \bool_set_true:N  \l__datatool_append_allowed_bool
      },
   format / tsv .code:n =
      {
        \tl_set:Nn \l__datatool_io_csv_init_tl
          { \DTLsettabseparator }
        \cs_set_eq:NN \__datatool_save:n \__datatool_save_csv:n
        \cs_set_eq:NN \__datatool_load: \__datatool_load_csv:
        \tl_set:Nn \l__datatool_default_ext_tl { .tsv }
        \bool_set_true:N  \l__datatool_append_allowed_bool
      },
   format / dtltex-2 .code:n =
      {
        \cs_set_eq:NN \__datatool_save:n \__datatool_save_dtltex_ii:n
        \cs_set_eq:NN \__datatool_load: \__datatool_load_tex:
        \tl_set:Nn \l__datatool_default_ext_tl { .dtltex }
        \bool_set_false:N  \l__datatool_append_allowed_bool
      },
   format / dtltex-3 .code:n =
      {
        \cs_set_eq:NN \__datatool_save:n \__datatool_save_dtltex_iii:n
        \cs_set_eq:NN \__datatool_load: \__datatool_load_tex:
        \tl_set:Nn \l__datatool_default_ext_tl { .dtltex }
        \bool_set_true:N  \l__datatool_append_allowed_bool
      },
   format / dtltex .code:n =
      {
        \cs_set_eq:NN \__datatool_save:n \__datatool_save_dtltex_iii:n
        \cs_set_eq:NN \__datatool_load: \__datatool_load_tex:
        \tl_set:Nn \l__datatool_default_ext_tl { .dtltex }
        \bool_set_true:N  \l__datatool_append_allowed_bool
      },
   format / dbtex-2 .code:n =
      {
        \cs_set_eq:NN \__datatool_save:n \__datatool_save_db_ii:n
        \cs_set_eq:NN \__datatool_load: \__datatool_load_tex:
        \tl_set:Nn \l__datatool_default_ext_tl { .dbtex }
        \bool_set_false:N  \l__datatool_append_allowed_bool
      },
   format / dbtex-3 .code:n =
      {
        \cs_set_eq:NN \__datatool_save:n \__datatool_save_db_iii:n
        \cs_set_eq:NN \__datatool_load: \__datatool_load_tex:
        \tl_set:Nn \l__datatool_default_ext_tl { .dbtex }
        \bool_set_false:N  \l__datatool_append_allowed_bool
      },
   format / dbtex .code:n =
      {
        \cs_set_eq:NN \__datatool_save:n \__datatool_save_db_iii:n
        \cs_set_eq:NN \__datatool_load: \__datatool_load_tex:
        \tl_set:Nn \l__datatool_default_ext_tl { .dbtex }
        \bool_set_false:N  \l__datatool_append_allowed_bool
      },
   add-delimiter .choice:,
   add-delimiter .value_required:n = true,
   add-delimiter / always .code:n =
      {
        \cs_set_eq:NN \__datatool_append_csv_item:n
          \__datatool_append_csv_item_always_delim:n
      },
   add-delimiter / detect .code:n =
      {
        \cs_set_eq:NN \__datatool_append_csv_item:n
          \__datatool_append_csv_item_detect_sep:n
      },
   add-delimiter / never .code:n =
      {
        \cs_set_eq:NN \__datatool_append_csv_item:n
          \__datatool_append_csv_item_no_delim:n
      },
   csv-escape-chars .choice:,
   csv-escape-chars .value_required:n = true,
   csv-escape-chars / none .code:n =
      {
        \cs_set:Nn \__datatool_save_data_postprocess:N { }
        \cs_set:Nn \__datatool_load_data_postprocess:N { }
      },
   csv-escape-chars / delim .code:n =
      {
        \cs_set_eq:NN \__datatool_save_data_postprocess:N
           \__datatool_save_escape_delim:N
        \cs_set_eq:NN \__datatool_load_data_postprocess:N
           \__datatool_load_unescape_delim:N
      },
   csv-escape-chars / delim + bksl .code:n =
      {
        \cs_set_eq:NN \__datatool_save_data_postprocess:N
           \__datatool_save_escape_delim_bksl:N
        \cs_set_eq:NN \__datatool_load_data_postprocess:N
           \__datatool_load_unescape_delim_bksl:N
      },
   csv-escape-chars / double-delim .code:n =
      {
        \cs_set_eq:NN \__datatool_save_data_postprocess:N
          \__datatool_save_double_delim:N
        \cs_set_eq:NN \__datatool_load_data_postprocess:N
           \__datatool_load_unescape_double_delim:N
      },
   csv-content .choice:,
   csv-content / literal .code:n =
      {
        \bool_set_true:N \l__datatool_csv_literal_content_bool
        \bool_set_false:N \l__datatool_csv_plain_content_bool
      },
   csv-content / tex .code:n =
      {
        \bool_set_false:N \l__datatool_csv_literal_content_bool
        \bool_set_false:N \l__datatool_csv_plain_content_bool
      },
   csv-content / no-parse .code:n =
      {
        \bool_set_false:N \l__datatool_csv_literal_content_bool
        \bool_set_true:N \l__datatool_csv_plain_content_bool
      },
   csv-skip-lines .code:n =
      {
         \tl_if_eq:nnTF { #1 } { false }
         { \int_zero:N \dtl@omitlines }
         {
           \int_compare:nNnTF { #1 } < { 0 }
           {
              \PackageError {datatool}
               {csv-skip-lines ~ value ~ can't ~ be ~ negative}{}
           }
           {
              \int_set:Nn \dtl@omitlines { #1 }
           }
         }
      },
   omitlines .int_set:N = \dtl@omitlines,
   csv-blank .choice:,
   csv-blank / ignore .code:n =
      { \cs_set:Nn \__datatool_csv_blank: { } },
   csv-blank / empty-row .code:n =
      { \cs_set:Nn \__datatool_csv_blank:
         { \int_incr:N \dtlrownum \DTLdbNewRow }
      },
     csv-blank / end .code:n =
      { \cs_set:Nn \__datatool_csv_blank: { \ior_map_break: } },
   overwrite .choice:,
   overwrite .value_required:n = true,
   overwrite / error .code:n =
      {
        \cs_set_eq:NN \__datatool_overwrite_action:nn
           \__datatool_save_error_overwrite:nn
      },
   overwrite / warn .code:n =
      {
        \cs_set_eq:NN \__datatool_overwrite_action:nn
           \__datatool_save_warn_overwrite:nn
      },
   overwrite / allow .code:n =
      {
        \cs_set_eq:NN \__datatool_overwrite_action:nn
           \__datatool_save_allow_overwrite:nn
      },
   noheader .legacy_if_set:n = dtlnoheader,
   no-header .legacy_if_set:n = dtlnoheader,
   autokeys .legacy_if_set:n = dtlautokeys,
   auto-keys .legacy_if_set:n = dtlautokeys,
   load-action .choice:,
   load-action / create .code:n =
      {
        \cs_set_eq:NN \__datatool_load_init:n
          \__datatool_load_init_create:n
      },
   load-action / append .code:n =
      {
        \cs_set_eq:NN \__datatool_load_init:n
          \__datatool_load_init_append:n
      },
   load-action / overwrite .code:n =
      {
        \cs_set_eq:NN \__datatool_load_init:n
          \__datatool_load_init_overwrite:n
      },
   load-action / detect .code:n =
      {
        \cs_set_eq:NN \__datatool_load_init:n
          \__datatool_load_init_append_or_create:n
      },
   load-action / old-style .code:n =
      {
        \cs_set_eq:NN \__datatool_load_init:n
          \__datatool_load_init_old_style:n
      },
   name .str_set_x:N = \l__datatool_io_name_tl,
 }
\keys_define:nn { datatool }
{
   io .code:n = { \keys_set:nn { datatool/io } { #1 } },
   display .code:n = { \keys_set:nn { datatool/display } { #1 } },
   separator .undefine:,
   delimiter .undefine:
}
\NewDocumentCommand \DTLwrite { o m }
{
  \group_begin:
    \IfValueT { #1 } { \keys_set:nn { datatool/io } { #1 } }
    \tl_if_empty:NT \l__datatool_io_name_tl
    {
      \tl_set_eq:NN \l__datatool_io_name_tl \l__datatool_default_dbname_tl
    }
    \DTLifdbexists { \l__datatool_io_name_tl }
    {
      \__datatool_get_filename:n { #2 }
      \file_if_exist:nTF { \l__datatool_name_tl }
      {
         \__datatool_overwrite_action:nn { \l__datatool_name_tl }
         {
           \dtl@message { Writing ~ \l__datatool_name_tl }
           \iow_open:Nn \g__datatool_out_stream { \l__datatool_name_tl }
           \__datatool_save:n { \l__datatool_io_name_tl }
           \iow_close:N \g__datatool_out_stream
         }
      }
      {
         \iow_open:Nn \g__datatool_out_stream { \l__datatool_name_tl }
         \dtl@message { Writing ~ \l__datatool_name_tl }
         \__datatool_save:n { \l__datatool_io_name_tl }
         \iow_close:N \g__datatool_out_stream
      }
    }
    {
      \PackageError {datatool}
       { Can't ~ save ~ database ~ `\l__datatool_io_name_tl': ~
         no ~ such ~ database }
       {}
    }
  \group_end:
}
\tl_new:N \l__datatool_dir_tl
\tl_new:N \l__datatool_name_tl
\tl_new:N \l__datatool_ext_tl
\cs_new:Nn \__datatool_get_filename:n
{
  \file_parse_full_name:nNNN { #1 }
   \l__datatool_dir_tl \l__datatool_name_tl \l__datatool_ext_tl
  \tl_if_empty:NT \l__datatool_ext_tl
   {
     \tl_set_eq:NN \l__datatool_ext_tl \l__datatool_default_ext_tl
   }
  \tl_put_right:NV \l__datatool_name_tl \l__datatool_ext_tl
  \tl_if_empty:NF \l__datatool_dir_tl
   {
     \tl_if_eq:NnTF \l__datatool_dir_tl { / }
      {
        \tl_put_left:Nn \l__datatool_name_tl { / }
      }
      {
        \tl_put_left:Nx \l__datatool_name_tl { \l__datatool_dir_tl / }
      }
   }
}
\cs_new:Nn \__datatool_write:n
{
  \iow_now:Nn \g__datatool_out_stream { #1 }
}
\cs_generate_variant:Nn \__datatool_write:n { x }
\cs_new:Nn \__datatool_write_wrap:n
{
  \iow_wrap:nnnN
   { #1 }
   { }
   { \dtl@init@write@wrap }
   \__datatool_write:n
}
\cs_generate_variant:Nn \__datatool_write_wrap:n { x }
\ExplSyntaxOff
\newcommand{\dtl@init@write@wrap}{%
  \def\%{\string\%}%
  \def\\{\string\\}%
  \def\#{\string\#}%
  \def\{{\string\{}%
  \def\}{\string\}}%
  \def\~{\string\~}%
  \def\ {\string\ }%
}
\ExplSyntaxOn
\cs_new:Nn \__datatool_save_csv:n
{
  \l__datatool_io_csv_init_tl
  \tl_clear:N \l__datatool_row_tl
  \ifdtlnoheader
  \else
    \int_zero:N \l__datatool_col_idx_int
    \dtlforeachkey
     (
       \l__datatool_item_key_tl,
       \l__datatool_item_col_tl,
       \l__datatool_item_type_tl,
       \l__datatool_item_head_tl
     )
      \in { #1 } \do
    {
      \int_incr:N \l__datatool_col_idx_int
      \tl_if_empty:NTF \l__datatool_item_head_tl
       { \exp_args:NV \__datatool_append_csv_item:n \l__datatool_item_key_tl }
       { \exp_args:NV \__datatool_append_csv_item:n \l__datatool_item_head_tl }
    }
    \__datatool_write:x { \l__datatool_row_tl }
  \fi
  \int_step_inline:nn { \DTLrowcount { #1 } }
   {
     \tl_clear:N \l__datatool_row_tl
     \dtlgetrow { #1 } { ##1 }
     \int_zero:N \l__datatool_col_idx_int
     \dtlforeachkey
      (
        \l__datatool_item_key_tl,
        \l__datatool_item_col_tl,
        \l__datatool_item_type_tl,
        \l__datatool_item_head_tl
      )
      \in { #1 } \do
      {
        \int_incr:N \l__datatool_col_idx_int
        \exp_args:NNx \dtlgetentryfromcurrentrow
          { \l__datatool_item_value_tl }
          { \l__datatool_item_col_tl }
        \__datatool_rm_datum:N \l__datatool_item_value_tl
        \datatool_if_null:NT \l__datatool_item_value_tl
         {
           \tl_clear:N \l__datatool_item_value_tl
         }
        \exp_args:NV \__datatool_append_csv_item:n \l__datatool_item_value_tl
      }
      \__datatool_write:x { \l__datatool_row_tl }
   }
}
\cs_new:Nn \__datatool_append_csv_item_detect_sep:n
{
  \int_compare:nNnF
     { \l__datatool_col_idx_int } = { \c_one_int }
   { \tl_put_right:Nx \l__datatool_row_tl { \@dtl@separator } }
  \__datatool_save_data:Nn \l__datatool_tmpa_tl { #1 }
  \exp_args:NNx
    \tl_if_in:NnTF \l__datatool_tmpa_tl { \token_to_str:N \@dtl@separator }
    {
      \tl_put_right:Nx \l__datatool_row_tl
        {
          \@dtl@delimiter \l__datatool_tmpa_tl \@dtl@delimiter
        }
    }
    {
      \tl_put_right:Nx \l__datatool_row_tl
        { \l__datatool_tmpa_tl }
    }
}
\cs_new:Nn \__datatool_append_csv_item_always_delim:n
{
  \int_compare:nNnF
     { \l__datatool_col_idx_int } = { \c_one_int }
    { \tl_put_right:Nx \l__datatool_row_tl { \@dtl@separator } }
  \__datatool_save_data:Nn \l__datatool_tmpa_tl { #1 }
  \tl_put_right:Nx \l__datatool_row_tl
    {
      \@dtl@delimiter \l__datatool_tmpa_tl \@dtl@delimiter
    }
}
\cs_new:Nn \__datatool_append_csv_item_no_delim:n
{
  \int_compare:nNnF
     { \l__datatool_col_idx_int } = { \c_one_int }
    { \tl_put_right:Nx \l__datatool_row_tl { \@dtl@separator } }
  \__datatool_save_data:Nn \l__datatool_tmpa_tl { #1 }
  \tl_put_right:Nx \l__datatool_row_tl
    { \l__datatool_tmpa_tl }
}
\cs_set_eq:NN \__datatool_append_csv_item:n
 \__datatool_append_csv_item_detect_sep:n
\tl_new:N \l__datatool_save_version_comment_tl
\tl_set:Nn \l__datatool_save_version_comment_tl
 {
    Created ~ by ~ datatool ~ \use:c { ver@datatool.sty } ~ on ~
    \cs_if_exist:NTF \DTMnow { \DTMnow } { \today }
 }
\cs_new:Nn \__datatool_save_dtltex_ii:n
{
  \__datatool_write:x
    { \c_percent_str \c_space_tl DTLTEX ~ 2.0 ~ \TrackLangEncodingName }
  \__datatool_write:x
    {
      \c_percent_str \c_space_tl \l__datatool_save_version_comment_tl
    }
  \__datatool_write:x
    { \token_to_str:N \DTLnewdb { #1 } \c_percent_str }
  \int_step_inline:nn { \DTLrowcount { #1 } }
   {
     \__datatool_write:x
      { \token_to_str:N \DTLnewrow { #1 } \c_percent_str }
     \dtlgetrow { #1 } { ##1 }
     \dtlforeachkey
      (
        \l__datatool_item_key_tl,
        \l__datatool_item_col_tl,
        \l__datatool_item_type_tl,
        \l__datatool_item_head_tl
      )
      \in { #1 } \do
      {
        \exp_args:NNx \dtlgetentryfromcurrentrow
          { \l__datatool_item_value_tl }
          { \l__datatool_item_col_tl }
        \__datatool_rm_datum:N \l__datatool_item_value_tl
        \datatool_if_null:NF \l__datatool_item_value_tl
         {
           \exp_args:NNo \__datatool_save_data:Nn
              \l__datatool_item_value_tl
             { \l__datatool_item_value_tl }
           \__datatool_write_wrap:x
            {
              \token_to_str:N \DTLnewdbentry { #1 }
               { \l__datatool_item_key_tl }
               { \l__datatool_item_value_tl } \c_percent_str
            }
         }
      }
   }
  \dtlforeachkey
   (
     \l__datatool_item_key_tl,
     \l__datatool_item_col_tl,
     \l__datatool_item_type_tl,
     \l__datatool_item_head_tl
   )
   \in { #1 } \do
   {
     \exp_args:NNo \__datatool_save_data:Nn \l__datatool_item_head_tl
         { \l__datatool_item_head_tl }
     \__datatool_write:x
      {
         \token_to_str:N \DTLsetheader { #1 }
         { \l__datatool_item_key_tl }
         { \l__datatool_item_head_tl }
         \c_percent_str
      }
   }
  \__datatool_write:x
    {
       \token_to_str:N \def
       \token_to_str:N \dtllastloadeddb { #1 }
       \c_percent_str
    }
}
\cs_new:Nn \__datatool_save_dtltex_iii:n
{
  \__datatool_write:x
    { \c_percent_str \c_space_tl DTLTEX ~ 3.0 ~ \TrackLangEncodingName }
  \__datatool_write:x
    {
      \c_percent_str \c_space_tl \l__datatool_save_version_comment_tl
    }
  \__datatool_write:x
    { \token_to_str:N \DTLdbProvideData { #1 } \c_percent_str }
  \int_step_inline:nn { \DTLrowcount { #1 } }
   {
     \__datatool_write:x
      { \token_to_str:N \DTLdbNewRow  }
     \dtlgetrow { #1 } { ##1 }
     \dtlforeachkey
      (
        \l__datatool_item_key_tl,
        \l__datatool_item_col_tl,
        \l__datatool_item_type_tl,
        \l__datatool_item_head_tl
      )
      \in { #1 } \do
      {
        \exp_args:NNx \dtlgetentryfromcurrentrow
          { \l__datatool_item_value_tl }
          { \l__datatool_item_col_tl }
        \__datatool_rm_datum:N \l__datatool_item_value_tl
        \datatool_if_null:NF \l__datatool_item_value_tl
         {
           \exp_args:NNo \__datatool_save_data:Nn
              \l__datatool_item_value_tl
             { \l__datatool_item_value_tl }
           \__datatool_write_wrap:x
            {
              \token_to_str:N \DTLdbNewEntry
               { \l__datatool_item_key_tl }
               { \l__datatool_item_value_tl } \c_percent_str
            }
         }
      }
   }
  \ifdtlnoheader
  \else
    \dtlforeachkey
     (
       \l__datatool_item_key_tl,
       \l__datatool_item_col_tl,
       \l__datatool_item_type_tl,
       \l__datatool_item_head_tl
     )
     \in { #1 } \do
     {
       \exp_args:NNo \__datatool_save_data:Nn \l__datatool_item_head_tl
           { \l__datatool_item_head_tl }
       \__datatool_write:x
        {
           \token_to_str:N \DTLdbSetHeader
           { \l__datatool_item_key_tl }
           { \l__datatool_item_head_tl }
           \c_percent_str
        }
     }
  \fi
}
\cs_new:Nn \__datatool_save_db_ii:n
{
  \cs_set_eq:NN \__datatool_dbtex_row:nn \__datatool_dbtex_row_ii:nn
  \cs_set_eq:NN \__datatool_dbtex_value:nn \__datatool_dbtex_value_ii:nn
  \cs_set_eq:NN \__datatool_dbtex_datum:nnnn \__datatool_dbtex_datum_ii:nnnn
  \cs_set_eq:NN \__datatool_datum:w \__datatool_datum_use_i:w
  \cs_set_eq:NN \__datatool_dbkeys_block:nnnn \__datatool_dbkeys_block_ii:nnnn
  \__datatool_init_dbwrite:
  \__datatool_write:x
    { \c_percent_str \c_space_tl DBTEX ~ 2.0 ~ \TrackLangEncodingName }
  \__datatool_write:x
    {
      \c_percent_str \c_space_tl \l__datatool_save_version_comment_tl
      \iow_newline:
      \token_to_str:N \DTLifdbexists { #1 } \c_percent_str \iow_newline:
      {
         \token_to_str:N \PackageError { datatool }
         { Database ~ `#1' ~ already ~ exists } { }
           \c_percent_str \iow_newline:
         \token_to_str:N \aftergroup
         \token_to_str:N \endinput
      }
      {} \c_percent_str \iow_newline:
      \token_to_str:N \bgroup
      \token_to_str:N \makeatletter
      \iow_newline:
      \token_to_str:N \dtl@message
       { Reconstructing ~ database ~ `#1' }
      \c_percent_str
    }
  \__datatool_write:x
    {
      \token_to_str:N \expandafter
      \token_to_str:N \global
      \token_to_str:N \expandafter
      \iow_newline:
      \token_to_str:N \newtoks
      \token_to_str:N \csname \c_space_tl
        dtlkeys@#1
      \token_to_str:N \endcsname
      \iow_newline:
      \token_to_str:N \expandafter
      \token_to_str:N \global
      \iow_newline:
      \token_to_str:N \csname \c_space_tl
        dtlkeys@#1
      \token_to_str:N \endcsname =
      \iow_char:N \{ \c_percent_str
    }
  \exp_args:Nv \__datatool_write:x { dtlkeys@#1 }
  \__datatool_write:x { \iow_char:N \} \c_percent_str }
  \__datatool_write:x
    {
      \token_to_str:N \expandafter
      \token_to_str:N \global
      \token_to_str:N \expandafter
      \iow_newline:
      \token_to_str:N \newtoks
      \token_to_str:N \csname \c_space_tl
        dtldb@#1
      \token_to_str:N \endcsname
      \iow_newline:
      \token_to_str:N \expandafter
      \token_to_str:N \global
      \iow_newline:
      \token_to_str:N \csname \c_space_tl
        dtldb@#1
      \token_to_str:N \endcsname =
      \iow_char:N \{ \c_percent_str
    }
  \exp_args:Nv \__datatool_write_wrap:x { dtldb@#1 }
  \__datatool_write:x { \iow_char:N \} \c_percent_str }
  \__datatool_write:x
    {
      \token_to_str:N \expandafter
      \token_to_str:N \global \iow_newline: \c_space_tl
      \token_to_str:N \expandafter
      \token_to_str:N \newcount
        \token_to_str:N \csname \c_space_tl
          dtlrows@#1
        \token_to_str:N \endcsname \iow_newline:
      \token_to_str:N \expandafter
      \token_to_str:N \global \iow_newline: \c_space_tl
        \token_to_str:N \csname \c_space_tl
           dtlrows@#1
        \token_to_str:N \endcsname
         = \DTLrowcount { #1 } \token_to_str:N \relax \iow_newline:
      \token_to_str:N \expandafter
      \token_to_str:N \global \iow_newline: \c_space_tl
      \token_to_str:N \expandafter
      \token_to_str:N \newcount
        \token_to_str:N \csname \c_space_tl
          dtlcols@#1
        \token_to_str:N \endcsname \iow_newline:
      \token_to_str:N \expandafter
      \token_to_str:N \global \iow_newline: \c_space_tl
        \token_to_str:N \csname \c_space_tl
           dtlcols@#1
        \token_to_str:N \endcsname
         = \DTLcolumncount { #1 } \token_to_str:N \relax
    }
  \dtlforeachkey
   (
     \l__datatool_item_key_tl,
     \l__datatool_item_col_tl,
     \l__datatool_item_type_tl,
     \l__datatool_item_head_tl
   )
    \in { #1 } \do
  {
    \__datatool_write:x
     {
       \token_to_str:N \expandafter \iow_newline: \c_space_tl
       \token_to_str:N \gdef
         \token_to_str:N \csname \c_space_tl
            dtl@ci@#1@ \l__datatool_item_key_tl
         \token_to_str:N \endcsname
        { \l__datatool_item_col_tl }
       \c_percent_str
     }
  }
  \__datatool_write:n { \egroup }
  \__datatool_write:x
    {
       \token_to_str:N \def
       \token_to_str:N \dtllastloadeddb { #1 }
       \c_percent_str
    }
}
\cs_new:Nn \__datatool_save_db_iii:n
{
  \cs_set_eq:NN \__datatool_dbtex_row:nn \__datatool_dbtex_row_iii:nn
  \cs_set_eq:NN \__datatool_dbtex_value:nn \__datatool_dbtex_value_iii:nn
  \cs_set_eq:NN \__datatool_dbtex_datum:nnnn \__datatool_dbtex_datum_iii:nnnn
  \cs_set_eq:NN \__datatool_datum:w \__datatool_datum_use:w
  \cs_set_eq:NN \__datatool_dbkeys_block:nnnn \__datatool_dbkeys_block_iii:nnnn
  \cs_set_eq:NN \DTLtemporalvalue \__datatool_dbtex_temporal_value:nn
  \__datatool_init_dbwrite:
  \__datatool_write:x
    { \c_percent_str \c_space_tl DBTEX ~ 3.0 ~ \TrackLangEncodingName }
  \__datatool_write:x
    {
      \c_percent_str \c_space_tl \l__datatool_save_version_comment_tl
    }
  \__datatool_write:x
    { \token_to_str:N \DTLdbProvideData { #1 } \c_percent_str }
  \__datatool_write:x { \token_to_str:N \DTLreconstructdatabase  }
  \__datatool_write:x
    { { \DTLrowcount { #1 } } { \DTLcolumncount { #1 } } }
  \exp_args:Nv \__datatool_dbkeys_write_iii:n { dtlkeys@#1 }
  \exp_args:Nv \__datatool_dbtex_content_iii:n { dtldb@#1 }
  \__datatool_write:x
    { \iow_char:N \{ \c_percent_str \c_space_tl Key ~ to ~ index }
  \dtlforeachkey
   (
     \l__datatool_item_key_tl,
     \l__datatool_item_col_tl,
     \l__datatool_item_type_tl,
     \l__datatool_item_head_tl
   )
    \in { #1 } \do
  {
    \__datatool_write:x
     {
        \token_to_str:N \dtldbreconstructkeyindex
         { \l__datatool_item_key_tl } { \l__datatool_item_col_tl }
        \c_percent_str
     }
  }
  \__datatool_write:x
    { \iow_char:N \} \c_percent_str \c_space_tl End ~ of ~ key ~ to ~ index }
}
\cs_new:Nn \__datatool_dbtex_row_ii:nn
 {
    \c_percent_str \iow_newline:
    \c_percent_str \c_space_tl Start ~ of ~ Row ~ #1 \iow_newline:
    \token_to_str:N \db@row@elt@w \c_space_tl
     \c_percent_str \iow_newline:
    \token_to_str:N \db@row@id@w \c_space_tl #1
      \c_percent_str \iow_newline:
    \token_to_str:N \db@row@id@end@ \c_space_tl
      \c_percent_str \iow_newline:
     #2
    \c_percent_str \c_space_tl End ~ of ~ Row ~ #1 \iow_newline:
    \token_to_str:N \db@row@id@w \c_space_tl #1
      \c_percent_str \iow_newline:
    \token_to_str:N \db@row@id@end@ \c_space_tl
      \c_percent_str \iow_newline:
    \token_to_str:N \db@row@elt@end@ \c_space_tl
 }
\cs_new:Nn \__datatool_dbtex_value_ii:nn
 {
   \c_percent_str \c_space_tl Column ~ #1 \iow_newline:
   \token_to_str:N \db@col@id@w \c_space_tl #1
     \c_percent_str \iow_newline:
   \token_to_str:N \db@col@id@end@ \c_space_tl
     \c_percent_str \iow_newline:
   \token_to_str:N \db@col@elt@w \c_space_tl
     \tl_if_head_eq_meaning:nNTF { #2 } \__datatool_datum:nnnn
     { #2 }
     {
       \tl_if_head_eq_meaning:nNTF { #2 } \__datatool_datum:w
       { #2 }
       {
         \__datatool_data_value:n { #2 }
       }
     }
     \c_percent_str \iow_newline:
   \token_to_str:N \db@col@elt@end@ \c_space_tl
     \c_percent_str \iow_newline:
   \token_to_str:N \db@col@id@w \c_space_tl #1
     \c_percent_str \iow_newline:
   \token_to_str:N \db@col@id@end@ \c_space_tl
     \c_percent_str \c_space_tl End ~ of ~ Column ~ #1 \iow_newline:
 }
\cs_new:Nn \__datatool_dbtex_datum_ii:nnnn
 {
   \__datatool_data_value:n { #1 }
 }
\cs_new:Nn \__datatool_dbkeys_block_ii:nnnn
 {
   \c_percent_str \iow_newline:
   \c_percent_str \c_space_tl Header ~ block ~ for ~ column ~ #1 \iow_newline:
   \token_to_str:N \db@plist@elt@w \c_space_tl
     \c_percent_str \iow_newline:
   \token_to_str:N \db@col@id@w \c_space_tl #1
     \c_percent_str \iow_newline:
   \token_to_str:N \db@col@id@end@ \c_space_tl
     \c_percent_str \iow_newline:
   \token_to_str:N \db@key@id@w \c_space_tl #2
     \c_percent_str \iow_newline:
   \token_to_str:N \db@key@id@end@ \c_space_tl
     \c_percent_str \iow_newline:
   \token_to_str:N \db@type@id@w \c_space_tl
     \__datatool_use_datatype:n { #3 }
     \c_percent_str \iow_newline:
   \token_to_str:N \db@type@id@end@ \c_space_tl
     \c_percent_str \iow_newline:
   \token_to_str:N \db@header@id@w \c_space_tl
     \__datatool_data_value:n { #4 }
     \c_percent_str \iow_newline:
   \token_to_str:N \db@header@id@end@ \c_space_tl
     \c_percent_str \iow_newline:
   \token_to_str:N \db@col@id@w \c_space_tl #1
     \c_percent_str \iow_newline:
   \token_to_str:N \db@col@id@end@ \c_space_tl
     \c_percent_str \iow_newline:
   \token_to_str:N \db@plist@elt@end@ \c_space_tl
 }
\cs_new:Nn \__datatool_dbtex_content_iii:n
 {
  \exp_args:Nx \__datatool_write_wrap:n
    {
      { \c_percent_str \c_space_tl Content \iow_newline:
        #1
      } \c_percent_str \c_space_tl End ~ of ~ Content
    }
 }
\cs_new:Nn \__datatool_dbtex_row_iii:nn
 {
   \c_percent_str \c_space_tl Row ~ #1 \iow_newline:
   \token_to_str:N \dtldbrowreconstruct
   { #1 } \c_percent_str
   \iow_newline: {  \c_percent_str \c_space_tl Row ~  #1 ~ Content
   \iow_newline: #2
   }\c_percent_str \iow_newline:
 }
\cs_new:Nn \__datatool_dbtex_value_iii:nn
 {
   \c_space_tl \c_space_tl \token_to_str:N \dtldbcolreconstruct
     { #1 } \c_percent_str \c_space_tl Column ~ #1 \iow_newline:
   \c_space_tl \c_space_tl
   {
     \tl_if_head_eq_meaning:nNTF { #2 } \__datatool_datum:nnnn
      { #2 }
      {
        \__datatool_data_value:n { #2 }
      }
   } \c_percent_str \iow_newline:
 }
\cs_new:Nn \__datatool_dbtex_datum_iii:nnnn
 {
   \token_to_str:N \dtldbdatumreconstruct
    {
      \__datatool_data_value:n { #1 }
    }
    { #2 }
    { \__datatool_data_value:n { #3 } } { \number#4 }
 }
\cs_new:Nn \__datatool_dbtex_temporal_value:nn
 {
   \token_to_str:N \DTLtemporalvalue { #1 } { #2 }
 }
\cs_new:Nn \__datatool_dbkeys_write_iii:n
 {
  \exp_args:Nx \__datatool_write_wrap:n
    {
      { \c_percent_str \c_space_tl Header \iow_newline:
        #1
      } \c_percent_str \c_space_tl End ~ of ~ Header
    }
 }
\cs_new:Nn \__datatool_dbkeys_block_iii:nnnn
 {
   \token_to_str:N \dtldbheaderreconstruct
   { #1 } { #2 } { #3 }
   {
     \__datatool_data_value:n { #4 }
   } \c_percent_str \iow_newline:
 }
\cs_set_eq:NN \__datatool_dbtex_row:nn \__datatool_dbtex_row_iii:nn
\cs_set_eq:NN \__datatool_dbtex_value:nn \__datatool_dbtex_value_iii:nn
\cs_set_eq:NN \__datatool_dbtex_datum:nnnn \__datatool_dbtex_datum_iii:nnnn
\cs_set_eq:NN \__datatool_dbkeys_block:nnnn \__datatool_dbkeys_block_iii:nnnn
\cs_new:Nn \__datatool_init_dbwrite:
{
  \def \db@plist@elt@w
    \db@col@id@w ##1 \db@col@id@end@ % ID
    \db@key@id@w ##2 \db@key@id@end@ % label
    \db@type@id@w ##3 \db@type@id@end@ % type
    \db@header@id@w ##4 \db@header@id@end@ % title
    \db@col@id@w ##5 \db@col@id@end@ % ID
    \db@plist@elt@end@
  {
    \__datatool_dbkeys_block:nnnn { ##1 } { ##2 } { ##3 } { ##4 }
  }
  \def \db@row@elt@w
    \db@row@id@w ##1 \db@row@id@end@
    ##2
    \db@row@id@w ##3 \db@row@id@end@
    \db@row@elt@end@
  {
     \__datatool_dbtex_row:nn { ##1 } { ##2 }
  }
  \def \db@col@id@w ##1 \db@col@id@end@
    \db@col@elt@w ##2 \db@col@elt@end@
    \db@col@id@w ##3 \db@col@id@end@
  {
    \__datatool_dbtex_value:nn { ##1 } { ##2 }
  }
  \cs_set:Nn \__datatool_datum:nnnn
  {
    \__datatool_dbtex_datum:nnnn { ##1 } { ##2 } { ##3 } { ##4 }
  }
}
\newcommand{\dtldbrowreconstruct}[2]{
  \__datatool_row_markup_full:nn { #1 } { #2 }
}
\newcommand{\dtldbcolreconstruct}[2]{
  \__datatool_row_element_markup_full:nn
   { #1 }
   {
     \tl_if_head_eq_meaning:nNTF
       { #2 } \dtldbvaluereconstruct
     { #2 }
     {
       \tl_if_head_eq_meaning:nNTF
          { #2 } \dtldbdatumreconstruct
        { #2 }
        { \exp_not:n { #2 } }
     }
   }
}
\newcommand{\dtldbdatumreconstruct}[4]{
  \__datatool_weird_datum:nnnn
    { #1 } { #2 } { #3 }
  {
    \int_case:nnF { #4 }
    {
       { \c_datatool_integer_int } { \exp_not:N \c_datatool_integer_int }
       { \c_datatool_decimal_int } { \exp_not:N \c_datatool_decimal_int }
       { \c_datatool_currency_int } { \exp_not:N \c_datatool_currency_int }
       { \c_datatool_datetime_int } { \exp_not:N \c_datatool_datetime_int }
       { \c_datatool_date_int } { \exp_not:N \c_datatool_date_int }
       { \c_datatool_time_int } { \exp_not:N \c_datatool_time_int }
       { \c_datatool_unknown_int } { \exp_not:N \c_datatool_unknown_int }
    }
    { \exp_not:N \c_datatool_string_int }
  }
}
\newcommand{\dtldbvaluereconstruct}[1]
{
  \exp_not:n { #1 }
}
\newcommand{\dtldbheaderreconstruct}[4]
{
  \exp_not:N \db@plist@elt@w
  \exp_not:N \db@col@id@w #1
  \exp_not:N \db@col@id@end@
  \exp_not:N \db@key@id@w #2
  \exp_not:N \db@key@id@end@
  \exp_not:N \db@type@id@w #3
  \exp_not:N \db@type@id@end@
  \exp_not:N \db@header@id@w \exp_not:n { #4 }
  \exp_not:N \db@header@id@end@
  \exp_not:N \db@col@id@w #1
  \exp_not:N \db@col@id@end@
  \exp_not:N \db@plist@elt@end@
}
\newcommand{\dtldbreconstructkeyindex}[2]
{
  \csgdef { dtl@ci@\dtllastloadeddb @ #1 } { #2 }
}
\newcommand{\dtl@reconstruct@data}[5]{
  \exp_args:Nxx \@dtl@reconstruct@data { #1 } {#2 }
    { #3 } { #4 } { #5 } { \dtllastloadeddb }
}
\newcommand{\@dtl@reconstruct@data}[6]{
 \expandafter\global
  \expandafter\newcount\csname dtlrows@#6\endcsname
 \expandafter\global
  \csname dtlrows@#6\endcsname=#3\relax
 \expandafter\global
  \expandafter\newcount\csname dtlcols@#6\endcsname
 \expandafter\global
  \csname dtlcols@#6\endcsname=#4\relax
 \expandafter\global\expandafter
 \newtoks\csname dtlkeys@#6\endcsname
 \__datatool_token_register_gset:cn { dtlkeys@#6 } { #1 }
 \expandafter\global\expandafter
    \newtoks\csname dtldb@#6\endcsname
 \__datatool_token_register_gset:cn { dtldb@#6 } { #2 }
 #5
}
\ExplSyntaxOff
\newcommand{\@dtl@def@write}{%
  \newwrite\@dtl@write
  \let\@dtl@def@write\relax
}
\newcommand*{\DTLsavedb}[2]{%
  \DTLwrite[name={#1},overwrite=warn,format=csv,expand=none,add-delimiter=detect]{#2}%
}
\newcommand*{\DTLsavetexdb}[2]{%
  \DTLwrite[name={#1},overwrite=warn,format=dtltex-2,expand=full]{#2}%
}
\newcommand*{\DTLsaverawdb}[2]{%
  \DTLwrite[name={#1},overwrite=warn,format=dbtex-2,expand=full]{#2}%
}
\newcommand*{\DTLprotectedsaverawdb}[2]{%
  \DTLwrite[name={#1},overwrite=warn,format=dbtex-2,expand=none]{#2}%
}
\ExplSyntaxOn
\newcommand{\DTLdbProvideData}[1]{%
  \tl_set:Nn \l__datatool_default_dbname_tl { #1 }
  \tl_set_eq:NN \dtllastloadeddb \l__datatool_default_dbname_tl
  \DTLifdbexists { #1 } { } { \DTLnewdb { #1 } }
}
\newcommand{\DTLreconstructdata}{%
  \PackageWarning { datatool }
  {
    Experimental ~ command ~
    \token_to_str:N \DTLreconstructdata \c_space_tl ~
    deprecated. ~ Re-save ~ file
  }
  \DTLifdbexists{\dtllastloadeddb}%
  {%
    \PackageError{datatool}{Database ~ `\dtllastloadeddb' ~ already ~ exists}{}%
    \use_none:nnnnn
  }%
  {%
    \bgroup\makeatletter
    \let
     \dtl@db@header@reconstruct
     \dtldbheaderreconstruct
    \let
     \dtl@db@row@reconstruct
     \dtldbrowreconstruct
    \def
       \dtl@db@col@reconstruct
       ##1 ##2
     {
      \__datatool_row_element_markup_full:nn
       { ##1 }
       {
         \tl_if_head_eq_meaning:nNTF
           { ##2 } \dtl@db@value@reconstruct
         { ##2 }
         {
           \tl_if_head_eq_meaning:nNTF
             { ##2 } \dtl@db@datum@reconstruct
             { ##2 }
            { \exp_not:n { ##2 } }
         }
       }
     }
    \cs_set_eq:NN
     \dtl@db@datum@reconstruct
     \dtldbdatumreconstruct
    \cs_set_eq:NN
     \dtl@db@value@reconstruct
     \dtldbvaluereconstruct
    \cs_set_eq:NN
     \dtl@db@reconstruct@keyindex
     \dtldbreconstructkeyindex
    \@DTLreconstructdata
  }%
}
\newcommand{\@DTLreconstructdata}[5]{%
  \dtl@message{Reconstructing ~ database ~ `\dtllastloadeddb'}%
  \dtl@reconstruct@data{#1}{#2}{#3}{#4}{#5}%
  \egroup
}
\newcommand{\DTLreconstructdbdata}[5]{
  \DTLifdbexists {\dtllastloadeddb}
  {
    \PackageError{datatool}{Database ~ `\dtllastloadeddb' ~ already ~ exists}{}
  }
  {
    \dtl@message{Reconstructing ~ database ~ `\dtllastloadeddb'}
    \dtl@reconstruct@data{#1}{#2}{#3}{#4}{#5}%
  }
}
\newcommand{\DTLreconstructdatabase}[5]{
  \DTLifdbexists {\dtllastloadeddb}
  {
    \PackageError{datatool}{Database ~ `\dtllastloadeddb' ~ already ~ exists}{}
  }
  {
    \dtl@message{Reconstructing ~ database ~ `\dtllastloadeddb'}
    \dtl@reconstruct@data{#3}{#4}{#1}{#2}{#5}%
  }
}
\cs_new:Nn \__datatool_provide_data:n
{
  \tl_if_empty:NT \l__datatool_io_name_tl
  {
    \tl_set:Nn \l__datatool_io_name_tl { #1 }
  }
  \tl_set_eq:NN \dtllastloadeddb \l__datatool_io_name_tl
  \tl_set_eq:NN \l__datatool_default_dbname_tl \l__datatool_io_name_tl
  \tl_if_eq:NnF \l__datatool_default_ext_tl { .dbtex }
   { \__datatool_load_init:n { \dtllastloadeddb } }
}
\cs_new:Nn \__datatool_set_no_header:nnn
{
  \@sDTLifhaskey { #1 } { #2 }
  {
    \int_set:Nn \dtlcolumnnum { \dtlcolumnindex { #1 } { #2 } }
    \prop_get:NVN \l__datatool_csv_headers_prop \dtlcolumnnum
     \l__datatool_tmpb_tl
    \quark_if_no_value:NTF \l__datatool_tmpb_tl
    {
      \dtl@message{Ignoring ~ header ~ for ~ column ~ `#2' ~ in ~
        database ~ `#1' ~ (no-header=true ~ and ~ no ~ corresponding ~
        key ~ in ~ headers ~ option)}
    }
    {
      \tl_set_eq:NN \l__datatool_item_head_tl \l__datatool_tmpb_tl
      \exp_args:Nnnx \@dtl@setheaderforindex { #1 } { \dtlcolumnnum }
        { \exp_not:o { \l__datatool_item_head_tl } }
    }
  }
  {
    \dtl@message{Ignoring ~ header ~ for ~ column ~ `#2' ~ in ~
      database ~ `#1' ~ (no-header=true ~ and ~ no ~ column ~ with ~ that ~ key)}
  }
}
\NewDocumentCommand \DTLread { o m }
{
  \group_begin:
    \cs_set_eq:NN \DTLdbProvideData \__datatool_provide_data:n
    \IfValueT { #1 } { \keys_set:nn { datatool/io } { #1 } }
    \bool_set_true:N \l__datatool_db_global_bool
    \ifdtlnoheader
     \cs_set_eq:NN \@sDTLsetheader \__datatool_set_no_header:nnn
    \fi
    \__datatool_get_filename:n { #2 }
    \file_if_exist:nTF { \l__datatool_name_tl }
    {
      \__datatool_load:
      \tl_if_eq:NNF
        \l__datatool_io_name_tl \dtllastloadeddb
      {
        \PackageWarning { datatool }
         { Database ~ name ~ `\l__datatool_io_name_tl' ~ requested ~
          but ~ name ~ hardcoded ~ in ~ file ~ as ~ ` \dtllastloadeddb '}
      }
    }
    {
      \PackageError {datatool}
       { Can't ~ open ~ file ~ `\l__datatool_name_tl' }
       {}
    }
    \global\let\dtllastloadeddb\dtllastloadeddb
  \group_end:
}
\int_new:N \l__datatool_line_int
\cs_new:Nn \__datatool_load_csv:
{
  \tl_if_empty:NT \l__datatool_io_name_tl
  {
    \tl_set_eq:NN \l__datatool_io_name_tl \l__datatool_default_dbname_tl
  }
  \int_zero:N \dtlrownum
  \__datatool_load_init:n { \l__datatool_io_name_tl }
  \int_zero:N \l__datatool_line_int
  \l__datatool_io_csv_init_tl
  \exp_args:NV \char_set_catcode_other:N \@dtl@delimiter
  \exp_args:NV \char_set_catcode_other:N \@dtl@separator
  \char_set_catcode_other:n { `\% }
  \ior_open:Nn \g__datatool_in_stream { \l__datatool_name_tl }
  \bool_if:NTF \l__datatool_csv_literal_content_bool
  {
    \ior_str_map_inline:Nn \g__datatool_in_stream
      {
        \__datatool_parse_csv_line:nN { ##1 }
          \__datatool_parse_str_csv_process_item:n
      }
  }
  {
    \ior_map_inline:Nn \g__datatool_in_stream
      {
        \__datatool_parse_csv_line:nN { ##1 }
          \__datatool_parse_csv_process_item:n
      }
  }
  \int_step_function:nN
   { \int_use:c { dtlcols@ \l__datatool_io_name_tl } }
    \__datatool_parse_csv_add_header:n
  \ior_close:N \g__datatool_in_stream
  \tl_set_eq:NN \dtllastloadeddb \l__datatool_io_name_tl
}
\cs_new:Nn \__datatool_parse_csv_add_header:n
{
  \prop_get:NnN
    \l__datatool_csv_types_prop { #1 }
    \l__datatool_tmpb_tl
  \quark_if_no_value:NTF \l__datatool_tmpb_tl
  {
    \tl_set:NV \l__datatool_item_type_tl \c_datatool_string_int
  }
  {
    \tl_set_eq:NN \l__datatool_item_type_tl \l__datatool_tmpb_tl
  }
  \prop_get:NnN
      \l__datatool_csv_keys_prop { #1 }
      \l__datatool_tmpb_tl
  \quark_if_no_value:NTF \l__datatool_tmpb_tl
  {
     \tl_set:Nx \l__datatool_item_key_tl
        { \dtldefaultkey #1 }
  }
  {
    \tl_set_eq:NN \l__datatool_item_key_tl \l__datatool_tmpb_tl
  }
  \@DTLifhaskey { \l__datatool_io_name_tl } \l__datatool_item_key_tl
  { }
  {
    \prop_get:NnN \l__datatool_csv_headers_prop { #1 }
      \l__datatool_tmpb_tl
    \quark_if_no_value:NTF \l__datatool_tmpb_tl
    {
     \tl_set_eq:NN \l__datatool_item_head_tl \l__datatool_item_key_tl
    }
    {
      \tl_set_eq:NN \l__datatool_item_head_tl \l__datatool_tmpb_tl
    }
    \tl_if_empty:NTF \l__datatool_item_type_tl
     {
       \int_set_eq:NN \l__datatool_item_type_int
        \c_datatool_unknown_int
     }
     {
       \int_set:Nn \l__datatool_item_type_int
        { \l__datatool_item_type_tl }
     }
    \__datatool_token_register_gput_right:cx
      { dtlkeys@ \l__datatool_io_name_tl }
    {
      \__datatool_column_markup:nVVV
       { #1 }
       \l__datatool_item_key_tl
       \l__datatool_item_type_int
       \l__datatool_item_head_tl
    }
    \tl_gset:cx
      { dtl@ci@ \l__datatool_io_name_tl @ \l__datatool_item_key_tl }
      { #1 }
  }
}
\cs_new:Nn \__datatool_csv_blank: { }
\cs_new:Nn \__datatool_parse_csv_process_item:n
{
   \@dtl@setnewvalue { #1 }
   \tl_set:NV \l__datatool_item_value_tl \@dtl@toks
   \__datatool_load_data_postprocess:N \l__datatool_item_value_tl
}
\tl_new:N \l_datatool_str_csv_regex_cases_tl
\tl_set:Nn \l_datatool_str_csv_regex_cases_tl
 {
   { \x { 5C } [ nrf ] } { ~ }
   { \x { 5C } t } { \t }
   { \cO(\x{5C}) } { \x{ 5C } textbackslash \cS\x{20} }
   { \cO(\x{23}|\x{24}|\x{25}|\x{26}|\x{5F}|\x{7B}|\x{7D}) } { \x{ 5C } \1 }
   { \cO(\x{5E}) } { \x{ 5C } textasciicircum \cS\x{20} }
   { \cO(\x{7E}) } { \x{ 5C } textasciitilde \cS\x{20} }
 }
\cs_new:Nn \__datatool_parse_str_csv_process_item:n
{
   \tl_set:Nn \l__datatool_item_value_tl { #1 }
   \__datatool_load_data_postprocess:N \l__datatool_item_value_tl
   \exp_args:NV \regex_replace_case_all:nN
     \l_datatool_str_csv_regex_cases_tl
     \l__datatool_item_value_tl
   \exp_args:NNnV \tl_set_rescan:Nnn \l__datatool_item_value_tl { }
     \l__datatool_item_value_tl
   \dtl@domappings \l__datatool_item_value_tl
}
\cs_new:Nn \datatool_if_blank_line:nTF
{
  \tl_if_blank:nTF { #1 }
  { #2 }
  {
    \tl_if_eq:nnTF { #1 } { \par }
     { #2 }
     {
       \regex_match:NnTF \l__datatool_blank_row_regex { #1 }
        { #2 } { #3 }
     }
  }
}
\cs_new:Nn \__datatool_parse_csv_line:nN
{
  \int_incr:N \l__datatool_line_int
  \int_compare:nNnT
     { \l__datatool_line_int } > { \dtl@omitlines }
  {
    \datatool_if_blank_line:nTF { #1 }
    {
      \__datatool_csv_blank:
    }
    {
      \tl_set:Nn \l__datatool_row_tl { #1 }
      \__datatool_split_line:
      \int_zero:N \dtlcolumnnum
      \dtlcurrentrow = { }
      \seq_map_inline:Nn \l__datatool_row_seq
      {
        \int_incr:N \dtlcolumnnum
        \tl_set:Nx \l__datatool_item_col_tl
          { \int_use:N \dtlcolumnnum }
        #2 { ##1 }
        \legacy_if:nTF { dtlnoheader }
        {
          \__datatool_csv_set_auto_reformat:
          \bool_if:NTF \l__datatool_csv_plain_content_bool
           {
             \__datatool_no_parse_csv_item:
           }
           {
             \__datatool_parse_csv_item:
           }
          \__datatool_csv_check_col_idx:
          \__datatool_token_register_put_right:Nx
            \dtlcurrentrow
            {
              \__datatool_row_element_markup:VV
                \l__datatool_item_col_tl
                \l__datatool_item_value_tl
            }
        }
        {
          \legacy_if:nTF { dtlautokeys }
          {
            \tl_set:Nx \l__datatool_item_key_tl
               { \dtldefaultkey \l__datatool_item_col_tl }
            \prop_put:NVV \l__datatool_csv_keys_prop
                \l__datatool_item_col_tl \l__datatool_item_key_tl
          }
          {
            \prop_get:NVN
              \l__datatool_csv_keys_prop \l__datatool_item_col_tl
              \l__datatool_tmpb_tl
            \quark_if_no_value:NTF \l__datatool_tmpb_tl
             {
               \tl_set:Nx \l__datatool_item_key_tl
                 { \tl_to_str:V \l__datatool_item_value_tl }
               \prop_put:NVV \l__datatool_csv_keys_prop
                  \l__datatool_item_col_tl \l__datatool_item_key_tl
             }
             {
               \tl_set_eq:NN
                 \l__datatool_item_key_tl
                 \l__datatool_tmpb_tl
             }
          }
          \__datatool_csv_check_key:
          \prop_if_in:NVF
             \l__datatool_csv_headers_prop
             \l__datatool_item_col_tl
           {
             \prop_put:NVV
               \l__datatool_csv_headers_prop
               \l__datatool_item_col_tl
               \l__datatool_item_value_tl
           }
        }
      }
      \legacy_if:nTF { dtlnoheader }
      {
        \int_incr:N \dtlrownum
        \__datatool_token_register_gput_right:cx
          { dtldb@ \l__datatool_io_name_tl }
          {
            \__datatool_row_markup:VV
              \dtlrownum
              \dtlcurrentrow
          }
        \int_gincr:c
           { dtlrows@ \l__datatool_io_name_tl }
      }
      {
        \legacy_if_set_true:n { dtlnoheader }
      }
      \__datatool_csv_check_col_count:
    }
  }
}
\cs_new:Nn \__datatool_parse_csv_item:
 {
   \seq_if_in:NVTF
     \l__datatool_no_parse_cols_seq
     \l__datatool_item_col_tl
    {
      \tl_if_empty:NTF \l__datatool_item_value_tl
       { \int_set_eq:NN \@dtl@datatype \c_datatool_unknown_int }
       { \int_set_eq:NN \@dtl@datatype \c_datatool_string_int }
    }
    {
     \bool_if:NTF \l__datatool_csv_convert_bool
      {
        \prop_get:NVN
          \l__datatool_csv_types_prop \l__datatool_item_col_tl
          \l__datatool_tmpb_tl
         \quark_if_no_value:NTF \l__datatool_tmpb_tl
          {
            \int_set_eq:NN \@dtl@datatype \c_datatool_unknown_int
          }
          {
            \int_set:Nn \@dtl@datatype { \l__datatool_tmpb_tl }
          }
         \datatool_if_temporal_datum_type:nTF { \@dtl@datatype }
          {
            \__datatool_parse:NV
             \l__datatool_tmpa_tl
             \l__datatool_item_value_tl
          }
          {
            \datatool_if_numeric_datum_type:nTF { \@dtl@datatype }
             {
               \int_compare:nNnTF
                 { \@dtl@datatype } = { \c_datatool_currency_int }
                {
                  \exp_args:NV \DTLdecimaltocurrency
                   \l__datatool_item_value_tl
                   \l__datatool_tmpa_tl
                }
                {
                  \exp_args:NV \DTLdecimaltolocale
                   \l__datatool_item_value_tl
                   \l__datatool_tmpa_tl
                }
             }
             {
               \__datatool_parse:NV
                \l__datatool_tmpa_tl
                \l__datatool_item_value_tl
             }
          }
        \int_set:Nn \@dtl@datatype { \DTLdatumtype \l__datatool_tmpa_tl }
        \bool_if:NF \l__datatool_db_store_datum_bool
         {
            \tl_set:Ne \l__datatool_item_value_tl
               { \l__datatool_tmpa_tl }
         }
      }
      {
        \__datatool_parse:NV
         \l__datatool_tmpa_tl
         \l__datatool_item_value_tl
      }
    }
   \bool_if:NT \l__datatool_db_store_datum_bool
    {
      \__datatool_to_weird_datum:N \l__datatool_tmpa_tl
      \tl_set_eq:NN
        \l__datatool_item_value_tl
        \l__datatool_tmpa_tl
    }
   \int_compare:nNnF
     { \@dtl@datatype } = { \c_datatool_unknown_int }
   {
     \prop_get:NVN
       \l__datatool_csv_types_prop \l__datatool_item_col_tl
       \l__datatool_tmpb_tl
     \quark_if_no_value:NTF \l__datatool_tmpb_tl
      {
        \prop_put:NVx
          \l__datatool_csv_types_prop
          \l__datatool_item_col_tl
          { \number\@dtl@datatype }
      }
      {
        \tl_set_eq:NN
          \l__datatool_item_type_tl
          \l__datatool_tmpb_tl
        \int_compare:nNnT
          { \@dtl@datatype } > { \l__datatool_item_type_tl }
        {
          \prop_put:NVx
            \l__datatool_csv_types_prop \l__datatool_item_col_tl
            { \number\@dtl@datatype }
        }
      }
   }
 }
\cs_new:Nn \__datatool_no_parse_csv_item:
 {
   \int_set_eq:NN
     \@dtl@datatype
     \c_datatool_string_int
   \prop_if_empty:NTF \l__datatool_csv_types_prop
    {
      \prop_put:NVx
        \l__datatool_csv_types_prop \l__datatool_item_col_tl
        { \number\@dtl@datatype }
    }
    {
      \prop_get:NVN
        \l__datatool_csv_types_prop
        \l__datatool_item_col_tl
        \l__datatool_tmpb_tl
     \quark_if_no_value:NTF \l__datatool_tmpb_tl
      {
        \prop_get:NeN
          \l__datatool_csv_types_prop
          { \int_eval:n { \l__datatool_item_col_tl - \c_one_int } }
          \l__datatool_tmpb_tl
        \quark_if_no_value:NF \l__datatool_tmpb_tl
         {
           \int_set:Nn \@dtl@datatype { \l__datatool_tmpb_tl }
         }
        \prop_put:NVx
          \l__datatool_csv_types_prop \l__datatool_item_col_tl
          { \number\@dtl@datatype }
      }
      {
        \int_set:Nn \@dtl@datatype { \l__datatool_tmpb_tl }
      }
    }
   \tl_set_eq:NN
     \l__datatool_datum_original_value_tl
     \l__datatool_item_value_tl
   \tl_clear:N \l__datatool_datum_value_tl
   \tl_clear:N \l__datatool_datum_currency_tl
   \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
    {
      \tl_set_eq:NN
       \l__datatool_datum_value_tl
       \l__datatool_item_value_tl
      \bool_if:NTF \l__datatool_csv_convert_bool
       {
         \int_case:nn { \@dtl@datatype }
          {
            { \c_datatool_integer_int }
             {
               \exp_args:NV \DTLdecimaltolocale
                \l__datatool_datum_original_value_tl
                \l__datatool_item_value_tl
             }
            { \c_datatool_decimal_int }
             {
               \exp_args:NV \DTLdecimaltolocale
                \l__datatool_datum_original_value_tl
                \l__datatool_item_value_tl
             }
            { \c_datatool_currency_int }
             {
               \exp_args:NV \DTLdecimaltocurrency
                \l__datatool_datum_original_value_tl
                \l__datatool_item_value_tl
             }
            { \c_datatool_datetime_int }
              {
                \datatool_decimal_to_temporal:NnV
                  \l__datatool_item_value_tl
                  { \c_datatool_datetime_int }
                  \l__datatool_datum_original_value_tl
              }
            { \c_datatool_date_int }
              {
                \datatool_decimal_to_temporal:NnV
                  \l__datatool_item_value_tl
                  { \c_datatool_date_int }
                  \l__datatool_datum_original_value_tl
              }
            { \c_datatool_time_int }
              {
                \datatool_decimal_to_temporal:NnV
                  \l__datatool_item_value_tl
                  { \c_datatool_time_int }
                  \l__datatool_datum_original_value_tl
              }
          }
         \bool_if:NF \l__datatool_db_store_datum_bool
          {
            \exp_args:NV \tl_if_head_eq_meaning:nNF
              \l__datatool_item_value_tl
              \__datatool_datum:nnnn
            {
              \tl_set:Ne \l__datatool_item_value_tl
               { \l__datatool_item_value_tl }
            }
          }
       }
       {
         \int_compare:nNnT { \@dtl@datatype } = { \c_datatool_currency_int }
          {
            \tl_set:Nn
               \l__datatool_datum_currency_tl
              { \DTLcurr { \DTLCurrencyCode } }
            \tl_set:Ne \l__datatool_item_value_tl
              {
                \exp_not:N \DTLcurrency
                  { \exp_not:V \l__datatool_datum_value_tl }
              }
            \tl_set_eq:NN
              \l__datatool_datum_original_value_tl
              \l__datatool_item_value_tl
          }
       }
    }
   \bool_if:NT \l__datatool_db_store_datum_bool
    {
     \exp_args:NV \tl_if_head_eq_meaning:nNF
        \l__datatool_item_value_tl
         \__datatool_datum:nnnn
      {
        \tl_set:Ne \l__datatool_item_value_tl
         {
           \exp_not:N \__datatool_datum:nnnn
           { \exp_not:V \l__datatool_datum_original_value_tl }
           { \l__datatool_datum_value_tl }
           { \exp_not:V \l__datatool_datum_currency_tl }
           { \int_use:N \@dtl@datatype }
        }
      }
      \__datatool_to_weird_datum_no_parse:N \l__datatool_item_value_tl
    }
 }
\cs_new:Nn \__datatool_split_line:n
{
  \tl_set:Nn \l__datatool_row_tl { #1 }
  \__datatool_split_line:
}
\seq_new:N \l__datatool_row_seq
\cs_new:Nn \__datatool_split_line:
{
  \exp_args:NNV \seq_set_split_keep_spaces:NnV \l__datatool_tmp_seq
    \@dtl@separator  \l__datatool_row_tl
  \seq_clear:N \l__datatool_row_seq
  \tl_set:Nn \l__datatool_item_value_tl { \q_no_value }
  \__datatool_map_row_seq:
  \quark_if_no_value:NF \l__datatool_item_value_tl
  {
    \seq_put_right:NV \l__datatool_row_seq \l__datatool_item_value_tl
  }
}
\cs_new:Nn \__datatool_map_row_seq:
{
  \seq_pop_left:NN \l__datatool_tmp_seq \l__datatool_tmpb_tl
  \quark_if_no_value:NF \l__datatool_tmpb_tl
  {
    \quark_if_no_value:NTF \l__datatool_item_value_tl
    {
       \regex_replace_once:NnNTF \l__datatool_delim_both_regex { \1 }
         \l__datatool_tmpb_tl
       {
          \seq_put_right:NV \l__datatool_row_seq \l__datatool_tmpb_tl
       }
       {
         \regex_replace_once:NnNTF \l__datatool_delim_left_regex { }
           \l__datatool_tmpb_tl
         {
            \tl_set_eq:NN \l__datatool_item_value_tl \l__datatool_tmpb_tl
         }
         {
            \bool_if:NT \l__datatool_new_element_trim_bool
            {
              \tl_trim_spaces:N \l__datatool_tmpb_tl
            }
            \seq_put_right:NV \l__datatool_row_seq \l__datatool_tmpb_tl
         }
       }
    }
    {
       \regex_replace_once:NnNTF \l__datatool_delim_right_regex { }
         \l__datatool_tmpb_tl
       {
          \tl_put_right:NV \l__datatool_item_value_tl \@dtl@separator
          \tl_put_right:NV \l__datatool_item_value_tl \l__datatool_tmpb_tl
          \seq_put_right:NV \l__datatool_row_seq \l__datatool_item_value_tl
          \tl_set:Nn \l__datatool_item_value_tl { \q_no_value }
       }
       {
          \tl_put_right:NV \l__datatool_item_value_tl \@dtl@separator
          \tl_put_right:NV \l__datatool_item_value_tl \l__datatool_tmpb_tl
       }
    }
    \__datatool_map_row_seq:
  }
}
\cs_new:Nn \__datatool_load_tex:
{
  \tl_clear:N \dtllastloadeddb
  \file_input:n { \l__datatool_name_tl }
  \tl_if_empty:NT \l__datatool_io_name_tl
  {
    \tl_set_eq:NN \l__datatool_io_name_tl \dtllastloadeddb
  }
}
\ExplSyntaxOff
\newcommand*{\DTLloaddbtex}[2]{%
  \DTLread[format=dbtex]{#2}%
  \let#1\dtllastloadeddb
}
\NewDocumentCommand\DTLloaddb{O{}mm}{%
  \DTLread[name={#2},format=csv,csv-content=tex,#1]{#3}%
}
\NewDocumentCommand\DTLloadrawdb{O{}mm}{%
  \DTLread[name={#2},format=csv,csv-content=literal,#1]{#3}%
}
\newcommand*{\DTLrawmap}[2]{%
  \expandafter\@dtl@toks\expandafter{\@dtl@rawmappings}%
  \ifdefempty{\@dtl@rawmappings}%
  {%
    \def\@dtl@rawmappings{{#1}{#2}}%
  }%
  {%
    \def\@dtl@tmp{{#1}{#2}}%
    \protected@edef\@dtl@rawmappings{\the\@dtl@toks,\@dtl@tmp}%
  }%
}
\newcommand*{\@dtl@rawmappings}{}
\newcommand*{\dtl@domappings}[1]{%
  \@for\@dtl@map:=\@dtl@rawmappings\do{%
    \expandafter\DTLsubstituteall\expandafter#1\@dtl@map
  }%
}
\ExplSyntaxOn
\NewDocumentCommand{\DTLdbLog} { m }
{
  \group_begin:
  \tl_set:Nv \__datatool_tmpa_tl { dtldb@#1 }
  \regex_replace_case_all:nN
   {
     { \c{ db@row@elt@w } } { < ROW \ BLOCK \ START > ^^J }
     { \c{ db@row@elt@end@ } } { < ROW \ BLOCK \ END > ^^J }
     { \c{ db@row@id@w } } { \ < ROW \ ID \ DELIM \ START > }
     { \c{ db@row@id@end@ } } { < ROW \ ID \ DELIM \ END > ^^J }
     { \c{ db@col@id@w } } { \ \ < COL \ ID \ DELIM \ START > }
     { \c{ db@col@id@end@ } } { < COL \ ID \ DELIM \ END > ^^J }
     { \c{ db@col@elt@w } } { \ \ \ < ELEMENT \ START > }
     { \c{ db@col@elt@end@ } } { < ELEMENT \ END >  ^^J }
   }
   \__datatool_tmpa_tl
  \PackageInfo { datatool }
  {
    Details ~ of ~ database ~ `#1': ^^J
    Column ~ Count: ~ \DTLcolumncount{#1}. ~
    Row ~ Count: ~ \DTLrowcount{#1}. ^^J
    Correctly ~ formatted ~ database ~ content ~
    should ~ have ~ each ~ row ~ marked ~ up ~
    with: ^^J
    <ROW ~ BLOCK ~ START> ^^J
    <ROW ~ ID ~ DELIM ~ START> rowidx
    <ROW ~ ID ~ DELIM ~ END> ^^J
    column data ^^J
    <ROW ~ ID ~ DELIM ~ START> rowidx
    <ROW ~ ID ~ DELIM ~ END> ^^J
    <ROW ~ BLOCK ~ END>^^J
    Content ~ of ~ `#1':^^J
    \exp_not:V \__datatool_tmpa_tl
  }
  \group_end:
}
\ExplSyntaxOff
\newcommand*{\dtlshowdb}[1]{%
  \ifcsundef{dtldb@#1}%
  {\PackageError{datatool}{Database `#1' not defined}{}}%
  {\expandafter\showthe\csname dtldb@#1\endcsname}%
}
\newcommand*{\dtlshowdbkeys}[1]{%
  \ifcsundef{dtlkeys@#1}%
  {\PackageError{datatool}{Database `#1' not defined}{}}%
  {\expandafter\showthe\csname dtlkeys@#1\endcsname}%
}
\newcommand*{\dtlshowtype}[2]{%
  \ifcsundef{dtldb@#1}%
  {\PackageError{datatool}{Database `#1' not defined}{}}%
  {\DTLgetdatatype{\@dtl@type}{#1}{#2}\show\@dtl@type}%
}
\edef\@dtl@construct@lopoff#1#2{%
  \noexpand\long
    \noexpand\def\noexpand\@dtl@lopoff#1##1##2\noexpand\to##3##4{%
      \noexpand\ifx#2##1\noexpand\relax
        \noexpand\ifstrempty{##1}%
        {\noexpand\@dtl@qlopoff#1{}##2\noexpand\to##3##4\relax}%
        {%
          \noexpand\dtl@ifsingle{##1}%
          {\noexpand\@dtl@qlopoff#1##1##2\noexpand\to##3##4\relax}%
          {\noexpand\@dtl@qlopoff#1{##1}##2\noexpand\to##3##4\relax}%
        }%
      \noexpand\else
        \noexpand\ifstrempty{##1}%
        {\noexpand\@dtl@lop@ff#1{}##2\noexpand\to##3##4\relax}%
        {%
          \noexpand\dtl@ifsingle{##1}%
          {\noexpand\@dtl@lop@ff#1##1##2\noexpand\to##3##4\relax}%
          {\noexpand\@dtl@lop@ff#1{##1}##2\noexpand\to##3##4\relax}%
        }%
      \noexpand\fi
  }%
}
\edef\@dtl@construct@qlopoff#1#2{%
  \noexpand\long
    \noexpand\def\noexpand\@dtl@qlopoff#1#2##1#2#1##2\noexpand\to##3##4{%
      \noexpand\def##4{##1}%
      \noexpand\DTLsubstituteall{##4}{#2#2}{#2}%
      \noexpand\edef\noexpand\@dtl@dosubs{%
        \noexpand\noexpand\noexpand\DTLsubstituteall{\noexpand\noexpand##4}%
        {\noexpand\expandafter\noexpand\noexpand\noexpand\csname#2\noexpand\endcsname#2}%
        {\noexpand\expandafter\noexpand\noexpand\noexpand\csname#2\noexpand\endcsname}%
      }%
      \noexpand\@dtl@dosubs
      \noexpand\def##3{#1##2}%
  }%
}
\edef\@dtl@construct@lop@ff#1{%
  \noexpand\long
    \noexpand\def\noexpand\@dtl@lop@ff#1##1#1##2\noexpand\to##3##4{%
      \noexpand\def##4{##1}%
      \noexpand\def##3{#1##2}%
  }%
}
\newcommand{\@dtl@construct@lopoffs}{%
  \edef\@dtl@chars{{\@dtl@separator}{\@dtl@delimiter}}%
  \expandafter\@dtl@construct@lopoff\@dtl@chars
  \expandafter\@dtl@construct@qlopoff\@dtl@chars
  \expandafter\@dtl@construct@lop@ff\expandafter{\@dtl@separator}%
}
\endinput
%%
%% End of file `datatool.sty'.
