%\iffalse
% datatool.dtx generated using makedtx version 1.2 (c) Nicola Talbot
% Command line args:
%   -setambles "databib\.bst=>\nopreamble\nopostamble"
%   -author "Nicola Talbot"
%   -doc "datatool-manual.tex"
%   -comment "databib\.bst"
%   -macrocode "databib\.bst"
%   -codetitle ""
%   -src "datatool-base.sty\Z=>datatool-base.sty"
%   -src "datatool-undetermined.ldf\Z=>datatool-undetermined.ldf"
%   -src "datatool-(utf8|latin1).ldf\Z=>datatool-\1.ldf"
%   -src "datatool-l3fp.def\Z=>datatool-l3fp.def"
%   -src "datatool-lua.def\Z=>datatool-lua.def"
%   -src "datatool-fp.def\Z=>datatool-fp.def"
%   -src "datatool-fp.sty\Z=>datatool-fp.sty"
%   -src "datatool-pgfmath.def\Z=>datatool-pgfmath.def"
%   -src "datatool-pgfmath.sty\Z=>datatool-pgfmath.sty"
%   -src "datatool.sty\Z=>datatool.sty"
%   -src "datagidx.sty\Z=>datagidx.sty"
%   -src "databib.sty\Z=>databib.sty"
%   -src "databar.sty\Z=>databar.sty"
%   -src "datapie.sty\Z=>datapie.sty"
%   -src "dataplot.sty\Z=>dataplot.sty"
%   -src "person.sty\Z=>person.sty"
%   -src "databib.bst\Z=>databib.bst"
%   -src "(.*-2019-09-27.sty\Z)=>\1"
%   datatool
% Created on 2025/4/25 14:23
%\fi
%\iffalse
%<*package>
%% \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         \~}
%</package>
%\fi
% \iffalse
% Doc-Source file to use with LaTeX2e
% Copyright (C) 2025 Nicola Talbot, all rights reserved.
% \fi
% \iffalse
%<*driver>
\iffalse
datatool-manual.tex is a stub file used by makedtx to create
datatool.dtx
\fi

\documentclass{article}

\usepackage{doc}
\CheckSum{61945}

\RecordChanges
\PageIndex
\SetupDoc{reportchangedates}
\setcounter{IndexColumns}{2}

\usepackage{fontspec}
\usepackage{tcolorbox}
\usepackage{datatool-base}
\usepackage{hologo}

\setmainfont{Liberation Serif}
\setsansfont{Liberation Sans}
\setmonofont{Liberation Mono}

\newcommand*{\LuaLaTeX}{\hologo{LuaLaTeX}}
\newcommand*{\XeLaTeX}{\hologo{XeLaTeX}}
\newcommand*{\pdfLaTeX}{\hologo{pdfLaTeX}}

\definecolor{defbackground}{rgb}{1,1,0.75}

\newtcolorbox{definition}{halign=flush left,
 colframe=black,colback=defbackground,
 fontupper=\ttfamily,
 before upper={\frenchspacing\obeylines},
 after={\par\noindent\ignorespacesafterend}
}

\newtcolorbox{important}[1][]{
 coltitle=red,colbacktitle=red!20!white,
 colframe=red,colback=red!5!white,#1}


\NewDocElement{Option}{option}
\NewDocElement{Counter}{counter}

\newcommand{\ics}[1]{\cs{#1}}
\newcommand{\sty}[1]{\textsf{#1}}
\newcommand{\isty}[1]{\textsf{#1}}
\newcommand{\cls}[1]{\textsf{#1}}
\newcommand{\app}[1]{\texttt{#1}}
\newcommand{\ctr}[1]{\textsf{#1}}
\newcommand{\env}[1]{\textsf{#1}}
\newcommand{\ctrfmt}[1]{\textsf{#1}}
\newcommand{\pkgoptfmt}[1]{\textsf{#1}}
\newcommand{\pkgopt}[2][]{\pkgoptfmt{#2\ifstrempty{#1}{}{=#1}}}
\newcommand{\filetype}[1]{\texttt{#1}}
\newcommand{\term}[1]{\emph{#1}}
\newcommand{\qt}[1]{``#1''}

\providecommand\marg[1]{%
  \texorpdfstring{\allowbreak{\ttfamily\char`\{}\meta{#1}{\ttfamily\char`\}}}
  {\{#1\}}}
\providecommand\oarg[1]{%
  \texorpdfstring{\allowbreak{\ttfamily[}\meta{#1}{\ttfamily]}}
  {[#1]}} 
  
\providecommand\parg[1]{\texorpdfstring{(\meta{#1})}{(#1)}}

\begin{document}
\DocInput{datatool.dtx}
\end{document}
%</driver>
%\fi
%\DeleteShortVerb{|}
%\MakeShortVerb{"}
%
%\title{Documented Code for datatool v3.4.1}
%\author{Nicola L. C. Talbot\\
%\url{http://www.dickimaw-books.com/}}
%
%\date{2025-04-25}
%\maketitle
%
%\section{Introduction}
%
%This is the documented code for the \sty{datatool} bundle. See
%\url{datatool-user.pdf} for the main user manual.
%
%\StopEventually{%
%  \clearpage
%  \PrintChanges
%  \clearpage
%  \PrintIndex
%}
%
%
%
%\iffalse
%    \begin{macrocode}
%<*datatool-base.sty>
%    \end{macrocode}
%\fi
%\section{datatool-base.sty}
%\label{sec:code:datatool-base}
% This package provides all the basic commands needed by various
% packages in the \sty{datatool} bundle.
%The \sty{datatool} package was first released in 2007. The
%\LaTeX\ kernel has changed significantly since then. I've started
%switching over to using \LaTeX3, but it's still in a hybrid state.
%I have too many large packages and not enough time to do a
%complete rewrite.
%
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
%    \end{macrocode}
% Rollback releases:
%    \begin{macrocode}
\DeclareRelease{v2.32}{2019-09-27}{datatool-base-2019-09-27.sty}
\DeclareCurrentRelease{v3.4.1}{2025-04-25}
%    \end{macrocode}
% Declare package:
%    \begin{macrocode}
\ProvidesPackage{datatool-base}[2025/04/25 v3.4.1 (NLCT)]
%    \end{macrocode}
% Required packages:
%    \begin{macrocode}
\RequirePackage{etoolbox}
\RequirePackage{amsmath}
\RequirePackage{xfor}
\RequirePackage{ifthen}
%    \end{macrocode}
%\sty{tracklang} now loaded as from v3.0. Ideally it needs to be
%v1.6.3+ but doesn't matter so much if no localisation support
%requested.
%    \begin{macrocode}
\RequirePackage{tracklang}[2022/12/13]
%    \end{macrocode}
%Removed substr.sty.
%
%\subsection{Package Options}
%
%Version 3.0 has switched to \LaTeX3 key=value interface.
%
%\begin{macro}{\ifdtlverbose}
%Switch to govern verbose mode.
%    \begin{macrocode}
\newif\ifdtlverbose
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\if@dtl@utf8}
%Switch to determine UTF-8 setting. Deprecated.
%    \begin{macrocode}
\expandafter\newif\csname if@dtl@utf8\endcsname
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\TrackLangEncodingName}
%Check for \cs{TrackLangEncodingName}. This is used to input the
%appropriate encoding file, if available. If this command isn't
%available then it's likely that an old version of \sty{tracklang}
%is installed.
%    \begin{macrocode}
\ExplSyntaxOn
\cs_if_exist:NF \TrackLangEncodingName
{
  \cs_if_exist:NTF \inputencodingname
  { \tl_set:NN \TrackLangEncodingName \inputencodingname }
  { \tl_set:Nn \TrackLangEncodingName { utf8 } }
}
%    \end{macrocode}
%\end{macro}
%Are we using a Unicode engine? (Affects regular expression matches
%with \c{x}\marg{hex} for sort handler functions.)
%    \begin{macrocode}
\bool_lazy_any:nTF
 {
  { \sys_if_engine_luatex_p: }
  { \sys_if_engine_xetex_p: }
  { \sys_if_engine_uptex_p: }
 }
 {
   \cs_set_eq:NN \datatool_if_unicode_engine:TF \use_i:nn
   \cs_set_eq:NN \datatool_if_unicode_engine:T \use:n
   \cs_set_eq:NN \datatool_if_unicode_engine:F \use_none:n
 }
 {
   \cs_set_eq:NN \datatool_if_unicode_engine:TF \use_ii:nn
   \cs_set_eq:NN \datatool_if_unicode_engine:T \use_none:n
   \cs_set_eq:NN \datatool_if_unicode_engine:F \use:n
 }
%    \end{macrocode}
%
%\begin{macro}{\@dtl@mathprocessor}
%As from v3.0, the default processor is lua if \cs{directlua} is
%defined or l3fp otherwise.
%\changes{3.0}{2025-03-03}{changed default processor to l3fp or lua}
%    \begin{macrocode}
\ifdef\directlua
{\providecommand*{\@dtl@mathprocessor}{lua}}
{\providecommand*{\@dtl@mathprocessor}{l3fp}}
%    \end{macrocode}
%\end{macro}
%
%Determine whether or not to load locale support. This command will
%either expand to its argument or ignore it.
%    \begin{macrocode}
\newcommand\datatool@load@locales[1]{#1}
%    \end{macrocode}
%List of locales to track (in addition to any that might already be
%tracked):
%    \begin{macrocode}
\clist_new:N \l__datatool_extra_locales_clist
%    \end{macrocode}
%
%When to purify sort values:
%    \begin{macrocode}
\bool_new:N \l__datatool_initial_purify_early_bool
\bool_set_true:N \l__datatool_initial_purify_early_bool
%    \end{macrocode}
%
%List of types to reformat if auto-reformat settings are on:
%    \begin{macrocode}
\seq_new:N \l__datatool_auto_reformat_types_seq
\seq_set_from_clist:Nn \l__datatool_auto_reformat_types_seq
 { integer , decimal , si , currency , datetime , date , time }
%    \end{macrocode}
% Append to sequence:
%    \begin{macrocode}
\cs_new:Nn \__datatool_auto_reformat_types_do:n
 {
   \tl_if_eq:nnTF { #1 } { integer }
    {
      \seq_put_right:Nn \l__datatool_auto_reformat_types_seq
        { #1 }
    }
    {
      \tl_if_eq:nnTF { #1 } { decimal }
       {
         \seq_put_right:Nn \l__datatool_auto_reformat_types_seq
           { #1 }
       }
       {
         \tl_if_eq:nnTF { #1 } { si }
          {
            \seq_put_right:Nn \l__datatool_auto_reformat_types_seq
              { #1 }
          }
          {
            \tl_if_eq:nnTF { #1 } { datetime }
             {
               \seq_put_right:Nn \l__datatool_auto_reformat_types_seq
                 { #1 }
             }
             {
               \tl_if_eq:nnTF { #1 } { date }
                {
                  \seq_put_right:Nn \l__datatool_auto_reformat_types_seq
                    { #1 }
                }
                {
                  \tl_if_eq:nnTF { #1 } { time }
                   {
                     \seq_put_right:Nn \l__datatool_auto_reformat_types_seq
                       { #1 }
                   }
                   {
                     \PackageError { datatool-base }
                      {
                        Unsupported ~ auto-reformat ~ data ~ type ~
                         identifier  ~ ` #1 '
                      }
                      {
                        Supported ~ identifiers: ~
                        `integer', ~ `decimal', ~ `si', ~
                        `datetime', ~ `date', ~ `time'
                      }
                   }
                }
             }
          }
       }
    }
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\prg_new_conditional:Npnn \__datatool_if_auto_reformat_on:n #1
   { T, F, TF }
 {
   \seq_if_in:NnTF
      \l__datatool_auto_reformat_types_seq
     { #1 }
    {
       \tl_if_eq:nnTF { #1 } { si }
        {
           \bool_if:NTF \l__datatool_reformat_numeric_bool
            { \prg_return_true: }
            { \prg_return_false: }
        }
        {
          \int_if_exist:cTF { c_datatool_ #1 _int }
           {
             \exp_args:Nc \datatool_if_temporal_datum_type:nTF
               { c_datatool_ #1 _int }
              {
                \bool_if:NTF \l__datatool_reformat_datetime_bool
                 { \prg_return_true: }
                 { \prg_return_false: }
              }
              {
                \exp_args:Nc \datatool_if_numeric_datum_type:nTF
                  { c_datatool_ #1 _int }
                 {
                   \bool_if:NTF \l__datatool_reformat_numeric_bool
                    { \prg_return_true: }
                    { \prg_return_false: }
                 }
                 { \prg_return_false: }
              }
           }
           { \prg_return_false: }
        }
    }
    { \prg_return_false: }
 }
%    \end{macrocode}
%
% Similarly, but data type under consideration is in
% \cs{@dtl@datatype}:
%    \begin{macrocode}
\prg_new_conditional:Npnn \__datatool_if_auto_reformat_on: 
   { T, F, TF }
 {
   \int_case:nnF { \@dtl@datatype }
    {
      { \c_datatool_integer_int }
       {
         \seq_if_in:NnTF
            \l__datatool_auto_reformat_types_seq
           { integer }
          {
            \bool_if:NTF \l__datatool_reformat_numeric_bool
             { \prg_return_true: }
             { \prg_return_false: }
          }
          { \prg_return_false: }
       }
      { \c_datatool_decimal_int }
       {
         \seq_if_in:NnTF
            \l__datatool_auto_reformat_types_seq
           { decimal }
          {
            \bool_if:NTF \l__datatool_reformat_numeric_bool
             { \prg_return_true: }
             { \prg_return_false: }
          }
          { \prg_return_false: }
       }
      { \c_datatool_currency_int }
       {
         \seq_if_in:NnTF
            \l__datatool_auto_reformat_types_seq
           { currency }
          {
            \bool_if:NTF \l__datatool_reformat_numeric_bool
             { \prg_return_true: }
             { \prg_return_false: }
          }
          { \prg_return_false: }
       }
      { \c_datatool_datetime_int }
       {
         \seq_if_in:NnTF
            \l__datatool_auto_reformat_types_seq
           { datetime }
          {
            \bool_if:NTF \l__datatool_reformat_datetime_bool
             { \prg_return_true: }
             { \prg_return_false: }
          }
          { \prg_return_false: }
       }
      { \c_datatool_date_int }
       {
         \seq_if_in:NnTF
            \l__datatool_auto_reformat_types_seq
           { date }
          {
            \bool_if:NTF \l__datatool_reformat_datetime_bool
             { \prg_return_true: }
             { \prg_return_false: }
          }
          { \prg_return_false: }
       }
      { \c_datatool_time_int }
       {
         \seq_if_in:NnTF
            \l__datatool_auto_reformat_types_seq
           { time }
          {
            \bool_if:NTF \l__datatool_reformat_datetime_bool
             { \prg_return_true: }
             { \prg_return_false: }
          }
          { \prg_return_false: }
       }
    }
    { \prg_return_false: }
 }
%    \end{macrocode}
%
%Option definitions. Note that all packages in the \sty{datatool}
%bundle now all use the same option group.
%    \begin{macrocode}
\keys_define:nn { datatool }
{
   verbose .legacy_if_set:n = dtlverbose,
   utf8 .legacy_if_set:n = @dtl@utf8,
   math .choices:nn = { fp, pgfmath, l3fp, lua }
                      { \tl_set_eq:NN \@dtl@mathprocessor \l_keys_choice_tl },
   math .usage:n = load,
   lang-warn .choice: ,
   lang-warn / true .code:n =
    {
      \cs_set_eq:NN \datatool_locale_warn:nn \PackageWarning
    } ,
   lang-warn / false .code:n =
    {
      \PackageInfo
        {datatool-base}
        {
         localisation ~ warnings ~ switched ~ 
         off ~ (including ~ tracklang ~ warnings)
        }
      \cs_set_eq:NN  \datatool_locale_warn:nn \use_none:nn
      \TrackLangShowWarningsfalse
    } ,
   lang-warn .default:n = true ,
   lang-warn .initial:n = true ,
   lang-warn .usage:n = load ,
   nolocale .code:n = 
    {
      \cs_set_eq:NN \datatool@load@locales \use_none:n
      \clist_clear:N \l__datatool_extra_locales_clist
    },
   nolocale .value_forbidden:n = true,
   nolocale .usage:n = load,
   locales .code:n = 
     {
        \cs_set_eq:NN \datatool@load@locales \use:n
        \clist_set:Nn \l__datatool_extra_locales_clist { #1 }
     },
   locales .usage:n = load,
%    \end{macrocode}
%Synonym:
%    \begin{macrocode}
   lang .code:n = 
     {
        \cs_set_eq:NN \datatool@load@locales \use:n
        \clist_set:Nn \l__datatool_extra_locales_clist { #1 }
     },
   lang .usage:n = load,
   lists .code:n = { \keys_set:nn { datatool / lists } { #1 } },
   initial-purify .choice:,
   initial-purify / early .code:n =
      { \bool_set_true:N \l__datatool_initial_purify_early_bool },
   initial-purify / late .code:n =
      { \bool_set_false:N \l__datatool_initial_purify_early_bool },
   auto-reformat-types .code:n = 
     {
       \seq_clear:N \l__datatool_auto_reformat_types_seq
       \exp_args:Ne \clist_map_function:nN { #1 }
          \__datatool_auto_reformat_types_do:n
     } ,
   compare .code:n = { \keys_set:nn { datatool / compare } { #1 } },
   datetime .code:n = { \keys_set:nn { datatool / datetime } { #1 } },
   numeric .code:n = { \keys_set:nn { datatool / numeric } { #1 } },
}
%    \end{macrocode}
%
% Numeric options (new to v3.0).
%\changes{3.0}{2025-03-03}{added numeric option}
%
%Should numeric values be reformatted after parsing?
%    \begin{macrocode}
\bool_new:N \l__datatool_reformat_numeric_bool
\bool_set_false:N \l__datatool_reformat_numeric_bool
%    \end{macrocode}
%
%\begin{macro}{\DTLscinum}
%\changes{3.0}{2025-03-03}{new}
% Used with auto-reformat for scientific notation.
%    \begin{macrocode}
\IfPackageLoadedTF { siunitx }
 {
   \newcommand \DTLscinum [ 1 ] { \num { #1 } }
 }
 {
   \newcommand \DTLscinum [ 1 ] { #1 }
   \AddToHook
     { package / siunitx / after }
     {
       \renewcommand \DTLscinum [ 1 ] { \num { #1 } }
     }
 }
%    \end{macrocode}
%\end{macro}
%
%Should region files override the default number chars?
%    \begin{macrocode}
\bool_new:N \l_datatool_region_set_numberchars_bool
\bool_set_true:N \l_datatool_region_set_numberchars_bool
%    \end{macrocode}
%
%Should region files override the default currency?
%    \begin{macrocode}
\bool_new:N \l_datatool_region_set_currency_bool
\bool_set_true:N \l_datatool_region_set_currency_bool
%    \end{macrocode}
%
%\begin{macro}{\datatoolcurrencysymbolprefixfmt}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\datatoolcurrencysymbolprefixfmt}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLcurrCodeOrSymOrChar}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLcurrCodeOrSymOrChar}[3]{#2}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\keys_define:nn { datatool / numeric }
{
   auto-reformat .bool_set:N = \l__datatool_reformat_numeric_bool ,
%    \end{macrocode}
%This will also switch off any region number chars.
%Value needs to be in the form \marg{grp-char}\marg{decimal-char}
%    \begin{macrocode}
   set-number-chars .code:n =
    {
      \DTLsetnumberchars #1 
      \bool_set_false:N \l_datatool_region_set_numberchars_bool
    } ,
%    \end{macrocode}
%Should the region set the number group and decimal characters:
%    \begin{macrocode}
   region-number-chars .choice: ,
   region-number-chars / true .code:n =
    {
      \bool_set_true:N \l_datatool_region_set_numberchars_bool
      \tl_if_empty:NF \l_datatool_current_region_tl
       {
         \cs_if_exist_use:cF
          { datatool \l_datatool_current_region_tl SetNumberChars }
          {
            \PackageWarning { datatool-base }
             {
               No ~ numberchars ~ hook ~ found ~ for ~ region ~ 
                 ` \l_datatool_current_region_tl '
             }
          }
       }
    } ,
   region-number-chars / false .code:n =
    {
      \bool_set_false:N \l_datatool_region_set_numberchars_bool
    } ,
   region-number-chars .default:n = true ,
%    \end{macrocode}
%Currency must already have been defined. This will also switch off
%any region currency.
%    \begin{macrocode}
   set-currency .code:n = 
    {
      \tl_if_exist:cTF { dtl@curr@ \tl_to_str:n { #1 } @sym }
       {
         \tl_set:Ne \@dtl@currency { \exp_not:c { DTLcurr #1 } }
         \tl_set:Ne \DTLCurrencyCode { #1 }
         \tl_set:Ne \DTLfmtcurrency
           { \exp_not:c { dtl@curr@ #1 @fmt } }
         \bool_false:N \l_datatool_region_set_currency_bool
       }
       {
         \PackageError { datatool-base }
          {
            Currency ~ ` #1 ' ~ has ~ not ~ been ~ defined
          }
          {
            Currency ~ must ~ first ~ be ~ defined ~ with ~
            \tl_to_str:N \DTLdefcurrency
          }
       }
    } ,
   region-currency .choice: ,
   region-currency / true .code:n =
    {
      \bool_set_true:N \l_datatool_region_set_currency_bool
      \tl_if_empty:NF \l_datatool_current_region_tl
       {
         \cs_if_exist_use:cF
          { datatool \l_datatool_current_region_tl SetCurrency }
          {
            \PackageWarning { datatool-base }
             {
               No ~ currency ~ hook ~ found ~ for ~ region ~ 
                 ` \l_datatool_current_region_tl '
             }
          }
       }
    } ,
   region-currency / false .code:n =
    {
      \bool_set_false:N \l_datatool_region_set_currency_bool
    } ,
   region-currency .default:n = true ,
%    \end{macrocode}
% Convenient method of redefining formatting command used by region
% files, but will only have an effect if the region uses it.
%    \begin{macrocode}
   region-currency-prefix .choice: ,
   region-currency-prefix / normal .code:n =
    {
      \cs_set_eq:NN \datatoolcurrencysymbolprefixfmt \use:n
    } ,
   region-currency-prefix / smallcaps .code:n =
    {
      \renewcommand \datatoolcurrencysymbolprefixfmt [ 1 ]
       {
         \exp_args:Ne \textsc { \text_lowercase:n { ##1 } }
       }
    } ,
   region-currency-prefix / smaller .code:n =
    {
      \renewcommand \datatoolcurrencysymbolprefixfmt [ 1 ]
       {
         \textsmaller { ##1 }
       }
    } ,
%    \end{macrocode}
%Redefine \cs{DTLcurrCodeOrSymOrChar}:
%    \begin{macrocode}
   currency-symbol-style .choice:,
   currency-symbol-style / iso .code:n = 
    {
      \cs_set_eq:NN \DTLcurrCodeOrSymOrChar \use_i:nnn
    },
   currency-symbol-style / symbol .code:n = 
    {
      \cs_set_eq:NN \DTLcurrCodeOrSymOrChar \use_ii:nnn
    },
   currency-symbol-style / string .code:n = 
    {
      \cs_set_eq:NN \DTLcurrCodeOrSymOrChar \use_iii:nnn
    },
}
%    \end{macrocode}
%
% Date/time options (new to v3.0).
%\changes{3.0}{2025-03-03}{added datetime option}
%
%Should date/time values be parsed?
%    \begin{macrocode}
\bool_new:N \l__datatool_parse_datetime_bool
\bool_set_false:N \l__datatool_parse_datetime_bool
%    \end{macrocode}
%
%The next two conditionals are only relevant if the above is true.
%Should date/time values be parsed for ISO format?
%    \begin{macrocode}
\bool_new:N \l__datatool_parse_datetime_iso_bool
\bool_set_true:N \l__datatool_parse_datetime_iso_bool
%    \end{macrocode}
%Should date/time values be parsed for regional format?
%This will require the appropriate regional support.
%    \begin{macrocode}
\bool_new:N \l__datatool_parse_datetime_regional_bool
\bool_set_true:N \l__datatool_parse_datetime_regional_bool
%    \end{macrocode}
%
%Should parsed date/time values be reformatted?
%    \begin{macrocode}
\bool_new:N \l__datatool_reformat_datetime_bool
\bool_set_false:N \l__datatool_reformat_datetime_bool
%    \end{macrocode}
%
%\begin{macro}{\DTLCurrentLocaleFormatDate}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLCurrentLocaleFormatDate}\marg{YYYY}\marg{MM}\marg{DD}\marg{DOW}
%\end{definition}
%Localisation support should redefine this.
%    \begin{macrocode}
\newcommand \DTLCurrentLocaleFormatDate [4]
 {
   \datatool_default_date_fmt:nnnn { #1 } { #2 } { #3 } { #4 }
 }
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new_nopar:Nn \datatool_default_date_fmt:nnnn
 {
   \cs_if_exist:NTF \DTMdisplaydate
    {
      \tl_if_empty:nTF { #4 }
       {
         \DTMdisplaydate { #1 } { #2 } { #3 } { -1 }
       }
       {
         \DTMdisplaydate { #1 } { #2 } { #3 } { #4 }
       }
    }
    {
      \datatool_date_iso_fmt:nnnn { #1 } { #2 } { #3 } { #4 }
    }
 }
%    \end{macrocode}
%
%\begin{macro}{\DataToolDateFmt}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DataToolDateFmt}\marg{YYYY}\marg{MM}\marg{DD}\marg{DOW}
%\end{definition}
%    \begin{macrocode}
\newcommand \DataToolDateFmt [4]
 {
   \DTLCurrentLocaleFormatDate { #1 } { #2 } { #3 } { #4 }
 }
%    \end{macrocode}
%\end{macro}
%
%The following has a fourth argument for consistency.
%    \begin{macrocode}
\cs_new_nopar:Nn \datatool_date_iso_fmt:nnnn
 {
   \datatool_date_iso_fmt:nnn { #1 } { #2 } { #3 }
 }
\cs_new_nopar:Nn \datatool_date_iso_fmt:nnn
 {
   \int_eval:n { #1 }
    - \datatool_two_digits:n { #2 }
    - \datatool_two_digits:n { #3 }
 }
%    \end{macrocode}
%
%\begin{macro}{\DTLCurrentLocaleFormatTime}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLCurrentLocaleFormatDate}\marg{hh}\marg{mm}\marg{ss}
%\end{definition}
%Localisation support should redefine this.
%    \begin{macrocode}
\newcommand \DTLCurrentLocaleFormatTime [3]
 {
   \datatool_default_time_fmt:nnn { #1 } { #2 } { #3 }
 }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new_nopar:Nn \datatool_default_time_fmt:nnn
 {
   \cs_if_exist:NTF \DTMdisplaytime
    {
      \tl_if_empty:nTF { #3 }
       {
         \DTMdisplaytime { #1 } { #2 } { 0 }
       }
       {
         \DTMdisplaytime { #1 } { #2 } { #3 }
       }
    }
    {
      \datatool_time_iso_fmt:nnn { #1 } { #2 } { #3 }
    }
 }
%    \end{macrocode}
%
%\begin{macro}{\DataToolTimeFmt}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DataToolTimeFmt}\marg{hh}\marg{mm}\marg{ss}
%\end{definition}
%    \begin{macrocode}
\newcommand \DataToolTimeFmt [3]
 {
   \DTLCurrentLocaleFormatTime { #1 } { #2 } { #3 }
 }
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new_nopar:Nn \datatool_time_iso_fmt:nnn
 {
   \datatool_two_digits:n { #1 }
   \c_colon_str
   \datatool_two_digits:n { #2 }
   \tl_if_empty:nF { #3 }
    {
      \c_colon_str
      \datatool_two_digits:n { #3 }
    }
 }
%    \end{macrocode}
%
%\begin{macro}{\DTLCurrentLocaleFormatTimeZone}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLCurrentLocaleFormatTimeZone}\marg{TZh}\marg{TZm}
%\end{definition}
%    \begin{macrocode}
\newcommand \DTLCurrentLocaleFormatTimeZone [2]
 {
   \datatool_default_timezone_fmt:nn { #1 } { #2 }
 }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \datatool_default_timezone_fmt:nn
 {
   \cs_if_exist:NTF \DTMdisplayzone
    {
      \DTMdisplayzone { #1 } { #2 }
    }
    {
      \datatool_timezone_iso_fmt:nn { #1 } { #2 }
    }
 }
%    \end{macrocode}
%
%\begin{macro}{\DataToolTimeZoneFmt}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DataToolTimeZoneFmt}\marg{TZh}\marg{TZm}
%\end{definition}
%    \begin{macrocode}
\newcommand \DataToolTimeZoneFmt [2]
 {
   \DTLCurrentLocaleFormatTimeZone { #1 } { #2 }
 }
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new_nopar:Nn \datatool_timezone_iso_fmt:nn
 {
   \datatool_signed_two_digits:n { #1 }
   \c_colon_str
   \datatool_two_digits:n { #2 }
 }
%    \end{macrocode}
%
%\begin{macro}{\DTLCurrentLocaleTimeStampFmtSep}
%\changes{3.0}{2025-03-03}{new}
%Localisation files may redefine this.
%    \begin{macrocode}
\newcommand \DTLCurrentLocaleTimeStampFmtSep { ~ }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DataToolTimeStampFmtSep}
%\changes{3.0}{2025-03-03}{new}
%Separator used in \verb|\datatool_default_timestamp_fmt:nnnnnnn| between the date and
%time. Defaults to a space.
%    \begin{macrocode}
\newcommand \DataToolTimeStampFmtSep
 {
   \DTLCurrentLocaleTimeStampFmtSep
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DataToolDateTimeFmt}
%\begin{definition}
%\cs{DataToolDateTimeFmt}\marg{date-args}\marg{time-args}\marg{time-zone-args}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%The \meta{date-args} argument may either be empty or in the form
%\marg{YYYY}\marg{MM}\marg{DD}\marg{DOW} appropriate for \cs{DataToolDateFmt}.
%The \meta{time-args} argument may either be empty or in the form
%\marg{hh}\marg{mm}\marg{ss} appropriate for \cs{DataToolTimeFmt}.
%The \meta{time-zone-args} argument may either be empty or in the form
%\marg{TZh}\marg{TZm} appropriate for \cs{DataToolTimeZoneFmt}.
%    \begin{macrocode}
\newcommand* \DataToolDateTimeFmt [3]
 {
   \tl_if_empty:nTF { #1 }
    {
%    \end{macrocode}
%No date arguments.
%    \begin{macrocode}
     \tl_if_empty:nF { #2 }
      {
%    \end{macrocode}
%Time arguments provided. (There should be three.)
%    \begin{macrocode}
        \DataToolTimeFmt #2 
      }
     \tl_if_empty:nF { #3 }
      {
%    \end{macrocode}
%Time zone arguments provided. (There should be two.)
%    \begin{macrocode}
        \DataToolTimeZoneFmt #3 
      }
    }
    {
%    \end{macrocode}
%Date arguments provided. (There should be four.)
%    \begin{macrocode}
      \tl_if_empty:nTF { #2 }
      {
%    \end{macrocode}
%No time arguments provided.
%    \begin{macrocode}
        \DataToolDateFmt #1
      }
      {
%    \end{macrocode}
%Time arguments provided. (There should be three.)
%    \begin{macrocode}
       \tl_if_empty:nTF { #3 }
        {
%    \end{macrocode}
%No time zone arguments provided.
%    \begin{macrocode}
          \DataToolTimeStampNoZoneFmt #1 #2
        }
        {
%    \end{macrocode}
%Time zone arguments provided. (There should be two.)
%    \begin{macrocode}
          \DataToolTimeStampWithZoneFmt #1 #2 #3
        }
      }
    }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DataToolTimeStampWithZoneFmt}
%\begin{definition}
%\cs{DataToolTimeStampWithZoneFmt}\marg{YYYY}\marg{MM}\marg{DD}\marg{DOW}%
%\marg{hh}\marg{mm}\marg{ss}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand \DataToolTimeStampWithZoneFmt [9]
 {
   \DTLCurrentLocaleFormatTimeStampWithZone
     { #1 } { #2 } { #3 } { #4 } { #5 } { #6 } { #7 } { #8 } { #9 }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DataToolTimeStampNoZoneFmt}
%\begin{definition}
%\cs{DataToolTimeStampNoZoneFmt}\marg{YYYY}\marg{MM}\marg{DD}\marg{DOW}%
%\marg{hh}\marg{mm}\marg{ss}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand \DataToolTimeStampNoZoneFmt [7]
 {
   \DTLCurrentLocaleFormatTimeStampNoZone
     { #1 } { #2 } { #3 } { #4 } { #5 } { #6 } { #7 } 
 }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new_nopar:Nn \datatool_default_timestamp_fmt:nnnnnnn
 {
   \cs_if_exist:NTF \DTMdisplay
    {
%    \end{macrocode}
%The \sty{datetime2} style will need to have the zone suppressed
%with showzone=false (if supported by the current style).
%    \begin{macrocode}
      \tl_if_empty:nTF { #4 }
       {
         \DTMdisplay
           { #1 } { #2 } { #3 } { -1 }
           { #5 } { #6 } { #7 }
           { 0 } { 0 }
       }
       {
         \DTMdisplay
           { #1 } { #2 } { #3 } { #4 }
           { #5 } { #6 } { #7 }
           { 0 } { 0 }
       }
    }
    {
      \datatool_default_date_fmt:nnnn
        { #1 } { #2 } { #3 } { #4 }
      \DataToolTimeStampFmtSep
      \datatool_default_time_fmt:nnn
        { #5 } { #6 } { #7 }
    }
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new_nopar:Nn \datatool_default_timestamp_fmt:nnnnnnnnn
 {
   \cs_if_exist:NTF \DTMdisplay
    {
      \tl_if_empty:nTF { #4 }
       {
         \DTMdisplay
           { #1 } { #2 } { #3 } { -1 }
           { #5 } { #6 } { #7 }
           { #8 } { #9 }
       }
       {
         \DTMdisplay
           { #1 } { #2 } { #3 } { #4 }
           { #5 } { #6 } { #7 }
           { #8 } { #9 }
       }
    }
    {
      \datatool_default_timestamp_fmt:nnnnnnn
        { #1 } { #2 } { #3 } { #4 }
        { #5 } { #6 } { #7 }
      \datatool_default_timezone_fmt:nn
        { #8 } { #9 }
    }
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new_nopar:Nn \datatool_timestamp_iso_fmt:nnnnnnn
 {
   \datatool_date_iso_fmt:nnnn { #1 } { #2 } { #3 } { #4 }
   T
   \datatool_time_iso_fmt:nnn { #5 } { #6 } { #7 }
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new_nopar:Nn \datatool_timestamp_iso_fmt:nnnnnnnnn
 {
   \datatool_timestamp_iso_fmt:nnnnnnn
    { #1 } { #2 } { #3 } { #4 }
    { #5 } { #6 } { #7 }
   \datatool_timezone_iso_fmt:nn { #8 } { #9 }
 }
%    \end{macrocode}
%
%\begin{macro}{\DTLCurrentLocaleFormatTimeStampWithZone}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLCurrentLocaleFormatTimeStampWithZone}\marg{YYYY}\marg{MM}\marg{DD}\marg{DOW}%
%\marg{hh}\marg{mm}\marg{ss}\marg{tzh}\marg{tzm}
%\end{definition}
%Localisation support should redefine this.
%    \begin{macrocode}
\newcommand \DTLCurrentLocaleFormatTimeStampWithZone [9]
 {
   \datatool_default_timestamp_fmt:nnnnnnnnn
    { #1 } { #2 } { #3 } { #4 } { #5 } { #6 } { #7 } { #8 } { #9 }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLCurrentLocaleFormatTimeStampNoZone}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLCurrentLocaleFormatTimeStampNoZone}\marg{YYYY}\marg{MM}\marg{DD}\marg{DOW}%
%\marg{hh}\marg{mm}\marg{ss}
%\end{definition}
%Localisation support should redefine this.
%    \begin{macrocode}
\newcommand \DTLCurrentLocaleFormatTimeStampNoZone [7]
 {
   \datatool_default_timestamp_fmt:nnnnnnn
      { #1 } { #2 } { #3 } { #4 } { #5 } { #6 } { #7 }
 }
%    \end{macrocode}
%\end{macro}
%
%
%    \begin{macrocode}
\keys_define:nn { datatool / datetime }
{
   parse .choice:,
   parse / false .code:n = 
    {
      \bool_set_false:N \l__datatool_parse_datetime_bool
    } ,
   parse / true .code:n = 
    {
      \bool_set_true:N \l__datatool_parse_datetime_bool
    } ,
   parse / parse-only .code:n = 
    {
      \bool_set_true:N \l__datatool_parse_datetime_bool
      \bool_set_false:N \l__datatool_reformat_datetime_bool
    } ,
   parse / auto-reformat .code:n = 
    {
      \bool_set_true:N \l__datatool_parse_datetime_bool
      \bool_set_true:N \l__datatool_reformat_datetime_bool
    } ,
   parse / iso-only .code:n =
    {
      \bool_set_true:N \l__datatool_parse_datetime_bool
      \bool_set_true:N \l__datatool_parse_datetime_iso_bool
      \bool_set_false:N \l__datatool_parse_datetime_regional_bool
    } ,
   parse / region-only .code:n =
    {
      \bool_set_true:N \l__datatool_parse_datetime_bool
      \bool_set_false:N \l__datatool_parse_datetime_iso_bool
      \bool_set_true:N \l__datatool_parse_datetime_regional_bool
    } ,
   parse / iso + region .code:n =
    {
      \bool_set_true:N \l__datatool_parse_datetime_bool
      \bool_set_true:N \l__datatool_parse_datetime_iso_bool
      \bool_set_true:N \l__datatool_parse_datetime_regional_bool
    } ,
   parse .default:n = true ,
   auto-reformat .choice: ,
   auto-reformat / false .code:n =
    {
      \bool_set_false:N \l__datatool_reformat_datetime_bool
    } ,
   auto-reformat / true .code:n =
    {
      \bool_set_true:N \l__datatool_reformat_datetime_bool
    } ,
   auto-reformat / region .code:n =
    {
      \bool_set_true:N \l__datatool_reformat_datetime_bool
      \renewcommand \DataToolDateFmt [ 4 ]
       {
         \DTLCurrentLocaleFormatDate { ##1 } { ##2 } { ##3 } { ##4 }
       }
      \renewcommand \DataToolTimeFmt [ 3 ]
       {
         \DTLCurrentLocaleFormatTime { ##1 } { ##2 } { ##3 }
       }
      \renewcommand \DataToolTimeZoneFmt [ 2 ]
       {
         \DTLCurrentLocaleFormatTimeZone { ##1 } { ##2 }
       }
      \renewcommand \DataToolTimeStampWithZoneFmt [ 9 ]
       {
         \DTLCurrentLocaleFormatTimeStampWithZone
           { ##1 } { ##2 } { ##3 } { ##4 }
           { ##5 } { ##6 } { ##7 }
           { ##8 } { ##9 }
       }
      \renewcommand \DataToolTimeStampNoZoneFmt [ 7 ]
       {
         \DTLCurrentLocaleFormatTimeStampNoZone
           { ##1 } { ##2 } { ##3 } { ##4 }
           { ##5 } { ##6 } { ##7 }
       }
      \renewcommand \DataToolTimeStampFmtSep
       {
        \DTLCurrentLocaleTimeStampFmtSep
       }
    } ,
   auto-reformat / iso .code:n =
    {
      \bool_set_true:N \l__datatool_reformat_datetime_bool
      \renewcommand \DataToolDateFmt [ 4 ]
       {
         \datatool_date_iso_fmt:nnnn
           { ##1 } { ##2 } { ##3 } { ##4 }
       }
      \renewcommand \DataToolTimeFmt [ 3 ]
       {
         \datatool_time_iso_fmt:nnn
           { ##1 } { ##2 } { ##3 }
       }
      \renewcommand \DataToolTimeZoneFmt [ 2 ]
       {
         \datatool_timezone_iso_fmt:nn
           { ##1 } { ##2 }
       }
      \renewcommand \DataToolTimeStampWithZoneFmt [ 9 ]
       {
         \datatool_timestamp_iso_fmt:nnnnnnnnn
           { ##1 } { ##2 } { ##3 } { ##4 }
           { ##5 } { ##6 } { ##7 }
           { ##8 } { ##9 }
       }
      \renewcommand \DataToolTimeStampNoZoneFmt [ 7 ]
       {
         \datatool_timestamp_iso_fmt:nnnnnnn
           { ##1 } { ##2 } { ##3 } { ##4 }
           { ##5 } { ##6 } { ##7 }
       }
    } ,
   auto-reformat / datetime2 .code:n = 
    {
      \cs_if_exist:NTF \DTLdisplay
       {
         \bool_set_true:N \l__datatool_reformat_datetime_bool
         \renewcommand \DataToolDateFmt [ 4 ]
          {
            \tl_if_empty:nTF { ##4 }
             {
               \DTMdisplaydate { ##1 } { ##2 } { ##3 } { -1 }
             }
             {
               \DTMdisplaydate { ##1 } { ##2 } { ##3 } { ##4 }
             }
          }
         \renewcommand \DataToolTimeFmt [ 3 ]
          {
            \tl_if_empty:nTF { ##3 }
             {
               \DTMdisplaytime { ##1 } { ##2 } { 0 }
             }
             {
               \DTMdisplaytime { ##1 } { ##2 } { ##3 }
             }
          }
         \renewcommand \DataToolTimeZoneFmt [ 2 ]
          {
            \DTMdisplayzone { ##1 } { ##2 }
          }
         \renewcommand \DataToolTimeStampWithZoneFmt [ 9 ]
          {
            \tl_if_empty:nTF { ##4 }
             {
               \DTMdisplay
                 { ##1 } { ##2 } { ##3 } {-1 }
                 { ##5 } { ##6 } { ##7 }
                 { ##8 } { ##9 }
             }
             {
               \DTMdisplay
                 { ##1 } { ##2 } { ##3 } { ##4 }
                 { ##5 } { ##6 } { ##7 }
                 { ##8 } { ##9 }
             }
          }
         \renewcommand \DataToolTimeStampNoZoneFmt [ 7 ]
          {
            \tl_if_empty:nTF { ##4 }
             {
               \DTMdisplay
                 { ##1 } { ##2 } { ##3 } {-1 }
                 { ##5 } { ##6 } { ##7 }
                 { 0 } { 0 }
             }
             {
               \DTMdisplay
                 { ##1 } { ##2 } { ##3 } { ##4 }
                 { ##5 } { ##6 } { ##7 }
                 { 0 } { 0 }
             }
          }
       }
       {
          \PackageError { datatool-base }
           {
              Option ~ datetime ~ = ~ { ~ auto-reformat ~ = ~ datetime2 ~ } ~
              requires ~ datetime2 ~ package
           }
           {
             You ~ need ~ to ~ load ~ datetime2.sty ~ before ~ you ~
             set ~ this ~ option
           }
       }
    } ,
   auto-reformat .default:n = false ,
}
%    \end{macrocode}
%
% List options (new to v3.0).
%\changes{3.0}{2025-03-03}{added lists option}
%
%If true, trim leading and trailing space around elements in the list-related commands below.
%Note the default changes the behaviour as from v3.0.
%    \begin{macrocode}
\bool_new:N \l__datatool_list_trim_bool
%    \end{macrocode}
%Reverse sort CSV lists:
%    \begin{macrocode}
\bool_new:N \l__datatool_sort_reverse_bool
%    \end{macrocode}
%Preprocess to create datum elements when sorting CSV lists:
%    \begin{macrocode}
\bool_new:N \l__datatool_sort_datum_bool
%    \end{macrocode}
%
%\begin{macro}{\DTLlistand}
%\changes{3.0}{2025-03-03}{new}
%Use either \cs{DTLandname} or \verb|\&| in lists.
%(No difference if no language support provided.)
%    \begin{macrocode}
\bool_new:N \l_datatool_list_and_bool
\bool_set_true:N \l_datatool_list_and_bool
\newcommand \DTLlistand
 {
   \bool_if:NTF \l_datatool_list_and_bool
    { \DTLandname }
    { \& }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\ifDTLlistskipempty}
%\changes{2.31}{2018-12-07}{new}
%If true, skip empty elements in the list-related commands below.
%    \begin{macrocode}
\newif\ifDTLlistskipempty
\DTLlistskipemptytrue
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\keys_define:nn { datatool / lists }
{
   skip-empty .legacy_if_set:n = DTLlistskipempty ,
   trim .bool_set:N = \l__datatool_list_trim_bool ,
   trim .initial:n = true ,
   sort-reverse .bool_set:N = \l__datatool_sort_reverse_bool ,
   sort-reverse .initial:n = false ,
   sort-datum .bool_set:N = \l__datatool_sort_datum_bool ,
   sort-datum .initial:n = false ,
   and .choice: ,
   and / word .code:n =
    {
      \bool_true:N \l_datatool_list_and_bool
    } ,
   and / symbol .code:n =
    {
      \bool_false:N \l_datatool_list_and_bool
    } ,
}
%    \end{macrocode}
%
% String comparison options.
%\begin{macro}{\ifdtlcompareskipcs}
%\changes{2.32}{2019-09-27}{new}
%If true, \cs{dtlcompare} should skip control sequences.
%    \begin{macrocode}
\newif\ifdtlcompareskipcs
\dtlcompareskipcsfalse
%    \end{macrocode}
%\end{macro}
%
%If the following boolean is true, \cs{dtlcompare} should expand control sequences.
%    \begin{macrocode}
\bool_new:N \l__datatool_compare_expand_cs_bool
%    \end{macrocode}
%
%    \begin{macrocode}
\keys_define:nn { datatool / compare }
{
   expand-cs .bool_set:N = \l__datatool_compare_expand_cs_bool,
   skip-cs .legacy_if_set:n = dtlcompareskipcs,
}
%    \end{macrocode}
%
%\begin{macro}{\DTLsetup}
%\changes{3.0}{2025-03-03}{new}
%Provide user interface command to set options later.
%    \begin{macrocode}
\DeclareDocumentCommand \DTLsetup { m }
 { \keys_set:nn { datatool } { #1 } }
%    \end{macrocode}
%\end{macro}
%Use \cs{DTLsetLocaleOptions} for options provided by localisation
%files.
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
% Process options:
%    \begin{macrocode}
\ProcessKeyOptions[datatool]
%    \end{macrocode}
%
% Load file dealing with numerical processes. As from 
%version 3.0, these are in def files not packages.
%    \begin{macrocode}
\InputIfFileExists
 {datatool-\@dtl@mathprocessor.def}
 {}
 {
   \InputIfFileExists
    {datatool-l3fp.def}
    {
       \PackageError{datatool}
       {%
         Missing file `datatool-\@dtl@mathprocessor.def'
         for math=\@dtl@mathprocessor. Falling back on
         math=l3fp
       }
       {%
         Check that your TeX distribution contains the file
         `datatool-\@dtl@mathprocessor.def'
       }
    } 
    {
       \PackageError{datatool}
       {%
         Missing file `datatool-\@dtl@mathprocessor.def'
         for math=\@dtl@mathprocessor. No math commands
         available!
       }
       {%
         Something is wrong with the datatool installation
       }
    }
 }
%    \end{macrocode}
%
%\subsection{Utilities}
%
%\begin{macro}{\dtl@message}
%\begin{definition}
%\cs{dtl@message}\marg{message string}
%\end{definition}
% Displays message only if the verbose option is set.
%    \begin{macrocode}
\newcommand*{\dtl@message}[1]{%
  \ifdtlverbose\typeout{#1}\fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@toks}
%    \begin{macrocode}
\newtoks\@dtl@toks
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@tmpcount}
% Define temporary count register
%    \begin{macrocode}
\newcount\@dtl@tmpcount
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@tmplength}
% Define temporary length register.
% TODO: rename \cs{l\_\_datatool\_tmp\_dim}?
%    \begin{macrocode}
\newlength\dtl@tmplength
%    \end{macrocode}
%\end{macro}
%
%Provide a way of measuring the height of text with problematic
%commands locally disabled:
%    \begin{macrocode}
\ExplSyntaxOn
\cs_new:Nn \datatool_measure_height:Nn
{
%    \end{macrocode}
%NB \cs{settoheight} automatically scopes the content.
%    \begin{macrocode}
  \settoheight #1 { \l_datatool_measure_hook_tl #2 }
}
\cs_new:Nn \datatool_measure_width:Nn
{
  \settowidth #1 { \l_datatool_measure_hook_tl #2 }
}
\cs_new:Nn \datatool_measure_depth:Nn
{
  \settodepth #1 { \l_datatool_measure_hook_tl #2 }
}
%    \end{macrocode}
%\begin{definition}
%\cs{datatool\_measure:NNNn} \meta{wd-dim} \meta{ht-dim}
%\meta{dp-dim} \marg{text}
%\end{definition}
%Measure all dimensions.
%    \begin{macrocode}
\cs_new:Nn \datatool_measure:NNNn
 {
   \hbox_set:Nn \l__datatool_measure_box
    { \l_datatool_measure_hook_tl #4 }
   \dim_set:Nn #1 { \box_wd:N \l__datatool_measure_box }
   \dim_set:Nn #2 {\box_ht:N \l__datatool_measure_box }
   \dim_set:Nn #3 { \box_dp:N \l__datatool_measure_box }
 }
%    \end{macrocode}
%\begin{definition}
%\cs{datatool\_measure\_ht\_plus\_dp:Nn} \meta{dim} \marg{text}
%\end{definition}
%Measure the height plus depth.
%    \begin{macrocode}
\cs_new:Nn \datatool_measure_ht_plus_dp:Nn
 {
   \hbox_set:Nn \l__datatool_measure_box
    { \l_datatool_measure_hook_tl #2 }
   \dim_set:Nn #1
    {
      \box_ht:N \l__datatool_measure_box
      + \box_dp:N \l__datatool_measure_box
    }
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\box_new:N \l__datatool_measure_box
%    \end{macrocode}
%Hook:
%    \begin{macrocode}
\tl_new:N \l_datatool_measure_hook_tl
\tl_set:Nn \l_datatool_measure_hook_tl
{
  \cs_set_eq:NN \label \use_none:n
  \cs_set_eq:NN \ref \use_none:n
  \cs_set_eq:NN \pageref \use_none:n
  \cs_set_eq:NN \refstepcounter \__datatool_local_stepcounter:n
  \cs_set_eq:NN \stepcounter \__datatool_local_stepcounter:n
  \cs_set_eq:NN \hypertarget \use_ii:nn
  \cs_set_eq:NN \hyperlink \use_ii:nn
}
%    \end{macrocode}
%Local step counter for use in the above. This doesn't trigger an
%error if the counter is undefined nor does it reset dependent counter.
%    \begin{macrocode}
\cs_new:Nn \__datatool_local_stepcounter:n
{
  \int_if_exist:cT { c@ #1 } { \int_incr:c { c@ #1 } }
}
%    \end{macrocode}
%Swap values in integer variables:
%    \begin{macrocode}
\cs_new:Nn \datatool_swap_ints:NN
{
  \exp_args:Nee \__datatool_swap_ints:nnNN
    { \int_use:N #1 } { \int_use:N #2 } #1 #2
}
\cs_new:Nn \__datatool_swap_ints:nnNN
{
  \int_set:Nn #3 { #2 }
  \int_set:Nn #4 { #1 }
}
%    \end{macrocode}
%
%\begin{macro}{\dtlifintopenbetween}
%\begin{definition}
%\cs{dtlifintopenbetween}\marg{num}\marg{min}\marg{max}\marg{true
%part}\marg{false part}
%\end{definition}
% If the values may be decimal, use \cs{dtlifnumopenbetween}
% instead.
%
%\changes{3.0}{2025-03-03}{switched to \LaTeX3}
% Version 3.0 rewritten to use \LaTeX3 commands. This means it can
% now fully expand. 
%    \begin{macrocode}
\newcommand{\dtlifintopenbetween}[5]{%
  \bool_lazy_and:nnTF
    { \int_compare_p:nNn { #1 } > { #2 } }
    { \int_compare_p:nNn { #1 } < { #3 } }
  { #4 } { #5 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlifintclosedbetween}
%\begin{definition}
%\cs{dtlifintclosedbetween}\marg{num}\marg{min}\marg{max}\marg{true
%part}\marg{false part}
%\end{definition}
% If the values may be decimal, use \cs{dtlifnumclosedbetween}
% instead.
%
%\changes{3.0}{2025-03-03}{switched to \LaTeX3}
% Version 3.0 rewritten to use \LaTeX3 commands. This means it can
% now fully expand. It's simpler to perform the reverse test (if
% outside the range).
%    \begin{macrocode}
\newcommand{\dtlifintclosedbetween}[5]{%
  \bool_lazy_or:nnTF
    { \int_compare_p:nNn { #1 } < { #2 } }
    { \int_compare_p:nNn { #1 } > { #3 } }
  { #5 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%
%This is mainly for formatting time zone hours with a required
%leading sign:
%    \begin{macrocode}
\cs_new:Nn \datatool_signed_two_digits:n
 {
   \int_compare:nNnTF { #1 } < { \c_zero_int }
    {
      - \datatool_two_digits:e { \int_abs:n { #1 } }
    }
    {
      + \datatool_two_digits:n { #1 }
    }
 }
%    \end{macrocode}
%Expanding first reduces computations if the argument is an
%expression rather than just a number.
%    \begin{macrocode}
\cs_generate_variant:Nn \datatool_signed_two_digits:n { e, V }
%    \end{macrocode}
%Argument an integer variable:
%    \begin{macrocode}
\cs_new:Nn \datatool_signed_two_digits:N
 {
   \int_compare:nNnTF { #1 } < { \c_zero_int }
    {
      - \datatool_two_digits:e { \int_abs:n { #1 } }
    }
    {
      + \datatool_two_digits:N #1 
    }
 }
%    \end{macrocode}
%Similar but omit sign for positive numbers.
%Note that \cs{two@digits} can discard a following space since it
%uses \cs{number}. This is avoided here by using \cs{int\_eval:n}.
%    \begin{macrocode}
\cs_new:Nn \datatool_two_digits:n
 {
   \int_compare:nNnTF { #1 } < { \c_zero_int }
    {
      - \datatool_two_digits:e { \int_abs:n { #1 } }
    }
    {
      \int_compare:nNnT { #1 } < { 10 } { 0 }
      \int_eval:n { #1 }
    }
 }
\cs_generate_variant:Nn \datatool_two_digits:n { e, V }
%    \end{macrocode}
%Argument an integer variable:
%    \begin{macrocode}
\cs_new:Nn \datatool_two_digits:N
 {
   \int_compare:nNnTF { #1 } < { \c_zero_int }
    {
      - \datatool_two_digits:e { \int_abs:n { #1 } }
    }
    {
      \int_compare:nNnT { #1 } < { 10 } { 0 }
      \int_use:N #1 
    }
 }
%    \end{macrocode}
%
%\begin{macro}{\dtlpadleadingzeros}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{dtlpadleadingzeros}\marg{num-digits}\marg{value}
%\end{definition}
%Pad leading zeros. This is designed for sorting numbers by
%character code (and so needs to be expandable).
%Zero-padding helps place them in numeric order.
%The \meta{num-digits} argument should be between 1 and 6.
%    \begin{macrocode}
\newcommand{\dtlpadleadingzeros}[2]{
  \fp_compare:nNnTF { #2 } < { \c_zero_fp }
  { \dtlpadleadingzerosminus } { \dtlpadleadingzerosplus }
  \__datatool_int_leading_zeros:ee
    { \int_eval:n { #1 } }
    { \fp_to_int:n { floor ( abs ( #2 ) ) } }
  \fp_to_decimal:n { abs ( #2 ) }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlpadleadingzerosminus}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtlpadleadingzerosminus}{-}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlpadleadingzerosplus}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtlpadleadingzerosplus}{}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_int_leading_zeros:nn
{
 \bool_lazy_and:nnTF
    { \int_compare_p:nNn { #1 } > { \c_one_int } }
    { \int_compare_p:nNn { #2 } < { 10 } }
  { \prg_replicate:nn { #1 - \c_one_int } { 0 } }
  {
    \bool_lazy_and:nnTF
      { \int_compare_p:nNn { #1 } > { 2 } }
      { \int_compare_p:nNn { #2 } < { 100 } }
     { \prg_replicate:nn { #1 - 2 } { 0 } }
     {
       \bool_lazy_and:nnTF
         { \int_compare_p:nNn { #1 } > { 3 } }
         { \int_compare_p:nNn { #2 } < { 1000 } }
        { \prg_replicate:nn { #1 - 3 } { 0 } }
        {
          \bool_lazy_and:nnTF
            { \int_compare_p:nNn { #1 } > { 4 } }
            { \int_compare_p:nNn { #2 } < { 10000 } }
           { \prg_replicate:nn { #1 - 4 } { 0 } }
           {
             \bool_lazy_and:nnTF
               { \int_compare_p:nNn { #1 } > { 5 } }
               { \int_compare_p:nNn { #2 } < { 100000 } }
              { \prg_replicate:nn { #1 - 5 } { 0 } }
              {
                \bool_lazy_and:nnT
                  { \int_compare_p:nNn { #1 } > { 6 } }
                  { \int_compare_p:nNn { #2 } < { 1000000 } }
                 { \prg_replicate:nn { #1 - 6 } { 0 } }
              }
           }
        }
     }
  }
}
\cs_generate_variant:Nn \__datatool_int_leading_zeros:nn { en, ee }
%    \end{macrocode}
%
% Pad trailing zeros for a plain decimal number (stored in a token list
% variable):
%    \begin{macrocode}
\cs_new:Nn \datatool_pad_trailing_zeros:Nn
{
  \int_compare:nNnT { #2 } > { \c_zero_int }
  {
    \tl_if_in:NnTF #1 { . }
    {
      \exp_after:wN \__datatool_split_decimal:wNN #1 \q_stop
        \l__datatool_prefix_tl
        \l__datatool_suffix_tl
      \int_step_inline:nnn { \tl_count:N \l__datatool_suffix_tl + 1 } { #2 }
       {
         \tl_put_right:Nn #1 { 0 }
       }
    }
    {
      \tl_put_right:Nn #1 { . }
      \int_step_inline:nn { #2 }
       {
         \tl_put_right:Nn #1 { 0 }
       }
    }
  }
}
\cs_generate_variant:Nn \datatool_pad_trailing_zeros:Nn { cn }
%    \end{macrocode}
%    \begin{macrocode}
\cs_new:Npn \__datatool_split_decimal:wNN #1 . #2 \q_stop #3 #4
{
  \tl_set:Nn #3 { #1 }
  \tl_set:Nn #4 { #2 }
}
%    \end{macrocode}
%
%\subsubsection{General List Utilities}
%
%\begin{macro}{\@dtl@assigntmpseq}
%\changes{3.0}{2025-03-03}{new}
%Assign \cs{l\_\_datatool\_tmp\_seq} taking into account the trim
%spaces and skip empty elements settings. The argument may be a single token
%(a command whose definition is a comma-separated list), in which case it 
%will be expanded once, or the argument should be a comma-separated list.
%Any nested use will need to be scoped.
%    \begin{macrocode}
\newcommand{\@dtl@assigntmpseq}[1]{
  \tl_if_single_token:nTF { #1 }
  {
    \exp_args:No \__datatool_create_tmp_list:n { #1 }
  }
  {
    \__datatool_create_tmp_list:n { #1 }
  }
}
\cs_new:Nn \__datatool_create_tmp_list:n 
 {
   \bool_if:NTF \l__datatool_list_trim_bool
   {
     \seq_set_from_clist:Nn \l__datatool_tmp_seq { #1 }
   }
   {
     \seq_set_split_keep_spaces:Nnn \l__datatool_tmp_seq { , } { #1 }
   }
   \ifDTLlistskipempty
    \seq_remove_all:Nn \l__datatool_tmp_seq {}
   \fi
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifinlist}
%\begin{definition}
%\cs{DTLifinlist}\marg{element}\marg{list}\marg{true part}\marg{false part}
%\end{definition}
% If \meta{element} is contained in the comma-separated list given
% by \meta{list}, then do \meta{true part} otherwise do false
% part. The \meta{list} may be a command whose definition is a
% comma-separated list.
%Rewritten in v3.0 to use \LaTeX3. This is fractionally slower than
%the old definition but more reliable.
%\changes{3.0}{2025-03-03}{rewritten in \LaTeX3}
%    \begin{macrocode}
\newcommand*{\DTLifinlist}[4]{
 \@dtl@assigntmpseq{#2}
 \seq_if_in:NnTF \l__datatool_tmp_seq { #1 } { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLlistelement}
%\begin{definition}
%\cs{DTLlistelement}\marg{list}\marg{idx}
%\end{definition}
%Does the \meta{idx}th element in the list. The index
%should start from 1 for the first element.
%\changes{3.0}{2025-03-03}{rewritten in \LaTeX3}
%    \begin{macrocode}
\newrobustcmd{\DTLlistelement}[2]{%
 \@dtl@assigntmpseq{#1}
 \seq_item:Nn \l__datatool_tmp_seq { #2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLfetchlistelement}
%\begin{definition}
%\cs{DTLfetchlistelement}\marg{list}\marg{idx}\marg{cs}
%\end{definition}
%Fetches the \meta{idx}th element in the list and stores in \meta{cs}. The index
%should start from 1 for the first element.
%\changes{3.0}{2025-03-03}{rewritten in \LaTeX3}
%    \begin{macrocode}
\newrobustcmd{\DTLfetchlistelement}[3]{%
 \@dtl@assigntmpseq{#1}
 \tl_set:Nx #3 { \seq_item:Nn \l__datatool_tmp_seq { #2 } }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLnumitemsinlist}
%\begin{definition}
%\cs{DTLnumitemsinlist}\marg{list}\marg{cmd}
%\end{definition}
% Counts number of elements in list and stores result in control
% sequence \meta{cmd}.
%\changes{2.31}{2018-12-07}{made robust}
%\changes{3.0}{2025-03-03}{rewritten in \LaTeX3}
%    \begin{macrocode}
\newrobustcmd{\DTLnumitemsinlist}[2]{%
  \@dtl@assigntmpseq{#1}
  \tl_set:Nx #2 { \seq_count:N \l__datatool_tmp_seq }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLDefaultLocaleWordHandler}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLDefaultLocaleWordHandler}[1]{%
  \DTLCurrentLocaleWordHandler{#1}%
  \appto#1{\datatoolctrlboundary}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLCurrentLocaleWordHandler}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLCurrentLocaleWordHandler}[1]{}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%The original sorting algorithm required a comparison command. This
%isn't a problem for a simple case-sensitive character code sort,
%but it's less efficient if the sort value needs to be obtained by
%processing the original value. If this is done by the comparison
%command, then it has to be done every time a comparison is made
%(for both values). It's more efficient to process the sort values
%first and then sort, but this means keeping track of the original
%value.
%
%Provide a convenient way of locally redefining commands while
%obtaining the sort value.
%\begin{macro}{\dtlSortWordCommands}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\dtlSortWordCommands}{%
 \begingroup\makeatletter
 \dtl@SortWordCommands
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@SortWordCommands}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtl@SortWordCommands}[1]{%
  \gappto\dtl@SortWordCommands@hook{#1}%
  \endgroup
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datatoolasciistart}
%\changes{3.0}{2025-03-03}{new}
%Does nothing but influences sorting.
%    \begin{macrocode}
\newcommand{\datatoolasciistart}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datatoolasciiend}
%\changes{3.0}{2025-03-03}{new}
%Does nothing but influences sorting.
%    \begin{macrocode}
\newcommand{\datatoolasciiend}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datatoolctrlboundary}
%\changes{3.0}{2025-03-03}{new}
%Does nothing but influences sorting.
%    \begin{macrocode}
\newcommand{\datatoolctrlboundary}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtltexorsort}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtltexorsort}[2]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@SortWordCommands@hook}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\begingroup
\ExplSyntaxOn
\char_set_catcode_other:n {0}
\char_set_catcode_other:n {28}
\char_set_catcode_other:n {29}
\char_set_catcode_other:n {30}
\char_set_catcode_other:n {31}
\char_set_catcode_other:n {127}
\ExplSyntaxOff
\gdef\dtl@SortWordCommands@hook{%
  \def\datatoolasciistart{^^@}%
  \def\datatoolpersoncomma{^^1c}%
  \def\datatoolplacecomma{^^1d}%
  \def\datatoolsubjectcomma{^^1e}%
  \def\datatoolparenstart{^^1f}%
  \def\datatoolctrlboundary{^^1f}%
  \def\datatoolparen{\datatoolctrlboundary\@gobble}%
  \def\datatoolasciiend{^^7f}%
  \let\nobreakspace\space
  \let\ \space
  \edef\${\expandafter\@gobble\string\$}%
  \edef\_{\expandafter\@gobble\string\_}%
  \edef\#{\expandafter\@gobble\string\#}%
  \edef\%{\expandafter\@gobble\string\%}%
  \edef\&{\expandafter\@gobble\string\&}%
  \let\dtltexorsort\@secondoftwo
  \def\TeX{TeX}%
  \def\LaTeX{LaTeX}%
  \datatoolSetCurrencySort
}
\endgroup
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%Apply basic sort pre-processing (no locale):
%    \begin{macrocode}
\cs_new:Nn \datatool_sort_preprocess:Nn
 {
   \datatool_sort_preprocess:NnN #1 { #2 }
    \l_datatool_sort_to_lowercase_bool
 }
%    \end{macrocode}
%Version 3.2: added variant that takes boolean final argument to
%determine whether or not to convert to lower case.
%    \begin{macrocode}
\cs_new:Nn \datatool_sort_preprocess:NnN
 {
   \group_begin:
    \dtl@SortWordCommands@hook
    \bool_if:NTF #3
     {
       \datatool_sort_handler_preprocess:Nn #1 { \text_lowercase:n { #2 } }
     }
     {
       \datatool_sort_handler_preprocess:Nn #1 { #2 }
     }
    \exp_args:NNNe
   \group_end:
    \tl_set:Nn #1 { \exp_args:NV \text_purify:n #1 }
 }
%    \end{macrocode}
%
%\begin{macro}{\DTLsortwordlist}
%\begin{definition}
%\cs{DTLsortwordlist}\marg{clist-var}\marg{handler-cs}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%The first argument is a macro containing the list.
%The second argument is a handler macro for converting the original value
%into a byte sequence, which needs to have the form \cs{cs}
%\marg{original}\marg{tl}, where \meta{original} is the
%original value and \meta{tl} is a token list in which to
%store the byte sequence.
%    \begin{macrocode}
\seq_new:N \l__datatool_wordlist_seq
\tl_new:N \l__datatool_word_tl
\NewDocumentCommand \DTLsortwordlist { m m }
{
  \datatool_sortwordlist:NNN #1 #2 \__datatool_append_sorteditem:w 
}
%    \end{macrocode}
%\end{macro}
%Syntax: \meta{clist-var} \meta{handler-cs} \meta{append-cs}
%
%The \meta{append-cs} function is used after sorting to append
% the sort markup to a token list register with each item in the form 
%\cs{\_\_datatool\_sorted:nnn} \marg{actual} \marg{sort value}
%\marg{letter group}. The \meta{append-cs} function should have the 
% syntax \meta{sort value}\cs{q\_mark}\meta{actual}\cs{q\_stop} \meta{tl var}
%    \begin{macrocode}
\cs_new:Nn \datatool_sortwordlist:NNN 
 {
  \__datatool_sortword_list:NN #1 #2 
  \__datatool_finish_sortword_list:NN #1 #3
 }
%    \end{macrocode}
%Syntax: \meta{clist-var} \meta{handler-cs}
%    \begin{macrocode}
\cs_new:Nn \__datatool_sortword_list:NN 
{
%    \end{macrocode}
%Scope to localise the effect of the hook.
%    \begin{macrocode}
  \group_begin:
  \dtl@SortWordCommands@hook
  \seq_clear:N \l__datatool_wordlist_seq
%    \end{macrocode}
%Convert clist to temporary sequence variable, according to the
%current settings.
%    \begin{macrocode}
  \exp_args:No \__datatool_create_tmp_list:n { #1 }
  \__datatool_sortword_seq:NN \l__datatool_tmp_seq #2
  \exp_args:No
   \__datatool_start_sortword_list:n
     { \l__datatool_wordlist_seq }
}
%    \end{macrocode}
%
%Alternative function if list is already in a sequence.
%Syntax: \meta{seq-var} \meta{handler-cs}
%    \begin{macrocode}
\cs_new:Nn \datatool_sortwordseq:NN 
 {
   \datatool_sortwordseq:NNN #1 #2 \__datatool_append_sorted_seq_item:w
 }
%    \end{macrocode}
%Syntax: \meta{seq-var} \meta{handler-cs} \meta{append-cs}
%    \begin{macrocode}
\cs_new:Nn \datatool_sortwordseq:NNN 
 {
  \__datatool_sortword_seqlist:NN #1 #2 
  \__datatool_finish_sortword_seq:NN #1 #3
 }
%    \end{macrocode}
%Syntax: \meta{seq-var} \meta{handler-cs}
%    \begin{macrocode}
\cs_new:Nn \__datatool_sortword_seqlist:NN 
{
%    \end{macrocode}
%Scope to localise the effect of the hook.
%    \begin{macrocode}
  \group_begin:
  \dtl@SortWordCommands@hook
  \seq_clear:N \l__datatool_wordlist_seq
  \__datatool_sortword_seq:NN #1 #2
  \exp_args:No
   \__datatool_start_sortword_list:n
     { \l__datatool_wordlist_seq }
}
%    \end{macrocode}
%
%Syntax: \meta{seq-var} \meta{handler-cs}
%Inner workings.
%    \begin{macrocode}
\cs_new:Nn \__datatool_sortword_seq:NN 
{
  \seq_map_inline:Nn #1
   { 
     \bool_if:NTF \l__datatool_sort_datum_bool
     {
       \DTLparse \l__datatool_tmpa_tl { ##1 } 
       \tl_set:No \l__datatool_word_tl { ##1 }
       #2 { ##1 } { \l__datatool_word_tl }
       \exp_args:Noo \__datatool_sortword_append:nn
        { \l__datatool_word_tl }
        { \l__datatool_tmpa_tl }
     }
     {
       \tl_set:Nn \l__datatool_word_tl { ##1 }
       #2 { ##1 } { \l__datatool_word_tl }
       \exp_args:No \__datatool_sortword_append:nn
        { \l__datatool_word_tl } { ##1 }
     }
   }
}
%    \end{macrocode}
%End group to cancel effect of sort hook. This allows the original
%definitions to be used by the fallback function.
%    \begin{macrocode}
\cs_new:Nn \__datatool_start_sortword_list:n 
{
   \group_end:
   \tl_set:Nn \l__datatool_wordlist_seq { #1 }
   \seq_sort:Nn \l__datatool_wordlist_seq
   {
     \__datatool_compare_sortitem:w ##1 ##2
   }
}
%    \end{macrocode}
%Add sort element to sequence.
%    \begin{macrocode}
\cs_new:Nn \__datatool_sortword_append:nn
 {
    \seq_put_right:Nn \l__datatool_wordlist_seq
     { #1 \q_mark #2 \q_stop }
 }
%    \end{macrocode}
%Comparator used by \cs{DTLsortwordlist}
%\begin{definition}
%\cs{\_\_datatool\_compare\_sortitem:w}
%\meta{sort1}\cs{q\_mark} \meta{actual1}\cs{q\_stop}
%\meta{sort2}\cs{q\_mark} \meta{actual2}\cs{q\_stop}
%\end{definition}
%    \begin{macrocode}
\cs_new:Npn \__datatool_compare_sortitem:w
 #1\q_mark#2\q_stop#3\q_mark#4\q_stop
 {
   \__datatool_compare_sortitem:nnnn
    { #1 } { #2 } { #3 } { #4 }
   \int_compare:nNnTF
       { \dtl@sortresult } < { \c_zero_int }
    {
      \sort_return_same:
    }
    {
      \int_compare:nNnTF
         { \dtl@sortresult } > { \c_zero_int }
       {
         \sort_return_swapped:
       }
       {
         \bool_if:NTF \l__datatool_sort_reverse_bool
          {
            \__datatool_fallback_action:nnnn { #4 } { #2 }
             { \sort_return_swapped: }
             { \sort_return_same: }
          }
          {
            \__datatool_fallback_action:nnnn { #2 } { #4 }
             { \sort_return_swapped: }
             { \sort_return_same: }
          }
       }
    }
 }
%    \end{macrocode}
%Syntax: \marg{sort A}\marg{actual A}\marg{sort B}\marg{actual B}
%If the actual values are in the datum format, a numeric comparison
%can be used if the datum format indicates the original value was
%identified as numeric.
%    \begin{macrocode}
\cs_new:Nn \__datatool_compare_sortitem:nnnn
{
  \tl_clear:N \l__datatool_tmpc_tl
  \tl_clear:N \l__datatool_tmpd_tl
  \int_set_eq:NN \@dtl@datatype \c_datatool_string_int
  \tl_if_head_eq_meaning:nNTF { #2 }  \__datatool_datum:nnnn 
   {
     \__datatool_get_datum:w #2 \q_nil \q_stop
     \int_set_eq:NN
       \l__datatool_tmp_datatype_int
       \@dtl@datatype
     \tl_set:No \l__datatool_tmpc_tl
       { \datatool_datum_currency:Nnnnn #2 }
   }
   {
     \datatool_if_null:nT { #2 } 
      {
        \int_set_eq:NN
          \l__datatool_tmp_datatype_int
          \c_datatool_unknown_int
      }
   }
  \tl_if_head_eq_meaning:nNTF { #4 }  \__datatool_datum:nnnn 
   {
     \__datatool_get_datum:w #4 \q_nil \q_stop
     \tl_set:No \l__datatool_tmpd_tl
       { \datatool_datum_currency:Nnnnn #4 }
   }
   {
     \datatool_if_null:nT { #4 } 
      {
        \int_set_eq:NN
          \@dtl@datatype
          \c_datatool_unknown_int
      }
   }
%    \end{macrocode}
%If they have different currency symbols, treat them as strings.
%    \begin{macrocode}
  \tl_if_eq:NNF \l__datatool_tmpc_tl \l__datatool_tmpd_tl
   {
     \int_set_eq:NN
       \l__datatool_tmp_datatype_int \c_datatool_string_int
     \int_set_eq:NN
       \@dtl@datatype \c_datatool_string_int
   }
  \int_compare:nNnTF
    { \l__datatool_tmp_datatype_int }
      =
    { \c_datatool_unknown_int }
   {
%    \end{macrocode}
%The first value has an unknown type.
%    \begin{macrocode}
     \int_compare:nNnTF
      { \@dtl@datatype } > { \c_datatool_string_int }
     {
%    \end{macrocode}
%The first value has an unknown type. The second is numeric,
%so treat the first as zero.
%    \begin{macrocode}
       \__datatool_numcmp:Nnn \dtl@sortresult
        { 0 }
        { \datatool_datum_value:Nnnnn #4 }
     }
     {
%    \end{macrocode}
%The first value has an unknown type. If the second is not numeric
%so use a string comparison.
%    \begin{macrocode}
       \__datatool_strcmp:Nnn \dtl@sortresult { #1 } { #3 }
     }
   }
   {
%    \end{macrocode}
%The first value has a known type.
%    \begin{macrocode}
     \int_case:nnF { \@dtl@datatype }
     {
       { \c_datatool_unknown_int }
         {
%    \end{macrocode}
%The first value is known. The second is unknown.
%    \begin{macrocode}
           \int_compare:nNnTF
              { \l__datatool_tmp_datatype_int }
                >
              { \c_datatool_string_int }
            {
%    \end{macrocode}
%The first value is numeric. The second is unknown so treat as 0.
%    \begin{macrocode}
              \__datatool_numcmp:Nnn \dtl@sortresult
               { \datatool_datum_value:Nnnnn #2 }
               { 0 }
            }
            {
%    \end{macrocode}
%The first value is not numeric. The second is unknown so use a
%string comparison.
%    \begin{macrocode}
              \__datatool_strcmp:Nnn \dtl@sortresult { #1 } { #3 }
            }
         }
       { \c_datatool_string_int }
         {
%    \end{macrocode}
%The first value is known. The second is a string. Use a string
%comparison
%    \begin{macrocode}
            \__datatool_strcmp:Nnn \dtl@sortresult { #1 } { #3 }
         }
     }
     {
%    \end{macrocode}
%The first value is known. The second is numeric.
%    \begin{macrocode}
       \int_compare:nNnTF
          { \l__datatool_tmp_datatype_int }
           >
          { \c_datatool_string_int }
        {
%    \end{macrocode}
%Both values are numeric.
%    \begin{macrocode}
          \__datatool_numcmp:Nnn \dtl@sortresult
           { \datatool_datum_value:Nnnnn #2 }
           { \datatool_datum_value:Nnnnn #4 }
        }
        {
%    \end{macrocode}
%The first value is a string. The second is numeric. Use a string
%comparison.
%    \begin{macrocode}
          \__datatool_strcmp:Nnn \dtl@sortresult { #1 } { #3 }
        }
     }
   }
  \bool_if:NT \l__datatool_sort_reverse_bool
   {
     \int_compare:nNnTF 
       { \dtl@sortresult } < { \c_zero_int }
      {
        \int_set:Nn \dtl@sortresult { \c_one_int }
      }
      {
        \int_set:Nn \dtl@sortresult { - \dtl@sortresult }
      }
   }
}
%    \end{macrocode}
%
%Finish off after sorting. 
%The first argument is the clist in which to store the sorted
%values. The second argument is the function that extracts the
%required information and appends it to the clist.
%    \begin{macrocode}
\cs_new:Nn \__datatool_finish_sortword_list:NN 
 {
   \clist_clear:N #1
   \seq_map_inline:Nn \l__datatool_wordlist_seq
   {
%    \end{macrocode}
%The argument \verb|##1| should be in the form
%\meta{sort}\cs{q\_mark} \meta{actual}\cs{q\_stop}\marg{tl}
%    \begin{macrocode}
     #2 ##1 #1
   }
 }
%    \end{macrocode}
%
%Similarly, but for a sequence variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_finish_sortword_seq:NN 
 {
   \seq_clear:N #1
   \seq_map_inline:Nn \l__datatool_wordlist_seq
   {
%    \end{macrocode}
%The argument \verb|##1| should be in the form
%\meta{sort}\cs{q\_mark} \meta{actual}\cs{q\_stop}\marg{tl}
%    \begin{macrocode}
     #2 ##1 #1
   }
 }
%    \end{macrocode}
%
%This is similar to the datum marker but allows the sort value and
%initial letter (which may be required for letter groups) to be
%available. The three arguments are: original item, sort string,
%letter group.
%    \begin{macrocode}
\cs_new:Nn \__datatool_sorted:nnn { \exp_not:n { #1 }}
%    \end{macrocode}
%\begin{definition}
%
%Unlike the datum markers, these are more likely to be passed
%directly (for example, in the argument of \cs{do}) so the \meta{sort
%marker} is expected to be in the form 
%\cs{\_\_datatool\_sorted:nnn}\marg{actual}\marg{sort}\marg{letter}.
%If using \cs{@for} to iterate over the list then the loop control
%sequence will need to be expanded first.
%\begin{macro}{\DTLsortedactual}
%\begin{definition}
%\cs{DTLsortedactual}\marg{sort marker}
%\end{definition}
%The original item.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLsortedactual} [1] { \use_ii:nnnn #1 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsortedvalue}
%\begin{definition}
%\cs{DTLsortedvalue}\marg{sort marker}
%\end{definition}
%The sort value.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLsortedvalue} [1] { \use_iii:nnnn #1 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsortedletter}
%\begin{definition}
%\cs{DTLsortedletter}\marg{sort marker}
%\end{definition}
%The letter group.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLsortedletter} [1] { \use_iv:nnnn #1 }
%    \end{macrocode}
%\end{macro}
%
%\cs{\_\_datatool\_append\_sorteditem:w}
%\meta{sort}\cs{q\_mark} \meta{actual}\cs{q\_stop} \meta{clist\_var}
%\end{definition}
%    \begin{macrocode}
\cs_new:Npn \__datatool_append_sorteditem:w
  #1 \q_mark #2 \q_stop #3
{
  \tl_clear:N \l__datatool_tmpa_tl
  \DTLassignlettergroup { #2 } { #1 } { \l__datatool_tmpa_tl }
  \clist_put_right:Nx #3
    { \exp_not:N \__datatool_sorted:nnn
        { \exp_not:n { #2 } } { \exp_not:n { #1 } }
        { \exp_not:o { \l__datatool_tmpa_tl } }
    }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Npn \__datatool_append_sorted_seq_item:w
  #1 \q_mark #2 \q_stop #3
{
  \tl_clear:N \l__datatool_tmpa_tl
  \DTLassignlettergroup { #2 } { #1 } { \l__datatool_tmpa_tl }
  \seq_put_right:Nx #3
    { \exp_not:N \__datatool_sorted:nnn
        { \exp_not:n { #2 } } { \exp_not:n { #1 } }
        { \exp_not:o { \l__datatool_tmpa_tl } }
    }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLassignlettergroup}
%\begin{definition}
%\cs{DTLassignlettergroup}\marg{actual}\marg{sort value}\marg{tl}
%\end{definition}
%Obtains the letter group from the sort value.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLassignlettergroup} [3] 
 {
   \@dtl@datatype = \c_datatool_string_int
   \tl_if_head_eq_meaning:nNT { #1 } \__datatool_datum:nnnn 
    {
      \__datatool_get_datum:w #1 \q_nil \q_stop
    }
   \int_case:nn { \@dtl@datatype }
   {
     { \c_datatool_string_int }
      {
       \DTLCurrentLocaleGetGroupString { #1 } { #2 } #3
       \exp_args:NV \datatool_get_first_grapheme:nN #3 #3 
       \exp_args:NV \datatool_if_letter:nTF #3 
        { 
           \exp_args:NV \DTLCurrentLocaleGetInitialLetter #3 { #3 }
           \DTLPreProcessLetterGroup { #3 }
           \tl_set:Nx #3 { \exp_not:N \dtllettergroup { #3 } }
        }
        {
          \DTLPreProcessNonLetterGroup { #3 }
          \tl_set:Nx #3 { \exp_not:N \dtlnonlettergroup { #3 } }
        }
      }
     { \c_datatool_integer_int }
      {
        \DTLPreProcessIntegerGroup
          { \l__datatool_datum_value_tl } { #1 }
        \tl_set:Nx #3
         { \exp_not:N \dtlnumbergroup { \l__datatool_datum_value_tl } }
      }
     { \c_datatool_decimal_int }
      {
        \DTLPreProcessDecimalGroup
          { \l__datatool_datum_value_tl } { #1 }
        \tl_set:Nx #3
         { \exp_not:N \dtlnumbergroup { \l__datatool_datum_value_tl } }
      }
     { \c_datatool_currency_int }
      {
        \DTLPreProcessCurrencyGroup
          { \l__datatool_datum_currency_tl }
          { \l__datatool_datum_value_tl }
          { #1 }
        \tl_set:Nx #3
         {
           \exp_not:N \dtlcurrencygroup
             { \exp_not:o { \l__datatool_datum_currency_tl } }
             { \l__datatool_datum_value_tl } 
         }
      }
     { \c_datatool_datetime_int }
      {
        \DTLPreProcessDateTimeGroup
          { \l__datatool_datum_value_tl } { #1 }
        \tl_set:Nx #3
         { \exp_not:N \dtldatetimegroup { \l__datatool_datum_value_tl } }
      }
     { \c_datatool_date_int }
      {
        \DTLPreProcessDateGroup
          { \l__datatool_datum_value_tl } { #1 }
        \tl_set:Nx #3
         { \exp_not:N \dtldategroup { \l__datatool_datum_value_tl } }
      }
     { \c_datatool_time_int }
      {
        \DTLPreProcessTimeGroup
          { \l__datatool_datum_value_tl } { #1 }
        \tl_set:Nx #3
         { \exp_not:N \dtltimegroup { \l__datatool_datum_value_tl } }
      }
   }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLCurrentLocaleGetGroupString}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLCurrentLocaleGetGroupString}\marg{actual}\marg{sort value}\marg{tl}
%\end{definition}
%May consist of more than one character.
%This determines whether to use the sort or actual value to obtain
%the letter group. The returned value may be truncated as only the
%initial part is of interest.
%    \begin{macrocode}
\newcommand{\DTLCurrentLocaleGetGroupString}[3]
{
  \tl_set:Nn #3 { #1 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLPreProcessLetterGroup}
%\changes{3.0}{2025-03-03}{new}
% Hook to pre-process letter group. The argument is a token list
% variable containing the current value.
%    \begin{macrocode}
\newcommand{\DTLPreProcessLetterGroup}[1]{}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLPreProcessNonLetterGroup}
%\changes{3.0}{2025-03-03}{new}
% Hook to pre-process non-letter group. The argument is a token list
% variable containing the current value.
%    \begin{macrocode}
\newcommand{\DTLPreProcessNonLetterGroup}[1]{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLPreProcessIntegerGroup}
%\changes{3.0}{2025-03-03}{new}
% Hook to pre-process integer number group. The first argument is a token list
% variable containing the current value. The second argument is the
% actual value. The hook may adjust the token list variable.
%    \begin{macrocode}
\newcommand{\DTLPreProcessIntegerGroup}[2]{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLPreProcessDecimalGroup}
%\changes{3.0}{2025-03-03}{new}
% Hook to pre-process decimal number group. The first argument is a token list
% variable containing the current value. The second argument is the
% actual value. The hook may adjust the token list variable.
%    \begin{macrocode}
\newcommand{\DTLPreProcessDecimalGroup}[2]{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLPreProcessCurrencyGroup}
%\begin{definition}
%\cs{DTLPreProcessCurrencyGroup}\marg{sym tl var}\marg{num tl var}\marg{actual}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
% Hook to pre-process currency number group. The \meta{num tl var} is a token list
% variable containing the current numeric value. The \meta{sym tl var} is a token list
% variable containing the currency symbol.
% The \meta{actual} argument is the
% actual value. The hook may adjust the token list variable.
%    \begin{macrocode}
\newcommand{\DTLPreProcessCurrencyGroup}[3]{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLPreProcessDateTimeGroup}
%\changes{3.0}{2025-03-03}{new}
% Hook to pre-process datetime group. The first argument is a token list
% variable containing the current value. The second argument is the
% actual value. The hook may adjust the token list variable.
%    \begin{macrocode}
\newcommand{\DTLPreProcessDateTimeGroup}[2]{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLPreProcessDateGroup}
%\changes{3.0}{2025-03-03}{new}
% Hook to pre-process date group. The first argument is a token list
% variable containing the current value. The second argument is the
% actual value. The hook may adjust the token list variable.
%    \begin{macrocode}
\newcommand{\DTLPreProcessDateGroup}[2]{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLPreProcessTimeGroup}
%\changes{3.0}{2025-03-03}{new}
% Hook to pre-process time group. The first argument is a token list
% variable containing the current value. The second argument is the
% actual value. The hook may adjust the token list variable.
%    \begin{macrocode}
\newcommand{\DTLPreProcessTimeGroup}[2]{}
%    \end{macrocode}
%\end{macro}
%
%
%\begin{macro}{\dtllettergroup}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtllettergroup}[1]{ \text_titlecase_first:n { #1 } }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlnonlettergroup}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtlnonlettergroup}[1]{\detokenize{#1}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlnumbergroup}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtlnumbergroup}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlcurrencygroup}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtlcurrencygroup}[2]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldatetimegroup}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtldatetimegroup}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldategroup}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtldategroup}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtltimegroup}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtltimegroup}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlfallbackaction}
%\begin{definition}
%\cs{dtlfallbackaction}\marg{val1}\marg{val2}\marg{swap code}\marg{no swap code}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%Used when two sort values are identical. This uses the original
%values for comparison.
%    \begin{macrocode}
\newcommand{\dtlfallbackaction}[4]{
  \DTLifstringgt { #1 } { #2 } { #3 } { #4 }
}
\cs_new:Nn \__datatool_fallback_action:nnnn
{
  \dtlfallbackaction { #1 } { #2 } { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%
%Word sort doesn't discard spaces or punctuation. So the first word
%of a compound word or phrase will come before a longer word that
%happens to have the other word at the start. This means that,
%for example, ``sea lion'' will come before ``seal'' because the
%space character comes before the character ``l''. This isn't
%exactly the same as a character sort as the localisation handler
%may convert the token list to series of bytes that alters the
%ordering.
%
%Letter sort is only really concerned with alphanumerics (letters
%and digits). Spaces and punctuation aren't considered relevant to
%order. Commands will be expanded first, then stripped after the
%locale handler has been applied. This will ensure that any accent
%commands, such as \verb|\'e|, will be converted to UTF-8 before
%the locale handler is applied.
%
%    \begin{macrocode}
\cs_new:Nn \datatool_sort_handler_preprocess:Nn
{
  \protected@edef #1 { #2 }
}
%    \end{macrocode}
%
%Handlers should set this to true if the sort value is converted to
%lowercase and false otherwise.
%    \begin{macrocode}
\bool_new:N \l_datatool_sort_to_lowercase_bool
\bool_set_true:N \l_datatool_sort_to_lowercase_bool
%    \end{macrocode}
%
%\begin{macro}{\DTLsortwordhandler}
%\begin{definition}
%\cs{DTLsortwordhandler}\marg{original}\marg{cs}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%\changes{3.1}{2025-03-03}{changed \cs{tl\_set:Nx} to \cs{tl\_set:Ne}}
%\changes{3.1}{2025-03-03}{moved case-change}
%    \begin{macrocode}
\newcommand{\DTLsortwordhandler}[2]{
  \bool_set_true:N \l_datatool_sort_to_lowercase_bool
  \datatool_sort_handler_preprocess:Nn 
     #2 { #1 }
  \DTLDefaultLocaleWordHandler { #2 }
%    \end{macrocode}
%v3.1: Case change has been moved from pre-process to here:
%    \begin{macrocode}
  \tl_set:Ne #2 { \text_lowercase:n { \text_purify:n { #2 } } }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsortwordcasehandler}
%\begin{definition}
%\cs{DTLsortwordcasehandler}\marg{original}\marg{cs}
%\end{definition}
%Case-sensitive handler.
%\changes{3.0}{2025-03-03}{new}
%\changes{3.1}{2025-03-03}{changed \cs{tl\_set:Nx} to \cs{tl\_set:Ne}}
%    \begin{macrocode}
\newcommand{\DTLsortwordcasehandler}[2]{
  \bool_set_false:N \l_datatool_sort_to_lowercase_bool
  \datatool_sort_handler_preprocess:Nn 
    #2 { #1 }
  \DTLDefaultLocaleWordHandler { #2 }
  \tl_set:Ne #2 { \text_purify:n { #2 } }
}
%    \end{macrocode}
%\end{macro}
%
%Regular expression for content that should be stripped for letter
%sort.
%    \begin{macrocode}
\regex_new:N \l_datatool_letter_regex
\regex_set:Nn \l_datatool_letter_regex { [\-[:space:]] }
%    \end{macrocode}
%
%\begin{macro}{\DTLsortletterhandler}
%\begin{definition}
%\cs{DTLsortletterhandler}\marg{original}\marg{cs}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%\changes{3.1}{2025-03-03}{changed \cs{tl\_set:Nx} to \cs{tl\_set:Ne}}
%\changes{3.1}{2025-03-03}{moved case-change}
%Hyphens and spaces are discarded from the sort value.
%    \begin{macrocode}
\newcommand{\DTLsortletterhandler}[2]{
  \bool_set_true:N \l_datatool_sort_to_lowercase_bool
  \datatool_sort_handler_preprocess:Nn 
    #2 { #1 }
  \regex_replace_all:NnN \l_datatool_letter_regex {} #2 
  \DTLDefaultLocaleWordHandler { #2 }
%    \end{macrocode}
%v3.1: Case change has been moved from pre-process to here:
%    \begin{macrocode}
  \tl_set:Ne #2 { \text_lowercase:n { \text_purify:n { #2 } } }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsortlettercasehandler}
%\begin{definition}
%\cs{DTLsortlettercasehandler}\marg{original}\marg{cs}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%\changes{3.1}{2025-03-03}{changed \cs{tl\_set:Nx} to \cs{tl\_set:Ne}}
%Case-sensitive handler.
%    \begin{macrocode}
\newcommand{\DTLsortlettercasehandler}[2]{
  \bool_set_false:N \l_datatool_sort_to_lowercase_bool
  \datatool_sort_handler_preprocess:Nn 
    #2 { #1 }
  \regex_replace_all:NnN \l_datatool_letter_regex {} #2 
  \DTLDefaultLocaleWordHandler { #2 }
  \tl_set:Ne #2 { \text_purify:n { #2 } }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsortlist}
%\changes{2.27}{2016-07-28}{new}
%\begin{definition}
% \cs{dtlsortlist}\marg{list-cs}\marg{criteria cmd}
%\end{definition}
% Sorts the given comma-separated list according to the
% \meta{criteria} command, which must take
% three arguments.
%    \begin{macrocode}
\newcommand{\dtlsortlist}[2]{%
  \exp_args:No \__datatool_create_tmp_list:n { #1 }
  \seq_set_eq:NN \l__datatool_wordlist_seq \l__datatool_tmp_seq
  \seq_sort:Nn \l__datatool_wordlist_seq
  {
    \bool_if:NTF \l__datatool_sort_reverse_bool
    {
      #2 { \dtl@sortresult } { ##2 } { ##1 }
    }
    {
      #2 { \dtl@sortresult } { ##1 } { ##2 }
    }
    \ifnum\dtl@sortresult > 0\relax 
      \sort_return_swapped: 
    \else
      \sort_return_same: 
    \fi
  }
  \tl_clear:N #1
  \seq_map_inline:Nn \l__datatool_wordlist_seq
  {
    \tl_if_empty:NF #1
     {
       \tl_put_right:Nn #1 { , }
     }
    \tl_if_in:nnTF { ##1 } { , }
    {
      \tl_put_right:Nn #1 { { ##1 } }
    }
    {
      \tl_put_right:Nn #1 { ##1 }
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlinsertinto}
%\changes{2.27}{2016-07-28}{new}
%\changes{3.0}{2025-03-03}{made robust}
%\begin{definition}
% \cs{dtlinsertinto}\marg{element}\marg{sorted-list}\marg{criteria cmd}
%\end{definition}
% Globally inserts \meta{element} into the sorted list \meta{sorted-list}
% according to the criteria given by \meta{criteria cmd}, which
% should be a command that takes three arguments
% \marg{reg}\marg{A}\marg{B}, where \meta{reg} is a count register
% in which to store the result, \meta{A} is the first element and
% \meta{B} is the second element to compare.
%    \begin{macrocode}
\newrobustcmd{\dtlinsertinto}[3]{%
  \exp_args:No \__datatool_create_tmp_list:n { #2 }
  \seq_set_eq:NN \l__datatool_wordlist_seq \l__datatool_tmp_seq
  \tl_gclear:N #2
  \@dtl@insertdonefalse
  \seq_map_inline:Nn \l__datatool_wordlist_seq
  {
    \tl_if_empty:NF #2
     {
       \tl_gput_right:Nn #2 { , }
     }
    \if@dtl@insertdone
    \else
      #3 { \dtl@sortresult } { ##1 } { #1 }
      \ifnum\dtl@sortresult > 0\relax 
        \@dtl@insertdonetrue
        \tl_if_in:nnTF { #1 } { , }
        {
          \tl_gput_right:Nn #2 { { #1 } , }
        }
        {
          \tl_gput_right:Nn #2 { #1 , }
        }
      \fi
    \fi
    \tl_if_in:nnTF { ##1 } { , }
    {
      \tl_gput_right:Nn #2 { { ##1 } }
    }
    {
      \tl_gput_right:Nn #2 { ##1 }
    }
  }
  \if@dtl@insertdone
  \else
    \tl_if_empty:NF #2
     {
       \tl_gput_right:Nn #2 { , }
     }
    \tl_if_in:nnTF { #1 } { , }
     {
       \tl_gput_right:Nn #2 { { #1 } }
     }
     {
       \tl_gput_right:Nn #2 { #1 }
     }
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\edtlinsertinto}
%\changes{2.27}{2016-07-28}{new}
%\begin{definition}
% \cs{edtlinsertinto}\marg{element}\marg{sorted-list}\marg{criteria cmd}
%\end{definition}
%First expands \meta{element} before inserting into the list.
%    \begin{macrocode}
\newcommand*{\edtlinsertinto}[3]{%
  \exp_args:Nx \dtlinsertinto {#1} {#2} {#3}%
}
\ExplSyntaxOff
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\if@dtl@insertdone}
% Define conditional to indicate whether the new entry has
% been inserted into the sorted list.
%    \begin{macrocode}
\newif\if@dtl@insertdone
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@sortresult}
% Define \cs{dtl@sortresult} to be set by comparison macro.
% TODO: replace with L3 int var? (NB used by glossaries.sty)
%    \begin{macrocode}
\newcount\dtl@sortresult
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%\begin{macro}{\DTLshufflelist}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLshufflelist}{\meta{clist-var}}
%\end{definition}
%Shuffle a comma-separated list. This just converts the list to 
%a sequence and shuffles that using \verb|seq_shuffle:N| and then
%converts that back to a comma-separated list.
%    \begin{macrocode}
\NewDocumentCommand \DTLshufflelist { m }
{
%    \end{macrocode}
%Convert clist to temporary sequence variable, according to the
%current settings.
%    \begin{macrocode}
  \group_begin:
  \exp_args:No \__datatool_create_tmp_list:n { #1 }
  \seq_shuffle:N \l__datatool_tmp_seq
  \clist_clear:N \l__datatool_tmp_clist
  \seq_map_inline:Nn \l__datatool_tmp_seq
   {
     \clist_put_right:Nn \l__datatool_tmp_clist { ##1 }
   }
  \exp_args:NNNV
  \group_end:
  \tl_set:Nn #1 \l__datatool_tmp_clist
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%This next command is based on the list iteration exercise
%given at
%\url{http://www.dickimaw-books.com/latex/admin/html/foreachtips.shtml#oxfordcomma}
%It's designed to format a comma-separated list using
%\cs{DTLlistformatsep}
%between each item except for the last. The separator for the
%last pair is \cs{DTLlistformatlastsep} if the list only contains two
%items or \cs{DTLlistformatoxford}\cs{DTLlistformatlastsep} if the list contains three or
%more items. Each item is formatted according to
%\cs{DTLlistformatitem}.
%\begin{macro}{\DTLlistformatsep}
%\changes{2.28}{2017-11-10}{new}
%    \begin{macrocode}
\newcommand*{\DTLlistformatsep}{, }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLlistformatoxford}
%\changes{2.28}{2017-11-10}{new}
%    \begin{macrocode}
\newcommand*{\DTLlistformatoxford}{}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLandname}
%\changes{2.28}{2017-11-10}{new}
%    \begin{macrocode}
\ifdef\andname
{\newcommand*{\DTLandname}{\andname}}
{\newcommand*{\DTLandname}{\&}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLlistformatlastsep}
%\changes{2.28}{2017-11-10}{new}
%    \begin{macrocode}
\newcommand*{\DTLlistformatlastsep}{ \DTLlistand\space}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLlistformatitem}
%\changes{2.28}{2017-11-10}{new}
%    \begin{macrocode}
\newcommand*{\DTLlistformatitem}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@formatlist@handler}
%\changes{2.28}{2017-11-10}{new}
%    \begin{macrocode}
\newcommand*{\@dtl@formatlist@handler}[1]{%
  \@dtl@formatlist@itemsep
  \@dtl@formatlist@lastitem
  \renewcommand{\@dtl@formatlist@lastitem}{%
    \renewcommand{\@dtl@formatlist@itemsep}{%
      \DTLlistformatsep 
      \renewcommand*{\@dtl@formatlist@prelastitemsep}{%
        \DTLlistformatoxford}}%
    \renewcommand{\@dtl@formatlist@prelastitem}{%
      \@dtl@formatlist@prelastitemsep
       \DTLlistformatlastsep}%
    \DTLlistformatitem{#1}%
  }%
}%
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatlist}
%\changes{2.28}{2017-11-10}{new}
%Formats the comma-separated list supplied in its argument.
%The unstarred version adds grouping.
%    \begin{macrocode}
\newrobustcmd*{\DTLformatlist}{%
 \@ifstar{\s@dtlformatlist}{\@dtlformatlist}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\s@dtlformatlist}
%\changes{2.28}{2017-11-10}{new}
%\changes{3.0}{2025-03-03}{rewritten to (partially) use \LaTeX3}
%Starred version of \cs{DTLformatlist} doesn't add grouping.
%    \begin{macrocode}
\ExplSyntaxOn
\newcommand*{\s@dtlformatlist}[1]{%
  \def\@dtl@formatlist@itemsep{}%
  \def\@dtl@formatlist@lastitem{}%
  \def\@dtl@formatlist@prelastitem{}%
  \def\@dtl@formatlist@prelastitemsep{}%
  \@dtl@assigntmpseq{#1}%
  \seq_map_function:NN \l__datatool_tmp_seq \@dtl@formatlist@handler
  \@dtl@formatlist@prelastitem\@dtl@formatlist@lastitem
}
\ExplSyntaxOff
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtlformatlist}
%\changes{2.28}{2017-11-10}{new}
%Unstarred version of \cs{DTLformatlist} adds grouping.
%    \begin{macrocode}
\newcommand*{\@dtlformatlist}[1]{{\s@dtlformatlist{#1}}}
%    \end{macrocode}
%\end{macro}
%
% \subsubsection{General Token Utilities}
%These commands are for altering the content of token registers.
%They're mostly used by \sty{datatool} for the internal database
%structure.
%\changes{2.28}{2017-11-10}{renamed \cs{toks@g...} to
%\cs{dtl@toks@g...}}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\begin{macro}{\@dtl@toks@gput@right@cx}
%\begin{definition}
%\cs{dtl@toks@gput@right@cx}\marg{toks name}\marg{stuff}
%\end{definition}
% Globally appends stuff to token register \cs{}\meta{toks name}
%Deprecated.
%TODO remove
%    \begin{macrocode}
\newcommand{\@dtl@toks@gput@right@cx}[2]{%
 \__datatool_token_register_gput_right:cx #1 { #2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@toks@gconcat@middle@cx}
%\begin{definition}
%\cs{dtl@toks@gconcat@middle@cx}\marg{toks name}\marg{before toks}\marg{stuff}\marg{after toks}
%\end{definition}
% Globally sets token register \cs{}\meta{toks name} to
% the contents of \meta{before toks} concatenated with
% \meta{stuff} (expanded) and the contents of \meta{after toks}
%Deprecated.
%TODO remove
%    \begin{macrocode}
\newcommand{\@dtl@toks@gconcat@middle@cx}[4]{%
  \__datatool_token_register_gset:cx
    { #1 } { \the #2 #3 \the #4 }
}
%    \end{macrocode}
%\end{macro}
%
% Use a token register.
%    \begin{macrocode}
\cs_new:Nn \__datatool_token_register_use:N 
{
  \the #1
}
\cs_generate_variant:Nn
  \__datatool_token_register_use:N { c }
%    \end{macrocode}
% Set a token register equal to another.
%    \begin{macrocode}
\cs_new:Nn \__datatool_token_register_set_eq:NN 
{
  #1 = #2 
}
\cs_generate_variant:Nn
   \__datatool_token_register_set_eq:NN 
 { cN, Nc, cc }
%    \end{macrocode}
% Globally set a token register equal to another.
%    \begin{macrocode}
\cs_new:Nn \__datatool_token_register_gset_eq:NN 
{
  \global #1 = #2 
}
\cs_generate_variant:Nn
   \__datatool_token_register_gset_eq:NN 
 { cN, Nc, cc }
%    \end{macrocode}
% Set the contents of a token register.
%    \begin{macrocode}
\cs_new:Nn \__datatool_token_register_set:Nn 
{
  #1 = { #2 }
}
\cs_generate_variant:Nn
   \__datatool_token_register_set:Nn
   { cn, cx, co, cV, No, Nx, NV }
%    \end{macrocode}
% Globally set the contents of a token register.
%    \begin{macrocode}
\cs_new:Nn \__datatool_token_register_gset:Nn 
{
  \global #1 = { #2 }
}
\cs_generate_variant:Nn
   \__datatool_token_register_gset:Nn
   { cn, cx, co, cV, No, Nx }
%    \end{macrocode}
% Append content to a token register.
%    \begin{macrocode}
\cs_new:Nn \__datatool_token_register_put_right:Nn 
{
  \__datatool_token_register_set:No #1
   { \the #1  #2 }
}
\cs_generate_variant:Nn
   \__datatool_token_register_put_right:Nn
   { cn, cx, co, cV, No, Nx }
%    \end{macrocode}
% Globally append content to a token register.
%    \begin{macrocode}
\cs_new:Nn \__datatool_token_register_gput_right:Nn 
{
  \__datatool_token_register_gset:No #1
   { \the #1  #2 }
}
\cs_generate_variant:Nn
   \__datatool_token_register_gput_right:Nn
   { cn, cx, co, cV, No, Nx }
%    \end{macrocode}
%
% Concatenate the content of two registers with extra stuff in
% between
%    \begin{macrocode}
\cs_new:Nn \__datatool_token_register_concat_middle:NNnN
{
  \__datatool_token_register_set:Nx
    #1 { \the #2 \exp_not:n { #3 } \the #4 }
}
\cs_generate_variant:Nn
   \__datatool_token_register_concat_middle:NNnN
   { cNnN, cNxN, NNxN }
%    \end{macrocode}
% Globally concatenate.
%    \begin{macrocode}
\cs_new:Nn \__datatool_token_register_gconcat_middle:NNnN
{
  \__datatool_token_register_gset:Nx
    #1 { \the #2 \exp_not:n { #3 } \the #4 }
}
\cs_generate_variant:Nn
   \__datatool_token_register_gconcat_middle:NNnN
   { cNnN, cNxN, NNxN }
%    \end{macrocode}
%
%\subsection{Encodings}
%\label{sec:code:encodings}
%
% Load file dealing with non-ASCII characters, but first initialise
% ASCII defaults. Each character is represented by both a string
% variable (for use by the sort comparator) and a token list
% variable (for typesetting, if required). The difference in
% expansion is more noticeable with \sty{input} than with native
% Unicode engines.
%\changes{3.0}{2025-03-03}{added encoding files}
%
%Define new variables with default ASCII value.
%
%Regular expression to match apostrophe.
%    \begin{macrocode}
\regex_new:N \l_datatool_apos_regex
\regex_set:Nn \l_datatool_apos_regex { \' }
%    \end{macrocode}
%
%Most symbols are currency signs, but mid-point is also added as
%it's sometimes used as a number separator.
%    \begin{macrocode}
\cs_new:Nn \__datatool_new_symbol:nn
 {
   \__datatool_new_symbol:nnn { #1 } { #2 } { #2 }
 }
\cs_new:Nn \__datatool_new_symbol:nnn
 {
   \tl_new:c { l_datatool_ #1 _tl }
   \tl_set:cn { l_datatool_ #1 _tl } { #2 }
   \str_new:c { l_datatool_ #1 _str }
   \str_set:cn { l_datatool_ #1 _str } { #3 }
 }
\cs_generate_variant:Nn \__datatool_new_symbol:nnn { nnV }
%    \end{macrocode}
%Regular expression to search for these symbols, which may be used
%in localisation files. Only those characters supported by the
%encoding will be added.
%    \begin{macrocode}
\regex_new:N \l_datatool_currencysigns_regex
\regex_set:Nn \l_datatool_currencysigns_regex { \$ }
%    \end{macrocode}
%Allow encoding file to change the values.
%    \begin{macrocode}
\cs_new:Nn \datatool_set_symbol:nn
 {
   \str_set:cn { l_datatool_ #1 _str } { #2 }
   \tl_set:cn { l_datatool_ #1 _tl } { #2 }
 }
\cs_generate_variant:Nn \datatool_set_symbol:nn { ne }
%    \end{macrocode}
% Designed for \sty{inputenc} to set by character code:
%    \begin{macrocode}
\cs_new:Nn \datatool_set_symbol_from_charcode:nn
 {
   \str_set:ce { l_datatool_ #1 _str }
    { \char_generate:nn { #2 } { 12 } }
   \exp_args:Nno \tl_set:co { l_datatool_ #1 _tl }
    { \char_generate:nn { #2 } { 13 } }
 }
%    \end{macrocode}
%Just for currency signs so that they can be added to the regular
%expression and list of known currencies:
%    \begin{macrocode}
\cs_new:Nn \datatool_set_currencysign:nn
 {
   \datatool_set_symbol:nn { #1 } { #2 }
   \regex_set:Nn \l_datatool_currencysigns_regex
     {
        \ur { l_datatool_currencysigns_regex }
       | \u { l_datatool_ #1 _str }
     }
   \DTLnewcurrencysymbol { #2 }
 }
\cs_generate_variant:Nn \datatool_set_currencysign:nn { ne }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \datatool_set_currencysign_from_charcode:nn
 {
   \datatool_set_symbol_from_charcode:nn { #1 } { #2 }
   \regex_set:Nn \l_datatool_currencysigns_regex
     {
        \ur { l_datatool_currencysigns_regex }
       | \u { l_datatool_ #1 _str }
     }
 }
%    \end{macrocode}
%These may be redefined by an encoding file, if available.
%See section~\ref{sec:code:encodings}.
%    \begin{macrocode}
\__datatool_new_symbol:nn { cent } { c }
\__datatool_new_symbol:nn { pound } { L }
\__datatool_new_symbol:nnV { currency } { \# } \c_hash_str
\__datatool_new_symbol:nn { yen } { Y }
\__datatool_new_symbol:nn { middot } { . }
\__datatool_new_symbol:nn { florin } { f }
\__datatool_new_symbol:nn { baht } { B }
\__datatool_new_symbol:nn { ecu } { CE }
\__datatool_new_symbol:nn { colonsign } { C }
\__datatool_new_symbol:nn { cruzerio } { Cr }
\__datatool_new_symbol:nn { frenchfranc } { F }
\__datatool_new_symbol:nn { lira } { L }
\__datatool_new_symbol:nn { mill } { m }
\__datatool_new_symbol:nn { naira } { N }
\__datatool_new_symbol:nn { peseta } { Pts }
\__datatool_new_symbol:nn { rupee } { Rs }
\__datatool_new_symbol:nn { won } { W }
\__datatool_new_symbol:nn { shekel } { S }
\__datatool_new_symbol:nn { dong } { d }
\__datatool_new_symbol:nn { euro } { E }
\__datatool_new_symbol:nn { kip } { K }
\__datatool_new_symbol:nn { tugrik } { T }
\__datatool_new_symbol:nn { drachma } { Dr }
\__datatool_new_symbol:nn { germanpenny } { p }
\__datatool_new_symbol:nn { peso } { P }
\__datatool_new_symbol:nn { guarani } { G. }
\__datatool_new_symbol:nn { austral } { A }
\__datatool_new_symbol:nn { hryvnia } { S }
\__datatool_new_symbol:nn { cedi } { C }
\__datatool_new_symbol:nn { livretournois } { lt }
\__datatool_new_symbol:nn { spesmilo } { Sm }
\__datatool_new_symbol:nn { tenge } { T }
\__datatool_new_symbol:nn { indianrupee } { R }
\__datatool_new_symbol:nn { turkishlira } { L }
\__datatool_new_symbol:nn { nordicmark } { M }
\__datatool_new_symbol:nn { manat } { M }
\__datatool_new_symbol:nn { ruble } { R }
\__datatool_new_symbol:nn { lari } { L }
\__datatool_new_symbol:nn { bitcoin } { B }
\__datatool_new_symbol:nn { som } { c }
%    \end{macrocode}
%
% An internal list that stores all known currencies.
% This sequence variable and \cs{DTLnewcurrencysymbol} need to be
% defined before the ldf encoding file is input.
%    \begin{macrocode}
\seq_new:N \l__datatool_known_currencies_seq
%    \end{macrocode}
% A list of all defined currency labels:
%    \begin{macrocode}
\seq_new:N \l_datatool_currencies_seq
%    \end{macrocode}
% A list of all defined currency labels registered by regions:
%    \begin{macrocode}
\seq_new:N \l_datatool_regional_currencies_seq
%    \end{macrocode}
% Map region to currency code:
%    \begin{macrocode}
\prop_new:N \l_datatool_regional_currencies_prop
%    \end{macrocode}
%
%\begin{macro}{\DTLnewcurrencysymbol}
%\changes{3.0}{2025-03-03}{changed to document command}
%\begin{definition}
% \cs{DTLaddcurrency}\marg{symbol}
%\end{definition}
% Adds \meta{symbol} to the list of known currencies
%    \begin{macrocode}
\NewDocumentCommand \DTLnewcurrencysymbol { m }
{
  \seq_if_in:NnF \l__datatool_known_currencies_seq { #1 } 
   {
     \seq_put_right:Nn \l__datatool_known_currencies_seq { #1 }
   }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%    \begin{macrocode}
\InputIfFileExists
 {datatool-\TrackLangEncodingName .ldf}
{}
{
  \PackageInfo{datatool-base}
   {Missing file `datatool-\TrackLangEncodingName .ldf'.
    Falling back on US-ASCII for \string\datatool\string_...\string_str commands}
}
%    \end{macrocode}
%
%
%\subsection{Locale Dependent Information}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\begin{macro}{\@dtl@decimal}
%\changes{3.0}{2025-03-03}{replaced}
% The current decimal character was stored in \cs{@dtl@decimal}
% prior to version 3.0. As from v3.0, there are separate variables
% for formatting and parsing.
%    \begin{macrocode}
\tl_new:N \__datatool_decimal_tl
%    \end{macrocode}
%Parsing may either be a token list:
%    \begin{macrocode}
\tl_new:N \__datatool_decimal_parse_tl
%    \end{macrocode}
%or a regular expression:
%    \begin{macrocode}
\regex_new:N \__datatool_decimal_parse_regex
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtl@numbergroupchar}
%\changes{3.0}{2025-03-03}{replaced}
% The current number group character was stored in 
% \cs{@dtl@numbergroupchar}
% prior to version 3.0. As from v3.0, there are separate variables
% for formatting and parsing.
%    \begin{macrocode}
\tl_new:N \__datatool_numbergroup_tl
%    \end{macrocode}
%Parsing may either be a token list:
%    \begin{macrocode}
\tl_new:N \__datatool_numbergroup_parse_tl
%    \end{macrocode}
%or a regular expression:
%    \begin{macrocode}
\regex_new:N \__datatool_numbergroup_parse_regex
%    \end{macrocode}
%\end{macro}
%Regular expressions for parsing numeric values.
% For parsing locale numbers:
%    \begin{macrocode}
\regex_new:N \l_datatool_locale_numeric_regex
\regex_new:N \l_datatool_locale_fractional_regex
\regex_new:N \l_datatool_locale_fractional_nozero_regex
%    \end{macrocode}
% TeX's maximum integer limit is 0x7FFFFFFF so provide a regular
% expression to capture large integers that would otherwise trigger
% an error. (For example, a numeric ID or a bare telephone number
% without parentheses or hyphens could well exceed this value.)
%    \begin{macrocode}
\regex_new:N \l_datatool_locale_bigint_regex
%    \end{macrocode}
% For splitting standard decimals into number groups
% (dot for decimal separator and no number groups):
%    \begin{macrocode}
\regex_const:Nn \c_datatool_decimal_grps_regex
  { \A ([+\-])? 0* (\d{1,3}) \. (\d+) \Z }
\regex_const:Nn \c_datatool_decimal_grps_i_regex
  { \A ([+\-])? 0* (\d{1,3}) (\d{3}) \. (\d+) \Z }
\regex_const:Nn \c_datatool_decimal_grps_ii_regex
  { \A ([+\-])? 0* (\d{1,3}) (\d{3}) (\d{3}) \. (\d+) \Z }
\regex_const:Nn \c_datatool_decimal_grps_iii_regex
  { \A ([+\-])? 0* (\d+) (\d{3}) (\d{3}) (\d{3}) \. (\d+) \Z }
%    \end{macrocode}
% Similarly for integers:
%    \begin{macrocode}
\regex_const:Nn \c_datatool_integer_grps_i_regex
  { \A ([+\-])? 0* (\d{1,3}) (\d{3}) \Z }
\regex_const:Nn \c_datatool_integer_grps_ii_regex
  { \A ([+\-])? 0* (\d{1,3}) (\d{3}) (\d{3}) \Z }
\regex_const:Nn \c_datatool_integer_grps_iii_regex
  { \A ([+\-])? 0* (\d+) (\d{3}) (\d{3}) (\d{3}) \Z }
\regex_const:Nn \c_datatool_integer_no_grps_regex
  { \A ([+\-])? (\d{1,3}) \Z }
%    \end{macrocode}
%Trimming:
%    \begin{macrocode}
\regex_const:Nn \c_datatool_numeric_leading_zeros_regex
  { \A ([+\-])? 0* (\d+ (?: (\.\d+) )? ) \Z }
\regex_const:Nn \c_datatool_integer_leading_zeros_regex
  { \A ([+\-])? 0* (\d+ ) \Z }
\regex_const:Nn \c_datatool_decimal_redundant_zeros_regex
  { \A ([+\-])? 0* (\d+ \. \d+ ) 0* \Z }
\regex_const:Nn \c_datatool_decimal_implicit_zero_regex
  { \A ([+\-])? ( \. \d+ ) 0* \Z }
%    \end{macrocode}
%Any unformatted number:
%    \begin{macrocode}
\regex_const:Nn \c_datatool_any_numeric_regex
  { \A ([+\-])? \d* \. ? \d+ ( [eE] [+\-]? \d+ ) ? \Z }
%    \end{macrocode}
%Get fractional part:
%    \begin{macrocode}
\cs_new:Nn \datatool_decimal_frac:n 
 {
   \__datatool_decimal_frac:w #1 . 0 . 0 \q_stop
 }
\cs_generate_variant:Nn \datatool_decimal_frac:n { e , o }
%    \end{macrocode}
% Internal command:
%    \begin{macrocode}
\cs_new:Npn \__datatool_decimal_frac:w #1.#2.#3 \q_stop
{
  \tl_if_head_eq_meaning:nNT { #1 } - { - }
  0 . #2
}
%    \end{macrocode}
%Right zero pad or truncate decimals.
%    \begin{macrocode}
\cs_new:Npn \__datatool_decimal_places_i:w #1.#2#3\q_stop
 {
   #1.#2
 }
\cs_new:Npn \__datatool_decimal_places_ii:w #1.#2#3#4\q_stop
 {
   #1.#2#3
 }
\cs_new:Npn \__datatool_decimal_places_iii:w #1.#2#3#4#5\q_stop
 {
   #1.#2#3#4
 }
\cs_new:Npn \__datatool_decimal_places_iv:w #1.#2#3#4#5#6\q_stop
 {
   #1.#2#3#4#5
 }
\cs_new:Npn \__datatool_decimal_places_v:w #1.#2#3#4#5#6#7\q_stop
 {
   #1.#2#3#4#5#6
 }
\cs_new:Npn \__datatool_decimal_places_vi:w #1.#2#3#4#5#6#7#8\q_stop
 {
   #1.#2#3#4#5#6#7
 }
\cs_new:Npn \__datatool_decimal_places_vii:w #1.#2#3#4#5#6#7#8#9\q_stop
 {
   #1.#2#3#4#5#6#7#8
 }
%    \end{macrocode}
%NB First check that the number is a decimal.
%Expand to padded value:
%    \begin{macrocode}
\cs_new:Nn \__datatool_decimal_trunc_pad_zeros:nn
 {
   \int_case:nnF { #2 }
    {
      { 1 }
        { \__datatool_decimal_places_i:w #1 0 \q_stop }
      { 2 }
        { \__datatool_decimal_places_ii:w #1 00 \q_stop }
      { 3 }
        { \__datatool_decimal_places_iii:w #1 000 \q_stop }
      { 4 }
        { \__datatool_decimal_places_iv:w #1 0000 \q_stop }
      { 5 }
        { \__datatool_decimal_places_v:w #1 00000 \q_stop }
      { 6 }
        { \__datatool_decimal_places_vi:w #1 000000 \q_stop }
    }
    {
      \__datatool_decimal_places_vii:w #1 0000000 \q_stop
    }
 }
%    \end{macrocode}
%
%\begin{macro}{\DTLsetnumberchars}
%\begin{definition}
%\cs{DTLsetnumberchars}\marg{number group char}\marg{decimal char}
%\end{definition}
% This sets the decimal character and number group
% characters.
%    \begin{macrocode}
\NewDocumentCommand \DTLsetnumberchars { m m }
 {
   \datatool_set_numberchars:nn { #1 } { #2 } 
 }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \datatool_set_numberchars:nn
 {
   \datatool_set_numberchars:nnnn { #1 } { #2 } { #1 } { #2 }
 }
\cs_generate_variant:Nn \datatool_set_numberchars:nn
 { nV , Vn , VV }
%    \end{macrocode}
%Syntax:  \marg{format number group char} \marg{format decimal char}
% \marg{parse number group char} \marg{parse decimal char}
%    \begin{macrocode}
\cs_new:Nn \datatool_set_numberchars:nnnn
 {
  \tl_set:Nn \__datatool_numbergroup_tl { #1 }
  \tl_set:Nn \__datatool_numbergroup_parse_tl { #3 }
  \tl_set:Nn \__datatool_decimal_tl { #2 }
  \tl_set:Nn \__datatool_decimal_parse_tl { #4 }
%    \end{macrocode}
%Regular expressions used for parsing.
%    \begin{macrocode}
  \regex_set:Nn \l_datatool_locale_bigint_regex
    {
       \A [+\-]?\d* [2-9]
         \u{__datatool_numbergroup_parse_tl}? \d{3}
         \u{__datatool_numbergroup_parse_tl}? \d{3}
         \u{__datatool_numbergroup_parse_tl}? \d{3}
          \Z
    }
  \regex_set:Nn \l_datatool_locale_numeric_regex
    {
       \A ([+\-]?\d+) 
         (?:\u{__datatool_numbergroup_parse_tl}(\d{3}))?
         (?:\u{__datatool_numbergroup_parse_tl}(\d{3}))?
         (?:\u{__datatool_numbergroup_parse_tl}(\d{3}))?
         (?:\u{__datatool_decimal_parse_tl}(\d+))? \Z
    }
  \regex_set:Nn \l_datatool_locale_fractional_regex
    {
       \A ([+\-]?\d+) \u{__datatool_decimal_parse_tl} (\d+) \Z
    }
  \regex_set:Nn \l_datatool_locale_fractional_nozero_regex
    {
       \A ([+\-]?) \u{__datatool_decimal_parse_tl} (\d+) \Z
    }
}
\cs_generate_variant:Nn \datatool_set_numberchars:nnnn
 { VVVV , eeee , VnVn , nnVn }
%    \end{macrocode}
%Set the default.
%    \begin{macrocode}
\DTLsetnumberchars{,}{.}
%    \end{macrocode}
%
%As above but where a regular expression is needed for parsing.
%    \begin{macrocode}
\cs_new:Nn \datatool_set_numberchars_regex:nnnn
 {
  \tl_set:Nn \__datatool_numbergroup_tl { #1 }
  \tl_clear:N \__datatool_numbergroup_parse_tl
  \regex_set:Nn \__datatool_numbergroup_parse_regex { #3 }
  \tl_set:Nn \__datatool_decimal_tl { #2 }
  \tl_clear:N \__datatool_decimal_parse_tl
  \regex_set:Nn \__datatool_decimal_parse_regex { #4 }
%    \end{macrocode}
%Regular expressions used for parsing.
%    \begin{macrocode}
  \regex_set:Nn \l_datatool_locale_bigint_regex
    {
       \A [+\-]?\d* [2-9]
         \ur{__datatool_numbergroup_parse_regex}? \d{3}
         \ur{__datatool_numbergroup_parse_regex}? \d{3}
         \ur{__datatool_numbergroup_parse_regex}? \d{3}
          \Z
    }
  \regex_set:Nn \l_datatool_locale_numeric_regex
    {
       \A ([+\-]?\d+) 
         (?:\ur{__datatool_numbergroup_parse_regex}(\d{3}))?
         (?:\ur{__datatool_numbergroup_parse_regex}(\d{3}))?
         (?:\ur{__datatool_numbergroup_parse_regex}(\d{3}))?
         (?:\ur{__datatool_decimal_parse_regex}(\d+))? \Z
    }
  \regex_set:Nn \l_datatool_locale_fractional_regex
    {
       \A ([+\-]?\d+) \ur{__datatool_decimal_parse_regex} (\d+) \Z
    }
  \regex_set:Nn \l_datatool_locale_fractional_nozero_regex
    {
       \A ([+\-]?) \ur{__datatool_decimal_parse_regex} (\d+) \Z
    }
}
\cs_generate_variant:Nn \datatool_set_numberchars_regex:nnnn
 { VVnn , Vnnn , nVnn }
%    \end{macrocode}
%
%As above but regex is needed for number group and token
%list for decimal group when parsing.
%    \begin{macrocode}
\cs_new:Nn \datatool_set_numberchars_regex_tl:nnnn
 {
  \tl_set:Nn \__datatool_numbergroup_tl { #1 }
  \tl_clear:N \__datatool_numbergroup_parse_tl
  \regex_set:Nn \__datatool_numbergroup_parse_regex { #3 }
  \tl_set:Nn \__datatool_decimal_tl { #2 }
  \tl_set:Nn \__datatool_decimal_parse_tl { #4 }
%    \end{macrocode}
%Regular expressions used for parsing.
%    \begin{macrocode}
  \regex_set:Nn \l_datatool_locale_bigint_regex
    {
       \A [+\-]?\d* [2-9]
         \ur{__datatool_numbergroup_parse_regex}? \d{3}
         \ur{__datatool_numbergroup_parse_regex}? \d{3}
         \ur{__datatool_numbergroup_parse_regex}? \d{3}
          \Z
    }
  \regex_set:Nn \l_datatool_locale_numeric_regex
    {
       \A ([+\-]?\d+) 
         (?:\ur{__datatool_numbergroup_parse_regex}(\d{3}))?
         (?:\ur{__datatool_numbergroup_parse_regex}(\d{3}))?
         (?:\ur{__datatool_numbergroup_parse_regex}(\d{3}))?
         (?:\u{__datatool_decimal_parse_tl}(\d+))? \Z
    }
  \regex_set:Nn \l_datatool_locale_fractional_regex
    {
       \A ([+\-]?\d+) \u{__datatool_decimal_parse_tl} (\d+) \Z
    }
  \regex_set:Nn \l_datatool_locale_fractional_nozero_regex
    {
       \A ([+\-]?) \u{__datatool_decimal_parse_tl} (\d+) \Z
    }
}
\cs_generate_variant:Nn \datatool_set_numberchars_regex_tl:nnnn
 { VVnn , Vnnn , nVnn , nVnV , nnnV }
%    \end{macrocode}
%
%As above but token list is needed for number group and regex for
%decimal group when parsing.
%    \begin{macrocode}
\cs_new:Nn \datatool_set_numberchars_tl_regex:nnnn
 {
  \tl_set:Nn \__datatool_numbergroup_tl { #1 }
  \tl_set:Nn \__datatool_numbergroup_parse_tl { #3 }
  \tl_set:Nn \__datatool_decimal_tl { #2 }
  \tl_clear:N \__datatool_decimal_parse_tl
  \regex_set:Nn \__datatool_decimal_parse_regex { #4 }
%    \end{macrocode}
%Regular expressions used for parsing.
%    \begin{macrocode}
  \regex_set:Nn \l_datatool_locale_bigint_regex
    {
       \A [+\-]?\d* [2-9]
         \u{__datatool_numbergroup_parse_tl}? \d{3}
         \u{__datatool_numbergroup_parse_tl}? \d{3}
         \u{__datatool_numbergroup_parse_tl}? \d{3}
          \Z
    }
  \regex_set:Nn \l_datatool_locale_numeric_regex
    {
       \A ([+\-]?\d+) 
         (?:\u{__datatool_numbergroup_parse_tl}(\d{3}))?
         (?:\u{__datatool_numbergroup_parse_tl}(\d{3}))?
         (?:\u{__datatool_numbergroup_parse_tl}(\d{3}))?
         (?:\ur{__datatool_decimal_parse_regex}(\d+))? \Z
    }
  \regex_set:Nn \l_datatool_locale_fractional_regex
    {
       \A ([+\-]?\d+) \ur{__datatool_decimal_parse_regex} (\d+) \Z
    }
  \regex_set:Nn \l_datatool_locale_fractional_nozero_regex
    {
       \A ([+\-]?) \ur{__datatool_decimal_parse_regex} (\d+) \Z
    }
}
\cs_generate_variant:Nn \datatool_set_numberchars_tl_regex:nnnn
 { VVnn , Vnnn , nVnn , VnVn , nnVn }
%    \end{macrocode}
%
%\changes{3.0}{2025-03-03}{added thinspace, apostrophe, and underscore
%number group commands}
%Convenient shortcut to set thin-space number group:
%    \begin{macrocode}
\datatool_if_unicode_engine:TF
 {
  \cs_new:Nn \datatool_set_thinspace_group_decimal_char:n
   {
     \datatool_set_numberchars_regex_tl:nnnn
       { \, } { #1 } { \c{,} | \s | \x{2009} } { #1 }
   }
 }
 {
  \cs_new:Nn \datatool_set_thinspace_group_decimal_char:n
   {
     \datatool_set_numberchars_regex_tl:nnnn
       { \, } { #1 } { \c{,} | \s | \x{E2} \x{80} \x{89} } { #1 }
   }
 }
\cs_generate_variant:Nn \datatool_set_thinspace_group_decimal_char:n { V }
%    \end{macrocode}
%
%Similarly for apostrophe:
%    \begin{macrocode}
\datatool_if_unicode_engine:TF
 {
  \regex_const:Nn \c_datatool_apostrophe_regex
    { \x{27} | \x{2019} }
 }
 {
  \regex_const:Nn \c_datatool_apostrophe_regex
    { \x{27} | \x{E2} \x{80} \x{99} }
 }
\cs_new:Nn \datatool_set_apos_group_decimal_char:n
 {
   \datatool_set_numberchars_regex_tl:nnnn
     { ' } { #1 } { \ur{c_datatool_apostrophe_regex} } { #1 }
 }
\cs_generate_variant:Nn \datatool_set_apos_group_decimal_char:n { V }
%    \end{macrocode}
%
%
%Similarly for underscore number group character.
%    \begin{macrocode}
\cs_new:Nn \datatool_set_underscore_group_decimal_char:n
 {
   \datatool_set_numberchars_regex_tl:nnnn
    { \_ } { #1 } { \c{_} | \x{5F} } { #1 }
 }
\cs_generate_variant:Nn \datatool_set_underscore_group_decimal_char:n { V }
%    \end{macrocode}
%
% \subsubsection{Determining Data Types}
% The control sequence \cs{\_\_datatool\_datum:nnnn} parses its
% argument to determine the data type of its argument. Each data
% type is represented by one of the following values: 0 (string),
% 1 (integer), 2 (decimal), 3 (currency), 4 (datetime), 5 (date),
% or 6 (time). The unknown data type -1
% is used when the data is empty. Numeric values are expected to use
% the number group and decimal characters currently in effect.
%
% The temporal data types (datetime, date and time) are new to v3.0
% and have a numeric value.
% This is mainly provided for the benefit of datatooltk
% as imported data typically treats date/time data as numeric
% and it may be useful to retain the numeric value and ISO format.
% The Julian day number (an integer) is used to represent a date,
% the Julian time (a decimal between $-0.5$ an $0.5$, where 0 is
% midday) is used to  represent a time, and the Julian date
% (the sum of the Julian day number and the Julian time) is used to
% represent a timestamp (datetime).
%
%\begin{macro}{\@dtl@datatype}
%Scratch variable used to represent the data type identifier.
%This may be replaced with an integer variable at some point.
%    \begin{macrocode}
\newcount\@dtl@datatype
%    \end{macrocode}
%\end{macro}
%Scratch variables. Integers:
%    \begin{macrocode}
\int_new:N \l__datatool_tmpa_int
\int_new:N \l__datatool_tmpb_int
\int_new:N \l__datatool_tmpc_int
\int_new:N \l__datatool_tmpd_int
%    \end{macrocode}
%Used to count or index items:
%    \begin{macrocode}
\int_new:N \l__datatool_count_int
%    \end{macrocode}
%Temporary storage of data type identifier:
%    \begin{macrocode}
\int_new:N \l__datatool_tmp_datatype_int
%    \end{macrocode}
%Token lists (the first one may have already been
%defined if \sty{datatool} has been loaded):
%    \begin{macrocode}
\tl_clear_new:N \l__datatool_tmpa_tl
\tl_new:N \l__datatool_tmpb_tl
\tl_new:N \l__datatool_tmpc_tl
\tl_new:N \l__datatool_tmpd_tl
\tl_new:N \l__datatool_tmp_currency_tl
\tl_new:N \l__datatool_tmp_initial_tl
\tl_new:N \l__datatool_result_tl
\tl_new:N \l__datatool_resulta_tl
\tl_new:N \l__datatool_resultb_tl
\tl_new:N \l__datatool_dialect_tl
%    \end{macrocode}
%Floating point:
%    \begin{macrocode}
\fp_new:N \l__datatool_tmpa_fp
\fp_new:N \l__datatool_tmpb_fp
\fp_new:N \l__datatool_tmpc_fp
\fp_new:N \l__datatool_tmpd_fp
\fp_new:N \l__datatool_mean_fp
\fp_new:N \l__datatool_total_fp
\fp_new:N \l__datatool_datum_value_fp
%    \end{macrocode}
%Dimensions:
%    \begin{macrocode}
\dim_new:N \l__datatool_tmpa_dim
\dim_new:N \l__datatool_tmpb_dim
%    \end{macrocode}
%Strings:
%    \begin{macrocode}
\str_new:N \l__datatool_tmpa_str
\str_new:N \l__datatool_tmpb_str
\str_new:N \l__datatool_tmpc_str
%    \end{macrocode}
%Clist.
%    \begin{macrocode}
\clist_new:N \l__datatool_tmp_clist
%    \end{macrocode}
%Sequences.
%    \begin{macrocode}
\seq_new:N \l__datatool_tmp_seq
\seq_new:N \l__datatool_tmpa_seq
\seq_new:N \l__datatool_tmpb_seq
%    \end{macrocode}
%The following may also be used by the localisation files, so not a private
%variable, but still just a scratch variable.
%    \begin{macrocode}
\seq_new:N \l_datatool_regex_match_seq
\seq_new:N \l_datatool_timestamp_match_seq
%    \end{macrocode}
%Regular expressions
%    \begin{macrocode}
\regex_new:N \l__datatool_tmpa_regex
\regex_new:N \l__datatool_tmpb_regex
%    \end{macrocode}
%Data type identifiers.
%    \begin{macrocode}
\int_const:Nn \c_datatool_unknown_int {-1}
\int_const:Nn \c_datatool_string_int {0}
\int_const:Nn \c_datatool_integer_int {1}
\int_const:Nn \c_datatool_decimal_int {2}
\int_const:Nn \c_datatool_currency_int {3}
\int_const:Nn \c_datatool_datetime_int {4}
\int_const:Nn \c_datatool_date_int {5}
\int_const:Nn \c_datatool_time_int {6}
%    \end{macrocode}
%Maximum known value. This will be updated if any new types are
%added in future.
%    \begin{macrocode}
\cs_new:Nn \datatool_max_known_type:
 {
   \c_datatool_time_int
 }
%    \end{macrocode}
%
%\begin{macro}{\DTLgetDataTypeName}
%\changes{3.0}{2025-03-03}{new}
%Get textual name for data type (expandable):
%    \begin{macrocode}
\newcommand*{\DTLgetDataTypeName}[1]
{
  \int_case:nnF { #1 }
   {
     { \c_datatool_unknown_int }
       { \DTLdatatypeunsetname }
     { \c_datatool_string_int }
       { \DTLdatatypestringname }
     { \c_datatool_integer_int }
       { \DTLdatatypeintegername }
     { \c_datatool_decimal_int }
       { \DTLdatatypedecimalname }
     { \c_datatool_currency_int }
       { \DTLdatatypecurrencyname }
     { \c_datatool_datetime_int }
       { \DTLdatatypedatetimename }
     { \c_datatool_date_int }
       { \DTLdatatypedatename }
     { \c_datatool_time_int }
       { \DTLdatatypetimename }
   }
   {
     \DTLdatatypeinvalidname
   }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLdatatypeunsetname}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand \DTLdatatypeunsetname { unset }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLdatatypestringname}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand \DTLdatatypestringname { string }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLdatatypeintegername}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand \DTLdatatypeintegername { integer }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLdatatypedecimalname}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand \DTLdatatypedecimalname { decimal }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLdatatypecurrencyname}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand \DTLdatatypecurrencyname { currency }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLdatatypedatetimename}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand \DTLdatatypedatetimename { date-time }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLdatatypedatename}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand \DTLdatatypedatename { date }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLdatatypetimename}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand \DTLdatatypetimename { time }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdatatypeinvalidname}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand \DTLdatatypeinvalidname { invalid }
%    \end{macrocode}
%\end{macro}
%
%Test for for datum types. The argument should be a number.
%Test if the number is a recognised type (including \qt{unknown}):
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_valid_datum_type:n #1
   { p, T, F, TF }
 {
    \bool_lazy_or:nnTF 
      { \int_compare_p:nNn { #1 } < { \c_datatool_unknown_int } }
      { \int_compare_p:nNn { #1 } > { \datatool_max_known_type: } }
    { \prg_return_false: }
    { \prg_return_true: }
 }
%    \end{macrocode}
%Test if the argument is a numeric datum type ID (currency, dates and times are
%considered numeric, in addition to integers and decimals):
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_numeric_datum_type:n #1
   { p, T, F, TF }
 {
    \bool_lazy_or:nnTF 
      { \int_compare_p:nNn { #1 } < { \c_datatool_integer_int } }
      { \int_compare_p:nNn { #1 } > { \c_datatool_time_int } }
    { \prg_return_false: }
    { \prg_return_true: }
 }
%    \end{macrocode}
%Test if the argument is a temporal datum type ID (timestamp, date
%or time):
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_temporal_datum_type:n #1
   { p, T, F, TF }
 {
    \bool_lazy_or:nnTF 
      { \int_compare_p:nNn { #1 } < { \c_datatool_datetime_int } }
      { \int_compare_p:nNn { #1 } > { \c_datatool_time_int } }
    { \prg_return_false: }
    { \prg_return_true: }
 }
%    \end{macrocode}
%Test if the argument is an integer or decimal datum type ID (not
%currency or temporal):
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_number_only_datum_type:n #1
   { p, T, F, TF }
 {
    \bool_lazy_or:nnTF 
      { \int_compare_p:nNn { #1 } = { \c_datatool_integer_int } }
      { \int_compare_p:nNn { #1 } = { \c_datatool_decimal_int } }
    { \prg_return_true: }
    { \prg_return_false: }
 }
%    \end{macrocode}
%Test if the argument is any datum type that has an integer
%value (integer or date):
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_any_int_datum_type:n #1
   { p, T, F, TF }
 {
    \bool_lazy_or:nnTF 
      { \int_compare_p:nNn { #1 } = { \c_datatool_integer_int } }
      { \int_compare_p:nNn { #1 } = { \c_datatool_date_int } }
    { \prg_return_true: }
    { \prg_return_false: }
 }
%    \end{macrocode}
%
%\changes{3.0}{2025-03-03}{moved lower-level null commands from \sty{datatool} to \sty{datatool-base}}
%The null commands were originally in \sty{datatool} but as from
%v3.0, the lower-level commands are now in 
%\sty{datatool-base} since commands like \cs{DTLdatumvalue} need to check for
%null. The \sty{person} package, which may be used with just
%\sty{datatool-base} and not \sty{datatool}, performs a null test.
%\begin{macro}{\@dtlnovalue}
%\changes{3.0}{2025-03-03}{replaced with \cs{c\_nullvalue\_tl}}
%The old internal command \cs{@dtlnovalue} has been replaced with
%the constant \verb|\c_datatool_nullvalue_tl|.
%This is similar in concept to \verb|c_novalue_tl| but is intended
%to represent missing values in \sty{datatool} return values,
%particularly when querying database values. This starts with a
%space in the event that a null value is expanded in a context that tests for
%an initial letter without checking the category code. It ends with a space 
%that has a letter category code. This mixture of category codes is designed 
%to make it harder to accidentally identify the
%string \qt{ Undefined Value } as null.
%
%Note that neither the old internal command nor this new constant
%are intended to be included in a list of data or in a
%database. DatatoolTk does still use the old command name
%as a marker for its own internal use, although it shouldn't make its
%way into an output file.
%    \begin{macrocode}
\tl_const:Nx \c_datatool_nullvalue_tl
 {
   \c_catcode_other_space_tl
   \tl_to_str:n { Undefined } 
   \c_catcode_other_space_tl
   \tl_to_str:n { Value }
   \char_generate:nn { 32 } { 11 }
 }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlnovalue}
%\changes{3.0}{2025-03-03}{moved from \sty{datatool} to \sty{datatool-base}}
%    \begin{macrocode}
\newcommand* \dtlnovalue { \c_datatool_nullvalue_tl }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLstringnull}
%\changes{3.0}{2025-03-03}{moved from \sty{datatool} to \sty{datatool-base}}
% String null value:
%    \begin{macrocode}
\newcommand* \DTLstringnull { \@dtlstringnull }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtlstringnull}
%\changes{2.13}{2013-01-15}{new}
%\changes{3.0}{2025-03-03}{moved from \sty{datatool} to \sty{datatool-base}}
% String null value:
%    \begin{macrocode}
\newcommand* \@dtlstringnull {NULL}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLnumbernull}
%\changes{3.0}{2025-03-03}{moved from \sty{datatool} to \sty{datatool-base}}
% Number null value:
%    \begin{macrocode}
\newcommand* \DTLnumbernull { \@dtlnumbernull }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtlnumbernull}
%\changes{2.13}{2013-01-15}{new}
%\changes{3.0}{2025-03-03}{moved from \sty{datatool} to \sty{datatool-base}}
% Number null value:
%    \begin{macrocode}
\newcommand*{\@dtlnumbernull}{0}
%    \end{macrocode}
%\end{macro}
%
%Constant representing an empty datum value with unknown type.
%    \begin{macrocode}
\tl_const:Nn \c_datatool_empty_datum_tl
 { 
   \__datatool_datum:nnnn { } { } { } { \c_datatool_unknown_int }
 }
%    \end{macrocode}
%
%\cs{DTLifnull} is still defined in \sty{datatool}. Provide low-level
%commands. Test token for null. NB this doesn't test against the expansion
%text of the string and number null values as they could easily be
%valid non-null values, but it will test against the actual null
%constant value.
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_null:N #1
  { p, T, F, TF }
{
  \bool_lazy_any:nTF
   {
     { \tl_if_eq_p:NN #1 \dtlnovalue }
     { \tl_if_eq_p:NN #1 \DTLstringnull }
     { \tl_if_eq_p:NN #1 \DTLnumbernull }
     { \tl_if_eq_p:NN #1 \c_datatool_nullvalue_tl }
   }
   { \prg_return_true: }
   { \prg_return_false: }
}
\cs_generate_variant:Nn \datatool_if_null:NTF { cTF }
%    \end{macrocode}
%Test for null where the argument may be a single token
%or may be a token list. If the argument is a single token, then
%the test is the same as the above otherwise the result will be
%false.
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_null:n #1 
  { T, F, TF }
{
  \tl_if_single_token:nTF { #1 }
  {
    \bool_lazy_any:nTF
     {
       { \tl_if_eq_p:NN #1 \dtlnovalue }
       { \tl_if_eq_p:NN #1 \DTLstringnull }
       { \tl_if_eq_p:NN #1 \DTLnumbernull }
       { \tl_if_eq_p:NN #1 \c_datatool_nullvalue_tl }
     }
     { \prg_return_true: }
     {
       \tl_if_eq:NnTF \dtlnovalue { #1 }
       { \prg_return_true: }
       {
         \tl_if_eq:NnTF \DTLstringnull { #1 }
         { \prg_return_true: }
         {
           \tl_if_eq:NnTF \DTLnumbernull { #1 }
           { \prg_return_true: }
           { \prg_return_false: }
         }
       }
     }
  }
  {
    \tl_if_eq:NnTF \c_datatool_nullvalue_tl { #1 }
     { \prg_return_true: }
     { \prg_return_false: }
  }
}
%    \end{macrocode}
%Test for null or empty. This includes testing for the empty datum.
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_null_or_empty:N #1 
  { p, T, F, TF }
{
  \bool_lazy_any:nTF
   {
     { \tl_if_empty_p:N #1 }
     { \tl_if_eq_p:NN #1 \dtlnovalue }
     { \tl_if_eq_p:NN #1 \DTLstringnull }
     { \tl_if_eq_p:NN #1 \DTLnumbernull }
     { \tl_if_eq_p:NN #1 \c_datatool_nullvalue_tl }
     { \tl_if_eq_p:NN #1 \c_datatool_empty_datum_tl }
   }
   { \prg_return_true: }
   { \prg_return_false: }
}
%    \end{macrocode}
%Test for null or empty where the argument may be a single token
%or may be a token list. If the argument is a single token, then
%the test is the same as the above. If the argument isn't a
%single token the result will be false unless the argument is empty.
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_null_or_empty:n #1 
  { T, F, TF }
{
  \tl_if_single_token:nTF { #1 }
  {
    \bool_lazy_any:nTF
     {
       { \tl_if_empty_p:N #1 }
       { \tl_if_eq_p:NN #1 \dtlnovalue }
       { \tl_if_eq_p:NN #1 \DTLstringnull }
       { \tl_if_eq_p:NN #1 \DTLnumbernull }
       { \tl_if_eq_p:NN #1 \c_datatool_nullvalue_tl }
       { \tl_if_eq_p:NN #1 \c_datatool_empty_datum_tl }
     }
     { \prg_return_true: }
     {
       \tl_if_eq:NnTF \dtlnovalue { #1 }
       { \prg_return_true: }
       {
         \tl_if_eq:NnTF \DTLstringnull { #1 }
         { \prg_return_true: }
         {
           \tl_if_eq:NnTF \DTLnumbernull { #1 }
           { \prg_return_true: }
           { \prg_return_false: }
         }
       }
     }
  }
  {
    \tl_if_empty:nTF { #1 }
    { \prg_return_true: }
    {
      \tl_if_eq:NnTF \c_datatool_nullvalue_tl { #1 }
       { \prg_return_true: }
       {
         \tl_if_eq:NnTF \c_datatool_empty_datum_tl { #1 }
         { \prg_return_true: }
         { \prg_return_false: }
       }
    }
  }
}
%    \end{macrocode}
%
%Datum marker. The first argument is the original content. The
%second should expand to a (non-locale) numeric representation or empty for
%non-numeric data. (The second argument may contain additional
%content that will ordinarily be discarded on expansion, but may be
%retrieved if required.)
%The third argument is the currency symbol or
%empty if not currency. The final argument is the integer data type identifier.
%    \begin{macrocode}
\cs_new:Nn \__datatool_datum:nnnn { \exp_not:n { #1 } }
%    \end{macrocode}
% This means that the datum control sequence content consists of five
% things: the datum marker and its four arguments. 
%
%The weird datum is designed to allow easy lookup in databases.
%It uses a similar trick to \sty{etoolbox} that uses the bar \verb"|"
%character with category code~3 as a marker.
%    \begin{macrocode}
\group_begin:
%    \end{macrocode}
%Locally change category code of \verb"|"
%    \begin{macrocode}
 \char_set_catcode_math_toggle:N \|
%    \end{macrocode}
%Create a constant token list that expands to this character with
%this category code.
%    \begin{macrocode}
 \tl_const:Nn \c__datatool_datum_weird_marker_tl { | }
%    \end{macrocode}
%Define the weird function to expand to the regular datum function.
%This is designed for expandable contexts to perform a quick conversion
% without expanding the arguments.
%    \begin{macrocode}
 \cs_new:Npn \__datatool_datum:w | #1 | #2 | #3 | #4 |
  {
    \exp_not:N \__datatool_datum:nnnn
     { \exp_not:n { #1 } }
     { \exp_not:n { #2 } }
     { \exp_not:n { #3 } }
     { \exp_not:n { #4 } }
  }
%    \end{macrocode}
%Allow marker to expand.
%    \begin{macrocode}
 \cs_new:Npn \__datatool_datum_use:w | #1 | #2 | #3 | #4 |
  {
    \__datatool_datum:nnnn
      { #1 } { #2 } { #3 } { #4 }
  }
%    \end{macrocode}
%Only the first argument (but don't expand that):
%    \begin{macrocode}
 \cs_new:Npn \__datatool_datum_use_i:w | #1 | #2 | #3 | #4 |
  {
    \exp_not:n { #1 }
  }
%    \end{macrocode}
%Define the reverse. Four arguments provided (datum marker not
%included). This only prevents the string and currency arguments
%from expanding.
%    \begin{macrocode}
 \cs_new:Nn \__datatool_weird_datum:nnnn
  {
    \exp_not:N \__datatool_datum:w 
      | \exp_not:n { #1 }
      | #2 
      | \exp_not:n { #3 }
      | #4 |
  }
%    \end{macrocode}
%Five arguments provided, starting with the
%datum marker that needs to be discarded.
%    \begin{macrocode}
 \cs_new:Nn \__datatool_weird_datum:Nnnnn
  {
    \exp_not:N \__datatool_datum:w
      | \exp_not:n { #2 }
      | \exp_not:n { #3 }
      | \exp_not:n { #4 }
      | \exp_not:n { #5 } |
  }
%    \end{macrocode}
%Parse content that starts with \cs{\_\_datatool\_weird\_datum:w}.
%    \begin{macrocode}
\cs_new:Npn \__datatool_get_weird_datum:w 
  \__datatool_datum:w | #1 | #2 | #3 | #4 | #5 \q_stop
 {
   \quark_if_nil:nTF { #5 }
   {
     \@dtl@datatype = #4
     \tl_set:Nn \l__datatool_datum_original_value_tl { #1 }
     \tl_set:Nn \l__datatool_datum_value_tl { #2 }
     \tl_set:Nn \l__datatool_datum_currency_tl { #3 }
   }
   {
     \@dtl@datatype = \c_datatool_string_int
   }
 }
%    \end{macrocode}
%Restore category code.
%    \begin{macrocode}
\group_end:
%    \end{macrocode}
%
%Test for equality where one or other value might be in either datum
%format.
%
%Syntax: \meta{tl var1} \meta{tl var2} \marg{true}\marg{false}
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_value_eq:NN #1 #2 
   { T, F, TF }
 {
   \exp_args:NV \tl_if_head_eq_meaning:nNTF
      #1 \__datatool_datum:w
    {
      \exp_args:NV \tl_if_head_eq_meaning:nNTF
         #2 \__datatool_datum:w
       {
         \datatool_if_value_eq:eeTF
          { #1 } { #2 } 
          { \prg_return_true: }
          { \prg_return_false: }
       }
       {
         \datatool_if_value_eq:eVTF
          { #1 } #2 
          { \prg_return_true: }
          { \prg_return_false: }
       }
    }
    {
      \exp_args:NV \tl_if_head_eq_meaning:nNTF
         #2 \__datatool_datum:w
       {
         \datatool_if_value_eq:VeTF
           #1 { #2 }  
          { \prg_return_true: }
          { \prg_return_false: }
       }
       {
         \datatool_if_value_eq:VVTF #1 #2 
          { \prg_return_true: }
          { \prg_return_false: }
       }
    }
 }
%    \end{macrocode}
%Syntax: \meta{tl var} \marg{tl}\marg{true}\marg{false}
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_value_eq:Nn #1 #2 
   { T, F, TF }
 {
   \exp_args:NV \tl_if_head_eq_meaning:nNTF
      #1 \__datatool_datum:w
    {
      \datatool_if_value_eq:enTF
       { #1 } { #2 }  
       { \prg_return_true: }
       { \prg_return_false: }
    }
    {
      \datatool_if_value_eq:VnTF #1 { #2 } 
       { \prg_return_true: }
       { \prg_return_false: }
    }
 }
%    \end{macrocode}
%Syntax: \marg{tl} \meta{tl var} \marg{true}\marg{false}
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_value_eq:nN #1 #2 
   { T, F, TF }
 {
   \exp_args:NV \tl_if_head_eq_meaning:nNTF
      #2 \__datatool_datum:w
    {
      \datatool_if_value_eq:neTF
       { #1 } { #2 }  
       { \prg_return_true: }
       { \prg_return_false: }
    }
    {
      \datatool_if_value_eq:nVTF { #1 } #2 
       { \prg_return_true: }
       { \prg_return_false: }
    }
 }
%    \end{macrocode}
%Syntax: \marg{tl1}\marg{tl2}\marg{true}\marg{false}
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_value_eq:nn #1 #2 
   { T, F, TF }
 {
   \tl_if_eq:nnTF { #1 } { #2 }
    { \prg_return_true: }
    {
      \tl_if_head_eq_meaning:nNTF { #1 } \__datatool_datum:nnnn
       {
         \tl_if_head_eq_meaning:nNTF { #2 } \__datatool_datum:nnnn
          {
%    \end{macrocode}
%Both in datum format. If both numeric use numeric comparison,
%otherwise use string comparison.
%    \begin{macrocode}
            \bool_lazy_and:nnTF
             { 
               \int_compare_p:nNn
                 { \__datatool_datum_type:n { #1 } }
                   >
                 { \c_datatool_string_int }
             }
             { 
               \int_compare_p:nNn
                 { \__datatool_datum_type:n { #2 } }
                   >
                 { \c_datatool_string_int }
             }
             {
%    \end{macrocode}
%Both numeric. Test both the value and currency symbol.
%    \begin{macrocode}
               \tl_if_eq:eeTF
                { \__datatool_datum_currency:n { #1 } }
                { \__datatool_datum_currency:n { #2 } }
                {
                  \fp_compare:nNnTF 
                    { \__datatool_datum_value:n { #1 } }
                    = 
                    { \__datatool_datum_value:n { #2 } }
                   { \prg_return_true: }
                   { \prg_return_false: }
                }
                { \prg_return_false: }
             }
             {
%    \end{macrocode}
%One or other isn't numeric. Compare string values only.
%    \begin{macrocode}
               \tl_if_eq:eeTF
                 { \__datatool_datum_string:n { #1 } }
                 { \__datatool_datum_string:n { #2 } }
                { \prg_return_true: }
                { \prg_return_false: }
             }
          }
          {
%    \end{macrocode}
%First in datum format, second isn't. Compare string values only
%    \begin{macrocode}
            \tl_if_eq:enTF { #1 } { #2 }
             { \prg_return_true: }
             { \prg_return_false: }
          }
       }
       {
%    \end{macrocode}
%First isn't in datum format. Compare string values only
%    \begin{macrocode}
         \tl_if_eq:enTF { #1 } { #2 }
          { \prg_return_true: }
          { \prg_return_false: }
       }
    }
 }
\cs_generate_variant:Nn \datatool_if_value_eq:nnTF
 { VVTF , VnTF , nVTF, eeTF, neTF, enTF, eVTF, VeTF }
%    \end{macrocode}
%Function to convert a token list variable so that it contains a
%weird datum function if the original contained a datum marker but
%don't parse and convert otherwise.
%    \begin{macrocode}
\cs_new:Nn \__datatool_to_weird_datum_no_parse:N 
{
  \exp_args:NV \__datatool_to_weird_datum_no_parse:nN #1 #1
}
\cs_new:Nn \__datatool_to_weird_datum_no_parse:nN 
{
  \tl_if_head_eq_meaning:nNTF
    { #1 } \__datatool_datum:nnnn
   {
     \tl_set:Nx #2 { \__datatool_weird_datum:Nnnnn #1 }
   }
   {
     \tl_set:Nn #2 { #1 }
   }
}
%    \end{macrocode}
%Function to convert a token list variable so that it contains a
%weird datum function.
%    \begin{macrocode}
\cs_new:Nn \__datatool_to_weird_datum:N 
{
  \exp_args:NV \__datatool_to_weird_datum:nN #1 #1
}
\cs_new:Nn \__datatool_to_weird_datum:nN 
{
  \tl_if_head_eq_meaning:nNTF
     { #1 } \__datatool_datum:w
   {
%    \end{macrocode}
%Already in weird format.
%    \begin{macrocode}
     \tl_set:Nn #2 { #1 }
   }
   {
%    \end{macrocode}
%Convert to datum.
%    \begin{macrocode}
     \__datatool_to_datum:nN { #1 } #2
%    \end{macrocode}
%Leave null unprocessed.
%    \begin{macrocode}
     \datatool_if_null:NF #2
      {
%    \end{macrocode}
%At this point the token list variable should start with the datum
%marker but check to make sure.
%    \begin{macrocode}
        \tl_if_head_eq_meaning:nNT
          { #1 } \__datatool_datum:nnnn
         {
           \tl_put_left:Nn #2 { \__datatool_weird_datum:Nnnnn }
           \tl_set:Nx #2 { #2 }
         }
      }
  }
}
%    \end{macrocode}
%Function to convert a token list variable so that it contains a
%normal datum function.
%    \begin{macrocode}
\cs_new:Nn \__datatool_to_datum:N
 {
   \exp_args:NV \__datatool_to_datum:nN #1 #1
 }
%    \end{macrocode}
%Function to convert a token list so that it contains a
%normal datum function and save it in the token list variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_to_datum:nN 
{
 \datatool_if_null:nTF { #1 }
  {
%    \end{macrocode}
%Set variable to null.
%    \begin{macrocode}
    \tl_set_eq:NN #2 \dtlnovalue
  }
  {
   \tl_if_head_eq_meaning:nNTF
      { #1 } \__datatool_datum:w
    {
%    \end{macrocode}
%Convert from weird format:
%    \begin{macrocode}
      \tl_set:Nx #2 { #1 }
    }
    {
      \tl_if_head_eq_meaning:nNTF
        { #1 } \__datatool_datum:nnnn
       {
%    \end{macrocode}
%Already in the correct format:
%    \begin{macrocode}
         \tl_set:Nn #2 { #1 }
       }
       {
%    \end{macrocode}
%Needs parsing.
%    \begin{macrocode}
         \__datatool_parse_datum:n { #1 }
         \tl_if_empty:NTF \l__datatool_datum_update_value_tl
          {
            \int_compare:nNnTF
              { \@dtl@datatype }
                =
              { \c_datatool_unknown_int }
            {
%    \end{macrocode}
% NB this may be blank (only a space) not empty, but it's still classed as
% unknown.
%    \begin{macrocode}
              \tl_set:Nn #2 
               {
                 \__datatool_datum:nnnn { #1 } { } { }
                 { \c_datatool_unknown_int }
               }
            }
            {
              \tl_set_eq:NN #2 \l__datatool_datum_original_value_tl
            }
         }
         {
           \tl_set_eq:NN #2 \l__datatool_datum_update_value_tl
         }
      }
    }
  }
}
%    \end{macrocode}
%Similar but don't parse. Only convert if it starts with weird form.
%    \begin{macrocode}
\cs_new:Nn \__datatool_rm_weird_datum:N 
{
  \exp_args:NV \__datatool_rm_weird_datum:nN #1 #1
}
\cs_new:Nn \__datatool_rm_weird_datum:nN 
{
 \tl_if_head_eq_meaning:nNTF
    { #1 } \__datatool_datum:w
  {
%    \end{macrocode}
%Convert from weird format:
%    \begin{macrocode}
    \tl_set:Nx #2 { #1 }
  }
  {
    \tl_set:Nn #2 { #1 }
  }
}
%    \end{macrocode}
%Remove either datum.
%    \begin{macrocode}
\cs_new:Nn \__datatool_rm_datum:N 
{
  \exp_args:NV \__datatool_rm_datum:nN #1 #1
}
\cs_new:Nn \__datatool_rm_datum:nN 
{
 \__datatool_rm_weird_datum:nN { #1 } #2
 \exp_args:NV \tl_if_head_eq_meaning:nNT
    #2 \__datatool_datum:nnnn
  {
%    \end{macrocode}
%Convert from datum format:
%    \begin{macrocode}
    \tl_set:Nx #2 { #2 }
  }
}
%    \end{macrocode}
%
%Used to encapsulate floating point datum value.
%The first argument is the (non-locale) original floating point value. The second
%is the \sty{l3fp} content to allow for reconstruction. The third is
%the decimal value for use where scientific notation can't be
%parsed. For example, with \sty{fp} functions.
%    \begin{macrocode}
\tl_if_eq:NnTF \@dtl@mathprocessor { fp }
 {
   \cs_new:Nn \datatool_datum_fp:nnn { #3 }
 } 
 {
   \cs_new:Nn \datatool_datum_fp:nnn { #1 }
 }
%    \end{macrocode}
%Pick out the \sty{l3fp} content:
%    \begin{macrocode}
\cs_new:Nn \datatool_datum_fp:Nnnn { \exp_not:n { #3 } }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \datatool_set_fp:Nn
 {
  \tl_if_single_token:nTF { #2 }
   {
     \exp_args:No \tl_if_head_eq_meaning:nNTF { #2 } \__datatool_datum:nnnn 
      {
        \exp_last_unbraced:NV \__datatool_get_datum:w #2 \q_nil \q_stop 
      }
      {
        \exp_args:No \tl_if_head_eq_meaning:nNTF { #2 }  \__datatool_datum:w 
         {
           \exp_last_unbraced:NV \__datatool_get_weird_datum:w #2 \q_nil \q_stop
         }
         {
           \exp_args:No \__datatool_parse_datum:n { #2 }
         }
      }
   }
   {
      \tl_if_head_eq_meaning:nNTF { #2 } \__datatool_datum:nnnn 
      {
        \__datatool_get_datum:w #2 \q_nil \q_stop 
      }
      {
        \tl_if_head_eq_meaning:nNTF { #2 } \__datatool_datum:w 
         {
           \__datatool_get_weird_datum:w #2 \q_nil \q_stop
         }
         {
           \__datatool_parse_datum:n { #2 }
         }
      }
   }
  \datatool_if_numeric_datum_type:nF { \@dtl@datatype }
   {
     \exp_args:Nx \__datatool_parse_datum:n
      { \tl_trim_spaces:e { \text_expand:n { #2 } } }
   }
  \tl_if_empty:NTF \l__datatool_datum_value_tl
   {
     \fp_zero:N #1
   }
   {
     \exp_args:NV \tl_if_head_eq_meaning:nNTF 
       \l__datatool_datum_value_tl
       { \datatool_datum_fp:nnn }
      {
        \tl_set:Nx #1
         {
           \exp_after:wN \datatool_datum_fp:Nnnn \l__datatool_datum_value_tl
         }
      }
      {
        \fp_set:Nn #1 { \l__datatool_datum_value_tl }
      }
   }
 }
\cs_generate_variant:Nn \datatool_set_fp:Nn { NV, No }
%    \end{macrocode}
%
%\begin{macro}{\DTLusedatum}
%\begin{definition}
%\cs{DTLusedatum}\marg{tl}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\cs_new:Nn \__datatool_datum_string:n { \datatool_datum_string:Nnnnn #1 }
\newcommand{\DTLusedatum} [1]
 {
   \tl_if_eq:NNTF #1 \dtlnovalue
   { \dtlnovalue }
   { \exp_args:NV \__datatool_datum_string:n #1 }
 }
%    \end{macrocode}
%\end{macro}
%At the time of writing there were no \cs{use...} commands for five
%arguments. There now are, but these are provided anyway in case of
%a slightly older kernel and they are better semantically.
%    \begin{macrocode}
\cs_new:Nn \datatool_datum_string:Nnnnn { #2 }
%    \end{macrocode}
%
%\begin{macro}{\DTLdatumvalue}
%\begin{definition}
%\cs{DTLdatumvalue}\marg{tl}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\cs_new:Nn \__datatool_datum_value:n { \datatool_datum_value:Nnnnn #1 }
\newcommand{\DTLdatumvalue} [1]
 {
   \tl_if_eq:NNTF #1 \dtlnovalue
   { \DTLnumbernull }
   { \exp_args:NV \__datatool_datum_value:n #1 }
 }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \datatool_datum_value:Nnnnn { #3 }
%    \end{macrocode}
%
%\begin{macro}{\DTLdatumcurrency}
%\begin{definition}
%\cs{DTLdatumcurrency}\marg{tl}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\cs_new:Nn \__datatool_datum_currency:n { \datatool_datum_currency:Nnnnn #1 }
\newcommand{\DTLdatumcurrency} [1]
 {
   \tl_if_eq:NNTF #1 \dtlnovalue
   { \dtlnovalue }
   { \exp_args:NV \__datatool_datum_currency:n #1 }
 }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \datatool_datum_currency:Nnnnn { \exp_not:n { #4 } }
%    \end{macrocode}
%
%\begin{macro}{\DTLdatumtype}
%\begin{definition}
%\cs{DTLdatumtype}\marg{tl}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\cs_new:Nn \__datatool_datum_type:n { \datatool_datum_type:Nnnnn #1 }
\newcommand{\DTLdatumtype} [1]
 { 
   \tl_if_eq:NNTF #1 \dtlnovalue
   { \int_use:N \c_datatool_unknown_int }
   { \int_eval:n { \exp_args:NV \__datatool_datum_type:n #1 } }
 }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \datatool_datum_type:Nnnnn { #5 }
%    \end{macrocode}
%
%Debugging:
%    \begin{macrocode}
\cs_new:Nn \datatool_datum_show:N
 {
  \int_compare:nNnTF { \tl_count:N #1 } = { 5 }
   {
     \exp_after:wN \__datatool_datum_show:NNnnnn
      \exp_after:wN #1 #1
   }
   {
    \datatool_if_null:NTF #1
     {
       \msg_show:nnnV
        { datatool-base }
        { show-datum-var-null }
        { #1 }
        #1
     }
     {
       \tl_if_empty:NTF #1
        {
          \msg_show:nnn
           { datatool-base }
           { show-datum-var-empty }
           { #1 }
        }
        {
         \PackageError { datatool-base } 
          { \tl_to_str:N #1 \c_space_tl is ~ not ~ a ~ datum ~ variable }
          { }
        }
     }
   }
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_datum_show:NNnnnn
 {
  \tl_if_eq:NNTF #2 \__datatool_datum:nnnn 
   {
     \int_case:nnF { #6 }
      {
        { \c_datatool_unknown_int }
         {
           \msg_show:nnnn
             { datatool-base }
             { show-datum-var-unset }
             { #1 }
             { #3 }
         }
        { \c_datatool_string_int }
         {
           \msg_show:nnnn
             { datatool-base }
             { show-datum-var-string }
             { #1 }
             { #3 }
         }
        { \c_datatool_integer_int }
         {
           \msg_show:nnnnn
             { datatool-base }
             { show-datum-var-integer }
             { #1 }
             { #3 }
             { #4 }
         }
        { \c_datatool_decimal_int }
         {
           \msg_show:nnnnn
             { datatool-base }
             { show-datum-var-decimal }
             { #1 }
             { #3 }
             { #4 }
         }
        { \c_datatool_currency_int }
         {
           \msg_show:nnnnnn
             { datatool-base }
             { show-datum-var-currency }
             { #1 }
             { #3 }
             { #4 }
             { #5 }
         }
        { \c_datatool_datetime_int }
         {
           \msg_show:nnnnn
             { datatool-base }
             { show-datum-var-datetime }
             { #1 }
             { #3 }
             { #4 }
         }
        { \c_datatool_date_int }
         {
           \msg_show:nnnnn
             { datatool-base }
             { show-datum-var-date }
             { #1 }
             { #3 }
             { #4 }
         }
        { \c_datatool_time_int }
         {
           \msg_show:nnnnn
             { datatool-base }
             { show-datum-var-time }
             { #1 }
             { #3 }
             { #4 }
         }
      }
      {
        \PackageError { datatool-base } 
         {
           datum ~ variable ~
           \tl_to_str:N #1 \c_space_tl has ~ invalid ~ data ~ type: ~
           \int_eval:n { #6 }
         }
         { }
      }
   }
   {
    \tl_if_eq:NNTF #2  \__datatool_datum:w 
      {
        \int_case:nnF { #6 }
         {
           { \c_datatool_unknown_int }
            {
              \msg_show:nnnn
                { datatool-base }
                { show-weird-datum-var-unset }
                { #1 }
                { #3 }
            }
           { \c_datatool_string_int }
            {
              \msg_show:nnnn
                { datatool-base }
                { show-weird-datum-var-string }
                { #1 }
                { #3 }
            }
           { \c_datatool_integer_int }
            {
              \msg_show:nnnnn
                { datatool-base }
                { show-weird-datum-var-integer }
                { #1 }
                { #3 }
                { #4 }
            }
           { \c_datatool_decimal_int }
            {
              \msg_show:nnnnn
                { datatool-base }
                { show-weird-datum-var-decimal }
                { #1 }
                { #3 }
                { #4 }
            }
           { \c_datatool_currency_int }
            {
              \msg_show:nnnnnn
                { datatool-base }
                { show-weird-datum-var-currency }
                { #1 }
                { #3 }
                { #4 }
                { #5 }
            }
           { \c_datatool_datetime_int }
            {
              \msg_show:nnnnn
                { datatool-base }
                { show-weird-datum-var-datetime }
                { #1 }
                { #3 }
                { #4 }
            }
           { \c_datatool_date_int }
            {
              \msg_show:nnnnn
                { datatool-base }
                { show-weird-datum-var-date }
                { #1 }
                { #3 }
                { #4 }
            }
           { \c_datatool_time_int }
            {
              \msg_show:nnnnn
                { datatool-base }
                { show-weird-datum-var-time }
                { #1 }
                { #3 }
                { #4 }
            }
         }
         {
           \PackageError { datatool-base } 
            {
              weird ~ datum ~ variable ~
              \tl_to_str:N #1 \c_space_tl has ~ invalid ~ data ~ type: ~
              \int_eval:n { #6 }
            }
            { }
         }
      }
      {
        \PackageError { datatool-base } 
         {
           Can't ~ show ~ \tl_to_str:N #1 : ~
           \tl_to_str:N #2 \c_space_tl is ~ not ~ a ~ datum ~ marker
         }
         { }
      }
   }
 }
%    \end{macrocode}
% Messages:
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-datum-var-null }
 {
   variable ~ \tl_to_str:N #1 \c_space_tl is ~ null \\
   > ~ #2
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-datum-var-empty }
 {
   variable ~ \tl_to_str:N #1 \c_space_tl is ~ empty
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-datum-var-unset }
 {
   Showing ~ unset ~ datum ~ variable ~ \tl_to_str:N #1 \\
   > ~ string ~ value ~ ` #2 '
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-weird-datum-var-unset }
 {
   Showing ~ unset ~ weird ~ datum ~ variable ~ \tl_to_str:N #1 \\
   > ~ string ~ value ~ ` #2 '
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-datum-var-string }
 {
   Showing ~ string ~ datum ~ variable ~ \tl_to_str:N #1 \\
   > ~ string ~ value ~ #2
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-weird-datum-var-string }
 {
   Showing ~ string ~ weird ~ datum ~ variable ~ \tl_to_str:N #1 \\
   > ~ string ~ value ~ #2
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-datum-var-integer }
 {
   Showing ~ integer ~ datum ~ variable ~ \tl_to_str:N #1 \\
   > ~ string ~ value ~ #2 \\
   > ~ numeric ~ value ~ #3
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-weird-datum-var-integer }
 {
   Showing ~ integer ~ weird ~ datum ~ variable ~ \tl_to_str:N #1 \\
   > ~ string ~ value ~ #2 \\
   > ~ numeric ~ value ~ #3
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-datum-var-decimal }
 {
   Showing ~ decimal ~ datum ~ variable ~ \tl_to_str:N #1 \\
   > ~ string ~ value ~ #2 \\
   > ~ numeric ~ value ~ #3
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-weird-datum-var-decimal }
 {
   Showing ~ decimal ~ weird ~ datum ~ variable ~ \tl_to_str:N #1 \\
   > ~ string ~ value ~ #2 \\
   > ~ numeric ~ value ~ #3
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-datum-var-currency }
 {
   Showing ~ currency ~ datum ~ variable ~ \tl_to_str:N #1 \\ 
   > ~ string ~ value ~ #2 \\
   > ~ numeric ~ value ~ #3 \\
   > ~ currency ~ symbol ~ #4
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-weird-datum-var-currency }
 {
   Showing ~ currency ~ weird ~ datum ~ variable ~ \tl_to_str:N #1 \\
   > ~ string ~ value ~ #2 \\
   > ~ numeric ~ value ~ #3 \\
   > ~ currency ~ symbol ~ #4
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-datum-var-datetime }
 {
   Showing ~ datetime ~ datum ~ variable ~ \tl_to_str:N #1 \\
   > ~ string ~ value ~ #2 \\
   > ~ numeric ~ value ~ #3
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-weird-datum-var-datetime }
 {
   Showing ~ datetime ~ weird ~ datum ~ variable ~ \tl_to_str:N #1 \\
   > ~ string ~ value ~ #2 \\
   > ~ numeric ~ value ~ #3
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-datum-var-date }
 {
   Showing ~ date ~ datum ~ variable ~ \tl_to_str:N #1 \\
   > ~ string ~ value ~ #2 \\
   > ~ numeric ~ value ~ #3
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-weird-datum-var-date }
 {
   Showing ~ date ~ weird ~ datum ~ variable ~ \tl_to_str:N #1 \\
   > ~ string ~ value ~ #2 \\
   > ~ numeric ~ value ~ #3
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-datum-var-time }
 {
   Showing ~ time ~ datum ~ variable ~ \tl_to_str:N #1 \\
   > ~ string ~ value ~ #2 \\
   > ~ numeric ~ value ~ #3
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\msg_new:nnn { datatool-base }
 { show-weird-datum-var-time }
 {
   Showing ~ time ~ weird ~ datum ~ variable ~ \tl_to_str:N #1 \\
   > ~ string ~ value ~ #2 \\
   > ~ numeric ~ value ~ #3
 }
%    \end{macrocode}
%
%The above document commands can be used on \meta{cs} obtained from parsing
%data. Document commands:
%\begin{macro}{\DTLparse}
%\begin{definition}
%\cs{DTLparse}\marg{cs}\marg{arg}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%Note that any dates or times will be treated as strings.
%    \begin{macrocode}
\NewDocumentCommand \DTLparse { m m } 
 {
   \__datatool_parse:Nn #1 { #2 }
 }
%    \end{macrocode}
%\end{macro}
%Internal function easier to remember the argument order:
%    \begin{macrocode}
\cs_new:Nn \__datatool_parse:Nn
{
  \tl_clear:N \l__datatool_datum_value_tl 
  \tl_clear:N \l__datatool_datum_currency_tl 
  \tl_clear:N \l__datatool_datum_update_value_tl 
  \datatool_if_null:nTF { #2 }
   {
     \tl_set_eq:NN #1 \dtlnovalue
     \tl_set:Nn \l__datatool_datum_original_value_tl { #2 }
     \int_set_eq:NN \@dtl@datatype \c_datatool_unknown_int
   }
   {
     \tl_if_head_eq_meaning:nNTF
       { #2 } \__datatool_datum:w
      {
%    \end{macrocode}
%Convert from weird format:
%    \begin{macrocode}
        \tl_set:Nx #1 { #2 }
      }
      {
        \tl_set:Nn #1 { #2 }
      }
     \exp_args:NV \tl_if_head_eq_meaning:nNTF
       #1 \__datatool_datum:nnnn
      {
        \int_set:Nn \@dtl@datatype
          { \exp_args:NV \__datatool_datum_type:n #1 }
        \tl_set:Nx \l__datatool_datum_original_value_tl { #1 }
        \tl_set:Nx \l__datatool_datum_currency_tl 
          { \exp_args:NV \__datatool_datum_currency:n #1  }
        \tl_set:Nx \l__datatool_datum_value_tl
          { \exp_args:NV \__datatool_datum_value:n #1 }
      }
      {
       \exp_args:NV \__datatool_parse_datum:n #1
       \tl_if_empty:NTF \l__datatool_datum_update_value_tl
        {
           \int_compare:nNnTF
             { \@dtl@datatype }
               =
             { \c_datatool_unknown_int }
           {
%    \end{macrocode}
% NB this may be blank (only a space) not empty, but it's still classed as
% unknown.
%    \begin{macrocode}
             \tl_set:Nn #1 
              {
                \__datatool_datum:nnnn { #2 } { } { }
                { \c_datatool_unknown_int }
              }
           }
           {
             \tl_set_eq:NN #1 \l__datatool_datum_original_value_tl
           }
        }
        {
          \tl_set_eq:NN #1 \l__datatool_datum_update_value_tl
        }
     }
   }
 }
\cs_generate_variant:Nn \__datatool_parse:Nn
  { NV }
%    \end{macrocode}
%    \begin{macrocode}
\cs_new:Nn \__datatool_parse:N
 {
   \exp_args:NNV \__datatool_parse:Nn #1 #1
 }
%    \end{macrocode}
%\begin{macro}{\DTLxparse}
%\begin{definition}
%\cs{DTLxparse}\marg{cs}\marg{arg}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%As above but expands second argument.
%    \begin{macrocode}
\NewDocumentCommand \DTLxparse { m m } 
 {
   \exp_args:NNx \__datatool_parse:Nn #1 { #2 }
 }
%    \end{macrocode}
%\end{macro}
%
%Low-level set datum parts.
%\begin{definition}
%\cs{datatool\_set\_datum:Nnnnn} \meta{datum-var} \marg{string}
%\marg{value} \marg{currency symbol} \marg{type}
%\end{definition}
%The type may be an integer expression
%    \begin{macrocode}
\cs_new:Nn \datatool_set_datum:Nnnnn
 {
   \int_case:nnF { #5 }
    {
      { \c_datatool_unknown_int }
       {
         \tl_set:Nn #1
          {
            \__datatool_datum:nnnn
              { #2 } { } { } { \c_datatool_unknown_int }
          }
       }
      { \c_datatool_string_int }
       {
         \tl_set:Nn #1
          {
            \__datatool_datum:nnnn
              { #2 } { } { } { \c_datatool_string_int }
          }
       }
      { \c_datatool_integer_int }
       {
         \tl_set:Nn #1
          {
            \__datatool_datum:nnnn
              { #2 } { #3 } { } { \c_datatool_integer_int }
          }
       }
      { \c_datatool_decimal_int }
       {
         \tl_set:Nn #1
          {
            \__datatool_datum:nnnn
              { #2 } { #3 } { } { \c_datatool_decimal_int }
          }
       }
      { \c_datatool_currency_int }
       {
         \tl_set:Nn #1
          {
            \__datatool_datum:nnnn
              { #2 } { #3 } { #4 } { \c_datatool_currency_int }
          }
       }
      { \c_datatool_datetime_int }
       {
         \tl_set:Nn #1
          {
            \__datatool_datum:nnnn
              { #2 } { #3 } { } { \c_datatool_datetime_int }
          }
       }
      { \c_datatool_date_int }
       {
         \tl_set:Nn #1
          {
            \__datatool_datum:nnnn
              { #2 } { #3 } { } { \c_datatool_date_int }
          }
       }
      { \c_datatool_time_int }
       {
         \tl_set:Nn #1
          {
            \__datatool_datum:nnnn
              { #2 } { #3 } { } { \c_datatool_time_int }
          }
       }
    }
    {
      \PackageError { datatool-base }
        { Unsupported ~ datum ~ type ~ \int_eval:n { #5 } }
        { }
    }
 }
\cs_generate_variant:Nn \datatool_set_datum:Nnnnn
  { Neeen , Nnenn , NnVVn , NnVnn }
%    \end{macrocode}
% No sanity check:
%    \begin{macrocode}
\cs_new:Nn \__datatool_set_datum:Nnnnn
 {
  \tl_set:Nn #1
   {
     \__datatool_datum:nnnn { #2 } { #3 } { #4 } { #5 }
   }
 }
\cs_generate_variant:Nn \__datatool_set_datum:Nnnnn
  { Neeen , NnVnn , NeVnn }
%    \end{macrocode}
%
%Document-level commands:
%\begin{macro}{\DTLsetintegerdatum}
%\begin{definition}
%\cs{DTLsetintegerdatum}\marg{cs}\marg{formatted value}\marg{value}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%Sets \meta{cs} to an integer datum.
%    \begin{macrocode}
\NewDocumentCommand \DTLsetintegerdatum { m m m } 
 {
   \tl_set:Nn #1
    {
      \__datatool_datum:nnnn
        { #2 } { #3 } { }
        { \c_datatool_integer_int }
    }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLxsetintegerdatum}
%\begin{definition}
%\cs{DTLxsetintegerdatum}\marg{cs}\marg{formatted value}\marg{value}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%As above but expands \meta{formatted value} and \meta{value}.
%    \begin{macrocode}
\NewDocumentCommand \DTLxsetintegerdatum { m m m } 
 {
   \exp_args:Neee \DTLsetintegerdatum { \exp_not:N #1 }
    { #2 } { #3 }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsetfpdatum}
%\begin{definition}
%\cs{DTLsetdecimaldatum}\marg{cs}\marg{formatted value}\marg{value}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%Sets \meta{cs} to a floating point datum.
%    \begin{macrocode}
\NewDocumentCommand \DTLsetfpdatum { m m m } 
 {
   \fp_set:Nn \l__datatool_datum_value_fp { #3 }
   \tl_set:Nx #1
    {
      \exp_not:N \__datatool_datum:nnnn
       { \exp_not:n { #2 } }
       { 
         \exp_not:N \datatool_datum_fp:nnn
          { #3 }
          { \exp_not:V \l__datatool_datum_value_fp }
          { \fp_to_decimal:N \l__datatool_datum_value_fp }
       }
       { }
       { \c_datatool_decimal_int }
    }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsetdecimaldatum}
%\begin{definition}
%\cs{DTLsetdecimaldatum}\marg{cs}\marg{formatted value}\marg{value}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%Sets \meta{cs} to a decimal datum.
%    \begin{macrocode}
\NewDocumentCommand \DTLsetdecimaldatum { m m m } 
 {
   \tl_set:Nn #1
    {
      \__datatool_datum:nnnn
       { #2 } { #3 } { }
       { \c_datatool_decimal_int }
    }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLxsetdecimaldatum}
%\begin{definition}
%\cs{DTLsxetdecimaldatum}\marg{cs}\marg{formatted value}\marg{value}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%As above but expands \meta{formatted value} and \meta{value}.
%    \begin{macrocode}
\NewDocumentCommand \DTLxsetdecimaldatum { m m m } 
 {
   \exp_args:Neee \DTLsetdecimaldatum { \exp_not:N #1 }
    { #2 } { #3 }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsetcurrencydatum}
%\begin{definition}
%\cs{DTLsetcurrencydatum}\marg{cs}\marg{formatted
%value}\marg{value}\marg{currency symbol}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%Sets \meta{cs} to a currency datum.
%    \begin{macrocode}
\NewDocumentCommand \DTLsetcurrencydatum { m m m m } 
 {
   \tl_set:Nn #1
    { 
      \__datatool_datum:nnnn
       { #2 } { #3 } { #4 }
       { \c_datatool_currency_int }
    }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLxsetcurrencydatum}
%\begin{definition}
%\cs{DTLxsetcurrencydatum}\marg{cs}\marg{formatted
%value}\marg{value}\marg{currency symbol}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%As above but expands \meta{formatted value}, \meta{value} and
%\meta{currency symbol}.
%    \begin{macrocode}
\NewDocumentCommand \DTLxsetcurrencydatum { m m m m } 
 {
   \exp_args:Neee \DTLsetcurrencydatum { \exp_not:N #1 }
    { #2 } { #3 } { #4 }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsettemporaldatum}
%\begin{definition}
%\cs{DTLsettemporaldatum}\marg{cs}\marg{formatted value}\marg{ISO}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%Sets \meta{cs} to a date/time datum. The actual type is determined
%by parsing \meta{ISO}. If the formatted value should be
%automatically calculated, use 
%\cs{DTLsetup}\{datetime=reformat\}\cs{DTLparse}\marg{cs}\marg{ISO}
%and adjust the formatting commands as applicable.
%    \begin{macrocode}
\NewDocumentCommand \DTLsettemporaldatum { m m m } 
 {
   \__datatool_parse_datetime:nTF { #3 }
    {
       \__datatool_set_datetime_value:Nn #1 { #2 }
    }
    {
       \PackageError { datatool-base }
        { Invalid ~ date/time format ~ ` \tl_to_str:n { #3 } '}
        {
          The ~ date/time ~ value ~ needs ~ to ~ be ~ a ~
          date ~ in ~ the ~ form ~ YYYY-MM-DD ~ or ~ a ~
          time ~ in ~ the ~ form ~ hh:mm ~ or ~ hh:mm:ss ~ or ~ a ~
          timestamp ~ in ~ the ~ form ~ YYYY-MM-DD ~ hh:mm:ssTZ ~
          where ~ the ~ timezone ~ TZ ~ is ~ optional ~ 
          and ~ may ~ be ~ the ~ letter ~
          `Z' ~ or ~ in ~ the ~ form ~ [+-]hh:mm ~
          (if ~ omitted ~ `Z' ~ is ~ assumed). ~ The ~
          separator ~ may ~ either ~ be ~ a ~ space ~ or ~
          the ~ letter ~ `T'
        }
    }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLxsettemporaldatum}
%\begin{definition}
%\cs{DTLxsetdatetimedatum}\marg{cs}\marg{formatted value}\marg{ISO}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%As above but expands all but the first argument.
%    \begin{macrocode}
\NewDocumentCommand \DTLxsettemporaldatum { m m m } 
 {
   \exp_args:Neee \DTLsettemporaldatum { \exp_not:N #1 }
    { #2 } { #3 } 
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsetstringdatum}
%\begin{definition}
%\cs{DTLsetstringdatum}\marg{cs}\marg{string}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%Sets \meta{cs} to a string datum.
%    \begin{macrocode}
\NewDocumentCommand \DTLsetstringdatum { m m } 
 {
   \tl_set:Nn #1
    {
      \__datatool_datum:nnnn
      { #2 } { } { }
      { \c_datatool_string_int }
    }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLxsetstringdatum}
%\begin{definition}
%\cs{DTLxsetstringdatum}\marg{cs}\marg{string}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%As above but expands \meta{string}.
%    \begin{macrocode}
\NewDocumentCommand \DTLxsetstringdatum { m m } 
 {
   \exp_args:NNe \DTLxsetstringdatum #1 { #2 }
 }
%    \end{macrocode}
%\end{macro}
%
%Temporary storage used when extracting data.
%The original value:
%    \begin{macrocode}
\tl_new:N \l__datatool_datum_original_value_tl 
%    \end{macrocode}
%The numeric value:
%    \begin{macrocode}
\tl_new:N \l__datatool_datum_value_tl 
%    \end{macrocode}
%The currency symbol:
%    \begin{macrocode}
\tl_new:N \l__datatool_datum_currency_tl 
%    \end{macrocode}
%The currency code:
%    \begin{macrocode}
\tl_new:N \l__datatool_datum_currency_code_tl 
%    \end{macrocode}
%If the data hasn't been parsed yet, the following is set to the
%tagged information:
%    \begin{macrocode}
\tl_new:N \l__datatool_datum_update_value_tl
%    \end{macrocode}
%Regular expression for scientific notation.
%Version 3.4: bug fix (expression needs to be anchored)
%    \begin{macrocode}
\regex_const:Nn \c_datatool_sci_regex
 { \A [+\-]? [0-9]* \.? [0-9]+ \s* [eE] [+\-]? [0-9]+ \Z }
%    \end{macrocode}
%Determine data type. For temporal data types, the argument must
%match the relevant format. See
%\verb|\__datatool_parse_datetime_if_enabled:n| and 
%\verb|\__datatool_parse_datetime:n| conditionals.
%    \begin{macrocode}
\cs_new:Nn \__datatool_parse_datum:n
 {
%    \end{macrocode}
%Initialise to unknown.
%    \begin{macrocode}
   \@dtl@datatype = \c_datatool_unknown_int
   \tl_set:Nn \l__datatool_datum_original_value_tl { #1 }
   \tl_clear:N \l__datatool_datum_value_tl 
   \tl_clear:N \l__datatool_datum_currency_tl 
   \tl_clear:N \l__datatool_datum_currency_code_tl 
   \tl_clear:N \l__datatool_datum_update_value_tl 
   \tl_if_blank:nF { #1 }
   {
%    \end{macrocode}
%Has the argument already had its data type set?
%    \begin{macrocode}
     \tl_if_head_eq_meaning:nNTF { #1 }  \__datatool_datum:nnnn 
     { \__datatool_get_datum:w #1 \q_nil \q_stop }
     {
       \tl_if_head_eq_meaning:nNTF { #1 }  \__datatool_datum:w 
       { \__datatool_get_weird_datum:w #1 \q_nil \q_stop }
       {
%    \end{macrocode}
%Doesn't start with the special marker, so it needs parsing. First
%check for scientific notation.
%    \begin{macrocode}
        \regex_match:NnTF \c_datatool_sci_regex { #1 }
         {
           \fp_set:Nn \l__datatool_datum_value_fp { #1 }
           \@dtl@datatype = \c_datatool_decimal_int
           \tl_set:Nx \l__datatool_datum_value_tl
            {
              \exp_not:N \datatool_datum_fp:nnn
               { #1 }
               { \exp_not:V \l__datatool_datum_value_fp }
               { \fp_to_decimal:N \l__datatool_datum_value_fp }
            }
           \__datatool_if_auto_reformat_on:nTF { si }
            {
%    \end{macrocode}
%Auto-reformat on.
%    \begin{macrocode}
              \tl_set:Nx \l__datatool_datum_update_value_tl 
               {
                 \exp_not:N \__datatool_datum:nnnn 
                  {
                    \exp_not:N \DTLscinum
                      { \exp_not:n { #1 } }
                  }
                  { \exp_not:V \l__datatool_datum_value_tl }
                  { } 
                  { \exp_not:N \c_datatool_decimal_int }
               }
            }
            {
              \tl_set:Nx \l__datatool_datum_update_value_tl 
               {
                 \exp_not:N \__datatool_datum:nnnn 
                  { \exp_not:n { #1 } }
                  { \exp_not:V \l__datatool_datum_value_tl }
                  { } 
                  { \exp_not:N \c_datatool_decimal_int }
               }
            }
         }
         {
%    \end{macrocode}
%Next for date/time if applicable.
%    \begin{macrocode}
          \__datatool_parse_datetime_if_enabled:nTF { #1 }
           {
             \__datatool_if_auto_reformat_on:TF
              { \__datatool_set_datetime_value: }
              { \__datatool_set_datetime_value:n { #1 } }
           }
           {
%    \end{macrocode}
%Not date/time.
%Next check if it starts with a + or - to allow for a sign before the
%currency symbol.
%    \begin{macrocode}
            \bool_lazy_or:nnTF
              { \tl_if_head_eq_meaning_p:nN { #1 } + }
              { \tl_if_head_eq_meaning_p:nN { #1 } - } 
             {
               \exp_args:Nxx \__datatool_parse_item:nn
                { \tl_tail:n { #1 } }
                { \tl_head:n { #1 } }
             }
             {
               \__datatool_parse_item:nn { #1 } { }
             }
           }
         }
       }
     }
   }
 }
%    \end{macrocode}
% Parse remaining after prefix check:
%    \begin{macrocode}
\cs_new:Nn \__datatool_parse_item:nn
{
  \tl_set:Nn \l__datatool_datum_original_value_tl { #1 }
%    \end{macrocode}
%Check if it starts with a known currency marker.
%    \begin{macrocode}
  \tl_if_head_eq_meaning:nNTF { #1 } { \DTLcurrency }
   { \__datatool_get_currency_datum:w #1 \q_nil \q_stop }
   {
     \tl_if_head_eq_meaning:nNTF { #1 } { \DTLfmtcurrency }
     { \__datatool_get_fmtcurrency_datum:w #1 \q_nil \q_stop }
     {
       \tl_if_head_eq_meaning:nNTF { #1 } { \DTLfmtcurr }
       { \__datatool_get_fmtcurr_datum:w #1 \q_nil \q_stop }
       {
%    \end{macrocode}
%Doesn't start with \cs{DTLcurrency} or \cs{DTLfmtcurrency} or
%\cs{DTLfmtcurr}.
%Does it start with a known currency symbol?
%    \begin{macrocode}
          \__datatool_check_known_currencies:
          \tl_if_empty:NTF \l__datatool_datum_currency_tl
          {
%    \end{macrocode}
%Not currency. Is it numeric?
%    \begin{macrocode}
            \__datatool_parse_numeric:n { #1 }
            \int_compare:nNnT
              { \@dtl@datatype } = { \c_datatool_unknown_int }
            {
              \int_set_eq:NN \@dtl@datatype \c_datatool_string_int
            }
          }
          {
%    \end{macrocode}
%Starts (or ends) with a currency symbol. Is it followed by a numeric value?
%    \begin{macrocode}
            \tl_trim_spaces:N \l__datatool_suffix_tl
            \__datatool_parse_numeric:N \l__datatool_suffix_tl
            \int_compare:nNnTF
              { \@dtl@datatype } = { \c_datatool_string_int }
            {
              \tl_clear:N \l__datatool_datum_currency_tl
            }
            {
              \int_set_eq:NN \@dtl@datatype \c_datatool_currency_int
            }
          }
        }
     }
   }
%    \end{macrocode}
%Check for a double sign which will be treated as a string.
%    \begin{macrocode}
   \tl_if_empty:nF { #2 }
    {
      \int_compare:nNnT
        { \@dtl@datatype } > { \c_datatool_string_int }
      {
        \bool_lazy_or:nnTF
          { \tl_if_head_eq_meaning_p:nN { #1 } + }
          { \tl_if_head_eq_meaning_p:nN { #1 } - } 
         {
           \int_set_eq:NN \@dtl@datatype \c_datatool_string_int
         }
         {
           \tl_if_eq:nnT { #2 } { - }
            {
              \tl_set:Nx \l__datatool_datum_value_tl
               { \fp_to_tl:n { - ( \l__datatool_datum_value_tl ) } }
            }
         }
      }
    }
   \int_case:nn { \@dtl@datatype }
   {
     { \c_datatool_string_int }
      {
%    \end{macrocode}
%Non-numeric.
%    \begin{macrocode}
       \tl_set:Nn \l__datatool_datum_update_value_tl 
        { 
          \__datatool_datum:nnnn { #2 #1 } { } { }
            { \c_datatool_string_int }
        }
      }
     { \c_datatool_integer_int }
      {
%    \end{macrocode}
%Integer.
%    \begin{macrocode}
       \__datatool_if_auto_reformat_on:nTF { integer }
        {
%    \end{macrocode}
%Auto-reformat on.
%    \begin{macrocode}
         \tl_set:Nx
           \l__datatool_datum_update_value_tl 
           { \int_eval:n { \l__datatool_datum_value_tl } }
         \regex_replace_case_once:nN
          {
            \c_datatool_integer_grps_i_regex
               { \1 \2 \u{__datatool_numbergroup_tl} \3 }
            \c_datatool_integer_grps_ii_regex
               { \1 \2 \u{__datatool_numbergroup_tl} \3
                    \u{__datatool_numbergroup_tl} \4 }
            \c_datatool_integer_grps_iii_regex
               { \1 \2 \u{__datatool_numbergroup_tl} \3 
                    \u{__datatool_numbergroup_tl} \4
                    \u{__datatool_numbergroup_tl} \5 }
          }
         \l__datatool_datum_update_value_tl 
         \tl_set:Ne \l__datatool_datum_update_value_tl 
          {
            \exp_not:N \__datatool_datum:nnnn
             {
               \exp_not:V \l__datatool_datum_update_value_tl 
             }
             { \int_eval:n { \l__datatool_datum_value_tl } } { }
             { \c_datatool_integer_int }
          }
        }
        {
         \tl_set:Nx \l__datatool_datum_update_value_tl 
          {
            \exp_not:N \__datatool_datum:nnnn
             { \exp_not:n { #2 #1 } }
             { \int_eval:n { \l__datatool_datum_value_tl } } { }
             { \c_datatool_integer_int }
          }
        }
      }
     { \c_datatool_decimal_int }
      {
%    \end{macrocode}
%Decimal.
%    \begin{macrocode}
        \tl_set_eq:NN
          \l__datatool_datum_update_value_tl 
          \l__datatool_datum_value_tl
        \fp_set:Nn \l__datatool_datum_value_fp
          { \l__datatool_datum_value_tl }
        \tl_set:Nx \l__datatool_datum_value_tl
         { 
          \exp_not:N \datatool_datum_fp:nnn
           { \exp_not:V \l__datatool_datum_value_tl }
           { \exp_not:V \l__datatool_datum_value_fp }
           { \fp_to_decimal:N \l__datatool_datum_value_fp }
         }
        \__datatool_if_auto_reformat_on:nTF { decimal }
         {
%    \end{macrocode}
%Auto-reformat on.
%    \begin{macrocode}
           \regex_replace_case_once:nN
             {
               \c_datatool_decimal_grps_regex
                 { \1 \2 \u{__datatool_decimal_tl} \3 }
               \c_datatool_decimal_grps_i_regex
                  { \1 \2 \u{__datatool_numbergroup_tl} \3 \u{__datatool_decimal_tl} \4 }
               \c_datatool_decimal_grps_ii_regex
                  { \1 \2 \u{__datatool_numbergroup_tl} \3
                       \u{__datatool_numbergroup_tl} \4
                       \u{__datatool_decimal_tl} \5 }
               \c_datatool_decimal_grps_iii_regex
                  { \1 \2 \u{__datatool_numbergroup_tl} \3
                       \u{__datatool_numbergroup_tl} \4
                       \u{__datatool_numbergroup_tl} \5
                       \u{__datatool_decimal_tl} \6 }
             }
            \l__datatool_datum_update_value_tl 
           \tl_set:Ne \l__datatool_datum_update_value_tl 
            {
              \exp_not:N \__datatool_datum:nnnn
               { 
                 \exp_not:V \l__datatool_datum_update_value_tl 
               }
               { \exp_not:V \l__datatool_datum_value_tl }
               { }
               { \c_datatool_decimal_int }
            }
         }
         {
%    \end{macrocode}
%Auto-reformat off.
%    \begin{macrocode}
           \tl_set:Nx \l__datatool_datum_update_value_tl 
            {
              \exp_not:N \__datatool_datum:nnnn
               { \exp_not:n { #2 #1 } }
               { \exp_not:V \l__datatool_datum_value_tl }
               { }
               { \c_datatool_decimal_int }
            }
         }
      }
     { \c_datatool_currency_int }
      {
%    \end{macrocode}
%Currency.
%    \begin{macrocode}
        \__datatool_if_auto_reformat_on:nTF { currency }
        {
%    \end{macrocode}
%Auto-reformat on.
%    \begin{macrocode}
          \__datatool_decimal_to_currency:VVNV
            \l__datatool_datum_currency_tl
            \l__datatool_datum_value_tl
            \l__datatool_datum_update_value_tl 
            \l__datatool_datum_currency_code_tl 
        }
        {
%    \end{macrocode}
%Auto-reformat off.
%    \begin{macrocode}
         \tl_set:Nx \l__datatool_datum_update_value_tl 
          {
            \exp_not:N \__datatool_datum:nnnn
              { \exp_not:n { #2 #1 } }
              { \l__datatool_datum_value_tl }
              { \exp_not:V \l__datatool_datum_currency_tl }
              { \c_datatool_currency_int }
          }
        }
      }
   }
  \tl_set:Nn \l__datatool_datum_original_value_tl { #2 #1 }
}
%    \end{macrocode}
%Parse for a numeric value, using the current group and decimal
%characters.
%    \begin{macrocode}
\tl_new:N \l__datatool_parser_num_tl
\cs_new:Nn \__datatool_parse_numeric:N
 {
   \exp_args:No \__datatool_parse_numeric:n { #1 }
 }
\cs_new:Nn \__datatool_parse_numeric:n
 {
   \tl_set:Nn \l__datatool_parser_num_tl { #1 }
   \tl_if_empty:nF { #1 }
   {
     \regex_match:NnTF \l_datatool_locale_bigint_regex { #1 }
      {
        \@dtl@datatype = \c_datatool_string_int
      }
      {
        \regex_replace_case_once:nNTF
         {
           \l_datatool_locale_numeric_regex
           { \c{q_mark} \1 \2 \3 \4 . \5 \c{q_stop}}
           \l_datatool_locale_fractional_regex
           { \c{q_mark} \1 . \2 \c{q_stop}}
           \l_datatool_locale_fractional_nozero_regex
           { \c{q_mark} \1 0 . \2 \c{q_stop}}
         }
         \l__datatool_parser_num_tl
         { \exp_after:wN \__datatool_parse_number:w \l__datatool_parser_num_tl } 
         { \@dtl@datatype = \c_datatool_string_int }
      }
   }
 }
\cs_new:Npn \__datatool_parse_number:w \q_mark #1.#2\q_stop
{
  \tl_if_empty:nTF { #2 }
  {
    \@dtl@datatype = \c_datatool_integer_int
    \tl_set:Nn \l__datatool_datum_value_tl { #1 }
  }
  {
    \@dtl@datatype = \c_datatool_decimal_int
    \tl_if_empty:nTF { #1 }
     { \tl_set:Nn \l__datatool_datum_value_tl { 0.#2 } }
     { \tl_set:Nn \l__datatool_datum_value_tl { #1.#2 } }
  }
}
%    \end{macrocode}
%Check for known currencies.
%    \begin{macrocode}
\cs_new:Nn \__datatool_check_known_currencies:
 {
%    \end{macrocode}
% Check regions first (this will obtain the
% currency code to save searching for it again).
%    \begin{macrocode}
   \tl_clear:N \l__datatool_datum_currency_code_tl 
   \prop_map_function:NN \l_datatool_regional_currencies_prop
     \__datatool_prop_parse_currency_do:nn
   \tl_if_empty:NT \l__datatool_datum_currency_tl
    {
     \seq_map_function:NN
      \l__datatool_known_currencies_seq 
      \__datatool_seq_parse_currency_do:n 
    }
%    \end{macrocode}
%If suffix is blank then it may just be the symbol.
%    \begin{macrocode}
   \exp_args:NV \tl_if_blank:nT \l__datatool_suffix_tl
    {
      \tl_clear:N \l__datatool_datum_currency_tl
    }
 }
%    \end{macrocode}
%Handler macro for currency iterator for property map.
%    \begin{macrocode}
\cs_new:Nn \__datatool_prop_parse_currency_do:nn
 {
   \__datatool_if_starts_or_ends_with:VvTF
       \l__datatool_datum_original_value_tl
       { dtl@curr@ #2 @tl }
    {
      \tl_set_eq:NN \l__datatool_datum_currency_tl \l__datatool_prefix_tl
      \tl_set:Nn \l__datatool_datum_currency_code_tl { #2 }
    }
    {
      \__datatool_if_starts_or_ends_with:VvTF
         \l__datatool_datum_original_value_tl
         { dtl@curr@ #2 @sym }
       {
         \tl_set_eq:NN \l__datatool_datum_currency_tl \l__datatool_prefix_tl
         \tl_set:Nn \l__datatool_datum_currency_code_tl { #2 }
       }
       {
         \cs_if_exist:cT { datatool #1 symbolprefix }
          {
            \__datatool_if_starts_or_ends_with:VeTF
               \l__datatool_datum_original_value_tl
               { #1 \exp_not:v { dtl@curr@ #2 @tl } }
             {
               \tl_set_eq:NN \l__datatool_datum_currency_tl \l__datatool_prefix_tl
               \tl_set:Nn \l__datatool_datum_currency_code_tl { #2 }
             }
             {
               \__datatool_if_starts_or_ends_with:VeTF
                   \l__datatool_datum_original_value_tl
                   { #1 \exp_not:v { dtl@curr@ #2 @sym } }
                {
                  \tl_set_eq:NN \l__datatool_datum_currency_tl \l__datatool_prefix_tl
                  \tl_set:Nn \l__datatool_datum_currency_code_tl { #2 }
                }
             }
         }
       }
    }
   \tl_if_empty:NF \l__datatool_datum_currency_tl
    {
      \prop_map_break:
    }
 }
%    \end{macrocode}
%Handler macro for currency iterator for sequence map.
%    \begin{macrocode}
\cs_new:Nn \__datatool_seq_parse_currency_do:n
 {
   \__datatool_if_starts_or_ends_with:VnTF
      \l__datatool_datum_original_value_tl { #1 }
     {
       \tl_set_eq:NN \l__datatool_datum_currency_tl \l__datatool_prefix_tl
       \seq_map_break:
     }
 }
%    \end{macrocode}
%Parse content that starts with \cs{\_\_datatool\_datum:nnnn}.
%    \begin{macrocode}
\cs_new:Npn \__datatool_get_datum:w 
  \__datatool_datum:nnnn #1 #2 #3 #4 #5 \q_stop
 {
   \quark_if_nil:nTF { #5 }
   {
     \@dtl@datatype = #4
     \tl_set:Nn \l__datatool_datum_original_value_tl { #1 }
     \tl_set:Nn \l__datatool_datum_value_tl { #2 }
     \tl_set:Nn \l__datatool_datum_currency_tl { #3 }
   }
   {
     \@dtl@datatype = \c_datatool_string_int
   }
 }
%    \end{macrocode}
%Parse content that starts with \cs{DTLcurrency}.
%    \begin{macrocode}
\cs_new:Npn \__datatool_get_currency_datum:w 
  \DTLcurrency #1 #2 \q_stop
 {
   \quark_if_nil:nTF { #2 }
   {
     \__datatool_parse_numeric:n { #1 }
     \ifnum\@dtl@datatype = \c_datatool_string_int
     \else
       \tl_set:Nn \l__datatool_datum_currency_tl { \@dtl@currency }
       \@dtl@datatype = \c_datatool_currency_int
     \fi
   }
   {
     \@dtl@datatype = \c_datatool_string_int
   }
 }
%    \end{macrocode}
%Parse content that starts with \cs{DTLfmtcurrency}.
%    \begin{macrocode}
\cs_new:Npn \__datatool_get_fmtcurrency_datum:w 
  \DTLfmtcurrency #1 #2 #3 \q_stop
 {
   \quark_if_nil:nTF { #3 }
   {
     \__datatool_parse_numeric:n { #2 }
     \ifnum\@dtl@datatype = \c_datatool_string_int
     \else
       \@dtl@datatype = \c_datatool_currency_int
       \tl_set:Nn \l__datatool_datum_currency_tl { #1 }
     \fi
   }
   {
     \@dtl@datatype = \c_datatool_string_int
   }
 }
%    \end{macrocode}
%Parse content that starts with \cs{DTLfmtcurr}.
%    \begin{macrocode}
\cs_new:Npn \__datatool_get_fmtcurr_datum:w 
  \DTLfmtcurr #1 #2 #3 \q_stop
 {
   \quark_if_nil:nTF { #3 }
   {
     \__datatool_parse_numeric:n { #2 }
     \ifnum\@dtl@datatype = \c_datatool_string_int
     \else
       \tl_set:Nn \l__datatool_datum_currency_code_tl { #1 }
       \@dtl@datatype = \c_datatool_currency_int
       \tl_set:Nn \l__datatool_datum_currency_tl { \DTLcurr { #1 } }
     \fi
   }
   {
     \@dtl@datatype = \c_datatool_string_int
   }
 }
%    \end{macrocode}
%
% Only parse for dates and times if enabled. Note that the boolean setting
% is just for general parsing commands. Temporal values can be
% explicitly parsed.
%    \begin{macrocode}
\prg_new_conditional:Npnn \__datatool_parse_datetime_if_enabled:n #1
  { T, F, TF }
{
  \bool_if:NTF \l__datatool_parse_datetime_bool
   {
     \__datatool_parse_datetime:nTF { #1 }
      { \prg_return_true: }
      { \prg_return_false: }
   }
   { \prg_return_false: }
}
%    \end{macrocode}
%
%Test if the given argument is a token list containing datum.
%    \begin{macrocode}
\cs_new:Nn \__datatool_if_datum_tl:nTF 
 {
   \tl_if_single_token:nTF { #1 }
   {
     \exp_args:No \tl_if_head_eq_meaning:nNTF { #1 } \__datatool_datum:nnnn 
     { #2 } 
     { #3 }
   }
   { #3 }
 }
%    \end{macrocode}
%
%Added v3.1:
%Not a number warning:
%    \begin{macrocode}
\cs_new:Nn \datatool_warn_not_number:n
 {
   \PackageWarning { datatool-base } { #1 }
 }
%    \end{macrocode}
%
%\begin{macro}{\DTLconverttodecimal}
%\begin{definition}
% \cs{DTLconverttodecimal}\marg{num}\marg{cmd} 
%\end{definition}
% \cs{DTLconverttodecimal} will convert locale dependent \meta{num}
% a decimal number in a standard form that can be used in 
% arithmetical calculations. The resulting
% number is stored in \meta{cmd}.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLconverttodecimal { m m }
{
  \tl_if_single_token:nTF { #1 }
   {
     \exp_args:No \__datatool_parse_datum:n { #1 }
   }
   {
     \__datatool_parse_datum:n { #1 }
   }
  \datatool_if_numeric_datum_type:nF { \@dtl@datatype }
   {
     \datatool_warn_not_number:n
      { Can't ~ convert ~ non-numerical ~ ` #1 ' ~ to ~ decimal }
   }
  \tl_if_empty:NTF \l__datatool_datum_value_tl
   { \tl_set:Nn #2 { 0 } }
   { \tl_set_eq:NN #2 \l__datatool_datum_value_tl }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdecimaltolocale}
%\begin{definition}
% \cs{DTLdecimaltolocale}\marg{number}\marg{cmd}
%\end{definition}
% Define command to convert a decimal number into the locale
% dependent format. Stores result in \meta{cmd} which must be
% a control sequence.
%    \begin{macrocode}
\newrobustcmd*{\DTLdecimaltolocale}[2]{%
 \tl_set:Nn #2 { #1 }
 \regex_replace_case_once:nN
 {
   \c_datatool_numeric_leading_zeros_regex { \1 \2 }
   \c_datatool_decimal_redundant_zeros_regex { \1 \2 }
   \c_datatool_decimal_implicit_zero_regex { \1 0 \2 }
 }
 #2
 \regex_replace_case_once:nNTF
 {
    \c_datatool_decimal_grps_regex
      { \1 \2 \u{__datatool_decimal_tl} \3 }
    \c_datatool_decimal_grps_i_regex
       { \1 \2 \u{__datatool_numbergroup_tl} \3 \u{__datatool_decimal_tl} \4 }
    \c_datatool_decimal_grps_ii_regex
       { \1 \2 \u{__datatool_numbergroup_tl} \3
            \u{__datatool_numbergroup_tl} \4
            \u{__datatool_decimal_tl} \5 }
    \c_datatool_decimal_grps_iii_regex
       { \1 \2 \u{__datatool_numbergroup_tl} \3
            \u{__datatool_numbergroup_tl} \4
            \u{__datatool_numbergroup_tl} \5
            \u{__datatool_decimal_tl} \6 }
 }
 #2
 {
   \tl_set:Ne #2
    {
     \exp_not:N \__datatool_datum:nnnn 
      { \exp_not:V #2 }
      { #1 }
      {  }
      { \exp_not:N \c_datatool_decimal_int }
    }
  }
  {
    \regex_replace_case_once:nNTF
    {
      \c_datatool_integer_grps_i_regex
         { \1 \2 \u{__datatool_numbergroup_tl} \3 }
      \c_datatool_integer_grps_ii_regex
         { \1 \2 \u{__datatool_numbergroup_tl} \3
              \u{__datatool_numbergroup_tl} \4 }
      \c_datatool_integer_grps_iii_regex
         { \1 \2 \u{__datatool_numbergroup_tl} \3 
              \u{__datatool_numbergroup_tl} \4
              \u{__datatool_numbergroup_tl} \5 }
%    \end{macrocode}
%This last one ensures that the true branch is followed:
%    \begin{macrocode}
      \c_datatool_integer_no_grps_regex { \1 \2 }
    }
    #2
    {
      \tl_set:Ne #2
       {
        \exp_not:N \__datatool_datum:nnnn 
         { \exp_not:V #2 }
         { #1 }
         {  }
         { \exp_not:N \c_datatool_integer_int }
       }
    }
    {
%    \end{macrocode}
%This is only a warning rather than an error to allow for databases
%that contain markup (e.g. \cs{textemdash} or \qt{N/A}) to indicate a missing value.
%The numeric functions may either interpret strings as zero or skip them.
%    \begin{macrocode}
      \datatool_warn_not_number:n
       {
         Can't ~ convert ~ `#1' ~ to ~ decimal: ~ not ~ a ~ number
       }
      \tl_set:Nn #2
       {
         \__datatool_datum:nnnn 
          { #1 }
          {  }
          {  }
          { \c_datatool_string_int }
       }
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdecimaltocurrency}
%\begin{definition}
% \cs{DTLdecimaltocurrency}\oarg{currency symbol}\marg{number}\marg{cmd}
%\end{definition}
% This converts a decimal number into the locale
% dependent currency format. Stores result in \meta{cmd} which must be
% a control sequence. An empty value will be treated as zero.
%\changes{3.0}{2025-03-03}{added optional argument}
%    \begin{macrocode}
\NewDocumentCommand{\DTLdecimaltocurrency} { O{} m m }
{
   \__datatool_decimal_to_currency:nnN { #1 } { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%Round currency to this many places (set to empty for no rounding):
%    \begin{macrocode}
\newcommand\DTLCurrentLocaleCurrencyDP{2}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_decimal_to_currency:nnN
{
  \__datatool_decimal_to_currency:nnNn { #1 } { #2 } #3 { }
}
\cs_generate_variant:Nn \__datatool_decimal_to_currency:nnN { VVN , nVN }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_decimal_to_currency:nnNn
{
 \regex_match:NnTF \c_datatool_any_numeric_regex { #2 }
  {
   \tl_if_empty:NTF \DTLCurrentLocaleCurrencyDP
    {
      \tl_set:Nn #3 { #2 }
      \tl_if_empty:NTF #3
       {
         \tl_set:Nn #3 { 0 }
       }
       {
         \regex_replace_once:NnN
          \c_datatool_integer_leading_zeros_regex { \1 \2 } #3
       }
    }
    {
      \tl_if_empty:nTF { #2 }
       {
         \dtlround { #3 } { 0 } { \DTLCurrentLocaleCurrencyDP }
       }
       {
         \dtlround { #3 } { #2 } { \DTLCurrentLocaleCurrencyDP }
       }
      \tl_set:Ne #3
        {
          \exp_args:Ne \__datatool_decimal_trunc_pad_zeros:nn
           { #3 }
           { \DTLCurrentLocaleCurrencyDP }
        }
    }
   \regex_replace_case_once:nN
    {
      \c_datatool_decimal_grps_regex { \1 \2 \u{__datatool_decimal_tl} \3 }
      \c_datatool_decimal_grps_i_regex
         { \1 \2 \u{__datatool_numbergroup_tl} \3 \u{__datatool_decimal_tl} \4 }
      \c_datatool_decimal_grps_ii_regex
         { \1 \2 \u{__datatool_numbergroup_tl} \3
              \u{__datatool_numbergroup_tl} \4
              \u{__datatool_decimal_tl} \5 }
      \c_datatool_decimal_grps_iii_regex
         { \1 \2 \u{__datatool_numbergroup_tl} \3
              \u{__datatool_numbergroup_tl} \4
              \u{__datatool_numbergroup_tl} \5
              \u{__datatool_decimal_tl} \6 }
      \c_datatool_integer_grps_i_regex
         { \1 \2 \u{__datatool_numbergroup_tl} \3 }
      \c_datatool_integer_grps_ii_regex
         { \1 \2 \u{__datatool_numbergroup_tl} \3
              \u{__datatool_numbergroup_tl} \4 }
      \c_datatool_integer_grps_iii_regex
         { \1 \2 \u{__datatool_numbergroup_tl} \3 
              \u{__datatool_numbergroup_tl} \4
              \u{__datatool_numbergroup_tl} \5 }
    }
    #3
    \tl_set:Ne \l__datatool_datum_currency_code_tl { #4 }
    \tl_if_blank:nTF { #1 }
     {
       \tl_if_empty:NTF \l__datatool_datum_currency_code_tl
        {
         \seq_if_in:NVTF 
            \l_datatool_regional_currencies_seq
            \DTLCurrencyCode
          {
           \tl_set:Ne #3
            {
             \exp_not:N \__datatool_datum:nnnn 
              {
                \exp_not:N \DTLfmtcurr
                  { \DTLCurrencyCode }
                  { \exp_not:V #3 }
              }
              { #2 }
              { \exp_not:V \@dtl@currency }
              { \exp_not:N \c_datatool_currency_int }
            }
          }
          {
           \tl_set:Ne #3
            {
             \exp_not:N \__datatool_datum:nnnn 
              {
                \exp_not:N \DTLfmtcurrency
                  { \exp_not:V \@dtl@currency }
                  { \exp_not:V #3 }
              }
              { #2 }
              { \exp_not:V \@dtl@currency }
              { \exp_not:N \c_datatool_currency_int }
            }
          }
        }
        {
         \tl_set:Ne #3
          {
           \exp_not:N \__datatool_datum:nnnn 
            {
              \exp_not:N \DTLfmtcurr
                { \l__datatool_datum_currency_code_tl }
                { \exp_not:V #3 }
            }
            { #2 }
            {
              \exp_not:N \DTLcurr
               { \l__datatool_datum_currency_code_tl }
            }
            { \exp_not:N \c_datatool_currency_int }
          }
        }
     }
     {
       \tl_if_empty:NT \l__datatool_datum_currency_code_tl
        {
          \datatool_get_currency_code:NV 
           \l__datatool_datum_currency_code_tl 
           \l__datatool_datum_currency_tl
          \tl_if_eq:NnT
           \l__datatool_datum_currency_code_tl { XXX }
           {
             \tl_clear:N \l__datatool_datum_currency_code_tl 
           }
        }
       \tl_if_empty:NTF \l__datatool_datum_currency_code_tl 
        {
          \tl_set:Ne #3
           {
             \exp_not:N \__datatool_datum:nnnn 
             {
               \exp_not:N \DTLfmtcurrency
                { \exp_not:n { #1 } }
                { \exp_not:V #3 }
             }
             { #2 }
             { \exp_not:n { #1 } }
             { \exp_not:N \c_datatool_currency_int }
           }
        }
        {
         \tl_set:Ne #3
          {
           \exp_not:N \__datatool_datum:nnnn 
            {
               \exp_not:N \DTLfmtcurr
                { \l__datatool_datum_currency_code_tl }
                { \exp_not:V #3 }
            }
            { #2 }
            { \exp_not:n { #1 } }
            { \exp_not:N \c_datatool_currency_int }
          }
        }
      }
  }
  {
%    \end{macrocode}
%This is only a warning rather than an error to allow for databases
%that contain markup (e.g. \cs{textemdash} or \qt{N/A}) to indicate a missing value.
%The numeric functions may either interpret strings as zero or skip them.
%    \begin{macrocode}
    \datatool_warn_not_number:n
     {
       Can't ~ convert ~ `#2' ~ to ~ currency: ~ not ~ a ~ number
     }
    \tl_set:Nn #3
     {
      \__datatool_datum:nnnn 
       { #2 }
       {  }
       {  }
       { \c_datatool_string_int }
     }
  }
}
\cs_generate_variant:Nn \__datatool_decimal_to_currency:nnNn
  { VVNV , nVNV }
%    \end{macrocode}
%
%\begin{macro}{\DTLdecimaltodatetime}
%\begin{definition}
% \cs{DTLdecimaltodatetime}\marg{number}\marg{cmd}
%\end{definition}
% Define command to convert a decimal number into a datetime datum.
% Stores result in \meta{cmd} which must be
% a control sequence.
%    \begin{macrocode}
\NewDocumentCommand \DTLdecimaltodatetime { m m }
 {
   \datatool_decimal_to_temporal:Nnn
     #2 { \c_datatool_datetime_int } { #1 }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdecimaltodate}
%\begin{definition}
% \cs{DTLdecimaltodate}\marg{JDN}\marg{cmd}
%\end{definition}
% Define command to convert a number (a Julian Day Number) into a date datum.
% Stores result in \meta{cmd} which must be
% a control sequence.
%    \begin{macrocode}
\NewDocumentCommand \DTLdecimaltodate { m m }
 {
   \datatool_decimal_to_temporal:Nnn
     #2 { \c_datatool_date_int } { #1 }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdecimaltotime}
%\begin{definition}
% \cs{DTLdecimaltotime}\marg{JT}\marg{cmd}
%\end{definition}
% Define command to convert a decimal number (Julian time of day fraction) into a time datum.
% Stores result in \meta{cmd} which must be
% a control sequence.
%    \begin{macrocode}
\NewDocumentCommand \DTLdecimaltotime { m m }
 {
   \datatool_decimal_to_temporal:Nnn
     #2 { \c_datatool_time_int } { #1 }
 }
%    \end{macrocode}
%\end{macro}
%
%\subsubsection{Dates and Times}
%New to version 3.0. No localisation is performed. These functions
%just deal with parsing ISO dates, times and timestamps. There are in
%total eight numeric elements of a timestamp: year, month number
%(1--12), day of month number (1--31), hour of day number (0--23),
%minute of hour number (0--59), seconds of minute number (0--59),
%time zone hour offset ($-12$--$+12$), and time zone minute offset
%(0--59). This can lead to commands with a lot of arguments, so a
%specially formatted sequence with 8 items is sometimes used instead.
%Each item is formatted when it is set in the sequence to make it
%easier to reconstruct a timestamp.
%
%Dates, times and timestamps can be represented numerically. The
%Julian Day Number (JDN) is the integer number of whole solar days since noon
%Universal Time. A value of zero represents Monday 1st January 4713~BC,
%according to the proleptic Julian Calendar. So, noon 1st January
%2000 has the Julian Day Number 2451545.
%The time of day is represented as a fractional number from noon.
%A negative value is a time before noon. A positive number is a time
%after noon. Noon has a value of $0.0$. Twelve hours before noon, that
%is, the previous midnight has a value of $-0.5$. Twelve hours after
%noon, that is, the following midnight has a value of $0.5$.
%
%The Julian Date is a decimal that is simply the sum of the Julian
%Day Number and the time fraction. For example, 
%$2451545 + 0.5 = 2451545.5$ is midnight after noon 2000-01-01
%and $2451545 - 0.5 = 2451544.5$ is midnight before
%noon 2000-01-01. Note that while the term \qt{Julian Date} is sometimes 
%used to refer to the ordinal day of year in other works, from \sty{datatool}'s
%point of view, \qt{Julian Date} is the decimal sum of the JDN and
%the time of day fraction.
%
%Since JDN~0 is a Monday, the day of the week (starting with
%Monday=0) can be obtained from the remainder of the
%integer division of the Julian Day Number and 7 (that is,
%\meta{JDN} modulo 7).
%
%A temporal datum command has the data type set to one of the
%temporal identifiers (datetime, date or time). The string part may
%be the original string parsed or maybe a formatted version of the
%temporal value.
%
%\begin{macro}{\DTLtemporalvalue}
%\changes{3.0}{2025-03-03}{new}
%It's useful to retain the original ISO specification. Since the
%string part of the datum variable may be used to provide a
%localised format, the original ISO format is hidden in the value
%part, which is set to:
%\begin{definition}
%\cs{DTLtemporalvalue}\marg{number}\marg{ISO}
%\end{definition}
%This allows both the original ISO representation to be extracted as
%well as the corresponding numerical value.
%The default definition allows the ISO part to be discarded
%in numerical contexts. This means that using \cs{DTLdatumvalue}
%will still expand to a numeric value, but it requires more
%expansion than the other numeric data types.
%
%This command isn't an internal command as it
%may occur in a dbtex-3 file, which expects the normal document
%category codes. The number depends on the datum type: a decimal
%Julian date for timestamps, an integer Julian day for date only
%or a decimal (fraction of a day) for time only.
%    \begin{macrocode}
\newcommand \DTLtemporalvalue [2] { #1 }
%    \end{macrocode}
%\end{macro}
%
%
%\begin{definition}
%\cs{datatool\_decimal\_to\_temporal:Nnn}
%\meta{datum-cs}\marg{data-type}\marg{number}
%\end{definition}
%Converts a number to a datum-cs.
%    \begin{macrocode}
\cs_new_nopar:Nn \datatool_decimal_to_temporal:Nnn
{ 
  \int_case:nnF { #2 }
   {
     { \c_datatool_datetime_int }
       {
         \datatool_from_julian_date:nN { #3 }
           \l__datatool_datetime_seq
         \datatool_timestamp_to_datetime_datum:NeN
          \l__datatool_datetime_seq
          { #3 }
          #1
       }
     { \c_datatool_date_int }
       {
         \int_set:Nn \l__datatool_julian_int { \fp_to_int:n { #3 } }
         \datatool_from_julian_day:VNNN \l__datatool_julian_int
          \l__datatool_year_int
          \l__datatool_month_int
          \l__datatool_day_int
         \tl_set:Ne #1
          {
            \exp_not:N \__datatool_datum:nnnn
             {% formatted string
               \exp_not:N \DataToolDateFmt
                 { \int_use:N \l__datatool_year_int }
                 { \int_use:N \l__datatool_month_int }
                 { \int_use:N \l__datatool_day_int }
                 {
                   \datatool_julian_day_to_dow:n { \l__datatool_julian_int }
                 }
             }
             {% value
               \exp_not:N \DTLtemporalvalue
                { \int_use:N \l__datatool_julian_int }
                { 
                  \int_use:N \l__datatool_year_int 
                   -
                  \datatool_two_digits:N \l__datatool_month_int 
                   -
                  \datatool_two_digits:N \l__datatool_day_int 
                }
             }
             { }% no currency symbol
             { \exp_not:N \c_datatool_date_int }
          }
       }
     { \c_datatool_time_int }
       {
         \datatool_from_julian_time:eNNN { #3 }
          \l__datatool_hour_int
          \l__datatool_minute_int
          \l__datatool_second_int
         \tl_set:Ne #1
          {
            \exp_not:N \__datatool_datum:nnnn
             {% formatted string
               \exp_not:N \DataToolTimeFmt
                 { \int_use:N \l__datatool_hour_int }
                 { \int_use:N \l__datatool_minute_int }
                 { \int_use:N \l__datatool_second_int }
             }
             {% value
               \exp_not:N \DTLtemporalvalue
                { #3 }
                { 
                  \datatool_two_digits:n { \l__datatool_hour_int }
                  \c_colon_str
                  \datatool_two_digits:N \l__datatool_minute_int 
                  \c_colon_str
                  \datatool_two_digits:N \l__datatool_second_int 
                }
             }
             { }% no currency symbol
             { \exp_not:N \c_datatool_time_int }
          }
       }
   }
   {
     \PackageError { datatool-base }
      { Invalid ~ data ~ type ~ \int_eval:n { #2 }: ~ not ~ a ~ temporal ~ type }
      {
        The ~ second ~ argument ~ of 
        \token_to_str:N \datatool_decimal_to_temporal:Nnn ~
        \c_space_tl ~ should ~ be ~ one ~ of: ~
        \exp_not:N \c_datatool_datetime_int , ~ 
        \exp_not:N \c_datatool_date_int , ~ or ~
        \exp_not:N \c_datatool_time_int 
      }
   }
}
\cs_generate_variant:Nn \datatool_decimal_to_temporal:Nnn
 { NnV }
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_extract\_timestamp:NN}\marg{datum-cs}\marg{result-tl}
%\end{definition}
%Extracts the date/time data stored in the given datum command and
%defines the supplied token list command to that value.
%If there's no date/time data in the datum command, the result token
%list variable will be set to empty. This may mean that the datum variable
%isn't a temporal variable, but may also mean that the value has
%lost its special markup due to expansion.
%    \begin{macrocode}
\cs_new:Nn \datatool_extract_timestamp:NN
 {
   \tl_clear:N #2
   \group_begin:
    \cs_set_eq:NN \DTLtemporalvalue \use_ii:nn
    \tl_set:Ne \l__datatool_tmpa_tl
      { \DTLdatumvalue { #1 } }
    \regex_match:NVTF
      \c_datatool_temporal_regex \l__datatool_tmpa_tl
     {
       \tl_set:Ne \l__datatool_tmpa_tl
         { \exp_not:N \tl_set:Nn \exp_not:N #2 { \l__datatool_tmpa_tl } }
     }
     {
       \tl_clear:N \l__datatool_tmpa_tl
     }
    \exp_after:wN
    \group_end:
       \l__datatool_tmpa_tl
 }
%    \end{macrocode}
%
%\begin{macro}{\DTLdatumtoDTM}
%\begin{definition}
%\cs{DTLdatumtoDTM}\marg{datum-cs}\marg{DTM-name}
%\end{definition}
%Converts the time stamp stored in the given datum command to a 
%\sty{datetime2} date saved with \cs{DTMsavetimestamp}, 
%\cs{DTMsavedate}, or \cs{DTLsavetime}. If the time stamp is
%missing, this will attempt to recalculate the relevant information
%based on the numeric value and data type.
%    \begin{macrocode}
\NewDocumentCommand \DTLdatumtoDTM { m m }
{
 \cs_if_exist:NTF \DTMsavetimestamp
  {
   \group_begin:
    \cs_set_eq:NN \DTLtemporalvalue \use_ii:nn
%    \end{macrocode}
%If the value contains \cs{DTLtemporalvalue} the temporary token
%list variable will be set to the ISO format in the second argument.
%    \begin{macrocode}
    \tl_set:Ne \l__datatool_tmpa_tl
      { \DTLdatumvalue { #1 } }
    \regex_match:NVTF
      \c_datatool_timestamp_regex \l__datatool_tmpa_tl
     {
%    \end{macrocode}
%Result matches a timestamp, so use \cs{DTMsavetimestamp}.
%    \begin{macrocode}
       \tl_set:Ne \l__datatool_tmpa_tl
        { \exp_not:N \DTMsavetimestamp { #2 } { \l__datatool_tmpa_tl } }
     }
     {
       \regex_match:NVTF
         \c_datatool_date_regex \l__datatool_tmpa_tl
        {
%    \end{macrocode}
%Result only matches a date, so use \cs{DTMsavedate}.
%    \begin{macrocode}
          \tl_set:Ne \l__datatool_tmpa_tl
            { \exp_not:N \DTMsavedate { #2 } { \l__datatool_tmpa_tl } }
        }
        {
          \regex_extract_once:NVNTF
            \c_datatool_time_regex
            \l__datatool_tmpa_tl
            \l_datatool_timestamp_match_seq
           {
%    \end{macrocode}
%Result only matches a time, so use \cs{DTMsavetime} but the seconds
%may be missing, which are required.
%    \begin{macrocode}
             \tl_if_empty:eTF
               { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
              {
                \tl_set:Ne \l__datatool_tmpa_tl
                  { \exp_not:N \DTMsavetime { #2 } { \l__datatool_tmpa_tl : 00 } }
              }
              {
                \tl_set:Ne \l__datatool_tmpa_tl
                  { \exp_not:N \DTMsavetime { #2 } { \l__datatool_tmpa_tl } }
              }
           }
           {
             \tl_if_empty:NTF \l__datatool_tmpa_tl
              {
                \PackageError { datatool-base }
                {
                   Can't ~ obtain ~ timestamp. ~
                   No ~ value ~ associated with ` \token_to_str:N #1 '
                }
                {
                  The ~ provided ~ value ~ doesn't ~ seem ~ to ~
                  be ~ numeric. ~ If ~ you ~ were ~ expecting ~ it ~
                  to ~ be ~ parsed ~ as ~ a ~ date/time ~ then ~
                  \bool_if:NTF \l__datatool_parse_datetime_bool
                   {
                     use ~ \token_to_str:N \DTLsetup { datetime=parse-only } ~
                     or ~ \token_to_str:N \DTLsetup { datetime=reformat } 
                   }
                   {
                    check ~ the ~ original ~ source ~ matched ~ 
                    the ~ correct ~ format
                   }
                }
              }
              {
                \int_set:Nn \@dtl@datatype { \DTLdatumtype { #1 } }
                \int_case:nn { \@dtl@datatype }
                {
                  { \c_datatool_integer_int }
                   {
                     \int_set_eq:NN \@dtl@datatype \c_datatool_date_int
                   }
                  { \c_datatool_decimal_int }
                   {
                     \fp_compare:nNnTF
                       { \fp_abs:n { \l__datatool_tmpa_tl } }
                         < { \c_one_fp }
                      {
                        \int_set_eq:NN \@dtl@datatype \c_datatool_time_int
                      }
                      {
                        \int_set_eq:NN \@dtl@datatype \c_datatool_datetime_int
                      }
                   }
                }
                \int_case:nnF { \@dtl@datatype }
                {
                  { \c_datatool_datetime_int }
                   {
                     \PackageWarning { datatool-base }
                     {
                       No ~ timestamp ~ could ~ be ~ found ~ in ~ the ~
                       provided ~ datum ~ command. ~ Value ~ found: ~
                       \l__datatool_tmpa_tl . ~ I'm ~ going ~
                       to ~ assume ~ this ~ is ~ a ~ Julian ~ Date
                     }
                    \datatool_from_julian_date:VN 
                     \l__datatool_tmpa_tl
                     \l__datatool_datetime_seq
                    \tl_set:Ne \l__datatool_tmpa_tl
                     { 
                       \exp_not:N \DTMsavetimestamp { #2 }
                        { 
                          \datatool_timestamp_to_iso:N
                            \l__datatool_datetime_seq
                        }
                      }
                   }
                  { \c_datatool_date_int }
                   {
                     \PackageWarning { datatool-base }
                     {
                       No ~ timestamp ~ could ~ be ~ found ~ in ~ the ~
                       provided ~ datum ~ command. ~ Value ~ found: ~
                       \l__datatool_tmpa_tl . ~ I'm ~ going ~
                       to ~ assume ~ this ~ is ~ a ~ Julian ~ Day ~ Number
                     }
                    \int_set:Nn \l__datatool_julian_int
                      { \l__datatool_tmpa_tl }
                    \datatool_from_julian_day:VNNN
                     \l__datatool_julian_int
                     \l__datatool_year_int
                     \l__datatool_month_int
                     \l__datatool_day_int
                    \tl_set:Ne \l__datatool_tmpa_tl
                     { 
                       \exp_not:N \DTMsavenoparsedate { #2 }
                        { \int_use:N \l__datatool_year_int }
                        { \int_use:N \l__datatool_month_int }
                        { \int_use:N \l__datatool_day_int }
                        {
                          \datatool_julian_day_to_dow:n
                           { \l__datatool_julian_int }
                        }
                      }
                   }
                  { \c_datatool_time_int }
                   {
                     \PackageWarning { datatool-base }
                     {
                       No ~ timestamp ~ could ~ be ~ found ~ in ~ the ~
                       provided ~ datum ~ command. ~ Value ~ found: ~
                       \l__datatool_tmpa_tl . ~ I'm ~ going ~
                       to ~ assume ~ this ~ is ~ a ~ Julian ~ 
                       time ~ of ~ day ~ fraction
                     }
                    \fp_set:Nn \l__datatool_julian_fp
                      { \l__datatool_tmpa_tl }
                    \datatool_from_julian_time:nNNN
                     { \l__datatool_julian_fp }
                     \l__datatool_hour_int
                     \l__datatool_minute_int
                     \l__datatool_second_int
                    \tl_set:Ne \l__datatool_tmpa_tl
                     { 
                       \exp_not:N \DTMsavetime { #2 }
                        {
                         \int_use:N \l__datatool_year_int 
                         \c_colon_str
                         \int_use:N \l__datatool_month_int 
                         \c_colon_str
                         \int_use:N \l__datatool_day_int 
                        }
                      }
                   }
                }
                {

                  \PackageError { datatool-base }
                   {
                     No ~ timestamp ~ could ~ be ~ found ~ in ~ the ~
                     provided ~ datum ~ command. ~ Value ~ found: ~
                     \l__datatool_tmpa_tl
                   }
                   {
                     The ~ datum ~ value ~ supplied ~ can't ~ be ~
                     interpreted ~ as ~ a ~ timestamp, ~ date ~ or ~ time.
                   }
                }
              }
           }
        }
     }
    \exp_after:wN
   \group_end: \l__datatool_tmpa_tl
  }
  {
   \PackageError { datatool-base }
    {
      \token_to_str:N \DTLdatumtoDTM ~ \c_space_tl ~ 
      can't ~ save ~ time ~ stamp. ~ 
      Command ~ \token_to_str:N \DTMsavetimestamp ~ \c_space_tl ~
      undefined ~ (datetime2 ~ required)
    }
    { You ~ need ~ to ~ load ~ datetime2.sty }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\paragraph{Formatting}
%
%Syntax:
%\meta{datum-cs}\meta{value-cs}\marg{string value}
%
%Used to set the value part of a temporal datum
%and the update token list variable.
%If the \marg{string value} argument is empty, 
%the string part is obtained by formatting the temporal value.
%    \begin{macrocode}
\cs_new:Nn \__datatool_set_datetime_value:NNn
 {
   \int_case:nn { \@dtl@datatype }
    {
      { \c_datatool_datetime_int }
      {
        \tl_set:Ne #2
         {
           \exp_not:N \DTLtemporalvalue
            { \fp_use:N \l__datatool_julian_fp }
            { 
              \__datatool_tm_iso:
            }
         }
        \tl_if_empty:nTF { #3 }
         {
           \__datatool_set_datum:NeVnn #1 
               {
                 \exp_not:N \DataToolDateTimeFmt
                  {
                    { \__datatool_tm_yr: }
                    { \__datatool_tm_mn: }
                    { \__datatool_tm_dy: }
                    { \__datatool_tm_dow: }
                  }
                  {
                    { \__datatool_tm_hr: }
                    { \__datatool_tm_mi: }
                    { \__datatool_tm_se: }
                  }
                  {
                    { \__datatool_tm_tzh: }
                    { \__datatool_tm_tzm: }
                  }
               } 
               #2 
               { } 
               { \c_datatool_datetime_int }
         }
         {
           \__datatool_set_datum:NnVnn #1
             { #3 }
             #2 
             { } 
             { \c_datatool_datetime_int }
         }
      }
      { \c_datatool_date_int }
      {
        \tl_set:Ne #2
         {
           \exp_not:N \DTLtemporalvalue
            { \int_use:N \l__datatool_julian_int }
            { 
              \__datatool_tm_iso_date:
            }
         }
        \tl_if_empty:nTF { #3 }
         {
           \__datatool_set_datum:NeVnn #1
               {
                 \exp_not:N \DataToolDateFmt
                  { \__datatool_tm_yr: }
                  { \__datatool_tm_mn: }
                  { \__datatool_tm_dy: }
                  { \__datatool_tm_dow: }
               } 
               #2 
               { } 
               { \c_datatool_date_int }
         }
         {
           \__datatool_set_datum:NnVnn #1
              { #3 }
              #2 
              { } 
              { \c_datatool_date_int }
         }
      }
      { \c_datatool_time_int }
      {
        \tl_set:Ne #2
         {
           \exp_not:N \DTLtemporalvalue
            { \fp_use:N \l__datatool_time_fp }
            { 
              \__datatool_tm_iso_time:
            }
         }
        \tl_if_empty:nTF { #3 }
         {
           \__datatool_set_datum:NeVnn #1
               {
                 \exp_not:N \DataToolTimeFmt
                  { \__datatool_tm_hr: }
                  { \__datatool_tm_mi: }
                  { \__datatool_tm_se: }
               } 
               #2 
               { } 
               { \c_datatool_time_int }
         }
         {
           \__datatool_set_datum:NnVnn #1
               { #3 } 
               #2 
               { } 
               { \c_datatool_time_int }
         }
      }
    }
 }
%    \end{macrocode}
%
%\paragraph{Constants and Scratch Variables}
%
%Scratch sequence for timestamps:
%    \begin{macrocode}
\seq_new:N \l__datatool_datetime_seq
%    \end{macrocode}
%Individual integer elements of a timestamp.
%    \begin{macrocode}
\int_new:N \l__datatool_year_int
\int_new:N \l__datatool_month_int
\int_new:N \l__datatool_day_int
\int_new:N \l__datatool_hour_int
\int_new:N \l__datatool_minute_int
\int_new:N \l__datatool_second_int
\int_new:N \l__datatool_tzhour_int
\int_new:N \l__datatool_tzminute_int
%    \end{macrocode}
%Integer Julian Day Number:
%    \begin{macrocode}
\int_new:N \l__datatool_julian_int
\int_new:N \l__datatool_local_julian_int
%    \end{macrocode}
%Floating point values for the Julian date and time of day fraction:
%    \begin{macrocode}
\fp_new:N \l__datatool_julian_fp
\fp_new:N \l__datatool_time_fp
%    \end{macrocode}
%Re-initialise date/time individual scratch variables.
%That is, set them all back to zero.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_var_init:
{
  \int_zero:N \l__datatool_year_int
  \int_zero:N \l__datatool_month_int
  \int_zero:N \l__datatool_day_int
  \int_zero:N \l__datatool_hour_int
  \int_zero:N \l__datatool_minute_int
  \int_zero:N \l__datatool_second_int
  \int_zero:N \l__datatool_tzhour_int
  \int_zero:N \l__datatool_tzminute_int
  \int_zero:N \l__datatool_julian_int
  \fp_zero:N \l__datatool_julian_fp
  \fp_zero:N \l__datatool_time_fp
}
%    \end{macrocode}
%
% Date time stamp regular expression. Time zone may be omitted.
% Separator between date and time may be either space or \qt{T}.
%    \begin{macrocode}
\regex_const:Nn \c_datatool_timestamp_regex
 {
   \A \s*
   ( \d+ ) \x { 2D } ( \d{2} ) \x { 2D } ( \d{2} )
   (?: \x { 54 } | \s+ )
   ( \d{2} ) \x { 3A } ( \d{2} ) \x { 3A } ( \d{2} )
   ( \x { 5A } | [\+\-]? \d{2} \x { 3A } ? \d{2} ) ?
   \s* \Z
 }
%    \end{macrocode}
%Date only.
%    \begin{macrocode}
\regex_const:Nn \c_datatool_date_regex
 {
   \A \s*
   ( \d+ ) \x { 2D } ( \d{2} )\x { 2D } ( \d{2} ) 
   \s* \Z
 }
%    \end{macrocode}
%Time only. Seconds are optional.
%    \begin{macrocode}
\regex_const:Nn \c_datatool_time_regex
 {
   \A \s*
   ( [\+\-]? \d{2} ) \x { 3A } ( \d{2} ) (?: \x { 3A } ( \d{2} ) )?
   \s* \Z
 }
%    \end{macrocode}
%Maybe timestamp or just date or just time:
%    \begin{macrocode}
\regex_const:Nn \c_datatool_temporal_regex
 {
   (?:
      (?: \d+ ) \x { 2D } (?: \d{2} ) \x { 2D } (?: \d{2} )
      (?:
         (?: \x { 54 } | \s+ )
         (?: \d{2} ) \x { 3A } (?: \d{2} ) \x { 3A } (?: \d{2} )
         (?: \x { 5A } | [\+\-]? \d{2} \x { 3A } ? \d{2} ) ?
      ) ?
   ) |
   (?:
     (?: \d{2} ) \x { 3A } (?: \d{2} ) (?: \x { 3A } (?: \d{2} ) )?
   )
 }
%    \end{macrocode}
%
%\paragraph{Timestamp Sequences}
%
%Time stamp sequences are just a nine-item sequence, where each
%item is a numeric value. All but the year (item 1) and day of week
%(item 9) should be correctly zero-padded. The year should include the century.
%For example, if the year is given as 24 then that means 0024 not
%2024. The day of week item may be negative to indicate a missing
%value.
%
%The following commands are simply wrappers that use normal sequence commands.
%
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_new:N
{
  \seq_new:N #1
  \datatool_timestamp_zero:N #1
}
%    \end{macrocode}
%
%Constant representing 0000-00-00T00:00:00+00:00
%(used for initialisation).
%    \begin{macrocode}
\seq_const_from_clist:Nn \c_datatool_timestamp_zero_seq
 { 0000 , 00 , 00 , 00 , 00 , 00 , +00 , 00, -1 }
%    \end{macrocode}
%
%Resets all elements of a timestamp sequence.
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_zero:N
{
  \seq_set_eq:NN #1 \c_datatool_timestamp_zero_seq
}
%    \end{macrocode}
%
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_seq_init:
{
  \datatool_timestamp_zero:N
    \l__datatool_datetime_seq
}
%    \end{macrocode}
%
%The following commands assume the provided sequence has been
%correctly defined with nine numeric items, as described above.
%Check for empty argument as regex match can have optional parts.
%
%Extract year from a timestamp sequence. (First item.)
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_get_year:N
 {
   \seq_item:Nn #1 { 1 }
 }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_yr:
 {
    \datatool_timestamp_get_year:N
      \l__datatool_datetime_seq
 }
%    \end{macrocode}
%Set year (item 1). If empty, assume current year.
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_set_year:Nn
 {
   \tl_if_empty:nTF { #2 }
    {
      \exp_args:NNne \seq_set_item:Nnn #1
        { 1 } { \int_use:N \c_sys_year_int }
    }
    {
      \seq_set_item:Nnn #1 { 1 } { #2 }
    }
 }
\cs_generate_variant:Nn \datatool_timestamp_set_year:Nn
  { Ne, NV }
%    \end{macrocode}
%As above but add century if between 0 and 99.
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_set_year_add_century:Nn
 {
   \tl_if_empty:nTF { #2 }
    {
      \exp_args:NNne \seq_set_item:Nnn #1
        { 1 } { \int_use:N \c_sys_year_int }
    }
    {
      \bool_lazy_or:nnTF
         { \int_compare_p:nNn { #2 } < { \c_zero_int } }
         { \int_compare_p:nNn { #2 } > { 99 } }
       {
         \seq_set_item:Nnn #1 { 1 } { #2 }
       }
       {
         \datatool_timestamp_set_year:Ne #1
          { 
           \int_eval:n
            { 
              ( \int_div_truncate:nn { \c_sys_year_int } { 100 } ) * 100
              + #2
            }
          }
       }
    }
 }
\cs_generate_variant:Nn \datatool_timestamp_set_year_add_century:Nn
  { Ne, NV }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_set_yr:n
 {
   \datatool_timestamp_set_year:Nn 
     \l__datatool_datetime_seq { #1 }
 }
\cs_generate_variant:Nn \__datatool_tm_set_yr:n { e }
%    \end{macrocode}
%
%Extract month from sequence. (Second item.)
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_get_month:N
 {
   \seq_item:Nn #1 { 2 }
 }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_mn:
 {
   \datatool_timestamp_get_month:N
     \l__datatool_datetime_seq 
 }
%    \end{macrocode}
%Set month (item 2):
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_set_month:Nn
 {
   \tl_if_empty:nTF { #2 }
    {
      \seq_set_item:Nnn #1 { 2 } { 00 }
    }
    {
      \exp_args:NNne \seq_set_item:Nnn
       #1 { 2 } { \datatool_two_digits:n { #2 } }
    }
 }
\cs_generate_variant:Nn \datatool_timestamp_set_month:Nn
  { Ne, NV }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_set_mn:n
 {
   \datatool_timestamp_set_month:Nn
    \l__datatool_datetime_seq { #1 }
 }
\cs_generate_variant:Nn \__datatool_tm_set_mn:n { e }
%    \end{macrocode}
%
%Extract day from sequence. (Third item.)
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_get_day:N
 {
   \seq_item:Nn #1 { 3 }
 }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_dy:
 {
   \datatool_timestamp_get_day:N
     \l__datatool_datetime_seq 
 }
%    \end{macrocode}
%Set day (item 3):
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_set_day:Nn
 {
   \tl_if_empty:nTF { #2 }
    {
      \seq_set_item:Nnn #1 { 3 } { 00 }
    }
    {
      \exp_args:NNne \seq_set_item:Nnn
       #1 { 3 } { \datatool_two_digits:n { #2 } }
    }
 }
\cs_generate_variant:Nn \datatool_timestamp_set_day:Nn
  { Ne, NV }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_set_dy:n
 {
   \datatool_timestamp_set_day:Nn
    \l__datatool_datetime_seq { #1 }
 }
\cs_generate_variant:Nn \__datatool_tm_set_dy:n { e }
%    \end{macrocode}
%
%Extract hour from sequence. (Fourth item.)
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_get_hour:N
 {
   \seq_item:Nn #1 { 4 }
 }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_hr:
 {
   \datatool_timestamp_get_hour:N
     \l__datatool_datetime_seq 
 }
%    \end{macrocode}
%Set hour (item 4):
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_set_hour:Nn
 {
   \tl_if_empty:nTF { #2 }
    {
      \seq_set_item:Nnn #1 { 4 } { 00 }
    }
    {
      \exp_args:NNne \seq_set_item:Nnn
       #1 { 4 } { \datatool_two_digits:n { #2 } }
    }
 }
\cs_generate_variant:Nn \datatool_timestamp_set_hour:Nn
  { Ne, NV }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_set_hr:n
 {
   \datatool_timestamp_set_hour:Nn
      \l__datatool_datetime_seq { #1 }
 }
\cs_generate_variant:Nn \__datatool_tm_set_hr:n { e }
%    \end{macrocode}
%
%Extract minute from sequence. (Fifth item.)
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_get_minute:N
 {
   \seq_item:Nn #1 { 5 }
 }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_mi:
 {
   \datatool_timestamp_get_minute:N
     \l__datatool_datetime_seq
 }
%    \end{macrocode}
%Set minute (item 5):
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_set_minute:Nn
 {
   \tl_if_empty:nTF { #2 }
    {
      \seq_set_item:Nnn #1 { 5 } { 00 }
    }
    {
      \exp_args:NNne \seq_set_item:Nnn
       #1 { 5 } { \datatool_two_digits:n { #2 } }
    }
 }
\cs_generate_variant:Nn \datatool_timestamp_set_minute:Nn
  { Ne, NV }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_set_mi:n
 {
   \datatool_timestamp_set_minute:Nn
     \l__datatool_datetime_seq { #1 }
 }
\cs_generate_variant:Nn \__datatool_tm_set_mi:n { e }
%    \end{macrocode}
%
%Extract second from sequence. (Sixth item.)
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_get_second:N
 {
   \seq_item:Nn #1 { 6 }
 }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_se:
 {
   \datatool_timestamp_get_second:N
     \l__datatool_datetime_seq
 }
%    \end{macrocode}
%Set second (item 6):
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_set_second:Nn
 {
   \tl_if_empty:nTF { #2 }
    {
      \seq_set_item:Nnn #1 { 6 } { 00 }
    }
    {
      \exp_args:NNne \seq_set_item:Nnn
       #1 { 6 } { \datatool_two_digits:n { #2 } }
    }
 }
\cs_generate_variant:Nn \datatool_timestamp_set_second:Nn
  { Ne, NV }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_set_se:n
 {
   \datatool_timestamp_set_second:Nn
     \l__datatool_datetime_seq { #1 }
 }
\cs_generate_variant:Nn \__datatool_tm_set_se:n { e }
%    \end{macrocode}
%
%Extract time zone hour from sequence. (Seventh item.)
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_get_tzhour:N
 {
   \seq_item:Nn #1 { 7 }
 }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_tzh:
 {
   \datatool_timestamp_get_tzhour:N
    \l__datatool_datetime_seq 
 }
%    \end{macrocode}
%Set time zone hour (item 7):
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_set_tzhour:Nn
 {
   \tl_if_empty:nTF { #2 }
    {
      \seq_set_item:Nnn #1 { 7 } { +00 }
    }
    {
      \exp_args:NNne \seq_set_item:Nnn
       #1 { 7 } 
       { \datatool_signed_two_digits:n { #2 } }
    }
 }
\cs_generate_variant:Nn \datatool_timestamp_set_tzhour:Nn
  { Ne, NV }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_set_tzh:n
 {
   \datatool_timestamp_set_tzhour:Nn
     \l__datatool_datetime_seq { #1 } 
 }
\cs_generate_variant:Nn \__datatool_tm_set_tzh:n { e }
%    \end{macrocode}
%
%Extract time zone minute from sequence. (Eighth item.)
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_get_tzminute:N
 {
   \seq_item:Nn #1 { 8 }
 }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_tzm:
 {
   \datatool_timestamp_get_tzminute:N
    \l__datatool_datetime_seq 
 }
%    \end{macrocode}
%Set time zone minute (item 8):
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_set_tzminute:Nn
 {
   \tl_if_empty:nTF { #2 }
    {
      \seq_set_item:Nnn #1 { 8 } { 00 }
    }
    {
      \exp_args:NNne \seq_set_item:Nnn
       #1 { 8 } { \datatool_two_digits:n { #2 } }
    }
 }
\cs_generate_variant:Nn \datatool_timestamp_set_tzminute:Nn
  { Ne, NV }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_set_tzm:n
 {
   \datatool_timestamp_set_tzminute:Nn 
     \l__datatool_datetime_seq { #1 } 
 }
\cs_generate_variant:Nn \__datatool_tm_set_tzm:n { e }
%    \end{macrocode}
%
%Extract day of week from sequence. (Ninth item.)
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_get_dow:N
 {
   \seq_item:Nn #1 { 9 }
 }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_dow:
 {
   \datatool_timestamp_get_dow:N
    \l__datatool_datetime_seq 
 }
%    \end{macrocode}
%Set day of week (item 9):
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_set_dow:Nn
 {
   \tl_if_empty:nTF { #2 }
    {
      \seq_set_item:Nnn #1 { 9 } { -1 }
    }
    {
      \exp_args:NNne \seq_set_item:Nnn
       #1 { 9 } { #2 }
    }
 }
\cs_generate_variant:Nn \datatool_timestamp_set_dow:Nn
  { Ne, NV }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_set_dow:n
 {
   \datatool_timestamp_set_dow:Nn 
     \l__datatool_datetime_seq { #1 } 
 }
\cs_generate_variant:Nn \__datatool_tm_set_dow:n { e, V }
%    \end{macrocode}
%
%
%Convert timestamp sequence to ISO string. Note that the supplied
%argument must be a timestamp sequence. That is, a sequence with 8
%numeric items that have already been correctly zero-padded.
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_to_iso:N
 {
   \datatool_timestamp_get_year:N #1
   -
   \datatool_timestamp_get_month:N #1
   -
   \datatool_timestamp_get_day:N #1
   T
   \datatool_timestamp_get_hour:N #1
   \c_colon_str
   \datatool_timestamp_get_minute:N #1
   \c_colon_str
   \datatool_timestamp_get_second:N #1
   \datatool_timestamp_get_tzhour:N #1
   \c_colon_str
   \datatool_timestamp_get_tzminute:N #1
 }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_iso:
 {
   \datatool_timestamp_to_iso:N
     \l__datatool_datetime_seq
 }
%    \end{macrocode}
%
%Just the year, month and date:
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_to_iso_date:N
 {
   \datatool_timestamp_get_year:N #1
   -
   \datatool_timestamp_get_month:N #1
   -
   \datatool_timestamp_get_day:N #1
 }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_iso_date:
 {
   \datatool_timestamp_to_iso_date:N
     \l__datatool_datetime_seq
 }
%    \end{macrocode}
%
%Just the time (hours, minutes and seconds):
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_to_iso_time:N
 {
   \datatool_timestamp_get_hour:N #1
   \c_colon_str
   \datatool_timestamp_get_minute:N #1
   \c_colon_str
   \datatool_timestamp_get_second:N #1
 }
%    \end{macrocode}
%Shortcut for internal scratch variable.
%    \begin{macrocode}
\cs_new:Nn \__datatool_tm_iso_time:
 {
   \datatool_timestamp_to_iso_time:N
     \l__datatool_datetime_seq
 }
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_timestamp\_to\_datetime\_datum:NnN} 
%\meta{timestamp seq-var}
%\marg{Julian Date}
%\meta{datum cs}
%\end{definition}
%Convert a timestamp sequence variable to a datetime datum variable.
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_to_datetime_datum:NnN
 {
   \tl_set:Ne #3
    {
      \exp_not:N \__datatool_datum:nnnn
       {% formatted string
         \exp_not:N \DataToolDateTimeFmt
           {
             { \datatool_timestamp_get_year:N #1 }
             { \datatool_timestamp_get_month:N #1 }
             { \datatool_timestamp_get_day:N #1 }
             { \datatool_timestamp_get_dow:N #1 }
           }
           {
             { \datatool_timestamp_get_hour:N #1 }
             { \datatool_timestamp_get_minute:N #1 }
             { \datatool_timestamp_get_second:N #1 }
           }
           {
             { \datatool_timestamp_get_tzhour:N #1 }
             { \datatool_timestamp_get_tzminute:N #1 }
           }
       }
       {% value
         \exp_not:N \DTLtemporalvalue
          { #2 }
          { 
            \datatool_timestamp_to_iso:N #1
          }
       }
       { }% no currency symbol
       { \exp_not:N \c_datatool_datetime_int }
    }
 }
\cs_generate_variant:Nn \datatool_timestamp_to_datetime_datum:NnN
 { NeN, NVN }
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_timestamp\_to\_date\_datum:NnN} 
%\meta{timestamp seq-var}
%\marg{Julian Day}
%\meta{datum cs}
%\end{definition}
%Convert a timestamp sequence variable to a date datum variable.
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_to_date_datum:NnN
 {
   \tl_set:Ne #3
    {
      \exp_not:N \__datatool_datum:nnnn
       {% formatted string
         \exp_not:N \DataToolDateFmt
           { \datatool_timestamp_get_year:N #1 }
           { \datatool_timestamp_get_month:N #1 }
           { \datatool_timestamp_get_day:N #1 }
           {
             \datatool_julian_day_to_dow:n { #2 }
           }
       }
       {% value
         \exp_not:N \DTLtemporalvalue
          { #2 }
          { 
            \datatool_timestamp_to_iso_date:N #1
          }
       }
       { }% no currency symbol
       { \exp_not:N \c_datatool_date_int }
    }
 }
\cs_generate_variant:Nn \datatool_timestamp_to_date_datum:NnN
 { NeN, NVN }
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_timestamp\_to\_time\_datum:NnN} 
%\meta{timestamp seq-var}
%\marg{Julian Time}
%\meta{datum cs}
%\end{definition}
%Convert a timestamp sequence variable to a time datum variable.
%    \begin{macrocode}
\cs_new:Nn \datatool_timestamp_to_time_datum:NnN
 {
   \tl_set:Ne #3
    {
      \exp_not:N \__datatool_datum:nnnn
       {% formatted string
         \exp_not:N \DataToolTimeFmt
           { \datatool_timestamp_get_hour:N #1 }
           { \datatool_timestamp_get_minute:N #1 }
           { \datatool_timestamp_get_second:N #1 }
       }
       {% value
         \exp_not:N \DTLtemporalvalue
          { #2 }
          { 
            \datatool_timestamp_to_iso_time:N #1
          }
       }
       { }% no currency symbol
       { \exp_not:N \c_datatool_time_int }
    }
 }
\cs_generate_variant:Nn \datatool_timestamp_to_time_datum:NnN
 { NeN, NVN }
%    \end{macrocode}
%
%\paragraph{Calculations}
%
%Convert Unix time to Julian date.
%    \begin{macrocode}
\cs_new:Nn \datatool_unix_to_julian:n
 {
   \fp_eval:n { #1 / 86400 + 2440587.5 }
 }
%    \end{macrocode}
%Convert a Julian date to Unix time. Note that the Unix time may be
%too large to be a TeX integer. The result will be a decimal.
%    \begin{macrocode}
\cs_new:Nn \datatool_julian_to_unix:n
 {
   \fp_eval:n { (#1 - 2440587.5 ) * 86400 }
 }
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_julian\_day\_to\_dow:n}
%\marg{JDN}
%\end{definition}
%Obtains the day of week from the integer Julian Day Number.
%    \begin{macrocode}
\cs_new:Nn \datatool_julian_day_to_dow:n
 {
   \int_mod:nn { #1 } { 7 }
 }
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_calc\_julian\_date:NN}
%\meta{result fp-var}
%\meta{timestamp seq-var}
%\end{definition}
%Calculate the Julian Date for the timestamp data contained in
%the timestamp sequence variable \meta{timestamp seq-var} and
%store the resulting value in the floating-point variable
%\meta{result fp-var}.
%    \begin{macrocode}
\cs_new:Nn \datatool_calc_julian_date:NN
 {
   \int_compare:nNnTF { \seq_count:N #2 } = { 8 }
    {
      \seq_set_eq:NN \l__datatool_datetime_seq #2
      \_datatool_calc_julian_date:
      \fp_set_eq:NN #1 \l__datatool_julian_fp
    }
    {
      \PackageError { datatool-base }
        {
           Not ~ a ~ timestamp ~ sequence: ~
           \token_to_str:N #2
        }
        {
           The ~ second ~ argument ~ of ~
           \token_to_str:N \datatool_calc_julian_date:NN ~
           \c_string_tl ~ must ~ be ~ a ~ sequence ~ containing ~
           8 ~ integer ~ elements
        }
    }
 }
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_calc\_julian\_day:Nnnn} \meta{result fp-var}
%\marg{year} \marg{month} \marg{day}
%\end{definition}
% Calculate the Julian Day value from the given year, month and day.
% NB very sensitive to rounding! Don't rearrange.
%    \begin{macrocode}
\cs_new:Nn \datatool_calc_julian_day:Nnnn
{
  \int_set:Nn #1
   {
     #4 - 32075
      + 
         \int_div_truncate:nn
          {
             1461
             * 
              ( #2 + 4800 + \int_div_truncate:nn { #3 - 14 } { 12 } )
          }
          { 4 }
      + 
         \int_div_truncate:nn
          {
            367
             *
             ( #3 - 2
               - \int_div_truncate:nn { #3 - 14 } { 12 } * 12 )
          }
          { 12 }
      -
         \int_div_truncate:nn
          {
            3
            *
            (
              \int_div_truncate:nn
              {
                #2 + 4900
                + \int_div_truncate:nn { #3 - 14 } { 12 }
              }
              { 100 }
            )
          }
          { 4 }
   }
}
\cs_generate_variant:Nn \datatool_calc_julian_day:Nnnn
  { NVVV , Neee }
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_from\_julian\_time:nNNN}
%\marg{JT} \meta{hour int var} \meta{minute int var} \meta{second int var}
%\end{definition}
%Calculate the time of day from the Julian time decimal fraction.
%    \begin{macrocode}
\cs_new:Nn \datatool_from_julian_time:nNNN
{
  \int_set:Nn #4 { 43200 + \fp_to_int:n { #1 * 86400 } }
  \int_set:Nn #3
    {
      \int_mod:nn { \int_div_truncate:nn { #4 } { 60 } } { 60 }
    }
  \int_set:Nn #2
    {
      \int_div_truncate:nn { #4 } { 3600 }
    }
  \int_set:Nn #4 { \int_mod:nn { #4 } { 60 } }
}
\cs_generate_variant:Nn \datatool_from_julian_time:nNNN
  { VNNN, eNNN }
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_from\_julian\_day:nNNN}
%\marg{JDN} \meta{year int var} \meta{month int var} \meta{day int var}
%\end{definition}
%Calculate the year, month and day from the Julian Day Number.
%    \begin{macrocode}
\cs_new:Nn \datatool_from_julian_day:nNNN
{
  \int_set:Nn \l__datatool_tmpa_int { #1 + 68569 }
  \int_set:Nn \l__datatool_tmpb_int
    { \int_div_truncate:nn { 4 * \l__datatool_tmpa_int } { 146097 } }
  \int_set:Nn \l__datatool_tmpa_int
    {
      \l__datatool_tmpa_int
       - \int_div_truncate:nn { 146097 * \l__datatool_tmpb_int + 3 } { 4 }
    }
  \int_set:Nn \l__datatool_tmpc_int
    {
      \int_div_truncate:nn { 4000 * \l__datatool_tmpa_int + 1 } { 1461001 }
    }
  \int_set:Nn \l__datatool_tmpa_int
    {
      \l__datatool_tmpa_int
      - \int_div_truncate:nn { 1461 * \l__datatool_tmpc_int } { 4 }
      + 31
    }
  \int_set:Nn \l__datatool_tmpd_int
    { 
      \int_div_truncate:nn { 80 * \l__datatool_tmpa_int } { 2447 }
    }
  \int_set:Nn #4
    {
      \l__datatool_tmpa_int
      - \int_div_truncate:nn { 2447 * \l__datatool_tmpd_int } { 80 }
    }
  \int_set:Nn \l__datatool_tmpa_int
    { 
      \int_div_truncate:nn { \l__datatool_tmpd_int } { 11 }
    }
  \int_set:Nn \l__datatool_tmpd_int
    {
      \l__datatool_tmpd_int + 2 - 12 * \l__datatool_tmpa_int
    }
  \int_set:Nn \l__datatool_tmpc_int
    { 
      100 * ( \l__datatool_tmpb_int - 49 )
      + \l__datatool_tmpc_int + \l__datatool_tmpa_int
    }
  \int_set_eq:NN #2 \l__datatool_tmpc_int 
  \int_set_eq:NN #3 \l__datatool_tmpd_int
}
\cs_generate_variant:Nn \datatool_from_julian_day:nNNN
  { VNNN , eNNN }
%    \end{macrocode}
%
%\begin{definition}
%\marg{JD} \meta{year int var} \meta{month int var} \meta{day int var}
%\meta{hour int var} \meta{minute int var} \meta{second int var}
%\end{definition}
%Calculate the timestamp (UTC+0) from the Julian Date.
%    \begin{macrocode}
\cs_new:Nn \datatool_from_julian_date:nNNNNNN
 {
   \int_set:Nn \l__datatool_julian_int
      { \fp_to_int:n { round ( #1 ) } }
   \datatool_from_julian_day:nNNN
      { \l__datatool_julian_int }
      #2 #3 #4
   \exp_args:Ne \datatool_from_julian_time:nNNN
      { \fp_eval:n { #1 - \l__datatool_julian_int } }
      #5 #6 #7
 }
\cs_generate_variant:Nn \datatool_from_julian_date:nNNNNNN 
 { VNNNNNN, eNNNNNN }
%    \end{macrocode}
%\begin{definition}
%\cs{datatool\_from\_julian\_date:nN} \marg{JD} \meta{timestamp seq-var}
%\end{definition}
%As above but the result should be stored in a timestamp sequence
%variable.
%    \begin{macrocode}
\cs_new:Nn \datatool_from_julian_date:nN
 {
   \datatool_from_julian_date:nNNNNNN 
    { #1 }
    \l__datatool_year_int
    \l__datatool_month_int
    \l__datatool_day_int
    \l__datatool_hour_int
    \l__datatool_minute_int
    \l__datatool_second_int
   \datatool_timestamp_zero:N #2
   \datatool_timestamp_set_year:NV #2 \l__datatool_year_int
   \datatool_timestamp_set_month:NV #2 \l__datatool_month_int
   \datatool_timestamp_set_day:NV #2 \l__datatool_day_int
   \datatool_timestamp_set_hour:NV #2 \l__datatool_hour_int
   \datatool_timestamp_set_minute:NV #2 \l__datatool_minute_int
   \datatool_timestamp_set_second:NV #2 \l__datatool_second_int
   \datatool_timestamp_set_dow:Ne #2 
    {
      \datatool_julian_day_to_dow:n
       { \fp_eval:n { round ( #1 ) } }
    }
 }
\cs_generate_variant:Nn \datatool_from_julian_date:nN { VN , eN }
%    \end{macrocode}
%
%\paragraph{Internal Calculation Functions}
%
%Calculate the Julian day from values provided by the scratch
%date and time variables (which needs to be set first).
%Time zone adjustment needed:
%    \begin{macrocode}
\cs_new:Nn \__datatool_calc_julian_day_tmz:
{
  \__datatool_calc_julian_day:
%    \end{macrocode}
%Keep a record of the unadjusted Julian day:
%    \begin{macrocode}
  \int_set_eq:NN \l__datatool_local_julian_int \l__datatool_julian_int
%    \end{macrocode}
%Implement time zone shift:
%    \begin{macrocode}
  \int_if_zero:nF { \l__datatool_tzminute_int }
    {
      \int_sub:Nn \l__datatool_minute_int { \l__datatool_tzminute_int }
      \int_compare:nNnTF { \l__datatool_minute_int } < { \c_zero_int }
       {
         \int_add:Nn \l__datatool_minute_int { 60 }
         \int_decr:N \l__datatool_hour_int
       }
       {
         \int_compare:nNnT { \l__datatool_minute_int } > { 59 }
          {
            \int_sub:Nn \l__datatool_minute_int { 60 }
            \int_incr:N \l__datatool_hour_int
          }
       }
    }
  \int_if_zero:nF { \l__datatool_tzhour_int }
    {
      \int_sub:Nn \l__datatool_hour_int { \l__datatool_tzhour_int }
    }
  \int_compare:nNnTF { \l__datatool_hour_int } < { \c_zero_int }
    {
      \int_add:Nn \l__datatool_hour_int { 23 }
      \int_decr:N \l__datatool_julian_int
    }
    {
      \int_compare:nNnT { \l__datatool_hour_int } > { 23 }
        {
          \int_decr:Nn \l__datatool_hour_int { 24 }
          \int_incr:N \l__datatool_julian_int
        }
    }
}
%    \end{macrocode}
%Calculate the Julian day number from just the year, month and day
%scratch variables (noon UTC+0).
%    \begin{macrocode}
\cs_new:Nn \__datatool_calc_julian_day:
{
  \datatool_calc_julian_day:Nnnn
    \l__datatool_julian_int
    { \l__datatool_year_int }
    { \l__datatool_month_int }
    { \l__datatool_day_int }
}
%    \end{macrocode}
%Calculate the Julian time, which is a fraction of the day starting
%from 12noon. (Note that this isn't the same as TeX's \cs{time}
%value, which is an integer number of minutes since midnight.)
%For example, 6am is $(6-12)/24 + 0 / 1440 + 0 / 86400 = -0.25$
%and 6pm is $(18-12)/24 + 0/1440 + 0/86400 = +0.25$.
%    \begin{macrocode}
\cs_new:Nn \__datatool_calc_julian_time:
{
  \fp_set:Nn \l__datatool_time_fp
   {
      ( \l__datatool_hour_int - 12) / 24
     + \l__datatool_minute_int / 1440
     + \l__datatool_second_int / 86400
   }
}
%    \end{macrocode}
%The Julian date can then be obtained by adding the Julian day and
%Julian time.
%
%Perform the reverse:
%    \begin{macrocode}
\cs_new:Nn \__datatool_from_julian_time:
{
  \__datatool_from_julian_time:n { \l__datatool_time_fp }
}
\cs_new:Nn \__datatool_from_julian_time:n
{
  \datatool_from_julian_time:nNNN { #1 }
    \l__datatool_hour_int
    \l__datatool_minute_int
    \l__datatool_second_int
}
%    \end{macrocode}
%
%Convert Julian Day number (integer) to Gregorian year, month and
%day.
%    \begin{macrocode}
\cs_new:Nn \__datatool_from_julian_day:
{
  \__datatool_from_julian_day:n { \l__datatool_julian_int }
}
\cs_new:Nn \__datatool_from_julian_day:n
{
   \datatool_from_julian_day:nNNN { #1 }
    \l__datatool_year_int
    \l__datatool_month_int
    \l__datatool_day_int
}
%    \end{macrocode}
%
%Convert from decimal Julian date to UTC+0.
%    \begin{macrocode}
\cs_new:Nn \__datatool_from_julian_date:
{
  \__datatool_from_julian_date:n { \l__datatool_julian_int }
}
\cs_new:Nn \__datatool_from_julian_date:n
{
   \datatool_from_julian_date:nNNNNNN { #1 }
    \l__datatool_year_int
    \l__datatool_month_int
    \l__datatool_day_int
    \l__datatool_hour_int
    \l__datatool_minute_int
    \l__datatool_second_int
}
%    \end{macrocode}
%
%\paragraph{Parsing}
%Timestamps, dates and times must be in ISO format in order to be
%parsed correctly unless support is provided by localisation files.
%
%\begin{definition}
%\cs{datatool\_parse\_timestamp:NnTF}
%\meta{timestamp seq var}
%\marg{ISO}
%\marg{true} \marg{false}
%\end{definition}
%Parse \meta{ISO} timestamp and save the numeric values in the
%provided timestamp sequence variable. Does \meta{true} if \meta{ISO}
%successfully parsed. Otherwise does \meta{false}.
%The timestamp format should be in the form
%\meta{YYYY}-\meta{MM}-\meta{DD}T\meta{hh}:\meta{mm}:\meta{ss}\meta{TZhr}:\meta{TZmin}
%The time zone parts may be omitted or may simply be the letter \qt{Z},
%in which case the time zone is UTC+0.
%The \qt{T} separator may be a space instead. Leading and trailing
%space is ignored.
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_parse_timestamp:Nn #1 #2
  { T, F, TF }
{
  \__datatool_parse_timestamp:nTF { #2 }
   {
     \seq_set_eq:NN #1 \l__datatool_datetime_seq
     \prg_return_true:
   }
   { \prg_return_false: }
}
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_parse\_date:NnTF}
%\meta{timestamp seq var}
%\marg{date}
%\marg{true} \marg{false}
%\end{definition}
%Parses the \meta{date} argument, which must be in the form
%\meta{YYYY}-\meta{MM}-\meta{DD} and saves the year, month and date
%in the provided timestamp sequence variable. Note that the other
%elements aren't changed unless the sequence was originally empty.
%Does \meta{true} if \meta{date} successfully parsed. Otherwise does \meta{false}.
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_parse_date:Nn #1 #2
  { T, F, TF }
{
  \__datatool_parse_date:nTF { #2 }
   {
     \seq_if_empty:NTF #1
      {
        \seq_set_eq:NN #1 \l__datatool_datetime_seq
      }
      {
         \datatool_timestamp_set_year:Ne #1
           { \__datatool_tm_yr: }
         \datatool_timestamp_set_month:Ne #1
           { \__datatool_tm_mn: }
         \datatool_timestamp_set_day:Ne #1
           { \__datatool_tm_day: }
      }
     \prg_return_true:
   }
   { \prg_return_false: }
}
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_parse\_time:NnTF}
%\meta{timestamp seq var}
%\marg{time}
%\marg{true} \marg{false}
%\end{definition}
%Parses the \meta{time} argument, which must be in the form
%\meta{hh}:\meta{mm}:\meta{ss} or \meta{hh}:\meta{mm}
%and saves the hour, minute and second (0, if omitted)
%in the provided timestamp sequence variable. Note that the other
%elements aren't changed unless the sequence was originally empty.
%Does \meta{true} if \meta{time} successfully parsed. Otherwise does \meta{false}.
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_parse_time:Nn #1 #2
  { T, F, TF }
{
  \__datatool_parse_time:nTF { #2 }
   {
     \seq_if_empty:NTF #1
      {
        \seq_set_eq:NN #1 \l__datatool_datetime_seq
      }
      {
         \datatool_timestamp_set_hour:Ne #1
           { \__datatool_tm_hr: }
         \datatool_timestamp_set_minute:Ne #1
           { \__datatool_tm_mi: }
         \datatool_timestamp_set_second:Ne #1
           { \__datatool_tm_se: }
      }
     \prg_return_true:
   }
   { \prg_return_false: }
}
%    \end{macrocode}
%
%
%Parse given timestamp and save values in the datetime scratch
%sequence variable.
%    \begin{macrocode}
\prg_new_conditional:Npnn \__datatool_parse_timestamp:n #1
  { T, F, TF }
{
 \bool_if:NTF \l__datatool_parse_datetime_iso_bool
  {
   \regex_extract_once:NnNTF
     \c_datatool_timestamp_regex { #1 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
%Initialise all elements to zero.
%    \begin{macrocode}
     \__datatool_tm_seq_init:
%    \end{macrocode}
%Year:
%    \begin{macrocode}
      \__datatool_tm_set_yr:e
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
%Month:
%    \begin{macrocode}
      \__datatool_tm_set_mn:e
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
%Day:
%    \begin{macrocode}
      \__datatool_tm_set_dy:e
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
%Hour:
%    \begin{macrocode}
      \__datatool_tm_set_hr:e
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } }
%    \end{macrocode}
%Minute:
%    \begin{macrocode}
      \__datatool_tm_set_mi:e
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 6 } }
%    \end{macrocode}
%Second:
%    \begin{macrocode}
      \__datatool_tm_set_se:e
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 7 } }
%    \end{macrocode}
%Check time zone present.
%    \begin{macrocode}
      \tl_if_empty:eF
        { \seq_item:Nn \l_datatool_timestamp_match_seq { 8 } }
       {
         \exp_args:NNe \regex_extract_once:NnNT
            \c_datatool_time_regex
            { \seq_item:Nn \l_datatool_timestamp_match_seq { 8 } }
            \l_datatool_timestamp_match_seq
          {
            \__datatool_tm_set_tzh:e 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
            \__datatool_tm_set_tzm:e 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
          }
       }
      \prg_return_true:
    }
    { 
     \bool_if:NTF \l__datatool_parse_datetime_regional_bool
      {
        \DTLCurrentLocaleParseTimeStamp
          \l__datatool_datetime_seq
          { #1 }
          { \prg_return_true: }
          { \prg_return_false: }
      }
      { \prg_return_false: }
   }
 }
 {
   \bool_if:NTF \l__datatool_parse_datetime_regional_bool
    {
      \DTLCurrentLocaleParseTimeStamp
        \l__datatool_datetime_seq
        { #1 }
        { \prg_return_true: }
        { \prg_return_false: }
    }
    { \prg_return_false: }
 }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLCurrentLocaleParseTimeStamp}
%\begin{definition}
%\cs{DTLCurrentLocaleParseTimeStamp}\marg{seq}\marg{value}\marg{true}\marg{false}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%Allow for regional support.
%If implemented, this should set the given timestamp sequence variable
% and do \meta{true} otherwise it should do \meta{false}.
%    \begin{macrocode}
\newcommand{\DTLCurrentLocaleParseTimeStamp}[4]{#4}
%    \end{macrocode}
%\end{macro}
%Parse a date (no time):
%    \begin{macrocode}
\prg_new_conditional:Npnn \__datatool_parse_date:n #1
  { T, F, TF }
{
 \bool_if:NTF \l__datatool_parse_datetime_iso_bool
  {
   \regex_extract_once:NnNTF
     \c_datatool_date_regex { #1 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
%Initialise all elements to zero:
%    \begin{macrocode}
      \__datatool_tm_seq_init:
%    \end{macrocode}
%Year:
%    \begin{macrocode}
      \__datatool_tm_set_yr:e
        { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
%Month:
%    \begin{macrocode}
      \__datatool_tm_set_mn:e
        { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
%Day:
%    \begin{macrocode}
      \__datatool_tm_set_dy:e
        { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
      \prg_return_true:
    }
    { 
      \bool_if:NTF \l__datatool_parse_datetime_regional_bool
       {
         \DTLCurrentLocaleParseDate
          \l__datatool_datetime_seq
          { #1 }
          { \prg_return_true: }
          { \prg_return_false: }
       }
       { \prg_return_false: }
    }
 }
 {
   \bool_if:NTF \l__datatool_parse_datetime_regional_bool
    {
      \DTLCurrentLocaleParseDate
       \l__datatool_datetime_seq
       { #1 }
       { \prg_return_true: }
       { \prg_return_false: }
    }
    { \prg_return_false: }
 }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLCurrentLocaleParseDate}
%\begin{definition}
%\cs{DTLCurrentLocaleParseDate}\marg{seq}\marg{value}\marg{true}\marg{false}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%Allow for regional support.
%If implemented, this should set the given date sequence variable
% and do \meta{true} otherwise it should do \meta{false}.
%    \begin{macrocode}
\newcommand{\DTLCurrentLocaleParseDate}[4]{#4}
%    \end{macrocode}
%\end{macro}
%Parse a time (no date):
%    \begin{macrocode}
\prg_new_conditional:Npnn \__datatool_parse_time:n #1
  { T, F, TF }
{
 \bool_if:NTF \l__datatool_parse_datetime_iso_bool
  {
   \regex_extract_once:NnNTF
     \c_datatool_time_regex { #1 }
     \l_datatool_timestamp_match_seq
    {
      \__datatool_tm_seq_init:
%    \end{macrocode}
%Hour:
%    \begin{macrocode}
      \__datatool_tm_set_hr:e 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
%Minute:
%    \begin{macrocode}
      \__datatool_tm_set_mi:e 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
%Second:
%    \begin{macrocode}
      \__datatool_tm_set_se:e 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
      \prg_return_true:
    }
    { 
      \bool_if:NTF \l__datatool_parse_datetime_regional_bool
       {
         \DTLCurrentLocaleParseTime
           \l__datatool_datetime_seq
           { #1 }
           { \prg_return_true: }
           { \prg_return_false: }
       }
       { \prg_return_false: }
    }
   }
   {
    \bool_if:NTF \l__datatool_parse_datetime_regional_bool
     {
       \DTLCurrentLocaleParseTime
         \l__datatool_datetime_seq
         { #1 }
         { \prg_return_true: }
         { \prg_return_false: }
     }
     { \prg_return_false: }
   }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLCurrentLocaleParseTime}
%\begin{definition}
%\cs{DTLCurrentLocaleParseTime}\marg{seq}\marg{value}\marg{true}\marg{false}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%Allow for regional support.
%If implemented, this should set the given date sequence variable
% and do \meta{true} otherwise it should do \meta{false}.
%    \begin{macrocode}
\newcommand{\DTLCurrentLocaleParseTime}[4]{#4}
%    \end{macrocode}
%\end{macro}
%
%Parse argument to determine if it's a timestamp, date only, time
%only or none of those. In the last case, false part is done,
%otherwise \cs{@dtl@datatype} will be set to the applicable data
%type, and the Julian Day number, Julian Date decimal and time
%fraction will be calculated, and then true part is done.
%    \begin{macrocode}
\prg_new_conditional:Npnn \__datatool_parse_datetime:n #1
  { T, F, TF }
{
  \seq_clear:N \l__datatool_datetime_seq
  \fp_zero:N \l__datatool_julian_fp
  \fp_zero:N \l__datatool_time_fp
  \int_zero:N \l__datatool_local_julian_int
  \int_zero:N \l__datatool_julian_int
  \__datatool_parse_timestamp:nTF { #1 }
   {
     \@dtl@datatype = \c_datatool_datetime_int
   }
   {
     \__datatool_parse_date:nTF { #1 }
      {
        \@dtl@datatype = \c_datatool_date_int
      }
      {
        \__datatool_parse_time:nT { #1 }
         {
           \@dtl@datatype = \c_datatool_time_int
         }
      }
   }
  \seq_if_empty:NTF \l__datatool_datetime_seq
   { \prg_return_false: }
   {
     \_datatool_calc_julian_date:
     \prg_return_true:
   }
}
%    \end{macrocode}
%Calculate the Julian Date from the scratch timestamp sequence.
%    \begin{macrocode}
\cs_new:Nn \_datatool_calc_julian_date:
 {
     \int_set:Nn \l__datatool_year_int { \__datatool_tm_yr: }
     \int_set:Nn \l__datatool_month_int { \__datatool_tm_mn: }
     \int_set:Nn \l__datatool_day_int { \__datatool_tm_dy: }
     \int_set:Nn \l__datatool_hour_int { \__datatool_tm_hr: }
     \int_set:Nn \l__datatool_minute_int { \__datatool_tm_mi: }
     \int_set:Nn \l__datatool_second_int { \__datatool_tm_se: }
     \int_set:Nn \l__datatool_tzhour_int { \__datatool_tm_tzh: }
     \int_set:Nn \l__datatool_tzminute_int { \__datatool_tm_tzm: }
%    \end{macrocode}
%Calculate the Julian day with time zone adjustment.
%    \begin{macrocode}
     \__datatool_calc_julian_day_tmz:
%    \end{macrocode}
%Update the day of week item in the timestamp sequence.
%    \begin{macrocode}
     \__datatool_tm_set_dow:e
      {
        \datatool_julian_day_to_dow:n
         { \l__datatool_local_julian_int }
      }
%    \end{macrocode}
%Calculate the Julian date. First the time part:
%    \begin{macrocode}
     \__datatool_calc_julian_time:
%    \end{macrocode}
%Then add the day and time values:
%    \begin{macrocode}
     \fp_set:Nn \l__datatool_julian_fp
      {
        \int_use:N \l__datatool_julian_int
        + \l__datatool_time_fp
      }
 }
%    \end{macrocode}
%Set the update token variable for a temporal datum
%    \begin{macrocode}
\cs_new:Nn \__datatool_set_datetime_value:
 {
   \__datatool_set_datetime_value:n { }
 }
\cs_new:Nn \__datatool_set_datetime_value:n
 {
   \__datatool_set_datetime_value:NNn
       \l__datatool_datum_update_value_tl
       \l__datatool_datum_value_tl
       { #1 } 
 }
\cs_new:Nn \__datatool_set_datetime_value:Nn
 {
   \__datatool_set_datetime_value:NNn
       #1 \l__datatool_datum_value_tl { #2 } 
 }
%    \end{macrocode}
%
%Some common formats for use with regions.
%First a property variable to store timezone mappings.
%    \begin{macrocode}
\prop_new:N \l_datatool_timezone_map_prop
\tl_new:N \l_datatool_timezone_map_value_tl
%    \end{macrocode}
%The regions can store applicable time zones in the format 
%\meta{region-tag} \verb|/| \meta{zone-id}
%(for example, \texttt{GB / GMT} and \texttt{GB / BST}).
%The value should be in the form \meta{sign}\meta{hh}:\meta{mm}.
%
%    \begin{macrocode}
\cs_new:Nn \datatool_region_set_timezone_map:nn
 {
   \prop_put:Nnn \l_datatool_timezone_map_prop { #1 } { #2 }
 }
%    \end{macrocode}
%Get the value and store in the scratch token list.
%    \begin{macrocode}
\cs_new:Nn \datatool_region_get_timezone_map:n
 {
   \prop_get:NnN \l_datatool_timezone_map_prop
     { #1 } 
     \l_datatool_timezone_map_value_tl
 }
%    \end{macrocode}
%Add regionless identifiers UTC and Z:
%    \begin{macrocode}
\datatool_region_set_timezone_map:nn { UTC } { +00:00 }
\datatool_region_set_timezone_map:nn { Z } { +00:00 }
%    \end{macrocode}
%
%    \begin{macrocode}
\newcommand{\DTLCurrentLocaleGetTimeZoneMap}[1]{
  \tl_set_eq:NN \l_datatool_timezone_map_value_tl \q_no_value
}
%    \end{macrocode}
% Similarly for month name to number mappings:
%    \begin{macrocode}
\prop_new:N \l_datatool_monthname_map_prop
\tl_new:N \l_datatool_monthname_map_value_tl
%    \end{macrocode}
%Set month name mapping:
%    \begin{macrocode}
\cs_new:Nn \datatool_region_set_monthname_map:nn
 {
   \prop_put:Nnn \l_datatool_monthname_map_prop { #1 } { #2 }
 }
%    \end{macrocode}
%Get the value and store in the scratch token list.
%    \begin{macrocode}
\cs_new:Nn \datatool_region_get_monthname_map:n
 {
   \prop_get:NnN \l_datatool_monthname_map_prop
     { #1 } 
     \l_datatool_monthname_map_value_tl
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\newcommand{\DTLCurrentLocaleGetMonthNameMap}[1]{
  \tl_set_eq:NN \l_datatool_monthname_map_value_tl \q_no_value
}
%    \end{macrocode}
%
% Day of month regular expression (1-31):
%    \begin{macrocode}
\regex_const:Nn \c_datatool_day_of_month_regex
 {   
   ( 0?[1-9] | [12][0-9] | 30 | 31 )
 }
%    \end{macrocode}
%One or two digit month number (1-12):
%    \begin{macrocode}
\regex_const:Nn \c_datatool_month_number_regex
 {   
  ( 0?[1-9] | 10 | 11 | 12 )
 }
%    \end{macrocode}
%One or two digit hour (0-24):
%    \begin{macrocode}
\regex_const:Nn \c_datatool_hour_regex
 {   
  ( 0?[0-9] | 1[0-9] | 2[0-4] )
 }
%    \end{macrocode}
%Two digit minute or second (0-59):
%    \begin{macrocode}
\regex_const:Nn \c_datatool_minsec_regex
 {   
   [0-5][0-9] 
 }
%    \end{macrocode}
%Time zone offset, optionally prefixed with GMT or UTC or UT:
%    \begin{macrocode}
\regex_const:Nn \c_datatool_timezone_regex
 {   
   (?:GMT|UTC?)?
   ( [\+\-]? \ur{c_datatool_hour_regex} )
   (?: \: ? ( \ur{c_datatool_minsec_regex} ) ) ?
 }
%    \end{macrocode}
%
%Time zone (either uppercase identifier, which will require a
%mapping, or offset):
%    \begin{macrocode}
\regex_const:Nn \c_datatool_timezone_id_regex
 {
   [A-Z]+ | \ur{c_datatool_timezone_regex}
 }
%    \end{macrocode}
%\begin{macro}{\DTLCurrentLocaleIfpmTF}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLCurrentLocaleIfpmTF}\marg{id}\marg{true}\marg{false}
%\end{definition}
%If the identifier is recognised as indicating the afternoon, do
%true, otherwise do false. Localisation support should redefine this
%as appropriate.
%    \begin{macrocode}
\newcommand \DTLCurrentLocaleIfpmTF [ 3 ]
 {
   \tl_if_eq:nnTF { #1 } { pm } { #2 } { #3 }
 }
%    \end{macrocode}
%\end{macro}
%
%The following regular expression constants are for common formats.
%Localisation support may use the applicable expression or provide
%custom patterns. Note that it's important that the captured groups match the
%applicable parsing function.
%
%Non-anchored expressions allow them to be used as a sub-expression
%for timestamp parsing.
%
% Common dd mm yyyy formats.
%    \begin{macrocode}
\regex_const:Nn \c_datatool_slash_ddmmyyyy_date_regex
 {   
   ( \ur{c_datatool_day_of_month_regex} )
    \/ 
   ( \ur{c_datatool_month_number_regex}  )
    \/ ( \d+ )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_slash_anchored_ddmmyyyy_date_regex
 {
   \A \s*
   ( \ur{c_datatool_day_of_month_regex} )
    \/ 
   ( \ur{c_datatool_month_number_regex}  )
    \/ ( \d+ )
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_slash_ddmmyy_date_regex
 {   
   ( \ur{c_datatool_day_of_month_regex} )
    \/ 
   ( \ur{c_datatool_month_number_regex}  )
    \/ ( \d{2} )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_slash_anchored_ddmmyy_date_regex
 {   
   \A \s*
   ( \ur{c_datatool_day_of_month_regex} )
    \/ 
   ( \ur{c_datatool_month_number_regex}  )
    \/ ( \d{2} )
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_hyphen_ddmmyyyy_date_regex
 {   
   ( \ur{c_datatool_day_of_month_regex} )
    \-
   ( \ur{c_datatool_month_number_regex}  )
    \- ( \d+ )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_hyphen_anchored_ddmmyyyy_date_regex
 {   
   \A \s*
   ( \ur{c_datatool_day_of_month_regex} )
    \-
   ( \ur{c_datatool_month_number_regex}  )
    \- ( \d+ )
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_hyphen_ddmmyy_date_regex
 {   
   ( \ur{c_datatool_day_of_month_regex} )
    \-
   ( \ur{c_datatool_month_number_regex} )
    \- ( \d{2} )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_hyphen_anchored_ddmmyy_date_regex
 {   
   \A \s*
   ( \ur{c_datatool_day_of_month_regex} )
    \-
   ( \ur{c_datatool_month_number_regex} )
    \- ( \d{2} )
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_dot_ddmmyyyy_date_regex
 {   
   ( \ur{c_datatool_day_of_month_regex} )
    \.
   ( \ur{c_datatool_month_number_regex} )
    \. ( \d+ )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_dot_anchored_ddmmyyyy_date_regex
 {   
   \A \s*
   ( \ur{c_datatool_day_of_month_regex} )
    \.
   ( \ur{c_datatool_month_number_regex} )
    \. ( \d+ )
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_dot_ddmmyy_date_regex
 {   
   ( \ur{c_datatool_day_of_month_regex} )
    \.
   ( \ur{c_datatool_month_number_regex} )
    \. ( \d{2} )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_dot_anchored_ddmmyy_date_regex
 {   
   \A \s*
   ( \ur{c_datatool_day_of_month_regex} )
    \.
   ( \ur{c_datatool_month_number_regex} )
    \. ( \d{2} )
   \s* \Z
 }
%    \end{macrocode}
%
% Common mm dd yyyy formats.
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_slash_mmddyyyy_date_regex
 {   
   ( \ur{c_datatool_month_number_regex} )
    \/
   ( \ur{c_datatool_day_of_month_regex} )
    \/ ( \d+ )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_slash_anchored_mmddyyyy_date_regex
 {   
   \A \s*
   ( \ur{c_datatool_month_number_regex} )
    \/
   ( \ur{c_datatool_day_of_month_regex} )
    \/ ( \d+ )
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_slash_mmddyy_date_regex
 {   
   ( \ur{c_datatool_month_number_regex} )
    \/
   ( \ur{c_datatool_day_of_month_regex} )
    \/ ( \d{2} )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_slash_anchored_mmddyy_date_regex
 {   
   \A \s*
   ( \ur{c_datatool_month_number_regex} )
    \/
   ( \ur{c_datatool_day_of_month_regex} )
    \/ ( \d{2} )
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_hyphen_mmddyyyy_date_regex
 {   
   ( \ur{c_datatool_month_number_regex} )
    \-
   ( \ur{c_datatool_day_of_month_regex} )
    \- ( \d+ )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_hyphen_anchored_mmddyyyy_date_regex
 {   
   \A \s*
   ( \ur{c_datatool_month_number_regex} )
    \-
   ( \ur{c_datatool_day_of_month_regex} )
    \- ( \d+ )
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_hyphen_mmddyy_date_regex
 {   
   ( \ur{c_datatool_month_number_regex} )
    \-
   ( \ur{c_datatool_day_of_month_regex} )
    \- ( \d{2} )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_hyphen_anchored_mmddyy_date_regex
 {   
   \A \s*
   ( \ur{c_datatool_month_number_regex} )
    \-
   ( \ur{c_datatool_day_of_month_regex} )
    \- ( \d{2} )
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_dot_mmddyyyy_date_regex
 {   
   ( \ur{c_datatool_month_number_regex} )
    \.
   ( \ur{c_datatool_day_of_month_regex} )
    \. ( \d+ )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_dot_anchored_mmddyyyy_date_regex
 {   
   \A \s*
   ( \ur{c_datatool_month_number_regex} )
    \.
   ( \ur{c_datatool_day_of_month_regex} )
    \. ( \d+ )
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_dot_mmddyy_date_regex
 {   
   ( \ur{c_datatool_month_number_regex} )
    \.
   ( \ur{c_datatool_day_of_month_regex} )
    \. ( \d{2} )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_dot_anchored_mmddyy_date_regex
 {   
   \A \s*
   ( \ur{c_datatool_month_number_regex} )
    \.
   ( \ur{c_datatool_day_of_month_regex} )
    \. ( \d{2} )
   \s* \Z
 }
%    \end{macrocode}
%
% Common yyyy mm dd formats.
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_slash_yyyymmdd_date_regex
 {   
   ( \d+ ) \/ 
   ( \ur{c_datatool_month_number_regex} )
    \/
   ( \ur{c_datatool_day_of_month_regex} )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_slash_anchored_yyyymmdd_date_regex
 {   
   \A \s*
   ( \d+ ) \/ 
   ( \ur{c_datatool_month_number_regex} )
    \/
   ( \ur{c_datatool_day_of_month_regex} )
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_slash_yymmdd_date_regex
 {   
   ( \d{2} ) \/ 
   ( \ur{c_datatool_month_number_regex} )
    \/
   ( \ur{c_datatool_day_of_month_regex} )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_slash_anchored_yymmdd_date_regex
 {   
   \A \s*
   ( \d{2} ) \/ 
   ( \ur{c_datatool_month_number_regex} )
    \/
   ( \ur{c_datatool_day_of_month_regex} )
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_hyphen_yyyymmdd_date_regex
 {   
   ( \d+ ) \- 
   ( \ur{c_datatool_month_number_regex} )
    \-
   ( \ur{c_datatool_day_of_month_regex} )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_hyphen_anchored_yyyymmdd_date_regex
 {   
   \A \s*
   ( \d+ ) \- 
   ( \ur{c_datatool_month_number_regex} )
    \-
   ( \ur{c_datatool_day_of_month_regex} )
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_hyphen_yymmdd_date_regex
 {   
   ( \d{2} ) \- 
   ( \ur{c_datatool_month_number_regex} )
    \-
   ( \ur{c_datatool_day_of_month_regex} )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_hyphen_anchored_yymmdd_date_regex
 {   
   \A \s*
   ( \d{2} ) \- 
   ( \ur{c_datatool_month_number_regex} )
    \-
   ( \ur{c_datatool_day_of_month_regex} )
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_dot_yyyymmdd_date_regex
 {   
   ( \d+ ) \. 
   ( \ur{c_datatool_month_number_regex} )
    \.
   ( \ur{c_datatool_day_of_month_regex} )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_dot_anchored_yyyymmdd_date_regex
 {   
   \A \s*
   ( \d+ ) \. 
   ( \ur{c_datatool_month_number_regex} )
    \.
   ( \ur{c_datatool_day_of_month_regex} )
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_dot_yymmdd_date_regex
 {   
   ( \d{2} ) \. 
   ( \ur{c_datatool_month_number_regex} )
    \.
   ( \ur{c_datatool_day_of_month_regex} )
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_dot_anchored_yymmdd_date_regex
 {   
   \A \s*
   ( \d{2} ) \. 
   ( \ur{c_datatool_month_number_regex} )
    \.
   ( \ur{c_datatool_day_of_month_regex} )
   \s* \Z
 }
%    \end{macrocode}
%
%Time formats. If a regional format needs to support other am/pm
%identifiers, they'll need to provide their own regular expressions.
%    \begin{macrocode}
\regex_const:Nn \c_datatool_colon_hhmmss_time_regex
 {   
   ( \ur{c_datatool_hour_regex} )
   \: ( \ur{c_datatool_minsec_regex} )
   (?: \: ( \ur{c_datatool_minsec_regex} ) ) ?
   (?: \s* ( am | pm ) ) ?
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_colon_anchored_hhmmss_time_regex
 {   
   \A \s*
   ( \ur{c_datatool_hour_regex} )
   \: ( \ur{c_datatool_minsec_regex} )
   (?: \: ( \ur{c_datatool_minsec_regex} ) ) ?
   (?: \s* ( am | pm ) ) ?
   \s* \Z
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_dot_hhmmss_time_regex
 {   
   ( \ur{c_datatool_hour_regex} )
   \. ( \ur{c_datatool_minsec_regex} )
   (?: \. ( \ur{c_datatool_minsec_regex} ) ) ?
   (?: \s* ( am | pm ) ) ?
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_datatool_dot_anchored_hhmmss_time_regex
 {   
   \A \s*
   ( \ur{c_datatool_hour_regex} )
   \. ( \ur{c_datatool_minsec_regex} )
   (?: \. ( \ur{c_datatool_minsec_regex} ) ) ?
   (?: \s* ( am | pm ) ) ?
   \s* \Z
 }
%    \end{macrocode}
%
%The timestamp parsing functions may either have a single regular
%expression that captures both the date and time, or may have
%two separate regular expressions for the date
%and time. In either case, the time zone part is optional and
%will be parsed with \cs{datatool\_parse\_regional\_timezone:NnTF}.
%
%Once the date, time and timestamp have been extracted, they can
%then be matched separately. This would be easier if named captured
%groups were supported, but they aren't at the time of
%writing, so multiple functions are needed to reference the groups by index.
%
%Syntax: \meta{date-regex} \meta{time-regex} \meta{timestamp-var}
%\marg{date} \marg{time} \marg{timezone}
%\marg{return true}\marg{return false}
%    \begin{macrocode}
\cs_new:Nn \datatool_ddmmyyyy_hhmmss_tz_parse_datetime:NNNnnnTF
 {
%    \end{macrocode}
%First extract the date:
%    \begin{macrocode}
   \regex_extract_once:NnNTF #1 { #4 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #3
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #3 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year:Ne #3 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
%Now extract the time.
%    \begin{macrocode}
        \regex_extract_once:NnNTF #2 { #5 }
          \l_datatool_timestamp_match_seq
         {
%    \end{macrocode}
% Set the hour:
%    \begin{macrocode}
           \exp_args:Ne \DTLCurrentLocaleIfpmTF 
            { \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } }
            {
             \datatool_timestamp_set_hour:Ne #3 
               {
                 \int_eval:n 
                  { 
                    12 +
                    \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } 
                  }
               }
            }
            {
             \datatool_timestamp_set_hour:Ne #3 
               { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
            }
%    \end{macrocode}
% Set the minute:
%    \begin{macrocode}
           \datatool_timestamp_set_minute:Ne #3 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
% Set the second:
%    \begin{macrocode}
           \datatool_timestamp_set_second:Ne #3 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
         }
         { #8 }
%    \end{macrocode}
% Parse timezone if present:
%    \begin{macrocode}
       \datatool_parse_regional_timezone:NnTF #3
        { #6 } { #7 } { #8 }
       }
       {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
         #8
       }
    }
    { #8 }
 }
\cs_generate_variant:Nn
 \datatool_ddmmyyyy_hhmmss_tz_parse_datetime:NNNnnnTF
 { NNNeeeTF }
%    \end{macrocode}
%
%As above but century needs to be added.
%    \begin{macrocode}
\cs_new:Nn \datatool_ddmmyy_hhmmss_tz_parse_datetime:NNNnnnTF
 {
%    \end{macrocode}
%First extract the date:
%    \begin{macrocode}
   \regex_extract_once:NnNTF #1 { #4 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #3
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #3 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year_add_century:Ne #3 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
%Now extract the time.
%    \begin{macrocode}
        \regex_extract_once:NnNTF #2 { #5 }
          \l_datatool_timestamp_match_seq
         {
%    \end{macrocode}
% Set the hour:
%    \begin{macrocode}
           \exp_args:Ne \DTLCurrentLocaleIfpmTF 
            { \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } }
            {
             \datatool_timestamp_set_hour:Ne #3 
               {
                 \int_eval:n 
                  { 
                    12 +
                    \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } 
                  }
               }
            }
            {
             \datatool_timestamp_set_hour:Ne #3 
               { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
            }
%    \end{macrocode}
% Set the minute:
%    \begin{macrocode}
           \datatool_timestamp_set_minute:Ne #3 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
% Set the second:
%    \begin{macrocode}
           \datatool_timestamp_set_second:Ne #3 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
         }
         { #8 }
%    \end{macrocode}
% Parse timezone if present:
%    \begin{macrocode}
       \datatool_parse_regional_timezone:NnTF #3
        { #6 } { #7 } { #8 }
       }
       {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
         #8
       }
    }
    { #8 }
 }
\cs_generate_variant:Nn
 \datatool_ddmmyy_hhmmss_tz_parse_datetime:NNNnnnTF
 { NNNeeeTF }
%    \end{macrocode}
%
%Same again but the month is in the first captured group and the day
%is in the second.
%
%Syntax: \meta{date-regex} \meta{time-regex} \meta{timestamp-var}
%\marg{date} \marg{time} \marg{timezone}
%\marg{return true}\marg{return false}
%    \begin{macrocode}
\cs_new:Nn \datatool_mmddyyyy_hhmmss_tz_parse_datetime:NNNnnnTF
 {
%    \end{macrocode}
%First extract the date:
%    \begin{macrocode}
   \regex_extract_once:NnNTF #1 { #4 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #3
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #3 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year:Ne #3 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
%Now extract the time.
%    \begin{macrocode}
        \regex_extract_once:NnNTF #2 { #5 }
          \l_datatool_timestamp_match_seq
         {
%    \end{macrocode}
% Set the hour:
%    \begin{macrocode}
           \exp_args:Ne \DTLCurrentLocaleIfpmTF 
            { \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } }
            {
             \datatool_timestamp_set_hour:Ne #3 
               {
                 \int_eval:n 
                  { 
                    12 +
                    \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } 
                  }
               }
            }
            {
             \datatool_timestamp_set_hour:Ne #3 
               { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
            }
%    \end{macrocode}
% Set the minute:
%    \begin{macrocode}
           \datatool_timestamp_set_minute:Ne #3 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
% Set the second:
%    \begin{macrocode}
           \datatool_timestamp_set_second:Ne #3 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
         }
         { #8 }
%    \end{macrocode}
% Parse timezone if present:
%    \begin{macrocode}
       \datatool_parse_regional_timezone:NnTF #3
        { #6 } { #7 } { #8 }
       }
       {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
         #8
       }
    }
    { #8 }
 }
\cs_generate_variant:Nn
 \datatool_mmddyyyy_hhmmss_tz_parse_datetime:NNNnnnTF
 { NNNeeeTF }
%    \end{macrocode}
%
%As above but century needs to be added:
%    \begin{macrocode}
\cs_new:Nn \datatool_mmddyy_hhmmss_tz_parse_datetime:NNNnnnTF
 {
%    \end{macrocode}
%First extract the date:
%    \begin{macrocode}
   \regex_extract_once:NnNTF #1 { #4 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #3
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #3 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year_add_century:Ne #3 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
%Now extract the time.
%    \begin{macrocode}
        \regex_extract_once:NnNTF #2 { #5 }
          \l_datatool_timestamp_match_seq
         {
%    \end{macrocode}
% Set the hour:
%    \begin{macrocode}
           \exp_args:Ne \DTLCurrentLocaleIfpmTF 
            { \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } }
            {
             \datatool_timestamp_set_hour:Ne #3 
               {
                 \int_eval:n 
                  { 
                    12 +
                    \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } 
                  }
               }
            }
            {
             \datatool_timestamp_set_hour:Ne #3 
               { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
            }
%    \end{macrocode}
% Set the minute:
%    \begin{macrocode}
           \datatool_timestamp_set_minute:Ne #3 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
% Set the second:
%    \begin{macrocode}
           \datatool_timestamp_set_second:Ne #3 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
         }
         { #8 }
%    \end{macrocode}
% Parse timezone if present:
%    \begin{macrocode}
       \datatool_parse_regional_timezone:NnTF #3
        { #6 } { #7 } { #8 }
       }
       {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
         #8
       }
    }
    { #8 }
 }
\cs_generate_variant:Nn
 \datatool_mmddyy_hhmmss_tz_parse_datetime:NNNnnnTF
 { NNNeeeTF }
%    \end{macrocode}
%
%Same again but the year is in the first captured group,
% the month is in the second captured group and the day
%is in the third.
%
%Syntax: \meta{date-regex} \meta{time-regex} \meta{timestamp-var}
%\marg{date} \marg{time} \marg{timezone}
%\marg{return true}\marg{return false}
%    \begin{macrocode}
\cs_new:Nn \datatool_yyyymmdd_hhmmss_tz_parse_datetime:NNNnnnTF
 {
%    \end{macrocode}
%First extract the date:
%    \begin{macrocode}
   \regex_extract_once:NnNTF #1 { #4 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #3
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #3 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year:Ne #3 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
%Now extract the time.
%    \begin{macrocode}
        \regex_extract_once:NnNTF #2 { #5 }
          \l_datatool_timestamp_match_seq
         {
%    \end{macrocode}
% Set the hour:
%    \begin{macrocode}
           \exp_args:Ne \DTLCurrentLocaleIfpmTF 
            { \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } }
            {
             \datatool_timestamp_set_hour:Ne #3 
               {
                 \int_eval:n 
                  { 
                    12 +
                    \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } 
                  }
               }
            }
            {
             \datatool_timestamp_set_hour:Ne #3 
               { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
            }
%    \end{macrocode}
% Set the minute:
%    \begin{macrocode}
           \datatool_timestamp_set_minute:Ne #3 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
% Set the second:
%    \begin{macrocode}
           \datatool_timestamp_set_second:Ne #3 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
         }
         { #8 }
%    \end{macrocode}
% Parse timezone if present:
%    \begin{macrocode}
       \datatool_parse_regional_timezone:NnTF #3
        { #6 } { #7 } { #8 }
       }
       {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
         #8
       }
    }
    { #8 }
 }
\cs_generate_variant:Nn
 \datatool_yyyymmdd_hhmmss_tz_parse_datetime:NNNnnnTF
 { NNNeeeTF }
%    \end{macrocode}
%
%As above, but the century needs to be added. (Unlikely with year
%first format, but provided for completeness.)
%    \begin{macrocode}
\cs_new:Nn \datatool_yymmdd_hhmmss_tz_parse_datetime:NNNnnnTF
 {
%    \end{macrocode}
%First extract the date:
%    \begin{macrocode}
   \regex_extract_once:NnNTF #1 { #4 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #3
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #3 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year_add_century:Ne #3 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
%Now extract the time.
%    \begin{macrocode}
        \regex_extract_once:NnNTF #2 { #5 }
          \l_datatool_timestamp_match_seq
         {
%    \end{macrocode}
% Set the hour:
%    \begin{macrocode}
           \exp_args:Ne \DTLCurrentLocaleIfpmTF 
            { \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } }
            {
             \datatool_timestamp_set_hour:Ne #3 
               {
                 \int_eval:n 
                  { 
                    12 +
                    \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } 
                  }
               }
            }
            {
             \datatool_timestamp_set_hour:Ne #3 
               { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
            }
%    \end{macrocode}
% Set the minute:
%    \begin{macrocode}
           \datatool_timestamp_set_minute:Ne #3 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
% Set the second:
%    \begin{macrocode}
           \datatool_timestamp_set_second:Ne #3 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
         }
         { #8 }
%    \end{macrocode}
% Parse timezone if present:
%    \begin{macrocode}
       \datatool_parse_regional_timezone:NnTF #3
        { #6 } { #7 } { #8 }
       }
       {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
         #8
       }
    }
    { #8 }
 }
\cs_generate_variant:Nn
 \datatool_yymmdd_hhmmss_tz_parse_datetime:NNNnnnTF
 { NNNeeeTF }
%    \end{macrocode}
%
%
%The timestamp separator used when date and time patterns provided
%separately:
%    \begin{macrocode}
\regex_new:N \l_datatool_regional_timestamp_sep_regex
\regex_set:Nn \l_datatool_regional_timestamp_sep_regex { \,? \s+ }
%    \end{macrocode}
%
%Syntax: \marg{date-regex-varname} \marg{time-regex-varname}
%\meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%
%The date regular expression should have the following groups (in order):
%day, month, year.
%The time regular expression should have the following groups: 
%hour, minute, second (may be empty), 
%am/pm (may be empty).
%The am/pm part can be anything that \cs{DTLCurrentLocaleIfpmTF}
%can detect as afternoon (that is, add 12 to the hour).
%This assumes that the date comes first and is separated from the
%time by 
%\verb|\l_datatool_regional_timestamp_sep_regex|.
%    \begin{macrocode}
\cs_new:Nn \datatool_ddmmyyyy_hhmmss_tz_parse_timestamp:NNNnTF
 {
   \tl_set_eq:NN \l__datatool_tmpa_regex #1
   \tl_set_eq:NN \l__datatool_tmpb_regex #2
   \regex_extract_once:nnNTF
    {
      \A \s*
      ( \ur{l__datatool_tmpa_regex} )
      \ur{l_datatool_regional_timestamp_sep_regex}
      ( \ur{l__datatool_tmpb_regex} )
      \s*
      ( \ur{c_datatool_timezone_id_regex} ) ?
      \s*
      \Z
    }
    { #4 }
     \l_datatool_timestamp_match_seq
    {
      \datatool_timestamp_zero:N #3
      \datatool_ddmmyyyy_hhmmss_tz_parse_datetime:NNNeeeTF
       #1 #2 #3
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
       { #5 } { #6 }
    }
    { #6 }
 }
\cs_generate_variant:Nn
 \datatool_ddmmyyyy_hhmmss_tz_parse_timestamp:NNNnTF
 { ccNnTF }
%    \end{macrocode}
%As above, but century needs to be added.
%    \begin{macrocode}
\cs_new:Nn \datatool_ddmmyy_hhmmss_tz_parse_timestamp:NNNnTF
 {
   \tl_set_eq:NN \l__datatool_tmpa_regex #1
   \tl_set_eq:NN \l__datatool_tmpb_regex #2
   \regex_extract_once:nnNTF
    {
      \A \s*
      ( \ur{l__datatool_tmpa_regex} )
      \ur{l_datatool_regional_timestamp_sep_regex}
      ( \ur{l__datatool_tmpb_regex} )
      \s*
      ( \ur{c_datatool_timezone_id_regex} ) ?
      \s*
      \Z
    }
    { #4 }
     \l_datatool_timestamp_match_seq
    {
      \datatool_timestamp_zero:N #3
      \datatool_ddmmyy_hhmmss_tz_parse_datetime:NNNeeeTF
       #1 #2 #3
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
       { #5 } { #6 }
    }
    { #6 }
 }
\cs_generate_variant:Nn
 \datatool_ddmmyy_hhmmss_tz_parse_timestamp:NNNnTF
 { ccNnTF }
%    \end{macrocode}
%
%Same again, but month in first captured group.
%    \begin{macrocode}
\cs_new:Nn \datatool_mmddyyyy_hhmmss_tz_parse_timestamp:NNNnTF
 {
   \tl_set_eq:NN \l__datatool_tmpa_regex #1
   \tl_set_eq:NN \l__datatool_tmpb_regex #2
   \regex_extract_once:nnNTF
    {
      \A \s*
      ( \ur{l__datatool_tmpa_regex} )
      \ur{l_datatool_regional_timestamp_sep_regex}
      ( \ur{l__datatool_tmpb_regex} )
      \s*
      ( \ur{c_datatool_timezone_id_regex} ) ?
      \s*
      \Z
    }
    { #4 }
     \l_datatool_timestamp_match_seq
    {
      \datatool_timestamp_zero:N #3
      \datatool_mmddyyyy_hhmmss_tz_parse_datetime:NNNeeeTF
       #1 #2 #3
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
       { #5 } { #6 }
    }
    { #6 }
 }
\cs_generate_variant:Nn
 \datatool_mmddyyyy_hhmmss_tz_parse_timestamp:NNNnTF
 { ccNnTF }
%    \end{macrocode}
%As above, but century needs to be added.
%    \begin{macrocode}
\cs_new:Nn \datatool_mmddyy_hhmmss_tz_parse_timestamp:NNNnTF
 {
   \tl_set_eq:NN \l__datatool_tmpa_regex #1
   \tl_set_eq:NN \l__datatool_tmpb_regex #2
   \regex_extract_once:nnNTF
    {
      \A \s*
      ( \ur{l__datatool_tmpa_regex} )
      \ur{l_datatool_regional_timestamp_sep_regex}
      ( \ur{l__datatool_tmpb_regex} )
      \s*
      ( \ur{c_datatool_timezone_id_regex} ) ?
      \s*
      \Z
    }
    { #4 }
     \l_datatool_timestamp_match_seq
    {
      \datatool_timestamp_zero:N #3
      \datatool_mmddyy_hhmmss_tz_parse_datetime:NNNeeeTF
       #1 #2 #3
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
       { #5 } { #6 }
    }
    { #6 }
 }
\cs_generate_variant:Nn
 \datatool_mmddyy_hhmmss_tz_parse_timestamp:NNNnTF
 { ccNnTF }
%    \end{macrocode}
%
%Same again, but year in first captured group.
%    \begin{macrocode}
\cs_new:Nn \datatool_yyyymmdd_hhmmss_tz_parse_timestamp:NNNnTF
 {
   \tl_set_eq:NN \l__datatool_tmpa_regex #1
   \tl_set_eq:NN \l__datatool_tmpb_regex #2
   \regex_extract_once:nnNTF
    {
      \A \s*
      ( \ur{l__datatool_tmpa_regex} )
      \ur{l_datatool_regional_timestamp_sep_regex}
      ( \ur{l__datatool_tmpb_regex} )
      \s*
      ( \ur{c_datatool_timezone_id_regex} ) ?
      \s*
      \Z
    }
    { #4 }
     \l_datatool_timestamp_match_seq
    {
      \datatool_timestamp_zero:N #3
      \datatool_yyyymmdd_hhmmss_tz_parse_datetime:NNNeeeTF
       #1 #2 #3
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
       { #5 } { #6 }
    }
    { #6 }
 }
\cs_generate_variant:Nn
 \datatool_yyyymmdd_hhmmss_tz_parse_timestamp:NNNnTF
 { ccNnTF }
%    \end{macrocode}
%As above, but century needs to be added.
%    \begin{macrocode}
\cs_new:Nn \datatool_yymmdd_hhmmss_tz_parse_timestamp:NNNnTF
 {
   \tl_set_eq:NN \l__datatool_tmpa_regex #1
   \tl_set_eq:NN \l__datatool_tmpb_regex #2
   \regex_extract_once:nnNTF
    {
      \A \s*
      ( \ur{l__datatool_tmpa_regex} )
      \ur{l_datatool_regional_timestamp_sep_regex}
      ( \ur{l__datatool_tmpb_regex} )
      \s*
      ( \ur{c_datatool_timezone_id_regex} ) ?
      \s*
      \Z
    }
    { #4 }
     \l_datatool_timestamp_match_seq
    {
      \datatool_timestamp_zero:N #3
      \datatool_yymmdd_hhmmss_tz_parse_datetime:NNNeeeTF
       #1 #2 #3
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
       { #5 } { #6 }
    }
    { #6 }
 }
\cs_generate_variant:Nn
 \datatool_yymmdd_hhmmss_tz_parse_timestamp:NNNnTF
 { ccNnTF }
%    \end{macrocode}
%
%Syntax: \meta{regex-var} \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%
%The regular expression should have the following groups (in order):
%day, month, year, hour, minute, second (may be empty), 
%am/pm (may be empty), time zone (may be empty).
%The am/pm part can be anything that \cs{DTLCurrentLocaleIfpmTF}
%can detect as afternoon (that is, add 12 to the hour).
%The constants provided with this base package only match
%\qt{am} and \qt{pm}.
%Localisation files may provide their own regular expressions with
%appropriate patterns.
%    \begin{macrocode}
\cs_new:Nn \datatool_ddmmyyyy_hhmmss_tz_parse_timestamp:NNnTF
 {
   \regex_extract_once:NnNTF #1 { #3 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
      \datatool_timestamp_zero:N #2
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #2
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
% Set the hour:
%    \begin{macrocode}
        \exp_args:Ne \DTLCurrentLocaleIfpmTF 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 8 } }
         {
          \datatool_timestamp_set_hour:Ne #2 
            {
              \int_eval:n 
               { 
                 12 +
                 \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } 
               }
            }
         }
         {
          \datatool_timestamp_set_hour:Ne #2 
            { \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } }
         }
%    \end{macrocode}
% Set the minute:
%    \begin{macrocode}
        \datatool_timestamp_set_minute:Ne #2 
          { \seq_item:Nn \l_datatool_timestamp_match_seq { 6 } }
%    \end{macrocode}
% Set the second:
%    \begin{macrocode}
        \datatool_timestamp_set_second:Ne #2 
          { \seq_item:Nn \l_datatool_timestamp_match_seq { 7 } }
%    \end{macrocode}
% Parse timezone if present:
%    \begin{macrocode}
       \datatool_parse_regional_timezone:NeTF #2
        { \seq_item:Nn \l_datatool_timestamp_match_seq { 9 } }
        { #4 } { #5 }
      }
      {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
        #5
      }
    }
    { #5 } % No match
 }
%    \end{macrocode}
%
%As above but century missing.
%Syntax: \meta{regex-var} \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%    \begin{macrocode}
\cs_new:Nn \datatool_ddmmyy_hhmmss_tz_parse_timestamp:NNnTF
 {
   \regex_extract_once:NnNTF #1 { #3 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
      \datatool_timestamp_zero:N #2
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #2
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year_add_century:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
% Set the hour:
%    \begin{macrocode}
        \exp_args:Ne \DTLCurrentLocaleIfpmTF 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 8 } }
         {
          \datatool_timestamp_set_hour:Ne #2 
            {
              \int_eval:n 
               { 
                 12 +
                 \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } 
               }
            }
         }
         {
           \datatool_timestamp_set_hour:Ne #2 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } }
         }
%    \end{macrocode}
% Set the minute:
%    \begin{macrocode}
        \datatool_timestamp_set_minute:Ne #2 
          { \seq_item:Nn \l_datatool_timestamp_match_seq { 6 } }
%    \end{macrocode}
% Set the second:
%    \begin{macrocode}
        \datatool_timestamp_set_second:Ne #2 
          { \seq_item:Nn \l_datatool_timestamp_match_seq { 7 } }
%    \end{macrocode}
% Parse timezone if present:
%    \begin{macrocode}
       \datatool_parse_regional_timezone:NeTF #2
        { \seq_item:Nn \l_datatool_timestamp_match_seq { 9 } }
        { #4 } { #5 }
      }
      {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
        #5
      }
    }
    { #5 } % No match
 }
%    \end{macrocode}
%
%Syntax: \meta{regex-var} \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%
%The regular expression should have the following groups (in order):
%month, day, year, hour, minute, second (may be empty), 
%am/pm (may be empty), time zone (may be empty).
%    \begin{macrocode}
\cs_new:Nn \datatool_mmddyyyy_hhmmss_tz_parse_timestamp:NNnTF
 {
   \regex_extract_once:NnNTF #1 { #3 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
      \datatool_timestamp_zero:N #2
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #2
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
% Set the hour:
%    \begin{macrocode}
        \exp_args:Ne \DTLCurrentLocaleIfpmTF 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 8 } }
         {
          \datatool_timestamp_set_hour:Ne #2 
            {
              \int_eval:n 
               { 
                 12 +
                 \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } 
               }
            }
         }
         {
           \datatool_timestamp_set_hour:Ne #2 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } }
         }
%    \end{macrocode}
% Set the minute:
%    \begin{macrocode}
        \datatool_timestamp_set_minute:Ne #2 
          { \seq_item:Nn \l_datatool_timestamp_match_seq { 6 } }
%    \end{macrocode}
% Set the second:
%    \begin{macrocode}
        \datatool_timestamp_set_second:Ne #2 
          { \seq_item:Nn \l_datatool_timestamp_match_seq { 7 } }
%    \end{macrocode}
% Parse timezone if present:
%    \begin{macrocode}
       \datatool_parse_regional_timezone:NeTF #2
        { \seq_item:Nn \l_datatool_timestamp_match_seq { 9 } }
        { #4 } { #5 }
      }
      {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
        #5
      }
    }
    { #5 } % No match
 }
%    \end{macrocode}
%
%As above but missing century.
%Syntax: \meta{regex-var} \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%    \begin{macrocode}
\cs_new:Nn \datatool_mmddyy_hhmmss_tz_parse_timestamp:NNnTF
 {
   \regex_extract_once:NnNTF #1 { #3 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
      \datatool_timestamp_zero:N #2
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #2
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year_add_century:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
% Set the hour:
%    \begin{macrocode}
        \exp_args:Ne \DTLCurrentLocaleIfpmTF 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 8 } }
         {
          \datatool_timestamp_set_hour:Ne #2 
            {
              \int_eval:n 
               { 
                 12 +
                 \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } 
               }
            }
         }
         {
           \datatool_timestamp_set_hour:Ne #2 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } }
         }
%    \end{macrocode}
% Set the minute:
%    \begin{macrocode}
        \datatool_timestamp_set_minute:Ne #2 
          { \seq_item:Nn \l_datatool_timestamp_match_seq { 6 } }
%    \end{macrocode}
% Set the second:
%    \begin{macrocode}
        \datatool_timestamp_set_second:Ne #2 
          { \seq_item:Nn \l_datatool_timestamp_match_seq { 7 } }
%    \end{macrocode}
% Parse timezone if present:
%    \begin{macrocode}
       \datatool_parse_regional_timezone:NeTF #2
        { \seq_item:Nn \l_datatool_timestamp_match_seq { 9 } }
        { #4 } { #5 }
      }
      {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
        #5
      }
    }
    { #5 } % No match
 }
%    \end{macrocode}
%
%Syntax: \meta{regex-var} \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%
%The regular expression should have the following groups (in order):
%year, month, day, hour, minute, second (may be empty), 
%am/pm (may be empty), time zone (may be empty).
%    \begin{macrocode}
\cs_new:Nn \datatool_yyyymmdd_hhmmss_tz_parse_timestamp:NNnTF
 {
   \regex_extract_once:NnNTF #1 { #3 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
      \datatool_timestamp_zero:N #2
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #2
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
% Set the hour:
%    \begin{macrocode}
        \exp_args:Ne \DTLCurrentLocaleIfpmTF 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 8 } }
         {
          \datatool_timestamp_set_hour:Ne #2 
            {
              \int_eval:n 
               { 
                 12 +
                 \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } 
               }
            }
         }
         {
           \datatool_timestamp_set_hour:Ne #2 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } }
         }
%    \end{macrocode}
% Set the minute:
%    \begin{macrocode}
        \datatool_timestamp_set_minute:Ne #2 
          { \seq_item:Nn \l_datatool_timestamp_match_seq { 6 } }
%    \end{macrocode}
% Set the second:
%    \begin{macrocode}
        \datatool_timestamp_set_second:Ne #2 
          { \seq_item:Nn \l_datatool_timestamp_match_seq { 7 } }
%    \end{macrocode}
% Parse timezone if present:
%    \begin{macrocode}
       \datatool_parse_regional_timezone:NeTF #2
        { \seq_item:Nn \l_datatool_timestamp_match_seq { 9 } }
        { #4 } { #5 }
      }
      {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
        #5
      }
    }
    { #5 } % No match
 }
%    \end{macrocode}
%
% As above but missing century.
%Syntax: \meta{regex-var} \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%    \begin{macrocode}
\cs_new:Nn \datatool_yymmdd_hhmmss_tz_parse_timestamp:NNnTF
 {
   \regex_extract_once:NnNTF #1 { #3 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
      \datatool_timestamp_zero:N #2
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #2
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year_add_century:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
% Set the hour:
%    \begin{macrocode}
        \exp_args:Ne \DTLCurrentLocaleIfpmTF 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 8 } }
         {
          \datatool_timestamp_set_hour:Ne #2 
            {
              \int_eval:n 
               { 
                 12 +
                 \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } 
               }
            }
         }
         {
           \datatool_timestamp_set_hour:Ne #2 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } }
         }
%    \end{macrocode}
% Set the minute:
%    \begin{macrocode}
        \datatool_timestamp_set_minute:Ne #2 
          { \seq_item:Nn \l_datatool_timestamp_match_seq { 6 } }
%    \end{macrocode}
% Set the second:
%    \begin{macrocode}
        \datatool_timestamp_set_second:Ne #2 
          { \seq_item:Nn \l_datatool_timestamp_match_seq { 7 } }
%    \end{macrocode}
% Parse timezone if present:
%    \begin{macrocode}
       \datatool_parse_regional_timezone:NeTF #2
        { \seq_item:Nn \l_datatool_timestamp_match_seq { 9 } }
        { #4 } { #5 }
      }
      {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
        #5
      }
    }
    { #5 } % No match
 }
%    \end{macrocode}
%
%Syntax: \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%    \begin{macrocode}
\cs_new:Nn \datatool_parse_regional_timezone:NnTF
 {
   \tl_if_empty:nTF { #2 }
    {
%    \end{macrocode}
% Missing time zone valid so return true.
%    \begin{macrocode}
      #3
    }
    {
%    \end{macrocode}
% Check for known timezone identifier. First try regionless mapping.
%    \begin{macrocode}
      \prop_get:NnNF \l_datatool_timezone_map_prop
        { #2 } 
       \l_datatool_timezone_map_value_tl
       {
%    \end{macrocode}
% No regionless mapping so now try locale mapping.
%    \begin{macrocode}
        \DTLCurrentLocaleGetTimeZoneMap { #2 }
        \quark_if_no_value:NT
          \l_datatool_timezone_map_value_tl
         {
%    \end{macrocode}
% No mapping so may be numeric.
%    \begin{macrocode}
           \tl_set:Nn \l_datatool_timezone_map_value_tl { #2 }
         }
       }
      \exp_args:NNV \regex_extract_once:NnNTF
         \c_datatool_timezone_regex
         \l_datatool_timezone_map_value_tl
         \l_datatool_regex_match_seq
        {
          \datatool_timestamp_set_tzhour:Ne #1
            { \seq_item:Nn \l_datatool_regex_match_seq { 2 } }
         \tl_if_empty:eF 
            { \seq_item:Nn \l_datatool_regex_match_seq { 3 } }
           {
             \datatool_timestamp_set_tzminute:Ne #1
               { \seq_item:Nn \l_datatool_regex_match_seq { 3 } }
           }
          #3
        }
        { #4 }
    }
 }
\cs_generate_variant:Nn \datatool_parse_regional_timezone:NnTF
  { NeTF }
%    \end{macrocode}
%
%Syntax: \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%    \begin{macrocode}
\cs_new:Nn \datatool_parse_regional_month:NnTF
 {
%    \end{macrocode}
% Check for month name mapping.
%    \begin{macrocode}
   \DTLCurrentLocaleGetMonthNameMap { #2 }
   \quark_if_no_value:NT
     \l_datatool_monthname_map_value_tl
    {
%    \end{macrocode}
% No mapping so check if numeric.
%    \begin{macrocode}
      \tl_set:Nn \l_datatool_monthname_map_value_tl { #2 }
    }
   \exp_args:NNV \regex_extract_once:NnNTF
      \c_datatool_month_number_regex
      \l_datatool_monthname_map_value_tl
      \l_datatool_regex_match_seq
     {
       \datatool_timestamp_set_month:Ne #1
         { \seq_item:Nn \l_datatool_regex_match_seq { 2 } }
       #3
     }
     { #4 }
 }
\cs_generate_variant:Nn \datatool_parse_regional_month:NnTF
 { NeTF }
%    \end{macrocode}
%
%Syntax: \meta{regex-var} \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%
%The regular expression should have the following groups (in order):
%day, month, year.
%    \begin{macrocode}
\cs_new:Nn \datatool_ddmmyyyy_parse_date:NNnTF
 {
   \regex_extract_once:NnNTF #1 { #3 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
      \datatool_timestamp_zero:N #2
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #2
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year:Ne #2
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
% Success
%    \begin{macrocode}
        #4
      }
      {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
        #5
      }
    }
    { #5 } % No match
 }
%    \end{macrocode}
%
%Syntax: \meta{regex-var} \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%
%The regular expression should have the following groups (in order):
%month, day, year.
%    \begin{macrocode}
\cs_new:Nn \datatool_mmddyyyy_parse_date:NNnTF
 {
   \regex_extract_once:NnNTF #1 { #3 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
      \datatool_timestamp_zero:N #2
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #2
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year:Ne #2
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
% Success
%    \begin{macrocode}
        #4
      }
      {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
        #5
      }
    }
    { #5 } % No match
 }
%    \end{macrocode}
%
%Syntax: \meta{regex-var} \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%
%The regular expression should have the following groups (in order):
%year, month, day.
%    \begin{macrocode}
\cs_new:Nn \datatool_yyyymmdd_parse_date:NNnTF
 {
   \regex_extract_once:NnNTF #1 { #3 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
      \datatool_timestamp_zero:N #2
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #2
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year:Ne #2
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
% Success
%    \begin{macrocode}
        #4
      }
      {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
        #5
      }
    }
    { #5 } % No match
 }
%    \end{macrocode}
%
% As above but century missing:
%Syntax: \meta{regex-var} \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%    \begin{macrocode}
\cs_new:Nn \datatool_ddmmyy_parse_date:NNnTF
 {
   \regex_extract_once:NnNTF #1 { #3 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
      \datatool_timestamp_zero:N #2
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #2
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year_add_century:Ne #2
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
% Success
%    \begin{macrocode}
        #4
      }
      {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
        #5
      }
    }
    { #5 } % No match
 }
%    \end{macrocode}
%
%Syntax: \meta{regex-var} \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%    \begin{macrocode}
\cs_new:Nn \datatool_mmddyy_parse_date:NNnTF
 {
   \regex_extract_once:NnNTF #1 { #3 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
      \datatool_timestamp_zero:N #2
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #2
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year_add_century:Ne #2
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
% Success
%    \begin{macrocode}
        #4
      }
      {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
        #5
      }
    }
    { #5 } % No match
 }
%    \end{macrocode}
%
%Syntax: \meta{regex-var} \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%    \begin{macrocode}
\cs_new:Nn \datatool_yymmdd_parse_date:NNnTF
 {
   \regex_extract_once:NnNTF #1 { #3 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
      \datatool_timestamp_zero:N #2
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #2
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year_add_century:Ne #2
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
% Success
%    \begin{macrocode}
        #4
      }
      {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
        #5
      }
    }
    { #5 } % No match
 }
%    \end{macrocode}
%
%
%As above, but year missing. Assume current year:
%Syntax: \meta{regex-var} \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%    \begin{macrocode}
\cs_new:Nn \datatool_ddmm_parse_date:NNnTF
 {
   \regex_extract_once:NnNTF #1 { #3 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
      \datatool_timestamp_zero:N #2
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #2
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year:Nn #2 { }
%    \end{macrocode}
% Success
%    \begin{macrocode}
        #4
      }
      {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
        #5
      }
    }
    { #5 } % No match
 }
%    \end{macrocode}
%
%Syntax: \meta{regex-var} \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%    \begin{macrocode}
\cs_new:Nn \datatool_mmdd_parse_date:NNnTF
 {
   \regex_extract_once:NnNTF #1 { #3 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
      \datatool_timestamp_zero:N #2
%    \end{macrocode}
% Set the month first, to skip rest on failure:
%    \begin{macrocode}
      \datatool_parse_regional_month:NeTF #2
       { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
       {
%    \end{macrocode}
% Successfully set month.
% Set the day of month:
%    \begin{macrocode}
        \datatool_timestamp_set_day:Ne #2 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
% Set the year:
%    \begin{macrocode}
        \datatool_timestamp_set_year:Nn #2 { }
%    \end{macrocode}
% Success
%    \begin{macrocode}
        #4
      }
      {
%    \end{macrocode}
% No match on month:
%    \begin{macrocode}
        #5
      }
    }
    { #5 } % No match
 }
%    \end{macrocode}
%
%Syntax: \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%    \begin{macrocode}
\cs_new:Nn \datatool_hhmmss_parse_time:NnTF
 {
   \datatool_hhmmss_parse_time:NNnTF
     \c_datatool_colon_anchored_hhmmss_time_regex #1 { #2 } { #3 } { #4 }
 }
%    \end{macrocode}
%Syntax: \meta{regex-var} \meta{timestamp seq-var} \marg{text}
% \marg{return true} \marg{return false}
%    \begin{macrocode}
\cs_new:Nn \datatool_hhmmss_parse_time:NNnTF
 {
   \regex_extract_once:NnNTF
     #1 { #3 }
     \l_datatool_timestamp_match_seq
    {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
      \datatool_timestamp_zero:N #2
%    \end{macrocode}
% Set the hour:
%    \begin{macrocode}
        \exp_args:Ne \DTLCurrentLocaleIfpmTF 
         { \seq_item:Nn \l_datatool_timestamp_match_seq { 5 } }
         {
          \datatool_timestamp_set_hour:Ne #2 
            {
              \int_eval:n 
               { 
                 12 +
                 \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } 
               }
            }
         }
         {
           \datatool_timestamp_set_hour:Ne #2 
             { \seq_item:Nn \l_datatool_timestamp_match_seq { 2 } }
         }
%    \end{macrocode}
% Set the minute:
%    \begin{macrocode}
      \datatool_timestamp_set_minute:Ne #2 
        { \seq_item:Nn \l_datatool_timestamp_match_seq { 3 } }
%    \end{macrocode}
% Set the second:
%    \begin{macrocode}
      \datatool_timestamp_set_second:Ne #2 
        { \seq_item:Nn \l_datatool_timestamp_match_seq { 4 } }
%    \end{macrocode}
% Success
%    \begin{macrocode}
      #4
    }
    { #5 } % No match
 }
\cs_generate_variant:Nn \datatool_hhmmss_parse_time:NNnTF
 { cNnTF }
%    \end{macrocode}
%
%\subsubsection{Currencies}
%\begin{macro}{\@dtl@currencies}
%\changes{3.0}{2025-03-03}{removed}
%Renamed \cs{@dtl@currencies} since the internal structure
%is changed. This will trigger an unknown control sequence error if
%it's being used by anything.
%The new name is \verb|\l__datatool_known_currencies_seq|
%but it needs to be defined before \verb|\datatool_set_currencysign:nn|
%is used.
%\end{macro}
% An internal list that stores all known currencies. 
%    \begin{macrocode}
\seq_put_right:Nn \l__datatool_known_currencies_seq { \$ }
\seq_put_right:Nn \l__datatool_known_currencies_seq {\pounds }
\seq_put_right:Nn \l__datatool_known_currencies_seq {\textdollar }
\seq_put_right:Nn \l__datatool_known_currencies_seq {\textstirling }
\seq_put_right:Nn \l__datatool_known_currencies_seq {\texteuro }
\seq_put_right:Nn \l__datatool_known_currencies_seq {\textyen }
\seq_put_right:Nn \l__datatool_known_currencies_seq {\textwon }
\seq_put_right:Nn \l__datatool_known_currencies_seq {\textcurrency }
%    \end{macrocode}
%
%\begin{macro}{\@dtl@currency}
%\cs{@dtl@currency} is set by \cs{DTLconverttodecimal} and
%\cs{@dtl@checknumerical}. It is used by \cs{DTLcurrency}.
% Set to "\$" by default.
%    \begin{macrocode}
\newcommand*{\@dtl@currency}{\$}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLCurrencySymbol}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLCurrencySymbol}{\@dtl@currency}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLCurrencyCode}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLCurrencyCode}{XXX}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLsetdefaultcurrency}
%\changes{3.0}{2025-03-03}{changed to document command}
%\cs{DTLsetdefaultcurrency}\marg{symbol} sets the default currency.
%    \begin{macrocode}
\NewDocumentCommand \DTLsetdefaultcurrency { m }
{
%    \end{macrocode}
% Detokenize in test in case argument isn't a currency code to allow
% for backward-compatibility.
%    \begin{macrocode}
  \tl_if_exist:cTF { dtl@curr@ \tl_to_str:n { #1 } @sym }
  {
%    \end{macrocode}
% Defined currency code.
%    \begin{macrocode}
    \tl_set:Ne \@dtl@currency { \exp_not:c { DTLcurr #1 } }
    \tl_set:Ne \DTLCurrencyCode { #1 }
    \tl_set:Ne \DTLfmtcurrency
      { \exp_not:c { dtl@curr@ #1 @fmt } }
  }
  {
%    \end{macrocode}
% Unknown currency.
%    \begin{macrocode}
    \tl_set:Nn \DTLCurrencyCode { XXX }
    \tl_set:Nn \@dtl@currency { #1 }
  }
}
%    \end{macrocode}
%\end{macro}
%
%For use with the region files.
%    \begin{macrocode}
\cs_new:Nn \datatool_register_regional_currency_code:nn
 {
   \seq_put_right:Nn \l_datatool_regional_currencies_seq { #2 }
   \prop_put:Nnn \l_datatool_regional_currencies_prop { #1 } { #2 }
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \datatool_currency_symbol_region_prefix:n
 {
   \DTLcurrCodeOrSymOrChar
    { }
    { \datatoolcurrencysymbolprefixfmt { #1 } }
    { \datatoolcurrencysymbolprefixfmt { #1 } }
 }
%    \end{macrocode}
%
%\begin{macro}{\DTLcurrSym}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLcurrSym}[1]{\csuse{dtl@curr@ #1 @sym}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLcurrChar}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLcurrChar}[1]{\csuse{dtl@curr@ #1 @tl}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLcurrStr}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLcurrStr}[1]{\csuse{dtl@curr@ #1 @str}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdefcurrency}
%Define currency.
%\begin{definition}
%\cs{DTLdefcurrency}\oarg{fmt}\marg{iso-code}\marg{symbol}\marg{string}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%NB \cs{DTLdefcurrency} doesn't pick up the fourth \meta{string}
%argument. Category code of \$ needs to be changed first.
%    \begin{macrocode}
\NewDocumentCommand{\DTLdefcurrency} { O{\dtlcurrdefaultfmt} m m }
{
  \group_begin:
  \char_set_catcode_other:N \$
  \__datatool_def_currency:nnnn { #1 } { #2 } { #3 } 
}
\cs_new:Nn \__datatool_def_currency:nnnn
{
  \group_end:
  \datatool_def_currency:nnne { #1 } { #2 } { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%Lower-level command. Three arguments, use default format:
%    \begin{macrocode}
\cs_new:Nn \datatool_def_currency:nnn
 {
   \datatool_def_currency:nnnn
     { \dtlcurrdefaultfmt } { #1 } { #2 } { #3 }
 }
\cs_generate_variant:Nn \datatool_def_currency:nnn
  { nnV , nne , nVV }
%    \end{macrocode}
% Four arguments, supply formatting command in first argument:
%    \begin{macrocode}
\cs_new:Nn \datatool_def_currency:nnnn
 {
  \seq_put_right:Nn \l_datatool_currencies_seq { #2 }
  \str_set:cn { dtl@curr@ #2 @str } { #4 }
  \tl_set:cn { dtl@curr@ #2 @tl } { #4 }
  \tl_set:cn { dtl@curr@ #2 @sym } { #3 }
  \csedef { DTLcurr #2 }
   {
    \exp_not:N \dtltexorsort
     {
       \exp_not:N \DTLcurrCodeOrSymOrChar
        { #2 }
        { \exp_not:N \DTLcurrSym { #2 } }
        { \exp_not:N \DTLcurrChar { #2 } }
     }
     {
       \exp_not:N \DTLcurrStr { #2 } 
     }
   }
  \DTLnewcurrencysymbol { #3 }
  \DTLnewcurrencysymbol { #4 }
  \exp_args:Nc \DTLnewcurrencysymbol { DTLcurr #2 }
  \csdef { dtl@curr@ #2 @fmt } { #1 }
 }
\cs_generate_variant:Nn \datatool_def_currency:nnnn
  { nnnV , nnne , nnVV }
%    \end{macrocode}
%
% Find first declared regional currency code that matches the given
% symbol. Returns empty if no match. First tests if a
% match on current currency.
%    \begin{macrocode}
\cs_new:Nn \datatool_get_currency_code:Nn
 {
   \__datatool_if_current_currency:nTF { #2 }
    {
      \tl_set_eq:NN #1 \DTLCurrencyCode
    }
    {
      \tl_clear:N #1
      \seq_map_inline:Nn \l_datatool_regional_currencies_seq
       {
         \tl_if_eq:nnTF { ##1 } { #2 }
          {
            \tl_set:Nn #1 { ##1 }
            \seq_map_break:
          }
          {
            \tl_if_eq:nnTF { \DTLcurr { ##1 } } { #2 }
             {
               \tl_set:Nn #1 { ##1 }
               \seq_map_break:
             }
             {
               \exp_args:Ne \tl_if_eq:nnTF { \exp_not:c { DTLcurr ##1 } } { #2 }
                {
                  \tl_set:Nn #1 { ##1 }
                  \seq_map_break:
                }
                {
                  \tl_if_eq:cnTF { dtl@curr@ ##1 @sym } { #2 }
                   {
                     \tl_set:Nn #1 { ##1 }
                     \seq_map_break:
                   }
                   {
                     \str_if_eq:vnT { dtl@curr@ ##1 @str } { #2 }
                      {
                        \tl_set:Nn #1 { ##1 }
                        \seq_map_break:
                      }
                   }
                }
             }
          }
       }
    }
 }
\cs_generate_variant:Nn  \datatool_get_currency_code:Nn { NV }
%    \end{macrocode}
%
%Test if the supplied token list matches the current symbol or
%string.
%    \begin{macrocode}
\prg_new_conditional:Npnn \__datatool_if_current_currency:n #1
   { T, F, TF }
 {
  \tl_if_eq:NnTF \@dtl@currency { #1 }
   {
     \prg_return_true:
   }
   {
     \tl_if_exist:cTF
      { DTLcurr \DTLCurrencyCode }
      {
        \tl_if_eq:cnTF
          { DTLcurr \DTLCurrencyCode }
          { #1 }
         {
           \prg_return_true:
         }
         {
           \exp_args:Nv \tl_if_eq:nnTF
             { dtl@curr@ \DTLCurrencyCode @sym } { #1 }
           {
             \prg_return_true:
           }
           {
             \str_if_eq:vnTF
               { dtl@curr@ \DTLCurrencyCode @str } 
               { #1 }
             {
               \prg_return_true:
             }
             {
               \exp_args:Ne \tl_if_eq:nnTF
                { \exp_not:N \DTLcurr { \DTLCurrencyCode } }
                { #1 }
                {
                  \prg_return_true:
                }
                {
                  \prg_return_false:
                }
             }
           }
         }
      }
      {
       \prg_return_false:
      }
   }
 }
%    \end{macrocode}
%
%Allow the command used for the currency to be changed.
%Note that this will add the new symbol to the list of known
%currency symbols but won't remove the old one.
%    \begin{macrocode}
\cs_new:Nn \datatool_set_currency_symbol:nn
 {
   \tl_if_exist:cTF { dtl@curr@ #1 @sym }
    {
      \csdef { dtl@curr@ #1 @sym } { #2 }
      \DTLnewcurrencysymbol { #2 }
    }
    {
      \PackageError { datatool-base }
       {
         Can't ~ set ~ currency ~ symbol ~ to ~ \tl_to_str:n { #2 } ~ : ~
         Currency ~ ` #1 ' ~ not ~ defined
       }
       {
         Check ~ that ~ you ~ have ~ spelt ~ the ~ 
         currency ~ code ~ correctly
       }
    }
 }
\cs_generate_variant:Nn \datatool_set_currency_symbol:nn
 { nV , ne }
%    \end{macrocode}
%
%\begin{macro}{\DTLcurrency}
%\changes{3.0}{2025-03-03}{new}
%Format currency using current symbol.
%    \begin{macrocode}
\newcommand{\DTLcurrency}[1]{\DTLfmtcurrency{\@dtl@currency}{#1}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLfmtcurr}
%\changes{3.0}{2025-03-03}{new}
%Format currency according to the given currency code or use the
%default format if not defined. May fully expand (unless currency
%symbol is fragile, which is less likely with newer \LaTeX\ kernel).
%    \begin{macrocode}
\newcommand{\DTLfmtcurr}[2]{%
  \cs_if_exist:cTF { dtl@curr@ #1 @fmt }
   { 
     \use:c { dtl@curr@ #1 @fmt } { \DTLcurr { #1 } } { #2 }
   }
   { \DTLcurrency { #2 } }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLfmtcurrency}
%\changes{3.0}{2025-03-03}{new}
%Format currency using given symbol.
%    \begin{macrocode}
\newcommand{\DTLfmtcurrency}{\dtlcurrdefaultfmt}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlcurrdefaultfmt}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtlcurrdefaultfmt}{\dtlcurrprefixfmt}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlcurrprefixfmt}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtlcurrprefixfmt}[2]{
 \datatool_prefix_adjust_sign:nnn { #1 } { \dtlcurrfmtsep } { #2 }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \datatool_prefix_adjust_sign:nnn
 {
   \bool_lazy_or:nnTF
      { \tl_if_head_eq_charcode_p:nN { #3 } + }
      { \tl_if_head_eq_charcode_p:nN { #3 } - }
    {
      \exp_args:Ne \datatool_adjust_sign_fmt:n { \tl_head:n { #3 } }
      #1 #2
      \tl_tail:n { #3 }
    }
    { #1 #2 #3 }
 }
%    \end{macrocode}
%Allow the sign to be formatted if outside of math mode.
%    \begin{macrocode}
\cs_new:Nn \datatool_adjust_sign_fmt:n
 {
   \ifmmode
     #1
   \else
    \tl_if_head_eq_charcode:nNTF { #1 } -
     {
       \textminus
     }
     { #1 }
  \fi
 }
%    \end{macrocode}
%\begin{macro}{\dtlcurrsuffixfmt}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtlcurrsuffixfmt}[2]{
 \datatool_suffix_adjust_sign:nnn { #1 } { \dtlcurrfmtsep } { #2 }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \datatool_suffix_adjust_sign:nnn
 {
   \bool_lazy_or:nnTF
      { \tl_if_head_eq_charcode_p:nN { #3 } + }
      { \tl_if_head_eq_charcode_p:nN { #3 } - }
    {
      \exp_args:Ne \datatool_adjust_sign_fmt:n { \tl_head:n { #3 } }
      \tl_tail:n { #3 }
       #2 #1
    }
    { #3 #2 #1 }
 }
%    \end{macrocode}
%\begin{macro}{\dtlcurrfmtsymsep}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtlcurrfmtsymsep}{}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%\begin{macro}{\dtlcurrfmtsep}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtlcurrfmtsep}{\DTLcurrCodeOrSymOrChar{~}{\dtlcurrfmtsymsep}{\dtlcurrfmtsymsep}}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%\begin{macro}{\DTLcurr}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLcurr}[1]{\ifcsdef{DTLcurr#1}{\csuse{DTLcurr#1}}{#1}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdefaultEURcurrencyfmt}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLdefaultEURcurrencyfmt}{\dtlcurrdefaultfmt}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLcurrXXX}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\datatool_def_currency:nnV
 { XXX }
 { \textcurrency } 
 \l_datatool_currency_str
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLcurrXBT}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\cs_if_exist:NT \faBtc
 {
   \datatool_def_currency:nnV
     { XBT }
     { \faBtc }
     \l_datatool_bitcoin_str
 }
%    \end{macrocode}
%\end{macro}
%
% Try to guess the command used for the Euro currency symbol.
% Mainly provided to retain backward compatibility with pre v3.0.
% If incorrect, this may be changed with 
% \verb|\datatool_set_currency_symbol:nn|
% Other currencies can be defined in the applicable region file.
%    \begin{macrocode}
\tl_new:N \l__datatool_eurocs_tl
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_if_exist:NTF \euro
 {
   \tl_set:Nn \l__datatool_eurocs_tl { \euro }
 }
 {
  \cs_if_exist:NTF \Euro
   {
     \tl_set:Nn \l__datatool_eurocs_tl { \Euro }
   }
   {
    \cs_if_exist:NTF \EUR
    {
      \tl_set:Nn \l__datatool_eurocs_tl { \EUR }
    }
    {
      \cs_if_exist:NTF \faEur
      {
        \tl_set:Nn \l__datatool_eurocs_tl { \faEur }
      }
      {
        \cs_if_exist:NTF \wasyeuro
        {
          \tl_set:Nn \l__datatool_eurocs_tl { \wasyeuro }
        }
        {
          \cs_if_exist:NTF \texteuro
          {
            \tl_set:Nn \l__datatool_eurocs_tl { \texteuro }
          }
          {
            \tl_set:Nn \l__datatool_eurocs_tl { \l_datatool_euro_str }
          }
        }
      }
    }
   }
 }
%    \end{macrocode}
%\begin{macro}{\DTLcurrEUR}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\datatool_def_currency:nnVV
 { \DTLdefaultEURcurrencyfmt }
 { EUR }
 \l__datatool_eurocs_tl
 \l_datatool_euro_str
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datatoolSetCurrencySort}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand \datatoolSetCurrencySort
 {
   \let \textdollar \c_dollar_str
   \let \textdollaroldstyle \c_dollar_str
   \let \textcentoldstyle \l_datatool_cent_str
   \let \textcent \l_datatool_cent_str
   \let \textsterling \l_datatool_pound_str
   \let \pounds \l_datatool_pound_str
   \let \textcurrency \l_datatool_currency_str
   \let \textyen \l_datatool_yen_str
   \let \textflorin \l_datatool_florin_str
   \let \texteuro \l_datatool_euro_str
   \let \textcolonmonetary \l_datatool_colonsign_str
   \let \textwon \l_datatool_won_str
   \let \textnaira \l_datatool_naira_str
   \let \textguarani \l_datatool_guarani_str
   \let \textpeso \l_datatool_peso_str
   \let \textlira \l_datatool_lira_str
   \let \textdong \l_datatool_dong_str
   \let \textbaht \l_datatool_baht_str
 }
%    \end{macrocode}
%\end{macro}
%
% \subsection{Floating Point Arithmetic}
%
% The commands defined in this section are designed for localised
% numeric values. They all have to first convert the formatted value
% to a numeric value acceptable to the underlying arithmetic function and then
% convert back again. If the original supplied values had different
% data types, the data type of the result depends on which type
% is dominant.
%
%First provide some common functions to update the datum structure after operating on
%two values with potentially different types.
%
%Determine dominant data type: decimal overrides integer, currency
%overrides integer and decimal, temporal values override other
%values, but time should be converted to datetime if
%added to a date or datetime or other numeric value, and date should
%be converted to datetime if added to anything other than an integer.
%
%NB if two currencies are added, their symbols are assumed to
%represent the same currency unit. No exchange rate information is
%available.
%    \begin{macrocode}
\cs_new:Nn \__datatool_update_datatype:
 {
   \datatool_update_datatype:NNNN
     \@dtl@datatype
     \l__datatool_tmp_datatype_int
     \l__datatool_datum_currency_tl
     \l__datatool_tmp_currency_tl
 }
%    \end{macrocode}
%Syntax: \meta{type1 int-var} \meta{type2 int-var}
%\meta{curr-sym1 tl-var} \meta{curr-sym2 tl-var}
%This will update \meta{type1 int-var} to the dominant type
%and (if the dominant type is currency)
%\meta{curr-sym1 tl-var} to the currency symbol.
%    \begin{macrocode}
\cs_new:Nn \datatool_update_datatype:NNNN
 {
  \bool_lazy_or:nnF
     { \int_compare_p:nNn { #1 } = { #2 } }
     { \int_compare_p:nNn { #2 } = { \c_datatool_unknown_int } }
   {
    \int_compare:nNnTF { #1 } = { \c_datatool_unknown_int }
     {
       \int_set_eq:NN #1 #2
       \tl_set_eq:NN #3 #4
     }
     {
       \bool_lazy_or:nnTF
         { \datatool_if_temporal_datum_type_p:n { #1 } }
         { \datatool_if_temporal_datum_type_p:n { #2 } }
        {
%    \end{macrocode}
%If either is a temporal value (and already checked they are not the
%same type), the result will be a timestamp unless an integer has
%been added to a date.
%    \begin{macrocode}
          \bool_lazy_or:nnTF
           { 
             \bool_lazy_and_p:nn
              { \int_compare_p:nNn { #1 } = { \c_datatool_date_int } }
              { \int_compare_p:nNn { #2 } = { \c_datatool_integer_int } }
           }
           { 
             \bool_lazy_and_p:nn
              { \int_compare_p:nNn { #2 } = { \c_datatool_date_int } }
              { \int_compare_p:nNn { #1 } = { \c_datatool_integer_int } }
           }
           {
             \int_set_eq:NN #1 \c_datatool_date_int
           }
           {
             \int_set_eq:NN #1 \c_datatool_datetime_int
           }
%    \end{macrocode}
%Doesn't make sense to add a currency to a date/time value.
%    \begin{macrocode}
          \tl_clear:N #3
        }
        {
%    \end{macrocode}
%Neither is a temporal type, so order of precedence can simply be
%determined by the type's numeric ID.
%    \begin{macrocode}
         \int_compare:nNnT { #1 } < { #2 }
          {
           \int_set_eq:NN #1 #2
           \tl_set_eq:NN #3 #4
          }
        }
     }
   }
 }
%    \end{macrocode}
%Convert the numeric value to localised format.
%Note that the \verb|\l__datatool_result_tl| should be correctly
%expanded before this function.
%    \begin{macrocode}
\cs_new:Nn \__datatool_assign_result:N
{
  \datatool_assign_result:NNNN #1
   \@dtl@datatype
   \l__datatool_result_tl
   \l__datatool_datum_currency_tl
}
%    \end{macrocode}
%Syntax: \meta{tl-var}
%\meta{type int-var}
%\meta{value tl-var}
%\meta{curr-sym tl-var}
%    \begin{macrocode}
\cs_new:Nn \datatool_assign_result:NNNN
{
 \datatool_if_temporal_datum_type:nTF { #2 }
  {
    \datatool_decimal_to_temporal:Nnn
      #1 { #2 } { #3 }
  }
  {
    \tl_if_empty:NTF #4
     {
       \exp_args:NV \DTLdecimaltolocale #3 #1
     }
     {
       \exp_args:NVV \__datatool_decimal_to_currency:nnN
          #4 #3 #1
     }
  }
}
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_numeric\_fn:NnnNN}
%\meta{result tl-var} \marg{num1} \marg{num2}
% \meta{decimal-operator-cs} \meta{int-operator-cs}
%\end{definition}
%Perform a numerical operation on two values that need parsing or
%that are already in datum format. If they are both integers, 
%use the \meta{int-operator-cs} function, which should take two
%arguments and expand to the result, otherwise use the
%\cs{decimal-operator-cs} function, which should take three arguments
%where the first is the result token list variable and the others
%are the numeric values.
%    \begin{macrocode}
\cs_new:Nn \datatool_numeric_fn:NnnNN
 {
  \DTLconverttodecimal { #2 } \l__datatool_resulta_tl
  \int_set_eq:NN 
    \l__datatool_tmp_datatype_int
    \@dtl@datatype
  \tl_set_eq:NN
    \l__datatool_tmp_currency_tl
    \l__datatool_datum_currency_tl
  \DTLconverttodecimal { #3 } \l__datatool_resultb_tl
%    \end{macrocode}
%If the data types are different, need to determine the dominant
%one.
%    \begin{macrocode}
  \__datatool_update_datatype:
%    \end{macrocode}
%Determine which function should be used.
%    \begin{macrocode}
  \datatool_if_any_int_datum_type:nTF { \@dtl@datatype }
   {
%    \end{macrocode}
%Version 3.1: changed \cs{tl\_set:Nx} to \cs{tl\_set:Ne}
%(not sure if this makes difference).
%    \begin{macrocode}
     \tl_set:Ne \l__datatool_result_tl
      { #5 { \l__datatool_resulta_tl } { \l__datatool_resultb_tl } }
   }
   {
     #4
       { \l__datatool_result_tl }
       { \l__datatool_resulta_tl }
       { \l__datatool_resultb_tl }
   }
  \__datatool_assign_result:N #1
 }
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_numeric\_fn:NnNN}
%\meta{result tl-var} \marg{num} 
% \meta{decimal-operator-cs} \meta{int-operator-cs}
%\end{definition}
%Perform a numerical operation on one value that needs parsing or
%that is already in datum format. If the value is an integer, 
%use the \meta{int-operator-cs} function, which should take one
%argument and expand to the result, otherwise use the
%\cs{decimal-operator-cs} function, which should take two arguments
%where the first is the result token list variable and the second is
%the numeric value.
%    \begin{macrocode}
\cs_new:Nn \datatool_numeric_fn:NnNN
 {
  \DTLconverttodecimal { #2 } \l__datatool_resulta_tl
%    \end{macrocode}
%Determine which function should be used.
%    \begin{macrocode}
  \datatool_if_any_int_datum_type:nTF { \@dtl@datatype }
   {
%    \end{macrocode}
%Version 3.1: changed \cs{tl\_set:Nx} to \cs{tl\_set:Ne}
%(not sure if this makes difference).
%    \begin{macrocode}
     \tl_set:Ne \l__datatool_result_tl
      { #4 { \l__datatool_resulta_tl }  }
   }
   {
     #3
       { \l__datatool_result_tl }
       { \l__datatool_resulta_tl }
   }
  \__datatool_assign_result:N #1
 }
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_numeric\_fn:NnN}
%\meta{result tl-var} \marg{num} 
% \meta{decimal-operator-cs}
%\end{definition}
%Perform a numerical operation on one value that needs parsing or
%that is already in datum format. The calculation is performed by
%\cs{decimal-operator-cs} function, which should take two arguments
%where the first is the result token list variable and the second is
%the numeric value. The result is not expected to be an integer.
%    \begin{macrocode}
\cs_new:Nn \datatool_numeric_fn:NnN
 {
  \DTLconverttodecimal { #2 } \l__datatool_resulta_tl
   #3
     { \l__datatool_result_tl }
     { \l__datatool_resulta_tl }
  \datatool_if_any_int_datum_type:nTF { \@dtl@datatype }
   {
      \int_set_eq:NN \@dtl@datatype \c_datatool_decimal_int
   }
  \__datatool_assign_result:N #1
 }
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_numeric\_list\_fn:NnNN}
%\meta{result tl-var} \marg{num list}
% \meta{decimal-operator-cs} \meta{int-operator-cs}
%\end{definition}
%Perform a numerical operation on a list of values that need parsing or
%that are already in datum format. As \cs{datatool\_numeric\_fn:NnnNN}
%but iterates through the list performing the applicable function
%sequentially. The result will be set to \cs{DTLnumbernull} if the
%list is empty (and a warning will occur).
%    \begin{macrocode}
\cs_new:Nn \datatool_numeric_list_fn:NnNN
 {
%    \end{macrocode}
%Convert the list to the scratch sequence:
%    \begin{macrocode}
   \@dtl@assigntmpseq { #2 }
%    \end{macrocode}
%Keep a note of the number of items in the sequence if required.
%    \begin{macrocode}
   \int_set:Nn \l__datatool_count_int
     { \seq_count:N \l__datatool_tmp_seq }
%    \end{macrocode}
%If sequence is empty, set the result to number null:
%    \begin{macrocode}
   \int_if_zero:nTF { \l__datatool_count_int }
    {
      \PackageWarning { datatool-base }
         { empty ~ list ~ ` #2 ' ~ found ~ in ~ aggregate ~ function }
      \tl_set_eq:NN #1 \DTLnumbernull
    }
    {
%    \end{macrocode}
%Pop the first item from the sequence:
%    \begin{macrocode}
      \seq_pop_left:NN \l__datatool_tmp_seq \l__datatool_resulta_tl
      \exp_args:NV \DTLconverttodecimal 
        \l__datatool_resulta_tl 
        \l__datatool_result_tl 
      \seq_map_inline:Nn \l__datatool_tmp_seq
       {
%    \end{macrocode}
% Save previous:
%    \begin{macrocode}
         \int_set_eq:NN 
           \l__datatool_tmp_datatype_int
           \@dtl@datatype
         \tl_set_eq:NN
           \l__datatool_tmp_currency_tl
           \l__datatool_datum_currency_tl
         \tl_set_eq:NN
           \l__datatool_resulta_tl
           \l__datatool_result_tl
%    \end{macrocode}
%Parse this value.
%    \begin{macrocode}
         \DTLconverttodecimal { ##1 } \l__datatool_resultb_tl 
%    \end{macrocode}
%If the data types are different, need to determine the dominant
%one.
%    \begin{macrocode}
         \__datatool_update_datatype:
%    \end{macrocode}
%Determine which function should be used.
%    \begin{macrocode}
         \datatool_if_any_int_datum_type:nTF { \@dtl@datatype }
          {
%    \end{macrocode}
%Version 3.1: changed \cs{tl\_set:Nx} to \cs{tl\_set:Ne}
%(not sure if this makes difference).
%    \begin{macrocode}
            \tl_set:Ne \l__datatool_result_tl
             { #4 { \l__datatool_resulta_tl } { \l__datatool_resultb_tl } }
          }
          {
            #3
              { \l__datatool_result_tl }
              { \l__datatool_resulta_tl }
              { \l__datatool_resultb_tl }
          }
       }
      \__datatool_assign_result:N #1
    }
 }
%    \end{macrocode}
%
%\begin{definition}
%\cs{datatool\_decimal\_list\_fn:NnN}
%\meta{result fp-var} \marg{num list}
% \meta{fp-operator-cs}
%\end{definition}
%Similar to the above but the result is expected to be a floating
%point variable and the function should by a l2fp update function,
%such as \cs{fp\_add:Nn}.
%    \begin{macrocode}
\cs_new:Nn \datatool_decimal_list_fn:NnN
 {
%    \end{macrocode}
%Convert the list to the scratch sequence:
%    \begin{macrocode}
   \@dtl@assigntmpseq { #2 }
%    \end{macrocode}
%Keep a note of the number of items in the sequence if required.
%    \begin{macrocode}
   \int_set:Nn \l__datatool_count_int
     { \seq_count:N \l__datatool_tmp_seq }
%    \end{macrocode}
%Trigger an error if the list is empty:
%    \begin{macrocode}
   \int_if_zero:nTF { \l__datatool_count_int }
    {
      \PackageError { datatool-base }
        { empty ~ list ~ ` #2 ' ~ found ~ in ~ aggregate ~ function }
        { one ~ or ~ more ~ numeric ~ items ~ are ~ expected }
    }
    {
%    \end{macrocode}
%Pop the first item from the sequence:
%    \begin{macrocode}
      \seq_pop_left:NN \l__datatool_tmp_seq \l__datatool_resulta_tl
      \datatool_set_fp:NV #1 \l__datatool_resulta_tl 
      \seq_map_inline:Nn \l__datatool_tmp_seq
       {
%    \end{macrocode}
% Save previous datum information:
%    \begin{macrocode}
         \int_set_eq:NN 
           \l__datatool_tmp_datatype_int
           \@dtl@datatype
         \tl_set_eq:NN
           \l__datatool_tmp_currency_tl
           \l__datatool_datum_currency_tl
%    \end{macrocode}
%Get this value as fp variable.
%    \begin{macrocode}
         \datatool_set_fp:Nn \l__datatool_tmpa_fp { ##1 }
%    \end{macrocode}
%If the data types are different, need to determine the dominant
%one.
%    \begin{macrocode}
         \__datatool_update_datatype:
%    \end{macrocode}
%Perform the update.
%    \begin{macrocode}
            #3 #1 { \l__datatool_tmpa_fp }
       }
    }
 }
%    \end{macrocode}
%
%
%\begin{macro}{\DTLadd}
%\begin{definition}
% \cs{DTLadd}\marg{cmd}\marg{num1}\marg{num2}
%\end{definition}
% Sets \meta{cmd} = \meta{num1} + \meta{num2}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLadd { m m m }
{
  \datatool_numeric_fn:NnnNN
    #1 { #2 } { #3 } \dtladd \datatool_int_add:nn
}
%    \end{macrocode}
%For use in the above and related commands:
%    \begin{macrocode}
\cs_new:Nn \datatool_int_add:nn { \int_eval:n { #1 + #2 } }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgadd}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgadd { m m m }
{
  \DTLadd { \l__datatool_resulta_tl } { #2 } { #3 }
  \tl_gset_eq:NN #1 \l__datatool_resulta_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLaddall}
%\begin{definition}
%\cs{DTLaddall}\marg{cmd}\marg{num list}
%\end{definition}
% Sums all the values in \meta{num list} and stores in
% \meta{cmd} which must be a control sequence.
%\changes{1.01}{2007 Aug 17}{removed extraneous space}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLaddall { m m }
{
  \datatool_numeric_list_fn:NnNN
    #1 { #2 } \dtladd \datatool_int_add:nn
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgaddall}
%\begin{definition}
%\cs{DTLgaddall}\marg{cmd}\marg{num list}
%\end{definition}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgaddall { m m }
{
  \DTLaddall { \l__datatool_resultb_tl } { #2 }
  \tl_gset_eq:NN #1 \l__datatool_resultb_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsub}
%\begin{definition}
% \cs{DTLsub}\marg{cmd}\marg{num1}\marg{num2}
%\end{definition}
% Sets \meta{cmd} = \meta{num1} - \meta{num2}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLsub { m m m }
{
  \datatool_numeric_fn:NnnNN
    #1 { #2 } { #3 } \dtlsub \datatool_int_sub:nn
}
%    \end{macrocode}
%For use in the above:
%    \begin{macrocode}
\cs_new:Nn \datatool_int_sub:nn { \int_eval:n { #1 - ( #2 ) } }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgsub}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgsub { m m m }
{
  \DTLsub { \l__datatool_resulta_tl } {#2} {#3}
  \tl_gset_eq:NN #1 \l__datatool_resulta_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLmul}
%\begin{definition}
% \cs{DTLmul}\marg{cmd}\marg{num1}\marg{num2}
%\end{definition}
% Sets \meta{cmd} = \meta{num1} $\times$ \meta{num2}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLmul { m m m }
{
  \datatool_numeric_fn:NnnNN
    #1 { #2 } { #3 } \dtlmul \datatool_int_mul:nn
}
%    \end{macrocode}
%For use in the above:
%    \begin{macrocode}
\cs_new:Nn \datatool_int_mul:nn { \int_eval:n { ( #1 ) * ( #2 ) } }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgmul}
% Global version
%    \begin{macrocode}
\newcommand*{\DTLgmul}[3]{%
  \DTLmul{ \l__datatool_resulta_tl } {#2} {#3}
  \tl_gset_eq:NN #1 \l__datatool_resulta_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdiv}
%\begin{definition}
% \cs{DTLdiv}\marg{cmd}\marg{num1}\marg{num2}
%\end{definition}
% Sets \meta{cmd} = \meta{num1} / \meta{num2}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLdiv { m m m }
{
  \datatool_numeric_fn:NnnNN
    #1 { #2 } { #3 } \dtldiv \int_div_round:nn
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgdiv}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgdiv { m m m }
{
  \DTLdiv{ \l__datatool_resulta_tl } {#2} {#3}
  \tl_gset_eq:NN #1 \l__datatool_resulta_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLabs}
%\begin{definition}
% \cs{DTLabs}\marg{cmd}\marg{num}
%\end{definition}
% Sets \meta{cmd} = abs(\meta{num})
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLabs { m m }
{
  \datatool_numeric_fn:NnNN
    #1 { #2 } \dtlabs \int_abs:n
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgabs}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgabs { m m }
{
  \DTLabs{ \l__datatool_resulta_tl } {#2} 
  \tl_gset_eq:NN #1 \l__datatool_resulta_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLneg}
%\begin{definition}
% \cs{DTLneg}\marg{cmd}\marg{num}
%\end{definition}
% Sets \meta{cmd} = -\meta{num}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLneg { m m }
{
  \datatool_numeric_fn:NnNN
    #1 { #2 } \dtlneg \datatool_int_neg:n
}
%    \end{macrocode}
%Integer negation function for use in the above.
%    \begin{macrocode}
\cs_new:Nn \datatool_int_neg:n
  { \int_eval:n { - ( #1 ) } }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgneg}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgneg { m m }
{
  \DTLneg{ \l__datatool_resulta_tl } {#2} 
  \tl_gset_eq:NN #1 \l__datatool_resulta_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsqrt}
%\begin{definition}
% \cs{DTLsqrt}\marg{cmd}\marg{num}
%\end{definition}
% Sets \meta{cmd} = sqrt(\meta{num})
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLsqrt { m m }
{
  \datatool_numeric_fn:NnN #1 { #2 } \dtlroot
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgsqrt}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgsqrt { m m }
{
  \DTLsqrt{ \l__datatool_resulta_tl } {#2} 
  \tl_gset_eq:NN #1 \l__datatool_resulta_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLmin}
%\begin{definition}
% \cs{DTLmin}\marg{cmd}\marg{num1}\marg{num2}
%\end{definition}
% Sets \meta{cmd} = min(\meta{num1}, \meta{num2})
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLmin { m m m }
{
  \datatool_numeric_fn:NnnNN
    #1 { #2 } { #3 } \dtlmin \int_min:nn
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgmin}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgmin { m m m }
{
  \DTLmin{ \l__datatool_resulta_tl } {#2} {#3}
  \tl_gset_eq:NN #1 \l__datatool_resulta_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLminall}
%\begin{definition}
%\cs{DTLminall}\marg{cmd}\marg{num list}
%\end{definition}
% Finds the minimum value in \meta{num list} and stores in
% \meta{cmd} which must be a control sequence.
%\changes{1.01}{2007 Aug 17}{removed extraneous space}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLminall { m m }
{
  \datatool_numeric_list_fn:NnNN
    #1 { #2 } \dtlmin \int_min:nn 
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgminall}
%\begin{definition}
%\cs{DTLgminall}\marg{cmd}\marg{num list}
%\end{definition}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgminall { m m }
{
  \DTLminall{ \l__datatool_resultb_tl } { #2 }
  \tl_gset_eq:NN #1 \l__datatool_resultb_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLmax}
%\begin{definition}
% \cs{DTLmax}\marg{cmd}\marg{num1}\marg{num2}
%\end{definition}
% Sets \meta{cmd} = max(\meta{num1}, \meta{num2})
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLmax { m m m }
{
  \datatool_numeric_fn:NnnNN
    #1 { #2 } { #3 } \dtlmax \int_max:nn
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgmax}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgmax { m m m }
{
  \DTLmax { \l__datatool_resultb_tl } { #2 } { #3 }
  \tl_gset_eq:NN #1 \l__datatool_resultb_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLmaxall}
%\begin{definition}
%\cs{DTLmaxall}\marg{cmd}\marg{num list}
%\end{definition}
% Finds the maximum value in \meta{num list} and stores in
% \meta{cmd} which must be a control sequence.
%\changes{1.01}{2007 Aug 17}{removed extraneous space}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLmaxall { m m }
{
  \datatool_numeric_list_fn:NnNN
    #1 { #2 } \dtlmax \int_max:nn 
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgmaxall}
%\begin{definition}
%\cs{DTLgmaxall}\marg{cmd}\marg{num list}
%\end{definition}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgmaxall { m m }
{
  \DTLmaxall{ \l__datatool_resultb_tl } { #2 }
  \tl_gset_eq:NN #1 \l__datatool_resultb_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLmeanforall}
%\begin{definition}
%\cs{DTLmeanforall}\marg{cmd}\marg{num list}
%\end{definition}
% Computes the arithmetic mean of all the values in \meta{num list} 
% and stores in \meta{cmd} which must be a control sequence.
%\changes{1.01}{2007 Aug 17}{removed extraneous space}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLmeanforall { m m }
{
  \datatool_decimal_list_fn:NnN
    \l__datatool_total_fp { #2 } \fp_add:Nn 
  \int_if_zero:nTF { \l__datatool_count_int }
   {
     \tl_set_eq:NN #1 \DTLnumbernull
   }
   {
     \fp_set:Nn
       \l__datatool_mean_fp 
       { \l__datatool_total_fp / \l__datatool_count_int }
%    \end{macrocode}
%\changes{3.1}{2025-03-10}{changed \cs{tl\_set:Nx} to \cs{tl\_set:Ne}}
%    \begin{macrocode}
     \tl_set:Ne
       \l__datatool_result_tl
       { \fp_use:N \l__datatool_mean_fp }
     \__datatool_assign_result:N #1
   }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgmeanforall}
%\begin{definition}
%\cs{DTLgmeanforall}\marg{cmd}\marg{num list}
%\end{definition}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgmeanforall { m m }
{
  \DTLmeanforall{ \l__datatool_resultb_tl } { #2 }
  \tl_gset_eq:NN #1 \l__datatool_resultb_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLvarianceforall}
%\begin{definition}
%\cs{DTLvarianceforall}\marg{cmd}\marg{num list}
%\end{definition}
% Computes the variance of all the values in \meta{num list} 
% and stores in \meta{cmd} which must be a control sequence.
% This is more complicated than the previous aggregate functions.
%\changes{1.01}{2007 Aug 17}{fixed bug}
%\changes{1.01}{2007 Aug 17}{removed extraneous space}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLvarianceforall { m m }
{
  \@dtl@assigntmpseq{#2}
  \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
  \seq_map_inline:Nn \l__datatool_tmp_seq
   {
     \int_incr:N \l__datatool_count_int
%    \end{macrocode}
% Save previous datum information:
%    \begin{macrocode}
     \int_set_eq:NN 
       \l__datatool_tmp_datatype_int
       \@dtl@datatype
     \tl_set_eq:NN
       \l__datatool_tmp_currency_tl
       \l__datatool_datum_currency_tl
%    \end{macrocode}
%Convert:
%    \begin{macrocode}
     \DTLconverttodecimal { ##1 } \l__datatool_resulta_tl 
     \__datatool_update_datatype:
     \seq_put_right:No \l__datatool_tmpb_seq { \l__datatool_resulta_tl }
     \fp_add:Nn
       \l__datatool_total_fp 
       { \l__datatool_resulta_tl }
   }
  \int_if_zero:nTF { \l__datatool_count_int }
   {
     \PackageWarning { datatool-base }
        { empty ~ list ~ ` #2 ' ~ found ~ in ~ aggregate ~ function }
     \tl_set_eq:NN #1 \DTLnumbernull
   }
   {
    \fp_set:Nn
      \l__datatool_mean_fp 
      {  \l__datatool_total_fp  /  \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 }
%    \end{macrocode}
%\changes{3.1}{2025-03-10}{changed \cs{tl\_set:Nx} to \cs{tl\_set:Ne}}
%    \begin{macrocode}
    \tl_set:Ne
      \l__datatool_result_tl
      { \fp_use:N \l__datatool_tmpa_fp }
    \__datatool_assign_result:N #1
   }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgvarianceforall}
%\begin{definition}
%\cs{DTLgvarianceforall}\marg{cmd}\marg{num list}
%\end{definition}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgvarianceforall { m m }
{
  \DTLvarianceforall { \l__datatool_resultb_tl } { #2 }
  \tl_gset_eq:NN #1 \l__datatool_resultb_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsdforall}
%\begin{definition}
%\cs{DTLsdforall}\marg{cmd}\marg{num list}
%\end{definition}
% Computes the standard deviation of all the values in \meta{num list} 
% and stores in \meta{cmd} which must be a control sequence.
%\changes{1.01}{2007 Aug 17}{fixed bug}
%\changes{1.01}{2007 Aug 17}{removed extraneous space}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLsdforall { m m }
{
  \@dtl@assigntmpseq{#2}
  \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
  \seq_map_inline:Nn \l__datatool_tmp_seq
   {
     \int_incr:N \l__datatool_count_int
%    \end{macrocode}
% Save previous datum information:
%    \begin{macrocode}
     \int_set_eq:NN 
       \l__datatool_tmp_datatype_int
       \@dtl@datatype
     \tl_set_eq:NN
       \l__datatool_tmp_currency_tl
       \l__datatool_datum_currency_tl
%    \end{macrocode}
%Convert:
%    \begin{macrocode}
     \DTLconverttodecimal { ##1 } \l__datatool_resulta_tl 
     \__datatool_update_datatype:
     \seq_put_right:No \l__datatool_tmpb_seq { \l__datatool_resulta_tl }
     \fp_add:Nn
       \l__datatool_total_fp 
       { \l__datatool_resulta_tl }
   }
  \int_if_zero:nTF { \l__datatool_count_int }
   {
     \PackageWarning { datatool-base }
        { empty ~ list ~ ` #2 ' ~ found ~ in ~ aggregate ~ function }
     \tl_set_eq:NN #1 \DTLnumbernull
   }
   {
     \fp_set:Nn
       \l__datatool_mean_fp 
       {  \l__datatool_total_fp  /  \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 ) }
%    \end{macrocode}
%\changes{3.1}{2025-03-10}{changed \cs{tl\_set:Nx} to \cs{tl\_set:Ne}}
%    \begin{macrocode}
     \tl_set:Ne
       \l__datatool_result_tl
       { \fp_use:N \l__datatool_tmpa_fp }
     \__datatool_assign_result:N #1
   }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgsdforall}
%\begin{definition}
%\cs{DTLgsdforall}\marg{cmd}\marg{num list}
%\end{definition}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgsdforall { m m }
{
  \DTLsdforall { \l__datatool_resultb_tl } { #2 }
  \tl_gset_eq:NN #1 \l__datatool_resultb_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLround}
%\begin{definition}
% \cs{DTLround}\marg{cmd}\marg{num}\marg{num digits}
%\end{definition}
% Sets \meta{cmd} to \meta{num} rounded to \meta{num digits}
% digits after the decimal character.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLround { m m m }
{
  \DTLconverttodecimal{#2} \l__datatool_result_tl
  \dtlround
    { \l__datatool_result_tl }
    { \l__datatool_result_tl }
    { #3 }
  \__datatool_assign_result:N #1
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLground}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLground { m m m }
{
  \DTLround { \l__datatool_resulta_tl } { #2 } { #3 }
  \tl_gset_eq:NN #1 \l__datatool_resulta_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLtrunc}
%\begin{definition}
% \cs{DTLtrunc}\marg{cmd}\marg{num}\marg{num digits}
%\end{definition}
% Sets \meta{cmd} to \meta{num} truncated to \meta{num digits}
% digits after the decimal character.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLtrunc { m m m }
{
  \DTLconverttodecimal { #2 } \l__datatool_result_tl
  \dtltrunc
    { \l__datatool_result_tl }
    { \l__datatool_result_tl }
    { #3 }
  \__datatool_assign_result:N #1
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgtrunc}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgtrunc { m m m }
{
  \DTLtrunc{ \l__datatool_resulta_tl } {#2} {#3}
  \tl_gset_eq:NN #1 \l__datatool_resulta_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLclip}
%\begin{definition}
% \cs{DTLclip}\marg{cmd}\marg{num}
%\end{definition}
% Sets \meta{cmd} to \meta{num} with all unnecessary 0's removed.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLclip { m m }
{
  \DTLconverttodecimal{#2} \l__datatool_result_tl
  \dtlclip
    { \l__datatool_result_tl }
    { \l__datatool_result_tl }
  \__datatool_assign_result:N #1
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgclip}
% Global version
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgclip { m m }
{
  \DTLclip{ \l__datatool_resulta_tl } {#2} 
  \tl_gset_eq:NN #1 \l__datatool_resulta_tl
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{String Macros}
%
%\begin{macro}{\DTLCurrentLocaleGetInitialLetter}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLCurrentLocaleGetInitialLetter}[2]{%
  \datatool_get_first_letter:nN { #1 } #2 
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\regex_new:N \l__datatool_initial_cs_regex
\regex_set:Nn \l__datatool_initial_cs_regex { \A \c{.+} \cB(.) }
%    \end{macrocode}
%
%\begin{macro}{\DTLGetInitialLetter}
%\changes{3.0}{2025-03-03}{new}
%Designed to obtain the first letter for initials or letter groups.
%This can't simply grab the first token as it may be a multi-byte
%character or may start with a command.
%    \begin{macrocode}
\NewDocumentCommand{\DTLGetInitialLetter} { m m }
 {
   \bool_if:NTF \l__datatool_initial_purify_early_bool
   {
     \tl_if_head_is_group:nTF { #1 }
      {
        \tl_set:Nx #2 { \text_purify:n { \tl_head:n { #1 } } }
      }
      {
        \exp_args:Nx \__datatool_get_initial_letter:nN
          { \text_purify:n { #1 } } #2
      }
   }
   {
     \__datatool_get_initial_letter:nN { #1 } #2
   }
 }
\cs_new:Nn \__datatool_get_initial_letter:nN
 {
   \tl_if_blank:nTF { #1 }
    {
      \tl_clear:N #2
    }
    {
      \tl_if_head_is_group:nTF { #1 }
       {
         \tl_set:Nx #2 { \tl_head:n { #1 } }
         \tl_set:Nx #2 { \text_purify:n { #2 } }
       }
       {
        \regex_match:NnTF \l__datatool_initial_cs_regex { #1 }
         {
           \tl_set:Nx #2 { \tl_tail:n { #1 } }
           \exp_args:NNx \tl_set:Nx #2 { \exp_args:No \tl_head:n { #2 } }
           \exp_args:No \DTLCurrentLocaleGetInitialLetter { #2 } { #2 }
           \tl_set:Nx #2 { \tl_head:n { #1 } { \exp_not:o { #2 } } }
         }
         {
           \DTLCurrentLocaleGetInitialLetter { #1 } { #2 }
         }
       }
    }
 }
%    \end{macrocode}
%\end{macro}
%Get the first grapheme (which may be letter or punctuation):
%    \begin{macrocode}
\cs_new:Nn \datatool_get_first_grapheme:nN
 {
   \tl_clear:N #2
   \exp_args:Nx \text_map_inline:nn { \text_purify:n { #1 } }
   {
     \tl_set:Nn #2 { ##1 }
     \text_map_break:
   }
 }
%    \end{macrocode}
%
%Getting an initial letter but skipping leading punctuation is awkward
%because \verb|[:alpha:]| only matches ASCII letters. Need to also 
%allow for the fact that UTF-8 characters are
%actually an active character with one or more arguments with
%\sty{inputenc} and it's also necessary to allow for
%control characters that are used to adjust sorting which have been
%given a letter character code.
%
%So the following first tests if the category code is a letter (which will be
%the case for UTF-8 letters with \XeLaTeX\ and \LuaLaTeX\ but not
%with \pdfLaTeX). If not a letter category code, then the next part
%is a bit of a hack and is based on the assumption that all
%letters have a different upper and lower representation. The first
%argument should be a single grapheme (which may be a multi-byte
%character). It won't work if the first argument consists of a
%mixture of letters and non-letters.
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_letter:n #1 
  { T, F, TF }
 {
   \tl_if_head_eq_catcode:nNTF { #1 } \c_catcode_letter_token
    { \prg_return_true: }
    {
      \exp_args:Nee \tl_if_eq:nnTF
       { \text_uppercase:n { #1 } } { \text_lowercase:n { #1 } }
       { \prg_return_false: }
       { \prg_return_true: }
    }
 }
%    \end{macrocode}
%Get the first letter (skipping any preceding non-letters).
%    \begin{macrocode}
\cs_new:Nn \datatool_get_first_letter:nN
 {
   \tl_clear:N #2
   \exp_args:Nx \text_map_inline:nn { \text_purify:n { #1 } }
   {
     \datatool_if_letter:nT { ##1 }
      {
        \tl_set:Nn #2 { ##1 }
        \text_map_break:
      }
   }
 }
%    \end{macrocode}
%
%Convert a string into a sequence of words.
%NB \verb|[:alpha:]| only matches ASCII letters (see above).
%Multi-byte characters match \verb|[:punct:]|.
%    \begin{macrocode}
\regex_new:N \l_datatool_word_head_regex
\regex_set:Nn \l_datatool_word_head_regex 
 {
    ([[:punct:][:digit:]]*)
    ([^[:punct:][:digit:][:space:]\~]+?)
    ([[:punct:][:digit:]]*)
    ((?:[[:space:]\~]|\c{protect}?\c{(?:nobreak)?space\ ?})+)
 }
\regex_set:Nn \l_datatool_word_hyphen_regex 
 {
    ([[:punct:][:digit:]]*)
    ([^[:punct:][:digit:][:space:]\~]+?)
    ([[:punct:][:digit:]]*)
    -{1} 
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_set:Nn \l_datatool_word_apos_regex 
 {
    ([[:punct:][:digit:]]*)
    ([^[:punct:][:digit:][:space:]\~]+)
    \ur{l_datatool_apos_regex} 
    (
      [^[:punct:][:digit:][:space:]\~]+
      (?:\ur{l_datatool_apos_regex}[^[:punct:][:digit:][:space:]\~]+)*
    )
    ([[:punct:][:digit:]]*)
    ((?:[[:space:]\~]|\c{protect}?\c{(?:nobreak)?space\ ?})+)
 }
\regex_set:Nn \l_datatool_word_apos_hyphen_regex 
 {
    ([[:punct:][:digit:]]*)
    ([^[:punct:][:digit:][:space:]\~]+)
    \ur{l_datatool_apos_regex} 
    (
      [^[:punct:][:digit:][:space:]\~]+
      (?:\ur{l_datatool_apos_regex}[^[:punct:][:digit:][:space:]\~]+)*
    )
    ([[:punct:][:digit:]]*)
    -{1} 
 }
\regex_set:Nn \l_datatool_word_apos_tail_regex 
 {
    ([[:punct:][:digit:]]*)
    ([^[:punct:][:digit:][:space:]\~]+)
    \ur{l_datatool_apos_regex} 
    (
      [^[:punct:][:digit:][:space:]\~]+
      (?:\ur{l_datatool_apos_regex}[^[:punct:][:digit:][:space:]\~]+)*
    )
    ([[:punct:][:digit:]]*)
    \Z
 }
\regex_set:Nn \l_datatool_word_tail_regex 
 {
    ([[:punct:][:digit:]]*)
    ([^[:punct:][:digit:][:space:]\~]+)
    ([[:punct:][:digit:]]*)
    \Z
 }
\regex_set:Nn \l_datatool_symbols_regex 
 {
    ((?:[!-@\[\]\^\_`\{\|\}]|\c{[[:alpha:][:punct:]]+})+?)
    ((?:[[:space:]\-\~]|\c{(?:nobreak)?space})+)
 }
\regex_set:Nn \l_datatool_other_regex 
 {
    (.+?)
    ((?:[[:space:]\-\~]|\c{(?:nobreak)?space})+)
 }
\cs_new:Nn \__datatool_leading_punc:n { \exp_not:n { #1 } }
\cs_new:Nn \__datatool_trailing_punc:n { \exp_not:n { #1 } }
\cs_new:Nn \__datatool_word:n { \exp_not:n { #1 } }
\cs_new:Nn \__datatool_word_hyphen:n { \exp_not:n { #1 } }
\cs_new:Nn \__datatool_word_apos:nn { \exp_not:n { #1 } ' \exp_not:n { #2 } }
\cs_new:Nn \__datatool_word_apos_hyphen:nn { \exp_not:n { #1 } ' \exp_not:n { #2 } }
\cs_new:Nn \__datatool_last_word_apos:nn { \exp_not:n { #1 } ' \exp_not:n { #2 } }
\cs_new:Nn \__datatool_last_word:n { \exp_not:n { #1 } }
\cs_new:Nn \__datatool_word_sep:n { \exp_not:n { #1 } }
\cs_new:Nn \__datatool_hyphen_sep: { - }
\cs_new:Nn \__datatool_symbol:n { \exp_not:n { #1 } }
\cs_new:Nn \datatool_parse_words:N
 {
   \exp_args:NNo \datatool_parse_words:Nn #1 { #1 }
 }
\cs_new:Nn \datatool_parse_words:Nn
 {
   \tl_set:Nx #1 { \tl_trim_spaces:n { #2 } }
   \regex_replace_case_all:nN
    {
      \l_datatool_word_head_regex
        { 
          \c{__datatool_leading_punc:n}{\1}
          \c{__datatool_word:n}{\2}
          \c{__datatool_trailing_punc:n}{\3}
          \c{__datatool_word_sep:n}{\4}
        }
      \l_datatool_word_apos_hyphen_regex
        { 
          \c{__datatool_leading_punc:n}{\1}
          \c{__datatool_word_apos_hyphen:nn}{\2}{\3}
          \c{__datatool_trailing_punc:n}{\4}
          \c{__datatool_hyphen_sep:n}
        }
      \l_datatool_word_hyphen_regex
        { 
          \c{__datatool_leading_punc:n}{\1}
          \c{__datatool_word_hyphen:n}{\2}
          \c{__datatool_trailing_punc:n}{\3}
          \c{__datatool_hyphen_sep:n}
        }
      \l_datatool_word_apos_regex
        { 
          \c{__datatool_leading_punc:n}{\1}
          \c{__datatool_word_apos:nn}{\2}{\3}
          \c{__datatool_trailing_punc:n}{\4}
          \c{__datatool_word_sep:n}{\5}
        }
      \l_datatool_word_apos_tail_regex
        { 
          \c{__datatool_leading_punc:n}{\1}
          \c{__datatool_last_word_apos:nn}{\2}{\3}
          \c{__datatool_trailing_punc:n}{\4}
        }
      \l_datatool_word_tail_regex
        { 
          \c{__datatool_leading_punc:n}{\1}
          \c{__datatool_last_word:n}{\2}
          \c{__datatool_trailing_punc:n}{\3}
        }
      \l_datatool_symbols_regex 
        {
          \c{__datatool_symbol:n}{\1}
          \c{__datatool_word_sep:n}{\4}
        }
      \l_datatool_other_regex 
        {
          \c{__datatool_symbol:n}{\1}
          \c{__datatool_word_sep:n}{\4}
        }
    }
    #1
 }
%    \end{macrocode}
%
%\begin{macro}{\DTLinitials}
%\begin{definition}
%\cs{DTLinitials}\marg{string}
%\end{definition}
% Convert a string into initials.
% (Any "~" character found is first converted into a space.)
% As from v3.0 this just uses \cs{DTLstoreinitials} with a
% temporary command. The extra grouping probably isn't necessary.
%\changes{1.01}{2007 Aug 17}{now works with unbreakable space symbol}
%\changes{1.01}{2007 Aug 17}{now uses \cs{DTLinitialhyphen}}
%\changes{3.0}{2025-03-03}{changed to new document command}
%    \begin{macrocode}
\NewDocumentCommand \DTLinitials { m }
{
  \group_begin:
   \DTLstoreinitials { #1 } { \l__datatool_tmpb_tl }
   \l__datatool_tmpb_tl
  \group_end:
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\xDTLinitials}
%\begin{definition}
%\cs{xDTLinitials}\marg{cmd}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \xDTLinitials { m }
{
  \exp_args:No \DTLinitials { #1 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLstoreinitials}
%\begin{definition}
%\cs{DTLstoreinitials}\marg{string}\marg{cmd}
%\end{definition}
% Convert a string into initials and store in \meta{cmd}.
% (Any "~" character found is first converted into a space.)
%\changes{1.01}{2007 Aug 17}{now works with unbreakable space symbol}
%\changes{1.01}{2007 Aug 17}{now uses \cs{DTLinitialhyphen}}
%\changes{3.0}{2025-03-03}{changed to new document command}
%    \begin{macrocode}
\NewDocumentCommand \DTLstoreinitials { m m }
{
  \tl_clear:N #2
  \tl_if_empty:nF { #1 } 
  {
    \datatool_parse_words:Nn \l__datatool_tmpa_tl { #1 }
    \tl_put_right:Nn \l__datatool_tmpa_tl { \q_recursion_tail }
    \__datatool_store_initials:N #2
    \q_recursion_stop
  }
}
\cs_new:Nn \__datatool_store_initials:N 
 {
   \tl_set:Nx \l__datatool_tmp_initial_tl { \tl_head:N \l__datatool_tmpa_tl }
   \tl_set:Nx \l__datatool_tmpa_tl { \tl_tail:N \l__datatool_tmpa_tl }
   \tl_if_eq:NnTF
    \l__datatool_tmp_initial_tl { \__datatool_word:n }
    {
      \tl_set:Nx \l__datatool_tmp_initial_tl { \tl_head:N \l__datatool_tmpa_tl }
      \tl_set:Nx \l__datatool_tmpa_tl { \tl_tail:N \l__datatool_tmpa_tl }
      \exp_args:No \DTLStoreInitialGetLetter
         { \l__datatool_tmp_initial_tl } \l__datatool_tmp_initial_tl
      \tl_if_empty:NF \l__datatool_tmp_initial_tl
       {
         \tl_put_right:Nn #1 { \DTLinitialpunc }
         \tl_put_right:Nx #1 { { \l__datatool_tmp_initial_tl } }
         \tl_put_right:Nn #1 { { \DTLbetweeninitials } }
       }
    }
    {
      \tl_if_eq:NnTF
       \l__datatool_tmp_initial_tl { \__datatool_last_word:n }
       {
         \tl_set:Nx \l__datatool_tmp_initial_tl { \tl_head:N \l__datatool_tmpa_tl }
         \tl_set:Nx \l__datatool_tmpa_tl { \tl_tail:N \l__datatool_tmpa_tl }
         \exp_args:No \DTLStoreInitialGetLetter
            { \l__datatool_tmp_initial_tl } \l__datatool_tmp_initial_tl
         \tl_if_empty:NF \l__datatool_tmp_initial_tl
          {
            \tl_put_right:Nn #1 { \DTLinitialpunc }
            \tl_put_right:Nx #1 { { \l__datatool_tmp_initial_tl } }
            \tl_put_right:Nn #1 { { \DTLafterinitials } }
          }
       }
       {
         \tl_if_eq:NnTF
          \l__datatool_tmp_initial_tl { \__datatool_word_hyphen:n }
          {
            \tl_set:Nx \l__datatool_tmp_initial_tl { \tl_head:N \l__datatool_tmpa_tl }
            \tl_set:Nx \l__datatool_tmpa_tl { \tl_tail:N \l__datatool_tmpa_tl }
            \exp_args:No \DTLStoreInitialGetLetter
              { \l__datatool_tmp_initial_tl } \l__datatool_tmp_initial_tl
            \tl_if_empty:NF \l__datatool_tmp_initial_tl
             {
               \tl_put_right:Nn #1 { \DTLinitialpunc }
               \tl_put_right:Nx #1 { { \l__datatool_tmp_initial_tl } }
               \tl_put_right:Nn #1 { { \DTLafterinitialbeforehyphen } }
               \tl_put_right:Nn #1 { \DTLinitialhyphen }
             }
          }
          {
            \tl_if_eq:NnTF
             \l__datatool_tmp_initial_tl { \__datatool_word_apos:nn }
             {
               \tl_set:Nx \l__datatool_tmp_initial_tl { \tl_head:N \l__datatool_tmpa_tl }
               \tl_set:Nx \l__datatool_tmpa_tl { \tl_tail:N \l__datatool_tmpa_tl }
               \exp_args:No \DTLStoreInitialGetLetter
                 { \l__datatool_tmp_initial_tl } \l__datatool_tmp_initial_tl
               \tl_if_empty:NTF \l__datatool_tmp_initial_tl
                {
                  \tl_set:Nx \l__datatool_tmp_initial_tl { \tl_head:N \l__datatool_tmpa_tl }
                  \tl_set:Nx \l__datatool_tmpa_tl { \tl_tail:N \l__datatool_tmpa_tl }
                  \exp_args:No \DTLStoreInitialGetLetter
                    { \l__datatool_tmp_initial_tl } \l__datatool_tmp_initial_tl
                  \tl_if_empty:NF \l__datatool_tmp_initial_tl
                   {
                     \tl_put_right:Nn #1 { \DTLinitialpunc }
                     \tl_put_right:Nx #1 { { \l__datatool_tmp_initial_tl } }
                     \tl_put_right:Nn #1 { { \DTLbetweeninitials } }
                   }
                }
                {
                  \tl_put_right:Nn #1 { \DTLaposinitialpunc }
                  \tl_put_right:Nx #1 { { \l__datatool_tmp_initial_tl } }
                  \tl_set:Nx \l__datatool_tmp_initial_tl { \tl_head:N \l__datatool_tmpa_tl }
                  \tl_set:Nx \l__datatool_tmpa_tl { \tl_tail:N \l__datatool_tmpa_tl }
                  \exp_args:No \DTLStoreInitialGetLetter
                    { \l__datatool_tmp_initial_tl } \l__datatool_tmp_initial_tl
                  \tl_put_right:Nx #1 { { \l__datatool_tmp_initial_tl } }
                  \tl_put_right:Nn #1 { { \DTLbetweeninitials } }
                }
             }
             {
               \tl_if_eq:NnTF
                \l__datatool_tmp_initial_tl { \__datatool_word_apos_hyphen:nn }
                {
                  \tl_set:Nx \l__datatool_tmp_initial_tl { \tl_head:N \l__datatool_tmpa_tl }
                  \tl_set:Nx \l__datatool_tmpa_tl { \tl_tail:N \l__datatool_tmpa_tl }
                  \exp_args:No \DTLStoreInitialGetLetter
                    { \l__datatool_tmp_initial_tl } \l__datatool_tmp_initial_tl
                  \tl_if_empty:NTF \l__datatool_tmp_initial_tl
                   {
                     \tl_set:Nx \l__datatool_tmp_initial_tl
                       { \tl_head:N \l__datatool_tmpa_tl }
                     \tl_set:Nx \l__datatool_tmpa_tl
                       { \tl_tail:N \l__datatool_tmpa_tl }
                     \exp_args:No \DTLStoreInitialGetLetter
                       { \l__datatool_tmp_initial_tl } \l__datatool_tmp_initial_tl
                     \tl_if_empty:NF \l__datatool_tmp_initial_tl
                      {
                        \tl_put_right:Nn #1 { \DTLinitialpunc }
                        \tl_put_right:Nx #1 { { \l__datatool_tmp_initial_tl } }
                        \tl_put_right:Nn #1 { { \DTLafterinitialbeforehyphen } }
                        \tl_put_right:Nn #1 { \DTLinitialhyphen }
                      }
                   }
                   {
                     \tl_put_right:Nn #1 { \DTLaposinitialpunc }
                     \tl_put_right:Nx #1 { { \l__datatool_tmp_initial_tl } }
                     \tl_set:Nx \l__datatool_tmp_initial_tl
                       { \tl_head:N \l__datatool_tmpa_tl }
                     \tl_set:Nx \l__datatool_tmpa_tl
                       { \tl_tail:N \l__datatool_tmpa_tl }
                     \exp_args:No \DTLStoreInitialGetLetter
                       { \l__datatool_tmp_initial_tl } \l__datatool_tmp_initial_tl
                     \tl_put_right:Nx #1 { { \l__datatool_tmp_initial_tl } }
                     \tl_put_right:Nn #1 { { \DTLafterinitialbeforehyphen } }
                     \tl_put_right:Nn #1 { \DTLinitialhyphen }
                   }
                }
                {
                  \tl_if_eq:NnT
                   \l__datatool_tmp_initial_tl { \__datatool_last_word_apos:nn }
                   {
                     \tl_set:Nx \l__datatool_tmp_initial_tl
                        { \tl_head:N \l__datatool_tmpa_tl }
                     \tl_set:Nx \l__datatool_tmpa_tl
                        { \tl_tail:N \l__datatool_tmpa_tl }
                     \exp_args:No \DTLStoreInitialGetLetter
                       { \l__datatool_tmp_initial_tl } \l__datatool_tmp_initial_tl
                     \tl_if_empty:NTF \l__datatool_tmp_initial_tl
                      {
                        \tl_set:Nx \l__datatool_tmp_initial_tl
                          { \tl_head:N \l__datatool_tmpa_tl }
                        \tl_set:Nx \l__datatool_tmpa_tl
                          { \tl_tail:N \l__datatool_tmpa_tl }
                        \exp_args:No \DTLStoreInitialGetLetter
                          { \l__datatool_tmp_initial_tl } \l__datatool_tmp_initial_tl
                        \tl_if_empty:NF \l__datatool_tmp_initial_tl
                         {
                           \tl_put_right:Nn #1 { \DTLinitialpunc }
                           \tl_put_right:Nx #1 { { \l__datatool_tmp_initial_tl } }
                           \tl_put_right:Nn #1 { { \DTLafterinitials } }
                         }
                      }
                      {
                        \tl_put_right:Nn #1 { \DTLaposinitialpunc }
                        \tl_put_right:Nx #1 { { \l__datatool_tmp_initial_tl } }
                        \tl_set:Nx \l__datatool_tmp_initial_tl
                          { \tl_head:N \l__datatool_tmpa_tl }
                        \tl_set:Nx \l__datatool_tmpa_tl
                          { \tl_tail:N \l__datatool_tmpa_tl }
                        \exp_args:No \DTLStoreInitialGetLetter
                          { \l__datatool_tmp_initial_tl } \l__datatool_tmp_initial_tl
                        \tl_put_right:Nx #1 { { \l__datatool_tmp_initial_tl } }
                        \tl_put_right:Nn #1 { { \DTLafterinitials } }
                      }
                   }
                }
             }
          }
       }
    }
   \quark_if_recursion_tail_stop:N \l__datatool_tmpa_tl
   \__datatool_store_initials:N #1
 }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLStoreInitialGetLetter}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLStoreInitialGetLetter}[2]{\DTLGetInitialLetter{#1}{#2}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLafterinitials}
% Defines what to do after the final initial.
%    \begin{macrocode}
\newcommand*{\DTLafterinitials}{.}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLbetweeninitials}
% Defines what to do between initials.
%    \begin{macrocode}
\newcommand*{\DTLbetweeninitials}{.}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLafterinitialbeforehyphen}
% Defines what to do before a hyphen.
%    \begin{macrocode}
\newcommand*{\DTLafterinitialbeforehyphen}{.}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLinitialhyphen}
%Defines what to do at the hyphen
%\changes{1.01}{2007 Aug 17}{new}
%    \begin{macrocode}
\newcommand*{\DTLinitialhyphen}{-}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLinitialpunc}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLinitialpunc}[2]{#1#2}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLaposinitialpunc}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLaposinitialpunc}[3]{#1#3}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifAllUpperCase}
%\begin{definition}
%\cs{DTLifAllUpperCase}\marg{string}\marg{true part}\marg{false part}
%\end{definition}
% If \meta{string} only contains uppercase characters do \meta{true
%part}, otherwise do \meta{false part}. This needs to take UTF-8
%characters into account. NB regular expression "lower" and "upper" character
%classes doesn't match UTF-8 characters (regardless of the engine).
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\DTLifAllUpperCase}[3]{%
  \tl_set:Nx \l__datatool_tmpa_tl { \text_purify:n { #1 } }
  \tl_set:Nx \l__datatool_tmpb_tl
    { \text_uppercase:n { \l__datatool_tmpa_tl } }
  \tl_if_eq:NNTF
   \l__datatool_tmpa_tl  \l__datatool_tmpb_tl
  { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLifAllLowerCase}
%\begin{definition}
%\cs{DTLifAllLowerCase}\marg{string}\marg{true part}\marg{false part}
%\end{definition}
% If \meta{string} only contains lowercase characters do \meta{true
%part}, otherwise do \meta{false part}.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\DTLifAllLowerCase}[3]{%
  \tl_set:Nx \l__datatool_tmpa_tl { \text_purify:n { #1 } }
  \tl_set:Nx \l__datatool_tmpb_tl
    { \text_lowercase:n { \l__datatool_tmpa_tl } }
  \tl_if_eq:NNTF
   \l__datatool_tmpa_tl \l__datatool_tmpb_tl
  { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsubstitute}
%\begin{definition}
%\cs{DTLsubstitute}\marg{cmd}\marg{original}\marg{replacement}
%\end{definition}
% Substitutes first occurrence of \meta{original} with
% \marg{replacement} within the string given by \meta{cmd}
%\changes{3.0}{2025-03-03}{rewritten in \LaTeX3}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd{\DTLsubstitute}[3]{%
 \tl_replace_once:Nnn #1 { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLsplitstring}
%\begin{definition}
%\cs{DTLsplitstring}\marg{string}\marg{split text}\marg{before 
%cmd}\marg{after cmd}
%\end{definition}
% Splits string at \meta{split text} stores the pre split text
% in \meta{before cmd} and the post split text in \meta{after cmd}.
%\changes{1.01}{2007 Aug 17}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\DTLsplitstring}[4]{%
  \def\dtl@splitstr##1#2##2\@nil{%
    \def#3{##1}%
    \def#4{##2}%
    \ifdefempty{#4}%
    {%
      \let\@dtl@replaced=\@empty
    }%
    {%
      \def\@dtl@replaced{#2}%
      \dtl@split@str##2\@nil
    }%
  }%
  \def\dtl@split@str##1#2\@nil{\def#4{##1}}%
  \dtl@splitstr#1#2\@nil
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLxsplitstring}
%\begin{definition}
%\cs{DTLxsplitstring}\marg{string}\marg{split text}\marg{before 
%cmd}\marg{after cmd}
%\end{definition}
%As above but expands the first two arguments
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newrobustcmd*{\DTLxsplitstring}[2]{%
  \exp_args:Noo \DTLsplitstring { #1 } { #2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsubstituteall}
%\begin{definition}
%\cs{DTLsubstituteall}\marg{cmd}\marg{original}\marg{replacement}
%\end{definition}
% Substitutes all occurrences of \meta{original} with
% \marg{replacement} within the string given by \meta{cmd}
%\changes{1.01}{2007 Aug 17}{fixed bug caused when certain commands
% occur in the string}
%\changes{2.10}{2012-07-18}{added \cs{long}}
%\changes{3.0}{2025-03-03}{rewritten in \LaTeX3}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd{\DTLsubstituteall}[3]{%
  \tl_replace_all:Nnn #1 { #2 } { #3 }
} 
%    \end{macrocode}
%\end{macro}
%
%\subsection{Conditionals}
%
%\begin{macro}{\if@dtl@condition}
%    \begin{macrocode}
\newif\if@dtl@condition
%    \end{macrocode}
%\end{macro}
% \subsubsection{Testing for a prefix}
%    \begin{macrocode}
%    \end{macrocode}
%The following are used when splitting content.
%    \begin{macrocode}
\tl_new:N \l__datatool_prefix_tl 
\tl_new:N \l__datatool_suffix_tl 
%    \end{macrocode}
%Use to store the length:
%    \begin{macrocode}
\int_new:N \l__datatool_prefix_int
\int_new:N \l__datatool_suffix_int
%    \end{macrocode}
%
%Used to test for currency symbol at either end, but
%consider the symbol the `prefix' and the rest the `suffix'
%regardless of which way round they are.
%The first argument is the token list the second is the possible
%prefix or suffix.
%    \begin{macrocode}
\prg_new_conditional:Npnn \__datatool_if_starts_or_ends_with:nn #1 #2
 { T, F, TF}
 {
   \tl_clear:N \l__datatool_prefix_tl 
   \tl_clear:N \l__datatool_suffix_tl
   \tl_if_eq:nnTF { #1 } { #2 }
    {
      \tl_set:Nn \l__datatool_prefix_tl { #2 }
      \prg_return_true:
    }
    {
     \int_set:Nn \l__datatool_prefix_int { \tl_count:n { #2 } }
     \int_set:Nn \l__datatool_suffix_int { \tl_count:n { #1 } }
     \int_compare:nNnTF { \l__datatool_suffix_int } > { \l__datatool_prefix_int }
      {
        \tl_if_eq:enTF
          { \tl_range:nnn { #1 } { \c_one_int } { \l__datatool_prefix_int } }
          { #2 }
         {
           \tl_set:Nn \l__datatool_prefix_tl { #2 }
           \tl_set:Ne \l__datatool_suffix_tl
             {
               \tl_range:nnn { #1 }
                { \l__datatool_prefix_int + \c_one_int }
                { \l__datatool_suffix_int }
             }
           \prg_return_true:
         }
         {
           \int_sub:Nn \l__datatool_suffix_int { \l__datatool_prefix_int }
           \tl_if_eq:enTF
             {
               \tl_range:nnn { #1 }
                 { \l__datatool_suffix_int + \c_one_int }
                 { - \c_one_int }
             }
             { #2 }
            {
              \tl_set:Nn \l__datatool_prefix_tl { #2 }
              \tl_set:Ne \l__datatool_suffix_tl
                {
                  \tl_range:nnn { #1 }
                   { \c_one_int }
                   { \l__datatool_suffix_int }
                }
              \prg_return_true:
            }
            {
              \prg_return_false:
            }
         }
      }
      {
        \prg_return_false:
      }
    }
 }
\cs_generate_variant:Nn \__datatool_if_starts_or_ends_with:nnTF
 { VvTF , VeTF, VnTF }
%    \end{macrocode}
%
% The following tests if the token list in the first argument starts with the
% tokens in the second argument (without taking the category code
% into account). If true, the prefix token list will
% contain the tokens in the second argument (the prefix) and the
% suffix token list will contain the remaining tokens. If false the
% prefix token list will be empty and the suffix will contain all
% the tokens in the first argument.
%    \begin{macrocode}
\cs_new:Nn \__datatool_if_starts_with:NnTF 
 {
   \exp_args:NV \__datatool_if_starts_with:nnTF #1 { #2 } { #3 } { #4 }
 } 
\cs_new:Nn \__datatool_if_starts_with:nnTF 
 {
%    \end{macrocode}
%Initialise the prefix to an empty list and the suffix to the 
%token list under examination.
%    \begin{macrocode}
   \tl_clear:N \l__datatool_prefix_tl
   \tl_set:Nn \l__datatool_suffix_tl { #1 }
   \tl_set:Nn \l__datatool_tmpa_tl { #1 \q_recursion_tail }
   \tl_set:Nn \l__datatool_tmpb_tl { #2 \q_recursion_tail }
   \__datatool_if_starts_with:
   \q_recursion_stop
   \__datatool_result:nn { #3 } { #4 }
 }
\cs_new:Nn \__datatool_if_starts_with:
{
   \exp_args:No \tl_if_head_is_space:nTF { \l__datatool_tmpa_tl }
   {
     \tl_set:Nn \l__datatool_tmpc_tl { ~ }
     \tl_set:Nx \l__datatool_tmpa_tl
       { \tl_head:N \l__datatool_tmpa_tl \tl_tail:N \l__datatool_tmpa_tl }
   }
   {
     \tl_set:Nx \l__datatool_tmpc_tl
       { \tl_head:N \l__datatool_tmpa_tl }
     \tl_set:Nx \l__datatool_tmpa_tl
       { \tl_tail:N \l__datatool_tmpa_tl }
   }
   \exp_args:No \tl_if_head_is_space:nTF { \l__datatool_tmpb_tl }
   {
     \tl_set:Nn \l__datatool_tmpd_tl { ~ }
     \tl_set:Nx \l__datatool_tmpb_tl
       { \tl_head:N \l__datatool_tmpb_tl \tl_tail:N \l__datatool_tmpb_tl }
   }
   {
     \tl_set:Nx \l__datatool_tmpd_tl
       { \tl_head:N \l__datatool_tmpb_tl }
     \tl_set:Nx \l__datatool_tmpb_tl
       { \tl_tail:N \l__datatool_tmpb_tl }
   }
   \cs_set:Nn \__datatool_next: { }
   \if_meaning:w \q_recursion_tail \l__datatool_tmpd_tl
     \cs_set_eq:NN \__datatool_next: \__datatool_starts_with_true:
   \else
     \if_meaning:w \q_recursion_tail \l__datatool_tmpc_tl
       \cs_set_eq:NN \__datatool_next: \__datatool_starts_with_false:
     \else
       \str_if_eq:NNTF \l__datatool_tmpc_tl \l__datatool_tmpd_tl
        {
         \tl_put_right:No \l__datatool_prefix_tl { \l__datatool_tmpd_tl }
        }
        {
         \cs_set_eq:NN \__datatool_next: \__datatool_starts_with_false:
        }
     \fi
   \fi
   \__datatool_next:
   \__datatool_if_starts_with:
}
\cs_new:Nn \__datatool_starts_with_false:
 {
   \cs_set:Nn \__datatool_result:nn { ##2 }
   \tl_clear:N \l__datatool_prefix_tl
   \use_none_delimit_by_q_recursion_stop:w 
 }
\cs_new:Nn \__datatool_starts_with_true:
 {
   \cs_set:Nn \__datatool_result:nn { ##1 }
   \if_meaning:w \q_recursion_tail \l__datatool_tmpc_tl
     \tl_clear:N \l__datatool_suffix_tl
   \else
     \tl_set_eq:NN \l__datatool_suffix_tl \l__datatool_tmpc_tl
     \exp_after:wN \__datatool_suffix_tail:w \l__datatool_tmpa_tl
   \fi
   \use_none_delimit_by_q_recursion_stop:w 
 }
\cs_new:Npn \__datatool_suffix_tail:w #1\q_recursion_tail
 {
   \tl_put_right:Nn \l__datatool_suffix_tl { #1 }
 }
\cs_new:Npn \__datatool_if_starts_with:NnT #1#2#3
 {
   \__datatool_if_starts_with:NnTF #1 { #2 } { #3 } { }
 }
%    \end{macrocode}
%
%Retain original internal command for checking if an argument is
%numerical but updated to use new parsing.
%\begin{macro}{\@dtl@checknumerical}
%\begin{definition}
%\cs{@dtl@checknumerical}\marg{arg}
%\end{definition}
% Checks if \meta{arg} is numerical
% (includes decimal numbers, but not scientific notation.)
% Sets \cs{@dtl@datatype}.
%    \begin{macrocode}
\newcommand{\@dtl@checknumerical}[1]{%
 \tl_if_single_token:nTF { #1 }
 { \exp_args:No \__datatool_parse_datum:n { #1 } }
 { \__datatool_parse_datum:n { #1 }}
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifnumerical}
%\begin{definition}
%\cs{DTLifnumerical}\marg{arg}\marg{true part}\marg{false part}
%\end{definition}
% Tests the first argument, if
% it's numerical do second argument, otherwise do third argument.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd{\DTLifnumerical}[3]{%
 \@dtl@checknumerical { #1 }
 \datatool_if_numeric_datum_type:nTF { \@dtl@datatype }
   { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLiftemporal}
%\begin{definition}
%\cs{DTLiftemporal}\marg{arg}\marg{true part}\marg{false part}
%\end{definition}
% Tests the first argument, if
% it's temporal do second argument, otherwise do third argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLiftemporal { m m m }
{
 \tl_if_single_token:nTF { #1 }
 { \exp_args:No \__datatool_parse_datum:n { #1 } }
 { \__datatool_parse_datum:n { #1 }}
 \datatool_if_temporal_datum_type:nTF { \@dtl@datatype }
   { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testbothnumerical}
%\begin{definition}
%\cs{dtl@testbothnumerical}\marg{arg1}\marg{arg2}
%\end{definition}
% Tests if both arguments are numerical. This sets
% the conditional \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testbothnumerical}[2]{%
  \DTLifnumerical { #1 }
  { 
    \DTLifnumerical { #2 } { \@dtl@conditiontrue } { \@dtl@conditionfalse }
  }
  { \@dtl@conditionfalse }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifreal}
%\begin{definition}
%\cs{DTLifreal}\marg{arg}\marg{true part}\marg{false part}
%\end{definition}
% Tests the first argument, if
% it's a real number (not an integer or currency) do second argument, 
% otherwise do third argument.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd{\DTLifreal}[3]{%
 \tl_if_single_token:nTF { #1 }
 { \exp_args:No \__datatool_parse_datum:n { #1 } }
 { \__datatool_parse_datum:n { #1 }}
 \int_compare:nNnTF { \@dtl@datatype } = { \c_datatool_decimal_int }
  { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifint}
%\begin{definition}
%\cs{DTLifint}\marg{arg}\marg{true part}\marg{false part}
%\end{definition}
% Tests the first argument, if
% it's an integer do second argument, 
% otherwise do third argument.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd{\DTLifint}[3]{%
 \tl_if_single_token:nTF { #1 }
 { \exp_args:No \__datatool_parse_datum:n { #1 } }
 { \__datatool_parse_datum:n { #1 }}
 \int_compare:nNnTF { \@dtl@datatype } = { \c_datatool_integer_int }
  { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifdatetime}
%\begin{definition}
%\cs{DTLifdatetime}\marg{arg}\marg{true part}\marg{false part}
%\end{definition}
% Tests the first argument, if
% it's a timestamp (date and time) do second argument, 
% otherwise do third argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLiftimestamp { m m m }
{
 \tl_if_single_token:nTF { #1 }
 { \exp_args:No \__datatool_parse_datum:n { #1 } }
 { \__datatool_parse_datum:n { #1 }}
 \int_compare:nNnTF { \@dtl@datatype } = { \c_datatool_datetime_int }
  { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifdate}
%\begin{definition}
%\cs{DTLifdate}\marg{arg}\marg{true part}\marg{false part}
%\end{definition}
% Tests the first argument, if
% it's a date (no time) do second argument, 
% otherwise do third argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLifdate { m m m }
{
 \tl_if_single_token:nTF { #1 }
 { \exp_args:No \__datatool_parse_datum:n { #1 } }
 { \__datatool_parse_datum:n { #1 }}
 \int_compare:nNnTF { \@dtl@datatype } = { \c_datatool_date_int }
  { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLiftime}
%\begin{definition}
%\cs{DTLiftime}\marg{arg}\marg{true part}\marg{false part}
%\end{definition}
% Tests the first argument, if
% it's a time (no date) do second argument, 
% otherwise do third argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLiftime { m m m }
{
 \tl_if_single_token:nTF { #1 }
  { \exp_args:No \__datatool_parse_datum:n { #1 } }
  { \__datatool_parse_datum:n { #1 }}
 \int_compare:nNnTF { \@dtl@datatype } = { \c_datatool_time_int }
  { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifstring}
%\begin{definition}
%\cs{DTLifstring}\marg{arg}\marg{true part}\marg{false part}
%\end{definition}
% Tests the first argument, if
% it's a string do second argument, 
% otherwise do third argument.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd{\DTLifstring}[3]{%
 \tl_if_single_token:nTF { #1 }
 { \exp_args:No \__datatool_parse_datum:n { #1 } }
 { \__datatool_parse_datum:n { #1 }}
 \int_compare:nNnTF { \@dtl@datatype } = { \c_datatool_string_int }
  { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifcurrency}
%\begin{definition}
%\cs{DTLifcurrency}\marg{arg}\marg{true part}\marg{false part}
%\end{definition}
% Tests the first argument, if it's currency do second argument, 
% otherwise do third argument.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd{\DTLifcurrency}[3]{%
 \tl_if_single_token:nTF { #1 }
 { \exp_args:No \__datatool_parse_datum:n { #1 } }
 { \__datatool_parse_datum:n { #1 }}
 \int_compare:nNnTF { \@dtl@datatype } = { \c_datatool_currency_int }
  { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifcurrencyunit}
%\begin{definition}
%\cs{DTLifcurrencyunit}\marg{arg}\marg{symbol}\marg{true 
% part}\marg{false part}
%\end{definition}
% This tests if \meta{arg} is currency, and uses the currency unit 
% \meta{symbol}. If true do third argument, otherwise
% do fourth argument.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\DTLifcurrencyunit}[4]{%
 \tl_if_single_token:nTF { #1 }
 { \exp_args:No \__datatool_parse_datum:n { #1 } }
 { \__datatool_parse_datum:n { #1 }}
 \ifnum\@dtl@datatype=\c_datatool_currency_int 
   \tl_if_eq:NnTF \l__datatool_datum_currency_tl { #2 }
   { #3 }
   { 
     \tl_if_eq:NnTF \l__datatool_datum_currency_tl { \@dtl@currency }
     {  
       \tl_if_eq:NnTF \@dtl@currency { #2 }
       { #3 }
       { #4 }
     }
     { #4 }
   }
 \else 
  #4
 \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifcasedatatype}
%\begin{definition}
%\cs{DTLifcasedatatype}\marg{arg}\marg{string case}\marg{int 
%case}\marg{real case}\marg{currency case}
%\end{definition}
% If \meta{arg} is a string, do \meta{string case}, if \meta{arg} 
% is an integer do \meta{int case}, if \meta{arg} is a real number,
% do \meta{real case}, if \meta{arg} is currency, do \meta{currency
%case}. Does nothing with other cases (unknown, or any
%data types introduced in new versions, such as date/time).
%
%Deprecated as it doesn't allow for new types.
%Note that it's more efficient to convert to a datum variable first
%and use \cs{int\_case:nn} on the datum type (which can be obtained
%with \cs{DTLdatumtype}).
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd{\DTLifcasedatatype}[5]{%
  \tl_if_single_token:nTF { #1 }
  { \exp_args:No \__datatool_parse_datum:n { #1 } }
  { \__datatool_parse_datum:n { #1 }}
  \ifcase\@dtl@datatype
   #2% string
  \or
   #3% integer
  \or
   #4% number
  \or
   #5% currency
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\subsubsection{Locale Numerical Comparisons}
%
% Parse two numerical values for comparison. Any non-numeric value
% will be treated as zero.
%    \begin{macrocode}
\cs_new:Nn \__datatool_parse_numbers_ii:nnNN
{
  \tl_if_single_token:nTF { #1 }
  { \exp_args:No \__datatool_parse_datum:n { #1 } }
  { \__datatool_parse_datum:n { #1 }}
  \ifnum\@dtl@datatype > \c_datatool_string_int
    \tl_set_eq:NN #3 \l__datatool_datum_value_tl
  \else
    \tl_set:Nn #3 { 0 }
  \fi
  \int_set_eq:NN \l__datatool_tmp_datatype_int \@dtl@datatype
  \tl_if_single_token:nTF { #2 }
  { \exp_args:No \__datatool_parse_datum:n { #2 } }
  { \__datatool_parse_datum:n { #2 }}
  \ifnum\@dtl@datatype > \c_datatool_string_int
    \tl_set_eq:NN #4 \l__datatool_datum_value_tl
  \else
    \tl_set:Nn #4 { 0 }
  \fi
%    \end{macrocode}
% Ensure that the dominant data type can be picked up afterwards.
%    \begin{macrocode}
  \int_compare:nNnT
   { \l__datatool_tmp_datatype_int } > { \@dtl@datatype }
  {
    \int_set_eq:NN \@dtl@datatype \l__datatool_tmp_datatype_int
  }
}
%    \end{macrocode}
%
% Parse three numerical values.
%    \begin{macrocode}
\cs_new:Nn \__datatool_parse_numbers_iii:nnnNNN
{
  \__datatool_parse_numbers_ii:nnNN { #1 } { #2 } #4 #5
  \tl_if_single_token:nTF { #3 }
  { \exp_args:No \__datatool_parse_datum:n { #3 } }
  { \__datatool_parse_datum:n { #3 }}
  \ifnum\@dtl@datatype > \c_datatool_string_int
    \tl_set_eq:NN #6 \l__datatool_datum_value_tl
  \else
    \tl_set:Nn #6 { 0 }
  \fi
}
%    \end{macrocode}
%
%\begin{macro}{\DTLifnumlt}
%\begin{definition}
%\cs{DTLifnumlt}\marg{num1}\marg{num2}\marg{true part}\marg{false part}
%\end{definition}
% Determines if \marg{num1} $<$ \marg{num2}. The numeric values need
% to be obtained to ensure that they work with \cs{dtlifnumlt}.
%\changes{2.10}{2012-07-18}{changed \cs{FPiflt} to \cs{dtlifnumlt}}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\DTLifnumlt}[4]{%
  \__datatool_parse_numbers_ii:nnNN { #1 } { #2 } \@dtl@numi \@dtl@numii
  \dtlifnumlt{\@dtl@numi}{\@dtl@numii} { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifnumgt}
%\begin{definition}
%\cs{DTLifnumgt}\marg{num1}\marg{num2}\marg{true part}\marg{false part}
%\end{definition}
% Determines if \marg{num1} $>$ \marg{num2}. The numeric values need
% to be obtained to ensure that they work with \cs{dtlifnumgt}.
%\changes{2.10}{2012-07-18}{changed \cs{FPifgt} to \cs{dtlifnumgt}}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\DTLifnumgt}[4]{%
  \__datatool_parse_numbers_ii:nnNN { #1 } { #2 } \@dtl@numi \@dtl@numii
  \dtlifnumgt{\@dtl@numi}{\@dtl@numii} { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifnumeq}
%\begin{definition}
%\cs{DTLifnumeq}\marg{num1}\marg{num2}\marg{true part}\marg{false part}
%\end{definition}
% Determines if \marg{num1} = \marg{num2}. The numeric values need
% to be obtained to ensure that they work with \cs{dtlifnumeq}.
%\changes{2.10}{2012-07-18}{changed \cs{FPifeq} to \cs{dtlifnumeq}}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\DTLifnumeq}[4]{%
  \__datatool_parse_numbers_ii:nnNN { #1 } { #2 } \@dtl@numi \@dtl@numii
  \dtlifnumeq{\@dtl@numi}{\@dtl@numii} { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifnumclosedbetween}
%\begin{definition}
%\cs{DTLifnumclosedbetween}\marg{num}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% Determines if \meta{min} $\leq$ \meta{num} $\leq$ \meta{max}.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\DTLifnumclosedbetween}[5]{%
 \__datatool_parse_numbers_iii:nnnNNN { #1 } { #2 } { #3 }
  \@dtl@numi \@dtl@numii \@dtl@numiii
 \DTLifFPclosedbetween{\@dtl@numi}{\@dtl@numii}{\@dtl@numiii}{#4}{#5}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifnumopenbetween}
%\begin{definition}
%\cs{DTLifnumopenbetween}\marg{num}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% Determines if \meta{min} $<$ \meta{num} $<$ \meta{max}.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\DTLifnumopenbetween}[5]{%
 \__datatool_parse_numbers_iii:nnnNNN { #1 } { #2 } { #3 }
  \@dtl@numi \@dtl@numii \@dtl@numiii
 \DTLifFPopenbetween{\@dtl@numi}{\@dtl@numii}{\@dtl@numiii}{#4}{#5}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLnumcompare}
%\begin{definition}
%\cs{DTLnumcompare}\meta{int var}\marg{num1}\marg{num2}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLnumcompare { m m m }
{
  \__datatool_parse_numbers_ii:nnNN { #2 } { #3 } \@dtl@numi \@dtl@numii
  \int_compare:nNnTF 
    { \@dtl@datatype } < { \c_datatool_decimal_int }
  {
    \int_compare:nNnTF { \@dtl@numi } < { \@dtl@numii }
     { \int_set:Nn #1 { -1 } }
     {
       \int_compare:nNnTF { \@dtl@numi } > { \@dtl@numii }
       { \int_set_eq:NN #1 \c_one_int }
       { \int_zero:N #1 }
     }
  }
  {
    \fp_compare:nNnTF { \@dtl@numi } < { \@dtl@numii }
     { \int_set:Nn #1 { -1 } }
     {
       \fp_compare:nNnTF { \@dtl@numi } > { \@dtl@numii }
       { \int_set_eq:NN #1 \c_one_int }
       { \int_zero:N #1 }
     }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\subsubsection{String Comparisons}
%
%\label{src:dtlcompare}
%\begin{macro}{\dtlcompare}
%\begin{definition}
%\cs{dtlcompare}\marg{count}\marg{string1}\marg{string2}
%\end{definition}
% Compares \meta{string1} and \meta{string2}, and stores the
% result in the count register \meta{count}. The result may be
% one of:
%\par\vskip\baselineskip\noindent
%\begin{tabular}{rl}
%-1 & if \meta{string1} is considered to be less than
%\meta{string2}\\
%0 & if \meta{string1} is considered to be the same as
%\meta{string2}\\
%1 & if \meta{string1} is considered to be greater than
%\meta{string2}
%\end{tabular}
%\par\vskip\baselineskip\noindent
%Note that for the purposes of string comparisons, commands within
%\meta{string1} and \meta{string2} are ignored, except for
%\cs{space} and "~", which are both treated as a space (character
%code 32.) The following
%examples assume that the count register \cs{mycount} has been
%defined as follows:
%\begin{verbatim}
%\newcount\mycount
%\end{verbatim}
%\newcount\mycount\par\noindent
%\textbf{Examples:}
%\begin{enumerate}
%\item
%\begin{verbatim}
%\dtlcompare{\mycount}{Z\"oe}{Zoe}\number\mycount
%\end{verbatim}
%produces:
%\dtlcompare{\mycount}{Z\"oe}{Zoe}\number\mycount, since the accent
%command is ignored.
%
%\item
%\begin{verbatim}
%\dtlcompare{\mycount}{foo}{Foo}\number\mycount
%\end{verbatim}
%produces:
%\dtlcompare{\mycount}{foo}{Foo}\number\mycount, since the comparison
%is case sensitive, however, note the following example:
%\item
%\begin{verbatim}
%\dtlcompare{\mycount}{foo}{\uppercase{f}oo}\number\mycount
%\end{verbatim}
%which produces:
%\dtlcompare{\mycount}{foo}{\uppercase{f}oo}\number\mycount, since
%the \cs{uppercase} command is ignored.
%
%\item A control sequence is treated as having the character code
%value of 0. Pre version 2.32, \cs{dtlcompare} was advertised here
%as skipping control sequences when actually it was treating a
%control sequence as character 0. To avoid breaking
%backward-compatibility where a control sequence is expected to have
%this behaviour, we now have a switch to determine whether to treat
%control sequences as 0 or to skip them.
%
%So you can now ``trick'' \cs{dtlcompare} using a command which doesn't
%output any text if switch this conditional on. Suppose you have defined the following command:
%\begin{verbatim}
%\newcommand*{\noopsort}[1]{}
%\end{verbatim}
%\providecommand*{\noopsort}[1]{}
%then "\noopsort{a}foo" produces the text: \noopsort{a}foo, however
%the following
%\begin{verbatim}
%\dtlcompare{\mycount}{\noopsort{a}foo}{bar}\number\mycount
%\end{verbatim}
%produces:
%\dtlcompare{\mycount}{\noopsort{a}foo}{bar}\number\mycount,
%since the command \cs{noopsort} is disregarded when the comparison
%is made, so \cs{dtlcompare} just compares "{a}foo" with "bar", and
%since "a" is less than "b", the first string is considered to be less
%than the second string.
%
%\item Note that this also means that:
%\begin{verbatim}
%\def\mystr{abc}%
%\dtlcompare{\mycount}{\mystr}{abc}\number\mycount
%\end{verbatim}
%produces:
%\def\mystr{abc}\relax
%\dtlcompare{\mycount}{\mystr}{abc}\number\mycount, since the command
%\cs{mystr} is disregarded, which means that \cs{dtlcompare} is
%comparing an empty string with the string "abc".
%
%\item Spaces count in the comparison:
%\begin{verbatim}
%\dtlcompare{\mycount}{ab cd}{abcd}\number\mycount
%\end{verbatim}
%produces:
%\dtlcompare{\mycount}{ab cd}{abcd}\number\mycount, 
%but sequential spaces are treated as a single space: 
%\begin{verbatim}
%\dtlcompare{\mycount}{ab  cd}{ab cd}\number\mycount
%\end{verbatim}
%produces:
%\dtlcompare{\mycount}{ab  cd}{ab cd}\number\mycount.
%
%\item As usual, spaces following command names are ignored, so
%\begin{verbatim}
%\dtlcompare{\mycount}{ab\relax cd}{ab cd}\number\mycount
%\end{verbatim}
%produces:
%\dtlcompare{\mycount}{ab\relax cd}{ab cd}\number\mycount.
%
%\item "~" and \cs{space} are considered to be the same as a
% space:
%\begin{verbatim}
%\dtlcompare{\mycount}{ab cd}{ab~cd}\number\mycount
%\end{verbatim}
%produces:
%\dtlcompare{\mycount}{ab cd}{ab~cd}\number\mycount.
%\end{enumerate}
%\changes{1.01}{2007 Aug 17}{replaces \cs{compare} (no
% longer using compare.tex)}
%
%To ensure backward-compatibility, this still needs to strip
%commands so that the \cs{noop} example can continue to work, but
%it's better to prepare the sort values first for sorting lists.
%    \begin{macrocode}
\newcommand*{\dtlcompare}[3]{%
  \__datatool_get_compare_sort:Nn \l__datatool_tmpa_str { #2 }
  \__datatool_get_compare_sort:Nn \l__datatool_tmpb_str { #3 }
  \__datatool_strcmp:NNN #1 \l__datatool_tmpa_str \l__datatool_tmpb_str 
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlicompare}
%\begin{definition}
%\cs{dtlicompare}\marg{count}\marg{string1}\marg{string2}
%\end{definition}
% As \cs{dtlcompare} but ignores case.
%\changes{1.01}{2007 Aug 17}{new}
%    \begin{macrocode}
\newcommand*{\dtlicompare}[3]{%
  \__datatool_get_icompare_sort:Nn \l__datatool_tmpa_str { #2 }
  \__datatool_get_icompare_sort:Nn \l__datatool_tmpb_str { #3 }
  \__datatool_strcmp:NNN #1 \l__datatool_tmpa_str \l__datatool_tmpb_str 
}
%    \end{macrocode}
%\end{macro}
%
%Compare two strings provided as token lists and store the result in
%the count register provided in the first argument.
%    \begin{macrocode}
\cs_new:Nn \__datatool_strcmp:NNN
{
  \exp_args:NNoo \__datatool_strcmp:Nnn #1 { #2 } { #3 }
}
%    \end{macrocode}
%
%If \cs{pdfstrcmp} or \cs{strcmp} is defined, use that for string comparisons
%otherwise use \LaTeX3 string comparison commands.
%    \begin{macrocode}
\cs_if_exist:NTF \strcmp
{
  \cs_new:Nn \__datatool_strcmp:Nnn
   {
     \int_set:Nn #1 { \strcmp { #2 } { #3 } }
   }
  \cs_new:Nn  \datatool_strcmp:nn
   {
     \strcmp { #1 } { #2 }
   }
}
{
  \cs_if_exist:NTF \pdfstrcmp
   {
     \cs_new:Nn \__datatool_strcmp:Nnn
      {
        \int_set:Nn #1 { \pdfstrcmp{ #2 } { #3 } }
      }
     \cs_new:Nn  \datatool_strcmp:nn
      {
        \pdfstrcmp { #1 } { #2 }
      }
   }
   {
%    \end{macrocode}
%Most likely LuaTeX.
%    \begin{macrocode}
     \cs_new:Nn \__datatool_strcmp:Nnn
      {
        \str_if_eq:nnTF { #2 } { #3 }
        { \int_zero:N #1 }
        { 
          \str_compare:nNnTF { #2 } < { #3 }
          { \int_set:Nn #1 { -1 } }
          { \int_set_eq:NN #1 \c_one_int }
        }
      }
     \cs_new:Nn  \datatool_strcmp:nn
      {
        \str_if_eq:nnTF { #1 } { #2 }
        { \c_zero_int }
        { 
          \str_compare:nNnTF { #1 } < { #2 }
           { -1 }
           { \c_one_int }
        }
      }
  }
}
%    \end{macrocode}
%
%Compare two numeric values and store the result in
%the count register provided in the first argument.
%    \begin{macrocode}
\cs_new:Nn \__datatool_numcmp:Nnn
{
 \dtlifnumlt { #2 } { #3 }
  { #1=-1\relax }
  {
    \dtlifnumgt { #2 } { #3} { #1=1\relax } { #1=0\relax }
  }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\tl_new:N \l__datatool_cmpa_tl
\tl_new:N \l__datatool_cmpb_tl
%    \end{macrocode}
% The token list mapping function skips spaces, so need to replace
% spaces with a token that represents a space.
%    \begin{macrocode}
\tl_const:Nn \c__datatool_space_tl { ~ }
%    \end{macrocode}
%Case-sensitive:
%    \begin{macrocode}
\cs_new:Nn \__datatool_get_compare_sort:Nn 
 {
   \bool_if:NTF \l__datatool_compare_expand_cs_bool
   {
     \tl_set:Nx \l__datatool_cmpb_tl { \text_purify:n { #2 } }
   }
   {
     \tl_set:Nn \l__datatool_cmpb_tl { #2 }
   }
   \tl_replace_all:Nnn \l__datatool_cmpb_tl { ~ } { \c__datatool_space_tl }
   \tl_clear:N \l__datatool_cmpa_tl
   \exp_args:No \tl_map_function:nN
     { \l__datatool_cmpb_tl }
     \__datatool_get_compare_sort_fn:n
   \bool_if:NTF \l__datatool_compare_expand_cs_bool
   {
     \str_set:Nx #1 { \l__datatool_cmpa_tl }
   }
   {
     \str_set:Nx #1 { \text_purify:n { \l__datatool_cmpa_tl } }
   }
 }
%    \end{macrocode}
%Case-insensitive:
%    \begin{macrocode}
\cs_new:Nn \__datatool_get_icompare_sort:Nn 
 {
   \bool_if:NTF \l__datatool_compare_expand_cs_bool
   {
     \tl_set:Nx \l__datatool_cmpb_tl { \text_purify:n { #2 } }
   }
   {
     \tl_set:Nn \l__datatool_cmpb_tl { #2 }
   }
   \tl_replace_all:Nnn \l__datatool_cmpb_tl { ~ } { \c__datatool_space_tl }
   \tl_clear:N \l__datatool_cmpa_tl
   \exp_args:No \tl_map_function:nN
     { \l__datatool_cmpb_tl }
     \__datatool_get_compare_sort_fn:n
   \bool_if:NTF \l__datatool_compare_expand_cs_bool
   {
     \str_set:Nx #1 { \text_lowercase:n { \l__datatool_cmpa_tl } }
   }
   {
     \str_set:Nx #1
      { \text_purify:n { \text_lowercase:n { \l__datatool_cmpa_tl } } }
   }
 }
\cs_new:Nn \__datatool_get_compare_sort_fn:n 
 {
   \tl_if_head_eq_catcode:nNTF { #1 } \relax 
   {
     \tl_if_eq:nnTF { \c__datatool_space_tl } { #1 }
      {
        \tl_put_right:Nn \l__datatool_cmpa_tl { ~ } 
      }
      {
        \ifdtlcompareskipcs
        \else
          \tl_put_right:Nn \l__datatool_cmpa_tl { ^^J } 
        \fi
      }
   }
   { \tl_put_right:Nn \l__datatool_cmpa_tl { #1 } }
 }
%    \end{macrocode}
%
%\begin{macro}{\dtlwordindexcompare}
% Word breaks come before all other letters of the alphabet
% (since the space character comes before all visible characters).
% Note that, as from v3.0, \cs{@dtl@wordbreak} is no longer used,
% except to provide a non-empty fourth argument for
% \cs{@dtldictcompare}.
%    \begin{macrocode}
\newcommand*{\dtlwordindexcompare}[3]{%
  \@dtldictcompare{#1}{#2}{#3}{\@dtl@wordbreak}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlletterindexcompare}
% Word breaks are ignored.
%    \begin{macrocode}
\newcommand*{\dtlletterindexcompare}[3]{%
  \@dtldictcompare{#1}{#2}{#3}{}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtldictcompare}
% Word or letter compare. Fourth argument should be
% empty for letter compare.
%    \begin{macrocode}
\newcommand*{\@dtldictcompare}[4]{%
  \group_begin:
  \dtl@SortWordCommands@hook
  \exp_args:Nxx \__datatool_set_tmp_dictcomp:nn { #2 } { #3 }
  \tl_if_empty:nT { #4 }
  {
    \tl_replace_all:Nnn \l__datatool_dictcompa_tl { ~ } {}
    \tl_replace_all:Nnn \l__datatool_dictcompb_tl { ~ } {}
  }
  \str_set:NV \l__datatool_tmpa_str \l__datatool_dictcompa_tl 
  \str_set:NV \l__datatool_tmpb_str \l__datatool_dictcompb_tl 
  \__datatool_strcmp:NNN #1 \l__datatool_tmpa_str \l__datatool_tmpb_str 
}
\tl_new:N \l__datatool_dictcompa_tl
\tl_new:N \l__datatool_dictcompb_tl
\cs_new:Nn \__datatool_set_tmp_dictcomp:nn
{
  \group_end:
  \DTLsortwordhandler { #1 } { \l__datatool_dictcompa_tl }
  \DTLsortwordhandler { #2 } { \l__datatool_dictcompb_tl }
}
%    \end{macrocode}
%\end{macro}
%
% Need to indicate type of inversion.
%\begin{macro}{\datatoolpersoncomma}
%    \begin{macrocode}
\newcommand*{\datatoolpersoncomma}{,\space}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datatoolplacecomma}
%    \begin{macrocode}
\newcommand*{\datatoolplacecomma}{,\space}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datatoolsubjectcomma}
%    \begin{macrocode}
\newcommand*{\datatoolsubjectcomma}{,\space}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datatoolparenstart}
%    \begin{macrocode}
\newcommand*{\datatoolparenstart}{\space}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datatoolparen}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\datatoolparen}[1]{\space (#1)}
%    \end{macrocode}
%\end{macro}
%
%
%The following commands \cs{dtlicomparewords} and
%\cs{dtlcomparewords} were never documented and don't seem to do
%anything different from \cs{dtlicompare} and \cs{dtlcompare}.
%They have existed since at least v2.25.
%\begin{macro}{\dtlicomparewords}
%\begin{definition}
%\cs{dtlicomparewords}\marg{count}\marg{word A}\marg{word B}
%\end{definition}
%This does a case insensitive comparison.
%    \begin{macrocode}
\newcommand{\dtlicomparewords}[3]{%
  \dtlicompare{#1}{#2}{#3}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlcomparewords}
%\begin{definition}
%\cs{dtlcomparewords}\marg{count}\marg{word A}\marg{word B}
%\end{definition}
%This does a case sensitive comparison.
%    \begin{macrocode}
\newcommand{\dtlcomparewords}[3]{%
  \dtlcompare{#1}{#2}{#3}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{macro}
%\begin{definition}
%\cs{dtlifcasechargroup}\marg{char}\marg{case letter}\marg{case
%digit}\marg{case symbol}
%\end{definition}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\dtlifcasechargroup}[4]{%
  \regex_match:nnTF { \A \d \Z} { #1 }
  { #3 }
  {
    \regex_match:nnTF { \A [[:alpha:]] \Z} { #1 }
    { #2 }
    { #3 }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlparsewords}
%\changes{2.13}{2013-01-15}{new}
%\begin{definition}
%\cs{dtlparsewords}\marg{phrase}\marg{handler cs}
%\end{definition}
% Iterates through the given phrase. Hyphens are considered word
% boundaries. Punctuation and digits before and after words are discarded.
%    \begin{macrocode}
\newcommand*{\dtlparsewords}[2]{%
  \group_begin:
   \tl_set:Nx \l__datatool_tmpa_tl { \tl_trim_spaces:n { #1 } }
   \regex_replace_case_all:nN
    {
      \l_datatool_word_head_regex
        { 
          \2 \cM\|
        }
      \l_datatool_word_hyphen_regex
        { 
          \2 \cM\|
        }
      \l_datatool_word_tail_regex
        { 
          \2
        }
      \l_datatool_symbols_regex 
        { }
    }
    \l__datatool_tmpa_tl
    \forlistloop { #2 } { \l__datatool_tmpa_tl }
  \group_end:
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifstringlt}
%\begin{definition}
%\cs{DTLifstringlt}\marg{string1}\marg{string2}\marg{true part}\marg{false part}
%\end{definition}
% String comparison (Starred version ignores case)
%\changes{1.01}{2007 Aug 17}{added starred version}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLifstringlt { s m m m m }
{
  \IfBooleanTF { #1 }
   {
     \@sDTLifstringlt { #2 } { #3 } { #4 } { #5 }
   }
   {
     \@DTLifstringlt { #2 } { #3 } { #4 } { #5 }
   }
}
%    \end{macrocode}
%\end{macro}
%The internals are used by \cs{DTLiflt} as well.
%\begin{macro}{\@DTLifstringlt}
% Unstarred version (case-sensitive).
%    \begin{macrocode}
\newcommand*{\@DTLifstringlt}[4]{
  \exp_args:NNo \__datatool_get_compare_sort:Nn
    \l__datatool_tmpa_str { #1 }
  \exp_args:NNo \__datatool_get_compare_sort:Nn
    \l__datatool_tmpb_str { #2 }
  \str_compare:eNeTF
    { \l__datatool_tmpa_str }
     <
    { \l__datatool_tmpb_str }
   { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\s@DTLifstringlt}
% Starred version (case-sensitive).
%    \begin{macrocode}
\newcommand*{\@sDTLifstringlt}[4]{
  \exp_args:NNo \__datatool_get_icompare_sort:Nn
    \l__datatool_tmpa_str { #1 }
  \exp_args:NNo \__datatool_get_icompare_sort:Nn
    \l__datatool_tmpb_str { #2 }
  \str_compare:eNeTF
    { \l__datatool_tmpa_str }
     <
    { \l__datatool_tmpb_str }
   { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
% 
%\begin{macro}{\DTLiflt}
%\begin{definition}
%\cs{DTLiflt}\marg{arg1}\marg{arg2}\marg{true part}\marg{false part}
%\end{definition}
% Does \cs{DTLifnumlt} if both \meta{arg1} and \meta{arg2} are
% numerical, otherwise do \cs{DTLifstringlt} (unstarred version)
% or \cs{DTLifstringlt*} (starred version). 
%\changes{1.01}{2007 Aug 17}{added starred version}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLiflt { s m m m m }
{
  \IfBooleanTF { #1 }
   {
     \@sDTLiflt { #2 } { #3 } { #4 } { #5 }
   }
   {
     \@DTLiflt { #2 } { #3 } { #4 } { #5 }
   }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@DTLiflt}
% Unstarred version (also used in \cs{DTLislt}).
%    \begin{macrocode}
\newcommand*{\@DTLiflt}[4]{
  \dtl@testbothnumerical {#1} {#2}
  \if@dtl@condition
   \DTLifnumlt {#1} {#2} {#3} {#4}
  \else
   \@DTLifstringlt {#1} {#2} {#3} {#4}
  \fi
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@sDTLiflt}
% Starred version (also used in \cs{DTLisilt}).
%    \begin{macrocode}
\newcommand*{\@sDTLiflt}[4]{
  \dtl@testbothnumerical {#1} {#2}
  \if@dtl@condition
   \DTLifnumlt {#1} {#2} {#3} {#4}
  \else
   \@sDTLifstringlt {#1} {#2} {#3} {#4}
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifstringgt}
%\begin{definition}
%\cs{DTLifstringgt}\marg{string1}\marg{string2}\marg{true part}\marg{false part}
%\end{definition}
% String comparison (starred version ignores case)
%\changes{1.01}{2007 Aug 17}{added starred version}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLifstringgt { s m m m m }
{
  \IfBooleanTF { #1 }
   {
     \@sDTLifstringgt { #2 } { #3 } { #4 } { #5 }
   }
   {
     \@DTLifstringgt { #2 } { #3 } { #4 } { #5 }
   }
}
%    \end{macrocode}
%\end{macro}
%The internals are used by \cs{DTLifgt} as well.
%\begin{macro}{\@DTLifstringgt}
% Unstarred version (case-sensitive).
%    \begin{macrocode}
\newcommand*{\@DTLifstringgt}[4]{
  \exp_args:NNo \__datatool_get_compare_sort:Nn
    \l__datatool_tmpa_str { #1 }
  \exp_args:NNo \__datatool_get_compare_sort:Nn
    \l__datatool_tmpb_str { #2 }
  \str_compare:eNeTF
    { \l__datatool_tmpa_str }
     >
    { \l__datatool_tmpb_str }
   { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@sDTLifstringgt}
% Starred version (case-insensitive).
%    \begin{macrocode}
\newcommand*{\@sDTLifstringgt}[4]{
  \exp_args:NNo \__datatool_get_icompare_sort:Nn
    \l__datatool_tmpa_str { #1 }
  \exp_args:NNo \__datatool_get_icompare_sort:Nn
    \l__datatool_tmpb_str { #2 }
  \str_compare:eNeTF
    { \l__datatool_tmpa_str }
     >
    { \l__datatool_tmpb_str }
   { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
% 
%\begin{macro}{\DTLifgt}
%\begin{definition}
%\cs{DTLifgt}\marg{arg1}\marg{arg2}\marg{true part}\marg{false part}
%\end{definition}
% Does \cs{DTLifnumgt} if both \meta{arg1} and \meta{arg2} are
% numerical, otherwise do \cs{DTLifstringgt} or \cs{DTLifstringgt*}. 
%\changes{1.01}{2007 Aug 17}{added starred version}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLifgt { s m m m m }
{
  \IfBooleanTF { #1 }
   {
     \@sDTLifgt { #2 } { #3 } { #4 } { #5 }
   }
   {
     \@DTLifgt { #2 } { #3 } { #4 } { #5 }
   }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@DTLifgt}
% Unstarred version (also used in \cs{DTLislt}).
%    \begin{macrocode}
\newcommand*{\@DTLifgt}[4]{
  \dtl@testbothnumerical {#1} {#2}
  \if@dtl@condition
   \DTLifnumgt {#1} {#2} {#3} {#4}
  \else
   \@DTLifstringgt {#1} {#2} {#3} {#4}
  \fi
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@sDTLifgt}
% Starred version (also used in \cs{DTLisigt}).
%    \begin{macrocode}
\newcommand*{\@sDTLifgt}[4]{
  \dtl@testbothnumerical {#1} {#2}
  \if@dtl@condition
   \DTLifnumgt {#1} {#2} {#3} {#4}
  \else
   \@sDTLifstringgt {#1} {#2} {#3} {#4}
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifstringeq}
%\begin{definition}
%\cs{DTLifstringeq}\marg{string1}\marg{string2}\marg{true part}\marg{false part}
%\end{definition}
% String comparison (starred version ignores case)
%\changes{1.01}{2007 Aug 17}{added starred version}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLifstringeq { s m m m m }
{
  \IfBooleanTF { #1 }
   {
     \@sDTLifstringeq { #2 } { #3 } { #4 } { #5 }
   }
   {
     \@DTLifstringeq { #2 } { #3 } { #4 } { #5 }
   }
}
%    \end{macrocode}
%\end{macro}
%The internals are used by \cs{DTLifeq} as well.
%\begin{macro}{\@DTLifstringeq}
% Unstarred version (case-sensitive).
%    \begin{macrocode}
\newcommand*{\@DTLifstringeq}[4]{
  \exp_args:NNo \__datatool_get_compare_sort:Nn
    \l__datatool_tmpa_str { #1 }
  \exp_args:NNo \__datatool_get_compare_sort:Nn
    \l__datatool_tmpb_str { #2 }
  \str_compare:eNeTF
    { \l__datatool_tmpa_str }
     =
    { \l__datatool_tmpb_str }
   { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@sDTLifstringeq}
% Starred version (case-insensitive).
%    \begin{macrocode}
\newcommand*{\@sDTLifstringeq}[4]{
  \exp_args:NNo \__datatool_get_icompare_sort:Nn
    \l__datatool_tmpa_str { #1 }
  \exp_args:NNo \__datatool_get_icompare_sort:Nn
    \l__datatool_tmpb_str { #2 }
  \str_compare:eNeTF
    { \l__datatool_tmpa_str }
     =
    { \l__datatool_tmpb_str }
   { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
% 
%\begin{macro}{\DTLifeq}
%\begin{definition}
%\cs{DTLifeq}\marg{arg1}\marg{arg2}\marg{true part}\marg{false part}
%\end{definition}
% Does \cs{DTLifnumeq} if both \meta{arg1} and \meta{arg2} are
% numerical, otherwise do \cs{DTLifstringeq} or \cs{DTLifstringeq*}. 
%\changes{1.01}{2007 Aug 17}{added starred version}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLifeq { s m m m m }
{
  \IfBooleanTF { #1 }
   {
     \@sDTLifeq { #2 } { #3 } { #4 } { #5 }
   }
   {
     \@DTLifeq { #2 } { #3 } { #4 } { #5 }
   }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@DTLifeq}
% Unstarred version (also used in \cs{DTLiseq}).
%    \begin{macrocode}
\newcommand*{\@DTLifeq}[4]{
  \dtl@testbothnumerical {#1} {#2}
  \if@dtl@condition
   \DTLifnumeq {#1} {#2} {#3} {#4}
  \else
   \@DTLifstringeq {#1} {#2} {#3} {#4}
  \fi
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@sDTLifeq}
% Starred version (also used in \cs{DTLisieq}).
%    \begin{macrocode}
\newcommand*{\@sDTLifeq}[4]{
  \dtl@testbothnumerical {#1} {#2}
  \if@dtl@condition
   \DTLifnumeq {#1} {#2} {#3} {#4}
  \else
   \@sDTLifstringeq {#1} {#2} {#3} {#4}
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\regex_new:N \l_datatool_space_regex
\regex_set:Nn \l_datatool_space_regex
 {
    ((?:[[:space:]\~])|(?:\c{protect}?\c{(?:nobreak)?space\ ?}))
 }
%    \end{macrocode}
%\begin{macro}{\DTLifSubString}
%\begin{definition}
%\cs{DTLifSubString}\marg{string}\marg{sub string}\marg{true 
%part}\marg{false part}
%\end{definition}
% If \meta{sub string} is contained in \meta{string} does
%\meta{true part}, otherwise does \meta{false part}.
%\changes{1.01}{2007 Aug 17}{new}
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{added starred version}
%Spaces are replaced with \verb|~| to prevent leading/trailing
%spaces from being trimmed.
%    \begin{macrocode}
\newrobustcmd*{\DTLifSubString}{\@ifstar\@sDTLifSubString\@DTLifSubString}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@DTLifSubString}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\@DTLifSubString}[4]{%
  \tl_set:Nx \l__datatool_tmpa_tl { \text_purify:n { #1 } }
  \tl_set:Nx \l__datatool_tmpb_tl { \text_purify:n { #2 } }
  \regex_replace_all:NnN \l_datatool_space_regex { \~ } \l__datatool_tmpa_tl
  \regex_replace_all:NnN \l_datatool_space_regex { \~ } \l__datatool_tmpb_tl
  \exp_args:NVV \str_if_in:nnTF
   \l__datatool_tmpa_tl \l__datatool_tmpb_tl 
   { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@sDTLifSubString}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\@sDTLifSubString}[2]{%
  \@DTLifSubString{ \text_lowercase:n { #1 } }{ \text_lowercase:n { #2 } }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifStartsWith}
%\begin{definition}
%\cs{DTLifStartsWith}\marg{string}\marg{substring}\marg{true
%part}\marg{false part}
%\end{definition}
%If \meta{string} starts with \meta{substring}, this does
%\meta{true part}, otherwise it does \meta{false part}.
%\changes{1.01}{2007 Aug 17}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\DTLifStartsWith}{\@ifstar\@sDTLifStartsWith\@DTLifStartsWith}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@DTLifStartsWith}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\@DTLifStartsWith}[4]{%
  \tl_set:Nx \l__datatool_tmpa_tl { \text_purify:n { #1 } }
  \tl_set:Nx \l__datatool_tmpb_tl { \text_purify:n { #2 } }
  \regex_replace_all:NnN \l_datatool_space_regex { \~ } \l__datatool_tmpa_tl
  \regex_replace_all:NnN \l_datatool_space_regex { \~ } \l__datatool_tmpb_tl
  \exp_args:NVV \__datatool_if_starts_with:nnTF
    \l__datatool_tmpa_tl \l__datatool_tmpb_tl  
   { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@sDTLifStartsWith}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\@sDTLifStartsWith}[2]{%
  \@DTLifStartsWith{ \text_lowercase:n { #1 } }{ \text_lowercase:n { #2 } }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifEndsWith}
%\begin{definition}
%\cs{DTLifEndsWith}\marg{string}\marg{substring}\marg{true
%part}\marg{false part}
%\end{definition}
%If \meta{string} ends with \meta{substring}, this does
%\meta{true part}, otherwise it does \meta{false part}.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newrobustcmd*{\DTLifEndsWith}{\@ifstar\@sDTLifEndsWith\@DTLifEndsWith}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@DTLifEndsWith}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\@DTLifEndsWith}[4]{%
  \tl_set:Nx \l__datatool_tmpa_tl { \text_purify:n { #1 } }
  \tl_set:Nx \l__datatool_tmpb_tl { \text_purify:n { #2 } }
  \regex_replace_all:NnN \l_datatool_space_regex { \~ } \l__datatool_tmpa_tl
  \regex_replace_all:NnN \l_datatool_space_regex { \~ } \l__datatool_tmpb_tl
  \tl_reverse:N \l__datatool_tmpa_tl
  \tl_reverse:N \l__datatool_tmpb_tl
  \exp_args:NVV \__datatool_if_starts_with:nnTF
   \l__datatool_tmpa_tl \l__datatool_tmpb_tl  
   { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@sDTLifEndsWith}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\@sDTLifEndsWith}[2]{%
  \@DTLifEndsWith{ \text_lowercase:n { #1 } }{ \text_lowercase:n { #2 } }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifstringclosedbetween}
%\begin{definition}
%\cs{DTLifstringclosedbetween}\marg{string}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% String comparison (starred version ignores case)
%\changes{1.01}{2007 Aug 17}{added starred version}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLifstringclosedbetween { s m m m m m m }
{
  \IfBooleanTF { #1 }
   {
     \@sDTLifstringclosedbetween { #2 } { #3 } { #4 } { #5 } { #6 }
   }
   {
     \@DTLifstringclosedbetween { #2 } { #3 } { #4 } { #5 } { #6 }
   }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@DTLifstringclosedbetween}
% Unstarred version (case-sensitive).
%    \begin{macrocode}
\newcommand*{\@DTLifstringclosedbetween}[5]{%
  \exp_args:NNo \__datatool_get_compare_sort:Nn
    \l__datatool_tmpa_str { #1 }
  \exp_args:NNo \__datatool_get_compare_sort:Nn
    \l__datatool_tmpb_str { #2 }
  \exp_args:NNo \__datatool_get_compare_sort:Nn
    \l__datatool_tmpc_str { #3 }
  \str_compare:eNeTF
    { \l__datatool_tmpa_str }
     <
    { \l__datatool_tmpb_str }
   { #5 }
   {
    \str_compare:eNeTF
      { \l__datatool_tmpa_str }
       >
      { \l__datatool_tmpc_str }
     { #5 } { #4 }
   }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@sDTLifstringclosedbetween}
% Starred version (case-sensitive).
%    \begin{macrocode}
\newcommand*{\@sDTLifstringclosedbetween}[5]{%
  \exp_args:NNo \__datatool_get_icompare_sort:Nn
    \l__datatool_tmpa_str { #1 }
  \exp_args:NNo \__datatool_get_icompare_sort:Nn
    \l__datatool_tmpb_str { #2 }
  \exp_args:NNo \__datatool_get_icompare_sort:Nn
    \l__datatool_tmpc_str { #3 }
  \str_compare:eNeTF
    { \l__datatool_tmpa_str }
     <
    { \l__datatool_tmpb_str }
   { #5 }
   {
    \str_compare:eNeTF
      { \l__datatool_tmpa_str }
       >
      { \l__datatool_tmpc_str }
     { #5 } { #4 }
   }
}
%    \end{macrocode}
%\end{macro}
% 
%\begin{macro}{\DTLifclosedbetween}
%\begin{definition}
%\cs{DTLifclosedbetween}\marg{arg}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% Does \cs{DTLifnumclosedbetween} if \marg{arg}, \meta{min} and \meta{max} are
% numerical, otherwise do \cs{DTLifstringclosedbetween}
% or \cs{DTLifstringclosedbetween*}. 
%\changes{1.01}{2007 Aug 17}{added starred version}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLifclosedbetween { s m m m m m }
{
  \IfBooleanTF { #1 }
   {
     \@sDTLifclosedbetween { #2 } { #3 } { #4 } { #5 } { #6 }
   }
   {
     \@DTLifclosedbetween { #2 } { #3 } { #4 } { #5 } { #6 }
   }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@DTLifclosedbetween}
% Unstarred version
%    \begin{macrocode}
\newcommand*{\@DTLifclosedbetween}[5]{%
  \dtl@testbothnumerical{#2}{#3}%
  \if@dtl@condition
    \DTLifnumerical { #1 }
     {
      \DTLifnumclosedbetween {#1} {#2} {#3} {#4} {#5}
     }
     {
      \@DTLifstringclosedbetween {#1} {#2} {#3} {#4} {#5}
     }
  \else
    \@DTLifstringclosedbetween {#1} {#2} {#3} {#4} {#5}
  \fi
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@sDTLifclosedbetween}
% Starred version
%    \begin{macrocode}
\newcommand*{\@sDTLifclosedbetween}[5]{%
  \dtl@testbothnumerical{#2}{#3}%
  \if@dtl@condition
    \DTLifnumerical { #1 }
     {
      \DTLifnumclosedbetween {#1} {#2} {#3} {#4} {#5}
     }
     {
      \@sDTLifstringclosedbetween {#1} {#2} {#3} {#4} {#5}
     }
  \else
    \@sDTLifstringclosedbetween {#1} {#2} {#3} {#4} {#5}
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifstringopenbetween}
%\begin{definition}
%\cs{DTLifstringopenbetween}\marg{string}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% String comparison (starred version ignores case)
%\changes{1.01}{2007 Aug 17}{added starred version}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLifstringopenbetween { s m m m m m m }
{
  \IfBooleanTF { #1 }
   {
     \@sDTLifstringopenbetween { #2 } { #3 } { #4 } { #5 } { #6 }
   }
   {
     \@DTLifstringopenbetween { #2 } { #3 } { #4 } { #5 } { #6 }
   }
}
%    \end{macrocode}
% Unstarred version:
%    \begin{macrocode}
\newcommand*{\@DTLifstringopenbetween}[5]{%
  \exp_args:NNo \__datatool_get_compare_sort:Nn
    \l__datatool_tmpa_str { #1 }
  \exp_args:NNo \__datatool_get_compare_sort:Nn
    \l__datatool_tmpb_str { #2 }
  \exp_args:NNo \__datatool_get_compare_sort:Nn
    \l__datatool_tmpc_str { #3 }
  \str_compare:eNeTF
    { \l__datatool_tmpa_str }
     >
    { \l__datatool_tmpb_str }
   {
    \str_compare:eNeTF
      { \l__datatool_tmpa_str }
       <
      { \l__datatool_tmpc_str }
      { #4 } { #5 }
   }
   { #5 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@sDTLifstringopenbetween}
% Starred version (case-sensitive).
%    \begin{macrocode}
\newcommand*{\@sDTLifstringopenbetween}[5]{%
  \exp_args:NNo \__datatool_get_icompare_sort:Nn
    \l__datatool_tmpa_str { #1 }
  \exp_args:NNo \__datatool_get_icompare_sort:Nn
    \l__datatool_tmpb_str { #2 }
  \exp_args:NNo \__datatool_get_icompare_sort:Nn
    \l__datatool_tmpc_str { #3 }
  \str_compare:eNeTF
    { \l__datatool_tmpa_str }
     >
    { \l__datatool_tmpb_str }
   {
    \str_compare:eNeTF
      { \l__datatool_tmpa_str }
       <
      { \l__datatool_tmpc_str }
      { #4 } { #5 }
   }
   { #5 }
}
%    \end{macrocode}
%\end{macro}
% 
%\begin{macro}{\DTLifopenbetween}
%\begin{definition}
%\cs{DTLifopenbetween}\marg{arg}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% Does \cs{DTLifnumopenbetween} if \marg{arg}, \meta{min} and \meta{max} are
% numerical, otherwise do \cs{DTLifstringopenbetween}
% or \cs{DTLifstringopenbetween*}. 
%\changes{1.01}{2007 Aug 17}{added starred version}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLifopenbetween { s m m m m m }
{
  \IfBooleanTF { #1 }
   {
     \@sDTLifopenbetween { #2 } { #3 } { #4 } { #5 } { #6 }
   }
   {
     \@DTLifopenbetween { #2 } { #3 } { #4 } { #5 } { #6 }
   }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@DTLifopenbetween}
% Unstarred version
%    \begin{macrocode}
\newcommand*{\@DTLifopenbetween}[5]{
  \dtl@testbothnumerical {#2} {#3}
  \if@dtl@condition
    \DTLifnumerical { #1 }
     {
      \DTLifnumopenbetween {#1} {#2} {#3} {#4} {#5}
     }
     {
      \@DTLifstringopenbetween {#1} {#2} {#3} {#4} {#5}
     }
  \else
    \@DTLifstringopenbetween {#1} {#2} {#3} {#4} {#5}
  \fi
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@sDTLifopenbetween}
% Starred version
%    \begin{macrocode}
\newcommand*{\@sDTLifopenbetween}[5]{
  \dtl@testbothnumerical {#2} {#3}
  \if@dtl@condition
    \DTLifnumerical { #1 }
     {
      \DTLifnumopenbetween {#1} {#2} {#3} {#4} {#5}
     }
     {
      \@sDTLifstringopenbetween {#1} {#2} {#3} {#4} {#5}
     }
  \else
    \@sDTLifstringopenbetween {#1} {#2} {#3} {#4} {#5}
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\begin{macro}{\DTLifFPopenbetween}
%\begin{definition}
%\cs{DTLifFPopenbetween}\marg{num}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% Determines if \meta{min} $<$ \meta{num} $<$ \meta{max} where
% all arguments are in standard fixed point notation.
% (Command name maintained for backward compatibility.)
%    \begin{macrocode}
\let\DTLifFPopenbetween\dtlifnumopenbetween
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifFPclosedbetween}
%\begin{definition}
%\cs{DTLifFPclosedbetween}\marg{num}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% Determines if \meta{min} $\leq$ \meta{num} $\leq$ \meta{max}.
% (Command name maintained for backward compatibility.)
%    \begin{macrocode}
\let\DTLifFPclosedbetween\dtlifnumclosedbetween
%    \end{macrocode}
%\end{macro}
%
%
%\subsubsection{ifthen Conditionals}
% The following commands provide conditionals \cs{DTLis}\ldots\ 
% which can be used in \cs{ifthenelse}.
%
%\begin{macro}{\dtl@testlt}
% Command to test if first argument is less than second argument.
% If either argument is a string, a case sensitive string comparison 
% is used instead.
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testlt}[2]{%
  \@DTLiflt{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLislt}
% Provide conditional command for use in \cs{ifthenelse}
%    \begin{macrocode}
\newcommand*{\DTLislt}[2]{%
  \TE@throw\noexpand\dtl@testlt{#1}{#2}\noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testiclt}
% Command to test if first argument is less than second argument.
% If either argument is a string, a case insensitive string comparison 
% is used instead.
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testiclt}[2]{%
  \@sDTLiflt{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLisilt}
% Provide conditional command for use in \cs{ifthenelse}
%\changes{1.01}{2007 Aug 17}{new}
%    \begin{macrocode}
\newcommand*{\DTLisilt}[2]{%
  \TE@throw\noexpand\dtl@testiclt{#1}{#2}\noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testgt}
% Command to test if first argument is greater than second argument.
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testgt}[2]{%
  \@DTLifgt{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisgt}
% Provide conditional command for use in \cs{ifthenelse}
%    \begin{macrocode}
\newcommand*{\DTLisgt}[2]{%
  \TE@throw\noexpand\dtl@testgt{#1}{#2}\noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testicgt}
% Command to test if first argument is greater than second argument
% (ignores case).
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testicgt}[2]{%
  \@sDTLifgt{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisigt}
% Provide conditional command for use in \cs{ifthenelse}
%\changes{1.01}{2007 Aug 17}{new}
%    \begin{macrocode}
\newcommand*{\DTLisigt}[2]{%
  \TE@throw\noexpand\dtl@testicgt{#1}{#2}\noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testeq}
% Command to test if first argument is equal to the second argument.
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testeq}[2]{%
  \@DTLifeq{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLiseq}
% Provide conditional command for use in \cs{ifthenelse}
%    \begin{macrocode}
\newcommand*{\DTLiseq}[2]{%
  \TE@throw\noexpand\dtl@testeq{#1}{#2}\noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testiceq}
% Command to test if first number is equal to the second number
% (ignores case).
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testiceq}[2]{%
  \@sDTLifeq{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisieq}
% Provide conditional command for use in \cs{ifthenelse}
%\changes{1.01}{2007 Aug 17}{new}
%    \begin{macrocode}
\newcommand*{\DTLisieq}[2]{%
  \TE@throw\noexpand\dtl@testiceq{#1}{#2}\noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testifsubstring}
% Command to test if second argument is a substring of the first argument.
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testifsubstring}[2]{%
  \@DTLifSubString{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLisSubString}
% Tests if second argument is contained in first argument.
%\changes{1.01}{2007 Aug 17}{new}
%    \begin{macrocode}
\newcommand*{\DTLisSubString}[2]{%
  \TE@throw\noexpand\dtl@testifsubstring{#1}{#2}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testifisubstring}
% Command to test if second argument is a substring of the first argument.
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testifisubstring}[2]{%
  \@sDTLifSubString{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLisiSubString}
% Tests if second argument is contained in first argument (no case).
%\changes{1.01}{2007 Aug 17}{new}
%    \begin{macrocode}
\newcommand*{\DTLisiSubString}[2]{%
  \TE@throw\noexpand\dtl@testifisubstring{#1}{#2}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@teststartswith}
% Command to test if second argument is a prefix of the first argument.
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@teststartswith}[2]{%
  \@DTLifStartsWith{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLisPrefix}
% Tests if first argument starts with second argument.
%\changes{1.01}{2007 Aug 17}{new}
%    \begin{macrocode}
\newcommand*{\DTLisPrefix}[2]{%
  \TE@throw\noexpand\dtl@teststartswith{#1}{#2}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testistartswith}
% Command to test if second argument is a prefix of the first argument.
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testistartswith}[2]{%
  \@sDTLifStartsWith{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLisiPrefix}
% Tests if first argument starts with second argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLisiPrefix}[2]{%
  \TE@throw\noexpand\dtl@testistartswith{#1}{#2}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testendswith}
% Command to test if second argument is a suffix of the first argument.
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testendswith}[2]{%
  \@DTLifEndsWith{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLisSuffix}
% Tests if first argument ends with second argument.
%\changes{1.01}{2007 Aug 17}{new}
%    \begin{macrocode}
\newcommand*{\DTLisSuffix}[2]{%
  \TE@throw\noexpand\dtl@testendswith{#1}{#2}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testiendswith}
% Command to test if second argument is a suffix of the first argument.
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testiendswith}[2]{%
  \@sDTLifEndsWith{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLisiSuffix}
% Tests if first argument ends with second argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLisiSuffix}[2]{%
  \TE@throw\noexpand\dtl@testiendswith{#1}{#2}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLisinlist}
% Tests if first argument is an element of the comma-separated list given
% in the second argument.
%\changes{2.23}{2015-07-11}{new}
%    \begin{macrocode}
\newcommand*{\DTLisinlist}[2]{%
  \TE@throw\noexpand\dtl@testinlist{#1}{#2}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testinlist}
%\changes{2.23}{2015-07-11}{new}
%    \begin{macrocode}
\newcommand*{\dtl@testinlist}[2]{%
  \DTLifinlist{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testnumclosedbetween}
% Command to test if first number lies between second and third 
% numbers. (End points included, all arguments are fixed point
% numbers in standard format.) This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testnumclosedbetween}[3]{%
  \DTLifnumclosedbetween{#1}{#2}{#3}%
    {\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%
% Provide conditional command for use in \cs{ifthenelse}
%\begin{macro}{\DTLisnumclosedbetween}
%    \begin{macrocode}
\newcommand*{\DTLisnumclosedbetween}[3]{%
  \TE@throw\noexpand\dtl@testnumclosedbetween{#1}{#2}{#3}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLisFPclosedbetween}
% Keep old command name for backwards compatibility:
%    \begin{macrocode}
\let\DTLisFPclosedbetween\DTLisnumclosedbetween
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testnumopenbetween}
% Command to test if first number lies between second and third 
% numbers. (End points excluded, all arguments are fixed point
% numbers in standard format.) This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testnumopenbetween}[3]{%
  \DTLifnumopenbetween{#1}{#2}{#3}%
    {\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisnumopenbetween}
% Provide conditional command for use in \cs{ifthenelse}
%    \begin{macrocode}
\newcommand*{\DTLisnumopenbetween}[3]{%
  \TE@throw\noexpand\dtl@testnumopenbetween{#1}{#2}{#3}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLisFPopenbetween}
% Keep old command name for backwards compatibility:
%\changes{3.0}{2025-03-03}{made synonym of \cs{DTLisnumopenbetween}}
%    \begin{macrocode}
\let\DTLisFPopenbetween\DTLisnumopenbetween
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testclosedbetween}
% Command to test if first value lies between second and third 
% values. (End points included, case sensitive.) 
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testclosedbetween}[3]{%
  \@DTLifclosedbetween{#1}{#2}{#3}%
    {\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisclosedbetween}
% Provide conditional command for use in \cs{ifthenelse}
%    \begin{macrocode}
\newcommand*{\DTLisclosedbetween}[3]{%
  \TE@throw\noexpand\dtl@testclosedbetween{#1}{#2}{#3}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testiclosedbetween}
% Command to test if first value lies between second and third 
% values. (End points included, case ignored.) 
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testiclosedbetween}[3]{%
  \@sDTLifclosedbetween{#1}{#2}{#3}%
    {\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisiclosedbetween}
% Provide conditional command for use in \cs{ifthenelse}
%\changes{1.01}{2007 Aug 17}{new}
%    \begin{macrocode}
\newcommand*{\DTLisiclosedbetween}[3]{%
  \TE@throw\noexpand\dtl@testiclosedbetween{#1}{#2}{#3}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testopenbetween}
% Command to test if first value lies between second and third 
% values. (End points excluded, case sensitive.) 
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testopenbetween}[3]{%
  \@DTLifopenbetween{#1}{#2}{#3}%
    {\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisopenbetween}
% Provide conditional command for use in \cs{ifthenelse}
%    \begin{macrocode}
\newcommand*{\DTLisopenbetween}[3]{%
  \TE@throw\noexpand\dtl@testopenbetween{#1}{#2}{#3}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testiopenbetween}
% Command to test if first value lies between second and third 
% values. (End points excluded, case ignored.) 
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testiopenbetween}[3]{%
  \@sDTLifopenbetween{#1}{#2}{#3}%
    {\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisiopenbetween}
% Provide conditional command for use in \cs{ifthenelse}
%\changes{1.01}{2007 Aug 17}{new}
%    \begin{macrocode}
\newcommand*{\DTLisiopenbetween}[3]{%
  \TE@throw\noexpand\dtl@testiopenbetween{#1}{#2}{#3}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testFPislt}
% Command to test if first number is less than second
% number where both numbers are in standard format (dot for decimal
% separator and no group separators).
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testFPislt}[2]{%
  \dtlifnumlt{#1}{#2}%
  {%
    \@dtl@conditiontrue
  }%
  {%
    \@dtl@conditionfalse
  }%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisnumlt}
%\changes{3.0}{2025-03-03}{new}
% Provide conditional command for use in \cs{ifthenelse}
%    \begin{macrocode}
\newcommand*{\DTLisnumlt}[2]{%
  \TE@throw\noexpand\dtl@testFPislt{#1}{#2}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLisFPlt}
% Synonym of \cs{DTLisnumlt}.
%    \begin{macrocode}
\let\DTLisFPlt\DTLisnumlt
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testFPisgt}
% Command to test if first number is greater than second
% number where both numbers are  in standard format.
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testFPisgt}[2]{%
  \dtlifnumgt{#1}{#2}%
  {%
    \@dtl@conditiontrue
  }%
  {%
   \@dtl@conditionfalse
  }%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisnumgt}
% Provide conditional command for use in \cs{ifthenelse}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLisnumgt}[2]{%
  \TE@throw\noexpand\dtl@testFPisgt{#1}{#2}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLisFPgt}
% Synonym
%    \begin{macrocode}
\let\DTLisFPgt\DTLisnumgt
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testFPiseq}
% Command to test if two numbers are equal, where both numbers
% are in standard decimal format
%    \begin{macrocode}
\newcommand*{\dtl@testFPiseq}[2]{%
 \dtlifnumeq{#1}{#2}%
 {%
   \@dtl@conditiontrue
 }%
 {%
  \@dtl@conditionfalse
 }%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisnumeq}
%\changes{3.0}{2025-03-03}{new}
% Provide conditional command for use in \cs{ifthenelse}
%    \begin{macrocode}
\newcommand*{\DTLisnumeq}[2]{%
  \TE@throw\noexpand\dtl@testFPiseq{#1}{#2}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLisFPeq}
% Synonym.
%    \begin{macrocode}
\let\DTLisFPeq\DTLisnumeq
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testFPislteq}
% Command to test if first number is less than or equal to second
% number where both numbers are in standard format.
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testFPislteq}[2]{%
 \dtlifnumlt{#1}{#2}%
 {%
   \@dtl@conditiontrue
 }%
 {%
   \@dtl@conditionfalse
 }%
 \if@dtl@condition
 \else
  \dtl@testFPiseq{#1}{#2}%
 \fi
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisnumlteq}
%\changes{3.0}{2025-03-03}{new}
% Provide conditional command for use in \cs{ifthenelse}
%    \begin{macrocode}
\newcommand*{\DTLisnumlteq}[2]{%
 \TE@throw\noexpand\dtl@testFPislteq{#1}{#2}%
 \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLisFPlteq}
% Synonym.
%    \begin{macrocode}
\let\DTLisFPlteq\DTLisnumlteq
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testFPisgteq}
% Command to test if first number is greater than or equal to second
% number where both numbers are  in standard format.
% This sets \cs{if@dtl@condition}.
%    \begin{macrocode}
\newcommand*{\dtl@testFPisgteq}[2]{%
 \dtlifnumgt{#1}{#2}%
 {%
   \@dtl@conditiontrue
 }%
 {%
   \@dtl@conditionfalse
 }%
 \if@dtl@condition
 \else
  \dtl@testFPiseq{#1}{#2}%
 \fi
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisnumgteq}
% Provide conditional command for use in \cs{ifthenelse}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLisnumgteq}[2]{%
  \TE@throw\noexpand\dtl@testFPisgteq{#1}{#2}%
  \noexpand\if@dtl@condition}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLisFPgteq}
% Synonym.
%    \begin{macrocode}
\let\DTLisFPgteq\DTLisnumgteq
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@teststring}
% Command to test if argument is a string. This sets
% \cs{if@dtl@condition}
%    \begin{macrocode}
\newcommand*{\dtl@teststring}[1]{%
  \DTLifstring{#1}{\@dtl@conditiontrue}{\@dtl@conditionfalse}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisstring}
% Provide conditional command for use in \cs{ifthenelse}
%    \begin{macrocode}
\newcommand*{\DTLisstring}[1]{%
  \TE@throw\noexpand\dtl@teststring{#1}\noexpand\if@dtl@condition}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testnumerical}
% Command to test if argument is a numerical. This sets
% \cs{if@dtl@condition}
%    \begin{macrocode}
\newcommand*{\dtl@testnumerical}[1]{%
  \DTLifnumerical{#1}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisnumerical}
% Provide conditional command for use in \cs{ifthenelse}
%    \begin{macrocode}
\newcommand*{\DTLisnumerical}[1]{%
  \TE@throw\noexpand\dtl@testnumerical{#1}\noexpand\if@dtl@condition}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testint}
% Command to test if argument is an integer. This sets
% \cs{if@dtl@condition}
%    \begin{macrocode}
\newcommand*{\dtl@testint}[1]{%
  \DTLifint{#1}{\@dtl@conditiontrue}{\@dtl@conditionfalse}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisint}
% Provide conditional command for use in \cs{ifthenelse}
%    \begin{macrocode}
\newcommand*{\DTLisint}[1]{%
  \TE@throw\noexpand\dtl@testint{#1}\noexpand\if@dtl@condition}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@testreal}
% Command to test if argument is a real. This sets
% \cs{if@dtl@condition}
%    \begin{macrocode}
\newcommand*{\dtl@testreal}[1]{%
  \DTLifreal{#1}{\@dtl@conditiontrue}{\@dtl@conditionfalse}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLisreal}
% Provide conditional command for use in \cs{ifthenelse}
%    \begin{macrocode}
\newcommand*{\DTLisreal}[1]{%
  \TE@throw\noexpand\dtl@testreal{#1}\noexpand\if@dtl@condition}
%    \end{macrocode}
%\end{macro}
% 
%\begin{macro}{\dtl@testcurrency}
% Command to test if argument is a currency. This sets
% \cs{if@dtl@condition}
%    \begin{macrocode}
\newcommand*{\dtl@testcurrency}[1]{%
  \DTLifcurrency{#1}{\@dtl@conditiontrue}{\@dtl@conditionfalse}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLiscurrency}
% Provide conditional command for use in \cs{ifthenelse}
%    \begin{macrocode}
\newcommand*{\DTLiscurrency}[1]{%
  \TE@throw\noexpand\dtl@testcurrency{#1}\noexpand\if@dtl@condition}
%    \end{macrocode}
%\end{macro}
% 
%\begin{macro}{\dtl@testcurrencyunit}
% Command to test if argument is a currency with given unit. This sets
% \cs{if@dtl@condition}
%    \begin{macrocode}
\newcommand*{\dtl@testcurrencyunit}[2]{%
  \DTLifcurrencyunit{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLiscurrencyunit}
% Provide conditional command for use in \cs{ifthenelse}
%    \begin{macrocode}
\newcommand*{\DTLiscurrencyunit}[2]{%
  \TE@throw\noexpand\dtl@testcurrencyunit{#1}{#2}%
  \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
% 
%\subsection{Loops}
%\begin{macro}{\dtlbreak}
% Break out of loop at the end of current iteration.
%    \begin{macrocode}
\newcommand*{\dtlbreak}{%
  \PackageError{datatool}{Can't break out of anything}{}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlforint}
%NB \LaTeX3 now provides a better integer step function, which should be used instead. This is retained for backward-compatibility.
%\begin{definition}
%\cs{dtlforint}\meta{ct}=\meta{start}\cs{to}\meta{end}\cs{step}%
%\meta{inc}\cs{do}\marg{body}
%\end{definition}
% \meta{ct} is a count register, \meta{start}, \meta{end} and 
% \meta{inc} are integers.
% Group if nested or use \cs{dtlgforint}.
% An infinite loop may result if \meta{inc}$=0$ and 
% \meta{start} $\le$ \meta{end} and \cs{dtlbreak} isn't used.
%    \begin{macrocode}
\long\def\dtlforint#1=#2\to#3\step#4\do#5{%
%    \end{macrocode}
% Make a copy of old version of break function
%    \begin{macrocode}
  \let\@dtl@orgbreak\dtlbreak
  \def\@dtl@endloophook{}%
%    \end{macrocode}
% Setup break function for the loop (sets \meta{ct} to \meta{end}
% at the end of the current iteration).
%    \begin{macrocode}
  \def\dtlbreak{\def\@dtl@endloophook{#1=#3}}%
%    \end{macrocode}
% Initialise \meta{ct}
%    \begin{macrocode}
  #1=#2\relax
%    \end{macrocode}
% Check if the steps are positive or negative.
%    \begin{macrocode}
  \ifnum#4<0\relax
%    \end{macrocode}
% Counting down
%    \begin{macrocode}
    \whiledo{\(#1>#3\)\TE@or\(#1=#3\)}%
    {%
      #5%
      \@dtl@endloophook
      \advance#1 by #4\relax
    }%
  \else
%    \end{macrocode}
% Counting up
%    \begin{macrocode}
    \whiledo{\(#1<#3\)\TE@or\(#1=#3\)}%
    {%
      #5%
      \@dtl@endloophook
      \advance#1 by #4\relax
    }%
  \fi
%    \end{macrocode}
% Restore break function.
%    \begin{macrocode}
  \let\dtlbreak\@dtl@orgbreak
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@foreach@level}
% Count register to keep track of global nested loops.
%    \begin{macrocode}
\newcount\@dtl@foreach@level
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlgforint}
%\begin{definition}
%\cs{dtlgforint}\meta{ct}=\meta{start}\cs{to}\meta{end}\cs{step}%
%\meta{inc}\cs{do}\marg{body}
%\end{definition}
% \meta{ct} is a count register, \meta{start}, \meta{end} and 
% \meta{inc} are integers.
% An infinite loop may result if \meta{inc}=0 and \meta{start} $\le$
% \meta{end} and \cs{dtlbreak} isn't used.
%    \begin{macrocode}
\long\def\dtlgforint#1=#2\to#3\step#4\do#5{%
%    \end{macrocode}
% Initialise
%    \begin{macrocode}
  \global#1=#2\relax
%    \end{macrocode}
% Increment level counter to allow for nested loops
%    \begin{macrocode}
  \global\advance\@dtl@foreach@level by 1\relax
%    \end{macrocode}
% Set up end loop hook
%    \begin{macrocode}
  \expandafter\global\expandafter
    \let\csname @dtl@endhook@\the\@dtl@foreach@level\endcsname
    \relax
%    \end{macrocode}
% Set up the break function:
% Copy current definition
%    \begin{macrocode}
  \expandafter\global\expandafter
    \let\csname @dtl@break@\the\@dtl@foreach@level\endcsname
    \dtlbreak
%    \end{macrocode}
% Set up definition for this level (sets <ct> to <end> at the end
% of the current iteration).
%    \begin{macrocode}
  \gdef\dtlbreak{\expandafter
     \gdef\csname @dtl@endhook@\the\@dtl@foreach@level\endcsname{%
       #1=#3}}%
%    \end{macrocode}
% check the direction
%    \begin{macrocode}
  \ifnum#4<0\relax
%    \end{macrocode}
% Counting down
%    \begin{macrocode}
    \whiledo{\(#1>#3\)\TE@or\(#1=#3\)}%
    {%
      #5%
      \csname @dtl@endhook@\the\@dtl@foreach@level\endcsname
      \global\advance#1 by #4\relax
    }%
  \else
%    \end{macrocode}
% Counting up (or 0 increments)
%    \begin{macrocode}
    \whiledo{\(#1<#3\)\TE@or\(#1=#3\)}%
    {%
      #5%
      \csname @dtl@endhook@\the\@dtl@foreach@level\endcsname
      \global\advance#1 by #4\relax
    }%
  \fi
%    \end{macrocode}
% Restore break function
%    \begin{macrocode}
  \expandafter\global\expandafter\let\expandafter\dtlbreak
    \csname @dtl@break@\the\@dtl@foreach@level\endcsname
%    \end{macrocode}
% Decrement level counter
%    \begin{macrocode}
  \global\advance\@dtl@foreach@level by -1\relax
}
%    \end{macrocode}
%\end{macro}
%
%\begin{environment}{dtlenvgforint}
% Environment form (contents are gathered, so verbatim can't be
% used):
%    \begin{macrocode}
\NewDocumentEnvironment{dtlenvgforint}
 { m +b }
 {\dtlgforint#1\do{#2}}
 {}
%    \end{macrocode}
%\end{environment}
%
%\begin{environment}{dtlenvgforint*}
% Starred form suppresses space-trimming:
%    \begin{macrocode}
\NewDocumentEnvironment{dtlenvgforint*}
 { m +!b }
 {\dtlgforint#1\do{#2}}
 {}
%    \end{macrocode}
%\end{environment}
%
%\subsection{Localisation Support}
%Load localisation support. None provided with \sty{datatool}, but
%support can be added by defining a file called
%\texttt{datatool-}\meta{tag}\texttt{.ldf} that adds a redefinition
%of \cs{DTLCurrentLocaleWordHandler} and \cs{DTLCurrentLocaleGetInitialLetter}
%to the language hook. If a region is supplied, also set the
%currency.
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%Provide quick way to reset all localisation support.
%\begin{macro}{\DTLresetLanguage}
%\changes{3.0}{2025-03-03}{new}
%Reset all language (not region) related commands.
%    \begin{macrocode}
\newcommand\DTLresetLanguage
{
  \tl_clear:N \l_datatool_current_language_tl
  \renewcommand \DTLandname { \& }
  \renewcommand \DTLdatatypeunsetname { unset }
  \renewcommand \DTLdatatypestringname { string }
  \renewcommand \DTLdatatypeintegername { integer }
  \renewcommand \DTLdatatypedecimalname { decimal }
  \renewcommand \DTLdatatypecurrencyname { currency }
  \renewcommand \DTLdatatypedatetimename { date-time }
  \renewcommand \DTLdatatypedatename { date }
  \renewcommand \DTLdatatypetimename { time }
  \renewcommand \DTLdatatypeinvalidname { invalid }
  \renewcommand \DTLCurrentLocaleWordHandler [1] { }
  \renewcommand \DTLCurrentLocaleGetInitialLetter [2]
   {
    \datatool_get_first_letter:nN { ##1 } ##2
   }
  \renewcommand \DTLCurrentLocaleGetGroupString [3]
   {
     \tl_set:Nn ##3 { ##1 }
   }
  \renewcommand \DTLCurrentLocaleGetMonthNameMap [1]
   {
    \tl_set_eq:NN \l_datatool_monthname_map_value_tl \q_no_value
   }
  \renewcommand \DTLCurrentLocaleIfpmTF [ 3 ]
   {
     \tl_if_eq:nnTF { ##1 } { pm } { ##2 } { ##3 }
   }
  \renewcommand \dtllettergroup [1]
   {
     \text_titlecase_first:n { ##1 }
   }
  \renewcommand \dtlnonlettergroup [1]
   {
     \detokenize { ##1 }
   }
  \renewcommand \dtlnumbergroup [1] { ##1 }
  \renewcommand \dtlcurrencygroup [2] { ##1 }
  \renewcommand \dtldatetimegroup [1] { ##1 }
  \renewcommand \dtldategroup [1] { ##1 }
  \renewcommand \dtltimegroup [1] { ##1 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLresetRegion}
%\changes{3.0}{2025-03-03}{new}
%Reset all region related commands.
%    \begin{macrocode}
\newcommand\DTLresetRegion
{
  \tl_clear:N \l_datatool_current_region_tl
  \DTLsetnumberchars { , } { . }
  \DTLsetdefaultcurrency { XXX }
  \renewcommand \DTLCurrentLocaleCurrencyDP { 2 }
  \renewcommand \dtlcurrfmtsymsep { }
  \renewcommand \DTLCurrentLocaleParseTimeStamp [ 4 ] { ##4 }
  \renewcommand \DTLCurrentLocaleParseDate [ 4 ] { ##4 }
  \renewcommand \DTLCurrentLocaleParseTime [ 4 ] { ##4 }
  \renewcommand \DTLCurrentLocaleGetTimeZoneMap [ 1 ]
   {
     \tl_set_eq:NN \l_datatool_timezone_map_value_tl \q_no_value
   }
  \renewcommand \DTLCurrentLocaleFormatDate [ 4 ]
   {
     \datatool_default_date_fmt:nnnn { ##1 } { ##2 } { ##3 } { ##4 }
   }
  \renewcommand \DTLCurrentLocaleFormatTime [ 3 ]
   {
     \datatool_default_time_fmt:nnn { ##1 } { ##2 } { ##3 }
   }
  \renewcommand \DTLCurrentLocaleFormatTimeZone [ 2 ]
   {
     \datatool_default_timezone_fmt:nn { ##1 } { ##2 }
   }
  \renewcommand \DTLCurrentLocaleFormatTimeStampNoZone [7]
   {
     \datatool_default_timestamp_fmt:nnnnnnn
       { ##1 } { ##2 } { ##3 } { ##4 } { ##5 } { ##6 } { ##7 }
   }
  \renewcommand \DTLCurrentLocaleFormatTimeStampWithZone [9]
   {
    \datatool_default_timestamp_fmt:nnnnnnnnn
      { ##1 } { ##2 } { ##3 } { ##4 } { ##5 } { ##6 } { ##7 } { ##8 } { ##9 }
   }
  \renewcommand \DTLCurrentLocaleTimeStampFmtSep { ~ }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\RequireDatatoolDialect}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand \RequireDatatoolDialect [1]
{
 \tl_clear:N \l_datatool_current_language_tl
 \__datatool_require_ldf:
  [
%    \end{macrocode}
%Ensure region language file is loaded, if it exists.
%    \begin{macrocode}
   \tl_if_empty:NF \CurrentTrackedRegion
    {
      \TrackLangRequireResource { \CurrentTrackedRegion }
%    \end{macrocode}
%Note that the region file is only loaded once. This means that if
%there are multiple dialects that share a region, the region hook
%would only be added to the first dialect with that region.
%Therefore, the region file shouldn't add anything to the captions hook
%but should instead just define a command called 
%\cs{DTL}\meta{Region}\texttt{LocaleHook} where \meta{Region} is the two
%letter (uppercase) region code. This means that it can be added
%here, even if the file wasn't loaded at this point.
%    \begin{macrocode}
      \tl_if_exist:cTF { DTL \CurrentTrackedRegion LocaleHook }
       {
         \exp_args:Nc \TrackLangAddToCaptions
           { DTL \CurrentTrackedRegion LocaleHook }
       }
       {
         \tl_new:c { DTL \CurrentTrackedRegion LocaleHook } 
         \datatool_locale_warn:nn
          { datatool-base }
          {
            No ~ locale ~ hook ~ available ~ for ~ region ~ ` \CurrentTrackedRegion '
          }
       }
    }
%    \end{macrocode}
%Search as usual from the locale tag to pick up any specific
% language + region, sub-languages or scripts.
%    \begin{macrocode}
   \TrackLangRequireResource { \CurrentTrackedTag }
  ]
  { datatool } { #1 }
 \tl_if_empty:NT \l_datatool_current_language_tl
  {
%    \end{macrocode}
%No support for this language so clear the token list in the
%language hook.
%    \begin{macrocode}
    \@TrackLangAddToHook
     {
       \tl_clear:N \l_datatool_current_language_tl
     }
     { captions }
  }
 \tl_if_empty:NF \CurrentTrackedRegion
  {
%    \end{macrocode}
%If the region isn't empty but the hook hasn't been defined then the
%region file wasn't loaded. This may be because there's no language
%support.
%    \begin{macrocode}
    \tl_if_exist:cF { DTL \CurrentTrackedRegion LocaleHook }
     {
       \cs_if_exist:NT \TrackLangRequireDialectOmitDialectLabel
        {
          \TrackLangRequireDialectOmitDialectLabel { datatool} { #1 }
          \csuse { DTL \CurrentTrackedRegion LocaleHook }
        }
     }
  }
}
%    \end{macrocode}
%\end{macro}
%It's best to omit dialect label and region code from search to
%ensure the search isn't prematurely terminated.
%    \begin{macrocode}
\cs_new:Nn \__datatool_require_ldf:
 {
  \TrackLangRequireDialectOmitDialectLabelOmitOnlyRegion
 }
%    \end{macrocode}
%Provide interface for locale options.
%Define options using l3keys interface.
%    \begin{macrocode}
\cs_new:Nn \datatool_locale_define_keys:nn
{
  \keys_define:nn { datatool / locale / #1 } { #2 }
}
%    \end{macrocode}
%\begin{macro}{\DTLsetLocaleOptions}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLsetLocaleOptions}\oarg{parent module(s)}\marg{module(s)}\marg{key-val list}
%\end{definition}
%User command for setting options for the given locale.
%The starred version ignores unknown keys. 
%    \begin{macrocode}
\NewDocumentCommand \DTLsetLocaleOptions { s O{} m m }
 {
   \IfBlankTF { #2 }
    {
      \IfBooleanTF { #1 }
       {
         \datatool_set_known_locale_options:nn { #3 } { #4 }
       }
       {
         \datatool_set_locale_options:nn { #3 } { #4 }
       }
    }
    {
      \clist_map_inline:nn { #2 }
       {
         \IfBooleanTF { #1 }
          {
            \datatool_set_known_locale_options:nn { ##1 / #3 } { #4 }
          }
          {
            \datatool_set_locale_options:nn { ##1 / #3 } { #4 }
          }
       }
    }
 }
%    \end{macrocode}
%\end{macro}
%
%Set options (error on unknown):
%    \begin{macrocode}
\cs_new:Nn \datatool_set_locale_options:nn
 {
   \clist_map_inline:nn { #1 }
    {
      \keys_set:nn { datatool / locale / ##1 } { #2 }
    }
 }
%    \end{macrocode}
%Set options (ignore unknown):
%    \begin{macrocode}
\cs_new:Nn \datatool_set_known_locale_options:nn
 {
   \clist_map_inline:nn { #1 }
    {
      \keys_set_known:nn { datatool / locale / ##1 } { #2 }
    }
 }
%    \end{macrocode}
%Provided for the ldf files to add to the captions hook to allow the 
%preferred label for language and region (that may be different from
%the dialect or language label).
%    \begin{macrocode}
\tl_new:N \l_datatool_current_language_tl
\tl_new:N \l_datatool_current_region_tl
%    \end{macrocode}
%\begin{definition}
%\cs{datatool\_if\_current\_lang\_region:nn} \marg{lang}
%\marg{region}
%\end{definition}
%Check if current language and region match \meta{lang} and
%\meta{region}.
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_current_lang_region:nn #1 #2
   { T, F, TF }
 {
  \tl_if_eq:NnTF \l_datatool_current_language_tl { #1 }
   {
    \tl_if_eq:NnTF \l_datatool_current_region_tl { #2 }
     { \prg_return_true: }
     { \prg_return_false: }
   }
   { \prg_return_false: }
 }
%    \end{macrocode}
%Check if empty language.
%    \begin{macrocode}
\cs_new:Nn \datatool_warn_check_language_empty:nnn
 {
   \tl_if_empty:NTF \l_datatool_current_language_tl
    {
      \cs_if_exist:NTF \TrackLangRequireDialectOmitDialectLabelOmitOnlyRegion
       {
         \datatool_locale_warn:nn
          { #1 }
          {
            #3 ~
            (check ~ your ~ localisation ~ setting ~ includes ~ 
             language ~ and ~ region)
          }
       }
       {
         \datatool_locale_warn:nn
          { #1 }
          {
            #2 ~
            (try ~ updating ~ tracklang ~ to ~ at ~ least ~ version ~ 1.6.3)
          }
       }
    }
    { \datatool_locale_warn:nn { #1 } { #3 } }
 }
%    \end{macrocode}
%Check if token list starts with or is \verb|\l_datatool_current_language_tl|
%and issue applicable warning.
%    \begin{macrocode}
\cs_new:Nn \datatool_warn_check_head_language_empty:nnnn
 {
   \tl_if_head_eq_meaning:nNTF { #1 } \l_datatool_current_language_tl 
    {
      \datatool_warn_check_language_empty:nnn { #2 } { #3 } { #4 }
    }
    {
      \datatool_locale_warn:nn { #2 } { #4 } 
    }
 }
\cs_generate_variant:Nn  \datatool_warn_check_head_language_empty:nnnn
 { Vnnn }
%    \end{macrocode}
%Track each additional locale requested in the locales/lang package option.
%Note that \cs{TrackLanguageTag} requires the language first but,
%for datatool, allow just the region. This needs to test if the
%supplied tag has exactly two uppercase characters. If extra
%information, such as the script is required, then the language part
%must be included. 
%    \begin{macrocode}
\clist_map_inline:Nn \l__datatool_extra_locales_clist
 {
   \regex_match:nnTF { \A [A-Z]{2} \Z } { #1 }
    {
      \tl_set:Nn \l__datatool_tmpa_tl
       {
         \dtl@message { Adding ~ tracked ~ locale ~ ` und-#1 ' }
         \TrackLanguageTag { und-#1 }
       }
      \ForEachTrackedDialect{\l__datatool_dialect_tl}
       {
         \IfTrackedIsoCode
           { \TwoLetterIsoCountryCode }
           { \l__datatool_dialect_tl }
           {
             \dtl@message
              {
                Dialect ~ ` \l__datatool_dialect_tl' ~ already ~ has ~ region
              }
           }
           {
             \dtl@message
              { Adding ~ region ~ `#1' ~ to ~ locale ~ ` \l__datatool_dialect_tl ' }
             \AddTrackedRegion { #1 } { \l__datatool_dialect_tl }
             \tl_clear:N \l__datatool_tmpa_tl
           }
       }
      \l__datatool_tmpa_tl
    }
    {
      \TrackIfKnownLanguage { #1 }
       {
         \dtl@message { Adding ~ tracked ~ locale ~ ` #1 ' }
       }
       {
         \datatool_locale_warn:nn
          { datatool-base }
         { Unrecognised ~ language ~ in ~ locale ~ tag ~ ` #1 '}
       }
    }
 }
%    \end{macrocode}
%\begin{macro}{\datatool@do@load@locales}
%The actual ldf loading needs to be after \LaTeX3 syntax has been
%switched off, so define a command for use afterwards.
%    \begin{macrocode}
\newcommand{\datatool@do@load@locales}{}
\datatool@load@locales
{
  \renewcommand\datatool@do@load@locales
   {
    \AnyTrackedLanguages
     {
       \cs_if_exist:NF
         \TrackLangRequireDialectOmitDialectLabelOmitOnlyRegion
        {
          \datatool_locale_warn:nn
           {
            old ~ version ~ of ~ tracklang: ~ localisation ~ files ~
            may ~ not ~ all ~ load. ~ Upgrade ~ to ~ at ~ least ~ 
            tracklang ~ v1.6.3 ~ for ~ better ~ support
           }
          \cs_set:Nn \__datatool_require_ldf:
           { \TrackLangRequireDialect }
        }
       \ForEachTrackedDialect { \l__datatool_dialect_tl }
        {
         \RequireDatatoolDialect { \l__datatool_dialect_tl }
        }
     }
     { }
   }
}
%    \end{macrocode}
%\end{macro}
%Switch off \LaTeX3 syntax:
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%Now load applicable locale files for each tracked dialect.
%    \begin{macrocode}
\datatool@do@load@locales
%    \end{macrocode}
%
%\begin{macro}{\@dtl@gobbletonil}
%NB still used by datatool.sty.
%May be removed in future.
%    \begin{macrocode}
\def\@dtl@gobbletonil#1\@nil{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@countdigits}
%NB still used by datatool-pgfmath.def
%May be removed in future.
%    \begin{macrocode}
\def\@dtl@countdigits#1#2\relax{%
  \advance\@dtl@tmpcount by 1\relax
  \ifx.#2\relax
    \let\@dtl@countnext=\@gobble
  \else
    \let\@dtl@countnext=\@dtl@countdigits
  \fi
  \@dtl@countnext#2\relax
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Deprecated}
%These commands are being phased out and should not be used.
%
%\begin{macro}{\dtlenableUTFviii}
%\changes{2.24}{2016-01-12}{new}
%Deprecated.
%    \begin{macrocode}
\newcommand*{\dtlenableUTFviii}{\booltrue{@dtl@utf8}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldisableUTFviii}
%\changes{2.24}{2016-01-12}{new}
%Deprecated.
%    \begin{macrocode}
\newcommand*{\dtldisableUTFviii}{\boolfalse{@dtl@utf8}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\long@collect@body}
% Need long versions of \isty{amsmath}'s \cs{collect@body}. These macros are 
% adapted from the macros defined by \sty{amsmath}.
% Use \sty{xparse} instead.
%    \begin{macrocode}
\long\def\long@collect@body#1{%
  \@envbody{\@xp#1\@xp{\the\@envbody}}%
  \edef\process@envbody{\the\@envbody\@nx\end{\@currenvir}}%
  \@envbody\@emptytoks \def\begin@stack{b}%
  \begingroup
  \@xp\let\csname\@currenvir\endcsname\long@collect@@body
  \edef\process@envbody{\@xp\@nx\csname\@currenvir\endcsname}%
  \process@envbody
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\long@addto@envbody}
%\changes{2.10}{2012-07-18}{new}
% Adapted from \isty{amsmath}'s \cs{addto@envbody}
%    \begin{macrocode}
\long\def\long@addto@envbody#1{%
  \toks@{#1}%
  \edef\@dtl@tmp{\the\@envbody\the\toks@}%
  \global\@envbody\@xp{\@dtl@tmp}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\long@collect@@body}
%\changes{2.10}{2012-07-18}{new}
% Adapted from \isty{amsmath}'s \cs{collect@body}
%    \begin{macrocode}
\long\def\long@collect@@body#1\end#2{%
  \protected@edef\begin@stack{%
    \long@push@begins#1\begin\end \@xp\@gobble\begin@stack
  }%
  \ifx\@empty\begin@stack
    \endgroup
    \@checkend{#2}%
    \long@addto@envbody{#1}%
  \else
    \long@addto@envbody{#1\end{#2}}%
  \fi
  \process@envbody
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\long@push@begins}
%\changes{2.10}{2012-07-18}{new}
%Adapted from \isty{amsmath}'s \cs{push@begins}
%    \begin{macrocode}
\long\def\long@push@begins#1\begin#2{%
  \ifx\end#2\else b\@xp\long@push@begins\fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@ifsingle}
%\begin{definition}
%\cs{dtl@ifsingle}\marg{arg}\marg{true part}\marg{false part}
%\end{definition}
% If there is only one object in \meta{arg} (without expansion)
% do \meta{true part}, otherwise do false part.
%This now just uses the new \LaTeX3 command to test for a single
%token. Old versions of \sty{glossaries} use \cs{dtl@ifsingle}.
%\changes{3.0}{2025-03-03}{now uses LaTeX3}
%    \begin{macrocode}
\ExplSyntaxOn
\newcommand \dtl@ifsingle [ 3 ]
 {
  \tl_if_single_token:nTF { #1 } { #2 } { #3 }
 }
\ExplSyntaxOff
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtl@ifsingle}
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
% Count registers to store character codes:
%    \begin{macrocode}
\newcount\dtl@codeA
\newcount\dtl@codeB
%    \end{macrocode}
%
%\begin{macro}{\@dtl@listelement@outofrange}
%\changes{2.31}{2018-12-07}{new}
%    \begin{macrocode}
\newcommand{\@dtl@listelement@outofrange}[1]{%
  \PackageWarning{datatool-base}{List index `\number#1' out of range}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@sortlist}
%I made a bit of a blunder here. \cs{dtl@sortlist} was supposed to
%work with commands like \cs{dtlcompare}, but those commands require
%three arguments, the first being the register in which to store the
%result. This contradicts the requirements of \cs{dtl@sortlist}. The
%\qt{bug fix} in v2.26 fixed it to work with commands like
%\cs{dtlcompare}, but that broke the documented design (which breaks
%the \sty{glossaries} package). The other problem is that
%\cs{dtl@insertinto} actually sorts in the reverse order.
%So v2.27 undoes the change from v2.26 to ensure backward
%compatibility and provides an alternative user-level command
%\cs{dtlsortlist}, that's designed to work with the three-argument
%handler commands like \cs{dtlcompare}.
%
%\begin{definition}
% \cs{dtl@sortlist}\marg{list}\marg{criteria cmd}
%\end{definition}
% Performs an insertion sort on \meta{list}, where \meta{criteria cmd}
% is a macro which takes two arguments \meta{a} and \meta{b}.
% \meta{criteria cmd} must set the count register \cs{dtl@sortresult}
% to either $-1$ (\meta{b} less than \meta{a}), 0 (\meta{a} is
% equal to \meta{b}) or 1 (\meta{b} is greater than \meta{a}.)
%
% DEPRECATED. To be removed. (NB used by glossaries.sty)
%    \begin{macrocode}
\newcommand{\dtl@sortlist}[2]{%
\def\@dtl@sortedlist{}%
\@for\@dtl@currentrow:=#1\do{%
\expandafter\dtl@insertinto\expandafter
  {\@dtl@currentrow}{\@dtl@sortedlist}{#2}%
\@endforfalse}%
\let#1=\@dtl@sortedlist
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@insertinto}
%\begin{definition}
% \cs{dtl@insertinto}\marg{element}\marg{sorted-list}\marg{criteria cmd}
%\end{definition}
% Inserts \meta{element} into the sorted list \meta{sorted-list}
% according to the criteria given by \meta{criteria cmd} (see above.)
% DEPRECATED. To be removed. NB used by glossaries.sty
%    \begin{macrocode}
\newcommand{\dtl@insertinto}[3]{%
  \def\@dtl@newsortedlist{}%
  \@dtl@insertdonefalse
  \@for\dtl@srtelement:=#2\do{%
    \if@dtl@insertdone
      \expandafter\toks@\expandafter{\dtl@srtelement}%
      \edef\@dtl@newstuff{{\the\toks@}}%
    \else
%    \end{macrocode}
%\changes{2.26}{2016-07-20}{fixed bug (missing \cs{dtl@sortresult})}
%\changes{2.27}{2016-07-28}{undone the incorrect change in v2.26}
%    \begin{macrocode}
      \expandafter#3\expandafter{\dtl@srtelement}{#1}%
%    \end{macrocode}
%\changes{2.26}{2016-07-20}{fixed bug (incorrect inequality sign)}
%\changes{2.27}{2016-07-28}{undone the incorrect change in v2.26}
%    \begin{macrocode}
      \ifnum\dtl@sortresult<0\relax
        \expandafter\toks@\expandafter{\dtl@srtelement}%
        \@dtl@toks{#1}%
        \edef\@dtl@newstuff{{\the\@dtl@toks},{\the\toks@}}%
        \@dtl@insertdonetrue
      \else
        \expandafter\toks@\expandafter{\dtl@srtelement}%
        \edef\@dtl@newstuff{{\the\toks@}}%
      \fi
    \fi
    \ifdefempty{\@dtl@newsortedlist}%
    {%
      \expandafter\toks@\expandafter{\@dtl@newstuff}%
      \edef\@dtl@newsortedlist{\the\toks@}%
    }%
    {%
      \expandafter\toks@\expandafter{\@dtl@newsortedlist}%
      \expandafter\@dtl@toks\expandafter{\@dtl@newstuff}%
      \edef\@dtl@newsortedlist{\the\toks@,\the\@dtl@toks}%
    }%
    \@endforfalse
  }%
  \ifdefempty{\@dtl@newsortedlist}%
  {%
    \@dtl@toks{#1}%
    \edef\@dtl@newsortedlist{{\the\@dtl@toks}}%
  }%
  {%
    \if@dtl@insertdone
    \else
      \expandafter\toks@\expandafter{\@dtl@newsortedlist}%
      \@dtl@toks{#1}%
      \edef\@dtl@newsortedlist{\the\toks@,{\the\@dtl@toks}}%
    \fi
  }%
  \global\let#2=\@dtl@newsortedlist
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@ifsingleorUTFviii}
%\changes{2.24}{2016-01-12}{new}
% Test if the argument is a single token or, if utf8 setting on,
% a two-octet UTF-8 character. This only tests for two octet UTF-8
% characters.
%\changes{3.0}{2025-03-03}{partially rewritten to use \LaTeX3}
%    \begin{macrocode}
\ExplSyntaxOn
\newcommand \dtl@ifsingleorUTFviii [ 3 ]
{
 \tl_if_single_token:nTF { #1 }
 { #2 }
 {
   \ifbool{@dtl@utf8}
   {
     \tl_if_head_is_N_type:nTF { #1 }
     {
       \__datatool_isheadactivechar:w#1\q_stop
       {
         \ifnum\tl_count_tokens:n { #1 } = 2
           \expandafter \__datatool_isheadUTFviiitwooctets \expandafter
           { \__datatool_xpactiveheadchar }
           { #2 }
           { #3 }
         \else
           #3
         \fi
       }
       { #3 }
     }
     { #3 }
   }
   { #3 }
 }
}
%    \end{macrocode}
%\end{macro}
%
%\changes{3.0}{2025-03-03}{new}
%Tests if the argument starts with an active character.
%    \begin{macrocode}
\catcode`\@13\relax
\cs_new:Npn \__datatool_isheadactivechar:w #1 #2 \q_stop #3 #4
{
  \tl_if_head_eq_catcode:nNTF { #1 } @ 
  { \tl_set:No \__datatool_xpactiveheadchar { #1 } #3 }
  { #4 }
}
\catcode`\@11\relax
%    \end{macrocode}
%\changes{3.0}{2025-03-03}{new}
%Tests if the first argument starts with \cs{UTFviii@two@octets}.
%    \begin{macrocode}
\cs_new:Npn \__datatool_isheadUTFviiitwooctets #1 #2 #3
{
 \tl_if_head_eq_meaning:nNTF { #1 } \UTFviii@two@octets
 { #2 } { #3 }
}
\ExplSyntaxOff
%    \end{macrocode}
%\begin{macro}{\dtl@if@two@octets}
%\changes{2.24}{2016-01-12}{new}
%Check if argument starts with \cs{UTFviii@two@octets}
%Deprecated.
%    \begin{macrocode}
\def\dtl@if@two@octets#1#2\dtl@end@if@two@octets#3#4{%
  \ifbool{@dtl@utf8}
  {%
    \ifx\UTFviii@two@octets#1\relax
     #3%
    \else
     #4%
    \fi
  }%
  {%
    #4%
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@getfirst@UTFviii}
%\changes{2.24}{2016-01-12}{new}
%Deprecated.
%    \begin{macrocode}
\def\dtl@getfirst@UTFviii#1#2#3\end@dtl@getfirst@UTFviii{%
  \def\dtl@first{#1#2}%
  \ifx\@nil#3\relax
    \def\dtl@rest{}%
  \else
    \expandafter\def\expandafter\dtl@rest\expandafter{\@dtl@firsttonil#3}%
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@firsttonil}
%\changes{2.24}{2016-01-12}{new}
%    \begin{macrocode}
\def\@dtl@firsttonil#1\@nil{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtlgetfirstchar}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{@dtlgetfirstchar}\marg{text}\marg{cs1}\marg{cs2}
%\end{definition}
%Stores the first character in \meta{text} in \meta{cs1} and the
%remainder in \meta{cs2}. Largely redundant, but can be used to
%obtain the letter group of a UTF-8 string.
%    \begin{macrocode}
\ExplSyntaxOn
\newcommand{\@dtlgetfirstchar}[3]{
   \tl_if_empty:nTF { #1 }
   { \cs_set:Nn #2 {} \cs_set:Nn #3 {} }
   {
     \tl_if_single_token:nTF { #1 }
     { \cs_set:Nn #2 { #1 } \cs_set:Nn #3 {} }
     {
       \edef #2 { \tl_head:n { #1 } }
       \edef #3 { \tl_tail:n { #1 } }
       \ifbool{@dtl@utf8}
       {
         \tl_if_head_is_N_type:nTF { #1 }
         {
           \__datatool_isheadactivechar:w#1\q_stop
           {
             \expandafter \__datatool_isheadUTFviiitwooctets \expandafter
             { \__datatool_xpactiveheadchar }
             {
               \tl_put_right:Nx #2 { \tl_head:N #3 }
               \edef #3 { \tl_tail:N #3 }
             }
             { }
           }
           { }
         }
         { }
       }
       { }
     }
   }
 }
\ExplSyntaxOff
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@getfirst}
% Gets the first object, and stores in \cs{dtl@first}. The remainder
% is stored in \cs{dtl@rest}.
%\changes{2.23}{2015-07-11}{changed \cs{end} to \cs{end@dtl@getfirst}}
%Deprecated.
%\changes{3.0}{2025-03-03}{deprecated}
%    \begin{macrocode}
\def\dtl@getfirst#1#2\end@dtl@getfirst{%
  \dtlgetfirstchar{#1#2}{\dtl@first}{\dtl@rest}%
}%
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@setcharcode}
%\begin{definition}
%\cs{dtl@setcharcode}\marg{c}\marg{count register}
%\end{definition}
% Sets \meta{count register} to the character code of \meta{c}, or
% to $-1$ if \meta{c} is empty, or to
% 0 if \meta{c} is a control sequence, unless \meta{c} is either
% \cs{space} or |~| in which case it sets \meta{count register}
% to the character code of the space character.
%Largely redundant. Likely to be removed in future.
%    \begin{macrocode}
\ExplSyntaxOn
\newcommand*{\dtl@setcharcode}[2]{%
 \exp_args:Nx \__datatool_setcharcode:nN { \text_purify:n { #1 } } #2
}
\cs_new:Npn \__datatool_setcharcode:nN #1#2
{
  \tl_if_empty:nTF { #1 }
  { #2=-1\relax }
  {
    \tl_if_head_is_space:nTF { #1 }
    { #2=32\relax }
    {
      \tl_if_head_eq_catcode:nNTF { #1 } \relax
      { #2=0\relax }
      {
        \tl_if_single_token:nTF { #1 }
        { \dtlsetcharcode{#1}{#2} }
        {
          \exp_args:Nx \dtlsetcharcode { \tl_head:n { #1 } } {#2}
          \ifbool{@dtl@utf8}
          {
            \ifnum#2>127
              \dtlsetUTFviiicharcode { #1 }{ #2 }
            \fi
          }
          { }
        }
      }
    }
  }
}
\ExplSyntaxOff
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsetcharcode}
%\changes{2.24}{2016-01-12}{new}
% Set the code for the given character. May be redefined by user
% for non-UTF8 encodings (e.g.\ Latin-1).
%Deprecated.
%    \begin{macrocode}
\newcommand*{\dtlsetcharcode}[2]{#2=`#1\relax}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsetlccharcode}
%\changes{2.24}{2016-01-12}{new}
% Set the lowercase code for the given character. May be redefined by user
% for non-UTF8 encodings (e.g.\ Latin-1).
%Deprecated.
%    \begin{macrocode}
\newcommand*{\dtlsetlccharcode}[2]{#2=\lccode`#1\relax}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsetUTFviiicharcode}
% Default behaviour is to set all UTF8 characters to code 64 (before
% A). This will need to be redefined according to the relevant alphabet.
%\changes{2.24}{2016-01-12}{new}
%Deprecated.
%    \begin{macrocode}
\newcommand*\dtlsetUTFviiicharcode[2]{\dtlsetdefaultUTFviiicharcode{#1}{#2}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsetUTFviiilccharcode}
%\changes{2.24}{2016-01-12}{new}
% Default behaviour is to set all UTF8 characters to code 96 (before
% a). This will need to be redefined according to the relevant alphabet.
%Deprecated.
%    \begin{macrocode}
\newcommand*\dtlsetUTFviiilccharcode[2]{\dtlsetdefaultUTFviiilccharcode{#1}{#2}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsetdefaultUTFviiicharcode}
%\changes{2.24}{2016-01-12}{new}
% Default codes for some supplemental Latin characters.
%Deprecated.
%    \begin{macrocode}
\newcommand*\dtlsetdefaultUTFviiicharcode[2]{%
 \ifboolexpr
 {
      test {\ifstrequal{#1}{À}}
   or test {\ifstrequal{#1}{Á}}
   or test {\ifstrequal{#1}{Á}}
   or test {\ifstrequal{#1}{Ã}}
   or test {\ifstrequal{#1}{Ä}}
 }%
 {%
   #2=`A\relax
 }%
 {%
   \ifstrequal{#1}{Ç}%
   {%
     #2=`C\relax
   }%
   {%
     \ifboolexpr
     {
          test {\ifstrequal{#1}{È}}
       or test {\ifstrequal{#1}{É}}
       or test {\ifstrequal{#1}{Ê}}
       or test {\ifstrequal{#1}{Ë}}
     }%
     {%
       #2=`E\relax
     }%
     {%
       \ifboolexpr
       {
            test {\ifstrequal{#1}{Ì}}
         or test {\ifstrequal{#1}{Í}}
         or test {\ifstrequal{#1}{Î}}
         or test {\ifstrequal{#1}{Ï}}
       }%
       {%
         #2=`I\relax
       }%
       {%
         \ifstrequal{#1}{Ñ}%
         {%
           #2=`N\relax
         }%
         {%
           \ifboolexpr
           {
                test {\ifstrequal{#1}{Ò}}
             or test {\ifstrequal{#1}{Ó}}
             or test {\ifstrequal{#1}{Ô}}
             or test {\ifstrequal{#1}{Õ}}
             or test {\ifstrequal{#1}{Ö}}
           }%
           {%
             #2=`O\relax
           }%
           {%
             \ifboolexpr
             {
                  test {\ifstrequal{#1}{Ù}}
               or test {\ifstrequal{#1}{Ú}}
               or test {\ifstrequal{#1}{Û}}
               or test {\ifstrequal{#1}{Ü}}
             }%
             {%
               #2=`U\relax
             }%
             {%
               \ifstrequal{#1}{Ý}%
               {%
                 #2=`Y\relax
               }%
               {%
                 \ifboolexpr
                 {
                      test {\ifstrequal{#1}{à}}
                   or test {\ifstrequal{#1}{á}}
                   or test {\ifstrequal{#1}{á}}
                   or test {\ifstrequal{#1}{ã}}
                   or test {\ifstrequal{#1}{ä}}
                 }%
                 {%
                   #2=`a\relax
                 }%
                 {%
                   \ifstrequal{#1}{ç}%
                   {%
                     #2=`c\relax
                   }%
                   {%
                     \ifboolexpr
                     {
                          test {\ifstrequal{#1}{è}}
                       or test {\ifstrequal{#1}{é}}
                       or test {\ifstrequal{#1}{ê}}
                       or test {\ifstrequal{#1}{ë}}
                     }%
                     {%
                       #2=`e\relax
                     }%
                     {%
                       \ifboolexpr
                       {
                            test {\ifstrequal{#1}{ì}}
                         or test {\ifstrequal{#1}{í}}
                         or test {\ifstrequal{#1}{î}}
                         or test {\ifstrequal{#1}{ï}}
                       }%
                       {%
                         #2=`i\relax
                       }%
                       {%
                         \ifstrequal{#1}{ñ}%
                         {%
                           #2=`n\relax
                         }%
                         {%
                           \ifboolexpr
                           {
                                test {\ifstrequal{#1}{ò}}
                             or test {\ifstrequal{#1}{ó}}
                             or test {\ifstrequal{#1}{ô}}
                             or test {\ifstrequal{#1}{õ}}
                             or test {\ifstrequal{#1}{ö}}
                           }%
                           {%
                             #2=`o\relax
                           }%
                           {%
                             \ifboolexpr
                             {
                                  test {\ifstrequal{#1}{ù}}
                               or test {\ifstrequal{#1}{ú}}
                               or test {\ifstrequal{#1}{û}}
                               or test {\ifstrequal{#1}{ü}}
                             }%
                             {%
                               #2=`u\relax
                             }%
                             {%
                               \ifstrequal{#1}{ý}%
                               {%
                                 #2=`y\relax
                               }%
                               {%
                                 #2=64\relax
                               }%
                             }%
                           }%
                         }%
                       }%
                     }%
                   }%
                 }%
               }%
             }%
           }%
         }%
       }%
     }%
   }%
 }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsetdefaultUTFviiilccharcode}
%\changes{2.24}{2016-01-12}{new}
% As above but for case-insensitive comparison.
%Deprecated.
%    \begin{macrocode}
\newcommand*\dtlsetdefaultUTFviiilccharcode[2]{%
 \ifboolexpr
 {
      test {\ifstrequal{#1}{à}}
   or test {\ifstrequal{#1}{á}}
   or test {\ifstrequal{#1}{á}}
   or test {\ifstrequal{#1}{ã}}
   or test {\ifstrequal{#1}{ä}}
   or test {\ifstrequal{#1}{À}}
   or test {\ifstrequal{#1}{Á}}
   or test {\ifstrequal{#1}{Á}}
   or test {\ifstrequal{#1}{Ã}}
   or test {\ifstrequal{#1}{Ä}}
 }%
 {%
   #2=`a\relax
 }%
 {%
   \ifboolexpr
   {
        test {\ifstrequal{#1}{ç}}
     or test {\ifstrequal{#1}{Ç}}
   }
   {%
     #2=`c\relax
   }%
   {%
     \ifboolexpr
     {
          test {\ifstrequal{#1}{è}}
       or test {\ifstrequal{#1}{é}}
       or test {\ifstrequal{#1}{ê}}
       or test {\ifstrequal{#1}{ë}}
       or test {\ifstrequal{#1}{È}}
       or test {\ifstrequal{#1}{É}}
       or test {\ifstrequal{#1}{Ê}}
       or test {\ifstrequal{#1}{Ë}}
     }%
     {%
       #2=`e\relax
     }%
     {%
       \ifboolexpr
       {
            test {\ifstrequal{#1}{ì}}
         or test {\ifstrequal{#1}{í}}
         or test {\ifstrequal{#1}{î}}
         or test {\ifstrequal{#1}{ï}}
         or test {\ifstrequal{#1}{Ì}}
         or test {\ifstrequal{#1}{Í}}
         or test {\ifstrequal{#1}{Î}}
         or test {\ifstrequal{#1}{Ï}}
       }%
       {%
         #2=`i\relax
       }%
       {%
         \ifboolexpr
         {
              test {\ifstrequal{#1}{ñ}}
           or test {\ifstrequal{#1}{Ñ}}
         }
         {%
           #2=`n\relax
         }%
         {%
           \ifboolexpr
           {
                test {\ifstrequal{#1}{ò}}
             or test {\ifstrequal{#1}{ó}}
             or test {\ifstrequal{#1}{ô}}
             or test {\ifstrequal{#1}{õ}}
             or test {\ifstrequal{#1}{ö}}
             or test {\ifstrequal{#1}{Ò}}
             or test {\ifstrequal{#1}{Ó}}
             or test {\ifstrequal{#1}{Ô}}
             or test {\ifstrequal{#1}{Õ}}
             or test {\ifstrequal{#1}{Ö}}
           }%
           {%
             #2=`o\relax
           }%
           {%
             \ifboolexpr
             {
                  test {\ifstrequal{#1}{ù}}
               or test {\ifstrequal{#1}{ú}}
               or test {\ifstrequal{#1}{û}}
               or test {\ifstrequal{#1}{ü}}
               or test {\ifstrequal{#1}{Ù}}
               or test {\ifstrequal{#1}{Ú}}
               or test {\ifstrequal{#1}{Û}}
               or test {\ifstrequal{#1}{Ü}}
             }%
             {%
               #2=`u\relax
             }%
             {%
               \ifboolexpr
               {
                    test {\ifstrequal{#1}{ý}}
                 or test {\ifstrequal{#1}{Ý}}
               }%
               {%
                 #2=`y\relax
               }%
               {%
                 #2=96\relax
               }%
             }%
           }%
         }%
       }%
     }%
   }%
 }%
}
%    \end{macrocode}
%\end{macro}
%
%\iffalse
%    \begin{macrocode}
%</datatool-base.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datatool-undetermined.ldf>
%    \end{macrocode}
%\fi
%\section{datatool-undetermined.ldf}\label{sec:code:datatool-undetermined}
% Minimal support for undetermined language.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\TrackLangProvidesResource{undetermined}[2025/04/25 v3.4.1]
%    \end{macrocode}
% Provide hook to customized:
%    \begin{macrocode}
\ExplSyntaxOn
\newcommand \DTLundLocaleHook
 {
  \DTLresetLanguage
  \tl_set:Nn \l_datatool_current_language_tl { und }
 }
\ExplSyntaxOff
%    \end{macrocode}
%Add to captions hook.
%    \begin{macrocode}
\TrackLangAddToCaptions{\DTLundLocaleHook}
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</datatool-undetermined.ldf>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datatool-latin1.ldf>
%    \end{macrocode}
%\fi
%\section{datatool-latin1.ldf}\label{sec:code:datatool-latin1}
% ISO-8859-1 (Latin-1) strings.
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesFile{datatool-latin1.ldf}[2025/04/25 v3.4.1 (NLCT)]
\ExplSyntaxOn
\datatool_set_currencysign_from_charcode:ne
  { cent } { "A2 }
\datatool_set_currencysign_from_charcode:nn
  { pound } { "A3 } 
\datatool_set_currencysign_from_charcode:nn
  { currency } { "A4 } 
\datatool_set_currencysign_from_charcode:nn
  { yen } { "A5 }
\datatool_set_symbol_from_charcode:nn
  { middot } { "B7 }
\ExplSyntaxOff
%    \end{macrocode}
% No available ISO-8859-1 characters to support other commands
%\iffalse
%    \begin{macrocode}
%</datatool-latin1.ldf>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datatool-utf8.ldf>
%    \end{macrocode}
%\fi
%\section{datatool-utf8.ldf}\label{sec:code:datatool-utf8}
%UTF-8 strings.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesFile{datatool-utf8.ldf}[2025/04/25 v3.4.1 (NLCT)]
\ExplSyntaxOn
%    \end{macrocode}
%Common numeric and currency formatting symbols.
%    \begin{macrocode}
\datatool_set_currencysign:nn { cent } { ¢ }
\datatool_set_currencysign:nn { pound } { £ }
\datatool_set_currencysign:nn { currency } { ¤ }
\datatool_set_currencysign:nn { yen } { ¥ }
\datatool_set_symbol:nn { middot } { · }
\datatool_set_currencysign:nn { florin } { ƒ }
\datatool_set_currencysign:nn { baht } { ฿ }
\datatool_set_currencysign:nn { ecu } { ₠ }
\datatool_set_currencysign:nn { colonsign } { ₡ }
\datatool_set_currencysign:nn { cruzerio } { ₢ }
\datatool_set_currencysign:nn { frenchfranc } { ₣ }
\datatool_set_currencysign:nn { lira } { ₤ }
\datatool_set_currencysign:nn { mill } { ₥ }
\datatool_set_currencysign:nn { naira } { ₦ }
\datatool_set_currencysign:nn { peseta } { ₧ }
\datatool_set_currencysign:nn { rupee } { ₨ }
\datatool_set_currencysign:nn { won } { ₩ }
\datatool_set_currencysign:nn { shekel } { ₪ }
\datatool_set_currencysign:nn { dong } { ₫ }
\datatool_set_currencysign:nn { euro } { € }
\datatool_set_currencysign:nn { kip } { ₭ }
\datatool_set_currencysign:nn { tugrik } { ₮ }
\datatool_set_currencysign:nn { drachma } { ₯ }
\datatool_set_currencysign:nn { germanpenny } { ₰ }
\datatool_set_currencysign:nn { peso } { ₱ }
\datatool_set_currencysign:nn { guarani } { ₲ }
\datatool_set_currencysign:nn { austral } { ₳ }
\datatool_set_currencysign:nn { hryvnia } { ₴ }
\datatool_set_currencysign:nn { cedi } { ₵ }
\datatool_set_currencysign:nn { livretournois } { ₶ }
\datatool_set_currencysign:nn { spesmilo } { Sm }
\datatool_set_currencysign:nn { tenge } { ₸ }
\datatool_set_currencysign:nn { indianrupee } { ₹ }
\datatool_set_currencysign:nn { turkishlira } { ₺ }
\datatool_set_currencysign:nn { nordicmark } { ₻ }
\datatool_set_currencysign:nn { manat } { ₼ }
\datatool_set_currencysign:nn { ruble } { ₽ }
\datatool_set_currencysign:nn { lari } { ₾ }
\datatool_set_currencysign:nn { bitcoin } { ₿ }
\datatool_set_currencysign:nn { som } { ⃀ }
%    \end{macrocode}
%Regular expression to match apostrophe.
%    \begin{macrocode}
\regex_set:Nn \l_datatool_apos_regex { \' | ’ }
\ExplSyntaxOff
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</datatool-utf8.ldf>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datatool-l3fp.def>
%    \end{macrocode}
%\fi
%\section{datatool-l3fp.def}\label{sec:code:datatool-l3fp}
% Definitions of fixed-point commands that use LaTeX3 commands.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesFile{datatool-l3fp.def}[2025/04/25 v3.4.1 (NLCT)]
%    \end{macrocode}
%
%This file provides commands that use l3fp interfaces.
%The commands defined here match the name and syntax of the commands
%in the alternative \texttt{datatool-\meta{processor}.def} files.
%
%\subsection{Comparisons}
%\begin{macro}{\dtlifnumeq}
%\begin{definition}
%\cs{dtlifnumeq}\marg{num1}\marg{num2}\marg{true part}\marg{false
%part}
%\end{definition}
% Does \meta{true part} if \meta{num1}=\meta{num2}, otherwise does
% \meta{false part}.
%    \begin{macrocode}
\ExplSyntaxOn
\newcommand*{\dtlifnumeq}[4]{%
 \fp_compare:nNnTF { #1 } = { #2 } { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlifnumlt}
%\begin{definition}
%\cs{dtlifnumlt}\marg{num1}\marg{num2}\marg{true part}\marg{false
%part}
%\end{definition}
% Does \meta{true part} if \meta{num1} \textless \meta{num2}, otherwise does
% \meta{false part}.
%    \begin{macrocode}
\newcommand*{\dtlifnumlt}[4]{%
 \fp_compare:nNnTF { #1 } < { #2 } { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlifnumgt}
%\begin{definition}
%\cs{dtlifnumgt}\marg{num1}\marg{num2}\marg{true part}\marg{false
%part}
%\end{definition}
% Does \meta{true part} if \meta{num1} \textgreater \meta{num2}, otherwise does
% \meta{false part}.
%    \begin{macrocode}
\newcommand*{\dtlifnumgt}[4]{%
 \fp_compare:nNnTF { #1 } > { #2 } { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlifnumopenbetween}
%\begin{definition}
%\cs{dtlifnumopenbetween}\marg{num}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% Determines if \meta{min} $<$ \meta{num} $<$ \meta{max}.
%    \begin{macrocode}
\newcommand*{\dtlifnumopenbetween}[5]{%
 \fp_compare:nTF { #2 < #1 < #3 }
   { #4 } { #5 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlifnumclosedbetween}
%\begin{definition}
%\cs{dtlifnumclosedbetween}\marg{num}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% Determines if \meta{min} $\leq$ \meta{num} $\leq$ \meta{max}.
%    \begin{macrocode}
\newcommand*{\dtlifnumclosedbetween}[5]{%
 \fp_compare:nTF { #2 <= #1 <= #3 }
  { #4 } { #5 }
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Functions}
%
%\begin{macro}{\dtladd}
%\begin{definition}
%\cs{dtladd}\marg{cs}\marg{num1}\marg{num2}
%\end{definition}
% Adds two numbers and stores the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand \dtladd { m m m }
{
  \fp_set:Nn \l__datatool_tmpa_fp { #2 + #3 }
  \tl_set:Nx #1 { \fp_use:N \l__datatool_tmpa_fp }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtladdall}
%\begin{definition}
%\cs{dtladdall}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the sum of all the values in the comma-separated list of
% numbers.
%    \begin{macrocode}
\NewDocumentCommand \dtladdall { m m }
{
  \fp_zero:N \l__datatool_tmpa_fp
  \exp_args:Nx \clist_map_inline:nn { #2 }
   { \fp_add:Nn \l__datatool_tmpa_fp { ##1 } }
  \tl_set:Nx #1 { \fp_use:N \l__datatool_tmpa_fp }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsub}
%\begin{definition}
%\cs{dtlsub}\marg{cs}\marg{num1}\marg{num2}
%\end{definition}
% Subtracts two numbers and stores the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand \dtlsub { m m m }
{
  \fp_set:Nn \l__datatool_tmpa_fp { #2 - #3 }
  \tl_set:Nx #1 { \fp_use:N \l__datatool_tmpa_fp }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmul}
%\begin{definition}
%\cs{dtlmul}\marg{cs}\marg{num1}\marg{num2}
%\end{definition}
% Multiplies two numbers and stores the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand \dtlmul { m m m }
{
  \fp_set:Nn \l__datatool_tmpa_fp { #2 * #3 }
  \tl_set:Nx #1 { \fp_use:N \l__datatool_tmpa_fp }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldiv}
%\begin{definition}
%\cs{dtldiv}\marg{cs}\marg{num1}\marg{num2}
%\end{definition}
% Divides two numbers and stores the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand \dtldiv { m m m }
{
  \fp_set:Nn \l__datatool_tmpa_fp { #2 / #3 }
  \tl_set:Nx #1 { \fp_use:N \l__datatool_tmpa_fp }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsqrt}
%\begin{definition}
%\cs{dtlsqrt}\marg{cs}\marg{num}
%\end{definition}
% Square root number and store the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand \dtlsqrt { m m }
{
  \fp_set:Nn \l__datatool_tmpa_fp { sqrt ( #2 ) }
  \tl_set:Nx #1 { \fp_use:N \l__datatool_tmpa_fp }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlroot}
%\begin{definition}
%\cs{dtlroot}\marg{cs}\marg{num}\marg{n}
%\end{definition}
% Nth root number and store the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand \dtlroot { m m m }
{
  \exp_args:Nx \tl_if_eq:nnTF { #3 } { 2 }
  {
    \fp_set:Nn \l__datatool_tmpa_fp { sqrt ( #2 ) }
    \tl_set:Nx #1 { \fp_use:N \l__datatool_tmpa_fp }
  }
  {
    \fp_set:Nn \l__datatool_tmpa_fp { ( #2 ) ^ ( 1 / ( #3 ) ) }
    \tl_set:Nx #1 { \fp_use:N \l__datatool_tmpa_fp }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlround}
%\begin{definition}
%\cs{dtlround}\marg{cs}\marg{num}\marg{dp}
%\end{definition}
% Rounds \meta{num} to \meta{dp} decimal places and stores the
% result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand \dtlround { m m m }
{
  \fp_set:Nn \l__datatool_tmpa_fp { round ( #2, #3 ) }
  \tl_set:Nx #1 { \fp_to_decimal:N \l__datatool_tmpa_fp }
  \datatool_pad_trailing_zeros:Nn #1 { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtltrunc}
%\begin{definition}
%\cs{dtltrunc}\marg{cs}\marg{num}\marg{dp}
%\end{definition}
% Truncates \meta{num} to \meta{dp} decimal places and stores the
% result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand \dtltrunc { m m m }
{
  \fp_set:Nn \l__datatool_tmpa_fp { trunc ( #2, #3 ) }
  \tl_set:Nx #1 { \fp_to_decimal:N \l__datatool_tmpa_fp }
  \datatool_pad_trailing_zeros:Nn #1 { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlclip}
%\begin{definition}
%\cs{dtlclip}\marg{cs}\marg{num}
%\end{definition}
% Removes redundant trailing zeros from \meta{num} and stores the
% result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand \dtlclip { m m }
{
  \fp_set:Nn \l__datatool_tmpa_fp { #2 }
  \tl_set:Nx #1 { \fp_to_decimal:N \l__datatool_tmpa_fp }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmin}
%\begin{definition}
%\cs{dtlmin}\marg{cs}\marg{num1}\marg{num2}
%\end{definition}
% Defines \meta{cs} to the smaller of the two numbers.
%    \begin{macrocode}
\NewDocumentCommand \dtlmin { m m m }
{
  \fp_set:Nn \l__datatool_tmpa_fp { min ( #2, #3 ) }
  \tl_set:Nx #1 { \fp_use:N \l__datatool_tmpa_fp }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlminall}
%\begin{definition}
%\cs{dtlminall}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the minimum in the comma-separated list of
% numbers.
%    \begin{macrocode}
\NewDocumentCommand \dtlminall { m m }
{
  \fp_set:Nn \l__datatool_tmpa_fp { min ( #2 ) }
  \tl_set:Nx #1 { \fp_use:N \l__datatool_tmpa_fp }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmax}
%\begin{definition}
%\cs{dtlmax}\marg{cs}\marg{num1}\marg{num2}
%\end{definition}
% Defines \meta{cs} to the larger of the two numbers.
%    \begin{macrocode}
\NewDocumentCommand \dtlmax { m m m }
{
  \fp_set:Nn \l__datatool_tmpa_fp { max ( #2, #3 ) }
  \tl_set:Nx #1 { \fp_use:N \l__datatool_tmpa_fp }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmaxall}
%\begin{definition}
%\cs{dtlmaxall}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the maximum in the comma-separated list of
% numbers.
%    \begin{macrocode}
\NewDocumentCommand \dtlmaxall { m m }
{
  \fp_set:Nn \l__datatool_tmpa_fp { max ( #2 ) }
  \tl_set:Nx #1 { \fp_use:N \l__datatool_tmpa_fp }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlabs}
%\begin{definition}
%\cs{dtlabs}\marg{cs}\marg{num}
%\end{definition}
% Defines \meta{cs} to the absolute value of \meta{num}.
%    \begin{macrocode}
\NewDocumentCommand \dtlabs { m m }
{
  \fp_set:Nn \l__datatool_tmpa_fp { abs ( #2 ) }
  \tl_set:Nx #1 { \fp_use:N \l__datatool_tmpa_fp }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlneg}
%\begin{definition}
%\cs{dtlneg}\marg{cs}\marg{num}
%\end{definition}
% Defines \meta{cs} to the negative of \meta{num}.
%    \begin{macrocode}
\NewDocumentCommand \dtlneg {m m }
{
  \fp_set:Nn \l__datatool_tmpa_fp { - ( #2 ) }
  \tl_set:Nx #1 { \fp_use:N \l__datatool_tmpa_fp }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmeanforall}
%\begin{definition}
%\cs{dtlmeanforall}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the mean (average) of all the values in the comma-separated list of
% numbers.
%    \begin{macrocode}
\NewDocumentCommand \dtlmeanforall { m m }
{
  \fp_zero:N \l__datatool_total_fp
  \int_zero:N \l__datatool_count_int
  \exp_args:No \clist_map_inline:nn { #2 }
    { 
      \int_incr:N \l__datatool_count_int
      \fp_add:Nn \l__datatool_total_fp { ##1 }
    }
  \fp_set:Nn \l__datatool_mean_fp 
    { \l__datatool_total_fp / \l__datatool_count_int }
  \tl_set:Nx #1 { \fp_use:N \l__datatool_mean_fp }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlvarianceforall}
%\begin{definition}
%\cs{dtlvarianceforall}\oarg{mean}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the variance of all the values in the comma-separated list of
% numbers. If the mean value has already been calculated, it can be
% supplied in the optional argument.
%    \begin{macrocode}
\NewDocumentCommand \dtlvarianceforall { o m m }
{
  \IfNoValueTF { #1 }
  {
    \fp_zero:N \l__datatool_total_fp
    \int_zero:N \l__datatool_count_int
    \exp_args:No \clist_map_inline:nn { #3 }
      { 
        \int_incr:N \l__datatool_count_int
        \fp_add:Nn \l__datatool_total_fp { ##1 }
      }
    \fp_set:Nn \l__datatool_mean_fp 
      { \l__datatool_total_fp / \l__datatool_count_int }
    \fp_zero:N \l__datatool_total_fp
    \exp_args:No \clist_map_inline:nn { #3 }
      { 
        \fp_set:Nn \l__datatool_tmpa_fp
          { ##1 - \l__datatool_mean_fp }
        \fp_add:Nn \l__datatool_total_fp
          { \l__datatool_tmpa_fp  * \l__datatool_tmpa_fp }
      }
  }
  {
    \fp_set:Nn { \l__datatool_mean_fp } { #1 }
    \fp_zero:N \l__datatool_total_fp
    \int_zero:N \l__datatool_count_int
    \exp_args:No \clist_map_inline:nn { #3 }
      { 
        \int_incr:N \l__datatool_count_int
        \fp_set:Nn \l__datatool_tmpa_fp
          { ##1 - \l__datatool_mean_fp }
        \fp_add:Nn \l__datatool_total_fp
          { \l__datatool_tmpa_fp  * \l__datatool_tmpa_fp }
      }
  }
  \fp_set:Nn \l__datatool_tmpa_fp
    { \l__datatool_total_fp / \l__datatool_count_int }
  \tl_set:Nx #2 { \fp_use:N \l__datatool_tmpa_fp }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsdforall}
%\begin{definition}
%\cs{dtlsdforall}\oarg{mean}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the standard deviation of all the values in the comma-separated list of
% numbers. If the mean value has already been calculated, it can be
% supplied in the optional argument.
%    \begin{macrocode}
\NewDocumentCommand \dtlsdforall { o m m }
{
  \IfNoValueTF { #1 }
  {
    \fp_zero:N \l__datatool_total_fp
    \int_zero:N \l__datatool_count_int
    \exp_args:No \clist_map_inline:nn { #3 }
      { 
        \int_incr:N \l__datatool_count_int
        \fp_add:Nn \l__datatool_total_fp { ##1 }
      }
    \fp_set:Nn \l__datatool_mean_fp 
      { \l__datatool_total_fp / \l__datatool_count_int }
    \fp_zero:N \l__datatool_total_fp
    \exp_args:No \clist_map_inline:nn { #3 }
      { 
        \fp_set:Nn \l__datatool_tmpa_fp
          { ##1 - \l__datatool_mean_fp }
        \fp_add:Nn \l__datatool_total_fp
          { \l__datatool_tmpa_fp  * \l__datatool_tmpa_fp }
      }
  }
  {
    \fp_set:Nn \l__datatool_mean_fp { #1 }
    \fp_zero:N \l__datatool_total_fp
    \int_zero:N \l__datatool_count_int
    \exp_args:No \clist_map_inline:nn { #3 }
      { 
        \int_incr:N \l__datatool_count_int
        \fp_set:Nn \l__datatool_tmpa_fp { ##1 - \l__datatool_mean_fp }
        \fp_add:Nn \l__datatool_total_fp
         { \l__datatool_tmpa_fp  * \l__datatool_tmpa_fp }
      }
  }
  \fp_set:Nn \l__datatool_tmpa_fp
    { sqrt ( \l__datatool_total_fp / \l__datatool_count_int ) }
  \tl_set:Nx #2 { \fp_use:N \l__datatool_tmpa_fp }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</datatool-l3fp.def>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datatool-lua.def>
%    \end{macrocode}
%\fi
%\section{datatool-lua.def}\label{sec:code:datatool-lua}
% Definitions of fixed-point commands that use Lua. Only for use
% with \LuaLaTeX.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesFile{datatool-lua.def}[2025/04/25 v3.4.1 (NLCT)]
%    \end{macrocode}
%
%This file provides commands that use \cs{directlua}.
%The commands defined here match the name and syntax of the commands
%in the alternative \texttt{datatool-\meta{processor}.def} files.
%
%\subsection{Comparisons}
%\begin{macro}{\dtlifnumeq}
%\begin{definition}
%\cs{dtlifnumeq}\marg{num1}\marg{num2}\marg{true part}\marg{false
%part}
%\end{definition}
% Does \meta{true part} if \meta{num1}=\meta{num2}, otherwise does
% \meta{false part}.
%    \begin{macrocode}
\newcommand*{\dtlifnumeq}[4]{%
  \ifnum\directlua{if #1==#2 then tex.print(1) else tex.print(0) end}=1 
   #3%
  \else
   #4%
  \fi
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlifnumlt}
%\begin{definition}
%\cs{dtlifnumlt}\marg{num1}\marg{num2}\marg{true part}\marg{false
%part}
%\end{definition}
% Does \meta{true part} if \meta{num1} \textless \meta{num2}, otherwise does
% \meta{false part}.
%    \begin{macrocode}
\newcommand*{\dtlifnumlt}[4]{%
  \ifnum\directlua{if #1<#2 then tex.print(1) else tex.print(0) end}=1 
   #3%
  \else
   #4%
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlifnumgt}
%\begin{definition}
%\cs{dtlifnumgt}\marg{num1}\marg{num2}\marg{true part}\marg{false
%part}
%\end{definition}
% Does \meta{true part} if \meta{num1} \textgreater \meta{num2}, otherwise does
% \meta{false part}.
%    \begin{macrocode}
\newcommand*{\dtlifnumgt}[4]{%
  \ifnum\directlua{if #1>#2 then tex.print(1) else tex.print(0) end}=1 
   #3%
  \else
   #4%
  \fi
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlifnumopenbetween}
%\begin{definition}
%\cs{dtlifnumopenbetween}\marg{num}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% Determines if \meta{min} $<$ \meta{num} $<$ \meta{max}.
%    \begin{macrocode}
\newcommand*{\dtlifnumopenbetween}[5]{%
  \ifnum\directlua{if #2 < #1 and #1 < #3 then tex.print(1) else tex.print(0) end}=1 
   #4%
  \else
   #5%
  \fi
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlifnumclosedbetween}
%\begin{definition}
%\cs{dtlifnumclosedbetween}\marg{num}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% Determines if \meta{min} $\leq$ \meta{num} $\leq$ \meta{max}.
%    \begin{macrocode}
\newcommand*{\dtlifnumclosedbetween}[5]{%
  \ifnum\directlua{if #2 <= #1 and #1 <= #3 then tex.print(1) else tex.print(0) end}=1 
   #4%
  \else
   #5%
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Functions}
%
%\begin{macro}{\dtladd}
%\begin{definition}
%\cs{dtladd}\marg{cs}\marg{num1}\marg{num2}
%\end{definition}
% Adds two numbers and stores the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand\dtladd{mmm}{%
  \edef#1{\directlua{ tex.print(#2+(#3)) }}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtladdall}
%\begin{definition}
%\cs{dtladdall}\marg{cs}\marg{num list}
%\end{definition}
% Adds all numbers and stores the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand\dtladdall{mm}{%
  \edef#1{\directlua{
    x = 0;
    array = { #2 };
    for key,val in ipairs(array) do
     x = x + val;
    end
    tex.print(x);
  }}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsub}
%\begin{definition}
%\cs{dtlsub}\marg{cs}\marg{num1}\marg{num2}
%\end{definition}
% Subtracts two numbers and stores the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand\dtlsub{mmm}{%
  \edef#1{\directlua{ tex.print(#2-(#3)) }}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmul}
%\begin{definition}
%\cs{dtlmul}\marg{cs}\marg{num1}\marg{num2}
%\end{definition}
% Multiplies two numbers and stores the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand\dtlmul{mmm}{%
  \edef#1{\directlua{ tex.print(#2*#3) }}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldiv}
%\begin{definition}
%\cs{dtldiv}\marg{cs}\marg{num1}\marg{num2}
%\end{definition}
% Divides two numbers and stores the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand\dtldiv{mmm}{%
  \edef#1{\directlua{ tex.print(#2/#3) }}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsqrt}
%\begin{definition}
%\cs{dtlsqrt}\marg{cs}\marg{num}
%\end{definition}
% Square root number and store the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand\dtlsqrt{mm}{%
  \edef#1{\directlua{ tex.print(math.sqrt(#2)) }}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlroot}
%\begin{definition}
%\cs{dtlroot}\marg{cs}\marg{num}\marg{n}
%\end{definition}
% Nth root number and store the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand\dtlroot{mmm}{%
  \edef#1{\directlua{ tex.print((#2)^(1/(#3))) }}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlround}
%\begin{definition}
%\cs{dtlround}\marg{cs}\marg{num}\marg{dp}
%\end{definition}
% Rounds \meta{num} to \meta{dp} decimal places and stores the
% result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand\dtlround{mmm}{%
  \edef#1{"\@percentchar0.\number#3f", #2}%
  \edef#1{\directlua{ tex.print(string.format(#1)) }}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtltrunc}
%\begin{definition}
%\cs{dtltrunc}\marg{cs}\marg{num}\marg{dp}
%\end{definition}
% Truncates \meta{num} to \meta{dp} decimal places and stores the
% result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand\dtltrunc{mmm}{%
 \edef#1{\directlua{
   local m = 10^(#3);
   tex.print(string.format("\@percentchar.#3f", math.floor(#2 * m)/m))
 }}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlclip}
%\begin{definition}
%\cs{dtlclip}\marg{cs}\marg{num}
%\end{definition}
% Removes redundant trailing zeros from \meta{num} and stores the
% result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand\dtlclip{mm}{%
 \edef#1{\directlua{
  local s = "#2";
  tex.print(s:match("^(\@percentchar d*\@percentchar.?0?\@percentchar d-)0*$"))
 }}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmin}
%\begin{definition}
%\cs{dtlmin}\marg{cs}\marg{num1}\marg{num2}
%\end{definition}
% Defines \meta{cs} to the smaller of the two numbers.
%    \begin{macrocode}
\NewDocumentCommand\dtlmin{mmm}{%
 \edef#1{\directlua{tex.print(math.min(#2,#3))}}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlminall}
%\begin{definition}
%\cs{dtlminall}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the minimum in the comma-separated list of
% numbers.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand\dtlminall{mm}{%
 \edef#1{\directlua{tex.print(math.min(#2))}}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmax}
%\begin{definition}
%\cs{dtlmax}\marg{cs}\marg{num1}\marg{num2}
%\end{definition}
% Defines \meta{cs} to the larger of the two numbers.
%    \begin{macrocode}
\NewDocumentCommand\dtlmax{mmm}{%
 \edef#1{\directlua{tex.print(math.max(#2,#3))}}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmaxall}
%\begin{definition}
%\cs{dtlmaxall}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the maximum in the comma-separated list of
% numbers.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand\dtlmaxall{mm}{%
 \edef#1{\directlua{tex.print(math.max(#2))}}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlabs}
%\begin{definition}
%\cs{dtlabs}\marg{cs}\marg{num}
%\end{definition}
% Defines \meta{cs} to the absolute value of \meta{num}.
%    \begin{macrocode}
\NewDocumentCommand\dtlabs{mm}{%
 \edef#1{\directlua{tex.print(math.abs(#2))}}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlneg}
%\begin{definition}
%\cs{dtlneg}\marg{cs}\marg{num}
%\end{definition}
% Defines \meta{cs} to the negative of \meta{num}.
%    \begin{macrocode}
\NewDocumentCommand\dtlneg{mm}{%
 \edef#1{\directlua{tex.print(-(#2))}}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmeanforall}
%\begin{definition}
%\cs{dtlmeanforall}\marg{cs}\marg{num list}
%\end{definition}
% Computes the mean and stores the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand\dtlmeanforall{mm}{%
  \edef#1{\directlua{
    x = 0;
    n = 0;
    array = { #2 };
    for key,val in ipairs(array) do
     n = n + 1;
     x = x + val;
    end
    tex.print(x/n);
  }}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlvarianceforall}
%\begin{definition}
%\cs{dtlvarianceforall}\oarg{mean}\marg{cs}\marg{num list}
%\end{definition}
% Computes the variance and stores the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand \dtlvarianceforall { o m m } 
{%
  \IfNoValueTF{#1}%
  {%  
     \edef#2{\directlua{
       n = 0;
       mean = 0;
       array = { #3 };
       for key,val in ipairs(array) do
        n = n + 1;
        mean = mean + val;
       end
       mean = mean / n;
       variance = 0;
       for key,val in ipairs(array) do
        x = val - mean;
        variance = variance + x * x;
       end
       variance = variance / n;
       tex.print(variance);
     }}%
  }%
  {%
     \edef#2{\directlua{
       n = 0;
       mean = #1;
       array = { #3 };
       variance = 0;
       for key,val in ipairs(array) do
        n = n + 1;
        x = val - mean;
        variance = variance + x * x;
       end
       variance = variance / n;
       tex.print(variance);
     }}%
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsdforall}
%\begin{definition}
%\cs{dtlsdforall}\oarg{mean}\marg{cs}\marg{num list}
%\end{definition}
% Computes the standard deviation and stores the result in \meta{cs}.
%    \begin{macrocode}
\NewDocumentCommand \dtlsdforall { o m m } 
{%
  \IfNoValueTF{#1}%
  {%  
     \edef#2{\directlua{
       n = 0;
       mean = 0;
       array = { #3 };
       for key,val in ipairs(array) do
        n = n + 1;
        mean = mean + val;
       end
       mean = mean / n;
       variance = 0;
       for key,val in ipairs(array) do
        x = val - mean;
        variance = variance + x * x;
       end
       variance = variance / n;
       tex.print(math.sqrt(variance));
     }}%
  }%
  {%
     \edef#2{\directlua{
       n = 0;
       mean = #1;
       array = { #3 };
       variance = 0;
       for key,val in ipairs(array) do
        n = n + 1;
        x = val - mean;
        variance = variance + x * x;
       end
       variance = variance / n;
       tex.print(math.sqrt(variance));
     }}%
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\iffalse
%    \begin{macrocode}
%</datatool-lua.def>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datatool-fp.def>
%    \end{macrocode}
%\fi
%\section{datatool-fp.def}\label{sec:code:datatool-fp}
% Definitions of fixed-point commands that use the \sty{fp} package.
% Provided for backward-compatibility.
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesFile{datatool-fp.def}[2025/04/25 v3.4.1 (NLCT)]
%    \end{macrocode}
% Required package:
%    \begin{macrocode}
\RequirePackage{fp}
%    \end{macrocode}
%
% If \sty{fp}'s \pkgopt{verbose} option set, switch on \pkgopt{verbose} for 
% \sty{datatool-base} as well:
%    \begin{macrocode}
\ifFPmessages
  \dtlverbosetrue
\fi
%    \end{macrocode}
%
%\subsection{Comparison Commands}
%
%\begin{macro}{\dtlifnumeq}
%\begin{definition}
%\cs{dtlifnumeq}\marg{num1}\marg{num2}\marg{true part}\marg{false
%part}
%\end{definition}
% Does \meta{true part} if \meta{num1}=\meta{num2}, otherwise does
% \meta{false part}. The numbers must use a full stop as the decimal
% character and no number group separator.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\dtlifnumeq}[4]{%
  \FPifeq{#1}{#2}%
    #3%
  \else
    #4%
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlifnumlt}
%\begin{definition}
%\cs{dtlifnumlt}\marg{num1}\marg{num2}\marg{true part}\marg{false
%part}
%\end{definition}
% Does \meta{true part} if \meta{num1} \textless \meta{num2}, otherwise does
% \meta{false part}. The numbers must use a full stop as the decimal
% character and no number group separator.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\dtlifnumlt}[4]{%
  \FPiflt{#1}{#2}%
   #3%
  \else
   #4%
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlifnumgt}
%\begin{definition}
%\cs{dtlifnumgt}\marg{num1}\marg{num2}\marg{true part}\marg{false
%part}
%\end{definition}
% Does \meta{true part} if \meta{num1} \textgreater \meta{num2}, otherwise does
% \meta{false part}. The numbers must use a full stop as the decimal
% character and no number group separator.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\dtlifnumgt}[4]{%
  \FPifgt{#1}{#2}%
   #3%
  \else
   #4%
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlifnumopenbetween}
%\begin{definition}
%\cs{dtlifnumopenbetween}\marg{num}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% Determines if \meta{min} $<$ \meta{num} $<$ \meta{max} where
% all numerical arguments are in standard fixed point notation.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\dtlifnumopenbetween}[5]{%
  \let\@dtl@dovalue\relax
  \dtlifnumgt{#1}{#2}%
  {}%
  {%
    \def\@dtl@dovalue{#5}%
  }%
  \dtlifnumlt{#1}{#3}%
  {%
    \ifx\@dtl@dovalue\relax
      \def\@dtl@dovalue{#4}%
    \fi
  }%
  {%
    \def\@dtl@dovalue{#5}%
  }%
  \@dtl@dovalue
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlifnumclosedbetween}
%\begin{definition}
%\cs{dtlifnumclosedbetween}\marg{num}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% Determines if \meta{min} $\leq$ \meta{num} $\leq$ \meta{max} where
% all numerical arguments are in standard fixed point notation.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\dtlifnumclosedbetween}[5]{%
  \let\@dtl@dovalue\relax
  \dtlifnumgt{#1}{#2}%
  {}%
  {%
    \dtlifnumeq{#1}{#2}%
    {%
      \def\@dtl@dovalue{#4}%
    }%
    {%
      \def\@dtl@dovalue{#5}%
    }%
  }%
  \dtlifnumlt{#1}{#3}%
  {%
    \ifx\@dtl@dovalue\relax
      \def\@dtl@dovalue{#4}%
    \fi
  }%
  {%
    \dtlifnumeq{#1}{#3}%
    {%
      \def\@dtl@dovalue{#4}%
    }%
    {%
      \def\@dtl@dovalue{#5}%
    }%
  }%
  \@dtl@dovalue
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Functions}
%
%\begin{macro}{\dtladd}
% Adds two numbers using fp.
%    \begin{macrocode}
\NewDocumentCommand{\dtladd}{mmm}{%
  \FPadd{#1}{#2}{#3}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtladdall}
%\begin{definition}
%\cs{dtladdall}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the sum of all the numbers in the comma-separated list of
% numbers.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand{\dtladdall}{mm}{%
  \def#1{0}%
  \@for\@dtl@tmp:=#2\do{%
    \FPadd{#1}{#1}{\@dtl@tmp}%
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsub}
% Subtracts two numbers using fp.
%    \begin{macrocode}
\NewDocumentCommand{\dtlsub}{mmm}{%
  \FPsub{#1}{#2}{#3}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmul}
% Multiplies two numbers using fp.
%    \begin{macrocode}
\NewDocumentCommand{\dtlmul}{mmm}{%
  \FPmul{#1}{#2}{#3}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldiv}
% Divides two numbers using fp.
%    \begin{macrocode}
\NewDocumentCommand{\dtldiv}{mmm}{%
  \FPdiv{#1}{#2}{#3}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsqrt}
% Square root using fp.
%    \begin{macrocode}
\NewDocumentCommand{\dtlsqrt}{mm}{%
  \FProot{#1}{#2}{2}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlroot}
% Nth root using fp.
%    \begin{macrocode}
\NewDocumentCommand{\dtlroot}{mmm}{%
  \FProot{#1}{#2}{#3}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlround}
% Rounds using fp.
%    \begin{macrocode}
\NewDocumentCommand{\dtlround}{mmm}{%
  \FPround{#1}{#2}{#3}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtltrunc}
% Truncates using fp.
% (Third argument is the number of digits.)
%    \begin{macrocode}
\NewDocumentCommand{\dtltrunc}{mmm}{%
  \FPtrunc{#1}{#2}{#3}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlclip}
%    \begin{macrocode}
\NewDocumentCommand{\dtlclip}{mm}{%
  \FPclip{#1}{#2}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmin}
% Minimum of two numbers using fp.
%    \begin{macrocode}
\NewDocumentCommand{\dtlmin}{mmm}{%
  \FPmin{#1}{#2}{#3}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlminall}
%\begin{definition}
%\cs{dtlminall}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the minimum in the comma-separated list of
% numbers.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand{\dtlminall}{mm}{%
  \let#1\empty
  \@for\@dtl@tmp:=#2\do{%
    \ifx\empty
     \let#1\@dtl@tmp
    \else
     \FPmin#1{#1}{\@dtl@tmp}%
    \fi
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmax}
% Maximum of two numbers using fp.
%    \begin{macrocode}
\NewDocumentCommand{\dtlmax}{mmm}{%
  \FPmax{#1}{#2}{#3}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmaxall}
%\begin{definition}
%\cs{dtlmaxall}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the maximum in the comma-separated list of
% numbers.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand{\dtlmaxall}{mm}{%
  \let#1\empty
  \@for\@dtl@tmp:=#2\do{%
    \ifx\empty
     \let#1\@dtl@tmp
    \else
     \FPmax#1{#1}{\@dtl@tmp}%
    \fi
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlabs}
% Absolute value using fp.
%    \begin{macrocode}
\NewDocumentCommand{\dtlabs}{mm}{%
  \FPabs{#1}{#2}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlneg}
% Negative of a value using fp.
%    \begin{macrocode}
\NewDocumentCommand{\dtlneg}{mm}{%
  \FPneg{#1}{#2}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmeanforall}
%\begin{definition}
%\cs{dtlmeanforall}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the mean (average) of all the numbers in the comma-separated list of
% numbers.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand{\dtlmeanforall}{mm}{%
  \def#1{0}%
  \count@=0\relax
  \@for\@dtl@tmp:=#2\do{%
    \advance\count@ by \@ne
    \FPadd{#1}{#1}{\@dtl@tmp}%
  }%
  \FPdiv{#1}{#1}{\number\count@}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlvarianceforall}
%\begin{definition}
%\cs{dtlvarianceforall}\oarg{mean}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the variance of all the numbers in the comma-separated list of
% numbers. If the mean has already been calculated it can be
% supplied in the optional argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand{\dtlvarianceforall}{omm}{%
  \IfNoValueTF{#1}%
  {%  
    \def\@dtl@mean{0}%
    \count@=0\relax
    \@for\@dtl@tmp:=#3\do{%
      \advance\count@ by \@ne
      \FPadd{\@dtl@mean}{\@dtl@mean}{\@dtl@tmp}%
    }%
    \FPdiv{\@dtl@mean}{\@dtl@mean}{\number\count@}%
    \def#2{0}%
    \@for\@dtl@tmp:=#3\do{%
      \FPsub{\@dtl@tmp}{\@dtl@tmp}{\@dtl@mean}%
      \FPmul{\@dtl@tmp}{\@dtl@tmp}{\@dtl@tmp}%
      \FPadd{#2}{#2}{\@dtl@tmp}%
    }%
    \FPdiv{#2}{#2}{\number\count@}%
  }%
  {%
    \def\@dtl@mean{#1}%
    \count@=0\relax
    \def#2{0}%
    \@for\@dtl@tmp:=#3\do{%
      \advance\count@ by \@ne
      \FPsub{\@dtl@tmp}{\@dtl@tmp}{\@dtl@mean}%
      \FPmul{\@dtl@tmp}{\@dtl@tmp}{\@dtl@tmp}%
      \FPadd{#2}{#2}{\@dtl@tmp}%
    }%
    \FPdiv{#2}{#2}{\number\count@}%
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsdforall}
%\begin{definition}
%\cs{dtlsdforall}\oarg{mean}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the standard deviation of all the numbers in the comma-separated list of
% numbers. If the mean has already been calculated it can be
% supplied in the optional argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand{\dtlsdforall}{omm}{%
  \IfNoValueTF{#1}%
  {%  
    \def\@dtl@mean{0}%
    \count@=0\relax
    \@for\@dtl@tmp:=#3\do{%
      \advance\count@ by \@ne
      \FPadd{\@dtl@mean}{\@dtl@mean}{\@dtl@tmp}%
    }%
    \FPdiv{\@dtl@mean}{\@dtl@mean}{\number\count@}%
    \def#2{0}%
    \@for\@dtl@tmp:=#3\do{%
      \FPsub{\@dtl@tmp}{\@dtl@tmp}{\@dtl@mean}%
      \FPmul{\@dtl@tmp}{\@dtl@tmp}{\@dtl@tmp}%
      \FPadd{#2}{#2}{\@dtl@tmp}%
    }%
  }%
  {%
    \def\@dtl@mean{#1}%
    \count@=0\relax
    \def#2{0}%
    \@for\@dtl@tmp:=#3\do{%
      \advance\count@ by \@ne
      \FPsub{\@dtl@tmp}{\@dtl@tmp}{\@dtl@mean}%
      \FPmul{\@dtl@tmp}{\@dtl@tmp}{\@dtl@tmp}%
      \FPadd{#2}{#2}{\@dtl@tmp}%
    }%
  }%
  \FPdiv{#2}{#2}{\number\count@}%
  \FProot{#2}{#2}{2}%
}
%    \end{macrocode}
%\end{macro}
%\iffalse
%    \begin{macrocode}
%</datatool-fp.def>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datatool-fp.sty>
%    \end{macrocode}
%\fi
%\section{datatool-fp.sty}
% Definitions of fixed-point commands that use the \sty{fp} package.
% Note that the newer \sty{datatool-l3fp} or \sty{datatool-lua} are
% preferable.
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
%    \end{macrocode}
% Rollback releases:
%    \begin{macrocode}
\DeclareRelease{v2.32}{2019-09-27}{datatool-fp-2019-09-27.sty}
\DeclareCurrentRelease{v3.4.1}{2025-04-25}
%    \end{macrocode}
% Declare package:
%    \begin{macrocode}
\ProvidesPackage{datatool-fp}[2025/04/25 v3.4.1 (NLCT)]
%    \end{macrocode}
% This package is deprecated.
%    \begin{macrocode}
\PackageWarning{datatool-fp}%
{datatool-fp.sty deprecated. Use
\string\usepackage[math=fp]{datatool} instead or
(better still) \string\usepackage{datatool}
}
\RequirePackage[math=fp]{datatool}
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</datatool-fp.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datatool-pgfmath.def>
%    \end{macrocode}
%\fi
%\section{datatool-pgfmath.sty}\label{sec:code:datatool-pgfmath}
% Definitions of fixed-point commands that use the \sty{pgfmath} package.
% Provided for backward-compatibility. The newer \texttt{datatool-l3fp.def} or
% \texttt{datatool-lua.def} are preferable.
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesFile{datatool-pgfmath.def}[2025/04/25 v3.4.1 (NLCT)]
%    \end{macrocode}
% Required packages:
%    \begin{macrocode}
\RequirePackage{pgfrcs,pgfkeys,pgfmath}
%    \end{macrocode}
%
% Old code has only been partially rewritten with \LaTeX3.
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\subsection{Comparison Commands}
%\begin{macro}{\dtlifnumeq}
%\begin{definition}
%\cs{dtlifnumeq}\marg{num1}\marg{num2}\marg{true part}\marg{false
%part}
%\end{definition}
% Does \meta{true part} if \meta{num1}=\meta{num2}, otherwise does
% \meta{false part}. The numbers must use a full stop as the decimal
% character and no number group separator. The \verb|\number0| part
% allows an empty argument to be treated as zero. (\cs{number}
% required to prevent a zero prefix indicating an octal number.)
%\changes{2.12}{2012-11-30}{fixed bug causing premature expansion}
%\changes{2.26}{2016-07-20}{added \cs{number}}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \dtlifnumeq { m m m m }
{
  \def\@dtl@truepart{#3}%
  \def\@dtl@falsepart{#4}%
  \exp_args:Nx \pgfmathifthenelse {#1==#2}%
    {"\exp_not:N \@dtl@truepart"}{"\exp_not:N \@dtl@falsepart"}%
  \pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlifnumlt}
%\begin{definition}
%\cs{dtlifnumlt}\marg{num1}\marg{num2}\marg{true part}\marg{false
%part}
%\end{definition}
% Does \meta{true part} if \meta{num1} \textless \meta{num2}, otherwise does
% \meta{false part}. The numbers must use a full stop as the decimal
% character and no number group separator.
%\changes{2.12}{2012-11-30}{fixed bug causing premature expansion}
%\changes{2.26}{2016-07-20}{added \cs{number}}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \dtlifnumlt { m m m m }
{
  \def\@dtl@truepart{#3}%
  \def\@dtl@falsepart{#4}%
  \exp_args:Nx \pgfmathifthenelse {#1 < #2}%
    {"\exp_not:N \@dtl@truepart"}{"\exp_not:N \@dtl@falsepart"}%
  \pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlifnumgt}
%\begin{definition}
%\cs{dtlifnumgt}\marg{num1}\marg{num2}\marg{true part}\marg{false
%part}
%\end{definition}
% Does \meta{true part} if \meta{num1} \textgreater \meta{num2}, otherwise does
% \meta{false part}. The numbers must use a full stop as the decimal
% character and no number group separator.
%\changes{2.12}{2012-11-30}{fixed bug causing premature expansion}
%\changes{2.26}{2016-07-20}{added \cs{number}}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \dtlifnumgt { m m m m }
{
  \def\@dtl@truepart{#3}%
  \def\@dtl@falsepart{#4}%
  \exp_args:Nx \pgfmathifthenelse {#1 > #2}%
    {"\exp_not:N \@dtl@truepart"}{"\exp_not:N \@dtl@falsepart"}%
  \pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlifnumopenbetween}
%\begin{definition}
%\cs{dtlifnumopenbetween}\marg{num}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% Determines if \meta{min} $<$ \meta{num} $<$ \meta{max} where
% all numerical arguments are in standard fixed point notation.
%\changes{2.12}{2012-11-30}{fixed bug causing premature expansion}
%\changes{2.26}{2016-07-20}{added \cs{number}}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \dtlifnumopenbetween { m m m m m }
{
  \dtlifnumlt { #1 } { #3 }
  { \dtlifnumgt { #1 } { #2 } { #4 } { #5 } }
  { #5 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlifnumclosedbetween}
%\begin{definition}
%\cs{dtlifnumclosedbetween}\marg{num}\marg{min}\marg{max}\marg{true part}\marg{false part}
%\end{definition}
% Determines if \meta{min} $\leq$ \meta{num} $\leq$ \meta{max} where
% all numerical arguments are in standard fixed point notation.
%\changes{2.12}{2012-11-30}{fixed bug causing premature expansion}
%\changes{2.26}{2016-07-20}{added \cs{number}}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \dtlifnumclosedbetween { m m m m m }
{
  \dtlifnumlt { #1 } { #2 } 
  { #5 }
  { \dtlifnumgt { #1 } { #3 } { #5 } { #4 } }
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Functions}
%\begin{macro}{\dtladd}
% Adds two numbers using PGF math engine.
%    \begin{macrocode}
\NewDocumentCommand \dtladd { m m m }
{
  \pgfmathadd{#2}{#3}%
  \let#1\pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtladdall}
%\begin{definition}
%\cs{dtladdall}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the sum of all the numbers in the comma-separated list of
% numbers.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \dtladdall { m m }
{
  \tl_set:Nn \pgfmathresult { 0 }
  \exp_args:No \clist_map_inline:nn { #2 } 
  {
    \pgfmathadd { \pgfmathresult } { ##1 }
  }
  \tl_set_eq:NN #1 \pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsub}
% Subtracts two numbers using PGF math engine.
%    \begin{macrocode}
\NewDocumentCommand \dtlsub { m m m }
{
  \pgfmathsubtract{#2}{#3}%
  \let#1\pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmul}
% Multiplies two numbers using PGF math engine.
%    \begin{macrocode}
\NewDocumentCommand \dtlmul { m m m }
{%
  \pgfmathmultiply{#2}{#3}%
  \let#1\pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldiv}
% Divides two numbers using PGF math engine.
%    \begin{macrocode}
\NewDocumentCommand \dtldiv { m m m }
{%
  \pgfmathdivide{#2}{#3}%
  \let#1\pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsqrt}
% Root using PGF math engine.
%    \begin{macrocode}
\NewDocumentCommand \dtlsqrt { m m }
{%
  \pgfmathsqrt{#2}%
  \let#1\pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlroot}
% Root using PGF math engine.
%\changes{3.0}{2025-03-03}{bug fix: added missing third argument}
%    \begin{macrocode}
\NewDocumentCommand \dtlroot { m m m }
{
  \exp_args:Nx \tl_if_eq:nnTF { #3 } { 2 }
  {
    \exp_args:Nx \pgfmathsqrt { #2 }
    \let#1\pgfmathresult
  }
  {
    \exp_args:Nxx \pgfmathpow { #2 } { 1 / ( #3 ) }
    \let#1\pgfmathresult
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlround}
% Rounds using PGF math engine.
%\changes{2.26}{2016-07-20}{fixed bug cause by rounding}
%    \begin{macrocode}
\NewDocumentCommand{\dtlround}{mmm}{%
  \ifnum#3=0\relax
    \exp_args:Nx \pgfmathparse { int( round ( #2 ) ) }
    \let#1\pgfmathresult
  \else
    \exp_args:Nx \pgfmathparse { int ( 10 \exp_not:N ^ #3 )}
    \let\dtl@tmpshift\pgfmathresult
%    \end{macrocode}
%Need to be careful not to trigger the dimension too large error,
%so this is a bit convoluted.
%    \begin{macrocode}
    \exp_args:Nx \pgfmathparse { int ( floor ( #2 ) ) }%
    \let\dtl@int@round\pgfmathresult
    \exp_args:Nx \pgfmathparse
      { int ( round ( ( #2 - \dtl@int@round ) * \dtl@tmpshift ) ) }
%    \end{macrocode}
%\changes{2.26}{2016-07-20}{fixed bug caused by rounding errors}
% This bit is awkward because simply dividing by multiples
% of 10 in pgfmath can cause rounding errors, so need to
% employ another method.
%    \begin{macrocode}
    \@dtl@tmpcount=0\relax
    \expandafter\@dtl@countdigits\pgfmathresult.\relax
    \advance\@dtl@tmpcount by -#3\relax
    \def\@dtl@intpart{}%
    \def\@dtl@fracpart{}%
    \expandafter\@dtl@gatherintfrac\pgfmathresult\relax
    \tl_if_empty:NT \@dtl@intpart { \tl_set:Nn \@dtl@intpart { 0 } }
    \edef\@dtl@intpart{\number\numexpr\dtl@int@round + \@dtl@intpart}%
    \edef#1{\@dtl@intpart.\@dtl@fracpart}%
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@gatherintfrac}
%\changes{2.26}{2016-07-20}{new}
%    \begin{macrocode}
\newcommand*{\@dtl@gatherintfrac}[1]{%
  \ifx\relax#1\relax
  \else
    \advance\@dtl@tmpcount by -1\relax
    \ifnum\@dtl@tmpcount<0\relax
      \edef\@dtl@fracpart{\@dtl@fracpart#1}%
    \else
      \edef\@dtl@intpart{\@dtl@intpart#1}%
    \fi
    \expandafter\@dtl@gatherintfrac
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtltrunc}
% Truncates using PGF math engine.
% (Third argument is the number of digits.)
%\changes{2.26}{2016-07-20}{new}
%This suffers from the same problems as \cs{dtlround}. Can
% cause dimension too large error or rounding errors.
%    \begin{macrocode}
\NewDocumentCommand \dtltrunc { m m m }
{
  \ifnum#3=0\relax
    \exp_args:Nx \pgfmathparse { int ( floor ( #2 ) ) }%
    \let#1\pgfmathresult
  \else
    \exp_args:Nx \pgfmathparse { int ( 10 \exp_not:N ^ #3 ) }
    \let\dtl@tmpshift\pgfmathresult
%    \end{macrocode}
%Need to be careful not to trigger the dimension too large error,
%so this is a bit convoluted.
%    \begin{macrocode}
    \exp_args:Nx \pgfmathparse { int ( floor ( #2 ) ) }
    \let\dtl@int@trunc\pgfmathresult
    \exp_args:Nx \pgfmathparse
     { int ( floor ( ( #2 - \dtl@int@trunc ) * \dtl@tmpshift ) ) }
%    \end{macrocode}
%\changes{2.26}{2016-07-20}{fixed bug caused by rounding errors}
% This bit is awkward because simply dividing by multiples
% of 10 in pgfmath can cause rounding errors, so need to
% employ another method.
%    \begin{macrocode}
    \@dtl@tmpcount=0\relax
    \expandafter\@dtl@countdigits\pgfmathresult.\relax
    \advance\@dtl@tmpcount by -#3\relax
    \def\@dtl@intpart{}%
    \def\@dtl@fracpart{}%
    \expandafter\@dtl@gatherintfrac\pgfmathresult\relax
    \tl_if_empty:NT \@dtl@intpart { \tl_set:Nn \@dtl@intpart { 0 } }
    \edef\@dtl@intpart{\number\numexpr\dtl@int@trunc
      + \@dtl@intpart}%
    \edef#1{\@dtl@intpart.\@dtl@fracpart}%
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlclip}
% There isn't a clip in pgfmath so use \LaTeX3 instead.
%    \begin{macrocode}
\NewDocumentCommand \dtlclip { m m } 
{
  \fp_set:Nn { \l__datatool_tmpa_fp } { #2 }
  \tl_set:Nx #1 { \fp_use:N { \l__datatool_tmpa_fp } }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmin}
% Minimum of two numbers using PGF math engine.
%    \begin{macrocode}
\NewDocumentCommand \dtlmin { m m m }
{
  \exp_args:Nxx \pgfmathmin { #2 } { #3 }
  \let#1\pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlminall}
%\begin{definition}
%\cs{dtlminall}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the minimum in the comma-separated list of
% numbers.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \dtlminall { m m }
{
  \exp_args:Nx \pgfmathmin { #2 } { }
  \let#1\pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmax}
% Maximum of two numbers using PGF math engine.
%    \begin{macrocode}
\NewDocumentCommand \dtlmax { m m m }
{
  \exp_args:Nx \pgfmathmax { #2 } { #3 }
  \let#1\pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmaxall}
%\begin{definition}
%\cs{dtlmaxall}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the maximum in the comma-separated list of
% numbers.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \dtlmaxall { m m }
{
  \exp_args:Nx \pgfmathmax { #2 } { }
  \let#1\pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlabs}
% Absolute value using PGF math engine.
%    \begin{macrocode}
\NewDocumentCommand \dtlabs { m m }
{
  \exp_args:Nx \pgfmathabs { #2 }
  \let#1\pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlneg}
% Negative of a value using PGF math engine.
%    \begin{macrocode}
\NewDocumentCommand \dtlneg { m m }
{
  \exp_args:Nx \pgfmathneg { #2 }
  \let#1\pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlmeanforall}
%\begin{definition}
%\cs{dtlmeanforall}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the mean of all the numbers in the comma-separated list of
% numbers.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \dtlmeanforall { m m }
{
  \tl_set:Nn \pgfmathresult { 0 }
  \int_zero:N \l__datatool_count_int
  \exp_args:No \clist_map_inline:nn { #2 }
  {
    \int_incr:N \l__datatool_count_int
    \exp_args:Nxx \pgfmathadd { \pgfmathresult } { ##1 }
  }
  \exp_args:Nxx \pgfmathdivide
    { \pgfmathresult }
    { \int_use:N \l__datatool_count_int }
  \tl_set_eq:NN #1 \pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlvarianceforall}
%\begin{definition}
%\cs{dtlvarianceforall}\oarg{mean}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the variance of all the numbers in the comma-separated list of
% numbers. If the mean has already been computed it can be supplied
% in the optional argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand {\dtlvarianceforall} { o m m } 
{%
  \IfNoValueTF { #1 }
  {
    \tl_set:Nn \pgfmathresult { 0 }
    \int_zero:N \l__datatool_count_int
    \exp_args:No \clist_map_inline:nn { #3 } 
    {
      \int_incr:N \l__datatool_count_int
      \exp_args:Nxx \pgfmathadd { \pgfmathresult } { ##1 }
    }
    \exp_args:Nxx \pgfmathdivide
      { \pgfmathresult }
      { \int_use:N \l__datatool_count_int }
    \tl_set_eq:NN \@dtl@mean \pgfmathresult
    \tl_set:Nn #2 { 0 }
    \exp_args:No \clist_map_inline:nn { #3 } 
    {
      \exp_args:Nxx \pgfmathsubtract { ##1 } { \@dtl@mean }
      \exp_args:Nxx \pgfmathmultiply { \pgfmathresult } { \pgfmathresult }
      \exp_args:Nxx \pgfmathadd { #2 } { \pgfmathresult }
      \tl_set_eq:NN #2 \pgfmathresult
    }
  }
  {
    \tl_set:Nx \@dtl@mean { #1 }
    \int_zero:N \l__datatool_count_int
    \tl_set:Nn #2 { 0 }
    \exp_args:No \clist_map_inline:nn { #3 } 
    {
      \int_incr:N \l__datatool_count_int
      \exp_args:Nxx \pgfmathsubtract { ##1 } { \@dtl@mean }
      \exp_args:Nxx \pgfmathmultiply
       { \pgfmathresult } { \pgfmathresult }
      \exp_args:Nxx \pgfmathadd { #2 } { \pgfmathresult }
      \tl_set_eq:NN #2 \pgfmathresult
    }
  }
  \exp_args:Nxx \pgfmathdivide
    { #2 }
    { \int_use:N \l__datatool_count_int }
  \tl_set_eq:NN #2 \pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsdforall}
%\begin{definition}
%\cs{dtlsdforall}\oarg{mean}\marg{cs}\marg{num list}
%\end{definition}
% Defines \meta{cs} to the standard deviation of all the numbers in the comma-separated list of
% numbers. If the mean has already been computed it can be supplied
% in the optional argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand{\dtlsdforall} { o m m }
{%
  \IfNoValueTF{#1}%
  {%
    \tl_set:Nn \pgfmathresult { 0 }
    \int_zero:N \l__datatool_count_int
    \exp_args:No \clist_map_inline:nn { #3 } 
    {
      \int_incr:N \l__datatool_count_int
      \exp_args:Nxx \pgfmathadd { \pgfmathresult } { ##1 }
    }
    \exp_args:Nxx \pgfmathdivide
      { \pgfmathresult }
      { \int_use:N \l__datatool_count_int }
    \tl_set_eq:NN \@dtl@mean \pgfmathresult
    \tl_set:Nn #2 { 0 }
    \exp_args:No \clist_map_inline:nn { #3 } 
    {
      \exp_args:Nxx \pgfmathsubtract { ##1 } { \@dtl@mean }
      \exp_args:Nxx \pgfmathmultiply { \pgfmathresult } { \pgfmathresult }
      \exp_args:Nxx \pgfmathadd { #2 } { \pgfmathresult }
      \tl_set_eq:NN #2 \pgfmathresult
    }
  }
  {
    \tl_set:Nn \@dtl@mean { #1 }
    \int_zero:N \l__datatool_count_int
    \tl_set:Nn #2 { 0 }
    \exp_args:No \clist_map_inline:nn { #3 } 
    {
      \int_incr:N \l__datatool_count_int
      \exp_args:Nxx \pgfmathsubtract { ##1 } { \@dtl@mean }
      \exp_args:Nxx \pgfmathmultiply
        { \pgfmathresult } { \pgfmathresult }
      \exp_args:Nxx \pgfmathadd { #2 } { \pgfmathresult }
      \tl_set_eq:NN #2 \pgfmathresult
    }
  }
  \exp_args:Nxx \pgfmathdivide
     { #2 } { \int_use:N \l__datatool_count_int }
  \exp_args:Nx \pgfmathsqrt { \pgfmathresult }
  \tl_set_eq:NN #2 \pgfmathresult
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\iffalse
%    \begin{macrocode}
%</datatool-pgfmath.def>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datatool-pgfmath.sty>
%    \end{macrocode}
%\fi
%\section{datatool-pgfmath.sty}
% Definitions of fixed-point commands that use the \sty{pgfmath} package.
% Note that the newer \sty{datatool-l3fp} or \sty{datatool-lua} are
% preferable.
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
%    \end{macrocode}
% Rollback releases:
%    \begin{macrocode}
\DeclareRelease{v2.32}{2019-09-27}{datatool-pgfmath-2019-09-27.sty}
\DeclareCurrentRelease{v3.4.1}{2025-04-25}
%    \end{macrocode}
% Declare package:
%    \begin{macrocode}
\ProvidesPackage{datatool-pgfmath}[2025/04/25 v3.4.1 (NLCT)]
%    \end{macrocode}
% This package is deprecated.
%    \begin{macrocode}
\PackageWarning{datatool-pgfmath}%
{datatool-pgfmath.sty deprecated. Use
\string\usepackage[math=pgfmath]{datatool} instead or
(better still) \string\usepackage{datatool}
}
\RequirePackage[math=pgfmath]{datatool}
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</datatool-pgfmath.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datatool.sty>
%    \end{macrocode}
%\fi
%\section{datatool.sty}
%\label{sec:code:datatool}
%\subsection{Package Declaration}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
%    \end{macrocode}
% Rollback releases:
%    \begin{macrocode}
\DeclareRelease{v2.32}{2019-09-27}{datatool-2019-09-27.sty}
\DeclareCurrentRelease{v3.4.1}{2025-04-25}
%    \end{macrocode}
% Declare package:
%    \begin{macrocode}
\ProvidesPackage{datatool}[2025/04/25 v3.4.1 (NLCT)]
%    \end{macrocode}
%\changes{3.0}{2025-03-03}{dropped \sty{xkeyval}}
% Version 3.0: no longer using \sty{xkeyval}.
% Load required packages:
%    \begin{macrocode}
\RequirePackage{ifthen}
\RequirePackage{xfor}
\RequirePackage{etoolbox}
\RequirePackage{tracklang}
%    \end{macrocode}
%\changes{2.0}{2009 February 27}{added etex as a required package}
%\changes{2.23}{2015-07-11}{removed etex as a required package}
%
%Version 3.0: partial rewrite using \LaTeX3\ (mainly for the CSV file
%parsing) so there's a mix of new and old style commands.
%This rewrite needs to take into account the format 
%of dbtex files, which can be read and written by datatooltk.
%There are three file format versions: 1, 2
%and 3. The first two are almost identical. Version 2 has the final
%line that defines \cs{dtllastloadeddb}. The internal commands are
%defined to allow a fast load that doesn't require any parsing to
%to split on separators or to determine data types. This means that
%the internal structure of the database needs to remain the same.
%Also, it's not possible to enable \LaTeX3 syntax in the dbtex file
%because the category code change will affect the data (the space
%character in particular). The version 3 file format is new to
%datatool v3.0. An undefined command on loading a dbtex will likely
%indicate that a newer dbtex is being used with an old datatool
%version and so should be considered incompatible.
%
%\subsection{Package Options}
%
%    \begin{macrocode}
\ExplSyntaxOn
\cs_new:Nn \__datatool_char_to_hex:nN 
 {
   \tl_set:Nx #2 { \int_to_hex:n { `#1 } }
 }
%    \end{macrocode}
%
%\begin{macro}{\@dtl@separator}
% The data separator character (comma by default) is stored in
% \cs{@dtl@separator}.
% This is the separator used in external data files, not in the
% \LaTeX\ code, which always uses a comma separator.
%    \begin{macrocode}
\newcommand*{\@dtl@separator}{,}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLsetseparator}
%\begin{definition}
%\cs{DTLsetseparator}\marg{char}
%\end{definition}
% The sets \cs{@dtl@separator}, and constructs the relevant macros 
% that require this character to be hardcoded into their definition.
%\changes{3.0}{2025-03-03}{changed to new document command and detokenize}
%    \begin{macrocode}
\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
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\begingroup
\catcode`\^^I12
%    \end{macrocode}
%\begin{macro}{\DTLsettabseparator}
%\cs{DTLsettabseparator} makes it easier to set a tab separator.
%\changes{2.10}{2012-07-18}{changed tab character to 
%\texttt{\textasciicircum\textasciicircum I}}
%    \begin{macrocode}
\gdef\DTLsettabseparator{%
  \catcode`\^^I12
  \DTLsetseparator{^^I}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLmaketabspace}
%\changes{2.23}{2015-07-11}{restores tab catcode to 10}
%    \begin{macrocode}
\gdef\DTLmaketabspace{%
  \catcode`\^^I10\relax
}
\endgroup
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@delimiter}
% The data delimiter character (double quote by default) is stored 
% in \cs{@dtl@delimiter}. This is used in external data files, not 
% in the \LaTeX\ code.
%    \begin{macrocode}
\newcommand{\@dtl@delimiter}{"}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
% This may have already been defined if \sty{datatool} was loaded
% first.
%    \begin{macrocode}
\tl_clear_new:N \l__datatool_tmpa_tl
%    \end{macrocode}
%
%\begin{macro}{\DTLsetdelimiter}
%\begin{definition}
%\cs{DTLsetdelimiter}\marg{char}
%\end{definition}
% This sets the delimiter.
%\changes{3.0}{2025-03-03}{changed to new document command and set up reg
%exp}
%    \begin{macrocode}
\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

%    \end{macrocode}
%Initialise:
%    \begin{macrocode}
\DTLsetdelimiter{"}
%    \end{macrocode}
%\end{macro}
%
%Default database name.
%    \begin{macrocode}
\tl_new:N \l__datatool_default_dbname_tl
\tl_set:Nn \l__datatool_default_dbname_tl { untitled }
%    \end{macrocode}
%Determine whether to use the local or global function.
%    \begin{macrocode}
\bool_new:N \l__datatool_db_global_bool
\bool_set_true:N \l__datatool_db_global_bool
%    \end{macrocode}
%
%Determine whether or not to trim spaces around new entries.
%    \begin{macrocode}
\bool_new:N \l__datatool_new_element_trim_bool
\bool_set_true:N \l__datatool_new_element_trim_bool
%    \end{macrocode}
%
%Version 3.0: provide boolean to check on the current new value
%expansion setting:
%    \begin{macrocode}
\bool_new:N \l__datatool_new_element_expand_bool
%    \end{macrocode}
%
%Determine whether or not to expand values before adding them to the
%database.
%\begin{macro}{\dtlexpandnewvalue}
% Expand new value before adding to database
%    \begin{macrocode}
\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}
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlnoexpandnewvalue}
% Don't expand new value before adding to database
%    \begin{macrocode}
\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}
    }
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtl@setnewvalue}
% Do this by default. This will define \cs{@dtl@setnewvalue}.
%    \begin{macrocode}
\dtlnoexpandnewvalue
%    \end{macrocode}
%\end{macro}
%Note that \verb|\__datatool_process_new_value:n| should be used to
%preprocess values. That uses \cs{dtl@setnewvalue} to follow the
%trim and expansion settings but then checks the store datum
%boolean.
%TODO remove \cs{@dtl@setnewvalue} and replace with another boolean?
%
%Determine whether or not to store values in the database in datum
%format.
%    \begin{macrocode}
\bool_new:N \l__datatool_db_store_datum_bool
%    \end{macrocode}
%
% Define options. The default-name value is expanded for consistency
% because it will be expanded if used as a package option so it
% should therefore also expand when used in \cs{DTLsetup}.
%    \begin{macrocode}
\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
%    \end{macrocode}
%
%If {datatool-base} may have already been loaded by another package,
%in which case the options need processing now.
%    \begin{macrocode}
\IfPackageLoadedTF{datatool-base}
{
  \ProcessKeyOptions[datatool]
}
{
%    \end{macrocode}
%The \sty{datatool-base} package hasn't been loaded, so pass all options
%to that.
%    \begin{macrocode}
 \DeclareOption*{\PassOptionsToPackage{\CurrentOption}{datatool-base}}
 \ProcessOptions
%    \end{macrocode}
%
% Load base package:
%    \begin{macrocode}
  \RequirePackage{datatool-base}
}
%    \end{macrocode}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%Private variables.
%
%Token lists:
%    \begin{macrocode}
\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
%    \end{macrocode}
%Numeric values that may be set to null to need a token list
%variable.
%    \begin{macrocode}
\tl_new:N \l__datatool_item_col_tl
%    \end{macrocode}
%Replaces \cs{dtl@rowidx}:
%    \begin{macrocode}
\tl_new:N \l__datatool_row_idx_tl
%    \end{macrocode}
%Integers:
%    \begin{macrocode}
\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
%    \end{macrocode}
%Sequences:
%    \begin{macrocode}
\seq_new:N \l__datatool_column_indexes_seq
\seq_new:N \l__datatool_column_keys_seq
%    \end{macrocode}
%Floating point:
%    \begin{macrocode}
\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
%    \end{macrocode}
% Temporary list:
%    \begin{macrocode}
\clist_new:N \l__datatool_tmpa_clist
%    \end{macrocode}
%
%\begin{macro}{\DTLpar}
% Many of the commands used by this package are short commands.
% This means that you can't use \cs{par}
% in the data. This command needs to be robust so it doesn't get
% expanded when written to a file. We also can't just use a synonym
% for \cs{@@par} because it may be used in a context where \cs{par}
% has a different meaning to \cs{@@par}.
%\changes{2.14}{2013-06-28}{changed to \cs{let}}
%\changes{2.18}{2013-09-06}{changed back to a robust command}
%    \begin{macrocode}
\newrobustcmd{\DTLpar}{\par}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLaction}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLaction}\oarg{options}\marg{action}
%\end{definition}
%General purpose action command to avoid typing long command names.
%Note that since some commands may only have a local effect, it's
%not possible to include grouping in \cs{DTLaction} (although some
%of the underlying commands may introduce grouping). Therefore the
%options need to be reset at the start, and so there's no provision
%for use of those options in \cs{DTLsetup}.
%    \begin{macrocode}
\NewDocumentCommand \DTLaction { o m }
{
%    \end{macrocode}
%Initialise settings.
%Column index:
%    \begin{macrocode}
  \int_zero:N \l__datatool_action_column_int
  \int_zero:N \l__datatool_action_column_ii_int
%    \end{macrocode}
%Column key:
%    \begin{macrocode}
  \tl_clear:N \l__datatool_action_key_tl
  \tl_clear:N \l__datatool_action_key_ii_tl
%    \end{macrocode}
% Data type:
%    \begin{macrocode}
  \int_set_eq:NN
    \l__datatool_action_type_int
    \c_datatool_unknown_int 
%    \end{macrocode}
%Row index:
%    \begin{macrocode}
  \int_zero:N \l__datatool_action_row_int
  \int_zero:N \l__datatool_action_row_ii_int
%    \end{macrocode}
%Value:
%    \begin{macrocode}
  \tl_set:Nn \l__datatool_action_value_tl { \q_no_value }
%    \end{macrocode}
%List settings:
%    \begin{macrocode}
  \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 
%    \end{macrocode}
%Return formatting:
%    \begin{macrocode}
  \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 }
%    \end{macrocode}
%Return Values:
%    \begin{macrocode}
  \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 } }
%    \end{macrocode}
%Get database name for actions that require a single name:
%    \begin{macrocode}
  \seq_if_empty:NTF \l__datatool_action_names_seq 
   {
%    \end{macrocode}
% Name not set so use default name.
%    \begin{macrocode}
     \tl_set_eq:NN
       \l__datatool_action_name_tl
       \l__datatool_default_dbname_tl
   }
   {
%    \end{macrocode}
% Set name to first one in the list.
%    \begin{macrocode}
     \tl_set:Nx \l__datatool_action_name_tl
      {
        \seq_item:Nn \l__datatool_action_names_seq
          { \c_one_int }
      }
   }
%    \end{macrocode}
%Save action name:
%    \begin{macrocode}
  \tl_set:Nx \l__datatool_action_tl { \tl_trim_spaces:n { #2 } }
%    \end{macrocode}
%Do action:
%    \begin{macrocode}
  \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 }
  }
%    \end{macrocode}
%Assign return values to provided token list variables, if set.
%    \begin{macrocode}
  \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
   }
}
%    \end{macrocode}
%\end{macro}
%
%Internal variables for \cs{DTLaction}:
%    \begin{macrocode}
\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
%    \end{macrocode}
%Scratch variables.
%    \begin{macrocode}
\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
%    \end{macrocode}
%Define keys. (These can only be set in the optional argument of
%\cs{DTLaction}.)
%    \begin{macrocode}
\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 
    },
%    \end{macrocode}
%Synonym:
%    \begin{macrocode}
  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 
    },
%    \end{macrocode}
%Synonym:
%    \begin{macrocode}
  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 
    },
%    \end{macrocode}
%Synonym:
%    \begin{macrocode}
  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 ,
}
%    \end{macrocode}
% Sub keys for datum option:
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%Define keys:
%    \begin{macrocode}
\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 , 
}
%    \end{macrocode}
%Return value(s):
%    \begin{macrocode}
\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
%    \end{macrocode}
%
% The datum option only governs the secondary return results.
% The primary return result isn't converted.
%
% Secondary return result is a string:
%    \begin{macrocode}
\cs_new:Nn \__datatool_put_return_action_string:nn
{
  \tl_if_head_eq_meaning:nNTF
    { #2 } \__datatool_datum:w
  {
%    \end{macrocode}
%In weird format. Needs conversion.
%    \begin{macrocode}
    \prop_put:Nnx \l__datatool_action_return_prop
     { #1 } { #2 }
  }
  {
    \tl_if_head_eq_meaning:nNTF
      { #2 }  \__datatool_datum:nnnn
    {
%    \end{macrocode}
%Already in datum format. Leave it as is.
%    \begin{macrocode}
       \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 }
%    \end{macrocode}
%Secondary return result is an integer:
%    \begin{macrocode}
\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
        {
%    \end{macrocode}
%In weird format. Needs conversion.
%    \begin{macrocode}
          \prop_put:Nnx \l__datatool_action_return_prop
           { #1 } { #2 }
        }
        {
          \tl_if_head_eq_meaning:nNTF
            { #2 } \__datatool_datum:nnnn
           {
%    \end{macrocode}
%Already in datum format. Leave it as is.
%    \begin{macrocode}
             \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 }
%    \end{macrocode}
% Secondary return result is a decimal.
%    \begin{macrocode}
\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 }
%    \end{macrocode}
% Secondary result should be parsed if applicable.
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%
% Assign secondary return properties using column keys as name and value
% obtained from corresponding element in the given row specs.
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%
%Add to options sequence unless already present:
%    \begin{macrocode}
\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 }
   }
}
%    \end{macrocode}
%Tests if the given option has been added:
%    \begin{macrocode}
\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 } 
}
%    \end{macrocode}
%
%\begin{macro}{\DTLifaction}
%\begin{definition}
%\cs{DTLifaction}\marg{prop-key}\marg{true}\marg{false}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\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 }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLget}
%Get the return value. The first argument should either be empty or
%a property key. The second argument should be a token list in which
%to store the return value.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\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 }
  } 
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\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
    }
  }
}
%    \end{macrocode}
%    \begin{macrocode}
\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
  }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLuse}
%\changes{3.0}{2025-03-03}{new}
%Use the return value. As \cs{DTLget} but typesets the result.
%Partially expandable.
%    \begin{macrocode}
\newcommand* \DTLuse [ 1 ]
{
  \tl_if_empty:nTF { #1 }
  { \l__datatool_action_return_tl }
  { \@dtl@useresult { #1 } }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@useresult}
%Robust.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\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
     }
  } 
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_action_noop:Nn
{
  \PackageError {datatool}
  {
    Action ~ `#2' ~ may ~ only ~ be ~ used ~ within ~ \token_to_str:N #1
  }
  {}
}
%    \end{macrocode}
%
%    \begin{macrocode}
\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 } { }
}
%    \end{macrocode}
%
%Commands provided for checking common syntax problems for actions.
%The true argument is done if successful. The false argument is done
%if an error occurs.
%
%    \begin{macrocode}
\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 } { }
}
%    \end{macrocode}
% For actions where the database name should be in \cs{dtldbname}
% not specified by the name key.
%    \begin{macrocode}
\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
        }
        { }
     }
  }
}
%    \end{macrocode}
%Gathers placeholder=colkey assignment and sets temporary sequence
%with each element in the form
%\marg{cs}\marg{col-key}\marg{col-idx}\marg{col-type}
%    \begin{macrocode}
\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
}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_action_placeholder_cskey:nn
{
  \tl_if_single_token:nTF { #1 }
   {
     \tl_set:Nn #1 { \c_datatool_nullvalue_tl }
%    \end{macrocode}
%Get the column index:
%    \begin{macrocode}
     \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>
      }
   }
}
%    \end{macrocode}
%Perform assignments on given row specs
%    \begin{macrocode}
\cs_new:Nn \__datatool_do_action_assignments:n
{
  \seq_map_inline:Nn \l__datatool_action_tmp_data_seq
   {
%    \end{macrocode}
%Each element in the form: \marg{cs}\marg{col-key}\marg{col-idx}\marg{col-type}
%    \begin{macrocode}
     \__datatool_do_action_row_assign:nNnnn { #1 } ##1
   }
}
%    \end{macrocode}
%Perform assignment.
%    \begin{macrocode}
\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
      }
   }
}
%    \end{macrocode}
%
% Generate error if both key and column found with given help
% message on error.
%    \begin{macrocode}
\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 }
  }
}
%    \end{macrocode}
% Require `value'.
%    \begin{macrocode}
\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={}
      }
   }
}
%    \end{macrocode}
%
%Arguments: \meta{row int var}\marg{not set}\marg{true}\marg{false}
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%
%Arguments: \marg{key setting name}\meta{key str var}\marg{column
%setting name}\meta{col int var}\marg{not set}\marg{true}\marg{false}
%
% One column may be identified:
% `key' or `column' allowed but both not permitted. A negative
% column index is an error and will reset the index to zero. 
% The \meta{no set} argument is done if neither set.
%    \begin{macrocode}
\cs_new:Nn \__datatool_optional_key_xor_column:nNnNnTF
{
%    \end{macrocode}
% Check the column index hasn't been set to a negative number.
%    \begin{macrocode}
  \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
%    \end{macrocode}
% Negative column index is a failure (do false).
%    \begin{macrocode}
   #7
  }
  {
    \tl_if_empty:NTF #2
    {
%    \end{macrocode}
% No key has been supplied. Has a column index been provided?
%    \begin{macrocode}
       \int_if_zero:nTF #4
        {
%    \end{macrocode}
% Neither provided. Do \meta{no set} argument.
%    \begin{macrocode}
         #5
        }
        {
%    \end{macrocode}
% A column index has been provided. Success (but no guarantee the
% column is defined). Do true argument.
%    \begin{macrocode}
          #6
        }
      }
    {
%    \end{macrocode}
% A key has been supplied. Has a column index been provided?
%    \begin{macrocode}
       \int_if_zero:nTF #4
        { 
%    \end{macrocode}
% A key has been supplied but no column index. Success (but no
% guarantee the key is valid). Do true argument.
%    \begin{macrocode}
          #6
        }
        {
%    \end{macrocode}
% A column index has been provided as well. Failure. Do false
% argument.
%    \begin{macrocode}
         \__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
        }
    }
  }
}
%    \end{macrocode}
%
%Arguments: \marg{not set}\marg{true}\marg{false}
%
% One column may be identified:
% `key' or `column' allowed but both not permitted. A negative
% column index is an error and will reset the index to zero. 
% The first argument is done if neither set.
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%
% Second column may be identified:
% `key2' or `column2' allowed but both not permitted. A negative
% column index is an error and will reset the index to zero. 
% The first argument is done if neither set.
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%
%Arguments: \marg{key setting name}\meta{key str var}\marg{column
%setting name}\meta{col int var}\marg{not set}\marg{key undef}\marg{true}\marg{false}
%
% One column may be identified:
% `key' or `column' optional but both not permitted.
% If the key is provided, obtain the index.
% The \meta{not set} argument is done if no key or column index provided.
% The \meta{key undef} argument is done if no column matches the given key.
%    \begin{macrocode}
\cs_new:Nn \__datatool_optional_key_xor_column_get_column:nNnNnnTF
{
  \__datatool_optional_key_xor_column:nNnNnTF 
   { #1 } { #2 } { #3 } { #4 }
   { 
%    \end{macrocode}
% No key or column index provided. Do \meta{not set} argument.
%    \begin{macrocode}
    #5
   }
   {
%    \end{macrocode}
% A column has been identified either by key or column index.
%    \begin{macrocode}
     \int_if_zero:nTF #4
      {
%    \end{macrocode}
% No column index so it must have been identified by key. Get the
% corresponding index.
%    \begin{macrocode}
        \tl_if_exist:cTF 
           {  
             dtl @ ci 
             @ \l__datatool_action_name_tl
             @ #2
           }
         {
%    \end{macrocode}
% Column index found. Success. Do true argument.
%    \begin{macrocode}
           \exp_args:NNv \int_set:Nn
             #4
            {  
              dtl @ ci 
              @ \l__datatool_action_name_tl
              @ #2
            }
           #7
         }
         {
%    \end{macrocode}
% No match on key. Do \meta{key undef} argument.
%    \begin{macrocode}
           #6
         }
      }
      { 
%    \end{macrocode}
% Column index was provided (but no guarantee it's within range).
% Success. Do true argument.
%    \begin{macrocode}
        #7
      }
   }
   {
%    \end{macrocode}
% Invalid key or column setting (negative index or both set).
% Failure. Do false argument.
%    \begin{macrocode}
     #8
   }
}
%    \end{macrocode}
%
%Arguments: \marg{not set}\marg{key undef}\marg{true}\marg{false}
%
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_optional_key_xor_column:TF 
{
  \__datatool_optional_key_xor_column:nTF { #1 } { #1 } { #2 }
}
%    \end{macrocode}
%
% Second column may be identified:
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_optional_key_ii_xor_column_ii:TF 
{
  \__datatool_optional_key_ii_xor_column_ii:nTF { #1 } { #1 } { #2 }
}
%    \end{macrocode}
%
%Arguments: \marg{key setting name}\meta{key str var}\marg{column
%setting name}\meta{col int var}\marg{not set}\marg{true}\marg{false}
%
% `key' or `column' optional but both not permitted.
% If the key is provided, obtain the index.
% The \meta{not set} argument is done if no key or column index provided.
% Require column existence if key or column provided.
%    \begin{macrocode}
\cs_new:Nn \__datatool_optional_key_xor_column_get_column:nNnNnTF
{
  \__datatool_optional_key_xor_column_get_column:nNnNnnTF
   { #1 } { #2 } { #3 } { #4 }
   { #5 }
   {
%    \end{macrocode}
%Column key not valid. Failure. Do false.
%    \begin{macrocode}
     \__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
   }
   {
%    \end{macrocode}
%Positive column index provided or obtained from the key.
%Check that column index is less than the total number of columns
%for the database.
%    \begin{macrocode}
     \int_compare:nNnTF
      { #4 }
       >
      { 
        \int_use:c
          { dtlcols@ \l__datatool_action_name_tl }
      }
     {
%    \end{macrocode}
% Out of range. Failure. Do false. (If the key was originally
% provided, then the column index must be valid unless the internals
% are incorrect.)
%    \begin{macrocode}
       \__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
     }
     {
%    \end{macrocode}
% Valid. Success. Do true.
%    \begin{macrocode}
       #6
     }
   }
   { #7 }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%
%Arguments: \marg{key setting name}\meta{key str var}\marg{column
%setting name}\meta{col int var}\marg{not set}\marg{true}\marg{false}
%
% One column may be identified:
% `key' or `column' optional but both not permitted.
% If the column index is provided, obtain the key.
% The \meta{not set} argument is done if no key or column index provided.
% An error will be triggered if the index exceeds the
% column count.
%    \begin{macrocode}
\cs_new:Nn
 \__datatool_optional_key_xor_column_get_key:nNnNnTF
{
  \__datatool_optional_key_xor_column:nNnNnTF 
   { #1 } { #2 } { #3 } { #4 }
   { 
%    \end{macrocode}
% No key or column index provided. Do \meta{not set} argument.
%    \begin{macrocode}
    #5
   }
   {
%    \end{macrocode}
% A column has been identified either by key or column index.
%    \begin{macrocode}
     \tl_if_empty:NTF #2
      {
%    \end{macrocode}
% No key so column must've been identified by its index.
% Check if index doesn't exceed the total number of columns.
%    \begin{macrocode}
       \int_compare:nNnTF
        { #4 }
         > 
        { \DTLcolumncount { \l__datatool_action_name_tl } }
        {
%    \end{macrocode}
% Index too large. Failure. Do false.
%    \begin{macrocode}
          \__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
        }
        {
%    \end{macrocode}
% Index in range. Find the key.
%    \begin{macrocode}
          \__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
          {
%    \end{macrocode}
% No key was found. Unlikely to happen given that the column index
% is in range. Has a column been removed from the column data
% without adjusting the other indexes?
%    \begin{macrocode}
            \__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
          }
          {
%    \end{macrocode}
% Key found. Success. Do true.
%    \begin{macrocode}
            \tl_set_eq:NN #2 \l__datatool_item_key_tl
            #6
          }
        }
      }
      {
%    \end{macrocode}
% Key provided. Success. Do true.
%    \begin{macrocode}
        #6
      }
   }
   {
%    \end{macrocode}
% Invalid key or column setting (negative index or both set).
% Failure. Do false argument.
%    \begin{macrocode}
     #7
   }
}
%    \end{macrocode}
%Arguments: \marg{not set}\marg{true}\marg{false}
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
% If second column allowed:
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%
%Arguments: \marg{key setting name}\meta{key str var}\marg{column
%setting name}\meta{col int var}\marg{true}\marg{false}
%
% One column required:
% `key' or `column' required but both not permitted. A negative
% column index will reset it to zero. No check if the key is valid
% or if the column index is less than the total number of columns.
%    \begin{macrocode}
\cs_new:Nn
  \__datatool_requires_key_xor_column:nNnNTF
{
  \__datatool_optional_key_xor_column:nNnNnTF
   { #1 } #2 { #3 } #4
   {
%    \end{macrocode}
% No key or column provided. Failure. Do false.
%    \begin{macrocode}
     \__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
   }
   { 
%    \end{macrocode}
% Key or column provided. Success (but no guarantee that column
% index isn't greater than the total number of columns). Do true.
%    \begin{macrocode}
    #5
   }
   {
%    \end{macrocode}
% Invalid syntax (negative column index or key and column both
% provided). Failure. Do false.
%    \begin{macrocode}
     #6
   }
}
%    \end{macrocode}
%First column required:
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%Do nothing if false (error messages will be triggered).
%    \begin{macrocode}
\cs_new:Nn \__datatool_requires_key_xor_column:T 
{
  \__datatool_requires_key_xor_column:TF { #1 } { }
}
%    \end{macrocode}
%Second column required:
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%Do nothing if false (error messages will be triggered).
%    \begin{macrocode}
\cs_new:Nn \__datatool_requires_key_ii_xor_column_ii:T 
{
  \__datatool_requires_key_ii_xor_column_ii:TF { #1 } { }
}
%    \end{macrocode}
%
%Arguments: \marg{key setting name}\meta{key str var}\marg{column
%setting name}\meta{col int var}\marg{not set}\marg{true}\marg{false}
%
% One column required:
% `key' or `column' required but both not permitted. The column
% index is required so if the key is provided, obtain the index.
% The \meta{not set} argument is done if no column matches the given key.
%    \begin{macrocode}
\cs_new:Nn
   \__datatool_requires_key_xor_column_get_column:nNnNnTF
{
  \__datatool_requires_key_xor_column:TF 
   {
     \int_if_zero:nTF #4
      {
%    \end{macrocode}
% No column index so it must have been identified by key. Get the
% corresponding index.
%    \begin{macrocode}
        \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
            }
%    \end{macrocode}
% Column index found. Success. Do true argument.
%    \begin{macrocode}
           #6
         }
         { 
%    \end{macrocode}
% No match on key. Do \meta{not set} argument.
%    \begin{macrocode}
           #5
         }
      }
      { 
%    \end{macrocode}
% Column index was provided (but no guarantee it's within range).
% Success. Do true argument.
%    \begin{macrocode}
       #6
      }
   }
   { 
%    \end{macrocode}
% Invalid key or column setting (negative index or both set).
% Failure. Do false argument.
%    \begin{macrocode}
     #7
   }
}
%    \end{macrocode}
% First column required.
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
% Second column required.
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%
%Arguments: \marg{key setting name}\meta{key str var}\marg{column
%setting name}\meta{col int var}\marg{true}\marg{false}
%
% One column required:
% `key' or `column' required but both not permitted. The column
% index is required so if the key is provided, obtain the index.
% Require column existence if key or column provided.
%    \begin{macrocode}
\cs_new:Nn
  \__datatool_requires_key_xor_column_get_column:nNnNTF
{
  \__datatool_requires_key_xor_column_get_column:nTF
   {
%    \end{macrocode}
%Column key not valid. Failure.
%    \begin{macrocode}
     \__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
   }
   {
%    \end{macrocode}
%Positive column index provided or obtained from the key.
%Check that column index is less than the total number of columns
%for the database.
%    \begin{macrocode}
     \int_compare:nNnTF
      { #4 }
       >
      { 
        \int_use:c
          { dtlcols@ \l__datatool_action_name_tl }
      }
     {
%    \end{macrocode}
% Out of range. Failure. Do false. (If the key was originally
% provided, then the column index must be valid unless the internals
% are incorrect.)
%    \begin{macrocode}
       \__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
     }
     {
%    \end{macrocode}
% Valid. Success. Do true.
%    \begin{macrocode}
       #5
     }
   }
   { #6 }
}
%    \end{macrocode}
% First column required.
%    \begin{macrocode}
\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 } { }
}
%    \end{macrocode}
% Second column required.
%    \begin{macrocode}
\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 } { }
}
%    \end{macrocode}
%
% Arguments: \marg{col1 not set}\marg{col2 not
% set}\marg{true}\marg{false}
%
% Two columns may be identified:
% `key' or `column' allowed for first column but both not
% permitted;
% `key2' or `column2' allowed for second column but both not permitted.
%If an error occurs while attempting to get the first column, the
%check for the second will be omitted. If the first column hasn't
%been identified (with key or column) \meta{col1 not set} will be
%done. If the second column hasn't been identified (with key2 or
%column2) \meta{col2 not set} will be done.
%    \begin{macrocode}
\cs_new:Nn
  \__datatool_optional_ii_key_xor_column:nnTF
{
%    \end{macrocode}
% Get the first column:
%    \begin{macrocode}
  \__datatool_optional_key_xor_column_get_column:nTF
   { #1 }
   { 
%    \end{macrocode}
% Get the second column:
%    \begin{macrocode}
     \__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
  { } { } { } { }
}
%    \end{macrocode}
%
% Arguments: \marg{col2 not set}\marg{true}\marg{false}
%
%Similar but the first column is required and the second optional.
%    \begin{macrocode}
\cs_new:Nn \__datatool_requires_i_optional_ii_key_xor_column:nTF
{
%    \end{macrocode}
% Get the first column:
%    \begin{macrocode}
  \__datatool_requires_key_xor_column_get_column:TF
   { 
%    \end{macrocode}
% Get the second column:
%    \begin{macrocode}
     \__datatool_optional_key_ii_xor_column_ii_get_column:nTF
      { #1 } { #2 } { #3 }
   }
   { #3 }
}
%    \end{macrocode}
%
%For actions that allow an arbitrary number of columns (up to the
%total number in the database). These may be specified by `keys' and/or
%`columns' options. The resulting list will be save in the sequence
% variable \cs{l\_\_datatool\_action\_columns\_seq}.
% The `columns' list may include ranges. Any keys will
%need to be defined. Note that the sequence won't be ordered
% (unless the user has supplied the order). If an ordered list is
% required, it will need to be sorted.
%    \begin{macrocode}
\cs_new:Nn \__datatool_optional_columns:
{
%    \end{macrocode}
% First check the `columns' setting.
%    \begin{macrocode}
  \clist_map_inline:Nn 
   \l__datatool_action_columns_clist
  {
    \__datatool_optional_columns:w ##1 - \q_nil \q_stop
  }
%    \end{macrocode}
% Next check the `keys' setting.
%    \begin{macrocode}
  \clist_map_inline:Nn
   \l__datatool_action_keys_clist
  {
    \__datatool_optional_keys:w ##1 - \q_nil \q_stop
  }
%    \end{macrocode}
% Make sure that the user hasn't accidentally used key.
%    \begin{macrocode}
  \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.)
       }
     }
   }
%    \end{macrocode}
% Make sure that the user hasn't accidentally used key.
%    \begin{macrocode}
  \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.)
       }
     }
   }
%    \end{macrocode}
% Make sure that the user hasn't accidentally used column.
%    \begin{macrocode}
  \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.)
       }
     }
   }
%    \end{macrocode}
% Make sure that the user hasn't accidentally used column2.
%    \begin{macrocode}
  \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.)
       }
     }
   }
}
%    \end{macrocode}
% Parse a column index range.
%    \begin{macrocode}
\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 }
  {
%    \end{macrocode}
%Start at 1.
%    \begin{macrocode}
    \tl_if_empty:nTF { #2 }
    {
%    \end{macrocode}
%End at the total number of columns.
%    \begin{macrocode}
      \__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 }
    {
%    \end{macrocode}
%End at the total number of columns.
%    \begin{macrocode}
      \__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 }
             }
          }
       }
    }
  }
}
%    \end{macrocode}
% Add a column range.
%    \begin{macrocode}
\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 }
%    \end{macrocode}
% Parse a column index range.
%    \begin{macrocode}
\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 }
  }
}
%    \end{macrocode}
% Add a column index to the sequence if not already there.
%    \begin{macrocode}
\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 }
%    \end{macrocode}
% Parse a column key range.
%    \begin{macrocode}
\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 }
  {
%    \end{macrocode}
%Start at 1.
%    \begin{macrocode}
    \tl_if_empty:nTF { #2 }
    {
%    \end{macrocode}
%End at the total number of columns.
%    \begin{macrocode}
      \__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 }
    {
%    \end{macrocode}
%End at the total number of columns.
%    \begin{macrocode}
      \__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 }
          {
%    \end{macrocode}
%Unlike the column index ranges, with a key range, if the start key
%has a smaller index than the end key, switch them round.
%    \begin{macrocode}
            \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
               }
             }
          }
       }
    }
  }
}
%    \end{macrocode}
%Check the column key is valid.
%    \begin{macrocode}
\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 }
    }
 }
}
%    \end{macrocode}
%
%Define actions. New database (action=new):
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%Set the return value:
%    \begin{macrocode}
    \tl_set_eq:NN \l__datatool_action_return_tl \l__datatool_action_name_tl
%    \end{macrocode}
%Duplicate this as a secondary return value.
%    \begin{macrocode}
    \__datatool_put_return_action_string:nV 
      { name } \l__datatool_action_name_tl
  }
}
%    \end{macrocode}
% Delete database (action=delete):
%    \begin{macrocode}
\cs_new:cn { __datatool_action_delete: }
{
  \__datatool_require_database:T
  {
%    \end{macrocode}
% Set the column and row return values first.
%    \begin{macrocode}
    \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 }
    }
%    \end{macrocode}
%Delete the database:
%    \begin{macrocode}
    \DTLdeletedb { \l__datatool_action_name_tl }
%    \end{macrocode}
%Set the primary return value:
%    \begin{macrocode}
    \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
  }
%    \end{macrocode}
%For consistency with the other actions, also set the name property.
%    \begin{macrocode}
}
%    \end{macrocode}
% Clear database (action=clear):
%    \begin{macrocode}
\cs_new:cn { __datatool_action_clear: }
{
  \__datatool_require_database:T
  {
%    \end{macrocode}
% Set the column and row return values first.
%    \begin{macrocode}
    \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 }
    }
%    \end{macrocode}
% Clear the database:
%    \begin{macrocode}
    \DTLcleardb { \l__datatool_action_name_tl }
%    \end{macrocode}
%Set the primary return value:
%    \begin{macrocode}
    \tl_set_eq:NN
      \l__datatool_action_return_tl
      \l__datatool_action_name_tl
  }
%    \end{macrocode}
%For consistency with the other actions, also set the name property.
%    \begin{macrocode}
  \__datatool_put_return_action_string:nV 
   { name } \l__datatool_action_name_tl
}
%    \end{macrocode}
% New row (action=new row):
%    \begin{macrocode}
\cs_new:cn { __datatool_action_new ~ row: }
{
 \__datatool_require_database:T
  {
    \tl_clear:N \l__datatool_action_value_tl
%    \end{macrocode}
%Check for column and key in case user has confused this action with
%`new entry'.
%    \begin{macrocode}
    \__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
     }
%    \end{macrocode}
%Check for `options' in case user has confused it for `assign'.
%    \begin{macrocode}
    \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
     {
%    \end{macrocode}
% Increment row count.
%    \begin{macrocode}
      \int_gincr:c { dtlrows@ \l__datatool_action_name_tl }
     }
     {
%    \end{macrocode}
% Increment row count.
%    \begin{macrocode}
      \int_incr:c { dtlrows@ \l__datatool_action_name_tl }
     }
%    \end{macrocode}
% Append the new row to the database
%    \begin{macrocode}
    \__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
     }
%    \end{macrocode}
% The primary return value is the row count:
%    \begin{macrocode}
    \tl_set:Nv \l__datatool_action_return_tl 
      { dtlrows@ \l__datatool_action_name_tl } 
  }
%    \end{macrocode}
%Set the return value:
%    \begin{macrocode}
  \__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 } } 
}
%    \end{macrocode}
% \cs{\_\_datatool\_new\_entry:nn}\marg{key}\marg{value}
% adds value to \cs{l\_\_datatool\_action\_value\_tl}
%using database column markup
%    \begin{macrocode}
\cs_new:Nn \__datatool_new_entry:nn
{
  \tl_set:Nx \l__datatool_item_key_tl { #1 }
%    \end{macrocode}
% This bit is as \cs{@dtl@storeandupdate}.
% Store the value of this entry in \verb|\l__datatool_item_value_tl|
% and also the older \cs{@dtl@toks} taking the
% expansion setting into account. This will also parse and set the
% data type in \cs{@dtl@datatype}.
%    \begin{macrocode}
  \__datatool_process_new_value:n { #2 }
%    \end{macrocode}
%Is there already a column with the given key?
%    \begin{macrocode}
  \tl_if_exist:cTF 
   { 
     dtl@ci@
     \l__datatool_action_name_tl 
     @ \l__datatool_item_key_tl
   }
  {
%    \end{macrocode}
%This column already exists. Get the column index from the key.
%    \begin{macrocode}
    \exp_args:NNc \int_set:Nn \l__datatool_action_column_int
      {
         dtl@ci@
          \l__datatool_action_name_tl 
        @ \l__datatool_item_key_tl
      }
%    \end{macrocode}
%Does the meta data need updating?
%    \begin{macrocode}
    \__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
%    \end{macrocode}
%Column type needs updating. Get current properties.
%    \begin{macrocode}
        \__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  
%    \end{macrocode}
%Update underlying token register.
%    \begin{macrocode}
        \__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
          }
      }
  }
  {
%    \end{macrocode}
%Column doesn't exist so create it.
%    \begin{macrocode}
    \int_set:Nn \l__datatool_action_column_int
     { \DTLcolumncount { \l__datatool_action_name_tl } + 1 }
%    \end{macrocode}
%Append to column metadata list.
%    \begin{macrocode}
    \__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
     {
%    \end{macrocode}
%Assign key to column index mapping.
%    \begin{macrocode}
       \tl_gset:cV
         {
          dtl@ci@
          \l__datatool_action_name_tl 
          @ \l__datatool_item_key_tl
         }
        \l__datatool_action_column_int
%    \end{macrocode}
%Update column count.
%    \begin{macrocode}
        \int_gset_eq:cN
          { dtlcols@ \l__datatool_action_name_tl }
         \l__datatool_action_column_int
     }
     {
%    \end{macrocode}
%Assign key to column index mapping.
%    \begin{macrocode}
       \tl_set:cV
         {
          dtl@ci@
          \l__datatool_action_name_tl 
          @ \l__datatool_item_key_tl
         }
        \l__datatool_action_column_int
%    \end{macrocode}
%Update column count.
%    \begin{macrocode}
        \int_set_eq:cN
         { dtlcols@ \l__datatool_action_name_tl }
         \l__datatool_action_column_int
     }
  }
%    \end{macrocode}
% Append to token list variable used to store the current row.
%    \begin{macrocode}
  \tl_put_right:Nx \l__datatool_action_value_tl
   {
      \__datatool_row_element_markup:VV
        \l__datatool_action_column_int
        \l__datatool_item_value_tl
   }
}
%    \end{macrocode}
%Value not provided:
%    \begin{macrocode}
\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 }
  }
}
%    \end{macrocode}
% New entry (action=new entry):
%    \begin{macrocode}
\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
        {
%    \end{macrocode}
% No key so the column index was provided instead.
% First check if there is a corresponding column.
%    \begin{macrocode}
          \int_set_eq:NN
            \dtlcolumnnum
            \l__datatool_action_column_int
%    \end{macrocode}
% This bit is as \cs{@dtl@storeandupdate}.
% Store the value of this entry in \cs{@dtl@toks} taking the
% expansion setting into account. This will also set the
% \verb|\l__datatool_item_value_tl| and \cs{@dtl@datatype} variables.
%    \begin{macrocode}
          \__datatool_process_new_value:V
            \l__datatool_action_value_tl
%    \end{macrocode}
%Is there a key associated with this column index?
% (This also gets the data type.)
%    \begin{macrocode}
          \__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
          {
%    \end{macrocode}
%No key, so this must be a new column. Create a default key.
%    \begin{macrocode}
            \tl_set:Nx \l__datatool_item_key_tl
              {
                \dtldefaultkey
                \int_use:N \l__datatool_action_column_int
              }
%    \end{macrocode}
% Append to property list
%    \begin{macrocode}
            \__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
%    \end{macrocode}
%Is the column index greater than the current size?
%    \begin{macrocode}
              \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
%    \end{macrocode}
%Is the column index greater than the current size?
%    \begin{macrocode}
              \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
              }
            }
          }
          {
%    \end{macrocode}
%This column already exists. Does the meta data need updating?
%    \begin{macrocode}
            \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 }
            {
%    \end{macrocode}
%Column type needs updating.
%    \begin{macrocode}
              \__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
%    \end{macrocode}
%Update underlying token register.
%    \begin{macrocode}
              \__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
                }
            }
          }
%    \end{macrocode}
% Now split off the last row and insert the new entry. This expects
% the value to be in \cs{@dtl@toks}:
%    \begin{macrocode}
          \@s@DTLnewdbentry \l__datatool_action_name_tl
        }
        {
%    \end{macrocode}
% Key provided, use \cs{DTLnewdbentry}:
%    \begin{macrocode}
          \int_zero:N \dtlcolumnnum
          \exp_args:NNVV \DTLnewdbentry
              \l__datatool_action_name_tl 
              \l__datatool_action_key_tl 
              \l__datatool_action_value_tl 
%    \end{macrocode}
%Get the column index for the return value.
%    \begin{macrocode}
          \int_set_eq:NN \l__datatool_action_column_int \dtlcolumnnum
        }
%    \end{macrocode}
%The primary return value is only set if successful.
%    \begin{macrocode}
        \tl_set:NV \l__datatool_action_return_tl 
           \l__datatool_action_column_int 
%    \end{macrocode}
% Set the return values to indicate success.
%    \begin{macrocode}
       \__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
      }
    }
%    \end{macrocode}
%The row property is just the row count:
%    \begin{macrocode}
    \__datatool_put_return_action_int:nv 
     { row }
     { dtlrows@ \l__datatool_action_name_tl } 
  }
%    \end{macrocode}
%Always set the name as a secondary return value to help debugging:
%    \begin{macrocode}
  \__datatool_put_return_action_string:nV 
   { name } \l__datatool_action_name_tl
}
%    \end{macrocode}
% Get column index (action=column index):
%    \begin{macrocode}
\cs_new:cn { __datatool_action_column ~ index: }
{
%    \end{macrocode}
% Column must be identified by its label. (No point identifying it
% by the column index as that's what's being queried.)
%    \begin{macrocode}
  \tl_if_empty:NTF \l__datatool_action_key_tl
  {
%    \end{macrocode}
% No key has been supplied.
%    \begin{macrocode}
    \__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 
      { 
%    \end{macrocode}
%Primary return value is the column index:
%    \begin{macrocode}
        \@sdtl@getcolumnindex
          { \l__datatool_action_return_tl }
          { \l__datatool_action_name_tl }
          { \l__datatool_action_key_tl }
%    \end{macrocode}
%Duplicate as a secondary return value:
%    \begin{macrocode}
        \__datatool_put_return_action_int:nV 
         { column } \l__datatool_action_return_tl
      }
      { }
    }
  }
%    \end{macrocode}
%Set the secondary return values:
%    \begin{macrocode}
  \__datatool_put_return_action_string:nV 
   { name } \l__datatool_action_name_tl
  \__datatool_put_return_action_string:nV 
   { key } \l__datatool_action_key_tl
}
%    \end{macrocode}
% Get column metadata (action=column data):
%    \begin{macrocode}
\cs_new:cn { __datatool_action_column ~ data: }
{
  \__datatool_require_database:T
   {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
    \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
%    \end{macrocode}
%Set the return values:
%    \begin{macrocode}
       \quark_if_no_value:NF \l__datatool_item_key_tl
        {
          \__datatool_put_return_action_string:nV 
           { key } \l__datatool_item_key_tl
%    \end{macrocode}
%Primary return value is the key.
%    \begin{macrocode}
          \tl_set_eq:NN
            \l__datatool_action_return_tl
            \l__datatool_item_key_tl
%    \end{macrocode}
%Duplicate as secondary return value.
%    \begin{macrocode}
          \__datatool_put_return_action_string:nV 
           { key } \l__datatool_item_key_tl
        }
%    \end{macrocode}
%Set the secondary return values:
%    \begin{macrocode}
       \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
  }
}
%    \end{macrocode}
% Display data (action=display):
%    \begin{macrocode}
\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:
%    \end{macrocode}
%Need to set return properties after the group ends:
%    \begin{macrocode}
      \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
   }
%    \end{macrocode}
%Set the secondary return values:
%    \begin{macrocode}
  \__datatool_put_return_action_string:nV
   { name } \l__datatool_action_name_tl
}
%    \end{macrocode}
% Display data in a longtable (action=display long):
%    \begin{macrocode}
\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:
%    \end{macrocode}
%Need to set return properties after the group ends:
%    \begin{macrocode}
      \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
   }
%    \end{macrocode}
%Set the secondary return values:
%    \begin{macrocode}
  \__datatool_put_return_action_string:nV
   { name } \l__datatool_action_name_tl
}
%    \end{macrocode}
% Add a new column (action=add column):
%    \begin{macrocode}
\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 '
      }
    }
%    \end{macrocode}
%The column index will be one more than the current number of
%columns.
%    \begin{macrocode}
    \int_set:Nn \l__datatool_action_column_int
      { \DTLcolumncount \l__datatool_action_name_tl + 1 }
%    \end{macrocode}
%If no key has been set, assign the default.
%    \begin{macrocode}
    \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
        }
     }
%    \end{macrocode}
%If no value has been set, assume the header is the same as the key.
%    \begin{macrocode}
    \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
%    \end{macrocode}
%The primary return value is only set if successful.
%    \begin{macrocode}
       \tl_set:NV \l__datatool_action_return_tl 
         \l__datatool_action_column_int 
%    \end{macrocode}
%Duplicate as a secondary return value.
%    \begin{macrocode}
      \__datatool_put_return_action_int:nV
       { column } \l__datatool_action_column_int  
    }
%    \end{macrocode}
%Set the return values:
%    \begin{macrocode}
    \__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
}
%    \end{macrocode}
%
% Get column aggregates (action=aggregate):
%    \begin{macrocode}
\cs_new:cn { __datatool_action_aggregate: }
{
%    \end{macrocode}
% Check if database exists:
%    \begin{macrocode}
  \__datatool_require_database:T
  {
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
    \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
%    \end{macrocode}
% List of aggregate types required:
%    \begin{macrocode}
    \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 ' }
            }
         }
      }
    }
%    \end{macrocode}
%Add dependent types if omitted:
%    \begin{macrocode}
    \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 }
        }
     }
%    \end{macrocode}
% List of numeric values:
%    \begin{macrocode}
    \seq_clear:N \l__datatool_tmpa_seq
    \seq_clear:N \l__datatool_tmpb_seq
%    \end{macrocode}
% Check the key and column settings, which will set the index for
% column~1 and column~2:
%    \begin{macrocode}
    \__datatool_optional_ii_key_xor_column:
%    \end{macrocode}
%Iterate and compute required aggregates:
%    \begin{macrocode}
    \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
%    \end{macrocode}
% Check that the value is numerical.
%    \begin{macrocode}
          \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
              }
            }
          }
       }
%    \end{macrocode}
%Second column, if set:
%    \begin{macrocode}
      \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
%    \end{macrocode}
% Check that the value is numerical.
%    \begin{macrocode}
          \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
              }
            }
          }
       }
    }
%    \end{macrocode}
%Primary return value is the number of items aggregated for the
%first column.
%This should be non-zero if no problems have occurred.
%    \begin{macrocode}
    \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
%    \end{macrocode}
%Set the secondary return values.
%    \begin{macrocode}
    \__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 }
%    \end{macrocode}
% Calculate the mean.
%    \begin{macrocode}
         \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 }
%    \end{macrocode}
% Calculate the variance.
%    \begin{macrocode}
           \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 }
%    \end{macrocode}
% Calculate the standard deviation.
%    \begin{macrocode}
             \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
%    \end{macrocode}
%Number of column2 items counted:
%    \begin{macrocode}
       \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 }
%    \end{macrocode}
% Calculate the mean.
%    \begin{macrocode}
            \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 }
%    \end{macrocode}
% Calculate the variance.
%    \begin{macrocode}
              \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 }
%    \end{macrocode}
% Calculate the standard deviation.
%    \begin{macrocode}
                \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 }
                }
              }
            }
          }
        }
     }
  }
%    \end{macrocode}
%Supplementary secondary return value:
%    \begin{macrocode}
  \__datatool_put_return_action_string:nV
   { name } \l__datatool_action_name_tl
}
%    \end{macrocode}
% Get row aggregates (action=row aggregate) for use in
% \cs{DTLmapdata}:
%    \begin{macrocode}
\cs_new:cn { __datatool_action_row ~ aggregate: }
{
  \__datatool_action_noop:Nn
    \DTLmapdata
    { row ~ aggregate: }
}
%    \end{macrocode}
%Action within \cs{DTLmapdata}:
%    \begin{macrocode}
\cs_new:Nn \__datatool_action_row_aggregate_op:
{
  \exp_args:NV \__datatool_action_row_aggregate:n
   \l__datatool_map_data_row_tl
}
%    \end{macrocode}
% Get row aggregates (action=current row aggregate) for use with
% \cs{dtlcurrentrow}:
%    \begin{macrocode}
\cs_new:cn { __datatool_action_current ~ row ~ aggregate: }
{
  \exp_args:NV \__datatool_action_row_aggregate:n
    \dtlcurrentrow
}
%    \end{macrocode}
%Underlying action of both \qt{row aggregate} and \qt{current row
%aggregate}:
%    \begin{macrocode}
\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
      }
   }
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
  \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
%    \end{macrocode}
% List of aggregate types required. These will be stored in the
% options sequence:
%    \begin{macrocode}
  \seq_clear:N \l__datatool_action_tmp_options_seq
%    \end{macrocode}
% Iterate through the list of options supplied by the user:
%    \begin{macrocode}
  \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 ' }
          }
       }
    }
  }
%    \end{macrocode}
%Add dependent types if omitted:
%    \begin{macrocode}
  \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 }
   }
%    \end{macrocode}
% List of numeric values:
%    \begin{macrocode}
  \seq_clear:N \l__datatool_action_tmp_data_seq
%    \end{macrocode}
% Populate the column sequence \cs{l\_\_datatool\_action\_columns\_seq}
% with all the required columns:
%    \begin{macrocode}
   \__datatool_optional_columns:
%    \end{macrocode}
% Iterate over all identified columns (or all if no subset provided).
%    \begin{macrocode}
  \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 }
     }
   }
%    \end{macrocode}
%Primary return value is the number of numeric items.
%This should be non-zero if no problems have occurred.
%    \begin{macrocode}
  \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
%    \end{macrocode}
%Set the secondary return values.
%    \begin{macrocode}
  \__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 }
%    \end{macrocode}
% Calculate the mean.
%    \begin{macrocode}
       \__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 }
%    \end{macrocode}
% Calculate the variance.
%    \begin{macrocode}
         \__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 }
%    \end{macrocode}
% Calculate the standard deviation.
%    \begin{macrocode}
           \__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 }
           }
         }
       }
     }
   }
%    \end{macrocode}
%Supplementary secondary return values. Database name:
%    \begin{macrocode}
  \__datatool_put_return_action_string:nV
   { name } \dtldbname
%    \end{macrocode}
%Row index:
%    \begin{macrocode}
  \__datatool_put_return_action_int:nV
   { row } \dtlrownum
 }
}
%    \end{macrocode}
%Arguments: \meta{col-idx}\marg{row markup}
%
%Parse given column in the current row.
%    \begin{macrocode}
\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 
%    \end{macrocode}
% Check that the value is numerical.
%    \begin{macrocode}
      \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
          }
        }
      }
   }
}
%    \end{macrocode}
%
% Select a row, which will make it the current row (action=select row):
%    \begin{macrocode}
\cs_new:cn { __datatool_action_select ~ row: }
{
  \int_zero:N \dtlrownum
  \int_zero:N \dtlcolumnnum 
%    \end{macrocode}
% Check if database exists:
%    \begin{macrocode}
  \__datatool_require_database:T
  {
    \int_if_zero:nTF \l__datatool_action_row_int
    {
%    \end{macrocode}
% No row index provided, so match on column.
%    \begin{macrocode}
     \__datatool_optional_key_xor_column_get_column:nTF
      {
%    \end{macrocode}
% No key or column index provided.
%    \begin{macrocode}
        \__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
         }
      }
      {
%    \end{macrocode}
% Column index available. Don't trigger an error if no match.
%    \begin{macrocode}
        \__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
        }
      }
      {
%    \end{macrocode}
% No match on column. Do nothing. User can check return value.
%    \begin{macrocode}
      }
    }
    {
      \int_set_eq:NN \dtlrownum \l__datatool_action_row_int
%    \end{macrocode}
% Row index provided, so check column and key haven't been set.
%    \begin{macrocode}
      \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
    }
%    \end{macrocode}
%Primary return value is the row index.
%    \begin{macrocode}
    \int_if_zero:nF \dtlrownum
     {
       \tl_set:NV
         \l__datatool_action_return_tl
         \dtlrownum
%    \end{macrocode}
%Set the secondary return values (column keys).
%    \begin{macrocode}
       \__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
     }
  }
}
%    \end{macrocode}
% Get each entry from the current row (action=current row values):
%    \begin{macrocode}
\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
    }
%    \end{macrocode}
% Populate the column sequence \cs{l\_\_datatool\_action\_columns\_seq}
% with all the required columns:
%    \begin{macrocode}
   \__datatool_optional_columns:
   \seq_if_empty:NTF \l__datatool_action_columns_seq
    {
%    \end{macrocode}
% No columns supplied so get all column values.
%    \begin{macrocode}
      \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
          }
       }
    }
    {
%    \end{macrocode}
% Iterate over subset
%    \begin{macrocode}
      \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
          }
       }
    }
  }
%    \end{macrocode}
%Primary return value is the total number of entries found in the
%current row.
%    \begin{macrocode}
  \tl_set:Nx
    \l__datatool_action_return_tl
    { \prop_count:N \l__datatool_action_return_prop }
}
%    \end{macrocode}
%
%Options for action=find. Match function:
%    \begin{macrocode}
\cs_new:Nn \__datatool_action_find_if:T { #1 }
%    \end{macrocode}
%Search direction:
%    \begin{macrocode}
\bool_new:N \__datatool_action_find_asc_bool
%    \end{macrocode}
%Select row if match found:
%    \begin{macrocode}
\bool_new:N \__datatool_action_find_select_bool
%    \end{macrocode}
%    \begin{macrocode}
\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 ,
}
%    \end{macrocode}
%
% Find a row matching criteria given in options (action=find):
%    \begin{macrocode}
\cs_new:cn { __datatool_action_find: }
{
%    \end{macrocode}
% Check if database exists:
%    \begin{macrocode}
  \__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 }
     { }
     {
%    \end{macrocode}
% The row and row2 settings may be used to limit the search range.
%    \begin{macrocode}
      \__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 } }
       }
%    \end{macrocode}
% Set options. The default is to select the first row. This makes it
% easier to select a row simply by index.
%    \begin{macrocode}
      \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
%    \end{macrocode}
% Gather assignment information, if provided.
%    \begin{macrocode}
      \__datatool_get_placeholder_assign:
%    \end{macrocode}
%If ascending, row should be less than row2 value.
%    \begin{macrocode}
      \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
          }
%    \end{macrocode}
% Iterate over all rows in ascending order.
%    \begin{macrocode}
        \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
         }
       }
       {
%    \end{macrocode}
%If descending, row2 should be less than row value.
%    \begin{macrocode}
         \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
          }
%    \end{macrocode}
% Iterate over all rows in descending order.
%    \begin{macrocode}
        \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
         }
       }
     }
  }
}
%    \end{macrocode}
% Loop body for `find' action (but doesn't update loop row counter):
%    \begin{macrocode}
\cs_new:Nn \__datatool_action_find_loop_body:
{
%    \end{macrocode}
% Get specs for this row.
%    \begin{macrocode}
  \__datatool_get_row:vVN
    { dtldb@ \l__datatool_action_name_tl } 
    \l__datatool_action_row_int
   \l__datatool_action_tmpa_tl
%    \end{macrocode}
% Perform assignments.
%    \begin{macrocode}
  \exp_args:NV \__datatool_do_action_assignments:n
   \l__datatool_action_tmpa_tl
%    \end{macrocode}
% Perform test.
%    \begin{macrocode}
  \__datatool_action_find_if:T
   {
%    \end{macrocode}
% The primary return value is the row index, if successful
%    \begin{macrocode}
     \tl_set:Nx
       \l__datatool_action_return_tl
       { \int_use:N \l__datatool_action_row_int }
%    \end{macrocode}
%Set secondary return values.
%    \begin{macrocode}
      \__datatool_set_return_from_row:Vn
       \l__datatool_action_tmpa_tl
       { \l__datatool_action_name_tl }
%    \end{macrocode}
%Select the row if applicable.
%    \begin{macrocode}
      \bool_if:NT \__datatool_action_find_select_bool
       {
         \exp_args:NNV \dtlgetrow
          \l__datatool_action_name_tl 
          \l__datatool_action_row_int
       }
%    \end{macrocode}
%Break out of loop.
%    \begin{macrocode}
     \int_set_eq:NN
       \l__datatool_action_row_int
       \l__datatool_action_row_ii_int
   }
}
%    \end{macrocode}
% Sort data (action=sort):
%    \begin{macrocode}
\cs_new:cn { __datatool_action_sort: }
{
  \__datatool_require_database:T
   {
%    \end{macrocode}
% Check that the user hasn't accidentally used `columns' or `keys'
% instead of `assign':
%    \begin{macrocode}
     \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
         }
      }
%    \end{macrocode}
% Check that the user has provided `assign':
%    \begin{macrocode}
     \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
%    \end{macrocode}
% The primary return value is the element count of word list. This
% will be empty if the sort failed. If successful, it should equal
% the number of rows.
%    \begin{macrocode}
        \seq_if_empty:NF \l__datatool_wordlist_seq
         {
           \tl_set:Nx \l__datatool_action_return_tl 
            { \seq_count:N \l__datatool_wordlist_seq } 
%    \end{macrocode}
%Set the secondary return values:
%    \begin{macrocode}
           \__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 } } 
         }
      }
   }
%    \end{macrocode}
%The name is always set as a secondary return value:
%    \begin{macrocode}
  \__datatool_put_return_action_string:nV
   { name } \l__datatool_action_name_tl
}
%    \end{macrocode}
%
% \subsection{Defining New Databases}
% As from v2.0, the internal structure of the database has changed
% to make it more efficient.\footnote{Thanks to Morten H\o gholm
% for the suggestion.} The database is now stored in a token
% register instead of a macro. Each row is represented as:\par
%\verb|\db@row@elt@w|%
%\verb|\db@row@id@w| \meta{row idx}\verb|\db@row@id@end@|%
%\meta{column data}%
%\verb|\db@row@id@w| \meta{row idx}\verb|\db@row@id@end@|%
%\verb|\db@row@elt@end@|\par
% where \meta{row idx} is the row index and \meta{column data} is
% the data for each column in the row. Each column for a given
% row is stored as:\par
%\verb|\db@col@id@w| \meta{column idx}\verb|\db@col@id@end@|%
%\verb|\db@col@elt@w| \meta{value}\verb|\db@col@elt@end@|%
%\verb|\db@col@id@w| \meta{column idx}\verb|\db@col@id@end@|\par
% where \meta{column idx} is the column index and \meta{value}
% is the entry for the given column and row.
%
% Note that \LaTeX3 syntax isn't used. Old dbtex files still need to
% be supported.
%
% Each row only has an associated index, but columns have
% a unique identifying key as well as an associated index. Columns
% also have an associated data type which may be: 0 (column 
% contains strings), 1 (column contains integers), 2 (column
% contains real numbers), 3 (column contains currency) or 
% \meta{empty} (column contains no data). Since the key sometimes
% has to be expanded, a header is also available in the event 
% that the user wants to use \cs{DTLdisplaydb} or
% \cs{DTLdisplaylongdb} and requires a column header that would
% cause problems if used as a key. The general column 
% information is stored in a token register where each column
% has information stored in the form:\par
%\verb|\db@plist@elt@w|%
%\verb|\db@col@id@w| \meta{index}\verb|\db@col@id@end@|%
%\verb|\db@key@id@w| \meta{key}\verb|\db@key@id@end@|%
%\verb|\db@type@id@w| \meta{type}\verb|\db@type@id@end@|%
%\verb|\db@header@id@w| \meta{type}\verb|\db@header@id@end@|%
%\verb|\db@col@id@w| \meta{index}\verb|\db@col@id@end@|%
%\verb|\db@plist@elt@end@|
%
% The column name (\meta{key}) is mapped to the column index
% using \cs{dtl@ci@}\meta{db}"@"\meta{key} where \meta{db} is
% the database name.
%
%\begin{definition}
%\cs{\_\_datatool\_column\_markup:nnnn}\marg{column
%idx}\marg{key}\marg{type}\marg{header}
%\end{definition}
%Expands to the column markup for the given column.
%    \begin{macrocode}
\cs_new:Nn \__datatool_column_markup:nnnn
{
  \__datatool_column_markup_full:nnnn
   { #1 } { #2 } { #3 } { \exp_not:n { #4 } }
}
%    \end{macrocode}
%NB don't include x variant or it won't expand.
%    \begin{macrocode}
\cs_generate_variant:Nn
  \__datatool_column_markup:nnnn
  { VVVV, VVVn, VnVn, VnVV, Vnnn, nVVV, nVnV, VnnV }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_column_markup_full:nnnn
{
%    \end{macrocode}
% Start of column block.
%    \begin{macrocode}
  \exp_not:N \db@plist@elt@w
%    \end{macrocode}
% Column index
%    \begin{macrocode}
    \exp_not:N \db@col@id@w 
      #1
    \exp_not:N \db@col@id@end@
%    \end{macrocode}
% Column key
%    \begin{macrocode}
    \exp_not:N \db@key@id@w 
      #2
    \exp_not:N \db@key@id@end@
%    \end{macrocode}
% Column type
%    \begin{macrocode}
    \exp_not:N \db@type@id@w
      #3
    \exp_not:N \db@type@id@end@
%    \end{macrocode}
% Column header
%    \begin{macrocode}
    \exp_not:N \db@header@id@w
      #4
    \exp_not:N \db@header@id@end@
%    \end{macrocode}
% Column index
%    \begin{macrocode}
    \exp_not:N \db@col@id@w 
      #1
    \exp_not:N \db@col@id@end@
%    \end{macrocode}
% End of Column block
%    \begin{macrocode}
  \exp_not:N \db@plist@elt@end@
}
%    \end{macrocode}
%
%\begin{definition}
%\cs{\_\_datatool\_row\_markup:nn}\marg{row idx}\marg{content}
%\end{definition}
%Expands to the row markup for the given row.
%    \begin{macrocode}
\cs_new:Nn \__datatool_row_markup:nn
{
  \__datatool_row_markup_full:nn
   { #1 } { \exp_not:n { #2 } }
}
%    \end{macrocode}
%NB don't include x variant or it won't expand.
%    \begin{macrocode}
\cs_generate_variant:Nn
  \__datatool_row_markup:nn
  { vn , Vn , vV, VV, vv , nV }
%    \end{macrocode}
% Don't inhibit expansion:
%    \begin{macrocode}
\cs_new:Nn \__datatool_row_markup_full:nn
{
%    \end{macrocode}
% Start of row block.
%    \begin{macrocode}
 \exp_not:N \db@row@elt@w
%    \end{macrocode}
% Row ID marker:
%    \begin{macrocode}
   \exp_not:N \db@row@id@w
     #1 % row ID
   \exp_not:N \db@row@id@end@
%    \end{macrocode}
% Content:
%    \begin{macrocode}
       #2 
%    \end{macrocode}
% Matching end row ID marker:
%    \begin{macrocode}
   \exp_not:N \db@row@id@w 
     #1  % row ID
   \exp_not:N \db@row@id@end@
%    \end{macrocode}
% End of row block
%    \begin{macrocode}
 \exp_not:N \db@row@elt@end@
}
\cs_generate_variant:Nn
 \__datatool_row_markup_full:nn
 { vn }
%    \end{macrocode}
%
%\begin{definition}
%\cs{\_\_datatool\_row\_element\_markup:nn}\marg{col idx}\marg{content}
%\end{definition}
%Expands to the element markup for the given row but prevents
%content from expanding.
%    \begin{macrocode}
\cs_new:Nn \__datatool_row_element_markup:nn
{
  \__datatool_row_element_markup_full:nn
   { #1 } { \exp_not:n { #2 } }
}
%    \end{macrocode}
%NB don't include x variant or it won't expand.
%    \begin{macrocode}
\cs_generate_variant:Nn
  \__datatool_row_element_markup:nn
  { vn, Vn , vV , VV, vv, nV }
%    \end{macrocode}
% Don't inhibit expansion of content:
%    \begin{macrocode}
\cs_new:Nn \__datatool_row_element_markup_full:nn
{
%    \end{macrocode}
% Column ID:
%    \begin{macrocode}
  \exp_not:N \db@col@id@w  
    #1
  \exp_not:N \db@col@id@end@ 
  \exp_not:N \db@col@elt@w
%    \end{macrocode}
% Content:
%    \begin{macrocode}
    #2 
  \exp_not:N \db@col@elt@end@ 
%    \end{macrocode}
% Matching end of column ID:
%    \begin{macrocode}
  \exp_not:N \db@col@id@w 
    #1
  \exp_not:N \db@col@id@end@ 
}
%    \end{macrocode}
%
%\begin{macro}{\DTLnewdb}
%\begin{definition}
% \cs{DTLnewdb}\marg{db name}
%\end{definition}
%\changes{2.0}{2009 February 27}{Changed way database is stored}
%\changes{3.0}{2025-03-03}{changed to new document command}
% Initialises a database called \meta{name}.
%    \begin{macrocode}
\NewDocumentCommand \DTLnewdb { m }
{
%    \end{macrocode}
% Check if there is already a database with this name.
%    \begin{macrocode}
  \DTLifdbexists{#1}
  {
     \PackageError{datatool}{Database ~ `#1' ~ already ~ exists}{}
  }
  {
    \__datatool_new_db:n { #1 }
  }
}
%    \end{macrocode}
%\end{macro}
%
%New registers are global. So new databases will always be globally
%defined.
%    \begin{macrocode}
\cs_new:Nn \__datatool_new_db:n
{%
%    \end{macrocode}
% Define new database. Add information message if in verbose
% mode.
%    \begin{macrocode}
  \dtl@message {Creating ~ database ~ `#1'}
%    \end{macrocode}
% Define token register used to store the contents of the database.
%    \begin{macrocode}
  \exp_args:Nc \newtoks { dtldb @ #1 }
%    \end{macrocode}
% Define token register used to store the column header information.
%    \begin{macrocode}
  \exp_args:Nc \newtoks { dtlkeys @ #1 }
%    \end{macrocode}
% Define count register used to store the row count.
%    \begin{macrocode}
  \int_new:c { dtlrows @ #1 }
%    \end{macrocode}
% Define count register used to store the column count.
%    \begin{macrocode}
  \int_new:c { dtlcols @ #1 }
}
%    \end{macrocode}
%
%
%\begin{macro}{\DTLcleardb}
%\begin{definition}
% \cs{DTLcleardb}\marg{db name}
%\end{definition}
% Clears the database. (Makes it empty, but still defined.)
%\changes{2.03}{2009 November 15}{new}
%\changes{3.0}{2025-03-03}{changed to new document command}
%    \begin{macrocode}
\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
      }
      { }
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdeletedb}
%\begin{definition}
% \cs{DTLdeletedb}\marg{db name}
%\end{definition}
% Deletes a database.
%\changes{2.03}{2009 November 15}{new}
%\changes{3.0}{2025-03-03}{changed to new document command}
%    \begin{macrocode}
\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}{}%
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgnewdb}
%\begin{definition}
% \cs{DTLgnewdb}\marg{db name}
%\end{definition}
%Since new registers are always globally defined, this is equivalent
%to \cs{DTLnewdb}.
%\changes{2.13}{2013-01-15}{new}
%    \begin{macrocode}
\newcommand\DTLgnewdb{\DTLnewdb}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgdeletedb}
%\begin{definition}
% \cs{DTLgdeletedb}\marg{db name}
%\end{definition}
% Deletes a database. (Global version.)
%\changes{2.13}{2013-01-15}{new}
%\changes{3.0}{2025-03-03}{changed to new document command}
%    \begin{macrocode}
\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
     }
     { }
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgcleardb}
%\begin{definition}
% \cs{DTLgcleardb}\marg{db name}
%\end{definition}
% Clears the database. (Global version.)
%\changes{2.13}{2013-01-15}{new}
%\changes{3.0}{2025-03-03}{changed to new document command}
%    \begin{macrocode}
\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
    }
    { }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLrowcount}
%\begin{definition}
% \cs{DTLrowcount}\marg{db name}
%\end{definition}
% The number of rows in the database called \meta{db name}.
% (Doesn't check if database exists.)
%    \begin{macrocode}
\newcommand*{\DTLrowcount}[1]{
 \int_use:c { dtlrows@ #1 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLcolumncount}
%\begin{definition}
% \cs{DTLcolumncount}\marg{db name}
%\end{definition}
% The number of columns in the database called \meta{db name}.
% (Doesn't check if database exists.)
%\changes{2.0}{2009 February 27}{new}%
%    \begin{macrocode}
\newcommand*{\DTLcolumncount}[1]{
  \int_use:c { dtlcols@ #1 }
}
%    \end{macrocode}
%\end{macro}
%Syntax: \marg{db-name}\marg{not empty}\marg{empty}\marg{no exists}
%    \begin{macrocode}
\cs_new:Nn \datatool_db_state:nnnn
{
  \DTLifdbexists { #1 }
  { \@DTLifdbempty { #1 } { #3 } { #2 } }
  { #4 }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLifdbempty}
%\begin{definition}
% \cs{DTLifdbempty}\marg{name}\marg{true part}\marg{false part}
%\end{definition}
% Check if named database is empty (i.e.\ no rows have been added).
%    \begin{macrocode}
\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
     }
     { }
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@DTLifdbempty}
%\begin{definition}
% \cs{@sDTLifdbempty}\marg{name}\marg{true part}\marg{false part}
%\end{definition}
% Check if named existing database is empty. (No check performed
% to determine if the database exists.)
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand{\@DTLifdbempty}[3]{%
  \int_if_zero:nTF
    { \int_use:c { dtlrows@ #1 } }
   { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLnewrow}
%\begin{definition}
% \cs{DTLnewrow}\marg{db name}
%\end{definition}
% Add a new row to named database. The starred version doesn't
% check for the existence of the database.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLnewrow { s m }
{
  \IfBooleanTF { #1 }
   { \@sDTLnewrow { #2 } }
   { \@DTLnewrow { #2 } }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@DTLnewrow}
%\begin{definition}
% \cs{@DTLnewrow}\marg{db name}
%\end{definition}
% Add a new row to named database. (Checks for the existence
% of the database.)
%\changes{2.13}{2013-01-15}{fixed typo in \cs{PackageError}}
%    \begin{macrocode}
\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
    }
    { }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@sDTLnewrow}
%\begin{definition}
% \cs{@sDTLnewrow}\marg{db name}
%\end{definition}
% Add a new row to named existing database. (No check performed
% to determine if the database exists.)
%\changes{2.0}{2009 February 27}{new}
%Version 3.0: check global boolean. Note that pre 3.0 this command
%only made a global change.
%    \begin{macrocode}
\newcommand*{\@sDTLnewrow}[1]{%
  \bool_if:NTF \l__datatool_db_global_bool
  {
%    \end{macrocode}
% Globally increment row count.
%    \begin{macrocode}
    \int_gincr:c { dtlrows@#1 }
  }
  {
%    \end{macrocode}
% Locally increment row count.
%    \begin{macrocode}
    \int_incr:c { dtlrows@#1 }
  }
%    \end{macrocode}
% Append an empty row to the database
%    \begin{macrocode}
  \__datatool_dtldb_put_right:nx
    { #1 }
   {
     \__datatool_row_markup:vn
      { dtlrows@#1 } % row count
      { }
   }
%    \end{macrocode}
% Display message on terminal and log file if in verbose mode.
%    \begin{macrocode}
  \dtl@message
    { New ~ row ~ added ~ to ~ database ~ `#1' }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlcolumnnum}
% Count register to keep track of column index.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcount\dtlcolumnnum
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlrownum}
% Count register to keep track of row index.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcount\dtlrownum
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifhaskey}
%\begin{definition}
%\cs{DTLifhaskey}\meta{db name}\meta{key}\meta{true part}\meta{false
% part}
%\end{definition}
% Checks if the named database \meta{db name} has a column with label
% \meta{key}. If column exists, do \meta{true part} otherwise
% do \meta{false part}. The starred version doesn't check if
% the named database exists.
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLifhaskey { s m m m m }
{
  \IfBooleanTF { #1 }
  {
    \@sDTLifhaskey { #2 } { #3 } { #4 } { #5 }
  }
  {
    \@DTLifhaskey { #2 } { #3 } { #4 } { #5 }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@DTLifhaskey}
% Unstarred version of \cs{DTLifhaskey}
%    \begin{macrocode}
\newcommand{\@DTLifhaskey} [4]
{
  \DTLifdbexists { #1 }
  {
    \@sDTLifhaskey { #1 } { #2 } { #3 } { #4 }
  }
  {
    \PackageError { datatool }
    { Database ~ `#1' ~ doesn't ~ exist }
    { }
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@sDTLifhaskey}
% Starred version of \cs{DTLifhaskey}. This simply tests for the
% existence of the key to column command.
%    \begin{macrocode}
\newcommand{\@sDTLifhaskey}{ \datatool_if_has_key:nnTF }
%    \end{macrocode}
%\end{macro}
%Version 3.0: the above now defined to use the new conditional:
%    \begin{macrocode}
\prg_new_conditional:Npnn \datatool_if_has_key:nn #1 #2 { p, T, F, TF }
{
  \tl_if_exist:cTF { dtl@ci@ #1 @ #2 }
  {
%    \end{macrocode}
% Key defined
%    \begin{macrocode}
    \prg_return_true:
  }
  {
%    \end{macrocode}
% Key not defined
%    \begin{macrocode}
    \prg_return_false:
  }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLgetcolumnindex}
%\begin{definition}
%\cs{DTLgetcolumnindex}\marg{cs}\marg{db}\marg{key}
%\end{definition}
% Gets index for column with label \meta{key} from database
% \meta{db} and stores in \meta{cs} which must be a control
% sequence.
% Unstarred version checks if database and key exist, unstarred
% version doesn't perform any checks.
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgetcolumnindex { s m m }
{
  \IfBooleanTF { #1 }
  { \@sdtl@getcolumnindex { #2 } { #3 } }
  { \@dtl@getcolumnindex { #2 } { #3 } }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@getcolumnindex}
% Unstarred version of \cs{DTLgetcolumnindex}
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\@dtl@getcolumnindex} [3]
{
%    \end{macrocode}
% Check if database exists.
%    \begin{macrocode}
  \DTLifdbexists { #2 }
  {
%    \end{macrocode}
% Database exists. Now check if key exists.
%    \begin{macrocode}
    \@sDTLifhaskey { #2 } { #3 }
    {
%    \end{macrocode}
% Key exists so go ahead and get column index.
%    \begin{macrocode}
      \@sdtl@getcolumnindex { #1 } { #2 } { #3 }
    }
    {
%    \end{macrocode}
% Key doesn't exists in named database.
%    \begin{macrocode}
      \PackageError { datatool }
      { Database ~ `#2' ~ doesn't ~ contain ~ key ~ `#3' }
      { }
    }
  }
  {
%    \end{macrocode}
% Named database doesn't exist.
%    \begin{macrocode}
    \PackageError { datatool }
    { Database ~ `#2' ~ doesn't ~ exist }
    { }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@getcolumnindex}
% Starred version of \cs{DTLgetcolumnindex}.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\@sdtl@getcolumnindex}[3]{%
  \tl_set_eq:Nc #1 { dtl@ci@#2@#3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlcolumnindex}
%\begin{definition}
% \cs{dtlcolumnindex}\marg{db}\marg{key}
%\end{definition}
% Column index corresponding to \meta{key} in database \meta{db}.
%Expands to zero if the key or database doesn't exist.
%\changes{2.0}{2009 February 27}{new}
%\changes{2.03}{2009 November 15}{renamed \cs{dtl@columnindex} to
%\cs{dtlcolumnindex}}
%\changes{3.0}{2025-03-03}{expand to 0 if undefined}
%    \begin{macrocode}
\newcommand*{\dtlcolumnindex}[2]{%
  \tl_if_exist:cTF { dtl@ci@#1@#2 }
   { \tl_use:c { dtl@ci@#1@#2 } }
   { \c_zero_int }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\begin{macro}{\DTLgetkeyforcolumn}
%\begin{definition}
%\cs{DTLgetkeyforcolumn}\marg{key cs}\marg{db}\marg{column index}
%\end{definition}
% Gets the key associated with the given column index and stores
% in \meta{key cs}. Unstarred version doesn't perform checks.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\DTLgetkeyforcolumn}{%
  \@ifstar\@sdtlgetkeyforcolumn\@dtlgetkeyforcolumn}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtlgetkeyforcolumn}
%    \begin{macrocode}
\newcommand*{\@dtlgetkeyforcolumn}[3]{%
  \DTLifdbexists{#2}%
  {%
%    \end{macrocode}
% Check if index is in range.
%    \begin{macrocode}
    \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}{}%
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@sdtlgetkeyforcolumn}
%\begin{definition}
%\cs{@sdtlgetkeyforcolumn}\marg{key cs}\marg{db}\marg{column index}
%\end{definition}
% Gets the key associated with the given column index and stores
% in \meta{key cs}
%    \begin{macrocode}
\newcommand*{\@sdtlgetkeyforcolumn}[3]{%
  \edef\@dtl@dogetkeyforcolumn{\noexpand\@dtl@getkeyforcolumn
    {\noexpand#1}{#2}{\number#3}}%
  \@dtl@dogetkeyforcolumn
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtl@getkeyforcolumn}
% Column index must be fully expanded before use.
%\changes{2.10}{2012-07-18}{fixed bug}
%    \begin{macrocode}
\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
}
%    \end{macrocode}
%\end{macro}
%
% Define some commands to indicate the various data types a database
% may contain. Version 3.0: note that \sty{datatool-base} now has
% constants but the unknown type is -1 in that case.
%\begin{macro}{\DTLunsettype}
% Unknown data type. (All entries in the column are blank so the
% type can't be determined.)
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{changed to \cs{newcommand}}
%    \begin{macrocode}
\newcommand*\DTLunsettype{}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLstringtype}
% Data type representing strings.
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{changed to \cs{newcommand}}
%    \begin{macrocode}
\newcommand*\DTLstringtype{0}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLinttype}
% Data type representing integers.
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{changed to \cs{newcommand}}
%    \begin{macrocode}
\newcommand*\DTLinttype{1}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLrealtype}
% Data type representing real numbers.
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{changed to \cs{newcommand}}
%    \begin{macrocode}
\newcommand*\DTLrealtype{2}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLcurrencytype}
% Data type representing currency.
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{changed to \cs{newcommand}}
%    \begin{macrocode}
\newcommand*\DTLcurrencytype{3}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgetdatatype}
%\begin{definition}
%\cs{DTLgetdatatype}\marg{cs}\marg{db}\marg{key}
%\end{definition}
% Gets data type associated with column labelled \meta{key} in
% database \meta{db} and stores in \meta{cs}. Type may be:
% \meta{empty} (unset), 0 (string), 1 (int),
% 2 (real), 3 (currency). Unstarred version checks if the database
% and key exist, starred version doesn't.
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\DTLgetdatatype}{%
  \@ifstar\@sdtlgetdatatype\@dtlgetdatatype
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtlgetdatatype}
% Unstarred version of \cs{DTLgetdatatype}.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\@dtlgetdatatype}[3]{%
%    \end{macrocode}
% Check if database exists.
%    \begin{macrocode}
  \DTLifdbexists{#2}%
  {%
%    \end{macrocode}
% Check if key exists in this database.
%    \begin{macrocode}
    \@sDTLifhaskey{#2}{#3}%
    {%
%    \end{macrocode}
% Get data type for this database and key.
%    \begin{macrocode}
      \@sdtlgetdatatype{#1}{#2}{#3}%
    }%
    {%
%    \end{macrocode}
% Key doesn't exist in this database.
%    \begin{macrocode}
      \PackageError{datatool}{Key `#3' undefined in database `#2'}{}%
    }%
  }%
  {%
%    \end{macrocode}
% Database doesn't exist.
%    \begin{macrocode}
    \PackageError{datatool}{Database `#2' doesn't exist}{}%
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@sdtlgetdatatype}
% Starred version of \cs{DTLgetdatatype}. This ensures that
% the key is fully expanded before being passed to
% \cs{@dtl@getdatatype}.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\@sdtlgetdatatype}[3]{%
  \edef\@dtl@dogetdata{\noexpand\@dtl@getdatatype{\noexpand#1}%
   {\expandafter\the\csname dtlkeys@#2\endcsname}%
   {\dtlcolumnindex{#2}{#3}}}%
  \@dtl@dogetdata
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@getdatatype}
%\begin{definition}
%\cs{@dtl@getdatatype}\marg{cs}\marg{data specs}\marg{column index}
%\end{definition}
% Column index must be expanded.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\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 
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\begin{macro}{\@dtl@getprops}
%\begin{definition}
%\cs{@dtl@getprops}\marg{key cs}\marg{type cs}\marg{header toks}\marg{before toks}\marg{after toks}\marg{data specs}\marg{column index}
%\end{definition}
% Column index must be expanded.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%\end{macro}
%Provide a newer L3 alternative. NB unlike \cs{@dtl@getprops}
%this uses token list variables not registers.
%Syntax: \meta{key cs}\meta{type cs}\meta{header tl var}\meta{before
%tl var}\meta{after tl var}\marg{data specs}\marg{column index}
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%
%Get all meta data for the given
%column where the before and after stuff isn't required.
%Automatically sets scratch variables.
%
%Syntax: \marg{col specs}\marg{col idx}
%
% Sets: \verb|\l__datatool_item_key_tl|,
% \verb|\l__datatool_item_head_tl| and
% \verb|\l__datatool_item_type_int|.
%    \begin{macrocode}
\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
%    \end{macrocode}
% In case the column isn't defined:
%    \begin{macrocode}
      \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 }
%    \end{macrocode}
%Similar but only type required.
%    \begin{macrocode}
\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
%    \end{macrocode}
% In case the column isn't defined:
%    \begin{macrocode}
      \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 }
%    \end{macrocode}
%Only type and header required:
%    \begin{macrocode}
\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
%    \end{macrocode}
% In case the column isn't defined:
%    \begin{macrocode}
      \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 
  }
}
%    \end{macrocode}
%Only type and key required:
%    \begin{macrocode}
\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
%    \end{macrocode}
% In case the column isn't defined:
%    \begin{macrocode}
      \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 }
%    \end{macrocode}
%Only key required:
%    \begin{macrocode}
\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
%    \end{macrocode}
% In case the column isn't defined:
%    \begin{macrocode}
      \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 }
%    \end{macrocode}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\begin{macro}{\@dtl@before}
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newtoks\@dtl@before
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtl@after}
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newtoks\@dtl@after
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtl@colhead}
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newtoks\@dtl@colhead
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
% Set dtlkeys@\meta{db-name} register according to the global
% setting.
%Syntax: \marg{db-name}\marg{content}
%    \begin{macrocode}
\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 }
%    \end{macrocode}
% Append to dtlkeys@\meta{db-name} register according to the global
% setting.
%Syntax: \marg{db-name}\marg{content}
%    \begin{macrocode}
\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 }
%    \end{macrocode}
% Set dtldb@\meta{db-name} register according to the global
% setting.
%Syntax: \marg{db-name}\marg{content}
%    \begin{macrocode}
\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 }
%    \end{macrocode}
% Append to dtldb@\meta{db-name} register according to the global
% setting.
%Syntax: \marg{db-name}\marg{content}
%    \begin{macrocode}
\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 }
%    \end{macrocode}
% Set dtldb@\meta{db-name} register according to the global
% setting where concatenation is used with before and after
% registers.
%Syntax: \marg{db-name}\meta{before reg}\marg{content}\meta{after
%reg}
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%
%
%Expands to numeric value of the given data type. Empty will expand
%to -1.
%    \begin{macrocode}
\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 } }
  }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLaddcolumn}
%\begin{definition}
%\cs{DTLaddcolumn}\marg{db}\marg{key}
%\end{definition}
% Adds a column with given key to given column. No data is added to
% the column. The starred version doesn't check for the existence of
% the database.
%\changes{2.11}{2012-09-25}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLaddcolumnwithheader}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLaddcolumnwithheader}\marg{db}\marg{key}\marg{header}
%\end{definition}
%    \begin{macrocode}
\NewDocumentCommand \DTLaddcolumnwithheader { s m m m }
{
  \IfBooleanTF { #1 }
  { 
    \__datatool_add_column_with_header:nxnn { #2 } { #3 } { -1 } { #4 }
  }
  {
    \@DTLaddcolumnwithheader { #2 } { #3 } { #4 }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@DTLaddcolumnwithheader}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\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
    }
    {}
  }
}
%    \end{macrocode}
%\end{macro}
%
%Syntax: \marg{db}\marg{key}\marg{type}\marg{header}
%    \begin{macrocode}
\cs_new:Nn \__datatool_add_column_with_header:nnnn
{
  \bool_if:NTF \l__datatool_db_global_bool
  {
%    \end{macrocode}
% Globally increment column count.
%    \begin{macrocode}
    \int_gincr:c { dtlcols@#1 }
    \int_set_eq:Nc \dtlcolumnnum { dtlcols@#1 }
%    \end{macrocode}
% Set column index for this key.
%    \begin{macrocode}
    \csxdef{dtl@ci@#1@#2}{\number\dtlcolumnnum}%
%    \end{macrocode}
% Globally append to property list
%    \begin{macrocode}
    \__datatool_token_register_gput_right:cx
      { dtlkeys@#1 }
    {
      \__datatool_column_markup:Vnnn
         \dtlcolumnnum { #2 } { #3 } { #4 }
    }
  }
  {
%    \end{macrocode}
% Locally increment column count.
%    \begin{macrocode}
    \int_incr:c { dtlcols@#1 }
    \int_set_eq:Nc \dtlcolumnnum { dtlcols@#1 }
%    \end{macrocode}
% Set column index for this key.
%    \begin{macrocode}
    \csedef{dtl@ci@#1@#2}{\number\dtlcolumnnum}%
%    \end{macrocode}
% Locally append to property list
%    \begin{macrocode}
    \__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 }
%    \end{macrocode}
%
%\begin{macro}{\@dtl@updatekeys}
%\begin{definition}
%\cs{@dtl@updatekeys}\marg{db}\marg{key}\marg{value}
%\end{definition}
% Adds key to database's key list if it doesn't exist.
% The value is used to update the data type associated with that key.
% Key must be fully expanded. Doesn't check if database exists.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\@dtl@updatekeys}[3]{%
%    \end{macrocode}
% Check if key already exists
%    \begin{macrocode}
  \@sDTLifhaskey{#1}{#2}%
  {%
%    \end{macrocode}
% Key exists, may need to update data type. First get the
% column index.
%    \begin{macrocode}
    \int_set:Nn \dtlcolumnnum { \dtlcolumnindex { #1 } { #2 } }
%    \end{macrocode}
% Get the properties for this column
%\changes{2.11}{2012-09-25}{remove unwanted space}
%    \begin{macrocode}
    \__datatool_get_keydata:NNNNNvV
      \@dtl@key \@dtl@type \@dtl@colhead \@dtl@before \@dtl@after
      { dtlkeys@#1 } \dtlcolumnnum
%    \end{macrocode}
% Is the value empty?
%\changes{2.14}{2013-06-28}{expand value before testing if it's
%empty}
%\changes{2.16}{2013-08-16}{reverted to not expanding value (2.14 change
%causes an error with fragile commands). Fix now in
%\cs{@dtl@checknumerical}}
%    \begin{macrocode}
    \ifstrempty{#3}%
    {%
%    \end{macrocode}
% Leave data type as it is
%    \begin{macrocode}
    }%
    {%
%    \end{macrocode}
% Make a copy of current data type
%    \begin{macrocode}
      \let\@dtl@oldtype\@dtl@type
%    \end{macrocode}
% Check the data type for this entry (stored in \cs{@dtl@datatype})
%    \begin{macrocode}
      \@dtl@checknumerical{#3}%
      \ifnum \@dtl@datatype = \c_datatool_unknown_int
      \else
%    \end{macrocode}
% If this column currently has no data type assigned to it
% then use the new type.
%    \begin{macrocode}
        \ifdefempty{\@dtl@type}%
        {%
          \edef\@dtl@type{\number\@dtl@datatype}%
        }%
        {%
%    \end{macrocode}
% This column already has an associated data type but it may
% need updating.
%    \begin{macrocode}
          \ifcase\@dtl@datatype % string
%    \end{macrocode}
% String overrides all other types
%    \begin{macrocode}
            \def\@dtl@type{0}%
          \or % int
%    \end{macrocode}
% All other types override int, so leave it as it is
%    \begin{macrocode}
          \or % real
%    \end{macrocode}
% Real overrides int, but not currency or string
%    \begin{macrocode}
            \ifnum\@dtl@type=1\relax
              \def\@dtl@type{2}%
            \fi
          \or % currency
%    \end{macrocode}
% Currency overrides int and real but not string
%    \begin{macrocode}
            \ifnum\@dtl@type>0\relax
              \def\@dtl@type{3}%
            \fi
          \fi
        }%
      \fi
%    \end{macrocode}
% Has the data type been updated?
%    \begin{macrocode}
      \ifx\@dtl@oldtype\@dtl@type
%    \end{macrocode}
% No change needed
%    \begin{macrocode}
      \else
%    \end{macrocode}
% Update required
%    \begin{macrocode}
        \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
    {
%    \end{macrocode}
% Key doesn't exist. Increment column count.
%    \begin{macrocode}
      \int_gincr:c { dtlcols @ #1}
      \int_set_eq:Nc \dtlcolumnnum { dtlcols @ #1 }
%    \end{macrocode}
% Set column index for this key.
%    \begin{macrocode}
      \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
    }
%    \end{macrocode}
% Get data type for this entry (stored in \cs{@dtl@datatype})
%    \begin{macrocode}
    \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
      }
    }
%    \end{macrocode}
% Append to property list
%    \begin{macrocode}
    \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 }
      }
    }
  }
}
%    \end{macrocode}
%\end{macro}
%Syntax: \marg{db}\marg{key}\marg{type}
%
%Update column meta data identified by column key
%where the type has already been found. If no column exists, a new
%one will be created.
%    \begin{macrocode}
\cs_new:Nn \__datatool_update_meta_data_with_type:nnn
{
%    \end{macrocode}
% Check if key already exists
%    \begin{macrocode}
  \@sDTLifhaskey { #1 } { #2 }
  {
%    \end{macrocode}
% Key exists, may need to update data type. First get the
% column index.
%    \begin{macrocode}
    \int_set:Nn \dtlcolumnnum
       { \dtlcolumnindex { #1 } { #2 } }
%    \end{macrocode}
% Get the properties for this column
%\changes{2.11}{2012-09-25}{remove unwanted space}
%    \begin{macrocode}
    \__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
%    \end{macrocode}
% Treat empty type as unknown.
%    \begin{macrocode}
    \tl_if_empty:NT \l__datatool_item_type_tl
     {
       \tl_set:Nn \l__datatool_item_type_tl
         { \c_datatool_unknown_int }
     }
%    \end{macrocode}
% Current column type:
%    \begin{macrocode}
   \int_set:Nn \l__datatool_tmp_datatype_int
     { \l__datatool_item_type_tl }
%    \end{macrocode}
% New column type:
%    \begin{macrocode}
   \int_set:Nn \@dtl@datatype { #3 }
%    \end{macrocode}
% Check if type needs updating:
%    \begin{macrocode}
   \__datatool_update_datatype:
%    \end{macrocode}
%If type has changed, update required.
%    \begin{macrocode}
   \int_compare:nNnF { \@dtl@datatype } = { \l__datatool_tmp_datatype_int }
    {
%    \end{macrocode}
% Update required
%    \begin{macrocode}
     \__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
    {
%    \end{macrocode}
% Key doesn't exist. Increment column count.
%    \begin{macrocode}
      \int_gincr:c { dtlcols @ #1}
      \int_set_eq:Nc \dtlcolumnnum { dtlcols @ #1 }
%    \end{macrocode}
% Set column index for this key.
%    \begin{macrocode}
      \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
    }
%    \end{macrocode}
% Append to property list
%    \begin{macrocode}
    \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 }
%    \end{macrocode}
%
%Syntax: \marg{db}\marg{col idx}\marg{type}
%
%Similar but the column is referenced by its index.
%    \begin{macrocode}
\cs_new:Nn \__datatool_update_meta_data_col_index_with_type:nnn
{
%    \end{macrocode}
% Does the column already exist?
%    \begin{macrocode}
  \int_compare:nNnTF
    { #2 } > { \DTLcolumncount { #1 } }
   {
%    \end{macrocode}
% New column but the index can only be one more than the total
% column count.
%    \begin{macrocode}
     \int_compare:nNnTF
       { #2 } = { \DTLcolumncount { #1 } + \c_one_int }
      {
%    \end{macrocode}
% Create default column key and add new column.
%    \begin{macrocode}
        \tl_set:Nn \l__datatool_item_key_tl { \dtldefaultkey #2 }
        \int_gincr:c { dtlcols @ #1}
%    \end{macrocode}
% Append to property list
%    \begin{macrocode}
        \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
         }
      }
   }
   {
%    \end{macrocode}
% Check index not invalid.
%    \begin{macrocode}
     \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' )
         }
      }
      {
%    \end{macrocode}
% Existing column.  Get the properties.
%    \begin{macrocode}
        \__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 }
%    \end{macrocode}
% Treat empty type as unknown.
%    \begin{macrocode}
        \tl_if_empty:NT
           \l__datatool_item_type_tl
         {
           \tl_set:Nn 
             \l__datatool_item_type_tl
             { \c_datatool_unknown_int }
         }
%    \end{macrocode}
% If the original type is a string, don't update.
%    \begin{macrocode}
        \int_compare:nNnF
           { \l__datatool_item_type_tl }
            =
           { \c_datatool_string_int }
         {
%    \end{macrocode}
% If the new type is greater than the old, then it needs updating.
%    \begin{macrocode}
           \int_compare:nNnT
               { #3 } > { \l__datatool_item_type_tl }
            {
%    \end{macrocode}
% Update required
%    \begin{macrocode}
              \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 }
%    \end{macrocode}
%
%
%\begin{macro}{\DTLsetheader}
%\begin{definition}
%\cs{DTLsetheader}\marg{db}\marg{key}\marg{header}
%\end{definition}
% Sets header for column given by \meta{key} in database \meta{db}.
% Starred version doesn't check for existence of database or key.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\DTLsetheader}{\@ifstar\@sDTLsetheader\@DTLsetheader}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@DTLsetheader}
% Unstarred version
%    \begin{macrocode}
\newcommand*{\@DTLsetheader}[3]{%
%    \end{macrocode}
% Check if database exists
%    \begin{macrocode}
  \DTLifdbexists{#1}%
  {%
%    \end{macrocode}
% Check if key exists.
%    \begin{macrocode}
    \@sDTLifhaskey{#1}{#2}%
    {%
      \@sDTLsetheader{#1}{#2}{#3}%
    }%
    {%
      \PackageError{datatool}{Database ~ `#1' ~ doesn't ~ contain ~ key ~ 
      `#2'}{}%
    }%
  }%
  {%
    \PackageError{datatool}{Database ~ `#1' ~ doesn't ~ exist}{}%
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@sDTLsetheader}
% Starred version
%    \begin{macrocode}
\newcommand*{\@sDTLsetheader}[3]{%
  \expandafter\dtlcolumnnum\expandafter
    =\dtlcolumnindex{#1}{#2}\relax
  \@dtl@setheaderforindex{#1}{\dtlcolumnnum}{#3}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@setheaderforindex}
%\begin{definition}
%\cs{@dtl@setheaderforindex}\marg{db}\marg{column index}\marg{header}
%\end{definition}
% Sets the header for column given by \meta{column index} in
% database \meta{db}. The header must be expanded.
%    \begin{macrocode}
\newcommand*{\@dtl@setheaderforindex}[3]{%
%    \end{macrocode}
% Get the properties for this column
%\changes{2.13}{2013-01-15}{removed spurious space}
%    \begin{macrocode}
  \__datatool_get_keydata:NNNNNve
    \@dtl@key \@dtl@type \@dtl@colhead \@dtl@before \@dtl@after
    { dtlkeys@#1 } { \int_eval:n { #2 } }
%    \end{macrocode}
% Ensure data type variable is numeric.
%    \begin{macrocode}
  \tl_if_empty:NTF \@dtl@type
   {
     \int_set_eq:NN \@dtl@datatype \c_datatool_unknown_int
   }
   {
     \int_set:Nn \@dtl@datatype { \@dtl@type }
   }
%    \end{macrocode}
% Store the header in \cs{@dtl@toks}
%    \begin{macrocode}
  \@dtl@colhead={#3}%
%    \end{macrocode}
% Reconstruct property list
%    \begin{macrocode}
  \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
  }
}
%    \end{macrocode}
%\end{macro}
%Process value to be added to a database according to the above
%settings. This will set both \cs{@dtl@toks} and \verb|\l__datatool_item_value_tl|
%    \begin{macrocode}
\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
  }
  {
%    \end{macrocode}
%Special case if value starts with \cs{dtlspecialvalue}:
%    \begin{macrocode}
   \tl_if_head_eq_meaning:nNTF
      { #1 } \dtlspecialvalue
    {
      \group_begin:
%    \end{macrocode}
%Expand and parse to get the data type.
%    \begin{macrocode}
        \exp_args:Nx \__datatool_parse_datum:n { #1 }
       \exp_args:NNNV
      \group_end:
       \int_set:Nn \@dtl@datatype \@dtl@datatype
%    \end{macrocode}
%Store as is without following the expansion, trim or store datum
%settings.
%    \begin{macrocode}
      \@dtl@toks { #1 }
      \tl_set:NV \l__datatool_item_value_tl \@dtl@toks
    }
    {
%    \end{macrocode}
%This sets the \cs{@dtl@toks} token register for
%backward-compatibility. It also follows the trim and
%expand new value settings.
%    \begin{macrocode}
      \@dtl@setnewvalue { #1 }
%    \end{macrocode}
%Update \verb|\l__datatool_item_value_tl| (which won't be set if
%trim not on.)
%    \begin{macrocode}
      \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
       }
       {
%    \end{macrocode}
%Just parse to get the data type.
%    \begin{macrocode}
         \exp_args:NV \__datatool_parse_datum:n
           \l__datatool_item_value_tl
       }
%    \end{macrocode}
%In case the value was provided in datum format.
%    \begin{macrocode}
      \__datatool_to_weird_datum_no_parse:N
        \l__datatool_item_value_tl 
%    \end{macrocode}
%Update \cs{@dtl@toks}:
%    \begin{macrocode}
      \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 }
%    \end{macrocode}
%
%\begin{macro}{\@dtl@storeandupdate}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\@dtl@storeandupdate}[3]{%
%    \end{macrocode}
% Store the value of this entry in \cs{@dtl@toks} taking the
% expansion setting into account.
%\changes{2.03}{2009 November 15}{value can be expanded before
% adding to database}%
%    \begin{macrocode}
  \__datatool_process_new_value:n { #3 }
%    \end{macrocode}
% This will have parsed the value and set \cs{@dtl@datatype} so it
% can be passed here:
%    \begin{macrocode}
  \__datatool_update_meta_data_with_type:nnV
     { #1 } { #2 } \@dtl@datatype
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLnewdbentry}
%\begin{definition}
% \cs{DTLnewdbentry}\marg{db name}\marg{id}\marg{value}.
%\end{definition}
% Adds an entry to the last row (adds new row if database is empty)
% and updates general column information if necessary. The
% starred version doesn't check if the database exists.
%\changes{3.0}{2025-03-03}{switch to new document command}
%    \begin{macrocode}
\NewDocumentCommand \DTLnewdbentry { s }
{
  \int_zero:N \dtlcolumnnum
  \IfBooleanTF { #1 } { \@sDTLnewdbentry } { \@DTLnewdbentry }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@DTLnewdbentry}
% Unstarred version of \cs{DTLnewdbentry}.
%    \begin{macrocode}
\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 }
     }
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@sDTLnewdbentry}
% Starred version of \cs{DTLnewdbentry} (doesn't check if the database exists).
%    \begin{macrocode}
\newcommand*{\@sDTLnewdbentry}[3]{%
%    \end{macrocode}
% Store the value of this entry in \cs{@dtl@toks}
% and update key list.
%    \begin{macrocode}
  \@dtl@storeandupdate { #1 } { #2 } { #3 }
%    \end{macrocode}
% Get the column index
%    \begin{macrocode}
  \int_set:Nn \dtlcolumnnum
    { \dtlcolumnindex { #1 } { #2 } }
%    \end{macrocode}
% Now split off the last row and insert the new entry:
%    \begin{macrocode}
  \tl_set:Nn \l__datatool_item_key_tl { #2 }
  \@s@DTLnewdbentry { #1 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@s@DTLnewdbentry}
%\changes{3.0}{2025-03-03}{new}
%Argument is the database name. The value is expected to be in
%\cs{@dtl@toks}, having been pre-processed by \verb|\__datatool_process_new_value:n|
%    \begin{macrocode}
\newcommand*{\@s@DTLnewdbentry}[1]{%
%    \end{macrocode}
% Get the current row:
%    \begin{macrocode}
  \dtlgetrow { #1 } { \int_use:c { dtlrows@#1} } 
%    \end{macrocode}
% Check if this row already has an entry for the given column.
%    \begin{macrocode}
  \exp_args:NNV 
    \dtlgetentryfromcurrentrow
    \dtl@entry
    \dtlcolumnnum
  \datatool_if_null:NTF \dtl@entry
  {
%    \end{macrocode}
% There are no entries in this row for the given column.
% Add this entry.
%    \begin{macrocode}
    \__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
%    \end{macrocode}
% Print information message if in verbose mode.
%    \begin{macrocode}
    \dtl@message
    {
     Added ~ \l__datatool_item_key_tl \c_space_tl ~
      -> ~ \the\@dtl@toks \c_space_tl ~ to ~ database ~ `#1'
    }
  }
  {
%    \end{macrocode}
% There's already an entry for the given column in this row
%    \begin{macrocode}
    \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
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\changes{2.0}{2009 February 27}{removed \cs{@dtl@setidtype}}
%\changes{2.0}{2009 February 27}{removed \cs{@dtl@setkeys}}
%\changes{2.0}{2009 February 27}{removed \cs{@dtl@getidtype}}
%
%\begin{macro}{\DTLifdbexists}
%\begin{definition}
%\cs{DTLifdbexists}\marg{db name}\marg{true part}\marg{false part}
%\end{definition}
% Checks if a data base with the given name exists.
%    \begin{macrocode}
\newcommand{\DTLifdbexists}[3]{
  \ifcsdef { dtldb@#1 } { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\changes{2.0}{2009 February 27}{removed \cs{@dtl@ifrowcontains}}
%\changes{2.0}{2009 February 27}{removed \cs{dtl@getentryvalue}}
%\changes{2.0}{2009 February 27}{removed \cs{dtl@getentryid}}
%
%These commands are provided for DTLTEX v3.0 format.
%\begin{macro}{\DTLdbNewRow}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLdbNewRow {  } 
 { 
   \@sDTLnewrow { \l__datatool_default_dbname_tl } 
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdbNewEntry}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLdbNewEntry { m m } 
 { 
  \@sDTLnewdbentry { \l__datatool_default_dbname_tl } { #1 } { #2 }
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdbSetHeader}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLdbSetHeader { m m } 
 { 
  \@sDTLsetheader { \l__datatool_default_dbname_tl } { #1 } { #2 }
 }
%    \end{macrocode}
%\end{macro}
%
%\subsection{Accessing Data}
%
%\begin{macro}{\DTLassign}
%\begin{definition}
%\cs{DTLassign}\marg{db}\marg{row idx}\marg{assign list}
%\end{definition}
% Assigns values given in \meta{assign list} for row \meta{row idx} in database
% \meta{db}. (Where \meta{assign list} is in the same form as in
% \cs{DTLforeach}.) This command doesn't check the `global' option.
%\changes{2.10}{2012-07-18}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLassign { m m m }
{
  \DTLifdbexists { #1 }
  {
%    \end{macrocode}
% Grouped in the event that \cs{dtlcurrentrow} is already in use.
% (Assignments in \cs{@dtl@assign} are global.)
%    \begin{macrocode}
    {
      \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
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLassignfirstmatch}
%\begin{definition}
%\cs{DTLassignfirstmatch}\marg{db}\marg{col key}\marg{value}\marg{assign list}
%\end{definition}
% Applies the assignment list to the first row that has the given
% value in the given column. (Value must be expanded.)
% This command doesn't check the `global' option.
%\changes{2.20}{2014-02-03}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLassignfirstmatch { m m m m }
{
  \dtl@assignfirstmatch { #3 } { #1 } { #2 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\xDTLassignfirstmatch}
%\begin{definition}
%\cs{xDTLassignfirstmatch}\marg{db}\marg{col key}\marg{value}\marg{assign list}
%\end{definition}
% Applies the assignment list to the first row that has the given
% value in the given column. (Performs \emph{one level} expansion on
% \meta{value}.)
%\changes{2.20}{2014-02-03}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \xDTLassignfirstmatch { m m m m }
{
  \exp_args:Nx
   \dtl@assignfirstmatch
     { \expandonce { #3 } }
     { #1 } { #2 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@assignfirstmatch}
%\begin{definition}
%\cs{dtl@assignfirstmatch}\marg{value}\marg{db}\marg{col key}\marg{assign list}
%\end{definition}
% Internal swaps the ordering around so the value is first. (This
% just makes it easier for \cs{xDTLassignfirstmatch}.)
%    \begin{macrocode}
\newcommand*{\dtl@assignfirstmatch}[4]{
  \DTLifdbexists { #2 }
  {%
%    \end{macrocode}
% Grouped in the event that \cs{dtlcurrentrow} is already in use.
% (Assignments in \cs{@dtl@assign} are global.)
%    \begin{macrocode}
    {
%    \end{macrocode}
% Get row idx:
%    \begin{macrocode}
      \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
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLassignfromcurrentrow}
%\begin{definition}
%\cs{DTLassignfromcurrentrow}\marg{assign list}
%\end{definition}
%For use where the current row has already been selected.
%The placeholder \cs{dtldbname} should already have been set.
%Unlike the above, this performs a local assignment.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\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
       }
     }
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@assign}
%\begin{definition}
%\cs{@dtl@assign}\marg{list}\marg{db}
%\end{definition}
% Assigns commands according to the given keys.
% The current row must be stored in \cs{dtlcurrentrow}.
%Version 3.0: definition rewritten in \LaTeX3. The command name and
%syntax remains the same.
%\changes{2.0}{2009 February 27}{updated to use new database
% structure}
%\changes{2.20}{2014-02-03}{Added check for empty argument}%
%\changes{3.0}{2025-03-03}{updated to \LaTeX3}
%    \begin{macrocode}
\newcommand*{\@dtl@assign}[2]{%
  \__datatool_assign_cskey_list:nnn { #1 } { #2 } { \c_true_bool }
}
%    \end{macrocode}
%\end{macro}
% New implementation:
%    \begin{macrocode}
\cs_new:Nn \__datatool_assign_cskey_list:nnn
{
  \keyval_parse:nnn
    { \__datatool_cskey_missing_val:n }
    { \__datatool_assign_parsed_cskey:nnnn { #2 } { #3 } }
    { #1 }
}
%    \end{macrocode}
%    \begin{macrocode}
\cs_new:Nn \__datatool_assign_parsed_cskey:nnnn
{
  \__datatool_assign_cskey:Nnnn { #3 } { #4 } { #1 } { #2 }
}
%    \end{macrocode}
%
%\begin{definition}
%\cs{\_\_datatool\_assign\_cskey:Nnn} \meta{tl
%var}\marg{key}\marg{db name}
%\end{definition}
% Assign command to the value of the item in the current row at the
% column identified by key. Local assignment:
%    \begin{macrocode}
\cs_new:Nn \__datatool_assign_cskey:Nnn
{
  \__datatool_assign_cskey:Nnnn { #1 } { #2 } { #3 } { \c_false_bool }
}
%    \end{macrocode}
% Global assignment:
%    \begin{macrocode}
\cs_new:Nn \__datatool_gassign_cskey:Nnn
{
  \__datatool_assign_cskey:Nnnn { #1 } { #2 } { #3 } { \c_false_bool }
}
%    \end{macrocode}
%\begin{definition}
%\cs{\_\_datatool\_assign\_cskey:Nnnn} \meta{tl
%var}\marg{key}\marg{db name}\marg{bool}
%\end{definition}
% If \meta{bool} true assign globally, otherwise local assignment.
%    \begin{macrocode}
\cs_new:Nn \__datatool_assign_cskey:Nnnn
{
  \tl_if_single:nTF { #1 }
  {
%    \end{macrocode}
% Replicate old behaviour of \cs{@dtl@assigncmd} which set
% \cs{@dtl@dbname} to the database name:
%    \begin{macrocode}
    \tl_gset:Nx \@dtl@dbname { #3 }
%    \end{macrocode}
%Check if the given key exists:
%    \begin{macrocode}
    \@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 
    }
  }
}
%    \end{macrocode}
%
%\begin{macro}{\@dtl@assigncmd}
%\begin{definition}
% \cs{@dtl@assigncmd}\meta{cmd}=\meta{id}\cs{@nil}
%\end{definition}
% Version 3.0: removed.
%\changes{2.03}{2009 November 15}{modified to ignore spaces after
% commas}
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%\begin{macro}{\@dtl@assigncmdnoop}
% Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\@dtl@setnull}
%\cs{@dtl@setnull}\marg{cmd}\marg{id} globally sets \meta{cmd} to either
% \cs{@dtlstringnull} or \cs{@dtlnumbernull} depending on the data 
% type for \meta{id}. (Database
% name should be stored in \cs{@dtl@dbname} prior to use.)
%\changes{2.0}{2009 February 27}{modified to use new database
% structure}
%    \begin{macrocode}
\newcommand*{\@dtl@setnull}[2]{
%    \end{macrocode}
% Check if database given by \cs{@dtl@dbname} has the required key.
%    \begin{macrocode}
  \@sDTLifhaskey { \@dtl@dbname } { #2 }
  {
%    \end{macrocode}
% Set to the null applicable to the column data type.
%    \begin{macrocode}
    \@@dtl@setnull #1 {#2}
  }
  {
%    \end{macrocode}
% Key not defined in database \cs{@dtl@dbname}.
%    \begin{macrocode}
    \tl_gset_eq:NN #1 \DTLstringnull
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@@dtl@setnull}
% As above, but doesn't check if key exists
%    \begin{macrocode}
\newcommand*{\@@dtl@setnull}[2]{%
 \__datatool_gset_null:Nnn #1 { #2 } { \@dtl@dbname }
}
%    \end{macrocode}
%\end{macro}
%
%New to v3.0:
%
%Global assignment:
%    \begin{macrocode}
\cs_new:Nn \__datatool_gset_null:Nnn
{
%    \end{macrocode}
% Get the data type associated with this key and store in
% \cs{@dtl@type}.
%    \begin{macrocode}
  \@sdtlgetdatatype \@dtl@type { #3 } { #2 }
%    \end{macrocode}
% Check data type.
%    \begin{macrocode}
   \bool_lazy_or:nnTF
     { \tl_if_empty_p:N \@dtl@type }
     { \int_compare_p:n { \@dtl@type < 1 } }
   {
%    \end{macrocode}
% Data type is unknown or 0, so set to string null.
%    \begin{macrocode}
     \tl_gset_eq:NN #1 \DTLstringnull
   }
   {
%    \end{macrocode}
% Data type is numerical, so set to number null.
%    \begin{macrocode}
     \tl_gset_eq:NN #1 \DTLnumbernull
   }
}
%    \end{macrocode}
%Local assignment:
%    \begin{macrocode}
\cs_new:Nn \__datatool_set_null:Nnn
{
%    \end{macrocode}
% Get the data type associated with this key and store in
% \cs{@dtl@type}.
%    \begin{macrocode}
  \@sdtlgetdatatype \@dtl@type { #3 } { #2 }
%    \end{macrocode}
% Check data type.
%    \begin{macrocode}
   \bool_lazy_or:nnTF
     { \tl_if_empty_p:N \@dtl@type }
     { \int_compare_p:n { \@dtl@type < 1 } }
   {
%    \end{macrocode}
% Data type is unknown or 0, so set to string null.
%    \begin{macrocode}
     \tl_set_eq:NN #1 \DTLstringnull
   }
   {
%    \end{macrocode}
% Data type is numerical, so set to number null.
%    \begin{macrocode}
     \tl_set_eq:NN #1 \DTLnumbernull
   }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLifnull}
%\begin{definition}
%\cs{DTLifnull}\marg{command}\marg{true part}\marg{false part}
%\end{definition}
% Checks if \meta{command} is null (either \cs{DTLstringnull} or
% \cs{DTLnumbernull}) if true, does \meta{true part} otherwise
% does \meta{false part}.
%    \begin{macrocode}
\newcommand*{\DTLifnull}[3]{%
  \datatool_if_null:nTF { #1 } { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifnullorempty}
%\begin{definition}
%\cs{DTLifnullorempty}\marg{command}\marg{true part}\marg{false part}
%\end{definition}
%\changes{2.20}{2014-02-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLifnullorempty}[3]{%
  \datatool_if_null_or_empty:nTF { #1 } { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgetkeydata}
%\begin{definition}
%\cs{DTLgetkeydata}\marg{key}\marg{db}\marg{col cs}\marg{type cs}\marg{header cs}
%\end{definition}
% Gets data for given key in database \meta{db}: the column index is 
% stored in \meta{col cs} and data type is stored in \meta{type cs}.
% The unstarred version checks for the existence of the database
% and key, the starred version doesn't.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\DTLgetkeydata}{%
  \@ifstar\@sdtlgetkeydata\@dtlgetkeydata
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtlgetkeydata}
% Unstarred version of \cs{DTLgetkeydata}
%    \begin{macrocode}
\newcommand*{\@dtlgetkeydata}[5]{
%    \end{macrocode}
% Check if the database exists.
%    \begin{macrocode}
  \DTLifdbexists { #2 }
  {
%    \end{macrocode}
% Check if the given key exists in the database.
%    \begin{macrocode}
    \@sDTLifhaskey { #2 } { #1 }
    {
%    \end{macrocode}
% Get the data.
%    \begin{macrocode}
      \@sdtlgetkeydata { #1 } { #2 } #3 #4 #5 
    }
    {
%    \end{macrocode}
% Key not defined in the given database.
%    \begin{macrocode}
      \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
      }
    }
  }
  {
%    \end{macrocode}
% Database not defined.
%    \begin{macrocode}
    \PackageError {datatool}
    {
      Can't ~ get ~ data ~ for ~ key ~ `#1' :
      database ~ `#2' ~ doesn't ~ exist
    }
    { 
      Check ~ that ~ you ~ have ~ correctly ~ spelt ~
      the ~ database ~ name
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@sdtlgetkeydata}
%\cs{@sdtlgetkeydata}\marg{key}\marg{db}\marg{col cs}\marg{type cs}\marg{header cs}
% Starred verison of \cs{DTLgetkeydata}.
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%Backward-compatibility.
%TODO is this needed?
%    \begin{macrocode}
   \__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
}
%    \end{macrocode}
%\end{macro}
%
%The gather value commands are used in \sty{databib}.
%\begin{macro}{\dtl@gathervalues}
%\begin{definition}
%\cs{dtl@gathervalues}\oarg{label}\marg{db name}\marg{row toks}
%\end{definition}
% Stores each element of \meta{row} in \meta{db name} into 
% the command \cs{@dtl@}\meta{label}\texttt{@}\meta{key}, 
% where \meta{key} is the
% key for that element, and \meta{label} defaults to "key".
%\changes{2.0}{2009 February 27}{updated to use new database structure}
%    \begin{macrocode}
\newcommand{\dtl@gathervalues}[3][key]{
  \dtlforeachkey
    ( \@dtl@key , \@dtl@col , \@dtl@type , \@dtl@head )
    \in { #2 }
   \do
   {
%    \end{macrocode}
%\changes{2.21}{2014-03-08}{fixed bug that ignore row tok argument}
%    \begin{macrocode}
     \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
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@g@gathervalues}
%\begin{definition}
%\cs{dtl@g@gathervalues}\oarg{label}\marg{db name}\marg{row toks}
%\end{definition}
% As above but makes global assignments
%\changes{2.21}{2014-03-08}{new}
%    \begin{macrocode}
\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
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlcurrentrow}
% Define token register to store current row.
%    \begin{macrocode}
\newtoks\dtlcurrentrow
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlbeforerow}
% Define token register to store everything before the current row.
%    \begin{macrocode}
\newtoks\dtlbeforerow
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlafterrow}
% Define token register to store everything after the current row.
%    \begin{macrocode}
\newtoks\dtlafterrow
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlgetrow}
%\begin{definition}
%\cs{dtlgetrow}\marg{db}\marg{row idx}
%\end{definition}
% Gets row with index \meta{row idx} from database named \meta{db}
% and stores the row in \cs{dtlcurrentrow}, the preceding rows in
% \cs{dtlbeforerow} and the following rows in \cs{dtlafterrow}.
% The row index, \meta{row idx}, is stored in \cs{dtlrownum}
% and the database name, \meta{db}, is stored in \cs{dtldbname}.
% This assumes that the given row exists.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \dtlgetrow { m m }
{
  \datatool_get_row:nnTF { #1 } { #2 } { } { }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\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 } { }
}
%    \end{macrocode}
%
%\begin{macro}{\edtlgetrowforvalue}
%\changes{2.17}{2013-08-29}{new}
%\begin{definition}
%\cs{edtlgetrowforvalue}\marg{db}\marg{column idx}\marg{value}
%\end{definition}
% A version of \cs{dtlgetrowforvalue} that expands its arguments.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd{\edtlgetrowforvalue}[3]{%
  \__datatool_get_row_for_value:xxx
    { #1 } { \int_eval:n { #2 } } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLfetch}
%\changes{2.17}{2013-08-29}{new}
%\begin{definition}
%\cs{DTLfetch}\marg{db name}\marg{column1 name}\marg{column1
%value}\marg{column2 name}
%\end{definition}
% Fetches and displays the value for \meta{column2 name} in the
% first row where the value of \meta{column1 name} is \meta{column1
% value}. Note that all arguments are expanded.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLfetch { m m m m }
{
  \edtlgetrowforvalue
    { #1 } { \dtlcolumnindex { #1 } { #2 } } { #3 }
  \dtlgetentryfromcurrentrow
   \dtlcurrentvalue { \dtlcolumnindex { #1 } { #4 } }
  \dtlcurrentvalue
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlgetrowforvalue}
%\begin{definition}
%\cs{dtlgetrowforvalue}\marg{db}\marg{column idx}\marg{value}
%\end{definition}
% Like \cs{dtlgetrow}, but gets the row where the entry in column
% \meta{column index} matches \meta{value}. Produces an error if row
% not found.
%\changes{2.11}{2012-09-25}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \dtlgetrowforvalue { m m m }
{
  \__datatool_get_row_for_value:xxn
    { #1 } { \int_eval:n { #2 } } { #3 }
}
%    \end{macrocode}
%\end{macro}
%Triggers an error if row not found:
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%
%
%\begin{macro}{\@dtlgetrow}
%\begin{definition}
%\cs{@dtlgetrow}\marg{data specs}\marg{row idx}
%\end{definition}
% Gets the row specs from \meta{data specs} for row with index
% \meta{row idx} which must be fully expanded.
%    \begin{macrocode}
\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
  }
  {
%    \end{macrocode}
% No such row.
%    \begin{macrocode}
    \dtlbeforerow = { }
    \dtlcurrentrow = { }
    \dtlafterrow = { }
    \int_set:Nn \l__datatool_row_idx_int { -1 }
  }
}
\cs_generate_variant:Nn \__datatool_get_row:nn
  { vn , vV, vx }
%    \end{macrocode}
%\end{macro}
%
%\begin{definition}
%\cs{\_\_datatool\_get\_row:nnN}\marg{data specs}\marg{row index}\meta{tl var}
%\end{definition}
%Similar but provide variables in which to store row markup.
%Before and after content not required. The token list will be
%cleared if row not found, otherwise it will be set to the row
%content.
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%
%\begin{macro}{\dtlrecombine}
%\begin{definition}
%\cs{dtlrecombine}
%\end{definition}
% Recombines database contents from \cs{dtlbeforerow},
% \cs{dtlcurrentrow} and \cs{dtlafterrow}.
%Version 3.0: check global option.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd* \dtlrecombine
{
  \__datatool_dtldb_concat:VNxN
    \dtldbname 
    \dtlbeforerow 
      {
        \__datatool_row_markup:VV
          \dtlrownum
          \dtlcurrentrow
      }
    \dtlafterrow 
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlrecombineomitcurrent}
%\begin{definition}
%\cs{dtlrecombineomitcurrent}
%\end{definition}
%Like \cs{dtlrecombine} but omits \cs{dtlcurrentrow}.
%\changes{2.10}{2012-07-18}{new}
%Version 3.0: check global option.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd* \dtlrecombineomitcurrent
{
%    \end{macrocode}
% Decrement row indices in \cs{dtlafterrow}:
%    \begin{macrocode}
  \dtl@decrementrows{\dtlafterrow}{\dtlrownum}
%    \end{macrocode}
% Reconstruct database contents by concatenating 
% \cs{dtlbeforerow} and \cs{dtlafterrow}
%    \begin{macrocode}
  \__datatool_dtldb_set:Vx
    \dtldbname
    {
      \exp_not:V \dtlbeforerow
      \exp_not:V \dtlafterrow
    }
%    \end{macrocode}
% Decrement row counter.
%    \begin{macrocode}
  \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 '
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlsplitrow}
%\begin{definition}
%\cs{dtlsplitrow}\marg{row specs}\marg{col num}\marg{before 
% cs}\marg{after cs}
%\end{definition}
% Splits the row around the entry given by \meta{col num}. The
% entries before the split are stored in \meta{before cs} and the
% entries after the split are stored in \meta{after cs}.
% \meta{row specs} and \meta{col num} need to be expanded before use.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \dtlsplitrow { m m m m }
{
  \exp_args:Nnx 
    \__datatool_split_row:nnNN
    { #1 } { \int_eval:n { #2 } } #3 #4
}
%    \end{macrocode}
%\end{macro}
%Syntax: \marg{row specs}\marg{col idx}\meta{before var}\meta{after var}
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%
%\begin{macro}{\dtlreplaceentryincurrentrow}
%\begin{definition}
%\cs{dtlreplaceentryincurrentrow}\marg{new value}\marg{col num}
%\end{definition}
% Replaces entry for column \meta{col num} in \cs{dtlcurrentrow}
% with \meta{new value}
%\changes{2.10}{2012-07-18}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \dtlreplaceentryincurrentrow { m m }
{
  \__datatool_replace_in_current:nx
   { #1 } { \int_eval:n { #2 } }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \__datatool_replace_in_current:nn
{
%    \end{macrocode}
% Split row
%    \begin{macrocode}
  \__datatool_split_row:VnNN
    \dtlcurrentrow
     { #2 }
    \l__datatool_before_tl
    \l__datatool_after_tl
%    \end{macrocode}
% Process value according to current options, which will save the
% value in the token register \cs{@dtl@toks} and in
% the token list variable \verb|\l__datatool_item_value_tl|.
%The datatype will also be set in the \cs{dtl@datatype} variable.
%    \begin{macrocode}
  \__datatool_process_new_value:n { #1 }
%    \end{macrocode}
% Recombine with new value and
% store in \cs{dtlcurrentrow}
%    \begin{macrocode}
  \__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
    }
%    \end{macrocode}
% Update column specs. This will set \verb|\l__datatool_item_key_tl|
%    \begin{macrocode}
  \__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 }
%    \end{macrocode}
%
%\begin{macro}{\dtlremoveentryincurrentrow}
%\begin{definition}
%\cs{dtlremoveentryincurrentrow}\marg{col idx}
%\end{definition}
% Removes entry for column \meta{col idx} from \cs{dtlcurrentrow}.
%\changes{2.10}{2012-07-18}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \dtlremoveentryincurrentrow { m }
{
%    \end{macrocode}
% Split row
%    \begin{macrocode}
  \__datatool_split_row:VxNN
    \dtlcurrentrow
     { \int_eval:n { #1 } }
    \l__datatool_before_tl
    \l__datatool_after_tl
%    \end{macrocode}
% Recombine without given column and
% store in \cs{dtlcurrentrow}
%    \begin{macrocode}
  \__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'
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlswapentriesincurrentrow}
%\begin{definition}
%\cs{dtlswapentriesincurrentrow}\marg{col1
%num}\marg{col2 num}
%\end{definition}
% Swaps columns \meta{col1 num} and \meta{col2 num} in \cs{dtlcurrentrow}
%\changes{2.10}{2012-07-18}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlgetentryfromcurrentrow}
%\begin{definition}
%\cs{dtlgetentryfromcurrentrow}\marg{cs}\marg{col num}
%\end{definition}
% Gets value for column \meta{col num} from \cs{dtlcurrentrow}
% and stores in \meta{cs}.  If not found, \meta{cs} is set to
% \cs{dtlnovalue}.
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \dtlgetentryfromcurrentrow { m m }
{
  \dtlgetentryfromrow { #1 } { #2 } \dtlcurrentrow 
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlgetentryfromrow}
%\begin{definition}
%\cs{dtlgetentryfromrow}\marg{cs}\marg{col num}\marg{row toks}
%\end{definition}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \dtlgetentryfromrow { m m m }
{
  \__datatool_get_entry_from_row:NxV
    #1 { \int_eval:n { #2 } } #3 
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@getentryfromrow}
%\begin{definition}
%\cs{dtl@getentryfromrow}\marg{cs}\marg{col num}\marg{row specs}
%\end{definition}
%    \begin{macrocode}
\newcommand*{\dtl@getentryfromrow}[3]{%
  \__datatool_get_entry_from_row:Nnn #1 { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\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 } 
%    \end{macrocode}
%
%\begin{macro}{\dtlappendentrytocurrentrow}
%\begin{definition}
%\cs{dtlappendentrytocurrentrow}\marg{key}\marg{value}
%\end{definition}
% Appends entry to \cs{dtlcurrentrow}.
%\changes{2.10}{2012-07-18}{new}
%\changes{3.0}{2025-03-03}{made robust}
%NB this originally always expanded \meta{value}. As from v3.0, 
%this will use the same settings as \cs{DTLnewdbentry}.
%\changes{3.0}{2025-03-03}{use new value setting instead of always expanding}
%    \begin{macrocode}
\NewDocumentCommand \dtlappendentrytocurrentrow { m m }
{
%    \end{macrocode}
% Parse and process value. This will set \verb|\l__datatool_item_value_tl|
%and \cs{@dtl@datatype}
%    \begin{macrocode}
  \__datatool_process_new_value:n { #2 }
%    \end{macrocode}
% Update information about this column (adding new column if
% necessary).
%    \begin{macrocode}
  \__datatool_update_meta_data_with_type:nnV
    { \dtldbname } { #1 } \@dtl@datatype
%    \end{macrocode}
% Get column index and store in \cs{dtlcolumnnum}
%    \begin{macrocode}
  \int_set:Nn \dtlcolumnnum
    { \dtlcolumnindex { \dtldbname } { #1 } }
%    \end{macrocode}
% Does this row already have an entry with this key?
%    \begin{macrocode}
  \exp_args:NNV \dtlgetentryfromcurrentrow
     \dtl@entry \dtlcolumnnum
  \datatool_if_null:NTF \dtl@entry
    {
%    \end{macrocode}
% There are no entries in this row for the given key.
% Append this entry to the current row.
%    \begin{macrocode}
        \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
          }
        }
%    \end{macrocode}
% Print information to terminal and log file if in verbose mode.
%    \begin{macrocode}
         \dtl@message
           {
             Appended ~ #1 \c_space_tl ->
             \exp_not:V \l__datatool_item_value_tl \c_space_tl ~ 
             to ~ current ~ row ~ of ~ database ~ `\dtldbname'
           }
       }
       {
%    \end{macrocode}
% There is already an entry in this row for the given key
%    \begin{macrocode}
        \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
         }
       }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlupdateentryincurrentrow}
%\begin{definition}
%\cs{dtlupdateentryincurrentrow}\marg{key}\marg{value}
%\end{definition}
% Appends entry to \cs{dtlcurrentrow} if column with given key
% doesn't exist, otherwise updates the value.
%\changes{2.11}{2012-09-25}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \dtlupdateentryincurrentrow { m m }
{
%    \end{macrocode}
% Parse and process value. This will set \verb|\l__datatool_item_value_tl|
%and \cs{@dtl@datatype}
%    \begin{macrocode}
  \__datatool_process_new_value:n { #2 }
%    \end{macrocode}
% Update information about this column (adding new column if
% necessary)
%    \begin{macrocode}
  \__datatool_update_meta_data_with_type:nnV
    { \dtldbname } { #1 } \@dtl@datatype
%    \end{macrocode}
% Get column index and store in \cs{dtlcolumnnum}
%    \begin{macrocode}
  \int_set:Nn \dtlcolumnnum
    { \dtlcolumnindex { \dtldbname } { #1 } }
%    \end{macrocode}
% Does this row already have an entry with this key?
%    \begin{macrocode}
  \exp_args:NNV \dtlgetentryfromcurrentrow
     \dtl@entry \dtlcolumnnum
  \datatool_if_null:NTF \dtl@entry
   {
%    \end{macrocode}
% There are no entries in this row for the given key.
% Append this entry to the current row.
%NB this used to always globally append.
%Version 3.0: check boolean setting.
%\changes{3.0}{2025-03-03}{check global setting}
%    \begin{macrocode}
     \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
        }
      }
%    \end{macrocode}
% Print information to terminal and log file if in verbose mode.
%    \begin{macrocode}
     \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'
       }
   }
   {
%    \end{macrocode}
% There is already an entry in this row for the given key
% Split row
%    \begin{macrocode}
     \__datatool_split_row:VVNN
       \dtlcurrentrow
       \dtlcolumnnum
       \l__datatool_before_tl
       \l__datatool_after_tl
%    \end{macrocode}
% Recombine with new value and
% store in \cs{dtlcurrentrow}
%    \begin{macrocode}
     \__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 '
       }
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgetvalue}
%\begin{definition}
%\cs{DTLgetvalue}\marg{cs}\marg{db}\marg{r}\marg{c}
%\end{definition}
% Gets the element in row \meta{r}, column \meta{c} from database
% \meta{db} and stores in \meta{cs}.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\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'
      }
      { }
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@getvalue}
%    \begin{macrocode}
\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'
      }
      { }
   }
}
%    \end{macrocode}
%\end{macro}
%Syntax: \meta{tl var} \marg{db}\marg{r}\marg{c}
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%
%\begin{macro}{\DTLgetlocation}
%\begin{definition}
%\cs{DTLgetlocation}\marg{row cs}\marg{column cs}\marg{database}%
%\marg{value}
%\end{definition}
% Assigns \meta{row cs} and \meta{column cs} to the indices of the 
% first entry in \meta{database} that matches \meta{value}.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\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'
     }
     { }
   }
}
%    \end{macrocode}
%\end{macro}
%Syntax:
%\meta{row tl var} \meta{column tl var}
%\marg{database}\marg{value}
%    \begin{macrocode}
\cs_new:Nn \__datatool_get_location:NNnn
{
  \tl_set_eq:NN #1 \dtlnovalue
%    \end{macrocode}
% Test if the value starts with datum marker.
%    \begin{macrocode}
  \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
   {
%    \end{macrocode}
% Try an exact match.
%    \begin{macrocode}
     \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
   {
%    \end{macrocode}
% Try matching weird datum.
%    \begin{macrocode}
     \tl_set:Nn \l__datatool_cs_builder_tl
      {
       \cs_set:Npn \__datatool_get_location:w
         ##1 % stuff before value
         \db@col@elt@w 
%    \end{macrocode}
% Encapsulate value with weird datum
%    \begin{macrocode}
        \__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 }
%    \end{macrocode}
%
% Similarly but to match a value supplied as a datum marker.
% Arguments:
%\meta{datum-marker-cs}\marg{string}\marg{value}\marg{currency}\marg{type}
%\meta{extra}\verb|\q_stop|
%\meta{row tl var} \meta{column tl var}
%\marg{database}
%If \meta{extra} isn't empty then there's some trailing material.
%    \begin{macrocode}
\cs_new:Npn \__datatool_get_location:NnnnnwNNn
  #1 #2 #3 #4 #5 #6 \q_stop #7 #8 #9
{
 \tl_if_empty:nT { #6 }
  {
%    \end{macrocode}
% No trailing material. Just match on the string value.
%    \begin{macrocode}
    \__datatool_get_location:NNnn #7 #8 { #9 } { #2 }
  }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLgetrowindex}
%\begin{definition}
%\cs{DTLgetrowindex}\marg{row cs}\marg{database}\marg{column index}%
%\marg{value}
%\end{definition}
% Assigns \meta{row cs} to the row index of the 
% first entry in \meta{database} where the entry in \meta{column
% index} matches \meta{value}.
%\changes{2.11}{2012-09-25}{new}
%\changes{3.0}{2025-03-03}{changed to new document command and added starred
%variant}
%    \begin{macrocode}
\NewDocumentCommand \DTLgetrowindex { s m m m m }
{
  \IfBooleanTF { #1 }
  { \dtlgetrowindex { #2 } { #3 } { #4 } { #5 } }
  { \@DTLgetrowindex { #2 } { #3 } { #4 } { #5 } }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@DTLgetrowindex}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\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'
    }
    { }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlgetrowindex}
%\begin{definition}
%\cs{dtlgetrowindex}\marg{row cs}\marg{database}\marg{column index}%
%\marg{value}
%\end{definition}
% As above but doesn't produce an error if not found.
%\changes{2.11}{2012-09-25}{new}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\dtlgetrowindex}[4]{%
  \__datatool_get_row_index:Nnxn #1
   { #2 } { \int_eval:n { #3 } } { #4 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\xdtlgetrowindex}
%\begin{definition}
%\cs{xdtlgetrowindex}\marg{row cs}\marg{database}\marg{column index}%
%\marg{value}
%\end{definition}
% As above but expands the value.
%\changes{2.28}{2017-11-10}{new}
%    \begin{macrocode}
\newcommand*{\xdtlgetrowindex}[4]{%
  \__datatool_get_row_index:Nnxx #1
   { #2 } { \int_eval:n { #3 } } { #4 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtlgetrowindex}
%\begin{definition}
%\cs{@dtlgetrowindex}\marg{row cs}\marg{database}\marg{column index}%
%\marg{value}
%\end{definition}
% Column index must be fully expanded.
%\changes{2.11}{2012-09-25}{new}
%    \begin{macrocode}
\newcommand*{\@dtlgetrowindex}[4]{
  \__datatool_get_row_index:Nnnn #1 { #2 } { #3 } { #4 }
}
%    \end{macrocode}
%\end{macro}
%
% Arguments: \marg{row tl var}\marg{database}\marg{column index}%
%\marg{value}
%NB the value mustn't contain any groups or other awkward tokens
%that can't be used in function parameter syntax.
%    \begin{macrocode}
\cs_new:Nn \__datatool_get_row_index:Nnnn
{
  \tl_set_eq:NN #1 \dtlnovalue 
%    \end{macrocode}
% Test if value starts with datum marker:
%    \begin{macrocode}
  \tl_if_head_eq_meaning:nNT { #4 } \__datatool_datum:nnnn
   {
     \__datatool_get_row_index:NnnnnwNnn
      #4 \q_stop #1 { #2 } { #3 }
   }
%    \end{macrocode}
% Try an exact match.
%    \begin{macrocode}
  \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 
   {
%    \end{macrocode}
% Try matching weird datum.
%    \begin{macrocode}
     \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 
%    \end{macrocode}
% Encapsulate value with weird datum
%    \begin{macrocode}
        \__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 }
%    \end{macrocode}
%
% Similarly but to match a value supplied as a datum marker.
% Arguments:
%\meta{datum-marker-cs}\marg{string}\marg{value}\marg{currency}\marg{type}
%\meta{extra}\verb|\q_stop|
%\marg{row tl var}\marg{database}\marg{column index}
%If \meta{extra} isn't empty then there's some trailing material.
%    \begin{macrocode}
\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 }
  {
%    \end{macrocode}
% First try an exact match.
%    \begin{macrocode}
  \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 
   }
%    \end{macrocode}
% Encapsulate value with weird datum
%    \begin{macrocode}
  \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 
   {
%    \end{macrocode}
% Try with just the string part.
%    \begin{macrocode}
     \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 
      }
%    \end{macrocode}
% Encapsulate value with weird datum
%    \begin{macrocode}
     \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
   }
  }
}
%    \end{macrocode}
%
%
%\subsection{Iterating Through Databases}
%\label{sec:code:loops}
%
%Temporary variables for use in \cs{DTLmapdata}
%    \begin{macrocode}
\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
%    \end{macrocode}
%Options for \cs{DTLmapdata}:
%    \begin{macrocode}
\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,
}
%    \end{macrocode}
%
%\begin{macro}{\DTLmapdata}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLmapdata}\oarg{options}\marg{loop}
%\end{definition}
%\cs{DTLforeach} has a lot of extra overhead. Version 3.0 
% provides \cs{DTLmapdata}, which is designed to be simpler but
% shouldn't be used in a tabular context.
%    \begin{macrocode}
\NewDocumentCommand \DTLmapdata
 { O{} +m }
 {
%    \end{macrocode}
% Initialise defaults:
%    \begin{macrocode}
   \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:
%    \end{macrocode}
% Get the contents of the database token register:
%    \begin{macrocode}
     \tl_set:Nv \l__datatool_map_data_tl { dtldb@\dtldbname }
%    \end{macrocode}
% Save the loop action:
%    \begin{macrocode}
     \tl_set:Nn \l__datatool_map_data_loop_body_tl { #2 }
     \int_zero:N \dtlrownum
%    \end{macrocode}
% Recurse:
%    \begin{macrocode}
     \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: { }
%    \end{macrocode}
%Update database if not read-only.
%    \begin{macrocode}
     \bool_if:NF \l__datatool_map_data_readonly_bool
     {
%    \end{macrocode}
%Add any pending updates.
%    \begin{macrocode}
       \__datatool_append_map_data_pending:
%    \end{macrocode}
%Append any remaining. The row indexes may be out if any rows have
%been removed, so iterate.
%    \begin{macrocode}
       \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
         }
       }
%    \end{macrocode}
%Have any column types changed?
%    \begin{macrocode}
       \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 }
         {
%    \end{macrocode}
%Get the column metadata if it exists.
%    \begin{macrocode}
           \__datatool_get_col_data:vn 
             { dtlkeys@ \dtldbname }
             { ##1 }
%    \end{macrocode}
%Has the metadata been updated for this column?
%    \begin{macrocode}
           \__datatool_map_data_get_col_type_prop:n { ##1 }
%    \end{macrocode}
%Ensure the data type is an integer.
%    \begin{macrocode}
           \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 }
            }
%    \end{macrocode}
% Append markup for this column in the temporary token list.
%    \begin{macrocode}
           \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 
           }
%    \end{macrocode}
%Any new columns will need to have the key to column mapping
%defined:
%    \begin{macrocode}
           \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 }
               }
            }
         }
       }
%    \end{macrocode}
%Update database:
%    \begin{macrocode}
       \__datatool_dtldb_set:VV
         \dtldbname
         \l__datatool_map_data_pending_tl
%    \end{macrocode}
% Update the column metadata if it has changed.
%    \begin{macrocode}
       \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
        {
%    \end{macrocode}
% Update the row count.
%    \begin{macrocode}
         \int_gset_eq:cN { dtlrows@\dtldbname }
          \l__datatool_row_idx_int
%    \end{macrocode}
% Update the column count.
%    \begin{macrocode}
         \int_gset_eq:cN
           { dtlcols@\dtldbname }
           \l__datatool_map_data_max_cols_int
        }
        {
%    \end{macrocode}
% Update the row count.
%    \begin{macrocode}
         \int_set_eq:cN { dtlrows@\dtldbname }
          \l__datatool_row_idx_int
%    \end{macrocode}
% Update the column count.
%    \begin{macrocode}
         \int_set_eq:cN
           { dtlcols@\dtldbname }
           \l__datatool_map_data_max_cols_int
        }
     }
%    \end{macrocode}
%Disable \cs{DTLmaponly} commands.
%    \begin{macrocode}
     \__datatool_map_data_disable:
   }
   {
     \PackageError { datatool }
     { database ~ `\dtldbname' ~ does ~ not ~ exist }
     { }
   }
 }
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_map_data_break: 
 { \prg_map_break:Nn \__datatool_map_data_break: { } }
%    \end{macrocode}
%
%\begin{macro}{\DTLmapdatabreak}
%\changes{3.0}{2025-03-03}{new}
%Break out of the \cs{DTLmapdata} loop. The starred form will
%discard edits.
%    \begin{macrocode}
\NewDocumentCommand \DTLmapdatabreak { s }
 {
   \IfBooleanT { #1 }
   {  
     \bool_set_true:N \l__datatool_map_data_readonly_bool
   }
   \__datatool_map_data_break:
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{environment}{DTLenvmapdata}
%Environment equivalent.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentEnvironment { DTLenvmapdata }
 { O{} +b }
 { \DTLmapdata [ #1 ] { #2 } }
 { }
%    \end{macrocode}
%\end{environment}
%
%Recursive command for \cs{DTLmapdata}:
%    \begin{macrocode}
\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
}
%    \end{macrocode}
%Append current row to pending:
%    \begin{macrocode}
\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
  }
}
%    \end{macrocode}
%Command to enable commands that may only be used in
%\cs{DTLmapdata}.
%    \begin{macrocode}
\cs_new:Nn \__datatool_map_data_enable:
{
%    \end{macrocode}
% Enable \cs{DTLmapget}:
%    \begin{macrocode}
  \cs_set_eq:NN
    \__datatool_map_get:n
    \__datatool_map_get_op:n
%    \end{macrocode}
% Enable \cs{DTLmapgetvalues}:
%    \begin{macrocode}
  \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
%    \end{macrocode}
% Enable \cs{DTLmaprow}:
%    \begin{macrocode}
  \cs_set_eq:NN
    \__datatool_map_row:Nn
    \__datatool_map_row_op:Nn
%    \end{macrocode}
% Enable `row aggregate' action:
%    \begin{macrocode}
  \cs_set_eq:cN
    { __datatool_action_row ~ aggregate: }
    \__datatool_action_row_aggregate_op:
%    \end{macrocode}
%Adjust settings according to read-only setting.
%    \begin{macrocode}
  \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
     }
  }
  {
%    \end{macrocode}
%If not read-only, setup token registers to reconstruct database:
%    \begin{macrocode}
    \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 }
     }
  }
}
%    \end{macrocode}
%Command to disable commands that may only be used in
%\cs{DTLmapdata}.
%    \begin{macrocode}
\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
%    \end{macrocode}
% Disable `row aggregate' action:
%    \begin{macrocode}
  \cs_set:cn { __datatool_action_row ~ aggregate: }
  {
    \__datatool_action_noop:Nn
      \DTLmapdata
      { row ~ aggregate: }
  }
}
%    \end{macrocode}
%Provide commands to edit data. Note that the database won't be
%updated until the loop has finished.
%    \begin{macrocode}
\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
  }
  { }
}
%    \end{macrocode}
% Read-only no-op:
%    \begin{macrocode}
\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 ] { ... }
  }
}
%    \end{macrocode}
%
%Options for editing commands:
%    \begin{macrocode}
\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,
}
%    \end{macrocode}
%Initialise edit options:
%    \begin{macrocode}
\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
  }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLsetentry}
%\changes{3.0}{2025-03-03}{new}
%Set the value in the given column.
%    \begin{macrocode}
\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
      }
    }
    {
%    \end{macrocode}
%Save the value in the token register \cs{@dtl@toks} and
%in the token list variable \verb|\l__datatool_item_value_tl|
% according to new-value-expand, trim and store datum settings.
%    \begin{macrocode}
      \__datatool_process_new_value:V
        \l__datatool_map_data_edit_value_tl 
%    \end{macrocode}
%Does the current row already have this column set?
%    \begin{macrocode}
      \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@
         }
      {
%    \end{macrocode}
%Replace existing entry.
%    \begin{macrocode}
        \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
      }
      {
%    \end{macrocode}
%Append new entry.
%    \begin{macrocode}
        \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
        }
      }
%    \end{macrocode}
% Does the metadata for this column need updating?
%    \begin{macrocode}
      \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
%    \end{macrocode}
% Has this column already been changed in a previous row?
%    \begin{macrocode}
      \__datatool_map_data_get_col_type_prop:V
        \l__datatool_map_data_edit_column_int
%    \end{macrocode}
% Update property.
%    \begin{macrocode}
      \__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
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%Add column type property.
%Syntax: \marg{col-index}\marg{col-type}\marg{col-key}
%The \meta{col-key} may be empty to denote no change or use the
%default.
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%Get the column type property. NB get the current metadata first
%which should set \verb|\l__datatool_item_type_int|, 
%\verb|\l__datatool_item_key_tl| and \verb|\l__datatool_item_head_tl|.
%    \begin{macrocode}
\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
  }
%    \end{macrocode}
% Does the new type override the original type?
%    \begin{macrocode}
  \tl_if_empty:NTF \l__datatool_item_type_tl
  {
%    \end{macrocode}
% New type unknown so retain original.
%    \begin{macrocode}
    \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 }
    {
%    \end{macrocode}
% New type not a string, so must be numerical. In which case the
% higher type dominates.
%    \begin{macrocode}
      \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 }
%    \end{macrocode}
%
%\begin{macro}{\DTLrmentry}
%\changes{3.0}{2025-03-03}{new}
%Removes the given column.
%    \begin{macrocode}
\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}
    { }
  }
  {
%    \end{macrocode}
%Does the current row already have this column set?
%    \begin{macrocode}
    \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@
       }
    {
%    \end{macrocode}
%Remove existing entry.
%    \begin{macrocode}
      \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
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLrmrow}
%\changes{3.0}{2025-03-03}{new}
%Remove the current row.
%    \begin{macrocode}
\NewDocumentCommand \DTLrmrow { }
{
  \__datatool_map_data_cs_noop:N \DTLrmrow
}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_map_data_rm_row: 
{
  \tl_clear:N \l__datatool_map_data_row_tl
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLmaprow}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLmaprow}\marg{cs}\marg{body}
%\end{definition}
%Maps over the columns in the current row.
%    \begin{macrocode}
\NewDocumentCommand \DTLmaprow { m m }
{
  \__datatool_map_row:Nn #1 { #2 }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\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
%    \end{macrocode}
%Map over each column in the current row:
%    \begin{macrocode}
\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: { }
}
%    \end{macrocode}
%Break command.
%    \begin{macrocode}
\cs_new:Nn \__datatool_map_row_break: 
 { \prg_map_break:Nn \__datatool_map_row_break: { } }
%    \end{macrocode}
%
%\begin{macro}{\DTLmaprowbreak}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLmaprowbreak}{ \__datatool_map_row_break: }
%    \end{macrocode}
%\end{macro}
%
%Macro within loop body:
%    \begin{macrocode}
\cs_new:Nn \__datatool_map_row_loop_body:n { }
%    \end{macrocode}
%Recursive macro:
%    \begin{macrocode}
\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
}
%    \end{macrocode}
%
%\begin{macro}{\DTLmapget}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLmapget}\marg{key=value options}
%\end{definition}
%Gets the value of the given key in the current row.
%    \begin{macrocode}
\NewDocumentCommand \DTLmapget { m }
{
  \__datatool_map_get:n { #1 }
}
%    \end{macrocode}
%\end{macro}
%The no-op command:
%    \begin{macrocode}
\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
%    \end{macrocode}
%
%    \begin{macrocode}
\tl_new:N \l__datatool_map_get_return_tl
%    \end{macrocode}
%
%Define options for \cs{DTLmapget}:
%    \begin{macrocode}
\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 = { },
}
%    \end{macrocode}
%
%The actual command:
%    \begin{macrocode}
\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 }
%    \end{macrocode}
% Parse settings.
%    \begin{macrocode}
  \keys_set:nn { datatool/mapdata } { #1 }
%    \end{macrocode}
% Fetch the required information.
%    \begin{macrocode}
  \__datatool_map_get_action:
%    \end{macrocode}
% Save or do the value.
%    \begin{macrocode}
  \__datatool_map_get_action_result:
}
%    \end{macrocode}
%Missing information
%    \begin{macrocode}
\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 
}
%    \end{macrocode}
%
%
%Get value from current row given column index:
%    \begin{macrocode}
\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
%    \end{macrocode}
%Was a value found?
%    \begin{macrocode}
     \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 
       }
     }
  }
}
%    \end{macrocode}
%Get value from current row given column key:
%    \begin{macrocode}
\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 
  }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLmapgetvalues}
%\changes{3.0}{2025-03-03}{new}
%Gets multiple values using the same assignment syntax as used by
%\cs{DTLforeach}
%    \begin{macrocode}
\NewDocumentCommand \DTLmapgetvalues { s m }
{
  \IfBooleanTF { #1 }
   {
     \__datatool_map_get_values_noerr:n { #2 }
   }
   {
     \__datatool_map_get_values:n { #2 }
   }
}
%    \end{macrocode}
%\end{macro}
%The no-op command:
%    \begin{macrocode}
\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
%    \end{macrocode}
%Gets values from the current row:
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%As above but no error if key doesn't exist.
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%
%
%\cs{\_\_datatool\_map\_assign\_cskey:nn}\marg{cs}\marg{key}
%    \begin{macrocode}
\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
   }
}
%    \end{macrocode}
%As above but no error if column key not defined.
%    \begin{macrocode}
\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
   }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\begin{macro}{\@dtlforeachrow}
%\begin{definition}
%\cs{@dtlforeachrow}(\meta{idx cs},\meta{row cs})\cs{in}\marg{db}%
%\cs{do}\marg{body}
%\end{definition}
% Iterates through each row in database. Assigns the current row
% index to \meta{idx cs} and the row specs to \meta{row cs}
%    \begin{macrocode}
\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
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@foreachrow}
%    \begin{macrocode}
\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{%
%    \end{macrocode}
% Define control sequence given by "#5"
%    \begin{macrocode}
  \gdef#5{#1}%
%    \end{macrocode}
% Hide the loop body in a macro
%    \begin{macrocode}
  \gdef\@dtl@loopbody{#7}%
%    \end{macrocode}
% Increment level counter to allow for nested loops
%    \begin{macrocode}
  \global\advance\@dtl@foreach@level by 1\relax
%    \end{macrocode}
% Check if we have reached the end of the loop
%    \begin{macrocode}
  \ifx#5\@nnil
    \expandafter\global\expandafter
      \let\csname @dtl@foreachnext\the\@dtl@foreach@level\endcsname
        =\@dtl@foreachnoop
  \else
    \gdef#6{#2}%
%    \end{macrocode}
% Set up the break function:
% Make a copy of current break function
%    \begin{macrocode}
    \expandafter\let
      \csname @dtl@break@\the\@dtl@foreach@level\endcsname
      \dtlbreak
%    \end{macrocode}
% Setup break function for this level
%    \begin{macrocode}
    \gdef\dtlbreak{\expandafter\global\expandafter
      \let\csname @dtl@foreachnext\the\@dtl@foreach@level\endcsname
        =\@dtl@foreachnoop}%
%    \end{macrocode}
% Initialise
%    \begin{macrocode}
    \expandafter\global\expandafter
      \let\csname @dtl@foreachnext\the\@dtl@foreach@level\endcsname
        =\@dtl@foreachrow
%    \end{macrocode}
% Do body of loop
%    \begin{macrocode}
    \@dtl@loopbody
%    \end{macrocode}
% Restore break function
%    \begin{macrocode}
    \expandafter\let\expandafter\dtlbreak
      \csname @dtl@break@\the\@dtl@foreach@level\endcsname
  \fi
%    \end{macrocode}
% Set up what to do next.
%    \begin{macrocode}
  \expandafter\let\expandafter\@dtl@foreachnext
    \csname @dtl@foreachnext\the\@dtl@foreach@level\endcsname
%    \end{macrocode}
% Decrement level counter.
%    \begin{macrocode}
  \global\advance\@dtl@foreach@level by -1\relax
%    \end{macrocode}
% Repeat loop if necessary.
%    \begin{macrocode}
  \@dtl@foreachnext#4\@@{#5}{#6}{#7}\q@nil
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@foreachnoop}
%    \begin{macrocode}
\long\def\@dtl@foreachnoop#1\@@#2\q@nil{}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%Version 3.0: This is a newer alternative to \cs{dtlforeachkey} implemented in
%\LaTeX3. Note that the unknown type will be -1 not empty so that it
%can be compared with the integer constants provided by
%\sty{datatool-base}. To make it consistent with
%\cs{dtlforeachkey} the handler function has four arguments:
%\marg{key}\marg{col idx}\marg{type}\marg{header}
% In line function:
%    \begin{macrocode}
\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
  }
}
%    \end{macrocode}
%Function handler:
%    \begin{macrocode}
\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
  }
}
%    \end{macrocode}
%Internal recursive command:
%    \begin{macrocode}
\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 
}
%    \end{macrocode}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\begin{macro}{\dtlforeachkey}
%\begin{definition}
%\cs{dtlforeachkey}(\meta{key cs},\meta{col cs},\meta{type cs},\meta{header cs})%
%\cs{in}\marg{db}\cs{do}\marg{body}
%\end{definition}
% Iterates through all the keys in database \meta{db}. In each 
% iteration, \meta{key cs} stores the key, \meta{col cs} stores the 
% column index,\meta{type cs} stores the data type and \meta{header
% cs} stores the header.
%    \begin{macrocode}
\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
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@updatefkcs}
%    \begin{macrocode}
\newcommand*{\@dtl@updatefkcs}[8]{%
  \gdef#1{#5}%
  \gdef#2{#6}%
  \gdef#3{#7}%
  \gdef#4{#8}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@foreachkey}
% Sets everything globally in case it occurs in a tabular environment
% Loop body needs to be stored in \cs{@dtl@loopbody}.
% "#7" indicates an update macro.
%    \begin{macrocode}
\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
%    \end{macrocode}
% Terminate loop
%    \begin{macrocode}
    \let\@dtl@foreachnext\@dtl@foreachnoop
  \else
%    \end{macrocode}
% Set up loop variables
%    \begin{macrocode}
    #7{#2}{#1}{#3}{#4}%
%    \end{macrocode}
% Increment level counter to allow for nested loops
%    \begin{macrocode}
    \global\advance\@dtl@foreach@level by 1\relax
%    \end{macrocode}
% Set up the break function
%    \begin{macrocode}
    \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}%
%    \end{macrocode}
% Initialise
%    \begin{macrocode}
    \expandafter\global\expandafter
      \let\csname @dtl@foreachnext\the\@dtl@foreach@level\endcsname
        =\@dtl@foreachkey
%    \end{macrocode}
% Do body of loop
%    \begin{macrocode}
    \@dtl@loopbody
%    \end{macrocode}
% Set up what to do next
%    \begin{macrocode}
    \expandafter\let\expandafter\@dtl@foreachnext
      \csname @dtl@foreachnext\the\@dtl@foreach@level\endcsname
%    \end{macrocode}
% Restore break function
%    \begin{macrocode}
    \expandafter\let\expandafter\dtlbreak
      \csname @dtl@break@\the\@dtl@foreach@level\endcsname
%    \end{macrocode}
% Decrement level counter
%    \begin{macrocode}
    \global\advance\@dtl@foreach@level by -1\relax
  \fi
%    \end{macrocode}
% Recurse if necessary
%    \begin{macrocode}
  \@dtl@foreachnext#6\@@{#7}\q@nil
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\begin{macro}{\dtlforcolumn}
%\begin{definition}
%\cs{dtlforcolumn}\marg{cs}\marg{db}\marg{key}\marg{body}
%\end{definition}
% Iterates through column given by \meta{key} in database \meta{db}.
% \meta{cs} is assign to the element of the column in the
% current iteration. Starred version doesn't check if data base
% exists
%\changes{3.0}{2025-03-03}{changed to document command}
%    \begin{macrocode}
\NewDocumentCommand \dtlforcolumn { s }
{
 \IfBooleanTF { #1 } { \@sdtlforcolumn } { \@dtlforcolumn }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtlforcolumn}
%    \begin{macrocode}
\newcommand{\@dtlforcolumn}[4]{
%    \end{macrocode}
% Check if data base exists
%    \begin{macrocode}
  \DTLifdbexists { #2 }
  {
    \@DTLifhaskey { #2 } { #3 }
    {
      \@sdtlforcolumn { #1 } { #2 } { #3 } { #4 }
    }
%    \end{macrocode}
% key not in data base
%    \begin{macrocode}
    {
      \PackageError { datatool }
       {
         Database ~ `#2' ~ doesn't ~ contain ~ key ~ `#3'
       }
       { }
    }
  }
  {
    \PackageError { datatool }
    { Database ~ `#2' ~ doesn't ~ exist} 
    { }
  }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_for_column:Nnnn
{
  \dtl@forcolumn #1 { #2 } { #3 } { #4 }
}
\cs_generate_variant:Nn  \__datatool_for_column:Nnnn
{ Nven }
%    \end{macrocode}
%
%\begin{macro}{\@sdtlforcolumn}
%    \begin{macrocode}
\newcommand{\@sdtlforcolumn}[4]{%
 \__datatool_for_column:Nven #1 { dtldb@#2 }
   { \dtlcolumnindex { #2 } { #3 } } { #4 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlforcolumnidx}
%\begin{definition}
%\cs{dtlforcolumnidx}\marg{cs}\marg{db}\marg{col num}\marg{body}
%\end{definition}
% Iterates through the column with index \meta{col num} in database \meta{db}.
% Starred version doesn't check if database exists.
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{changed to document command}
%    \begin{macrocode}
\NewDocumentCommand \dtlforcolumnidx { s }
{
  \IfBooleanTF { #1 } { \@sdtlforcolumnidx } { \@dtlforcolumnidx }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@sdtlforcolumnidx}
%    \begin{macrocode}
\newcommand{\@sdtlforcolumnidx}[4]{%
 \__datatool_for_column:Nven #1 { dtldb@#2 }
   { \int_eval:n { #3 } } { #4 }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\begin{macro}{\@dtlforcolumnidx}
%    \begin{macrocode}
\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
  }%
%    \end{macrocode}
% data base doesn't exist
%    \begin{macrocode}
  {%
    \PackageError{datatool}{Database `#2' doesn't exist}{}%
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@forcolumn}
%\begin{definition}
%\cs{dtl@forcolumn}\marg{cs}\marg{db specs}\marg{col num}\marg{body}
%\end{definition}
% \meta{col num} needs to be fully expanded
%    \begin{macrocode}
\newcommand{\dtl@forcolumn}[4]{%
%    \end{macrocode}
% make a copy of break function
%    \begin{macrocode}
  \let\@dtl@oldbreak\dtlbreak
%    \end{macrocode}
% set up break function
%    \begin{macrocode}
  \def\dtlbreak{\let\@dtl@forcolnext=\@dtl@forcolnoop}%
%    \end{macrocode}
% define loop macro for this column
%    \begin{macrocode}
  \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>
%    \end{macrocode}
% check if end of loop
%    \begin{macrocode}
      \ifx#1\@nnil
        \let\@dtl@forcolnext=\@dtl@forcolnoop
      \else
%    \end{macrocode}
% do body of loop
%    \begin{macrocode}
        #4%
        \let\@dtl@forcolnext=\@dtl@forcolumn
      \fi
%    \end{macrocode}
% repeat if necessary
%    \begin{macrocode}
      \@dtl@forcolnext##3\q@nil
    }%
%    \end{macrocode}
% do loop
%    \begin{macrocode}
  \@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
%    \end{macrocode}
% restore break function
%    \begin{macrocode}
  \let\dtlbreak\@dtl@oldbreak
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtl@forcolnoop}
%    \begin{macrocode}
\def\@dtl@forcolnoop#1\q@nil{}
%    \end{macrocode}
%\end{macro}

%\begin{macro}{\dtlforeachlevel}
%\cs{DTLforeach} can only be nested up to three levels.
% \cs{dtlforeachlevel} keeps track of the current level.
%    \begin{macrocode}
\newcount\dtlforeachlevel
%    \end{macrocode}
%\end{macro}
%
% The counter DTLrow\meta{n} keeps track of each row of data during
% the \meta{n} nested \cs{DTLforeach}. It is only incremented in the 
% conditions (given by the optional argument) are met. In order to work 
% well with \sty{hyperref}, a parent counter is also defined.
%    \begin{macrocode}
\newcounter{DTLrow}
\newcounter{DTLrowi}
\newcounter{DTLrowii}
\newcounter{DTLrowiii}
%    \end{macrocode}
% Keep \sty{hyperref} happy
%    \begin{macrocode}
\def\theHDTLrow{\arabic{DTLrow}}
\def\theHDTLrowi{\theHDTLrow.\arabic{DTLrowi}}
\def\theHDTLrowii{\theHDTLrowi.\arabic{DTLrowii}}
\def\theHDTLrowiii{\theHDTLrowii.\arabic{DTLrowiii}}
%    \end{macrocode}
%Allow use of these counters outside of \cs{DTLforeach}.
%\begin{macro}{\DTLrowreset}
%\changes{3.0}{2025-03-03}{new}
%Reset counter for the next level.
%    \begin{macrocode}
\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
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLrowincr}
%\changes{3.0}{2025-03-03}{new}
%Increment counter for the next level.
%    \begin{macrocode}
\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
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLtherow}
%\changes{3.0}{2025-03-03}{new}
%Show the counter for the next level.
%    \begin{macrocode}
\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
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\newcount\dtl@rowi
\newcount\dtl@rowii
\newcount\dtl@rowiii
%    \end{macrocode}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
% Keep track of filtered rows to help \cs{DTLiflastrow}:
%    \begin{macrocode}
\int_new:N \g__filtered_row_i_int
\int_new:N \g__filtered_row_ii_int
\int_new:N \g__filtered_row_iii_int
%    \end{macrocode}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%    \begin{macrocode}
\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
%    \end{macrocode}
%
%\begin{macro}{\DTLsaverowcount}
%\begin{definition}
%\cs{DTLsavelastrowcount}\marg{cmd}
%\end{definition}
% Stores the maximum row count for the last \cs{DTLforeach}.
%    \begin{macrocode}
\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}
%    \end{macrocode}
%\end{macro}
%
%\begin{environment}{DTLenvforeach}
% Environment form of \cs{DTLforeach} (contents are gathered, so
% verbatim can't be used).
%\changes{3.0}{2025-03-03}{changed to xparse}
%    \begin{macrocode}
\NewDocumentEnvironment{DTLenvforeach}{O{\boolean{true}}mm+b}
{%
  \@DTLforeach[#1]{#2}{#3}{#4}%
}
{}
%    \end{macrocode}
%\end{environment}
%
%\begin{environment}{DTLenvforeach*}
% Environment form of \cs{DTLforeach*} (contents are gathered, so
% verbatim can't be used).
%\changes{3.0}{2025-03-03}{changed to xparse}
%    \begin{macrocode}
\NewDocumentEnvironment{DTLenvforeach*}{O{\boolean{true}}mm+b}
{%
  \@sDTLforeach[#1]{#2}{#3}{#4}%
}
{}
%    \end{macrocode}
%\end{environment}
%
%NB \cs{DTLmapdata} is better but \cs{DTLforeach} retained for
%backward-compatibility and for use with \env{tabular} environments
%(although there are still situations where it will cause alignment
%issues).
%
%\begin{macro}{\DTLforeach}
%\begin{definition}
% \cs{DTLforeach}\oarg{conditions}\marg{db name}\marg{values}\marg{text}
%\end{definition}
% For each row of data in the database given by \meta{db name},
% do \meta{text}, if the specified conditions are satisfied.
% The argument \marg{values} is a comma separated list of
% \meta{cmd}\texttt{=}\meta{key} pairs. At the start of each row,
% each of the commands in this list are set to the value of the
% entry with the corresponding key \meta{key}.
% (\cs{gdef} is used to ensure \cs{DTLforeach} works in a tabular
% environment.) The database may be edited in the unstarred
% version, in the starred version the database is read only.
%\changes{1.01}{2007 Aug 17}{added starred version}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\DTLforeach}{\@ifstar\@sDTLforeach\@DTLforeach}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%Version 3.0: provided common command for starred and unstarred
%code.
% Syntax: \marg{conditions}\marg{db
% name}\marg{values}\marg{text}\marg{ro}
%The \meta{ro} argument should be a boolean indicating if
%read-only.
%    \begin{macrocode}
\cs_new:Nn \__datatool_for_each:nnnnN
{
%    \end{macrocode}
% Check database exists
%    \begin{macrocode}
  \DTLifdbexists { #2 }
  {
%    \end{macrocode}
% Keep \sty{hyperref} happy
%    \begin{macrocode}
    \refstepcounter {DTLrow}
%    \end{macrocode}
% Store database name.
%\changes{2.20}{2014-02-03}{change \cs{gdef} to \cs{xdef}}
%    \begin{macrocode}
    \tl_gset:Nx \@dtl@dbname { #2 }
%    \end{macrocode}
% Increment level and check not exceeded 3
%    \begin{macrocode}
    \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 }
%    \end{macrocode}
% Do nothing if database is empty
%    \begin{macrocode}
      { }
      {
%    \end{macrocode}
% Set level dependent information (needs to be global to ensure
% it works in the \env{tabular} environment). Row counter:
%    \begin{macrocode}
        \int_gzero:c
         { c@DTLrow\romannumeral\dtlforeachlevel }
%    \end{macrocode}
% Keep track of the number of filtered rows:
%    \begin{macrocode}
        \int_gzero:c
         {
            g__filtered_row_
             \romannumeral \dtlforeachlevel
            _int
         }
%    \end{macrocode}
% Store previous value of \cs{DTLiffirstrow}
%    \begin{macrocode}
        \cs_gset_eq:cN
          { @dtl@iffirstrow\the\dtlforeachlevel }
          \DTLiffirstrow
%    \end{macrocode}
% Define current \cs{DTLiffirstrow}
%\changes{2.32}{2019-09-27}{removed \cs{relax}}
%    \begin{macrocode}
        \cs_gset_eq:NN 
          \DTLiffirstrow
          \__datatool_foreach_if_first_row:TF
%    \end{macrocode}
% Store previous value of \cs{DTLiflastrow}
%    \begin{macrocode}
        \cs_gset_eq:cN
          { @dtl@iflastrow\the\dtlforeachlevel }
          \DTLiflastrow
%    \end{macrocode}
% Define current \cs{DTLiflastrow}
%\changes{2.12}{2012-11-30}{fixed bug in \cs{DTLiflastrow}}
%\changes{2.32}{2019-09-27}{removed \cs{relax}}
%    \begin{macrocode}
        \cs_gset_eq:NN 
          \DTLiflastrow
          \__datatool_foreach_if_last_row:TF
%    \end{macrocode}
% Store previous value of \cs{DTLifoddrow}
%    \begin{macrocode}
        \cs_gset_eq:cN
          { @dtl@ifoddrow\the\dtlforeachlevel }
          \DTLifoddrow
%    \end{macrocode}
% Define current \cs{DTLifoddrow}
%    \begin{macrocode}
        \cs_gset_eq:NN 
         \DTLifoddrow
         \__datatool_foreach_if_odd_row:TF
%    \end{macrocode}
% Store data base name for current level
%    \begin{macrocode}
        \tl_gset_eq:cN
          { @dtl@dbname@\romannumeral\dtlforeachlevel }
            \@dtl@dbname
%    \end{macrocode}
% Read only setting.
%    \begin{macrocode}
        \bool_if:NTF #5
         {
           \tl_gset:cn
             { @dtl@ro@\romannumeral\dtlforeachlevel }
             { \c_one_int }
         }
         {
           \tl_gset:cn
             { @dtl@ro@\romannumeral\dtlforeachlevel }
             { \c_zero_int }
         }
%    \end{macrocode}
% Loop through each row.
% Loop counter given by \cs{dtl@row}\meta{level}
%    \begin{macrocode}
        \exp_args:Nc \dtlgforint
          { dtl@row\romannumeral\dtlforeachlevel }
          =1\to\csname dtlrows@#2\endcsname\step1\do
        {
%    \end{macrocode}
% Get current row from the data base
%    \begin{macrocode}
          \exp_args:Nnx
            \dtlgetrow { #2 } 
            { \int_use:c { dtl@row\romannumeral\dtlforeachlevel } }
%    \end{macrocode}
% Store the current row for this level
%    \begin{macrocode}
          \__datatool_token_register_gset_eq:cN
            { @dtl@cur\romannumeral\dtlforeachlevel }
            \dtlcurrentrow
%    \end{macrocode}
% Current and previous rows only need to be save if not read only.
%    \begin{macrocode}
           \bool_if:NF #5
            {
%    \end{macrocode}
% Store the previous rows for this level.
%    \begin{macrocode}
              \__datatool_token_register_gset_eq:cN
                { @dtl@prev\romannumeral\dtlforeachlevel }
                 \dtlbeforerow
%    \end{macrocode}
% Store the subsequent rows for this level.
%    \begin{macrocode}
              \__datatool_token_register_gset_eq:cN
                { @dtl@next\romannumeral\dtlforeachlevel }
                 \dtlafterrow
            }
%    \end{macrocode}
% Assign commands to the required entries
%\changes{2.32}{2019-09-27}{changed \cs{ifx} test to \cs{ifblank}}
%    \begin{macrocode}
          \ifblank{#3}{}{\@dtl@assign{#3}{#2}}
%    \end{macrocode}
% Do the main body of text if condition is satisfied
%    \begin{macrocode}
          \ifthenelse { #1 }
          {
%    \end{macrocode}
% Increment user row counter
%    \begin{macrocode}
            \refstepcounter 
              { DTLrow \romannumeral \dtlforeachlevel }
            \tl_set:Nx
               \DTLcurrentindex
              { 
                \arabic { DTLrow\romannumeral\dtlforeachlevel }
              }
            #4
%    \end{macrocode}
% Reconstruct database if not read only.
%    \begin{macrocode}
           \bool_if:NF #5
            {
%    \end{macrocode}
% Has this row been marked for deletion?
%    \begin{macrocode}
              \exp_args:Nx \tl_if_eq:nnTF
                {
                  \__datatool_token_register_use:c
                   { @dtl@cur\romannumeral \dtlforeachlevel }
                }
                { \@nil }
              {
%    \end{macrocode}
% Row needs to be deleted
% Decrement row indices for rows with a higher index than this one
%    \begin{macrocode}
                \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
                   }
%    \end{macrocode}
% Globally reconstruct data base without this row
%    \begin{macrocode}
                \__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
                   }
                 }
%    \end{macrocode}
% Decrement the row count for this database:
%    \begin{macrocode}
                \int_gdecr:c { dtlrows@#2 }
%    \end{macrocode}
% Decrement the counter for this loop
%    \begin{macrocode}
                \int_gdecr:c 
                 { dtl@row\romannumeral \dtlforeachlevel }
            }
            {
%    \end{macrocode}
% No row deletion.
% Reconstruct data base.
%    \begin{macrocode}
               \__datatool_token_register_set_eq:Nc
                 \@dtl@before
                 { @dtl@prev\romannumeral \dtlforeachlevel }
               \__datatool_token_register_set_eq:Nc
                 \@dtl@after
                 {  @dtl@next\romannumeral \dtlforeachlevel }
%    \end{macrocode}
% Concatenate.
%    \begin{macrocode}
                \__datatool_token_register_gconcat_middle:cNxN
                 { dtldb@#2 }
                 \@dtl@before 
                {
%    \end{macrocode}
% This row
%    \begin{macrocode}
                  \__datatool_row_markup:vv
                   { dtl@row\romannumeral \dtlforeachlevel }
                   { @dtl@cur\romannumeral \dtlforeachlevel }
                  }
                 \@dtl@after 
              }
            }
          }
%    \end{macrocode}
% Condition not met so just increment the number of filtered rows
% variable.
%    \begin{macrocode}
          {
            \int_gincr:c
             {
                g__filtered_row_
                 \romannumeral \dtlforeachlevel
                _int
             }
          }
        }
%    \end{macrocode}
% Restore previous value of \cs{DTLiffirstrow}
%    \begin{macrocode}
       \cs_gset_eq:Nc
         \DTLiffirstrow
         { @dtl@iffirstrow\the\dtlforeachlevel }
%    \end{macrocode}
% Restore previous value of \cs{DTLiflastrow}
%    \begin{macrocode}
       \cs_gset_eq:Nc
          \DTLiflastrow
          { @dtl@iflastrow\the\dtlforeachlevel }
%    \end{macrocode}
% Restore previous value of \cs{DTLifoddrow}
%    \begin{macrocode}
       \cs_gset_eq:Nc
          \DTLifoddrow
          { @dtl@ifoddrow\the\dtlforeachlevel }
      }
    }
%    \end{macrocode}
% Decrement level
%    \begin{macrocode}
    \int_gdecr:N \dtlforeachlevel
  }
%    \end{macrocode}
% else part (data base doesn't exist):
%    \begin{macrocode}
  {
    \PackageError {datatool}
     { Database ~ `#2' ~ doesn't ~ exist }
     {}
  }
}
%    \end{macrocode}
%
%\begin{macro}{\@DTLforeach}
% \cs{@DTLforeach} is the unstarred version of \cs{DTLforeach}.
% The database is reconstructed to allow for rows to be edited.
% Use the starred version for faster access.
%\changes{2.0}{2009 February 27}{updated to use new database structure}
%    \begin{macrocode}
\newcommand{\@DTLforeach}[4][\boolean{true}]{%
 \__datatool_for_each:nnnnN { #1 } { #2 } { #3 } { #4 }
   \c_false_bool 
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@sDTLforeach}
% \cs{@sDTLforeach} is the starred version of \cs{DTLforeach}.
% The database rows can't be edited.
%\changes{2.0}{2009 February 27}{updated to use new database structure}
%    \begin{macrocode}
\newcommand{\@sDTLforeach}[4][\boolean{true}]{
 \__datatool_for_each:nnnnN { #1 } { #2 } { #3 } { #4 }
   \c_true_bool 
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%
%\begin{macro}{\@dtlifreadonly}
%\begin{definition}
%\cs{@dtlifreadonly}\marg{true part}\marg{false part}
%\end{definition}
% Checks if current loop level is read only
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\@dtlifreadonly}[2]{%
  \expandafter\ifx
    \csname @dtl@ro@\romannumeral\dtlforeachlevel\endcsname1\relax
%    \end{macrocode}
% Read only
%    \begin{macrocode}
    #1%
  \else
%    \end{macrocode}
% Not read only
%    \begin{macrocode}
    #2%
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\begin{macro}{\DTLappendtorow}
%\begin{definition}
%\cs{DTLappendtorow}\marg{key}\marg{value}
%\end{definition}
% Appends entry to current row. (The current row is given by
% \cs{@dtl@cur}\meta{n} where \meta{n} is roman
% numeral value of \cs{dtlforeachlevel}.
% One level expansion is applied to \meta{value}.
%\changes{2.0}{2009 February 27}{updated to use new database structure}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\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
    }
    { }
  }
  {
%    \end{macrocode}
% Set \cs{@dtl@thisdb} to the current database name:
%    \begin{macrocode}
    \tl_set_eq:Nc \@dtl@thisdb
      { @dtl@dbname@\romannumeral\dtlforeachlevel }
%    \end{macrocode}
% Check this isn't in \cs{DTLforeach*}
%    \begin{macrocode}
    \@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}
    }%
    {%
%    \end{macrocode}
% Store current row number in \cs{dtlrownum}
%    \begin{macrocode}
      \int_set_eq:Nc \dtlrownum
        { dtl@row\romannumeral\dtlforeachlevel }
%    \end{macrocode}
% Update information about this column (adding new column if
% necessary)
%    \begin{macrocode}
      \@dtl@updatekeys{\@dtl@thisdb}{#1}{#2}%
%    \end{macrocode}
% Get column index and store in \cs{dtlcolumnnum}
%    \begin{macrocode}
      \int_set:Nn \dtlcolumnnum
        { \dtlcolumnindex{\@dtl@thisdb}{#1} }
%    \end{macrocode}
% Set \cs{dtlcurrentrow} to the current row
%    \begin{macrocode}
      \dtlcurrentrow = 
        \csname @dtl@cur\romannumeral\dtlforeachlevel\endcsname
%    \end{macrocode}
% Does this row already have an entry with this key?
%    \begin{macrocode}
      \exp_args:NNV 
       \dtlgetentryfromcurrentrow
       \dtl@entry
       \dtlcolumnnum
      \datatool_if_null:NTF \dtl@entry
       {
%    \end{macrocode}
% There are no entries in this row for the given key.
% Expand entry value before storing.
%\changes{2.03}{2009 November 15}{value expanded before storing}
%    \begin{macrocode}
        \protected@edef\@dtl@tmp{#2}%
        \expandafter\@dtl@toks\expandafter{\@dtl@tmp}%
%    \end{macrocode}
% Globally append this entry to the current row.
%    \begin{macrocode}
        \__datatool_token_register_gput_right:cx
          { @dtl@cur\romannumeral\dtlforeachlevel }
        {
          \__datatool_row_element_markup:VV
            \dtlcolumnnum
            \@dtl@toks
        }
%    \end{macrocode}
% Print information to terminal and log file if in verbose mode.
%    \begin{macrocode}
         \dtl@message{Appended ~ #1\space -> ~ \the\@dtl@toks\space to ~
         database ~ `\@dtl@thisdb'}%
       }
       {
%    \end{macrocode}
% There is already an entry in this row for the given key
%    \begin{macrocode}
        \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
          }
         { }
       }
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLremoveentryfromrow}
%\begin{definition}
%\cs{DTLremoveentryfromrow}\marg{key}
%\end{definition}
% Removes entry given by \meta{key} from current row. 
% (The current row is given by
% \cs{@dtl@cur}\meta{n} where \meta{n} is roman
% numeral value of \cs{dtlforeachlevel}.
%\changes{2.0}{2009 February 27}{updated to use new database structure}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\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
    }
    {}%
  }
  {
%    \end{macrocode}
% Set \cs{@dtl@thisdb} to the current database name:
%    \begin{macrocode}
    \tl_set_eq:Nv
      \@dtl@thisdb
      { @dtl@dbname@\romannumeral\dtlforeachlevel }
%    \end{macrocode}
% Check this isn't in \cs{DTLforeach*}
%    \begin{macrocode}
    \@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
      }%
    }%
    {%
%    \end{macrocode}
% Store current row number in \cs{dtlrownum}
%    \begin{macrocode}
      \int_set_eq:Nc \dtlrownum
        { dtl@row\romannumeral\dtlforeachlevel }
%    \end{macrocode}
% Is there a column corresponding to this key?
%    \begin{macrocode}
      \@DTLifhaskey{\@dtl@thisdb}{#1}%
      {%
%    \end{macrocode}
% There exists a column for this key, so get the index:
%    \begin{macrocode}
        \@dtl@getcolumnindex{\thiscol}{\@dtl@thisdb}{#1}\relax
        \dtlcolumnnum=\thiscol\relax
%    \end{macrocode}
% Set \cs{dtlcurrentrow} to the current row
%    \begin{macrocode}
        \dtlcurrentrow = 
          \csname @dtl@cur\romannumeral\dtlforeachlevel\endcsname
%    \end{macrocode}
% Does this row have an entry with this key?
%    \begin{macrocode}
        \exp_args:NNV 
         \dtlgetentryfromcurrentrow
         \dtl@entry
         \dtlcolumnnum
        \datatool_if_null:NTF \dtl@entry
         {
%    \end{macrocode}
% This row doesn't contain an entry with this key
%    \begin{macrocode}
          \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'
            }%
         }
         {
%    \end{macrocode}
% Split the current row around the unwanted entry
%    \begin{macrocode}
          \edef\@dtl@dosplitrow{%
            \noexpand\dtlsplitrow{\the\dtlcurrentrow}%
              {\number\dtlcolumnnum}{\noexpand\dtl@pre}%
              {\noexpand\dtl@post}%
          }%
          \@dtl@dosplitrow
%    \end{macrocode}
% Reconstruct row without unwanted entry
%    \begin{macrocode}
          \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
         }
         {}
      }
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLreplaceentryforrow}
%\begin{definition}
%\cs{DTLreplaceentryforrow}\marg{key}\marg{value}
%\end{definition}
% Replaces entry given by \meta{key} in current row with 
% \meta{value}. 
% (The current row is given by the token register
% \cs{@dtl@cur}\meta{n} where \meta{n} is roman
% numeral value of \cs{dtlforeachlevel}.
%\changes{2.0}{2009 February 27}{updated to use new database structure}
%    \begin{macrocode}
\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
    }
    {}%
  }
  {
%    \end{macrocode}
% Set \cs{@dtl@thisdb} to the current database name:
%    \begin{macrocode}
    \tl_set_eq:Nc
      \@dtl@thisdb
      { @dtl@dbname@\romannumeral\dtlforeachlevel }
%    \end{macrocode}
% Check this isn't in \cs{DTLforeach*}
%    \begin{macrocode}
    \@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
       }%
    }%
    {%
%    \end{macrocode}
% Store current row number in \cs{dtlrownum}
%    \begin{macrocode}
      \int_set_eq:Nc \dtlrownum
        { dtl@row\romannumeral\dtlforeachlevel }
%    \end{macrocode}
% Is there a column corresponding to this key?
%    \begin{macrocode}
      \@DTLifhaskey{\@dtl@thisdb}{#1}%
      {%
%    \end{macrocode}
% There exists a column for this key, so get the index:
%    \begin{macrocode}
        \@dtl@getcolumnindex{\thiscol}{\@dtl@thisdb}{#1}
        \int_set_eq:NN \dtlcolumnnum \thiscol
%    \end{macrocode}
% Set \cs{dtlcurrentrow} to the current row
%    \begin{macrocode}
        \dtlcurrentrow = 
          \csname @dtl@cur\romannumeral\dtlforeachlevel\endcsname
%    \end{macrocode}
% Does this row have an entry with this key?
%    \begin{macrocode}
        \exp_args:NNV 
         \dtlgetentryfromcurrentrow
         \dtl@entry
         \dtlcolumnnum
        \datatool_if_null:NTF \dtl@entry
         {
%    \end{macrocode}
% This row doesn't contain an entry with this key
%    \begin{macrocode}
          \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'
           }%
         }
         {
%    \end{macrocode}
% Split the current row around the requested entry
%    \begin{macrocode}
          \edef\@dtl@dosplitrow{%
            \noexpand\dtlsplitrow{\the\dtlcurrentrow}%
              {\number\dtlcolumnnum}{\noexpand\dtl@pre}%
              {\noexpand\dtl@post}%
          }%
          \@dtl@dosplitrow
%    \end{macrocode}
% Reconstruct row with new value (given by \verb|#2|).
%\changes{2.03}{2009 November 15}{expand replacement entry}
%    \begin{macrocode}
          \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
%    \end{macrocode}
% Print information to terminal and log file if in verbose mode.
%    \begin{macrocode}
         \dtl@message{Updated ~ #1\space -> \the\@dtl@toks\space 
           in ~ database ~ `\@dtl@thisdb'}%
         }
      }%
      {%
%    \end{macrocode}
% There doesn't exist a column for this key.
%    \begin{macrocode}
         \PackageError {datatool}
          {
           Can't ~ replace ~ key ~ `#1' ~ - ~ no ~ such ~
           key ~ in ~ database ~ `\@dtl@thisdb'
          }
          {}%
      }%
    }%
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLremovecurrentrow}
%\begin{definition}
%\cs{DTLremovecurrentrow}
%\end{definition}
% Removes current row. This just sets the current row to empty
%\changes{1.01}{2007 Aug 17}{fix bug caused by missing \cs{fi}
% and unrequired argument}
%\changes{2.0}{2009 February 27}{updated to use new database structure}
%    \begin{macrocode}
\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
    }
    { }
  }
  {
%    \end{macrocode}
% Set \cs{@dtl@thisdb} to the current database name:
% TODO why is this needed?
%    \begin{macrocode}
    \tl_set_eq:Nc
      \@dtl@thisdb
      { @dtl@dbname@\romannumeral\dtlforeachlevel }
%    \end{macrocode}
% Check this isn't in \cs{DTLforeach*}
%    \begin{macrocode}
    \@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
      }
    }
    {
%    \end{macrocode}
% Set the current row to \cs{@nil} (\cs{DTLforeach} needs to check
% for this)
%    \begin{macrocode}
      \__datatool_token_register_gset:cn
        { @dtl@cur\romannumeral\dtlforeachlevel }
        { \@nil }
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\begin{macro}{\DTLaddentryforrow}
%\begin{definition}
%\cs{DTLaddentryforrow}\marg{db name}\marg{assign list}\marg{condition}\marg{key}\marg{value}
%\end{definition}
% Adds the entry with key given by \meta{key} and value given by
% \meta{value} to the first row in the database \meta{db name}
% which satisfies the condition given by \meta{condition}. The
% \meta{assign list} is the same as for \cs{DTLforeach} and may
% be used to set the values which are to be tested in \meta{condition}.
%\changes{2.0}{2009 February 27}{updated to use new database structure}
%    \begin{macrocode}
\newcommand{\DTLaddentryforrow}[5]{%
%    \end{macrocode}
% Iterate through the data base until condition is met
%\changes{2.13}{2013-01-15}{removed spurious space}
%    \begin{macrocode}
  \DTLifdbexists{#1}%
  {%
    \def\@dtl@notdone{\PackageError{datatool}{Unable to add entry
      given by key `#4': condition not met for any row in database 
      `#1'}{}}%
%    \end{macrocode}
% Iterate through each row
%    \begin{macrocode}
    \DTLforeach[#3]{#1}{#2}%
    {%
%    \end{macrocode}
% add entry to this row
%    \begin{macrocode}
      \DTLappendtorow{#4}{#5}%
%    \end{macrocode}
% disable error message
%    \begin{macrocode}
      \let\@dtl@notdone\relax
%    \end{macrocode}
% break out of loop
%    \begin{macrocode}
      \dtlbreak
    }%
    \@dtl@notdone
  }%
  {%
    \PackageError{datatool}{Unable to add entry given by key `#4':
      database `#1' doesn't exist}{}%
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLforeachkeyinrow}
%\begin{definition}
%\cs{DTLforeachkeyinrow}\marg{cmd}\marg{text}
%\end{definition}
% Iterates through each key in the current row of \cs{DTLforeach}, 
% and does \meta{text}.
%\changes{2.0}{2009 February 27}{updated to use new database structure}
%    \begin{macrocode}
\newcommand*{\DTLforeachkeyinrow}[2]{%
  \ifnum\dtlforeachlevel=0\relax
    \PackageError{datatool}{\string\DTLforeachkeyinrow\space can only
     be used inside \string\DTLforeach}{}%
  \else
%    \end{macrocode}
% Set \cs{@dtl@thisdb} to the current database name:
%    \begin{macrocode}
    \expandafter\let\expandafter\@dtl@thisdb
      \csname @dtl@dbname@\romannumeral\dtlforeachlevel\endcsname
%    \end{macrocode}
% Iterate through key list
%    \begin{macrocode}
    \dtlforeachkey(\dtlkey,\dtlcol,\dtltype,\dtlheader)\in
      \@dtl@thisdb\do{%
%    \end{macrocode}
% store row in \cs{dtlcurrentrow}
% (This may get nested so need to do it here instead of
% outside this loop in case
% \meta{text} changes it.)
%    \begin{macrocode}
      \dtlcurrentrow = 
        \csname @dtl@cur\romannumeral\dtlforeachlevel\endcsname
%    \end{macrocode}
% Get the value for this key and store in "#1"
%    \begin{macrocode}
      \edef\dtl@dogetentry{\noexpand\dtlgetentryfromcurrentrow
        {\noexpand#1}{\dtlcol}}%
      \dtl@dogetentry
%    \end{macrocode}
% Check if null
%    \begin{macrocode}
      \ifx#1\dtlnovalue
        \ifnum0\dtltype=0\relax
%    \end{macrocode}
%\changes{2.13}{2013-01-15}{changed to use \cs{@dtlstringnull} and
%\cs{@dtlnumbernull}}%
% Data type is \meta{empty} or 0, so set to string null.
%TODO: why did v2.13 change this to use \cs{@dtlstringnull} and
%\cs{@dtlnumbernull}? (Why not \cs{DTLstringnull} and
%\cs{DTLnumbernull}?)
%    \begin{macrocode}
          \let#1=\@dtlstringnull
        \else
%    \end{macrocode}
% Data type is numerical, so set to number null.
%    \begin{macrocode}
          \let#1=\@dtlnumbernull
        \fi
      \fi
%    \end{macrocode}
% Make "#1" global in case this is in a tabular environment (or
% something similar)
%    \begin{macrocode}
      \global\let#1#1%
%    \end{macrocode}
% Store loop body so that any scoping commands (such as "&") don't
% cause a problem for \cs{ifx}
%    \begin{macrocode}
      \def\@dtl@loop@body{#2}%
      \@dtl@loop@body
    }%
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{DTLforeach Conditionals}
% The following conditionals are only meant to be used within
% \cs{DTLforeach} as they depend on the counter 
% \texttt{DTLrow}\meta{n}.
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\begin{macro}{\DTLiffirstrow}
%\begin{definition}
%\cs{DTLiffirstrow}\marg{true part}\marg{false part}
%\end{definition}
% Test if the current row is the first row. (This takes 
% \meta{condition}, the optional argument of \cs{DTLforeach},
% into account, so it may not correspond to row~1 of the
% database.) Can only be used in \cs{DTLforeachrow}.
%\changes{2.0}{2009 February 27}{modified to have different
% definition depending on location}
%    \begin{macrocode}
\newcommand{\DTLiffirstrow}[2]{
  \PackageError {datatool}
  {
    \token_to_str:N \DTLiffirstrow\c_space_tl ~ can ~ only ~
    be ~ used ~ inside ~ \token_to_str:N \DTLforeach
  }
  {}
}
%    \end{macrocode}
%\end{macro}
%Definition within \cs{DTLforeach}:
%    \begin{macrocode}
\cs_new:Nn \__datatool_foreach_if_first_row:TF 
{
  \int_compare:nNnTF
    { \int_use:c { c@DTLrow\romannumeral\dtlforeachlevel } }
     =
    { \c_one_int }
  { #1 } { #2 }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLiflastrow}
%\begin{definition}
%\cs{DTLiflastrow}\marg{true part}\marg{false part}
%\end{definition}
% Checks if the current row is the last row of the database.
% It doesn't take the condition (the optional argument of
% \cs{DTLforeach}) into account, so its possible it may never
% do \meta{true part}, as the last row of the database may not
% meet the condition. It is therefore not very useful and is
% confusing since it behaves differently to \cs{DTLiffirstrow}
% which does take the condition into account, so I have removed 
% its description from the main part of the manual. If you need
% to use the optional argument of \cs{DTLforeach}, you will first
% have to iterate through the database to count up the number
% of rows which meet the condition, and then do another pass, 
% checking if the current row has reached that number.
%\changes{1.01}{2007 Aug 17}{fixed bug}
%\changes{2.0}{2009 February 27}{modified to have different
% definition depending on location}
%    \begin{macrocode}
\newcommand{\DTLiflastrow}[2]{
  \PackageError {datatool}
  {
   \token_to_str:N \DTLiflastrow\c_space_tl ~ can ~ only ~
   be ~ used ~ inside ~ \token_to_str:N \DTLforeach
  }
 { }
}
%    \end{macrocode}
%\end{macro}
%Definition within \cs{DTLforeach}:
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLifoddrow}
%\begin{definition}
%\cs{DTLifoddrow}\marg{true part}\marg{false part}
%\end{definition}
% Determines whether the current row is odd (takes the optional
% argument of \cs{DTLforeach} into account.)
%\changes{2.0}{2009 February 27}{modified to have different
% definition depending on location}
%    \begin{macrocode}
\newcommand{\DTLifoddrow}[2]{%
  \PackageError 
   {datatool}
   {
     \token_to_str:N \DTLifoddrow \c_space_tl ~ can ~ only ~
     be ~ used ~ inside ~ \token_to_str:N \DTLforeach
   }
   { }
}
%    \end{macrocode}
%\end{macro}
%Definition within \cs{DTLforeach}:
%    \begin{macrocode}
\cs_new:Nn \__datatool_foreach_if_odd_row:TF 
{
  \int_if_odd:nTF
   { \int_use:c { c@DTLrow\romannumeral\dtlforeachlevel } }
   { #1 } { #2 }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\subsection{Displaying Database}
% This section defines commands to display the entire database
% in a \env{tabular} or \env{longtable} environment.
%
%\begin{macro}{\dtlbetweencols}
% This specifies what to put between the column alignment
% specifiers.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\dtlbetweencols}{}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlbeforecols}
% This specifies what to put before the first column alignment
% specifier.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\dtlbeforecols}{}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlaftercols}
% This specifies what to put after the last column alignment
% specifier.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\dtlaftercols}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlstringalign}
% Alignment character for columns containing strings
%    \begin{macrocode}
\newcommand*{\dtlstringalign}{l}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlintalign}
% Alignment character for columns containing integers
%    \begin{macrocode}
\newcommand*{\dtlintalign}{r}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlrealalign}
% Alignment character for columns containing real numbers
%    \begin{macrocode}
\newcommand*{\dtlrealalign}{r}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlcurrencyalign}
% Alignment character for columns containing currency numbers
%    \begin{macrocode}
\newcommand*{\dtlcurrencyalign}{r}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldatetimealign}
% Alignment character for columns containing timestamps.
%    \begin{macrocode}
\newcommand*{\dtldatetimealign}{l}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldatealign}
% Alignment character for columns containing dates.
%    \begin{macrocode}
\newcommand*{\dtldatealign}{l}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtltimealign}
% Alignment character for columns containing times.
%    \begin{macrocode}
\newcommand*{\dtltimealign}{l}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\begin{macro}{\dtladdalign}
%\begin{definition}
%\cs{dtladdalign}\marg{cs}\marg{type}\marg{col num}\marg{max cols}
%\end{definition}
% Adds tabular column alignment character to \meta{cs} for column
% \meta{col num} which contains data type \meta{type}.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\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 }
      {
%    \end{macrocode}
% string
%    \begin{macrocode}
        \tl_put_right:NV #1 \dtlstringalign 
      }
    { \c_datatool_integer_int }
      {
%    \end{macrocode}
% integer
%    \begin{macrocode}
        \tl_put_right:NV #1 \dtlintalign 
      }
    { \c_datatool_decimal_int }
      {
%    \end{macrocode}
% real number
%    \begin{macrocode}
        \tl_put_right:NV #1 \dtlrealalign 
      }
    { \c_datatool_currency_int }
      {
%    \end{macrocode}
% currency
%    \begin{macrocode}
        \tl_put_right:NV #1 \dtlcurrencyalign 
      }
    { \c_datatool_datetime_int }
      {
%    \end{macrocode}
% datetime
%    \begin{macrocode}
        \tl_put_right:NV #1 \dtldatetimealign 
      }
    { \c_datatool_date_int }
      {
%    \end{macrocode}
% date
%    \begin{macrocode}
        \tl_put_right:NV #1 \dtldatealign 
      }
    { \c_datatool_time_int }
      {
%    \end{macrocode}
% time
%    \begin{macrocode}
        \tl_put_right:NV #1 \dtltimealign 
      }
    { \c_datatool_unknown_int }
      {
%    \end{macrocode}
% Unknown type
%    \begin{macrocode}
        \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
  }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\regex_new:N \l_datatool_colalign_regex
\regex_set:Nn \l_datatool_colalign_regex { [ m p r c l ] }
%    \end{macrocode}
%
%\begin{macro}{\dtladdheaderalign}
%\begin{definition}
%\cs{dtladdheaderalign}\marg{cs}\marg{type}\marg{col num}\marg{max cols}
%\end{definition}
% Similar but for the column alignment in the header row.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \dtladdheaderalign { m m m m }
{
  \int_compare:nNnTF { #3 } = { 1 }
  {
%    \end{macrocode}
%If \cs{dtlbeforecols} includes "m", "p", "r", "c", or "l" don't append.
%    \begin{macrocode}
    \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
     }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlheaderformat}
%\begin{definition}
%\cs{dtlheaderformat}\marg{text}
%\end{definition}
% Specifies how to format the column title. Pre v3.0 the definition
% included \cs{hfil} to centre without affecting vertical lines.
% This is now dealt with by \cs{dtlheaderformat}.
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{removed \cs{hfil}}
%    \begin{macrocode}
\newcommand*{\dtlheaderformat}[1]{\textbf{#1}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlcolumnheader}
%\begin{definition}
%\cs{dtlcolumnheader}\marg{align}\marg{text}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%Newer alternative. This needs to expand to avoid a \qt{Misplaced
%\cs{omit}} error.
%    \begin{macrocode}
\newcommand \dtlcolumnheader [2]
{
   \multicolumn { 1 } { #1 } { \dtlheaderformat{ #2 } } 
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlstringformat}
%\begin{definition}
%\cs{dtlstringformat}\marg{text}
%\end{definition}
% Specifies how to format entries in columns with string data type.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\dtlstringformat}[1]{#1}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlnumericformat}
%\begin{definition}
%\cs{dtlnumericformat}\marg{text}
%\end{definition}
% Specifies how to format entries in columns with a numeric data type.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\dtlnumericformat}[1]{#1}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlintformat}
%\begin{definition}
%\cs{dtlintformat}\marg{text}
%\end{definition}
% Specifies how to format entries in columns with integer data type.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\dtlintformat}[1]{\dtlnumericformat{#1}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlrealformat}
%\begin{definition}
%\cs{dtlrealformat}\marg{text}
%\end{definition}
% Specifies how to format entries in columns with real data type.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\dtlrealformat}[1]{\dtlnumericformat{#1}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtlcurrencyformat}
%\begin{definition}
%\cs{dtlcurrencyformat}\marg{text}
%\end{definition}
% Specifies how to format entries in columns with currency data type.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\dtlcurrencyformat}[1]{\dtlnumericformat{#1}}
%    \end{macrocode}
%\end{macro}
%
%NB the datetime=reformat setting can instead be used to format
%dates and times. These are just wrappers to change the font etc in
%tables.
%\begin{macro}{\dtldatetimeformat}
%\begin{definition}
%\cs{dtldatetimeformat}\marg{text}
%\end{definition}
% Specifies how to format entries in columns with datetime data type.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\dtldatetimeformat}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldateformat}
%\begin{definition}
%\cs{dtldateformat}\marg{text}
%\end{definition}
% Specifies how to format entries in columns with date data type.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\dtldateformat}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtltimeformat}
%\begin{definition}
%\cs{dtltimeformat}\marg{text}
%\end{definition}
% Specifies how to format entries in columns with time data type.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\dtltimeformat}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldisplaystarttab}
% Indicates what to do just after "\begin{tabular}"\marg{column specs}
% (e.g.\ \cs{hline}).
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\dtldisplaystarttab}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldisplayendtab}
% Indicates what to do just before "\end{tabular}".
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\dtldisplayendtab}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldisplayafterhead}
% Indicates what to do after the header row, before the first row
% of data.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\dtldisplayafterhead}{}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtldisplayvalign}
%\changes{2.11}{2012-09-25}{new}
% Stores the vertical alignment specifier for the tabular
% environment used in \cs{DTLdisplaydb}
%    \begin{macrocode}
\newcommand*{\dtldisplayvalign}{c}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldisplaystartrow}
% Indicates what to do at the start of each row (not including
% the header row or the first row of data).
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\dtldisplaystartrow}{}
%    \end{macrocode}
%\end{macro}
%
%The number of database rows per tabular row.
%    \begin{macrocode}
\int_new:N \l_datatool_display_per_row_int
\int_set_eq:NN \l_datatool_display_per_row_int \c_one_int
%    \end{macrocode}
%The number of tabular lines (not including header and footer or
%extra rows inserted by hooks). This value also doesn't take into
%account filtering. It's simply set to the database row count
%divided by the above and rounded.
%    \begin{macrocode}
\int_new:N \l_datatool_display_tab_rows_int
%    \end{macrocode}
%
%For use in the display hooks, this tests if the given column number
%is at the start of the tabular row taking replication into account.
%
%Syntax: \marg{row num}\marg{column num}\marg{true}\marg{false}
%    \begin{macrocode}
\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: }
   }
}
%    \end{macrocode}
%
%Function (which must expand to an integer) that obtains the
%database row index from the step function value that uses 
%\verb|\__datatool_display_db_row_fn:n|.
%    \begin{macrocode}
\cs_new:Nn \__datatool_display_row_idx:n { #1 }
%    \end{macrocode}
%\begin{macro}{\DTLdisplayTBrowidxmap}
%Convenient function to produce top to bottom instead of left to
%right arrangement, but only works if there's no filtering.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\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
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldisplaycr}
%\changes{2.19}{2014-01-17}{new}
%    \begin{macrocode}
\newcommand{\dtldisplaycr}{\tabularnewline}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldisplaydbenv}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtldisplaydbenv}{tabular}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdisplaydbAddBegin}
%\begin{definition}
%\cs{DTLdisplaydbAddBegin}\marg{content tl}\marg{align token
%list}\marg{header token list}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLdisplaydbAddBegin { m m m }
{
  \tl_put_right:Nx #1 { \exp_not:N \begin { \dtldisplaydbenv } } 
%    \end{macrocode}
% Note that \cs{dtldisplayvalign} needs to expand.
%    \begin{macrocode}
  \tl_if_empty:NF \dtldisplayvalign
  {
    \tl_put_right:Nx #1 
     {
       [ \dtldisplayvalign ]
     }
  }
  \tl_put_right:Nn #1 {  { #2 }  } 
%    \end{macrocode}
%Add the header row:
%    \begin{macrocode}
  \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 
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdisplaydbAddEnd}
%\begin{definition}
%\cs{DTLdisplaydbAddEnd}\marg{content tl}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\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 } } 
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\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
%    \end{macrocode}
%The following are for \cs{DTLdisplaylongdb}. These aren't private
%in case the user wants to customize \cs{DTLdisplaylongdbAddBegin}.
%The table caption (no value indicates no caption):
%    \begin{macrocode}
\tl_new:N \l_datatool_caption_tl
\tl_set:Nn \l_datatool_caption_tl { \c_novalue_tl }
%    \end{macrocode}
% The short form for the lot (no value indicates use the above):
%    \begin{macrocode}
\tl_new:N \l_datatool_short_caption_tl
\tl_set:Nn \l_datatool_short_caption_tl { \c_novalue_tl }
%    \end{macrocode}
% The continuation caption (no value indicates use the main caption):
%    \begin{macrocode}
\tl_new:N \l_datatool_cont_caption_tl
\tl_set:Nn \l_datatool_cont_caption_tl { \c_novalue_tl }
%    \end{macrocode}
%The label:
%    \begin{macrocode}
\tl_new:N \l_datatool_label_tl
\tl_set:Nn \l_datatool_label_tl { \c_novalue_tl }
%    \end{macrocode}
%The footer:
%    \begin{macrocode}
\tl_new:N \l_datatool_foot_tl
\tl_set:Nn \l_datatool_foot_tl { \c_novalue_tl }
%    \end{macrocode}
%The last footer:
%    \begin{macrocode}
\tl_new:N \l_datatool_last_foot_tl
\tl_set:Nn \l_datatool_last_foot_tl { \c_novalue_tl }
%    \end{macrocode}
%The following is used for both \cs{DTLdisplay} and
%\cs{DTLdisplaylongdb} after the header but before \cs{dtldisplaycr}
%and \cs{dtldisplayafterhead}: 
%    \begin{macrocode}
\tl_new:N \l_datatool_post_head_tl
%    \end{macrocode}
%
%Keys for \cs{DTLdisplaydb*} and \cs{DTLdisplaylongdb}:
%    \begin{macrocode}
\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,
%    \end{macrocode}
%This key is only for the \env{tabular} version:
%    \begin{macrocode}
  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 },
%    \end{macrocode}
%These keys are only for the \env{longtable} version:
%    \begin{macrocode}
  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 ,
%    \end{macrocode}
%Synonyms for backward compatibility:
%    \begin{macrocode}
  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 
       }
    },
}
%    \end{macrocode}
%
%\begin{macro}{\DTLdisplaydb}
%\begin{definition}
%\cs{DTLdisplaydb}\oarg{omit list}\marg{db}
%\end{definition}
% Displays the database \meta{db} in a tabular environment.
%Version 3.0: rewritten so that the content is first built to avoid
%the problems with having a loop in a tabular context.
%\changes{2.0}{2009 February 27}{new}
%\changes{2.10}{2012-07-18}{added optional arg}
%\changes{3.0}{2025-03-03}{changed to new document command}
%    \begin{macrocode}
\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 }
    { }
  }
}
%    \end{macrocode}
%\end{macro}
%Internal command:
%    \begin{macrocode}
\cs_new:Nn \__datatool_display_db:
{
%    \end{macrocode}
%Initialise and check supplied options make sense:
%    \begin{macrocode}
  \__datatool_if_display_init:T
  {
    \l__datatool_init_display_tl
%    \end{macrocode}
%Start constructing the content token list. Add the begin part and
%header row:
%    \begin{macrocode}
    \exp_args:NNVV \DTLdisplaydbAddBegin \l__datatool_content_tl
      \l__datatool_align_tl \l__datatool_row_tl
%    \end{macrocode}
%Add the entry rows. There are two row counts: the row index (as
%given in the database) and the
%row number (which may be different if any rows are skipped).
%    \begin{macrocode}
    \int_zero:N \dtlrownum
    \int_zero:N \l__datatool_row_idx_int
%    \end{macrocode}
% Iterate over all rows:
%    \begin{macrocode}
    \__datatool_display_loop:
%    \end{macrocode}
%Add the end part:
%    \begin{macrocode}
    \DTLdisplaydbAddEnd \l__datatool_content_tl
%    \end{macrocode}
%Do the content:
%    \begin{macrocode}
    \l__datatool_pre_display_tl
    \l__datatool_content_tl
  }
}
%    \end{macrocode}
%Row loop:
%    \begin{macrocode}
\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 } }
      }
   }
}
%    \end{macrocode}
%Initialise and check supplied options make sense:
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%Keep track of which columns should be included:
%    \begin{macrocode}
  \seq_clear:N \l__datatool_column_indexes_seq
%    \end{macrocode}
%If only-keys has been set, obtain the corresponding column indexes:
%    \begin{macrocode}
  \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'
         }
       }
     }
  }
%    \end{macrocode}
%If omit-keys has been set, obtain the corresponding column indexes:
%    \begin{macrocode}
  \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
  {
%    \end{macrocode}
% No inclusion list supplied. Columns will be ordered by column index,
% skipping omitted columns.
%    \begin{macrocode}
    \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 }
       }
     }
  }
  {
%    \end{macrocode}
% Inclusion list supplied:
%    \begin{macrocode}
      \seq_set_eq:NN \l__datatool_column_indexes_seq
        \l__datatool_only_columns_seq
  }
%    \end{macrocode}
%Total number of columns to be shown:
%    \begin{macrocode}
  \int_set:Nn \l__datatool_max_cols_int
     { \seq_count:N \l__datatool_column_indexes_seq }
%    \end{macrocode}
%Make sure there are 1 or more columns.
%    \begin{macrocode}
  \int_compare:nNnTF { \l__datatool_max_cols_int } > { \c_zero_int }
  {
%    \end{macrocode}
%Get the alignment specifications.
%    \begin{macrocode}
    \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
      {
%    \end{macrocode}
% If no header, locally set this to a token list containing a single
% empty value to skip the default header formation, which is 
% redundant in this case.
%    \begin{macrocode}
        \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
    }
%    \end{macrocode}
%If there should be multiple database rows per tabular row, add
%copies.
%    \begin{macrocode}
    \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 }
       }
    }
%    \end{macrocode}
%Calculate database row count divided by replicate count. Note that
%this doesn't take into account any filtering.
%    \begin{macrocode}
    \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 } }
     }
%    \end{macrocode}
%Initialisation successful, do the main body:
%    \begin{macrocode}
    #1
  }
  {
%    \end{macrocode}
%Trigger an error or warning if no columns. Only a warning is issued
%if the database is empty (to allow for saving a database at the end
%of one run to be loaded at the next).
%    \begin{macrocode}
    \@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 
       }
     }
  }
}
%    \end{macrocode}
% Handler for iterating over column data:
%    \begin{macrocode}
\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 } } }
  }
}
%    \end{macrocode}
%Row handler:
%    \begin{macrocode}
\cs_new:Nn \__datatool_display_db_row_fn:n
{
%    \end{macrocode}
%Get the current row from the given row index. This sets the
%\cs{dtlcurrentrow} token register so that the condition can lookup
%values in the current row if required.
%    \begin{macrocode}
  \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 }
  }
}
%    \end{macrocode}
%Function handler when iterating over columns within a row.
%    \begin{macrocode}
\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 { & } 
  }
%    \end{macrocode}
%This may not be the same as \verb|#1| if columns are missing or not
%in sequential order. Use \verb|#1| to reference the column.
%    \begin{macrocode}
  \int_incr:N \l__datatool_col_idx_int
%    \end{macrocode}
% Get the entry for the current column and row.
%    \begin{macrocode}
  \dtlgetentryfromcurrentrow { \l__datatool_item_value_tl } { #1 }
  \exp_args:NV \__datatool_get_col_type:nn \l__datatool_keydata_tl
   { #1 }
%    \end{macrocode}
%Ensure that any null values match their data type.
%    \begin{macrocode}
  \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
     }
  }
%    \end{macrocode}
%Add the item to the content list variable:
%    \begin{macrocode}
  \__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
}
%    \end{macrocode}
%
%\begin{macro}{\DTLdisplaydbAddItem}
%\begin{definition}
%\cs{DTLdisplaydbAddItem}\meta{content-tl}\marg{item}\marg{fmt-cs}\marg{type}\marg{row}\marg{row idx}\marg{col}\marg{col idx}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%Add the column item:
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldisplaylongdbenv}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtldisplaylongdbenv}{longtable}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdisplaylongdbAddBegin}
%\begin{definition}
%\cs{DTLdisplaydblongAddBegin}\marg{content tl}\marg{align token
%list}\marg{header token list}
%\end{definition}
%    \begin{macrocode}
\NewDocumentCommand \DTLdisplaylongdbAddBegin { m m m }
{
  \tl_put_right:Nx #1 
   {
     \exp_not:N \begin { \dtldisplaylongdbenv }
   }
  \tl_put_right:Nn #1 
   {
     { #2 }
   }
%    \end{macrocode}
%Add the header row. Is there a caption?
%    \begin{macrocode}
  \tl_if_eq:NnTF \l_datatool_caption_tl { \c_novalue_tl }
  {
%    \end{macrocode}
%No caption.
%    \begin{macrocode}
    \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 }
    }
  }
  {
%    \end{macrocode}
%Caption. Is there a short caption?
%    \begin{macrocode}
    \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 } } }
%    \end{macrocode}
% Is there a label?
%    \begin{macrocode}
    \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 }
%    \end{macrocode}
%Caption. Is there a continuation caption?
%    \begin{macrocode}
    \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 }
  }
%    \end{macrocode}
% Is there a footer?
%    \begin{macrocode}
  \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 }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdisplaylongdbAddEnd}
%\begin{definition}
%\cs{DTLdisplaylongdbAddEnd}\marg{content tl}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLdisplaylongdbAddEnd { m }
{
  \tl_put_right:NV #1 \dtldisplayendtab 
  \tl_put_right:Nx #1 { \exp_not:N \end { \dtldisplaylongdbenv } }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdisplaylongdb}
%\begin{definition}
%\cs{DTLdisplaylongdb}\oarg{options}\marg{db}
%\end{definition}
% Displays the database \meta{db} in a longtable environment.
% (User needs to load \sty{longtable}).
%\changes{2.0}{2009 February 27}{new}
%\changes{2.10}{2012-07-18}{added omit option}
%\changes{3.0}{2025-03-03}{made robust and reimplemented}
%    \begin{macrocode}
\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 }
    { }
  }
}
%    \end{macrocode}
%\end{macro}
% Inner command:
%    \begin{macrocode}
\cs_new:Nn \__datatool_display_long_db:
{
%    \end{macrocode}
%Initialise and check supplied options make sense:
%    \begin{macrocode}
  \__datatool_if_display_init:T
  {
    \l__datatool_init_display_tl
%    \end{macrocode}
%Start constructing the content token list. Add the begin part and
%header row:
%    \begin{macrocode}
    \exp_args:NNVV \DTLdisplaylongdbAddBegin \l__datatool_content_tl
      \l__datatool_align_tl \l__datatool_row_tl
%    \end{macrocode}
% The rest is much the same as for \cs{DTLdisplaydb}:
%    \begin{macrocode}
    \int_zero:N \dtlrownum
    \int_zero:N \l__datatool_row_idx_int
%    \end{macrocode}
% Iterate over all rows:
%    \begin{macrocode}
    \__datatool_display_loop:
%    \end{macrocode}
%Add the end part:
%    \begin{macrocode}
    \DTLdisplaylongdbAddEnd \l__datatool_content_tl
%    \end{macrocode}
%Do the content:
%    \begin{macrocode}
    \l__datatool_pre_display_tl
    \l__datatool_content_tl
  }
}
%    \end{macrocode}
%
%\subsection{Editing Databases}
%
%\begin{macro}{\@dtl@toksA}
%    \begin{macrocode}
\newtoks\@dtl@toksA
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtl@toksB}
%    \begin{macrocode}
\newtoks\@dtl@toksB
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlswaprows}
%\begin{definition}
%\cs{dtlswaprows}\marg{db}\marg{row1 idx}\marg{row2 idx}
%\end{definition}
% Swaps the rows with indices \meta{row1 idx} and \meta{row2 idx}
% in the database \meta{db}.
% (Doesn't check if data base exists or if indices are out of
% bounds.)
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd*{\dtlswaprows}[3]{%
  \ifnum#2=#3\relax
%    \end{macrocode}
% Attempt to swap row with itself: do nothing.
%    \begin{macrocode}
  \else
%    \end{macrocode}
% Let row A be the row with the lower index and row B be the
% row with ther higher index.
%    \begin{macrocode}
    \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
%    \end{macrocode}
% Split the database around row A.
%    \begin{macrocode}
    \edef\@dtl@dosplit{\noexpand\dtlgetrow{#1}{\@dtl@rowAidx}}%
    \@dtl@dosplit
%    \end{macrocode}
% Store first part of database in \cs{@dtl@firstpart}.
%    \begin{macrocode}
    \expandafter\def\expandafter\@dtl@firstpart\expandafter
      {\the\dtlbeforerow}%
%    \end{macrocode}
% Store row A in \cs{@dtl@toksA}.
%    \begin{macrocode}
    \@dtl@toksA=\dtlcurrentrow
%    \end{macrocode}
% Split the second part (everything after row A).
%    \begin{macrocode}
    \edef\@dtl@dosplit{\noexpand\@dtlgetrow
      {\the\dtlafterrow}{\@dtl@rowBidx}}%
    \@dtl@dosplit
%    \end{macrocode}
% Store the mid part (everything between row A and row B)
%    \begin{macrocode}
    \expandafter\def\expandafter\@dtl@secondpart\expandafter
      {\the\dtlbeforerow}%
%    \end{macrocode}
% Store row B in \cs{@dtl@toksB}.
%    \begin{macrocode}
    \@dtl@toksB=\dtlcurrentrow
%    \end{macrocode}
% Store the last part (everything after row B).
%    \begin{macrocode}
    \expandafter\def\expandafter\@dtl@thirdpart\expandafter
      {\the\dtlafterrow}%
%    \end{macrocode}
% Reconstruct database: store first part in \cs{toks@}
%    \begin{macrocode}
    \toks@=\expandafter{\@dtl@firstpart}%
%    \end{macrocode}
% Store mid part in \cs{dtl@toks}
%    \begin{macrocode}
    \@dtl@toks=\expandafter{\@dtl@secondpart}%
%    \end{macrocode}
% Format data for first part, row B and mid part.
%    \begin{macrocode}
    \edef\@dtl@tmp{\the\toks@
      \__datatool_row_markup:VV
         \@dtl@rowAidx
         \@dtl@toksB
     \the\@dtl@toks}%
%    \end{macrocode}
% Store data so far in \cs{toks@}.
%    \begin{macrocode}
    \toks@=\expandafter{\@dtl@tmp}%
%    \end{macrocode}
% Store last part in \cs{dtl@toks}.
%    \begin{macrocode}
    \@dtl@toks=\expandafter{\@dtl@thirdpart}%
%    \end{macrocode}
% Format row A and end part.
%    \begin{macrocode}
    \edef\@dtl@tmp{\the\toks@
     \__datatool_row_markup:VV
       \@dtl@rowBidx
       \@dtl@toksA
     \the\@dtl@toks
    }%
%    \end{macrocode}
% Update the database according to global setting.
%\changes{3.0}{2025-03-03}{obey global setting}
%    \begin{macrocode}
    \__datatool_dtldb_set:nV { #1 } \@dtl@tmp
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@decrementrows}
%\begin{definition}
%\cs{dtl@decrementrows}\marg{toks}\marg{n}
%\end{definition}
% decrement by 1 all rows in \meta{toks} with row index above
% \meta{n}. NB this doesn't change the database row count.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\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}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@decrementrows}
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\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
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\begin{macro}{\DTLremoverow}
%\begin{definition}
%\cs{DTLremoverow}\marg{db}\marg{row index}
%\end{definition}
% Remove row with given index from database named \meta{db}.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\DTLremoverow}[2]{%
%    \end{macrocode}
% Check database exists
%    \begin{macrocode}
  \DTLifdbexists{#1}%
  {%
%    \end{macrocode}
% Check index if index is out of bounds
%    \begin{macrocode}
    \ifnum#2>0\relax
%    \end{macrocode}
% Check if data base has at least \meta{row index} rows
%    \begin{macrocode}
      \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}{}%
  }%
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\begin{macro}{\@DTLremoverow}
%\begin{definition}
%\cs{@DTLremoverow}\marg{db}\marg{row index}
%\end{definition}
% Doesn't perform any checks for the existence of the database
% or if the index is in range.
%\changes{2.0}{2009 February 27}{new}
%    \begin{macrocode}
\newcommand*{\@DTLremoverow}[2]{%
%    \end{macrocode}
% Get row from data base
%    \begin{macrocode}
      \edef\dtl@dogetrow{\noexpand\dtlgetrow{#1}{\number#2}}%
      \dtl@dogetrow
%    \end{macrocode}
% Update the row indices
%    \begin{macrocode}
      \expandafter\dtl@decrementrows\expandafter
        {\dtlbeforerow}{#2}%
      \expandafter\dtl@decrementrows\expandafter
        {\dtlafterrow}{#2}%
%    \end{macrocode}
% Reconstruct database according to global setting.
%\changes{3.0}{2025-03-03}{obey global setting}
%    \begin{macrocode}
  \__datatool_dtldb_set:nx { #1 }
   {
     \exp_not:V \dtlbeforerow
     \exp_not:V \dtlafterrow
   }
%    \end{macrocode}
% Decrement row counter.
%    \begin{macrocode}
  \bool_if:NTF \l__datatool_db_global_bool
   {
     \int_gdecr:c { dtlrows@ #1 }
   }
   {
     \int_decr:c { dtlrows@ #1 }
   }
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Database Functions}
%
%\begin{macro}{\DTLsumforkeys}
%\begin{definition}
%\cs{DTLsumforkeys}\oarg{condition}\oarg{assign list}\marg{db list}\marg{key list}\marg{cmd}
%\end{definition}
% Sums all entries for key \meta{key} over all databases listed
% in \meta{db list},
% and stores in \meta{cmd}, which must be a control sequence.
% The first argument \meta{condition} is the same as that
% for \cs{DTLforeach}. The second optional argument provides
% an assignment list to pass to \cs{DTLforeach} in case extra
% information is need by \meta{condition}.
% Version 3.0: switched to using \cs{DTLmapdata}
%\changes{2.0}{2009 February 27}{added second optional argument}
%\changes{3.0}{2025-03-03}{changed to new document command}
%\changes{3.0}{2025-03-03}{changed to use \cs{DTLmapdata}}
%    \begin{macrocode}
\NewDocumentCommand \DTLsumforkeys { o o m m m }
{
%    \end{macrocode}
%Scope to prevent problems if nested.
%    \begin{macrocode}
  \group_begin:
  \fp_zero:N \l__datatool_total_fp
%    \end{macrocode}
%Initialise.
%    \begin{macrocode}
  \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
   }
%    \end{macrocode}
% Iterate over all the listed data bases:
%    \begin{macrocode}
  \clist_map_inline:nn { #3 } 
   {
%    \end{macrocode}
% Iterate over the current database (read only):
%    \begin{macrocode}
     \DTLmapdata [ name = { ##1 }, read-only ] 
     {
       \IfValueT { #2 }
        {
          \DTLmapgetvalues { #2 }
        }
       \__datatool_filter:n
        {
%    \end{macrocode}
% Iterate through key list.
%    \begin{macrocode}
          \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
%    \end{macrocode}
% Check that the value is numerical.
%    \begin{macrocode}
               \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
               {
%    \end{macrocode}
% Save previous settings.
%    \begin{macrocode}
                 \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
%    \end{macrocode}
% Obtain this item's numeric value as a floating point variable.
%    \begin{macrocode}
                 \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 }
%    \end{macrocode}
% Update data type, if applicable.
%    \begin{macrocode}
                 \__datatool_update_datatype:
               }
             }
          }
        }
     }
   }
%    \end{macrocode}
% Convert floating point variable and format result.
%\changes{3.1}{2025-03-10}{expand result}
%    \begin{macrocode}
  \tl_set:Ne \l__datatool_result_tl
   { \fp_to_decimal:N \l__datatool_total_fp }
  \__datatool_assign_result:N #5
%    \end{macrocode}
%Expand to the formatted value if store-datum = false.
%    \begin{macrocode}
  \bool_if:NF \l__datatool_db_store_datum_bool
   { \tl_set:Nx #5 { #5 } }
%    \end{macrocode}
%End scope.
%    \begin{macrocode}
  \exp_args:NNNV
   \group_end: \tl_set:Nn #5 #5
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtlsumforkeys}
%Version 3.0: removed \cs{@dtlsumforkeys}.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\DTLsumcolumn}
%\begin{definition}
%\cs{DTLsumcolumn}\marg{db}\marg{key}\marg{cmd}
%\end{definition}
% Quicker version of \cs{DTLsumforkeys} that just sums over
% one column (specified by \meta{key}) for a single database
% (specified by \meta{db}) and stores the result in \meta{cmd}.
% Version 3.0: switched to using \cs{DTLmapdata}
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{changed to new document command}
%\changes{3.0}{2025-03-03}{changed to use \cs{DTLmapdata}}
%    \begin{macrocode}
\NewDocumentCommand \DTLsumcolumn { m m m }
{
%    \end{macrocode}
%Scope to prevent problems if nested.
%    \begin{macrocode}
  \group_begin:
  \fp_zero:N \l__datatool_total_fp
%    \end{macrocode}
%Initialise.
%    \begin{macrocode}
  \int_set_eq:NN
     \@dtl@datatype
     \c_datatool_unknown_int
  \tl_clear:N \l__datatool_datum_currency_tl
%    \end{macrocode}
% Iterate over the database (read only):
%    \begin{macrocode}
  \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
%    \end{macrocode}
% Check that the value is numerical.
%    \begin{macrocode}
        \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
        {
%    \end{macrocode}
% Save previous settings.
%    \begin{macrocode}
           \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
%    \end{macrocode}
% Obtain this item's numeric value as a floating point variable.
%    \begin{macrocode}
           \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 }
%    \end{macrocode}
% Update data type, if applicable.
%    \begin{macrocode}
           \__datatool_update_datatype:
        }
      }
   }
%    \end{macrocode}
% Convert floating point variable and format result.
%\changes{3.1}{2025-03-10}{expand result}
%    \begin{macrocode}
  \tl_set:Ne \l__datatool_result_tl
   { \fp_to_decimal:N \l__datatool_total_fp }
  \__datatool_assign_result:N #3
%    \end{macrocode}
%Expand to the formatted value if store-datum = false.
%    \begin{macrocode}
  \bool_if:NF \l__datatool_db_store_datum_bool
   { \tl_set:Nx #3 { #3 } }
%    \end{macrocode}
%End scope.
%    \begin{macrocode}
  \exp_args:NNNV
   \group_end: \tl_set:Nn #3 #3
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@elements}
% Count register to keep track of number of elements.
% TODO remove.
%    \begin{macrocode}
\newcount\@dtl@elements
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLmeanforkeys}
%\begin{definition}
%\cs{DTLmeanforkeys}\oarg{condition}\oarg{assign list}\marg{db list}\marg{key list}\marg{cmd}
%\end{definition}
% Computes the arithmetic mean of all entries for each key in 
% \meta{key list} over all databases in \meta{db list},
% and stores in \meta{cmd}, which must be a control sequence.
% The first argument \meta{condition} is the same as that
% for \cs{DTLforeach}. The second optional argument allows an
% assignment list to be passed to \cs{DTLmapgetvalues}.
% Version 3.0: now uses \cs{DTLmapdata}.
%\changes{2.0}{2009 February 27}{added second optional argument}
%\changes{3.0}{2025-03-03}{changed to new document command}
%\changes{3.0}{2025-03-03}{changed to use \cs{DTLmapdata}}
%    \begin{macrocode}
\NewDocumentCommand \DTLmeanforkeys { o o m m m }
{%
%    \end{macrocode}
%Scope to prevent problems if nested.
%    \begin{macrocode}
  \group_begin:
  \int_zero:N \l__datatool_count_int
  \fp_zero:N \l__datatool_total_fp
%    \end{macrocode}
%Initialise.
%    \begin{macrocode}
  \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 }
%    \end{macrocode}
% Convert floating point variable and format result.
%\changes{3.1}{2025-03-10}{expand result}
%    \begin{macrocode}
  \tl_set:Ne \l__datatool_result_tl
   { \fp_to_decimal:N \l__datatool_mean_fp }
  \__datatool_assign_result:N #5
%    \end{macrocode}
%Expand to the formatted value if store-datum = false.
%    \begin{macrocode}
  \bool_if:NF \l__datatool_db_store_datum_bool
   { \tl_set:Nx #5 { #5 } }
%    \end{macrocode}
%End scope.
%    \begin{macrocode}
  \exp_args:NNNV
   \group_end: \tl_set:Nn #5 #5
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_filtered_calc_mean:nnn
{
%    \end{macrocode}
% Iterate over all the listed data bases:
%    \begin{macrocode}
  \clist_map_inline:nn { #2 } 
   {
%    \end{macrocode}
% Iterate over the current database (read only):
%    \begin{macrocode}
     \DTLmapdata [ name = { ##1 }, read-only ] 
     {
       \IfValueT { #1 }
        {
          \DTLmapgetvalues { #1 }
        }
       \__datatool_filter:n
        {
%    \end{macrocode}
% Iterate through key list.
%    \begin{macrocode}
          \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
%    \end{macrocode}
% Check that the value is numerical.
%    \begin{macrocode}
               \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
               {
%    \end{macrocode}
% Save previous settings.
%    \begin{macrocode}
                 \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
%    \end{macrocode}
% Increment item count.
%    \begin{macrocode}
                 \int_incr:N \l__datatool_count_int
%    \end{macrocode}
% Obtain this item's numeric value as a floating point variable.
%    \begin{macrocode}
                 \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 } }
%    \end{macrocode}
% Update data type, if applicable.
%    \begin{macrocode}
                 \__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 }
  }
}
%    \end{macrocode}
%
%\begin{macro}{\@dtlmeanforkeys}
%Version 3.0: removed \cs{@dtlmeanforkeys}.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\DTLmeanforcolumn}
%\begin{definition}
%\cs{DTLmeanforcolumn}\marg{db}\marg{key}\marg{cmd}
%\end{definition}
% Quicker version of \cs{DTLmeanforkeys} that just computes the
% mean over one column (specified by \meta{key}) for a single 
% database (specified by \meta{db}) and stores the result in 
% \meta{cmd}.
%Version 3.0: switched to \cs{DTLmapdata}.
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{changed to new document command}
%\changes{3.0}{2025-03-03}{switched to using \cs{DTLmapdata}}
%    \begin{macrocode}
\NewDocumentCommand \DTLmeanforcolumn { m m m }
{
%    \end{macrocode}
% Check data base exists
%    \begin{macrocode}
  \DTLifdbexists { #1 }
  {
%    \end{macrocode}
%Scope to prevent problems if nested.
%    \begin{macrocode}
    \group_begin:
    \int_zero:N \l__datatool_count_int
    \fp_zero:N \l__datatool_total_fp
%    \end{macrocode}
%Initialise.
%    \begin{macrocode}
    \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 }
%    \end{macrocode}
% Convert floating point variable and format result.
%\changes{3.1}{2025-03-10}{expand result}
%    \begin{macrocode}
    \tl_set:Ne \l__datatool_result_tl
     { \fp_to_decimal:N \l__datatool_mean_fp }
    \__datatool_assign_result:N #3
%    \end{macrocode}
%Expand to the formatted value if store-datum = false.
%    \begin{macrocode}
    \bool_if:NF \l__datatool_db_store_datum_bool
     { \tl_set:Nx #3 { #3 } }
%    \end{macrocode}
%End scope.
%    \begin{macrocode}
    \exp_args:NNNV
     \group_end: \tl_set:Nn #3 #3
  }
  {
    \PackageError { datatool }
    { 
      Database ~ `#1' ~ isn't ~ defined
    }
    { }
  }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\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
%    \end{macrocode}
% Check that the value is numerical.
%    \begin{macrocode}
       \datatool_if_numeric_datum_type:nT { \@dtl@datatype }
       {
%    \end{macrocode}
% Save previous settings.
%    \begin{macrocode}
         \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
%    \end{macrocode}
% Increment item count.
%    \begin{macrocode}
         \int_incr:N \l__datatool_count_int
%    \end{macrocode}
% Obtain this item's numeric value as a floating point variable.
%    \begin{macrocode}
         \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 } }
%    \end{macrocode}
% Update data type, if applicable.
%    \begin{macrocode}
         \__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 }
  }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLvarianceforkeys}
%\begin{definition}
%\cs{DTLvarianceforkeys}\oarg{condition}\oarg{assign list}\marg{db list}\marg{key list}\marg{cmd}
%\end{definition}
% Computes the variance of all entries for each key in 
% \meta{key list} over all databases in \meta{db list},
% and stores in \meta{cmd}, which must be a control sequence.
% The first optional argument \meta{condition} is the same as that
% for \cs{DTLforeach}. The second optional argument is an assignment
% list to pass to \cs{DTLmapgetvalues} in case it is required for the
% condition.
% Version 3.0: changed to use \cs{DTLmapdata}
%\changes{2.0}{2009 February 27}{added second optional argument}
%\changes{3.0}{2025-03-03}{changed to new document command}
%\changes{3.0}{2025-03-03}{changed to use \cs{DTLmapdata}}
%    \begin{macrocode}
\NewDocumentCommand \DTLvarianceforkeys 
  { o o m m m }
{
%    \end{macrocode}
%Scope to prevent problems if nested.
%    \begin{macrocode}
  \group_begin:
  \int_zero:N \l__datatool_count_int
  \fp_zero:N \l__datatool_total_fp
%    \end{macrocode}
%Initialise.
%    \begin{macrocode}
  \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
   }
%    \end{macrocode}
% The mean needs to be calculated first.
%    \begin{macrocode}
   \__datatool_filtered_calc_mean:nnn { #2 } { #3 } { #4 }
  \int_if_zero:nF { \l__datatool_count_int }
  {
%    \end{macrocode}
% Calculate the variance.
%    \begin{macrocode}
    \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 }
%    \end{macrocode}
% Convert floating point variable and format result.
%\changes{3.1}{2025-03-10}{expand result}
%    \begin{macrocode}
    \tl_set:Ne \l__datatool_result_tl
     { \fp_to_decimal:N \l__datatool_tmpa_fp }
    \__datatool_assign_result:N #5
%    \end{macrocode}
%Expand to the formatted value if store-datum = false.
%    \begin{macrocode}
    \bool_if:NF \l__datatool_db_store_datum_bool
     { \tl_set:Nx #5 { #5 } }
  }
%    \end{macrocode}
%End scope.
%    \begin{macrocode}
  \exp_args:NNNV
   \group_end: \tl_set:Nn #5 #5
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtlvarianceforkeys}
%Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\DTLvarianceforcolumn}
%\begin{definition}
%\cs{DTLvarianceforcolumn}\marg{db}\marg{key}\marg{cmd}
%\end{definition}
% Quicker version of \cs{DTLvarianceforkeys} that just computes the
% variance over one column (specified by \meta{key}) for a single 
% database (specified by \meta{db}) and stores the result in 
% \meta{cmd}.
%Version 3.0: switched to \cs{DTLmapdata}.
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{changed to new document command}
%\changes{3.0}{2025-03-03}{switched to using \cs{DTLmapdata}}
%    \begin{macrocode}
\NewDocumentCommand \DTLvarianceforcolumn { m m m }
{
%    \end{macrocode}
% Check data base exists
%    \begin{macrocode}
  \DTLifdbexists { #1 }
  {
%    \end{macrocode}
%Scope to prevent problems if nested.
%    \begin{macrocode}
    \group_begin:
    \int_zero:N \l__datatool_count_int
    \fp_zero:N \l__datatool_total_fp
%    \end{macrocode}
%Initialise.
%    \begin{macrocode}
    \int_set_eq:NN
       \@dtl@datatype
       \c_datatool_unknown_int
    \tl_clear:N \l__datatool_datum_currency_tl
%    \end{macrocode}
% The mean needs to be calculated first.
%    \begin{macrocode}
    \__datatool_column_calc_mean:nn { #1 } { #2 }
    \int_if_zero:nF { \l__datatool_count_int }
    {
%    \end{macrocode}
% Calculate the variance.
%    \begin{macrocode}
      \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 }
%    \end{macrocode}
% Convert floating point variable and format result.
%\changes{3.1}{2025-03-10}{expand result}
%    \begin{macrocode}
      \tl_set:Ne \l__datatool_result_tl
       { \fp_to_decimal:N \l__datatool_tmpa_fp }
      \__datatool_assign_result:N #3
%    \end{macrocode}
%Expand to the formatted value if store-datum = false.
%    \begin{macrocode}
      \bool_if:NF \l__datatool_db_store_datum_bool
       { \tl_set:Nx #3 { #3 } }
    }
%    \end{macrocode}
%End scope.
%    \begin{macrocode}
    \exp_args:NNNV
     \group_end: \tl_set:Nn #3 #3
  }
  {
    \PackageError { datatool }
    { 
      Database ~ `#1' ~ isn't ~ defined
    }
    { }
  }
}
%    \end{macrocode}
%\end{macro}
%
%
%\begin{macro}{\DTLsdforkeys}
%\begin{definition}
%\cs{DTLsdforkeys}\oarg{condition}\oarg{assign list}\marg{db list}\marg{key list}\marg{cmd}
%\end{definition}
% Computes the standard deviation of all entries for each key in 
% \meta{key list} over all databases in \meta{db list},
% and stores in \meta{cmd}, which must be a control sequence.
% The first optional argument \meta{condition} is the same as that
% for \cs{DTLmapgetvalues}. The second optional argument is an 
% assignment list for \cs{DTLforeach} in case it is needed for
% the condition.
%Version 3.0: now uses \cs{DTLmapdata}.
%\changes{2.0}{2009 February 27}{added second optional argument}
%\changes{3.0}{2025-03-03}{changed to new document command}
%\changes{3.0}{2025-03-03}{changed to use \cs{DTLmapdata}}
%    \begin{macrocode}
\NewDocumentCommand \DTLsdforkeys
  { o o m m m }
{
%    \end{macrocode}
%Scope to prevent problems if nested.
%    \begin{macrocode}
  \group_begin:
  \int_zero:N \l__datatool_count_int
  \fp_zero:N \l__datatool_total_fp
%    \end{macrocode}
%Initialise.
%    \begin{macrocode}
  \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
   }
%    \end{macrocode}
% The mean needs to be calculated first.
%    \begin{macrocode}
   \__datatool_filtered_calc_mean:nnn { #2 } { #3 } { #4 }
  \int_if_zero:nF { \l__datatool_count_int }
  {
%    \end{macrocode}
% Calculate the variance.
%    \begin{macrocode}
    \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 ) }
%    \end{macrocode}
% Convert floating point variable and format result.
%\changes{3.1}{2025-03-10}{expand result}
%    \begin{macrocode}
    \tl_set:Ne \l__datatool_result_tl
     { \fp_to_decimal:N \l__datatool_tmpa_fp }
    \__datatool_assign_result:N #5
%    \end{macrocode}
%Expand to the formatted value if store-datum = false.
%    \begin{macrocode}
    \bool_if:NF \l__datatool_db_store_datum_bool
     { \tl_set:Nx #5 { #5 } }
  }
%    \end{macrocode}
%End scope.
%    \begin{macrocode}
  \exp_args:NNNV
   \group_end: \tl_set:Nn #5 #5
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtlsdforkeys}
%\changes{3.0}{2025-03-03}{removed}
%Version 3.0: removed.
%\end{macro}
%
%\begin{macro}{\DTLsdforcolumn}
%\begin{definition}
%\cs{DTLsdforcolumn}\marg{db}\marg{key}\marg{cmd}
%\end{definition}
% Quicker version of \cs{DTLsdforkeys} that just computes the
% standard deviation over one column (specified by \meta{key}) for 
% a single database (specified by \meta{db}) and stores the result 
% in \meta{cmd}.
%Version 3.0: now uses \cs{DTLmapdata}.
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{changed to new document command}
%\changes{3.0}{2025-03-03}{changed to use \cs{DTLmapdata}}
%    \begin{macrocode}
\NewDocumentCommand \DTLsdforcolumn { m m m }
{
%    \end{macrocode}
% Check data base exists
%    \begin{macrocode}
  \DTLifdbexists { #1 }
  {
%    \end{macrocode}
%Scope to prevent problems if nested.
%    \begin{macrocode}
    \group_begin:
    \int_zero:N \l__datatool_count_int
    \fp_zero:N \l__datatool_total_fp
%    \end{macrocode}
%Initialise.
%    \begin{macrocode}
    \int_set_eq:NN
       \@dtl@datatype
       \c_datatool_unknown_int
    \tl_clear:N \l__datatool_datum_currency_tl
%    \end{macrocode}
% The mean needs to be calculated first.
%    \begin{macrocode}
    \__datatool_column_calc_mean:nn { #1 } { #2 }
    \int_if_zero:nF { \l__datatool_count_int }
    {
%    \end{macrocode}
% Calculate the variance.
%    \begin{macrocode}
      \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 ) }
%    \end{macrocode}
% Convert floating point variable and format result.
%\changes{3.1}{2025-03-10}{expand result}
%    \begin{macrocode}
      \tl_set:Ne \l__datatool_result_tl
       { \fp_to_decimal:N \l__datatool_tmpa_fp }
      \__datatool_assign_result:N #3
%    \end{macrocode}
%Expand to the formatted value if store-datum = false.
%    \begin{macrocode}
      \bool_if:NF \l__datatool_db_store_datum_bool
       { \tl_set:Nx #3 { #3 } }
    }
%    \end{macrocode}
%End scope.
%    \begin{macrocode}
    \exp_args:NNNV
     \group_end: \tl_set:Nn #3 #3
  }
  {
    \PackageError { datatool }
    { 
      Database ~ `#1' ~ isn't ~ defined
    }
    { }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLminforkeys}
%\begin{definition}
%\cs{DTLminforkeys}\oarg{condition}\oarg{assign list}\marg{db list}\marg{key list}\marg{cmd}
%\end{definition}
% Determines the minimum over all entries for each key in 
% \meta{key list} over all databases in \meta{db list},
% and stores in \meta{cmd}, which must be a control sequence.
% The first optional argument \meta{condition} is the same as that
% for \cs{DTLforeach}. The second optional argument is an 
% assignment list for \cs{DTLforeach} in the event that extra
% information is need for the condition.
% Version 3.0: changed to use \cs{DTLmapdata}
%\changes{2.0}{2009 February 27}{added second optional argument}
%\changes{3.0}{2025-03-03}{changed to new document command}
%\changes{3.0}{2025-03-03}{changed to use \cs{DTLmapdata}}
%    \begin{macrocode}
\NewDocumentCommand \DTLminforkeys { o o m m m }
{
%    \end{macrocode}
%Scope to prevent problems if nested.
%    \begin{macrocode}
  \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
   }
%    \end{macrocode}
% Iterate over all the listed data bases:
%    \begin{macrocode}
  \clist_map_inline:nn { #3 } 
   {
%    \end{macrocode}
% Iterate over the current database (read only):
%    \begin{macrocode}
     \DTLmapdata [ name = { ##1 }, read-only ] 
     {
       \IfValueT { #2 }
        {
          \DTLmapgetvalues { #2 }
        }
       \__datatool_filter:n
        {
%    \end{macrocode}
% Iterate through key list.
%    \begin{macrocode}
          \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
%    \end{macrocode}
% Check that the value is numerical.
%    \begin{macrocode}
               \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 }
      }
   }
%    \end{macrocode}
%End scope.
%    \begin{macrocode}
  \exp_args:NNNV
   \group_end: \tl_set:Nn #5 #5
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtlminforkeys}
%Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\DTLminforcolumn}
%\begin{definition}
%\cs{DTLminforcolumn}\marg{db}\marg{key}\marg{cmd}
%\end{definition}
% Quicker version of \cs{DTLminforkeys} that just finds the
% minimum value in one column (specified by \meta{key}) for a 
% single database (specified by \meta{db}) and stores the result 
% in \meta{cmd}.
% Version 3.0: switched to using \cs{DTLmapdata}
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{changed to new document command}
%\changes{3.0}{2025-03-03}{changed to use \cs{DTLmapdata}}
%    \begin{macrocode}
\NewDocumentCommand \DTLminforcolumn { m m m }
{
%    \end{macrocode}
% Check data base exists
%    \begin{macrocode}
  \DTLifdbexists{#1}%
  {
%    \end{macrocode}
%Scope to prevent problems if nested.
%    \begin{macrocode}
  \group_begin:
  \fp_set_eq:NN \l__datatool_min_fp \c_inf_fp
  \tl_clear:N #3
%    \end{macrocode}
% Iterate over the database (read only):
%    \begin{macrocode}
  \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
%    \end{macrocode}
% Check that the value is numerical.
%    \begin{macrocode}
        \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 }
      }
   }
%    \end{macrocode}
%End scope.
%    \begin{macrocode}
  \exp_args:NNNV
   \group_end: \tl_set:Nn #3 #3
  }
%    \end{macrocode}
% data base doesn't exist
%    \begin{macrocode}
  {
    \PackageError {datatool}
    { Database ~ `#1' ~ doesn't ~ exist}
    {}
  }
}
%    \end{macrocode}
%\end{macro}
%
%
%\begin{macro}{\DTLmaxforkeys}
%\begin{definition}
%\cs{DTLmaxforkeys}\oarg{condition}\oarg{assign list}\marg{db list}\marg{key list}\marg{cmd}
%\end{definition}
% Determines the maximum over all entries for each key in 
% \meta{key list} over all databases in \meta{db list},
% and stores in \meta{cmd}, which must be a control sequence.
% The first optional argument \meta{condition} is the same as that
% for \cs{DTLforeach}. The second optional argument is an
% assignment list to pass to \cs{DTLforeach} in the event that
% extra information is required in the condition.
% Version 3.0: changed to use \cs{DTLmapdata}
%\changes{2.0}{2009 February 27}{added second optional argument}
%\changes{3.0}{2025-03-03}{changed to new document command}
%\changes{3.0}{2025-03-03}{changed to use \cs{DTLmapdata}}
%    \begin{macrocode}
\NewDocumentCommand \DTLmaxforkeys { o o m m m }
{
%    \end{macrocode}
%Scope to prevent problems if nested.
%    \begin{macrocode}
  \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
   }
%    \end{macrocode}
% Iterate over all the listed data bases:
%    \begin{macrocode}
  \clist_map_inline:nn { #3 } 
   {
%    \end{macrocode}
% Iterate over the current database (read only):
%    \begin{macrocode}
     \DTLmapdata [ name = { ##1 }, read-only ] 
     {
       \IfValueT { #2 }
        {
          \DTLmapgetvalues { #2 }
        }
       \__datatool_filter:n
        {
%    \end{macrocode}
% Iterate through key list.
%    \begin{macrocode}
          \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
%    \end{macrocode}
% Check that the value is numerical.
%    \begin{macrocode}
               \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 }
      }
   }
%    \end{macrocode}
%End scope.
%    \begin{macrocode}
  \exp_args:NNNV
   \group_end: \tl_set:Nn #5 #5
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtlmaxforkeys}
%Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\DTLmaxforcolumn}
%\begin{definition}
%\cs{DTLmaxforcolumn}\marg{db}\marg{key}\marg{cmd}
%\end{definition}
% Quicker version of \cs{DTLmaxforkeys} that just finds the
% maximum value in one column (specified by \meta{key}) for a 
% single database (specified by \meta{db}) and stores the result 
% in \meta{cmd}.
% Version 3.0: switched to using \cs{DTLmapdata}
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{changed to new document command}
%\changes{3.0}{2025-03-03}{changed to use \cs{DTLmapdata}}
%    \begin{macrocode}
\NewDocumentCommand \DTLmaxforcolumn { m m m }
{
%    \end{macrocode}
% Check data base exists
%    \begin{macrocode}
  \DTLifdbexists{#1}%
  {
%    \end{macrocode}
%Scope to prevent problems if nested.
%    \begin{macrocode}
  \group_begin:
  \fp_set_eq:NN \l__datatool_max_fp \c_minus_inf_fp
  \tl_clear:N #3
%    \end{macrocode}
% Iterate over the database (read only):
%    \begin{macrocode}
  \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
%    \end{macrocode}
% Check that the value is numerical.
%    \begin{macrocode}
        \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 }
      }
   }
%    \end{macrocode}
%End scope.
%    \begin{macrocode}
  \exp_args:NNNV
   \group_end: \tl_set:Nn #3 #3
  }
%    \end{macrocode}
% data base doesn't exist
%    \begin{macrocode}
  {
    \PackageError {datatool}
    { Database ~ `#1' ~ doesn't ~ exist}
    {}
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLcomputebounds}
%\begin{definition}
%\cs{DTLcomputebounds}\oarg{condition}\marg{db list}\marg{x key}\marg{y key}\marg{minX cmd}\marg{minY cmd}\marg{maxX cmd}\marg{maxY cmd}
%\end{definition}
% Computes the maximum and minimum $x$ and $y$ values over all
% the databases listed in \meta{db list} where the $x$ value
% is given by \meta{x key} and the $y$ value is given by 
% \meta{y key}. The results are stored in \meta{minX cmd},
% \meta{minY cmd}, \meta{maxX cmd} and \meta{maxY cmd} in 
% plain decimal format.
% Version 3.0: switched to using \cs{DTLmapdata}
%\changes{3.0}{2025-03-03}{changed to new document command}
%\changes{3.0}{2025-03-03}{changed to use \cs{DTLmapdata}}
%    \begin{macrocode}
\NewDocumentCommand \DTLcomputebounds { o m m m m m m m }
{
%    \end{macrocode}
%Scope to prevent problems if nested.
%    \begin{macrocode}
  \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
   }
%    \end{macrocode}
% Iterate over all the listed data bases:
%    \begin{macrocode}
  \clist_map_inline:nn { #2 } 
   {
%    \end{macrocode}
% Iterate over the current database (read only):
%    \begin{macrocode}
     \DTLmapdata [ name = { ##1 }, read-only ] 
     {
       \DTLmapgetvalues
        {
          \DTLthisX = #3, \DTLthisY = #4
        }
       \__datatool_filter:n
        {
%    \end{macrocode}
% x key:
%    \begin{macrocode}
          \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
%    \end{macrocode}
% Check that the value is numerical.
%    \begin{macrocode}
             \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 } }
               }
             }
           }
%    \end{macrocode}
% y key:
%    \begin{macrocode}
          \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
%    \end{macrocode}
% Check that the value is numerical.
%    \begin{macrocode}
             \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'
     }
     { }
   }
%    \end{macrocode}
%End scope.
%    \begin{macrocode}
  \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
}
%    \end{macrocode}
%\end{macro}
%
%Map over columns in the current row.
%\begin{macro}{\dtlmapcurrentrow}
%\begin{definition}
%\cs{dtlmapcurrentrow}\marg{cs}\marg{body}
%\end{definition}
%    \begin{macrocode}
\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 }
  }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\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: { }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\begin{macro}{\DTLgetvalueforkey}
%\begin{definition}
%\cs{DTLgetvalueforkey}\marg{cmd}\marg{key}\marg{db name}\marg{ref
% key}\marg{ref value}
%\end{definition}
% This (globally) sets \meta{cmd} (a control sequence) to the
% value of the key specified by \meta{key} in the first row
% of the database called \meta{db name} which contains the key
% \meta{ref key} which has the value \meta{value}.
%\changes{1.01}{2007 Aug 17}{new}
%\changes{2.0}{2009 February 27}{updated to use new database
% structure}
%TODO
%    \begin{macrocode}
\newcommand*{\DTLgetvalueforkey}[5]{%
%    \end{macrocode}
% Get row containing referenced (key,value) pair
%    \begin{macrocode}
  \DTLgetrowforkey{\@dtl@row}{#3}{#4}{#5}%
%    \end{macrocode}
% Get column number for \meta{key}
%    \begin{macrocode}
  \@sdtl@getcolumnindex{\@dtl@col}{#3}{#2}%
%    \end{macrocode}
% Get value for given column
%    \begin{macrocode}
  {%
    \dtlcurrentrow=\expandafter{\@dtl@row}%
    \edef\@dtl@dogetval{\noexpand\dtlgetentryfromcurrentrow
      {\noexpand\@dtl@val}{\@dtl@col}}%
    \@dtl@dogetval
    \global\let#1=\@dtl@val
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgetrowforkey}
%\begin{definition}
%\cs{DTLgetrowforkey}\marg{cmd}\marg{db name}\marg{ref
% key}\marg{ref value}
%\end{definition}
% This (globally) sets \meta{cmd} (a control sequence) to the
% first row
% of the database called \meta{db name} which contains the key
% \meta{ref key} that has the value \meta{value}.
%\changes{1.01}{2007 Aug 17}{new}
%\changes{2.0}{2009 February 27}{update to use new database 
% structure}
%    \begin{macrocode}
\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
      }%
      {}%
    }%
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Sorting Databases}
%The sorting commands have been rewritten in v3.0 to use l3seq.
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
% The older \cs{dtlsort} has the following arguments:
%\begin{itemize}
%
%  \item Replacement list (optional): comma-separated list of column keys to
%  use as a replacement if the given value is null.
%
%  \item Sort criteria: comma-separated list of 
%  \meta{key}\texttt{=}\meta{order} items,  
%  where \meta{order} may be "ascending" or "descending". The
%  order may be omitted, in which case ascending is assumed.
%
%  \item Handler function: as for \cs{dtlsortlist}
%\end{itemize}
%
% The newer \cs{DTLsortdata} command
% works using a similar principle to \cs{DTLsortwordlist}
% but the items in the word list sequence need to include the row
% specs so the database can be reconstructed afterwards.
%
% The sort criteria argument has a different syntax for the list
% where each criteria in the list is given by
% \meta{key}=\marg{options} where \meta{key} is the column key.
% The options may be: ascending, descending, and
% replacements=\marg{key list}. This means that a different set of
% replacements in the event of null can be supplied for each key.
%
% The handler function is the same as for \cs{DTLsortword list}.
% Need a sequence to keep track of order options: true indicates
% ascending and false descending.
%    \begin{macrocode}
\seq_new:N \l__datatool_sort_order_seq
%    \end{macrocode}
%A sequence to keep track of whether the current sort criteria is
%for a numeric column. True for a numeric column, false otherwise.
%    \begin{macrocode}
\seq_new:N \l__datatool_sort_numeric_seq
%    \end{macrocode}
%A sequence to keep track of the replacement lists. Each item in the
%sequence will be the csv list.
%    \begin{macrocode}
\seq_new:N \l__datatool_replacement_indexes_seq
%    \end{macrocode}
%    \begin{macrocode}
\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 }
   } ,
}
%    \end{macrocode}
%Map function used for obtaining indexes of replacement columns:
%    \begin{macrocode}
\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 ~
    }
  }
}
%    \end{macrocode}
% Parse the sort criteria list. This will set three sequences that
% correspond to each item in the list, so the \meta{i}th item in
% each sequence corresponds to the \meta{i}th item in the criteria
% list.
%    \begin{macrocode}
\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 }
   {
%    \end{macrocode}
%Parse optional criteria:
%    \begin{macrocode}
     \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
           }
%    \end{macrocode}
% Set the column index for this key.
%    \begin{macrocode}
       \seq_put_right:Nx \l__datatool_column_indexes_seq
         { \int_use:N \l__datatool_col_idx_int }
%    \end{macrocode}
% Set the order for this key.
%    \begin{macrocode}
       \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 }
        }
%    \end{macrocode}
% Get the column type.
%    \begin{macrocode}
       \__datatool_get_col_type:vV
         { dtlkeys @ \l__datatool_default_dbname_tl }
          \l__datatool_col_idx_int
%    \end{macrocode}
% Set the numeric flag for this key.
%    \begin{macrocode}
       \datatool_if_numeric_datum_type:nTF 
          { \l__datatool_item_type_int }
        {
%    \end{macrocode}
% Numeric column.
%    \begin{macrocode}
         \seq_put_right:Nn \l__datatool_sort_numeric_seq
           { \c_true_bool }
        }
        {
%    \end{macrocode}
% Not numeric column.
%    \begin{macrocode}
         \seq_put_right:Nn \l__datatool_sort_numeric_seq
           { \c_false_bool }
        }
%    \end{macrocode}
% Indexes of replacement columns.
%    \begin{macrocode}
       \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 ~
         }
      }
   }
}
%    \end{macrocode}
%Parse the sort criteria where only the column key is provided.
%    \begin{macrocode}
\cs_new:Nn \__datatool_parse_sort_criteria:n
{
%    \end{macrocode}
%Set defaults:
%    \begin{macrocode}
  \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 }
}
%    \end{macrocode}
%Parse the sort criteria where the column key and option list are provided.
%    \begin{macrocode}
\cs_new:Nn \__datatool_parse_sort_criteria:nn
{
  \__datatool_parse_sort_criteria:n { #1 }
%    \end{macrocode}
% Parse the sort criteria options.
%    \begin{macrocode}
  \keys_set:nn { datatool / sortdata / criteria } { #2 }
}
%    \end{macrocode}
% Syntax: \meta{handler-cs}\marg{numeric bool
% var}\marg{col-idx}\marg{row-spec}\meta{replacement list}
%The column index \meta{col-idx} needs to be fully expanded.
%This will set \verb|\l__datatool_content_tl| to the sort value,
%using the fallback list in the event of a null value.
%    \begin{macrocode}
\cs_new:Nn \__datatool_set_sort_value:NNnnN
{
%    \end{macrocode}
% Get the entry from the row specs.
%    \begin{macrocode}
  \__datatool_get_entry_from_row:Nnn
    \l__datatool_item_value_tl { #3 } { #4 }
%    \end{macrocode}
% Parse the value.
%    \begin{macrocode}
  \__datatool_if_replace:NTF \l__datatool_item_value_tl 
   {
%    \end{macrocode}
% No value. Try column in the replacement list if available.
%    \begin{macrocode}
      \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 
      {
%    \end{macrocode}
% Numeric column.
% Set sort to the numeric value.
%    \begin{macrocode}
       \datatool_if_numeric_datum_type:nTF { \@dtl@datatype }
        {
          \tl_set_eq:NN \l__datatool_content_tl 
            \l__datatool_datum_value_tl
        }
        {
%    \end{macrocode}
% If the column type is numeric but the value isn't, expand fully
% and try again.
%    \begin{macrocode}
          \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
          }
          {
%    \end{macrocode}
% Value still isn't numeric so set to zero.
%    \begin{macrocode}
            \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 }
             }
          }
        }
      }
      {
%    \end{macrocode}
% Not numeric column.
% Convert to byte sequence.
%    \begin{macrocode}
       \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 }
         }
      }
   }
%    \end{macrocode}
% If null, treat as empty for strings and 0 for numbers.
%    \begin{macrocode}
  \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
   }
%    \end{macrocode}
% The sort value should now be in \verb|\l__datatool_content_tl|
% and the actual value (or null) in \verb|\l__datatool_item_value_tl|.
%    \begin{macrocode}
}
\cs_generate_variant:Nn
  \__datatool_set_sort_value:NNnnN
  { NNnVN , NNVnN }
%    \end{macrocode}
%
%Options for \cs{DTLsortdata}. Handler function:
%    \begin{macrocode}
\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
%    \end{macrocode}
% If the sort value should be saved in a column. Index or key:
%    \begin{macrocode}
\int_new:N \__datatool_sort_data_sortcol_int
\tl_new:N \__datatool_sort_data_sortcol_tl
%    \end{macrocode}
% If the letter group should be saved in a column. Index or key:
%    \begin{macrocode}
\int_new:N \__datatool_sort_data_grpcol_int
\tl_new:N \__datatool_sort_data_grpcol_tl
%    \end{macrocode}
%Determines whether or not the value in the given token list
%variable should be replaced.
%    \begin{macrocode}
\cs_set_eq:NN
  \__datatool_if_replace:NTF
  \datatool_if_null_or_empty:NTF
%    \end{macrocode}
% Encapsulate non-numeric sort values:
%    \begin{macrocode}
\cs_new:Nn \__datatool_encap_sort:nnn { #1 }
\cs_generate_variant:Nn \__datatool_encap_sort:nnn { onn }
%    \end{macrocode}
%Allow user to choose between error, warning or ignore missing
%columns.
%    \begin{macrocode}
\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
%    \end{macrocode}
% Define keys.
%    \begin{macrocode}
\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 ,
}
%    \end{macrocode}
%
%
%\begin{macro}{\DTLsortdata}
%\begin{definition}
%\cs{DTLsortdata}\oarg{options}\marg{db-name}\marg{sort criteria}
%\end{definition}
%    \begin{macrocode}
\NewDocumentCommand \DTLsortdata
  { o m m }
{
  \__datatool_db_sort:nnn { #1 } { #2 } { #3 }
}
%    \end{macrocode}
% This just makes it easier for the sort action to expand the
% arguments without the inconvenience of the optional syntax.
%    \begin{macrocode}
\cs_new:Nn \__datatool_db_sort:nnn
{
%    \end{macrocode}
%Initialise:
%    \begin{macrocode}
  \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 }
%    \end{macrocode}
%\end{macro}
%
%Syntax: \marg{db-name}\marg{sort criteria}
%    \begin{macrocode}
\cs_new:Nn \__datatool_db_sort:nn
{
 \datatool_db_state:nnnn { #1 }
 {
  \__datatool_sort_db_check_opts:n { #1 }
%    \end{macrocode}
%Scope to localise the effect of the hook.
%    \begin{macrocode}
  \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
%    \end{macrocode}
%Parse the sort criteria. This will set the sequence variables:
%\verb|\l__datatool_sort_order_seq|
%\verb|\l__datatool_sort_numeric_seq|
%\verb|\l__datatool_column_indexes_seq|
%    \begin{macrocode}
  \__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
     }
  }
  {
%    \end{macrocode}
% For each row in the database, set the list of sort values for each
% specified column.
%    \begin{macrocode}
    \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 }
            }
%    \end{macrocode}
%This will set \verb|\l__datatool_content_tl| to the sort value,
% and the original value (or null) will be in
% \verb|\l__datatool_item_value_tl|.
%    \begin{macrocode}
         \__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 }
        }
%    \end{macrocode}
%Append to the word list:
%    \begin{macrocode}
         \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
   }
 }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\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
   }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\seq_new:N \l__datatool_sorta_seq
\seq_new:N \l__datatool_sortb_seq
%    \end{macrocode}
%
%    \begin{macrocode}
\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:
        }
    }
}
%    \end{macrocode}
%Arguments expected to be braced pairs.
%    \begin{macrocode}
\cs_new:Nn \__datatool_compare_sortitem:nn
{
  \__datatool_compare_sortitem:nnnn #1 #2
}
%    \end{macrocode}
% Check the options provided to \cs{DTLsortdata}
%    \begin{macrocode}
\cs_new:Nn \__datatool_sort_db_check_opts:n
{
%    \end{macrocode}
%Does the sort value or letter group need to be saved?
%Sort column:
%    \begin{macrocode}
  \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 }
         }
      }
      {
%    \end{macrocode}
%Doesn't exist so a new column needs to be created.
%    \begin{macrocode}
        \__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 } }
      }
   }
%    \end{macrocode}
%Letter group column:
%    \begin{macrocode}
  \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 }
         }
      }
      {
%    \end{macrocode}
%Doesn't exist so a new column needs to be created.
%    \begin{macrocode}
        \__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 } }
      }
   }
%    \end{macrocode}
%If the column indexes were supplied rather than the keys, check if
%they exist. This needs to be done after the key check in case one
%was referenced by key and another by index. A reference by column index
%must exist otherwise it gets too complicated.
%    \begin{macrocode}
   \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
   }
}
%    \end{macrocode}
%Reconstruct database after sorting.
%    \begin{macrocode}
\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 
    }
}
%    \end{macrocode}
%
%\begin{macro}{\dtlsortdatavalue}
%\changes{3.0}{2025-03-03}{new}
%Used to encapsulate the sort value when returned by the
%save-sort... settings. The first argument is the actual byte
%sequence. The second is the value used to form the byte sequence.
%    \begin{macrocode}
\newcommand{\dtlsortdatavalue}[2]{#2}
%    \end{macrocode}
%\end{macro}
%Obtain the original row markup.
%    \begin{macrocode}
\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 }
   {
%    \end{macrocode}
%The sort value will be in the form
%\cs{dtlsortdatavalue}\marg{sort}\marg{datum}
%    \begin{macrocode}
     \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
        {
%    \end{macrocode}
%Something already exists in this column, so replace it.
%    \begin{macrocode}
          \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 }
   {
%    \end{macrocode}
%Fetch the letter group corresponding to the first sort item.
%    \begin{macrocode}
     \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
%    \end{macrocode}
%Use hook to post-process the letter group token variable.
%    \begin{macrocode}
     \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
      {
%    \end{macrocode}
%Something already exists in this column, so replace it.
%    \begin{macrocode}
        \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
         }
      }
   }
%    \end{macrocode}
%Add in the start and end row markers.
%    \begin{macrocode}
  \tl_put_right:Nx \l__datatool_content_tl
   {
     \__datatool_row_markup:nV
       { #3 } \l__datatool_row_tl
   }
}
%    \end{macrocode}
%Syntax: \meta{grp tl var}
%The current row specs should be in \verb|\l__datatool_row_tl|
%    \begin{macrocode}
\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
}
%    \end{macrocode}
%Hook to post-process the letter group token variable.
%    \begin{macrocode}
\cs_new:Nn \datatool_post_process_lettergroup:N
{
}
%    \end{macrocode}
%
%
%\begin{macro}{\@dtl@list}
%Version 3.0: removed \cs{@dtl@list}.
% Token register to store data when sorting.
%\changes{2.0}{2009 February 27}{new}
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\DTLsort}
%\begin{definition}
% \cs{DTLsort}\oarg{replacement keys}\marg{sort criteria}\marg{db name}
%\end{definition}
% Sorts database \meta{db name} according to \marg{sort criteria},
% which must be a comma separated list of keys, and optionally
% \texttt{=}\meta{order}, where \meta{order} is either
% "ascending" or "descending". The optional argument is a list of
% keys to uses if the given key has a null value. The starred
% version uses a case insensitive string comparison.
%\changes{1.01}{2007 Aug 17}{added optional argument}
%\changes{1.01}{2007 Aug 17}{added starred version}
%\changes{2.0}{2009 February 27}{updated to use new data structure}
%    \begin{macrocode}
\newcommand*{\DTLsort}{\@ifstar\@sDTLsort\@DTLsort}
%    \end{macrocode} 
%\end{macro}
%
%\begin{macro}{\@DTLsort}
% Unstarred (case sensitive) version.
%\changes{2.23}{2015-07-11}{bug fix: replaced \cs{dtlicompare} with
%\cs{dtlcompare}}
%    \begin{macrocode}
\newcommand{\@DTLsort}[3][]{%
  \dtlsort[#1]{#2}{#3}{\dtlcompare}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@sDTLsort}
% Starred (case insensitive) version.
%    \begin{macrocode}
\newcommand*{\@sDTLsort}[3][]{%
  \dtlsort[#1]{#2}{#3}{\dtlicompare}%
}
%    \end{macrocode}
%\end{macro}
%
%The \cs{dtlsort} macro uses some of the same sequences as \cs{DTLsortdata}:
%\verb|\l__datatool_replacement_indexes_seq| (list of replacement columns),
%\verb|\l__datatool_sort_numeric_seq| (boolean sort sequence),
%\verb|\l__datatool_column_indexes_seq| (list of columns to sort by) and
%\verb|\l__datatool_sort_order_seq| (sort order).
% The database rows will be temporarily stored in the following sequence,
% which will be sorted according to the handler.
%    \begin{macrocode}
\seq_new:N \l__datatool_dtlsort_rows_seq
%    \end{macrocode}
%
%Temporary sort values.
%    \begin{macrocode}
\tl_new:N \l__datatool_sorta_tl
\tl_new:N \l__datatool_sortb_tl
%    \end{macrocode}
%
%\begin{macro}{\dtlsort}
%\changes{2.13}{2013-01-15}{new}
%\begin{definition}
%\cs{dtlsort}\oarg{replacement keys}\marg{sort criteria}\marg{db
%name}\marg{handler}
%\end{definition}
% More general version where user supplies a handler for the
% comparison.
%\changes{3.0}{2025-03-03}{rewritten in \LaTeX3}
%    \begin{macrocode}
\NewDocumentCommand \dtlsort { o m m m }
{
%    \end{macrocode}
% Check the database exists
%    \begin{macrocode}
  \DTLifdbexists { #3 }
  {
%    \end{macrocode}
%Scope.
%    \begin{macrocode}
    \group_begin:
    \tl_set:Nx \dtldbname { #3 }
%    \end{macrocode}
% List of replacement column indexes.
%    \begin{macrocode}
    \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 }
           }
        }
     }
%    \end{macrocode}
% List of sort column indexes. This also needs to keep track of the direction.
%    \begin{macrocode}
    \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} { }
  }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\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 } }
      }
   }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\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: } }
      }
   }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\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
         }
      }
   }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\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 }
      {
%    \end{macrocode}
% Numeric column.
%    \begin{macrocode}
       \seq_put_right:Nn \l__datatool_sort_numeric_seq
         { \c_true_bool }
      }
      {
%    \end{macrocode}
% Not numeric column.
%    \begin{macrocode}
       \seq_put_right:Nn \l__datatool_sort_numeric_seq
         { \c_false_bool }
      }
   }
   {
     \PackageWarning { datatool }
       { Ignoring ~ unknown ~ sort ~ column ~ #1 }
   }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\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'
            }
         }
      }
   }
}
%    \end{macrocode}
%
%\begin{macro}{\@dtl@rowa}
% Token register to store first row when sorting. 
% Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%\begin{macro}{\@dtl@rowb}
% Token register to store comparison row when sorting.
% Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\dtl@sortdata}
%\begin{definition}
%\cs{dtl@sortdata}\marg{db}
%\end{definition}
% Sorts the data in named database using an insertion sort
% algorithm.
% \cs{@dtl@replacementkeys}, \cs{@dtl@sortorder} and 
% \cs{@dtl@comparecs} must be set prior to use.
%\changes{2.0}{2009 February 27}{new}
% Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\@dtl@sortcriteria}
%\begin{definition}
% \cs{@dtl@sortcriteria}\marg{row a toks}\marg{row b toks}
%\end{definition}
% \cs{@dtl@dbname} and \cs{@dtl@sortorder} must be set before use
% \cs{@dtl@sortorder} is a comma separated list of either just keys
% or \meta{key}=\meta{direction}.
% (Check keys are valid before use.)
%\changes{2.0}{2009 February 27}{updated to take account of new
% database structure}
% Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\@dtl@getsortdirection}
% Get the direction from either \meta{key} or
% \meta{key}=\meta{direction}. Sets \cs{@dtl@sortdirection} to
% either -1 (ascending) or 1 (descending).
% Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\@dtl@get@sortdirection}
% Get direction (trims trailing = sign)
% Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\dtl@compare}
%\begin{definition}
%\cs{dtl@compare}\marg{key}\marg{a toks}\marg{b toks}
%\end{definition}
% Compares two values according to \meta{key} of database given by
% \cs{@dtl@dbname}. Sets \cs{dtl@sortresult}. \cs{@dtl@comparecs}
% must be set to the required comparison macro.
% Version 3.0: removed.
%\changes{2.0}{2009 February 27}{no longer used}
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\dtl@compare@}
%\begin{definition}
% \cs{dtl@compare@}\marg{keyA}\marg{keyB}\marg{A toks}\marg{B toks}
%\end{definition}
% Compare \meta{A} and \meta{B} according \meta{keyA} and
% \meta{keyB} for database given by \cs{@dtl@dbname}. Sets 
% \cs{dtl@sortresult}.
% \cs{@dtl@comparecs} must be set before use.
% Version 3.0: removed.
%\changes{2.0}{2009 February 27}{updated to use new database
%  structure}
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\subsection{Saving a database to an external file}
% Version 3.0 provides a single command to save to the different
% types of files with an optional argument to specify the format.
%
%    \begin{macrocode}
\ior_new:N \g__datatool_in_stream
\iow_new:N \g__datatool_out_stream
%    \end{macrocode}
%Control whether or not the data should be expanded as it's written.
%The default doesn't expand.
%    \begin{macrocode}
\cs_new:Nn \__datatool_data:n { \exp_not:n { #1 } }
%    \end{macrocode}
%\begin{macro}{\dtlspecialvalue}
%\changes{3.0}{2025-03-03}{new}
%Elements starting with the special value marker will always
%be expanded.
%    \begin{macrocode}
\newcommand{\dtlspecialvalue}[1]{#1}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \__datatool_data_value:n
{ 
  \tl_if_head_eq_meaning:nNTF { #1 } \dtlspecialvalue
   { #1 }
   { \__datatool_data:n { #1 } }
}
%    \end{macrocode}
%    \begin{macrocode}
\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 
%    \end{macrocode}
%Escape delimiters:
%    \begin{macrocode}
\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
%    \end{macrocode}
%Similarly unescape for reading data:
%    \begin{macrocode}
\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
%    \end{macrocode}
%The default format to save is CSV.
%    \begin{macrocode}
\cs_new:Nn \__datatool_save:n { \__datatool_save_csv:n { #1 } }
%    \end{macrocode}
%The default file extension.
%    \begin{macrocode}
\tl_new:N \l__datatool_default_ext_tl
\tl_set:Nn \l__datatool_default_ext_tl { .csv }
%    \end{macrocode}
%
%The default format to load is CSV.
%    \begin{macrocode}
\cs_new:Nn \__datatool_load: { \__datatool_load_csv: }
%    \end{macrocode}
%
%File overwrite.
%    \begin{macrocode}
\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
%    \end{macrocode}
%Hook to run at the start of save/load CSV to allow for local catcode
%change for TSV format.
%    \begin{macrocode}
\tl_new:N \l__datatool_io_csv_init_tl
%    \end{macrocode}
%
%\begin{macro}{\ifdtlnoheader}
% The "noheader" option indicates that the file doesn't have a 
% header row.
%    \begin{macrocode}
\newif\ifdtlnoheader
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\ifdtlautokeys}
%\changes{2.22}{2014-06-10}{new}
%Assign the default keys even if a header row is supplied (input
%only).
%    \begin{macrocode}
\newif\ifdtlautokeys
\dtlautokeysfalse
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldefaultkey}
% Default key to use if none specified (column index will be
% appended).
%    \begin{macrocode}
\newcommand*{\dtldefaultkey}{Column}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\ifDTLnewdbonload}
% [Deprecated]  Governs whether or not the database should be defined by
% \ics{DTLloaddb} and \ics{DTLloadrawdb}.
%\changes{2.13}{2013-01-15}{new}
%    \begin{macrocode}
\newif\ifDTLnewdbonload
%    \end{macrocode}
% Ensure compatibility with previous versions:
%    \begin{macrocode}
\DTLnewdbonloadtrue
%    \end{macrocode}
%\end{macro}
%
%Conditional set by format to indicate whether or not appending to a
%database is allowed.
%    \begin{macrocode}
\bool_new:N \l__datatool_append_allowed_bool
\bool_set_true:N  \l__datatool_append_allowed_bool
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_load_init_old_style:n 
 {
   \ifDTLnewdbonload
     \__datatool_load_init_create:n { #1 }
   \else
     \__datatool_load_init_append:n { #1 }
   \fi
 }
%    \end{macrocode}
% Use old method as the default for backward-compatibility.
%    \begin{macrocode}
\cs_set_eq:NN \__datatool_load_init:n
  \__datatool_load_init_old_style:n
%    \end{macrocode}
% Error if database already exists with create setting.
%    \begin{macrocode}
\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
   }
}
%    \end{macrocode}
% Create database.
%    \begin{macrocode}
\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 }
   }
 }
%    \end{macrocode}
%
%When appending, need to map the column in the new data being parse
%to the corresponding column in the database to which the new data
%is being appended.
%    \begin{macrocode}
\prop_new:N \l__datatool_csv_colnew_to_colold_prop
%    \end{macrocode}
%
%    \begin{macrocode}
\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? }
   }
   { }
 }
%    \end{macrocode}
%Appending so set up map.
%    \begin{macrocode}
\cs_new:Nn \__datatool_csv_check_key_append:
 {
   \@sDTLifhaskey \l__datatool_io_name_tl \l__datatool_item_key_tl
   { 
%    \end{macrocode}
%Get the existing column index.
%    \begin{macrocode}
     \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 } }
   }
 }
%    \end{macrocode}
%
%With load-action=create, any extra columns can be detected if
%\cs{dtlcolumn} is greater than the column count for the database.
%    \begin{macrocode}
\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
  }
}
%    \end{macrocode}
%With load-action=append, there's no need to do anything as the
%index check will increment the column count.
%
%Update column index. Nothing needs to be done with
%load-action=create.
%    \begin{macrocode}
\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
  }
}
%    \end{macrocode}
%Default is for load-action=create
%    \begin{macrocode}
\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: { }
%    \end{macrocode}
%
%    \begin{macrocode}
\tl_new:N \l__datatool_io_name_tl
%    \end{macrocode}
%\begin{macro}{\dtllastloadedeb}
%This should be set at the end of DTLTEX v2.0 and DBTEX v2.0 files.
%Initialise to empty in case v1.0 file format loaded.
%    \begin{macrocode}
\tl_new:N \dtllastloadedeb
%    \end{macrocode}
%\end{macro}
%Does CSV/TSV source contain \LaTeX\ commands or should \TeX\
%characters be considered literally?
%    \begin{macrocode}
\bool_new:N \l__datatool_csv_literal_content_bool
\bool_set_true:N \l__datatool_csv_literal_content_bool
%    \end{macrocode}
%Version 3.2:
%Does CSV/TSV source contain plain numbers or should content be
%parsed?
%    \begin{macrocode}
\bool_new:N \l__datatool_csv_plain_content_bool
\bool_set_false:N \l__datatool_csv_plain_content_bool
%    \end{macrocode}
%
%Default keys when reading CSV/TSV files.
%    \begin{macrocode}
\prop_new:N \l__datatool_csv_keys_prop
\prop_new:N \l__datatool_csv_headers_prop
\prop_new:N \l__datatool_csv_types_prop
%    \end{macrocode}
%Version 3.4: List of column indexes that shouldn't be parsed.
%(Columns explicitly identified as string in data-types shouldn't
%have items parsed.)
%    \begin{macrocode}
\seq_new:N \l__datatool_no_parse_cols_seq
%    \end{macrocode}
%
%    \begin{macrocode}
\bool_new:N \l__datatool_csv_convert_bool
\bool_set_false:N \l__datatool_csv_convert_bool
%    \end{macrocode}
%
%\begin{macro}{\dtl@omitlines}
%\changes{2.10}{2012-07-18}{new}
%TODO replace with l3int variable?
%    \begin{macrocode}
\newcount{\dtl@omitlines}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\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
      }
   }
 }
%    \end{macrocode}
%
% Options:
%    \begin{macrocode}
\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
      },
%    \end{macrocode}
%Version 3.2:
%lines and cells are read as per csv-content=tex but the cell
%contents aren't parsed but are assumed to match the data type
%identified in the data-type option. Any numerical columns must
%have plain numbers (no currency symbol, no number group and
%a decimal point, regardless of localisation).
%    \begin{macrocode}
   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,
 }
%    \end{macrocode}
%Allow these options to be set in \cs{DTLsetup} and remove the
%separator and delimiter package options.
%    \begin{macrocode}
\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:
}
%    \end{macrocode}
%
%\begin{macro}{\DTLwrite}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLwrite}\oarg{options}\marg{filename}
%\end{definition}
%    \begin{macrocode}
\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:
}
%    \end{macrocode}
%\end{macro}
%Append default extension if omitted.
%    \begin{macrocode}
\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 / }
      }
   }
}
%    \end{macrocode}
%Wrapper function.
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%\begin{macro}{\dtl@init@write@wrap}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\ExplSyntaxOff
\newcommand{\dtl@init@write@wrap}{%
  \def\%{\string\%}%
  \def\\{\string\\}%
  \def\#{\string\#}%
  \def\{{\string\{}%
  \def\}{\string\}}%
  \def\~{\string\~}%
  \def\ {\string\ }%
}
\ExplSyntaxOn
%    \end{macrocode}
%\end{macro}
%
%Save to CSV file.
%    \begin{macrocode}
\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 }
   }
}
%    \end{macrocode}
%Append CSV entry to row token list.
%Only add delimiter if separator detected.
%    \begin{macrocode}
\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 }
    }
}
%    \end{macrocode}
%Always add delimiters.
%    \begin{macrocode}
\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
    }
}
%    \end{macrocode}
%Don't add delimiters.
%    \begin{macrocode}
\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 }
}
%    \end{macrocode}
%Default:
%    \begin{macrocode}
\cs_set_eq:NN \__datatool_append_csv_item:n
 \__datatool_append_csv_item_detect_sep:n
%    \end{macrocode}
%
%    \begin{macrocode}
\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 }
 }
%    \end{macrocode}
%
%Save to dtltex v2 file.
%    \begin{macrocode}
\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 }
%    \end{macrocode}
% Content:
%    \begin{macrocode}
  \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
            }
         }
      }
   }
%    \end{macrocode}
% Headers (no check for headers option):
%    \begin{macrocode}
  \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
      }
   }
%    \end{macrocode}
% Trailer:
%    \begin{macrocode}
  \__datatool_write:x 
    { 
       \token_to_str:N \def 
       \token_to_str:N \dtllastloadeddb { #1 } 
       \c_percent_str
    }
}
%    \end{macrocode}
%
%Save to dtltex v3 file.
%    \begin{macrocode}
\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 }
%    \end{macrocode}
% Content:
%    \begin{macrocode}
  \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
            }
         }
      }
   }
%    \end{macrocode}
% Headers:
%    \begin{macrocode}
  \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
}
%    \end{macrocode}
%
%Save to dbtex v2 file.
%    \begin{macrocode}
\cs_new:Nn \__datatool_save_db_ii:n
{
%    \end{macrocode}
% Initialise commands:
%    \begin{macrocode}
  \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:
%    \end{macrocode}
% Header:
%    \begin{macrocode}
  \__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
    }
%    \end{macrocode}
% Database Header:
%    \begin{macrocode}
  \__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 }
%    \end{macrocode}
% Database Content:
%    \begin{macrocode}
  \__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 }
%    \end{macrocode}
% Row and column count registers:
%    \begin{macrocode}
  \__datatool_write:x 
    {
%    \end{macrocode}
% Number of Rows :
%    \begin{macrocode}
      \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:
%    \end{macrocode}
% Number of Columns :
%    \begin{macrocode}
      \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 
    }
%    \end{macrocode}
% Column label to index assignments:
%    \begin{macrocode}
  \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
     }
  }
%    \end{macrocode}
% Trailer:
%    \begin{macrocode}
  \__datatool_write:n { \egroup }
  \__datatool_write:x 
    { 
       \token_to_str:N \def 
       \token_to_str:N \dtllastloadeddb { #1 } 
       \c_percent_str
    }
}
%    \end{macrocode}
%
%Save to dbtex v3 file.
%    \begin{macrocode}
\cs_new:Nn \__datatool_save_db_iii:n
{
%    \end{macrocode}
% Initialise commands:
%    \begin{macrocode}
  \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:
%    \end{macrocode}
% Header:
%    \begin{macrocode}
  \__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 }
%    \end{macrocode}
% Database Construction:
%    \begin{macrocode}
  \__datatool_write:x { \token_to_str:N \DTLreconstructdatabase  }
%    \end{macrocode}
% Number of rows and columns:
%    \begin{macrocode}
  \__datatool_write:x
    { { \DTLrowcount { #1 } } { \DTLcolumncount { #1 } } }
%    \end{macrocode}
% Database Header:
%    \begin{macrocode}
  \exp_args:Nv \__datatool_dbkeys_write_iii:n { dtlkeys@#1 }
%    \end{macrocode}
% Database Content:
%    \begin{macrocode}
  \exp_args:Nv \__datatool_dbtex_content_iii:n { dtldb@#1 }
%    \end{macrocode}
% Column label to index assignments:
%    \begin{macrocode}
  \__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 }
}
%    \end{macrocode}
%
%When saving the contents of the database in a dbtex file, the datum
%markers need to be hidden as the \LaTeX3 catcode changes won't be
%available.
%
%DBTeX v2 writes all the internal marker tokens.
%Row:
%    \begin{macrocode}
\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
 }
%    \end{macrocode}
%Column value:
%    \begin{macrocode}
\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: 
 }
%    \end{macrocode}
%DBTEX v2 doesn't support datum:
%    \begin{macrocode}
\cs_new:Nn \__datatool_dbtex_datum_ii:nnnn
 {
   \__datatool_data_value:n { #1 }
 }
%    \end{macrocode}
% Header block:
%    \begin{macrocode}
\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 
 }
%    \end{macrocode}
%DBTeX v3 hides the internal marker tokens.
%    \begin{macrocode}
\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
    }
 }
%    \end{macrocode}
%Row:
%    \begin{macrocode}
\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:   
 }
%    \end{macrocode}
%Column value:
%    \begin{macrocode}
\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:
 }
%    \end{macrocode}
%Datum:
%    \begin{macrocode}
\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 }
 }
%    \end{macrocode}
%Temporal value:
%    \begin{macrocode}
\cs_new:Nn \__datatool_dbtex_temporal_value:nn
 {
   \token_to_str:N \DTLtemporalvalue { #1 } { #2 }
 }
%    \end{macrocode}
% Headers:
%    \begin{macrocode}
\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
    }
 }
%    \end{macrocode}
% Header block:
%    \begin{macrocode}
\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:
 }
%    \end{macrocode}
%Default.
%    \begin{macrocode}
\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
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_init_dbwrite:
{
%    \end{macrocode}
% Header markers:
%    \begin{macrocode}
  \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 }
  }
%    \end{macrocode}
% Content markers:
%    \begin{macrocode}
  \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 }
  }
}
%    \end{macrocode}
%Reconstruction macros.
%\begin{macro}{\dtldbrowreconstruct}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtldbrowreconstruct}[2]{
  \__datatool_row_markup_full:nn { #1 } { #2 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtldbcolreconstruct}
%\changes{3.0}{2025-03-03}{new}
%The second argument should be \cs{dtldbvaluereconstruct} or
%\cs{dtldbdatumreconstruct} in which case it can be allowed to
%expand, but if not prevent any further expansion.
%    \begin{macrocode}
\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 } }
     }
   }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtldbdatumreconstruct}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\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 } 
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtldbvaluereconstruct}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtldbvaluereconstruct}[1]
{
  \exp_not:n { #1 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtldbheaderreconstruct}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\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@ 
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtldbreconstructkeyindex}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtldbreconstructkeyindex}[2]
{
  \csgdef { dtl@ci@\dtllastloadeddb @ #1 } { #2 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@reconstruct@data}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{dtl@reconstruct@data}\marg{header}\marg{content}\marg{num rows}\marg{num cols}
%\marg{mappings}
%\end{definition}
%    \begin{macrocode}
\newcommand{\dtl@reconstruct@data}[5]{
  \exp_args:Nxx \@dtl@reconstruct@data { #1 } {#2 }
    { #3 } { #4 } { #5 } { \dtllastloadeddb }
}
\newcommand{\@dtl@reconstruct@data}[6]{
%    \end{macrocode}
% Number of rows:
%    \begin{macrocode}
 \expandafter\global
  \expandafter\newcount\csname dtlrows@#6\endcsname
 \expandafter\global
  \csname dtlrows@#6\endcsname=#3\relax
%    \end{macrocode}
% Number of columns:
%    \begin{macrocode}
 \expandafter\global
  \expandafter\newcount\csname dtlcols@#6\endcsname
 \expandafter\global
  \csname dtlcols@#6\endcsname=#4\relax
%    \end{macrocode}
% Header:
%    \begin{macrocode}
 \expandafter\global\expandafter
 \newtoks\csname dtlkeys@#6\endcsname
 \__datatool_token_register_gset:cn { dtlkeys@#6 } { #1 }
%    \end{macrocode}
% Content:
%    \begin{macrocode}
 \expandafter\global\expandafter
    \newtoks\csname dtldb@#6\endcsname
 \__datatool_token_register_gset:cn { dtldb@#6 } { #2 }
%    \end{macrocode}
% Column label to index assignments:
%    \begin{macrocode}
 #5
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\subsubsection{Deprecated Save Functions}
%\begin{macro}{\@dtl@write}
%Used with the older functions, which are now deprecated, so
%\cs{@dtl@write} should only be defined if the older functions are used.
%\end{macro}
%\begin{macro}{\@dtl@def@write}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\@dtl@def@write}{%
  \newwrite\@dtl@write
  \let\@dtl@def@write\relax
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsavedb}
%\begin{definition}
% \cs{DTLsavedb}\marg{db name}\marg{filename}
%\end{definition}
% Save a database as an ASCII data file using the separator
% and delimiter given by \cs{@dtl@separator} and \cs{@dtl@delimiter}.
%\changes{2.0}{2009 February 27}{updated to use new database structure}
%\changes{3.0}{2025-03-03}{rewritten to use \cs{DTLwrite}}
%    \begin{macrocode}
\newcommand*{\DTLsavedb}[2]{%
  \DTLwrite[name={#1},overwrite=warn,format=csv,expand=none,add-delimiter=detect]{#2}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsavetexdb}
%\begin{definition}
% \cs{DTLsavetexdb}\marg{db name}\marg{filename}
%\end{definition}
% Save a database as a \LaTeX\ file.
%\changes{2.0}{2009 February 27}{updated to use new database structure}
%\changes{3.0}{2025-03-03}{rewritten to use \cs{DTLwrite}}
%    \begin{macrocode}
\newcommand*{\DTLsavetexdb}[2]{%
  \DTLwrite[name={#1},overwrite=warn,format=dtltex-2,expand=full]{#2}%
}
%    \end{macrocode}
%\end{macro}
%
%
%\begin{macro}{\DTLsaverawdb}
%\changes{2.13}{2013-01-15}{new}
%\changes{3.0}{2025-03-03}{rewritten to use \cs{DTLwrite}}
%    \begin{macrocode}
\newcommand*{\DTLsaverawdb}[2]{%
  \DTLwrite[name={#1},overwrite=warn,format=dbtex-2,expand=full]{#2}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLprotectedsaverawdb}
%\changes{2.15}{2013-07-10}{new}
%\changes{3.0}{2025-03-03}{rewritten to use \cs{DTLwrite}}
%    \begin{macrocode}
\newcommand*{\DTLprotectedsaverawdb}[2]{%
  \DTLwrite[name={#1},overwrite=warn,format=dbtex-2,expand=none]{#2}%
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Loading a database from an external file}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\begin{macro}{\DTLdbProvideData}
%\changes{3.0}{2025-03-03}{new}
%Default definition changes the default-name option and defines
%\cs{dtllastloadeddb}. This is in case the file is simply \cs{input}
%instead of using \cs{DTLread}.
%    \begin{macrocode}
\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 } }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLreconstructdata}
%\changes{3.0}{2025-03-03}{new}
%Deprecated. Experimental version that changes category code of "@"
%which is best avoided.
%TODO remove when a new rollback is provided after v3.0.
%    \begin{macrocode}
\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
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@DTLreconstructdata}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\@DTLreconstructdata}[5]{%
  \dtl@message{Reconstructing ~ database ~ `\dtllastloadeddb'}%
  \dtl@reconstruct@data{#1}{#2}{#3}{#4}{#5}%
  \egroup
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLreconstructdbdata}
%\begin{definition}
%\cs{DTLreconstructdbdata}\marg{header}\marg{content}\marg{rows}\marg{cols}\marg{index}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%Deprecated. Experimental command dropped in favour of
%\cs{DTLreconstructdatabase}, which has the number of rows and
%columns at the start.
%    \begin{macrocode}
\newcommand{\DTLreconstructdbdata}[5]{
  \DTLifdbexists {\dtllastloadeddb}
  {
    \PackageError{datatool}{Database ~ `\dtllastloadeddb' ~ already ~ exists}{}
  }
  {
    \dtl@message{Reconstructing ~ database ~ `\dtllastloadeddb'}
    \dtl@reconstruct@data{#1}{#2}{#3}{#4}{#5}%
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLreconstructdatabase}
%\begin{definition}
%\cs{DTLreconstructdatabase}\marg{rows}\marg{cols}\marg{header}\marg{content}\marg{index}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%This has the number of rows and columns in the first two arguments,
%which makes it easier for datatooltk to parse.
%    \begin{macrocode}
\newcommand{\DTLreconstructdatabase}[5]{
  \DTLifdbexists {\dtllastloadeddb}
  {
    \PackageError{datatool}{Database ~ `\dtllastloadeddb' ~ already ~ exists}{}
  }
  {
    \dtl@message{Reconstructing ~ database ~ `\dtllastloadeddb'}
    \dtl@reconstruct@data{#3}{#4}{#1}{#2}{#5}%
  }
}
%    \end{macrocode}
%\end{macro}
%
%Behaviour of \cs{DTLdbProvideData} within \cs{DTLread}:
%    \begin{macrocode}
\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
%    \end{macrocode}
%Don't create the database for the dbtex formats as the database
%internals are created either explicitly (dbtex v2.0) or via
%\cs{DTLreconstructdatabase}.
%    \begin{macrocode}
  \tl_if_eq:NnF \l__datatool_default_ext_tl { .dbtex }
   { \__datatool_load_init:n { \dtllastloadeddb } }
}
%    \end{macrocode}
%
%Behaviour of \cs{@sDTLsetheader} within \cs{DTLread} if noheader on:
%    \begin{macrocode}
\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)}
  }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLread}
%\cs{DTLread}\oarg{options}\marg{file}
%\end{macro}
%Reads a database from the given file according to the format option.
%    \begin{macrocode}
\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:
}
%    \end{macrocode}
%
%    \begin{macrocode}
\int_new:N \l__datatool_line_int
%    \end{macrocode}
%
%Load CSV/TSV file:
%    \begin{macrocode}
\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
      }
  }
%    \end{macrocode}
% Headers need setting now that the data type has been found.
%    \begin{macrocode}
  \int_step_function:nN
   { \int_use:c { dtlcols@ \l__datatool_io_name_tl } }
    \__datatool_parse_csv_add_header:n
%    \end{macrocode}
% Close stream.
%    \begin{macrocode}
  \ior_close:N \g__datatool_in_stream
  \tl_set_eq:NN \dtllastloadeddb \l__datatool_io_name_tl 
}
%    \end{macrocode}
%
%Add column header but check first if the key is already defined in
%case of load-action-append:
%    \begin{macrocode}
\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
  }
%    \end{macrocode}
%Has a key has been provided for this column?
%    \begin{macrocode}
  \prop_get:NnN 
      \l__datatool_csv_keys_prop { #1 }
      \l__datatool_tmpb_tl
  \quark_if_no_value:NTF \l__datatool_tmpb_tl
  {
%    \end{macrocode}
%No key has been provided for this column, so create a default.
%    \begin{macrocode}
     \tl_set:Nx \l__datatool_item_key_tl
        { \dtldefaultkey #1 }
  }
  {
%    \end{macrocode}
%A key has been provided for this column, so use that.
%    \begin{macrocode}
    \tl_set_eq:NN \l__datatool_item_key_tl \l__datatool_tmpb_tl
  }
  \@DTLifhaskey { \l__datatool_io_name_tl } \l__datatool_item_key_tl
  { }
  {
%    \end{macrocode}
%Has a header has been provided for this column?
%    \begin{macrocode}
    \prop_get:NnN \l__datatool_csv_headers_prop { #1 }
      \l__datatool_tmpb_tl
    \quark_if_no_value:NTF \l__datatool_tmpb_tl
    {
%    \end{macrocode}
%No header has been provided for this column, so use the key.
%    \begin{macrocode}
     \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
    }
%    \end{macrocode}
%Define the key to column index mapping:
%    \begin{macrocode}
    \tl_gset:cx
      { dtl@ci@ \l__datatool_io_name_tl @ \l__datatool_item_key_tl }
      { #1 }
  }
}
%    \end{macrocode}
%
%Action for blank line:
%    \begin{macrocode}
\cs_new:Nn \__datatool_csv_blank: { }
%    \end{macrocode}
%
%The literal setting is awkward as letters still need to have
%the letter category code (or be active UTF-8).
%
%    \begin{macrocode}
\cs_new:Nn \__datatool_parse_csv_process_item:n
{
%    \end{macrocode}
%TODO replace with \verb|\__datatool_process_new_value:n| ?
%    \begin{macrocode}
   \@dtl@setnewvalue { #1 }
   \tl_set:NV \l__datatool_item_value_tl \@dtl@toks
   \__datatool_load_data_postprocess:N \l__datatool_item_value_tl
}
%    \end{macrocode}
%
%    \begin{macrocode}
\tl_new:N \l_datatool_str_csv_regex_cases_tl
\tl_set:Nn \l_datatool_str_csv_regex_cases_tl
 {
%    \end{macrocode}
% replace \verb|\n|, \verb|\r| and \verb|\f| with a space
%    \begin{macrocode}
   { \x { 5C } [ nrf ] } { ~ }
%    \end{macrocode}
% replace \verb|\t| with a tab character
%    \begin{macrocode}
   { \x { 5C } t } { \t }
%    \end{macrocode}
% replace \verb|\| with \verb*|\textbackslash |
%    \begin{macrocode}
   { \cO(\x{5C}) } { \x{ 5C } textbackslash \cS\x{20} }
%    \end{macrocode}
% replace special character \meta{c} with \verb|\|\meta{c}
% for \# \$ \% \& \_ \{ and \}
%    \begin{macrocode}
   { \cO(\x{23}|\x{24}|\x{25}|\x{26}|\x{5F}|\x{7B}|\x{7D}) } { \x{ 5C } \1 }
%    \end{macrocode}
% replace \verb|^| with \verb*|\textasciicircum |
%    \begin{macrocode}
   { \cO(\x{5E}) } { \x{ 5C } textasciicircum \cS\x{20} }
%    \end{macrocode}
% replace \verb|~| with \verb*|\textasciitilde |
%    \begin{macrocode}
   { \cO(\x{7E}) } { \x{ 5C } textasciitilde \cS\x{20} }
 }
%    \end{macrocode}
%
%    \begin{macrocode}
\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
}
%    \end{macrocode}
%
%Test for blank line. Note that a blank line may be represented as a
%series of separator characters without anything else. This is
%checked for by the regex. This command may be redefined to omit the
%regular expression match if not appropriate, but the regular
%expression variable is private as it's redefined by
%\cs{DTLsetseparator}.
%    \begin{macrocode}
\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 }
     }
  }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\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 }
%    \end{macrocode}
%Split the line on the separator.
%    \begin{macrocode}
      \__datatool_split_line:
      \int_zero:N \dtlcolumnnum
      \dtlcurrentrow = { }
%    \end{macrocode}
%Iterate over each item in the row.
%    \begin{macrocode}
      \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 }
        {
%    \end{macrocode}
%Row of data.
%    \begin{macrocode}
          \__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:
%    \end{macrocode}
%Append this item to the current row token register. This only
%needs a local assignment.
%    \begin{macrocode}
          \__datatool_token_register_put_right:Nx
            \dtlcurrentrow 
            { 
              \__datatool_row_element_markup:VV
                \l__datatool_item_col_tl
                \l__datatool_item_value_tl
            }
        }
        {
%    \end{macrocode}
%Header row.
%Get the column label (key).
%    \begin{macrocode}
          \legacy_if:nTF { dtlautokeys }
          {
%    \end{macrocode}
%The autokeys setting is on so generate the default key.
%    \begin{macrocode}
            \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
             {
%    \end{macrocode}
%No key has been provided and autokeys is off so use the header as
%the key.
%    \begin{macrocode}
               \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
             }
          }
%    \end{macrocode}
% If a key already exists for this column then either we're
% appending or there's a duplicate key.
%    \begin{macrocode}
          \__datatool_csv_check_key:
%    \end{macrocode}
% Has a header been provided?
%    \begin{macrocode}
          \prop_if_in:NVF
             \l__datatool_csv_headers_prop
             \l__datatool_item_col_tl
           {
%    \end{macrocode}
%A header has not been provided for this column so use the value
%found on this line of the file.
%    \begin{macrocode}
             \prop_put:NVV
               \l__datatool_csv_headers_prop
               \l__datatool_item_col_tl
               \l__datatool_item_value_tl
           }
%    \end{macrocode}
%Unless data-types has been used, no information is currently available
%about the data type so header information needs to be saved
%(in the property map) and set later.
%    \begin{macrocode}
        }
      }
      \legacy_if:nTF { dtlnoheader }
      {
%    \end{macrocode}
%Globally append this row of data to the database token register.
%    \begin{macrocode}
        \int_incr:N \dtlrownum 
        \__datatool_token_register_gput_right:cx
          { dtldb@ \l__datatool_io_name_tl }
          {
            \__datatool_row_markup:VV
              \dtlrownum
              \dtlcurrentrow
          }
%    \end{macrocode}
%Globally increment the row count.
%    \begin{macrocode}
        \int_gincr:c
           { dtlrows@ \l__datatool_io_name_tl }
      }
      {
%    \end{macrocode}
%Header has been found. The next row will be data.
%    \begin{macrocode}
        \legacy_if_set_true:n { dtlnoheader }
      }
%    \end{macrocode}
%If the number of columns is greater than the current column count
%for the database, update it.
%    \begin{macrocode}
      \__datatool_csv_check_col_count:
    }
  }
}
%    \end{macrocode}
%
%Version 3.2: function for parsing CSV item:
%    \begin{macrocode}
\cs_new:Nn \__datatool_parse_csv_item:
 {
%    \end{macrocode}
%Check if this column shouldn't be parsed:
%    \begin{macrocode}
   \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 }
    }
    {
%    \end{macrocode}
%Check if this column should be converted:
%    \begin{macrocode}
     \bool_if:NTF \l__datatool_csv_convert_bool
      {
%    \end{macrocode}
%Convert if number.
%    \begin{macrocode}
        \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 }
         }
      }
      {
%    \end{macrocode}
%Parse to determine data type.
%    \begin{macrocode}
        \__datatool_parse:NV
         \l__datatool_tmpa_tl
         \l__datatool_item_value_tl 
      }
    }
%    \end{macrocode}
%If store-datum option on, set the current item to its datum
%representation.
%    \begin{macrocode}
   \bool_if:NT \l__datatool_db_store_datum_bool
    {
%    \end{macrocode}
% Convert to weird datum.
%    \begin{macrocode}
      \__datatool_to_weird_datum:N \l__datatool_tmpa_tl
      \tl_set_eq:NN
        \l__datatool_item_value_tl
        \l__datatool_tmpa_tl
    }
%    \end{macrocode}
%If the data type was determined (that is, the item isn't empty), 
%update the column metadata if applicable.
%    \begin{macrocode}
   \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 }
        }
      }
   }
 }
%    \end{macrocode}
%Just for csv-content=no-parse:
%    \begin{macrocode}
\cs_new:Nn \__datatool_no_parse_csv_item:
 {
%    \end{macrocode}
%Assume string if no information provided in data-types:
%    \begin{macrocode}
   \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
      {
%    \end{macrocode}
%Assume same as the final data type unless this is the first column:
%    \begin{macrocode}
        \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
%    \end{macrocode}
% Column has been identified with a numeric type.
% Does it need converting?
%    \begin{macrocode}
      \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
              }
          }
%    \end{macrocode}
%Bit strange to want conversion but not store as datum but
%allow it anyway.
%    \begin{macrocode}
         \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 }
          {
%    \end{macrocode}
%No conversion requested but markup currency.
%    \begin{macrocode}
            \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
          }
       }
    }
%    \end{macrocode}
%If store-datum option on, set the current item to its datum
%representation.
%    \begin{macrocode}
   \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 }
        }
      }
%    \end{macrocode}
% Convert to weird datum.
%    \begin{macrocode}
      \__datatool_to_weird_datum_no_parse:N \l__datatool_item_value_tl
    }
 }
%    \end{macrocode}
%
%Splitting is awkward because the delimiter needs to be taken into
%account.
%
%    \begin{macrocode}
\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
%    \end{macrocode}
%
%    \begin{macrocode}
\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
  }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\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
         }
       }
    }
    {
%    \end{macrocode}
%\changes{3.3}{2025-03-15}{bug fix: multiple separators in delimited cell}
%Version 3.3: bug fix (middle part missing if multiple separators in
%delimited cell).
%If we've reached here, the cell is under construction. Only end the
%cell construction if the closing separator is found.
%    \begin{macrocode}
       \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:
  }
}
%    \end{macrocode}
%
%Load DTLTEX or DBTEX file:
%    \begin{macrocode}
\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
  }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\begin{macro}{\DTLloaddbtex}
%\begin{definition}
%\cs{DTLloaddbtex}\marg{cs}\marg{file}
%\end{definition}
%Version 3.0: This now just uses \cs{DTLread} and assigns the control sequence.
%\changes{2.20}{2014-02-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLloaddbtex}[2]{%
  \DTLread[format=dbtex]{#2}%
  \let#1\dtllastloadeddb
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLloaddb}
%\begin{definition}
% \cs{DTLloaddb}\oarg{options}\marg{db name}\marg{filename}
%\end{definition}
%Version 3.0: rewritten to simply use \cs{DTLread}.
%\changes{2.0}{2009 February 27}{removed checks to see if the 
% database exists when adding to it}
%\changes{2.0}{2009 February 27}{added optional argument}
%    \begin{macrocode}
\NewDocumentCommand\DTLloaddb{O{}mm}{%
  \DTLread[name={#2},format=csv,csv-content=tex,#1]{#3}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLloadrawdb}
%\begin{definition}
%\cs{DTLloadrawdb}\oarg{options}\marg{db name}\marg{filename}
%\end{definition}
%    \begin{macrocode}
\NewDocumentCommand\DTLloadrawdb{O{}mm}{%
  \DTLread[name={#2},format=csv,csv-content=literal,#1]{#3}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLrawmap}
%\begin{definition}
%\cs{DTLrawmap}\marg{string}\marg{replacement}
%\end{definition}
% Additional mappings to perform when reading a raw data file.
%Version 3.0: this is still supported for the literal CSV read.
%    \begin{macrocode}
\newcommand*{\DTLrawmap}[2]{%
  \expandafter\@dtl@toks\expandafter{\@dtl@rawmappings}%
  \ifdefempty{\@dtl@rawmappings}%
  {%
    \def\@dtl@rawmappings{{#1}{#2}}%
  }%
  {%
%    \end{macrocode}
%\changes{2.13}{2013-01-15}{removed spurious space}
%    \begin{macrocode}
    \def\@dtl@tmp{{#1}{#2}}%
    \protected@edef\@dtl@rawmappings{\the\@dtl@toks,\@dtl@tmp}%
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@rawmappings}
% List of mappings.
%    \begin{macrocode}
\newcommand*{\@dtl@rawmappings}{}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@domappings}
%\begin{definition}
% \cs{dtl@domappings}\marg{cmd}
%\end{definition}
% Do all mappings in string given by \meta{cmd}.
%\changes{2.10}{2012-07-18}{replaced \cs{DTLsubstitute} with
%\cs{DTLsubstituteall}}
%    \begin{macrocode}
\newcommand*{\dtl@domappings}[1]{%
  \@for\@dtl@map:=\@dtl@rawmappings\do{%
    \expandafter\DTLsubstituteall\expandafter#1\@dtl@map
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Debugging commands}
% These commands are provided to assist debugging
%
%\begin{macro}{\DTLdbLog}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLdbLog}\marg{db name}
%\end{definition}
%Writes detailed information in the log file about the database
%structure. NB this uses a regex for convenience but it means
%that it can only be used for small database as l3regex can't
%handle more than 32732 tokens. For large databases, try saving as
%dbtex instead. This is here to debug the code to check for
%incorrect markup.
%    \begin{macrocode}
\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
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlshowdb}
%\begin{definition}
%\cs{dtlshowdb}\marg{db name}
%\end{definition}
% Shows the database.
%\changes{2.0}{2009 February 27}{updated to use new database structure}
%    \begin{macrocode}
\newcommand*{\dtlshowdb}[1]{%
  \ifcsundef{dtldb@#1}%
  {\PackageError{datatool}{Database `#1' not defined}{}}%
  {\expandafter\showthe\csname dtldb@#1\endcsname}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlshowdbkeys}
%\begin{definition}
%\cs{dtlshowdbkeys}\marg{db name}
%\end{definition}
% Shows the key list for the named database.
%\changes{2.0}{2009 February 27}{updated to use new database structure}
%    \begin{macrocode}
\newcommand*{\dtlshowdbkeys}[1]{%
  \ifcsundef{dtlkeys@#1}%
  {\PackageError{datatool}{Database `#1' not defined}{}}%
  {\expandafter\showthe\csname dtlkeys@#1\endcsname}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlshowtype}
%\begin{definition}
%\cs{dtlshowtype}\marg{db name}\marg{key}
%\end{definition}
% Show the data type for given key in the named database.
% This should be an integer from 0 to 3.
%\changes{2.0}{2009 February 27}{updated to use new database structure}
%    \begin{macrocode}
\newcommand*{\dtlshowtype}[2]{%
  \ifcsundef{dtldb@#1}%
  {\PackageError{datatool}{Database `#1' not defined}{}}%
  {\DTLgetdatatype{\@dtl@type}{#1}{#2}\show\@dtl@type}%
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Deprecated}
%These commands are marked for removal.
%
%\begin{macro}{\@dtl@construct@lopoff}
%\begin{definition}
%\cs{@dtl@construct@lopoff}\meta{separator char}\meta{delimiter char}
%\end{definition}
% This defines 
%\begin{definition}
% \cs{@dtl@lopoff}\meta{first element}\meta{sep}\meta{rest of list}\cs{to}\meta{cmd1}\meta{cmd2} 
%\end{definition}
%for the current separator and delimiter.
%\changes{2.30}{2018-04-16}{removed spurious spaces}
%    \begin{macrocode}
\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
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@construct@qlopoff}
%\begin{definition}
% \cs{@dtl@construct@qlopoff}\meta{separator char}\meta{delimiter char}
%\end{definition}
% This constructs \cs{@dtl@qlopoff} to be used 
% when the entry is surrounded by the current delimiter value.
%    \begin{macrocode}
\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}%
%    \end{macrocode}
% Replace any escaped delimiters
%\changes{2.10}{2012-07-18}{Added code to replace escaped delimiters}
%    \begin{macrocode}
      \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}%
  }%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtl@construct@lop@ff}
%\begin{definition}
% \cs{@dtl@construct@lop@ff}\meta{separator char}
%\end{definition}
% This constructs \cs{@dtl@lop@ff} to be used when 
% the entry isn't surrouded by the delimiter.
%    \begin{macrocode}
\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}%
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@construct@lopoffs}
%\begin{definition}
%\cs{@dtl@construct@lopoffs}
%\end{definition}
% This constructs all the lopoff macros
% using the given separator and delimiter characters.
%    \begin{macrocode}
\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}%
}
%    \end{macrocode}
%\end{macro}
%
%\iffalse
%    \begin{macrocode}
%</datatool.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datagidx.sty>
%    \end{macrocode}
%\fi
%\section{datagidx.sty}
%\changes{2.13}{2013-01-15}{added datagidx package}
% This package provides a means to produces indices and glossaries
% without the need for an external indexing application, such as
% \app{makeindex} or \app{xindy}. However, the code here has
% been developed to implement the word order style described by the
% Oxford Style Manual. If you are not writing in English, this may
% not be applicable to your needs. You may be able to define your
% own comparison handler to use with \cs{dtlsort}. If not, you'll
% need to use \app{xindy} with a package such as
% \sty{glossaries}.
%
% Declare package:
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
%    \end{macrocode}
% Rollback releases:
%    \begin{macrocode}
\DeclareRelease{v2.32}{2019-09-27}{datagidx-2019-09-27.sty}
\DeclareCurrentRelease{v3.4.1}{2025-04-25}
%    \end{macrocode}
% Declare package:
%    \begin{macrocode}
\ProvidesPackage{datagidx}[2025/04/25 v3.4.1 (NLCT)]
%    \end{macrocode}
%
%    \begin{macrocode}
\RequirePackage{etoolbox}
%    \end{macrocode}
%
%\section{Default Settings}
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
% These commands need to be defined before the package options are
% used.
%\begin{macro}{\datagidx@columns}
% The number of columns to use for the index/glossary.
% Version 3.0: replaced \cs{datagidx@columns} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datagidx\_columns\_int}}
%    \begin{macrocode}
\int_new:N \l__datagidx_columns_int
\int_set:Nn \l__datagidx_columns_int { 2 }
%    \end{macrocode}
%\end{macro}
%
%Define commands used in package options.
%
%\begin{macro}{\DTLgidxSetColumns}
%\changes{3.0}{2025-03-03}{mad robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLgidxSetColumns { m }
{
  \int_compare:nNnTF
    { #1 } < { \c_one_int }
  {
    \PackageError { datagidx }
    {
      \token_to_str:N \DTLgidxSetColumns \c_space_tl ~
      invalid ~ number ~ of ~ columns ~ #1
    }
    {
      The ~ number ~ of ~ columns ~ must ~ be ~
      greater ~ than ~ 0
    }
  }
  {
    \int_set:Nn \l__datagidx_columns_int { #1 }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxChildStyle}
% Should the child name be displayed? (Default: show name.)
% If the name shouldn't be displayed, replace with a number.
%    \begin{macrocode}
\newcommand*{\DTLgidxChildStyle}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@setchildstyle}
%Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \__datagidx_set_child_style_named:
{
  \cs_set_eq:NN \DTLgidxChildStyle \use:n
}
\cs_new:Nn \__datagidx_set_child_style_noname:
{
  \renewcommand*{\DTLgidxChildStyle}[1]{
    \DTLgidxChildCountLabel
  }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLgidxPostName}
% What to put after the name. (Defaults to space.)
%    \begin{macrocode}
\newcommand*{\DTLgidxPostName}{ ~ }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgidxPostChildName}
% What to put after the child name.
%    \begin{macrocode}
\newcommand*{\DTLgidxPostChildName}{\DTLgidxPostName}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxNameCase}
% Should the name have a case change in the index/glossary?
% (Default: no change.)
%    \begin{macrocode}
\newcommand*{\DTLgidxNameCase}[1]{#1}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\datagidx@setnamecase}
%Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\DTLgidxNameFont}
% The font to use for the name in the index/glossary.
% (Default: normal font.)
%    \begin{macrocode}
\newcommand*{\DTLgidxNameFont}[1]{\textnormal{#1}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxPostDescription}
% What to put after the description. (Defaults to nothing.)
%    \begin{macrocode}
\newcommand*{\DTLgidxPostDescription}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@setpostdesc}
%Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\DTLgidxPreLocation}
% What to put before the location list. (Defaults to en-space.)
%    \begin{macrocode}
\newcommand*{\DTLgidxPreLocation}{\enspace}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxPostLocation}
%\changes{3.0}{2025-03-03}{new}
% What to put after the location list. (Defaults to space.)
%    \begin{macrocode}
\newcommand*{\DTLgidxPostLocation}{ ~ }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@setprelocation}
%Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\DTLgidxLocation}
% How to display the location. (Defaults to show the location list.)
%    \begin{macrocode}
\newcommand*{\DTLgidxLocation}{\dtldolocationlist}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@setlocation}
%Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\DTLgidxSee}
% How to display the cross-reference list.
%    \begin{macrocode}
\newcommand*{\DTLgidxSee}{
  \datatool_if_null_or_empty:NF \See 
  {
    \DTLgidxPreLocation
    \DTLgidxFormatSee { \seename } { \See }
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgidxSeeAlso}
% How to display the ``see also'' list.
%    \begin{macrocode}
\newcommand*{\DTLgidxSeeAlso}{%
  \datatool_if_null_or_empty:NF \SeeAlso 
  {
    \DTLgidxFormatSeeAlso
     { \seealsoname } { \SeeAlso }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxChildrenSeeAlso}
% Display the children and the see also attributes.
%    \begin{macrocode}
\newcommand*{\DTLgidxChildrenSeeAlso}{%
  \DTLgidxChildren
  \DTLgidxSeeAlso
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@setsee}
%Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\DTLgidxSymDescSep}
% Separator character between symbol and description if both are
% present.
%    \begin{macrocode}
\newcommand*{\DTLgidxSymDescSep}{\space}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxFormatDesc}
% How to format the description.
%    \begin{macrocode}
\newcommand{\DTLgidxFormatDesc}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxSymbolDescription}
% How to format the symbol and description fields.
%    \begin{macrocode}
\newcommand*{\DTLgidxSymbolDescription}
{
  \DTLgidxSymbolDescLeft
  \DTLgidxSymbolDescRight
}
\newcommand*{\DTLgidxSymbolDescLeft}
{
  \tl_if_empty:NF \Symbol
    { ( \Symbol ) \DTLgidxSymDescSep }
}
\newcommand*{\DTLgidxSymbolDescRight}
{
  \tl_if_empty:NF \Description
  {
    \DTLgidxFormatDesc { \Description }
    \DTLgidxPostDescription
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\if@datagidxsymbolleft}
% Identifies whether the symbol has been set to left or right.
%Version 3.0: replaced \cs{if@datagidxsymbolleft} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datagidx\_symbol\_left\_bool}}
%    \begin{macrocode}
\bool_new:N \l__datagidx_symbol_left_bool
\bool_set_true:N \l__datagidx_symbol_left_bool
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@formatsymdesc}
%Version 3.0: removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
% Only symbol
%    \begin{macrocode}
\cs_new:Nn \__datagix_set_symbol_only:
{
  \tl_set:Nn \DTLgidxSymbolDescLeft
   {
     \tl_if_empty:NF \Symbol { \Symbol }
   }
  \tl_clear:N \DTLgidxSymbolDescRight
  \bool_set_true:N \l__datagidx_symbol_left_bool
}
%    \end{macrocode}
% Only description
%    \begin{macrocode}
\cs_new:Nn \__datagix_set_desc_only:
{
  \tl_set:Nn \DTLgidxSymbolDescLeft
   {
     \tl_if_empty:NF \Description
      {
        \DTLgidxFormatDesc { \Description }
        \DTLgidxPostDescription
      }
   }
  \tl_clear:N \DTLgidxSymbolDescRight
  \bool_set_false:N \l__datagidx_symbol_left_bool
}
%    \end{macrocode}
% (symbol) description
%    \begin{macrocode}
\cs_new:Nn \__datagix_set_symbol_paren_desc:
{
  \tl_set:Nn \DTLgidxSymbolDescLeft
   {
     \tl_if_empty:NF \Symbol
       { ( \Symbol ) \DTLgidxSymDescSep }
   }
  \tl_set:Nn \DTLgidxSymbolDescRight
   {
     \tl_if_empty:NF \Description
       {
         \DTLgidxFormatDesc { \Description }
         \DTLgidxPostDescription
       }
   }
  \bool_set_true:N \l__datagidx_symbol_left_bool
}
%    \end{macrocode}
% description (symbol)
%    \begin{macrocode}
\cs_new:Nn \__datagix_set_desc_symbol_paren:
{
  \tl_set:Nn \DTLgidxSymbolDescLeft
   {
     \tl_if_empty:NF \Description
      {
        \DTLgidxFormatDesc { \Description }
        \DTLgidxPostDescription
        \DTLgidxSymDescSep
      }
   }
  \tl_set:Nn \DTLgidxSymbolDescRight
   {
     \tl_if_empty:NF \Symbol
      { (\Symbol) }
   }
  \bool_set_false:N \l__datagidx_symbol_left_bool
}
%    \end{macrocode}
% symbol description
%    \begin{macrocode}
\cs_new:Nn \__datagix_set_symbol_desc:
{
  \tl_set:Nn \DTLgidxSymbolDescLeft
   {
     \tl_if_empty:NF \Symbol
       { \Symbol \DTLgidxSymDescSep }
   }
  \tl_set:Nn \DTLgidxSymbolDescRight
   {
     \tl_if_empty:NF \Description
      {
        \DTLgidxFormatDesc { \Description }
        \DTLgidxPostDescription
      }
   }
  \bool_set_true:N \l__datagidx_symbol_left_bool
}
%    \end{macrocode}
% description symbol
%    \begin{macrocode}
\cs_new:Nn \__datagix_set_desc_symbol:
{
  \tl_set:Nn \DTLgidxSymbolDescLeft
   {
     \tl_if_empty:NF \Description
      {
        \DTLgidxFormatDesc { \Description }
        \DTLgidxPostDescription
        \DTLgidxSymDescSep
      }
   }
  \tl_set:Nn \DTLgidxSymbolDescRight
   {
     \tl_if_empty:NF \Symbol { \Symbol }
   }
  \bool_set_false:N \l__datagidx_symbol_left_bool
}
%    \end{macrocode}
%
%\begin{macro}{\datagidx@compositor}
%Version 3.0: replaced \cs{datagidx@compositor} with:
%    \begin{macrocode}
\tl_new:N \l__datagidx_compositor_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxSetCompositor}
%\begin{definition}
%\cs{DTLgidxSetCompositor}\marg{symbol}
%\end{definition}
%Set the location compositor.
%    \begin{macrocode}
\NewDocumentCommand \DTLgidxSetCompositor { m }
{
  \tl_set:Nn \l__datagidx_compositor_tl { #1 }
}
%    \end{macrocode}
%\end{macro}
%Set the default compositor to \texttt{.} (full stop).
%    \begin{macrocode}
\DTLgidxSetCompositor{.}
%    \end{macrocode}
%
%\begin{macro}{\DTLgidxCounter}
% The counter used for the location lists.
%    \begin{macrocode}
\newcommand*{\DTLgidxCounter}{page}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new:Nn \__datagidx_set_counter:n
{
  \tl_if_empty:nTF { #1 }
   {
     \PackageError { datagidx }
     { Missing ~ counter ~ name }
     { The ~ argument ~ of ~ `counter' ~ must ~ be ~ a ~ counter ~ name }
   }
   {
    \tl_if_exist:cTF { the#1 }
     {
       \tl_set:Nn \DTLgidxCounter { #1 }
     }
     {
       \PackageError { datagidx }
       { Invalid ~ counter ~ name ~ `#1' }
       {
         The ~ argument ~ of ~ `counter' ~ must ~ be ~ a ~ 
         counter ~ name ~ (or ~ \token_to_str:N \the#1 \c_space_tl
         must ~ be ~ defined)
       }
     }
   }
}
%    \end{macrocode}
%
% Sorting can take a long time (especially with large databases) but
% two \LaTeX\ runs are usually required to get the index or glossary
% up-to-date, so we usually don't need to worry about sorting on the
% first run (unless the order in some way affects the document,
% e.g.\ the group headings are to appear in the table of contents).
% It may also be that some modifications are done to the document
% that don't require a re-sort. The optimize setting tries to
% minimize the amount of sorting done to help speed up document
% compilation.
%
% There are to optimization levels: low and high. The low level
% optimization just sorts every other \LaTeX\ run. This is done by
% writing to the aux file to determine whether or not the sort
% should be done next run. This is a cheap and easy hack that won't
% work if sorting makes the document out-of-date (for example, if
% the sorted index or glossary affects the table of contents by,
% say, making the group headings a sectional unit).
%
% The high level optimization is more complicated and involves
% writing the sorted database to an external file and reading it in
% on the next run. This requires checks to see if the location lists
% have changed, in which case a new sort may be required.
%
%\begin{important}
% The optimization function is only implemented when the sorting is
% specified via the sort key. Any explicit sorting done by the user
% via commands such as \ics{dltsort} are not effected by the
% optimization setting.
%\end{important}
%
%\begin{macro}{\datagidx@do@sort}
% Indicate what to do when it's time to sort the index/glossary.
% This defaults to un-optimised setting to avoid confusing users who
% don't like to read the manual.
%    \begin{macrocode}
\newcommand*{\datagidx@do@sort}{ \l__datagidx_sort_tl }
%    \end{macrocode}
%\end{macro}
%
% First deal with the low-level optimization as it's easier to
% implement.
%
%\begin{macro}{\datagidx@optimize@sort}
% The code to perform when the low optimize setting is on.
% If the command \ics{datagidx@do@optimize@sort} has been defined,
% do the sort. If it hasn't been defined, don't sort. If a sort
% isn't performed, the command definition is written to the aux
% file. If a sort is performed, the command definition isn't written
% to the aux file. This will do the sort every other run.
%    \begin{macrocode}
\newcommand*{\datagidx@optimize@sort}{%
%    \end{macrocode}
% First, has \cs{datagidx@do@optimize@sort} been defined?
%    \begin{macrocode}
  \ifdef\datagidx@do@optimize@sort
  {
%    \end{macrocode}
% It has been defined so go ahead and do the sort.
%    \begin{macrocode}
    \l__datagidx_sort_tl
  }
  {
%    \end{macrocode}
% It hasn't been defined so don't sort. Write the command definition
% into the aux file for the next run.
%    \begin{macrocode}
    \protected@write\@auxout{}{%
      \string\gdef\string\datagidx@do@optimize@sort{}%
    }
%    \end{macrocode}
% Let the user know they need to recompile the document.
%    \begin{macrocode}
    \cs_gset_eq:NN 
      \@datagidx@dorerun@warn@sort
      \@data@rerun@warn@sort
  }
}
%    \end{macrocode}
%\end{macro}
%
%List of labels to check for re-run.
%    \begin{macrocode}
\seq_new:N \g__datagidx_refd_labels_seq
%    \end{macrocode}
%
%\begin{macro}{\if@datagidx@warn}
% Provide a switch to allow warnings to be suppressed.
%Version 3.0: replaced \cs{if@datagidx@warn} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datagidx\_warn\_bool}}
%    \begin{macrocode}
\bool_new:N \l__datagidx_warn_bool
\bool_set_true:N \l__datagidx_warn_bool
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@datagidx@dorerun@warn}
%    \begin{macrocode}
\newcommand*\@datagidx@dorerun@warn{%
  \seq_map_inline:Nn \g__datagidx_refd_labels_seq
   {
     \iftermexists { ##1 }
     {
       \DTLaction
        [
          name = { \__datagidx_term_database:n { ##1 } },
          key = Label, value = { ##1 },
          return = 
           {
%    \end{macrocode}
% Locations from previous run that may be a page out:
%    \begin{macrocode}
             \UnsafeLocation = UnsafeLocation,
%    \end{macrocode}
% Locations from this run that may be a page out:
%    \begin{macrocode}
             \CurrentLocation = CurrentLocation
%    \end{macrocode}
% If the document hasn't changed, they will both be out by the same
% amount so they can still be compared. However, protected expansion
% may cause extra spaces.
%    \begin{macrocode}
           }
        ]
        { select ~ row }
       \tl_set:Nx \UnsafeLocation
         { \text_purify:n { \UnsafeLocation } }
       \tl_set:Nx \CurrentLocation
         { \text_purify:n { \CurrentLocation } }
       \tl_remove_all:Nn  \UnsafeLocation { ~ }
       \tl_remove_all:Nn  \CurrentLocation { ~ }
       \tl_if_eq:NNF \UnsafeLocation \CurrentLocation 
        {
          \@data@rerun@warn
          \seq_map_break:
        }
     }
     { }
   }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@datagidx@dorerun@warn@sort}
%    \begin{macrocode}
\newcommand*\@datagidx@dorerun@warn@sort{}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\AtEndDocument
 {
  \bool_if:NT \l__datagidx_warn_bool
   { 
     \@datagidx@dorerun@warn 
     \@datagidx@dorerun@warn@sort
   }
 }
%    \end{macrocode}
%\begin{macro}{\@datagidx@rerun@warn@sort}
% Warning issued when a rerun is required to sort the index or
% glossary.
%    \begin{macrocode}
\newcommand*\@data@rerun@warn@sort{
  \PackageWarningNoLine {datagidx}
   {
     Rerun ~ required ~ to ~ sort ~ the ~ 
     index/glossary ~ databases
   }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@datagidx@rerun@warn}
% Warning issued when a rerun is required to update the location
% lists.
%    \begin{macrocode}
\newcommand*\@data@rerun@warn{
  \PackageWarningNoLine {datagidx}
   {
     Rerun ~ required ~ to ~ ensure ~ the ~
     index/glossary ~ location ~ lists ~ are ~ up-to-date
   }
}
%    \end{macrocode}
%\end{macro}
%
%
%The high optimize setting is more complicated.
% This involves writing each database to an external file (named
% \cs{jobname}-\meta{db label}.gidx). The sort is only performed if
% new terms are added or used.
%
%\begin{macro}{\datagidx@do@highopt@optimize}
%    \begin{macrocode}
\newcommand*{\datagidx@do@highopt@optimize}{%
  \renewcommand*{\datagidx@do@sort}{%
%    \end{macrocode}
% Only sort if database has changed.
%    \begin{macrocode}
    \ifcsdef{datagidx@do@highopt@sort@\DTLgidxCurrentdb}%
    {%
      \csuse{datagidx@do@highopt@sort@\DTLgidxCurrentdb}%
    }%
    {%
%    \end{macrocode}
% Do nothing
%    \begin{macrocode}
     \dtl@message
      {
        Not ~ sorting ~ `\DTLgidxCurrentdb' : ~
        optimize=high ~ on ~ and ~ no ~ change ~ 
        detected
      }
    }%
%    \end{macrocode}
% Save the database to file.
%    \begin{macrocode}
    \group_begin:
%    \end{macrocode}
% Locally redefine \verb|\__datagidx_used:n| to expand to 0.
%Since this is encapsulated with \cs{dtlspecialvalue}, it will
%expand regardless of the "expand" setting. Similarly the location
%needs to expand to nothing.
%    \begin{macrocode}
      \cs_set:Nn \__datagidx_used:n { 0 }
      \cs_set_eq:NN \__datagidx_location:n \use_none:n
      \cs_set_eq:NN 
       \__datagidx_letter_group:n
       \__datagidx_write_letter_group:n
%    \end{macrocode}
% Write in latest dbtex format.
%    \begin{macrocode}
      \DTLwrite
       [
         overwrite=allow,
         format = dbtex, expand=none, 
         name = \DTLgidxCurrentdb
       ]
       { \datagidxhighoptfilename \DTLgidxCurrentdb }
    \group_end:
  }%
%    \end{macrocode}
% Change the behaviour of \cs{newgidx}
%    \begin{macrocode}
  \def\newgidx{\datagidx@highopt@newgidx}%
%    \end{macrocode}
% Change the behaviour of \cs{newterm}
%    \begin{macrocode}
  \def\newterm{\datagidx@highopt@newterm}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@datagidx@db@col@id@w}
%\changes{3.0}{2025-03-03}{removed}
%Version 3.0: removed.
%\end{macro}
%
% With the `highopt optimize' setting, whenever a location is written to the aux 
% file, if no location has been defined the database needs sorting.
%
%\begin{macro}{\datagidx@do@highopt@update}
% Default does nothing. (Argument is the entry's label.)
%    \begin{macrocode}
\newcommand*{\datagidx@do@highopt@update}[1]{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxhighoptfilename}
% Expands to the name of the filename associated with the database
% identified by the argument for the `highopt' setting.
%    \begin{macrocode}
\newcommand*{\datagidxhighoptfilename}[1]{\jobname-#1.gidx}
%    \end{macrocode}
%\end{macro}
%
%\section{Package Options}
%
% These options govern the general layout of the glossary/index and
% may be passed as package options.
%    \begin{macrocode}
\keys_define:nn { datatool }
{
  columns .code:n = { \DTLgidxSetColumns { #1 } },
  child .choice: ,
  child / named .code:n = { \__datagidx_set_child_style_named: } ,
  child / noname .code:n = { \__datagidx_set_child_style_noname: } ,
  namecase .choice: ,
  namecase / nochange .code:n =
    { \let \DTLgidxNameCase \use:n } ,
  namecase / uc .code:n =
    { \renewcommand \DTLgidxNameCase [ 1 ] { \text_uppercase:n { ##1 } } } ,
  namecase / lc .code:n =
    { \renewcommand \DTLgidxNameCase [ 1 ] { \text_lowercase:n { ##1 } } } ,
  namecase / firstuc .code:n =
    { \renewcommand \DTLgidxNameCase [ 1 ] { \xmakefirstuc { ##1 } } } ,
  namecase / capitalise .code:n =
    { \renewcommand \DTLgidxNameCase [ 1 ] { \xcapitalisewords { ##1 } } } ,
  namefont .code:n =
    { \renewcommand \DTLgidxNameFont [ 1 ] { { #1 { ##1 } } } } ,
  postname .code:n =
    { \renewcommand \DTLgidxPostName { #1 } } ,
  postdesc .choice: ,
  postdesc / none .code:n =
   { \tl_clear:N \DTLgidxPostDescription } ,
  postdesc / dot .code:n =
   { \tl_set:Nn \DTLgidxPostDescription { . } } ,
  prelocation .choice: ,
  prelocation / none .code:n = 
   { \tl_clear:N \DTLgidxPreLocation } ,
  prelocation / enspace .code:n = 
   { \tl_set:Nn \DTLgidxPreLocation { \enspace } } ,
  prelocation / space .code:n = 
   { \tl_set:Nn \DTLgidxPreLocation { ~ } } ,
  prelocation / dotfill .code:n = 
   { \tl_set:Nn \DTLgidxPreLocation { \dotfill } } ,
  prelocation / hfill .code:n = 
   { \tl_set:Nn \DTLgidxPreLocation { \hfill } } ,
  location .choice: ,
  location / hide .code:n =
    { \tl_clear:N \DTLgidxLocation },
  location / list .code:n =
    { \tl_set:Nn \DTLgidxLocation { \dtldolocationlist } },
  location / first .code:n =
    { \tl_set:Nn \DTLgidxLocation { \dtldofirstlocation } },
  see .choice: ,
  see / comma .code:n =
    {
      \renewcommand*{\DTLgidxSee}
       {
         \datatool_if_null_or_empty:NF \See 
          {
           , ~ \DTLgidxFormatSee { \seename } { \See }
          }
       }
    } ,
  see / brackets .code:n =
    {
      \renewcommand*{\DTLgidxSee}
       {
         \datatool_if_null_or_empty:NF \See 
          {
            \space ( \DTLgidxFormatSee { \seename } { \See } )
          }
       }
    } ,
  see / dot .code:n =
    {
      \renewcommand*{\DTLgidxSee}
       {
         \datatool_if_null_or_empty:NF \See 
          {
            .  ~
            \DTLgidxFormatSee
              { \xmakefirstuc { \seename } } { \See }
          }
       }
    } ,
  see / space .code:n =
    {
      \renewcommand*{\DTLgidxSee}
       {
         \datatool_if_null_or_empty:NF \See
          {
            \space  
            \DTLgidxFormatSee { \seename } { \See }
          }
       }
    } ,
  see / nosep .code:n =
    {
      \renewcommand*{\DTLgidxSee}
       {
         \datatool_if_null_or_empty:NF \See
          {
            \DTLgidxFormatSee { \seename } { \See }
          }
       }
    } ,
  see / semicolon .code:n =
    {
      \renewcommand*{\DTLgidxSee}
       {
         \datatool_if_null_or_empty:NF \See
          {
            ; ~ \DTLgidxFormatSee { \seename } { \See }
          }
       }
    } ,
  see / location .code:n =
    {
      \renewcommand*{\DTLgidxSee}
       {
         \datatool_if_null_or_empty:NF \See
          {
            \DTLgidxPreLocation 
            \DTLgidxFormatSee { \seename } { \See }
          }
       }
    } ,
  symboldesc .choice: ,
  symboldesc / symbol .code:n =
   {
     \__datagix_set_symbol_only:
   } ,
  symboldesc / desc .code:n =
   {
     \__datagix_set_desc_only:
   } ,
  symboldesc / (symbol) ~ desc .code:n =
   {
     \__datagix_set_symbol_paren_desc:
   } ,
  symboldesc / desc ~ (symbol) .code:n =
   {
     \__datagix_set_desc_symbol_paren:
   } ,
  symboldesc / symbol ~ desc .code:n =
   {
     \__datagix_set_symbol_desc:
   } ,
  symboldesc / desc ~ symbol .code:n =
   {
     \__datagix_set_desc_symbol:
   } ,
  counter .code:n =
   { 
     \__datagidx_set_counter:n { #1 }
   },
  counter .value_required:n = true ,
  compositor .tl_set:N = \l__datagidx_compositor_tl ,
  compositor .value_required:n = true ,
  final .code:n =
    { \cs_set_eq:NN \datagidxshowifdraft \use_none:n } ,
  final .value_forbidden:n = true ,
  draft .code:n =
    { \cs_set_eq:NN \datagidxshowifdraft \use:n } ,
  draft .value_forbidden:n = true ,
%    \end{macrocode}
%\begin{option}{optimize}
% A boolean option indicating whether or not to optimize the sort.
% This is only available as a global option. If you want to optimize
% some glossaries but not others, switch on the optimize function
% and clear the sort key for the relevant glossaries and manually
% sort using \ics{dtlsort} before the glossary is displayed.
%\end{option}
%    \begin{macrocode}
  optimize .choice: ,
  optimize / off .code:n = 
   {
    \renewcommand*{\datagidx@do@sort}{ \l__datagidx_sort_tl }
   } ,
  optimize / low .code:n = 
   {
    \renewcommand*{\datagidx@do@sort}{\datagidx@optimize@sort}
   } ,
  optimize / high .code:n = 
   {
     \datagidx@do@highopt@optimize
   } ,
  optimize .default:n = { high } ,
  nowarn .bool_set_inverse:N = \l__datagidx_warn_bool ,
}
%    \end{macrocode}
%
% Set final as default:
%    \begin{macrocode}
\newcommand{\datagidxshowifdraft}[1]{}
%    \end{macrocode}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
% Process package options and load \sty{datatool} if not already
% loaded. This allows \sty{datatool} package options to be supplied
% with \sty{datagix}.
%    \begin{macrocode}
\IfPackageLoadedTF{datatool}
{
  \ProcessKeyOptions[datatool]
}
{
  \DeclareOption*{\PassOptionsToPackage{\CurrentOption}{datatool}}
  \ProcessOptions
}
\RequirePackage{datatool}
%    \end{macrocode}
% Remove the package option keys so they can't be used with
% \cs{DTLsetup} directly.
%    \begin{macrocode}
\ExplSyntaxOn
\keys_define:nn { datatool }
{
  columns .undefine: ,
  child .undefine: ,
  namecase .undefine: ,
  postname .undefine: ,
  postdesc .undefine: ,
  prelocation .undefine: ,
  location .undefine: ,
  see .undefine: ,
  symboldesc .undefine: ,
  counter .undefine: ,
  compositor .undefine: ,
  final .undefine: ,
  draft .undefine: ,
  optimize .undefine: ,
  nowarn .undefine:
}
\ExplSyntaxOff
%
%    \end{macrocode}
%
%\section{Initialisation}
%
% Required packages.
% Version 3.0: removed \sty{xkeyval}
%    \begin{macrocode}
\RequirePackage{mfirstuc}[2022/10/14]
\RequirePackage{multicol}
%    \end{macrocode}
%\changes{2.15}{2013-07-10}{added afterpage as a required package}
%\changes{3.0}{2025-03-03}{removed afterpage as a required package}
%\changes{3.0}{2025-03-03}{removed textcase as a required package}
%\changes{3.0}{2025-03-03}{removed xfor as a required package}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%Scratch variables:
%\begin{macro}{\datagidx@child}
%Version 3.0: replaced \cs{datagidx@child} with:
%    \begin{macrocode}
\tl_new:N \l__datagidx_child_label_tl
\clist_new:N \l__datagidx_child_clist
\seq_new:N \l__datagidx_child_seq
%    \end{macrocode}
%\end{macro}
%These labels are token list variables not strings as they may be
%set to another command that the user may want to redefine.
%\begin{macro}{\datagidx@parentdatabase}
%Version 3.0: replaced \cs{datagidx@parentdatabase} with:
%    \begin{macrocode}
\tl_new:N \l__datagidx_parent_database_tl
%    \end{macrocode}
%\end{macro}
%Temporary database, column and term labels.
%    \begin{macrocode}
\tl_new:N \l__datagidx_database_tl
\tl_new:N \l__datagidx_label_tl
%    \end{macrocode}
%\begin{macro}{\datagidx@id}
%Version 3.0: replaced \cs{datagidx@id} with:
%    \begin{macrocode}
\tl_new:N \l__datagidx_id_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@datagidx@target}
%Version 3.0: replaced \cs{@datagidx@target} with:
%    \begin{macrocode}
\tl_new:N \l__datagidx_target_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\datagidx@parent}
%Version 3.0: replaced \cs{datagidx@parent} with:
%    \begin{macrocode}
\tl_new:N \l__datagidx_parent_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\datagidx@list}
%Version 3.0: replaced \cs{datagidx@list} with:
%    \begin{macrocode}
\clist_new:N \l__datagidx_database_clist
%    \end{macrocode}
%\end{macro}
%Temporary value:
%    \begin{macrocode}
\tl_new:N \l__datagidx_value_tl
%    \end{macrocode}
%\begin{macro}{\datagidx@title}
%Database title. Replaced \cs{datagidx@title} with:
%    \begin{macrocode}
\tl_new:N \l__datagidx_title_tl
%    \end{macrocode}
%\end{macro}
%Location variables
%\begin{macro}{\datagidx@loc}
%Version 3.0: replaced \cs{datagidx@loc} with:
%    \begin{macrocode}
\tl_new:N \l__datagidx_location_tl
\clist_new:N \l__datagidx_location_clist
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@sep}
%Version 3.0: replaced \cs{datagidx@sep} with:
%    \begin{macrocode}
\tl_new:N \l__datagidx_sep_tl
%    \end{macrocode}
%\end{macro}
%
% Database to keep track of all the defined terms.
%    \begin{macrocode}
\DTLnewdb{datagidx}
%    \end{macrocode}
%
%\begin{counter}{DTLgidxChildCount}
% Child counter.
%    \begin{macrocode}
\newcounter{DTLgidxChildCount}
%    \end{macrocode}
%\end{counter}
%\begin{macro}{\theHDTLgidxChildCount}
% Reduce duplicate identifier warnings if \sty{hyperref} in use.
% \cs{Label} is a placeholder locally set while iterating. 
%    \begin{macrocode}
\def\theHDTLgidxChildCount{\Label.\arabic{DTLgidxChildCount}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxChildCountLabel}
% Label for child counter.
%    \begin{macrocode}
\newcommand*{\DTLgidxChildCountLabel}{\theDTLgidxChildCount) ~ }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@foreachchild}
% Iterate through each child label in \cs{Children}, which should be
% a comma-separated list of labels. The current database should be
% in \cs{DTLgidxCurrentdb}.
%    \begin{macrocode}
\newcommand{\datagidx@foreachchild}[1]{%
 \bool_if:NTF \l__datagidx_childsort_bool
  { 
    \seq_clear:N \l__datagidx_child_seq
    \clist_map_inline:Nn \Children 
     {
%    \end{macrocode}
% Get the row index only
%    \begin{macrocode}
       \__datatool_get_row_index:Nnxn 
        \l__datatool_row_idx_tl
        { \DTLgidxCurrentdb }
        { \dtlcolumnindex { \DTLgidxCurrentdb } { Label } }
        { ##1 }
       \datatool_if_null:NF \l__datatool_row_idx_tl
        {
          \seq_put_right:Nx \l__datagidx_child_seq
            { { \l__datatool_row_idx_tl } { ##1 } }
        }
     }
%    \end{macrocode}
% Sort by row index:
%    \begin{macrocode}
    \seq_sort:Nn \l__datagidx_child_seq
     {
       \int_compare:nNnTF
         { \use_i:nn ##1 } > { \use_i:nn ##2 }
        { \sort_return_swapped: }
        { \sort_return_same: }
     }
    \seq_map_inline:Nn \l__datagidx_child_seq
     {
       \tl_set:Nx \Label { \use_ii:nn ##1 }
       #1
     }
  }
  {
    \clist_map_variable:NNn \Children \Label
     { #1 }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@sortchildren}
% The list of child labels needs to be sorted so that the child list
% follows the same ordering as the database.
% Version 3.0: removed \cs{datagidx@sortchildren}.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\datagidx@sort@foreachchild}
% Sorted iteration through all the child labels.
% Version 3.0: deprecated. Hierarchical sorting now implemented
% with HierSort column.
% Version 3.0: removed \cs{datagidx@sort@foreachchild}.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\datagidx@unsort@foreachchild}
% Unsorted iteration through all the child labels.
% Version 3.0: removed \cs{datagidx@unsort@foreachchild}.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\datagidx@setchildsort}
%Version 3.0: removed \cs{datagidx@setchildsort}.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\datagidxsymbolwidth}
% Space to allocate for the symbol. If zero or negative, symbol just
% occupies its natural space.
%    \begin{macrocode}
\newlength\datagidxsymbolwidth
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxlocationwidth}
% Space to allocate for the location list. If zero or negative, the
% list just occupies its natural space.
%    \begin{macrocode}
\newlength\datagidxlocationwidth
%    \end{macrocode}
%\end{macro}
%
%\section{Glossary/Index Formatting}
%\begin{macro}{\seename}
%    \begin{macrocode}
\providecommand*{\seename}{see}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\seealsoname}
%\changes{3.0}{2025-03-03}{added check for \cs{alsoname}}
%    \begin{macrocode}
\tl_if_exist:NF \seealsoname
 {
  \tl_if_exist:NTF \alsoname
  {
    \tl_set:Nn \seealsoname { \alsoname }
  }
  {
    \tl_set:Nn \seealsoname { see ~ also }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxSeeTagFont}
%    \begin{macrocode}
\newcommand*{\DTLgidxSeeTagFont}[1]{\emph{#1}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxFormatSee}
%\begin{definition}
%\cs{DTLgidxFormatSee}\marg{tag}\marg{label list}
%\end{definition}
%    \begin{macrocode}
\newcommand*{\DTLgidxFormatSee}[2]{%
  \DTLgidxSeeTagFont{ #1 } ~ \DTLgidxSeeList{ #2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxFormatSeeAlso}
%\begin{definition}
%\cs{DTLgidxFormatSeeAlso}\marg{tag}\marg{label list}
%\end{definition}
%\changes{3.0}{2025-03-03}{changed to new document command}
%    \begin{macrocode}
\NewDocumentCommand \DTLgidxFormatSeeAlso { m m }
{
  \datagidxdoseealso
  {
    \DTLgidxSeeTagFont{ #1 } ~ \DTLgidxSeeList { #2 }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxdoseealso}
%\changes{3.0}{2025-03-03}{changed to new document command}
%    \begin{macrocode}
\NewDocumentCommand \datagidxdoseealso { m } 
{
  \datagidxseealsostart
   #1
  \datagidxseealsoend
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxSeeList}
%\begin{definition}
%\cs{DTLgidxSeeList}\marg{label list}
%\end{definition}
%\changes{3.0}{2025-03-03}{changed to new document command}
%\changes{3.0}{2025-03-03}{added group}
%    \begin{macrocode}
\NewDocumentCommand \DTLgidxSeeList { m }
{
 \group_begin:
  \tl_clear:N \l__datagidx_sep_tl
  \exp_args:NNo \seq_set_from_clist:Nn
    \l__datatool_tmp_seq { #1 }
  \int_set:Nn \l__datatool_count_int
    { \seq_count:N \l__datatool_tmp_seq }
  \seq_map_indexed_inline:Nn \l__datatool_tmp_seq
  {
    \tl_set:Nn \l_datatool_label_tl { ##2 }
    \int_compare:nNnTF
       { ##1 } = { \l__datatool_count_int }
     {
%    \end{macrocode}
% Last iteration.
%    \begin{macrocode}
      \tl_if_empty:NF \l__datagidx_sep_tl
       {
%    \end{macrocode}
% Not the only element in the list so separator needed.
%    \begin{macrocode}
         \DTLidxSeeLastSep
       }
     }
     {
%    \end{macrocode}
% Not last iteration
%    \begin{macrocode}
       \l__datagidx_sep_tl
       \tl_set_eq:NN \l__datagidx_sep_tl \DTLidxSeeSep
     }
    \DTLidxFormatSeeItem { \l_datatool_label_tl }
  }
 \group_end:
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLidxFormatSeeItem}
%\begin{definition}
%\cs{DTLidxFormatSeeItem}\marg{label}
%\end{definition}
%\changes{3.0}{2025-03-03}{changed to new document command}
%\changes{3.0}{2025-03-03}{added group}
%    \begin{macrocode}
\NewDocumentCommand \DTLidxFormatSeeItem { m }
{
 \group_begin:
   \DTLgidxFetchEntry 
     \l__datagidx_value_tl { #1 } { Name }
   \datatool_if_null:NTF \l__datagidx_value_tl
   {
     \bool_if:NT \l__datagidx_warn_bool
      {
        \PackageWarning { datagidx }
         { Can't ~ find ~ cross-reference ~ `#1' }
      }
   }
   {
     \datagidxlink { #1 }
       { \l__datagidx_value_tl }
   }
 \group_end:
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLidxSeeSep}
% Separator in cross-reference list.
%    \begin{macrocode}
\newcommand*{\DTLidxSeeSep}{ , ~ }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLidxSeeLastSep}
% Final separator in cross-reference list.
%    \begin{macrocode}
\newcommand*{\DTLidxSeeLastSep}{ ~ \& ~ }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxDoSeeOrLocation}
% You shouldn't have both a ``see'' list and a location list. This
% checks if \cs{See} is null. If it isn't null, it does the ``see''
% part, otherwise it deals with the location list.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\newrobustcmd* \DTLgidxDoSeeOrLocation
{
  \datatool_if_null_or_empty:NTF \See
  {
%    \end{macrocode}
% \cs{See} is null. Do we have a location?
%    \begin{macrocode}
     \datatool_if_null_or_empty:NF \Location 
      {
        \tl_if_empty:NF \DTLgidxLocation
         {
           \DTLgidxPreLocation
           \DTLgidxLocation
           \DTLgidxPostLocation
         }
      }
  }
  {
%    \end{macrocode}
% \cs{See} is not null, so do the cross-reference.
%    \begin{macrocode}
     \DTLgidxSee
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{environment}{dtlgidxchildlist}
%Provided to allow for any font changes etc that might be required.
%\changes{3.0}{2025-03-03}{}
%    \begin{macrocode}
\newenvironment{dtlgidxchildlist}{}{}
%    \end{macrocode}
%\end{environment}
%
%\begin{macro}{\DTLgidxChildren}
% How to display the children
%    \begin{macrocode}
\newcommand*{\DTLgidxChildren}{
  \begin { dtlgidxchildlist }
    \datatool_if_null_or_empty:NF \Children
    {
      \int_incr:N \datagidx@level
      \datagidxchildstart
      \let\Parent\Label
      \datagidx@foreachchild
      {
        \DTLaction
        [
          name = \DTLgidxCurrentdb ,
          key = Label,
          expand-value = \Label ,
          return =
           {
             \Location = Location ,
             \See = See ,
             \SeeAlso = SeeAlso 
           }
        ]
        { select ~ row }
        \bool_lazy_all:nF
            {
             { \datatool_if_null_or_empty_p:N \Location }
             { \datatool_if_null_or_empty_p:N \See }
             { \datatool_if_null_or_empty_p:N \SeeAlso }
            }
          {
            \datagidx@displaychild
          }
      }
      \datagidxchildend
    }
  \end { dtlgidxchildlist }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxgetchildfields}
% Get the child fields from the current row.
%    \begin{macrocode}
\newcommand*{\datagidxgetchildfields}{%
  \dtlgetentryfromcurrentrow
    {\Name}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Name}}%
  \dtlgetentryfromcurrentrow
    {\Description}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Description}}%
  \dtlgetentryfromcurrentrow
    {\Symbol}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Symbol}}%
  \dtlgetentryfromcurrentrow
    {\Long}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Long}}%
  \dtlgetentryfromcurrentrow
    {\Short}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Short}}%
  \dtlgetentryfromcurrentrow
    {\Text}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Text}}%
  \dtlgetentryfromcurrentrow
    {\Plural}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Plural}}%
  \dtlgetentryfromcurrentrow
    {\Used}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Used}}%
  \dtlgetentryfromcurrentrow
    {\Children}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Child}}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@displaychild}
%    \begin{macrocode}
\newcommand*{\datagidx@displaychild}{%
  \datagidxgetchildfields
  \datagidxchilditem
}
%    \end{macrocode}
%\end{macro}
%
%
%\begin{macro}{\datagidx@heading}
% Indicates how to format the heading in the glossary/index.
%Version 3.0: replaced \cs{datagidx@heading} with: 
%    \begin{macrocode}
\tl_new:N \l__datagidx_heading_tl
\cs_if_exist:NTF \chapter
{
  \tl_set:Nn \l__datagidx_heading_tl { \chapter* }
}
{
  \tl_set:Nn \l__datagidx_heading_tl { \section* }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgidxNoHeading}
% Allow user to suppress the heading. (So to suppress the
% heading do \texttt{heading=\cs{DTLgidxNoHeading}}).
%    \begin{macrocode}
\newcommand{\DTLgidxNoHeading}[1]{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@postheading}
% Indicates what to do immediately after the heading.
%Version 3.0: replaced \cs{datagidx@postheading} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datagidx\_post\_heading\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_post_heading_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@multicols}
% Should we use \env{multicols} or \env{multicols*}?
%Version 3.0: replaced \cs{datagidx@multicols} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datagidx\_multicols\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_multicols_tl
\tl_set:Nn \l__datagidx_multicols_tl { multicols }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@sort}
% Indicates how to sort the glossary/index.
% Defaults to word order. The expansion of this token list variable is
% added to the "datagidx" database (in the Sort column), so it needs
% to be expanded (and is therefore a token list variable not a function).
% Version 3.0: replaced \cs{dtlsort} with \cs{DTLsortdata}
% replaced \cs{datagidx@sort} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datagidx\_sort\_tl}}
%\changes{3.0}{2025-03-03}{replaced \cs{dtlsort} with \cs{DTLsortdata}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_sort_tl
\tl_set:Nn \l__datagidx_sort_tl
 {
    \DTLsortdata
     [ save-group-key = LetterGroup ]
     { \DTLgidxCurrentdb }
     {HierSort={replacements=Sort},FirstId}
 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@idxitem}
% Some classes, such as \cls{beamer}, don't define \cs{@idxitem} so
% if it's not already defined, define it here.
%    \begin{macrocode}
\providecommand{\@idxitem}{\par\hangindent 40\p@}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\datagidxstart}
% Indicates what to do at the start of the glossary/index.
%    \begin{macrocode}
\newcommand* \datagidxstart 
{
  \group_begin:
  \dim_zero:N \parindent
  \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
  \let\item\@idxitem
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\datagidxend}
% Indicates what to do at the end of the glossary/index.
%    \begin{macrocode}
\newcommand*{\datagidxend} { \datagidx_end: }
\cs_new:Nn \datagidx_end: { \expandafter \group_end: \if@endpe\@doendpe\fi }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxtarget}
% Provide a means to add a hypertarget if \ics{hypertarget} has been
% defined.
%    \begin{macrocode}
\newcommand*{\datagidxtarget}{ \__datagidx_target:nn }
%    \end{macrocode}
%\end{macro}
%Version 3.0: replaced \cs{@datagidxtarget} with:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_target:nn
{
  \cs_if_exist:NT \hypertarget
  {
    \group_begin:
      \datatool_measure_height:Nn
        \l__datatool_tmpa_dim { #2 }
      \raisebox
        { \l__datatool_tmpa_dim }
        { \hypertarget { #1 } { } }
    \group_end:
  }
  #2
}
%    \end{macrocode}
%
%\begin{macro}{\datagidxlink}
% Provide a means to add a link if \ics{hyperlink} has been
% defined.
%    \begin{macrocode}
\newcommand*{\datagidxlink}{ \__datagidx_link:nn }
%    \end{macrocode}
%\end{macro}
%Version 3.0: replaced \cs{@datagidxlink} with:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_link:nn
{
  \cs_if_exist:NTF \hyperlink
   { \hyperlink { #1 } { #2 } }
   { #2 }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLgidxEnableHyper}
% Enable hyperlinks (if they are defined).
%    \begin{macrocode}
\NewDocumentCommand \DTLgidxEnableHyper { }
{
  \cs_set_eq:NN \datagidxtarget \__datagidx_target:nn
  \cs_set_eq:NN \datagidxlink \__datagidx_link:nn
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxDisableHyper}
% Disable hyperlinks (if they are defined).
%    \begin{macrocode}
\NewDocumentCommand \DTLgidxDisableHyper { }
{
  \cs_set_eq:NN \datagidxtarget \use_ii:nn
  \cs_set_eq:NN \datagidxlink \use_ii:nn
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxgroupsep}
% Indicates what to do between groups (after the previous group and
% before the header of the next group).
%    \begin{macrocode}
\newcommand*{\datagidxgroupsep}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxgroupheader}
% Indicates what to do at the start of a group. (The current group
% label can be accessed via \cs{datagidxcurrentgroup} and the previous
% group label can be accessed via \cs{datagidxprevgroup}.)
%    \begin{macrocode}
\newcommand*{\datagidxgroupheader}{}
%    \end{macrocode}
%\end{macro}
%
%
%\begin{macro}{\datagidxitem}
% Indicates what to do at the start of each item of the glossary/index.
%    \begin{macrocode}
\newcommand*{\datagidxitem}{}%
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxchildstart}
% Indicates what to do at the start of the child glossary/index.
%    \begin{macrocode}
\newcommand*{\datagidxchildstart}{}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\datagidxchildend}
% Indicates what to do at the end of the child glossary/index.
%    \begin{macrocode}
\newcommand*{\datagidxchildend}{}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\datagidxchilditem}
% Indicates what to do at the start of each item of the child glossary/index.
%    \begin{macrocode}
\newcommand*{\datagidxchilditem}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxseealsostart}
% Indicates what to do at the start of the ``see also'' list.
%    \begin{macrocode}
\newcommand*{\datagidxseealsostart}{}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\datagidxseealsoend}
% Indicates what to do at the end of the ``see also'' list.
%    \begin{macrocode}
\newcommand*{\datagidxseealsoend}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxEndItem}
%    \begin{macrocode}
\newcommand{\DTLgidxEndItem}{\par\smallskip}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@doifsymlocwidth}
%\begin{definition}
%\cs{datagidx@doifsymlocwidth}\marg{indent}\marg{Name code}\marg{Location code}
%\end{definition}
% What to do if both the symbol width and the location width have
% been set.
%Version 3.0: replaced \cs{datagidx@doifsymlocwidth} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{\_\_datagidx\_do\_ifsymlocwidth:nnn}}
%    \begin{macrocode}
\cs_new:Nn \__datagidx_do_ifsymlocwidth:nnn
{
%    \end{macrocode}
% Calculate remaining space left for the description.
% If $L$ is the linewidth, $i$ is the \meta{indent},
% $w_N$ is the width of \meta{Name code}, 
% $w_S$ is the symbol width (\cs{datagidxsymbolwidth}),
% $w_L$ is the location width (\cs{datagidxlocationwidth}),
% $w_P$ is the width of \cs{DTLgidxPreLocation}, and
% $w_D$ is the width of \cs{DTLgidxSymDescSep} then the
% available width is
% $ L - i - w_N - w_S - w_L - w_P$.
%    \begin{macrocode}
  \datatool_measure_width:Nn
    \l__datatool_tmpa_dim
     { #2 \DTLgidxPreLocation \DTLgidxSymDescSep } 
  \dim_set:Nn \l__datatool_tmpa_dim
    {
      \linewidth - #1
      - \l__datatool_tmpa_dim
      - \datagidxsymbolwidth
      - \datagidxlocationwidth
    }
  \bool_if:NTF \l__datagidx_symbol_left_bool
  {
    \begin{minipage} [t]
       { \datagidxsymbolwidth }
      \datagidxsymalign
      \tl_clear:N \DTLgidxSymDescSep
      \DTLgidxSymbolDescLeft
    \end{minipage}
    \DTLgidxSymDescSep
    \begin{minipage} [t] 
       { \l__datatool_tmpa_dim }
      \tl_clear:N \DTLgidxSymDescSep
      \DTLgidxSymbolDescRight
    \end{minipage}
  }
  {
    \begin{minipage} [t]
       { \l__datatool_tmpa_dim }
      \tl_clear:N \DTLgidxSymDescSep
      \DTLgidxSymbolDescRight
    \end{minipage}
    \DTLgidxSymDescSep
    \begin{minipage} [t]
       { \datagidxsymbolwidth }
      \datagidxsymalign
      \tl_clear:N \DTLgidxSymDescSep
      \DTLgidxSymbolDescLeft
    \end{minipage}
  }
  \DTLgidxPreLocation
  \begin{minipage} [t]
     { \datagidxlocationwidth }
    \datagidxlocalign
    \tl_clear:N \DTLgidxPreLocation
    #3
  \end{minipage}
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@doiflocwidth}
%\begin{definition}
%\cs{datagidx@doiflocwidth}\marg{indent}\marg{Name code}\marg{Location code}
%\end{definition}
% What to do if only the location width has
% been set.
%Version 3.0: replaced \cs{datagidx@doiflocwidth} with:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_do_iflocwidth:nnn
{
%    \end{macrocode}
% Calculate remaining space left for the symbol and description.
%    \begin{macrocode}
  \datatool_measure_width:Nn
    \l__datatool_tmpa_dim
     { #2 \DTLgidxPreLocation } 
  \dim_set:Nn \l__datatool_tmpa_dim
    {
      \linewidth - #1
      - \l__datatool_tmpa_dim
      - \datagidxlocationwidth
    }
  \begin{minipage} [t]
      { \l__datatool_tmpa_dim }
    \DTLgidxSymbolDescription
  \end{minipage}
  \DTLgidxPreLocation
  \begin{minipage} [t]
      { \datagidxlocationwidth }
    \datagidxlocalign
    \tl_clear:N \DTLgidxPreLocation
    #3
  \end{minipage}
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@doifsymwidth}
% TODO: what uses this?
%\begin{definition}
%\cs{datagidx@doifsymwidth}\marg{indent}\marg{Name code}\marg{Location code}
%\end{definition}
% What to do if only the location width has
% been set.
% Version 3.0: replaced \cs{datagidx@doifsymwidth} with:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_do_ifsymwidth:nnn
{
%    \end{macrocode}
% Calculate remaining space left for the description and location.
%    \begin{macrocode}
  \datatool_measure_width:Nn
    \l__datatool_tmpa_dim
     { #2 \DTLgidxSymDescSep } 
  \dim_set:Nn \l__datatool_tmpa_dim
    {
      \linewidth -#1
      - \l__datatool_tmpa_dim
      - \datagidxsymbolwidth
    }
  \bool_if:NTF \l__datagidx_symbol_left_bool
  {
    \begin{minipage} [t]
       { \datagidxsymbolwidth }
      \datagidxsymalign
      \tl_clear:N \DTLgidxSymDescSep
      \DTLgidxSymbolDescLeft
    \end{minipage}
    \DTLgidxSymDescSep
    \begin{minipage} [t]
     { \l__datatool_tmpa_dim }
      \tl_clear:N \DTLgidxSymDescSep
      \DTLgidxSymbolDescRight
      #3
    \end{minipage}
  }
  {
    \begin{minipage} [t]
       { \l__datatool_tmpa_dim }
      \tl_clear:N \DTLgidxSymDescSep
      \DTLgidxSymbolDescRight
    \end{minipage}
    \DTLgidxSymDescSep
    \begin{minipage} [t]
        { \datagidxsymbolwidth }
      \datagidxsymalign
      \tl_clear:N \DTLgidxSymDescSep
      \DTLgidxSymbolDescLeft
%    \end{macrocode}
% This arrangement may look a bit weird.
%    \begin{macrocode}
      #3
    \end{minipage}
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxlocalign}
% Alignment of the location when the location width has been set.
%    \begin{macrocode}
\newcommand*{\datagidxlocalign}{\raggedleft}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxsymalign}
% Alignment of the symbol when the symbol width has been set.
%    \begin{macrocode}
\newcommand*{\datagidxsymalign}{\centering}
%    \end{macrocode}
%\end{macro}
%
%
%\subsection{Predefined styles}
%
%\begin{macro}{\datagidxsetstyle}
% Sets the current index/glossary style
%    \begin{macrocode}
\NewDocumentCommand \datagidxsetstyle { m }
{
  \cs_if_exist_use:cF {datagidx@style@#1}
  {
    \PackageError {datagidx} {Unknown ~ style ~ `#1'} {}
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxnewstyle}
% Defines a new index/glossary style
%    \begin{macrocode}
\NewDocumentCommand \datagidxnewstyle { m +m }
{
  \cs_if_exist:cTF { datagidx@style@#1 }
   {
     \PackageError { datagidx }
     {
       Style ~ `#1 ' ~ already ~ exists
     }
     { }
   }
   {
     \cs_set:cpn {  datagidx@style@#1 } { #2 }
   }
}
%    \end{macrocode}
%\end{macro}
%
%\subsubsection{index}
%\begin{macro}{\datagidx@style@index}
% Basic index style.
%    \begin{macrocode}
\datagidxnewstyle{index}
{
  \tl_set:Nn \datagidxstart
  {
    \group_begin:
    \dim_zero:N \parindent
    \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
%    \end{macrocode}
% Index columns are usually too narrow for fully justified text.
%    \begin{macrocode}
    \raggedright
    \cs_set_eq:NN \item \@idxitem
%    \end{macrocode}
% Have the symbol or location widths been set?
%    \begin{macrocode}
    \dim_compare:nNnTF
      { \datagidxsymbolwidth } > { \c_zero_dim } 
     {
%    \end{macrocode}
% Symbol width has been set
% Has the location width been set?
%    \begin{macrocode}
      \dim_compare:nNnTF
         { \datagidxlocationwidth } > { \c_zero_dim }
       {
%    \end{macrocode}
% Both have been set.
%    \begin{macrocode}
        \tl_set:Nn \datagidx@item@body
         {
           \__datagidx_do_ifsymlocwidth:nnn
            { \c_zero_dim }
            {
              \DTLgidxNameFont { \DTLgidxNameCase { \Name } }
            }
            {
              \DTLgidxDoSeeOrLocation
            }
         }
       }
       {
%    \end{macrocode}
% Location width hasn't been set.
%    \begin{macrocode}
        \tl_set:Nn \datagidx@item@body
         {
           \__datagidx_do_iflocwidth:nnn
            { \c_zero_dim }
            {
              \DTLgidxNameFont
                { \DTLgidxNameCase { \Name } }
            }
            {
              \DTLgidxDoSeeOrLocation
            }
         }
       }
    }
    {
%    \end{macrocode}
% Symbol width hasn't been set
% Has the location width been set?
%    \begin{macrocode}
      \dim_compare:nNnTF
       { \datagidxlocationwidth } > { \c_zero_dim }
      {
%    \end{macrocode}
% Location width has been set.
%    \begin{macrocode}
        \tl_set:Nn \datagidx@item@body
         {
           \__datagidx_do_iflocwidth:nnn
            { \c_zero_dim }
            {
              \DTLgidxNameFont
               { \DTLgidxNameCase { \Name } }
            }
            {
              \DTLgidxDoSeeOrLocation
            }
         }
      }
      {
%    \end{macrocode}
% Neither have been set.
%    \begin{macrocode}
        \tl_set:Nn \datagidx@item@body
         {
          \DTLgidxSymbolDescription
          \DTLgidxDoSeeOrLocation
         }
      }
    }
  }
  \tl_set:Nn \datagidxend { \datagidx_end: }
  \tl_set:Nn \datagidxgroupsep
   { \ifdatagidxshowgroups \indexspace \fi }
  \tl_set:Nn \datagidxgroupheader
   {
    \legacy_if:nT { datagidxshowgroups }
     {
      \item
       \makebox [ \linewidth ]
       {
         \textbf
          { 
            \DTLgidxGroupHeaderTitle { \datagidxcurrentgroup }
          }
       }
       \DTLpar\nobreak\@afterheading
     }
   }
  \tl_set:Nn \datagidxitem
   {
%    \end{macrocode}
% Is this the start of a new group?
%    \begin{macrocode}
    \tl_if_empty:NTF \datagidxprevgroup
    {
%    \end{macrocode}
% First item of the list.
%    \begin{macrocode}
      \datagidxgroupheader
    }
    {
%    \end{macrocode}
% Not the first item of the list. Is this item's group the same as
% the last item's group? Do nothing if the same.
%    \begin{macrocode}
      \tl_if_eq:NNF \datagidxcurrentgroup \datagidxprevgroup
      {
%    \end{macrocode}
% Different, so do the separator and the header.
%    \begin{macrocode}
        \datagidxgroupsep
        \datagidxgroupheader
      }
    }
%    \end{macrocode}
% Now get on with this item.
%    \begin{macrocode}
    \item
    \datagidxtarget { \Label }
    {
      \DTLgidxNameFont { \DTLgidxNameCase { \Name } }
    }
    \DTLgidxPostName
    \datagidx@item@body
    \DTLgidxChildrenSeeAlso
    \DTLgidxEndItem
   }
  \tl_set:Nn \datagidxchildstart
   {
     \group_begin:
     \dim_zero:N \parindent
     \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
     \cs_set_eq:NN \item \@idxitem
   }
  \tl_set:Nn \datagidxchildend { \group_end: }
  \tl_set:Nn \datagidxchilditem
   {
    \dim_set:Nn \l__datatool_tmpa_dim
      {
         \datagidx@level \datagidxindent
      }
    \@idxitem
    \hspace* { \l__datatool_tmpa_dim }
    \refstepcounter {DTLgidxChildCount}
    \datagidxtarget { \Label }
    {
      \DTLgidxChildStyle
      {
        \DTLgidxNameFont { \DTLgidxNameCase { \Name } }
        \DTLgidxPostChildName
      }
    }
    \DTLgidxSymbolDescription
    \DTLgidxDoSeeOrLocation
    \DTLgidxChildrenSeeAlso
   }
  \tl_set:Nn \datagidxseealsostart
   {
    \group_begin:
      \dim_zero:N \parindent
      \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
      \int_incr:N \datagidx@level
      \dim_set:Nn \l__datatool_tmpa_dim
       {
         \datagidx@level \datagidxindent
       }
      \@idxitem 
      \hspace* { \l__datatool_tmpa_dim }
   }
  \tl_set:Nn \datagidxseealsoend { \group_end: }
}
%    \end{macrocode}
%\end{macro}
%
%Make this the default style:
%    \begin{macrocode}
\datagidxsetstyle { index }
%    \end{macrocode}
%
%    \begin{macrocode}
\dim_new:N \l__datagidx_childindent_dim
%    \end{macrocode}
%
%\begin{macro}{\datagidxmapdata}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \datagidxmapdata { m }
{
 \DTLmapdata [ name = \DTLgidxCurrentdb , read-only ]
  {
    \exp_args:NV \__datatool_map_get_values_noerr:n 
       \DTLgidxAssignList
    \__datagidx_filter:T 
     {
       \__datagidx_unwrap_location:N \Location
       #1
     }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\subsubsection{indexalign}
% Similar to index style but aligns the descriptions.
%\begin{macro}{\datagidx@style@indexalign}
%    \begin{macrocode}
\datagidxnewstyle { indexalign }
{
  \tl_set:Nn \datagidxstart
   {
    \group_begin:
    \dim_zero:N \parindent
    \dim_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
    \dim_zero:N \datagidxnamewidth
    \datagidxmapdata
     {
       \datatool_if_null:NT \Parent 
       {
         \datagidx@doifdisplayed
         {
           \datatool_measure_width:Nn
             \l__datatool_tmpa_dim
            {
              \DTLgidxNameFont { \DTLgidxNameCase { \Name } }
            }
           \dim_compare:nNnT
             { \l__datatool_tmpa_dim }
              >
             { \datagidxnamewidth }
            {
              \dim_set_eq:NN
                \datagidxnamewidth
                \l__datatool_tmpa_dim
            }
         }
       }
     }
    \datatool_measure_width:Nn
      \l__datatool_tmpa_dim { \DTLgidxPostName }
    \dim_add:Nn \datagidxnamewidth
      {
        \l__datatool_tmpa_dim
      }
    \dim_set:Nn \datagidxdescwidth
      {
        \linewidth - \datagidxnamewidth
      }
    \dim_compare:nNnT
       { \datagidxsymbolwidth } > { \c_zero_dim }
     {
       \datatool_measure_width:Nn
         \l__datatool_tmpa_dim { \DTLgidxSymDescSep }
       \dim_sub:Nn \datagidxdescwidth
        {
           \datagidxsymbolwidth
          + \l__datatool_tmpa_dim
        }
     }
    \dim_compare:nNnT
       { \datagidxlocationwidth } > { \c_zero_dim }
     {
       \datatool_measure_width:Nn
         \l__datatool_tmpa_dim { \DTLgidxPreLocation }
       \dim_sub:Nn \datagidxdescwidth
        {
          \datagidxlocationwidth
          + \l__datatool_tmpa_dim
        }
     }
%    \end{macrocode}
% Has the symbol width been set?
%    \begin{macrocode}
    \dim_compare:nNnTF
      { \datagidxsymbolwidth } > { \c_zero_dim }
     {
%    \end{macrocode}
% Yes, symbol width has been set.
% Has the location width been set?
%    \begin{macrocode}
      \dim_compare:nNnTF
        { \datagidxlocationwidth } > { \c_zero_dim }
       {
%    \end{macrocode}
% Both symbol and location widths have been set.
%    \begin{macrocode}
        \bool_if:NTF \l__datagidx_symbol_left_bool
         {
%    \end{macrocode}
% Symbol is on the left.
%    \begin{macrocode}
          \tl_set:Nn \datagidx@item@body
           {
            \begin{minipage} [t]
               { \datagidxsymbolwidth }
              \datagidxsymalign
              \tl_clear:N \DTLgidxSymDescSep
              \DTLgidxSymbolDescLeft
            \end{minipage}
            \DTLgidxSymDescSep
            \begin{minipage} [t]
               { \datagidxdescwidth }
              \tl_clear:N \DTLgidxSymDescSep
              \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
              \DTLgidxSymbolDescRight
            \end{minipage}
            \DTLgidxPreLocation
            \begin{minipage} [t]
               { \datagidxlocationwidth }
              \datagidxlocalign
              \tl_clear:N \DTLgidxPreLocation
              \DTLgidxDoSeeOrLocation
            \end{minipage}
           }
         }
         {
%    \end{macrocode}
% Symbol is on the right.
%    \begin{macrocode}
          \tl_set:Nn \datagidx@item@body
           {
            \begin{minipage} [t]
               { \datagidxdescwidth }
              \tl_clear:N \DTLgidxSymDescSep
              \DTLgidxSymbolDescLeft
            \end{minipage}
            \DTLgidxSymDescSep
            \begin{minipage} [t]
               { \datagidxsymbolwidth }
              \datagidxsymalign
              \tl_clear:N \DTLgidxSymDescSep
              \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt}
              \DTLgidxSymbolDescRight
            \end{minipage}
            \DTLgidxPreLocation
            \begin{minipage} [t]
               { \datagidxlocationwidth }
              \datagidxlocalign
              \tl_clear:N \DTLgidxPreLocation
              \DTLgidxDoSeeOrLocation
            \end{minipage}
           }
         }
       }
       {
%    \end{macrocode}
% Location width hasn't been set. (Only symbol width has been set.)
%    \begin{macrocode}
        \bool_if:NTF \l__datagidx_symbol_left_bool
         {
          \tl_set:Nn \datagidx@item@body
           {
            \begin{minipage} [t]
               { \datagidxsymbolwidth }
              \datagidxsymalign
              \tl_clear:N \DTLgidxSymDescSep
              \DTLgidxSymbolDescLeft
            \end{minipage}
            \DTLgidxSymDescSep
            \begin{minipage} [t]
               { \datagidxdescwidth }
              \tl_clear:N \DTLgidxSymDescSep
              \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
              \DTLgidxSymbolDescRight
              \DTLgidxDoSeeOrLocation
            \end{minipage}
           }
         }
         {
%    \end{macrocode}
% Symbol is on the right.
% This combination may look weird.
%    \begin{macrocode}
          \tl_set:Nn \datagidx@item@body
           {
            \begin{minipage} [t]
               { \datagidxdescwidth }
              \tl_clear:N \DTLgidxSymDescSep
              \DTLgidxSymbolDescLeft
            \end{minipage}
            \DTLgidxSymDescSep
            \begin{minipage} [t]
               { \datagidxsymbolwidth }
              \datagidxsymalign
              \tl_clear:N \DTLgidxSymDescSep
              \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
              \DTLgidxSymbolDescRight
              \DTLgidxDoSeeOrLocation
            \end{minipage}
           }
         }
       }
     }
     {
%    \end{macrocode}
% Symbol width hasn't been set.
% Has the location width been set?
%    \begin{macrocode}
      \dim_compare:nNnTF
        { \datagidxlocationwidth } > { \c_zero_dim }
       {
%    \end{macrocode}
% Only location width has been set.
%    \begin{macrocode}
        \tl_set:Nn \datagidx@item@body
         {
          \begin{minipage} [t]
             { \datagidxdescwidth }
           \skip_set:Nn \parskip {0pt ~ plus ~ 0.3pt }
           \DTLgidxSymbolDescription
          \end{minipage}
          \DTLgidxPreLocation
          \begin{minipage} [t]
             { \datagidxlocationwidth }
           \datagidxlocalign
           \tl_clear:N \DTLgidxPreLocation
           \DTLgidxDoSeeOrLocation
         }
       }
       {
%    \end{macrocode}
% Neither location nor symbol widths have been set.
%    \begin{macrocode}
        \tl_set:Nn \datagidx@item@body
         {
          \begin{minipage} [t]
             { \datagidxdescwidth }
           \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
           \DTLgidxSymbolDescription
           \DTLgidxDoSeeOrLocation
          \end{minipage}
         }
       }
     }
   }
  \tl_set:Nn \datagidxend { \datagidx_end: }
  \tl_clear:N \datagidxgroupsep
  \tl_clear:N \datagidxgroupheader
  \tl_set:Nn \datagidxitem
   {
%    \end{macrocode}
% Is this the start of a new group?
%    \begin{macrocode}
    \tl_if_empty:NTF \datagidxprevgroup
     {
%    \end{macrocode}
% First item of the list.
%    \begin{macrocode}
      \datagidxgroupheader
     }
     {
%    \end{macrocode}
% Not the first item of the list. Is this item's group the same as
% the last item's group? If the same, do nothing
%    \begin{macrocode}
      \tl_if_eq:NNF \datagidxcurrentgroup \datagidxprevgroup
       {
%    \end{macrocode}
% Different, so do the separator and the header.
%    \begin{macrocode}
        \datagidxgroupsep
        \datagidxgroupheader
       }
     }
%    \end{macrocode}
% Get on with this item
%    \begin{macrocode}
    \hangindent0pt\relax
    \parindent0pt\relax
    \makebox [ \datagidxnamewidth ] [l]
     {
      \datagidxtarget { \Label }
       {
        \DTLgidxNameFont
          { \DTLgidxNameCase { \Name } }
        \DTLgidxPostName
       }
     }
    \datagidx@item@body
    \par
    \DTLgidxChildrenSeeAlso
    \DTLgidxEndItem
  }
  \tl_set:Nn \datagidxchildstart
   {
    \group_begin:
    \dim_set:Nn \l__datatool_tmpa_dim
      { \datagidx@level \datagidxindent }
    \dim_set:Nn \l__datagidx_childindent_dim
      {
        \linewidth -  \l__datatool_tmpa_dim
      }
    \dim_zero:N \parindent
    \dim_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
    \tl_set:Nx \item
      {
        \exp_not:N \parshape = \c_one_int
        \dim_use:N \l__datatool_tmpa_dim \c_space_tl ~
        \dim_use:N \l__datagidx_childindent_dim 
      }
    \dim_zero:N \datagidxnamewidth
    \datagidxmapdata
     {
       \datatool_if_null:NT \Parent 
        {
          \datagidx@doifdisplayed
           {
             \datatool_measure_width:Nn
               \l__datatool_tmpa_dim
              {
                \DTLgidxChildStyle
                 {
                   \DTLgidxNameFont
                    { \DTLgidxNameCase { \Name } }
                 }
              }
             \dim_compare:nNnT
               { \l__datatool_tmpa_dim }
                 >
               { \datagidxnamewidth }
              {
                \dim_set_eq:NN
                  \datagidxnamewidth
                  \l__datatool_tmpa_dim
              }
           }
        }
    }
   \datatool_measure_width:Nn
     \l__datatool_tmpa_dim
     { \DTLgidxChildStyle \DTLgidxPostChildName }
   \dim_add:Nn \datagidxnamewidth
    {
      \l__datatool_tmpa_dim
    }
   \dim_set:Nn \datagidxdescwidth
    {
      \l__datagidx_childindent_dim
      - \datagidxnamewidth
    }
   }
  \tl_set:Nn \datagidxchildend { \group_end: }
  \tl_set:Nn \datagidxchilditem
   {
     \item
     \refstepcounter {DTLgidxChildCount}
     \makebox [ \datagidxnamewidth ] [l]
      {
        \datagidxtarget { \Label }
         {
           \DTLgidxChildStyle
            {
              \DTLgidxNameFont
               { \DTLgidxNameCase {\Name} }
              \DTLgidxPostChildName
            }
         }
      }
     \begin{minipage} [t]
       { \datagidxdescwidth }
      \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
      \DTLgidxSymbolDescription
      \DTLgidxDoSeeOrLocation
      \DTLgidxChildrenSeeAlso
     \end{minipage}
     \par
   }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\datagidxindent}
% Indent used by "index" and "indexalign" styles.
%    \begin{macrocode}
\newlength\datagidxindent
\dim_set:Nn \datagidxindent { 10pt }
%    \end{macrocode}
%\end{macro}
%
%
%\subsubsection{align}
%\begin{macro}{\datagidxnamewidth}
% Length used by "align" and "indexalign" style name.
%    \begin{macrocode}
\newlength\datagidxnamewidth
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxdescwidth}
% Length used by "align" and "indexalign" style description.
%    \begin{macrocode}
\newlength\datagidxdescwidth
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@style@align}
%    \begin{macrocode}
\datagidxnewstyle { align }
{
  \tl_set:Nn \datagidxstart
   {
    \group_begin:
    \dim_zero:N \parindent
    \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
    \dim_zero:N \datagidxnamewidth
    \datagidxmapdata
     {
       \datatool_if_null:NT \Parent 
        {
         \datagidx@doifdisplayed
          {
            \datatool_measure_width:Nn
             \l__datatool_tmpa_dim
              {
                \DTLgidxNameFont
                  { \DTLgidxNameCase { \Name } }
              }
           \dim_compare:nNnT 
             { \l__datatool_tmpa_dim }
              >
             { \datagidxnamewidth }
            {
              \dim_set_eq:NN
                \datagidxnamewidth
                \l__datatool_tmpa_dim
            }
          }
        }
     }
    \datatool_measure_width:Nn
       \l__datatool_tmpa_dim
       { \DTLgidxPostName }
    \dim_add:Nn \datagidxnamewidth
     {
       \l__datatool_tmpa_dim
     }
    \dim_set:Nn \datagidxdescwidth
     {
       \linewidth - \datagidxnamewidth
     }
    \dim_compare:nNnT
       { \datagidxsymbolwidth } > { \c_zero_dim }
     {
       \datatool_measure_width:Nn
         \l__datatool_tmpa_dim
         { \DTLgidxSymDescSep }
      \dim_sub:Nn \datagidxdescwidth
         {
           \datagidxsymbolwidth
           + \l__datatool_tmpa_dim
         }
     }
    \dim_compare:nNnT 
      { \datagidxlocationwidth } > { \c_zero_dim }
     {
       \datatool_measure_width:Nn
         \l__datatool_tmpa_dim
         { \DTLgidxPreLocation }
       \dim_sub:Nn \datagidxdescwidth
        {
          \datagidxlocationwidth
          + \l__datatool_tmpa_dim
        }
     }
%    \end{macrocode}
% Has the symbol width been set?
%    \begin{macrocode}
    \dim_compare:nNnTF
      { \datagidxsymbolwidth } > { \c_zero_dim }
     {
%    \end{macrocode}
% Yes, symbol width has been set.
% Has the location width been set?
%    \begin{macrocode}
       \dim_compare:nNnTF
         { \datagidxlocationwidth } > { \c_zero_dim }
        {
%    \end{macrocode}
% Both symbol and location widths have been set.
%    \begin{macrocode}
          \bool_if:NTF \l__datagidx_symbol_left_bool
           {
%    \end{macrocode}
% Symbol is on the left.
%    \begin{macrocode}
             \tl_set:Nn \datagidx@item@body
              {
                \begin{minipage} [t]
                  { \datagidxsymbolwidth }
                \datagidxsymalign
                \tl_clear:N \DTLgidxSymDescSep
                \DTLgidxSymbolDescLeft
                \end{minipage}
                \DTLgidxSymDescSep
                \begin{minipage} [t]
                 { \datagidxdescwidth }
                \tl_clear:N \DTLgidxSymDescSep
                \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
                \DTLgidxSymbolDescRight
                \end{minipage}
                \DTLgidxPreLocation
                \begin{minipage} [t]
                   { \datagidxlocationwidth }
                  \datagidxlocalign
                  \tl_clear:N \DTLgidxPreLocation
                  \DTLgidxDoSeeOrLocation
                  \DTLgidxChildrenSeeAlso
                \end{minipage}
              }
           }
         {
%    \end{macrocode}
% Symbol is on the right.
%    \begin{macrocode}
          \tl_set:Nn \datagidx@item@body
           {
            \begin{minipage} [t]
               { \datagidxdescwidth }
              \tl_clear:N \DTLgidxSymDescSep
              \DTLgidxSymbolDescLeft
            \end{minipage}
            \DTLgidxSymDescSep
            \begin{minipage} [t]
               { \datagidxsymbolwidth }
              \datagidxsymalign
              \tl_clear:N \DTLgidxSymDescSep
              \skip_set:N \parskip { 0pt ~ plus ~ 0.3pt }
              \DTLgidxSymbolDescRight
            \end{minipage}
            \DTLgidxPreLocation
            \begin{minipage} [t]
               { \datagidxlocationwidth }
              \datagidxlocalign
              \tl_clear:N \DTLgidxPreLocation
              \DTLgidxDoSeeOrLocation
              \DTLgidxChildrenSeeAlso
            \end{minipage}
           }
         }
        }
        {
%    \end{macrocode}
% Location width hasn't been set. (Only symbol width has been set.)
%    \begin{macrocode}
        \bool_if:NTF \l__datagidx_symbol_left_bool
         {
          \tl_set:Nn \datagidx@item@body
           {
            \begin{minipage} [t]
               { \datagidxsymbolwidth }
              \datagidxsymalign
              \tl_clear:N \DTLgidxSymDescSep
              \DTLgidxSymbolDescLeft
            \end{minipage}
            \DTLgidxSymDescSep
            \begin{minipage} [t]
               { \datagidxdescwidth }
              \tl_clear:N \DTLgidxSymDescSep
              \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
              \DTLgidxSymbolDescRight
              \DTLgidxDoSeeOrLocation
              \DTLgidxChildrenSeeAlso
            \end{minipage}
           }
         }
         {
%    \end{macrocode}
% Symbol is on the right.
% This combination may look weird.
%    \begin{macrocode}
          \tl_set:Nn \datagidx@item@body
           {
            \begin{minipage} [t]
               { \datagidxdescwidth }
              \tl_clear:N \DTLgidxSymDescSep
              \DTLgidxSymbolDescLeft
            \end{minipage}
            \DTLgidxSymDescSep
            \begin{minipage} [t]
               { \datagidxsymbolwidth }
              \datagidxsymalign
              \tl_clear:N \DTLgidxSymDescSep
              \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
              \DTLgidxSymbolDescRight
              \DTLgidxDoSeeOrLocation
              \DTLgidxChildrenSeeAlso
            \end{minipage}
           }
         }
        }
     }
     {
%    \end{macrocode}
% Symbol width hasn't been set.
% Has the location width been set?
%    \begin{macrocode}
      \dim_compare:nNnTF
        { \datagidxlocationwidth } > { \c_zero_dim }
       {
%    \end{macrocode}
% Only location width has been set.
%    \begin{macrocode}
        \tl_set:Nn \datagidx@item@body
         {
          \begin{minipage} [t]
            { \datagidxdescwidth }
           \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
           \DTLgidxSymbolDescription
          \end{minipage}
          \DTLgidxPreLocation
          \begin{minipage} [t]
            { \datagidxlocationwidth }
           \datagidxlocalign
           \tl_clear:N \DTLgidxPreLocation
           \DTLgidxDoSeeOrLocation
           \DTLgidxChildrenSeeAlso
          \end{minipage}
         }
       }
       {
%    \end{macrocode}
% Neither location nor symbol widths have been set.
%    \begin{macrocode}
        \tl_set:Nn \datagidx@item@body
         {
          \begin{minipage} [t]
             { \datagidxdescwidth }
           \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
           \DTLgidxSymbolDescription
           \DTLgidxDoSeeOrLocation
           \DTLgidxChildrenSeeAlso
          \end{minipage}
         }
       }
     }
   }
  \tl_set:Nn \datagidxend { \datagidx_end: }
  \tl_set:Nn \datagidxgroupsep
   { \ifdatagidxshowgroups \indexspace \fi }
  \tl_set:Nn \datagidxgroupheader
   {
    \ifdatagidxshowgroups
      \item
       \makebox [ \linewidth ]
       {
         \textbf
          {
            \DTLgidxGroupHeaderTitle
              { \datagidxcurrentgroup }
          }
       }
       \DTLpar\nobreak\@afterheading
    \fi
   }
  \tl_set:Nn \datagidxitem
   {
%    \end{macrocode}
% Is this the start of a new group?
%    \begin{macrocode}
    \tl_if_empty:NTF \datagidxprevgroup
     {
%    \end{macrocode}
% First item of the list.
%    \begin{macrocode}
       \datagidxgroupheader
     }
     {
%    \end{macrocode}
% Not the first item of the list. Is this item's group the same as
% the last item's group? If the same, do nothing.
%    \begin{macrocode}
       \tl_if_eq:NNF \datagidxcurrentgroup \datagidxprevgroup
       {
%    \end{macrocode}
% Different, so do the separator and the header.
%    \begin{macrocode}
         \datagidxgroupsep
         \datagidxgroupheader
       }
     }
    \hangindent \c_zero_dim 
    \parindent \c_zero_dim 
    \makebox [ \datagidxnamewidth ] [l]
     {
       \datagidxtarget { \Label }
        {
          \DTLgidxNameFont
            { \DTLgidxNameCase {\Name} }
          \DTLgidxPostName
        }
     }
     \datagidx@item@body
     \DTLgidxEndItem
   }
  \tl_set:Nn \datagidxchildstart
   {
    \group_begin:
    \dim_zero:N \parindent
    \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
    \dim_zero:N \datagidxnamewidth
    \datagidxmapdata
     {
       \datatool_if_null:NT \Parent 
        {
          \datagidx@doifdisplayed
           {
             \datatool_measure_width:Nn
               \l__datatool_tmpa_dim
              {
                \DTLgidxChildStyle
                 {
                   \DTLgidxNameFont
                    { \DTLgidxNameCase {\Name} }
                 }
              }
             \dim_compare:nNnT
               { \l__datatool_tmpa_dim }
                >
               { \datagidxnamewidth }
              {
                \dim_set_eq:NN 
                  \datagidxnamewidth
                  \l__datatool_tmpa_dim
              }
           }
        }
     }
    \datatool_measure_width:Nn
      \l__datatool_tmpa_dim
     {
       \DTLgidxChildStyle \DTLgidxPostChildName
     }
    \dim_add:Nn \datagidxnamewidth
      { \l__datatool_tmpa_dim }
    \dim_set:Nn \datagidxdescwidth
     {
       \linewidth - \datagidxnamewidth
     }
   }
  \tl_set:Nn \datagidxchildend { \group_end: }
  \tl_set:Nn \datagidxchilditem
   {
    \hangindent \c_zero_dim
    \parindent \c_zero_dim
    \refstepcounter {DTLgidxChildCount}
    \makebox [ \datagidxnamewidth ] [l]
     {
       \datagidxtarget {\Label}
        {
          \DTLgidxChildStyle
           {
             \DTLgidxNameFont
               { \DTLgidxNameCase {\Name} }
             \DTLgidxPostChildName
           }
        }
     }
    \begin{minipage} [t]
       { \datagidxdescwidth }
     \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
     \DTLgidxSymbolDescription
     \DTLgidxDoSeeOrLocation
     \DTLgidxChildrenSeeAlso
    \end{minipage}
    \par
   }
}
%    \end{macrocode}
%\end{macro}
%
%\subsubsection{gloss}
%\begin{macro}{\datagidx@style@gloss}
%    \begin{macrocode}
\datagidxnewstyle { gloss }
{
  \tl_set:Nn \datagidxstart
   {
    \group_begin:
    \dim_zero:N \parindent
    \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
    \dim_zero:N \datagidxnamewidth
    \datagidxmapdata
     {
       \datatool_if_null:NT \Parent 
        {
          \datagidx@doifdisplayed
           {
             \datatool_measure_width:Nn
              \l__datatool_tmpa_dim
              {
                \DTLgidxNameFont
                 { \DTLgidxNameCase {\Name} }
              }
            \dim_compare:nNnT
              { \l__datatool_tmpa_dim }
                >
              { \datagidxnamewidth }
             {
               \dim_set_eq:NN
                 \datagidxnamewidth
                 \l__datatool_tmpa_dim 
             }
           }
        }
     }
    \datatool_measure_width:Nn
       \l__datatool_tmpa_dim
      { \DTLgidxPostName }
    \dim_add:Nn \datagidxnamewidth
       { \l__datatool_tmpa_dim }
    \dim_set:Nn \datagidxdescwidth
     {
      \linewidth - \datagidxnamewidth
     }
   }
  \tl_set:Nn \datagidxend { \datagidx_end: }
  \tl_set:Nn \datagidxgroupsep
   {
     \ifdatagidxshowgroups \indexspace \fi
   }
  \tl_set:Nn \datagidxgroupheader
   {
     \ifdatagidxshowgroups
       \item
       \makebox [ \linewidth ]
        {
         \textbf
          {
            \DTLgidxGroupHeaderTitle
              { \datagidxcurrentgroup }
          }
        }
       \DTLpar\nobreak\@afterheading
     \fi
   }
  \tl_set:Nn \datagidxitem
   {
%    \end{macrocode}
% Is this the start of a new group?
%    \begin{macrocode}
    \tl_if_empty:NTF \datagidxprevgroup
     {
%    \end{macrocode}
% First item of the list.
%    \begin{macrocode}
       \datagidxgroupheader
     }
     {
%    \end{macrocode}
% Not the first item of the list. Is this item's group the same as
% the last item's group? If the same, do nothing.
%    \begin{macrocode}
       \tl_if_eq:NNF \datagidxcurrentgroup\datagidxprevgroup
        {
%    \end{macrocode}
% Different, so do the separator and the header.
%    \begin{macrocode}
          \datagidxgroupsep
          \datagidxgroupheader
        }
     }
    \dim_zero:N \hangindent
    \dim_zero:N \parindent
    \makebox [ \datagidxnamewidth ] [l]
     {
       \datagidxtarget {\Label}
        {
          \DTLgidxNameFont
            { \DTLgidxNameCase {\Name} }
          \DTLgidxPostName
        }
     }
    \begin{minipage} [t]
      { \datagidxdescwidth }
     \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
     \bool_lazy_all:nTF
        {
         { \tl_if_empty_p:N \Description }
         { \tl_if_empty_p:N \Symbol }
         { \tl_if_empty_p:N \Location }
        }
      {
        \mbox { }
      }
      {
        \DTLgidxSymbolDescription
        \DTLgidxDoSeeOrLocation
%    \end{macrocode}
%\changes{2.14}{2013-06-28}{removed spurious see also line}
%    \begin{macrocode}
      }
     \DTLgidxChildrenSeeAlso
    \end{minipage}
    \DTLgidxEndItem
   }
  \tl_set:Nn \datagidxchildstart
   {
     \group_begin:
     \tl_clear:N \datagidx@childsep
     \setcounter {DTLgidxChildCount} { 0 }
   }
  \tl_set:Nn \datagidxchildend
   { \DTLgidxPostChild \group_end: }
  \tl_set:Nn \datagidxchilditem
   {
     \datagidx@childsep
     \refstepcounter {DTLgidxChildCount}
     \datagidxtarget {\Label}
      {
        \DTLgidxChildStyle
         {
           \DTLgidxNameFont
             { \DTLgidxNameCase {\Name} }
           \DTLgidxPostChildName
         }
      }
     \DTLgidxSymbolDescription
     \DTLgidxDoSeeOrLocation
     \DTLgidxChildrenSeeAlso
     \tl_set_eq:NN \datagidx@childsep \DTLgidxChildSep
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxChildSep}
% Separator between child entries for gloss style.
%    \begin{macrocode}
\newcommand*{\DTLgidxChildSep}{ ~ }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxPostChild}
%What to put at the end of child entries for gloss style.
%    \begin{macrocode}
\newcommand*{\DTLgidxPostChild}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxDictHead}
% Group header for dict style.
%    \begin{macrocode}
\cs_if_exist:NTF \chapter
{
  \newcommand \DTLgidxDictHead
   {
     \chapter
      {
        \DTLgidxGroupHeaderTitle { \datagidxcurrentgroup }
      }
   }
}
{
  \newcommand \DTLgidxDictHead
   {
     \section
      {
        \DTLgidxGroupHeaderTitle { \datagidxcurrentgroup }
      }
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxCategoryNameFont}
% Font used for `category' entries with `dict' style.
%    \begin{macrocode}
\newcommand*{\DTLgidxCategoryNameFont}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxCategorySep}
% Separator used with `dict' style.
%    \begin{macrocode}
\newcommand*{\DTLgidxCategorySep}{\space}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxSubCategorySep}
% Separator used with `dict' style.
%    \begin{macrocode}
\newcommand*{\DTLgidxSubCategorySep}{\space}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxdictindent}
% Indent used by `dict' style.
%    \begin{macrocode}
\newcommand*{\datagidxdictindent}{1em}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxDictPostItem}
% What to do at the end of each item in the `dict' style.
%    \begin{macrocode}
\newcommand{\DTLgidxDictPostItem}{\DTLgidxEndItem}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@style@dict}
% Dictionary style. This assumes a hierarchical structure where the
% top level entries have a name. The next level is used to indicate
% a category, such as ``adjective'' or ``noun''. If there is only
% one meaning this level also has a description. If there is more
% than one meaning, each meaning should be a child of the category
% entry. Only third level entries are numbered. The "child" key is
% ignored in this style. The symbol is ignored. The location and
% symbols widths are also ignored.
%    \begin{macrocode}
\datagidxnewstyle { dict }
{
  \tl_set:Nn \datagidxstart
   {
     \group_begin:
     \dim_zero:N \parindent
     \dim_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
     \dim_set:Nn \l__datatool_tmpa_dim
      {
        \linewidth - \datagidxdictindent
      }
     \dim_set:Nn \l__datagidx_childindent_dim
      { \datagidxdictindent }
     \tl_set:Nx \datagidxdictparshape
      {
        \exp_not:N \parshape=2 ~ 0pt ~
         \dim_use:N \linewidth \c_space_tl ~
         \dim_use:N \l__datagidx_childindent_dim
         \c_space_tl ~
         \dim_use:N \l__datatool_tmpa_dim
      }
    \int_set_eq:NN \datagidx@level \c_one_int
%    \end{macrocode}
% Index columns are usually too narrow for fully justified text.
%    \begin{macrocode}
    \raggedright
   }
  \tl_set:Nn \datagidxend { \datagidx_end: }
  \tl_clear:N \datagidxgroupsep
  \tl_set:Nn \datagidxgroupheader
   {
     \ifdatagidxshowgroups
       \datagidxend
       \datagidx@postend
       \DTLgidxDictHead
       \l__datagidx_prestart_tl
       \datagidxstart
     \fi
   }
  \tl_set:Nn \datagidxitem
   {
%    \end{macrocode}
% Is this the start of a new group?
%    \begin{macrocode}
    \tl_if_empty:NTF \datagidxprevgroup
     {
%    \end{macrocode}
% First item of the list.
%    \begin{macrocode}
       \datagidxgroupheader
     }
     {
%    \end{macrocode}
% Not the first item of the list. Is this item's group the same as
% the last item's group? If the same, do nothing.
%    \begin{macrocode}
      \tl_if_eq:NNF \datagidxcurrentgroup \datagidxprevgroup
       {
%    \end{macrocode}
% Different, so do the separator and the header.
%    \begin{macrocode}
         \datagidxgroupsep
         \datagidxgroupheader
       }
     }
%    \end{macrocode}
% Now get on with this item.
%    \begin{macrocode}
    \datagidxdictparshape
    \datagidxtarget {\Label}
    {
      \DTLgidxNameFont
        { \DTLgidxNameCase {\Name} }
    }
    \DTLgidxPostName
%    \end{macrocode}
% Initialise category separator to do nothing.
%    \begin{macrocode}
    \tl_clear:N \datagidx@catsep
    \tl_clear:N \datagidx@subcatsep
    \DTLgidxSymbolDescription
%    \end{macrocode}
% No location list.
%    \begin{macrocode}
    \DTLgidxChildrenSeeAlso
    \DTLgidxDictPostItem
   }
  \tl_set:Nn \datagidxchildstart
   { \group_begin: }
  \tl_set:Nn \datagidxchildend { \group_end: }
  \tl_set:Nn \datagidxchilditem
   {
%    \end{macrocode}
% Which level are we on?
%    \begin{macrocode}
     \int_compare:nNnTF
       { \datagidx@level } = { 2 }
      {
%    \end{macrocode}
% Category entry
%    \begin{macrocode}
        \datagidx@catsep
        \tl_set_eq:NN 
          \datagidx@catsep
          \DTLgidxCategorySep
        \tl_clear:N \datagidx@subcapsep
        \datagidxtarget { \Label }
         {
           \DTLgidxChildStyle
            {
              \DTLgidxCategoryNameFont
               { \DTLgidxNameCase{\Name} }
              \DTLgidxPostChildName
            }
         }
        \setcounter {DTLgidxChildCount} {0}
      }
      {
%    \end{macrocode}
% Sub Category entry
%    \begin{macrocode}
        \datagidx@subcatsep
        \tl_set_eq:NN
          \datagidx@subcatsep
          \DTLgidxSubCategorySep
        \refstepcounter {DTLgidxChildCount}
        \DTLgidxChildCountLabel
        \DTLgidxPostChildName
      }
     \DTLgidxSymbolDescription
     \DTLgidxDoSeeOrLocation
     \DTLgidxChildrenSeeAlso
   }
  \tl_set:Nn \datagidxseealsostart
   {
    \group_begin:
      \dim_zero:N \parindent
      \skip_set:Nn \parskip { 0pt ~ plus ~ 0.3pt }
      \int_incr:N \datagidx@level 
      \dim_set:Nn \l__datatool_tmpa_dim
       {
         \datagidx@level \datagidxindent
       }
      \@idxitem
      \hspace* { \l__datatool_tmpa_dim }
   }
  \tl_set:Nn \datagidxseealsoend { \group_end: }
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Location Lists}
%\begin{macro}{\dtldofirstlocation}
% Only display the first location in the list.
%    \begin{macrocode}
\newcommand*{\dtldofirstlocation}{%
 \group_begin:
 \__datagidx_unwrap_location:N \Location
 \clist_map_inline:Nn \Location
 {
    \tl_if_empty:nF { ##1 }
    {
       \tl_if_head_eq_meaning:nNTF { ##1 } [
        {
          \__datagidx_getlocation:wnn ##1
        }
        {
          \__datagidx_getlocation:wnn [ ] { } { }
        }
       \datagidxlink
        { \datagidx@current@target }
        {
           \__datagidx_formatlocation:VV
            \datagidx@current@format
            \datagidx@current@locationstring 
        }
%    \end{macrocode}
% Only interested in the first item, so break out of loop.
%    \begin{macrocode}
      \clist_map_break:
    }
  }
 \group_end:
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@formatlocation}
%Version 3.0: replaced \cs{datagidx@formatlocation} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{\_\_datagidx\_formatlocation:nn}}
%    \begin{macrocode}
\cs_new:Nn \__datagidx_formatlocation:nn
{
 \tl_if_empty:nTF { #1 }
  { #2 }
  {
    \cs_if_exist:cTF { #1 }
    {
      \use:c { #1 } { #2 }
    }
    {
      \PackageWarning {datagidx}
       { Unknown ~ format ~ `#1' }
      #2
    }
  }
}
\cs_generate_variant:Nn
 \__datagidx_formatlocation:nn
 { VV }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtldolocationlist}
% Display the location list.
%    \begin{macrocode}
\NewDocumentCommand \dtldolocationlist { }
{
 \datatool_if_null_or_empty:NF \Location 
  {
    \group_begin:
    \int_set:Nn  \l__datagidx_prevloc_int {-1}
    \tl_clear:N \datagidx@prev@locationstring
    \tl_clear:N \datagidx@prev@format
    \tl_clear:N \datagidx@prev@locationformat
    \tl_clear:N \datagidx@prev@prefix
    \tl_clear:N \datagidx@prev@target
    \tl_clear:N \datagidx@location@sep
    \int_set:Nn \l__datagidx_startloc_int {-1}
    \__datagidx_unwrap_location:N \Location
    \clist_map_function:NN
      \Location \datagidx@parse@location
    \__datagidx_do_prev_location: % tidy up loose ends
    \group_end:
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\if@dtl@sequential}
% Conditional to keep track of sequences.
%Version 3.0: replaced \cs{if@dtl@sequential} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datagidx\_sequential\_bool}}
%    \begin{macrocode}
\bool_new:N \l__datagidx_sequential_bool
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\datagidx@getlocdo}
% Handler for \cs{datagidx@docomplist}
%Version 3.0: removed
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%Assign a numeric value to a location:
%    \begin{macrocode}
\regex_const:Nn \c__datagidx_location_int_regex
  { ( \d+ ) \Z }
\regex_const:Nn \c__datagidx_location_roman_regex
  { ( [ I V X L C D M ]+ | [ i v x l c d m ]+ ) \Z }
\regex_const:Nn \c__datagidx_location_alph_regex
  { ( [ a-z A-Z ] ) \Z }
\cs_new:Nn \__datagidx_get_location_num:Nn
{
  \DTLifint { #2 }
   {
     \int_set:Nn #1 { \l__datatool_datum_value_tl }
   }
   {
     \regex_extract_once:NnNTF
       \c__datagidx_location_int_regex { #2 }
        \l__datatool_tmpb_seq
     {
       \exp_args:NNx \int_set:Nn #1
         { \seq_item:Nn \l__datatool_tmpb_seq { 2 } }
     }
     {
        \regex_extract_once:NnNTF
          \c__datagidx_location_roman_regex { #2 }
           \l__datatool_tmpb_seq
        {
          \exp_args:NNx \tl_set:Nn
            \l__datatool_tmpb_tl
             {
               \exp_not:N \int_from_roman:n
                {
                  \seq_item:Nn \l__datatool_tmpb_seq { 2 }
                }
             }
          \int_set:Nn #1 { \l__datatool_tmpb_tl }
        }
        {
          \regex_extract_once:NnNTF
            \c__datagidx_location_alph_regex { #2 }
             \l__datatool_tmpb_seq
          {
            \exp_args:NNx \tl_set:Nn
              \l__datatool_tmpb_tl
               {
                 \exp_not:N \int_from_alph:n
                  {
                    \seq_item:Nn \l__datatool_tmpb_seq { 2 }
                  }
               }
            \int_set:Nn #1 { \l__datatool_tmpb_tl }
          }
          {
            \int_set:Nn #1 { -1 }
          }
        }
     }
   }
}
\cs_generate_variant:Nn  \__datagidx_get_location_num:Nn
 { NV }
%    \end{macrocode}
%
%    \begin{macrocode}
\int_new:N \l__datagidx_prevloc_int
\int_new:N \l__datagidx_currentloc_int
%    \end{macrocode}
%\begin{macro}{\datagidx@location@start}
%Version 3.0: replaced \cs{datagidx@location@start} with:
%    \begin{macrocode}
\int_new:N \l__datagidx_startloc_int
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@getlocation}
% Syntax: \oarg{format}\marg{location}\marg{anchor}
% Get the location and store in \cs{current@location}:
%    \begin{macrocode}
\cs_new:Npn \__datagidx_getlocation:wnn
  [ #1 ] #2 #3
{
%    \end{macrocode}
% Store the original value.
%    \begin{macrocode}
  \tl_set:Nn \datagidx@current@locationstring { #2 }
%    \end{macrocode}
% Store the format:
%    \begin{macrocode}
  \tl_set:Nn \datagidx@current@format { #1 }
%    \end{macrocode}
% Store the target:
%    \begin{macrocode}
  \tl_set:Nn \datagidx@current@target { #3 }
  \tl_if_empty:nTF { #2 }
  {
    \tl_clear:N \datagidx@current@prefix
    \tl_clear:N \datagidx@current@location
  }
  {
%    \end{macrocode}
% If the location contains a compositor, we need to get the final
% element and store the rest as a prefix:
%    \begin{macrocode}
    \seq_set_split:NVn \l__datatool_tmpa_seq 
      \l__datagidx_compositor_tl
      { #2 }
    \seq_pop_right:NN \l__datatool_tmpa_seq
       \datagidx@current@location
    \tl_set:Nx \datagidx@current@prefix
      { 
        \seq_use:Nn \l__datatool_tmpa_seq
          \l__datagidx_compositor_tl
      }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@parse@location}
% Parses the location list (given in the argument).
%    \begin{macrocode}
\newcommand*{\datagidx@parse@location}[1]{
  \tl_set:Nn \l__datagidx_location_tl { #1 }
  \__datagidx_unwrap_location:N
    \l__datagidx_location_tl
%    \end{macrocode}
% Parse location format.
%    \begin{macrocode}
   \exp_args:NV \tl_if_head_eq_meaning:nNTF
    \l__datagidx_location_tl [
   {
     \exp_after:wN \__datagidx_getlocation:wnn
        \l__datagidx_location_tl
   }
   {
     \__datagidx_getlocation:wnn [ ] { } { }
   }
%    \end{macrocode}
% If this is the same as the previous location, do nothing.
%    \begin{macrocode}
  \tl_if_eq:NNTF 
    \datagidx@prev@locationstring
    \datagidx@current@locationstring
  {
%    \end{macrocode}
% If the format is different, let the non-empty format over-ride
% the empty format.
%\changes{2.21}{2014-03-08}{replaced \cs{ifstrequal} with
%\cs{ifdefequal}}
%    \begin{macrocode}
    \tl_if_eq:NNF 
      \datagidx@prev@format
      \datagidx@current@format
    {
      \tl_if_empty:NF \datagidx@current@format
      {
        \tl_if_empty:NTF \datagidx@prev@format
        {
%    \end{macrocode}
% Previous format is empty, so update.
%    \begin{macrocode}
          \tl_set_eq:NN
            \datagidx@prev@format
            \datagidx@current@format
        }
        {
           \PackageWarning {datagidx}
           {
              Conflicting ~ location ~ formats ~
              `\datagidx@prev@format' ~ and ~
              `\datagidx@current@format' ~ for ~ 
              location ~ `\datagidx@current@location'
           }
        }
      }
    }
  }
  {
    \@datagidx@parse@location
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@datagidx@parse@location}
%    \begin{macrocode}
\newcommand*{\@datagidx@parse@location}{
%    \end{macrocode}
% Get a numeric value for this location.
%    \begin{macrocode}
  \__datagidx_get_location_num:NV
    \l__datagidx_currentloc_int
    \datagidx@current@location
%    \end{macrocode}
% Check if we have a sequence.
%    \begin{macrocode}
  \bool_set_true:N \l__datagidx_sequential_bool
%    \end{macrocode}
% A change in font format breaks the sequence.
%    \begin{macrocode}
  \tl_if_eq:NNTF
     \datagidx@prev@format \datagidx@current@format
   {
%    \end{macrocode}
% A change in location format breaks the sequence.
%    \begin{macrocode}
    \tl_if_eq:NNTF
      \datagidx@prev@locationformat
      \datagidx@current@locationformat
     {
%    \end{macrocode}
% A change in prefix breaks the sequence.
%    \begin{macrocode}
       \tl_if_eq:NNF
          \datagidx@prev@prefix \datagidx@current@prefix
        {
%    \end{macrocode}
% Prefixes are different, so not a sequence.
%    \begin{macrocode}
          \bool_set_false:N \l__datagidx_sequential_bool
        }
     }
     {
%    \end{macrocode}
% Formats are different, so not a sequence.
%    \begin{macrocode}
        \bool_set_false:N \l__datagidx_sequential_bool
     }
   }
   {
%    \end{macrocode}
% Formats are different, so not a sequence.
%    \begin{macrocode}
    \bool_set_false:N \l__datagidx_sequential_bool
   }
  \bool_if:NT \l__datagidx_sequential_bool
   {
%    \end{macrocode}
% Is this location one more than the previous location?
%    \begin{macrocode}
    \int_compare:nNnTF
      { \l__datagidx_prevloc_int + 1 }
        =
      { \l__datagidx_currentloc_int }
    {
%    \end{macrocode}
% It is one more than previous value.
% Is this location the same type as the previous location?
%    \begin{macrocode}
       \tl_if_eq:NNTF
         \datagidx@current@locationformat
         \datagidx@prev@locationformat
      {
%    \end{macrocode}
% They are the same, so we have a sequence.
%    \begin{macrocode}
        \bool_set_true:N \l__datagidx_sequential_bool
      }
      {
%    \end{macrocode}
% They aren't the same, so we don't have a sequence.
%    \begin{macrocode}
        \bool_set_false:N \l__datagidx_sequential_bool
      }
    }
    {
      \bool_set_false:N \l__datagidx_sequential_bool
    }
   }
%    \end{macrocode}
% Has the sequence flag been set?
%    \begin{macrocode}
  \bool_if:NTF \l__datagidx_sequential_bool
   {
%    \end{macrocode}
% Yes, we have a sequence.
% Has the start of the sequence been set?
%    \begin{macrocode}
     \int_compare:nNnT
      { \l__datagidx_startloc_int } = { -1 }
     {
%    \end{macrocode}
% No it hasn't, so set it
%    \begin{macrocode}
       \int_set_eq:NN
         \l__datagidx_startloc_int
         \l__datagidx_prevloc_int
       \let\datagidx@location@startval\datagidx@prev@locationstring
       \let\datagidx@location@format\datagidx@prev@format
       \let\datagidx@location@target\datagidx@prev@target
     }
    }
    {
%    \end{macrocode}
% We don't have a sequence, so do the previous location.
%    \begin{macrocode}
      \__datagidx_do_prev_location:
    }
%    \end{macrocode}
% Update previous location macros to this location.
%    \begin{macrocode}
  \let\datagidx@prev@location\datagidx@current@location
  \let\datagidx@prev@format\datagidx@current@format
  \let\datagidx@prev@prefix\datagidx@current@prefix
  \let\datagidx@prev@locationformat\datagidx@current@locationformat
  \let\datagidx@prev@locationstring\datagidx@current@locationstring
  \let\datagidx@prev@target\datagidx@current@target
  \int_set_eq:NN
    \l__datagidx_prevloc_int
    \l__datagidx_currentloc_int
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxLocationSep}
% Separator between locations.
%    \begin{macrocode}
\newcommand*{\DTLgidxLocationSep}{, ~ }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgidxLocationF}
% How to format a location list consisting of only two locations.
%    \begin{macrocode}
\newcommand*{\DTLgidxLocationF}[2]{%
  #1\DTLgidxLocationSep#2%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgidxLocationFF}
% How to format a location list consisting of three or more locations.
%    \begin{macrocode}
\newcommand*{\DTLgidxLocationFF}[2]{%
  #1--#2%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\do@prevlocation}
% Do the previous location in the current list.
% Version 3.0: replaced \cs{do@prevlocation} with:
%\changes{3.0}{2025-03-03}{removed}
%    \begin{macrocode}
\cs_new:Nn \__datagidx_do_prev_location:
{
%    \end{macrocode}
% Have we come to the end of a sequence?
%    \begin{macrocode}
  \int_compare:nNnTF
    { \l__datagidx_startloc_int} = { -1 }
  {
%    \end{macrocode}
% Not the end of a sequence.
%    \begin{macrocode}
     \tl_if_empty:NF \datagidx@prev@locationstring
     {
       \datagidx@location@sep
       \datagidxlink{\datagidx@prev@target}%
       {
         \__datagidx_formatlocation:VV
           \datagidx@prev@format
           \datagidx@prev@locationstring 
       }
       \def\datagidx@location@sep{\DTLgidxLocationSep}%
     }
  }
  {
%    \end{macrocode}
% At the end of a sequence.
%    \begin{macrocode}
    \datagidx@location@sep
    \do@locrange
    \def\datagidx@location@sep{\DTLgidxLocationSep}%
    \int_set:Nn \l__datagidx_startloc_int { -1 }
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\do@locrange}
% Format the location range.
%    \begin{macrocode}
\newcommand*{\do@locrange}{%
%    \end{macrocode}
% Are the start and end locations 2 or more apart?
%    \begin{macrocode}
  \int_compare:nNnTF
   {\l__datagidx_prevloc_int}
     >
   { \l__datagidx_startloc_int + 1}%
  {%
%    \end{macrocode}
% Yes, they are, so form a range:
%    \begin{macrocode}
     \DTLgidxLocationFF
       {%
         \datagidxlink{\datagidx@location@target}%
         {%
           \__datagidx_formatlocation:VV
             \datagidx@location@format
             \datagidx@location@startval 
         }%
       }%
       {%
         \datagidxlink{\datagidx@prev@target}%
         {%
           \__datagidx_formatlocation:VV
             \datagidx@prev@format
             \datagidx@prev@locationstring 
         }%
       }%
  }%
  {%
%    \end{macrocode}
% No, they aren't so don't form a range:
%\changes{2.15}{2013-07-10}{removed spurious space}
%    \begin{macrocode}
     \DTLgidxLocationF
       {%
         \datagidxlink{\datagidx@location@target}%
         {%
           \__datagidx_formatlocation:VV
             \datagidx@location@format
             \datagidx@location@startval 
         }%
       }%
       {%
         \datagidxlink{\datagidx@prev@target}%
         {%
           \__datagidx_formatlocation:VV
             \datagidx@prev@format
             \datagidx@prev@locationstring 
         }%
       }%
  }%
}
%    \end{macrocode}
%\end{macro}
%
%\section{Defining New Glossary/Index Databases}
%
%\begin{macro}{\datagidx@defaultdatabase}
% The default database to which terms should be added.
%\changes{3.0}{2025-03-03}{replaced with
%\cs{l\_\_datagidx\_default\_database\_tl}}
% Version 3.0: replaced \cs{datagidx@defaultdatabase} with:
%    \begin{macrocode}
\tl_new:N \l__datagidx_default_database_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxSetDefaultDB}
% Allow user to set the default database. Note that the name isn't
% expanded yet to allow for a placeholder command.
%    \begin{macrocode}
\newcommand*{\DTLgidxSetDefaultDB}[1]{%
  \tl_set:Nn \l__datagidx_default_database_tl { #1 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\ifdatagidxbalance}
%\changes{2.28}{2017-11-10}{new}
%    \begin{macrocode}
\newif\ifdatagidxbalance
\datagidxbalancetrue
%    \end{macrocode}
%\end{macro}
% Default style is `index':
%    \begin{macrocode}
\newcommand*{\datagidx@style}{index}
%    \end{macrocode}
% Define conditional to determine whether or not to show group
% headers and do sep. (Default is false.)
%\begin{macro}{\ifdatagidxshowgroups}
%    \begin{macrocode}
\newif\ifdatagidxshowgroups
\newcommand*{\datagidx@showgroups}{false}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\newgidx}
%\begin{definition}
% \cs{newgloss}\oarg{options}\marg{database name}\marg{title}
%\end{definition}
% Define \cs{newgidx} if it hasn't already been defined by the
% `highopt' optimize setting.
%    \begin{macrocode}
\ifundef\newgidx
{%
  \newcommand*{\newgidx}{\datagidx@newgidx}
}%
{}
%    \end{macrocode}
% May only be used in the preamble (otherwise the entries will be
% undefined when their locations are read from the aux file).
%    \begin{macrocode}
\@onlypreamble\newgidx
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@highopt@newgidx}
% The behaviour of \cs{newgidx} when the `highopt' optimize option has
% been set.
%    \begin{macrocode}
\NewDocumentCommand \datagidx@highopt@newgidx { o m m }
{
%    \end{macrocode}
% Get the file name:
%    \begin{macrocode}
  \tl_set:Nx \datagidx@indexfilename
    { \text_purify:n { \datagidxhighoptfilename{ #2 } } }
%    \end{macrocode}
% Has the file been created?
%    \begin{macrocode}
  \IfFileExists{\datagidx@indexfilename}%
  {%
%    \end{macrocode}
% File does exists. Load it.
%    \begin{macrocode}
    \DTLread[format=dbtex] { \datagidx@indexfilename }
%    \end{macrocode}
% If the file was created pre v3.0, the new columns will be missing.
%    \begin{macrocode}
    \__datagidx_update_loaded_data:n { #2 }
%    \end{macrocode}
% Update the `datagidx' database.
%    \begin{macrocode}
    \group_begin:
      \IfValueT { #1 }
       {
         \keys_set_groups:nnn
          { datatool / index } { newgloss } { #1 }
       }
      \datagidx@newgidx@update {#2} {#3}
    \group_end:
  }
  {
%    \end{macrocode}
% File doesn't exist. Behave as normal.
%    \begin{macrocode}
    \datagidx@newgidx[#1]{#2}{#3}
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\loadgidx}
%\begin{definition}
%\cs{loadgidx}\oarg{options}\marg{filename}\marg{title}
%\end{definition}
%\changes{2.15}{2013-07-10}{new}
%\changes{3.0}{2025-03-03}{made robust}
% Loads a datagidx database.
%    \begin{macrocode}
\NewDocumentCommand \loadgidx { O{} m m }
{%
%    \end{macrocode}
% Load database:
%    \begin{macrocode}
  \DTLread [ format = dbtex ] { #2 }
%    \end{macrocode}
% Update the `datagidx' database. (Assume database is already
% sorted.)
%    \begin{macrocode}
  \__datagidx_update_loaded_data:n { \dtllastloadeddb }
  \bgroup
    \keys_set_groups:nnn
     { datatool / index } { newgloss }
      { sort = {} , #1 }
    \expandafter\datagidx@newgidx@update\expandafter
      {\dtllastloadeddb}{#3}%
  \egroup
%    \end{macrocode}
% Set this as the default database:
%    \begin{macrocode}
  \tl_set_eq:NN
    \l__datagidx_default_database_tl
    \dtllastloadeddb
%    \end{macrocode}
% Assign labels to this database.
%    \begin{macrocode}
  \dtlforcolumn{\Label}{\dtllastloadeddb}{Label}%
  {%
     \__datagidx_assign_label_db:nn
       { \Label } { \dtllastloadeddb }
  }%
}
%    \end{macrocode}
% May only be used in the preamble (otherwise the entries will be
% undefined when their locations are read from the aux file).
%    \begin{macrocode}
\@onlypreamble\loadgidx
%    \end{macrocode}
%\end{macro}
%Marker for Used field:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_used:n { #1 } 
%    \end{macrocode}
%Marker for LetterGroup field:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_letter_group:n { #1 } 
%    \end{macrocode}
%Make sure the letter group doesn't expand.
%    \begin{macrocode}
\cs_new:Nn \__datagidx_write_letter_group:n
{
  \tl_to_str:n { #1 } 
} 
%    \end{macrocode}
%Marker for Location field:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_location:n { \exp_not:n { #1 } } 
%    \end{macrocode}
%Extract the location from the special markers.
%    \begin{macrocode}
\cs_new:Nn \__datagidx_unwrap_location:N
{
  \exp_args:NV
     \tl_if_head_eq_meaning:nNT #1
     { \dtlspecialvalue }
   {
     \group_begin:
      \cs_set_eq:NN \__datagidx_location:n \exp_not:n
      \cs_set_eq:NN \dtlspecialvalue \use:n
      \exp_args:NNNx
     \group_end:
       \tl_set:Nn #1 { #1 }
  }
}
%    \end{macrocode}
%
%
%\begin{macro}{\datagidx@newgidx}
% The normal behaviour of \cs{newgidx}
%    \begin{macrocode}
\NewDocumentCommand \datagidx@newgidx { o m m }
{
\bgroup
  \IfValueT { #1 }
   {
%    \end{macrocode}
%NB \cs{keys\_set\_filter:nnnN} is being deprecated in favour of
%\cs{keys\_set\_exclude\_groups:nnn} (\sty{l3kernel} 2024-01-22).
%    \begin{macrocode}
     \keys_set_filter:nnnN
       { datatool / index } { general , print-only } { #1 }
       \l__datagidx_remainder_tl
     \tl_if_empty:NF \l__datagidx_remainder_tl
      {
        \PackageError { datagidx }
         {
           Invalid ~ \token_to_str:N \newgidx \c_space_tl option(s): ~ 
            \tl_to_str:N \l__datagidx_remainder_tl
         }
         {
           The ~ listed ~ option ~ or ~ options ~ can't ~ be ~ passed ~ 
           to ~ \token_to_str:N \newgidx . \MessageBreak 
           Try ~ \token_to_str:N \DTLsetup
            { index = { \tl_to_str:N \l__datagidx_remainder_tl } } ~
           or ~ pass ~ the ~ option(s) ~ to ~ 
           \token_to_str:N \printterms \c_space_tl instead
         }
      }
   }
%    \end{macrocode}
% If no default database has been identified, set the default to
% this database.
%    \begin{macrocode}
  \tl_if_empty:NT \l__datagidx_default_database_tl
   {
     \tl_gset:Nx \l__datagidx_default_database_tl { #2 }
   }
  \DTLgnewdb{#2}%
  \__datagidx_add_column:nn {#2} {Label}
  \__datagidx_add_column:nnn
    {#2} {Used} { \c_datatool_integer_int }
  \__datagidx_add_column:nn {#2} {Location}
  \__datagidx_add_column:nn {#2} {UnsafeLocation}
  \__datagidx_add_column:nn {#2} {CurrentLocation}
  \__datagidx_add_column:nnn {#2} {FirstId} { \c_datatool_integer_int }
  \__datagidx_add_column:nn {#2} {Name}
  \__datagidx_add_column:nn {#2} {Text}
  \__datagidx_add_column:nn {#2} {Plural}
  \__datagidx_add_column:nn {#2} {Parent}
  \__datagidx_add_column:nn {#2} {Child}
  \__datagidx_add_column:nn {#2} {Description}
  \__datagidx_add_column:nn {#2} {HierSort}
  \__datagidx_add_column:nn {#2} {Sort}
  \__datagidx_add_column:nn {#2} {LetterGroup}
  \__datagidx_add_column:nn {#2} {Symbol}
  \__datagidx_add_column:nn {#2} {Long}
  \__datagidx_add_column:nn {#2} {LongPlural}
  \__datagidx_add_column:nn {#2} {Short}
  \__datagidx_add_column:nn {#2} {ShortPlural}
  \__datagidx_add_column:nn {#2} {See}
  \__datagidx_add_column:nn {#2} {SeeAlso}
  \datagidx@newgidx@update{#2}{#3}%
\egroup
}
%    \end{macrocode}
%\end{macro}
%Assume all columns contain strings.
%    \begin{macrocode}
\cs_new:Nn \__datagidx_add_column:nn
{
  \__datagidx_add_column:nnn
   { #1 } { #2 } { \c_datatool_string_int }
}
\cs_new:Nn \__datagidx_add_column:nnn
{
  \__datatool_add_column_with_header:nxxx
    { #1 } { #2 } { \int_eval:n { #3 } } { #2 }
}
%    \end{macrocode}
%
% Add new fields in the event a pre v3.0 database has been loaded:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_update_loaded_data:n
{
  \datatool_if_has_key:nnF { #1 } { HierSort }
  {
    \__datagidx_add_column:nn { #1 } { HierSort }
  }
  \datatool_if_has_key:nnF { #1 } { LetterGroup }
  {
    \__datagidx_add_column:nn { #1 } { LetterGroup }
  }
  \datatool_if_has_key:nnF { #1 } { UnsafeLocation }
  {
    \__datagidx_add_column:nn {#1 } { UnsafeLocation }
  }
}
%    \end{macrocode}
%
%\begin{macro}{\datagidx@newgidx@update}
% Update the `datagidx' database.
%    \begin{macrocode}
\newcommand*{\datagidx@newgidx@update}[2]{%
  \group_begin:
    \bool_set_true:N \l__datatool_db_global_bool
    \dtlexpandnewvalue
    \DTLnewrow {datagidx}
    \DTLnewdbentry {datagidx} {Glossary}
     { \exp_not:n { #1 } }
    \DTLnewdbentry {datagidx} {Title}
     { \exp_not:n { #2 } }
    \DTLnewdbentry {datagidx} {Heading}
     { \exp_not:V \l__datagidx_heading_tl }
    \DTLnewdbentry{datagidx}{PostHeading}
      {\exp_not:V \l__datagidx_post_heading_tl }
    \DTLnewdbentry {datagidx} {MultiCols}
      {\exp_not:V \l__datagidx_multicols_tl }
    \DTLnewdbentry{datagidx}{Sort}
     { \exp_not:V \l__datagidx_sort_tl }
    \DTLnewdbentry{datagidx}{Style}{\expandonce\datagidx@style}%
    \DTLnewdbentry{datagidx}{ShowGroups}{\expandonce\datagidx@showgroups}%
  \group_end:
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\printterms@condition}
%Version 3.0: replaced \cs{printterms@condition} with a function
%instead.
%    \begin{macrocode}
\cs_new:Npn \__datagidx_filter:T #1 { #1 }
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\bool_new:N \l__datagidx_childsort_bool
\bool_set_true:N \l__datagidx_childsort_bool
%    \end{macrocode}
%
%\section{General Options}
%    \begin{macrocode}
\clist_new:N \l__datagidx_styles_clist
%    \end{macrocode}
%Define options for use in the "index" setting in \cs{DTLsetup}:
%    \begin{macrocode}
\keys_define:nn { datatool / index }
{
%    \end{macrocode}
%The default index database name:
%    \begin{macrocode}
  database .tl_set:N =
    \l__datagidx_default_database_tl ,
  database .groups:n = { print-only },
%    \end{macrocode}
%Number of columns in the index:
%    \begin{macrocode}
  columns .code:n =
    { \DTLgidxSetColumns { #1 } },
  columns .groups:n = { print-only },
%    \end{macrocode}
%Symbol width:
%    \begin{macrocode}
  symbolwidth .dim_set:N =
    \datagidxsymbolwidth ,
  symbolwidth .groups:n = { print-only },
%    \end{macrocode}
%Synonym:
%    \begin{macrocode}
  symbol-width .dim_set:N =
    \datagidxsymbolwidth ,
  symbol-width .groups:n = { print-only },
%    \end{macrocode}
%Location width:
%    \begin{macrocode}
  locationwidth .dim_set:N =
    \datagidxlocationwidth ,
  locationwidth .groups:n = { print-only },
%    \end{macrocode}
%Synonym:
%    \begin{macrocode}
  location-width .dim_set:N =
    \datagidxlocationwidth ,
  location-width .groups:n = { print-only },
%    \end{macrocode}
%Child style (should the child's name be shown):
%    \begin{macrocode}
  child .choice: ,
  child .groups:n = { print-only },
  child / named .code:n =
   { \__datagidx_set_child_style_named: } ,
  child / noname .code:n =
   { \__datagidx_set_child_style_noname: } ,
%    \end{macrocode}
% Child sort. NB the child entries will be sorted by HierSort
%by default, along with all the parent entries. This option
%Governs whether or not \cs{datagidx@foreachchild} follows the
%ordering from the database (which will in the sort order, if the
%database has been sorted.)
%    \begin{macrocode}
  childsort .bool_set:N = \l__datagidx_childsort_bool ,
  childsort .groups:n = { print-only } ,
%    \end{macrocode}
%Synonym.
%    \begin{macrocode}
  child-sort .bool_set:N = \l__datagidx_childsort_bool ,
  child-sort .groups:n = { print-only } ,
%    \end{macrocode}
%Should a case change be applied to the name?
%    \begin{macrocode}
  namecase .choice: ,
  namecase .groups:n = { print-only },
  namecase / nochange .code:n =
    { \let \DTLgidxNameCase \use:n } ,
  namecase / uc .code:n =
    { \renewcommand \DTLgidxNameCase [ 1 ] { \text_uppercase:n { ##1 } } } ,
  namecase / lc .code:n =
    { \renewcommand \DTLgidxNameCase [ 1 ] { \text_lowercase:n { ##1 } } } ,
  namecase / firstuc .code:n =
    { \renewcommand \DTLgidxNameCase [ 1 ] { \xmakefirstuc { ##1 } } } ,
  namecase / capitalise .code:n =
    { \renewcommand \DTLgidxNameCase [ 1 ] { \xcapitalisewords { ##1 } } } ,
%    \end{macrocode}
%Synonym:
%    \begin{macrocode}
  name-case .choice: ,
  name-case .groups:n = { print-only },
  name-case / nochange .code:n =
    { \let \DTLgidxNameCase \use:n } ,
  name-case / uc .code:n =
    { \renewcommand \DTLgidxNameCase [ 1 ] { \text_uppercase:n { ##1 } } } ,
  name-case / lc .code:n =
    { \renewcommand \DTLgidxNameCase [ 1 ] { \text_lowercase:n { ##1 } } } ,
  name-case / firstuc .code:n =
    { \renewcommand \DTLgidxNameCase [ 1 ] { \xmakefirstuc { ##1 } } } ,
  name-case / capitalise .code:n =
    { \renewcommand \DTLgidxNameCase [ 1 ] { \xcapitalisewords { ##1 } } } ,
%    \end{macrocode}
%Font to apply to the name:
%    \begin{macrocode}
  namefont .code:n =
    { \renewcommand*\DTLgidxNameFont [ 1 ] { { #1 { ##1 } } } } ,
  namefont .groups:n = { print-only },
%    \end{macrocode}
%Synonym:
%    \begin{macrocode}
  name-font .code:n =
    { \renewcommand*\DTLgidxNameFont [ 1 ] { { #1 { ##1 } } } } ,
  name-font .groups:n = { print-only },
%    \end{macrocode}
%Content to be placed after the name:
%    \begin{macrocode}
  postname .code:n =
    { \renewcommand \DTLgidxPostName { #1 } } ,
  postname .groups:n = { print-only },
%    \end{macrocode}
%Synonym:
%    \begin{macrocode}
  post-name .code:n =
    { \renewcommand \DTLgidxPostName { #1 } } ,
  post-name .groups:n = { print-only },
%    \end{macrocode}
%Content to be placed after the description:
%    \begin{macrocode}
  postdesc .choice: ,
  postdesc / none .code:n =
   { \tl_clear:N \DTLgidxPostDescription } ,
  postdesc / dot .code:n =
   { \tl_set:Nn \DTLgidxPostDescription { . } } ,
  postdesc .groups:n = { print-only },
%    \end{macrocode}
%Synonym:
%    \begin{macrocode}
  post-desc .choice: ,
  post-desc / none .code:n =
   { \tl_clear:N \DTLgidxPostDescription } ,
  post-desc / dot .code:n =
   { \tl_set:Nn \DTLgidxPostDescription { . } } ,
  post-desc .groups:n = { print-only },
%    \end{macrocode}
%Content to be placed before the location:
%    \begin{macrocode}
  prelocation .choice: ,
  prelocation .groups:n = { print-only },
  prelocation / none .code:n = 
   { \tl_clear:N \DTLgidxPreLocation } ,
  prelocation / enspace .code:n = 
   { \tl_set:Nn \DTLgidxPreLocation { \enspace } } ,
  prelocation / space .code:n = 
   { \tl_set:Nn \DTLgidxPreLocation { ~ } } ,
  prelocation / dotfill .code:n = 
   { \tl_set:Nn \DTLgidxPreLocation { \dotfill } } ,
  prelocation / hfill .code:n = 
   { \tl_set:Nn \DTLgidxPreLocation { \hfill } } ,
%    \end{macrocode}
%Synonym:
%    \begin{macrocode}
  pre-location .choice: ,
  pre-location .groups:n = { print-only },
  pre-location / none .code:n = 
   { \tl_clear:N \DTLgidxPreLocation } ,
  pre-location / enspace .code:n = 
   { \tl_set:Nn \DTLgidxPreLocation { \enspace } } ,
  pre-location / space .code:n = 
   { \tl_set:Nn \DTLgidxPreLocation { ~ } } ,
  pre-location / dotfill .code:n = 
   { \tl_set:Nn \DTLgidxPreLocation { \dotfill } } ,
  pre-location / hfill .code:n = 
   { \tl_set:Nn \DTLgidxPreLocation { \hfill } } ,
%    \end{macrocode}
%Location style (don't show, only show first, or show list):
%    \begin{macrocode}
  location .choice: ,
  location .groups:n = { print-only },
  location / hide .code:n =
    { \tl_clear:N \DTLgidxLocation },
  location / list .code:n =
    { \tl_set:Nn \DTLgidxLocation { \dtldolocationlist } },
  location / first .code:n =
    { \tl_set:Nn \DTLgidxLocation { \dtldofirstlocation } },
%    \end{macrocode}
%Cross-reference (see) style:
%    \begin{macrocode}
  see .choice: ,
  see .groups:n = { print-only },
  see / comma .code:n =
    {
      \renewcommand \DTLgidxSee
       {
         \datatool_if_null_or_empty:NF \See
          {
           , ~ \DTLgidxFormatSee { \seename } { \See }
          }
       }
    } ,
  see / brackets .code:n =
    {
      \renewcommand \DTLgidxSee
       {
         \datatool_if_null_or_empty:NF \See
          {
            \space ( \DTLgidxFormatSee { \seename } { \See } )
          }
       }
    } ,
  see / dot .code:n =
    {
      \renewcommand \DTLgidxSee
       {
         \datatool_if_null_or_empty:NF \See
          {
            .  ~
            \DTLgidxFormatSee
              { \xmakefirstuc { \seename } } { \See }
          }
       }
    } ,
  see / space .code:n =
    {
      \renewcommand \DTLgidxSee
       {
         \datatool_if_null_or_empty:NF \See
          {
            \space  
            \DTLgidxFormatSee { \seename } { \See }
          }
       }
    } ,
  see / nosep .code:n =
    {
      \renewcommand \DTLgidxSee
       {
         \datatool_if_null_or_empty:NF \See
          {
            \DTLgidxFormatSee { \seename } { \See }
          }
       }
    } ,
  see / semicolon .code:n =
    {
      \renewcommand \DTLgidxSee
       {
         \datatool_if_null_or_empty:NF \See
          {
            ; ~ \DTLgidxFormatSee { \seename } { \See }
          }
       }
    } ,
  see / location .code:n =
    {
      \renewcommand \DTLgidxSee
       {
         \datatool_if_null_or_empty:NF \See
          {
            \DTLgidxPreLocation 
            \DTLgidxFormatSee { \seename } { \See }
          }
       }
    } ,
%    \end{macrocode}
%Style of the symbol and description:
%    \begin{macrocode}
  symboldesc .choice: ,
  symboldesc .groups:n = { print-only },
  symboldesc / symbol .code:n =
   {
     \__datagix_set_symbol_only:
   } ,
  symboldesc / desc .code:n =
   {
     \__datagix_set_desc_only:
   } ,
  symboldesc / (symbol) ~ desc .code:n =
   {
     \__datagix_set_symbol_paren_desc:
   } ,
  symboldesc / desc ~ (symbol) .code:n =
   {
     \__datagix_set_desc_symbol_paren:
   } ,
  symboldesc / symbol ~ desc .code:n =
   {
     \__datagix_set_symbol_desc:
   } ,
  symboldesc / desc ~ symbol .code:n =
   {
     \__datagix_set_desc_symbol:
   } ,
%    \end{macrocode}
%Synonym:
%    \begin{macrocode}
  symbol-desc .choice: ,
  symbol-desc .groups:n = { print-only },
  symbol-desc / symbol .code:n =
   {
     \__datagix_set_symbol_only:
   } ,
  symbol-desc / desc .code:n =
   {
     \__datagix_set_desc_only:
   } ,
  symbol-desc / (symbol) ~ desc .code:n =
   {
     \__datagix_set_symbol_paren_desc:
   } ,
  symbol-desc / desc ~ (symbol) .code:n =
   {
     \__datagix_set_desc_symbol_paren:
   } ,
  symbol-desc / symbol ~ desc .code:n =
   {
     \__datagix_set_symbol_desc:
   } ,
  symbol-desc / desc ~ symbol .code:n =
   {
     \__datagix_set_desc_symbol:
   } ,
%    \end{macrocode}
%Compositor:
%    \begin{macrocode}
  compositor .tl_set:N = \l__datagidx_compositor_tl ,
  compositor .value_required:n = true ,
  compositor .groups:n = { general } ,
%    \end{macrocode}
%Counter:
%    \begin{macrocode}
  counter .code:n =
   { 
     \__datagidx_set_counter:n { #1 }
   },
  counter .value_required:n = true ,
  counter .groups:n = { general } ,
%    \end{macrocode}
%Switch warnings on or off:
%    \begin{macrocode}
  warn .bool_set:N = \l__datagidx_warn_bool ,
  warn .groups:n = { general } ,
%    \end{macrocode}
%Index heading:
%    \begin{macrocode}
  heading .code:n =
   {
     \clist_put_right:Nn \l__datagidx_styles_clist
       { heading = { #1 } }
     \tl_set:Nn \l__datagidx_heading_tl { #1 }
   } ,
  heading .groups:n = { newgloss, print } ,
%    \end{macrocode}
%After index heading:
%    \begin{macrocode}
  postheading .code:n =
   {
     \clist_put_right:Nn \l__datagidx_styles_clist
       { postheading = { #1 } }
     \tl_set:Nn \l__datagidx_post_heading_tl { #1 }
   },
  postheading .groups:n = { newgloss, print } ,
%    \end{macrocode}
%Synonym:
%    \begin{macrocode}
  post-heading .code:n =
   {
     \clist_put_right:Nn \l__datagidx_styles_clist
       { postheading = { #1 } }
     \tl_set:Nn \l__datagidx_post_heading_tl { #1 }
   },
  post-heading .groups:n = { newgloss, print } ,
%    \end{macrocode}
%Balance columns:
%    \begin{macrocode}
  balance .choice: ,
  balance .groups:n = { newgloss, print } ,
  balance / true .code:n = 
   {
     \clist_put_right:Nn \l__datagidx_styles_clist
       { balance = true }
     \tl_set:Nn \l__datagidx_multicols_tl { multicols }
     \datagidxbalancetrue
   } ,
  balance / false .code:n = 
   {
     \clist_put_right:Nn \l__datagidx_styles_clist
       { balance = false }
     \tl_set:Nn \l__datagidx_multicols_tl { multicols* }
     \datagidxbalancefalse
   } ,
  balance .default:n = { true } ,
%    \end{macrocode}
%Sort. The value should be the code to sort the glossary/index
%database. The database should be referenced with \cs{DTLgidxCurrentdb}:
%    \begin{macrocode}
  sort .code:n =
   {
     \clist_put_right:Nn \l__datagidx_styles_clist
       { sort = { #1 } }
     \tl_set:Nn \l__datagidx_sort_tl { #1 }
   } ,
  sort .groups:n = { newgloss, print } ,
%    \end{macrocode}
%Style:
%    \begin{macrocode}
  style .code:n =
    {
      \clist_put_right:Nn \l__datagidx_styles_clist
       { style = { #1 } }
      \tl_set:Nn \datagidx@style { #1 }
    } ,
  style .groups:n = { newgloss, print } ,
%    \end{macrocode}
%Show groups:
%    \begin{macrocode}
  showgroups .choice: ,
  showgroups .groups:n = { newgloss, print } ,
  showgroups / true .code:n =
    {
      \clist_put_right:Nn \l__datagidx_styles_clist
        { showgroups = true }
      \tl_set:Nn \datagidx@showgroups { true }
    } ,
  showgroups / false .code:n =
    {
      \clist_put_right:Nn \l__datagidx_styles_clist
        { showgroups = false }
      \tl_set:Nn \datagidx@showgroups { false }
    } ,
  showgroups .default:n = { true } ,
%    \end{macrocode}
%Synonym:
%    \begin{macrocode}
  show-groups .choice: ,
  show-groups .groups:n = { newgloss, print } ,
  show-groups / true .code:n =
    {
      \clist_put_right:Nn \l__datagidx_styles_clist
        { showgroups = true }
      \tl_set:Nn \datagidx@showgroups { true }
    } ,
  show-groups / false .code:n =
    {
      \clist_put_right:Nn \l__datagidx_styles_clist
        { showgroups = false }
      \tl_set:Nn \datagidx@showgroups { false }
    } ,
  show-groups .default:n = { true } ,
%    \end{macrocode}
%Condition:
%    \begin{macrocode}
  condition .code:n =
    {
      \cs_set:Npn \__datagidx_filter:T ##1
       {
         \ifthenelse { #1 } { ##1 } { }
       }
    } ,
  condition .groups:n = { print-only } ,
  include-if .cs_set:Np = \__datagidx_filter:T #1,
  include-if .groups:n = { print-only } ,
  include-if-fn .code:n =
   {
     \cs_set_eq:NN \__datagidx_filter:T #1
   },
  include-if-fn .groups:n = { print-only } ,
}
%    \end{macrocode}
% NB package options draft, final and optimize not available.
%
% Allow these keys to be set in \verb|\DTLsetup{index={...}}|
%    \begin{macrocode}
\keys_define:nn { datatool }
{ 
  index .code:n = { \keys_set:nn { datatool / index } { #1 } }
}
%    \end{macrocode}
%Used to store filtered settings:
%    \begin{macrocode}
\tl_new:N \l__datagidx_remainder_tl
%    \end{macrocode}
%
%\section{Defining New Terms}
%
%\subsection{Options}
% Options for \cs{newterm}:
%
%The internal commands all need to be in the form
%\cs{l\_\_datagidx\_term\_\meta{key}\_tl} to work with
%\cs{newtermfield}
%\begin{macro}{\newterm@label}
%Version 3.0: replaced \cs{newterm@label} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datagidx\_term\_label\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_label_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\newterm@parent}
%Version 3.0: replaced \cs{newterm@parent} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datagidx\_term\_parent\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_parent_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\newterm@name}
%Version 3.0: replaced \cs{newterm@name} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datagidx\_term\_name\_tl}}
%This doesn't have a corresponding option since the name is the
%mandatory argument of \cs{newterm}:
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_name_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\newterm@text}
%Version 3.0: replaced \cs{newterm@text} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datagidx\_term\_text\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_text_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\newterm@description}
%Version 3.0: replaced \cs{newterm@description} with:
%\changes{3.0}{2025-03-03}{replaced with
%\cs{l\_\_datagidx\_term\_description\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_description_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\newterm@plural}
%Version 3.0: replaced \cs{newterm@plural} with:
%\changes{3.0}{2025-03-03}{replaced with
%\cs{l\_\_datagidx\_term\_plural\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_plural_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\newterm@sort}
%Version 3.0: replaced \cs{newterm@sort} with:
%\changes{3.0}{2025-03-03}{replaced with
%\cs{l\_\_datagidx\_term\_sort\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_sort_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\newterm@symbol}
%Version 3.0: replaced \cs{newterm@symbol} with:
%\changes{3.0}{2025-03-03}{replaced with
%\cs{l\_\_datagidx\_term\_symbol\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_symbol_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\newterm@database}
%Version 3.0: replaced \cs{newterm@database} with:
%\changes{3.0}{2025-03-03}{replaced with
%\cs{l\_\_datagidx\_term\_database\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_database_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\newterm@long}
%Version 3.0: replaced \cs{newterm@long} with:
%\changes{3.0}{2025-03-03}{replaced with
%\cs{l\_\_datagidx\_term\_long\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_long_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\newterm@short}
%Version 3.0: replaced \cs{newterm@short} with:
%\changes{3.0}{2025-03-03}{replaced with
%\cs{l\_\_datagidx\_term\_short\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_short_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\newterm@longplural}
%Version 3.0: replaced \cs{newterm@longplural} with:
%\changes{3.0}{2025-03-03}{replaced with
%\cs{l\_\_datagidx\_term\_longplural\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_longplural_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\newterm@shortplural}
%Version 3.0: replaced \cs{newterm@shortplural} with:
%\changes{3.0}{2025-03-03}{replaced with
%\cs{l\_\_datagidx\_term\_shortplural\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_shortplural_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\newterm@see}
% ``see'' should not be used with a location list. If you have a
% location list and want a cross-reference use ``see also'' instead.
%Version 3.0: replaced \cs{newterm@see} with:
%\changes{3.0}{2025-03-03}{replaced with
%\cs{l\_\_datagidx\_term\_see\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_see_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\newterm@seealso}
% ``see also'' should be used with a location list (or with child
% entries with location lists). If an entry has no location list and
% not child entries use ``see'' instead.
%Version 3.0: replaced \cs{newterm@seealso} with:
%\changes{3.0}{2025-03-03}{replaced with
%\cs{l\_\_datagidx\_term\_seealso\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_seealso_tl
%    \end{macrocode}
%\end{macro}
%The following commands allow users to, say, specify the name as
% \meta{name}\ics{glsadd}\marg{other label} without having to specify 
% the label when defining a term.
% Assign the label token list variable:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_create_label:n
{
 \group_begin:
   \cs_set_eq:NN \glsadd \DTLgidxGobble
%    \end{macrocode}
% Strip common formatting commands.
%NB v3.0: \verb|\text_purify:n| should now deal with these.
%    \begin{macrocode}
   \cs_set_eq:NN \MakeUppercase \DTLgidxNoFormat
   \cs_set_eq:NN \MakeTextUppercase \DTLgidxNoFormat
   \cs_set_eq:NN \MakeLowercase \DTLgidxNoFormat
   \cs_set_eq:NN \MakeTextLowercase \DTLgidxNoFormat
   \cs_set_eq:NN \acronymfont \DTLgidxNoFormat
   \cs_set_eq:NN \textrm \DTLgidxNoFormat
   \cs_set_eq:NN \texttt \DTLgidxNoFormat
   \cs_set_eq:NN \textsf \DTLgidxNoFormat
   \cs_set_eq:NN \textsc \DTLgidxNoFormat
   \cs_set_eq:NN \textbf \DTLgidxNoFormat
   \cs_set_eq:NN \textmd \DTLgidxNoFormat
   \cs_set_eq:NN \textit \DTLgidxNoFormat
   \cs_set_eq:NN \textsl \DTLgidxNoFormat
   \cs_set_eq:NN \emph \DTLgidxNoFormat
   \cs_set_eq:NN \textsuperscript \DTLgidxNoFormat
%    \end{macrocode}
% Convert character commands like \cs{\&}.
%    \begin{macrocode}
  \datagidxconvertchars
%    \end{macrocode}
% Strip \cs{ensuremath}.
%    \begin{macrocode}
   \cs_set_eq:NN \ensuremath \DTLgidxNoFormat
%    \end{macrocode}
% Ensure that inversions are dealt with for the label.
%    \begin{macrocode}
   \cs_set_eq:NN \DTLgidxParen \use_none:n
   \cs_set_eq:NN \DTLgidxName \use_ii:nn 
   \cs_set_eq:NN \DTLgidxPlace \datagidx@invert
   \cs_set_eq:NN \DTLgidxSubject \datagidx@invert
   \cs_set_eq:NN \DTLgidxOffice \use_ii:nn
   \cs_set_eq:NN \DTLgidxParticle \datagidx@bothoftwo
   \cs_set_eq:NN \__datagidx_punc:n \use_none:n
%    \end{macrocode}
% Convert Greek maths (such as \cs{alpha}) to text.
%    \begin{macrocode}
   \datagidxwordifygreek
%    \end{macrocode}
% Allow user to hook into this.
%    \begin{macrocode}
   \newtermlabelhook
%    \end{macrocode}
%Using protected expansion first allows for the localised change to
%\cs{ensuremath}:
%    \begin{macrocode}
    \protected@edef \l__datagidx_term_label_tl { #1 }
%    \end{macrocode}
% Strip punctuation characters that are likely to cause a problem
% for syntax parsing.
%    \begin{macrocode}
    \regex_replace_all:nnN
        { [,=] } { } \l__datagidx_term_label_tl
   \tl_set:Nx \l__datagidx_term_label_tl
    {
      \text_purify:n { \l__datagidx_term_label_tl }
    }
  \exp_args:NNNV
 \group_end:
    \tl_set:Nn
      \l__datagidx_term_label_tl 
      \l__datagidx_term_label_tl
}
%    \end{macrocode}
% Assign the sort token list variable:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_create_sort:n
{
 \group_begin:
   \cs_set_eq:NN \glsadd \use_none:n
%    \end{macrocode}
% Strip common formatting commands.
%NB v3.0: \verb|\text_purify:n| should now deal with these.
%    \begin{macrocode}
   \cs_set_eq:NN \MakeUppercase \DTLgidxNoFormat
   \cs_set_eq:NN \MakeTextUppercase \DTLgidxNoFormat
   \cs_set_eq:NN \MakeLowercase \DTLgidxNoFormat
   \cs_set_eq:NN \MakeTextLowercase \DTLgidxNoFormat
   \cs_set_eq:NN \acronymfont \DTLgidxNoFormat
   \cs_set_eq:NN \textrm \DTLgidxNoFormat
   \cs_set_eq:NN \texttt \DTLgidxNoFormat
   \cs_set_eq:NN \textsf \DTLgidxNoFormat
   \cs_set_eq:NN \textsc \DTLgidxNoFormat
   \cs_set_eq:NN \textbf \DTLgidxNoFormat
   \cs_set_eq:NN \textmd \DTLgidxNoFormat
   \cs_set_eq:NN \textit \DTLgidxNoFormat
   \cs_set_eq:NN \textsl \DTLgidxNoFormat
   \cs_set_eq:NN \emph \DTLgidxNoFormat
   \cs_set_eq:NN \textsuperscript \DTLgidxNoFormat
%    \end{macrocode}
% Convert character commands like \cs{\&}.
%    \begin{macrocode}
  \datagidxconvertchars
%    \end{macrocode}
% Strip \cs{ensuremath}.
%    \begin{macrocode}
   \cs_set_eq:NN \ensuremath \DTLgidxNoFormat
%    \end{macrocode}
% These commands behave differently for the sort key:
%    \begin{macrocode}
   \cs_set_eq:NN \__datagidx_punc:n \exp_not:n
   \cs_set_eq:NN \DTLgidxName \datagidx@person
   \cs_set_eq:NN \DTLgidxPlace \datagidx@place
   \cs_set_eq:NN \DTLgidxSubject \datagidx@subject
   \cs_set_eq:NN \DTLgidxOffice \datagidx@person
   \cs_set_eq:NN \DTLgidxParen \datagidx@paren
   \cs_set_eq:NN \DTLgidxMac \datagidx@mac
   \cs_set_eq:NN \DTLgidxSaint \datagidx@saint
   \cs_set_eq:NN \DTLgidxIgnore \@gobble
   \cs_set_eq:NN \DTLgidxRank \datagidx@rank
   \cs_set_eq:NN \DTLgidxParticle \datagidx@particle
   \cs_set_eq:NN \DTLgidxNameNum \datagidx@namenum
%    \end{macrocode}
% Convert Greek maths (such as \cs{alpha}) to text.
%    \begin{macrocode}
   \datagidxwordifygreek
%    \end{macrocode}
% Allow user to hook into this.
%    \begin{macrocode}
   \newtermsorthook
%    \end{macrocode}
%Using protected expansion first allows for the localised change to
%\cs{ensuremath}:
%    \begin{macrocode}
   \protected@edef \l__datagidx_term_sort_tl { #1 }
  \exp_args:NNNV
 \group_end:
    \tl_set:Nn
      \l__datagidx_term_sort_tl
      \l__datagidx_term_sort_tl
}
%    \end{macrocode}
%Variable to store hierarchical sort value.
%    \begin{macrocode}
\tl_new:N \l__datagidx_term_hiersort_tl
%    \end{macrocode}
%
%    \begin{macrocode}
\keys_define:nn { datatool / index / newterm }
{
%    \end{macrocode}
%Labels:
%    \begin{macrocode}
  database .code:n =
   {
     \tl_set:Nx \l__datagidx_term_database_tl 
       { #1 }
   },
  label .code:n =
   {
     \__datagidx_create_label:n { #1 }
   } ,
  parent .code:n =
   {
     \tl_set:Nx \l__datagidx_term_parent_tl
       { #1 }
   } ,
%    \end{macrocode}
%Token lists:
%    \begin{macrocode}
  text .code:n =
    {
      \tl_set:Nn \l__datagidx_term_text_tl { #1 }
      \tl_if_eq:NnT \l__datagidx_term_plural_tl { \c_novalue_tl }
       {
         \tl_set:Nn \l__datagidx_term_plural_tl { #1 s }
       }
    } ,
  description .tl_set:N = \l__datagidx_term_description_tl ,
  plural .tl_set:N = \l__datagidx_term_plural_tl ,
  sort .code:n =
   {
     \__datagidx_create_sort:n { #1 }
   } ,
  symbol .tl_set:N = \l__datagidx_term_symbol_tl ,
  long .code:n = 
   {
     \tl_set:Nn \l__datagidx_term_long_tl { #1 }
     \tl_if_eq:NnT \l__datagidx_term_longplural_tl { \c_novalue_tl }
      {
        \tl_set:Nn \l__datagidx_term_longplural_tl { #1 s }
      }
   } ,
  short .code:n = 
   {
     \tl_set:Nn \l__datagidx_term_short_tl { #1 }
     \tl_if_eq:NnT \l__datagidx_term_shortplural_tl { \c_novalue_tl }
      {
        \tl_set:Nn \l__datagidx_term_shortplural_tl { #1 s }
      }
   } ,
  longplural .tl_set:N = \l__datagidx_term_longplural_tl ,
  shortplural .tl_set:N = \l__datagidx_term_shortplural_tl ,
  see .tl_set:N = \l__datagidx_term_see_tl ,
  seealso .tl_set:N = \l__datagidx_term_seealso_tl ,
}
%    \end{macrocode}
%
%\subsection{New Terms}
%
%\begin{macro}{\newterm@defaultshook}
%Version 3.0: replaced \cs{newterm@defaultshook} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{g\_\_datagidx\_term\_defaults\_tl}}
%    \begin{macrocode}
\tl_new:N \g__datagidx_newterm_defaults_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\newterm@extrafields}
%Version 3.0: replaced \cs{newterm@extrafields} with
%\changes{3.0}{2025-03-03}{replaced with \cs{g\_\_datagidx\_extra\_fields\_tl}}
%    \begin{macrocode}
\tl_new:N \g__datagidx_extra_fields_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxAssignList}
% Assignment list used by \ics{printterms}
%    \begin{macrocode}
\newcommand*{\DTLgidxAssignList}{
  \Name=Name,
  \Description=Description,
  \Used=Used,
  \Symbol=Symbol,
  \Long=Long,
  \Short=Short,
  \LongPlural=LongPlural,
  \ShortPlural=ShortPlural,
  \Location=Location,
  \See=See,
  \SeeAlso=SeeAlso,
  \Text=Text,
  \Plural=Plural,
  \CurrentLocation=CurrentLocation,
  \Label=Label,
  \Parent=Parent, 
  \Children=Child,
  \FirstId=FirstId,
  \HierSort=HierSort,
  \Sort=Sort,
  \datagidxcurrentgroup=LetterGroup
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxtermkeys}
% Keys defined for \cs{newterm} corresponding to fields.
%    \begin{macrocode}
\newcommand*{\datagidxtermkeys}{}
%    \end{macrocode}
%\end{macro}
%
% Access keys corresponding to given fields.
%
%Syntax: \marg{column key}\marg{term key}
%
%Add term key to list and define mapping:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_add_field:nn
{
  \clist_gput_right:Nn \datagidxtermkeys { #2 }
  \tl_gset:cn { g__datagidx_fieldkey_ #1 } { #2 }
  \tl_clear_new:c { l__datagidx_term_ #2 _tl } 
}
%    \end{macrocode}
%Syntax: \marg{column key}\marg{term key}\marg{default val}
%Add term key to list, define mapping and provide the key as an
%option:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_add_field_option:nnnN
{
  \__datagidx_add_field:nn { #1 } { #2 }
  \keys_define:nn { datatool / index / newterm }
  {
    #2 .tl_set:N = #4
  }
  \tl_put_right:Nn \g__datagidx_newterm_defaults_tl
   { 
     \tl_if_eq:NnT #4 { \c_novalue_tl }
      {
        \tl_set:Nx #4 { #3 }
      }
   }
}
\cs_generate_variant:Nn
 \__datagidx_add_field_option:nnnN
 { xxnc }
%    \end{macrocode}
%Predefined fields:
%    \begin{macrocode}
\__datagidx_add_field:nn { Name } { name }
\__datagidx_add_field:nn { Description} { description }
\__datagidx_add_field:nn { Symbol } { symbol }
\__datagidx_add_field:nn { Long } { long }
\__datagidx_add_field:nn { Short } { short }
\__datagidx_add_field:nn { See } { see }
\__datagidx_add_field:nn { SeeAlso } { seealso }
\__datagidx_add_field:nn { Text } { text }
\__datagidx_add_field:nn { Plural } { plural }
\__datagidx_add_field:nn { Label } { label }
\__datagidx_add_field:nn { Parent } { parent }
\__datagidx_add_field:nn { Sort } { sort }
%    \end{macrocode}
%
%\begin{macro}{\newtermaddfield}
%\begin{definition}
%\cs{newtermaddfield}\oarg{db list}\marg{column key}\oarg{placeholder cs}\marg{new term
%key}\oarg{data type}\marg{default value}
%\end{definition}
% The default value may contain \ics{field}\marg{key} to get the
% value of another field.
%    \begin{macrocode}
\NewDocumentCommand \newtermaddfield
  { O{} m o m O { \c_datatool_string_int } m }
{
  \__datagidx_set_label:Nn \l__datagidx_label_tl { #2 }
%    \end{macrocode}
% If optional argument not specified, iterate over all defined
% glossaries/indices.
%\changes{3.0}{2025-03-03}{check for column existence}
%    \begin{macrocode}
  \tl_if_empty:nTF { #1 }
  {
    \dtlforcolumn
      \l__datagidx_database_tl { datagidx } { Glossary }
    {
      \datatool_if_has_key:nnF
       { \l__datagidx_database_tl } { \l__datagidx_label_tl }
       { 
         \__datagidx_add_column:nnn
           { \l__datagidx_database_tl }
           { \l__datagidx_label_tl }
           { #5 }
       }
    }
  }
  {
    \clist_map_inline:nn { #1 }
    {
      \datatool_if_has_key:nnF
       { ##1 } { \l__datagidx_label_tl }
       { 
         \__datagidx_add_column:nnn
           { ##1 }
           { \l__datagidx_label_tl }
           { #5 }
       }
    }
  }
  \__datagidx_add_field_option:xxnc
    { \l__datagidx_label_tl } { #4 } { #6 }
    { l__datagidx_term_ #4 _tl }
  \__datagidx_append_extra_field:cV
    { l__datagidx_term_ #4 _tl }
    \l__datagidx_label_tl 
  \IfValueTF { #3 }
  {
    \tl_if_blank:n { #3 }
    {
      \__datagidx_add_placeholder:cV 
        { \l__datagidx_label_tl }
        \l__datagidx_label_tl
    }
    {
      \__datagidx_add_placeholder:NV
        #3
        \l__datagidx_label_tl
    }
  }
  {
    \__datagidx_add_placeholder:cV 
      { \l__datagidx_label_tl }
      \l__datagidx_label_tl
  }
}
%    \end{macrocode}
%\end{macro}
%Append to extra fields hook.
%    \begin{macrocode}
\cs_new:Nn \__datagidx_append_extra_field:Nn
{
  \tl_gput_right:Nn \g__datagidx_extra_fields_tl
   {
     \__datagidx_append_element_with_check:Nn
       #1 { #2 }
   }
}
\cs_generate_variant:Nn
  \__datagidx_append_extra_field:Nn
  { cV }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__datagidx_add_placeholder:Nn
{
 \tl_if_exist:NTF #1
  {
    \PackageError { datagidx }
    {
      \token_to_str: \newtermaddfield : ~ placeholder ~
      \token_to_str #1 \c_space_tl ~ already ~ exists
    }
    {
      Choose ~ a ~ different ~ command ~ name ~ in 
      \token_to_str: \newtermaddfield [ ... ]
      { #2 } [ \token_to_str #1 ] { ... }
    }
  }
  {
    \clist_gput_right:Nn \DTLgidxAssignList
    {
      #1 = #2
    }
    \tl_gput_right:Nn \datagidxgetchildfields
    {
      \datatool_if_has_key:nnTF
        { \DTLgidxCurrentdb } { #2 }
       {
         \dtlgetentryfromcurrentrow { #1 }
         { \dtlcolumnindex { \DTLgidxCurrentdb } { #2 } }
       }
       {
         \tl_set_eq:NN #1 \dtlnovalue
       }
    }
  }
}
\cs_generate_variant:Nn \__datagidx_add_placeholder:Nn
 { cn, cV, NV }
%    \end{macrocode}
%
%\begin{macro}{\newtermlabelhook}
%Hook used to provide local redefinitions to adjust the label.
%    \begin{macrocode}
\newcommand*{\newtermlabelhook}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\newtermsorthook}
%Hook used to provide local redefinitions to adjust the sort.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\newtermsorthook}{}
%    \end{macrocode}
%\end{macro}
%
%
%\begin{macro}{\DTLgidxNoFormat}
%    \begin{macrocode}
\newcommand*{\DTLgidxNoFormat}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxGobble}
%    \begin{macrocode}
\newcommand*{\DTLgidxGobble}[1]{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxStripBackslash}
% Argument must be a control sequence. This is stringified and the
% backslash is removed).
%    \begin{macrocode}
\newcommand*{\DTLgidxStripBackslash}[1]{ \cs_to_str:N #1 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxName}
%\begin{definition}
%\cs{DTLgidxName}\marg{forenames}\marg{surname}
%\end{definition}
% How to format a person's name in the text.
%    \begin{macrocode}
\newcommand*{\DTLgidxName}[2]{ #1 \c_space_tl #2 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxNameNum}
%\begin{definition}
%\cs{DTLgidxNameNum}\marg{n}
%\end{definition}
% The argument \meta{n} should be a number applied to a name 
% (e.g.\ \verb*"James "\cs{DTLgidxNameNum}{1}). This is converted to
% a two-digit number for sorting but a Roman numeral for the label
% and in the text.
%    \begin{macrocode}
\newcommand*{\DTLgidxNameNum}[1]{\@Roman{#1}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\datagidx@namenum}
% Conversion for sort key.
%    \begin{macrocode}
\newcommand*{\datagidx@namenum}[1]{\two@digits{#1}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxPlace}
%\begin{definition}
%\cs{DTLgidxPlace}\marg{country}\marg{town/city}
%\end{definition}
% How to format a place in the text.
%    \begin{macrocode}
\newcommand*{\DTLgidxPlace}[2]{ #2 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxSubject}
%\begin{definition}
%\cs{DTLgidxSubject}\marg{main}\marg{category}
%\end{definition}
% How to format a subject in the text. Ignore the main part in the
% text.
%    \begin{macrocode}
\newcommand*{\DTLgidxSubject}[2]{ #2 }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxOffice}
%\begin{definition}
%\cs{DTLgidxOffice}\marg{office}\marg{name}
%\end{definition}
% Put the office in parentheses in the document text.
%    \begin{macrocode}
\newcommand*{\DTLgidxOffice}[2]{ #2 ~ ( #1 ) }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxIgnore}
% Show argument in document text, but disregard in the sort and
% label.
%    \begin{macrocode}
\newcommand*{\DTLgidxIgnore}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxMac}
%\begin{definition}
%\cs{DTLgidxMac}\marg{text}
%\end{definition}
% In the document, just does \meta{text}, but gets converted to
% ``Mac'' in the sort key. (Unless overridden by the user.)
%    \begin{macrocode}
\newcommand*{\DTLgidxMac}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@mac}
%\cs{DTLgidxMac} gets temporarily redefined to \cs{datagidx@mac}
%when construction the sort key.
%    \begin{macrocode}
\newcommand*{\datagidx@mac}[1]{Mac}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxSaint}
%\begin{definition}
%\cs{DTLgidxSaint}\marg{text}
%\end{definition}
% In the document, just does \meta{text}, but gets converted to
% ``Saint'' in the sort key. (Unless overridden by the user.)
%    \begin{macrocode}
\newcommand*{\DTLgidxSaint}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@saint}
%\cs{DTLgidxMac} gets temporarily redefined to \cs{datagidx@saint}
%when construction the sort key.
%    \begin{macrocode}
\newcommand*{\datagidx@saint}[1]{Saint}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%\begin{macro}{\DTLgidxRank}
%\begin{definition}
%\cs{DTLgidxRank}\marg{rank}\marg{forenames}
%\end{definition}
% A person's title, rank or sanctity should be ignored when sorting.
%This has a non-breakable space separator.
%    \begin{macrocode}
\newcommand*{\DTLgidxRank}[2]{#1~#2}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@rank}
%\cs{DTLidxRank} gets temporarily redefined to \cs{datagidx@rank}
%when constructing the sort key. An extra dot is added to the end to
%ensure names without a rank are sorted before identical names with
%a rank.
%    \begin{macrocode}
\newcommand*{\datagidx@rank}[2]{#2.}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxParticle}
%\begin{definition}
%\cs{DTLgidxParticle}\marg{particle}\marg{surname}
%\end{definition}
% A particle such as ``of'', ``de'' or ``von'' should be ignored when sorting.
%This has a non-breakable space separator.
%    \begin{macrocode}
\newcommand*{\DTLgidxParticle}[2]{#1~#2}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\begin{macro}{\datagidx@particle}
%\cs{DTLidxParticle} gets temporarily redefined to \cs{datagidx@particle}
%when constructing the sort key. An extra dot is added to the end to
%ensure names without a particle are sorted before identical names with
%a particle.
%    \begin{macrocode}
\newcommand*{\datagidx@particle}[2]{#2.}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@bothoftwo}
%    \begin{macrocode}
\newcommand*{\datagidx@bothoftwo}[2]{#1#2}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new:Nn \__datagidx_punc:n { #1 }
%    \end{macrocode}
%
%\begin{macro}{\datagidx@person}
% Used when constructing the sort key for a name.
%    \begin{macrocode}
\newcommand*{\datagidx@person}[2]{
 #2 \__datagidx_punc:n { \datatoolpersoncomma } #1
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@place}
% Used when constructing the sort key for a place.
%    \begin{macrocode}
\newcommand*{\datagidx@place}[2]{
 #2 \__datagidx_punc:n { \datatoolplacecomma } #1
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@subject}
% Used when constructing the sort key for a place.
%    \begin{macrocode}
\newcommand*{\datagidx@subject}[2]{
 #2 \__datagidx_punc:n { \datatoolsubjectcomma } #1
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@paren}
% Used when constructing the sort key for a parenthesis.
%    \begin{macrocode}
\newcommand*{\datagidx@paren}[1]{
 \__datagidx_punc:n { \datatoolparenstart } #1
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@invert}
%    \begin{macrocode}
\newcommand*{\datagidx@invert}[2]{
 #2 \__datagidx_punc:n { , } ~ #1
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxParen}
% Parenthetical material.
%    \begin{macrocode}
\newcommand*{\DTLgidxParen}[1]{ \c_space_tl ( #1 ) }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxwordifygreek}
% Convert commands like \ics{alpha} into words for indexing and
% labelling purposes.
%    \begin{macrocode}
\newcommand*{\datagidxwordifygreek}{%
  \def\alpha{alpha}%
  \def\beta{beta}%
  \def\gamma{gamma}%
  \def\delta{delta}%
  \def\epsilon{epsilon}%
  \def\varepsilon{epsilon}%
  \def\zeta{zeta}%
  \def\eta{eta}%
  \def\theta{theta}%
  \def\vartheta{theta}%
  \def\iota{iota}%
  \def\kappa{kappa}%
  \def\lambda{lambda}%
  \def\mu{mu}%
  \def\nu{nu}%
  \def\xi{xi}%
  \def\pi{pi}%
  \def\varpi{pi}%
  \def\rho{rho}%
  \def\varrho{rho}%
  \def\sigma{sigma}%
  \def\varsigma{sigma}%
  \def\tau{tau}%
  \def\upsilon{upsilon}%
  \def\phi{phi}%
  \def\varphi{phi}%
  \def\chi{chi}%
  \def\psi{psi}%
  \def\omega{omega}%
  \def\Gamma{Gamma}%
  \def\Delta{Delta}%
  \def\Theta{Theta}%
  \def\Lambda{Lambda}%
  \def\Xi{Xi}%
  \def\Pi{Pi}%
  \def\Sigma{Sigma}%
  \def\Upsilon{Upsilon}%
  \def\Phi{Phi}%
  \def\Psi{Psi}%
  \def\Omega{Omega}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidxextendedtoascii}
% Convert commands like \ics{aa} into the closest ASCII equivalent.
%\changes{2.28}{2017-11-10}{new}
%Version 3.0: Deprecated (use localisation)
%    \begin{macrocode}
\newcommand{\datagidxextendedtoascii}{%
  \def\AE{AE}%
  \def\ae{ae}%
  \def\OE{OE}%
  \def\oe{oe}%
  \def\AA{AA}%
  \def\aa{aa}%
  \def\L{L}%
  \def\l{l}%
  \def\O{O}%
  \def\o{o}%
  \def\SS{SS}%
  \def\ss{ss}%
  \def\th{th}%
  \def\TH{TH}%
  \def\dh{dh}%
  \def\DH{DH}%
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\begin{macro}{\datagidxconvertchars}
% Convert commands like \cs{\&}.
%\changes{2.28}{2017-11-10}{new}
%This can mostly be done with the new kernel commands
%and v3.0 localisation support but
%maintained for backward compatibility.
%    \begin{macrocode}
\newcommand*{\datagidxconvertchars}{%
  \let~\space
  \ifdef\andname
  {%
    \let\&\andname
  }%
  {%
    \def\&{\expandafter\@gobble\string\&}%
  }%
  \def\_{\string_}%
  \def\${\string$}%
  \def\#{\expandafter\@gobble\string\#}%
  \def\%{\expandafter\@gobble\string\%}%
  \def\{{\expandafter\@gobble\string\{}%
  \def\}{\expandafter\@gobble\string\}}%
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\begin{macro}{\datagidxstripaccents}
% Strip accents so they don't interfere with the label and sort.
% If you want to write your own comparison handler macro, you'll
% need to redefine this if you want accented letters to be sorted
% differently from the unaccented version. This command should
% only be used within a scope.
%\changes{2.32}{2019-09-27}{added test for kernel version}
% Need to test kernel version. The new 2019/10/01 version has a
% different definition of \cs{UTFviii@two@octets} which won't expand
% UTF-8 characters to \cs{IeC} in the required context. (This is
% really useful if you want extended characters in labels but not if
% they need to be stripped.) If you don't want accents stripped then
% redefine \cs{datagidxstripaccents} to do nothing.
%
%Version 3.0: deprecated. This may not work with modern \LaTeX\
%kernels.
%    \begin{macrocode}
\newcommand*{\datagidxstripaccents}{%
  \PackageWarning { datagidx }
   {
     \token_to_str:N \datagidxstripaccents \c_space_tl ~
     is ~ deprecated
   }
%    \end{macrocode}
% These redefinitions will only work with \cs{edef} or \cs{xdef}:
%    \begin{macrocode}
   \let\add@accent@\@secondoftwo
   \let\@text@composite@x\@secondoftwo
   \let\@tabacckludge\@secondoftwo
%    \end{macrocode}
% The following are need with \cs{protected@edef} or \cs{protected@xdef}:
%    \begin{macrocode}
   \expandafter\def\csname \encodingdefault-cmd\endcsname##1##2##3{##3}%
   \expandafter\def\csname OT1-cmd\endcsname##1##2##3{##3}%
   \expandafter\def\csname T1-cmd\endcsname##1##2##3{##3}%
   \expandafter\def\csname PD1-cmd\endcsname##1##2##3{##3}%
   \def\IeC##1{\@gobbletwo##1}%
   \let\UTFviii@two@octets\UTFviii@two@octets@combine
}%
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\newterm}
%\begin{definition}
% \cs{newterm}\oarg{options}{name}
%\end{definition}
% Defaults to normal behaviour.
%    \begin{macrocode}
\providecommand{\newterm}{\datagidx@newterm}
%    \end{macrocode}
%\end{macro}
%
%May only be used in the preamble. (Terms must be defined before the
%aux file is read.)
%    \begin{macrocode}
\@onlypreamble\newterm
%    \end{macrocode}
%Set \verb|\l__datagidx_term_label_tl| to the given label:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_set_label:n
{
  \__datagidx_set_label:Nn
    \l__datagidx_term_label_tl { #1 }
}
\cs_new:Nn \__datagidx_set_label:Nn
{
  \tl_set:Nx #1 { \text_purify:n { #2 } }
}
%    \end{macrocode}
%
%\begin{macro}{\datagidx@setfieldvalues}
%Syntax: \marg{options}\marg{name}
%
% Sets the values for all the field.
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%Version 3.0: replaced \cs{datagidx@setfieldvalues} with:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_set_field_values:nn
{
%    \end{macrocode}
% Initialise all token list variables to null to begin with:
%    \begin{macrocode}
 \clist_map_inline:Nn \datagidxtermkeys
  {
    \tl_set:cn { l__datagidx_term_ ##1 _ tl} { \c_novalue_tl }
  }
%    \end{macrocode}
% Now set non-null defaults.
%    \begin{macrocode}
 \tl_set:Nx \l__datagidx_term_database_tl
   { \l__datagidx_default_database_tl }
 \tl_clear:N \l__datagidx_term_parent_tl
%    \end{macrocode}
%Predefined field values:
%    \begin{macrocode}
 \tl_set:Nn \l__datagidx_term_name_tl { #2 }
 \tl_set:Nn \l__datagidx_term_text_tl { #2 }
 \tl_clear:N \l__datagidx_term_description_tl
 \tl_clear:N \l__datagidx_term_symbol_tl 
 \tl_set:Nn \l__datagidx_term_short_tl { #2 }
 \tl_set:Nn \l__datagidx_term_long_tl { #2 }
 \tl_clear:N \l__datagidx_term_see_tl 
 \tl_clear:N \l__datagidx_term_seealso_tl 
%    \end{macrocode}
% Assign values given in optional argument.
%    \begin{macrocode}
 \keys_set:nn { datatool / index / newterm } { #1 }
%    \end{macrocode}
% Allow hook to access other fields.
%    \begin{macrocode}
 \cs_set_eq:NN \__datagidx_org_field:n \field
 \def \field ##1
   { \exp_not:v { l__datagidx_term_ ##1 _tl } }
%    \end{macrocode}
% Hook to make it easier to add extra fields.
%    \begin{macrocode}
 \g__datagidx_newterm_defaults_tl
 \let \field \__datagidx_org_field:n
%    \end{macrocode}
%Set label if not provided in the options:
%    \begin{macrocode}
  \tl_if_eq:NnT \l__datagidx_term_label_tl { \c_novalue_tl }
   {
     \__datagidx_create_label:n { #2 }
   }
  \exp_args:NV \tl_if_blank:nT \l__datagidx_term_label_tl
   {
      \PackageError { datagidx }
      {
        Blank ~ label ~ not ~ permitted ~
        ( for ~ term ~ ` \tl_to_str:n { #2 } ')
      }
      {
        Use ~ label={...} ~ in ~ options ~
        to ~ set ~ the ~ label ~ to ~ a ~ non-blank ~ value. ~
        Bear ~ in ~ mind ~ that ~ commas ~ (,) ~ and ~ 
        equals ~ (=) ~ and ~ other ~ problematic ~
        content ~ will ~ be ~ stripped
      }
   }
  \tl_if_eq:NnT \l__datagidx_term_sort_tl { \c_novalue_tl }
   {
     \__datagidx_create_sort:n { #2 }
   }
}
%    \end{macrocode}
%
%\begin{macro}{\datagidx@add@term}
%The argument is the term's name.
% Add term (once all fields have been set. Argument is the name
% field. The column label should be in \verb|\l__datagidx_term_label_tl|
%and the database label should be in \verb|\l__datagidx_term_database_tl|
%Version 3.0: replaced \cs{datagidx@add@term} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{\_\_datagidx\_add\_term:n}}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \__datagidx_add_term:n
{
  \__datagidx_assign_label_db:nn
    { \l__datagidx_term_label_tl }
    { \l__datagidx_term_database_tl }
%    \end{macrocode}
%Build the row. Don't use datum for labels, clists.
%    \begin{macrocode}
  \tl_clear:N \l__datatool_row_tl
  \tl_put_right:Nx \l__datatool_row_tl
   {
     \__datatool_row_element_markup:vV
      { dtl@ci@ \l__datagidx_term_database_tl @ Label }
       \l__datagidx_term_label_tl 
   }
%    \end{macrocode}
%Markup "Used" with \cs{dtlspecialvalue}
%    \begin{macrocode}
  \tl_put_right:Nx \l__datatool_row_tl
   {
     \__datatool_row_element_markup:vn
      { dtl@ci@ \l__datagidx_term_database_tl @ Used }
      {
        \dtlspecialvalue { \__datagidx_used:n { 0 } }
      }
   }
  \__datagidx_append_element:nn { Name } { #1 }
  \__datagidx_append_element:nV
    { Text } \l__datagidx_term_text_tl 
  \__datagidx_append_element:nV
    { Description } \l__datagidx_term_description_tl 
  \__datagidx_append_element:nV
    { Sort } \l__datagidx_term_sort_tl 
  \__datagidx_append_element:nV
    { Symbol } \l__datagidx_term_symbol_tl 
  \__datagidx_append_element:nV
    { Short } \l__datagidx_term_short_tl 
  \__datagidx_append_element:nV
    { Long } \l__datagidx_term_long_tl 
  \tl_if_eq:NnTF \l__datagidx_term_plural_tl { \c_novalue_tl }
    {
      \__datagidx_append_element:no
        { Plural }
        { \l__datagidx_term_text_tl s }
    }
    {
      \__datagidx_append_element:nV
        { Plural } \l__datagidx_term_plural_tl 
    }
  \tl_if_eq:NnTF \l__datagidx_term_shortplural_tl { \c_novalue_tl }
    {
      \__datagidx_append_element:no
        { ShortPlural }
        { \l__datagidx_term_short_tl s }
    }
    {
      \__datagidx_append_element:nV
        { ShortPlural }
        \l__datagidx_term_shortplural_tl 
    }
   \tl_if_eq:NnTF \l__datagidx_term_longplural_tl { \c_novalue_tl }
    {
      \__datagidx_append_element:no
        { LongPlural }
        { \l__datagidx_term_long_tl s }
    }
    {
      \__datagidx_append_element:nV
        { LongPlural }
        \l__datagidx_term_longplural_tl 
    }
   \tl_if_empty:NF \l__datagidx_term_see_tl 
    {
      \tl_put_right:Nx \l__datatool_row_tl
       {
         \__datatool_row_element_markup:vV
           { dtl@ci@ \l__datagidx_term_database_tl @ See }
           \l__datagidx_term_see_tl 
       }
    }
   \tl_if_empty:NF \l__datagidx_term_seealso_tl 
    {
      \tl_put_right:Nx \l__datatool_row_tl
       {
         \__datatool_row_element_markup:vV
           { dtl@ci@ \l__datagidx_term_database_tl @ SeeAlso }
           \l__datagidx_term_see_tl 
       }
    }
%    \end{macrocode}
% Hook to make it easier to add extra fields.
%    \begin{macrocode}
   \g__datagidx_extra_fields_tl
%    \end{macrocode}
% Add parent, if supplied.
%    \begin{macrocode}
   \tl_if_empty:NF \l__datagidx_term_parent_tl 
    {
      \iftermexists { \l__datagidx_term_parent_tl }
       {
         \tl_set:Nx \l__datagidx_parent_database_tl
          { 
            \__datagidx_term_database:n
              { \l__datagidx_term_parent_tl }
          }
%    \end{macrocode}
% Parent entry is required to belong to same database as child entry.
%    \begin{macrocode}
         \tl_if_eq:NNTF
           \l__datagidx_parent_database_tl
           \l__datagidx_term_database_tl
         {
           \tl_put_right:Nx \l__datatool_row_tl
            {
              \__datatool_row_element_markup:vV
               { dtl@ci@ \l__datagidx_term_database_tl @ Parent }
               \l__datagidx_term_parent_tl 
            }
           \__datagidx_add_child:xxx
             { \l__datagidx_term_database_tl }
             { \l__datagidx_term_parent_tl }
             { \l__datagidx_term_label_tl }
%    \end{macrocode}
%Add the hierarchical sort.
%    \begin{macrocode}
           \tl_set:Nn \l__datagidx_term_hiersort_tl { \c_novalue_tl }
           \__datagidx_set_hiersort:
           \tl_if_eq:NnF
             \l__datagidx_term_hiersort_tl { \c_novalue_tl }
            {
              \__datagidx_append_element:nV
                { HierSort } \l__datagidx_term_hiersort_tl 
            }
         }
         {
           \PackageError{datagidx}
           {
             Parent ~ entry ~ ` \l__datagidx_term_parent_tl ' ~ must ~ 
             belong ~ to ~ the ~ same ~ database ~ as ~ 
             child ~ entry ~ `\l__datagidx_term_label_tl '
           }
           {
             Parent ~ entry ~ is ~ in ~ database ~
             ` \l__datagidx_parent_database_tl ' ~ and ~ 
             child ~ entry ~ is ~ in ~
             database ~ `\l__datagidx_term_database_tl'
           }
         }
       }
       {
         \PackageError {datagidx}
         {
            Can't ~ assign ~ parent ~ to ~
            `\l__datagidx_term_label_tl': ~
            ` \l__datagidx_term_parent_tl ' ~ doesn't ~ exist
         }
         {}
       }
    }
%    \end{macrocode}
%Add the row to the database.
%Increment total number of rows:
%    \begin{macrocode}
  \int_gincr:c { dtlrows@  \l__datagidx_term_database_tl  }
%    \end{macrocode}
%Append row markup to the database's internal token register.
%    \begin{macrocode}
  \__datatool_token_register_gput_right:cx
   { dtldb@ \l__datagidx_term_database_tl }
   {
     \__datatool_row_markup:vV
      { dtlrows@ \l__datagidx_term_database_tl } 
      \l__datatool_row_tl
   }
%    \end{macrocode}
% Provide user with a means to access the label of the latest defined
% term:
%    \begin{macrocode}
  \tl_gset:Nx \datagidxlastlabel
   { \l__datagidx_term_label_tl }
%    \end{macrocode}
%\changes{2.14}{2013-06-28}{added \cs{postnewtermhook}}
% Allow user to hook in here:
%    \begin{macrocode}
  \postnewtermhook
}
%    \end{macrocode}
%    \begin{macrocode}
\cs_new:Nn \__datagidx_set_hiersort:
{
  \group_begin:
    \DTLaction
     [
       key = Label,
       expand-value = \l__datagidx_term_parent_tl ,
       name =  \l__datagidx_term_database_tl ,
       return = { \Sort = Sort, \HierSort = HierSort}
     ]
     { select ~ row }
    \datatool_if_null_or_empty:NTF \HierSort
     {
       \datatool_if_null_or_empty:NF \Sort
        {
          \tl_set:Nx \l__datagidx_term_hiersort_tl
            {
               \exp_not:V \Sort
               \exp_not:N \datatoolctrlboundary
               \exp_not:N \datatoolasciistart
               \exp_not:V \l__datagidx_term_sort_tl
            }
        }
     }
     {
       \tl_set:Nx \l__datagidx_term_hiersort_tl
         {
            \exp_not:V \HierSort
            \exp_not:N \datatoolctrlboundary
            \exp_not:N \datatoolasciistart
            \exp_not:V \l__datagidx_term_sort_tl
         }
     }
  \exp_args:NNNV
  \group_end:
    \tl_set:Nn \l__datagidx_term_hiersort_tl
      \l__datagidx_term_hiersort_tl
}
%    \end{macrocode}
%Append an element to the current row token list variable,
%taking the current expansion setting into account.
%Syntax: \marg{column key}\marg{value}
%    \begin{macrocode}
\cs_new:Nn \__datagidx_append_element:nn
{
  \tl_if_exist:cTF
     { dtl@ci@ \l__datagidx_term_database_tl @ #1 }
   {
     \__datatool_process_new_value:n { #2 }
     \tl_put_right:Nx \l__datatool_row_tl
      {
        \__datatool_row_element_markup:vV
          { dtl@ci@ \l__datagidx_term_database_tl @ #1 }
          \l__datatool_item_value_tl 
      }
   }
   {
     \PackageError { datagidx }
     {
       Column ~ ` #1' ~ hasn't ~ been ~ defined ~ in ~
       database ~ ` \l__datagidx_term_database_tl '
     }
     {
       A ~ new ~ term ~ can't ~ be ~ added ~ to ~
       database ~ ` \l__datagidx_term_database_tl '. ~
       Has ~ the ~ database ~ been ~ defined ~ with ~
       \token_to_str:N \newgidx ? ~ If ~ this ~ is ~
       a ~ custom ~ field, ~ was ~
       \token_to_str:N  \newtermaddfield \c_space_tl ~
       used ~ after ~ ` \l__datagidx_term_database_tl ' ~
       was ~ defined?
     }
   }
}
\cs_generate_variant:Nn
  \__datagidx_append_element:nn
  { nV , no, nv }
%    \end{macrocode}
%
%Syntax: \meta{value tl var}\marg{col-key}
%    \begin{macrocode}
\cs_new:Nn \__datagidx_append_element_with_check:Nn
{
  \datatool_if_null:NF #1
   {
     \datatool_if_has_key:nnT
       { \l__datagidx_term_database_tl } { #2 }
      {
        \__datagidx_append_element:nV
          { #2 } #1 
      }
   }
}
%    \end{macrocode}
%
%\begin{macro}{\postnewtermhook}
%\changes{2.14}{2013-06-28}{new}
%    \begin{macrocode}
\newcommand*{\postnewtermhook}{}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\newtermfield}
%\changes{2.14}{2013-06-28}{new}
% Expandable access to field name. (No check for existence of the
% field. Expands to an empty
% string if the field is undefined.)
%    \begin{macrocode}
\newcommand*{\newtermfield}[1]{
 \use:c { l__datagidx_term_ #1 _ tl }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\ifnewtermfield}
%\changes{2.14}{2013-06-28}{new}
% If the named field (given in first argument) is empty
% or undefined do third argument,
% otherwise do second argument.
%    \begin{macrocode}
\newcommand{\ifnewtermfield}[3]{%
  \tl_if_exist:cTF { l__datagidx_term_ #1 _ tl }
   {
     \tl_if_empty:cTF { l__datagidx_term_ #1 _ tl }
       { #3 } { #2 }
   }
   { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@newterm}
% Normal behaviour for \cs{newterm}
%    \begin{macrocode}
\NewDocumentCommand \datagidx@newterm { O{} m }
{
%    \end{macrocode}
% Assign values to all the fields.
%    \begin{macrocode}
  \__datagidx_set_field_values:nn { #1 } { #2 }
%    \end{macrocode}
% Check if database exists.
%    \begin{macrocode}
  \DTLifdbexists { \l__datagidx_term_database_tl }
  {
%    \end{macrocode}
% Database exists. Check if term already exists.
%    \begin{macrocode}
    \iftermexists { \l__datagidx_term_label_tl }
    {
      \PackageError {datagidx}
      {
        Term ~ ` \l__datagidx_term_label_tl ' ~
        already ~ exists ~ in ~ database ~
         ` \l__datagidx_term_database_tl '
      }
      { }
    }
    {
%    \end{macrocode}
% Add this entry to the database.
%    \begin{macrocode}
       \__datagidx_add_term:n { #2 }
       \dtl@message
        {
          Added ~ term ~  ` \l__datagidx_term_label_tl ' ~
          to ~ database ~ ` \l__datagidx_term_database_tl ' ~
          (name: ~ ` \tl_to_str:N \l__datagidx_term_name_tl ' , ~
           sort: ~ ` \tl_to_str:N \l__datagidx_term_sort_tl ')
        }
    }
  }
  {
%    \end{macrocode}
% Database doesn't exist.
%    \begin{macrocode}
    \PackageError {datagidx}
    {
      Glossary/index ~ database ~ `\l__datagidx_term_database_tl' ~ 
      doesn't ~ exist
    }
    {
      You ~ must ~ define ~ the ~ glossary/index ~ 
      database ~ with \token_to_str:N \newgidx \c_space_tl ~
      before ~ you ~ can ~ add ~ any ~ terms ~ to ~ it.
    }
  }%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\datagidx@highopt@newterm}
% Used when high optimized setting enabled. This setting must be
% switched off if the user wants to modify the database.
%    \begin{macrocode}
\NewDocumentCommand \datagidx@highopt@newterm { O{} m }
{
%    \end{macrocode}
% Assign values to all the fields.
%    \begin{macrocode}
  \__datagidx_set_field_values:nn { #1 } { #2 }
%    \end{macrocode}
% Check if database exists.
%    \begin{macrocode}
  \DTLifdbexists { \l__datagidx_term_database_tl }
  {
%    \end{macrocode}
% Database exists. If this is the first run, we need to add the term
% as usual, otherwise we just need to define
% the mapping from the term label to the database label
%    \begin{macrocode}
    \__datatool_get_row_index:Nxxx
      \l__datatool_row_idx_tl
       { \l__datagidx_term_database_tl }
       {
         \dtlcolumnindex
           { \l__datagidx_term_database_tl } {Label}
       }
       { \l__datagidx_term_label_tl }
    \datatool_if_null:NTF \l__datatool_row_idx_tl
     {
%    \end{macrocode}
% Hasn't been defined so add.
%    \begin{macrocode}
       \__datagidx_add_term:n { #2 }
%    \end{macrocode}
% Database will need to be sorted.
%    \begin{macrocode}
       \tl_set:cx
         { 
           datagidx@do@highopt@sort@
           \l__datagidx_term_database_tl
         }
         { \exp_not:V \l__datagidx_sort_tl }
     }
     {
%    \end{macrocode}
% Has been defined, so just define the mapping
% and \cs{datagidxlastlabel}
%    \begin{macrocode}
      \__datagidx_assign_label_db:nn
        \l__datagidx_term_label_tl
        \l__datagidx_term_database_tl
      \tl_set:Nx \datagidxlastlabel
        { \l__datagidx_term_label_tl }
     }
  }%
  {%
%    \end{macrocode}
% Database doesn't exist.
%    \begin{macrocode}
    \PackageError {datagidx}
    {
      Glossary/index ~ database ~ 
      `\l__datagidx_term_database_tl' ~ doesn't ~ exist
    }
    {
      You ~ must ~ define ~ the ~ glossary/index ~ 
      database ~ before ~ you ~ can ~
      add ~ any ~ terms ~ to ~ it.
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%
%\begin{macro}{\datagidx@addchild}
%Version 3.0: replaced \cs{datagidx@addchild} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{}}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \__datagidx_add_child:nnn
{
  \__datatool_get_row_for_value:xxn
   { #1 }
   { \dtlcolumnindex { #1 } { Label } }
   { #2 }
  \dtlgetentryfromcurrentrow
    \l__datagidx_child_clist
    { \dtlcolumnindex { #1 } { Child } }
  \datatool_if_null:NT \l__datagidx_child_clist 
   {
     \clist_clear:N \l__datagidx_child_clist
   }
  \clist_put_right:Nx \l__datagidx_child_clist
    { #3 }
  \exp_args:NnV
    \dtlupdateentryincurrentrow
    {Child} \l__datagidx_child_clist
  \dtlrecombine
}
\cs_generate_variant:Nn
  \__datagidx_add_child:nnn
  { xxx }
%    \end{macrocode}
%
%\subsection{Defining Acronyms}
%\begin{macro}{\newacro}
%\begin{definition}
%\cs{newacro}\oarg{options}\marg{short}\marg{long}
%\end{definition}
% Shortcut command for acronyms.
%    \begin{macrocode}
\NewDocumentCommand \newacro { O{} m m }
{
  \exp_args:Nno \datagidx_new_acro:nnnn { #1 }
   { \text_uppercase:n { #2 } } { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \datagidx_new_acro:nnnn
{
  \bool_if:NTF \l__datatool_new_element_expand_bool
   {
     \newterm
       [
         description = { \exp_not:N \capitalisewords { #4 } },
         short = { \exp_not:N \acronymfont { #3 } },
         long = { #4 },
         text =
          {
            \exp_not:N \DTLgidxAcrStyle
              { #4 } { \exp_not:N \acronymfont { #3 } }
          },
         plural = 
          {
             \exp_not:N \DTLgidxAcrStyle
               { #4 s } {\exp_not:N \acronymfont { #3 s } }
          },
         sort = { #3 }, label = { #3 },
         #1
       ]
       { #2 }
   }
   {
     \newterm
       [
         description = { \capitalisewords { #4 } },
         short = { \acronymfont { #3 } },
         long = { #4 },
         text = { \DTLgidxAcrStyle { #4 } { \acronymfont { #3 } } },
         plural = { \DTLgidxAcrStyle { #4 s } { \acronymfont { #3 s } } },
         sort = { #3 }, label = { #3 },
         #1
       ]
       { #2 }
   }
}
%    \end{macrocode}
%
%\begin{macro}{\acronymfont}
% The font to use for the acronym.
%    \begin{macrocode}
\newcommand*{\acronymfont}[1]{#1}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLgidxAcrStyle}
%\begin{definition}
%\cs{DTLgidxAcrStyle}\marg{long}\marg{short}
%\end{definition}
%    \begin{macrocode}
\newcommand*{\DTLgidxAcrStyle}[2]{#1 ~ (#2)}
%    \end{macrocode}
%\end{macro}
%
%
%\section{Conditionals}
%
%\begin{macro}{\iftermexists}
%\begin{definition}
% \cs{iftermexists}\marg{label}\marg{true part}\marg{false part}
%\end{definition}
% Check if term with given label exists. This uses the term to
% database mapping to avoid a database lookup.
%    \begin{macrocode}
\newcommand{\iftermexists}[3]{%
  \tl_if_exist:cTF
     { g__datagidxentry_ #1 _ tl }
   { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
% Term label to database assignment.
%    \begin{macrocode}
\cs_new:Nn \__datagidx_assign_label_db:nn
{
  \tl_if_exist:cTF
    {  g__datagidxentry_ #1 _ tl }
  {
     \exp_args:Nxx \tl_if_eq:nnF
       { #2 } { \tl_use:c { g__datagidxentry_ #1 _ tl } }
     {
       \PackageError { datagidx}
       {
          Can't ~ associated ~ label ~ ` #1 ' ~
          with ~ database ~ `#2'. ~ Label ~
          already ~ associated ~ with ~ database ~
          ` \tl_use:c { g__datagidxentry_ #1 _ tl } ' 
       }
       { }
     }
  }
  {
    \tl_gset:cx { g__datagidxentry_ #1 _ tl } { #2 }
  }
}
%    \end{macrocode}
%Expands to the database label the given term is in.
%    \begin{macrocode}
\cs_new:Nn \__datagidx_term_database:n
{
  \csuse { g__datagidxentry_ #1 _ tl }
}
%    \end{macrocode}
%
%\begin{macro}{\datagidxdb}
%\changes{2.14}{2013-06-28}{new}
% Gets the label of database containing the given entry. No check is
% made for the existence of the entry. Expands to empty if label is
% undefined.
%    \begin{macrocode}
\newcommand*{\datagidxdb}[1]{
  \__datagidx_term_database:n { #1 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlunknowntag}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\dtlunknowntag}{??}
%    \end{macrocode}
%\end{macro}
%
%Fetch the row from the index/glossary database associated with the given term.
%The term's label should be in \verb|\l__datagidx_term_label_tl|.
%This will set \verb|\l__datagidx_term_database_tl| to the database
%label and \cs{dtlcurrentrow} if successful.
%    \begin{macrocode}
\cs_new:Nn \__datagidx_fetch_row_for_label:NT
{
%    \end{macrocode}
% Fetch the name of the database with which this entry is
% associated.
%    \begin{macrocode}
  \tl_set:Nx \l__datagidx_term_database_tl
   { 
     \__datagidx_term_database:n
       { \l__datagidx_term_label_tl }
   }
  \tl_if_empty:NTF \l__datagidx_term_database_tl
   {
     \dtlunknowntag
      \PackageError {datagidx}
      {
       \token_to_str:N #1 : ~
        No ~ term ~ ` \l__datagidx_term_label_tl ' ~ defined
      }
      {
       Check ~ that ~ you ~ have ~ correctly ~ spelt ~ the ~
       label. ~ If ~ you ~ defined ~ a ~ term ~ containing ~
       commands ~ and ~ you ~ didn't ~ explicitly ~ set ~
       the ~ label, ~ 
       \legacy_if:nF { dtlverbose } { switch ~ on ~ verbose ~
       mode ~ and ~ } examine ~ the ~ transcript ~
       messages ~ (Added ~ term ~ ` [...] ' ~ to ~
       database ~ ` [...] ') ~ to ~ check ~ the ~ label
      }
   }
   {
%    \end{macrocode}
% Get the row associated with this label and make it the current row.
%    \begin{macrocode}
    \tl_set:Nx \l__datatool_item_col_tl
     {
       \dtlcolumnindex
         { \l__datagidx_term_database_tl } {Label}
     }
    \tl_if_empty:NTF \l__datatool_item_col_tl
     {
        \PackageError { datagidx }
        {
          \token_to_str:N #1 : ~
          No ~ column ~ `Label' ~ found ~ in ~ database ~ 
          ` \l__datagidx_term_database_tl '
        }
        {
          Something ~ has ~ gone ~ wrong. ~ The ~
          index/glossary ~ database
          ` \l__datagidx_term_database_tl ' ~ 
          is ~ associated ~ with ~ the ~ term ~
          label ~ `  \l__datagidx_term_label_tl ' ~
          but ~ it ~ doesn't ~ have ~ a ~ column ~ with ~
          the ~ key ~ `Label'
        }
     }
     {
      \exp_args:NNVV
      \__datatool_get_row_for_value:nnnTF
        \l__datagidx_term_database_tl 
        \l__datatool_item_col_tl
        \l__datagidx_term_label_tl 
      {
%    \end{macrocode}
% Found it.
%    \begin{macrocode}
        #2
      }
      {
%    \end{macrocode}
% Not found. Something has gone wrong.
%    \begin{macrocode}
        \PackageError { datagidx }
        {
          \token_to_str:N #1 : ~
          No ~ row ~ found ~ in ~ database ~ 
          ` \l__datagidx_term_database_tl ' ~ in ~ column ~
          ` Label ' ~ matching ~ ` \l__datagidx_term_label_tl '
        }
        {
          Something ~ has ~ gone ~ wrong. ~ Term ~ with ~
          label ~ ` \l__datagidx_term_label_tl ' ~ was ~
          assigned ~ to ~ database ~ 
          ` \l__datagidx_term_database_tl ' ~ but ~
          there ~ isn't ~ a ~ corresponding ~ row ~
          in ~ that ~ database ~ for ~ the ~ term
        }
      }
    }
  }
}
%    \end{macrocode}
%
%\begin{macro}{\ifentryused}
%\begin{definition}
% \cs{ifentryused}\marg{label}\marg{true part}\marg{false part}
%\end{definition}
% Check if entry with given label has been used.
%    \begin{macrocode}
\NewDocumentCommand \ifentryused { m m m }
{
  \__datagidx_set_label:n { #1 }
  \__datagidx_fetch_row_for_label:NT \ifentryused
   {
     \dtlgetentryfromcurrentrow
       \l__datagidx_value_tl
       { \dtlcolumnindex { \l__datagidx_term_database_tl } {Used} }
     \datatool_if_null_or_empty:NTF
        \l__datagidx_value_tl
      { #1 }
      {
        \int_compare:nNnTF
           { \l__datagidx_value_tl } = { \c_one_int }
          { #2 } { #3 }
      }
   }
}
%    \end{macrocode}
%\end{macro}
%
%\section{Unsetting and Resetting}
%
%\begin{macro}{\glsreset}
%\begin{definition}
%\cs{glsunset}\marg{label}
%\end{definition}
% Mark as un-used.
%    \begin{macrocode}
\NewDocumentCommand \glsreset { m }
{
  \__datagidx_set_label:n { #1 }
  \__datagidx_fetch_row_for_label:NT \glsreset
   {
%    \end{macrocode}
% Update the "Used" field.
%    \begin{macrocode}
     \dtlreplaceentryincurrentrow
       {  
         \dtlspecialvalue { \__datagidx_used:n { 0 } }
       }
       { 
         \dtlcolumnindex { \l__datagidx_term_database_tl } {Used}
       }
%    \end{macrocode}
% Current row has been edited, so we need to merge the current row
% back into the database.
%    \begin{macrocode}
     \dtlrecombine
   }
}
\MFUexcl { \glsreset }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\glsunset}
%\begin{definition}
%\cs{glsunset}\marg{label}
%\end{definition}
% Mark as used without affecting location.
%    \begin{macrocode}
\NewDocumentCommand \glsunset { m }
{
  \__datagidx_set_label:n { #1 }
  \__datagidx_fetch_row_for_label:NT \glsunset
   {
%    \end{macrocode}
% Update the "Used" field.
%    \begin{macrocode}
    \dtlreplaceentryincurrentrow
      { 
        \dtlspecialvalue { \__datagidx_used:n { 1 } }
      }
      { 
        \dtlcolumnindex
          { \l__datagidx_term_database_tl } {Used}
      }
%    \end{macrocode}
% Current row has been edited, so we need to merge the current row
% back into the database.
%    \begin{macrocode}
    \dtlrecombine
   }
}
\MFUexcl { \glsunset }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\glsresetall}
%\begin{definition}
%\cs{glsresetall}\marg{db}
%\end{definition}
% Resets all entries in the given database.
%    \begin{macrocode}
\NewDocumentCommand \glsresetall { m }
{
  \dtlforcolumn \l__datagidx_label_tl { #1 } { Label }
  {
    \exp_args:NV \glsreset \l__datagidx_label_tl
  }
}
\MFUexcl { \glsresetall }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\glsunsetall}
%\begin{definition}
%\cs{glsunsetall}\marg{db}
%\end{definition}
% Resets all entries in the given database.
%    \begin{macrocode}
\NewDocumentCommand \glsunsetall { m }
{
  \dtlforcolumn \l__datagidx_label_tl { #1 } { Label }
  {
    \exp_args:NV \glsunset \l__datagidx_label_tl
  }
}
\MFUexcl { \glsunsetall }
%    \end{macrocode}
%\end{macro}
%
%\section{Accessing Entry Information}
%
%\begin{macro}{\datagidx@anchorcount}
% Register to make unique anchors.
%Version 3.0: replaced \cs{datagidx@anchorcount} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{g\_\_datagidx\_anchor\_count\_int}}
%    \begin{macrocode}
\int_new:N \g__datagidx_anchor_count_int
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\datagidx@formatanchor}
% Format number using six digits.
%Version 3.0: replaced \cs{datagidx@formatanchor} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{\_\_datagidx\_format\_anchor:n}}
%    \begin{macrocode}
\cs_new:Nn \__datagidx_format_anchor:n
{
 \int_compare:nNnT { #1 } < { 10000 }
  {
   0
   \int_compare:nNnT { #1 } < { 1000 }
    {
      0
      \int_compare:nNnT { #1 } < { 100 }
       {
         0
         \int_compare:nNnT { #1 } < { 10 }
          { 0 }
       }
    }
  }
 \int_eval:n { #1 }
}
%    \end{macrocode}
%\end{macro}
%
%Append FirstId to current row.
%    \begin{macrocode}
\cs_new:Nn \__datagidx_append_firstid:n
{
  \exp_args:Nnx \dtlappendentrytocurrentrow
   {FirstId} { \__datagidx_format_anchor:n { #1 } }
}
%    \end{macrocode}
%Syntax: \meta{tl var}\marg{db-name}
%
%Gets the FirstId from the current row:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_get_firstid:Nn
{
  \dtlgetentryfromcurrentrow #1
   {
     \dtlcolumnindex { #2 } {FirstId}
   }
}
%    \end{macrocode}
%
%\begin{macro}{\@datagidx@escloc}
%    \begin{macrocode}
\newcommand*{\@datagidx@escloc}[2]
{
  \token_to_str:c { #1 } { \exp_not:N \int_eval:n { #2 } }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@escapelocation}
%\changes{3.0}{2025-03-03}{removed}
%Version 3.0: removed \cs{datagidx@escapelocation}
%\end{macro}
%\begin{macro}{\datagidx@escapelocationformat}
%\changes{3.0}{2025-03-03}{removed}
%Version 3.0: removed \cs{datagidx@escapelocationformat}
%\end{macro}
%\begin{macro}{\datagidx@clearlocationformat}
%\changes{3.0}{2025-03-03}{removed}
%Version 3.0: removed \cs{datagidx@clearlocationformat}
%\end{macro}
%
%\begin{macro}{\DTLgidxAddLocationType}
% Allow user to add their own location type. Argument must be
% control sequence name without initial backslash.
%Version 3.0: deprecated
%\changes{3.0}{2025-03-03}{deprecated}
%    \begin{macrocode}
\newcommand*{\DTLgidxAddLocationType}[1]{%
 \PackageWarning { datagidx }
  {
    \token_to_str:N \DTLgidxAddLocationType \c_space_tl ~
    deprecated. ~ Ignoring
  }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\tl_put_right:Nn \l_datatool_measure_hook_tl
 {
   \cs_set_eq:NN \glsadd \use_none:n
 }
%    \end{macrocode}
%
%\begin{macro}{\datagidx@target}
%\begin{definition}
%\cs{datagidx@target}\marg{label}\marg{format}\marg{location}\marg{text}
%\end{definition}
% Make a target if \cs{hypertarget} has been defined, but only
% write content in the document environment. Ignore the location in
% the preamble (the aux file isn't open at this point, but increment
% the anchor count).
%Version 3.0: replaced \cs{datagidx@target} with:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_target:nnnn
 {
  \__datagidx_target_create_anchor:nnn
    { #1 } { #2 } {  } 
 }
%    \end{macrocode}
%    \begin{macrocode}
\AtBeginDocument
  {
    \cs_set_eq:NN
      \__datagidx_target:nnnn
      \__datagidx_target_doc:nnnn
  }
%    \end{macrocode}
% Create just the anchor information.
%    \begin{macrocode}
\cs_new:Nn \__datagidx_target_create_anchor:nnn
 {
  \int_gincr:N \g__datagidx_anchor_count_int
  \tl_set:Nx \l__datagidx_target_tl
   {
     datagidx .
     \__datagidx_format_anchor:n { \g__datagidx_anchor_count_int }
   }
  \tl_if_empty:nTF { #3 }
  {
    \exp_args:Nx \datagidx@write@usedentry
     { #1 } { }
  }
  {
    \__datagidx_write_used_entry:xxnx
      { #1 } { #2 } { #3 } { \l__datagidx_target_tl } 
  }
 }
%    \end{macrocode}
%Document environment version:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_target_doc:nnnn
{
  \__datagidx_target_create_anchor:nnn
    { #1 } { #2 } { #3 } 
  \cs_if_exist:NT \hypertarget
  {
%    \end{macrocode}
% Make sure the current line doesn't scroll off the top of the
% screen.
%    \begin{macrocode}
    \datagidxshowifdraft
    {
      [ \l__datagidx_target_tl ]
      \discretionary {} {} {}
    }
    \group_begin:
      \datatool_measure_height:Nn
       \l__datatool_tmpa_dim { #4 }
      \raisebox { \l__datatool_tmpa_dim }
      {
        \datagidxtarget { \l__datagidx_target_tl } { }
      }
    \group_end:
  }
  \datagidxshowifdraft
    { [ #1 ] \discretionary {} {} {} }
  #4
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\glsdispentry}
%\begin{definition}
%\cs{glsdispentry}\marg{label}\marg{field}
%\end{definition}
% Short cut that fetches and displays a value.
%    \begin{macrocode}
\NewDocumentCommand \glsdispentry { m m }
{
  \DTLgidxFetchEntry \l__datagidx_value_tl { #1 } { #2 }
  \datatool_if_null:NF \l__datagidx_value_tl
   {
     \l__datagidx_value_tl
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Glsdispentry}
%\begin{definition}
%\cs{Glsdispentry}\marg{label}\marg{field}
%\end{definition}
% As previous but makes the first letter upper case.
%    \begin{macrocode}
\NewDocumentCommand \Glsdispentry { m m }
{
  \DTLgidxFetchEntry \l__datagidx_value_tl { #1 } { #2 }
  \datatool_if_null:NF \l__datagidx_value_tl
   {
    \xmakefirstuc \l__datagidx_value_tl
   }
}
\MFUaddmap { \glsdispentry } { \Glsdispentry }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxFetchEntry}
%\begin{definition}
%\cs{DTLgidxFetchEntry}\meta{tl var}\marg{label}\marg{field}
%\end{definition}
% Fetch value for the given field for the term identified by
% \meta{label} and store the value in \meta{cs} (a control sequence).
% Triggers an error and sets \meta{tl var} to null if not found.
%    \begin{macrocode}
\NewDocumentCommand \DTLgidxFetchEntry { m m m }
{
  \__datagidx_set_label:n { #2 }
  \tl_set_eq:NN #1 \dtlnovalue
%    \end{macrocode}
% Does this entry exist?
%    \begin{macrocode}
  \iftermexists { \l__datagidx_term_label_tl } 
  {
    \tl_set:Nx \l__datagidx_term_database_tl 
     {
       \__datagidx_term_database:n
        { \l__datagidx_term_label_tl }
     }
    \datatool_if_has_key:nnTF
        { \l__datagidx_term_database_tl }
        { #3 }
     {
       \__datagidx_fetch_row_for_label:NT \DTLgidxFetchEntry
        {
%    \end{macrocode}
% Get the entry for the given field in the current row and store in
% \meta{cs}.
%    \begin{macrocode}
          \dtlgetentryfromcurrentrow
           #1 
          { 
            \dtlcolumnindex { \l__datagidx_term_database_tl } { #3 }
          }
        }
     }
     {
       \PackageError { datagidx }
       {
          Database ~ ` \l__datagidx_term_database_tl ' ~
          doesn't ~ have ~ a ~ column ~ labelled ~ `#3'
       }
       {
          Check ~ that ~ you ~ have ~ correctly ~ spelt ~
          the ~ column ~ label ~ (key)
       }
     }
   }
   {
     \PackageError { datagidx }
      {
        No ~ term ~ ` \l__datagidx_term_label_tl ' ~ defined
      }
      {
        Check ~ that ~ you ~ have ~ spelt ~ the ~ label ~
        correctly ~ and ~ that ~ the ~ term ~ was ~ added ~
        to ~ the ~ database ~ with ~ \token_to_str:N \newterm
        \c_space_tl ~ or \token_to_str:N \newacro
      }
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@format}
%Version 3.0: replaced \cs{datagidx@format} with:
%    \begin{macrocode}
\tl_new:N \l__datagidx_format_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@parse@formatlabel}
%\begin{definition}
%\cs{parse@formatlabel}\marg{\oarg{format}label}
%\end{definition}
% Separate format and label from argument.
%    \begin{macrocode}
\newcommand*{\datagidx@parse@formatlabel}[1]{%
  \datagidx@parse@format@label@#1\@endparse@formatlabel@
}
\newcommand*\datagidx@parse@format@label@{%
  \@ifnextchar[{\datagidx@parse@formatlabel@}{\datagidx@parse@formatlabel@[]}%
}
\def\datagidx@parse@formatlabel@[#1]#2\@endparse@formatlabel@{%
  \tl_set:Nn \l__datagidx_format_tl { #1 }
  \__datagidx_set_label:n { #2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@datagidx@use@entry}
%\begin{definition}
%\cs{@datagidx@use@entry}\marg{link text}
%\end{definition}
% The label and format should have been stored in
% \verb|\l__datagidx_term_label_tl| and \verb|\l__datagidx_format_tl|
% before calling this  macro.
%\changes{2.19}{2014-01-17}{renamed \cs{datagidx@use@entry} to
%\cs{@datagidx@use@entry} and removed redundant field argument}
%Version 3.0: replaced \cs{@datagidx@use@entry} with:
%\end{macro}
%Syntax: \meta{calling-cs} \marg{link text}
%    \begin{macrocode}
\cs_new:Nn \__datagidx_use_entry:Nn
{
  \__datagidx_fetch_row_for_label:NT #1
   {
%    \end{macrocode}
% Get the entry for the "FirstId" field and store in 
%\verb|\l__datagidx_id_tl|
%    \begin{macrocode}
     \__datagidx_get_firstid:Nn
       \l__datagidx_id_tl
       { \l__datagidx_term_database_tl }
%    \end{macrocode}
% If it hasn't been defined set it.
%    \begin{macrocode}
     \datatool_if_null:NT \l__datagidx_id_tl 
      {
%    \end{macrocode}
% Count register hasn't been updated yet so add 1.
%    \begin{macrocode}
       \exp_args:Nx \__datagidx_append_firstid:n
        { 
          \int_eval:n
            {  \g__datagidx_anchor_count_int + \c_one_int }
        }
      }
%    \end{macrocode}
% Update the "Used" field.
%    \begin{macrocode}
     \dtlreplaceentryincurrentrow
      { 
        \dtlspecialvalue { \__datagidx_used:n { 1 } }
      }
      {
        \dtlcolumnindex
         { \l__datagidx_term_database_tl } {Used}
      }
%    \end{macrocode}
% Get the parent entry label (if one exists).
%    \begin{macrocode}
     \dtlgetentryfromcurrentrow
       \l__datagidx_parent_tl
       {
         \dtlcolumnindex
           { \l__datagidx_term_database_tl }{Parent}
       }
%    \end{macrocode}
% Current row has been edited, so we need to merge the current row
% back into the database.
%    \begin{macrocode}
     \dtlrecombine
%    \end{macrocode}
% If parent hasn't be used, give it an empty location.
%    \begin{macrocode}
     \__datagidx_mark_parent:NN
      \l__datagidx_term_database_tl \l__datagidx_parent_tl
%    \end{macrocode}
% Write the location to the auxiliary file and display value of
% field.
%    \begin{macrocode}
      \__datagidx_target:nnnn
       { \l__datagidx_term_label_tl }
       { \l__datagidx_format_tl }
       { \csuse { the\DTLgidxCounter } }
       { #2 }
   }
}
%    \end{macrocode}
%
%\begin{macro}{\datagidx@markparent}
% Syntax: \meta{db-tl-var} \meta{parent-tl-var}
% Assign empty location to parent, if location of that parent is null.
% (Recursive).
%Version 3.0: replaced \cs{datagidx@markparent} with:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_mark_parent:NN
{
  \datatool_if_null:NF #2 
   {
%    \end{macrocode}
% Write empty location to the auxiliary file.
%    \begin{macrocode}
    \__datagidx_target:nnnn { #2 } { } { } { }
%    \end{macrocode}
% Fetch this parent's parent entry.
% Get the row associated with this and make it the current row.
%    \begin{macrocode}
    \__datatool_get_row_for_value:xxx
      { #1 }
      { \dtlcolumnindex { #1 } { Label } }
      { #2 }
%    \end{macrocode}
% Get the entry for the "FirstId" field and store in 
%\verb|\l__datagidx_id_tl|
%    \begin{macrocode}
      \__datagidx_get_firstid:Nn
        \l__datagidx_id_tl
        { \l__datagidx_term_database_tl }
%    \end{macrocode}
% If it hasn't been defined set it.
%    \begin{macrocode}
    \datatool_if_null:NT \l__datagidx_id_tl 
    {
      \__datagidx_append_firstid:n
       { 
         \g__datagidx_anchor_count_int
       }
    }
%    \end{macrocode}
% Get the parent
%    \begin{macrocode}
      \dtlgetentryfromcurrentrow
        \l__datagidx_parent_tl
        {\dtlcolumnindex{#1}{Parent}}%
%    \end{macrocode}
% Current row has been edited, so we need to merge the current row
% back into the database.
%    \begin{macrocode}
    \dtlrecombine
%    \end{macrocode}
% Recurse
%    \begin{macrocode}
    \__datagidx_mark_parent:NN #1 \l__datagidx_parent_tl
  }
}
%    \end{macrocode}
%\end{macro}
%Syntax: \marg{label}\marg{format}\marg{location}\marg{target}
%    \begin{macrocode}
\cs_new:Nn \__datagidx_write_used_entry:nnnn
{
  \datagidx@write@usedentry
    { #1 }
    { [ #2 ] { #3 } { #4 } }
}
\cs_generate_variant:Nn
  \__datagidx_write_used_entry:nnnn
   { xxnx }
%    \end{macrocode}
%
%\begin{macro}{\datagidx@write@usedentry}
% Write out location to aux file and add location to the location
% list for the current run.
%    \begin{macrocode}
\newcommand*{\datagidx@write@usedentry}[2]{%
%    \end{macrocode}
% Do update if `highopt optimize' setting is on.
%    \begin{macrocode}
  \datagidx@do@highopt@update{#1}%
%    \end{macrocode}
%    \begin{macrocode}
  \protected@edef \l__datagidx_location_tl { #2 }
%    \end{macrocode}
% Write out location to aux file.
%    \begin{macrocode}
  \protected@write{\@auxout}{}%
    {%
      \token_to_str:N \datagidx@aux@usedentry
       { #1 } { #2 }
       { \tl_to_str:N \l__datagidx_location_tl }
    }%
%    \end{macrocode}
% This may be a page off if indexing occurs by a page break,
% so it should be checked with UnsafeLocation rather than Location.
%\changes{2.15}{2013-07-10}{Added check for page counter}
%\changes{3.0}{2025-03-03}{Removed check for page counter}
%    \begin{macrocode}
   \__datagidx_used_entry:nxx
      { CurrentLocation } { #1 }
      { \tl_to_str:N \l__datagidx_location_tl }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@xusedentry}
%\begin{definition}
%\cs{datagidx@usedentry}\marg{location tag}\marg{label}\marg{location}
%\end{definition}
% Like \cs{datagidx@usedentry} but expands the location. Unlike 
% \cs{datagidx@usedentry} the first argument isn't optional.
% Version 3.0: replaced \cs{datagidx@xusedentry} with:
%    \begin{macrocode}
\cs_new:Nn \__datagidx_used_entry:nnn
{
  \datagidx@usedentry [ #1 ] { #2 } { #3 }
}
\cs_generate_variant:Nn
 \__datagidx_used_entry:nnn
 { xxx, nxx }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@usedentry}
%\begin{definition}
%\cs{datagidx@usedentry}\oarg{location tag}\marg{label}\marg{location}
%\end{definition}
% Add to the location list for the given entry.
%    \begin{macrocode}
\newcommand*{\datagidx@usedentry}[3][Location]{%
%    \end{macrocode}
% Check if label exists. (It may have been deleted or had a label
% change.)
%    \begin{macrocode}
  \iftermexists { #2 }
  {%
%    \end{macrocode}
% Fetch the name of the database with which this entry is
% associated.
%    \begin{macrocode}
    \tl_set:Nx \l__datagidx_term_database_tl
      {  \__datagidx_term_database:n { #2 } }
%    \end{macrocode}
% Get the row associated with this label and make it the current row.
%    \begin{macrocode}
    \__datatool_get_row_for_value:xxx
      { \l__datagidx_term_database_tl }
      { \dtlcolumnindex{\l__datagidx_term_database_tl}{Label} }
      { #2 }
%    \end{macrocode}
% Get the entry for the \meta{location tag} field in the current row and store in
% \verb|\l__datagidx_location_tl|.
%    \begin{macrocode}
    \dtlgetentryfromcurrentrow
      \l__datagidx_location_tl
      { \dtlcolumnindex { \l__datagidx_term_database_tl } { #1 } }
    \__datagidx_unwrap_location:N
      \l__datagidx_location_tl
%    \end{macrocode}
% Check the success of the previous command.
%    \begin{macrocode}
    \datatool_if_null:NTF \l__datagidx_location_tl
     {
%    \end{macrocode}
% There's no \meta{location tag} field in the current row, so add one with
% the given location.
%    \begin{macrocode}
      \tl_set:Nn \l__datagidx_location_tl { #3 }
      \__datagidx_unwrap_location:N
        \l__datagidx_location_tl
      \exp_args:Nne \dtlappendentrytocurrentrow
        { #1 }
        {
          \exp_not:N \dtlspecialvalue
          {
            \exp_not:N \__datagidx_location:n
            {
              \exp_not:V \l__datagidx_location_tl
            }
          }
        }
     }
     {
%    \end{macrocode}
% There is a \meta{location tag} field in the current row, so append the
% given location to the list, unless one or the other is empty.
%    \begin{macrocode}
      \clist_clear:N \l__datagidx_location_clist
      \tl_if_empty:NTF \l__datagidx_location_tl
      {
        \clist_put_right:Nn \l__datagidx_location_clist {#3}
      }
      {
        \clist_set:NV \l__datagidx_location_clist
          \l__datagidx_location_tl
        \tl_if_empty:nF { #3 }
        {
          \clist_put_right:Nn \l__datagidx_location_clist { #3 }
        }
      }
%    \end{macrocode}
% and update the entry in the current row.
%    \begin{macrocode}
      \exp_args:Nx \dtlreplaceentryincurrentrow
        {
          \exp_not:N \dtlspecialvalue
          {
            \exp_not:N \__datagidx_location:n
            {
              \clist_use:Nn \l__datagidx_location_clist { , }
            }
          }
        }
        { \dtlcolumnindex { \l__datagidx_term_database_tl } {#1} }
     }
%    \end{macrocode}
% Current row has been edited, so we need to merge the current row
% back into the database.
%    \begin{macrocode}
    \dtlrecombine
  }
  {
    \PackageWarning{datagidx}{No ~ term ~ `#2' ~ defined. ~ Ignoring}
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@aux@usedentry}
%\begin{definition}
%\cs{datagidx@aux@usedentry}\marg{label}\marg{location}\marg{unsafe location}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%This command is for the aux file. The unsafe location is a location 
%that may be off due to the delayed shipout. The value is used to
%compare with the current location to determine if a rerun is
%required.
%    \begin{macrocode}
\newcommand*{\datagidx@aux@usedentry}[3]
{
%    \end{macrocode}
% Check if label exists. (It may have been deleted or had a label
% change.)
%    \begin{macrocode}
  \iftermexists { #1 }
  {
%    \end{macrocode}
% Fetch the name of the database with which this entry is
% associated.
%    \begin{macrocode}
    \tl_set:Nx \l__datagidx_term_database_tl
      {  \__datagidx_term_database:n { #1 } }
%    \end{macrocode}
% Get the row associated with this label and make it the current row.
%    \begin{macrocode}
    \__datatool_get_row_for_value:xxxT
      { \l__datagidx_term_database_tl }
      { \dtlcolumnindex{\l__datagidx_term_database_tl}{Label} }
      { #1 }
    {
%    \end{macrocode}
% Get the entry for the Location field in the current row and store in
% \verb|\l__datagidx_location_tl|.
%    \begin{macrocode}
      \dtlgetentryfromcurrentrow
        \l__datagidx_location_tl
        { \dtlcolumnindex { \l__datagidx_term_database_tl } { Location } }
      \__datagidx_unwrap_location:N
        \l__datagidx_location_tl
%    \end{macrocode}
% Check the success of the previous command.
%    \begin{macrocode}
      \datatool_if_null:NTF \l__datagidx_location_tl
       {
%    \end{macrocode}
% There's no Location field in the current row, so add one with
% the given location.
%    \begin{macrocode}
        \tl_set:Nn \l__datagidx_location_tl { #2 }
        \__datagidx_unwrap_location:N
          \l__datagidx_location_tl
        \exp_args:Nne \dtlappendentrytocurrentrow
          { Location }
          {
            \exp_not:N \dtlspecialvalue
            {
              \exp_not:N \__datagidx_location:n
              {
                \exp_not:V \l__datagidx_location_tl
              }
            }
          }
       }
       {
%    \end{macrocode}
% There is a Location field in the current row, so append the
% given location to the list, unless one or the other is empty.
%    \begin{macrocode}
        \clist_clear:N \l__datagidx_location_clist
        \tl_if_empty:NTF \l__datagidx_location_tl
        {
          \clist_put_right:Nn \l__datagidx_location_clist
            { #2 }
        }
        {
          \clist_set:NV \l__datagidx_location_clist
            \l__datagidx_location_tl
          \tl_if_empty:nF { #2 }
          {
            \clist_put_right:Nn \l__datagidx_location_clist
              { #2 }
          }
        }
%    \end{macrocode}
% and update the entry in the current row.
%    \begin{macrocode}
        \exp_args:Nx \dtlreplaceentryincurrentrow
          {
            \exp_not:N \dtlspecialvalue
            {
              \exp_not:N \__datagidx_location:n
              {
                \clist_use:Nn \l__datagidx_location_clist { , }
              }
            }
          }
          {
            \dtlcolumnindex
              { \l__datagidx_term_database_tl }
              { Location }
          }
       }
%    \end{macrocode}
% Get the entry for the UnsafeLocation field in the current row and store in
% \verb|\l__datagidx_location_tl|.
%    \begin{macrocode}
      \dtlgetentryfromcurrentrow
        \l__datagidx_location_tl
        {
          \dtlcolumnindex
          { \l__datagidx_term_database_tl }
          { UnsafeLocation }
        }
      \__datagidx_unwrap_location:N
        \l__datagidx_location_tl
%    \end{macrocode}
% Check the success of the previous command.
%    \begin{macrocode}
      \datatool_if_null:NTF \l__datagidx_location_tl
       {
%    \end{macrocode}
% There's no UnsafeLocation field in the current row, so add one with
% the given location.
%    \begin{macrocode}
        \tl_set:Nn \l__datagidx_location_tl { #3 }
        \__datagidx_unwrap_location:N
          \l__datagidx_location_tl
        \exp_args:Nne \dtlappendentrytocurrentrow
          { UnsafeLocation }
          {
            \exp_not:N \dtlspecialvalue
            {
              \exp_not:N \__datagidx_location:n
              {
                \tl_to_str:N \l__datagidx_location_tl
              }
            }
          }
       }
       {
%    \end{macrocode}
% There is an UnsafeLocation field in the current row, so append the
% given location to the list, unless one or the other is empty.
%    \begin{macrocode}
        \clist_clear:N \l__datagidx_location_clist
        \tl_if_empty:NTF \l__datagidx_location_tl
        {
          \tl_set:Nn \l__datagidx_location_tl { #3 }
          \__datagidx_unwrap_location:N
            \l__datagidx_location_tl
          \clist_put_right:Nx \l__datagidx_location_clist
            { \tl_to_str:N \l__datagidx_location_tl }
        }
        {
          \clist_set:NV \l__datagidx_location_clist
            \l__datagidx_location_tl
          \tl_if_empty:nF { #3 }
          {
            \tl_set:Nn \l__datagidx_location_tl { #3 }
            \__datagidx_unwrap_location:N
              \l__datagidx_location_tl
            \clist_put_right:Nx \l__datagidx_location_clist
              { \tl_to_str:N \l__datagidx_location_tl }
          }
        }
%    \end{macrocode}
% and update the entry in the current row.
%    \begin{macrocode}
        \exp_args:Nx \dtlreplaceentryincurrentrow
          {
            \exp_not:N \dtlspecialvalue
            {
              \exp_not:N \__datagidx_location:n
              {
                \clist_use:Nn \l__datagidx_location_clist { , }
              }
            }
          }
          {
            \dtlcolumnindex
              { \l__datagidx_term_database_tl }
              { UnsafeLocation }
          }
       }
%    \end{macrocode}
% Current row has been edited, so we need to merge the current row
% back into the database.
%    \begin{macrocode}
      \dtlrecombine
    }
  }
  {
    \PackageWarning{datagidx}{No ~ term ~ `#1' ~ defined. ~ Ignoring}
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@save@loc}
% Store the current location from the previous run.
%Version 3.0: no longer used but still defined until next version
%as it may be in the aux file of existing documents.
%TODO: remove
%    \begin{macrocode}
\newcommand*{\datagidx@save@loc}[2]{%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\glsadd}
%\begin{definition}
%\cs{glsadd}\marg{\oarg{format}label}
%\end{definition}
%    \begin{macrocode}
\NewDocumentCommand \glsadd { m }
{
  \group_begin:
%    \end{macrocode}
% Check term has been defined.
%    \begin{macrocode}
  \iftermexists { \l__datagidx_term_label_tl }
  {
%    \end{macrocode}
%    \begin{macrocode}
    \datagidx@parse@formatlabel{#1}%
%    \end{macrocode}
% Write the location to the auxiliary file.
%    \begin{macrocode}
    \__datagidx_target:nnnn
      { \l__datagidx_term_label_tl }
      { \l__datagidx_format_tl }
      { \csuse{the\DTLgidxCounter} }
      { }
%    \end{macrocode}
% Fetch the name of the database with which this entry is
% associated.
%    \begin{macrocode}
    \tl_set:Nx \l__datagidx_term_database_tl
     { \__datagidx_term_database:n { \l__datagidx_term_label_tl } }
%    \end{macrocode}
% Get the row associated with this label and make it the current row.
%    \begin{macrocode}
    \__datatool_get_row_for_value:xxxTF
      { \l__datagidx_term_database_tl }
      { \dtlcolumnindex { \l__datagidx_term_database_tl } {Label} }
      { \l__datagidx_term_label_tl }
     {
%    \end{macrocode}
% Update the "Used" field.
%    \begin{macrocode}
       \dtlreplaceentryincurrentrow
        {
          \dtlspecialvalue { \__datagidx_used:n { 1 } }
        }
        {
          \dtlcolumnindex { \l__datagidx_term_database_tl } {Used}
        }
%    \end{macrocode}
% Get the entry for the "FirstId" field and store in
% \verb|\l__datagidx_id_tl|
%    \begin{macrocode}
       \__datagidx_get_firstid:Nn
         \l__datagidx_id_tl
         { \l__datagidx_term_database_tl }
%    \end{macrocode}
% If it hasn't been defined set it.
%    \begin{macrocode}
       \datatool_if_null:NT \l__datagidx_id_tl 
        {
          \__datagidx_append_firstid:n
           { \g__datagidx_anchor_count_int }
        }
%    \end{macrocode}
% Current row has been edited, so we need to merge the current row
% back into the database.
%    \begin{macrocode}
       \dtlrecombine
     }
     {
%    \end{macrocode}
% The label to database mapping has been found but the label can't
% be found in the database.
%    \begin{macrocode}
       \PackageError { datagidx }
        {
          No ~ row ~ found ~ for ~ label ~ 
          ` \l__datagidx_term_label_tl ' ~ in ~
          database ~ ` \l__datagidx_term_database_tl '
        }
        {
          Something ~ has ~ gone ~ wrong ~ with ~ the ~ underlying ~
          structure. ~ Did ~ you ~ define ~ ` \l__datagidx_term_label_tl ' ~
          using ~ \token_to_str:N \newterm \c_space_tl or
          \token_to_str:N \newacro ? ~ Has ~ the ~ database ~
          ` \l__datagidx_term_database_tl ' ~ been ~ modified ~
          outside ~ of ~ datagidx.sty ~ commands?
        }
     }
  }
  {
    \PackageError {datagidx}
     { Term ~ `\l__datagidx_term_label_tl' ~ doesn't ~ exist }
     {
       Check ~ the ~ spelling ~ of ~ the ~ label. ~ Verbose ~ mode ~
       will ~ show ~ the ~ labels ~ in ~ the ~ transcript ~ for ~
       each ~ new ~ term
     }
  }
 \group_end:
}
\MFUexcl { \glsadd }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@count}
%\changes{3.0}{2025-03-03}{removed}
% Version 3.0: Removed \cs{datagidx@count}
%\end{macro}
%
%\begin{macro}{\glsaddall}
%\begin{definition}
%\cs{glsaddall}\marg{db}
%\end{definition}
% Adds all entries in the given database.
%    \begin{macrocode}
\NewDocumentCommand \glsaddall { m } 
{
  \DTLifdbexists{#1}%
  {
    \int_step_inline:nn
       { \DTLrowcount { #1 } }
    {
      \datatool_get_row:nnT { #1 } { ##1 }
      {
%    \end{macrocode}
% Get the label for this row.
%\changes{2.19}{2014-01-17}{Fixed bug in database reference}
%    \begin{macrocode}
        \dtlgetentryfromcurrentrow
           \l__datagidx_term_label_tl 
          { \dtlcolumnindex { #1 } {Label} }
%    \end{macrocode}
% Write blank location to the auxiliary file but temporarily change
% \cs{hypertarget} as it doesn't make sense to have a target here.
%    \begin{macrocode}
        \group_begin:
          \cs_set_eq:NN \hypertarget \use_ii:nn
          \__datagidx_target:nnnn
            { \l__datagidx_term_label_tl }{}{}{}
        \group_end:
%    \end{macrocode}
% Update the "Used" field.
%\changes{2.19}{2014-01-17}{Fixed bug in database reference}
%    \begin{macrocode}
         \dtlreplaceentryincurrentrow
          {
            \dtlspecialvalue { \__datagidx_used:n { 1 } }
          }
          { \dtlcolumnindex { #1 } {Used} }
%    \end{macrocode}
% Get the entry for the "FirstId" field and store in
% \verb|\l__datagidx_id_tl|
%\changes{2.19}{2014-01-17}{Fixed bug in database reference}
%    \begin{macrocode}
        \__datagidx_get_firstid:Nn
          \l__datagidx_id_tl { #1 }
%    \end{macrocode}
% If it hasn't been defined set it.
%    \begin{macrocode}
        \datatool_if_null:NT \l__datagidx_id_tl 
        {
          \__datagidx_append_firstid:n
           { \g__datagidx_anchor_count_int }
        }
%    \end{macrocode}
% Current row has been edited, so we need to merge the current row
% back into the database.
%    \begin{macrocode}
        \dtlrecombine
      }
    }
  }
  {
    \PackageError {datagidx}
    {
      \token_to_str:N \glsaddall : ~
       Database ~ `#1' ~ doesn't ~ exist
    }
    {
      Check ~ that ~ you ~ have ~ correctly ~ spelt ~ the ~ database
      ~ name ~ in ~ the ~ argument ~ of \token_to_str:N \glsaddall
    }
  }
}
\MFUexcl { \glsaddall }
%    \end{macrocode}
%\end{macro}
%
%Version 3.0 has added grouping to the following to prevent leakage
%of scratch token list variables.
%
%\begin{macro}{\glslink}
%\begin{definition}
%\cs{glslink}\marg{label}\marg{text}
%\end{definition}
% Use given entry but user supplies text.
%    \begin{macrocode}
\NewDocumentCommand \glslink { m m }
{
 \group_begin:
  \datagidx@parse@formatlabel {#1}
  \datagidxlink { \l__datagidx_term_label_tl }
  {
    \__datagidx_use_entry:Nn \glslink { #2 }
  }
 \group_end:
}
\NewDocumentCommand \Glslink { m m }
{
  \glslink { #1 } { \makefirstuc { #2 } }
}
\MFUaddmap { \glslink } { \Glslink }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\useentry}
%\begin{definition}
%\cs{useentry}\marg{label}\marg{field}
%\end{definition}
% Fetch and use the given field for the given entry.
%    \begin{macrocode}
\NewDocumentCommand \useentry { m m }
{
 \group_begin:
  \datagidx@parse@formatlabel { #1 }
  \DTLgidxFetchEntry
    \l__datagidx_value_tl
    { \l__datagidx_term_label_tl } { #2 }
%    \end{macrocode}
% Just test for null, \cs{DTLgidxFetchEntry} will have already
% triggered an error.
%    \begin{macrocode}
  \datatool_if_null:NF \l__datagidx_value_tl
  {
    \datagidxlink { \l__datagidx_term_label_tl }
    {
      \__datagidx_use_entry:Nn \useentry
        { \l__datagidx_value_tl }
    }
  }
 \group_end:
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\Useentry}
%\begin{definition}
%\cs{Useentry}\marg{label}\marg{field}
%\end{definition}
% As \cs{useentry}, but capitalise the first word.
%    \begin{macrocode}
\NewDocumentCommand \Useentry { m m }
{
 \group_begin:
  \datagidx@parse@formatlabel { #1 }
  \DTLgidxFetchEntry
    \l__datagidx_value_tl
     { \l__datagidx_term_label_tl } { #2 }
  \datatool_if_null:NF \l__datagidx_value_tl
  {
    \datagidxlink { \l__datagidx_term_label_tl }
    {
      \__datagidx_use_entry:Nn \Useentry
        { \xmakefirstuc {  \l__datagidx_value_tl } }
    }
  }
 \group_end:
}
\MFUaddmap { \useentry } { \Useentry }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\USEentry}
%\begin{definition}
%\cs{USEentry}\marg{label}\marg{field}
%\end{definition}
% As \cs{useentry}, but make the whole term upper case.
%    \begin{macrocode}
\NewDocumentCommand \USEentry { m m }
{
 \group_begin:
  \datagidx@parse@formatlabel { #1 }
  \DTLgidxFetchEntry
    \l__datagidx_value_tl
     { \l__datagidx_term_label_tl } { #2 }
  \datatool_if_null:NF \l__datagidx_value_tl
  {
    \datagidxlink { \l__datagidx_term_label_tl }
    {
      \__datagidx_use_entry:Nn \USEentry
       { \text_uppercase:n { \l__datagidx_value_tl } }
    }
  }
 \group_end:
}
\MFUexcl { \USEentry }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\useentrynl}
%\begin{definition}
%\cs{useentrynl}\marg{label}\marg{field}
%\end{definition}
% Fetch and use the given field for the given entry without creating
% a hyperlink.
%    \begin{macrocode}
\NewDocumentCommand \useentrynl { m m }
{
 \group_begin:
  \datagidx@parse@formatlabel { #1 }
  \DTLgidxFetchEntry
    \l__datagidx_value_tl
      { \l__datagidx_term_label_tl } { #2 }
  \datatool_if_null:NF \l__datagidx_value_tl
  {
    \__datagidx_use_entry:Nn \useentrynl
      { \l__datagidx_value_tl }
  }
 \group_end:
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\Useentrynl}
%\begin{definition}
%\cs{Useentrynl}\marg{label}\marg{field}
%\end{definition}
% As \cs{useentry}, but capitalise the first word.
%    \begin{macrocode}
\NewDocumentCommand \Useentrynl { m m }
{
 \group_begin:
  \datagidx@parse@formatlabel { #1 }
  \DTLgidxFetchEntry
    \l__datagidx_value_tl
     { \l__datagidx_term_label_tl } { #2 }
  \datatool_if_null:NF \l__datagidx_value_tl
  {
    \__datagidx_use_entry:Nn \Useentrynl
      { \xmakefirstuc {  \l__datagidx_value_tl } }
  }
 \group_end:
}
\MFUaddmap { \useentrynl } { \Useentrynl }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\USEentrynl}
%\begin{definition}
%\cs{USEentrynl}\marg{label}\marg{field}
%\end{definition}
% As \cs{useentry}, but make the whole term upper case.
%    \begin{macrocode}
\NewDocumentCommand \USEentrynl { m m }
{
 \group_begin:
  \datagidx@parse@formatlabel { #1 }
  \DTLgidxFetchEntry
    \l__datagidx_value_tl 
      { \l__datagidx_term_label_tl } { #2 }
  \datatool_if_null:NF \l__datagidx_value_tl
  {
    \__datagidx_use_entry:Nn \USEentrynl
     { \text_uppercase:n {  \l__datagidx_value_tl } }
  }
 \group_end:
}
\MFUexcl { \USEentrynl } 
%    \end{macrocode}
%\end{macro}
%
% Short cuts to common fields.
%\begin{macro}{\gls}
%    \begin{macrocode}
\newrobustcmd*{\gls}[1]{\useentry{#1}{Text}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\glspl}
%    \begin{macrocode}
\newrobustcmd*{\glspl}[1]{\useentry{#1}{Plural}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\Gls}
%    \begin{macrocode}
\newrobustcmd*{\Gls}[1]{\Useentry{#1}{Text}}
\MFUaddmap { \gls } { \Gls }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\Glspl}
%    \begin{macrocode}
\newrobustcmd*{\Glspl}[1]{\Useentry{#1}{Plural}}
\MFUaddmap { \glspl } { \Glspl }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\glsnl}
%    \begin{macrocode}
\newrobustcmd*{\glsnl}[1]{\useentrynl{#1}{Text}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\glsplnl}
%    \begin{macrocode}
\newrobustcmd*{\glsplnl}[1]{\useentrynl{#1}{Plural}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\Glsnl}
%    \begin{macrocode}
\newrobustcmd*{\Glsnl}[1]{\Useentrynl{#1}{Text}}
\MFUaddmap { \glsnl } { \Glsnl }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\Glsplnl}
%    \begin{macrocode}
\newrobustcmd*{\Glsplnl}[1]{\Useentrynl{#1}{Plural}}
\MFUaddmap { \glsplnl } { \Glsplnl }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\glssym}
%    \begin{macrocode}
\newrobustcmd*{\glssym}[1]{\useentry{#1}{Symbol}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\Glssym}
%    \begin{macrocode}
\newrobustcmd*{\Glssym}[1]{\Useentry{#1}{Symbol}}
\MFUaddmap { \glssym } { \Glssym }
%    \end{macrocode}
%\end{macro}
%
%\subsection{Using Acronyms}
%\begin{macro}{\DTLgidxFormatAcr}
%\begin{definition}
%\cs{DTLgidxFormatAcr}\marg{label}\marg{long field}\marg{short field}
%\end{definition}
%    \begin{macrocode}
\newcommand*{\DTLgidxFormatAcr}[3]{%
  \DTLgidxAcrStyle{\glsdispentry{#1}{#2}}{\useentry{#1}{#3}}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxFormatAcrUC}
%\begin{definition}
%\cs{DTLgidxFormatAcr}\marg{label}\marg{long field}\marg{short field}
%\end{definition}
%As previous but capitalise first word.
%    \begin{macrocode}
\newcommand*{\DTLgidxFormatAcrUC}[3]{%
  \DTLgidxAcrStyle{\Glsdispentry{#1}{#2}}{\useentry{#1}{#3}}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\acr}
%    \begin{macrocode}
\newrobustcmd*{\acr}[1]{%
  \ifentryused{#1}%
  {\useentry{#1}{Short}}%
  {\DTLgidxFormatAcr{#1}{Long}{Short}}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\acrpl}
%    \begin{macrocode}
\newrobustcmd*{\acrpl}[1]{%
  \ifentryused{#1}%
  {\useentry{#1}{ShortPlural}}%
  {\DTLgidxFormatAcr{#1}{LongPlural}{ShortPlural}}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\Acr}
%    \begin{macrocode}
\newrobustcmd*{\Acr}[1]{%
  \ifentryused{#1}%
  {\Useentry{#1}{Short}}%
  {\DTLgidxFormatAcrUC{#1}{Long}{Short}}%
}
\MFUaddmap { \acr } { \Acr }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\Acrpl}
%    \begin{macrocode}
\newrobustcmd*{\Acrpl}[1]{%
  \ifentryused{#1}%
  {\Useentry{#1}{ShortPlural}}%
  {\DTLgidxFormatAcrUC{#1}{LongPlural}{ShortPlural}}%
}
\MFUaddmap { \acrpl } { \Acrpl }
%    \end{macrocode}
%\end{macro}
%
%\section{Displaying Glossaries, Lists of Acronyms, Indices}
%
%\begin{macro}{\printtermsstartpar}
%\changes{2.28}{2017-11-10}{new}
%    \begin{macrocode}
\newcommand{\printtermsstartpar}{\par}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@prestart}
%Version 3.0: replaced \cs{datagidx@prestart} with:
%    \begin{macrocode}
\tl_new:N \l__datagidx_prestart_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\printterms@setupmulticol}
%\changes{2.28}{2017-11-10}{new}
%    \begin{macrocode}
\newcommand*{\printterms@setupmulticol}{
 \tl_if_empty:NTF \l__datagidx_post_heading_tl
 {
   \tl_set:Nx \l__datagidx_prestart_tl
    {
      \exp_not:N \l__datagidx_heading_tl
        { \exp_not:N \l__datagidx_title_tl }
      \exp_not:N \begin
        { \l__datagidx_multicols_tl }
        { \int_use:N \l__datagidx_columns_int }
    }
 }
 {
   \tl_set:Nx \l__datagidx_prestart_tl
    {
      \exp_not:N \l__datagidx_heading_tl
        {\exp_not:N \l__datagidx_title_tl }
      \exp_not:N \begin { \l__datagidx_multicols_tl }
        { \int_use:N \l__datagidx_columns_int }
      [ \exp_not:N \l__datagidx_post_heading_tl ]
   }
 }
 \tl_set:Nx \datagidx@postend
  {
    \exp_not:N \end { \l__datagidx_multicols_tl }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\printterms@setuptwocol}
%\changes{2.28}{2017-11-10}{new}
%    \begin{macrocode}
\newcommand*{\printterms@setuptwocol}{
 \tl_set:Nn \l__datagidx_prestart_tl
  {
    \twocolumn
     [
       \l__datagidx_heading_tl
         { \l__datagidx_title_tl }
       \l__datagidx_post_heading_tl
     ]
  }
 \if@twocolumn
   \tl_clear:N \datagidx@postend
 \else
   \tl_set:Nn \datagidx@postend
    { \printtermsrestoreonecolumn }
 \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\printtermsrestoreonecolumn}
%    \begin{macrocode}
\newcommand{\printtermsrestoreonecolumn}{\onecolumn}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\printterms}
%\begin{definition}
%\cs{printterms}[options]
%\end{definition}
% Print the list of terms
%    \begin{macrocode}
\NewDocumentCommand \printterms { o }
{
\group_begin:
%    \end{macrocode}
% Ensure that sorting has a global effect:
%    \begin{macrocode}
  \bool_set_true:N \l__datatool_db_global_bool
%    \end{macrocode}
% Initialise key list for style:
%    \begin{macrocode}
  \clist_clear:N \l__datagidx_styles_clist
%    \end{macrocode}
% Set options. If the database key is used, this will set
% \verb|\l__datagidx_default_database_tl|
%    \begin{macrocode}
  \IfValueT { #1 }
   {
     \keys_set_filter:nnnN
       { datatool / index } { general } { #1 }
       \l__datagidx_remainder_tl
     \tl_if_empty:NF \l__datagidx_remainder_tl
      {
        \PackageError { datagidx }
         {
           Invalid ~ \token_to_str:N \printterms \c_space_tl option(s): ~ 
            \tl_to_str:N \l__datagidx_remainder_tl
         }
         {
           The ~ listed ~ option ~ or ~ options ~ can't ~ be ~ passed ~ 
           to ~ \token_to_str:N \printterms . \MessageBreak  
           Try ~ \token_to_str:N \DTLsetup
            { index = { \tl_to_str:N \l__datagidx_remainder_tl } } 
         }
      }
   }
%    \end{macrocode}
% Set the database.
%    \begin{macrocode}
  \tl_set_eq:NN
   \l__datagidx_term_database_tl
   \l__datagidx_default_database_tl 
%    \end{macrocode}
% Check if database exists.
%    \begin{macrocode}
  \DTLifdbexists { \l__datagidx_term_database_tl }
  {
%    \end{macrocode}
% Provide user the means to access the current database name.
%    \begin{macrocode}
   \tl_set_eq:NN
     \DTLgidxCurrentdb
     \l__datagidx_term_database_tl 
%    \end{macrocode}
% Get the fields from datagidx:
%    \begin{macrocode}
   \__datatool_get_row_for_value:xxxTF
     { datagidx }
     { \dtlcolumnindex{datagidx}{Glossary} }
     { \l__datagidx_term_database_tl }
   {
    \dtlgetentryfromcurrentrow
      \l__datagidx_title_tl
      {\dtlcolumnindex{datagidx}{Title}}%
    \dtlgetentryfromcurrentrow
      \l__datagidx_heading_tl
      {\dtlcolumnindex{datagidx}{Heading}}%
    \dtlgetentryfromcurrentrow
      \l__datagidx_post_heading_tl
      { \dtlcolumnindex {datagidx} {PostHeading} }
    \dtlgetentryfromcurrentrow
      \l__datagidx_multicols_tl
      { \dtlcolumnindex {datagidx} {MultiCols} }
    \dtlgetentryfromcurrentrow
      \l__datagidx_sort_tl
      { \dtlcolumnindex {datagidx} {Sort} }
    \dtlgetentryfromcurrentrow
      {\datagidx@style}%
      {\dtlcolumnindex{datagidx}{Style}}%
    \dtlgetentryfromcurrentrow
      {\datagidx@showgroups}%
      {\dtlcolumnindex{datagidx}{ShowGroups}}%
%    \end{macrocode}
% Allow user to override style here.
%    \begin{macrocode}
    \keys_set_groups:nnV { datatool / index } { print }
       \l__datagidx_styles_clist 
%    \end{macrocode}
% Do we need to use \env{multicols}?
%    \begin{macrocode}
    \int_case:nnF { \l__datagidx_columns_int }
     {
       { \c_one_int }
         {
           \tl_clear:N \l__datagidx_post_heading_tl
           \tl_clear:N \datagidx@postend
         }
       { 2 }
         {
           \ifdatagidxbalance
             \printterms@setupmulticol
           \else
             \printterms@setuptwocol
           \fi
         }
     }
     {
       \printterms@setupmulticol
     }
    \tl_set_eq:NN \@dtl@dbname \DTLgidxCurrentdb
%    \end{macrocode}
% Set the style
%    \begin{macrocode}
    \csuse{datagidxshowgroups\datagidx@showgroups}%
    \datagidxsetstyle{\datagidx@style}%
%    \end{macrocode}
% Now display the glossary/index:
%    \begin{macrocode}
    \int_compare:nNnT
      { \l__datagidx_columns_int } = { \c_one_int }
     {
       \l__datagidx_heading_tl
         { \l__datagidx_title_tl }
       \l__datagidx_post_heading_tl
     }
    \datagidx@do@sort
    \l__datagidx_prestart_tl
%    \end{macrocode}
%\changes{2.28}{2017-11-10}{added paragraph break at the start}:
%    \begin{macrocode}
    \printtermsstartpar
    \datagidxstart
    \let\DTLgidxName\datagidx@invert
    \let\DTLgidxPlace\datagidx@invert
    \let\DTLgidxSubject\datagidx@invert
    \let\DTLgidxOffice\datagidx@invert
    \DTLgidxForeachEntry
     {
       \datagidxitem
     }
    \datagidxend
    \datagidx@postend
   }
   {
     \PackageError { datagidx }
      {
        \token_to_str:N \printterms : ~
        Unable ~ to ~ access ~ `Glossary' ~
        column ~ in ~ ` datagidx ' ~
        database ~ matching ~ ` \l__datagidx_term_database_tl '
      }
      { }
   }
  }
  {
%    \end{macrocode}
% Database doesn't exist.
%    \begin{macrocode}
    \PackageError{datagidx}%
    {
      Glossary/index ~ database ~
      `\l__datagidx_term_database_tl' ~ doesn't ~ exist
    }
    {
      You ~ must ~ define ~ the ~ glossary/index ~ 
      database ~ before ~ you ~ can ~ use ~ it.
    }
  }
 \expandafter \group_end: \if@endpe \@doendpe \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@getgroup}
% Version 3.0: removed \cs{datagidx@getgroup}
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\DTLgidxGroupHeaderTitle}
% Produce the group title from the group label.
%    \begin{macrocode}
\newcommand*{\DTLgidxGroupHeaderTitle}[1]{%
  \cs_if_exist_use:cF
  { datagidx #1 name }
  { #1 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgidxForeachEntry}
%\begin{definition}
%\cs{DTLgidxForeachEntry}\marg{body}
%\end{definition}
% Iterate through the current database, but only do \meta{body}
% if there is a location or cross-reference.
%\changes{2.14}{2013-06-28}{Added optional argument when using
%\cs{DTLforeach}}
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{switched to \cs{DTLmapdata}}
%    \begin{macrocode}
\NewDocumentCommand \DTLgidxForeachEntry { m }
{
 \tl_clear:N \datagidxprevgroup
 \DTLmapdata [ name = \DTLgidxCurrentdb , read-only ]
  {
%    \end{macrocode}
%Don't trigger an error if a column key is undefined. This
%may simply mean that the column was only added for some
%glossary/index databases and not others. The return value
%will be null in that case.
%    \begin{macrocode}
    \exp_args:NV \__datatool_map_get_values_noerr:n 
       \DTLgidxAssignList
       \__datagidx_filter:T 
     {
%    \end{macrocode}
% Iterate through top-level entries.
%    \begin{macrocode}
       \datatool_if_null:NT \Parent 
        {
%    \end{macrocode}
% The letter group needs to be expanded to allow
% non-letter groups to merge if they expand to a word
% (such as Symbols) regardless of their argument.
%    \begin{macrocode}
         \legacy_if:nT { datagidxshowgroups }
          {
            \tl_set:Ne \datagidxcurrentgroup
             { \datagidxcurrentgroup }
          }
         \datagidx@doifdisplayed
          {
            \seq_gput_right:NV
              \g__datagidx_refd_labels_seq
              \Label
%    \end{macrocode}
% Initialise level.
%    \begin{macrocode}
            \int_set_eq:NN \datagidx@level \c_one_int
            #1
            \cs_gset_eq:NN \datagidxprevgroup \datagidxcurrentgroup
          }
        }
     }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlgidx@checklocationchange}
%\changes{2.32}{2019-09-27}{new}
%Version 3.0: Removed \cs{dtlgidx@checklocationchange}
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\datagidx@doifdisplayed}
%\begin{definition}
%\cs{datagidx@doifdisplayed}\marg{body}
%\end{definition}
% Do \meta{body} if entry should appear in the glossary/index.
% \cs{Location}, \cs{See} and \cs{SeeAlso} must be set before use.
%    \begin{macrocode}
\newcommand{\datagidx@doifdisplayed}[1]{
  \__datagidx_unwrap_location:N \Location
  \DTLifnull \Location
  {
     \datatool_if_null_or_empty:NTF \See
     {
       \datatool_if_null_or_empty:NF \SeeAlso { #1 }
     }
     {
%    \end{macrocode}
% "See" is not null, but have any of the cross-referenced items been
% used?
%    \begin{macrocode}
       \clist_map_inline:Nn \See
       {
%    \end{macrocode}
% Does the cross-referenced term exist?
%    \begin{macrocode}
           \iftermexists { ##1 }
           {
%    \end{macrocode}
% Has it been used?
%    \begin{macrocode}
             \ifentryused { ##1 }
             {
               #1
%    \end{macrocode}
% Break out of loop.
%    \begin{macrocode}
               \clist_map_break:
             }
             { }
           }
           { }
       }
     }
  }
  { #1 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\datagidx@level}
% Keep track of current level
%    \begin{macrocode}
\newcount\datagidx@level
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\iffalse
%    \begin{macrocode}
%</datagidx.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*databib.sty>
%    \end{macrocode}
%\fi
%\section{databib.sty}
%\label{sec:src:databib}
%\subsection{Package Declaration}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
%    \end{macrocode}
% Rollback releases:
%    \begin{macrocode}
\DeclareRelease{v2.32}{2019-09-27}{databib-2019-09-27.sty}
\DeclareCurrentRelease{v3.4.1}{2025-04-25}
%    \end{macrocode}
% Declare package:
%    \begin{macrocode}
\ProvidesPackage{databib}[2025/04/25 v3.4.1 (NLCT)]
%    \end{macrocode}
%\changes{3.0}{2025-03-03}{dropped \sty{xkeyval}}
% Version 3.0: no longer using \sty{xkeyval}.
%
%\begin{macro}{\andname}
%Version 3.0: removed definition of \cs{andname} (\sty{datatool-base}
%now provides \cs{DTLandname})
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\subsection{Package Options}
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%\begin{macro}{\dtlbib@style}
%The default bib style. Version 3.0 replaced \cs{dtlbib@style} with
%    \begin{macrocode}
\tl_new:N \l__databib_style_tl
\tl_set:Nn \l__databib_style_tl { plain }
%    \end{macrocode}
%\end{macro}
%Provide option to run \app{bibtex} from the shell escape. Off
%by default.
%    \begin{macrocode}
\bool_new:N \l_databib_auto_build_bool
\bool_set_false:N \l_databib_auto_build_bool
\cs_new:Nn \__databib_auto_build:n
{
 \bool_if:NT \l_databib_auto_build_bool
  {
    \sys_if_shell:TF
     {
       \IfFileExists { #1.aux }
        {
          \sys_shell_now:e { bibtex ~ #1 }
        }
        {
          \PackageWarning { databib }
           {
             File ~ `#1' ~ does ~ not ~ exist. ~
             Rerun ~ may ~ be ~ required
           }
        }
     }
     {
       \PackageWarning { databib }
        {
          Can't ~ run ~ `bibtex #1`: shell ~ not ~ enabled
        }
     }
  }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\keys_define:nn { datatool }
{
   style .choices:nn = { plain, abbrv, alpha }
     { \tl_set_eq:NN \l__databib_style_tl \l_keys_choice_tl } ,
   auto .bool_set:N = \l_databib_auto_build_bool ,
}
%    \end{macrocode}
%Switch off \LaTeX3 syntax to prevent interference with package
%loading.
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
% Process options:
%    \begin{macrocode}
\IfPackageLoadedTF{datatool}
{
  \ProcessKeyOptions[datatool]
}
{
  \DeclareOption*{\PassOptionsToPackage{\CurrentOption}{datatool}}
  \ProcessOptions
}
\RequirePackage{datatool}
%    \end{macrocode}
%Switch \LaTeX3 syntax back on.
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
% Remove the package option keys so they can't be used with
% \cs{DTLsetup} (otherwise they may conflict with \sty{databar} etc).
%    \begin{macrocode}
\keys_define:nn { datatool }
{
  style .undefine: ,
  auto .undefine: ,
}
%    \end{macrocode}
%
%\subsection{Loading BBL file}
%\begin{macro}{\DTLloadbbl}
%\begin{definition}
%\cs{DTLloadbib}\oarg{bbl file}\marg{db name}\marg{bib list}
%\end{definition}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLloadbbl { O{\jobname.bbl} m m }
{
  \bibliographystyle {databib}
%    \end{macrocode}
%The code below that writes to the aux file is adapted from the definition
%of \cs{bibliographystyle}.
%\changes{3.0}{2025-03-03}{add check for begin document hook}
%    \begin{macrocode}
  \ifx \@begindocumenthook \@undefined
    \bool_if:NT \l_databib_auto_build_bool
    {
      \PackageError { databib }
       {
         \token_to_str:N \DTLloadbbl \c_space_tl 
         may ~ only ~ be ~ used ~ in ~ the ~ preamble ~
         with `auto=true'
       }
       {
         Move ~ \token_to_str:N \DTLloadbbl \c_space_tl
         to ~ the ~ preamble ~ or ~ switch ~ off ~ the ~
         `auto' ~ option
       }
    }
  \else
    \__databib_auto_build:n { \c_sys_jobname_str }
    \expandafter \AtBeginDocument \fi
   {
    \if@filesw
      \immediate \write \@auxout { \string \bibdata { #3 } }
    \fi
   }
%    \end{macrocode}
%Version 3.0: allow empty name to indicate the default database.
%\changes{3.0}{2025-03-03}{allow empty name to indicate default}
%    \begin{macrocode}
  \tl_if_blank:nTF { #2 }
   {
     \tl_set_eq:NN \DTLBIBdbname \l__datatool_default_dbname_tl
   }
   {
     \tl_set:Nx \DTLBIBdbname { \tl_trim_spaces:n { #2 } }
   }
  \DTLnewdb { \DTLBIBdbname }
  \@input@ { #1 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLnewbibrow}
% \cs{DTLnewbibrow} adds a new row to the bibliography database. 
% (\cs{DTLBIBdbname} must be set prior to use to the name
% of the \sty{datatool} database which must exist. Any check
% to determine its existence should be performed when 
% \cs{DTLBIBdbname} is set.)
%\changes{1.03}{2009 January 27}{removed check if database exists}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLnewbibrow { }
 {
   \@sDTLnewrow { \DTLBIBdbname }
 }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLnewbibitem}
%\begin{definition}
%\cs{DTLnewbibitem}\marg{key}\marg{value}
%\end{definition}
% Adds a new database entry with the given key and value.
%\changes{1.03}{2009 January 27}{removed check if database exists}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLnewbibitem { m m }
{
  \@sDTLnewdbentry { \DTLBIBdbname } { #1 } { #2 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLnewbibliteralitem}
%\begin{definition}
%\cs{DTLnewliteralbibitem}\marg{key}\marg{value}
%\end{definition}
% For use with fields where the value needs detokenizing.
% The percent is the most awkward. The rest can be detokenize
% with \cs{detokenize} but it will need balanced braces if any
% occur.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLnewbibliteralitem { m }
{
  \group_begin:
   \char_set_catcode_other:N \%
   \__databib_literal_item:nn { #1 }
}
\cs_new:Nn \__databib_literal_item:nn
{
  \exp_args:NNne
   \group_end:
   \DTLnewbibitem { #1 } { \detokenize { #2 } }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbibsetlongestlabel}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLbibsetlongestlabel { O { \DTLBIBdbname } m }
{
  \tl_set:cn { __databib_longest_label_ #1 _ tl } { #2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbibgetlongestlabel}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLbibgetlongestlabel}[1]{
  \tl_if_exist:cT {  __databib_longest_label_ #1 _ tl }
   { \tl_use:c { __databib_longest_label_ #1 _ tl } } 
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\tl_new:N \__databib_longest_label_tl
%    \end{macrocode}
%
%\subsection{Predefined text}
%\label{sec:src:bibnames}
%\begin{macro}{\ofname}
%    \begin{macrocode}
\providecommand*{\ofname}{of}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\inname}
%    \begin{macrocode}
\providecommand*{\inname}{in}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\etalname}
%    \begin{macrocode}
\providecommand*{\etalname}{et ~ al.}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\editorname}
%    \begin{macrocode}
\providecommand*{\editorname}{editor}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\editorsname}
%    \begin{macrocode}
\providecommand*{\editorsname}{editors}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\volumename}
%    \begin{macrocode}
\providecommand*{\volumename}{volume}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\numbername}
%    \begin{macrocode}
\providecommand*{\numbername}{number}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\pagesname}
%    \begin{macrocode}
\providecommand*{\pagesname}{pages}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\pagename}
%    \begin{macrocode}
\providecommand*{\pagename}{page}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\editionname}
%    \begin{macrocode}
\providecommand*{\editionname}{edition}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\techreportname}
%    \begin{macrocode}
\providecommand*{\techreportname}{Technical ~ report}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\mscthesisname}
%    \begin{macrocode}
\providecommand*{\mscthesisname}{Master's ~ thesis}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\phdthesisname}
%    \begin{macrocode}
\providecommand*{\phdthesisname}{PhD ~ thesis}
%    \end{macrocode}
%\end{macro}
%
%
%\subsection{Displaying the bibliography}
%\label{sec:src:displaybib}
%\begin{definition}
%\cs{DTLbibliography}\marg{bib dbname}
%\end{definition}
% Displays the bibliography for the database \meta{bib dbname}
% which must have previously been loaded using
% \cs{DTLloadbbl}.
%\begin{macro}{\DTLbibliography}
%\changes{3.0}{2025-03-03}{made robust}
%Version 3.0: switched to read-only loop.
%\changes{3.0}{2025-03-03}{switched to read-only loop}
%    \begin{macrocode}
\NewDocumentCommand \DTLbibliography { O{\boolean{true}} m }
{
  \begin{DTLthebibliography} [ #1 ] { #2 }
  \DTLforeachbibentry * [ #1 ] { #2 }
   {
     \DTLbibitem \DTLformatbibentry \DTLendbibitem
   }
  \end{DTLthebibliography}
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new:Nn \databib_check_fmt:nT 
{
  \tl_if_exist:cTF { DTLformat #1 }
   { #2 }
   {
     \PackageError { databib }
     {
       Don't ~ know ~ how ~ to ~ format ~ bibliography ~ 
       entries ~ of ~ type ~ ` #1 '
     }
     {
       The ~ current ~ bibliography ~ format ~ doesn't ~ seem ~ to ~
       support ~ the ~ given ~ entry ~ type
     }
   }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLformatbibentry}
%\begin{definition}
%\cs{DTLformatbibentry}
%\end{definition}
% Formats the current bib entry.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatbibentry { }
{
%    \end{macrocode}
% Check format for this type is defined.
%    \begin{macrocode}
  \databib_check_fmt:nT { \DBIBentrytype }
  {
%    \end{macrocode}
% Print information to terminal and log file if in verbose mode.
%    \begin{macrocode}
    \dtl@message { [ \DBIBcitekey ] }
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
    \legacy_if_set_false:n { DTLstartsentence }
    \legacy_if_set_false:n { DTLmidsentence }
    \legacy_if_set_false:n { DTLperiod }
%    \end{macrocode}
% Format this entry
%    \begin{macrocode}
    \tl_use:c { DTLformat \DBIBentrytype }
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\gDTLformatbibentry}
%\begin{definition}
%\cs{gDTLformatbibentry}
%\end{definition}
%\changes{2.21}{2014-03-08}{new}
% Global version.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \gDTLformatbibentry { }
{
%    \end{macrocode}
% Check format for this type is defined.
%    \begin{macrocode}
  \databib_check_fmt:nT { \DBIBentrytype }
  {
%    \end{macrocode}
% Print information to terminal and log file if in verbose mode.
%    \begin{macrocode}
    \dtl@message { [ \DBIBcitekey ] }
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
    \legacy_if_gset_false:n { DTLstartsentence }
    \legacy_if_gset_false:n { DTLmidsentence }
    \legacy_if_gset_false:n { DTLperiod }
%    \end{macrocode}
% Format this entry
%    \begin{macrocode}
    \tl_use:c { DTLformat \DBIBentrytype }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatthisbibentry}
%\begin{definition}
%\cs{DTLformatthisbibentry}\marg{db}\marg{cite key}
%\end{definition}
%\changes{2.22}{2014-06-10}{new}
% Just does \cs{DTLformatbibentry} for a given entry.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatthisbibentry { m m }
{
  \tl_set:Ne \DBIBname { \exp_args:Ne \tl_to_str:n { #1 } }
  \tl_set:Ne \DBIBcitekey { \exp_args:Ne \tl_to_str:n { #2 } }
  \edtlgetrowforvalue { \DBIBname }
    { \dtlcolumnindex { \DBIBname } { CiteKey } }
    { \DBIBcitekey }
  \dtl@gathervalues { \DBIBname } { \dtlcurrentrow }
  \tl_set_eq:Nc \DBIBentrytype { @dtl@key@EntryType }
  \DTLformatbibentry
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLendbibitem}
% Hook to add extra information at the end of a bibliography item.
% This does nothing by default.
%    \begin{macrocode}
\newcommand*{\DTLendbibitem}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@widest}
%Define a length to store the widest bib entry label.
%Version 3.0 replaced \cs{dtl@widest} with:
%    \begin{macrocode}
\dim_new:N \l__databib_widest_dim
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLcomputewidestbibentry}
%\begin{definition}
%\cs{DTLcomputewidestbibentry}\marg{conditions}\marg{db 
%name}\marg{bib label}\marg{tl var}
%\end{definition}
%Computes the widest bibliography entry over all entries satisfying
%\meta{condition} for the database called \meta{db name}, where
% the bibliography label is formatted according to \meta{bib label} and
% stores the result in the token list
% variable \meta{tl var}.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{switched to read-only loop}
%Version 3.0: switched to read-only loop.
%    \begin{macrocode}
\NewDocumentCommand \DTLcomputewidestbibentry { m m m m }
{
 \dim_zero:N \l__databib_widest_dim
 \tl_clear:N #4
 \DTLforeachbibentry * [ #1 ] { #2 }
 {
   \datatool_measure_width:Nn \dtl@tmplength { #3 }
   \dim_compare:nNnT
     { \dtl@tmplength } > { \l__databib_widest_dim }
    {
      \dim_set_eq:NN \l__databib_widest_dim \dtl@tmplength
      \tl_set:Ne #4 { #3 }
    }
 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLforeachbibentry}
%\begin{definition}
%\cs{DTLforeachbibentry}\oarg{condition}\marg{db name}\marg{body}
%\end{definition}
%\begin{definition}
%\cs{DTLforeachbibentry*}\oarg{condition}\marg{db name}\marg{body}
%\end{definition}
% Iterates through the database called \meta{db name} and does
% \meta{text} if \meta{condition} is met. As with 
% \cs{DTLforeach}, the starred version is read only.
%\changes{2.21}{2014-03-08}{fixed bug in starred version}
%\changes{3.0}{2025-03-03}{made robust}
%Version 3.0 bug fix: only locally assign \cs{DBIBcitekey} and
%\cs{DBIBentrytype}.
%\changes{3.0}{2025-03-03}{only assign \cs{DBIBcitekey} and \cs{DBIBentrytype} locally}
%    \begin{macrocode}
\NewDocumentCommand \DTLforeachbibentry { s o m m }
{
  \IfBooleanTF { #1 }
  {
%    \end{macrocode}
% Iterate through the database (read only).
%    \begin{macrocode}
    \__databib_foreach_entry:Nnnn
     \@sDTLforeach { #2 } { #3 } { #4 } 
  }
  {
%    \end{macrocode}
% Unstarred version (edits allowed).
%    \begin{macrocode}
    \__databib_foreach_entry:Nnnn
      \@DTLforeach { #2 } { #3 } { #4 } 
  }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \__databib_foreach_entry:Nnnn
{
%    \end{macrocode}
% Store database name.
%    \begin{macrocode}
  \tl_set:Ne \DBIBname { \exp_args:Ne \tl_to_str:n { #3 } }
%    \end{macrocode}
% Reset row counter.
%    \begin{macrocode}
  \int_zero:c { c@DTLbibrow }
%    \end{macrocode}
% Condition.
%    \begin{macrocode}
  \IfValueTF { #2 }
   {
     \tl_if_empty:nTF { #2 }
      {
        \cs_set_eq:NN \__databib_do_foreach:n \use:n 
      }
      {
        \cs_set:Nn \__databib_do_foreach:n 
        { 
          \ifthenelse { #2 } { ##1 } { }
        }
      }
   }
   {
     \cs_set_eq:NN \__databib_do_foreach:n \use:n 
   }
  #1 { #3 } {  }
   {
      \dtl@gathervalues { #3 } { \dtlcurrentrow }
      \tl_set_eq:Nc \DBIBcitekey { @dtl@key@CiteKey }
      \tl_set_eq:Nc \DBIBentrytype { @dtl@key@EntryType }
      \__databib_do_foreach:n 
       {
         \refstepcounter{DTLbibrow}
         #4
       }
   }
}
%    \end{macrocode}
%\begin{macro}{\@DTLforeachbibentry}
%\changes{3.0}{2025-03-03}{removed}
%Version 3.0: removed \cs{@DTLforeachbibentry}
%\end{macro}
%\begin{macro}{\@sDTLforeachbibentry}
%\changes{3.0}{2025-03-03}{removed}
%Version 3.0: removed \cs{@sDTLforeachbibentry}
%\end{macro}
%
%\begin{macro}{\gDTLforeachbibentry}
%\begin{definition}
%\cs{gDTLforeachbibentry}\oarg{condition}\marg{db name}\marg{body}
%\end{definition}
%\begin{definition}
%\cs{gDTLforeachbibentry*}\oarg{condition}\marg{db name}\marg{body}
%\end{definition}
% Global version.
%\changes{2.21}{2014-03-08}{new}
%    \begin{macrocode}
\NewDocumentCommand \gDTLforeachbibentry { s O{\boolean{true}} m m }
{
  \IfBooleanTF { #1 }
  {
    \__databib_gforeach_entry:Nnnn
     \@sDTLforeach { #2 } { #3 } { #4 } 
  }
  {
    \__databib_gforeach_entry:Nnnn
     \@DTLforeach { #2 } { #3 } { #4 } 
  }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \__databib_gforeach_entry:Nnnn
{
%    \end{macrocode}
% Store database name.
%    \begin{macrocode}
  \tl_gset:Ne \DBIBname { \exp_args:Ne \tl_to_str:n { #3 } }
%    \end{macrocode}
% Reset row counter.
%    \begin{macrocode}
  \int_gzero:c { c@DTLbibrow }
%    \end{macrocode}
% Iterate through the database.
%    \begin{macrocode}
  #1 { #3 }
      { \DBIBcitekey = CiteKey , \DBIBentrytype = EntryType }
   {
     \dtl@g@gathervalues { #3 } { \dtlcurrentrow }
     \ifthenelse { #2 }
      {
        \refstepcounter{DTLbibrow}
        #4
      }
      { }
   }
}
%    \end{macrocode}
%\begin{macro}{\@gDTLforeachbibentry}
%\changes{2.21}{2014-03-08}{new}
%\changes{3.0}{2025-03-03}{removed}
%Version 3.0: removed \cs{@gDTLforeachbibentry}
%\end{macro}
%\begin{macro}{\@sgDTLforeachbibentry}
%\changes{2.21}{2014-03-08}{new}
%\changes{3.0}{2025-03-03}{removed}
%Version 3.0: removed \cs{@sgDTLforeachbibentry}
%\end{macro}
%
%\begin{counter}{DTLbibrow}
%The counter \ctrfmt{DTLbibrow} keeps track of the current
%row in the body of \cs{DTLforeachbibentry}. (You can't rely on
%\ctr{DTLrowi}, \ctr{DTLrowii} and 
% \ctr{DTLrowiii}, as \cs{DTLforeachbibentry} pass the
% conditions to the optional argument of \cs{DTLforeach}, but
% instead uses \cs{ifthenelse}, which means that \ctr{DTLrowi}
% etc will be incremented, even when the given condition is not met.)
%    \begin{macrocode}
\newcounter{DTLbibrow}
%    \end{macrocode}
%\end{counter}
%\begin{macro}{\theHDTLbibrow}
% Keep \sty{hyperref} happy:
%    \begin{macrocode}
\def\theHDTLbibrow{\theHDTLrow.bib.\arabic{DTLbibrow}}%
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbibfield}
%\begin{definition}
%\cs{DTLbibfield}\marg{field name}
%\end{definition}
% Gets the value assigned to the field \meta{field name} for
% the current row of \cs{DTLforeachbibentry}. This should be
% expandable but not fully in case the field contains fragile
% commands.
%    \begin{macrocode}
\newcommand*{\DTLbibfield}[1]{
 \tl_if_exist:cT { @dtl@key@#1 }
  {
    \exp_not:v { @dtl@key@#1 }
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLbibdatefield}
%\begin{definition}
%\cs{DTLbibdatefield}\marg{field name}
%\end{definition}
% Used for fields that have dates (Date and UrlDate).
% This may be redefined to format the date.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLbibdatefield}[1]{
  \DTLbibfield { #1 }
}
%    \end{macrocode}
%\end{macro}
%Provide a means of passing the actual field value to another
%command. This may be needed for commands that require their
%argument in a specific format.
%\begin{macro}{\DTLencapbibfield}
%\begin{definition}
%\cs{DTLencapbibfield}\marg{cs}\marg{field}
%\end{definition}
%    \begin{macrocode}
\newcommand*{\DTLencapbibfield}[2]{
 \tl_if_exist:cT { @dtl@key@#2 }
  {
    \exp_args:Nv #1 { @dtl@key@#2 }
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLbibfieldlet}
%\begin{definition}
%\cs{DTLbibfield}\marg{cs}\marg{field name}
%\end{definition}
% Gets the value assigned to the field \meta{field name} for
% the current row of \cs{DTLforeachbibentry} and assigns it to the
% control sequence \meta{cs}. (Doesn't check if
% the field exists, or if it is being used within 
% \cs{DTLforeachbibentry}.)
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLbibfieldlet { m m }
{
  \tl_set_eq:Nc #1 { @dtl@key@#2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifbibfieldexists}
%\begin{definition}
%\cs{DTLifbibfieldexists}\marg{field name}\marg{true part}\marg{false
%part}
%\end{definition}
%Determines whether the given field name exists for the current
% row of \cs{DTLforeachbibentry}.
%    \begin{macrocode}
\newcommand*{\DTLifbibfieldexists}[3]{%
 \tl_if_exist:cTF { @dtl@key@#1 }
  { 
    \datatool_if_null:cTF { @dtl@key@ #1 }
     { #3 } { #2 }
  }
  { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLifanybibfieldexists}
%\begin{definition}
%\cs{DTLifanybibfieldexists}\marg{list of field names}\marg{true part}\marg{false
%part}
%\end{definition}
% Determines whether any of the listed fields exist for the current
% row of \cs{DTLforeachbibentry}.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLifanybibfieldexists { m m m }
{
  \cs_set_eq:NN \__databib_do:nn \use_ii:nn
  \clist_map_inline:nn { #1 } 
   {
     \DTLifbibfieldexists { ##1 }
     {
       \cs_set_eq:NN \__databib_do:nn \use_i:nn
       \clist_map_break:
     }
     { }
   }
  \__databib_do:nn { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\ifDTLperiod}
% The conditional \cs{ifDTLperiod} is used to keep track of any
% abbreviations ending with a period, this is to ensure that 
% abbreviations aren't followed by a full stop if they already
% have a full stop terminating the abbreviation.
%    \begin{macrocode}
\newif\ifDTLperiod
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\regex_new:N \l_databib_end_sentence_regex
\regex_set:Nn \l_databib_end_sentence_regex
  { [\.?!] \Z }
%    \end{macrocode}
%\begin{macro}{\DTLcheckendsperiod}
%\begin{definition}
%\cs{DTLcheckendperiod}\marg{string}
%\end{definition}
% Checks if \meta{string} ends with an end of sentence punctuation
% mark. This sets \cs{ifDTLperiod}.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLcheckendsperiod { m }
{
 \regex_match:NnTF \l_databib_end_sentence_regex { #1 }
  { \legacy_if_gset_true:n { DTLperiod } }
  { \legacy_if_gset_false:n { DTLperiod } }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLcheckbibfieldendsperiod}
%\begin{definition}
%\cs{DTLcheckbibfieldendperiod}\marg{label}
%\end{definition}
% Checks if the bib field \meta{label} ends with a full stop. 
% This sets \cs{ifDTLperiod}.
%    \begin{macrocode}
\NewDocumentCommand \DTLcheckbibfieldendsperiod { m }
{
  \exp_args:Ne \DTLcheckendsperiod { \DTLbibfield { #1 } }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new:Nn \databib_do_and_check:n
 {
   \legacy_if:nTF { DTLmidsentence }
    { #1 }
    {
      \DTLstartsentencespace
      \text_titlecase:n { #1 }
    }
   \DTLcheckendsperiod { #1 }
 }
\cs_generate_variant:Nn \databib_do_and_check:n { e }
%    \end{macrocode}
%
%\begin{macro}{\ifDTLmidsentence}
%\begin{definition}
%\cs{ifDTLmidsentence}
%\end{definition}
% Determine whether we are in the middle
% of a sentence.
%    \begin{macrocode}
\newif\ifDTLmidsentence
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\ifDTLstartsentence}
%\begin{definition}
%\cs{ifDTLstartsentence}
%\end{definition}
% Determine whether we are at the start
% of a sentence.
%    \begin{macrocode}
\newif\ifDTLstartsentence
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLaddperiod}
%\begin{definition}
%\cs{DTLaddperiod}
%\end{definition}
%Adds a full stop and sets \cs{DTLmidsentencefalse},
%\cs{DTLstartsentencetrue} and
%\cs{DTLperiodfalse}.
%\changes{3.0}{2025-03-03}{bug fix: insert period before resetting conditional}
%    \begin{macrocode}
\newcommand*{\DTLaddperiod}{
 \ifDTLperiod\else.\fi
 \DTLmidsentencefalse
 \DTLperiodfalse
 \DTLstartsentencetrue
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLaddcomma}
%\begin{definition}
%\cs{DTLaddcomma}
%\end{definition}
%Adds a comma and sets \cs{DTLmidsentencetrue}, \cs{DTLperiodfalse}
%and \cs{DTLstartsentencefalse}
%    \begin{macrocode}
\newcommand*{\DTLaddcomma}{
 , ~ 
 \DTLmidsentencetrue
 \DTLperiodfalse
 \DTLstartsentencefalse
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\begin{macro}{\DTLstartsentencespace}
%Adds a space if at the start of the sentence, otherwise does
% nothing. (The space between sentences is added this way, rather
% than in \cs{DTLaddperiod} otherwise spurious extra space can
% occur at the end of the bib item. The spacefactor needs to be
% set manually, because there's stuff in the way of the previous
% sentence's full stop and this space which confuses the inter
% sentence spacing (and, of course, the previous sentence could
% have ended with a capital letter.)
%    \begin{macrocode}
\newcommand*{\DTLstartsentencespace}{%
\ifDTLstartsentence\spacefactor=\sfcode`\.\relax\space
\fi\DTLstartsentencefalse}
%    \end{macrocode} 
%\end{macro}
%
%\begin{macro}{\DTLtwoand}
% In a list of only two author (or editor) names, the text between the
% two names is given by \cs{DTLtwoand}:
%    \begin{macrocode}
\newcommand*{\DTLtwoand}{\ \DTLlistand \ }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLandlast}
% In a list of author (or editor) names, the text between the
% penultimate and last name is given by \cs{DTLandlast}:
%    \begin{macrocode}
\newcommand*{\DTLandlast}{, \DTLlistand \ }
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\begin{macro}{\DTLandnotlast}
% In a list of author (or editor) names, the text between the
% names (except the penultimate and last name) is given by 
% \cs{DTLandnotlast}:
%    \begin{macrocode}
\newcommand*{\DTLandnotlast}{, ~ }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@authorcount}
% Define a integer variable to keep track of the number of authors.
%Version 3.0: replaced \cs{dtl@authorcount} with:
%    \begin{macrocode}
\int_new:N \l__databib_author_count_int
%    \end{macrocode}
%\end{macro}
%\begin{counter}{DTLmaxauthors}
% The counter \ctrfmt{DTLmaxauthors} indicates the maximum number of
% author names to display, if there are more than that number,
% \cs{etalname} is used.
%    \begin{macrocode}
\newcounter{DTLmaxauthors}
\setcounter{DTLmaxauthors}{10}
%    \end{macrocode}
%\end{counter}
%\begin{macro}{\DTLformatbibnamelist}
%\begin{description}
%\cs{DTLformatbibnamelist}\marg{clist}\marg{max}\marg{formatting cmd}
%\end{description}
%Generic formatting command for list of names.
%    \begin{macrocode}
\NewDocumentCommand \DTLformatbibnamelist { m m m }
{
 \int_set:Nn \l__databib_author_count_int
   { \clist_count:n { #1 } }
 \int_zero:N \l__datatool_count_int
 \int_compare:nNnTF
    { \l__databib_author_count_int } > { #2 }
  {
    \clist_map_inline:nn { #1 }
    {
      \int_incr:N \l__datatool_count_int
      \int_compare:nNnTF
         { \l__datatool_count_int } = { \c_one_int } 
       {
         \__databib_format_name:Nn #3 { ##1 }
       }
       {
         \int_compare:nNnTF
           { \l__datatool_count_int } > { #2 }
          {
            \DTLandnotlast
            \etalname
            \exp_args:NV \DTLcheckendsperiod \etalname
            \clist_map_break:
          }
          {
            \DTLandnotlast
            \__databib_format_name:Nn #3 { ##1 }
          }
       }
    }
  }
  {
    \clist_map_inline:nn { #1 }
     {
       \int_incr:N \l__datatool_count_int
       \int_compare:nNnTF { \l__datatool_count_int } = { \c_one_int }
        {
          \__databib_format_name:Nn #3 { ##1 }
        }
        {
          \int_compare:nNnTF
             { \l__datatool_count_int } = { \l__databib_author_count_int }
           {
             \int_compare:nNnTF
               { \l__databib_author_count_int } = { 2 }
              {
                \DTLtwoand 
              }
              {
                \DTLandlast 
              }
             \__databib_format_name:Nn #3 { ##1 }
           }
           {
             \DTLandnotlast
             \__databib_format_name:Nn #3 { ##1 }
           }
        }
     }
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLformatauthorlist}
% Format a list of author names (the list is stored in
% \cs{@dtl@key@Author}):
%    \begin{macrocode}
\newcommand*{\DTLformatauthorlist}{
 \DTLifbibfieldexists{Author}
 {
   \DTLstartsentencespace
   \exp_args:NV \DTLformatbibnamelist \@dtl@key@Author
    { \c@DTLmaxauthors } { \DTLformatauthor }
 }
 { }
}
%    \end{macrocode}
%\end{macro}
%\begin{counter}{DTLmaxeditors}
% The counter \ctrfmt{DTLmaxeditors} indicates the maximum number of
% editor names to display, if there are more than that number,
% \cs{etalname} is used.
%    \begin{macrocode}
\newcounter{DTLmaxeditors}
\setcounter{DTLmaxeditors}{10}
%    \end{macrocode}
%\end{counter}
%
%\begin{macro}{\DTLformateditorlist}
% Format a list of editor names (the list is stored in
% \cs{@dtl@key@Editor}):
%    \begin{macrocode}
\newcommand*{\DTLformateditorlist}{%
 \DTLifbibfieldexists {Editor}
  {
   \DTLstartsentencespace
   \exp_args:NV \DTLformatbibnamelist \@dtl@key@Editor
    { \c@DTLmaxeditors } { \DTLformateditor }
    , ~
   \int_compare:nNnTF
      { \l__databib_author_count_int } = { \c_one_int }
    {
      \editorname
      \exp_args:NV \DTLcheckendsperiod \editorname
    }
    {
      \editorsname 
      \exp_args:NV \DTLcheckendsperiod \editorsname
    }
  }
  { }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\begin{macro}{\DTLpostvon}
%Space to insert after \meta{von part}.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLpostvon}{~}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLpostvolnum}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLpostvolnum}{:}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLpostpagename}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLpostpagename}{~}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLpostvolumename}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLpostvolumename}{~}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLpostchaptername}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLpostchaptername}{~}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLpostnumbername}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLpostnumbername}{~}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLprecite}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLprecite}{~}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLofseries}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLofseries}[1]{%
  \ \ofname\ \DTLofseriesfmt{#1}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLofseriesfmt}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLofseriesfmt}[1]{\emph{#1}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLinseries}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLinseries}[1]{%
  \ \inname\ #1%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLjournalfmt}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLjournalfmt}[1]{\emph{#1}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLinbooktitlefmt}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLinbooktitlefmt}[1]{\emph{#1}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLmanualtitlefmt}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLmanualtitlefmt}[1]{\emph{#1}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLthesistitlefmt}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLthesistitlefmt}[1]{\emph{#1}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLproceedingstitlefmt}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLproceedingstitlefmt}[1]{\emph{#1}}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\begin{macro}{\DTLformatsurnameonly}
%\begin{definition}
%\cs{DTLformatsurnameonly}\marg{von part}\marg{surname}\marg{jr
%part}\marg{forenames}
%\end{definition}
% This is used when only the surname should be displayed. (The
% final argument, \meta{forenames}, is ignored.)
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatsurnameonly { m m m m }
{
  \tl_if_empty:nF { #1 }
   { #1 \DTLpostvon }
  #2
  \tl_if_empty:nTF { #3 }
   {
     \DTLcheckendsperiod { #2 }
   }
   {
     , ~ #3
     \DTLcheckendsperiod { #3 }
   }
}
%    \end{macrocode}
%\end{macro}
%
%
%\begin{macro}{\DTLformatforenames}
%\begin{definition}
%\cs{DTLformatforenames}\marg{forenames}
%\end{definition}
% The format of an author/editor's forenames. If the forenames
% occur at the start of sentence, a new sentence space is added.
% The argument is checked to determine whether it ends with a 
% full stop (sometimes the forenames may include initials.)
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatforenames { m }
{
  #1
  \DTLcheckendsperiod { #1 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatabbrvforenames}
%\begin{definition}
%\cs{DTLformatabbrvforenames}\marg{forenames}
%\end{definition}
% The format of an author/editor's abbreviated forenames. The initials
% may or may not end in a full stop depending on the commands 
% governing the format of \cs{DTLstoreinitials}, so the initials
% need to be check using \cs{DTLcheckendsperiod}.
%    \begin{macrocode}
\NewDocumentCommand \DTLformatabbrvforenames { m }
{
  \DTLstoreinitials { #1 } \@dtl@tmp
  \@dtl@tmp
  \exp_args:NV \DTLcheckendsperiod \@dtl@tmp
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatvon}
%\begin{definition}
%\cs{DTLformatvon}\marg{von part}
%\end{definition}
% The format of the ``von'' part. This does nothing if the argument
% is empty, otherwise it does the argument followed by a 
% non-breakable space.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatvon { m }
{
  \tl_if_empty:nF { #1 }
   {
     #1 \DTLpostvon
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatsurname}
%\begin{definition}
%\cs{DTLformatsurname}\marg{surname}
%\end{definition}
% The format of an author/editor's surname.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatsurname { m }
{
  #1
  \DTLcheckendsperiod { #1 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatjr}
%\begin{definition}
%\cs{DTLformatjr}\marg{jr part}
%\end{definition}
% The format of the ``jr'' part. This does nothing if the
% argument is empty.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatjr { m }
{
  \tl_if_empty:nF { #1 }
   {
     , ~ #1
     \DTLcheckendsperiod { #1 }
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatcrossrefeditor}
% Format cross reference editors:
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatcrossrefeditor { }
{
 \DTLifbibfieldexists {Editor}
  {
    \DTLstartsentencespace
    \int_set:Nn \l__databib_author_count_int
     { \clist_count:N \@dtl@key@Editor }
    \int_zero:N \l__datatool_count_int
    \clist_map_inline:Nn \@dtl@key@Editor
     {
       \int_compare:nNnTF 
         { \l__databib_author_count_int } = { \c_one_int }
        {
          \__databib_format_name:Nn
            \DTLformatsurnameonly { ##1 }
        }
        {
          \int_incr:N \l__datatool_count_int
          \int_compare:nNnTF 
            { \l__datatool_count_int } = { \c_one_int }
           {
             \__databib_format_name:Nn
               \DTLformatsurnameonly { ##1 }
           }
           {
             \int_compare:nNnTF 
               { \l__databib_author_count_int } = { 2 }
              {
                \DTLtwoand
                \__databib_format_name:Nn
                  \DTLformatsurnameonly { ##1 }
              }
              {
                \c_space_tl
                \etalname
                \exp_args:NV \DTLcheckendsperiod \etalname
              }
             \clist_map_break:
           }
        }
     }
  }
  { }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatvolnumpages}
% Format volume, number and pages (of an article).
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatvolnumpages { }
{
 \cs_set_eq:NN \__databib_do:nn \use_ii:nn
 \DTLifbibfieldexists {Volume}
  {
    \DTLstartsentencespace
    \DTLbibfield {Volume}
    \DTLperiodfalse
    \cs_set_eq:NN \__databib_do:nn \use_i:nn
  }
  { }
 \DTLifbibfieldexists {Number}
  {
    \DTLstartsentencespace
    ( \DTLbibfield{Number} )
    \DTLperiodfalse
    \cs_set_eq:NN \__databib_do:nn \use_i:nn
  }
  { }
 \DTLifbibfieldexists {Pages}
  {
    \__databib_do:nn
     { \DTLpostvolnum }
     {
       \DTLstartsentencespace
       \exp_args:Ne \DTLifnumerical
         { 0 \DTLbibfield {Pages} }
         { \pagename } { \pagesname }
       \DTLpostpagename
     }
    \DTLbibfield {Pages}
    \DTLperiodfalse
  }
  { }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatbvolume}
% Format book volume.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatbvolume { }
{
 \DTLifbibfieldexists {Volume}
  {
    \legacy_if:nTF { DTLmidsentence }
     {
       \volumename
     }
     {
       \DTLstartsentencespace
       \text_titlecase_first:n { \volumename }
     }
    \DTLpostvolumename
    \DTLbibfield {Volume}
    \DTLifbibfieldexists {Series}
     {
       \DTLofseries { \DTLbibfield{Series} }
       \DTLcheckbibfieldendsperiod {Series}
     }
     {
       \DTLcheckbibfieldendsperiod {Volume}
     }
  }
  { }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatchapterpages}
% Format chapter and pages:
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatchapterpages { }
{
 \DTLifbibfieldexists {Chapter}
  {
    \DTLifbibfieldexists {Type}
     {
       \DTLstartsentencespace
       \DTLbibfield {Type}
     }
     {
       \DTLstartsentencespace
       \chaptername
     }
    \DTLpostchaptername
    \DTLbibfield {Chapter}
    \DTLifbibfieldexists {Pages}
     { \DTLaddcomma }
     {
       \DTLcheckbibfieldendsperiod {Chapter}
     }
  }
  { }
 \DTLstartsentencespace
 \DTLformatpages
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatpages}
% Format pages:
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatpages { }
{
  \DTLifbibfieldexists{Pages}
   {
     \DTLstartsentencespace
     \exp_args:Ne \DTLifnumerical { 0\DTLbibfield{Pages} }
       { \pagename } { \pagesname }
     \DTLpostpagename
     \DTLbibfield {Pages}
     \DTLcheckbibfieldendsperiod {Pages}
   }
   { }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatnumberseries}
% Format number and series (of book)
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatnumberseries { }
{
 \DTLifbibfieldexists {Volume}
  { }
  {
   \DTLifbibfieldexists {Number}
    {
      \legacy_if:nTF { DTLmidsentence }
       {
         \numbername
       }
       {
         \DTLstartsentencespace
         \text_titlecase_first:n { \numbername }
       }
     \DTLpostnumbername
     \DTLbibfield {Number}
     \DTLifbibfieldexists {Series}
       {
         \DTLinseries { \DTLbibfield {Series} }
         \DTLcheckbibfieldendsperiod {Series}
       }
       {
         \DTLcheckbibfieldendsperiod {Number}
       }
    }
    {
      \DTLifbibfieldexists {Series}
       {
         \DTLstartsentencespace
         \DTLbibfield {Series}
         \DTLcheckbibfieldendsperiod {Series}
       }
       { }
    }
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLformatbookcrossref}
% Format a book cross reference.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatbookcrossref { }
{
 \DTLifbibfieldexists {Volume}
  {
    \legacy_if:nTF { DTLmidsentence }
     {
       \volumename
     }
     {
       \DTLstartsentencespace
       \text_titlecase:n { \volumename }
     }
    \DTLpostvolumename
    \DTLbibfield {Volume}
    \c_space_tl \ofname \c_space_tl 
  }
  {
    \legacy_if:nTF { ifDTLmidsentence }
     {
       \inname
     }
     {
       \DTLstartsentencespace
       \text_titlecase:n { \inname }
     }
    \c_space_tl 
  }
 \DTLifbibfieldexists {Editor}
  {
    \DTLformatcrossrefeditor
  }
  {
    \DTLifbibfieldexists {Key}
     {
       \DTLbibfield {Key}
     }
     {
       \DTLifbibfieldexists {Series}
        {
          \DTLofseriesfmt { \DTLbibfield {Series} }
        }
        { }
     }
  }
 \DTLprecite
 \DTLpcite { \DTLbibfield{CrossRef} }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLformatincollproccrossref}
% Format `incollections' cross reference.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatincollproccrossref { }
{
 \DTLifbibfieldexists {Editor}
  {
    \legacy_if:nTF { DTLmidsentence }
     {
       \inname
     }
     {
       \DTLstartsentencespace
       \text_titlecase:n { \inname }
     }
    \c_space_tl 
    \DTLformatcrossrefeditor
  }
  {
    \DTLifbibfieldexists {Key}
     {
       \legacy_if:nTF { DTLmidsentence }
        {
          \inname
        }
        {
          \DTLstartsentencespace
          \text_titlecase_first:n { \inname }
        }
       \c_space_tl
       \DTLbibfield {Key}
     }
     {
       \DTLifbibfieldexists {BookTitle}
        {
          \legacy_if:nTF { DTLmidsentence }
           {
             \inname
           }
           {
             \DTLstartsentencespace
             \text_titlecase_first:n { \inname }
           }
          \c_space_tl
          \DTLformatbooktitle { \DTLbibfield {BookTitle} }
        }
        { }
     }
  }
 \DTLprecite
 \DTLpcite { \DTLbibfield {CrossRef} }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatinedbooktitle}
%Format editor and booktitle:
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatinedbooktitle { }
{
 \DTLifbibfieldexists {BookTitle}
  {
    \legacy_if:nTF { DTLmidsentence }
     {
       \inname
     }
     {
       \DTLstartsentencespace
       \text_titlecase_first:n { \inname }
     }
    \c_space_tl 
    \DTLifbibfieldexists {Editor}
     {
       \DTLformateditorlist
       \DTLaddcomma
       \DTLformatbooktitle { \DTLbibfield {BookTitle} }
       \DTLcheckbibfieldendsperiod {BookTitle}
     }
     {
       \DTLformatbooktitle { \DTLbibfield {BookTitle} }
       \DTLcheckbibfieldendsperiod {BookTitle}
     }
  }
  { }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatdate}
%Format date.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatdate { }
{
 \DTLifbibfieldexists {Date}
  {
    \DTLstartsentencespace
    \DTLbibdatefield {Date}
  }
  {
   \DTLifbibfieldexists {Year}
    {
      \DTLifbibfieldexists {Month}
       {
         \legacy_if:nTF { DTLmidsentence }
          {
            \DTLbibfield {Month}
          }
          {
            \DTLstartsentencespace
            \text_titlecase:n { \DTLbibfield {Month} }
          }
         \c_space_tl 
         \DTLmidsentencefalse
       }
       { }
      \DTLstartsentencespace
      \DTLbibfield {Year}
    }
    {
      \DTLifbibfieldexists {Month}
       {
         \legacy_if:nTF { DTLmidsentence }
          {
            \DTLbibfield {Month}
          }
          {
            \DTLstartsentencespace
            \text_titlecase_first:n { \DTLbibfield {Month} }
          }
         \DTLcheckbibfieldendsperiod {Month}
       }
       { }
    }
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLformatarticlecrossref}
% Format article cross reference.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLformatarticlecrossref { }
{
 \DTLifbibfieldexists {Key}
  {
    \legacy_if:nTF { DTLmidsentence }
     {
       \inname
     }
     {
       \DTLstartsentencespace
       \text_titlecase_first:n { \inname }
     }
    \c_space_tl 
    \DTLjournalfmt { \DTLbibfield {Key} }
  }
  {
    \DTLifbibfieldexists {Journal}
     {
       \legacy_if:nTF { DTLmidsentence }
        {
          \inname
        }
        {
          \DTLstartsentencespace
          \text_titlecase_first:n { \inname }
        }
       \c_space_tl
       \DTLjournalfmt { \DTLbibfield{Journal} }
     }
     { }
  }
 \DTLprecite
 \DTLpcite { \DTLbibfield{CrossRef} }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLpcite}
%\changes{2.22}{2014-06-10}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLpcite { m }
{
  \exp_args:Ne \cite { #1 }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\begin{macro}{\DTLbibdoihome}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLbibdoihome}{https://doi.org/}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbibpubmedhome}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLbibpubmedhome}{https://pubmed.ncbi.nlm.nih.gov/}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbibdoitag}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLbibdoitag}{\textsc{doi}: }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbibpubmedtag}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLbibpubmedtag}{\textsc{pmid}: }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbiburldate}
%\begin{definition}
%\cs{DTLbiburldate}\marg{date}
%\end{definition}
% Used for UrlDate field.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLbiburldate}[1]{%
  \space (\DTLbibaccessedname : #1)%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbibeprints}
%\begin{definition}
%\cs{DTLbibeprints}\marg{eprint}\marg{eprint-type}
%\end{definition}
% Used for Eprints field.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLbibeprints}[2]{%
 \ifdef\href
  {\href{#1}{#2}}%
  {%
    #2: % space intended
    \ifdef\url{\url{#1}}{\texttt{#1}}%
  }%
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\begin{macro}{\DTLbibdoi}
%\begin{definition}
%\cs{DTLbibdoi}\marg{doi}
%\end{definition}
% Used for DOI field, which should now have its content sanitized.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLbibdoi}[1]{
 \DTLbibdoitag
 \cs_if_exist:NTF \href
  {
     \exp_args:Ne \href { \DTLbibdoihome #1 } { \nolinkurl { #1 } }
  }
  {
    \cs_if_exist:NTF \url
     { \url { #1 } } { \texttt { #1 } }
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLbiburl}
%\begin{definition}
%\cs{DTLbiburl}\marg{url}
%\end{definition}
% Used for Url field, which should now have its content sanitized.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLbiburl}[1]{
  \cs_if_exist:NTF \url
   { \url { #1 } }
   { \texttt { #1 } }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLbibaccessedname}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLbibaccessedname}{accessed}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLbibpubmed}
%\begin{definition}
%\cs{DTLbibpubmed}\marg{id}
%\end{definition}
% Used for PubMed field.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLbibpubmed}[1]{
  \DTLbibpubmedtag 
  \cs_if_exist:NTF \href
   { \exp_args:Ne \href { \DTLbibpubmedhome #1 } { #1 } }
   { #1 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLbibformatdigital}
%Format the digital elements.
%\changes{3.0}{2025-03-03}{made new}
%    \begin{macrocode}
\NewDocumentCommand \DTLbibformatdigital { }
{
 \DTLifbibfieldexists {PubMed}
  {
    \exp_args:Ne \DTLbibpubmed { \DTLbibfield { PubMed } }
    \legacy_if_set_true:n { DTLmidsentence }
  }
  { }
 \DTLifbibfieldexists {DOI}
  {
    \legacy_if:nT { DTLmidsentence }
     {
       \DTLaddcomma
     }
    \exp_args:Ne \DTLbibdoi { \DTLbibfield { DOI } }
    \legacy_if_set_true:n { DTLmidsentence }
  }
  { }
 \DTLifbibfieldexists {Url}
  {
    \legacy_if:nT { DTLmidsentence }
     {
       \DTLaddcomma
     }
    \exp_args:Ne \DTLbiburl { \DTLbibfield { Url } }
    \DTLifbibfieldexists {UrlDate}
     {
       \DTLbiburldate { \DTLbibdatefield { UrlDate } }
     }
     { }
    \legacy_if_set_true:n { DTLmidsentence }
  }
  { }
 \DTLifbibfieldexists {Eprints}
  {
    \legacy_if:nT { DTLmidsentence }
     {
       \DTLaddcomma
     }
    \exp_args:Nee \DTLbibeprints
     { \DTLbibfield { Eprints } }
     { 
       \tl_if_exist:NTF \@dtl@key@EprintType 
        { \exp_not:V \@dtl@key@EprintType } { eprint }
     }
    \legacy_if_set_true:n { DTLmidsentence }
  }
  { }
}
%    \end{macrocode}
%\end{macro}
%
%\subsubsection{ifthen conditionals}
% The conditionals defined in this section may be used in the
% optional argument of \cs{DTLforeachbibentry}. They may also be
% used in the first argument of \cs{ifthenelse}, but only if the 
% command occurs within the body of \cs{DTLforeachbibentry}.
% NB these commands need to expand.
%
%\begin{macro}{\DTLbibfieldexists}
%\begin{definition}
%\cs{DTLbibfieldexists}\marg{field label}
%\end{definition}
% Checks if named bib field exists for current entry
%    \begin{macrocode}
\newcommand*{\DTLbibfieldexists}[1]{%
 \TE@throw
 \noexpand \dtl@testbibfieldexists {#1}
 \noexpand \if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@testbibfieldexists}
%    \begin{macrocode}
\newcommand*{\dtl@testbibfieldexists}[1]{%
 \DTLifbibfieldexists {#1}
  { \@dtl@conditiontrue }
  { \@dtl@conditionfalse }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbibfieldiseq}
%\begin{definition}
%\cs{DTLbibfieldiseq}\marg{field label}\marg{value}
%\end{definition}
% Checks if the value of the bib field given by \meta{field label} 
% is equal to \meta{value}. (Uses \cs{dtlcompare} to determine if
% the values are equal. If the bib field doesn't exist, the
% condition is false.)
%    \begin{macrocode}
\newcommand*{\DTLbibfieldiseq}[2]{%
 \TE@throw
 \noexpand
 \dtl@testbibfieldiseq {#1} {#2}
 \noexpand\if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@testbibfieldiseq}
%    \begin{macrocode}
\newcommand*{\dtl@testbibfieldiseq}[2]{%
 \DTLifbibfieldexists {#1}
  {
    \exp_args:NNv \dtlcompare
       \@dtl@tmpcount
       { @dtl@key@ #1 } { #2 }
    \int_if_zero:nTF { \@dtl@tmpcount }
     {
       \@dtl@conditiontrue
     }
     {
       \@dtl@conditionfalse
     }
  }
  {
    \@dtl@conditionfalse
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbibfieldislt}
%\begin{definition}
%\cs{DTLbibfieldislt}\marg{field label}\marg{value}
%\end{definition}
% Checks if the value of the bib field given by \meta{field label} 
% is less than \meta{value}. (If the bib field doesn't exist, the
% condition is false.)
%    \begin{macrocode}
\newcommand*{\DTLbibfieldislt}[2]{%
 \TE@throw
 \noexpand \dtl@testbibfieldislt { #1 } { #2 }
 \noexpand \if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@testbibfieldislt}
%    \begin{macrocode}
\newcommand*{\dtl@testbibfieldislt}[2]{%
 \DTLifbibfieldexists {#1}
  {
    \exp_args:NNv \dtlcompare
       \@dtl@tmpcount
       { @dtl@key@ #1 } { #2 }
    \int_compare:nNnTF { \@dtl@tmpcount } = { -1 }
     {
       \@dtl@conditiontrue
     }
     {
       \@dtl@conditionfalse
     }
  }
  {
    \@dtl@conditionfalse
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbibfieldisle}
%\begin{definition}
%\cs{DTLbibfieldisle}\marg{field label}\marg{value}
%\end{definition}
% Checks if the value of the bib field given by \meta{field label} 
% is less than or equal to \meta{value}. 
% (If the bib field doesn't exist, the
% condition is false.)
%    \begin{macrocode}
\newcommand*{\DTLbibfieldisle}[2]{%
 \TE@throw
 \noexpand \dtl@testbibfieldisle { #1 } { #2 }
 \noexpand \if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@testbibfieldisle}
%    \begin{macrocode}
\newcommand*{\dtl@testbibfieldisle}[2]{%
 \DTLifbibfieldexists {#1}
  {
    \exp_args:NNv \dtlcompare
       \@dtl@tmpcount
       { @dtl@key@ #1 } { #2 }
    \int_compare:nNnTF { \@dtl@tmpcount } < { \c_one_int }
     {
       \@dtl@conditiontrue
     }
     {
       \@dtl@conditionfalse
     }
  }
  {
    \@dtl@conditionfalse
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbibfieldisgt}
%\begin{definition}
%\cs{DTLbibfieldisgt}\marg{field label}\marg{value}
%\end{definition}
% Checks if the value of the bib field given by \meta{field label} 
% is greater than \meta{value}. (If the bib field doesn't exist, the
% condition is false.)
%    \begin{macrocode}
\newcommand*{\DTLbibfieldisgt}[2]{%
 \TE@throw 
 \noexpand \dtl@testbibfieldisgt { #1 } { #2 }
 \noexpand \if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@testbibfieldisgt}
%    \begin{macrocode}
\newcommand*{\dtl@testbibfieldisgt}[2]{%
 \DTLifbibfieldexists {#1}
  {
    \exp_args:NNv \dtlcompare
       \@dtl@tmpcount
       { @dtl@key@ #1 } { #2 }
    \int_compare:nNnTF { \@dtl@tmpcount } = { \c_one_int }
     {
       \@dtl@conditiontrue
     }
     {
       \@dtl@conditionfalse
     }
  }
  {
    \@dtl@conditionfalse
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbibfieldisge}
%\begin{definition}
%\cs{DTLbibfieldisge}\marg{field label}\marg{value}
%\end{definition}
% Checks if the value of the bib field given by \meta{field label} 
% is less than or equal to \meta{value}. 
% (If the bib field doesn't exist, the
% condition is false.)
%    \begin{macrocode}
\newcommand*{\DTLbibfieldisge}[2]{%
 \TE@throw
 \noexpand \dtl@testbibfieldisge { #1 } { #2 }
 \noexpand \if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@testbibfieldisge}
%    \begin{macrocode}
\newcommand*{\dtl@testbibfieldisge}[2]{%
 \DTLifbibfieldexists {#1}
  {
    \exp_args:NNv \dtlcompare
       \@dtl@tmpcount
       { @dtl@key@ #1 } { #2 }
    \int_compare:nNnTF { \@dtl@tmpcount } > { -1 }
     {
       \@dtl@conditiontrue
     }
     {
       \@dtl@conditionfalse
     }
  }
  {
    \@dtl@conditionfalse
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbibfieldcontains}
%\begin{definition}
%\cs{DTLbibfieldcontains}\marg{field label}\marg{sub string}
%\end{definition}
% Checks if the value of the bib field given by \meta{field label} 
% contains \meta{sub string}. 
% (If the bib field doesn't exist, the
% condition is false.)
%    \begin{macrocode}
\newcommand*{\DTLbibfieldcontains}[2]{%
 \TE@throw
 \noexpand \dtl@testbibfieldcontains { #1 } { #2 }
 \noexpand \if@dtl@condition
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@testbibfieldcontains}
%    \begin{macrocode}
\newcommand*{\dtl@testbibfieldcontains}[2]{%
 \DTLifbibfieldexists { #1 }
 {
   \exp_args:Nc \dtl@testifsubstring
     { @dtl@key@ #1 } { #2 }
 }
 { \@dtl@conditionfalse }
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Bibliography Style Macros}
%\label{sec:src:bibstyle}
% The macros defined in this section should be redefined by
% bibliography styles.
%
%\begin{environment}{DTLthebibliography}
% How to format the entire bibliography:
%    \begin{macrocode}
\NewDocumentEnvironment { DTLthebibliography }
 { O{\boolean{true}} m }
{
  \tl_clear:N \__databib_longest_label_tl 
  \tl_if_eq:nnT { #1 } { \boolean{true} }
   {
    \tl_if_exist:cT { __databib_longest_label_ #1 _ tl }
     { 
       \tl_set_eq:Nc
         \__databib_longest_label_tl 
        { __databib_longest_label_ #1 _ tl }
     }
   }
  \tl_if_empty:NT \__databib_longest_label_tl 
   {
     \int_zero:N \l__datatool_count_int
     \@sDTLforeach [ #1 ] { #2 } { }
      { \int_incr:N \l__datatool_count_int }
     \tl_set:NV \__databib_longest_label_tl 
       \l__datatool_count_int
   }
  \exp_args:NnV \begin { thebibliography }
   \__databib_longest_label_tl 
}
{ \end {thebibliography} }
%    \end{macrocode}
%\end{environment}
%\begin{macro}{\DTLmonthname}
% The monthname style. The argument must be a number from~1
% to~12. By default, uses \cs{dtl@monthname}.
%    \begin{macrocode}
\newcommand*{\DTLmonthname}[1]{%
\dtl@monthname{#1}}
%    \end{macrocode}
%\end{macro}
%Ensure the value is the numeric argument if sorting by month.
%    \begin{macrocode}
\dtlSortWordCommands{\let\DTLmonthname\@firstofone}
%    \end{macrocode}
%\begin{macro}{\dtl@monthname}
% Full month names:
%    \begin{macrocode}
\newcommand*{\dtl@monthname}[1]{%
  \ifcase#1%
  \or January%
  \or February%
  \or March%
  \or April%
  \or May%
  \or June%
  \or July%
  \or August%
  \or September%
  \or October%
  \or November%
  \or December%
  \fi
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@abbrvmonthname}
% Abbreviated months:
%    \begin{macrocode}
\newcommand*{\dtl@abbrvmonthname}[1]{%
  \ifcase#1%
   \or Jan.%
   \or Feb.%
   \or Mar.%
   \or Apr.%
   \or May%
   \or June%
   \or July%
   \or Aug.%
   \or Sept.%
   \or Oct.%
   \or Nov.%
   \or Dec.%
  \fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbibitem}
% Define how to start a new bibitem:
%    \begin{macrocode}
\newcommand*{\DTLbibitem}{\bibitem{\DBIBcitekey}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLmbibitem}
% As \cs{DTLbibitem} but for \cs{DTLmbibliography}
%    \begin{macrocode}
\newcommand*{\DTLmbibitem}[1]{\bibitem{#1@\DBIBcitekey}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLcustombibitem}
%\changes{2.22}{2014-06-10}{new}
%\begin{definition}
%\cs{DTLcustombibitem}\marg{item code}\marg{ref text}\marg{cite key}
%\end{definition}
% As \cs{DTLbibitem} but user provides \meta{item code} to use in place of
% \cs{item}. This code can access the cite key using
% \cs{DBIBcitekey}. The \meta{ref text} is the text associated with
% this bib item. (For example, if used in an enumerate environment,
% \meta{ref text} might be \verb|\theenumi|.)
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLcustombibitem { m m m }
{%
  #1%
  \if@filesw
    \immediate\write\@auxout{\string\bibcite{#3}{#2}}%
  \fi
  \ignorespaces
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatauthor}
%\begin{definition}
%\cs{DTLformatauthor}\marg{von part}\marg{surname}\marg{junior 
%part}\marg{forenames}
%\end{definition}
% The format of an author's name.
%    \begin{macrocode}
\newcommand*{\DTLformatauthor}[4]{%
 \DTLformatforenames { #4 } ~
 \DTLformatvon { #1 }
 \DTLformatsurname { #2 }
 \DTLformatjr { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%Syntax: \meta{cs} \marg{name specs} where \meta{name specs} is
%in the form \marg{von}\marg{surname}\marg{jr}\marg{forename}
%    \begin{macrocode}
\cs_new:Nn \__databib_format_name:Nn
 {
   #1 #2
 }
%    \end{macrocode}
%
%\begin{macro}{\DTLformateditor}
% The format of an editor's name.
%    \begin{macrocode}
\newcommand*{\DTLformateditor}[4]{%
 \DTLformatforenames { #4 } ~
 \DTLformatvon { #1 }
 \DTLformatsurname { #2 }
 \DTLformatjr { #3 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatedition}
% The format of an edition:
%    \begin{macrocode}
\newcommand*{\DTLformatedition}[1]{#1 ~ \editionname}
%    \end{macrocode}
%\end{macro}
%
%The following will be redefined by the style.
%
%\begin{macro}{\DTLformatarticle}
% The format of an article:
%    \begin{macrocode}
\newcommand{\DTLformatarticle}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatbook}
% The format of a book:
%    \begin{macrocode}
\newcommand{\DTLformatbook}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatbooklet}
% The format of a booklet:
%    \begin{macrocode}
\newcommand{\DTLformatbooklet}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatinbook}
% The format of an ``inbook'' type:
%    \begin{macrocode}
\newcommand{\DTLformatinbook}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatincollection}
% The format of an ``incollection'' type:
%    \begin{macrocode}
\newcommand{\DTLformatincollection}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatinproceedings}
% The format of an ``inproceedings'' type:
%    \begin{macrocode}
\newcommand{\DTLformatinproceedings}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatmanual}
% The format of a manual:
%    \begin{macrocode}
\newcommand{\DTLformatmanual}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatmastersthesis}
% The format of a master's thesis:
%    \begin{macrocode}
\newcommand{\DTLformatmastersthesis}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatmisc}
% The format of a miscellaneous entry:
%    \begin{macrocode}
\newcommand{\DTLformatmisc}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatphdthesis}
% The format of a Ph.D. thesis:
%    \begin{macrocode}
\newcommand{\DTLformatphdthesis}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatproceedings}
% The format of a proceedings:
%    \begin{macrocode}
\newcommand{\DTLformatproceedings}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformattechreport}
% The format of a technical report:
%    \begin{macrocode}
\newcommand{\DTLformattechreport}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLformatunpublished}
% The format of an unpublished work:
%    \begin{macrocode}
\newcommand{\DTLformatunpublished}{}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
% Predefined names (these correspond to the standard \BibTeX\ 
% predefined strings of the same name without the leading 
% \cs{DTL}):
%\begin{macro}{\DTLacmcs}
%    \begin{macrocode}
\newcommand*{\DTLacmcs}{ACM Computing Surveys}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLacta}
%    \begin{macrocode}
\newcommand*{\DTLacta}{Acta Informatica}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLcacm}
%    \begin{macrocode}
\newcommand*{\DTLcacm}{Communications of the ACM}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLibmjrd}
%    \begin{macrocode}
\newcommand*{\DTLibmjrd}{IBM Journal of Research and Development}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLibmsj}
%    \begin{macrocode}
\newcommand*{\DTLibmsj}{IBM Systems Journal}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLieeese}
%    \begin{macrocode}
\newcommand*{\DTLieeese}{IEEE Transactions on Software Engineering}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLieeetc}
%    \begin{macrocode}
\newcommand*{\DTLieeetc}{IEEE Transactions on Computers}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLieeetcad}
%    \begin{macrocode}
\newcommand*{\DTLieeetcad}{IEEE Transactions on Computer-Aided Design
of Integrated Circuits}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLipl}
%    \begin{macrocode}
\newcommand*{\DTLipl}{Information Processing Letters}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLjacm}
%    \begin{macrocode}
\newcommand*{\DTLjacm}{Journal of the ACM}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLjcss}
%    \begin{macrocode}
\newcommand*{\DTLjcss}{Journal of Computer and System Sciences}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLscp}
%    \begin{macrocode}
\newcommand*{\DTLscp}{Science of Computer Programming}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsicomp}
%    \begin{macrocode}
\newcommand*{\DTLsicomp}{SIAM Journal on Computing}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLtocs}
%    \begin{macrocode}
\newcommand*{\DTLtocs}{ACM Transactions on Computer Systems}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLtods}
%    \begin{macrocode}
\newcommand*{\DTLtods}{ACM Transactions on Database Systems}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLtog}
%    \begin{macrocode}
\newcommand*{\DTLtog}{ACM Transactions on Graphics}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLtoms}
%    \begin{macrocode}
\newcommand*{\DTLtoms}{ACM Transactions on Mathematical Software}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLtoois}
%    \begin{macrocode}
\newcommand*{\DTLtoois}{ACM Transactions on Office Information 
Systems}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLtoplas}
%    \begin{macrocode}
\newcommand*{\DTLtoplas}{ACM Transactions on Programming Languages 
and Systems}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLtcs}
%    \begin{macrocode}
\newcommand*{\DTLtcs}{Theoretical Computer Science}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLresetpredefined}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLresetpredefined}{%
  \renewcommand*{\DTLacmcs}{ACM Computing Surveys}
  \renewcommand*{\DTLacta}{Acta Informatica}
  \renewcommand*{\DTLcacm}{Communications of the ACM}
  \renewcommand*{\DTLibmjrd}{IBM Journal of Research and Development}
  \renewcommand*{\DTLibmsj}{IBM Systems Journal}
  \renewcommand*{\DTLieeese}{IEEE Transactions on Software Engineering}
  \renewcommand*{\DTLieeetc}{IEEE Transactions on Computers}
  \renewcommand*{\DTLieeetcad}{IEEE Transactions on Computer-Aided Design
of Integrated Circuits}
  \renewcommand*{\DTLipl}{Information Processing Letters}
  \renewcommand*{\DTLjacm}{Journal of the ACM}
  \renewcommand*{\DTLjcss}{Journal of Computer and System Sciences}
  \renewcommand*{\DTLscp}{Science of Computer Programming}
  \renewcommand*{\DTLsicomp}{SIAM Journal on Computing}
  \renewcommand*{\DTLtocs}{ACM Transactions on Computer Systems}
  \renewcommand*{\DTLtods}{ACM Transactions on Database Systems}
  \renewcommand*{\DTLtog}{ACM Transactions on Graphics}
  \renewcommand*{\DTLtoms}{ACM Transactions on Mathematical Software}
  \renewcommand*{\DTLtoois}{ACM Transactions on Office Information 
Systems}
  \renewcommand*{\DTLtoplas}{ACM Transactions on Programming Languages 
and Systems}
  \renewcommand*{\DTLtcs}{Theoretical Computer Science}
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLresetpredefinedabbrv}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLresetpredefinedabbrv}{%
  \renewcommand*{\DTLacmcs}{ACM Comput.\ Surv.}%
  \renewcommand*{\DTLacta}{Acta Inf.}%
  \renewcommand*{\DTLcacm}{Commun.\ ACM}%
  \renewcommand*{\DTLibmjrd}{IBM J.\ Res.\ Dev.}%
  \renewcommand*{\DTLibmsj}{IBM Syst.~J.}%
  \renewcommand*{\DTLieeese}{IEEE Trans. Softw.\ Eng.}%
  \renewcommand*{\DTLieeetc}{IEEE Trans.\ Comput.}%
  \renewcommand*{\DTLieeetcad}{IEEE Trans.\ Comput.-Aided Design
     Integrated Circuits}%
  \renewcommand*{\DTLipl}{Inf.\ Process.\ Lett.}%
  \renewcommand*{\DTLjacm}{J.~ACM}%
  \renewcommand*{\DTLjcss}{J.~Comput.\ Syst.\ Sci.}%
  \renewcommand*{\DTLscp}{Sci.\ Comput.\ Programming}%
  \renewcommand*{\DTLsicomp}{SIAM J.~Comput.}%
  \renewcommand*{\DTLtocs}{ACM Trans.\ Comput.\ Syst.}%
  \renewcommand*{\DTLtods}{ACM Trans.\ Database Syst.}%
  \renewcommand*{\DTLtog}{ACM Trans.\ Gr.}%
  \renewcommand*{\DTLtoms}{ACM Trans.\ Math. Softw.}%
  \renewcommand*{\DTLtoois}{ACM Trans. Office Inf.\ Syst.}%
  \renewcommand*{\DTLtoplas}{ACM Trans.\ Prog. Lang.\ Syst.}%
  \renewcommand*{\DTLtcs}{Theoretical Comput.\ Sci.}%
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\subsection{Bibliography Styles}
% Each bibliography style is set by the command 
% \cs{dtlbst@}\meta{style}, where \meta{style} is the name
% of the bibliography style.
%
%\begin{macro}{\dtlbst@plain}
% The `plain' style:
%    \begin{macrocode}
\newcommand{\dtlbst@plain}{%
%    \end{macrocode}
% Set how to format the entire bibliography:
%    \begin{macrocode}
  \RenewDocumentEnvironment { DTLthebibliography }
   { O{\boolean{true}} m }
  {
    \int_zero:N \l__datatool_count_int
    \@sDTLforeach [ ##1 ] { ##2 } { }
     { \int_incr:N \l__datatool_count_int }
    \exp_args:NnV \begin { thebibliography }
      \l__datatool_count_int
  }
  { \end {thebibliography} }
%    \end{macrocode}
% Set how to start the bibliography entry:
%    \begin{macrocode}
  \renewcommand*{\DTLbibitem}{\bibitem{\DBIBcitekey}}%
  \renewcommand*{\DTLmbibitem}[1]{\bibitem{##1@\DBIBcitekey}}%
%    \end{macrocode}
% Sets the author name format.
%    \begin{macrocode}
  \renewcommand*{\DTLformatauthor}[4]{
    \DTLformatforenames {##4} ~
    \DTLformatvon {##1}
    \DTLformatsurname {##2}
    \DTLformatjr {##3}
  }
%    \end{macrocode}
% Sets the editor name format.
%    \begin{macrocode}
  \renewcommand*{\DTLformateditor}[4]{
    \DTLformatforenames {##4} ~
    \DTLformatvon {##1}
    \DTLformatsurname {##2}
    \DTLformatjr {##3}
  }
%    \end{macrocode}
% Sets the edition format.
%    \begin{macrocode}
  \renewcommand*{\DTLformatedition}[1]{##1 ~ \editionname}%
%    \end{macrocode}
% Sets the monthname format.
%    \begin{macrocode}
  \let\DTLmonthname\dtl@monthname
%    \end{macrocode}
% Sets other predefined names:
%    \begin{macrocode}
  \DTLresetpredefined
%    \end{macrocode}
% The format of an article.
%    \begin{macrocode}
  \renewcommand*{\DTLformatarticle}{%
    \DTLformatauthorlist
    \DTLifbibfieldexists {Author}
     { \DTLaddperiod } { }
    \DTLifbibfieldexists {Title}
     {
       \DTLstartsentencespace
       \DTLbibfield {Title}
       \DTLcheckbibfieldendsperiod {Title}
       \DTLaddperiod
     }
     { }
    \DTLifbibfieldexists{CrossRef}
     {
%    \end{macrocode}
% Cross ref field
%    \begin{macrocode}
       \DTLformatarticlecrossref 
       \DTLifbibfieldexists {Pages}
         {\DTLaddcomma} { }
       \DTLformatpages
       \DTLaddperiod
     }
     {
%    \end{macrocode}
% No cross ref field
%    \begin{macrocode}
       \DTLifbibfieldexists {Journal}
        {
          \DTLstartsentencespace
          \DTLjournalfmt { \DTLbibfield{Journal} }
          \DTLcheckbibfieldendsperiod {Journal}
          \DTLifanybibfieldexists {Number,Volume,Pages,Month,Year}
           { \DTLaddcomma }
           { \DTLaddperiod }
        }
        { }
       \DTLformatvolnumpages
       \DTLifanybibfieldexists {Volume,Number,Pages}
        {
          \DTLifanybibfieldexists {Year,Month}
           { \DTLaddcomma }
           { \DTLaddperiod }
          \DTLmidsentencefalse
        }
        { }
       \DTLformatdate
       \DTLifanybibfieldexists {Year,Month}
        { \DTLaddperiod } { }
     }
    \DTLifbibfieldexists {Note}
     {
      \DTLstartsentencespace
      \DTLbibfield {Note}
      \DTLcheckbibfieldendsperiod {Note}
      \DTLaddperiod
     }
     { }
  }
%    \end{macrocode}
% The format of a book.
%    \begin{macrocode}
  \renewcommand*{\DTLformatbook}{
   \DTLifbibfieldexists {Author}
    {
      \DTLformatauthorlist
      \DTLaddperiod 
    }
    {
      \DTLformateditorlist
      \DTLifbibfieldexists {Editor}
      {
        \DTLaddperiod
      }
      { }
    }
  \DTLifbibfieldexists {Title}
   {
    \DTLstartsentencespace
    \DTLformatbooktitle { \DTLbibfield {Title} }
    \DTLcheckbibfieldendsperiod {Title}
   }
   { }
  \DTLifbibfieldexists{CrossRef}
   {
%    \end{macrocode}
% Cross ref field
%    \begin{macrocode}
    \DTLifbibfieldexists {Title}
      { \DTLaddperiod } { }
    \DTLformatbookcrossref
    \DTLifanybibfieldexists {Edition,Month,Year}
     { \DTLaddcomma }
     { \DTLaddperiod }
   }
   {
%    \end{macrocode}
% no cross ref field
%    \begin{macrocode}
    \DTLifbibfieldexists {Title}
     {
      \DTLifbibfieldexists {Volume}
        { \DTLaddcomma }
        { \DTLaddperiod }
     }
     { }
    \DTLformatbvolume
    \DTLformatnumberseries
    \DTLifanybibfieldexists {Number,Series,Volume}
     { \DTLaddperiod } { }
    \DTLifbibfieldexists {Publisher}
     {
      \DTLstartsentencespace
      \DTLbibfield {Publisher}
      \DTLcheckbibfieldendsperiod {Publisher}
      \DTLifbibfieldexists {Address}
       { \DTLaddcomma }
       {
         \DTLifanybibfieldexists {Month,Year}
         { \DTLaddcomma }
         { \DTLaddperiod }
       }
     }
     { }
    \DTLifbibfieldexists{Address}
     {
      \DTLstartsentencespace
      \DTLbibfield {Address}
      \DTLcheckbibfieldendsperiod {Address}
      \DTLifanybibfieldexists {Month,Year}
       { \DTLaddcomma }
       { \DTLaddperiod }
     }
     { }
   }
  \DTLifbibfieldexists{Edition}
   {
    \databib_do_and_check:e 
      { \DTLformatedition { \DTLbibfield {Edition} } }
    \DTLifanybibfieldexists { Month, Year }
      { \DTLaddcomma } { \DTLaddperiod }
   }
   { }
  \DTLformatdate
  \DTLifanybibfieldexists {Year,Month}
    { \DTLaddperiod } { }
  \DTLifbibfieldexists {Note}
   {
     \DTLstartsentencespace
     \DTLbibfield {Note}
     \DTLcheckbibfieldendsperiod{Note}
     \DTLaddperiod
   }
   { }
 }
%    \end{macrocode}
% The format of a booklet.
%    \begin{macrocode}
  \renewcommand*{\DTLformatbooklet}{
    \DTLifbibfieldexists {Author}
     {
       \DTLformatauthorlist
       \DTLaddperiod
     }
     { }
    \DTLifbibfieldexists {Title}
     {
       \DTLstartsentencespace
       \DTLbibfield {Title}
       \DTLcheckbibfieldendsperiod {Title}
       \DTLaddperiod
     }
     { }
    \DTLifbibfieldexists {HowPublished}
     {
       \DTLstartsentencespace
       \DTLbibfield {HowPublished}
       \DTLcheckbibfieldendsperiod {HowPublished}
       \DTLifanybibfieldexists {Address,Month,Year}
        { \DTLaddcomma } { \DTLaddperiod }
     }
     { }
    \DTLifbibfieldexists {Address}
     {
       \DTLstartsentencespace
       \DTLbibfield {Address}
       \DTLcheckbibfieldendsperiod {Address}
       \DTLifanybibfieldexists {Month,Year}
         { \DTLaddcomma } { \DTLaddperiod }
     }
     { }
    \DTLformatdate
    \DTLifanybibfieldexists {Year,Month}
     { \DTLaddperiod } { }
    \DTLifbibfieldexists {Note}
     {
       \DTLstartsentencespace
       \DTLbibfield{Note}
       \DTLcheckbibfieldendsperiod {Note}
       \DTLaddperiod
     }
     { }
   }
%    \end{macrocode}
% The format of an `inbook' entry.
%    \begin{macrocode}
  \renewcommand*{\DTLformatinbook}
   {
     \DTLifbibfieldexists {Author}
      {
        \DTLformatauthorlist
        \DTLaddperiod
      }
      {
        \DTLifbibfieldexists {Editor}
         {
           \DTLformateditorlist
           \DTLaddperiod
         }
         { }
      } 
     \DTLifbibfieldexists {Title}
      {
        \DTLstartsentencespace
        \DTLinbooktitlefmt { \DTLbibfield{Title} }
        \DTLcheckbibfieldendsperiod {Title}
      }
      { }
     \DTLifbibfieldexists {CrossRef}
      {
%    \end{macrocode}
% Cross ref entry
%    \begin{macrocode}
        \DTLifbibfieldexists {Title}
         {
           \DTLifbibfieldexists {Chapter}
            { \DTLaddcomma } { \DTLaddperiod }
         }
         { }
        \DTLformatchapterpages
        \DTLifanybibfieldexists {Chapter,Pages}
         { \DTLaddperiod } { }
        \DTLformatbookcrossref
      }
      {
%    \end{macrocode}
% No cross ref
%    \begin{macrocode}
        \DTLifbibfieldexists {Title}
         {
           \DTLifanybibfieldexists {Chapter,Volume}
            { \DTLaddcomma } { \DTLaddperiod }
         }
         { }
        \DTLformatbvolume
        \DTLifanybibfieldexists {Volume,Series}
         {
           \DTLifanybibfieldexists {Chapter,Pages}
            { \DTLaddcomma } { \DTLaddperiod }
         }
         { }
        \DTLformatchapterpages
        \DTLifanybibfieldexists {Chapter,Pages}
         { \DTLaddperiod } { }
        \DTLifbibfieldexists {Publisher}
         {
           \DTLstartsentencespace
           \DTLbibfield {Publisher}
           \DTLcheckbibfieldendsperiod {Publisher}
           \DTLifbibfieldexists {Address}
            { \DTLaddcomma } { }
         }
         { }
        \DTLifbibfieldexists{Address}
         {
           \DTLstartsentencespace
           \DTLbibfield {Address}
           \DTLcheckbibfieldendsperiod {Address}
         }
         { }
      }
     \DTLifanybibfieldexists {Edition,Month,Year}
      { \DTLaddcomma } { \DTLaddperiod }
     \DTLifbibfieldexists {Edition}
      {
        \databib_do_and_check:e 
         { \DTLformatedition { \DTLbibfield{Edition} } }
        \DTLifanybibfieldexists {Month,Year}
         { \DTLaddcomma } { \DTLaddperiod }
      }
      { }
     \DTLformatdate
     \DTLifanybibfieldexists {Month,Year}
      { \DTLaddperiod } { }
     \DTLifbibfieldexists {Note}
      {
        \DTLstartsentencespace
        \DTLbibfield {Note}
        \DTLcheckbibfieldendsperiod {Note}
        \DTLaddperiod
      }
      { }
   }
%    \end{macrocode}
% The format of an `incollection' entry.
%    \begin{macrocode}
  \renewcommand*{\DTLformatincollection}
   {
     \DTLifbibfieldexists {Author}
      { 
        \DTLformatauthorlist
        \DTLaddperiod
      }
      { }
     \DTLifbibfieldexists {Title}
      {
        \DTLstartsentencespace
        \DTLbibfield {Title}
        \DTLcheckbibfieldendsperiod {Title}
        \DTLaddperiod
      }
      { }
     \DTLifbibfieldexists {CrossRef}
      {
%    \end{macrocode}
% Cross ref entry
%    \begin{macrocode}
        \DTLformatincollproccrossref
        \DTLifanybibfieldexists {Chapter,Pages}
         { \DTLaddcomma } { }
        \DTLformatchapterpages
        \DTLaddperiod
      }
      {
%    \end{macrocode}
% No cross ref entry
%    \begin{macrocode}
        \DTLformatinedbooktitle
        \DTLifbibfieldexists {BookTitle}
         {
           \DTLifanybibfieldexists
             {Volume,Series,Chapter,Pages,Number}
            { \DTLaddcomma } { \DTLaddperiod }
         }
         { }
        \DTLformatbvolume
        \DTLifbibfieldexists {Volume}
         {
           \DTLifanybibfieldexists
             {Number,Series,Chapter,Pages}
            { \DTLaddcomma } { \DTLaddperiod }
         }
         { }
        \DTLformatnumberseries
        \DTLifanybibfieldexists {Number,Series}
         {
          \DTLifanybibfieldexists {Chapter,Pages}
           { \DTLaddcomma } { \DTLaddperiod }
         }
         { }
        \DTLformatchapterpages
        \DTLifanybibfieldexists {Chapter,Pages}
         { \DTLaddperiod } { }
        \DTLifbibfieldexists {Publisher}
         {
           \DTLstartsentencespace
           \DTLbibfield {Publisher}
           \DTLcheckbibfieldendsperiod {Publisher}
           \DTLifanybibfieldexists {Address,Edition,Month,Year}
            { \DTLaddcomma } { \DTLaddperiod }
         }
         { }
        \DTLifbibfieldexists{Address}
         {
           \DTLstartsentencespace
           \DTLbibfield {Address}
           \DTLcheckbibfieldendsperiod {Address}
           \DTLifanybibfieldexists {Edition,Month,Year}
            { \DTLaddcomma } { \DTLaddperiod }
         }
         { }
        \DTLifbibfieldexists {Edition}
         {
           \databib_do_and_check:e 
            { \DTLformatedition { \DTLbibfield {Edition} } }
           \DTLifanybibfieldexists {Month,Year}
            { \DTLaddcomma } { \DTLaddperiod }
         }
         { }
        \DTLformatdate
        \DTLifanybibfieldexists {Month,Year}
         { \DTLaddperiod } { }
      }
     \DTLifbibfieldexists {Note}
      {
        \DTLstartsentencespace
        \DTLbibfield {Note}
        \DTLcheckbibfieldendsperiod {Note}
        \DTLaddperiod
      }
      { }
   }%
%    \end{macrocode}
% The format of an `inproceedings' entry.
%    \begin{macrocode}
  \renewcommand*{\DTLformatinproceedings}
   {
     \DTLifbibfieldexists {Author}
      {
        \DTLformatauthorlist 
        \DTLaddperiod
      }
      { }
     \DTLifbibfieldexists {Title}
      {
        \DTLstartsentencespace
        \DTLbibfield {Title}
        \DTLcheckbibfieldendsperiod {Title}
        \DTLaddperiod
      }
      { }
     \DTLifbibfieldexists {CrossRef}
      {
%    \end{macrocode}
% Cross ref entry
%    \begin{macrocode}
       \DTLformatincollproccrossref
       \DTLifbibfieldexists {Pages}
        { \DTLaddcomma } { \DTLaddperiod } 
       \DTLformatpages
       \DTLifbibfieldexists {Pages}
        { \DTLaddperiod } { } 
      }
      {
%    \end{macrocode}
% No cross ref
%    \begin{macrocode}
        \DTLformatinedbooktitle
        \DTLifbibfieldexists {BookTitle}
         {
           \DTLifanybibfieldexists
            { Volume,Series,Pages,Number,Address,Month,Year}
           { \DTLaddcomma } { \DTLaddperiod }
         }
         { }
        \DTLformatbvolume
        \DTLifbibfieldexists {Volume}
         {
           \DTLifanybibfieldexists
            { Number,Series,Pages,Address,Month,Year }
           { \DTLaddcomma } { \DTLaddperiod }
         }
         { }
        \DTLformatnumberseries
        \DTLifanybibfieldexists {Number,Series}
         {
           \DTLifanybibfieldexists
             {Pages,Address,Month,Year}
            { \DTLaddcomma } { \DTLaddperiod }
         }
         { }
        \DTLformatpages
        \DTLifbibfieldexists {Pages}
         {
           \DTLifanybibfieldexists {Address,Month,Year}
            { \DTLaddcomma } { \DTLaddperiod }
         }
         { }
        \DTLifbibfieldexists {Address}
         {
           \DTLstartsentencespace
           \DTLbibfield {Address}
           \DTLcheckbibfieldendsperiod {Address}
           \DTLifanybibfieldexists {Month,Year}
            { \DTLaddcomma } { \DTLaddperiod }
           \DTLformatdate
           \DTLifanybibfieldexists {Month,Year}
            { \DTLaddperiod } { }
           \DTLifbibfieldexists {Organization}
            {
              \DTLstartsentencespace
              \DTLbibfield {Organization}
              \DTLcheckbibfieldendsperiod {Organization}
              \DTLifbibfieldexists {Publisher}
               { \DTLaddcomma } { \DTLaddperiod }
            }
            { }
           \DTLifbibfieldexists {Publisher}
            {
              \DTLstartsentencespace
              \DTLbibfield {Publisher}
              \DTLcheckbibfieldendsperiod {Publisher}
              \DTLaddperiod
            }
            { }
         }
         {
           \DTLifanybibfieldexists {Publisher,Organization}
            { \DTLaddperiod } { }
           \DTLifbibfieldexists {Organization}
            {
              \DTLstartsentencespace
              \DTLbibfield {Organization}
              \DTLcheckbibfieldendsperiod {Organization}
              \DTLifanybibfieldexists {Publisher,Month,Year}
               { \DTLaddcomma } { }
            }
            { }
           \DTLifbibfieldexists {Publisher}
            {
              \DTLstartsentencespace
              \DTLbibfield{Publisher}
              \DTLcheckbibfieldendsperiod {Publisher}
              \DTLifanybibfieldexists {Month,Year}
               { \DTLaddcomma } { \DTLaddperiod }
            }
            { }
           \DTLformatdate
           \DTLifanybibfieldexists {Month,Year}
            { \DTLaddperiod } { }
         }
      }
     \DTLifbibfieldexists{Note}
      {
        \DTLstartsentencespace
        \DTLbibfield {Note}
        \DTLcheckbibfieldendsperiod {Note}
        \DTLaddperiod
      }
      { }
    }
%    \end{macrocode}
% The format of a manual.
%    \begin{macrocode}
   \renewcommand*{\DTLformatmanual}
    {
      \DTLifbibfieldexists {Author}
       {
         \DTLformatauthorlist  
         \DTLaddperiod
       }
       {
         \DTLifbibfieldexists {Organization}
          {
            \DTLstartsentencespace
            \DTLbibfield {Organization}
            \DTLcheckbibfieldendsperiod {Organization}
            \DTLifbibfieldexists {Address}
             {
               \DTLaddcomma
               \DTLbibfield {Address}
               \DTLcheckbibfieldendsperiod {Address}
             }
             { }
            \DTLaddperiod
          }
          { }
       }
      \DTLifbibfieldexists {Title}
       {
         \DTLstartsentencespace
         \DTLmanualtitlefmt { \DTLbibfield{Title} }
         \DTLcheckbibfieldendsperiod {Title}
         \DTLifbibfieldexists {Author}
          {
            \DTLifanybibfieldexists {Organization,Address}
             { \DTLaddperiod } { \DTLaddcomma }
          }
          {
            \DTLifanybibfieldexists
              {Organization,Address,Edition,Month,Year}
             { \DTLaddcomma } { \DTLaddperiod }
          }
       }
       { }
      \DTLifbibfieldexists {Author}
       {
         \DTLifbibfieldexists {Organization}
          {
            \DTLstartsentencespace
            \DTLbibfield{Organization}%
            \DTLcheckbibfieldendsperiod{Organization}%
            \DTLifanybibfieldexists {Address,Edition,Month,Year}
             { \DTLaddcomma } { \DTLaddperiod }
          }
          { }
         \DTLifbibfieldexists {Address}
          {
            \DTLstartsentencespace
            \DTLbibfield {Address}
            \DTLcheckbibfieldendsperiod {Address}
            \DTLifanybibfieldexists
              {Edition,Month,Year}
             { \DTLaddcomma } { \DTLaddperiod }
          }
          { }
       }
       {
         \DTLifbibfieldexists {Organization} { }
          {
            \DTLifbibfieldexists {Address}
             {
               \DTLstartsentencespace
               \DTLbibfield {Address}
               \DTLcheckbibfieldendsperiod {Address}
               \DTLifanybibfieldexists
                 {Edition,Month,Year}
                { \DTLaddcomma } { \DTLaddperiod }
             }
             { }
          }
       }
      \DTLifbibfieldexists {Edition}
       {
        \databib_do_and_check:e 
         { \DTLformatedition { \DTLbibfield {Edition} } }
        \DTLifanybibfieldexists {Month,Year}
         { \DTLaddcomma } { \DTLaddperiod }
       }
       { }
      \DTLformatdate
      \DTLifanybibfieldexists {Month,Year}
       { \DTLaddperiod } { }
      \DTLifbibfieldexists {Note}
       {
         \DTLstartsentencespace
         \DTLbibfield {Note}
         \DTLcheckbibfieldendsperiod {Note}
         \DTLaddperiod
       }
       { }
    }
%    \end{macrocode}
% The format of a master's thesis.
%    \begin{macrocode}
  \renewcommand*{\DTLformatmastersthesis}
   {
     \DTLifbibfieldexists {Author}
      {
        \DTLformatauthorlist
        \DTLaddperiod
      }
      { }
     \DTLifbibfieldexists {Title}
      {
        \DTLstartsentencespace
        \DTLbibfield {Title}
        \DTLcheckbibfieldendsperiod {Title}
        \DTLaddperiod
      }
      { }
     \DTLifbibfieldexists{Type}
      {
        \DTLstartsentencespace
        \DTLbibfield {Type}
        \DTLcheckbibfieldendsperiod {Type}
        \DTLifanybibfieldexists
          {School,Address,Month,Year}
         { \DTLaddcomma } { \DTLaddperiod }
      }
      { }
     \DTLifbibfieldexists {School}
      {
        \DTLstartsentencespace
        \DTLbibfield {School}
        \DTLcheckbibfieldendsperiod {School}
        \DTLifanybibfieldexists
          {Address,Month,Year}
         { \DTLaddcomma } { \DTLaddperiod }
      }
      { }
     \DTLifbibfieldexists {Address}
      {
        \DTLstartsentencespace
        \DTLbibfield {Address}
        \DTLcheckbibfieldendsperiod {Address}
        \DTLifanybibfieldexists {Month,Year}
         { \DTLaddcomma } { \DTLaddperiod }
      }
      { }
     \DTLformatdate
     \DTLifanybibfieldexists {Month,Year}
      { \DTLaddperiod } { }
     \DTLifbibfieldexists {Note}
      {
        \DTLstartsentencespace
        \DTLbibfield {Note}
        \DTLcheckbibfieldendsperiod {Note}
        \DTLaddperiod
      }
      { }
   }
%    \end{macrocode}
% The format of a miscellaneous entry.
%    \begin{macrocode}
  \renewcommand*{\DTLformatmisc}
   {
     \DTLifbibfieldexists {Author}
      {
        \DTLformatauthorlist
        \DTLaddperiod
      }
      { }
     \DTLifbibfieldexists {Title}
      {
        \DTLstartsentencespace
        \DTLbibfield {Title}
        \DTLcheckbibfieldendsperiod {Title}
        \DTLifbibfieldexists {HowPublished}
         {
           \DTLaddperiod
         }
         {
           \DTLifanybibfieldexists {Month,Year}
             { \DTLaddcomma } { \DTLaddperiod }
         }
        \DTLmidsentencefalse
      }
      { }
     \DTLifbibfieldexists {HowPublished}
      {
        \DTLstartsentencespace
        \DTLbibfield {HowPublished}
        \DTLcheckbibfieldendsperiod {HowPublished}
        \DTLifanybibfieldexists {Month,Year}
         { \DTLaddcomma } { \DTLaddperiod }
      }
      { }
     \DTLformatdate
     \DTLifanybibfieldexists {Month,Year}
      { \DTLaddperiod } { } 
     \DTLifbibfieldexists {Note}
      {
        \DTLstartsentencespace
        \DTLbibfield {Note}
        \DTLcheckbibfieldendsperiod {Note}
        \DTLaddperiod
      }
      { }
   }
%    \end{macrocode}
% The format of a PhD thesis.
%    \begin{macrocode}
  \renewcommand*{\DTLformatphdthesis}
   {
     \DTLifbibfieldexists {Author}
      {
        \DTLformatauthorlist
        \DTLaddperiod
      }
      { }
     \DTLifbibfieldexists {Title}
      {
        \DTLstartsentencespace
        \DTLthesistitlefmt { \DTLbibfield{Title} }
        \DTLcheckbibfieldendsperiod {Title}
        \DTLaddperiod
      }
      { }
     \DTLifbibfieldexists {Type}
      {
        \DTLstartsentencespace
        \DTLbibfield {Type}
        \DTLcheckbibfieldendsperiod {Type}
        \DTLifanybibfieldexists {School,Address,Month,Year}
          { \DTLaddcomma } { \DTLaddperiod }
      }
      { }
     \DTLifbibfieldexists {School}
      {
        \DTLstartsentencespace
        \DTLbibfield {School}
        \DTLcheckbibfieldendsperiod {School}
        \DTLifanybibfieldexists {Address,Month,Year}
          { \DTLaddcomma } { \DTLaddperiod }
      }
      { }
     \DTLifbibfieldexists {Address}
      {
        \DTLstartsentencespace
        \DTLbibfield {Address}
        \DTLcheckbibfieldendsperiod {Address}
        \DTLifanybibfieldexists {Month,Year}
          { \DTLaddcomma } { \DTLaddperiod }
      }
      { }
     \DTLformatdate
     \DTLifanybibfieldexists {Month,Year}
      { \DTLaddperiod } { }
     \DTLifbibfieldexists {Note}
      {
        \DTLstartsentencespace
        \DTLbibfield {Note}
        \DTLcheckbibfieldendsperiod {Note}
        \DTLaddperiod
      }
      { }
   }
%    \end{macrocode}
% The format of a proceedings.
%    \begin{macrocode}
  \renewcommand*{\DTLformatproceedings}
   {
     \DTLifbibfieldexists {Editor}
      {
        \DTLformateditorlist
        \DTLaddperiod
      }
      {
        \DTLifbibfieldexists {Organization}
         {
           \DTLstartsentencespace
           \DTLbibfield {Organization}
           \DTLcheckbibfieldendsperiod {Organization}
           \DTLaddperiod
         }
         { }
      }
     \DTLifbibfieldexists {Title}
      {
        \DTLstartsentencespace
        \DTLproceedingstitlefmt { \DTLbibfield{Title} }
        \DTLcheckbibfieldendsperiod {Title}
        \DTLifanybibfieldexists
          {
            Volume,Number,Address,Editor,Publisher,
            Month,Year
          }
        { \DTLaddcomma } { \DTLaddperiod }
      }
      { }
     \DTLformatbvolume
     \DTLifbibfieldexists {Volume}
      {
        \DTLifanybibfieldexists
          {
            Number,Address,Editor,Publisher,
            Month,Year
          }
         { \DTLaddcomma } { \DTLaddperiod }
      }
      { }
     \DTLformatnumberseries
     \DTLifbibfieldexists {Number}
      {
        \DTLifanybibfieldexists
          {
            Address,Editor,Publisher,Month,Year
          }
         { \DTLaddcomma } { \DTLaddperiod }
      }
      { }
     \DTLifbibfieldexists {Address}
      {
        \DTLstartsentencespace
        \DTLbibfield {Address}
        \DTLcheckbibfieldendsperiod {Address}
        \DTLifanybibfieldexists {Month,Year}
         { \DTLaddcomma } { \DTLaddperiod }
        \DTLformatdate
        \DTLifanybibfieldexists {Month,Year}
         { \DTLaddperiod } { }
        \DTLifbibfieldexists {Editor}
         {
           \DTLifbibfieldexists {Organization}
            {
              \DTLstartsentencespace
              \DTLbibfield {Organization}
              \DTLcheckbibfieldendsperiod {Organization}
              \DTLifbibfieldexists {Publisher}
               { \DTLaddcomma } { \DTLaddperiod }
            }
            { }
         }
         { }
        \DTLifbibfieldexists {Publisher}
         {
           \DTLstartsentencespace
           \DTLbibfield {Publisher}
           \DTLcheckbibfieldendsperiod {Publisher}
           \DTLaddperiod
         }
         { }
      }
      {
%    \end{macrocode}
% no address
%    \begin{macrocode}
        \DTLifbibfieldexists {Editor}
         {
           \DTLifbibfieldexists {Organization}
            {
              \DTLstartsentencespace
              \DTLbibfield {Organization}
              \DTLcheckbibfieldendsperiod {Organization}
              \DTLifanybibfieldexists {Publisher,Month,Year}
               { \DTLaddcomma } { \DTLaddperiod }
            }
            { }
         }
         { }
        \DTLifbibfieldexists {Publisher}
         {
           \DTLstartsentencespace
           \DTLbibfield {Publisher}
           \DTLcheckbibfieldendsperiod {Publisher}
           \DTLifanybibfieldexists {Month,Year}
            { \DTLaddcomma } { \DTLaddperiod }
         }
         { }
        \DTLformatdate
        \DTLifanybibfieldexists {Month,Year}
         { \DTLaddperiod } { }
      }
     \DTLifbibfieldexists {Note}
      {
        \DTLstartsentencespace
        \DTLbibfield {Note}
        \DTLcheckbibfieldendsperiod {Note}
        \DTLaddperiod
      }
      { }
   }
%    \end{macrocode}
% The format of a technical report.
%    \begin{macrocode}
  \renewcommand*{\DTLformattechreport}
   {
     \DTLifbibfieldexists {Author}
      {
        \DTLformatauthorlist
        \DTLaddperiod
      }
      { }
     \DTLifbibfieldexists {Title}
      {
        \DTLstartsentencespace
        \DTLbibfield {Title}
        \DTLcheckbibfieldendsperiod {Title}
        \DTLaddperiod
      }
      { }
     \DTLifbibfieldexists {Type}
      {
        \DTLstartsentencespace
        \DTLbibfield {Type}
        \DTLcheckbibfieldendsperiod {Type}
        \DTLifbibfieldexists {Number}
         { \DTLpostnumbername } { }
      }
      { }
     \DTLifbibfieldexists {Number}
      {
        \DTLstartsentencespace
        \DTLbibfield {Number}
        \DTLcheckbibfieldendsperiod {Number}
      }
      { }
     \DTLifanybibfieldexists {Type,Number}
      {
        \DTLifanybibfieldexists
          {Institution,Address,Month,Year}
         { \DTLaddcomma } { \DTLaddperiod }
      }
      { }
     \DTLifbibfieldexists {Institution}
      {
        \DTLstartsentencespace
        \DTLbibfield {Institution}
        \DTLcheckbibfieldendsperiod {Institution}
        \DTLifanybibfieldexists {Address,Month,Year}
         { \DTLaddcomma } { \DTLaddperiod }
      }
      { }
     \DTLifbibfieldexists {Address}
      {
        \DTLstartsentencespace
        \DTLbibfield {Address}
        \DTLcheckbibfieldendsperiod {Address}
        \DTLifanybibfieldexists {Month,Year}
          { \DTLaddcomma } { \DTLaddperiod }
      }
      { }
     \DTLformatdate
     \DTLifanybibfieldexists {Month,Year}
      { \DTLaddperiod } { }
     \DTLifbibfieldexists {Note}
      {
        \DTLstartsentencespace
        \DTLbibfield {Note}
        \DTLcheckbibfieldendsperiod {Note}
        \DTLaddperiod
      }
      { }
   }
%    \end{macrocode}
% The format of an unpublished work.
%    \begin{macrocode}
  \renewcommand*{\DTLformatunpublished}
   {
     \DTLifbibfieldexists {Author}
      {
        \DTLformatauthorlist
        \DTLaddperiod
      }
      { }
     \DTLifbibfieldexists {Title}
      {
        \DTLstartsentencespace
        \DTLbibfield {Title}
        \DTLcheckbibfieldendsperiod {Title}
        \DTLaddperiod
      }
      { }
     \DTLifbibfieldexists {Note}
      {
        \DTLstartsentencespace
        \DTLbibfield {Note}
        \DTLcheckbibfieldendsperiod {Note}
        \DTLifanybibfieldexists {Month,Year}
         { \DTLaddcomma } { \DTLaddperiod }
      }
      { }
     \DTLformatdate
     \DTLifanybibfieldexists {Month,Year}
      { \DTLaddperiod } { }
    }
%    \end{macrocode}
% End of `plain' style.
%    \begin{macrocode}
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLformatbooktitle}
%\changes{2.22}{2014-06-10}{new}
%    \begin{macrocode}
\newcommand*{\DTLformatbooktitle}[1]{\emph{#1}}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlbst@abbrv}
% Define `abbrv' style. This is similar to `plain' except that 
% some of the values are abbreviated
%    \begin{macrocode}
\newcommand{\dtlbst@abbrv}{%
%    \end{macrocode}
% Base this style on `plain':
%    \begin{macrocode}
  \dtlbst@plain
%    \end{macrocode}
% Sets the author name format.
%    \begin{macrocode}
  \renewcommand*{\DTLformatauthor}[4]{
   \DTLformatabbrvforenames { ##4 } ~
   \DTLformatvon { ##1 }
   \DTLformatsurname { ##2 }
   \DTLformatjr { ##3 }
  }
%    \end{macrocode}
% Sets the editor name format.
%    \begin{macrocode}
  \renewcommand*{\DTLformateditor}[4]{
    \DTLformatabbrvforenames { ##4 } ~
    \DTLformatvon { ##1 }
    \DTLformatsurname { ##2 }
    \DTLformatjr { ##3 }
  }
%    \end{macrocode}
% Sets the monthname format.
%    \begin{macrocode}
  \let\DTLmonthname\dtl@abbrvmonthname
%    \end{macrocode}
% Sets other predefined names:
%    \begin{macrocode}
  \DTLresetpredefinedabbrv
%    \end{macrocode}
% End of `abbrv' style.
%    \begin{macrocode}
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlbst@alpha}
% Define `alpha' style. This is similar to `plain' except that 
% the labels are strings rather than numerical.
%    \begin{macrocode}
\newcommand{\dtlbst@alpha}{%
%    \end{macrocode}
% Base this style on `plain':
%    \begin{macrocode}
  \dtlbst@plain
%    \end{macrocode}
% Set how to format the entire bibliography:
%    \begin{macrocode}
  \RenewDocumentEnvironment { DTLthebibliography }
   { O{\boolean{true}} m }
  {
    \dtl@createalphabiblabels { ##1 } { ##2 }
    \exp_args:NnV \begin { thebibliography }
      \g__databib_widest_label_tl
  }
  { \end {thebibliography} }
%    \end{macrocode}
% Set how to start the bibliography entry:
%    \begin{macrocode}
  \renewcommand* \DTLbibitem 
   {
     \exp_last_unbraced:NNf
      \bibitem
       [
         { \tl_use:c { dtl@biblabel@ \DBIBcitekey } }
       ]
      { \DBIBcitekey }
   }
  \renewcommand* \DTLmbibitem [1]
   {
     \exp_last_unbraced:NNf
      \bibitem
       [
         { \tl_use:c { dtl@biblabel@ \DBIBcitekey } }
       ]
      { ##1 @ \DBIBcitekey }
   }
%    \end{macrocode}
% End of `alpha' style.
%    \begin{macrocode}
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@widestlabel}
%Version 3.0: replaced \cs{@dtl@widestlabel} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{g\_\_databib\_widest\_label\_tl}}
%    \begin{macrocode}
\tl_new:N \g__databib_widest_label_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@thislabel}
%Version 3.0: replaced \cs{@dtl@thislabel} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_databib\_label\_tl}}
%    \begin{macrocode}
\tl_new:N \l__databib_label_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{dtl@createalphabiblabels}
%\begin{definition}
%\cs{dtl@createalphabiblabels}\marg{condition}\marg{db name}
%\end{definition}
% Constructs the alpha style bib labels for the given database.
% (Labels are stored in the control sequence 
%\cs{dtl@biblabel@}\meta{citekey}.) This also sets 
% the widest label.
%    \begin{macrocode}
\newcommand*{\dtl@createalphabiblabels}[2]{
 \dtl@message { Creating ~ bib ~ labels }
 \group_begin:
   \tl_gclear:N \g__databib_widest_label_tl
   \dim_zero:N \l__databib_widest_dim
%    \end{macrocode}
%Version 3.0: switched to read-only loop.
%\changes{3.0}{2025-03-03}{switched to read-only loop}
%    \begin{macrocode}
   \DTLforeachbibentry * [ #1 ] { #2 }
    {
      \dtl@message { \DBIBcitekey }
      \DTLifbibfieldexists {Author}
       {
         \__databib_get_alpha_label:NV
           \l__databib_label_tl \@dtl@key@Author 
       }
       {
         \DTLifbibfieldexists {Editor}
          {
            \__databib_get_alpha_label:NV
              \l__databib_label_tl \@dtl@key@Editor 
          }
          {
            \DTLifbibfieldexists {Key}
             {
               \__databib_get_first_three:Nn
                 \l__databib_label_tl { \@dtl@key@Key }
             }
             {
               \DTLifbibfieldexists {Organization}
                {
                  \__databib_get_first_three:Nn
                    \l__databib_label_tl
                    { \@dtl@key@Organization }
                }
                {
                  \__databib_get_first_three:Nn
                    \l__databib_label_tl
                    { \DBIBentrytype }
                }
             }
          }
       }
      \DTLifbibfieldexists {Year}
       { }
       {
         \DTLifbibfieldexists {CrossRef}
          {
            \DTLgetvalueforkey
             \@dtl@key@Year 
             { Year } { #2 } { CiteKey }
             { \@dtl@key@CrossRef }
          }
          { }
       }
      \DTLifbibfieldexists {Year}
       {
         \__databib_get_year_suffix:V \@dtl@key@Year
         \tl_put_right:Ne \l__databib_label_tl
          { \l__databib_year_tl }
       }
       { }
%    \end{macrocode}
%Version 3.0: replaced \cs{c@biblabel@\meta{label}} with
%\cs{g\_\_databib\_label\_\meta{label}\_int},
%replaced \cs{@dtl@bibfirst@\meta{label}} with
%\cs{g\_\_databib\_first\_\meta{label}\_tl} and replaced
%\cs{dtl@biblabel@\meta{key}} with \cs{g\_\_databib\_key\_\meta{key}\_tl }
%    \begin{macrocode}
      \int_if_exist:cTF
        { g__databib_label_ \l__databib_label_tl  _int }
       {
         \int_compare:nNnT
            { \int_use:c { g__databib_label_ \l__databib_label_tl _int } }
              =
            { \c_one_int }
          {
            \tl_gset:ce
              {
                g__databib_key_
                \tl_use:c {  g__databib_first_ \l__databib_label_tl _tl }
                 _tl
              }
              { \l__databib_label_tl a }
          }
         \int_gincr:c
          { g__databib_label_ \l__databib_label_tl _int }
         \tl_gset:ce
           { g__databib_key_ \DBIBcitekey _tl }
           { 
             \l__databib_label_tl
             \int_to_alph:n
               { \int_use:c { g__databib_label_ \l__databib_label_tl _int } }
           }
       }
       {
         \int_new:c { g__databib_label_ \l__databib_label_tl _int }
         \int_gset_eq:cN
            { g__databib_label_ \l__databib_label_tl _int }
           \c_one_int
         \tl_gset_eq:cN
            { g__databib_first_ \l__databib_label_tl _tl }
           \DBIBcitekey
         \tl_gset_eq:cN
            { g__databib_key_ \DBIBcitekey _tl }
           \l__databib_label_tl
       }
      \datatool_measure_width:Nn \dtl@tmplength
       { \tl_use:c { g__databib_key_ \DBIBcitekey _tl } }
      \dim_compare:nNnT
         { \dtl@tmplength } > { \l__databib_widest_dim }
       {
         \dim_set_eq:NN
           \l__databib_widest_dim \dtl@tmplength
         \tl_gset_eq:Nc \g__databib_widest_label_tl
           { g__databib_key_ \DBIBcitekey _tl }
       }
    }
 \group_end:
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@listgetalphalabel}
% Determine the alpha style label from a list of authors/editors
% (the first argument must be a control sequence (in which the
% label is stored), the second argument must be the list of names.)
%Version 3.0: replaced \cs{dtl@listgetalphalabel} with:
%    \begin{macrocode}
\cs_new:Nn \__databib_get_alpha_label:Nn
{
 \group_begin:
   \tl_clear:N \DTLafterinitials
   \tl_clear:N \DTLbetweeninitials
   \tl_clear:N \DTLafterinitialbeforehyphen
   \tl_clear:N \DTLinitialhyphen
   \int_set:Nn \l__databib_author_count_int
    { \clist_count:n { #2 } }
   \int_compare:nNnTF 
     { \l__databib_author_count_int } = { \c_one_int }
    {
      \int_compare:nNnTF
        { \tl_count:o { #2 } } = { 4 }
       {
         \exp_last_unbraced:No
           \databib_get_solo_author_initials:nnnnN
            #2 #1
       }
       {
         \exp_args:No \DTLstoreinitials { #2 } #1
       }
    }
    {
      \tl_clear:N #1
      \int_zero:N \l__datatool_count_int
      \clist_map_inline:nn { #2 }
       {
        \int_compare:nNnTF
          { \tl_count:n { ##1 } } = { 4 }
         {
           \databib_get_author_initials:nnnnN
             ##1 \l__databib_author_initials_tl
         }
         {
           \DTLstoreinitials
             { ##1 } \l__databib_author_initials_tl
         }
        \tl_put_right:NV #1  \l__databib_author_initials_tl 
        \int_incr:N \l__datatool_count_int
        \int_compare:nNnT
          { \l__datatool_count_int } > { 2 }
         {
           \clist_map_break:
         }
       }
    }
 \exp_args:NNNV
   \group_end:
   \tl_set:Nn #1 #1
}
\cs_generate_variant:Nn \__databib_get_alpha_label:Nn { NV }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@getauthorinitial}
% Get author's initial:
%    \begin{macrocode}
\tl_new:N \l__databib_author_initials_tl
\cs_new:Nn \databib_get_author_initials:nnnnN
{
 \tl_if_empty:nTF { #1 }
  {
    \DTLstoreinitials { #2 } #5
  }
  {
    \DTLstoreinitials { #1 ~ #2 } #5
  }
}
%    \end{macrocode}
%\end{macro}
% Get label for single author (last argument is control sequence
% in which to store the label):
%    \begin{macrocode}
\cs_new:Nn \databib_get_solo_author_initials:nnnnN
{
  \tl_if_empty:nTF { #1 }
   {
     \DTLstoreinitials { #2 } #5
   }
   {
     \DTLstoreinitials { #1 ~ #2 } #5
   }
  \int_compare:nNnT
     { \tl_count:N #5 } < { 2 }
   {
     \__databib_get_first_three:Nn #5 { #1 #2 }
   }
}
%    \end{macrocode}
%
%\begin{macro}{\dtl@get@firstthree}
%Get first three letters from the given string.
%Version 3.0: replaced \cs{dtl@get@firstthree} with:
%    \begin{macrocode}
\cs_new:Nn \__databib_get_first_three:Nn
{
  \tl_clear:N #1
  \int_zero:N \l__datatool_count_int
  \exp_args:Nx \text_map_inline:nn { \text_purify:n { #2 } }
   {
     \tl_put_right:Nn #1 { ##1 }
     \int_incr:N \l__datatool_count_int
     \int_compare:nNnT
       { \l__datatool_count_int } = { 3 }
      {
        \text_map_break:
      }
   }  
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@get@yearsuffix}
%Get year suffix.
%Version 3.0: replaced \cs{dtl@get@yearsuffix} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{\_\_databib\_get\_year\_suffix:n}}
%\changes{3.4.1}{2025-04-25}{bug fix: incorrect parameters}
%    \begin{macrocode}
\cs_new:Nn \__databib_get_year_suffix:n
{
  \regex_extract_once:NnNTF
     \c_databib_year_suffix_regex
     { #1 }
     \l__datatool_tmp_seq
   {
     \seq_get_left:NN 
       \l__datatool_tmp_seq
       \l__databib_year_tl
   }
   {
     \tl_clear:N \l__databib_year_tl
   }
}
\cs_generate_variant:Nn  \__databib_get_year_suffix:n { V }
\regex_const:Nn \c_databib_year_suffix_regex
 { ( \d \d ) \Z }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@year}
%Version 3.0: replaced \cs{@dtl@year} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_databib\_year\_tl}}
%    \begin{macrocode}
\tl_new:N \l__databib_year_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbibliographystyle}
%\begin{definition}
%\cs{DTLbibliographystyle}\marg{style}
%\end{definition}
% Sets the bibliography style.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLbibliographystyle { m }
{
  \tl_if_exist:cTF { dtlbst@ #1 }
   {
     \tl_use:c { dtlbst@ #1 }
   }
   {
     \PackageError {databib}
      {
        Unknown ~ bibliography ~ style ~ `#1'
      }
      { }
   }
}
%
%    \end{macrocode}
%\end{macro}
%Set the default bibliography style:
%    \begin{macrocode}
\DTLbibliographystyle { \l__databib_style_tl }
%    \end{macrocode}
%
%\subsection{Multiple Bibliographies}
% In order to have multiple bibliographies, there needs to be
% an aux file for each bibliography. The main bibliography is in
% \cs{jobname}.aux, but need to provide a means of creating 
% additional aux files.
%\begin{macro}{\DTLmultibibs}
%\begin{definition}
%\cs{DTLmultibibs}\marg{list}
%\end{definition}
%\changes{1.01}{2007/08/22}{new}
% This creates an auxiliary file for each name in \meta{list}.
% For example, "\DTLmultibibs{foo,bar}" will create the files
% "foo.aux" and "bar.aux".
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLmultibibs { m }
{
  \clist_map_inline:nn { #1 }
   {
     \__databib_auto_build:n { ##1 }
     \iow_new:c { g__databib_aux_ ##1 _iow }
     \iow_open:cn { g__databib_aux_ ##1 _iow } { ##1 .aux }
     \tl_new:c { b@ ##1 @ * }
   }
}
%    \end{macrocode}
% Can only be used in the preamble:
%    \begin{macrocode}
\@onlypreamble { \DTLmultibibs }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@cite@write}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\@dtl@cite@write}[2]{%
 \if@filesw
   \tl_if_exist:cTF { g__databib_aux_ #1 _iow }
    {
      \iow_now:ce { g__databib_aux_ #1 _iow } { #2 }
    }
    {
      \PackageError {databib}
       { multibib ~ `#1' ~ not ~ defined }
       {
         You ~ need ~ to ~ define ~ `#1' ~ in ~
         \token_to_str:N \DTLmultibibs
       }
    }
 \fi
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\begin{macro}{\DTLcite}
%\begin{definition}
%\cs{DTLcite}\oarg{text}\marg{mbib}\marg{labels}
%\end{definition}
% This is similar to \cs{cite}\oarg{text}\marg{labels}, except
% 1) the cite information is written to the auxiliary file 
% associated with the multi-bib \meta{mbib} (which must be named
% in \cs{DTLmultibibs}) and 2) the cross referencing label is 
% constructed from \meta{mbib} and \meta{label} to allow for
% the same citation to appear in multiple bibliographies.
%    \begin{macrocode}
\newcommand*{\DTLcite}{\@ifnextchar[{\@tempswatrue \dtl@citex
}{\@tempswafalse \dtl@citex[]}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@citex}
%Adapted from \cs{@citex}
%    \begin{macrocode}
\def\dtl@citex[#1]#2#3{%
\leavevmode\let\@citea\@empty
\@cite{\@for\@citeb:=#3\do{\@citea
  \def\@citea{,\penalty \@m \ }%
  \edef\@citeb{\expandafter\@firstofone\@citeb\@empty}%
  \@dtl@cite@write{#2}{\string\citation{\@citeb}}%
  \@ifundefined{b@#2@\@citeb}{%
    \hbox{\reset@font\bfseries ?}%
    \G@refundefinedtrue 
    \PackageWarning{databib}{Citation `\@citeb ' for multibib `#2'
      undefined}%
  }{%
    \@cite@ofmt{\csname b@#2@\@citeb \endcsname }%
  }%
  }}{#1}%
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLnocite}
%\begin{definition}
%\cs{DTLnocite}\marg{mbib}\marg{key list}
%\end{definition}
% As \cs{nocite} but uses the aux file associated with \meta{mbib}
% which must have been defined using \cs{DTLmultibibs}.
%    \begin{macrocode}
\newcommand*{\DTLnocite}[2]{%
  \@bsphack
  \ifx\@onlypreamble\document
    \@for\@citeb:=#2\do{%
      \edef\@citeb{\expandafter\@firstofone\@citeb}%
      \@dtl@cite@write{#1}{\string\citation{\@citeb}}%
      \@ifundefined{b@#1@\@citeb}{%
        \G@refundefinedtrue
        \PackageWarning{databib}{Citation `\@citeb ' undefined for
         multibib `#1'}}{}%
    }%
  \else
    \@latex@error{Cannot be used in preamble}\@eha
  \fi
  \@esphack
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\begin{macro}{\DTLloadmbbl}
%\begin{definition}
%\cs{DTLloadmbib}\marg{mbib}\marg{db name}\marg{bib list}
%\end{definition}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLloadmbbl { m m m }
{
 \@dtl@cite@write { #1 }
  {
    \token_to_str:N \bibstyle { databib } 
    \iow_newline:
    \token_to_str:N \bibdata { #3 } 
  }
  \DTLnewdb { #2 }
  \tl_set:Nx \DTLBIBdbname { \exp_args:Ne \tl_to_str:n { #2 } }
  \@input@ { #1 .bbl }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLmbibliography}
%\begin{definition}
%\cs{DTLmbibliography}\oarg{condition}\marg{mbib name}\marg{bib dbname}
%\end{definition}
% Displays the bibliography for the database \meta{bib dbname}
% which must have previously been loaded using
% \cs{DTLloadmbbl}, where \meta{mbib name} must be listed
% in \cs{DTLmultibibs}.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \DTLmbibliography
  { O{\boolean{true}} m m }
{
  \begin {DTLthebibliography} [ #1 ] { #3 }
   \DTLforeachbibentry * [ #1 ] { #3 }
    {
      \DTLmbibitem { #2 }
      \DTLformatbibentry
      \DTLendbibitem
    }
  \end {DTLthebibliography}
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Sort Support}
%The author and editor columns will be comma-separated lists with
%each item in the form
%\marg{von}\marg{surname}\marg{jr}\marg{forenames}. This means that
%if the database is sorted by author or editor, the names will be
%munged together. If it's not sufficient to sort by this, a command
%is provided for use with the encap sort option.
%\begin{macro}{\DTLbibsortencap}
%\begin{definition}
%\cs{DTLbibsortencap}\marg{value}\marg{col-idx}\marg{db-name}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand \DTLbibsortencap [3]
{
   \bool_lazy_or:nnTF
     { \int_compare_p:nNn { #2 } = { \dtlcolumnindex { #3 } { Author } } }
     { \int_compare_p:nNn { #2 } = { \dtlcolumnindex { #3 } { Editor } } }
   {
     \exp_not:N \DTLbibsortname
     \clist_use:nn { #1 } { \DTLbibsortnamesep \DTLbibsortname }
   }
   { \exp_not:n { #1 } }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLbibsortname}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLbibsortname}[4]{
 \tl_if_empty:nF { #1 } { #1 ~ }
 #2
 \tl_if_empty:nF { #3 } { , ~ #3 }
 \datatoolpersoncomma
 #4
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLbibsortnamesep}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLbibsortnamesep}{ \datatoolasciiend }
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\subsection{Localisation Support}
%Version 3.0: added localisation support.
%\begin{macro}{\RequireDataBibDialect}
%    \begin{macrocode}
\newcommand*{\RequireDataBibDialect}[1]{%
 \TrackLangRequireDialect{databib}{#1}%
} 
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\datatool@load@locales{%
  \AnyTrackedLanguages
  {%
    \ForEachTrackedDialect{\@dtl@thisdialect}%
    {%
      \RequireDataBibDialect{\@dtl@thisdialect}%
    }%
  }%
  {}%
}
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</databib.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*databar.sty>
%    \end{macrocode}
%\fi
%\section{databar.sty}
% Declare package:
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
%    \end{macrocode}
% Rollback releases:
%    \begin{macrocode}
\DeclareRelease{v2.32}{2019-09-27}{databar-2019-09-27.sty}
\DeclareCurrentRelease{v3.4.1}{2025-04-25}
%    \end{macrocode}
% Declare package:
%    \begin{macrocode}
\ProvidesPackage{databar}[2025/04/25 v3.4.1 (NLCT)]
%    \end{macrocode}
%\changes{3.0}{2025-03-03}{dropped \sty{xkeyval}}
% Version 3.0: no longer using \sty{xkeyval}.
%
%\begin{macro}{\ifDTLcolorbarchart}
% The conditional \cs{ifDTLcolorbarchart} is used to determine 
% whether to use colour or grey scale.
% NB may be removed or deprecated.
%    \begin{macrocode}
\newif\ifDTLcolorbarchart
\DTLcolorbarcharttrue
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\ifDTLverticalbars}
% Define boolean keys to govern bar chart orientation.
%    \begin{macrocode}
\newif\ifDTLverticalbars
%    \end{macrocode}
%\end{macro}
% Set defaults:
%    \begin{macrocode}
\DTLverticalbarstrue
%    \end{macrocode}
%
%\begin{macro}{\DTLbarXlabelalign}
%Alignment for lower bar labels.
%    \begin{macrocode}
\newcommand\DTLbarXlabelalign{%
 \ifDTLverticalbars left,rotate=-90\else right\fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbarXupperlabelalign}
%Alignment for upper bar labels.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand\DTLbarXupperlabelalign{%
 \ifDTLverticalbars bottom,center\else left\fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbarXneglabelalign}
%Alignment for lower label on negative bars.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand\DTLbarXneglabelalign{%
 \ifDTLverticalbars right,rotate=-90\else left\fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbarXnegupperlabelalign}
%Alignment for upper bar labels on negative bars.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand\DTLbarXnegupperlabelalign{%
 \ifDTLverticalbars top,center\else right\fi
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbarYticklabelalign}
%Alignment for $y$ tick labels.
%    \begin{macrocode}
\newcommand\DTLbarYticklabelalign{%
 \ifDTLverticalbars right\else top,center\fi
}
%    \end{macrocode}
%\end{macro}
% The package options have been changed to use \sty{l3keys}.
%
%Define package options:
%    \begin{macrocode}
\ExplSyntaxOn
\keys_define:nn { datatool }
{
  color .legacy_if_set:n = DTLcolorbarchart,
  gray .legacy_if_set_inverse:n = DTLcolorbarchart,
  verticalbars .legacy_if_set:n = DTLverticalbars ,
  vertical .code:n =
   {
     \DTLverticalbarstrue
   },
  horizontal .code:n = 
   {
     \DTLverticalbarsfalse
   },
}
\ExplSyntaxOff
%    \end{macrocode}
% Process options:
%    \begin{macrocode}
\IfPackageLoadedTF{datatool}
{
  \ProcessKeyOptions[datatool]
}
{
  \DeclareOption*{\PassOptionsToPackage{\CurrentOption}{datatool}}
  \ProcessOptions
}
\RequirePackage{datatool}
%    \end{macrocode}
% Remove the package option keys so they can't be used with
% \cs{DTLsetup} (otherwise they may conflict with \sty{datapie} etc).
%    \begin{macrocode}
\ExplSyntaxOn
\keys_define:nn { datatool }
{
  color .undefine: ,
  gray .undefine: ,
  verticalbars .undefine: ,
  vertical .undefine: ,
  horizontal .undefine: ,
}
\ExplSyntaxOff
%    \end{macrocode}
% Require \sty{dataplot} package. Note that this also loads
% \sty{tikz}.
%    \begin{macrocode}
\RequirePackage{dataplot}
%    \end{macrocode}
%
%\subsection{Scratch Variables}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%Token list to construct content:
%    \begin{macrocode}
\tl_new:N \l__databar_content_tl
%    \end{macrocode}
%
%\begin{macro}{\DTLtotalbars}
%Used to store the total number of bars.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\tl_new:N \DTLtotalbars
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbartotalvariables}
%Used to store the total number of variables for multi charts.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\tl_new:N \DTLbartotalvariables
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLtotalbargroups}
%Used to store the total number of bar groups for multi charts.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\tl_new:N \DTLtotalbargroups
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbarchartwidth}
%Used to store the total width of the $x$-axis. This doesn't include
%the $y$ tick marks or labels.
%    \begin{macrocode}
\tl_new:N \DTLbarchartwidth
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\dim_new:N \l__databar_miny_dim
\dim_new:N \l__databar_maxy_dim
%    \end{macrocode}
%
%\begin{macro}{\DTLbarvariable}
%The bar chart variable.
%    \begin{macrocode}
\tl_new:N \DTLbarvariable
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlbar@variables}
% Token list variables used to create the multi bar chart are now stored in a
% sequence. Version 3.0: changed \cs{dtlbar@variables} to:
%\changes{3.0}{2025-03-03}{changed to \cs{l\_\_databar\_variables\_seq}}
%    \begin{macrocode}
\seq_new:N \l__databar_variables_seq
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@dtl@start}
%Lower bar labels. Version 3.0: renamed \cs{@dtl@start} to:
%\changes{3.0}{2025-03-03}{renamed \cs{l\_\_databar\_start\_fp}}
%    \begin{macrocode}
\fp_new:N \l__databar_start_fp
%    \end{macrocode}
%\end{macro}
%
%Consider a bar as a thick line (of one bar width). This is anchored
%to the $x$ axis at the line's start \cs{DTLstartpt}
%and extends to the line's end \cs{DTLendpt}. Half-way along this
%line is the mid point \cs{DTLmidpt}. The $x$-coordinate for all
%these points is the same and will be stored in:
%    \begin{macrocode}
\tl_new:N \l__databar_mid_tl
\dim_new:N \l__databar_mid_dim
%    \end{macrocode}
%The extent is the current bar's $y$ value converted to a dimension:
%    \begin{macrocode}
\dim_new:N \l__databar_extent_dim
%    \end{macrocode}
%The $y$ point of the starting point will be 0. The $y$ point of the
%ending point will be the variable value. But we also need the
%starting point of the rectangle and the end point in order to draw
%the bar so that the line passes through the middle.
%The $x$ co-ordinate of the start of the rectangle is stored in:
%    \begin{macrocode}
\tl_new:N \l__databar_start_tl
%    \end{macrocode}
%The $x$ co-ordinate of the end of the rectangle is stored in:
%    \begin{macrocode}
\tl_new:N \l__databar_end_tl
%    \end{macrocode}
%Four points of the rectangle are obtained with those $x$
%co-ordinates for $y=0$ and $y=v$, where $v$ is the bar variable's
%value.
% 
%Offset for group label.
%    \begin{macrocode}
\dim_new:N \l__databar_group_label_offset_dim
%    \end{macrocode}
%
%\begin{macro}{\dtl@extent}
%Bar extent. Version 3.0: renamed \cs{dtl@extent} to:
%\changes{3.0}{2025-03-03}{renamed \cs{l\_\_databar\_extent\_fp}}
%    \begin{macrocode}
\fp_new:N \l__databar_extent_fp
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@unit}
%Lower bar labels. Version 3.0: renamed \cs{dtl@unit} to:
%\changes{3.0}{2025-03-03}{renamed \cs{l\_\_databar\_unit\_fp}}
%    \begin{macrocode}
\fp_new:N \l__databar_unit_fp
%    \end{macrocode}
%\end{macro}
%Current label offset and alignment:
%    \begin{macrocode}
\tl_new:N \l__databar_label_current_offset_tl
\tl_new:N \l__databar_label_current_align_tl
%    \end{macrocode}
%\begin{macro}{\dtl@barlabel}
%Lower bar labels. Version 3.0: renamed \cs{dtl@barlabel} to:
%\changes{3.0}{2025-03-03}{renamed \cs{l\_\_databar\_barlabel\_tl}}
%    \begin{macrocode}
\tl_new:N \l__databar_barlabel_tl
%    \end{macrocode}
%\end{macro}
% Lower bar label in multi-bar chart.
%    \begin{macrocode}
\tl_new:N \l__databar_lowerbarlabel_tl
%    \end{macrocode}
%
%\begin{macro}{\dtl@multibarlabels}
% Lower bar labels for multi-bar charts. Version 3.0: changed \cs{dtl@multibarlabels}
% to:
%\changes{3.0}{2025-03-03}{changed to \cs{l\_\_databar\_multibarlabels\_seq}}
%    \begin{macrocode}
\seq_new:N \l__databar_multibarlabels_seq
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlbar@groupgap}
% Gap between groups in multi-bar charts (This should be in $x$ units
% where 1 $x$ unit is the width of a bar.) Version 3.0: changed
% \cs{dtlbar@groupgap} to:
%\changes{3.0}{2025-03-03}{changed to \cs{l\_\_databar\_groupgap\_tl}}
%    \begin{macrocode}
\tl_new:N \l__databar_groupgap_tl
\tl_set:Nn \l__databar_groupgap_tl { 1 }
%    \end{macrocode}
%\end{macro}
%Gap between bars.
%    \begin{macrocode}
\tl_new:N \l__databar_bargap_tl
\tl_set:Nn \l__databar_bargap_tl { 0 }
%    \end{macrocode}
%
%\begin{macro}{\dtl@upperbarlabel}
% Upper bar labels. Version 3.0: changed \cs{dtl@upperbarlabel} to:
%\changes{3.0}{2025-03-03}{changed to \cs{l\_\_databar\_upperbarlabel\_tl}}
%    \begin{macrocode}
\tl_new:N \l__databar_upperbarlabel_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@uppermultibarlabels}
% Upper bar labels for multi-bar charts.
% Version 3.0: changed \cs{dtl@uppermultibarlabels} to:
%\changes{3.0}{2025-03-03}{changed to \cs{l\_\_databar\_uppermultibarlabels\_seq}}
%    \begin{macrocode}
\seq_new:N \l__databar_uppermultibarlabels_seq
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{dtlbar@yticlist}
% Define list of points for $y$ tics. (Must be a comma separated list
% of decimal numbers.) Version 3.0: changed from \cs{dtlbar@yticlist} to
% a sequence.
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_databar\_yticpoints\_seq}}
%    \begin{macrocode}
\clist_new:N \l__databar_yticpoints_clist 
\seq_new:N \l__databar_yticpoints_seq 
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlbar@yticgap}
% The $y$ tick gap. Version 3.0: changed \cs{dtlbar@yticgap} to:
%\changes{3.0}{2025-03-03}{renamed to \cs{l\_\_databar\_yticgap\_tl}}
%    \begin{macrocode}
\tl_new:N \l__databar_yticgap_tl
\fp_new:N \l__databar_yticgap_fp
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{dtlbar@yticlabels}
% Define list of points for $y$ tick labels. 
% Version 3.0: changed from \cs{dtlbar@yticlabels} to
% a sequence.
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_databar\_yticlabels\_seq}}
%    \begin{macrocode}
\seq_new:N \l__databar_yticlabels_seq
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlbar@ylabel}
% $y$ axis label. Version 3.0: changed \cs{dtlbar@ylabel} to:
%\changes{3.0}{2025-03-03}{renamed \cs{l\_\_databar\_ylabel\_tl}}
%    \begin{macrocode}
\tl_new:N \l__databar_ylabel_tl 
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLyAxisLabelStyle}
%$y$-axis label style.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLyAxisLabelStyle}{%
 bottom, center \ifDTLverticalbars , rotate = 90 \fi
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\tl_new:N \l__databar_ylabel_offset_tl
\tl_set:Nn \l__databar_ylabel_offset_tl
 { \baselineskip }
%    \end{macrocode}
%
%    \begin{macrocode}
\tl_new:N \l__databar_ylabel_pos_tl
\tl_set:Nn \l__databar_ylabel_pos_tl
 { \c__databar_ylabel_pos_centred_tl }
%    \end{macrocode}
%
% Centred along the $y$ axis:
%    \begin{macrocode}
\tl_const:Nn \c__databar_ylabel_pos_centred_tl
 {
   \ifDTLverticalbars
     \exp_not:N \pgfpoint
      {
        - \exp_not:N \l__dataplot_y_tic_label_width_dim 
      }
      { 
        \dim_eval:n 
         {
            0.5 \DTLbarchartlength + \l__databar_miny_dim
         }
      }
   \else
     \exp_not:N \pgfpoint
      { 
        \dim_eval:n 
         {
            0.5 \DTLbarchartlength + \l__databar_miny_dim
         }
      }
      {
        - \exp_not:N \l__dataplot_y_tic_label_width_dim 
      }
   \fi
 }
%    \end{macrocode}
%At the minimum end of the $y$ axis:
%    \begin{macrocode}
\tl_const:Nn \c__databar_ylabel_pos_min_tl
 {
   \ifDTLverticalbars
     \exp_not:N \pgfpoint
      {
        - \exp_not:N \l__dataplot_y_tic_label_width_dim 
      }
      { \exp_not:N \l__databar_miny_dim }
   \else
     \exp_not:N \pgfpoint
      { \exp_not:N \l__databar_miny_dim }
      {
        - \exp_not:N \l__dataplot_y_tic_label_width_dim 
      }
   \fi
 }
%    \end{macrocode}
% At the maximum end:
%    \begin{macrocode}
\tl_const:Nn \c__databar_ylabel_pos_max_tl
 {
   \ifDTLverticalbars
     \exp_not:N \pgfpoint
      {
        - \exp_not:N \l__dataplot_y_tic_label_width_dim 
      }
      { \exp_not:N \l__databar_maxy_dim }
   \else
     \exp_not:N \pgfpoint
      { \exp_not:N \l__databar_maxy_dim }
      {
        - \exp_not:N \l__dataplot_y_tic_label_width_dim 
      }
   \fi
 }
%    \end{macrocode}
% At the origin:
%    \begin{macrocode}
\tl_const:Nn \c__databar_ylabel_pos_zero_tl
 {
   \ifDTLverticalbars
     \exp_not:N \pgfpoint
      {
        - \exp_not:N \l__dataplot_y_tic_label_width_dim 
      }
      { \exp_not:N \c_zero_dim }
   \else
     \exp_not:N \pgfpoint
      { \exp_not:N \c_zero_dim }
      {
        - \exp_not:N \l__dataplot_y_tic_label_width_dim 
      }
   \fi
 }
%    \end{macrocode}
%
%\begin{macro}{\@dtl@barcount}
%\changes{3.0}{2025-03-03}{removed}
%Version 3.0: integer register \cs{@dtl@barcount} removed.
%\end{macro}
%
%\begin{macro}{\DTLbarindex}
% May be used in hooks to access the bar index.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLbarindex}{0}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbargroupindex}
% May be used in hooks to access the bar group index.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand{\DTLbargroupindex}{0}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Settings}
%
% Define some variables that govern the appearance of the bar
% chart.
%
%\begin{macro}{\DTLbargrouplabelalign}
%Alignment for group label.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand\DTLbargrouplabelalign{\DTLbarXlabelalign}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbarchartlength}
% The total height of the bar chart is given by \cs{DTLbarchartheight}
%    \begin{macrocode}
\newlength\DTLbarchartlength
\DTLbarchartlength=3in
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbarwidth}
% The width of each bar is given by \cs{DTLbarwidth}.
%    \begin{macrocode}
\newlength\DTLbarwidth
\DTLbarwidth=1cm
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbarmax}
% The maximum value of the $y$ axis.
%    \begin{macrocode}
\tl_new:N \DTLbarmax
%    \end{macrocode}
%\end{macro}
%Set the maximum, with a check to make sure that the value
%isn't negative:
%    \begin{macrocode}
\cs_new:Nn \__databar_set_max:n
 {
   \tl_if_empty:nTF { #1 }
    {
     \tl_clear:N \DTLbarmax
    }
    {
     \fp_compare:nNnTF
      { #1 } < { \c_zero_fp }
     {
       \PackageError { databar }
        {
          max ~ must ~ be ~ zero ~ or ~ positive ~ (or ~
          empty ~ for ~ the ~ default)
        }
        { }
     }
     {
       \tl_set:Nn \DTLbarmax { #1 }
     }
   }
 }
%    \end{macrocode}
%
%\begin{macro}{\DTLnegextent}
% The negative extent.
%    \begin{macrocode}
\tl_new:N \DTLnegextent
%    \end{macrocode}
%\end{macro}
%Set the negative extent, with a check to make sure that the value
%isn't positive:
%    \begin{macrocode}
\cs_new:Nn \__databar_set_max_depth:n
 {
   \tl_if_empty:nTF { #1 }
    {
     \tl_clear:N \DTLnegextent
    }
    {
     \fp_compare:nNnTF
      { #1 } > { \c_zero_fp }
     {
       \PackageError { databar }
        {
          maxdepth ~ must ~ be ~ zero ~ or ~ negative ~ (or ~
          empty ~ for ~ the ~ default)
        }
        { }
     }
     {
       \tl_set:Nn \DTLnegextent { #1 }
     }
   }
 }
%    \end{macrocode}
%
%\begin{macro}{\DTLbarlabeloffset}
% The offset from the $x$ axis to the bar label is given by
% \cs{DTLbarlabeloffset}.
%    \begin{macrocode}
\newlength\DTLbarlabeloffset
\setlength\DTLbarlabeloffset{10pt}
%    \end{macrocode}
%\end{macro}
%
%The offset from the upper edge of the bar to the upper label.
%    \begin{macrocode}
\tl_new:N \l__databar_upper_label_offset_tl
\tl_set:Nn \l__databar_upper_label_offset_tl
 { \DTLbarlabeloffset }
%    \end{macrocode}
%
% Label offsets depend on the label-style setting. Lower labels:
%    \begin{macrocode}
\tl_new:N \l__databar_label_pos_offset_tl
\tl_set:Nn \l__databar_label_pos_offset_tl
 { \dim_to_decimal:n { -\DTLbarlabeloffset } pt }
\tl_new:N \l__databar_label_neg_offset_tl
\tl_set:Nn \l__databar_label_neg_offset_tl
 { \dim_to_decimal:n { \DTLbarlabeloffset } pt }
%    \end{macrocode}
% Upper labels:
%    \begin{macrocode}
\tl_new:N \l__databar_upper_label_pos_offset_tl
\tl_set:Nn \l__databar_upper_label_pos_offset_tl
 { \dim_to_decimal:n { \l__databar_upper_label_offset_tl } pt }
\tl_new:N \l__databar_upper_label_neg_offset_tl
\tl_set:Nn \l__databar_upper_label_neg_offset_tl
 { \dim_to_decimal:n { -\l__databar_upper_label_offset_tl } pt }
%    \end{macrocode}
%
%\begin{macro}{\DTLBarXAxisStyle}
% The style of the $x$ axis is given by \cs{DTLBarXAxisStyle}
%    \begin{macrocode}
\newcommand*{\DTLBarXAxisStyle}{-}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLBarYAxisStyle}
% The style of the $y$ axis is given by \cs{DTLBarYAxisStyle}.
%    \begin{macrocode}
\newcommand*{\DTLBarYAxisStyle}{-}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLBarStyle}
%Any additional styling for each bar.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLBarStyle}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{counter}{DTLbarroundvar}
% \ctrfmt{DTLbarroundvar} is a counter governing the number of digits
% to round to for the label.
%    \begin{macrocode}
\newcounter{DTLbarroundvar}
\setcounter{DTLbarroundvar}{1}
%    \end{macrocode}
%\end{counter}
% Number of digits to round the y tick label:
%    \begin{macrocode}
\tl_new:N \l__databar_ytic_round_tl
\tl_set:Nn \l__databar_ytic_round_tl { \int_use:N \c@DTLbarroundvar }
%    \end{macrocode}
%
%\begin{macro}{\DTLbardisplayYticklabel}
%\cs{DTLbardisplayYticklabel} governs how the $y$ tick labels appear.
%    \begin{macrocode}
\newcommand*{\DTLbardisplayYticklabel}[1]{#1}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLdisplaylowerbarlabel}
%\cs{DTLdisplaylowerbarlabel} governs how the lower bar labels appear.
%    \begin{macrocode}
\newcommand*{\DTLdisplaylowerbarlabel}[1]{#1}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLdisplaybargrouplabel}
%\cs{DTLdisplaybargrouplabel} governs how the bar group labels appear.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLdisplaybargrouplabel}[1]{#1}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLdisplaylowermultibarlabel}
%\cs{DTLdisplaylowermultibarlabel} governs how the lower multi bar labels appear.
%    \begin{macrocode}
\newcommand*{\DTLdisplaylowermultibarlabel}[1]{#1}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLdisplayupperbarlabel}
%\cs{DTLdisplayupperbarlabel} governs how the upper bar labels 
% appear.
%    \begin{macrocode}
\newcommand*{\DTLdisplayupperbarlabel}[1]{#1}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLdisplayuppermultibarlabel}
%\cs{DTLdisplayuppermultibarlabel} governs how the upper multi bar 
% labels appear.
%    \begin{macrocode}
\newcommand*{\DTLdisplayuppermultibarlabel}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbaratbegintikz}
% \cs{DTLbaratbegintikz} specifies any commands
% to apply at the start of the \env{tikzpicture} environment. 
% By default it does nothing.
%    \begin{macrocode}
\newcommand*{\DTLbaratbegintikz}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbaratendtikz}
% \cs{DTLbaratendtikz} specifies any commands
% to apply at the end of the \env{tikzpicture} environment. 
% By default it does nothing.
%    \begin{macrocode}
\newcommand*{\DTLbaratendtikz}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\ifDTLbarxaxis}
% The conditional \cs{ifDTLbarxaxis} is used to determine 
% whether or not to display the $x$ axis
%    \begin{macrocode}
\newif\ifDTLbarxaxis
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\ifDTLbaryaxis}
% The conditional \cs{ifDTLbaryaxis} is used to determine 
% whether or not to display the $y$ axis.
%    \begin{macrocode}
\newif\ifDTLbaryaxis
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\ifDTLbarytics}
% The conditional \cs{ifDTLbarytics} to determine whether 
% or not to display the $y$ tick marks.
%    \begin{macrocode}
\newif\ifDTLbarytics
%    \end{macrocode}
%\end{macro}

% No-op command:
%    \begin{macrocode}
\cs_new:Npn \__databar_noop:N #1
{
  \PackageError { databar }
  {
    Can't ~ use ~ \token_to_str:N #1 \c_space_tl ~ outside ~
    \token_to_str:N \DTLbarchart \c_space_tl ~ or ~
    \token_to_str:N \DTLmultibarchart
  }
  { }
}
%    \end{macrocode}
%
%There are two colour lists: the default colour list and the
%negative bar list. If a bar value is negative, the colour will
%first be looked up in the negative bar list. If missing, the
%default colour list will be used. Bars with positive values will
%just use the default colour list.
%
%    \begin{macrocode}
\int_new:N \l__databar_max_colors_int
\int_new:N \l__databar_max_neg_colors_int
%    \end{macrocode}
%
%\begin{macro}{\DTLsetbarcolor}
%\begin{definition}
% \cs{DTLsetbarcolor}\marg{n}\marg{color}
%\end{definition}
% Assigns colour name \meta{color} to the \meta{n}th bar.
%    \begin{macrocode}
\NewDocumentCommand \DTLsetbarcolor { m m }
{
 \tl_set:cn { dtlbar@col\romannumeral#1 } { #2 }
 \int_compare:nNnT { #1 } > { \l__databar_max_colors_int }
  {
   \int_set:Nn \l__databar_max_colors_int { #1 }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLclearbarcolors}
%\begin{definition}
% \cs{DTLclearbarcolors}
%\end{definition}
% Clears all bar colours.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLclearbarcolors { }
{
  \int_step_inline:nn { \l__databar_max_colors_int }
   {
     \cs_undefine:c { dtlbar@col\romannumeral##1 } 
   }
  \int_zero:N \l__databar_max_colors_int
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsetnegbarcolor}
%\begin{definition}
% \cs{DTLsetnegbarcolor}\marg{n}\marg{color}
%\end{definition}
% Assigns colour name \meta{color} to the \meta{n}th bar for
% negative bar values.
%    \begin{macrocode}
\NewDocumentCommand \DTLsetnegbarcolor { m m }
{
 \tl_set:cn { dtlbar@negcol\romannumeral#1 } { #2 }
 \int_compare:nNnT { #1 } > { \l__databar_max_neg_colors_int }
  {
   \int_set:Nn \l__databar_max_neg_colors_int { #1 }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLclearnegbarcolors}
%\begin{definition}
% \cs{DTLclearnegbarcolors}
%\end{definition}
% Clears all negative bar colours.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLclearnegbarcolors { }
{
  \int_step_inline:nn { \l__databar_max_neg_colors_int }
   {
     \cs_undefine:c { dtlbar@negcol\romannumeral##1 } 
   }
   \int_zero:N \l__databar_max_neg_colors_int
}
%    \end{macrocode}
%\end{macro}
%
% Provide an option to cycle round the bar colours. This is a token
% list variable rather than an integer to allow it to be set to
% \cs{l\_\_databar\_max\_colors\_int} which can then take into account
% any extra colours added afterwards.
%    \begin{macrocode}
\tl_new:N \l__databar_color_max_index_tl
\tl_set:Nn \l__databar_color_max_index_tl { \c_zero_int }
\tl_new:N \l__databar_neg_color_max_index_tl
\tl_set:Nn \l__databar_neg_color_max_index_tl
  { \l__databar_color_max_index_tl }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__databar_cycle_index:nn
{
  \int_eval:n
   {
     \__databar_cycle_index_adjust:on
      {
        \int_mod:nn { #1 } { \l__databar_color_max_index_tl }
      }
      {  \l__databar_color_max_index_tl }
   }
}
\cs_new:Nn \__databar_cycle_index_adjust:nn
{
  \int_if_zero:nTF { #1 } { #2 } { #1 }
}
\cs_generate_variant:Nn \__databar_cycle_index_adjust:nn { on }
%    \end{macrocode}
%
%\begin{macro}{\DTLgetbarcolor}
%\begin{definition}
%\cs{DTLgetbarcolor}\marg{n}
%\end{definition}
% Gets the colour specification for the \meta{n}th bar.
%    \begin{macrocode}
\newcommand*{\DTLgetbarcolor}[1]{%
 \int_compare:nNnTF
   { \l__databar_color_max_index_tl } > { \c_zero_int }
  {
    \__databar_get_bar_col:n
     {
       \__databar_cycle_index:nn
        { #1 } { \l__databar_color_max_index_tl }
     }
  }
  {
    \__databar_get_bar_col:n { #1 }
  }
}
\cs_new:Nn \__databar_get_bar_col:n
{
  \cs_if_exist_use:cF { dtlbar@col\romannumeral#1 } { white }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgetnegbarcolor}
%\begin{definition}
%\cs{DTLgetnegbarcolor}\marg{n}
%\end{definition}
% Gets the negative bar colour specification for the \meta{n}th bar.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLgetnegbarcolor}[1]{%
 \int_compare:nNnTF
   { \l__databar_neg_color_max_index_tl } > { \c_zero_int }
  {
    \__databar_get_neg_bar_col:n
     {
       \__databar_cycle_index:nn
        { #1 } { \l__databar_neg_color_max_index_tl }
     }
  }
  {
    \__databar_get_neg_bar_col:n { #1 }
  }
}
\cs_new:Nn \__databar_get_neg_bar_col:n
{
  \cs_if_exist_use:cF { dtlbar@negcol\romannumeral#1 }
    { \__databar_get_bar_col:n { #1 } }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdobarcolor}
%\begin{definition}
%\cs{DTLdobarcolor}\oarg{value}\marg{n}
%\end{definition}
% Sets the colour to that for the \meta{n}th bar. The value
% determines whether to use the default colour set or the negative
% set.
%    \begin{macrocode}
\NewDocumentCommand \DTLdobarcolor { o m }
{
  \IfValueTF { #1 }
   {
     \fp_compare:nNnTF { #1 } < { \c_zero_fp }
      {
        \__databar_do_col_or_nothing:x { \DTLgetnegbarcolor { #2 } }
      }
      {
        \__databar_do_col_or_nothing:x { \DTLgetbarcolor { #2 } }
      }
   }
   {
     \__databar_do_col_or_nothing:x { \DTLgetbarcolor { #2 } }
   }
}
\cs_new:Nn \__databar_do_col_or_nothing:n
{
  \tl_if_empty:nF { #1 } { \color { #1 } }
}
\cs_generate_variant:Nn \__databar_do_col_or_nothing:n { x }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdocurrentbarcolor}
%\cs{DTLdocurrentbarcolor}
% sets the colour to that of the current bar. NB this only consults
% the default colour list unless \cs{DTLbarvalue} is defined and 
% \cs{\_\_databar\_index\_int} is greater than 0. In which case,
% the bar value will be tested to determine if the negative bar
% colour list should be used.
%    \begin{macrocode}
\NewDocumentCommand \DTLdocurrentbarcolor { }
{
 \int_compare:nNnTF
   { \int_use:N \l__databar_index_int } > { \c_zero_int }
  {
    \tl_if_exist:N \DTLbarvalue
     { 
       \exp_args:NV \tl_if_head_eq_meaning:nNTF
         \DTLbarvalue \__datatool_datum:nnnn
        {
          \fp_compare:nNnTF
             { \DTLdatumvalue \DTLbarvalue } < { \c_zero_int }
           {
             \__databar_do_col_or_nothing:x 
               { \DTLgetnegbarcolor { \l__databar_index_int } }
           }
           {
             \__databar_do_col_or_nothing:x 
               { \DTLgetbarcolor { \l__databar_index_int } }
           }
        }
        {
          \fp_compare:nNnTF
             { \DTLbarvalue } < { \c_zero_int }
           {
             \__databar_do_col_or_nothing:x 
               { \DTLgetnegbarcolor { \l__databar_index_int } }
           }
           {
             \__databar_do_col_or_nothing:x 
               { \DTLgetbarcolor { \l__databar_index_int } }
           }
        }
     }
     { 
       \__databar_do_col_or_nothing:x 
        { \DTLgetbarcolor { \l__databar_index_int } }
     }
  }
  {
    \int_compare:nNnTF
      { \l__datatool_row_idx_int } > { \c_zero_int }
    {
      \exp_args:NV \DTLdobarcolor \l__datatool_row_idx_int
    }
    {
      \int_compare:nNnTF { \dtlforeachlevel } > { 0 }
       {
         \exp_args:Nv \DTLdobarcolor
            { c@DTLrow\romannumeral\dtlforeachlevel }
       }
       {
         \__databar_noop:N \DTLdocurrentbarcolor
       }
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbaroutlinecolor}
%\cs{DTLbaroutlinecolor} specifies what colour to draw the
% outline.
%    \begin{macrocode}
\newcommand*{\DTLbaroutlinecolor}{black}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbaroutlinewidth}
%\cs{DTLbaroutlinewidth} specifies the line width of the outline:
% Outline is only drawn if the linewidth is greater than 0pt.
%    \begin{macrocode}
\newlength\DTLbaroutlinewidth
\DTLbaroutlinewidth=0pt
%    \end{macrocode}
%\end{macro}
%
% Set the default colours. If there are more than eight bars,
% more colours will need to be defined.
%    \begin{macrocode}
\ifDTLcolorbarchart
 \DTLsetbarcolor{1}{red}
 \DTLsetbarcolor{2}{green}
 \DTLsetbarcolor{3}{blue}
 \DTLsetbarcolor{4}{yellow}
 \DTLsetbarcolor{5}{magenta}
 \DTLsetbarcolor{6}{cyan}
 \DTLsetbarcolor{7}{orange}
 \DTLsetbarcolor{8}{white}
\else
 \DTLsetbarcolor{1}{black!15}
 \DTLsetbarcolor{2}{black!25}
 \DTLsetbarcolor{3}{black!35}
 \DTLsetbarcolor{4}{black!45}
 \DTLsetbarcolor{5}{black!55}
 \DTLsetbarcolor{6}{black!65}
 \DTLsetbarcolor{7}{black!75}
 \DTLsetbarcolor{8}{black!85}
\fi
%    \end{macrocode}
%\begin{macro}{\DTLeverybarhook}
% Code to apply at every bar after the bar has been drawn.
%\changes{2.0}{2009 February 27}{new}%
%    \begin{macrocode}
\newcommand*{\DTLeverybarhook}{}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLeveryprebarhook}
% Code to apply at every bar before the bar has been drawn.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLeveryprebarhook}{}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLeverybargrouphook}
%Hook at the end of every bar group for \cs{DTLmultibarchart}.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand*{\DTLeverybargrouphook}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLbarsetupperlabelalign}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLbarsetupperlabelalign { o m }
{
  \IfValueT { #1 }
   {
     \tl_set:Nn \DTLbarXnegupperlabelalign { #1 }
   }
  \tl_set:Nn \DTLbarXupperlabelalign { #2 }
}
%    \end{macrocode}
%\end{macro}
%Used by "upper-label-align" to allow for missing braces around
%mandatory part (e.g. "upper-label-align=[left]right" or
%"upper-label-align=right").
%    \begin{macrocode}
\cs_new:Npn \__databar_set_upper_align:w #1 \q_stop
{
  \tl_if_head_eq_charcode:nNTF { #1 } [
   { 
     \__databar_set_upper_align_both:w #1 \q_stop
   }
   {
     \tl_if_single:NTF { #1 }
      {
        \DTLbarsetupperlabelalign #1 
      }
      {
        \DTLbarsetupperlabelalign { #1 }
      }
   }
}
\cs_new:Npn \__databar_set_upper_align_both:w [ #1 ] #2 \q_stop
{
  \tl_if_single:NTF { #2 }
   {
     \DTLbarsetupperlabelalign [ #1 ] #2
   }
   {
     \DTLbarsetupperlabelalign [ #1 ] { #2 }
   }
}
%    \end{macrocode}
%Initialisation code after options have been parsed:
%    \begin{macrocode}
\tl_new:N \l__databar_init_tl
%    \end{macrocode}
%Initialisation code before options have been parsed:
%    \begin{macrocode}
\tl_new:N \l__databar_pre_init_tl
%    \end{macrocode}
%
%    \begin{macrocode}
\keys_define:nn { datatool/bar }
{
%    \end{macrocode}
% Variable used to create the bar chart. (Must be a control
% sequence.)
%    \begin{macrocode}
  variable .code:n =
   {
     \tl_if_single:nTF { #1 }
      {
        \tl_set:Nn \DTLbarvariable { #1 }
      }
      {
        \PackageError { databar }
        {
          Invalid ~ value ~ in ~ `variable = \tl_to_str:n { #1 }'
        }
        {
          A ~ single ~ control ~ sequence ~ expected ~ as ~ the ~
          value ~ for ~ the ~ `variable' ~ setting
        }
      }
   } ,
%    \end{macrocode}
% Variables used to create the multi bar chart. (Must be a
% comma separated list of control sequences.)
%    \begin{macrocode}
  variables .code:n = 
   {
     \seq_set_from_clist:Nn
       \l__databar_variables_seq { #1 }
   },
%    \end{macrocode}
% Rounding:
%    \begin{macrocode} 
  round .int_set:N = \c@DTLbarroundvar ,
  y-tick-round .code:n =
   {
     \tl_if_empty:nTF { #1 }
      {
        \tl_set:Nn \l__databar_ytic_round_tl
         { \int_use:N \c@DTLbarroundvar }
      }
      {
        \tl_set:Nn \l__databar_ytic_round_tl { #1 }
      }
   } ,
  y-tick-round .default:n = { } ,
%    \end{macrocode}
% Synonym:
%    \begin{macrocode} 
  ytic-round .code:n =
   {
     \tl_if_empty:nTF { #1 }
      {
        \tl_set:Nn \l__databar_ytic_round_tl
         { \int_use:N \c@DTLbarroundvar }
      }
      {
        \tl_set:Nn \l__databar_ytic_round_tl { #1 }
      }
   } ,
  ytic-round .default:n = { } ,
%    \end{macrocode}
% Bar width:
%    \begin{macrocode} 
  barwidth .dim_set:N = \DTLbarwidth,
  bar-width .dim_set:N = \DTLbarwidth,
%    \end{macrocode}
% Lower bar labels
%    \begin{macrocode}
  barlabel .tl_set:N = \l__databar_barlabel_tl ,
  bar-label .tl_set:N = \l__databar_barlabel_tl ,
%    \end{macrocode}
% Lower bar labels for multi-bar charts:
%    \begin{macrocode}
  multibarlabels .code:n = 
   {
     \seq_set_from_clist:Nn 
      \l__databar_multibarlabels_seq { #1 }
   },
  multi-bar-labels .code:n = 
   {
     \seq_set_from_clist:Nn 
      \l__databar_multibarlabels_seq { #1 }
   }, 
%    \end{macrocode}
% Gap between bars:
%    \begin{macrocode}
  bargap .tl_set:N = \l__databar_bargap_tl ,
  bar-gap .tl_set:N = \l__databar_bargap_tl ,
%    \end{macrocode}
% Gap between groups in multi-bar charts:
%    \begin{macrocode}
  groupgap .tl_set:N = \l__databar_groupgap_tl ,
  group-gap .tl_set:N = \l__databar_groupgap_tl ,
%    \end{macrocode}
% Upper bar labels:
%    \begin{macrocode}
  upperbarlabel .tl_set:N = \l__databar_upperbarlabel_tl ,
  upper-bar-label .tl_set:N = \l__databar_upperbarlabel_tl ,
%    \end{macrocode}
% Upper bar labels for multi-bar charts:
%    \begin{macrocode}
  uppermultibarlabels .code:n =
   {
     \seq_set_from_clist:Nn 
       \l__databar_uppermultibarlabels_seq { #1 }
   },
  upper-multi-bar-labels .code:n =
   {
     \seq_set_from_clist:Nn 
       \l__databar_uppermultibarlabels_seq { #1 }
   },
%    \end{macrocode}
% Define list of points for $y$ tics:
%    \begin{macrocode}
  yticpoints .code:n = 
   {
     \clist_set:Nn
       \l__databar_yticpoints_clist { #1 }
     \DTLbaryticstrue
     \DTLbaryaxistrue
   },
  y-tick-points .code:n = 
   {
     \clist_set:Nn
       \l__databar_yticpoints_clist { #1 }
     \DTLbaryticstrue
     \DTLbaryaxistrue
   },
%    \end{macrocode}
% Set the $y$ tick gap:
%    \begin{macrocode}
  yticgap .code:n = 
   {
     \tl_set:Nn \l__databar_yticgap_tl { #1 }
     \DTLbaryticstrue
     \DTLbaryaxistrue
   },
  y-tick-gap .code:n = 
   {
     \tl_set:Nn \l__databar_yticgap_tl { #1 }
     \DTLbaryticstrue
     \DTLbaryaxistrue
   },
%    \end{macrocode}
% List of labels for $y$ tics.
%    \begin{macrocode}
  yticlabels .code:n = 
   {
     \seq_set_from_clist:Nn
       \l__databar_yticlabels_seq { #1 }
     \DTLbaryticstrue
     \DTLbaryaxistrue
   },
  y-tick-labels .code:n = 
   {
     \seq_set_from_clist:Nn
       \l__databar_yticlabels_seq { #1 }
     \DTLbaryticstrue
     \DTLbaryaxistrue
   },
%    \end{macrocode}
% $y$ axis label:
%    \begin{macrocode}
  ylabel .tl_set:N = \l__databar_ylabel_tl ,
%    \end{macrocode}
% $y$ axis label position:
% $y$ axis label:
%    \begin{macrocode}
  ylabel-position .choice: ,
  ylabel-position / center .code:n = 
   {
     \tl_set:Nn \l__databar_ylabel_pos_tl
      { \c__databar_ylabel_pos_centred_tl }
   },
  ylabel-position / min .code:n = 
   {
     \tl_set:Nn \l__databar_ylabel_pos_tl
      { \c__databar_ylabel_pos_min_tl }
   },
  ylabel-position / max .code:n = 
   {
     \tl_set:Nn \l__databar_ylabel_pos_tl
      { \c__databar_ylabel_pos_max_tl }
   },
  ylabel-position / zero .code:n = 
   {
     \tl_set:Nn \l__databar_ylabel_pos_tl
      { \c__databar_ylabel_pos_zero_tl }
   },
%    \end{macrocode}
% Vertical bars:
%    \begin{macrocode} 
  vertical .code:n = 
   {
     \DTLverticalbarstrue
   },
%    \end{macrocode}
% Horizontal:
%    \begin{macrocode} 
  horizontal .code:n = 
   {
     \DTLverticalbarsfalse
   },
%    \end{macrocode}
% Option that takes a value:
%    \begin{macrocode} 
  verticalbars .legacy_if_set:n = DTLverticalbars , 
%    \end{macrocode}
% Label alignment:
%    \begin{macrocode} 
  upper-label-align .code:n =
   {
     \tl_if_empty:nTF { #1 }
      {
        \tl_set:Nn \DTLbarXupperlabelalign
         {
           \ifDTLverticalbars
             bottom, center
           \else
            left
           \fi
         }
        \tl_set:Nn \DTLbarXnegupperlabelalign
         {
           \ifDTLverticalbars
            top, center
           \else
            right
           \fi
         }
      }
      {
       \__databar_set_upper_align:w #1 \q_stop
      }
   },
  upper-label-align .default:n = { } ,
  lower-label-style .choice: ,
  lower-label-style / below .code:n =
   {
     \tl_set:Nn \DTLbarXlabelalign
      {
        \ifDTLverticalbars
         left, rotate=-90
        \else
         right 
        \fi
      }
     \tl_set:Nn \DTLbarXneglabelalign
      {
        \ifDTLverticalbars
         left, rotate=-90
        \else
         right 
        \fi
      }
     \tl_set:Nn \l__databar_label_pos_offset_tl
       { \dim_to_decimal:n { -\DTLbarlabeloffset } pt }
     \tl_set:Nn \l__databar_label_neg_offset_tl
       { \dim_to_decimal:n { -\DTLbarlabeloffset } pt }
   } ,
  lower-label-style / above .code:n =
   {
     \tl_set:Nn \DTLbarXlabelalign
      {
        \ifDTLverticalbars
          right, rotate=-90
        \else
          left 
        \fi
      }
    \tl_set:Nn \DTLbarXneglabelalign
      {
        \ifDTLverticalbars
          right, rotate=-90
        \else
          left 
        \fi
      }
     \tl_set:Nn \l__databar_label_pos_offset_tl
       { \dim_to_decimal:n { \DTLbarlabeloffset } pt }
     \tl_set:Nn \l__databar_label_neg_offset_tl
       { \dim_to_decimal:n { \DTLbarlabeloffset } pt }
   } ,
  lower-label-style / same .code:n =
   {
    \tl_set:Nn \DTLbarXlabelalign
     {
       \ifDTLverticalbars
         right, rotate=-90
       \else
         left 
       \fi
     }
    \tl_set:Nn \DTLbarXneglabelalign
     {
       \ifDTLverticalbars
         left, rotate=-90
       \else
         right
       \fi
     }
    \tl_set:Nn \l__databar_label_pos_offset_tl
      { \dim_to_decimal:n { \DTLbarlabeloffset } pt }
    \tl_set:Nn \l__databar_label_neg_offset_tl
      { \dim_to_decimal:n { -\DTLbarlabeloffset } pt }
   } ,
  lower-label-style / opposite .code:n =
   {
    \tl_set:Nn \DTLbarXneglabelalign
     {
       \ifDTLverticalbars
         right, rotate=-90
       \else
         left 
       \fi
     }
    \tl_set:Nn \DTLbarXlabelalign
     {
       \ifDTLverticalbars
         left, rotate=-90
       \else
         right 
       \fi
     }
    \tl_set:Nn \l__databar_label_pos_offset_tl
      { \dim_to_decimal:n { -\DTLbarlabeloffset } pt }
    \tl_set:Nn \l__databar_label_neg_offset_tl
      { \dim_to_decimal:n { \DTLbarlabeloffset } pt }
   } ,
%    \end{macrocode}
% Maximum value of the $y$ axis:
%    \begin{macrocode} 
  max .code:n =
   {
     \__databar_set_max:n { #1 }
   } ,
  max .default:n = { } ,
%    \end{macrocode}
% The total length of the bar chart
%    \begin{macrocode} 
  length .dim_set:N = \DTLbarchartlength,
%    \end{macrocode}
% The maximum depth (negative extent):
%    \begin{macrocode}
  maxdepth .code:n = 
   {
     \__databar_set_max_depth:n { #1 }
   },
  maxdepth .default:n = { } ,
  max-depth .code:n = 
   {
     \__databar_set_max_depth:n { #1 }
   },
  max-depth .default:n = { } ,
%    \end{macrocode}
% Determine which axes should be shown:
%    \begin{macrocode} 
  axes .choice: ,
  axes .default:n = { both } ,
  axes / both .code:n =
   {
     \DTLbarxaxistrue
     \DTLbaryaxistrue
     \DTLbaryticstrue
   },
  axes / x .code:n =
   {
     \DTLbarxaxistrue
     \DTLbaryaxisfalse
     \DTLbaryticsfalse
   },
  axes / y .code:n = 
   {
     \DTLbarxaxisfalse
     \DTLbaryaxistrue
     \DTLbaryticstrue
   },
  axes / none .code:n =
   {
     \DTLbarxaxisfalse
     \DTLbaryaxisfalse
     \DTLbaryticsfalse
   },
  yaxis .legacy_if_set:n = DTLbaryaxis,
  y-axis .legacy_if_set:n = DTLbaryaxis,
  y-ticks .choice:   ,
  y-ticks / true .code:n = 
   {
     \DTLbaryaxistrue
     \DTLbaryticstrue
   },
  y-ticks / false .code:n = 
   {
     \DTLbaryticsfalse
   },
  y-ticks .default:n = true,
%    \end{macrocode}
% Synonym:
%    \begin{macrocode}
  ytics .choice:   ,
  ytics / true .code:n = 
   {
     \DTLbaryaxistrue
     \DTLbaryticstrue
   },
  ytics / false .code:n = 
   {
     \DTLbaryticsfalse
   },
  ytics .default:n = true,
%    \end{macrocode}
% Axis style:
%    \begin{macrocode} 
  x-axis-style .tl_set:N = \DTLBarXAxisStyle ,
  y-axis-style .tl_set:N = \DTLBarYAxisStyle ,
%    \end{macrocode}
% Bar colours:
%    \begin{macrocode} 
  color-style .choice: ,
  color-style / cycle .code:n =
   {
     \tl_set:Nn \l__databar_color_max_index_tl
      { \l__databar_max_colors_int }
   },
  color-style / single .code:n =
   {
     \tl_set:Nn \l__databar_color_max_index_tl
      { \c_one_int }
   },
  color-style / default .code:n =
   {
     \tl_set:Nn \l__databar_color_max_index_tl
      { \c_zero_int }
   },
  bar-colors .code:n = 
   {
     \seq_set_from_clist:Nn \l__datatool_tmp_seq { #1 }
     \seq_map_indexed_function:NN
       \l__datatool_tmp_seq
       \DTLsetbarcolor
   },
  bar-default-colors .code:n =
   {
     \DTLcolorbarcharttrue
     \DTLsetbarcolor{1}{red}
     \DTLsetbarcolor{2}{green}
     \DTLsetbarcolor{3}{blue}
     \DTLsetbarcolor{4}{yellow}
     \DTLsetbarcolor{5}{magenta}
     \DTLsetbarcolor{6}{cyan}
     \DTLsetbarcolor{7}{orange}
     \DTLsetbarcolor{8}{white}
   },
  bar-default-colors .value_forbidden:n = true,
  bar-default-gray .code:n =
   {
     \DTLcolorbarchartfalse
     \DTLsetbarcolor{1}{black!15}
     \DTLsetbarcolor{2}{black!25}
     \DTLsetbarcolor{3}{black!35}
     \DTLsetbarcolor{4}{black!45}
     \DTLsetbarcolor{5}{black!55}
     \DTLsetbarcolor{6}{black!65}
     \DTLsetbarcolor{7}{black!75}
     \DTLsetbarcolor{8}{black!85}
   },
  bar-default-gray .value_forbidden:n = true,
%    \end{macrocode}
% Colours for bars with negative values.
%    \begin{macrocode}
  negative-bar-colors .code:n = 
   {
     \seq_set_from_clist:Nn \l__datatool_tmp_seq { #1 }
     \seq_map_indexed_function:NN
       \l__datatool_tmp_seq
       \DTLsetnegbarcolor
   },
  negative-color-style .choice: ,
  negative-color-style / cycle .code:n =
   {
     \tl_set:Nn \l__databar_neg_color_max_index_tl
      { \l__databar_max_neg_colors_int }
   },
  negative-color-style / single .code:n =
   {
     \tl_set:Nn \l__databar_neg_color_max_index_tl
      { \c_one_int }
   },
%    \end{macrocode}
% Default negative-color-style matches color-style.
%    \begin{macrocode}
  negative-color-style / default .code:n =
   {
     \tl_set:Nn \l__databar_neg_color_max_index_tl
      { \l__databar_color_max_index_tl }
   },
%    \end{macrocode}
% Bar outline colour:
%    \begin{macrocode} 
  outline-color .tl_set:N = \DTLbaroutlinecolor,
  outline-width .dim_set:N = \DTLbaroutlinewidth,
%    \end{macrocode}
% Distance from the bar to the bar label:
%    \begin{macrocode} 
  label-offset .dim_set:N = \DTLbarlabeloffset ,
%    \end{macrocode}
% Distance from the outer edge of the bar to the upper bar label:
%    \begin{macrocode} 
  upper-label-offset .tl_set:N = \l__databar_upper_label_offset_tl ,
%    \end{macrocode}
% group label alignment:
%    \begin{macrocode} 
  group-label-align .tl_set:N = \DTLbargrouplabelalign ,
%    \end{macrocode}
% $y$ tick label alignment:
%    \begin{macrocode} 
  y-tick-label-align .tl_set:N = \DTLbarYticklabelalign ,
  y-tic-label-align .tl_set:N = \DTLbarYticklabelalign ,
  ytic-label-align .tl_set:N = \DTLbarYticklabelalign ,
%    \end{macrocode}
% Initialisation code:
%    \begin{macrocode} 
  init .tl_set:N = \l__databar_init_tl ,
  pre-init .tl_set:N = \l__databar_pre_init_tl ,
  pre-init .groups:n = { pre-parse },
%    \end{macrocode}
% Filter:
%    \begin{macrocode} 
  include-if .cs_set:Np = \__databar_filter:T #1,
  include-if-fn .code:n =
   {
     \cs_set_eq:NN \__databar_filter:T #1
   },
%    \end{macrocode}
% Deprecated experimental setting.
% TODO remove
%    \begin{macrocode} 
  condition .code:n =
   {
     \PackageWarning{databar}{Deprecated ~ option ~ `condition'. ~
      Use ~ `include-if' ~ instead}
     \cs_set:Npn = \__databar_filter:T ##1 { #1 }
   }
}
%    \end{macrocode}
% Allow these keys to be set in \verb|\DTLsetup{bar={...}}|
%    \begin{macrocode}
\keys_define:nn { datatool }
{
  bar .code:n = { \keys_set:nn { datatool/bar } { #1 } }
}
%    \end{macrocode}
%
%Provide a filter function:
%    \begin{macrocode}
\cs_new:Npn \__databar_filter:T #1 { #1 }
%    \end{macrocode}
%
%\begin{macro}{\@dtl@bar}
%Current filtered row number.
%\changes{3.0}{2025-03-03}{changed to \cs{\_\_databar\_index\_int}}
%Version 3.0: replaced \cs{@dtl@bar} with:
%    \begin{macrocode}
\int_new:N \l__databar_index_int
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new:Npn \__databar_filtered_map:nn #1 #2
{
  \int_zero:N \l__databar_index_int
  \DTLmapdata
  {
    \DTLmapgetvalues { #1 }
    \__databar_filter:T 
     { 
       \int_incr:N \l__databar_index_int
       #2
     }
  }
}
%    \end{macrocode}
%
% Boolean to check the current chart type.
%    \begin{macrocode}
\bool_new:N \l_databar_multi_bars_bool
\bool_set_false:N \l_databar_multi_bars_bool
%    \end{macrocode}
%
%\begin{macro}{\DTLbarchart}
%\begin{definition}
% \cs{DTLbarchart}\oarg{conditions}\marg{option list}\marg{db name}\marg{assign list}
%\end{definition}
% Make a bar chart from data given in data base \meta{db name},
% where \meta{assign list} is a comma-separated list of
% \meta{cmd}\texttt{=}\meta{key} pairs. \meta{option list} must
% include \texttt{variable}\texttt{=}\meta{cmd}, where \meta{cmd}
% is included in \meta{assign list}. The optional argument
% \meta{conditions} is the same as that for \cs{DTLforeach}.
%    \begin{macrocode}
\NewDocumentCommand \DTLbarchart { o m m m }
{
  \group_begin:
   \bool_set_false:N \l_databar_multi_bars_bool
   \tl_set:Nn \DTLbargroupindex { 0 }
   \IfValueT { #1 }
   {
     \cs_set:Npn \__databar_filter:T ##1
     {
       \ifthenelse { #1 } { ##1 } { }
     }
   }
   \tl_if_blank:nF { #3 }
    {
      \DTLsetup { default-name = { #3 } }
    }
   \keys_set_groups:nnn { datatool/bar } { pre-parse } { #2 }
   \l__databar_pre_init_tl
   \keys_set:nn { datatool/bar } { #2 }
   \l__databar_init_tl
   \cs_set_eq:NN \__databar_do:n \use:n
   \tl_if_empty:NT \DTLbarvariable
   {
     \cs_set_eq:NN \__databar_do:n \use_none:n
     \seq_if_empty:NTF \l__databar_variables_seq
     {
       \PackageError { databar }
       {
         \token_to_str:N \DTLbarchart \c_space_tl ~ missing ~ variable
       }
       {
         You ~ need ~ to ~ set ~ the ~ `variable' ~ 
         key ~ to ~ the ~ placeholder ~ command
       }
     }
     {
       \PackageError { databar }
       {
         \token_to_str:N \DTLbarchart \c_space_tl ~ missing ~ variable
       }
       {
         Remember ~ that ~ you ~ need ~ to ~ use ~ `variable' ~ 
         not ~ `variables' ~ with ~ \token_to_str:N \DTLbarchart
       }
     }
   }
   \tl_if_empty:NF \l__databar_yticgap_tl
    {
      \fp_set:Nn \l__databar_yticgap_fp
        { \l__databar_yticgap_tl }
      \fp_compare:nNnT { \l__databar_yticgap_fp } < { \c_zero_fp }
        {
          \PackageError { databar }
          {
            y-tick ~ gap ~ `\tl_to_str:o { \l__databar_yticgap_tl } ' ~
            is ~ negative
          }
          {
            The ~ y ~ tick ~ gap ~ specification ~ evaluates ~ to ~
            a ~ negative ~ number ~ ( \fp_to_decimal:N \l__databar_yticgap_fp) ~
            so ~ the ~ yticgap ~ setting ~ will ~ be ~ ignored
          }
          \tl_clear:N \l__databar_yticgap_tl
          \fp_zero:N \l__databar_yticgap_fp
        }
    }
   \__databar_do:n
   {
     \__databar_do_chart:n { #4 }
   }
  \group_end:
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new:Nn \__databar_do_chart:n 
{
%    \end{macrocode}
% Clear the underlying placeholder command used to reference the
% variable.
%    \begin{macrocode}
 \exp_args:NV \tl_clear:N \DTLbarvariable
 \tl_clear:N \DTLtotalbars
 \__datatool_calc_bar_lengths:n { #1 }
%    \end{macrocode}
%Calculate the total number of bars if not already found.
%    \begin{macrocode}
 \tl_if_empty:NT \DTLtotalbars
  { 
    \__databar_filtered_map:nn { #1 } { }
    \tl_set:NV \DTLtotalbars \l__databar_index_int
  }
%    \end{macrocode}
%A bar width is one unit so the chart
%width is the number of bars.
%    \begin{macrocode}
 \tl_set_eq:NN \DTLbarchartwidth \DTLtotalbars
%    \end{macrocode}
%Add on the inter bar gaps:
%    \begin{macrocode}
 \tl_if_eq:NnF \l__databar_bargap_tl { 0 }
  {
    \tl_set:Nx \DTLbarchartwidth 
     { 
       \fp_eval:n
       {
         \DTLbarchartwidth
           + \l__databar_bargap_tl 
            * (\DTLtotalbars - \c_one_fp )
       }
     }
  }
 \__databar_calc_scale:
 \__databar_construct_ytics:
%    \end{macrocode}
% Draw the chart.
%    \begin{macrocode}
 \__databar_chart:n { #1 }
}
%    \end{macrocode}
%
% Action `bar chart':
%    \begin{macrocode}
\cs_new:cn { __datatool_action_ bar ~ chart : }
{
 \group_begin:
   \tl_if_empty:NF \l__datatool_action_name_tl
    {
      \tl_set_eq:NN
        \l__datatool_default_dbname_tl
        \l__datatool_action_name_tl
    }
   \bool_set_false:N \l_databar_multi_bars_bool
   \cs_set_eq:NN \__databar_do:n \use:n
   \tl_set:Nn \DTLbargroupindex { 0 }
%    \end{macrocode}
% If the `key' or `column' has been set, then that provides the variable,
% unless it's overridden by the variable setting in the options.
%    \begin{macrocode}
   \__datatool_optional_key_xor_column_get_key:nTF
    {
%    \end{macrocode}
% No key or column. The variable will need to be provided in the
% options list instead (or may have already been set with
% \cs{DTLsetup}).
%    \begin{macrocode}
    }
    {
%    \end{macrocode}
% Key or column provided.
%    \begin{macrocode}
      \tl_set:Nn \DTLbarvariable
        { \l__databar_action_variable_tl }
      \clist_put_right:Nx \l__datatool_action_assign_clist
       { 
         \exp_not:N \l__databar_action_variable_tl =
          \l__datatool_action_key_tl 
       }
    }
    {
%    \end{macrocode}
% Invalid syntax.
%    \begin{macrocode}
      \cs_set_eq:NN \__databar_do:n \cs_none:n
    }
  \__databar_do:n
   {
%    \end{macrocode}
% Parse options if set.
%    \begin{macrocode}
     \clist_if_empty:NTF \l__datatool_action_options_clist
      {
        \l__databar_pre_init_tl
      }
      { 
        \keys_set_groups:nnV { datatool/bar } { pre-parse }
          \l__datatool_action_options_clist
        \l__databar_pre_init_tl
        \keys_set:nV { datatool/bar } 
          \l__datatool_action_options_clist
      }
     \l__databar_init_tl
%    \end{macrocode}
% Check variable has been set.
%    \begin{macrocode}
     \tl_if_empty:NT \DTLbarvariable
     {
       \cs_set_eq:NN \__databar_do:n \use_none:n
       \seq_if_empty:NTF \l__databar_variables_seq
       {
        \__datatool_action_error:nnn { databar }
         {
           \token_to_str:N \DTLbarchart \c_space_tl ~ missing ~ variable
         }
         {
           You ~ need ~ to ~ either ~ use ~ `key' ~ or ~ `column' ~
           to ~ identify ~ the ~ variable ~ column ~ or ~ set ~ 
           the ~ `variable' ~ key ~ to ~ the ~ placeholder ~ 
           command ~ within ~ `options'
         }
       }
       {
        \__datatool_action_error:nnn { databar }
         {
           missing ~ variable ~ (either ~ use ~ `key' ~ or ~ `column' ~
           to ~ identify ~ the ~ variable ~ column ~ or ~ set ~ 
           the ~ `variable' ~ key ~ to ~ the ~ placeholder ~ 
           command ~ within ~ `options')
         }
         {
           Remember ~ that ~ you ~ need ~ to ~ use ~ `variable' ~ 
           not ~ `variables' ~ for ~ a ~ single ~ variable ~ bar ~ chart
         }
       }
     }
    \tl_if_empty:NF \l__databar_yticgap_tl
     {
       \fp_set:Nn \l__databar_yticgap_fp
        { \l__databar_yticgap_tl }
       \fp_compare:nNnT { \l__databar_yticgap_fp } < { \c_zero_fp }
        {
         \__datatool_action_error:nnn { databar }
          {
            y-tick ~ gap ~ `\tl_to_str:o { \l__databar_yticgap_tl } ' ~
            is ~ negative
          }
          {
            The ~ y ~ tick ~ gap ~ specification ~ evaluates ~ to ~
            a ~ negative ~ number ~ ( \fp_to_decimal:N \l__databar_yticgap_fp) ~
            so ~ the ~ yticgap ~ setting ~ will ~ be ~ ignored
          }
          \tl_clear:N \l__databar_yticgap_tl
          \fp_zero:N \l__databar_yticgap_fp
        }
     }
    \__databar_do:n
     {
       \exp_args:NV \__databar_do_chart:n 
        \l__datatool_action_assign_clist
     }
   }
 \group_end:
}
%    \end{macrocode}
%Scratch placeholder for action if key or column given instead of
%variable:
%    \begin{macrocode}
\tl_new:N \l__databar_action_variable_tl
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__databar_single_update:
{
  \exp_args:NNV \tl_set_eq:NN
   \l__dataplot_y_tl \DTLbarvariable
  \__databar_update_bounds:
}
\cs_new:Nn \__databar_do_update:
{ 
  \__databar_single_update:
}
\cs_new:Nn \__databar_multi_variables_update:
{
  \seq_map_inline:Nn \l__databar_variables_seq
   {
     \tl_set_eq:NN \l__dataplot_y_tl ##1
      \__databar_update_bounds:
   }
}
\cs_new:Nn \__databar_multi_columns_update:
{
  \seq_map_inline:Nn \l__datatool_action_columns_seq
   {
     \exp_args:NNno
       \dtl@getentryfromrow
       \l__dataplot_y_tl
       { ##1 }
       \l__datatool_map_data_row_tl
     \__databar_update_bounds:
   }
}
%    \end{macrocode}
%
% Compute the bar heights unless \cs{DTLbarmax} has been set
% and negative extent unless \cs{DTLnegextent} has been set.
% This needs to take into account number formatting.
%    \begin{macrocode}
\cs_new:Nn \__datatool_calc_bar_lengths:n
{
  \bool_lazy_or:nnTF
     { \tl_if_empty_p:N \DTLbarmax }
     { \tl_if_empty_p:N \DTLnegextent }
   {
     \fp_set_eq:NN \l__dataplot_max_y_fp \c_minus_inf_fp
     \fp_zero:N \l__dataplot_min_y_fp
     \__databar_filtered_map:nn { #1 } 
      {
        \__databar_do_update:
      }
     \tl_set:NV \DTLtotalbars \l__databar_index_int
     \tl_if_empty:NTF \DTLbarmax
      {
        \fp_compare:nNnTF
           { \l__dataplot_max_y_fp } > { \c_minus_inf_fp }
         {
           \tl_set:Nx \DTLbarmax
             { \fp_to_decimal:N \l__dataplot_max_y_fp }
         }
         {
           \PackageError { databar }
            {
               Can't ~ determine ~ maximum ~ extent. ~
               Check ~ data ~ and ~ filtering
            }
            {
              Either ~ there's ~ no ~ numeric ~ data ~ provided ~ in ~
              the ~ column ~ referenced ~ by ~ \tl_to_str:V \DTLbarvariable
              \c_space_tl ~ or ~ all ~ rows ~ have ~ been ~ filtered 
            }
           \tl_set:Nn \DTLbarmax { 1 }
         }
        \tl_if_empty:NF \l__databar_yticgap_tl
         {
           \fp_set_eq:NN \l__dataplot_y_fp \l__databar_yticgap_fp
           \fp_while_do:nNnn { \l__dataplot_y_fp } < { \l__dataplot_max_y_fp }
            {
              \fp_add:Nn \l__dataplot_y_fp { \l__databar_yticgap_fp }
            }
           \tl_set:Nx \DTLbarmax { \fp_to_decimal:N \l__dataplot_y_fp }
         }
      }
      {
        \fp_set:Nn \l__dataplot_max_y_fp { \DTLbarmax }
      }
     \tl_if_empty:NTF \DTLnegextent
      {
        \fp_compare:nNnTF
           { \l__dataplot_min_y_fp } < { \c_zero_fp }
         {
           \tl_set:Nx \DTLnegextent
             { \fp_to_decimal:N \l__dataplot_min_y_fp }
           \tl_if_empty:NF \l__databar_yticgap_tl
            {
              \fp_set:Nn \l__dataplot_y_fp { - \l__databar_yticgap_fp }
              \fp_while_do:nNnn { \l__dataplot_y_fp } > { \l__dataplot_min_y_fp }
               {
                 \fp_sub:Nn \l__dataplot_y_fp { \l__databar_yticgap_fp }
               }
              \tl_set:Nx \DTLnegextent { \fp_to_decimal:N \l__dataplot_y_fp }
            }
         }
         {
           \tl_set:Nn \DTLnegextent { 0 }
         }
      }
      {
        \fp_set:Nn \l__dataplot_min_y_fp { \DTLnegextent }
      }
   }
   {
     \tl_if_empty:NF \DTLbarmax
      {
        \fp_set:Nn \l__dataplot_max_y_fp { \DTLbarmax }
      }
     \tl_if_empty:NF \DTLnegextent
      {
        \fp_set:Nn \l__dataplot_min_y_fp { \DTLnegextent }
      }
   }
}
%    \end{macrocode}
%Update max and min values. The current value
%from the database column should be in \cs{l\_\_dataplot\_y\_tl}.
%    \begin{macrocode}
\cs_new:Nn \__databar_update_bounds:
{
  \tl_if_empty:NF \l__dataplot_y_tl
   {
     \datatool_set_fp:Nn \l__dataplot_y_fp
       { \l__dataplot_y_tl } 
     \tl_if_empty:NT \DTLbarmax
      {
       \fp_compare:nNnT
         { \l__dataplot_y_fp }
           >
         { \l__dataplot_max_y_fp }
        {
         \fp_set_eq:NN
            \l__dataplot_max_y_fp
            \l__dataplot_y_fp 
        }
      }
     \tl_if_empty:NT \DTLnegextent
      {
       \fp_compare:nNnT
         { \l__dataplot_y_fp }
          <
         { \l__dataplot_min_y_fp }
        {
         \fp_set_eq:NN
            \l__dataplot_min_y_fp
            \l__dataplot_y_fp 
        }
      }
   }
}
%    \end{macrocode}
%Calculate the scaling factor.
%    \begin{macrocode}
\cs_new:Nn \__databar_calc_scale:
{
  \fp_set:Nn \l__databar_extent_fp
   { \DTLbarmax - \DTLnegextent }
  \fp_set:Nn \l__databar_unit_fp
   { 
     \dim_to_decimal_in_sp:n { \DTLbarchartlength }
       / \l__databar_extent_fp
   }
  \dim_set:Nn \l__databar_miny_dim
   { 
     \fp_eval:n
      {
        \DTLnegextent * \l__databar_unit_fp
      }
      sp
   }
  \dim_set:Nn \l__databar_maxy_dim
   { 
     \fp_eval:n
      {
        \DTLbarmax * \l__databar_unit_fp
      }
      sp
   }
}
%    \end{macrocode}
% Construct $y$ tick list if required.
%    \begin{macrocode}
\cs_new:Nn \__databar_construct_ytics:
{
  \dim_zero:N \l__dataplot_y_tic_label_width_dim
  \ifDTLbarytics
    \clist_if_empty:NTF \l__databar_yticpoints_clist
     {
       \tl_if_empty:NTF \l__databar_yticgap_tl
        {
          \fp_set:Nn \l__databar_min_gap_fp
           {
             \dim_to_decimal_in_sp:n { \DTLmintickgap }
              / \l__databar_unit_fp
           }
          \__dataplot_construct_tick_list:NNNN
           \l__dataplot_min_y_fp
           \l__dataplot_max_y_fp
           \l__databar_min_gap_fp
           \l__databar_yticpoints_seq
        }
        {
          \__dataplot_construct_tick_list_with_gap_excl:NNNN
           \l__dataplot_min_y_fp
           \l__dataplot_max_y_fp
           \l__databar_yticpoints_seq
           \l__databar_yticgap_fp
        }
     }
     {
       \__dataplot_to_fp_seq:NNnn
        \l__databar_yticpoints_seq
        \l__databar_y_tic_clist
        \l__dataplot_min_y_fp
        \l__dataplot_max_y_fp 
     }
    \seq_if_empty:NT \l__databar_yticlabels_seq
      {
        \seq_map_inline:Nn \l__databar_yticpoints_seq
         {
           \tl_set:Nn \l__dataplot_y_fp { ##1 }
           \__dataplot_get_default_tic_label:Nx
             \l__dataplot_y_fp { \l__databar_ytic_round_tl }
           \seq_put_right:Nx \l__databar_yticlabels_seq
             { 
               \exp_not:V \l__dataplot_tic_label_tl
             }
         }
      }
  \fi
}
%    \end{macrocode}
%
% Do the bar chart
%    \begin{macrocode}
\cs_new:Nn \__databar_chart:n
{
  \begin{tikzpicture}
%    \end{macrocode}
% Set unit vectors
%    \begin{macrocode}
  \ifDTLverticalbars
    \pgfsetyvec
     {
       \pgfpoint 
        { 0pt } { \fp_to_int:N \l__databar_unit_fp ~ sp }
     }
    \pgfsetxvec
     {
       \pgfpoint { \DTLbarwidth } { 0pt }
     }
  \else
    \pgfsetxvec
     {
       \pgfpoint 
        { \fp_to_int:N \l__databar_unit_fp ~ sp } { 0pt }
     }
    \pgfsetyvec
     {
       \pgfpoint { 0pt } { \DTLbarwidth }
     }
  \fi
%    \end{macrocode}
% Begin hook
%    \begin{macrocode}
  \DTLbaratbegintikz
%    \end{macrocode}
% Initialise the start point. This is the $y$ co-ordinate for the
% first corner of the bar along the $y$ axis. The end point is the
% start point plus one unit (which will be the same as the bar index).
%    \begin{macrocode}
  \fp_zero:N \l__databar_start_fp
%    \end{macrocode}
% Iterate through data
%    \begin{macrocode}
  \__databar_filtered_map:nn { #1 } 
   {
%    \end{macrocode}
% The bar index is the same as the value of
% \cs{l\_\_databar\_index\_int}:
%    \begin{macrocode}
     \tl_set:Nx \DTLbarindex { \int_use:N \l__databar_index_int }
%    \end{macrocode}
% Draw the current bar.
%    \begin{macrocode}
     \__databar_draw_bar:
   }
%    \end{macrocode}
% Clear content token list variable.
%    \begin{macrocode}
  \tl_clear:N \l__dataplot_content_tl
%    \end{macrocode}
% Add axes drawing code to content:
%    \begin{macrocode}
  \__databar_draw_axes:
%    \end{macrocode}
% Add $y$ tick marks if required
%    \begin{macrocode}
  \seq_map_indexed_function:NN
    \l__databar_yticpoints_seq
    \__databar_draw_y_tics_fn:nn
%    \end{macrocode}
% Do any pending content and clear token list variable.
%    \begin{macrocode}
  \l__dataplot_content_tl
  \tl_clear:N \l__dataplot_content_tl
%    \end{macrocode}
% Add the $y$ label if required
%    \begin{macrocode}
  \__databar_plot_ylabel:
%    \end{macrocode}
% Do any pending content and clear token list variable.
%    \begin{macrocode}
  \l__dataplot_content_tl
  \tl_clear:N \l__dataplot_content_tl
%    \end{macrocode}
% End hook
%    \begin{macrocode}
  \DTLbaratendtikz
  \end{tikzpicture}
}
%    \end{macrocode}
% Draw bar:
%    \begin{macrocode}
\cs_new:Nn \__databar_draw_bar:
{
%    \end{macrocode}
% \cs{DTLbarvariable} should be defined to the actual placeholder
% command.
%    \begin{macrocode}
  \__databar_draw_bar:NVxx \DTLbarvariable
    \l__databar_index_int 
    {
      \tl_if_empty:NF \l__databar_barlabel_tl 
       {
         \exp_not:N \DTLdisplaylowerbarlabel 
          { \exp_not:V \l__databar_barlabel_tl }
       }
    }
    {
      \tl_if_empty:NF \l__databar_upperbarlabel_tl
       {
         \exp_not:N \DTLdisplayupperbarlabel
          { \exp_not:V \l__databar_upperbarlabel_tl }
       }
    }
%    \end{macrocode}
% Update start.
%    \begin{macrocode}
  \fp_add:Nn \l__databar_start_fp { \c_one_fp }
}
%    \end{macrocode}
%
%\cs{\_\_databar\_draw\_bar:Nnnn}\meta{tl-var}\marg{bar index}\marg{lower
%label}\marg{upper label}
% Draw bar for the given variable. The starting point
% \cs{l\_\_databar\_start\_int} should already be set.
%For multi bar charts, the bar index is the index within the group
%(that is, it's reset to 1 at the start of each group).
%    \begin{macrocode}
\cs_new:Nn \__databar_draw_bar:Nnnn 
{
%    \end{macrocode}
% Add the inter bar gap if this isn't the first bar.
%    \begin{macrocode}
  \tl_if_eq:NnF \l__databar_bargap_tl { 0 }
   {
     \int_compare:nNnT
       { #2 } > { \c_one_int }
      {
        \fp_add:Nn \l__databar_start_fp
         { \l__databar_bargap_tl }
      }
   }
  \tl_set:Nx \l__databar_start_tl
    { \fp_to_decimal:N \l__databar_start_fp }
%    \end{macrocode}
% Content token list variable is reset for each bar to allow the
% hook to access the current bar settings.
%    \begin{macrocode}
  \tl_clear:N \l__dataplot_content_tl
%    \end{macrocode}
% Get the variable. The original variable data is stored in
% \cs{l\_\_dataplot\_y\_tl} but this will be a formatted number that
% needs converting to a decimal. This is stored twice: as a floating
% point variable \cs{l\_\_dataplot\_y\_fp} to avoid repeated parsing
% where calculations are required, and as a token list variable
% \cs{l\_\_dataplot\_decimal\_y\_tl} where the value needs expanding
% for the \sty{pgf} parser.
%    \begin{macrocode}
  \tl_set_eq:NN \l__dataplot_y_tl #1
  \tl_if_empty:NTF \l__dataplot_y_tl
   {
%    \end{macrocode}
% Missing data for this row so assume 0.
%    \begin{macrocode}
     \fp_zero:N \l__dataplot_y_fp
     \tl_set:Nn \l__dataplot_decimal_y_tl { 0 }
     \tl_set:Nn \DTLbarvalue
       { \__datatool_datum:nnnn { 0 } { 0 } { } { \c_datatool_decimal_int } }
   }
   {
     \datatool_set_fp:NV \l__dataplot_y_fp
        \l__dataplot_y_tl 
     \tl_set:Nx \l__dataplot_decimal_y_tl
       { \fp_to_tl:N \l__dataplot_y_fp }
%    \end{macrocode}
% Set \cs{DTLbarvalue} to the rounded formatted value for use in the
% labels or hook.
%    \begin{macrocode}
     \tl_set:Nx \l__datatool_result_tl
      {
        \fp_to_decimal:n
         { round ( \l__dataplot_y_fp, \c@DTLbarroundvar) }
      }
     \datatool_pad_trailing_zeros:Nn 
      \l__datatool_result_tl \c@DTLbarroundvar
     \exp_args:NV \DTLdecimaltolocale
       \l__datatool_result_tl \l__datatool_tmpa_tl
     \tl_set:Nx \DTLbarvalue
       { 
         \exp_not:N \__datatool_datum:nnnn 
         { \exp_not:V \l__datatool_tmpa_tl }
         { \exp_not:V \l__datatool_result_tl }
         { }
         { \exp_not:N \c_datatool_decimal_int }
       }
   }
%    \end{macrocode}
% Mid point of bar (along the $y$ axis).
%    \begin{macrocode}
  \tl_set:Nx \l__databar_mid_tl
    { \fp_eval:n { \l__databar_start_fp + 0.5 } }
  \dim_set_eq:NN \l__databar_mid_dim \DTLbarwidth 
  \dim_set:Nn \l__databar_mid_dim
    { 
      \l__databar_mid_tl \DTLbarwidth
    } 
  \dim_set:Nn \l__databar_extent_dim
    {
      \fp_eval:n
      {
        \l__dataplot_y_fp * \l__databar_unit_fp 
      }
      sp
    }
%    \end{macrocode}
% End point.
%    \begin{macrocode}
  \tl_set:Nx \l__databar_end_tl
    { \fp_eval:n { \l__databar_start_fp + \c_one_fp } }
%    \end{macrocode}
% Set placeholder commands \cs{DTLstartpt}, \cs{DTLmidpt} and \cs{DTLendpt}.
% These are the start, middle and end of the bar along the bar's mid
% axis.
%    \begin{macrocode}
  \ifDTLverticalbars
   \tl_set:Nx \DTLstartpt
     { 
       \exp_not:N \pgfpointxy
         { \l__databar_mid_tl } { 0 }
     }
    \tl_set:Nx \DTLmidpt
     { 
       \exp_not:N \pgfpointxy
         { \l__databar_mid_tl }
         { \fp_to_decimal:n { 0.5 \l__dataplot_y_fp } }
     }
    \tl_set:Nx \DTLendpt
     { 
       \exp_not:N \pgfpointxy
         { \l__databar_mid_tl }
         { \l__dataplot_decimal_y_tl }
     } 
  \else
     \tl_set:Nx \DTLstartpt
       { 
         \exp_not:N \pgfpointxy
         { 0 } { \l__databar_mid_tl }
       } 
    \tl_set:Nx \DTLmidpt
     { 
       \exp_not:N \pgfpointxy
         { \fp_to_decimal:n { 0.5 \l__dataplot_y_fp } }
         { \l__databar_mid_tl }
     }
    \tl_set:Nx \DTLendpt
     { 
       \exp_not:N \pgfpointxy
         { \l__dataplot_decimal_y_tl }
         { \l__databar_mid_tl }
     } 
  \fi
%    \end{macrocode}
% Draw bar.
%    \begin{macrocode}
  \tl_put_right:Nn \l__dataplot_content_tl
     {
       \begin{scope}
     }
%    \end{macrocode}
% Every pre bar hook
%    \begin{macrocode}
  \tl_put_right:Nn \l__dataplot_content_tl
    { \DTLeveryprebarhook }
  \fp_compare:nNnTF { \l__dataplot_y_fp } < { \c_zero_fp }
    {
      \tl_set:Nx \l__datatool_tmpa_tl
       {
         \DTLgetnegbarcolor { #2 } 
       }
    }
    {
      \tl_set:Nx \l__datatool_tmpa_tl
       {
         \DTLgetbarcolor { #2 } 
       }
    }
  \tl_put_right:Nn \l__dataplot_content_tl
   {
     \path ~ [
   }
  \tl_if_empty:NF \l__datatool_tmpa_tl
    {
      \tl_put_right:Nx \l__dataplot_content_tl
      {
         fill = \l__datatool_tmpa_tl ,
      }
    }
  \bool_lazy_and:nnT
   {
     \tl_if_empty_p:N \DTLbaroutlinecolor
   }
   {
     \dim_compare_p:nNn
       { \DTLbaroutlinewidth } > { \c_zero_dim }
   }
   {
     \tl_put_right:Nx \l__dataplot_content_tl
     {
        draw = \DTLbaroutlinecolor ,
        line ~ width = \exp_not:N \DTLbaroutlinewidth ,
     }
   }
  \tl_put_right:Nx \l__dataplot_content_tl
   {
     \DTLBarStyle ]
   }
  \ifDTLverticalbars
    \tl_put_right:Nx \l__dataplot_content_tl
     {
       ( \l__databar_start_tl , ~ 0) ~
        -- ~
       (
         \l__databar_start_tl , ~
         \l__dataplot_decimal_y_tl
       ) ~
        -- ~
       ( 
         \l__databar_end_tl, ~
         \l__dataplot_decimal_y_tl
       ) ~
       -- ~ 
       ( \l__databar_end_tl , ~ 0)
     }
  \else
    \tl_put_right:Nx
      \l__dataplot_content_tl
      {
        ( 0 , ~ \l__databar_start_tl ) ~
         -- ~
        (
          \l__dataplot_decimal_y_tl , ~
          \l__databar_start_tl
        ) ~
         -- ~
        ( 
          \l__dataplot_decimal_y_tl , ~
          \l__databar_end_tl
        ) ~
        -- ~ 
        ( 0 , ~ \l__databar_end_tl )
      }
  \fi
  \tl_put_right:Nn \l__dataplot_content_tl
   {
     -- ~ cycle; ~
     \end{scope}
   }
%    \end{macrocode}
% Draw lower bar label.
%    \begin{macrocode}
   \fp_compare:nNnTF { \l__dataplot_y_fp } < { \c_zero_fp }
    {
      \tl_set_eq:NN \l__databar_label_current_offset_tl
        \l__databar_label_neg_offset_tl
      \tl_set:Nx \l__databar_label_current_align_tl
        { \DTLbarXneglabelalign }
    }
    {
      \tl_set_eq:NN \l__databar_label_current_offset_tl
        \l__databar_label_pos_offset_tl
      \tl_set:Nx \l__databar_label_current_align_tl
        { \DTLbarXlabelalign }
    }
    \tl_if_empty:nF { #3 }
    {
     \tl_put_right:Nn \l__dataplot_content_tl
      {
        \pgftext [ at =
      }
     \ifDTLverticalbars
      \tl_put_right:Nx \l__dataplot_content_tl
       {
         {
            \exp_not:N \pgfpoint
              { \l__databar_mid_dim }
              { \l__databar_label_current_offset_tl }
         }
       }
     \else
      \tl_put_right:Nx \l__dataplot_content_tl
       {
         {
            \exp_not:N \pgfpoint
              { \l__databar_label_current_offset_tl }
              { \l__databar_mid_dim }
         }
       }
     \fi
     \tl_put_right:Nx \l__dataplot_content_tl
      {
         , \exp_not:V \l__databar_label_current_align_tl
        ]
      }
     \tl_put_right:Nn \l__dataplot_content_tl
      {
        { #3 }
      }
     \bool_lazy_and:nnT
       { \bool_if_p:N \l_databar_multi_bars_bool }
       { ! \tl_if_empty_p:N \l__databar_barlabel_tl }
      {
        \tl_put_right:Nn \l__dataplot_content_tl
         { \__databar_update_group_offset: }
      }
    }
%    \end{macrocode}
% Draw upper bar label
%    \begin{macrocode}
   \tl_if_empty:nF { #4 }
    {
     \tl_put_right:Nn \l__dataplot_content_tl
      {
        \pgftext [ at =
      }
     \ifDTLverticalbars
%    \end{macrocode}
% Vertical bars.
%    \begin{macrocode}
       \fp_compare:nNnTF
          { \l__dataplot_y_fp } < { \c_zero_fp }
        {
          \tl_put_right:Nx \l__dataplot_content_tl
           {
             {
                \exp_not:N \pgfpoint
                 { \l__databar_mid_dim }
                 { 
                   \dim_eval:n
                    {
                      \l__databar_extent_dim
                      + \l__databar_upper_label_neg_offset_tl 
                    }
                 }
             }
             , ~ \DTLbarXnegupperlabelalign 
           }
        }
        {
          \tl_put_right:Nx \l__dataplot_content_tl
           {
             {
                \exp_not:N \pgfpoint
                 { \l__databar_mid_dim }
                 { 
                   \dim_eval:n
                    {
                      \l__databar_extent_dim
                      + \l__databar_upper_label_pos_offset_tl 
                    }
                 }
             }
             , ~ \DTLbarXupperlabelalign 
           }
        }
     \else
%    \end{macrocode}
% Horizontal bars.
%    \begin{macrocode}
       \fp_compare:nNnTF
          { \l__dataplot_y_fp } < { \c_zero_fp }
        {
          \tl_put_right:Nx \l__dataplot_content_tl
           {
             {
                \exp_not:N \pgfpoint
                 { 
                   \dim_eval:n
                    {
                      \l__databar_extent_dim
                      + \l__databar_upper_label_neg_offset_tl 
                    }
                 }
                 { \l__databar_mid_dim }
             }
             , ~ \DTLbarXnegupperlabelalign 
           }
        }
        {
          \tl_put_right:Nx \l__dataplot_content_tl
           {
             {
                \exp_not:N \pgfpoint
                 { 
                   \dim_eval:n
                    {
                      \l__databar_extent_dim
                      + \l__databar_upper_label_pos_offset_tl 
                    }
                 }
                 { \l__databar_mid_dim }
             }
             , ~ \DTLbarXupperlabelalign 
           }
        }
     \fi
     \tl_put_right:Nn \l__dataplot_content_tl
      {
        ]
        { #4 }
      }
    }
%    \end{macrocode}
% Every bar hook
%    \begin{macrocode}
  \tl_put_right:Nn \l__dataplot_content_tl
    { \DTLeverybarhook }
%    \end{macrocode}
% Do content and clear token list variable.
%    \begin{macrocode}
  \l__dataplot_content_tl
  \tl_clear:N \l__dataplot_content_tl
}
\cs_generate_variant:Nn \__databar_draw_bar:Nnnn
 { NVxx , NnVV }
%    \end{macrocode}
% Draw axes if applicable.
%    \begin{macrocode}
\cs_new:Nn \__databar_draw_axes:
{
%    \end{macrocode}
% Draw $x$ axis
%    \begin{macrocode}
  \ifDTLbarxaxis
    \tl_put_right:Nx \l__dataplot_content_tl
     {
       \exp_not:N \draw [ \DTLBarXAxisStyle ] ~
       (0,0) ~ -- ~
     }
    \ifDTLverticalbars
      \tl_put_right:Nn \l__dataplot_content_tl
       {
         ( \DTLbarchartwidth, 0 );
       }
    \else
      \tl_put_right:Nn \l__dataplot_content_tl
       {
         ( 0, \DTLbarchartwidth );
       }
    \fi
  \fi
%    \end{macrocode}
% Draw $y$ axis
%    \begin{macrocode}
  \ifDTLbaryaxis
    \tl_put_right:Nx \l__dataplot_content_tl
     {
       \exp_not:N \draw [ \DTLBarYAxisStyle ] ~
     }
    \ifDTLverticalbars
      \tl_put_right:Nn \l__dataplot_content_tl
       {
         ( 0, \DTLnegextent ) ~ -- ~ ( 0, \DTLbarmax );
       }
    \else
      \tl_put_right:Nn \l__dataplot_content_tl
       {
         ( \DTLnegextent, 0) ~ -- ~ ( \DTLbarmax, 0 );
       }
    \fi
  \fi
}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__databar_draw_y_tics_fn:nn
{
  \tl_set:Nn \l__dataplot_y_fp { #2 }
  \tl_set:Nx \l__dataplot_decimal_y_tl
    { \fp_to_decimal:N \l__dataplot_y_fp }
  \dim_set:Nn \l__databar_extent_dim
    {
      \fp_eval:n
      {
        \l__dataplot_y_fp * \l__databar_unit_fp 
      }
      sp
    }
  \ifDTLverticalbars
   \tl_put_right:Nx \l__dataplot_content_tl
    {
      \exp_not:N \pgfpathmoveto
        {
          \exp_not:N \pgfpointxy
            { 0 } { \l__dataplot_decimal_y_tl }
        }
      \exp_not:N \pgfpathlineto
       {
         \exp_not:N \pgfpoint 
          { \dim_to_decimal:n { - \DTLticklength } pt }
          { \l__databar_extent_dim }
       }
    }
  \else
   \tl_put_right:Nx \l__dataplot_content_tl
    {
      \exp_not:N \pgfpathmoveto
        {
          \exp_not:N \pgfpointxy
            { \l__dataplot_decimal_y_tl } { 0 }
        }
      \exp_not:N \pgfpathlineto
       {
         \exp_not:N \pgfpoint 
          { \l__databar_extent_dim }
          { \dim_to_decimal:n { - \DTLticklength } pt }
       }
    }
  \fi
  \tl_put_right:Nn \l__dataplot_content_tl
   {
     \pgfusepath{stroke}
   }
  \int_compare:nNnTF
    { #1 } > { \seq_count:N \l__databar_yticlabels_seq }
   {
     \__dataplot_get_default_tic_label:Nx
      \l__dataplot_y_fp { \l__databar_ytic_round_tl }
   }
   {
     \tl_set:Nx \l__dataplot_tic_label_tl
      { \seq_item:Nn \l__databar_yticlabels_seq { #1 } }
   }
  \tl_put_right:Nx \l__dataplot_content_tl
   {
     \exp_not:N \pgftext [ \DTLbarYticklabelalign , at=
   }
  \ifDTLverticalbars
    \tl_put_right:Nx \l__dataplot_content_tl
     {
       {
         \exp_not:N \pgfpoint
           { \dim_to_decimal:n { -\DTLticklabeloffset } pt }
           { \l__databar_extent_dim }
       }
     }
  \else
    \tl_put_right:Nx \l__dataplot_content_tl
     {
       {
         \exp_not:N \pgfpoint
           { \l__databar_extent_dim }
           { \dim_to_decimal:n { -\DTLticklabeloffset } pt }
       }
     }
  \fi
  \tl_put_right:Nx \l__dataplot_content_tl
   {
     ]
     {
       \exp_not:N \DTLbardisplayYticklabel
         { \exp_not:V \l__dataplot_tic_label_tl }
     }
   }
  \tl_if_empty:NF \l__databar_ylabel_tl 
   {
     \tl_put_right:Nn \l__dataplot_content_tl
      { \__databar_update_ylabel_offset: }
   }
  \l__dataplot_content_tl
  \tl_clear:N \l__dataplot_content_tl
}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__databar_update_ylabel_offset:
{
  \ifDTLverticalbars
   \dim_set:Nn
    \l__dataplot_y_tic_label_width_dim
    {
      \dim_max:nn
       { \l__dataplot_y_tic_label_width_dim }
       { \dim_abs:n { \pgf@pathminx} }
    }
  \else
   \dim_set:Nn
    \l__dataplot_y_tic_label_width_dim
    {
      \dim_max:nn
       { \l__dataplot_y_tic_label_width_dim }
       { \dim_abs:n { \pgf@pathminy} }
    }
  \fi
}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__databar_plot_ylabel:
{
  \tl_if_empty:NF \l__databar_ylabel_tl 
  {
    \dim_add:Nn \l__dataplot_y_tic_label_width_dim
      { \l__databar_ylabel_offset_tl }
    \tl_put_right:Nx \l__dataplot_content_tl
     {
       \exp_not:N \pgftext
        [
          at = { \l__databar_ylabel_pos_tl } , 
          \DTLyAxisLabelStyle
        ]
        { \exp_not:V \l__databar_ylabel_tl }
     }
  }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLmultibarchart}
%\begin{definition}
% \cs{DTLmultibarchart}\oarg{conditions}\marg{option list}\marg{db name}\marg{assign list}
%\end{definition}
% Make a multi-bar chart from data given in data base \meta{db name},
% where \meta{assign list} is a comma-separated list of
% \meta{cmd}\texttt{=}\meta{key} pairs. \meta{option list} must
% include the \texttt{variables} key which must be a comma separated
% list of commands, where each command
% is included in \meta{assign list}. The optional argument
% \meta{conditions} is the same as that for \cs{DTLforeach}.
%    \begin{macrocode}
\NewDocumentCommand \DTLmultibarchart { o m m m }
{
  \group_begin:
   \bool_set_true:N \l_databar_multi_bars_bool
   \IfValueT { #1 }
   {
     \cs_set:Npn \__databar_filter:T ##1
     {
       \ifthenelse { #1 } { ##1 } { }
     }
   }
   \tl_if_blank:nF { #3 }
    {
      \DTLsetup { default-name = { #3 } }
    }
   \keys_set_groups:nnn { datatool/bar } { pre-parse } { #2 }
   \l__databar_pre_init_tl
   \keys_set:nn { datatool/bar } { #2 }
   \l__databar_init_tl
   \cs_set_eq:NN \__databar_do:n \use:n
   \seq_if_empty:NT \l__databar_variables_seq
   {
     \cs_set_eq:NN \__databar_do:n \use_none:n
     \tl_if_empty:NTF \DTLbarvariable
     {
       \PackageError { databar }
       {
         \token_to_str:N \DTLmultibarchart \c_space_tl ~ missing ~ variables
       }
       {
         You ~ need ~ to ~ set ~ the ~ `variables' ~ 
         key ~ to ~ the ~ list ~ of ~ placeholder ~ commands
       }
     }
     {
       \PackageError { databar }
       {
         \token_to_str:N \DTLmultibarchart \c_space_tl ~ missing ~ 
         variables ~ (`variable' ~ setting ~ not ~ applicable ~ 
         for ~ multibar ~ charts)
       }
       {
         Remember ~ that ~ you ~ need ~ to ~ use ~ `variables' ~ 
         not ~ `variable' ~ with ~ \token_to_str:N \DTLmultibarchart
       }
     }
   }
   \tl_if_empty:NF \l__databar_yticgap_tl
    {
      \fp_set:Nn \l__databar_yticgap_fp
        { \l__databar_yticgap_tl }
      \fp_compare:nNnT { \l__databar_yticgap_fp } < { \c_zero_fp }
        {
          \PackageError { databar }
          {
            y-tick ~ gap ~ `\tl_to_str:o { \l__databar_yticgap_tl } ' ~
            is ~ negative
          }
          {
            The ~ y ~ tick ~ gap ~ specification ~ evaluates ~ to ~
            a ~ negative ~ number ~ ( \fp_to_decimal:N \l__databar_yticgap_fp) ~
            so ~ the ~ yticgap ~ setting ~ will ~ be ~ ignored
          }
          \tl_clear:N \l__databar_yticgap_tl
          \fp_zero:N \l__databar_yticgap_fp
        }
    }
   \__databar_do:n
   {
     \cs_set_eq:NN
       \__databar_do_update:
       \__databar_multi_variables_update:
     \cs_set_eq:NN
       \__databar_draw_bars:
       \__databar_variables_draw_bars:
     \tl_set:Nx \DTLbartotalvariables
       { \seq_count:N \l__databar_variables_seq }
     \__databar_do_multi_chart:n { #4 }
   }
  \group_end:
}
%    \end{macrocode}
%\end{macro}
%
% Action `multibar chart':
%    \begin{macrocode}
\cs_new:cn { __datatool_action_ multibar ~ chart : }
{
 \group_begin:
   \tl_if_empty:NF \l__datatool_action_name_tl
    {
      \tl_set_eq:NN
        \l__datatool_default_dbname_tl
        \l__datatool_action_name_tl
    }
   \bool_set_true:N \l_databar_multi_bars_bool
   \cs_set_eq:NN \__databar_do:n \use:n
   \__datatool_optional_columns:
   \clist_if_empty:NTF \l__datatool_action_options_clist
    {
      \l__databar_pre_init_tl
    }
    { 
      \keys_set_groups:nnV { datatool/bar } { pre-parse }
        \l__datatool_action_options_clist
      \l__databar_pre_init_tl
      \keys_set:nV { datatool/bar } 
        \l__datatool_action_options_clist
    }
   \l__databar_init_tl
   \seq_if_empty:NTF \l__datatool_action_columns_seq
    {
      \seq_if_empty:NTF \l__databar_variables_seq
       {
         \__datatool_action_error:nn
          {
            missing ~ variables
          }
          {
            You ~ need ~ to ~ either ~ set ~ the ~ `keys' ~ 
            or ~ `columns' ~ action ~ settings ~ or ~ set ~ 
            `variables' ~ within ~ the ~ `options' ~
            action ~ setting ~ in ~
            \token_to_str:N \DTLaction [...]{ \l__datatool_action_tl }
          }
         \cs_set_eq:NN \__databar_do:n \cs_none:n
       }
       {
         \tl_set:Nx \DTLbartotalvariables
          { \seq_count:N \l__databar_variables_seq }
         \cs_set_eq:NN
           \__databar_do_update:
           \__databar_multi_variables_update:
         \cs_set_eq:NN
           \__databar_draw_bars:
           \__databar_variables_draw_bars:
       }
    }
    {
      \tl_set:Nx \DTLbartotalvariables
       { \seq_count:N \l__datatool_action_columns_seq }
      \cs_set_eq:NN
        \__databar_do_update:
        \__databar_multi_columns_update:
      \cs_set_eq:NN
        \__databar_draw_bars:
        \__databar_columns_draw_bars:
    }
   \__databar_do:n
   {
     \exp_args:NV 
       \__databar_do_multi_chart:n
       \l__datatool_action_assign_clist
   }
 \group_end:
}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__databar_do_multi_chart:n
{
 \tl_clear:N \DTLtotalbars
 \__datatool_calc_bar_lengths:n { #1 }
%    \end{macrocode}
%Calculate the total number of bar groups if not already found.
%    \begin{macrocode}
 \tl_if_empty:NT \DTLtotalbars
  { 
    \__databar_filtered_map:nn { #1 } { }
    \tl_set:NV \DTLtotalbars \l__databar_index_int
  }
 \tl_set_eq:NN \DTLtotalbargroups \DTLtotalbars
%    \end{macrocode}
%Calculate the overall total number of bars.
%    \begin{macrocode}
 \tl_set:Nx \DTLtotalbars
   { \int_eval:n { \DTLtotalbargroups * \DTLbartotalvariables } }
%    \end{macrocode}
%Calculate the bar group width, which needs to take into account the
%bar gap, but first set it to just the total number of bars in a
%group.
%    \begin{macrocode}
 \tl_set:Nx \DTLbargroupwidth 
  { \DTLbartotalvariables }
%    \end{macrocode}
%Add on the inter bar gaps to the group width.
%    \begin{macrocode}
 \tl_if_eq:NnF \l__databar_bargap_tl { 0 }
  {
    \tl_set:Nx \DTLbargroupwidth
     {
       \fp_eval:n 
        {
          \DTLbargroupwidth
            + \l__databar_bargap_tl
               * ( \DTLbargroupwidth - \c_one_fp )
        }
     }
   }
%    \end{macrocode}
%Calculate the bar chart width ($x$-axis length).
%    \begin{macrocode}
 \tl_set:Nx \DTLbarchartwidth
  { 
    \fp_eval:n 
     {
       \DTLtotalbargroups * \DTLbargroupwidth  
       + \l__databar_groupgap_tl
         * ( \DTLtotalbargroups - \c_one_fp )
     }
  }
 \__databar_calc_scale:
 \__databar_construct_ytics:
 \__databar_multi_chart:n { #1 }
}
%    \end{macrocode}
%Code that loops through each variable:
%    \begin{macrocode}
\cs_new:Nn \__databar_draw_bars:
{
  \__databar_variables_draw_bars:
}
%    \end{macrocode}
%Draw bars if variables option used:
%    \begin{macrocode}
\cs_new:Nn \__databar_variables_draw_bars:
{
  \seq_map_indexed_function:NN
    \l__databar_variables_seq
    \__databar_draw_bar:nn
}
%    \end{macrocode}
%Draw bars if columns or keys action setting used:
%    \begin{macrocode}
\cs_new:Nn \__databar_columns_draw_bars:
{
  \seq_map_indexed_inline:Nn
   \l__datatool_action_columns_seq
   {
     \exp_args:NNno
       \dtl@getentryfromrow
       \l__databar_action_variable_tl
       { ##2 }
       \l__datatool_map_data_row_tl
      \__databar_draw_bar:nn
       { ##1 }
       \l__databar_action_variable_tl
   }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__databar_multi_chart:n
{
  \begin{tikzpicture}
%    \end{macrocode}
% Set unit vectors
%    \begin{macrocode}
  \ifDTLverticalbars
    \pgfsetyvec
     {
       \pgfpoint 
        { 0pt } { \fp_to_int:N \l__databar_unit_fp ~ sp }
     }
    \pgfsetxvec
     {
       \pgfpoint { \DTLbarwidth } { 0pt }
     }
  \else
    \pgfsetxvec
     {
       \pgfpoint 
        { \fp_to_int:N \l__databar_unit_fp ~ sp } { 0pt }
     }
    \pgfsetyvec
     {
       \pgfpoint { 0pt } { \DTLbarwidth }
     }
  \fi
%    \end{macrocode}
% Begin hook
%    \begin{macrocode}
  \DTLbaratbegintikz
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
  \fp_zero:N \l__databar_start_fp
%    \end{macrocode}
% Iterate through data
%    \begin{macrocode}
  \__databar_filtered_map:nn { #1 } 
   {
%    \end{macrocode}
% Initialise bar group label offset.
%    \begin{macrocode}
     \dim_zero:N \l__databar_group_label_offset_dim
%    \end{macrocode}
% Draw each bar in the group.
%    \begin{macrocode}
     \__databar_draw_bars:
%    \end{macrocode}
% Do the bar group label, if set.
%    \begin{macrocode}
     \tl_if_empty:NF \l__databar_barlabel_tl
      {
        \dim_add:Nn
          \l__databar_group_label_offset_dim
          { \DTLbarlabeloffset }
        \tl_put_right:Nn \l__dataplot_content_tl
          { \pgftext [ at = \pgfpoint }
        \ifDTLverticalbars
         \tl_put_right:Nx \l__dataplot_content_tl
          {
            {
              \fp_eval:n
               { 
                 \l__databar_start_fp
                    - 0.5 * \DTLbargroupwidth
               }
              \exp_not:N \DTLbarwidth
            }
            {
              - \exp_not:N \l__databar_group_label_offset_dim
            }
          }
        \else
         \tl_put_right:Nx \l__dataplot_content_tl
          {
            {
              - \exp_not:N \l__databar_group_label_offset_dim
            }
            {
              \fp_eval:n
               { 
                 \l__databar_start_fp
                    - 0.5 * \DTLbargroupwidth
               }
              \exp_not:N \DTLbarwidth
            }
          }
        \fi
        \tl_put_right:Nx \l__dataplot_content_tl
          { 
            , ~ \DTLbargrouplabelalign ] ~
             { 
               \exp_not:N \DTLdisplaybargrouplabel
                { \exp_not:V \l__databar_barlabel_tl }
             }
          }
        \l__dataplot_content_tl
        \tl_clear:N \l__dataplot_content_tl
      }
%    \end{macrocode}
% Bar group hook
%    \begin{macrocode}
      \DTLeverybargrouphook
      \fp_add:Nn \l__databar_start_fp { \l__databar_groupgap_tl }
   }
%    \end{macrocode}
% Add axes drawing code to content:
%    \begin{macrocode}
  \__databar_draw_axes:
%    \end{macrocode}
% Add $y$ tick marks if required
%    \begin{macrocode}
  \seq_map_indexed_function:NN
    \l__databar_yticpoints_seq
    \__databar_draw_y_tics_fn:nn
%    \end{macrocode}
% Do any pending content and clear token list variable.
%    \begin{macrocode}
  \l__dataplot_content_tl
  \tl_clear:N \l__dataplot_content_tl
%    \end{macrocode}
% Add the $y$ label if required
%    \begin{macrocode}
  \__databar_plot_ylabel:
%    \end{macrocode}
% Do any pending content and clear token list variable.
%    \begin{macrocode}
  \l__dataplot_content_tl
  \tl_clear:N \l__dataplot_content_tl
%    \end{macrocode}
% End hook
%    \begin{macrocode}
  \DTLbaratendtikz
  \end{tikzpicture}
}
%    \end{macrocode}
%Draw all bars in the current group.
%    \begin{macrocode}
\cs_new:Nn \__databar_draw_bar:nn
{
%    \end{macrocode}
% The bar group index is the same as the value of
% \cs{l\_\_databar\_index\_int}:
%    \begin{macrocode}
  \tl_set:Nx \DTLbargroupindex
    { \int_use:N \l__databar_index_int }
  \tl_set:Nn \DTLbarindex { #1 }
%    \end{macrocode}
% Set the lower label
%    \begin{macrocode}
  \tl_set:Nx \l__databar_lowerbarlabel_tl
   { \seq_item:Nn \l__databar_multibarlabels_seq { #1 } }
  \tl_if_empty:NF \l__databar_lowerbarlabel_tl
   {
     \tl_set:Nx \l__databar_lowerbarlabel_tl
      { 
        \exp_not:N \DTLdisplaylowermultibarlabel
         { \exp_not:V \l__databar_lowerbarlabel_tl }
      }
   }
%    \end{macrocode}
% Set the upper label
%    \begin{macrocode}
  \tl_set:Nx \l__databar_upperbarlabel_tl
   { \seq_item:Nn \l__databar_uppermultibarlabels_seq { #1 } }
  \tl_if_empty:NF \l__databar_upperbarlabel_tl
   {
     \tl_set:Nx \l__databar_upperbarlabel_tl
      { 
        \exp_not:N \DTLdisplayuppermultibarlabel
         { \exp_not:V \l__databar_upperbarlabel_tl }
      }
   }
  \__databar_draw_bar:NnVV #2 { #1 }
    \l__databar_lowerbarlabel_tl
    \l__databar_upperbarlabel_tl
  \fp_add:Nn \l__databar_start_fp { \c_one_fp }
}
%    \end{macrocode}
%Update group offset.
%    \begin{macrocode}
\cs_new:Nn \__databar_update_group_offset:
{
  \ifDTLverticalbars
    \dim_compare:nNnT
      { \pgf@pathminy } < { \c_zero_dim }
     {
       \dim_set:Nn
         \l__databar_group_label_offset_dim
        {
          \dim_max:nn
           { \l__databar_group_label_offset_dim }
           { \dim_abs:n { \pgf@pathminy} }
        }
     }
  \else
    \dim_compare:nNnT
      { \pgf@pathminx } < { \c_zero_dim }
     {
       \dim_set:Nn
         \l__databar_group_label_offset_dim
        {
          \dim_max:nn
           { \l__databar_group_label_offset_dim }
           { \dim_abs:n { \pgf@pathminx} }
        }
     }
  \fi
}
%    \end{macrocode}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</databar.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datapie.sty>
%    \end{macrocode}
%\fi
%\section{datapie.sty}
% Declare package:
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
%    \end{macrocode}
% Rollback releases:
%    \begin{macrocode}
\DeclareRelease{v2.32}{2019-09-27}{datapie-2019-09-27.sty}
\DeclareCurrentRelease{v3.4.1}{2025-04-25}
%    \end{macrocode}
% Declare package:
%    \begin{macrocode}
\ProvidesPackage{datapie}[2025/04/25 v3.4.1 (NLCT)]
%    \end{macrocode}
%\changes{3.0}{2025-03-03}{dropped \sty{xkeyval}}
% Version 3.0: no longer using \sty{xkeyval}.
%
%\begin{macro}{\ifDTLcolorpiechart}
% The conditional \cs{ifDTLcolorpiechart} is to determine 
% whether to use colour or grey scale.
% NB may be removed or deprecated.
%    \begin{macrocode}
\newif\ifDTLcolorpiechart
\DTLcolorpiecharttrue
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\ifDTLrotateinner}
% Define boolean keys to govern label rotations.
%    \begin{macrocode}
\newif\ifDTLrotateinner
\DTLrotateinnerfalse
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\ifDTLrotateouter}
%    \begin{macrocode}
\newif\ifDTLrotateouter
\DTLrotateouterfalse
%    \end{macrocode}
%\end{macro}
%
% The package options have been changed to use \sty{l3keys}.
%
%Define package options:
%    \begin{macrocode}
\ExplSyntaxOn
\keys_define:nn { datatool }
{
  color .legacy_if_set:n = DTLcolorpiechart,
  gray .legacy_if_set_inverse:n = DTLcolorpiechart,
  rotateinner .legacy_if_set:n = DTLrotateinner,
  norotateinner .legacy_if_set_inverse:n = DTLrotateinner,
  rotateouter .legacy_if_set:n = DTLrotateouter,
  norotateouter .legacy_if_set_inverse:n = DTLrotateouter,
}
\ExplSyntaxOff
%    \end{macrocode}
%
% Process options:
%    \begin{macrocode}
\IfPackageLoadedTF{datatool}
{
  \ProcessKeyOptions[datatool]
}
{
  \DeclareOption*{\PassOptionsToPackage{\CurrentOption}{datatool}}
  \ProcessOptions
}
\RequirePackage{datatool}
%    \end{macrocode}
% Remove the package option keys so they can't be used with
% \cs{DTLsetup} (otherwise they may conflict with \sty{databar} etc).
%    \begin{macrocode}
\ExplSyntaxOn
\keys_define:nn { datatool }
{
  color .undefine: ,
  gray .undefine: ,
  rotateinner .undefine: ,
  norotateinner .undefine: ,
  rotateouter .undefine: ,
  norotateouter .undefine: ,
}
\ExplSyntaxOff
%    \end{macrocode}
% Required packages:
%    \begin{macrocode}
\RequirePackage{tikz}
%    \end{macrocode}
% Define some variables that govern the appearance of the pie
% chart.
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%The pie chart variable.
%    \begin{macrocode}
\tl_new:N \DTLpievariable
%    \end{macrocode}
%\begin{macro}{\DTLradius}
% The radius of the pie chart is given by \cs{DTLradius}.
%    \begin{macrocode}
\newlength\DTLradius
\DTLradius=2cm
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLinnerratio}
% The inner label offset ratio is given by \cs{DTLinnerratio}
%    \begin{macrocode}
\newcommand*{\DTLinnerratio}{0.5}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLouterratio}
% The outer label offset ratio is given by \cs{DTLouterratio}.
%    \begin{macrocode}
\newcommand*{\DTLouterratio}{1.25}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLcutawayratio}
% The cutaway offset ratio is given by \cs{DTLcutawayratio}.
%    \begin{macrocode}
\newcommand*\DTLcutawayratio{0.2}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLstartangle}
% The angle of the first segment is given by \cs{DTLstartangle}.
%    \begin{macrocode}
\newcommand*{\DTLstartangle}{0}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@inneroffset}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datapie\_inner\_offset\_dim}}
%Version 3.0: replaced \cs{dtl@inneroffset} with:
%    \begin{macrocode}
\dim_new:N \l__datapie_inner_offset_dim
\dim_set:Nn \l__datapie_inner_offset_dim
 { \DTLinnerratio \DTLradius }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@outeroffset}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datapie\_outer\_offset\_dim}}
%Version 3.0: replaced \cs{dtl@outeroffset} with:
%    \begin{macrocode}
\dim_new:N \l__datapie_outer_offset_dim
\dim_set:Nn \l__datapie_outer_offset_dim
 { \DTLouterratio \DTLradius }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@cutawayoffset}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datapie\_cutaway\_offset\_dim}}
%Version 3.0: replaced \cs{dtl@cutawayoffset} with:
%    \begin{macrocode}
\dim_new:N \l__datapie_cutaway_offset_dim
\dim_set:Nn \l__datapie_cutaway_offset_dim
 { \DTLcutawayratio \DTLradius }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@piecutaways}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datapie\_cutaways\_clist}}
% A comma separated list of segments that
% need to be cut away from the pie chart.
% This now uses a clist variable.
%    \begin{macrocode}
\clist_new:N \l__datapie_cutaways_clist
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@innerlabel}
% \cs{dtl@innerlabel} specifies the label to appear inside
% the segment. By default this is the variable used to create
% the pie chart.
%
% Version 3.0: renamed \cs{l\_\_datatool\_pie\_inner\_label\_tl}
%\changes{3.0}{2025-03-03}{renamed \cs{l\_\_datatool\_pie\_inner\_label\_tl}}
%    \begin{macrocode}
\tl_new:N \l__datapie_inner_label_tl
\tl_set:Nn \l__datapie_inner_label_tl { \DTLpievariable }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@outerlabel}
%\changes{3.0}{2025-03-03}{renamed \cs{l\_\_datatool\_pie\_outer\_label\_tl}}
% Version 3.0: renamed \cs{l\_\_datatool\_pie\_outer\_label\_tl}
%    \begin{macrocode}
\tl_new:N \l__datapie_outer_label_tl
%    \end{macrocode}
%\end{macro}
%\begin{counter}{DTLpieroundvar}
% \ctrfmt{DTLpieroundvar} is a counter governing the number of digits
% to round to (for display).
%    \begin{macrocode}
\newcounter{DTLpieroundvar}
\setcounter{DTLpieroundvar}{1}
%    \end{macrocode}
%\end{counter}
%
%\begin{macro}{\DTLdisplayinnerlabel}
%\begin{definition}
%\cs{DTLdisplayinnerlabel}\marg{label}
%\end{definition}
% This is used to format the inner label. This just does the
% label by default.
%    \begin{macrocode}
\newcommand*{\DTLdisplayinnerlabel}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdisplayouterlabel}
%\begin{definition}
%\cs{DTLdisplayouterlabel}\marg{label}
%\end{definition}
% This is used to format the outer label. This just does the
% label by default.
%    \begin{macrocode}
\newcommand*{\DTLdisplayouterlabel}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
% No-op command:
%    \begin{macrocode}
\cs_new:Nn \__datapie_noop:N 
{
  \PackageError { datapie }
  {
    Can't ~ use ~ \token_to_str:N #1 \c_space_tl ~ outside ~ 
    \token_to_str:N \DTLpiechart
  }
  {
    Certain ~ datapie.sty ~ commands ~ may ~ only ~ be ~ used ~ 
    within ~ pie ~ chart ~ hooks ~ or ~ option ~ values
  }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLpiepercent}
%\cs{DTLpiepercent}
% returns the percentage value of the current segment.
%    \begin{macrocode}
\newcommand*{\DTLpiepercent}{%
  \__datapie_noop:N \DTLpiepercent
}
\cs_new:Nn \__datapie_percent:
{
  \use:c
   {
     dtl@piepercent@
     \romannumeral \l__datapie_segment_int
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLpieatsegment}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLpieatsegment}\marg{segment index}\marg{total}\marg{start
%angle}\marg{mid angle}\marg{end angle}\marg{shift angle}\marg{shift
%offset}\marg{inner offset}\marg{outer offset}
%\end{definition}
% Hook at each segment.
%    \begin{macrocode}
\newcommand*{\DTLpieatsegment}[9]{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLpieatbegintikz}
% \cs{DTLpieatbegintikz} specifies any commands
% to apply at the start of the \env{tikzpicture} environment. 
% By default it does nothing.
%    \begin{macrocode}
\newcommand*{\DTLpieatbegintikz}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLpieatendtikz}
% \cs{DTLpieatendtikz} specifies any commands
% to apply at the end of the \env{tikzpicture} environment. 
% By default it does nothing.
%    \begin{macrocode}
\newcommand*{\DTLpieatendtikz}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLsetpiesegmentcolor}
%\begin{definition}
% \cs{DTLsetpiesegmentcolor}\marg{n}\marg{color}
%\end{definition}
% Assign colour name \meta{color} to the \meta{n}th segment.
%    \begin{macrocode}
\newcommand*{\DTLsetpiesegmentcolor}[2]{%
 \tl_set:cn { dtlpie@segcol\romannumeral#1 } { #2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLgetpiesegmentcolor}
%\begin{definition}
%\cs{DTLgetpiesegmentcolor}\marg{n}
%\end{definition}
% Get the colour specification for segment \meta{n}
%    \begin{macrocode}
\newcommand*{\DTLgetpiesegmentcolor}[1]{%
 \cs_if_exist_use:cF { dtlpie@segcol\romannumeral#1 } { white }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLdopiesegmentcolor}
%\begin{definition}
%\cs{DTLdopiesegmentcolor}\marg{n}
%\end{definition}
% Set the colour to that for segment \meta{n}
%    \begin{macrocode}
\newcommand*{\DTLdopiesegmentcolor}[1]{
 \cs_if_exist:cTF { dtlpie@segcol\romannumeral#1 }
 {
   \exp_args:Nc \color { dtlpie@segcol\romannumeral#1 }
 }
 {
   \PackageWarning{datapie}{No ~ colour ~ assigned ~ to ~ segment ~ \number#1}
 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLdocurrentpiesegmentcolor}
%\cs{DTLdocurrentpiesegmentcolor}
% sets the colour to that of the current segment. Allow it to also
% be used within \cs{DTLmapdata} or \cs{DTLforeach}.
%    \begin{macrocode}
\NewDocumentCommand \DTLdocurrentpiesegmentcolor { }
{
  \int_compare:nNnTF
    { \l__datapie_segment_int } > { \c_zero_int }
  {
    \exp_args:NV \DTLdopiesegmentcolor \l__datapie_segment_int
  }
  {
    \int_compare:nNnTF
      { \l__datatool_row_idx_int } > { \c_zero_int }
    {
      \exp_args:NV \DTLdopiesegmentcolor \l__datatool_row_idx_int
    }
    {
      \int_compare:nNnTF
        { \dtlforeachlevel } > { \c_zero_int }
      {
        \exp_args:Nv \DTLdopiesegmentcolor { c@DTLrow\romannumeral\dtlforeachlevel }
      }
      {
         \__datapie_noop:N \DTLdocurrentpiesegmentcolor
      }
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLpieoutlinecolor}
%\cs{DTLpieoutlinecolor} specifies what colour to draw the
% outline.
%    \begin{macrocode}
\newcommand*{\DTLpieoutlinecolor}{black}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLpieoutlinewidth}
%\cs{DTLpieoutlinewidth} specifies the line width of the outline:
% Outline is only drawn if the linewidth is greater than 0pt.
%    \begin{macrocode}
\newlength\DTLpieoutlinewidth
\DTLpieoutlinewidth=0pt
%    \end{macrocode}
%\end{macro}
%
% Set the default colours. If there are more than eight segments,
% more colours will need to be defined.
%    \begin{macrocode}
\ifDTLcolorpiechart
 \DTLsetpiesegmentcolor{1}{red}
 \DTLsetpiesegmentcolor{2}{green}
 \DTLsetpiesegmentcolor{3}{blue}
 \DTLsetpiesegmentcolor{4}{yellow}
 \DTLsetpiesegmentcolor{5}{magenta}
 \DTLsetpiesegmentcolor{6}{cyan}
 \DTLsetpiesegmentcolor{7}{orange}
 \DTLsetpiesegmentcolor{8}{white}
\else
 \DTLsetpiesegmentcolor{1}{black!15}
 \DTLsetpiesegmentcolor{2}{black!25}
 \DTLsetpiesegmentcolor{3}{black!35}
 \DTLsetpiesegmentcolor{4}{black!45}
 \DTLsetpiesegmentcolor{5}{black!55}
 \DTLsetpiesegmentcolor{6}{black!65}
 \DTLsetpiesegmentcolor{7}{black!75}
 \DTLsetpiesegmentcolor{8}{black!85}
\fi
%    \end{macrocode}
%
%Provide key=value for "pie" option in \cs{DTLsetup}
%    \begin{macrocode}
\keys_define:nn { datatool/pie }
{
  rotateinner .legacy_if_set:n = DTLrotateinner,
  rotate-inner .legacy_if_set:n = DTLrotateinner,
  rotateouter .legacy_if_set:n = DTLrotateouter,
  rotate-outer .legacy_if_set:n = DTLrotateouter,
%    \end{macrocode}
% Inner ratio:
%    \begin{macrocode}
  innerratio .code:n =
   {
     \tl_set:Nn \DTLinnerratio { #1 }
     \dim_set:Nn \l__datapie_inner_offset_dim
       { \DTLinnerratio \DTLradius }
   },
  inner-ratio .code:n =
   {
     \tl_set:Nn \DTLinnerratio { #1 }
     \dim_set:Nn \l__datapie_inner_offset_dim
       { \DTLinnerratio \DTLradius }
   },
%    \end{macrocode}
% Outer ratio:
%    \begin{macrocode}
  outerratio .code:n =
   {
     \tl_set:Nn \DTLouterratio { #1 }
     \dim_set:Nn \l__datapie_outer_offset_dim
       { \DTLouterratio \DTLradius }
   },
  outer-ratio .code:n =
   {
     \tl_set:Nn \DTLouterratio { #1 }
     \dim_set:Nn \l__datapie_outer_offset_dim
      { \DTLouterratio \DTLradius }
   },
%    \end{macrocode}
% Cutaway ratio:
%    \begin{macrocode}
  cutawayratio .code:n =
   {
     \tl_set:Nn \DTLcutawayratio { #1 }
     \dim_set:Nn \l__datapie_cutaway_offset_dim
       { \DTLcutawayratio \DTLradius }
   },
  cutaway-ratio .code:n =
   {
     \tl_set:Nn \DTLcutawayratio { #1 }
     \dim_set:Nn \l__datapie_cutaway_offset_dim
       { \DTLcutawayratio \DTLradius }
   },
%    \end{macrocode}
% Sets the inner offset as an absolute value (not dependent
% on the radius):
%    \begin{macrocode}
  inneroffset .dim_set:N = \l__datapie_inner_offset_dim ,
  inner-offset .dim_set:N = \l__datapie_inner_offset_dim ,
%    \end{macrocode}
% Sets the outer offset as an absolute value (not dependent
% on the radius):
%    \begin{macrocode}
  outeroffset .dim_set:N = \l__datapie_outer_offset_dim ,
  outer-offset .dim_set:N = \l__datapie_outer_offset_dim ,
%    \end{macrocode}
% Sets the cutaway offset as an absolute value (not dependent
% on the radius):
%    \begin{macrocode}
  cutawayoffset .dim_set:N = \l__datapie_cutaway_offset_dim ,
  cutaway-offset .dim_set:N = \l__datapie_cutaway_offset_dim ,
%    \end{macrocode}
% Starting angle:
%    \begin{macrocode}
  start-angle .tl_set:N = \DTLstartangle,
  start .tl_set:N = \DTLstartangle,
%    \end{macrocode}
% Set the radius of the pie chart.
%    \begin{macrocode}
  radius .code:n =
   {
     \dim_set:Nn \DTLradius { #1 }
     \dim_set:Nn \l__datapie_inner_offset_dim
       { \DTLinnerratio \DTLradius }
     \dim_set:Nn \l__datapie_outer_offset_dim
       { \DTLouterratio \DTLradius }
     \dim_set:Nn \l__datapie_cutaway_offset_dim
       { \DTLcutawayratio \DTLradius }
   },
%    \end{macrocode}
% Set the radius of the pie chart without adjusting the offsets.
%    \begin{macrocode}
  radius* .dim_set:N = \DTLradius ,
%    \end{macrocode}
% Rounding:
%    \begin{macrocode}
  round .int_set:N = \c@DTLpieroundvar,
  segment-colors .code:n = 
   {
     \seq_set_from_clist:Nn \l__datatool_tmp_seq { #1 }
     \seq_map_indexed_function:NN 
       \l__datatool_tmp_seq
       \DTLsetpiesegmentcolor
   },
  segment-default-colors .code:n = 
   {
     \DTLcolorpiecharttrue
     \DTLsetpiesegmentcolor{1}{red}
     \DTLsetpiesegmentcolor{2}{green}
     \DTLsetpiesegmentcolor{3}{blue}
     \DTLsetpiesegmentcolor{4}{yellow}
     \DTLsetpiesegmentcolor{5}{magenta}
     \DTLsetpiesegmentcolor{6}{cyan}
     \DTLsetpiesegmentcolor{7}{orange}
     \DTLsetpiesegmentcolor{8}{white}
   },
  segment-default-colors .value_forbidden:n = true, 
  segment-default-gray .code:n = 
   {
     \DTLcolorpiechartfalse
     \DTLsetpiesegmentcolor{1}{black!15}
     \DTLsetpiesegmentcolor{2}{black!25}
     \DTLsetpiesegmentcolor{3}{black!35}
     \DTLsetpiesegmentcolor{4}{black!45}
     \DTLsetpiesegmentcolor{5}{black!55}
     \DTLsetpiesegmentcolor{6}{black!65}
     \DTLsetpiesegmentcolor{7}{black!75}
     \DTLsetpiesegmentcolor{8}{black!85}
   },
  segment-default-gray .value_forbidden:n = true, 
  outline-color .tl_set:N = \DTLpieoutlinecolor,
  outline-width .dim_set:N = \DTLpieoutlinewidth,
%    \end{macrocode}
% List of cut away segments.
%    \begin{macrocode}
  cutaway .clist_set:N = \l__datapie_cutaways_clist,
%    \end{macrocode}
% Variable used to create the pie chart. (Must be a control
% sequence.)
%    \begin{macrocode}
  variable .tl_set:N = \DTLpievariable,
%    \end{macrocode}
% Inner label:
%    \begin{macrocode}
  innerlabel .tl_set:N = \l__datapie_inner_label_tl,
  inner-label .tl_set:N = \l__datapie_inner_label_tl,
%    \end{macrocode}
% Outer label:
%    \begin{macrocode}
  outerlabel .tl_set:N = \l__datapie_outer_label_tl,
  outer-label .tl_set:N = \l__datapie_outer_label_tl,
%    \end{macrocode}
% Filter:
%    \begin{macrocode}
  include-if .cs_set:Np = \__datapie_filter:T #1,
  include-if-fn .code:n =
   {
     \cs_set_eq:NN \__datapie_filter:T #1
   },
%    \end{macrocode}
% Deprecated experimental setting.
% TODO remove
%    \begin{macrocode} 
  condition .code:n =
   {
     \PackageWarning{datapie}{Deprecated ~ option ~ `condition'. ~
      Use ~ `include-if' ~ instead}
     \cs_set:Nn = \__datapie_filter:T { #1 }
   }
}
%    \end{macrocode}
% Allow these keys to be set in \verb|\DTLsetup{pie={...}}|
%    \begin{macrocode}
\keys_define:nn { datatool }
{
  pie .code:n = { \keys_set:nn { datatool/pie } { #1 } }
}
%    \end{macrocode}
%
%Provide a filter function:
%    \begin{macrocode}
\cs_new:Nn \__datapie_filter:T { #1 }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__datapie_filtered_map:nn 
{
  \DTLmapdata
  {
    \DTLmapgetvalues { #1 }
    \__datapie_filter:T { #2 }
  }
}
%    \end{macrocode}
%Floating point variables.
%    \begin{macrocode}
\fp_new:N \l__datapie_x_fp
\fp_new:N \l__datapie_total_fp
\fp_new:N \l__datapie_start_fp
\fp_new:N \l__datapie_end_fp
\fp_new:N \l__datapie_extent_fp
\fp_new:N \l__datapie_angle_fp
\fp_new:N \l__datapie_mid_fp
%    \end{macrocode}
%Constants:
%    \begin{macrocode}
\fp_const:Nn \c_datapie_whole_circle_fp { 360 }
\fp_const:Nn \c_datapie_half_circle_fp { 180 }
\fp_const:Nn \c_datapie_minus_half_circle_fp { -180 }
\fp_const:Nn \c_datapie_quarter_circle_fp { 90 }
\fp_const:Nn \c_datapie_three_quarter_circle_fp { 270 }
\fp_const:Nn \c_datapie_minus_quarter_circle_fp { -90 }
%    \end{macrocode}
%
% Define integer variable to keep track of segments
%\begin{macro}{\@dtl@seg}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datapie\_segment\_int}}
%Version 3.0 replaced \cs{@dtl@seg} with:
%    \begin{macrocode}
\int_new:N \l__datapie_segment_int
%    \end{macrocode}
%\end{macro}
%
%Token lists containing values to write to content token list.
%\begin{macro}{\@dtl@start}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datapie\_start\_tl}}
%Version 3.0: replaced \cs{@dtl@start} with:
%    \begin{macrocode}
\tl_new:N \l__datapie_start_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@midangle}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datapie\_mid\_tl}}
%Version 3.0: replaced \cs{dtl@midangle} with:
%    \begin{macrocode}
\tl_new:N \l__datapie_mid_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@endangle}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datapie\_end\_tl}}
%Version 3.0: replaced \cs{dtl@endangle} with:
%    \begin{macrocode}
\tl_new:N \l__datapie_end_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@extent}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datapie\_extent\_tl}}
%Version 3.0: replaced \cs{dtl@extent} with:
%    \begin{macrocode}
\tl_new:N \l__datapie_extent_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@angle}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datapie\_angle\_tl}}
%Version 3.0: replaced \cs{dtl@angle} with:
%    \begin{macrocode}
\tl_new:N \l__datapie_angle_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@cutlen}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datapie\_cut\_length\_tl}}
%Version 3.0: replaced \cs{dtl@cutlen} with:
%    \begin{macrocode}
\tl_new:N \l__datapie_cut_length_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@innernodeopt}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datapie\_inner\_node\_opt\_tl}}
%Version 3.0: replaced \cs{dtl@innernodeopt} with:
%    \begin{macrocode}
\tl_new:N \l__datapie_inner_node_opt_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@outernodeopt}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_datapie\_outer\_node\_opt\_tl}}
%Version 3.0: replaced \cs{dtl@outernodeopt} with:
%    \begin{macrocode}
\tl_new:N \l__datapie_outer_node_opt_tl
%    \end{macrocode}
%\end{macro}
%
%Calculate the total (filtered column sum).
%    \begin{macrocode}
\cs_new:Nn \__datapie_compute_total:n 
 {
   \fp_zero:N \l__datapie_total_fp
   \__datapie_filtered_map:nn { #1 }
    {
      \datatool_set_fp:NV \l__datapie_x_fp \DTLpievariable
      \fp_add:Nn \l__datapie_total_fp { \l__datapie_x_fp }
    }
 }
%    \end{macrocode}
%Calculate the angles for each segment.
%    \begin{macrocode}
\cs_new:Nn \__datapie_compute_angles:n 
 {
   \datatool_set_fp:NV \l__datapie_start_fp \DTLstartangle
   \tl_set:Nn \l__datapie_start_tl
     { \fp_to_decimal:N \l__datapie_start_fp }
   \int_zero:N \l__datapie_segment_int
   \__datapie_filtered_map:nn { #1 }
    {
      \int_incr:N \l__datapie_segment_int
      \datatool_set_fp:NV
        \l__datapie_x_fp \DTLpievariable
      \__datapie_compute_angles:nN
        { \l__datapie_segment_int }
        \l__datapie_x_fp 
      \fp_set:Nn \l__datapie_x_fp
       { 
         round
         (
           ( 100 * \l__datapie_x_fp ) / \l__datapie_total_fp,
           \c@DTLpieroundvar
         )
       }
      \tl_set:cx
       {
         dtl@piepercent@
         \romannumeral \l__datapie_segment_int
       }
       { \fp_to_decimal:N \l__datapie_x_fp }
     \datatool_pad_trailing_zeros:cn
       { 
         dtl@piepercent@
         \romannumeral \l__datapie_segment_int
       }
       { \c@DTLpieroundvar }
    }
 }
%    \end{macrocode}
%Compute angles for segment.
%Syntax: \marg{segment index}\meta{x-var} 
%    \begin{macrocode}
\cs_new:Nn \__datapie_compute_angles:nN 
 {
   \fp_compare:nNnTF
      { \l__datapie_start_fp } > { \c_datapie_half_circle_fp }
    {
%    \end{macrocode}
% if $\mathrm{startangle} > 180$
%    \begin{macrocode}
      \fp_sub:Nn \l__datapie_start_fp
        { \c_datapie_whole_circle_fp }
    }
    {
      \fp_compare:nNnT
        { \l__datapie_start_fp } < { \c_datapie_minus_half_circle_fp }
      {
%    \end{macrocode}
% if $\mathrm{startangle} < -180$
%    \begin{macrocode}
        \fp_add:Nn \l__datapie_start_fp
          { \c_datapie_whole_circle_fp }
      }
    }
   \tl_set:cx
     { dtl@sang@ \romannumeral #1 }
     { \fp_to_decimal:N \l__datapie_start_fp }
   \fp_set:Nn \l__datapie_tmpa_fp
     { ( #2 * \c_datapie_whole_circle_fp ) / \l__datapie_total_fp }
   \tl_set:cx
     { dtl@angle@ \romannumeral #1 }
     { \fp_to_decimal:N \l__datapie_tmpa_fp }
   \fp_add:Nn \l__datapie_start_fp
     { \l__datapie_tmpa_fp }
   \tl_set:cx { dtl@cut@angle@ \romannumeral #1 } { 0 }
   \tl_set:cx { dtl@cut@len@ \romannumeral #1 } { 0cm }
   \tl_set:Nx \l__datapie_start_tl
     { \fp_to_decimal:N \l__datapie_start_fp }
 }
%    \end{macrocode}
%
%The new version of \sty{tikz} doesn't seem to like command names in
%certain contexts, so use a token list variable to build up the
%content of the \env{tikzpicture}.
%    \begin{macrocode}
\tl_new:N \l__datapie_content_tl
\tl_new:N \l__datapie_hook_tl
%    \end{macrocode}
%
%This has only partially been converted to \LaTeX3:
%    \begin{macrocode}
\cs_new:Nn \__datapie_draw:n 
{
%    \end{macrocode}
% Set the starting angle
%    \begin{macrocode}
  \tl_set:Nx \l__datapie_start_tl { \DTLstartangle }
  \int_zero:N \l__datapie_segment_int 
  \int_zero:N \l__datatool_row_idx_int
  \begin{tikzpicture}
  \DTLpieatbegintikz
  \__datapie_filtered_map:nn { #1 }
   {
%    \end{macrocode}
% Clear the content token list used for this segment.
%    \begin{macrocode}
     \tl_clear:N \l__datapie_content_tl
     \int_incr:N \l__datapie_segment_int
%    \end{macrocode}
% Set the start angle.
%    \begin{macrocode}
     \tl_set:Nx \l__datapie_start_tl
      {
        \tl_use:c
          {
            dtl@sang@ \romannumeral \l__datapie_segment_int
          }
      }
     \fp_set:Nn \l__datapie_start_fp
       { \l__datapie_start_tl }
%    \end{macrocode}
% Set the extent
%    \begin{macrocode}
     \tl_set:Nx \l__datapie_extent_tl
       {
         \tl_use:c
           {
             dtl@angle@
             \romannumeral \l__datapie_segment_int
           }
       }
     \fp_set:Nn \l__datapie_extent_fp
       { \l__datapie_extent_tl }
%    \end{macrocode}
% Compute the end angle
%    \begin{macrocode}
     \fp_set:Nn \l__datapie_end_fp
      { \l__datapie_start_fp + \l__datapie_extent_fp } 
     \tl_set:Nx \l__datapie_end_tl
       { \fp_to_decimal:N \l__datapie_end_fp }
%    \end{macrocode}
% Compute the shift.
%    \begin{macrocode}
     \tl_set:Nx \l__datapie_angle_tl
       {
         \tl_use:c
          {
            dtl@cut@angle@
            \romannumeral \l__datapie_segment_int
          }
       }
     \fp_set:Nn \l__datapie_angle_fp
       { \l__datapie_angle_tl }
     \fp_compare:nNnT
       { \l__datapie_angle_fp } > { \c_datapie_half_circle_fp }
      {
        \fp_sub:Nn \l__datapie_angle_fp
          { \c_datapie_whole_circle_fp }
        \tl_set:Nx \l__datapie_angle_tl
          { \fp_to_decimal:N \l__datapie_angle_fp }
      }
     \tl_set:Nx \l__datapie_cut_length_tl
      {
        \tl_use:c
         {
           dtl@cut@len@
           \romannumeral \l__datapie_segment_int
         }
      }
     \tl_put_right:Nn \l__datapie_content_tl
       { \begin { scope} }
     \tl_set:Nx \l__datapie_hook_tl
       { { \l__datapie_angle_tl } { \l__datapie_cut_length_tl } }
     \tl_put_right:Nx \l__datapie_content_tl
       {
         [ shift =
           {
             (
               \l__datapie_angle_tl \c_colon_str
               \l__datapie_cut_length_tl
             )
           } ]
       }
%    \end{macrocode}
% Compute the mid way angle.
%    \begin{macrocode}
     \fp_set:Nn \l__datapie_mid_fp
       { 0.5 * \l__datapie_extent_fp + \l__datapie_start_fp }
     \tl_set:Nx \l__datapie_mid_tl
       { \fp_to_decimal:N \l__datapie_mid_fp }
%    \end{macrocode}
% Draw the segment.
%\changes{2.26}{2016-07-20}{removed truncation (caused minor gap between
%first and last segments)}
%    \begin{macrocode}
     \tl_put_right:Nn \l__datapie_content_tl { \fill }
     \tl_put_right:Nx \l__datapie_content_tl
     { 
       [color= \DTLgetpiesegmentcolor \l__datapie_segment_int ]
        (0,0) ~ -- ~
         ( \l__datapie_start_tl \c_colon_str \DTLradius) ~
        arc
         (
           \l__datapie_start_tl \c_colon_str
           \l__datapie_end_tl \c_colon_str
           \DTLradius
         ) ~
        -- ~ cycle;
     }
%    \end{macrocode}
% Draw the outline if required:
%    \begin{macrocode}
     \dim_compare:nNnT
       { \DTLpieoutlinewidth } > { \c_zero_dim }
      {
        \tl_put_right:Nn \l__datapie_content_tl { \draw }
        \tl_put_right:Nx \l__datapie_content_tl
        {
          [color=\DTLpieoutlinecolor,
              line ~ width=\DTLpieoutlinewidth]
          (0,0) ~ -- ~
            (
              \l__datapie_start_tl \c_colon_str \DTLradius
            ) ~
          arc
           (
             \l__datapie_start_tl \c_colon_str
             \l__datapie_end_tl \c_colon_str
             \DTLradius
           ) ~
          -- ~ cycle;
        }
      }
%    \end{macrocode}
% Continue constructing segment hook:
%    \begin{macrocode}
     \tl_put_left:Nx \l__datapie_hook_tl
      {
         \exp_not:N \DTLpieatsegment 
         { \int_use:N \l__datapie_segment_int }
         { \fp_to_decimal:N \l__datapie_total_fp }
         { \l__datapie_start_tl }
         { \l__datapie_mid_tl }
         { \l__datapie_end_tl }
      }
%    \end{macrocode}
% Determine whether to rotate inner labels
%    \begin{macrocode}
     \legacy_if:nTF { DTLrotateinner }
      {
%    \end{macrocode}
% If the mid way angle is between 90 and 270, the text will look
% upside-down, so adjust accordingly.
%\changes{2.26}{2016-07-20}{replaced \cs{ifthen}}
%    \begin{macrocode}
       \fp_compare:nTF
         {
           \c_datapie_quarter_circle_fp
           < \l__datapie_mid_fp
           < \c_datapie_three_quarter_circle_fp
         }
        {
          \cs_set_eq:NN \__datapie_next:nn \use_i:nn
        }
        {
         \fp_compare:nNnTF
           { \l__datapie_mid_fp }
            < { \c_datapie_minus_quarter_circle_fp }
          {
            \cs_set_eq:NN \__datapie_next:nn \use_i:nn
          }
          {
            \cs_set_eq:NN \__datapie_next:nn \use_ii:nn
          }
        }
       \__datapie_next:nn
        {
         \tl_set:Nx \l__datapie_inner_node_opt_tl
           {
             anchor = east ,
             rotate =
              \fp_eval:n
                { \l__datapie_mid_fp - \c_datapie_half_circle_fp }
           }
        }
        {
          \tl_set:Nx \l__datapie_inner_node_opt_tl
           {
             anchor = west ,
             rotate = \l__datapie_mid_tl
           }
        }
%    \end{macrocode}
% Don't rotate inner labels
%    \begin{macrocode}
      }
      {
        \tl_set:Nn \l__datapie_inner_node_opt_tl
          { anchor = center }
      }
%    \end{macrocode}
% Determine whether to rotate outer labels
%    \begin{macrocode}
     \legacy_if:nTF { DTLrotateouter }
      {
%    \end{macrocode}
% If the mid way angle is between 90 and 270, the text will look
% upside-down, so adjust accordingly.
%    \begin{macrocode}
       \fp_compare:nTF
        {
          \c_datapie_quarter_circle_fp
           < \l__datapie_mid_fp
           < \c_datapie_three_quarter_circle_fp
        }
       {
          \cs_set_eq:NN \__datapie_next:nn \use_i:nn
       }
       {
         \fp_compare:nNnTF
          { \l__datapie_mid_fp } < { \c_datapie_minus_quarter_circle_fp }
          {
            \cs_set_eq:NN \__datapie_next:nn \use_i:nn
          }
          {
            \cs_set_eq:NN \__datapie_next:nn \use_ii:nn
          }
       }
       \__datapie_next:nn 
       {
          \tl_set:Nx \l__datapie_outer_node_opt_tl
           {
             anchor = east ,
             rotate =
              \fp_eval:n
               { \l__datapie_mid_fp - \c_datapie_half_circle_fp }
           }
       }
       {
         \tl_set:Nx \l__datapie_outer_node_opt_tl
          {
            anchor = west ,
            rotate = \l__datapie_mid_tl
          }
       }
      }
      {
%    \end{macrocode}
% Don't rotate outer labels
% If ($\theta>-45$ and $\theta < 45$) or $\theta=45$ or $\theta>315$
%    \begin{macrocode}
        \fp_compare:nNnTF
          { \l__datapie_mid_fp } > { 315 }
        {
          \cs_set_eq:NN \__datapie_next:nn \use_i:nn
        }
        {
          \fp_compare:nTF
            { -45 < \l__datapie_mid_fp <= 45 }
           {
             \cs_set_eq:NN \__datapie_next:nn \use_i:nn
           }
           {
             \cs_set_eq:NN \__datapie_next:nn \use_ii:nn
           }
        }
       \__datapie_next:nn
        {
%    \end{macrocode}
% East quadrant
%    \begin{macrocode}
          \tl_set:Nn \l__datapie_outer_node_opt_tl
           { anchor = west }
        }
        {
          \fp_compare:nTF
            { 45 < \l__datapie_mid_fp <= 135 }
           {
             \cs_set_eq:NN \__datapie_next:nn \use_i:nn
           }
           {
             \cs_set_eq:NN \__datapie_next:nn \use_ii:nn
           }
          \__datapie_next:nn 
           {
%    \end{macrocode}
% North quadrant
%    \begin{macrocode}
             \tl_set:Nn \l__datapie_outer_node_opt_tl
               { anchor = south }
           }
           {
%    \end{macrocode}
%If ($\theta>135$ and $\theta<225$) or $\theta=225$ or $\theta=-135$
%or $\theta<-135$
%    \begin{macrocode}
            \fp_compare:nTF
              { 135 <= \l__datapie_mid_fp <= 225 }
             {
               \cs_set_eq:NN \__datapie_next:nn \use_i:nn
             }
             {
                \fp_compare:nNnTF
                 { \l__datapie_mid_fp } > { -135 }
                  {
                    \cs_set_eq:NN \__datapie_next:nn \use_ii:nn
                  }
                  {
                    \cs_set_eq:NN \__datapie_next:nn \use_i:nn
                  }
                }
             }
           \__datapie_next:nn
           {
%    \end{macrocode}
% West quadrant
%    \begin{macrocode}
             \tl_set:Nn \l__datapie_outer_node_opt_tl
              { anchor = east }
           }
           {
             \tl_set:Nn \l__datapie_outer_node_opt_tl
              { anchor = north }
           }
        }
      }
%    \end{macrocode}
% Inner label drawing code:
%    \begin{macrocode}
     \tl_put_right:Nx \l__datapie_content_tl
     {
       \exp_not:N \draw
       (
         \l__datapie_mid_tl
         \c_colon_str
         \dim_use:N \l__datapie_inner_offset_dim
       ) ~
       node [ \l__datapie_inner_node_opt_tl ] ~
       {
         \exp_not:N \DTLdisplayinnerlabel
          { \exp_not:V \l__datapie_inner_label_tl }
       };
     }
%    \end{macrocode}
% Outer label drawing code:
%    \begin{macrocode}
     \tl_put_right:Nx \l__datapie_content_tl
     {
       \exp_not:N \draw
       ( 
         \l__datapie_mid_tl \c_colon_str
         \dim_use:N \l__datapie_outer_offset_dim
       ) ~
       node [ \l__datapie_outer_node_opt_tl ] ~
       { 
         \exp_not:N \DTLdisplayouterlabel 
          { \exp_not:V \l__datapie_outer_label_tl }
       };
     }
     \tl_put_right:Nn \l__datapie_hook_tl
      {
        { \l__datapie_inner_offset_dim }
        { \l__datapie_outer_offset_dim }
      }
     \tl_put_right:Nn \l__datapie_content_tl
      {
        \l__datapie_hook_tl
        \end { scope }
      }
     \l__datapie_content_tl
   }
   \int_zero:N \l__datapie_segment_int 
   \int_zero:N \l__datatool_row_idx_int
   \DTLpieatendtikz
   \end{tikzpicture}
}
%    \end{macrocode}
%
%\begin{macro}{\DTLpiechart}
%\begin{definition}
% \cs{DTLpiechart}\oarg{conditions}\marg{option list}\marg{db name}\marg{assign list}
%\end{definition}
% Make a pie chart from data given in data base \meta{db name},
% where \meta{assign list} is a comma-separated list of
% \meta{cmd}\texttt{=}\meta{key} pairs. \meta{option list} must
% include \texttt{variable}\texttt{=}\meta{cmd}, where \meta{cmd}
% is included in \meta{assign list}. The optional argument
% \meta{conditions} is the same as that for \cs{DTLforeach}.
%
%Version 3.0 now uses \cs{DTLmapdata}. The condition can now be
%passed as an argument in \meta{option list} but the optional
%argument is retained for backward compatibility. The condition in
%the option list will override the optional argument.
%\changes{2.26}{2016-07-20}{replaced fp commands}
%\changes{3.0}{2025-03-03}{switched to using \cs{DTLmapdata}}
%    \begin{macrocode}
\NewDocumentCommand \DTLpiechart { o m m m }
{
  \group_begin:
   \IfValueT { #1 }
   {
     \cs_set:Nn \__datapie_filter:T 
     {
       \ifthenelse { #1 } { ##1 } { }
     }
   }
   \tl_if_blank:nF { #3 }
    {
      \DTLsetup { default-name = { #3 } }
    }
   \keys_set:nn { datatool/pie } { #2 }
   \tl_if_empty:NTF \DTLpievariable 
   {
     \PackageError { datapie }
     {
       \token_to_str:N \DTLpiechart \c_space_tl ~ missing ~ variable
     }
     {
       You ~ need ~ to ~ set ~ the ~ `variable' ~ key ~ in ~ the ~
       first ~ mandatory ~ argument ~ of ~ 
       \token_to_str:N \DTLpiechart \c_space_tl ~ to ~ the ~
       applicable ~ placeholder ~ command ~ listed ~ in ~ the ~
       final ~ argument
     }
   }
   {
     \__datapie_do_chart:n { #4 }
   }
  \group_end:
}
%    \end{macrocode}
%\end{macro}
%Do the chart. The argument is the assignment list.
%    \begin{macrocode}
\cs_new:Nn \__datapie_do_chart:n
{
%    \end{macrocode}
% Enable commands that may only be used within \cs{DTLpiechart}:
%    \begin{macrocode}
  \cs_set_eq:NN \DTLpiepercent \__datapie_percent:
%    \end{macrocode}
% Compute the total. This needs to take into account number
% formatting.
%\changes{1.01}{2007 Aug 17}{uses \cs{@sDTLforeach} instead of
%\cs{DTLforeach}}
%    \begin{macrocode}
  \__datapie_compute_total:n { #1 }
  \__datapie_compute_angles:n { #1 }
%    \end{macrocode}
% Compute the offsets for each cut away segment
%    \begin{macrocode}
  \clist_map_inline:Nn \l__datapie_cutaways_clist
   {
     \__datapie_set_offset:nw ##1 - \q_stop
   }
  \__datapie_draw:n { #1 }
}
%    \end{macrocode}
%Scratch placeholder for action if key or column given instead of
%variable:
%    \begin{macrocode}
\tl_new:N \l__datapie_action_variable_tl 
%    \end{macrocode}
%   
% Action `pie chart':
%    \begin{macrocode}
\cs_new:cn { __datatool_action_ pie ~ chart : }
{
 \group_begin:
   \tl_if_empty:NF \l__datatool_action_name_tl
    {
      \tl_set_eq:NN
        \l__datatool_default_dbname_tl
        \l__datatool_action_name_tl
    }
   \cs_set_eq:NN \__datapie_do:n \use:n
%    \end{macrocode}
% If the `key' or `column' has been set, then that provides the variable,
% unless it's overridden by the variable setting in the options.
%    \begin{macrocode}
   \__datatool_optional_key_xor_column_get_key:nTF
    {
%    \end{macrocode}
% No key or column. The variable will need to be provided in the
% options list instead (or may have already been set with
% \cs{DTLsetup}).
%    \begin{macrocode}
    }
    {
%    \end{macrocode}
% Key or column provided.
%    \begin{macrocode}
      \tl_set:Nn  \DTLpievariable 
        { \l__datapie_action_variable_tl }
      \clist_put_right:Nx \l__datatool_action_assign_clist
       {
         \exp_not:N \l__datapie_action_variable_tl =
          \l__datatool_action_key_tl
       }
    }
    {
%    \end{macrocode}
% Invalid syntax.
%    \begin{macrocode}
      \cs_set_eq:NN \__datapie_do:n \cs_none:n
    }
   \__datapie_do:n
    {
%    \end{macrocode}
% Parse options if set.
%    \begin{macrocode}
     \clist_if_empty:NF \l__datatool_action_options_clist
      {
        \keys_set:nV { datatool/pie }
          \l__datatool_action_options_clist
      }
%    \end{macrocode}
% Check variable has been set.
%    \begin{macrocode}
     \tl_if_empty:NT \DTLpievariable
      {
        \cs_set_eq:NN \__datapie_do:n \use_none:n
        \__datatool_action_error:nnn { datapie }
         {
           \token_to_str:N \DTLpiechart \c_space_tl ~ missing ~ variable
         }
         {
           You ~ need ~ to ~ either ~ use ~ `key' ~ or ~ `column' ~
           to ~ identify ~ the ~ variable ~ column ~ or ~ set ~
           the ~ `variable' ~ key ~ to ~ the ~ placeholder ~
           command ~ within ~ `options'
         }
       }
      \__datapie_do:n 
       {
         \exp_args:NV \__datapie_do_chart:n
          \l__datatool_action_assign_clist
       }
     }
 \group_end:
}
%    \end{macrocode}
%
% Set the offset angles.
%\begin{macro}{\@dtl@set@off}
%\changes{3.0}{2025-03-03}{replaced with \cs{\_\_datapie\_set\_offset:nw}}
%Version 3.0: replaced \cs{@dtl@set@off} with:
%    \begin{macrocode}
\cs_new:Npn \__datapie_set_offset:nw
 #1 - #2 \q_stop
{
  \tl_if_empty:nTF { #2 }
  {
    \__datapie_set_offset:n { #1 }
  }
  {
    \__datapie_set_offset_range:nw #1 - #2 \q_stop
  }
}
%    \end{macrocode}
%\end{macro}
%
% Set offset for individual segment:
%\begin{macro}{\@@dtl@set@off}
%\changes{2.26}{2016-07-20}{replaced fp commands}
%\changes{3.0}{2025-03-03}{replaced with \cs{\_\_datapie\_set\_offset:n}}
%    \begin{macrocode}
\cs_new:Nn \__datapie_set_offset:n
{
  \tl_set:cx { dtl@cut@angle@ \romannumeral #1 }
   {
     \fp_eval:n
      {
        0.5 * \tl_use:c { dtl@angle@ \romannumeral #1 }
        + \tl_use:c { dtl@sang@ \romannumeral #1 }
      }
   }
  \tl_set:cx
    { dtl@cut@len@ \romannumeral #1 }
    { \dim_use:N \l__datapie_cutaway_offset_dim }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@@dtl@setoffr}
% Set offset for a range of segments.
%\changes{2.26}{2016-07-20}{replaced fp commands}
%\changes{3.0}{2025-03-03}{replaced with \cs{\_\_datapie\_set\_offset\_range:nw}}
%Version 3.0: replaced \cs{@@dtl@setoffr} with:
%    \begin{macrocode}
\cs_new:Npn \__datapie_set_offset_range:nw
 #1 - #2 - \q_stop
{
 \int_compare:nNnTF { #1 } > { #2 }
  {
   \PackageError {datapie}
   {
     Segment ~ ranges ~ must ~ go ~ in ~ ascending ~ order
   }
   {
     Try ~ #2 - #1 ~ instead of ~ #1 - #2
   }
  }
  {
    \fp_zero:N \l__datapie_angle_fp
    \int_step_inline:nnn { #1 } { #2 }
     {
       \fp_add:Nn \l__datapie_angle_fp
        {
          \tl_use:c { dtl@angle@ \romannumeral ##1 }
        }
     }
    \tl_set:Nx \l__datapie_angle_tl
     {
       \fp_eval:n
        {
          0.5 * \l__datapie_angle_fp
          + \tl_use:c { dtl@sang@\romannumeral#1 }
        }
     }
    \int_step_inline:nnn { #1 } { #2 }
     {
       \tl_set_eq:cN { dtl@cut@angle@\romannumeral ##1 }
        \l__datapie_angle_tl
       \tl_set:cx
        { dtl@cut@len@\romannumeral ##1 }
         { \dim_use:N \l__datapie_cutaway_offset_dim }
     }
  }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</datapie.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*dataplot.sty>
%    \end{macrocode}
%\fi
%\section{dataplot.sty}
% Declare package:
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
%    \end{macrocode}
% Rollback releases:
%    \begin{macrocode}
\DeclareRelease{v2.32}{2019-09-27}{dataplot-2019-09-27.sty}
\DeclareCurrentRelease{v3.4.1}{2025-04-25}
%    \end{macrocode}
% Declare package:
%    \begin{macrocode}
\ProvidesPackage{dataplot}[2025/04/25 v3.4.1 (NLCT)]
%    \end{macrocode}
%\changes{3.0}{2025-03-03}{dropped \sty{xkeyval}}
% Version 3.0: no longer using \sty{xkeyval}.
%    \begin{macrocode}
\IfPackageLoadedTF{datatool}
{
  \DeclareOption*{\expandafter\DTLsetup\expandafter{\CurrentOption}}
}
{
  \DeclareOption*{\PassOptionsToPackage{\CurrentOption}{datatool}}
}
\ProcessOptions
%    \end{macrocode}
%
% Required packages
%    \begin{macrocode}
\RequirePackage{datatool}
\RequirePackage{tikz}
%    \end{macrocode}
% Load TikZ plot libraries
%    \begin{macrocode}
\usetikzlibrary{plotmarks}
\usetikzlibrary{plothandlers}
%    \end{macrocode}
% Load calc library
%\changes{2.14}{2013-06-28}{added calc library requirement}
%    \begin{macrocode}
\usetikzlibrary{calc}
%    \end{macrocode}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
%\subsection{Scratch variables.}
%
% List of x keys:
%    \begin{macrocode}
\clist_new:N \l__dataplot_x_keys_clist
\seq_new:N \l__dataplot_x_keys_seq
\int_new:N \l_dataplot_x_key_int
%    \end{macrocode}
% List of y keys:
%    \begin{macrocode}
\seq_new:N \l__dataplot_y_keys_seq
\int_new:N \l_dataplot_y_key_int
%    \end{macrocode}
% Any extra assignments that need to be made in the loop. For
% example, to assist filtering.
%    \begin{macrocode}
\tl_new:N \l__dataplot_extra_assign_tl
%    \end{macrocode}
%
%\begin{macro}{\dtl@xkey}
%\changes{3.0}{2025-03-03}{replaced \cs{dtl@xkey} with
%\cs{l\_\_dataplot\_x\_key\_tl}}
% The database key for the $x$ value.
%Version 3.0 replaced \cs{dtl@xkey} with:
%    \begin{macrocode}
\tl_new:N \l__dataplot_x_key_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@ykey}
%\changes{3.0}{2025-03-03}{replaced \cs{dtl@ykey} with
%\cs{l\_\_dataplot\_y\_key\_tl}}
% The database key for the $y$ value.
%Version 3.0 replaced \cs{dtl@ykey} with:
%    \begin{macrocode}
\tl_new:N \l__dataplot_y_key_tl
%    \end{macrocode}
%\end{macro}
%
% Placeholders for x and y obtained from database:
%\begin{macro}{\dtl@x}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_x\_tl}}
%Version 3.0 replaced \cs{dtl@x} with:
%    \begin{macrocode}
\tl_new:N \l__dataplot_x_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@decx}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_decimal\_x\_tl}}
%Version 3.0 replaced \cs{dtl@decx} with:
%    \begin{macrocode}
\tl_new:N \l__dataplot_decimal_x_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@y}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_y\_tl}}
%Version 3.0 replaced \cs{dtl@y} with:
%    \begin{macrocode}
\tl_new:N \l__dataplot_y_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@decy}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_decimal\_y\_tl}}
%Version 3.0 replaced \cs{dtl@decy} with:
%    \begin{macrocode}
\tl_new:N \l__dataplot_decimal_y_tl
%    \end{macrocode}
%\end{macro}
%Floating point values:
%    \begin{macrocode}
\fp_new:N \l__dataplot_x_fp
\fp_new:N \l__dataplot_y_fp
%    \end{macrocode}
%Plot bounds.
%    \begin{macrocode}
\fp_new:N \l__dataplot_min_x_fp
\fp_new:N \l__dataplot_min_y_fp
\fp_new:N \l__dataplot_max_x_fp
\fp_new:N \l__dataplot_max_y_fp
%    \end{macrocode}
%Support for extending the axes beyond the plot bounds:
%    \begin{macrocode}
\fp_new:N \l__dataplot_extend_min_x_axis_fp
\fp_new:N \l__dataplot_extend_max_x_axis_fp
\fp_new:N \l__dataplot_extend_min_y_axis_fp
\fp_new:N \l__dataplot_extend_max_y_axis_fp
%    \end{macrocode}
%Extended plot bounds.
%    \begin{macrocode}
\fp_new:N \l__dataplot_extended_min_x_fp
\fp_new:N \l__dataplot_extended_min_y_fp
\fp_new:N \l__dataplot_extended_max_x_fp
\fp_new:N \l__dataplot_extended_max_y_fp
%    \end{macrocode}
%Tick gaps:
%    \begin{macrocode}
\fp_const:Nn \c_dataplot_smallest_gap_fp { 0.000001 }
%    \end{macrocode}
%\begin{macro}{\@dtl@neggap}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_neg\_gap\_fp}}
%Version 3.0 replaced \cs{@dtl@neggap} with:
%    \begin{macrocode}
\fp_new:N \l__dataplot_neg_gap_fp
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtl@posgap}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_pos\_gap\_fp}}
%Version 3.0 replaced \cs{@dtl@posgap} with:
%    \begin{macrocode}
\fp_new:N \l__dataplot_pos_gap_fp
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtl@gap}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_gap\_fp}}
%Version 3.0 replaced \cs{@dtl@gap} with:
%    \begin{macrocode}
\fp_new:N \l__dataplot_gap_fp
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\fp_new:N \l__dataplot_min_minor_gap_fp
%    \end{macrocode}
%Scale factors and offsets.
%\begin{macro}{\dtl@scale@x}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_scale\_x\_fp}}
%Version 3.0 replaced \cs{dtl@scale@x} with:
%    \begin{macrocode}
\fp_new:N \l__dataplot_scale_x_fp
\fp_set_eq:NN \l__dataplot_scale_x_fp \c_one_fp
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@scale@y}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_scale\_y\_fp}}
%Version 3.0 replaced \cs{dtl@scale@y} with:
%    \begin{macrocode}
\fp_new:N \l__dataplot_scale_y_fp
\fp_set_eq:NN \l__dataplot_scale_y_fp \c_one_fp
%    \end{macrocode}
%\end{macro}
% Inverse scale factors:
%    \begin{macrocode}
\fp_new:N \l__dataplot_inv_scale_x_fp
\fp_set_eq:NN \l__dataplot_inv_scale_x_fp \c_one_fp 
\fp_new:N \l__dataplot_inv_scale_y_fp
\fp_set_eq:NN \l__dataplot_inv_scale_y_fp \c_one_fp
%    \end{macrocode}
%\begin{macro}{\dtl@offset@x}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_offset\_x\_fp}}
%Version 3.0 replaced \cs{dtl@offset@x} with:
%    \begin{macrocode}
\fp_new:N \l__dataplot_offset_x_fp
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@offset@y}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_offset\_y\_fp}}
%Version 3.0 replaced \cs{dtl@offset@y} with:
%    \begin{macrocode}
\fp_new:N \l__dataplot_offset_y_fp
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@dx}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_x\_extent\_fp}}
%Version 3.0 replaced \cs{dtl@dx} with:
%    \begin{macrocode}
\fp_new:N \l__dataplot_x_extent_fp
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@dy}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_y\_extent\_fp}}
%Version 3.0 replaced \cs{dtl@dy} with:
%    \begin{macrocode}
\fp_new:N \l__dataplot_y_extent_fp
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@ticklength}
%\changes{3.0}{2025-03-03}{replaced with \cs{}}
%Tick length in terms of canvas co-ordinates.
%Version 3.0 replaced \cs{dtl@ticklength} with:
%    \begin{macrocode}
\fp_new:N \l__dataplot_tick_length_fp
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtl@width}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_width\_fp}}
%Version 3.0 replaced \cs{@dtl@width} with:
%    \begin{macrocode}
\fp_new:N \l__dataplot_width_fp
\fp_new:N \l__dataplot_height_fp
%    \end{macrocode}
%\begin{macro}{\dtl@xticlabelheight}
% The height of the $x$ tick labels.
% Version 3.0: replaced \cs{dtl@xticlabelheight} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_x\_tic\_label\_height\_dim}}
%    \begin{macrocode}
\dim_new:N \l__dataplot_x_tic_label_height_dim
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@yticlabelwidth}
% Dimension used to store the width of the $y$ tick labels.
%Version 3.0 replaced \cs{dtl@yticlabelwidth} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_ytic\_label\_width\_dim}}
%    \begin{macrocode}
\dim_new:N \l__dataplot_y_tic_label_width_dim
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@ticlabeloffset}
% The offset of the label.
% Version 3.0: replaced \cs{dtl@ticlabeloffset} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_tic\_label\_offset\_fp}}
%    \begin{macrocode}
\fp_new:N \l__dataplot_tic_label_offset_fp
%    \end{macrocode}
%\end{macro}
%Tick label:
%    \begin{macrocode}
\tl_new:N \l__dataplot_tic_label_tl
%    \end{macrocode}
%List of database names:
%    \begin{macrocode}
\seq_new:N \l__dataplot_dbnames_seq
%    \end{macrocode}
%List of plot marks (obtained from \cs{DTLplotmarks}):
%    \begin{macrocode}
\seq_new:N \l__dataplot_mark_seq
%    \end{macrocode}
%\begin{macro}{\dtl@mark}
% Current mark.
% Version 3.0: replaced \cs{dtl@mark} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_current\_mark\_tl}}
%    \begin{macrocode}
\tl_new:N \l__dataplot_current_mark_tl
%    \end{macrocode}
%\end{macro}
%Boolean to determine whether to cycle marks for each database or
%for each (x, y) pair.
%    \begin{macrocode}
\bool_new:N \l__dataplot_mark_group_bool
\bool_set_false:N \l__dataplot_mark_group_bool
%    \end{macrocode}
%List of plot mark colours (obtained from \cs{DTLplotmarkcolors}):
%    \begin{macrocode}
\seq_new:N \l__dataplot_mark_colors_seq
%    \end{macrocode}
%\begin{macro}{\dtl@thisplotmarkcolor}
% Current mark colour.
%Version 3.0: replaced \cs{dtl@thisplotmarkcolor} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_current\_mark\_color\_tl}}
%    \begin{macrocode}
\tl_new:N \l__dataplot_current_mark_color_tl
%    \end{macrocode}
%\end{macro}
%Boolean to determine whether to cycle mark colour for each database or
%for each (x, y) pair.
%    \begin{macrocode}
\bool_new:N \l__dataplot_mark_color_group_bool
\bool_set_false:N \l__dataplot_mark_color_group_bool
%    \end{macrocode}
%Current mark style and colour combination:
%    \begin{macrocode}
\tl_new:N \l__dataplot_current_mark_style_tl
%    \end{macrocode}
%List of line styles (obtained from \cs{DTLplotlines}):
%    \begin{macrocode}
\seq_new:N \l__dataplot_line_seq
%    \end{macrocode}
%\begin{macro}{\dtl@linestyle}
% Current line.
% Version 3.0: replaced \cs{dtl@linestyle} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_current\_line\_tl}}
%    \begin{macrocode}
\tl_new:N \l__dataplot_current_line_tl
%    \end{macrocode}
%\end{macro}
%Boolean to determine whether to cycle line style for each database or
%for each (x, y) pair.
%    \begin{macrocode}
\bool_new:N \l__dataplot_line_group_bool
\bool_set_false:N \l__dataplot_line_group_bool
%    \end{macrocode}
%List of line colours (obtained from \cs{DTLplotlinecolors}):
%    \begin{macrocode}
\seq_new:N \l__dataplot_line_colors_seq
%    \end{macrocode}
%\begin{macro}{\dtl@thisplotlinecolor}
% Current line colour.
% Version 3.0: replace \cs{dtl@thisplotlinecolor} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_current\_line\_color\_tl}}
%    \begin{macrocode}
\tl_new:N \l__dataplot_current_line_color_tl
%    \end{macrocode}
%\end{macro}
%Boolean to determine whether to cycle line colour for each database or
%for each (x, y) pair.
%    \begin{macrocode}
\bool_new:N \l__dataplot_line_color_group_bool
\bool_set_false:N \l__dataplot_line_color_group_bool
%    \end{macrocode}
%Current line style and colour combination:
%    \begin{macrocode}
\tl_new:N \l__dataplot_current_line_style_tl
%    \end{macrocode}
%Boolean to determine whether to reset or cycle if group setting not
%on.
%    \begin{macrocode}
\bool_new:N \l__dataplot_no_group_mark_reset_bool
\bool_set_false:N \l__dataplot_no_group_mark_reset_bool
\bool_new:N \l__dataplot_no_group_mark_color_reset_bool
\bool_set_false:N \l__dataplot_no_group_mark_color_reset_bool
\bool_new:N \l__dataplot_no_group_line_reset_bool
\bool_set_false:N \l__dataplot_no_group_line_reset_bool
\bool_new:N \l__dataplot_no_group_line_color_reset_bool
\bool_set_false:N \l__dataplot_no_group_line_color_reset_bool
%    \end{macrocode}
%
%\begin{macro}{\dtl@xminorticlist}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_x\_minor\_tic\_seq}}
%List of minor x ticks.
%Version 3.0: replaced \cs{dtl@xminorticlist} with:
%    \begin{macrocode}
\seq_new:N \l__dataplot_x_minor_tic_seq
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@yminorticlist}
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_y\_minor\_tic\_seq}}
%List of minor y ticks.
%Version 3.0: replaced \cs{dtl@yminorticlist} with:
%    \begin{macrocode}
\seq_new:N \l__dataplot_y_minor_tic_seq
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@stream}
%Plot stream token list.
%Version 3.0: replaced \cs{dtl@stream} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_stream\_tl}}
%    \begin{macrocode}
\tl_new:N \l_dataplot_stream_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@legend}
%Plot legend token list. This is a public temporary variable as it
%needs to be referenced in \cs{DTLaddtoplotlegend}, which may be
%redefined by the user.
%Version 3.0: replaced \cs{dtl@legend} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_dataplot\_legend\_tl}}
%    \begin{macrocode}
\tl_new:N \l_dataplot_legend_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@bounds}
% The bounds of the graph may be given as a four-element list
% \meta{min x}, \meta{min y}, \meta{max x}, \meta{max y}.
% Version 3.0 replaced \cs{dtl@bounds} comma-separated list with
% the sequence variable:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_bounds\_seq}}
%    \begin{macrocode}
\seq_new:N \l__dataplot_bounds_seq
%    \end{macrocode}
%\end{macro}
%Token list to contain the plot code:
%    \begin{macrocode}
\tl_new:N \l__dataplot_content_tl
%    \end{macrocode}
%
%\subsection{Settings}
%
%\begin{macro}{\DTLplotmarks}
%\cs{DTLplotmarks} contains a list of plot marks used by
%\cs{DTLplot}. This will be converted to the sequence
% \cs{l\_\_dataplot\_marks\_seq} in \cs{DTLplot}.
%    \begin{macrocode}
\newcommand*{\DTLplotmarks}{%
  \pgfuseplotmark{o},%
  \pgfuseplotmark{x},%
  \pgfuseplotmark{+},%
  \pgfuseplotmark{square},%
  \pgfuseplotmark{triangle},%
  \pgfuseplotmark{diamond},%
  \pgfuseplotmark{pentagon},%
  \pgfuseplotmark{asterisk},%
  \pgfuseplotmark{star}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLplotmarkcolors}
%\cs{DTLplotmarkcolors} contains a list of the plot mark colours.
% This will be converted to the sequence
% \cs{l\_\_dataplot\_mark\_colors\_seq} in \cs{DTLplot}.
%    \begin{macrocode}
\newcommand*{\DTLplotmarkcolors}{%
  red,%
  green,%
  blue,%
  yellow,%
  magenta,%
  cyan,%
  orange,%
  black,%
  gray}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLplotlines}
%\cs{DTLplotlines} contains a list of dash patterns used by
%\cs{DLTplot}.
% This will be converted to the sequence \cs{l\_\_dataplot\_line\_seq} in \cs{DTLplot}.
%\changes{1.03}{2009 January 27}{fixed error in solid line setting}
%    \begin{macrocode}
\newcommand*{\DTLplotlines}{%
  \pgfsetdash{}{0pt},% solid line
  \pgfsetdash{{10pt}{5pt}}{0pt},%
  \pgfsetdash{{5pt}{5pt}}{0pt},%
  \pgfsetdash{{1pt}{5pt}}{0pt},%
  \pgfsetdash{{5pt}{5pt}{1pt}{5pt}}{0pt},%
  \pgfsetdash{{1pt}{3pt}}{0pt}%
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLplotlinecolors}
%\cs{DTLplotlinecolors} contains a list of the plot line colours.
% This will be converted to the sequence
% \cs{l\_\_dataplot\_line\_colors\_seq} in \cs{DTLplot}.
%    \begin{macrocode}
\newcommand*{\DTLplotlinecolors}{%
  red,%
  green,%
  blue,%
  yellow,%
  magenta,%
  cyan,%
  orange,%
  black,%
  gray}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLplotwidth}
% The default total plot width is stored in the length 
% \cs{dtlplotwidth}
%    \begin{macrocode}
\newlength\DTLplotwidth
\setlength\DTLplotwidth{4in}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLplotheight}
% The default total plot height is stored in the length 
% \cs{dtlplotheight}
%    \begin{macrocode}
\newlength\DTLplotheight
\setlength\DTLplotheight{4in}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLticklength}
% The length of the tick marks is given by \cs{DTLticklength}
%    \begin{macrocode}
\newlength\DTLticklength
\setlength\DTLticklength{5pt}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLminorticklength}
% The length of the minor tick marks is given by 
% \cs{DTLminorticklength}.
%    \begin{macrocode}
\newlength\DTLminorticklength
\setlength\DTLminorticklength{2pt}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLticklabeloffset}
% The offset from the axis to the tick label is given by
% \cs{DTLticklabeloffset}.
%    \begin{macrocode}
\newlength\DTLticklabeloffset
\setlength\DTLticklabeloffset{8pt}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLmintickgap}
% \cs{DTLmintickgap} stores the suggested minimum distance between 
% tick marks where the gap is not specified.
%    \begin{macrocode}
\newlength\DTLmintickgap
\setlength\DTLmintickgap{20pt}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLminminortickgap}
% The suggested minimum distance between minor tick marks where the 
% gap is not specified is given by \cs{DTLminminortickgap}.
%    \begin{macrocode}
\newlength\DTLminminortickgap
\setlength\DTLminminortickgap{5pt}
%    \end{macrocode}
%\end{macro}
%\begin{counter}{DTLplotroundXvar}
% Round $x$ tick labels to the number of digits given by
% the counter \ctrfmt{DTLplotroundXvar}.
%    \begin{macrocode}
\newcounter{DTLplotroundXvar}
\setcounter{DTLplotroundXvar}{2}
%    \end{macrocode}
%\end{counter}
%\begin{counter}{DTLplotroundYvar}
% Round $y$ tick labels to the number of digits given by
% the counter \ctrfmt{DTLplotroundYvar}.
%    \begin{macrocode}
\newcounter{DTLplotroundYvar}
\setcounter{DTLplotroundYvar}{2}
%    \end{macrocode}
%\end{counter}
%\begin{macro}{\ifDTLxaxis}
% The conditional \cs {ifDTLxaxis} is used to determine whether or 
% not to display the $x$ axis.
%    \begin{macrocode}
\newif\ifDTLxaxis
\DTLxaxistrue
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLXAxisStyle}
% The style of the $x$ axis is given by \cs{DTLXAxisStyle}. This
% is just a solid line by default.
%    \begin{macrocode}
\newcommand*{\DTLXAxisStyle}{-}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\ifDTLyaxis}
% The conditional \cs{ifDTLyaxis} is used to determine whether or 
% not to display the $y$ axis
%    \begin{macrocode}
\newif\ifDTLyaxis
\DTLyaxistrue
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLYAxisStyle}
% The style of the $y$ axis is given by \cs{DTLYAxisStyle}. This
% is just a solid line by default.
%    \begin{macrocode}
\newcommand*{\DTLYAxisStyle}{-}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLmajorgridstyle}
% The style of the major grid lines is given by 
% \cs{DTLmajorgridstyle}.
%    \begin{macrocode}
\newcommand*{\DTLmajorgridstyle}{color=gray,-}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLminorgridstyle}
% The style of the minor grid lines is given by 
% \cs{DTLminorgridstyle}.
%    \begin{macrocode}
\newcommand*{\DTLminorgridstyle}{color=lightgray,very ~ thin}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\ifDTLxticsin}
% The conditional \cs{ifDTLxticsin} is used to determine whether 
% the $x$ tics should point in or out.
%    \begin{macrocode}
\newif\ifDTLxticsin
\DTLxticsintrue
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\ifDTLyticsin}
% The conditional \cs{ifDTLyticsin} is used to determine whether 
% the $y$ tics should point in or out.
%    \begin{macrocode}
\newif\ifDTLyticsin
\DTLyticsintrue
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLlegendxoffset}
% The gap between the border of plot and legend is given by the
% lengths \cs{DTLlegendxoffset} and \cs{DTLlegendyoffset}
%    \begin{macrocode}
\newlength\DTLlegendxoffset
\setlength\DTLlegendxoffset{10pt}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLlegendyoffset}
%    \begin{macrocode}
\newlength\DTLlegendyoffset
\setlength\DTLlegendyoffset{10pt}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLformatlegend}
%\begin{definition}
%\cs{DTLformatlegend}\marg{legend}
%\end{definition}
% This formats the legend.
%    \begin{macrocode}
\newcommand*{\DTLformatlegend}[1]{%
\setlength{\fboxrule}{1.1pt}%
\fcolorbox{black}{white}{#1}}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLcustomlegend}
%\changes{3.0}{2025-03-03}{new}
%\begin{definition}
%\cs{DTLcustomlegend}\marg{legend}
%\end{definition}
%Used with the custom legend setting. This should be redefined to
%place the legend at the desired position.
%    \begin{macrocode}
\newcommand{\DTLcustomlegend}[1]{
  \node { \DTLformatlegend { #1 } } ;
}
%    \end{macrocode}
%\end{macro}
% Adds the begin \env{tabular} code for the legend:
%    \begin{macrocode}
\cs_new:Nn \dataplot_legend_add_begin:
{
  \tl_put_left:Nn \l_dataplot_legend_tl
   { \begin { tabular } { c l } }
}
%    \end{macrocode}
% Adds the end \env{tabular} code for the legend:
%    \begin{macrocode}
\cs_new:Nn \dataplot_legend_add_end:
{
  \tl_put_right:Nn \l_dataplot_legend_tl
   { \end { tabular } }
}
%    \end{macrocode}
%\begin{macro}{\ifDTLshowmarkers}
% The conditional \cs{ifDTLshowmarkers} is used to specify 
% whether or not to use markers.
%    \begin{macrocode}
\newif\ifDTLshowmarkers
\DTLshowmarkerstrue
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\ifDTLshowlines}
% The conditional \cs{ifDTLshowlines} is used to specify 
% whether or not to use lines.
%    \begin{macrocode}
\newif\ifDTLshowlines
\DTLshowlinesfalse
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\ifDTLbox}
% Enclose plot in a box
%    \begin{macrocode}
\newif\ifDTLbox
\DTLboxfalse
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\ifDTLxticstrue}
% Condition to determine whether to show the $x$ tick marks
%    \begin{macrocode}
\newif\ifDTLxtics
\DTLxticstrue
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\ifDTLyticstrue}
% Condition to determine whether to show the $y$ tick marks
%    \begin{macrocode}
\newif\ifDTLytics
\DTLyticstrue
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\ifDTLxminortics}
% Condition to determine whether to show the $x$ minor tick marks
%    \begin{macrocode}
\newif\ifDTLxminortics
\DTLxminorticsfalse
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\ifDTLyminortics}
% Condition to determine whether to show the $y$ minor tick marks
%    \begin{macrocode}
\newif\ifDTLyminortics
\DTLyminorticsfalse
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\ifDTLgrid}
% Determine whether to draw the grid
%    \begin{macrocode}
\newif\ifDTLgrid
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLplotdisplayticklabel}
%\begin{definition}
%\cs{DTLplotdisplayticklabel}\marg{text}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
% Formatting used for default $x$ tick labels.
%    \begin{macrocode}
\newcommand{\DTLplotdisplayticklabel}[1]{#1}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLplotdisplayXticklabel}
%\begin{definition}
%\cs{DTLplotdisplayXticklabel}\marg{text}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
% Formatting used for default $x$ tick labels.
%    \begin{macrocode}
\newcommand{\DTLplotdisplayXticklabel}[1]{\DTLplotdisplayticklabel{#1}}
%    \end{macrocode}
%\end{macro}
%$x$ tick label node style:
%    \begin{macrocode}
\tl_new:N \l__dataplot_x_tick_label_style_tl
%    \end{macrocode}
%
%\begin{macro}{\DTLplotdisplayYticklabel}
%\begin{definition}
%\cs{DTLplotdisplayYticklabel}\marg{text}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
% Formatting used for default $y$ tick labels.
%    \begin{macrocode}
\newcommand{\DTLplotdisplayYticklabel}[1]{\DTLplotdisplayticklabel{#1}}
%    \end{macrocode}
%\end{macro}
%$y$ tick label node style:
%    \begin{macrocode}
\tl_new:N \l__dataplot_y_tick_label_style_tl
\tl_set:Nn \l__dataplot_y_tick_label_style_tl { anchor=east }
%    \end{macrocode}
%
%\begin{macro}{\dtl@minx}
% The lower $x$ bound.
%Version 3.0: replaced \cs{dtl@minx} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_min\_x\_tl}}
%    \begin{macrocode}
\tl_new:N \l__dataplot_min_x_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@maxx}
% The upper $x$ bound:
%Version 3.0: replaced \cs{dtl@maxx} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_max\_x\_tl}}
%    \begin{macrocode}
\tl_new:N \l__dataplot_max_x_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@miny}
% The lower $y$ bound:
%Version 3.0: replaced \cs{dtl@miny} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_min\_y\_tl}}
%    \begin{macrocode}
\tl_new:N \l__dataplot_min_y_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@maxy}
% The upper $y$ bound:
%Version 3.0: replaced \cs{dtl@maxy} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_max\_y\_tl}}
%    \begin{macrocode}
\tl_new:N \l__dataplot_max_y_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@xticlist}
% The list of floating point values for $x$ ticks.
%Version 3.0: replaced \cs{dtl@xticlist} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_x\_tic\_seq}}
%    \begin{macrocode}
\clist_new:N \l__dataplot_x_tic_clist
\seq_new:N \l__dataplot_x_tic_seq
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@yticlist}
% The list of decimal values for $y$ ticks.
%Version 3.0: replaced \cs{dtl@yticlist} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_y\_tic\_seq}}
%    \begin{macrocode}
\clist_new:N \l__dataplot_y_tic_clist
\seq_new:N \l__dataplot_y_tic_seq
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@xticgap}
% The gap between $x$ tick marks ("xticpoints" overrides
% "xticgap")
%Version 3.0: replaced \cs{dtl@xticgap} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_xtic\_gap\_tl}}
%    \begin{macrocode}
\tl_new:N \l__dataplot_xtic_gap_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@yticgap}
% The gap between $y$ tick marks ("yticpoints" overrides
% "yticgap")
%Version 3.0: replaced \cs{dtl@yticgap} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_ytic\_gap\_tl}}
%    \begin{macrocode}
\tl_new:N \l__dataplot_ytic_gap_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@xticlabels}
% A comma separated list of labels for $x$ ticks.
%Version 3.0 replaced \cs{dtl@xticlabels} with a sequence variable:
%\changes{3.0}{2025-03-03}{converted to sequence
%\cs{l\_\_dataplot\_xtic\_labels\_seq}}
%    \begin{macrocode}
\seq_new:N \l__dataplot_xtic_labels_seq
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@yticlabels}
% A comma separated list of labels for $y$ ticks.
%Version 3.0 replaced \cs{dtl@yticlabels} with a sequence variable:
%\changes{3.0}{2025-03-03}{converted to sequence
%\cs{l\_\_dataplot\_ytic\_labels\_seq}}
%    \begin{macrocode}
\seq_new:N \l__dataplot_ytic_labels_seq
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@xlabel}
% $x$ axis label
%Version 3.0 replaced \cs{dtl@xlabel} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_xlabel\_tl}}
%    \begin{macrocode}
\tl_new:N \l__dataplot_xlabel_tl
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\dtl@ylabel}
% $y$ axis label
%Version 3.0 replaced \cs{dtl@ylabel} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_ylabel\_tl}}
%    \begin{macrocode}
\tl_new:N \l__dataplot_ylabel_tl
%    \end{macrocode}
%\end{macro}
%Labels at either end of axes and their style:
%    \begin{macrocode}
\tl_new:N \l__dataplot_x_min_label_tl
\tl_new:N \l__dataplot_x_min_label_style_tl
\tl_set:Nn \l__dataplot_x_min_label_style_tl { left }
\tl_new:N \l__dataplot_x_max_label_tl
\tl_new:N \l__dataplot_x_max_label_style_tl
\tl_set:Nn \l__dataplot_x_max_label_style_tl { right }
\tl_new:N \l__dataplot_y_min_label_tl
\tl_new:N \l__dataplot_y_min_label_style_tl
\tl_set:Nn \l__dataplot_y_min_label_style_tl { below }
\tl_new:N \l__dataplot_y_max_label_tl
\tl_new:N \l__dataplot_y_max_label_style_tl
\tl_set:Nn \l__dataplot_y_max_label_style_tl { above }
%    \end{macrocode}
%
%
%\begin{macro}{\dtl@legendlabels}
% Legend labels (comma separated list). If empty, the database
% name is used.
%Version 3.0 replaced \cs{dtl@legendlabels} with the sequence:
%\changes{3.0}{2025-03-03}{converted to sequence
%\cs{l\_\_dataplot\_legend\_labels\_seq}}
%    \begin{macrocode}
\seq_new:N \l__dataplot_legend_labels_seq
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtl@legendsetting}
% Integer variable corresponding to the legend setting:
% "none" (0, don't show it),
% "north" (1), "northeast" (2), "east" (3), "southeast" (4),
% "south" (5), "southwest" (6), "west" (7), "northwest" (8)
% or "custom" (9). The custom setting will use \cs{DTLcustomlegend}
% to position the legend which should be redefined as applicable.
%Version 3.0 replaced \cs{dtl@legendsetting} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_dataplot\_legend\_setting\_int}}
%    \begin{macrocode}
\int_new:N \l__dataplot_legend_setting_int
%    \end{macrocode}
%\end{macro}
%
%Provide a filter function:
%    \begin{macrocode}
\cs_new:Nn \__dataplot_filter:T { #1 }
%    \end{macrocode}
%
%Convert a list of numbers to a sequence containing floating point
%markup (so that the numbers don't have to keep being reparsed).
%Only include numbers in the given range (inclusive)
%Syntax: \meta{seq}\marg{clist}\marg{min}\marg{max}
%    \begin{macrocode}
\cs_new:Nn \__dataplot_to_fp_seq:NNnn
{
  \seq_clear:N #1
  \clist_map_inline:Nn { #2 }
  {
    \datatool_set_fp:Nn \l__datatool_tmpa_fp { ##1 }
    \fp_compare:nT
      { #3 <= \l__datatool_tmpa_fp <= #4 }
     {
       \seq_put_right:NV #1 \l__datatool_tmpa_fp
     }
  }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__dataplot_filtered_map:nnn 
{
  \int_zero:N \l_dataplot_x_key_int
  \int_zero:N \l_dataplot_y_key_int
  \seq_set_from_clist:NN \l__dataplot_x_keys_seq
    \l__dataplot_x_keys_clist
%    \end{macrocode}
% Iterate over the (x, y) coordinate pairs:
%    \begin{macrocode}
  \seq_map_inline:Nn \l__dataplot_y_keys_seq
  {
    \int_incr:N \l_dataplot_y_key_int
%    \end{macrocode}
% The y key is the sequence iteration value:
%    \begin{macrocode}
    \tl_set:Nn \l__dataplot_y_key_tl { ##1 }
%    \end{macrocode}
% Get the x key:
%    \begin{macrocode}
      \seq_if_empty:NT \l__dataplot_x_keys_seq
      {
        \int_zero:N \l_dataplot_x_key_int
        \seq_set_from_clist:NN \l__dataplot_x_keys_seq
          \l__dataplot_x_keys_clist
      }
      \seq_pop_left:NN \l__dataplot_x_keys_seq
        \l__dataplot_x_key_tl
      \int_incr:N \l_dataplot_x_key_int
%    \end{macrocode}
%Both the x and y key must exist for the current database for this
%plot stream. If either doesn't exist, skip this iteration.
% Check the y key exists for the current database:
%    \begin{macrocode}
    \datatool_if_has_key:nnTF 
      { \l__datatool_default_dbname_tl }
      { \l__dataplot_y_key_tl }
     {
%    \end{macrocode}
% Check the x key exists for the current database:
%    \begin{macrocode}
      \datatool_if_has_key:nnTF
         { \l__datatool_default_dbname_tl }
         { \l__dataplot_x_key_tl }
       {
%    \end{macrocode}
% Pre map code:
%    \begin{macrocode}
         #1
%    \end{macrocode}
% Iterate over all rows in this database:
%    \begin{macrocode}
         \DTLmapdata
         { 
           \tl_if_empty:NF \l__dataplot_extra_assign_tl
           {
             \exp_args:NV \DTLmapgetvalues
              \l__dataplot_extra_assign_tl
           }
%    \end{macrocode}
% Get the x coordinate:
%    \begin{macrocode}
           \DTLmapget
            {
              return = \l__dataplot_x_tl ,
              key = \l__dataplot_x_key_tl
            }
%    \end{macrocode}
% Get the y coordinate:
%    \begin{macrocode}
           \DTLmapget
            {
              return = \l__dataplot_y_tl ,
              key = \l__dataplot_y_key_tl
            }
           \__dataplot_filter:T { #2 }
         }
%    \end{macrocode}
% Post map code:
%    \begin{macrocode}
         #3
       }
       {
%    \end{macrocode}
% The y key isn't defined for the current database.
%    \begin{macrocode}
         \dataplot_skipping_key_msg:nnnn
           { \seq_count:N \l__dataplot_dbnames_seq }
           { x } { \l__dataplot_x_key_tl }
           { \l__datatool_default_dbname_tl }
       }
    }
    {
%    \end{macrocode}
% The y key isn't defined for the current database.
%    \begin{macrocode}
      \dataplot_skipping_key_msg:nnnn
        { \seq_count:N \l__dataplot_dbnames_seq }
        { y } { \l__dataplot_y_key_tl }
        { \l__datatool_default_dbname_tl }
    }
  }
}
\cs_new:Nn \__dataplot_filtered_map:n 
{
  \__dataplot_filtered_map:nnn { } { #1 } { }
}
%    \end{macrocode}
%Syntax: \marg{num databases}\marg{x/y}\marg{key}\marg{db-name}
%Warn or verbose message if skipping key:
%    \begin{macrocode}
\cs_new:Nn \dataplot_skipping_key_msg:nnnn
{
  \int_compare:nNnTF { #1 } = { \c_one_int }
   {
      \PackageWarning { dataplot }
      {
        \token_to_str:N \DTLplot : ~ skipping ~ #2 ~ key ~
        ` #3 ' ~ for ~ database ~ `\l__datatool_default_dbname_tl' ~
        (column ~ not ~ defined)
      }
   }
   {
     \dtl@message
      {
        \token_to_str:N \DTLplot : ~ skipping ~ #2 ~ key ~
        ` #3 ' ~ for ~ database ~ `\l__datatool_default_dbname_tl' ~
        (column ~ not ~ defined)
      }
   }
}
%    \end{macrocode}
%If the side axes boolean is on, the axes will always be draw on the
%left and bottom. If the setting is off and there are negative
%values, the axes will pass through 0 otherwise they will be drawn
%on the side.
%    \begin{macrocode}
\bool_new:N \l__dataplot_side_axes_bool
\bool_set_false:N \l__dataplot_side_axes_bool
%    \end{macrocode}
%Booleans set after computing the bounds. True if x axis includes 0.
%That is, the y axis should intersect at x=0:
%    \begin{macrocode}
\bool_new:N \l__dataplot_zero_x_axis_bool
%    \end{macrocode}
%True if y axis includes 0.
%That is, the x axis should intersect at y=0:
%    \begin{macrocode}
\bool_new:N \l__dataplot_zero_y_axis_bool
%    \end{macrocode}
%Omit tick label at $x=0$ or $y=0$ if true:
%    \begin{macrocode}
\bool_new:N \l__dataplot_omit_zero_x_label_bool
\bool_new:N \l__dataplot_omit_zero_y_label_bool
%    \end{macrocode}
%Setting to determine whether or not to omit zero labels:
%1 (auto), 2 (omit both), 3 (omit x), 4 (omit y), 5 (don't omit)
%    \begin{macrocode}
\int_new:N \l__dataplot_omit_zero_label_action_int
%    \end{macrocode}
%Determines whether to show ticks on the box if box=true.
%Values: 1=match axes, 2=in, 3=out, 0=none. Any other value is
%treated as 0.
%    \begin{macrocode}
\int_new:N \l__dataplot_box_ticks_int
\int_set_eq:NN \l__dataplot_box_ticks_int \c_one_int
%    \end{macrocode}
%
%    \begin{macrocode}
\keys_define:nn { datatool/plot }
{
%    \end{macrocode}
% Plot settings. The database key for the $x$ value is
% given by the "x" setting:
%    \begin{macrocode}
  x .clist_set:N = \l__dataplot_x_keys_clist ,
%    \end{macrocode}
% The database key for the $y$ value is given by the "y" setting:
%    \begin{macrocode}
  y .code:n  = 
    {
      \seq_set_from_clist:Nn \l__dataplot_y_keys_seq { #1 }
    },
%    \end{macrocode}
% Any extra assignments that need to be made in \cs{DTLmapdata}:
%    \begin{macrocode}
  extra-assign .tl_set:N = \l__dataplot_extra_assign_tl ,
%    \end{macrocode}
% The list of plot mark colours is given by the "markcolors" setting. 
% (This should be a comma separated list of colour names.)
%    \begin{macrocode}
  markcolors .clist_set:N = \DTLplotmarkcolors,
  mark-colors .clist_set:N = \DTLplotmarkcolors,
%    \end{macrocode}
% The list of plot line colours is given by the "linecolors" setting. 
% (This should be a comma separated list of colour names.)
%    \begin{macrocode}
  linecolors .clist_set:N = \DTLplotlinecolors,
  line-colors .clist_set:N = \DTLplotlinecolors,
%    \end{macrocode}
% The list of plot mark and line colours is given by the "colors" 
% setting.  (This should be a comma separated list of colour names.)
%    \begin{macrocode}
  colors .code:n = 
    {
      \clist_set:Nn \DTLplotmarkcolors { #1 }
      \clist_set:Nn \DTLplotlinecolors { #1 }
    },
%    \end{macrocode}
% The list of plot marks is given by the "marks" setting. (This
% should be a comma separated list of code that generates 
% pgf plot marks.)
%    \begin{macrocode}
   marks .clist_set:N = \DTLplotmarks ,
%    \end{macrocode}
% The list of plot line styles is given by the "lines" setting. (This
% should be a comma separated list of code that sets the line
% style.) An empty set will create solid lines.
%    \begin{macrocode}
   lines .clist_set:N = \DTLplotlines ,
%    \end{macrocode}
% Determine whether or not styles not enabled with group-styles
% should continue cycling or reset at the start of each database.
%    \begin{macrocode}
   style-resets .multichoice: ,
   style-resets .default:n = { all },
   style-resets / mark-style .code:n =
    {
      \bool_set_true:N \l__dataplot_no_group_mark_reset_bool
    } ,
   style-resets / mark-color .code:n =
    {
      \bool_set_true:N \l__dataplot_no_group_mark_color_reset_bool
    } ,
   style-resets / line-style .code:n =
    {
      \bool_set_true:N \l__dataplot_no_group_line_reset_bool
    } ,
   style-resets / line-color .code:n =
    {
      \bool_set_true:N \l__dataplot_no_group_line_color_reset_bool
    } ,
   style-resets / all .code:n =
    {
      \bool_set_true:N \l__dataplot_no_group_mark_reset_bool
      \bool_set_true:N \l__dataplot_no_group_mark_color_reset_bool
      \bool_set_true:N \l__dataplot_no_group_line_reset_bool
      \bool_set_true:N \l__dataplot_no_group_line_color_reset_bool
    } ,
   style-resets / none .code:n =
    {
      \bool_set_false:N \l__dataplot_no_group_mark_reset_bool
      \bool_set_false:N \l__dataplot_no_group_mark_color_reset_bool
      \bool_set_false:N \l__dataplot_no_group_line_reset_bool
      \bool_set_false:N \l__dataplot_no_group_line_color_reset_bool
    } ,
%    \end{macrocode}
% Determine whether to cycle mark and line styles for each database
% or for each (x, y) pair.
%    \begin{macrocode}
   group-styles .multichoice:,
   group-styles .default:n = { all },
   group-styles / mark-style .code:n =
    {
      \bool_set_true:N \l__dataplot_mark_group_bool
    } ,
   group-styles / mark-color .code:n =
    {
      \bool_set_true:N \l__dataplot_mark_color_group_bool
    } ,
   group-styles / line-style .code:n =
    {
      \bool_set_true:N \l__dataplot_line_group_bool
    } ,
   group-styles / line-color .code:n =
    {
      \bool_set_true:N \l__dataplot_line_color_group_bool
    } ,
   group-styles / all .code:n =
    {
      \bool_set_true:N \l__dataplot_mark_group_bool
      \bool_set_true:N \l__dataplot_mark_color_group_bool
      \bool_set_true:N \l__dataplot_line_group_bool
      \bool_set_true:N \l__dataplot_line_color_group_bool
    } ,
   group-styles / none .code:n =
    {
      \bool_set_false:N \l__dataplot_mark_group_bool
      \bool_set_false:N \l__dataplot_mark_color_group_bool
      \bool_set_false:N \l__dataplot_line_group_bool
      \bool_set_false:N \l__dataplot_line_color_group_bool
    } ,
%    \end{macrocode}
% The total width of the plot is given by the "width" setting.
%    \begin{macrocode}
   width .dim_set:N = \DTLplotwidth ,
%    \end{macrocode}
% The total height of the plot is given by the "height" setting.
%    \begin{macrocode}
   height .dim_set:N =\DTLplotheight ,
%    \end{macrocode}
% Tick label offset.
%    \begin{macrocode}
   tick-label-offset .dim_set:N = \DTLticklabeloffset ,
%    \end{macrocode}
% Determine whether to show lines, markers or both
%    \begin{macrocode}
   style .choice:,
   style / both .code:n = 
    {
      \DTLshowlinestrue
      \DTLshowmarkerstrue
    },
   style / lines .code:n = 
    {
      \DTLshowlinestrue
      \DTLshowmarkersfalse
    },
   style / markers .code:n = 
    {
      \DTLshowmarkerstrue
      \DTLshowlinesfalse
    },
%    \end{macrocode}
% Determine whether or not to display the axes
%    \begin{macrocode}
   axes .choice:,
   axes / both .code:n =
    {
      \DTLxaxistrue
      \DTLxticstrue
      \DTLyaxistrue
      \DTLyticstrue
    },
   axes / x .code:n =
    {
      \DTLxaxistrue
      \DTLxticstrue
      \DTLyaxisfalse
      \DTLyticsfalse
    },
   axes / y .code:n =
    {
      \DTLxaxisfalse
      \DTLxticsfalse
      \DTLyaxistrue
      \DTLyticstrue
    },
   axes / none .code:n =
    {
     \DTLxaxisfalse
     \DTLxticsfalse
     \DTLyaxisfalse
     \DTLyticsfalse
    },
%    \end{macrocode}
% Axis drawing style:
%    \begin{macrocode}
   x-axis-style .tl_set:N = \DTLXAxisStyle ,
   y-axis-style .tl_set:N = \DTLYAxisStyle ,
   axis-style .code:n =
    {
      \tl_set:Nn \DTLXAxisStyle { #1 }
      \tl_set:Nn \DTLYAxisStyle { #1 }
    },
%    \end{macrocode}
% Major and minor grid drawing style:
%    \begin{macrocode}
   major-grid-style .tl_set:N = \DTLmajorgridstyle ,
   minor-grid-style .tl_set:N = \DTLminorgridstyle ,
%    \end{macrocode}
% Indicate whether or not the plot should be enclosed in a box:
%    \begin{macrocode}
   box .legacy_if_set:n = DTLbox ,
%    \end{macrocode}
% Indicate if the box should also have tick marks:
%    \begin{macrocode}
   box-ticks .choices:nn=
     { none, match-axes, in , out }
    {
      \int_set:Nn \l__dataplot_box_ticks_int
       { \l_keys_choice_int - \c_one_int }
    } ,
   box-ticks .default:n = { match-axes },
   box-ticks .initial:n = { match-axes },
%    \end{macrocode}
% Rounding for default tick labels:
%    \begin{macrocode}
   round-x .int_set:N = \c@DTLplotroundXvar ,
   round-y .int_set:N = \c@DTLplotroundYvar ,
   round .code:n =
    {
      \int_set:Nn \c@DTLplotroundXvar { #1 }
      \int_set:Nn \c@DTLplotroundYvar { #1 }
    },
%    \end{macrocode}
% Indicates whether to show the $x$ tick marks:
%    \begin{macrocode}
   xtics .legacy_if_set:n = DTLxtics ,
   x-ticks .legacy_if_set:n = DTLxtics ,
%    \end{macrocode}
% Indicates whether to show the $y$ tick marks:
%    \begin{macrocode}
   ytics .legacy_if_set:n = DTLytics ,
   y-ticks .legacy_if_set:n = DTLytics ,
%    \end{macrocode}
% Indicates whether to show the $x$ and $y$ tick marks:
%    \begin{macrocode}
   tics .code:n = 
    {
      \setbool{DTLxtics}{#1}
      \setbool{DTLytics}{#1}
    },
   ticks .code:n = 
    {
      \setbool{DTLxtics}{#1}
      \setbool{DTLytics}{#1}
    },
%    \end{macrocode}
% Indicates whether to show the $x$ minor tick marks:
%    \begin{macrocode}
   xminortics .choice: , 
   xminortics / true .code:n = 
     {
       \DTLxminorticstrue
       \DTLxticstrue
     } ,
   xminortics / false .code:n = 
     {
       \DTLxminorticsfalse
     } ,
   xminortics .default:n = true,
%    \end{macrocode}
% Synonym:
%    \begin{macrocode}
   x-minor-ticks .choice: , 
   x-minor-ticks / true .code:n = 
     {
       \DTLxminorticstrue
       \DTLxticstrue
     } ,
   x-minor-ticks / false .code:n = 
     {
       \DTLxminorticsfalse
     } ,
   x-minor-ticks .default:n = true,
%    \end{macrocode}
% Indicates whether to show the $y$ minor tick marks:
%    \begin{macrocode}
   yminortics .choice:, 
   yminortics / true .code:n = 
     {
       \DTLyminorticstrue
       \DTLyticstrue
     } ,
   yminortics / false .code:n = 
     {
       \DTLyminorticsfalse
     } ,
   yminortics .default:n = true,
%    \end{macrocode}
% Synonym:
%    \begin{macrocode}
   y-minor-ticks .choice:, 
   y-minor-ticks / true .code:n = 
     {
       \DTLyminorticstrue
       \DTLyticstrue
     } ,
   y-minor-ticks / false .code:n = 
     {
       \DTLyminorticsfalse
     } ,
   y-minor-ticks .default:n = true,
%    \end{macrocode}
% Indicates whether to show the $x$ and $y$ minor tick marks:
%    \begin{macrocode}
   minor-ticks .choice: , 
   minor-ticks / true .code:n = 
     {
       \DTLxminorticstrue
       \DTLxticstrue
       \DTLyminorticstrue
       \DTLyticstrue
     } ,
   minor-ticks / false .code:n = 
     {
       \DTLxminorticsfalse
       \DTLyminorticsfalse
     } ,
   minor-ticks .default:n = true,
%    \end{macrocode}
%Synonym:
%    \begin{macrocode}
   minortics .choice: , 
   minortics / true .code:n = 
     {
       \DTLxminorticstrue
       \DTLxticstrue
       \DTLyminorticstrue
       \DTLyticstrue
     } ,
   minortics / false .code:n = 
     {
       \DTLxminorticsfalse
       \DTLyminorticsfalse
     } ,
   minortics .default:n = true,
%    \end{macrocode}
% Determine whether to draw the grid:
%    \begin{macrocode}
   grid .legacy_if_set:n = DTLgrid ,
%    \end{macrocode}
% Determine whether the $x$ tick marks should point
% in or out:
%    \begin{macrocode}
   xticdir .choice: ,
   xticdir / in .code:n = 
     {
       \DTLxticsintrue
     },
   xticdir / out .code:n = 
     {
       \DTLxticsinfalse
     },
%    \end{macrocode}
% Synonym:
%    \begin{macrocode}
   x-tick-dir .choice: ,
   x-tick-dir / in .code:n = 
     {
       \DTLxticsintrue
     },
   x-tick-dir / out .code:n = 
     {
       \DTLxticsinfalse
     },
%    \end{macrocode}
% Determine whether the $y$ tick marks should point
% in or out:
%    \begin{macrocode}
   yticdir .choice: ,
   yticdir / in .code:n = 
     {
       \DTLyticsintrue
     },
   yticdir / out .code:n = 
     {
       \DTLyticsinfalse
     },
%    \end{macrocode}
% Synonym:
%    \begin{macrocode}
   y-tick-dir .choice: ,
   y-tick-dir / in .code:n = 
     {
       \DTLyticsintrue
     },
   y-tick-dir / out .code:n = 
     {
       \DTLyticsinfalse
     },
%    \end{macrocode}
% Determine whether the $x$ and $y$ tick marks should point
% in or out:
%    \begin{macrocode}
   ticdir .choice: ,
   ticdir / in .code:n = 
     {
       \DTLxticsintrue
       \DTLyticsintrue
     },
   ticdir / out .code:n = 
     {
       \DTLxticsinfalse
       \DTLyticsinfalse
     },
%    \end{macrocode}
% Synonym:
%    \begin{macrocode}
   tick-dir .choice: ,
   tick-dir / in .code:n = 
     {
       \DTLxticsintrue
       \DTLyticsintrue
     },
   tick-dir / out .code:n = 
     {
       \DTLxticsinfalse
       \DTLyticsinfalse
     },
%    \end{macrocode}
% Set the bounds of the graph (value must be in the form
% \meta{min x}","\meta{min y}","\meta{max x}","\meta{max y}.
% Version 3.0: bounds no longer overrides minx, miny, maxx and maxy settings.
% Set to empty to cancel.
%    \begin{macrocode}
   bounds .code:n = 
    {
      \seq_set_from_clist:Nn \l__dataplot_bounds_seq { #1 }
      \seq_if_empty:NF \l__dataplot_bounds_seq
      {
        \int_compare:nNnTF
          { \seq_count:N \l__dataplot_bounds_seq } = { 4 }
        {
          \tl_set:Nx \l__dataplot_min_x_tl
            { \seq_item:Nn \l__dataplot_bounds_seq { 1 } }
          \tl_set:Nx \l__dataplot_min_y_tl
            { \seq_item:Nn \l__dataplot_bounds_seq { 2 } }
          \tl_set:Nx \l__dataplot_max_x_tl
            { \seq_item:Nn \l__dataplot_bounds_seq { 3 } }
          \tl_set:Nx \l__dataplot_max_y_tl
            { \seq_item:Nn \l__dataplot_bounds_seq { 4 } }
        }
        {
           \PackageError {dataplot}
           {
             Invalid ~ syntax ~ in ~ bounds=#1 ~
             (expected ~ empty ~ or ~ four ~ items, ~ found ~
              \seq_count:N \l__dataplot_bounds_seq)
           }
           {
             The ~ bounds ~ must ~ be ~ specified ~ as ~ minX,minY,maxX,maxY
           }
        }
      }
    },
%    \end{macrocode}
% Set only the lower $x$ bound
%    \begin{macrocode}
   minx .tl_set:N = \l__dataplot_min_x_tl ,
   min-x .tl_set:N = \l__dataplot_min_x_tl ,
%    \end{macrocode}
% Set only the upper $x$ bound
%    \begin{macrocode}
   maxx .tl_set:N = \l__dataplot_max_x_tl ,
   max-x .tl_set:N = \l__dataplot_max_x_tl ,
%    \end{macrocode}
% Set only the lower $y$ bound
%    \begin{macrocode}
   miny .tl_set:N = \l__dataplot_min_y_tl ,
   min-y .tl_set:N = \l__dataplot_min_y_tl ,
%    \end{macrocode}
% Set only the upper $y$ bound
%    \begin{macrocode}
   maxy .tl_set:N = \l__dataplot_max_y_tl ,
   max-y .tl_set:N = \l__dataplot_max_y_tl ,
%    \end{macrocode}
% Define list of points for $x$ ticks. (Must be a comma separated
% list of decimal numbers.) These need to be converted to a sequence
% containing the floating point representation.
%    \begin{macrocode}
  xticpoints .code:n =
    {
      \tl_if_empty:nTF { #1 }
       {
         \clist_clear:N \l__dataplot_x_tic_clist
       }
       {
         \clist_set:Nn \l__dataplot_x_tic_clist { #1 }
         \DTLxticstrue
         \DTLxaxistrue
       }
    },
  x-tick-points .code:n =
    {
      \tl_if_empty:nTF { #1 }
       {
         \clist_clear:N \l__dataplot_x_tic_clist
       }
       {
         \clist_set:Nn \l__dataplot_x_tic_clist { #1 }
         \DTLxticstrue
         \DTLxaxistrue
       }
    },
%    \end{macrocode}
% Define list of points for $y$ ticks. (Must be a comma separated
% list of decimal numbers.)
%    \begin{macrocode}
  yticpoints .code:n =
    {
      \tl_if_empty:nTF { #1 }
       {
         \clist_clear:N \l__dataplot_y_tic_clist
       }
       {
         \clist_set:Nn \l__dataplot_y_tic_clist { #1 }
         \DTLyticstrue
         \DTLyaxistrue
       }
    },
  y-tick-points .code:n =
    {
      \tl_if_empty:nTF { #1 }
       {
         \clist_clear:N \l__dataplot_y_tic_clist
       }
       {
         \clist_set:Nn:Nn \l__dataplot_y_tic_clist { #1 }
         \DTLyticstrue
         \DTLyaxistrue
       }
    },
%    \end{macrocode}
% Define the gap between $x$ tick marks (xticpoints overrides
% xticgap)
%    \begin{macrocode}
  xticgap .code:n = 
   {
     \tl_set:Nn \l__dataplot_xtic_gap_tl { #1 }
     \tl_if_empty:NF \l__dataplot_xtic_gap_tl
      {
        \DTLxticstrue
        \DTLxaxistrue
      }
   },
  x-tick-gap .code:n = 
   {
     \tl_set:Nn \l__dataplot_xtic_gap_tl { #1 }
     \tl_if_empty:NF \l__dataplot_xtic_gap_tl
      {
       \DTLxticstrue
       \DTLxaxistrue
      }
   },
%    \end{macrocode}
% Define the gap between $y$ tick marks (yticpoints overrides
% yticgap)
%    \begin{macrocode}
  yticgap .code:n =
   {
     \tl_set:Nn \l__dataplot_ytic_gap_tl { #1 }
     \tl_if_empty:NF \l__dataplot_ytic_gap_tl
      {
       \DTLyticstrue
       \DTLyaxistrue
      }
   },
  y-tick-gap .code:n =
   {
     \tl_set:Nn \l__dataplot_ytic_gap_tl { #1 }
     \tl_if_empty:NF \l__dataplot_ytic_gap_tl
      {
       \DTLyticstrue
       \DTLyaxistrue
      }
   },
%    \end{macrocode}
% Define the gap between $x$ and $y$ tick marks:
%    \begin{macrocode}
  ticgap .code:n =
   {
     \tl_set:Nn \l__dataplot_xtic_gap_tl { #1 }
     \tl_set:Nn \l__dataplot_ytic_gap_tl { #1 }
     \tl_if_empty:NF \l__dataplot_xtic_gap_tl
      {
       \DTLxticstrue
       \DTLxaxistrue
       \DTLyticstrue
       \DTLyaxistrue
      }
   },
  tick-gap .code:n =
   {
     \tl_set:Nn \l__dataplot_xtic_gap_tl { #1 }
     \tl_set:Nn \l__dataplot_ytic_gap_tl { #1 }
     \tl_if_empty:NF \l__dataplot_xtic_gap_tl
      {
       \DTLxticstrue
       \DTLxaxistrue
       \DTLyticstrue
       \DTLyaxistrue
      }
   },
%    \end{macrocode}
% Define comma separated list of labels for $x$ ticks.
%    \begin{macrocode}
  xticlabels .code:n = 
   {
     \tl_if_empty:nTF { #1 }
      {
        \seq_clear:N \l__dataplot_xtic_labels_seq
      }
      {
       \seq_set_from_clist:Nn \l__dataplot_xtic_labels_seq { #1 }
       \DTLxticstrue
       \DTLxaxistrue
      }
   },
  x-tick-labels .code:n = 
   {
     \tl_if_empty:nTF { #1 }
      {
        \seq_clear:N \l__dataplot_xtic_labels_seq
      }
      {
       \seq_set_from_clist:Nn \l__dataplot_xtic_labels_seq { #1 }
       \DTLxticstrue
       \DTLxaxistrue
      }
   },
%    \end{macrocode}
% Define comma separated list of labels for $y$ ticks.
%    \begin{macrocode}
  yticlabels .code:n =
   {
     \tl_if_empty:nTF { #1 }
      {
        \seq_clear:N \l__dataplot_ytic_labels_seq
      }
      {
       \seq_set_from_clist:Nn \l__dataplot_ytic_labels_seq { #1 }
       \DTLyticstrue
       \DTLyaxistrue
      }
   },
  y-tick-labels .code:n =
   {
     \tl_if_empty:nTF { #1 }
      {
        \seq_clear:N \l__dataplot_ytic_labels_seq
      }
      {
       \seq_set_from_clist:Nn \l__dataplot_ytic_labels_seq { #1 }
       \DTLyticstrue
       \DTLyaxistrue
      }
   },
%    \end{macrocode}
% Define $x$ axis label:
%    \begin{macrocode}
  xlabel .tl_set:N = \l__dataplot_xlabel_tl ,
  x-label .tl_set:N = \l__dataplot_xlabel_tl ,
%    \end{macrocode}
% Define $y$ axis label:
%    \begin{macrocode}
  ylabel .tl_set:N = \l__dataplot_ylabel_tl ,
  y-label .tl_set:N = \l__dataplot_ylabel_tl ,
%    \end{macrocode}
% The legend setting may be one of: "none" (don't show it),
% "north", "northeast", "east", "southeast", "south", "southwest",
% "west", or "northwest". These set the count register 
% \cs{dtl@legendsetting}.
%    \begin{macrocode}
  legend .choices:nn =
   {
      none, north, northeast, east, southeast,
      south, southwest, west, northwest, custom
   }
   {
     \int_set:Nn \l__dataplot_legend_setting_int
       { \l_keys_choice_int - 1 }
   },
   legend .default:n = { northeast },
%    \end{macrocode}
% Legend labels (comma separated list). If omitted, the default is used.
%    \begin{macrocode}
   legendlabels .code:n =
    {
      \seq_set_from_clist:Nn
         \l__dataplot_legend_labels_seq { #1 }
    } ,
   legend-labels .code:n =
    {
      \seq_set_from_clist:Nn
         \l__dataplot_legend_labels_seq { #1 }
    } ,
%    \end{macrocode}
% Legend offset may be a single value, in which case it applies to
% both x and y offsets, or two values.
%    \begin{macrocode} 
   legend-offset .code:n =
    {
      \seq_set_from_clist:Nn
        \l__datatool_tmp_seq { #1 }
      \int_case:nnF { \seq_count:N \l__datatool_tmp_seq  }
       {
         { 1 }
           {
             \dim_set:Nn \DTLlegendxoffset
               { \seq_item:Nn \l__datatool_tmp_seq { 1 } }
             \dim_set_eq:Nn \DTLlegendyoffset \DTLlegendxoffset
           }
         { 2 }
           {
             \dim_set:Nn \DTLlegendxoffset
               { \seq_item:Nn \l__datatool_tmp_seq { 1 } }
             \dim_set:Nn \DTLlegendyoffset
               { \seq_item:Nn \l__datatool_tmp_seq { 2 } }
           }
       }
       {
         \PackageError { dataplot }
         {
            Invalid ~ legend-offset ~ value ~ `#1': one ~ or ~ two ~
            dimensions ~ required
         }
         {
           The ~ legend-offset ~ value ~ must ~ be ~ either ~ a ~
           single ~ dimension ~ or ~ two ~ comma-separated ~ dimensions
         }
       }
    },
%    \end{macrocode}
% Set whether or not to enforce side axes:
%    \begin{macrocode} 
  side-axes .bool_set:N = \l__dataplot_side_axes_bool ,
%    \end{macrocode}
% Should the zero labels be omitted:
%    \begin{macrocode} 
  omit-zero-label .choices:nn =
   { auto , both , x , y , false }
   {
     \int_set_eq:NN
       \l__dataplot_omit_zero_label_action_int
       \l_keys_choice_int
   } ,
  omit-zero-label .initial:n = { auto } ,
  omit-zero-label .default:n = { both } ,
%    \end{macrocode}
% Allow the x axis to be extended beyond the plot bounds:
%    \begin{macrocode} 
  extend-x-axis .code:n = 
   {
     \int_case:nnF
        { \clist_count:n { #1 } } 
      {
        { \c_one_int }
          {
            \exp_args:NNx \datatool_set_fp:Nn
              \l__dataplot_extend_min_x_axis_fp
              { \clist_item:nn { #1 } { 1 } }
            \fp_set_eq:NN
              \l__dataplot_extend_max_x_axis_fp
              \l__dataplot_extend_min_x_axis_fp
          }
        { 2 }
          {
            \exp_args:NNx \datatool_set_fp:Nn
              \l__dataplot_extend_min_x_axis_fp
              { \clist_item:nn { #1 } { 1 } }
            \exp_args:NNx \datatool_set_fp:Nn
               \l__dataplot_extend_max_x_axis_fp
              { \clist_item:nn { #1 } { 2 } }
          }
      }
      {
         \PackageError { dataplot }
         {
            Invalid ~ extend-x-axis ~ value ~ `#1': one ~ or ~ two ~
            numbers ~ required
         }
         {
           The ~ extend-x-axis ~ value ~ must ~ be ~ either ~ a ~
           single ~ number ~ or ~ two ~ comma-separated ~ numbers
         }
      }
   },
%    \end{macrocode}
% Allow the y axis to be extended beyond the plot bounds:
%    \begin{macrocode} 
  extend-y-axis .code:n = 
   {
     \int_case:nnF
        { \clist_count:n { #1 } } 
      {
        { \c_one_int }
          {
            \exp_args:NNx \datatool_set_fp:Nn
               \l__dataplot_extend_min_y_axis_fp
              { \clist_item:nn { #1 } { 1 } }
            \fp_set_eq:NN
              \l__dataplot_extend_max_y_axis_fp
              \l__dataplot_extend_min_y_axis_fp
          }
        { 2 }
          {
            \exp_args:NNx \datatool_set_fp:Nn
               \l__dataplot_extend_min_y_axis_fp
              { \clist_item:nn { #1 } { 1 } }
            \exp_args:NNx \datatool_set_fp:Nn
              \l__dataplot_extend_max_y_axis_fp
              { \clist_item:nn { #1 } { 2 } }
          }
      }
      {
         \PackageError { dataplot }
         {
            Invalid ~ extend-y-axis ~ value ~ `#1': one ~ or ~ two ~
            numbers ~ required
         }
         {
           The ~ extend-y-axis ~ value ~ must ~ be ~ either ~ a ~
           single ~ number ~ or ~ two ~ comma-separated ~ numbers
         }
      }
   },
%    \end{macrocode}
% Set the extension for both $x$ and $y$ axes:
%    \begin{macrocode} 
  extend-axes .code:n = 
   {
     \int_case:nnF
        { \clist_count:n { #1 } } 
      {
        { \c_one_int }
          {
            \exp_args:NNx \datatool_set_fp:Nn
              \l__dataplot_extend_min_x_axis_fp
              { \clist_item:nn { #1 } { 1 } }
            \fp_set_eq:NN
              \l__dataplot_extend_max_x_axis_fp
              \l__dataplot_extend_min_x_axis_fp
          }
        { 2 }
          {
            \exp_args:NNx \datatool_set_fp:Nn
              \l__dataplot_extend_min_x_axis_fp
              { \clist_item:nn { #1 } { 1 } }
            \exp_args:NNx \datatool_set_fp:Nn
               \l__dataplot_extend_max_x_axis_fp
              { \clist_item:nn { #1 } { 2 } }
          }
      }
      {
         \PackageError { dataplot }
         {
            Invalid ~ extend-x-axis ~ value ~ `#1': one ~ or ~ two ~
            numbers ~ required
         }
         {
           The ~ extend-x-axis ~ value ~ must ~ be ~ either ~ a ~
           single ~ number ~ or ~ two ~ comma-separated ~ numbers
         }
      }
     \fp_set_eq:NN
       \l__dataplot_extend_min_y_axis_fp
       \l__dataplot_extend_min_x_axis_fp
     \fp_set_eq:NN
       \l__dataplot_extend_max_y_axis_fp
       \l__dataplot_extend_max_x_axis_fp
   },
%    \end{macrocode}
% Label minimum end of the x axis:
%    \begin{macrocode} 
  min-x-label .tl_set:N = \l__dataplot_x_min_label_tl ,
  min-x-label-style .tl_set:N = \l__dataplot_x_min_label_style_tl ,
%    \end{macrocode}
% Label maximum end of the x axis:
%    \begin{macrocode} 
  max-x-label .tl_set:N = \l__dataplot_x_max_label_tl ,
  max-x-label-style .tl_set:N = \l__dataplot_x_max_label_style_tl ,
%    \end{macrocode}
% Label minimum end of the y axis:
%    \begin{macrocode} 
  min-y-label .tl_set:N = \l__dataplot_y_min_label_tl ,
  min-y-label-style .tl_set:N = \l__dataplot_y_min_label_style_tl ,
%    \end{macrocode}
% Label maximum end of the y axis:
%    \begin{macrocode} 
  max-y-label .tl_set:N = \l__dataplot_y_max_label_tl ,
  max-y-label-style .tl_set:N = \l__dataplot_y_max_label_style_tl ,
%    \end{macrocode}
% Style for x tick labels:
%    \begin{macrocode} 
  x-tick-label-style .tl_set:N = \l__dataplot_x_tick_label_style_tl ,
  x-tic-label-style .tl_set:N = \l__dataplot_x_tick_label_style_tl ,
%    \end{macrocode}
% Style for y tick labels:
%    \begin{macrocode} 
  y-tick-label-style .tl_set:N = \l__dataplot_y_tick_label_style_tl ,
  y-tic-label-style .tl_set:N = \l__dataplot_y_tick_label_style_tl ,
%    \end{macrocode}
% Shortcut for both:
%    \begin{macrocode} 
  tick-label-style .code:n =
   {
     \tl_set:Nn \l__dataplot_x_tick_label_style_tl { #1 }
     \tl_set:Nn \l__dataplot_y_tick_label_style_tl { anchor=east, #1 }
   } ,
  tic-label-style .code:n =
   {
     \tl_set:Nn \l__dataplot_x_tick_label_style_tl { #1 }
     \tl_set:Nn \l__dataplot_y_tick_label_style_tl { anchor=east, #1 }
   } ,
%    \end{macrocode}
% Filter:
%    \begin{macrocode} 
  include-if .cs_set:Np = \__dataplot_filter:T #1,
  include-if-fn .code:n =
   {
     \cs_set_eq:NN \__dataplot_filter:T #1
   },
%    \end{macrocode}
% Deprecated experimental setting.
% TODO remove
%    \begin{macrocode} 
  condition .code:n =
   {
     \PackageWarning{dataplot}{Deprecated ~ option ~ `condition'. ~
      Use ~ `include-if' ~ instead}
     \cs_set:Nn = \__dataplot_filter:T { #1 }
   }
}
%    \end{macrocode}
% Allow these keys to be set in \verb|\DTLsetup{plot={...}}|
%    \begin{macrocode}
\keys_define:nn { datatool }
{
  plot .code:n = { \keys_set:nn { datatool/plot } { #1 } }
}
%    \end{macrocode}
%
%\section{Plotting Commands and Hooks}
%
% Calculate the transformation matrix and extent based on 
% \cs{DTLminX}, \cs{DTLminY}, \cs{DTLmaxX}, \cs{DTLmaxY},
% \cs{DTLplotwidth} and \cs{DTLplotheight}. Note that this doesn't
% take the labels and axis extensions into account.
% The $x$-scale factor is given by:
%\[
% s_x = \frac{W}{x_{\max}-x_{\min}}
%\]
% where $W$ is the plot width. The $x$ offset is $-s_x x_{\min}$.
% Similarly for $y$.
%    \begin{macrocode}
\cs_new:Nn \dataplot_calc_transform:
{
  \fp_set:Nn \l__dataplot_x_extent_fp
    { \DTLmaxX - \DTLminX }
  \fp_set:Nn \l__dataplot_scale_x_fp
   {
%    \end{macrocode}
% The width as points:
%    \begin{macrocode}
     \dim_to_decimal:n \DTLplotwidth / \l__dataplot_x_extent_fp
   }
  \fp_set:Nn \l__dataplot_offset_x_fp
   {
     - \l__dataplot_scale_x_fp * \DTLminX
   }
  \fp_set:Nn \l__dataplot_y_extent_fp
   { \DTLmaxY - \DTLminY }
  \fp_set:Nn \l__dataplot_scale_y_fp
   {
%    \end{macrocode}
% The height as points:
%    \begin{macrocode}
     \dim_to_decimal:n \DTLplotheight / \l__dataplot_y_extent_fp
   }
  \fp_set:Nn \l__dataplot_offset_y_fp
   {
     - \l__dataplot_scale_y_fp * \DTLminY
   }
%    \end{macrocode}
% Calculate the inverse scale factors:
%    \begin{macrocode}
  \fp_set:Nn \l__dataplot_inv_scale_x_fp
     { 1 / \l__dataplot_scale_x_fp }
  \fp_set:Nn \l__dataplot_inv_scale_y_fp
     { 1 / \l__dataplot_scale_y_fp }
}
%    \end{macrocode}
%Transform data co-ordinates using the scales and offsets calculated
%above.
%    \begin{macrocode}
\cs_new:Nn \dataplot_transform_data_coords:NN
{
  \fp_set:Nn \l__dataplot_x_fp 
   {
     \l__dataplot_scale_x_fp * #1 + \l__dataplot_offset_x_fp
   }
  \fp_set:Nn \l__dataplot_y_fp 
   {
     \l__dataplot_scale_y_fp * #2 + \l__dataplot_offset_y_fp
   }
  \tl_set:Nx #1 { \fp_to_tl:N \l__dataplot_x_fp }
  \tl_set:Nx #2 { \fp_to_tl:N \l__dataplot_y_fp }
}
%    \end{macrocode}
%Apply pgf transform using the scales and offsets calculated
%above.
%    \begin{macrocode}
\cs_new:Nn \dataplot_apply_pgftransform:
{
  \pgftransformcm
    { \fp_use:N \l__dataplot_scale_x_fp } { 0 }
    { 0 } { \fp_use:N \l__dataplot_scale_y_fp }
    {
      \pgfpoint
       { \fp_to_decimal:N \l__dataplot_offset_x_fp pt }
       { \fp_to_decimal:N \l__dataplot_offset_y_fp pt }
    }
}
%    \end{macrocode}
%Apply inverse pgf transform.
%    \begin{macrocode}
\cs_new:Nn \dataplot_apply_inverse_pgftransform:
{
  \pgftransformcm
    { \fp_use:N \l__dataplot_inv_scale_x_fp } { 0 }
    { 0 } { \fp_use:N \l__dataplot_inv_scale_y_fp }
    {
      \pgfpoint
       { 
         \fp_to_decimal:n 
          { - \l__dataplot_offset_x_fp * \l__dataplot_inv_scale_x_fp }
         pt
       }
       {
          \fp_to_decimal:n
           { - \l__dataplot_offset_y_fp * \l__dataplot_inv_scale_y_fp }
          pt
       }
    }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLplotstream}
%\begin{definition}
%\cs{DTLplotstream}\oarg{condition}\marg{db name}\marg{x key}\marg{y key}
%\end{definition}
% Add points to a stream from the database called \meta{db name}
% where the $x$ co-ordinates are given by the key \meta{x key}
% and the $y$ co-ordinates are given by the key \meta{y key}.
% The optional argument \meta{condition} is the same as that
% for \cs{DTLforeach}
%\changes{1.01}{2007 Aug 17}{uses \cs{@sDTLforeach} instead of
%\cs{DTLforeach}}
%    \begin{macrocode}
\NewDocumentCommand \DTLplotstream { o m m m } 
{
  \DTLmapdata [ name = { #2 } ]
  {
    \DTLmapgetvalues { \l__dataplot_x_tl = #3 , \l__dataplot_y_tl= #4 } 
    \IfValueTF { #1 }
    {
      \ifthenelse { #1 } { \__dataplot_stream: } { }
    }
    {
      \__dataplot_stream:
    }
  }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new:Nn \__dataplot_stream:
{
  \DTLconverttodecimal
    { \l__dataplot_x_tl } { \l__dataplot_decimal_x_tl }
  \DTLconverttodecimal
    { \l__dataplot_y_tl } { \l__dataplot_decimal_y_tl }
  \dataplot_transform_data_coords:NN
    \l__dataplot_decimal_x_tl
    \l__dataplot_decimal_y_tl
  \pgfplotstreampoint
   {
     \pgfpointxy
      { \l__dataplot_decimal_x_tl }
      { \l__dataplot_decimal_y_tl }
   }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLplotatbegintikz}
% \cs{DTLplotatbegintikz} is a hook to insert stuff at the 
% start of the \env{tikzpicture}
% environment (after the unit vectors have been set).
%    \begin{macrocode}
\newcommand*{\DTLplotatbegintikz}{}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\dtlplothandlermark}
%\changes{2.15}{2013-07-10}{new}
%    \begin{macrocode}
\newcommand*{\dtlplothandlermark}[1]{
  \PackageWarning { dataplot }
   {
     \string \dtlplothandlermark \space ~
     found ~ outside ~ \string \DTLplot
   }
  \pgfplothandlermark { #1 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\@dtlplothandlermark}
%    \begin{macrocode}
\newcommand*{\@dtlplothandlermark}[1]{
  \pgftransformreset
  \tl_set:Nn \__dataplot_post_at_begin_tl
   {
     \dataplot_apply_pgftransform:
   }
  \pgfplothandlermark { #1 }
}
\tl_new:N \__dataplot_post_at_begin_tl
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\DTLplotatendtikz}
% \cs{DTLplotatendtikz} is a hook to insert stuff at the end 
% of the \env{tikzpicture} environment.
%    \begin{macrocode}
\newcommand*{\DTLplotatendtikz}{}
%    \end{macrocode}
%\end{macro}
%
%Obtain the plot bounds, either from the settings (if provided) or
%calculate from the data. This will set token list variables
%\cs{DTLminX}, \cs{DTLminY}, \cs{DTLmaxX} and \cs{DTLmaxY} as
%well as floating point variables:
%\cs{l\_\_dataplot\_min\_x\_fp}, \cs{l\_\_dataplot\_min\_y\_fp},
%\cs{l\_\_dataplot\_max\_x\_fp}, \cs{l\_\_dataplot\_max\_y\_fp}
% to avoid repeatedly converting between the two representations.
%The arguments are the column keys for the x and y data.
%    \begin{macrocode}
\cs_new:Nn \__dataplot_update_bounds: 
{
  \bool_lazy_all:nTF
   {
     { \bool_not_p:n { \tl_if_empty_p:N \l__dataplot_min_x_tl } }
     { \bool_not_p:n { \tl_if_empty_p:N \l__dataplot_min_y_tl } }
     { \bool_not_p:n { \tl_if_empty_p:N \l__dataplot_max_x_tl } }
     { \bool_not_p:n { \tl_if_empty_p:N \l__dataplot_max_y_tl } }
   }
  {
%    \end{macrocode}
%Store the numbers as floating point variables to save
%repeated parsing.
%    \begin{macrocode}
    \datatool_set_fp:Nn \l__dataplot_min_x_fp { \l__dataplot_min_x_tl }
    \datatool_set_fp:Nn \l__dataplot_min_y_fp { \l__dataplot_min_y_tl }
    \datatool_set_fp:Nn \l__dataplot_max_x_fp { \l__dataplot_max_x_tl }
    \datatool_set_fp:Nn \l__dataplot_max_y_fp { \l__dataplot_max_y_tl }
%    \end{macrocode}
%Placeholders for use in hooks.
%    \begin{macrocode}
    \tl_set:Nx \DTLminX { \fp_to_tl:N \l__dataplot_min_x_fp }
    \tl_set:Nx \DTLminY { \fp_to_tl:N \l__dataplot_min_y_fp }
    \tl_set:Nx \DTLmaxX { \fp_to_tl:N \l__dataplot_max_x_fp }
    \tl_set:Nx \DTLmaxY { \fp_to_tl:N \l__dataplot_max_y_fp }
  }
  {
    \tl_set_eq:NN \l__dataplot_min_x_fp \c_novalue_tl 
    \tl_set_eq:NN \l__dataplot_min_y_fp \c_novalue_tl
    \tl_set_eq:NN \l__dataplot_max_x_fp \c_novalue_tl
    \tl_set_eq:NN \l__dataplot_max_y_fp \c_novalue_tl
    \seq_map_inline:Nn \l__dataplot_dbnames_seq
    {
      \DTLsetup{ default-name = { ##1 } }
      \__dataplot_filtered_map:n
      {
        \datatool_set_fp:Nn \l__dataplot_x_fp
           { \l__dataplot_x_tl }
        \datatool_set_fp:Nn \l__dataplot_y_fp
           { \l__dataplot_y_tl }
        \exp_args:NV \tl_if_novalue:nTF \l__dataplot_min_x_fp 
        {
          \tl_if_empty:NT \l__dataplot_min_x_tl
          {
            \fp_set_eq:NN
               \l__dataplot_min_x_fp
               \l__dataplot_x_fp
          }
        }
        {
          \fp_set:Nn \l__dataplot_min_x_fp
            { min ( \l__dataplot_min_x_fp , \l__dataplot_x_fp ) }
        }
        \exp_args:NV \tl_if_novalue:nTF \l__dataplot_min_y_fp
        {
          \tl_if_empty:NT \l__dataplot_min_y_tl
          {
            \fp_set_eq:NN
              \l__dataplot_min_y_fp
              \l__dataplot_y_fp 
          }
        }
        {
          \fp_set:Nn \l__dataplot_min_y_fp
            { min ( \l__dataplot_min_y_fp , \l__dataplot_y_fp ) }
        }
        \exp_args:NV \tl_if_novalue:nTF \l__dataplot_max_x_fp
        {
          \tl_if_empty:NT \l__dataplot_max_x_tl
          {
            \fp_set_eq:NN
              \l__dataplot_max_x_fp
              \l__dataplot_x_fp
          }
        }
        {
          \fp_set:Nn \l__dataplot_max_x_fp
            { max ( \l__dataplot_max_x_fp , \l__dataplot_x_fp ) }
        }
        \exp_args:NV \tl_if_novalue:nTF \l__dataplot_max_y_fp
        {
          \tl_if_empty:NT \l__dataplot_max_y_tl
          {
            \fp_set_eq:NN
              \l__dataplot_max_y_fp
              \l__dataplot_y_fp
          }
        }
        {
          \fp_set:Nn \l__dataplot_max_y_fp
            { max ( \l__dataplot_max_y_fp , \l__dataplot_y_fp ) }
        }
      }
    }
%    \end{macrocode}
% Update \cs{DTLminX}
%    \begin{macrocode}
    \exp_args:NV \tl_if_novalue:nT \l__dataplot_min_x_fp
     {
       \tl_if_empty:NTF \l__dataplot_min_x_tl
       {
         \PackageError { dataplot }
         { No ~ minimum ~ X ~ available! }
         { 
           Check ~ that ~ your ~ filter ~ criteria ~ 
           hasn't ~ excluded ~ all ~ data
         }
         \fp_zero:N \l__dataplot_min_x_fp
         \cs_set_eq:NN \__dataplot_do_plot:n \use_none:n
       }
       {
         \datatool_set_fp:Nn \l__dataplot_min_x_fp
           { \l__dataplot_min_x_tl }
       }
     }
    \tl_set:Nx \DTLminX { \fp_to_tl:N \l__dataplot_min_x_fp }
%    \end{macrocode}
% Update \cs{DTLminY}
%    \begin{macrocode}
    \exp_args:NV \tl_if_novalue:nT \l__dataplot_min_y_fp
     {
       \tl_if_empty:NTF \l__dataplot_min_y_tl
       {
         \PackageError { dataplot }
         { No ~ minimum ~ Y ~ available! }
         { 
           Check ~ that ~ your ~ filter ~ criteria ~ 
           hasn't ~ excluded ~ all ~ data
         }
         \fp_zero:N \l__dataplot_min_y_fp
         \cs_set_eq:NN \__dataplot_do_plot:n \use_none:n
       }
       {
         \datatool_set_fp:Nn \l__dataplot_min_y_fp
           { \l__dataplot_min_y_tl }
       }
     }
    \tl_set:Nx \DTLminY { \fp_to_tl:N \l__dataplot_min_y_fp }
%    \end{macrocode}
% Update \cs{DTLmaxX}
%    \begin{macrocode}
    \exp_args:NV \tl_if_novalue:nT \l__dataplot_max_x_fp
     {
       \tl_if_empty:NTF \l__dataplot_max_x_tl
       {
         \PackageError { dataplot }
         { No ~ maximum ~ X ~ available! }
         { 
           Check ~ that ~ your ~ filter ~ criteria ~ 
           hasn't ~ excluded ~ all ~ data
         }
         \fp_zero:N \l__dataplot_max_x_fp
         \cs_set_eq:NN \__dataplot_do_plot:n \use_none:n
       }
       {
         \datatool_set_fp:Nn \l__dataplot_max_x_fp
           { \l__dataplot_max_x_tl }
       }
     }
    \tl_set:Nx \DTLmaxX { \fp_use:N \l__dataplot_max_x_fp }
%    \end{macrocode}
% Update \cs{DTLmaxY}
%    \begin{macrocode}
    \exp_args:NV \tl_if_novalue:nT \l__dataplot_max_y_fp
     {
       \tl_if_empty:NTF \l__dataplot_max_y_tl
       {
         \PackageError { dataplot }
         { No ~ maximum ~ Y ~ available! }
         { 
           Check ~ that ~ your ~ filter ~ criteria ~ 
           hasn't ~ excluded ~ all ~ data
         }
         \fp_zero:N \l__dataplot_max_y_fp
         \cs_set_eq:NN \__dataplot_do_plot:n \use_none:n
       }
       {
         \datatool_set_fp:Nn \l__dataplot_max_y_fp
           { \l__dataplot_max_y_tl }
       }
    }
    \tl_set:Nx \DTLmaxY { \fp_use:N \l__dataplot_max_y_fp }
  }
%    \end{macrocode}
% Don't bother checking if an error has already occurred.
%    \begin{macrocode}
  \__dataplot_do_plot:n 
  {
    \fp_compare:nNnT
      { \l__dataplot_min_x_fp } > { \l__dataplot_max_x_fp }
    {
       \PackageError {dataplot}
       {
         Min ~ X ~ ( \fp_use:N \l__dataplot_min_x_fp ) ~
         > ~ Max ~ X ~ ( \fp_use:N \l__dataplot_min_x_fp )
       }
       {
         If ~ you ~ have used ~ the ~ `bounds' ~ setting ~, check ~
         that ~ it ~ is ~ specified ~ as ~ minX,minY,maxX,maxY
       }
       \cs_set_eq:NN \__dataplot_do_plot:n \use_none:n
    }
    \fp_compare:nNnT
      { \l__dataplot_min_y_fp } > { \l__dataplot_max_y_fp }
    {
       \PackageError {dataplot}
       {
         Min ~ Y ~ ( \fp_use:N \l__dataplot_min_y_fp ) ~
         > ~ Max ~ Y ~ ( \fp_use:N \l__dataplot_min_y_fp )
       }
       {
         If ~ you ~ have used ~ the ~ `bounds' ~ setting ~, check ~
         that ~ it ~ is ~ specified ~ as ~ minX,minY,maxX,maxY
       }
       \cs_set_eq:NN \__dataplot_do_plot:n \use_none:n
    }
  }
}
%    \end{macrocode}
%
%\begin{definition}
%\cs{\_\_dataplot\_x\_y\_bounded:nnnnnnTF}
%\marg{x}\marg{min x}\marg{max x}
%\marg{y}\marg{min y}\marg{max y}
%\marg{true}\marg{false}
%\end{definition}
%Test if x is between min x and max x and y is between min y and max
%y
%    \begin{macrocode}
\cs_new:Nn \__dataplot_x_y_bounded:nnnnnnTF
{
  \bool_lazy_any:nTF
   {
     { \fp_compare_p:n { #1 < #2 } } 
     { \fp_compare_p:n { #1 > #3 } } 
     { \fp_compare_p:n { #4 < #5 } } 
     { \fp_compare_p:n { #4 > #6 } } 
   }
  { #8 } { #7 }
}
\cs_new:Nn \__dataplot_x_y_bounded:nnnnnnT
{
  \__dataplot_x_y_bounded:nnnnnnTF
  { #1 } { #2 } { #3 } { #4 } { #5 } { #6 } { #7 } { }
}
%    \end{macrocode}
%
% Tick lists.
%    \begin{macrocode}
\fp_new:N \l__dataplot_min_gap_fp
%    \end{macrocode}
%Calculate $x$ tick gap or construct tick list if x tics required.
%    \begin{macrocode}
\cs_new:Nn \__dataplot_calc_x_tics:
{
 \legacy_if:nT { DTLxtics }
  {
    \clist_if_empty:NTF \l__dataplot_x_tic_clist
    {
      \seq_clear:N \l__dataplot_x_tic_seq
      \tl_if_empty:NTF \l__dataplot_xtic_gap_tl
      {
%    \end{macrocode}
%No tick list or gap set by user.
%Get the min tick gap in data co-ordinates
%    \begin{macrocode}
        \fp_set:Nn \l__dataplot_min_gap_fp
        {
          ( 
            ( \DTLmintickgap - \l__dataplot_offset_x_fp )
              / \l__dataplot_scale_x_fp
          )
          / 65536
        }
%    \end{macrocode}
% construct tick list
%    \begin{macrocode}
        \__dataplot_construct_tick_list:NNNN
          \l__dataplot_min_x_fp 
          \l__dataplot_max_x_fp 
          \l__dataplot_min_gap_fp 
          \l__dataplot_x_tic_seq
      }
      {
%    \end{macrocode}
%Gap set by user
%    \begin{macrocode}
        \fp_set:Nn 
          \l__dataplot_min_gap_fp 
          { \l__dataplot_xtic_gap_tl }
        \fp_compare:nTF
          { \l__dataplot_min_x_fp < \c_zero_fp < \l__dataplot_max_x_fp }
        {
          \__dataplot_construct_tick_list_over_zero:NNNN
            \l__dataplot_min_x_fp 
            \l__dataplot_max_x_fp 
            \l__dataplot_x_tic_seq
            \l__dataplot_min_gap_fp 
        }
        {
          \__dataplot_construct_tick_list_with_gap:NNNN
            \l__dataplot_min_x_fp 
            \l__dataplot_max_x_fp 
            \l__dataplot_x_tic_seq
            \l__dataplot_min_gap_fp 
        }
      }
    }
    {
%    \end{macrocode}
% Check that the provided tick marks are within the bounds and save
% to sequence.
%    \begin{macrocode}
      \__dataplot_to_fp_seq:NNnn 
        \l__dataplot_x_tic_seq
        \l__dataplot_x_tic_clist
        \l__dataplot_min_x_fp 
        \l__dataplot_max_x_fp 
    }
    \__dataplot_calc_minor_x_tics:
    \__dataplot_calc_x_label_dim:
  }
}
%    \end{macrocode}
% Minor x ticks:
%    \begin{macrocode}
\cs_new:Nn \__dataplot_calc_minor_x_tics:
{
  \dim_zero:N \l__dataplot_x_tic_label_height_dim
%    \end{macrocode}
% Construct a list of $x$ minor tick points if required
%    \begin{macrocode}
  \seq_clear:N \l__dataplot_x_minor_tic_seq
  \legacy_if:nT { DTLxminortics }
   {
    \__dataplot_construct_minor_ticks:NNNNN
      \l__dataplot_x_tic_seq
      \l__dataplot_min_x_fp
      \l__dataplot_max_x_fp
      \l__dataplot_scale_x_fp
      \l__dataplot_x_minor_tic_seq
  }
}
%    \end{macrocode}
%
%\begin{definition}
%\cs{\_\_dataplot\_get\_default\_tic\_label:NN}
%\meta{fp-var}\meta{int-var}
%\end{definition}
%Get the default tic label for position \meta{fp-var} and rounding
%value \meta{int-var}
%    \begin{macrocode}
\cs_new:Nn \__dataplot_get_default_tic_label:Nn
{
  \fp_set:Nn \l__datatool_tmpa_fp
    {
      round ( #1 , #2 )
    }
  \tl_set:Nx \l__dataplot_tic_label_tl
   { \fp_to_decimal:N \l__datatool_tmpa_fp }
  \datatool_pad_trailing_zeros:Nn
    \l__dataplot_tic_label_tl { #2 }
  \exp_args:NV \DTLdecimaltolocale \l__dataplot_tic_label_tl
   \l__dataplot_tmpa_tl
  \tl_set:Nx \l__dataplot_tic_label_tl
   {
     \exp_not:N \__datatool_datum:nnnn
      { \exp_not:V \l__dataplot_tic_label_tl }
      { \exp_not:V \l__dataplot_tmpa_tl }
      { }
      { \c_datatool_decimal_int }
   }
}
\cs_generate_variant:Nn \__dataplot_get_default_tic_label:Nn
 { NV , Nx }
%    \end{macrocode}
% Determine the height of the $x$ tick labels and save default tick
% labels if required.
%    \begin{macrocode}
\cs_new:Nn \__dataplot_calc_x_label_dim:
{
  \seq_if_empty:NTF \l__dataplot_xtic_labels_seq
  {
    \seq_map_inline:Nn \l__dataplot_x_tic_seq
     {
%    \end{macrocode}
% Tick label not provided, use the $x$ value.
% Reconstruct the floating point variable:
%    \begin{macrocode}
       \tl_set:Nn \l__datatool_tmpa_fp { ##1 }
%    \end{macrocode}
% Obtain the default label:
%    \begin{macrocode}
       \__dataplot_get_default_tic_label:NV
         \l__datatool_tmpa_fp \c@DTLplotroundXvar
%    \end{macrocode}
% Save for later:
%    \begin{macrocode}
       \seq_put_right:NV \l__dataplot_xtic_labels_seq
         \l__dataplot_tic_label_tl
%    \end{macrocode}
% Calculate height:
%    \begin{macrocode}
       \settoheight \dtl@tmplength
         { \l__dataplot_tic_label_tl }
%    \end{macrocode}
% Compare with current max height:
%    \begin{macrocode}
       \dim_compare:nNnT
         { \dtl@tmplength }
          >
         { \l__dataplot_x_tic_label_height_dim }
        {
          \dim_set_eq:NN
            \l__dataplot_x_tic_label_height_dim \dtl@tmplength
        }
     }
  }
  {
    \seq_map_inline:Nn  \l__dataplot_xtic_labels_seq
     {
       \datatool_measure_height:Nn \dtl@tmplength
         { \DTLplotdisplayXticklabel { ##1 } }
       \dim_compare:nNnT
         { \dtl@tmplength } > { \l__dataplot_x_tic_label_height_dim }
        {
          \dim_set_eq:NN \l__dataplot_x_tic_label_height_dim \dtl@tmplength
        }
     }
  }
}
%    \end{macrocode}
%Calculate $y$ tick gap or construct tick list if y tics required.
%    \begin{macrocode}
\cs_new:Nn \__dataplot_calc_y_tics:
{
 \dim_zero:N \l__dataplot_y_tic_label_width_dim
 \legacy_if:nT { DTLytics }
  {
    \clist_if_empty:NTF \l__dataplot_y_tic_clist
    {
      \seq_clear:N \l__dataplot_y_tic_seq
%    \end{macrocode}
% List of y tics not provided.
%    \begin{macrocode}
      \tl_if_empty:NTF \l__dataplot_ytic_gap_tl
      {
%    \end{macrocode}
% No y tic gap not provided.
% Get the min tick gap in data co-ordinates.
%    \begin{macrocode}
        \fp_set:Nn \l__dataplot_min_gap_fp
        {
          ( 
            ( \DTLmintickgap - \l__dataplot_offset_y_fp )
              / \l__dataplot_scale_y_fp
          )
          / 65536
        }
%    \end{macrocode}
% Construct tick list
%    \begin{macrocode}
        \__dataplot_construct_tick_list:NNNN
          \l__dataplot_min_y_fp 
          \l__dataplot_max_y_fp 
          \l__dataplot_min_gap_fp 
          \l__dataplot_y_tic_seq
      }
      {
%    \end{macrocode}
%Gap set by user
%    \begin{macrocode}
        \fp_set:Nn
         \l__dataplot_min_gap_fp 
         { \l__dataplot_ytic_gap_tl }
        \fp_compare:nTF
          { \l__dataplot_min_y_fp < \c_zero_fp < \l__dataplot_max_y_fp }
        {
          \__dataplot_construct_tick_list_over_zero:NNNN
            \l__dataplot_min_y_fp 
            \l__dataplot_max_y_fp 
            \l__dataplot_y_tic_seq
            \l__dataplot_min_gap_fp 
        }
        {
          \__dataplot_construct_tick_list_with_gap:NNNN
            \l__dataplot_min_y_fp 
            \l__dataplot_max_y_fp 
            \l__dataplot_y_tic_seq
            \l__dataplot_min_gap_fp 
        }
      }
    }
    {
%    \end{macrocode}
% Check that the provided tick marks are within the bounds and save
% to sequence.
%    \begin{macrocode}
      \__dataplot_to_fp_seq:NNnn 
        \l__dataplot_y_tic_seq
        \l__dataplot_y_tic_clist
        \l__dataplot_min_y_fp 
        \l__dataplot_max_y_fp 
    }
    \__dataplot_calc_minor_y_tics:
    \__dataplot_calc_y_label_dim:
  }
}
%    \end{macrocode}
%
% Minor y ticks:
%    \begin{macrocode}
\cs_new:Nn \__dataplot_calc_minor_y_tics:
{
 \seq_clear:N \l__dataplot_y_minor_tic_seq
 \legacy_if:nT { DTLyminortics }
  {
    \__dataplot_construct_minor_ticks:NNNNN
      \l__dataplot_y_tic_seq
      \l__dataplot_min_y_fp
      \l__dataplot_max_y_fp
      \l__dataplot_scale_y_fp
      \l__dataplot_y_minor_tic_seq
  }
}
%    \end{macrocode}
% Determine the width of the $y$ tick labels and save default tick
% labels if required.
%    \begin{macrocode}
\cs_new:Nn \__dataplot_calc_y_label_dim:
{
  \seq_if_empty:NTF\l__dataplot_ytic_labels_seq
  {
    \seq_map_inline:Nn \l__dataplot_y_tic_seq
     {
%    \end{macrocode}
% Tick label not provided, use the $y$ value.
% Reconstruct the floating point variable:
%    \begin{macrocode}
       \tl_set:Nn \l__datatool_tmpa_fp { ##1 }
%    \end{macrocode}
% Obtain the default label:
%    \begin{macrocode}
       \__dataplot_get_default_tic_label:NV
         \l__datatool_tmpa_fp \c@DTLplotroundYvar
%    \end{macrocode}
% Save for later:
%    \begin{macrocode}
       \seq_put_right:NV \l__dataplot_ytic_labels_seq
         \l__dataplot_tic_label_tl
%    \end{macrocode}
% Calculate width:
%    \begin{macrocode}
       \settowidth \dtl@tmplength 
         { \l__dataplot_tic_label_tl }
%    \end{macrocode}
% Compare with current max width:
%    \begin{macrocode}
       \dim_compare:nNnT
          { \dtl@tmplength }
           >
          { \l__dataplot_y_tic_label_width_dim }
        {
          \dim_set_eq:NN \l__dataplot_y_tic_label_width_dim \dtl@tmplength
        }
     }
  }
  {
    \seq_map_inline:Nn  \l__dataplot_ytic_labels_seq
     {
       \datatool_measure_width:Nn \dtl@tmplength
         { \DTLplotdisplayYticklabel { ##1 } }
       \dim_compare:nNnT
           { \dtl@tmplength }
            >
           { \l__dataplot_y_tic_label_width_dim }
        {
          \dim_set_eq:NN \l__dataplot_y_tic_label_width_dim \dtl@tmplength
        }
     }
  }
}
%    \end{macrocode}
%
%Do the plot:
%    \begin{macrocode}
\cs_new:Nn \__dataplot_do_plot:
{
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
  \tl_clear:N \l__dataplot_content_tl
  \tl_clear:N \l_dataplot_legend_tl
%    \end{macrocode}
% Enable \cs{dtlplothandlermark} for the begin and end hooks.
%    \begin{macrocode}
  \tl_clear:N \__dataplot_post_at_begin_tl
  \tl_set_eq:NN \dtlplothandlermark \@dtlplothandlermark
%    \end{macrocode}
% Start the picture.
%    \begin{macrocode}
  \begin{tikzpicture}
%    \end{macrocode}
% Set the $x$ and $y$ unit vectors.
%    \begin{macrocode}
    \pgfsetxvec{\pgfpoint{1pt}{0pt}}%
    \pgfsetyvec{\pgfpoint{0pt}{1pt}}%
%    \end{macrocode}
% Set the transformation matrix.
%    \begin{macrocode}
    \dataplot_apply_pgftransform:
%    \end{macrocode}
% Initial hook.
%    \begin{macrocode}
    \DTLplotatbegintikz
    \__dataplot_post_at_begin_tl
%    \end{macrocode}
% Plot grid if required
%    \begin{macrocode}
    \__dataplot_add_grid:
%    \end{macrocode}
% Plot box or axes:
%    \begin{macrocode}
    \__dataplot_add_axes:
%    \end{macrocode}
% Plot $x$ tics if required.
%    \begin{macrocode}
    \__dataplot_add_x_tics:
%    \end{macrocode}
% Plot $x$ label if required.
%    \begin{macrocode}
    \__dataplot_add_x_label:
%    \end{macrocode}
% Plot the $x$ minor ticks if required
%    \begin{macrocode}
    \__dataplot_add_minor_x:
%    \end{macrocode}
% Plot $y$ tics if required.
%    \begin{macrocode}
    \__dataplot_add_y_tics:
%    \end{macrocode}
% Plot $y$ label if required.
%    \begin{macrocode}
    \__dataplot_add_y_label:
%    \end{macrocode}
% Plot the $y$ minor ticks if required
%    \begin{macrocode}
    \__dataplot_add_minor_y:
%    \end{macrocode}
% Do the accumulated plot instructions if any remaining:
%    \begin{macrocode}
    \l__dataplot_content_tl
    \tl_clear:N \l__dataplot_content_tl
%    \end{macrocode}
% Reset transformation matrix. (Don't want marker shapes to be
% scaled or skewed.)
%    \begin{macrocode}
   \begin{scope}
    \pgftransformreset
%    \end{macrocode}
% Draw marks and lines for each database and the legend:
%    \begin{macrocode}
    \__dataplot_plot_data:
%    \end{macrocode}
%Retain local assignments after scope:
%    \begin{macrocode}
    \tl_put_right:Nx \l__dataplot_content_tl
     {
%    \end{macrocode}
%End current scope:
%    \begin{macrocode}
       \exp_not:N \end{scope}
       \exp_not:N \tl_set:Nn \exp_not:N \DTLminX { \DTLminX }
       \exp_not:N \tl_set:Nn \exp_not:N \DTLminY { \DTLminY }
       \exp_not:N \tl_set:Nn \exp_not:N \DTLmaxX { \DTLmaxX }
       \exp_not:N \tl_set:Nn \exp_not:N \DTLmaxY { \DTLmaxY }
       \exp_not:N \int_set:Nn
        \exp_not:N \l_dataplot_stream_index_int
           { \int_use:N \l_dataplot_stream_index_int }
     }
    \l__dataplot_content_tl
    \tl_clear:N \l__dataplot_content_tl
%    \end{macrocode}
% End hook
%    \begin{macrocode}
    \DTLplotatendtikz
%    \end{macrocode}
%Retain local assignments after scope:
%    \begin{macrocode}
    \tl_put_right:Nx \l__dataplot_content_tl
     {
%    \end{macrocode}
%End \env{tikzpicture} environment:
%    \begin{macrocode}
       \exp_not:N \end{tikzpicture}
       \exp_not:N \tl_set:Nn \exp_not:N \DTLminX { \DTLminX }
       \exp_not:N \tl_set:Nn \exp_not:N \DTLminY { \DTLminY }
       \exp_not:N \tl_set:Nn \exp_not:N \DTLmaxX { \DTLmaxX }
       \exp_not:N \tl_set:Nn \exp_not:N \DTLmaxY { \DTLmaxY }
       \exp_not:N \int_set:Nn
        \exp_not:N \l_dataplot_stream_index_int
           { \int_use:N \l_dataplot_stream_index_int }
     }
    \l__dataplot_content_tl
    \tl_clear:N \l__dataplot_content_tl
}
%    \end{macrocode}
%Add box or axes:
%    \begin{macrocode}
\cs_new:Nn \__dataplot_add_axes:
{
%    \end{macrocode}
% Determine whether to put a box around the plot
%    \begin{macrocode}
 \legacy_if:nT { DTLbox }
  {
    \tl_put_right:Nx \l__dataplot_content_tl
     {
       \exp_not:N \draw
        ( 
          \fp_to_decimal:N \l__dataplot_extended_min_x_fp ,
          \fp_to_decimal:N \l__dataplot_extended_min_y_fp
        ) ~
         -- ~
        (
          \fp_to_decimal:N \l__dataplot_extended_max_x_fp ,
          \fp_to_decimal:N \l__dataplot_extended_min_y_fp
        ) ~
          -- ~
        (
          \fp_to_decimal:N \l__dataplot_extended_max_x_fp ,
          \fp_to_decimal:N \l__dataplot_extended_max_y_fp
        ) ~
          -- ~
        (
          \fp_to_decimal:N \l__dataplot_extended_min_x_fp ,
          \fp_to_decimal:N \l__dataplot_extended_max_y_fp
        ) ~
         -- ~ cycle ; ~
     }
  }
%    \end{macrocode}
% Plot $x$ axis if required.
%    \begin{macrocode}
 \legacy_if:nT { DTLxaxis }
  {
    \tl_put_right:Nx \l__dataplot_content_tl
     {
       \exp_not:N \draw [ \exp_not:V \DTLXAxisStyle ]
     }
    \bool_if:NTF \l__dataplot_zero_y_axis_bool
     {
%    \end{macrocode}
% $x$ axis passes through $y=0$.
%    \begin{macrocode}
      \tl_put_right:Nx \l__dataplot_content_tl
       {
         (
           \fp_to_decimal:N \l__dataplot_extended_min_x_fp ,
          0 )
       }
      \tl_if_empty:NF \l__dataplot_x_min_label_tl
       {
         \tl_put_right:Nx \l__dataplot_content_tl
          {
            ~ node [ \exp_not:V \l__dataplot_x_min_label_style_tl ]
            { \exp_not:V \l__dataplot_x_min_label_tl }
          }
       }
      \tl_put_right:Nx \l__dataplot_content_tl
       {
          --
          (
           \fp_to_decimal:N \l__dataplot_extended_max_x_fp ,
          0 )
       }
     }
     {
%    \end{macrocode}
% $x$ axis passes through $y=\min_y$.
%    \begin{macrocode}
      \tl_put_right:Nx \l__dataplot_content_tl
       {
         (
           \fp_to_decimal:N \l__dataplot_extended_min_x_fp ,
           \DTLminY
         )
       }
      \tl_if_empty:NF \l__dataplot_x_min_label_tl
       {
         \tl_put_right:Nx \l__dataplot_content_tl
          {
            ~ node [ \exp_not:V \l__dataplot_x_min_label_style_tl ]
            { \exp_not:V \l__dataplot_x_min_label_tl }
          }
       }
      \tl_put_right:Nx \l__dataplot_content_tl
       {
         --
         (
          \fp_to_decimal:N \l__dataplot_extended_max_x_fp ,
          \DTLminY
         )
       }
     }
    \tl_if_empty:NF \l__dataplot_x_max_label_tl
     {
       \tl_put_right:Nx \l__dataplot_content_tl
        {
          ~ node [ \exp_not:V \l__dataplot_x_max_label_style_tl ]
          { \exp_not:V \l__dataplot_x_max_label_tl }
        }
     }
    \tl_put_right:Nn \l__dataplot_content_tl
     { ; ~ }
  }
%    \end{macrocode}
% Plot $y$ axis if required.
%    \begin{macrocode}
 \legacy_if:nT { DTLyaxis }
  {
    \tl_put_right:Nx \l__dataplot_content_tl
     {
       \exp_not:N \draw [ \exp_not:V \DTLYAxisStyle ]
     }
    \bool_if:NTF \l__dataplot_zero_x_axis_bool
     {
%    \end{macrocode}
% $y$ axis passes through $x=0$.
%    \begin{macrocode}
      \tl_put_right:Nx \l__dataplot_content_tl
       {
         (0,
           \fp_to_decimal:N \l__dataplot_extended_min_y_fp
         )
       }
      \tl_if_empty:NF \l__dataplot_y_min_label_tl
       {
         \tl_put_right:Nx \l__dataplot_content_tl
          {
            ~ node [ \exp_not:V \l__dataplot_y_min_label_style_tl ]
            { \exp_not:V \l__dataplot_y_min_label_tl }
          }
       }
      \tl_put_right:Nx \l__dataplot_content_tl
       {
          --
         (0,
          \fp_to_decimal:N \l__dataplot_extended_max_y_fp
         )
       }
     }
     {
%    \end{macrocode}
% $y$ axis passes through $x=\min_x$.
%    \begin{macrocode}
      \tl_put_right:Nx \l__dataplot_content_tl
       {
         (\DTLminX,
           \fp_to_decimal:N \l__dataplot_extended_min_y_fp
         )
       }
      \tl_if_empty:NF \l__dataplot_y_min_label_tl
       {
         \tl_put_right:Nx \l__dataplot_content_tl
          {
            ~ node [ \exp_not:V \l__dataplot_y_min_label_style_tl ]
            { \exp_not:V \l__dataplot_y_min_label_tl }
          }
       }
      \tl_put_right:Nx \l__dataplot_content_tl
       {
          --
         (\DTLminX,
           \fp_to_decimal:N \l__dataplot_extended_max_y_fp
         )
       }
     }
    \tl_if_empty:NF \l__dataplot_y_max_label_tl
     {
       \tl_put_right:Nx \l__dataplot_content_tl
        {
          ~ node [ \exp_not:V \l__dataplot_y_max_label_style_tl ]
          { \exp_not:V \l__dataplot_y_max_label_tl }
        }
     }
    \tl_put_right:Nn \l__dataplot_content_tl
     { ; ~ }
  }
 \l__dataplot_content_tl
 \tl_clear:N \l__dataplot_content_tl
}
%    \end{macrocode}
%Plot grid if required:
%    \begin{macrocode}
\cs_new:Nn \__dataplot_add_grid:
{
  \legacy_if:nT { DTLgrid }
   {
%    \end{macrocode}
% Draw minor grid lines if minor ticks enabled and if there is a
% line style set.
%    \begin{macrocode}
   \tl_if_empty:NF \DTLminorgridstyle
    {
      \legacy_if:nT { DTLxminortics }
       {
%    \end{macrocode}
% Iterate through minor x ticks.
%    \begin{macrocode}
         \seq_map_inline:Nn \l__dataplot_x_minor_tic_seq
          {
%    \end{macrocode}
% Reconstruct floating point variable.
%    \begin{macrocode}
           \tl_set:Nn \l__dataplot_x_fp { ##1 }
           \tl_set:Nx \l__dataplot_x_tl
             { \fp_to_decimal:N \l__dataplot_x_fp }
%    \end{macrocode}
% Draw minor grid line.
%    \begin{macrocode}
           \tl_put_right:Nx \l__dataplot_content_tl
            {
              \exp_not:N \draw [ \exp_not:V \DTLminorgridstyle ]
              ( \l__dataplot_x_tl ,
                \fp_to_decimal:N \l__dataplot_extended_min_y_fp
              )
              --
              ( \l__dataplot_x_tl,
                \fp_to_decimal:N \l__dataplot_extended_max_y_fp
              ); ~
            }
          }
       }
      \legacy_if:nT { DTLyminortics }
       {
%    \end{macrocode}
% Iterate through minor y ticks.
%    \begin{macrocode}
         \seq_map_inline:Nn \l__dataplot_y_minor_tic_seq
          {
%    \end{macrocode}
% Reconstruct floating point variable.
%    \begin{macrocode}
           \tl_set:Nn \l__dataplot_y_fp { ##1 }
           \tl_set:Nx \l__dataplot_y_tl
             { \fp_to_decimal:N \l__dataplot_y_fp }
%    \end{macrocode}
% Draw minor grid line.
%    \begin{macrocode}
           \tl_put_right:Nx \l__dataplot_content_tl
            {
              \exp_not:N \draw [ \exp_not:V \DTLminorgridstyle ]
              (
                \fp_to_decimal:N \l__dataplot_extended_min_x_fp ,
                \l__dataplot_y_tl
              )
              --
              ( 
                \fp_to_decimal:N \l__dataplot_extended_max_x_fp ,
                \l__dataplot_y_tl
              ); ~
            }
          }
       }
    }
%    \end{macrocode}
% Draw major grid lines.
%    \begin{macrocode}
    \seq_map_inline:Nn \l__dataplot_x_tic_seq
     {
       \tl_set:Nn \l__dataplot_x_fp { ##1 }
       \tl_set:Nx \l__dataplot_x_tl
         { \fp_to_decimal:N \l__dataplot_x_fp }
       \tl_put_right:Nx \l__dataplot_content_tl
       {
         \exp_not:N \draw [ \exp_not:V \DTLmajorgridstyle ]
         ( \l__dataplot_x_tl ,
           \fp_to_decimal:N \l__dataplot_extended_min_y_fp
         )
         --
         ( \l__dataplot_x_tl ,
           \fp_to_decimal:N \l__dataplot_extended_max_y_fp
         ); ~
       }
     }
    \seq_map_inline:Nn \l__dataplot_y_tic_seq
    {
      \tl_set:Nn \l__dataplot_y_fp { ##1 }
      \tl_set:Nx \l__dataplot_y_tl
       { \fp_to_decimal:N \l__dataplot_y_fp }
      \tl_put_right:Nx \l__dataplot_content_tl
       {
         \exp_not:N \draw [ \exp_not:V \DTLmajorgridstyle ]
         (
           \fp_to_decimal:N  \l__dataplot_extended_min_x_fp ,
           \l__dataplot_y_tl
         )
          --
         (
           \fp_to_decimal:N \l__dataplot_extended_max_x_fp ,
           \l__dataplot_y_tl
         ); ~
       }
    }
   \l__dataplot_content_tl
   \tl_clear:N \l__dataplot_content_tl
  }
}
%    \end{macrocode}
%Plot x tics if required:
%    \begin{macrocode}
\cs_new:Nn \__dataplot_add_x_tics:
{
 \legacy_if:nT { DTLxtics }
  {
%    \end{macrocode}
% Get tick length in terms of canvas co-ordinates
%    \begin{macrocode}
    \fp_set:Nn \l__dataplot_tick_length_fp 
     {
       (
         (
           \dim_to_decimal_in_sp:n { \DTLticklength }
            - \l__dataplot_offset_y_fp 
         )   / \l__dataplot_scale_y_fp
       )
       / 65536
     }
%    \end{macrocode}
% Get tick label offset in terms of canvas co-ordinates
%    \begin{macrocode}
     \dim_add:Nn \l__dataplot_x_tic_label_height_dim
       { \DTLticklabeloffset }
     \fp_set:Nn \l__dataplot_tic_label_offset_fp
       {
         (
           (
             \dim_to_decimal_in_sp:n
                { \l__dataplot_x_tic_label_height_dim }
             + \l__dataplot_offset_y_fp
           ) / \l__dataplot_scale_y_fp
         ) / 65536
       }
%    \end{macrocode}
% Iterate through tick list. The default tick labels should have
% already been added in \cs{\_\_dataplot\_calc\_x\_label\_dim:}
%    \begin{macrocode}
     \seq_map_indexed_inline:Nn \l__dataplot_x_tic_seq
       {
%    \end{macrocode}
% Reconstruct the floating point variable:
%    \begin{macrocode}
         \tl_set:Nn \l__dataplot_x_fp { ##2 }
         \tl_set:Nn \l__dataplot_x_tl
           { \fp_use:N \l__dataplot_x_fp }
%    \end{macrocode}
% Fetch current tick label:
%    \begin{macrocode}
         \tl_set:Nx \l__dataplot_tic_label_tl
         {
           \seq_item:Nn \l__dataplot_xtic_labels_seq { ##1 }
         }
%    \end{macrocode}
% Draw tick.
%    \begin{macrocode}
      \legacy_if:nTF { DTLxticsin }
       {
%    \end{macrocode}
% Tick above the $x$-axis. (Tick "in")
%    \begin{macrocode}
         \bool_if:NTF \l__dataplot_zero_y_axis_bool
          {
%    \end{macrocode}
% Enforce $x$ axis to pass through $y=0$.
%    \begin{macrocode}
            \fp_set_eq:NN \l__dataplot_y_fp
              \l__dataplot_tick_length_fp
            \tl_put_right:Nx \l__dataplot_content_tl
              {
                \exp_not:N \draw
                 ( \l__dataplot_x_tl , 0 )
                  --
                 ( \l__dataplot_x_tl ,
                     \fp_to_decimal:N  \l__dataplot_y_fp ) ; ~
              }
            \fp_set:Nn \l__dataplot_y_fp
              { - \l__dataplot_tic_label_offset_fp }
          }
          {
%    \end{macrocode}
% Don't enforce $x$ axis to pass through $y=0$ (but this will occur
% if min y = 0).
%    \begin{macrocode}
            \fp_set:Nn \l__dataplot_y_fp
              { 
                \l__dataplot_min_y_fp
                 + \l__dataplot_tick_length_fp
              }
            \tl_put_right:Nx \l__dataplot_content_tl
              {
                \exp_not:N \draw
                 ( \l__dataplot_x_tl , \DTLminY )
                  --
                 ( \l__dataplot_x_tl ,
                   \fp_to_decimal:N \l__dataplot_y_fp ) ; ~
              }
            \fp_set:Nn \l__dataplot_y_fp
              {
                \l__dataplot_min_y_fp
                - \l__dataplot_tic_label_offset_fp
              }
          }
%    \end{macrocode}
%Draw the label unless 0 and omit zero label on.
%    \begin{macrocode}
         \bool_lazy_and:nnF
           { \l__dataplot_omit_zero_x_label_bool }
           {
             \fp_compare_p:nNn
               { \l__dataplot_x_fp } = { \c_zero_fp }
           }
         {
           \tl_put_right:Nx \l__dataplot_content_tl
            {
              \exp_not:N \draw
               ( \l__dataplot_x_tl , 
                 \fp_to_decimal:N \l__dataplot_y_fp ) ~
               node [ \exp_not:V \l__dataplot_x_tick_label_style_tl ]
                {
                  \exp_not:N \DTLplotdisplayXticklabel
                   { \exp_not:V \l__dataplot_tic_label_tl }
                } ; ~
            }
         }
       }
       {
%    \end{macrocode}
% Tick below the $x$-axis. (Tick "out")
%    \begin{macrocode}
         \bool_if:NTF \l__dataplot_zero_y_axis_bool
          {
           \fp_set:Nn \l__dataplot_y_fp
             { - \l__dataplot_tick_length_fp }
           \tl_put_right:Nx \l__dataplot_content_tl
             {
               \exp_not:N \draw
                ( \l__dataplot_x_tl , 0 )
                 --
                ( \l__dataplot_x_tl ,
                    \fp_to_decimal:N \l__dataplot_y_fp ) ; ~
             }
           \fp_sub:Nn \l__dataplot_y_fp
             { \l__dataplot_tic_label_offset_fp }
          }
          {
%    \end{macrocode}
% Don't enforce $x$ axis to pass through $y=0$ (but this will occur
% if min y = 0).
%    \begin{macrocode}
           \fp_set:Nn \l__dataplot_y_fp
            {
              \l__dataplot_min_y_fp
               - \l__dataplot_tick_length_fp
            }
           \tl_put_right:Nx \l__dataplot_content_tl
             {
               \exp_not:N \draw
                ( \l__dataplot_x_tl , \DTLminY )
                 --
                ( \l__dataplot_x_tl ,
                    \fp_to_decimal:N \l__dataplot_y_fp ) ;
             }
           \fp_sub:Nn \l__dataplot_y_fp
             { \l__dataplot_tic_label_offset_fp }
          }
%    \end{macrocode}
%Draw the label unless 0 and omit zero label on.
%    \begin{macrocode}
         \bool_lazy_and:nnF
          { \l__dataplot_omit_zero_x_label_bool }
          { \fp_compare_p:nNn { \l__dataplot_x_fp } = { \c_zero_fp } }
         {
           \tl_put_right:Nx \l__dataplot_content_tl
             {
                \exp_not:N \draw
                ( \l__dataplot_x_tl ,
                     \fp_to_decimal:N \l__dataplot_y_fp ) ~
                node [ \exp_not:V \l__dataplot_x_tick_label_style_tl ]
                 {
                   \exp_not:N \DTLplotdisplayXticklabel
                    { \exp_not:V \l__dataplot_tic_label_tl }
                 } ; ~
             }
           }
       }
%    \end{macrocode}
% Draw box ticks, if box setting is on.
%    \begin{macrocode}
      \legacy_if:nT { DTLbox }
       {
%    \end{macrocode}
% If min y isn't 0 and the box setting is on, need to draw tick on
% the bottom of the box according to the box setting.
%    \begin{macrocode}
          \__dataplot_draw_box_ticks_lower_x:n
           {
             \int_case:nnT { \l__dataplot_box_ticks_int }
              {
                { \c_one_int } % match
                 {
                   \legacy_if:nTF { DTLxticsin }
                   {
                     \fp_set:Nn \l__dataplot_y_fp 
                      {
                        \l__dataplot_extended_min_y_fp
                         + \l__dataplot_tick_length_fp
                      }
                   }
                   {
                     \fp_set:Nn \l__dataplot_y_fp 
                      {
                        \l__dataplot_extended_min_y_fp
                        - \l__dataplot_tick_length_fp
                      }
                   }
                 }
                { 2 } % in
                 {
                   \fp_set:Nn \l__dataplot_y_fp 
                    {
                      \l__dataplot_extended_min_y_fp
                      + \l__dataplot_tick_length_fp
                    }
                 }
                { 3 } % out
                 {
                   \fp_set:Nn \l__dataplot_y_fp 
                    {
                      \l__dataplot_extended_min_y_fp
                       - \l__dataplot_tick_length_fp
                    }
                 }
              }
              {
                \tl_put_right:Nx \l__dataplot_content_tl
                 {
                   \exp_not:N \draw
                   ( \l__dataplot_x_tl ,
                     \fp_to_decimal:N \l__dataplot_extended_min_y_fp )
                    --
                   ( \l__dataplot_x_tl ,
                      \fp_to_decimal:N \l__dataplot_y_fp ) ; ~
                 }
              }
           }
%    \end{macrocode}
%Draw the ticks on the opposite side of the box.
%    \begin{macrocode}
         \int_case:nnT { \l__dataplot_box_ticks_int }
          {
            { \c_one_int } % match
             {
               \legacy_if:nTF  { DTLxticsin }
                {
                  \fp_set:Nn \l__dataplot_y_fp
                   {
                     \l__dataplot_extended_max_y_fp
                     - \l__dataplot_tick_length_fp
                   }
                }
                {
                  \fp_set:Nn \l__dataplot_y_fp
                   {
                     \l__dataplot_extended_max_y_fp
                     + \l__dataplot_tick_length_fp
                   }
                }
             }
            { 2 } % in
             {
               \fp_set:Nn \l__dataplot_y_fp
                {
                  \l__dataplot_extended_max_y_fp
                  - \l__dataplot_tick_length_fp
                }
             }
            { 3 } % out
             {
               \fp_set:Nn \l__dataplot_y_fp
                {
                  \l__dataplot_extended_max_y_fp
                   + \l__dataplot_tick_length_fp
                }
             }
          }
          {
            \tl_put_right:Nx \l__dataplot_content_tl
             {
               \exp_not:N \draw
               ( \l__dataplot_x_tl ,
                 \fp_to_decimal:N \l__dataplot_extended_max_y_fp )
                --
               ( \l__dataplot_x_tl ,
                 \fp_to_decimal:N \l__dataplot_y_fp ) ; ~
             }
          }
       }
     }
   \l__dataplot_content_tl
   \tl_clear:N \l__dataplot_content_tl
  }
}
%    \end{macrocode}
% Plot $x$ label if required.
%    \begin{macrocode}
\cs_new:Nn \__dataplot_add_x_label:
{
  \tl_if_empty:NF \l__dataplot_xlabel_tl
  {
%    \end{macrocode}
% Get baseline in terms of canvas co-ordinates
%    \begin{macrocode}
    \fp_add:Nn \l__dataplot_tic_label_offset_fp
      { 
        (
          (
            \dim_to_decimal_in_sp:n { \baselineskip }
             + \l__dataplot_offset_y_fp
          ) / \l__dataplot_scale_y_fp
        ) / 65536
      }
%    \end{macrocode}
% Get halfway position
%    \begin{macrocode}
    \fp_set:Nn \l__dataplot_x_fp
      { \l__dataplot_min_x_fp + 0.5 * \l__dataplot_x_extent_fp  }
    \bool_if:NTF \l__dataplot_zero_y_axis_bool
     {
      \fp_set:Nn \l__dataplot_y_fp
        { - \l__dataplot_tic_label_offset_fp }
     }
     {
      \fp_set:Nn \l__dataplot_y_fp
        { \l__dataplot_min_y_fp - \l__dataplot_tic_label_offset_fp }
     }
    \tl_put_right:Nx \l__dataplot_content_tl
      {
        \exp_not:N \draw
        ( \fp_to_decimal:N \l__dataplot_x_fp ,
          \fp_to_decimal:N \l__dataplot_y_fp ) ~
        node[anchor=north] { \exp_not:V \l__dataplot_xlabel_tl }; ~
      }
    \l__dataplot_content_tl
    \tl_clear:N \l__dataplot_content_tl
  }
}
%    \end{macrocode}
% Plot the $x$ minor ticks if required
%    \begin{macrocode}
\cs_new:Nn \__dataplot_add_minor_x:
{
 \legacy_if:nT { DTLxminortics }
  {
%    \end{macrocode}
% Get tick length in terms of canvas co-ordinates
%    \begin{macrocode}
   \fp_set:Nn \l__dataplot_tick_length_fp
    {
      (
        (
          \dim_to_decimal_in_sp:n { \DTLminorticklength }
           + \l__dataplot_offset_y_fp
        ) / \l__dataplot_scale_y_fp
      ) / 65536
    }
%    \end{macrocode}
% Iterate through minor ticks.
%    \begin{macrocode}
   \seq_map_inline:Nn \l__dataplot_x_minor_tic_seq
    {
%    \end{macrocode}
% Reconstruct floating point variable.
%    \begin{macrocode}
      \tl_set:Nn \l__dataplot_x_fp { ##1 }
      \tl_set:Nx \l__dataplot_x_tl
        { \fp_to_decimal:N \l__dataplot_x_fp }
%    \end{macrocode}
% Draw tick.
%    \begin{macrocode}
      \legacy_if:nTF { DTLxticsin }
       {
%    \end{macrocode}
% Tick above the $x$-axis. (Tick "in")
%    \begin{macrocode}
         \bool_if:NTF \l__dataplot_zero_y_axis_bool
          {
%    \end{macrocode}
% Enforce $x$ axis to pass through $y=0$.
%    \begin{macrocode}
           \fp_set_eq:NN \l__dataplot_y_fp 
              \l__dataplot_tick_length_fp 
           \tl_put_right:Nx \l__dataplot_content_tl
            {
              \exp_not:N \draw
              ( \l__dataplot_x_tl , 0 )
               --
              ( \l__dataplot_x_tl ,
                 \fp_to_decimal:N \l__dataplot_y_fp ); ~
            }
          }
          {
%    \end{macrocode}
% Don't enforce $x$ axis to pass through $y=0$ (but this will occur
% if min y = 0).
%    \begin{macrocode}
           \fp_set:Nn \l__dataplot_y_fp 
            {
              \l__dataplot_min_y_fp
               + \l__dataplot_tick_length_fp
            }
           \tl_put_right:Nx \l__dataplot_content_tl
            {
              \exp_not:N \draw
              ( \l__dataplot_x_tl , \DTLminY )
               --
              ( \l__dataplot_x_tl ,
                 \fp_to_decimal:N \l__dataplot_y_fp ); ~
            }
         }
       }
       {
%    \end{macrocode}
% Tick below the $x$-axis. (Tick "out")
%    \begin{macrocode}
         \bool_if:NTF \l__dataplot_zero_y_axis_bool
          {
           \fp_set:Nn \l__dataplot_y_fp 
             { - \l__dataplot_tick_length_fp }
           \tl_put_right:Nx \l__dataplot_content_tl
            {
              \exp_not:N \draw
              ( \l__dataplot_x_tl , 0 )
               --
              ( \l__dataplot_x_tl ,
                 \fp_to_decimal:N \l__dataplot_y_fp ); ~
            }
          }
          {
%    \end{macrocode}
% Don't enforce $x$ axis to pass through $y=0$ (but this will occur
% if min y = 0).
%    \begin{macrocode}
           \fp_set:Nn \l__dataplot_y_fp 
             { 
               \l__dataplot_min_y_fp
                - \l__dataplot_tick_length_fp
             }
           \tl_put_right:Nx \l__dataplot_content_tl
            {
              \exp_not:N \draw
              ( \l__dataplot_x_tl , \DTLminY )
               --
              ( \l__dataplot_x_tl ,
                 \fp_to_decimal:N \l__dataplot_y_fp ); ~
            }
          }
       }
%    \end{macrocode}
% Draw box ticks, if box setting is on.
%    \begin{macrocode}
      \legacy_if:nT { DTLbox }
       {
%    \end{macrocode}
% If min y isn't 0 and the box setting is on, need to draw tick on
% the bottom of the box according to the box setting.
%    \begin{macrocode}
          \__dataplot_draw_box_ticks_lower_x:n
           {
             \int_case:nnT { \l__dataplot_box_ticks_int }
              {
                { \c_one_int } % match
                 {
                   \legacy_if:nTF { DTLxticsin }
                   {
                     \fp_set:Nn \l__dataplot_y_fp 
                      {
                        \l__dataplot_extended_min_y_fp
                         + \l__dataplot_tick_length_fp
                      }
                   }
                   {
                     \fp_set:Nn \l__dataplot_y_fp 
                      {
                        \l__dataplot_extended_min_y_fp
                        - \l__dataplot_tick_length_fp
                      }
                   }
                 }
                { 2 } % in
                 {
                   \fp_set:Nn \l__dataplot_y_fp 
                    {
                      \l__dataplot_extended_min_y_fp
                      + \l__dataplot_tick_length_fp
                    }
                 }
                { 3 } % out
                 {
                   \fp_set:Nn \l__dataplot_y_fp 
                    {
                      \l__dataplot_extended_min_y_fp
                       - \l__dataplot_tick_length_fp
                    }
                 }
              }
              {
                \tl_put_right:Nx \l__dataplot_content_tl
                 {
                   \exp_not:N \draw
                   ( \l__dataplot_x_tl ,
                     \fp_to_decimal:N \l__dataplot_extended_min_y_fp )
                    --
                   ( \l__dataplot_x_tl ,
                      \fp_to_decimal:N \l__dataplot_y_fp ); ~
                 }
              }
           }
%    \end{macrocode}
%Draw the ticks on the opposite side of the box.
%    \begin{macrocode}
         \int_case:nnT { \l__dataplot_box_ticks_int }
          {
            { \c_one_int } % match
             {
               \legacy_if:nTF  { DTLxticsin }
                {
                  \fp_set:Nn \l__dataplot_y_fp
                   {
                     \l__dataplot_extended_max_y_fp
                     - \l__dataplot_tick_length_fp
                   }
                }
                {
                  \fp_set:Nn \l__dataplot_y_fp
                   {
                     \l__dataplot_extended_max_y_fp
                     + \l__dataplot_tick_length_fp
                   }
                }
             }
            { 2 } % in
             {
               \fp_set:Nn \l__dataplot_y_fp
                {
                  \l__dataplot_extended_max_y_fp
                  - \l__dataplot_tick_length_fp
                }
             }
            { 3 } % out
             {
               \fp_set:Nn \l__dataplot_y_fp
                {
                  \l__dataplot_extended_max_y_fp
                   + \l__dataplot_tick_length_fp
                }
             }
          }
          {
            \tl_put_right:Nx \l__dataplot_content_tl
             {
               \exp_not:N \draw
               ( \l__dataplot_x_tl ,
                 \fp_to_decimal:N \l__dataplot_extended_max_y_fp )
                --
               ( \l__dataplot_x_tl ,
                 \fp_to_decimal:N \l__dataplot_y_fp ); ~
             }
          }
       }
    }
   \l__dataplot_content_tl
   \tl_clear:N \l__dataplot_content_tl
  }
}
%    \end{macrocode}
%Plot y tics if required:
%    \begin{macrocode}
\cs_new:Nn \__dataplot_add_y_tics:
{
 \legacy_if:nT { DTLytics }
  {
%    \end{macrocode}
% Get tick length in terms of canvas co-ordinates
%    \begin{macrocode}
    \fp_set:Nn \l__dataplot_tick_length_fp 
     {
       (
         (
           \dim_to_decimal_in_sp:n { \DTLticklength }
            + \l__dataplot_offset_x_fp 
         )   / \l__dataplot_scale_x_fp
       )
       / 65536
     }
%    \end{macrocode}
% Get tick label offset in terms of canvas co-ordinates
%    \begin{macrocode}
     \fp_set:Nn \l__dataplot_tic_label_offset_fp
       {
         (
           (
             \dim_to_decimal_in_sp:n { \DTLticklabeloffset }
             + \l__dataplot_offset_x_fp
           ) / \l__dataplot_scale_x_fp
         ) / 65536
       }
%    \end{macrocode}
% Iterate through tick list. The default tick labels should have
% already been added in \cs{\_\_dataplot\_calc\_y\_label\_dim:}
%    \begin{macrocode}
     \seq_map_indexed_inline:Nn  \l__dataplot_y_tic_seq
       {
%    \end{macrocode}
% Reconstruct the floating point variable:
%    \begin{macrocode}
         \tl_set:Nn \l__dataplot_y_fp { ##2 }
         \tl_set:Nn \l__dataplot_y_tl
           { \fp_use:N \l__dataplot_y_fp }
%    \end{macrocode}
% Fetch current tick label:
%    \begin{macrocode}
         \tl_set:Nx \l__dataplot_tic_label_tl
         {
           \seq_item:Nn \l__dataplot_ytic_labels_seq { ##1 }
         }
%    \end{macrocode}
% Draw tick.
%    \begin{macrocode}
       \legacy_if:nTF { DTLyticsin }
        {
%    \end{macrocode}
% Tick to the right of the $y$-axis. (Tick "in")
%    \begin{macrocode}
         \bool_if:NTF \l__dataplot_zero_x_axis_bool
          {
%    \end{macrocode}
% Enforce $y$ axis to pass through $x=0$.
%    \begin{macrocode}
            \fp_set_eq:NN \l__dataplot_x_fp
                \l__dataplot_tick_length_fp
            \tl_put_right:Nx \l__dataplot_content_tl
              {
                \exp_not:N \draw
                 ( 0,  \l__dataplot_y_tl )
                  --
                 ( \fp_to_decimal:N  \l__dataplot_x_fp,
                     \l__dataplot_y_tl ); ~
              }
            \fp_set:Nn \l__dataplot_x_fp
              { - \l__dataplot_tic_label_offset_fp }
          }
          {
%    \end{macrocode}
% Don't enforce $y$ axis to pass through $x=0$ (but this will occur
% if min x = 0).
%    \begin{macrocode}
            \fp_set:Nn \l__dataplot_x_fp
              { 
                \l__dataplot_min_x_fp
                + \l__dataplot_tick_length_fp
              }
            \tl_put_right:Nx \l__dataplot_content_tl
              {
                \exp_not:N \draw
                 (
                   \DTLminX , \l__dataplot_y_tl
                 )
                  --
                 ( \fp_to_decimal:N  \l__dataplot_x_fp,
                     \l__dataplot_y_tl ); ~
              }
            \fp_set:Nn \l__dataplot_x_fp
              {
                \l__dataplot_min_x_fp
                 - \l__dataplot_tic_label_offset_fp
              }
           }
%    \end{macrocode}
%Draw the label unless 0 and omit zero label on.
%    \begin{macrocode}
         \bool_lazy_and:nnF
           { \l__dataplot_omit_zero_y_label_bool }
           {
             \fp_compare_p:nNn
               { \l__dataplot_y_fp } = { \c_zero_fp }
           }
         {
           \tl_put_right:Nx \l__dataplot_content_tl
             {
               \exp_not:N \draw
                ( \fp_to_decimal:N \l__dataplot_x_fp,
                     \l__dataplot_y_tl ) ~
                node [ \exp_not:V \l__dataplot_y_tick_label_style_tl ]
                 {
                   \exp_not:N \DTLplotdisplayYticklabel
                    { \exp_not:V \l__dataplot_tic_label_tl }
                 }; ~
             }
           }
        }
        {
%    \end{macrocode}
% Tick to the left of the $y$-axis. (Tick "out")
%    \begin{macrocode}
         \bool_if:NTF \l__dataplot_zero_x_axis_bool
          {
            \fp_set:Nn \l__dataplot_x_fp
              { - \l__dataplot_tick_length_fp }
            \tl_put_right:Nx \l__dataplot_content_tl
              {
                \exp_not:N \draw
                 ( 0 ,  \l__dataplot_y_tl )
                  --
                 ( \fp_to_decimal:N \l__dataplot_x_fp,
                     \l__dataplot_y_tl  ) ; ~
              }
          }
          {
%    \end{macrocode}
% Don't enforce $y$ axis to pass through $x=0$ (but this will occur
% if min x = 0).
%    \begin{macrocode}
            \fp_set:Nn \l__dataplot_x_fp
              {
                \l__dataplot_min_x_fp
                 - \l__dataplot_tick_length_fp
              }
            \tl_put_right:Nx \l__dataplot_content_tl
              {
                \exp_not:N \draw
                 (
                   \DTLminX , \l__dataplot_y_tl
                 )
                  --
                 ( \fp_to_decimal:N \l__dataplot_x_fp,
                     \l__dataplot_y_tl  ) ; ~
              }
          }
%    \end{macrocode}
%Draw the label unless 0 and omit zero label on.
%    \begin{macrocode}
         \bool_lazy_and:nnF
           { \l__dataplot_omit_zero_y_label_bool }
           {
             \fp_compare_p:nNn
               { \l__dataplot_y_fp } = { \c_zero_fp }
           }
         {
           \fp_sub:Nn \l__dataplot_x_fp
             { \l__dataplot_tic_label_offset_fp }
           \tl_put_right:Nx \l__dataplot_content_tl
             {
               \exp_not:N \draw
                ( \fp_to_decimal:N \l__dataplot_x_fp,
                     \l__dataplot_y_tl ) ~
                node [ \exp_not:V \l__dataplot_y_tick_label_style_tl ]
                 {
                   \exp_not:N \DTLplotdisplayYticklabel
                    { \exp_not:V \l__dataplot_tic_label_tl }
                 } ; ~
             }
           }
        }
%    \end{macrocode}
% Draw box ticks, if box setting is on.
%    \begin{macrocode}
       \legacy_if:nT { DTLbox }
        {
%    \end{macrocode}
% If min x isn't 0 and the box setting is on, need to draw tick on
% the left of the box according to the box setting.
%    \begin{macrocode}
          \__dataplot_draw_box_ticks_lower_y:n
           {
             \int_case:nnT { \l__dataplot_box_ticks_int }
              {
                { \c_one_int } % match
                 {
                   \legacy_if:nTF { DTLyticsin }
                   {
                     \fp_set:Nn \l__dataplot_x_fp 
                      {
                        \l__dataplot_extended_min_x_fp
                        + \l__dataplot_tick_length_fp
                      }
                   }
                   {
                     \fp_set:Nn \l__dataplot_x_fp 
                      {
                        \l__dataplot_extended_min_x_fp
                        - \l__dataplot_tick_length_fp
                      }
                   }
                 }
                { 2 } % in
                 {
                   \fp_set:Nn \l__dataplot_x_fp 
                    {
                      \l__dataplot_extended_min_x_fp
                       + \l__dataplot_tick_length_fp
                    }
                 }
                { 3 } % out
                 {
                   \fp_set:Nn \l__dataplot_x_fp 
                    {
                      \l__dataplot_extended_min_x_fp
                       - \l__dataplot_tick_length_fp
                    }
                 }
              }
              {
                \tl_put_right:Nx \l__dataplot_content_tl
                 {
                   \exp_not:N \draw
                   (
                     \fp_to_decimal:N \l__dataplot_extended_min_x_fp ,
                     \l__dataplot_y_tl
                    )
                    --
                   ( \fp_to_decimal:N \l__dataplot_x_fp,
                       \l__dataplot_y_tl ); ~
                 }
              }
           }
%    \end{macrocode}
% Draw opposite tick.
%    \begin{macrocode}
         \int_case:nnT { \l__dataplot_box_ticks_int }
          {
            { \c_one_int } % match
             {
               \legacy_if:nTF { DTLyticsin }
                {
                 \fp_set:Nn \l__dataplot_x_fp
                  {
                    \l__dataplot_extended_max_x_fp
                     - \l__dataplot_tick_length_fp
                  }
                }
                {
                 \fp_set:Nn \l__dataplot_x_fp
                  {
                    \l__dataplot_extended_max_x_fp
                    + \l__dataplot_tick_length_fp
                  }
                }
              }
            { 2 } % in
             {
               \fp_set:Nn \l__dataplot_x_fp
                {
                  \l__dataplot_extended_max_x_fp
                    - \l__dataplot_tick_length_fp
                }
             }
            { 3 } % out
             {
               \fp_set:Nn \l__dataplot_x_fp
                {
                  \l__dataplot_extended_max_x_fp
                  + \l__dataplot_tick_length_fp
                }
             }
          }
          {
            \tl_put_right:Nx \l__dataplot_content_tl
              {
                \exp_not:N \draw
                (
                  \fp_to_decimal:N \l__dataplot_extended_max_x_fp ,
                  \l__dataplot_y_tl
                )
                 --
                ( \fp_to_decimal:N \l__dataplot_x_fp,
                    \l__dataplot_y_tl); ~
              }
          }
        }
     }
   \l__dataplot_content_tl
   \tl_clear:N \l__dataplot_content_tl
  }
}
%    \end{macrocode}
% Plot $y$ label if required.
%    \begin{macrocode}
\cs_new:Nn \__dataplot_add_y_label:
{
  \tl_if_empty:NF \l__dataplot_ylabel_tl
  {
    \fp_set:Nn \l__dataplot_tic_label_offset_fp
    {
      (
       (
        \dim_to_decimal_in_sp:n
         {
           \baselineskip
           + \l__dataplot_y_tic_label_width_dim
           + \DTLticklabeloffset
         }
         + \l__dataplot_offset_x_fp
       )
        / \l__dataplot_scale_x_fp
      ) / 65536
    }
%    \end{macrocode}
% Get halfway position
%    \begin{macrocode}
    \bool_if:NTF \l__dataplot_zero_x_axis_bool
     {
       \fp_set:Nn \l__dataplot_x_fp
        { - \l__dataplot_tic_label_offset_fp }
     }
     {
       \fp_set:Nn \l__dataplot_x_fp
        { \l__dataplot_min_x_fp - \l__dataplot_tic_label_offset_fp }
     }
    \fp_set:Nn \l__dataplot_y_fp 
     { \l__dataplot_min_y_fp + 0.5 * \l__dataplot_y_extent_fp }
    \tl_put_right:Nx \l__dataplot_content_tl
     {
       \exp_not:N \draw
        ( \fp_use:N \l__dataplot_x_fp,
            \fp_use:N \l__dataplot_y_fp ) ~
        node[rotate=90,anchor=south]
          { \exp_not:V \l__dataplot_ylabel_tl }; ~
     }
    \l__dataplot_content_tl
    \tl_clear:N \l__dataplot_content_tl
  }
}
%    \end{macrocode}
% Plot the $y$ minor ticks if required
%    \begin{macrocode}
\cs_new:Nn \__dataplot_add_minor_y:
{
 \legacy_if:nT { DTLyminortics }
  {
%    \end{macrocode}
% Get tick length in terms of canvas co-ordinates
%    \begin{macrocode}
   \fp_set:Nn \l__dataplot_tick_length_fp
    {
      (
        (
          \dim_to_decimal_in_sp:n { \DTLminorticklength }
           + \l__dataplot_offset_x_fp
        ) / \l__dataplot_scale_x_fp
      ) / 65536
    }
%    \end{macrocode}
% Iterate through minor ticks.
%    \begin{macrocode}
   \seq_map_inline:Nn \l__dataplot_y_minor_tic_seq
    {
      \tl_set:Nn \l__dataplot_y_fp { ##1 }
      \tl_set:Nx \l__dataplot_y_tl
        { \fp_to_decimal:N \l__dataplot_y_fp }
     \legacy_if:nTF { DTLyticsin }
      {
%    \end{macrocode}
% Tick right of the $y$-axis. (Tick "in")
%    \begin{macrocode}
         \bool_if:NTF \l__dataplot_zero_x_axis_bool
          {
%    \end{macrocode}
% Enforce $y$ axis to pass through $x=0$.
%    \begin{macrocode}
           \fp_set_eq:NN \l__dataplot_x_fp 
              \l__dataplot_tick_length_fp 
           \tl_put_right:Nx \l__dataplot_content_tl
            {
              \exp_not:N \draw
              ( 0 , \l__dataplot_y_tl )
               --
              ( \fp_to_decimal:N \l__dataplot_x_fp, 
                 \l__dataplot_y_tl ); ~
            }
          }
          {
%    \end{macrocode}
% Don't enforce $y$ axis to pass through $x=0$ (but this will occur
% if min x = 0).
%    \begin{macrocode}
           \fp_set:Nn \l__dataplot_x_fp 
             { \l__dataplot_min_x_fp + \l__dataplot_tick_length_fp }
           \tl_put_right:Nx \l__dataplot_content_tl
            {
              \exp_not:N \draw
              ( \DTLminX, \l__dataplot_y_tl )
               --
              ( \fp_to_decimal:N \l__dataplot_x_fp, 
                 \l__dataplot_y_tl ); ~
            }
          }
      }
      {
%    \end{macrocode}
% Tick left of the $y$-axis. (Tick "out")
%    \begin{macrocode}
         \bool_if:NTF \l__dataplot_zero_x_axis_bool
          {
            \fp_set:Nn \l__dataplot_x_fp 
              { - \l__dataplot_tick_length_fp }
            \tl_put_right:Nx \l__dataplot_content_tl
             {
               \exp_not:N \draw
               ( 0 , \l__dataplot_y_tl )
                --
               ( \fp_to_decimal:N \l__dataplot_x_fp ,
                   \l__dataplot_y_tl ); ~
             }
          }
          {
%    \end{macrocode}
% Don't enforce $y$ axis to pass through $x=0$ (but this will occur
% if min x = 0).
%    \begin{macrocode}
            \fp_set:Nn \l__dataplot_x_fp 
              { \l__dataplot_min_x_fp - \l__dataplot_tick_length_fp }
            \tl_put_right:Nx \l__dataplot_content_tl
             {
               \exp_not:N \draw
               ( \DTLminX, \l__dataplot_y_tl )
                --
               ( \fp_to_decimal:N \l__dataplot_x_fp ,
                   \l__dataplot_y_tl ); ~
             }
          }
      }
%    \end{macrocode}
% Draw box ticks, if box setting is on.
%    \begin{macrocode}
       \legacy_if:nT { DTLbox }
        {
%    \end{macrocode}
% If min x isn't 0 and the box setting is on, need to draw tick on
% the left of the box according to the box setting.
%    \begin{macrocode}
          \__dataplot_draw_box_ticks_lower_y:n
           {
             \int_case:nnT { \l__dataplot_box_ticks_int }
              {
                { \c_one_int } % match
                 {
                   \legacy_if:nTF { DTLyticsin }
                   {
                     \fp_set:Nn \l__dataplot_x_fp 
                      {
                        \l__dataplot_extended_min_x_fp
                        + \l__dataplot_tick_length_fp
                      }
                   }
                   {
                     \fp_set:Nn \l__dataplot_x_fp 
                      {
                        \l__dataplot_extended_min_x_fp
                        - \l__dataplot_tick_length_fp
                      }
                   }
                 }
                { 2 } % in
                 {
                   \fp_set:Nn \l__dataplot_x_fp 
                    {
                      \l__dataplot_extended_min_x_fp
                       + \l__dataplot_tick_length_fp
                    }
                 }
                { 3 } % out
                 {
                   \fp_set:Nn \l__dataplot_x_fp 
                    {
                      \l__dataplot_extended_min_x_fp
                       - \l__dataplot_tick_length_fp
                    }
                 }
              }
              {
                \tl_put_right:Nx \l__dataplot_content_tl
                 {
                   \exp_not:N \draw
                   (
                     \fp_to_decimal:N \l__dataplot_extended_min_x_fp ,
                     \l__dataplot_y_tl
                    )
                    --
                   ( \fp_to_decimal:N \l__dataplot_x_fp,
                       \l__dataplot_y_tl ); ~
                 }
              }
           }
%    \end{macrocode}
% Draw opposite tick.
%    \begin{macrocode}
         \int_case:nnT { \l__dataplot_box_ticks_int }
          {
            { \c_one_int } % match
             {
               \legacy_if:nTF { DTLyticsin }
                {
                 \fp_set:Nn \l__dataplot_x_fp
                  {
                    \l__dataplot_extended_max_x_fp
                     - \l__dataplot_tick_length_fp
                  }
                }
                {
                 \fp_set:Nn \l__dataplot_x_fp
                  {
                    \l__dataplot_extended_max_x_fp
                    + \l__dataplot_tick_length_fp
                  }
                }
              }
            { 2 } % in
             {
               \fp_set:Nn \l__dataplot_x_fp
                {
                  \l__dataplot_extended_max_x_fp
                    - \l__dataplot_tick_length_fp
                }
             }
            { 3 } % out
             {
               \fp_set:Nn \l__dataplot_x_fp
                {
                  \l__dataplot_extended_max_x_fp
                  + \l__dataplot_tick_length_fp
                }
             }
          }
          {
            \tl_put_right:Nx \l__dataplot_content_tl
              {
                \exp_not:N \draw
                (
                  \fp_to_decimal:N \l__dataplot_extended_max_x_fp ,
                  \l__dataplot_y_tl
                )
                 --
                ( \fp_to_decimal:N \l__dataplot_x_fp,
                    \l__dataplot_y_tl); ~
              }
          }
        }
    }
   \l__dataplot_content_tl
   \tl_clear:N \l__dataplot_content_tl
  }
}
%    \end{macrocode}
%
%Used to count the total number of plot streams. Primarily provided
%for use as a return value for the "plot" action, but may also be
%used in the legend hook to access the stream index.
%    \begin{macrocode}
\int_new:N \l_dataplot_stream_index_int
%    \end{macrocode}
%
%Plot marks and draw lines for each database.
%    \begin{macrocode}
\cs_new:Nn \__dataplot_plot_data:
{
%    \end{macrocode}
% Initialise:
%    \begin{macrocode}
  \tl_clear:N \l__dataplot_current_mark_tl
  \tl_clear:N \l__dataplot_current_mark_color_tl
  \tl_clear:N \l__dataplot_current_line_tl
  \tl_clear:N \l__dataplot_current_line_color_tl
  \int_zero:N \l_dataplot_stream_index_int
%    \end{macrocode}
% Iterate through each database
%    \begin{macrocode}
  \seq_map_indexed_inline:Nn \l__dataplot_dbnames_seq
  {
%    \end{macrocode}
% Reset styles at the start of each database unless corresponding
% group by setting on: 
%    \begin{macrocode}
    \bool_if:NTF \l__dataplot_mark_group_bool
     {
        \__dataplot_get_style:NNN
          \l__dataplot_mark_seq \DTLplotmarks 
          \l__dataplot_current_mark_tl
     }
     {
       \bool_if:NT \l__dataplot_no_group_mark_reset_bool
        {
          \__dataplot_set_style_from_clist:NN
            \l__dataplot_mark_seq \DTLplotmarks 
        }
       \tl_clear:N \l__dataplot_current_mark_tl
     }
    \bool_if:NTF \l__dataplot_mark_color_group_bool
     {
       \__dataplot_get_style:NNN
         \l__dataplot_mark_colors_seq \DTLplotmarkcolors
         \l__dataplot_current_mark_color_tl
     }
     {
       \bool_if:NT \l__dataplot_no_group_mark_color_reset_bool
        {
          \__dataplot_set_style_from_clist:NN
            \l__dataplot_mark_colors_seq \DTLplotmarkcolors
        }
       \tl_clear:N \l__dataplot_current_mark_color_tl
     }
    \bool_if:NTF \l__dataplot_line_group_bool
     {
       \__dataplot_get_style:NNN
         \l__dataplot_line_seq \DTLplotlines
         \l__dataplot_current_line_tl
     }
     {
       \bool_if:NT \l__dataplot_no_group_line_reset_bool
        {
          \__dataplot_set_style_from_clist:NN
            \l__dataplot_line_seq \DTLplotlines
        }
       \tl_clear:N \l__dataplot_current_line_tl
     }
    \bool_if:NTF \l__dataplot_line_color_group_bool
     {
       \__dataplot_get_style:NNN
         \l__dataplot_line_colors_seq \DTLplotlinecolors
         \l__dataplot_current_line_color_tl
     }
     {
       \bool_if:NT \l__dataplot_no_group_line_color_reset_bool
        {
          \__dataplot_set_style_from_clist:NN
            \l__dataplot_line_colors_seq \DTLplotlinecolors
        }
       \tl_clear:N \l__dataplot_current_line_color_tl
     }
    \DTLsetup{ default-name = { ##2 } }
    \__dataplot_filtered_map:nnn
    {
%    \end{macrocode}
% Pre-map code.
%    \begin{macrocode}
      \int_incr:N \l_dataplot_stream_index_int
%    \end{macrocode}
% Current mark setting.
%    \begin{macrocode}
      \legacy_if:nT { DTLshowmarkers }
      {
%    \end{macrocode}
% Get the current plot mark if applicable.
%    \begin{macrocode}
        \bool_if:NF \l__dataplot_mark_group_bool
         {
           \__dataplot_get_style:NNN
             \l__dataplot_mark_seq \DTLplotmarks 
             \l__dataplot_current_mark_tl
         }
%    \end{macrocode}
% Get the current plot mark colour.
%    \begin{macrocode}
        \bool_if:NF \l__dataplot_mark_color_group_bool
         {
           \__dataplot_get_style:NNN
             \l__dataplot_mark_colors_seq \DTLplotmarkcolors
             \l__dataplot_current_mark_color_tl
         }
%    \end{macrocode}
% Save the current marker and mark colour.
%    \begin{macrocode}
         \tl_if_empty:NTF \l__dataplot_current_mark_tl
          {
            \tl_clear:N \l__dataplot_current_mark_style_tl
          }
          {
           \tl_set_eq:NN
             \l__dataplot_current_mark_style_tl
             \l__dataplot_current_mark_tl
           \tl_if_empty:NF \l__dataplot_current_mark_color_tl
            {
              \tl_put_left:Nx \l__dataplot_current_mark_style_tl
               {
                  \exp_not:N \pgfsetstrokecolor
                    { \l__dataplot_current_mark_color_tl }
               }
            }
          }
      }
%    \end{macrocode}
% Current line setting.
%    \begin{macrocode}
      \legacy_if:nT { DTLshowlines }
      {
%    \end{macrocode}
% Get the current plot line.
%    \begin{macrocode}
        \bool_if:NF \l__dataplot_line_group_bool
         {
           \__dataplot_get_style:NNN
             \l__dataplot_line_seq \DTLplotlines
             \l__dataplot_current_line_tl
         }
%    \end{macrocode}
% Get the current plot line colour.
%    \begin{macrocode}
         \bool_if:NF \l__dataplot_line_color_group_bool
         {
           \__dataplot_get_style:NNN
             \l__dataplot_line_colors_seq \DTLplotlinecolors
             \l__dataplot_current_line_color_tl
         }
%    \end{macrocode}
% Save the current marker and mark colour.
%    \begin{macrocode}
         \tl_if_empty:NTF \l__dataplot_current_line_tl
          {
            \tl_clear:N \l__dataplot_current_line_style_tl
          }
          {
           \tl_set_eq:NN
             \l__dataplot_current_line_style_tl
             \l__dataplot_current_line_tl
           \tl_if_empty:NF \l__dataplot_current_line_color_tl
            {
              \tl_put_left:Nx \l__dataplot_current_line_style_tl
               {
                  \exp_not:N \pgfsetstrokecolor
                    { \l__dataplot_current_line_color_tl }
               }
            }
          }
      }
%    \end{macrocode}
% Append this plot setting to the legend if applicable.
%    \begin{macrocode}
      \int_if_zero:nF \l__dataplot_legend_setting_int
      {
%    \end{macrocode}
% Get legend label.
%    \begin{macrocode}
         \seq_pop_left:NNF
            \l__dataplot_legend_labels_seq 
            \l__datatool_tmpa_tl
          {
            \tl_clear:N \l__datatool_tmpa_tl
            \exp_args:NNx
             \dataplot_get_default_legend:Nnnnn
               \l__datatool_tmpa_tl
              { ##1 } { ##2 }
              { \l__dataplot_x_key_tl }
              { \l__dataplot_y_key_tl }
          }
%    \end{macrocode}
% Only add to the legend if the label isn't empty.
%    \begin{macrocode}
         \tl_if_empty:NF \l__datatool_tmpa_tl
          {
           \exp_args:Noox
            \DTLaddtoplotlegend
            { \l__dataplot_current_mark_style_tl }
            { \l__dataplot_current_line_style_tl }
            { \l__datatool_tmpa_tl }
          }
      }
%    \end{macrocode}
% Store stream in \cs{l\_\_dataplot\_stream\_tl}:
%    \begin{macrocode}
      \tl_set:Nn \l__dataplot_stream_tl
        { \pgfplotstreamstart }
%    \end{macrocode}
% Only plot points that lie inside bounds.
%    \begin{macrocode}
    }
    {
      \datatool_set_fp:Nn \l__dataplot_x_fp
       {
         \l__dataplot_x_tl
       }
      \datatool_set_fp:Nn \l__dataplot_y_fp
       {
         \l__dataplot_y_tl
       }
      \tl_set:Nx \l__dataplot_decimal_x_tl
        { \fp_to_tl:N \l__dataplot_x_fp }
      \tl_set:Nx \l__dataplot_decimal_y_tl
        { \fp_to_tl:N \l__dataplot_y_fp }
      \__dataplot_x_y_bounded:nnnnnnT
        { \l__dataplot_x_fp }
          { \l__dataplot_min_x_fp } { \l__dataplot_max_x_fp }
        { \l__dataplot_y_fp }
          { \l__dataplot_min_y_fp } { \l__dataplot_max_y_fp }
      {
%    \end{macrocode}
% Apply transformation to co-ordinates
%    \begin{macrocode}
        \fp_set:Nn \l__dataplot_x_fp
         {
           \l__dataplot_x_fp * \l__dataplot_scale_x_fp
           + \l__dataplot_offset_x_fp
         }
        \tl_set:Nx \l__dataplot_x_tl
          { \fp_to_tl:N \l__dataplot_x_fp }
        \fp_set:Nn \l__dataplot_y_fp
         {
           \l__dataplot_y_fp * \l__dataplot_scale_y_fp
           + \l__dataplot_offset_y_fp
         }
        \tl_set:Nx \l__dataplot_y_tl
          { \fp_to_tl:N \l__dataplot_y_fp }
        \tl_put_right:Nx \l__dataplot_stream_tl
         {
           \exp_not:N \pgfplotstreampoint
            {
              \exp_not:N \pgfpointxy
               { \l__dataplot_x_tl }
               { \l__dataplot_y_tl }
            }
         }
      }
   }
   {
%    \end{macrocode}
% Add end of plot stream.
%    \begin{macrocode}
      \tl_put_right:Nn \l__dataplot_stream_tl
        { \pgfplotstreamend }
%    \end{macrocode}
% Draw path if applicable.
%    \begin{macrocode}
      \tl_if_empty:NF \l__dataplot_current_line_style_tl
       {
         \begin{scope}
           \l__dataplot_current_line_style_tl
           \pgfplothandlerlineto
           \l__dataplot_stream_tl
           \pgfusepath{stroke}
         \end{scope}
       }
      \tl_if_empty:NF \l__dataplot_current_mark_style_tl
       {
         \begin{scope}
           \exp_args:NV \pgfplothandlermark
             \l__dataplot_current_mark_style_tl
           \l__dataplot_stream_tl
           \pgfusepath{stroke}
         \end{scope}
       }
    }
  }
%    \end{macrocode}
% Plot legend if required.
%    \begin{macrocode}
  \int_if_zero:nF \l__dataplot_legend_setting_int
  {
%    \end{macrocode}
% Prior to v3.0, \cs{DTLaddtoplotlegend} appended to \cs{dtl@legend}
% so allow for backward-compatibility (this may be removed in future):
%    \begin{macrocode}
    \bool_lazy_and:nnT
     { \tl_if_empty_p:N \l_dataplot_legend_tl }
     { \tl_if_exist_p:N \dtl@legend }
    {
      \tl_set_eq:NN \l_dataplot_legend_tl \dtl@legend
    }
  }
  \tl_if_empty:NF \l_dataplot_legend_tl
  {
    \__dataplot_do_legend:
  }
}
%    \end{macrocode}
%Do the legend:
%    \begin{macrocode}
\cs_new:Nn \__dataplot_do_legend:
{
  \tl_clear:N \l__dataplot_content_tl
  \dataplot_legend_add_begin:
  \dataplot_legend_add_end:
  \int_case:nnTF { \l__dataplot_legend_setting_int }
   {
     { 1 } % north
       {
         \fp_set:Nn \l__dataplot_x_fp
          {
            \l__dataplot_scale_x_fp * 
              ( \l__dataplot_min_x_fp + 0.5 * \l__dataplot_x_extent_fp )
            + \l__dataplot_offset_x_fp
          }
         \fp_set:Nn \l__dataplot_y_fp
          {
            \l__dataplot_max_y_fp * \l__dataplot_scale_y_fp
            + \l__dataplot_offset_y_fp
            - \DTLlegendyoffset
          }
%    \end{macrocode}
%If the y offset is negative anchor at the south otherwise anchor
%north.
%    \begin{macrocode}
         \fp_compare:nNnTF
           { \DTLlegendyoffset } < { \c_zero_fp }
          {
            \tl_put_right:Nn \l__dataplot_content_tl
             {
               node [ anchor = south ]
             }
          }
          {
           \tl_put_right:Nn \l__dataplot_content_tl
            {
              node [ anchor = north ]
            }
          }
       }
     { 2 } % northeast
       {
         \fp_set:Nn \l__dataplot_x_fp
          {
            \l__dataplot_max_x_fp * \l__dataplot_scale_x_fp
              + \l__dataplot_offset_x_fp
              - \DTLlegendxoffset
          }
         \fp_set:Nn \l__dataplot_y_fp
          {
            \l__dataplot_max_y_fp * \l__dataplot_scale_y_fp
              + \l__dataplot_offset_y_fp
              - \DTLlegendyoffset
          }
         \tl_put_right:Nn \l__dataplot_content_tl
          {
            node [ anchor = 
          }
%    \end{macrocode}
%If the y offset is negative anchor at the south otherwise anchor
%north.
%    \begin{macrocode}
         \fp_compare:nNnTF
           { \DTLlegendyoffset } < { \c_zero_fp }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { south ~ }
          }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { north ~ }
          }
%    \end{macrocode}
%If the x offset is negative anchor at the west otherwise anchor
%east.
%    \begin{macrocode}
         \fp_compare:nNnTF
           { \DTLlegendxoffset } < { \c_zero_fp }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { west }
          }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { east }
          }
         \tl_put_right:Nn \l__dataplot_content_tl
          {
             ]
          }
       }
     { 3 } % east
       {
         \fp_set:Nn \l__dataplot_x_fp
          {
            \l__dataplot_max_x_fp * \l__dataplot_scale_x_fp
             + \l__dataplot_offset_x_fp
             - \DTLlegendxoffset
          }
         \fp_set:Nn \l__dataplot_y_fp
          {
            \l__dataplot_offset_y_fp
            + \l__dataplot_scale_y_fp
              * ( \l__dataplot_min_y_fp + 0.5 * \l__dataplot_y_extent_fp )
          }
%    \end{macrocode}
%If the x offset is negative anchor at the west otherwise anchor
%east.
%    \begin{macrocode}
         \fp_compare:nNnTF
           { \DTLlegendxoffset } < { \c_zero_fp }
          {
           \tl_put_right:Nn \l__dataplot_content_tl
            {
              node [ anchor = west ]
            }
          }
          {
           \tl_put_right:Nn \l__dataplot_content_tl
            {
              node [ anchor = east ]
            }
          }
       }
     { 4 } % southeast
       {
         \fp_set:Nn \l__dataplot_x_fp
          {
            \l__dataplot_max_x_fp * \l__dataplot_scale_x_fp
            + \l__dataplot_offset_x_fp
            - \DTLlegendxoffset
          }
         \fp_set:Nn \l__dataplot_y_fp
          {
            \l__dataplot_min_y_fp * \l__dataplot_scale_y_fp
            + \l__dataplot_offset_y_fp
            + \DTLlegendyoffset
          }
         \tl_put_right:Nn \l__dataplot_content_tl
          {
            node [ anchor = 
          }
%    \end{macrocode}
%If the y offset is negative anchor at the north otherwise anchor
%south.
%    \begin{macrocode}
         \fp_compare:nNnTF
           { \DTLlegendyoffset } < { \c_zero_fp }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { north ~ }
          }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { south ~ }
          }
%    \end{macrocode}
%If the x offset is negative anchor at the west otherwise anchor
%east.
%    \begin{macrocode}
         \fp_compare:nNnTF
           { \DTLlegendxoffset } < { \c_zero_fp }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { west }
          }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { east }
          }
         \tl_put_right:Nn \l__dataplot_content_tl
          {
            ]
          }
       }
     { 5 } % south
       {
         \fp_set:Nn \l__dataplot_x_fp
          {
            \l__dataplot_scale_x_fp
            * ( \l__dataplot_min_x_fp + 0.5 * \l__dataplot_x_extent_fp )
            + \l__dataplot_offset_x_fp
          }
         \fp_set:Nn \l__dataplot_y_fp
          {
            \l__dataplot_min_y_fp * \l__dataplot_scale_y_fp
             + \l__dataplot_offset_y_fp
             + \DTLlegendyoffset
          }
%    \end{macrocode}
%If the y offset is negative anchor at the north otherwise anchor
%south.
%    \begin{macrocode}
         \fp_compare:nNnTF 
           { \DTLlegendyoffset } < { \c_zero_fp }
          {
           \tl_put_right:Nn \l__dataplot_content_tl
            {
              node [ anchor = north ]
            }
          }
          {
           \tl_put_right:Nn \l__dataplot_content_tl
            {
              node [ anchor = south ]
            }
          }
       }
     { 6 } % southwest
       {
         \fp_set:Nn \l__dataplot_x_fp
          {
            \l__dataplot_min_x_fp * \l__dataplot_scale_x_fp
            + \l__dataplot_offset_x_fp
            + \DTLlegendxoffset
          }
         \fp_set:Nn \l__dataplot_y_fp
          {
            \l__dataplot_min_y_fp * \l__dataplot_scale_y_fp
            + \l__dataplot_offset_y_fp
            + \DTLlegendyoffset
          }
         \tl_put_right:Nn \l__dataplot_content_tl
          {
            node [ anchor = 
          }
%    \end{macrocode}
%If the y offset is negative anchor at the north otherwise anchor
%south.
%    \begin{macrocode}
         \fp_compare:nNnTF
           { \DTLlegendyoffset } < { \c_zero_fp }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { north ~ }
          }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { south ~ }
          }
%    \end{macrocode}
%If the x offset is negative anchor at the east otherwise anchor
%west.
%    \begin{macrocode}
         \fp_compare:nNnTF
           { \DTLlegendxoffset } < { \c_zero_fp }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { east }
          }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { west }
          }
         \tl_put_right:Nn \l__dataplot_content_tl
          {
             ]
          }
       }
     { 7 } % west
       {
         \fp_set:Nn \l__dataplot_x_fp
          {
            \l__dataplot_offset_x_fp
             + \l__dataplot_min_x_fp * \l__dataplot_scale_x_fp
             + \DTLlegendxoffset
          }
         \fp_set:Nn \l__dataplot_y_fp
          {
            \l__dataplot_scale_y_fp
             * ( \l__dataplot_min_y_fp + 0.5 * \l__dataplot_y_extent_fp )
            + \l__dataplot_offset_y_fp
          }
%    \end{macrocode}
%If the x offset is negative anchor at the east otherwise anchor
%west.
%    \begin{macrocode}
         \fp_compare:nNnTF
           { \DTLlegendxoffset } < { \c_zero_fp }
          {
           \tl_put_right:Nn \l__dataplot_content_tl
            {
              node [ anchor = east ]
            }
          }
          {
           \tl_put_right:Nn \l__dataplot_content_tl
            {
              node [ anchor = west ]
            }
          }
       }
     { 8 } % northwest
       {
         \fp_set:Nn \l__dataplot_x_fp
          {
            \l__dataplot_offset_x_fp
             + \l__dataplot_min_x_fp * \l__dataplot_scale_x_fp
             + \DTLlegendxoffset
          }
         \fp_set:Nn \l__dataplot_y_fp
          {
            \l__dataplot_offset_y_fp
             + \l__dataplot_max_y_fp * \l__dataplot_scale_y_fp
             - \DTLlegendyoffset
          }
         \tl_put_right:Nn \l__dataplot_content_tl
          {
            node [ anchor = 
          }
%    \end{macrocode}
%If the y offset is negative anchor at the south otherwise anchor
%north.
%    \begin{macrocode}
         \fp_compare:nNnTF
           { \DTLlegendyoffset } < { \c_zero_fp }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { south ~ }
          }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { north ~ }
          }
%    \end{macrocode}
%If the x offset is negative anchor at the east otherwise anchor
%west.
%    \begin{macrocode}
         \fp_compare:nNnTF
           { \DTLlegendxoffset } < { \c_zero_fp }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { east }
          }
          {
            \tl_put_right:Nn \l__dataplot_content_tl { west }
          }
         \tl_put_right:Nn \l__dataplot_content_tl
          {
             ]
          }
       }
   }
   {
     \tl_put_left:Nx \l__dataplot_content_tl
       {
         \exp_not:N \draw
          ( \fp_to_decimal:N \l__dataplot_x_fp,
           \fp_to_decimal:N \l__dataplot_y_fp ) ~
       }
     \tl_put_right:Nx \l__dataplot_content_tl
       {
         ~ {
           \exp_not:N \DTLformatlegend
            {
              \exp_not:V \l_dataplot_legend_tl
            }
         }; ~
       }
   }
   {
     \tl_put_right:Nx \l__dataplot_content_tl
       {
         \exp_not:N \DTLcustomlegend
         {
           \exp_not:V \l_dataplot_legend_tl
         } 
       }
   }
  \l__dataplot_content_tl
  \tl_clear:N \l__dataplot_content_tl
}
%    \end{macrocode}
%Syntax: \meta{seq var} \meta{clist var} \meta{tl var}
%
%Get style setting from sequence, and reset \meta{seq var} from
%\meta{clist var} if empty:
%    \begin{macrocode}
\cs_new:Nn \__dataplot_get_style:NNN
{
  \seq_if_empty:NT #1
  {
    \__dataplot_set_style_from_clist:NN #1 #2 
  }
  \seq_pop_left:NN #1 #3
%    \end{macrocode}
% Check for \cs{relax} to indicate no mark for the current set.
%    \begin{macrocode}
  \tl_if_eq:NnT #3 { \relax }
  {
    \tl_clear:N #3
  }
}
%    \end{macrocode}
%
%\begin{macro}{\DTLplot}
%\begin{definition}
%\cs{DTLplot}\oarg{condition}\marg{db list}\marg{settings}
%\end{definition}
% Creates a plot (inside a \env{tikzpicture} environment) of all
% the data given in the databases listed in \meta{db list}.
%    \begin{macrocode}
\NewDocumentCommand \DTLplot { o m m }
{
 \group_begin:
%    \end{macrocode}
%Initialise command to encapsulate plot code. Any errors should
%change this to do nothing.
%    \begin{macrocode}
  \cs_set_eq:NN \__dataplot_do_plot:n \use:n
%    \end{macrocode}
%Parse options:
%    \begin{macrocode}
  \keys_set:nn { datatool / plot } { #3 }
%    \end{macrocode}
%Check optional argument:
%    \begin{macrocode}
  \IfValueT { #1 }
   {
%    \end{macrocode}
%The optional argument overrides the condition setting:
%    \begin{macrocode}
     \cs_set:Nn \__dataplot_filter:T 
     {
       \ifthenelse { #1 } { ##1 } { }
     }
   }
%    \end{macrocode}
%Store the list of database names in a sequence variable:
%    \begin{macrocode}
  \exp_args:NNx \seq_set_from_clist:Nn
     \l__dataplot_dbnames_seq { #2 }
%    \end{macrocode}
%Check that all the databases exist.
%    \begin{macrocode}
  \seq_map_inline:Nn \l__dataplot_dbnames_seq
   {
     \DTLifdbexists { ##1 } { }
      {
       \PackageError { dataplot }
        {
          Database ~ `##1' ~ doesn't ~ exist
        }
        {
          Check ~ you ~ have ~ spelt ~ the ~ database ~ name ~
          correctly ~ in ~ the ~ argument ~ of ~
          \token_to_str:N \DTLplot { \exp_not:n { #2 } } { ... }
        }
       \cs_set_eq:NN \__dataplot_do_plot:n \use_none:n
      }
   }
  \__dataplot_do_plot:n 
   {
%    \end{macrocode}
%Convert the minor gap dimension to user co-ordinates:
%    \begin{macrocode}
     \fp_set:Nn \l__dataplot_min_minor_gap_fp
       { \DTLminminortickgap / 65536 }
%    \end{macrocode}
%Save the plot marks comma-separated list as a sequence:
%    \begin{macrocode}
     \__dataplot_set_style_from_clist:NN
       \l__dataplot_mark_seq \DTLplotmarks 
%    \end{macrocode}
%Save the plot lines comma-separated list as a sequence:
%    \begin{macrocode}
     \__dataplot_set_style_from_clist:NN
       \l__dataplot_line_seq \DTLplotlines
%    \end{macrocode}
%Save the plot mark colours comma-separated list as a sequence:
%    \begin{macrocode}
     \__dataplot_set_style_from_clist:NN
       \l__dataplot_mark_colors_seq \DTLplotmarkcolors
%    \end{macrocode}
%Save the plot line colours comma-separated list as a sequence:
%    \begin{macrocode}
     \__dataplot_set_style_from_clist:NN
       \l__dataplot_line_colors_seq \DTLplotlinecolors
%    \end{macrocode}
%Clear the legend token list:
%    \begin{macrocode}
     \tl_clear:N \l_dataplot_legend_tl
%    \end{macrocode}
%Check the user has supplied at least one "x" variable:
%    \begin{macrocode}
     \clist_if_empty:NT \l__dataplot_x_keys_clist
     {
       \PackageError { dataplot }
       {Missing ~ x ~ setting ~ for ~ \token_to_str:N \DTLplot}
       {}
       \cs_set_eq:NN \__dataplot_do_plot:n \use_none:n
     }
%    \end{macrocode}
%Check the user has supplied at least one "y" variable:
%    \begin{macrocode}
     \seq_if_empty:NTF \l__dataplot_y_keys_seq
     {
       \PackageError { dataplot }
       {Missing ~ y ~ setting ~ for ~ \token_to_str:N \DTLplot}
       {}
       \cs_set_eq:NN \__dataplot_do_plot:n \use_none:n
     }
     {
%    \end{macrocode}
%Check that there aren't more x than y variables:
%    \begin{macrocode}
       \int_compare:nNnT
         { \clist_count:N \l__dataplot_x_keys_clist }
          >
         { \seq_count:N \l__dataplot_y_keys_seq }
       {
         \PackageError { dataplot }
         { Too ~ many ~ x ~ keys ~ listed ~ in ~ \token_to_str:N \DTLplot }
         { 
            The ~ number ~ of ~ x ~ keys ~ must ~ be ~ less ~ than ~ 
            or ~ equal ~ to ~ the ~ number ~ of ~ y ~ keys
         }
         \cs_set_eq:NN \__dataplot_do_plot:n \use_none:n
       }
     }
    \__dataplot_init_and_do_plot:
  }
 \group_end:
}
%    \end{macrocode}
%\end{macro}
%Initialisation code:
%    \begin{macrocode}
\cs_new:Nn \__dataplot_init_and_do_plot:
{
%    \end{macrocode}
% If user didn't specified bounds, 
% compute the maximum and minimum $x$ and $y$ values over 
% all the databases listed.
%    \begin{macrocode}
 \__dataplot_do_plot:n
  {
    \__dataplot_update_bounds:
  }
%    \end{macrocode}
%Do the plot code:
%    \begin{macrocode}
 \__dataplot_do_plot:n
  {
%    \end{macrocode}
%Calculate the extended bounds for the axes and box:
%    \begin{macrocode}
    \fp_set:Nn \l__dataplot_extended_min_x_fp
     {
       \l__dataplot_min_x_fp
       - \l__dataplot_extend_min_x_axis_fp
     }
    \fp_set:Nn \l__dataplot_extended_min_y_fp
     {
       \l__dataplot_min_y_fp
       - \l__dataplot_extend_min_y_axis_fp
     }
    \fp_set:Nn \l__dataplot_extended_max_x_fp
     {
       \l__dataplot_max_x_fp
       + \l__dataplot_extend_max_x_axis_fp
     }
    \fp_set:Nn \l__dataplot_extended_max_y_fp
     {
       \l__dataplot_max_y_fp
       + \l__dataplot_extend_max_y_axis_fp
     }
%    \end{macrocode}
%Determine whether to set the zero axis booleans:
%    \begin{macrocode}
    \bool_set_false:N \l__dataplot_zero_x_axis_bool
    \bool_set_false:N \l__dataplot_zero_y_axis_bool
    \bool_if:NF \l__dataplot_side_axes_bool 
    {
      \fp_compare:nT
        {
          \l__dataplot_extended_min_x_fp
          < \c_zero_fp
          < \l__dataplot_extended_max_x_fp
         }
       {
%    \end{macrocode}
%X axis includes 0. That is, the y axis should intersect at x=0:
%    \begin{macrocode}
         \bool_set_true:N \l__dataplot_zero_x_axis_bool
       }
      \fp_compare:nT
        {
          \l__dataplot_extended_min_y_fp
          < \c_zero_fp
          < \l__dataplot_extended_max_y_fp
        }
       {
%    \end{macrocode}
%Y axis includes 0. That is, the x axis should intersect at y=0:
%    \begin{macrocode}
         \bool_set_true:N \l__dataplot_zero_y_axis_bool
       }
    }
%    \end{macrocode}
%Check the omit zero label setting:
%    \begin{macrocode}
   \int_case:nnF { \l__dataplot_omit_zero_label_action_int }
    {
      { 2 } % omit both
       {
         \bool_set_true:N \l__dataplot_omit_zero_x_label_bool
         \bool_set_true:N \l__dataplot_omit_zero_y_label_bool
       }
      { 3 } % omit x
       {
         \bool_set_true:N \l__dataplot_omit_zero_x_label_bool
         \bool_set_false:N \l__dataplot_omit_zero_y_label_bool
       }
      { 4 } % omit y
       {
         \bool_set_false:N \l__dataplot_omit_zero_x_label_bool
         \bool_set_true:N \l__dataplot_omit_zero_y_label_bool
       }
      { 5 } % don't omit
       {
         \bool_set_false:N \l__dataplot_omit_zero_x_label_bool
         \bool_set_false:N \l__dataplot_omit_zero_y_label_bool
       }
    }
    {% auto
      \bool_set_eq:NN
        \l__dataplot_omit_zero_x_label_bool
        \l__dataplot_zero_x_axis_bool
      \bool_set_eq:NN
        \l__dataplot_omit_zero_y_label_bool
        \l__dataplot_zero_y_axis_bool
    }
%    \end{macrocode}
%Determine whether or not to draw ticks along the minimum box edges.
%    \begin{macrocode}
   \bool_lazy_all:nTF
    {
     { \legacy_if_p:n { DTLbox } }
     { \bool_not_p:n { \l__dataplot_side_axes_bool } }
     { ! \fp_compare_p:nNn { \l__dataplot_extended_min_y_fp } = { \c_zero_fp } }
    }
    {
      \cs_set_eq:NN \__dataplot_draw_box_ticks_lower_x:n \use:n
    }
    {
      \cs_set_eq:NN \__dataplot_draw_box_ticks_lower_x:n \use_none:n
    }
   \bool_lazy_all:nTF 
    {
     { \legacy_if_p:n { DTLbox } }
     { \bool_not_p:n { \l__dataplot_side_axes_bool } }
     { ! \fp_compare_p:nNn { \l__dataplot_extended_min_x_fp } = { \c_zero_fp } }
    }
    {
      \cs_set_eq:NN \__dataplot_draw_box_ticks_lower_y:n \use:n
    }
    {
      \cs_set_eq:NN \__dataplot_draw_box_ticks_lower_y:n \use_none:n
    }
%    \end{macrocode}
% Determine scaling factors and offsets.
%\changes{2.14}{2013-06-28}{replaced \cs{FPsub} etc with \cs{dtlsub} etc}
%\changes{3.0}{2025-03-03}{switched to \sty{l3fp}}
%    \begin{macrocode}
    \dataplot_calc_transform:
%    \end{macrocode}
% If x tics specified, construct a list of x tic points if not
% already specified.
%    \begin{macrocode}
    \__dataplot_calc_x_tics:
%    \end{macrocode}
% If y tics specified, construct a list of y tic points if not
% already specified.
%    \begin{macrocode}
    \__dataplot_calc_y_tics:
%    \end{macrocode}
% Do the picture.
%    \begin{macrocode}
    \__dataplot_do_plot:
  }
}
%    \end{macrocode}
%Action `plot':
%    \begin{macrocode}
\cs_new:cn { __datatool_action_ plot : }
{
 \group_begin:
%    \end{macrocode}
%This will be the primary return value.
%    \begin{macrocode}
  \int_zero:N \l_dataplot_stream_index_int
  \tl_clear:N \l__dataplot_content_tl
%    \end{macrocode}
%Initialise command to encapsulate plot code. Any errors should
%change this to do nothing.
%    \begin{macrocode}
  \cs_set_eq:NN \__dataplot_do_plot:n \use:n
%    \end{macrocode}
% Parse options if provided.
%    \begin{macrocode}
  \clist_if_empty:NF \l__datatool_action_options_clist
   {
     \keys_set:nV { datatool / plot } 
       \l__datatool_action_options_clist
   }
%    \end{macrocode}
%The `name' action setting may be a comma-separated list of database
%names. These will already be available in a sequence, so just copy
%it.
%    \begin{macrocode}
  \seq_set_eq:NN
     \l__dataplot_dbnames_seq \l__datatool_action_names_seq
%    \end{macrocode}
%Check that all the databases exist.
%    \begin{macrocode}
  \seq_map_inline:Nn \l__dataplot_dbnames_seq
   {
     \DTLifdbexists { ##1 } { }
      {
       \__datatool_action_error:nn
        {
          Database ~ `##1' ~ doesn't ~ exist
        }
        {
          Check ~ you ~ have ~ spelt ~ the ~ database ~ name ~
          correctly ~ in ~
          \token_to_str:N \DTLaction [name=
          { \seq_use::N \l__datatool_action_names_seq { , } } ]
          { \l__datatool_action_tl } ~ or ~ check ~ the ~ default-name ~
          option ~ in ~ \token_to_str:N \DTLsetup
        }
       \cs_set_eq:NN \__dataplot_do_plot:n \use_none:n
      }
   }
  \__dataplot_do_plot:n 
   {
%    \end{macrocode}
%Convert the minor gap dimension to user co-ordinates:
%    \begin{macrocode}
     \fp_set:Nn \l__dataplot_min_minor_gap_fp
       { \DTLminminortickgap / 65536 }
%    \end{macrocode}
%Save the plot marks comma-separated list as a sequence:
%    \begin{macrocode}
     \__dataplot_set_style_from_clist:NN
       \l__dataplot_mark_seq \DTLplotmarks 
%    \end{macrocode}
%Save the plot lines comma-separated list as a sequence:
%    \begin{macrocode}
     \__dataplot_set_style_from_clist:NN
       \l__dataplot_line_seq \DTLplotlines
%    \end{macrocode}
%Save the plot mark colours comma-separated list as a sequence:
%    \begin{macrocode}
     \__dataplot_set_style_from_clist:NN
       \l__dataplot_mark_colors_seq \DTLplotmarkcolors
%    \end{macrocode}
%Save the plot line colours comma-separated list as a sequence:
%    \begin{macrocode}
     \__dataplot_set_style_from_clist:NN
       \l__dataplot_line_colors_seq \DTLplotlinecolors
%    \end{macrocode}
%Clear the legend token list:
%    \begin{macrocode}
     \tl_clear:N \l_dataplot_legend_tl
%    \end{macrocode}
%Check the user has supplied at least one "x" variable:
%    \begin{macrocode}
     \clist_if_empty:NT \l__dataplot_x_keys_clist
     {
       \__datatool_action_error:nn
       {
         Missing ~ x ~ setting
       }
       {
         The ~ `x' ~ setting ~ needs ~ to ~ be ~ set ~ to ~
         the ~ column ~ key ~ to ~ be ~ used ~ for ~
         the ~ plot ~ x ~ values, ~ either ~ in ~
         \token_to_str:N
         \DTLaction [ options={x={...}}, ~ ... ]
           { \l__datatool_action_tl } ~ or ~ in ~
         \token_to_str:N \DTLsetup { plot = { x= { ... } } }
       }
       \cs_set_eq:NN \__dataplot_do_plot:n \use_none:n
     }
%    \end{macrocode}
%Check the user has supplied at least one "y" variable:
%    \begin{macrocode}
     \seq_if_empty:NTF \l__dataplot_y_keys_seq
     {
       \__datatool_action_error:nn
       {
         Missing ~ y ~ setting
       }
       {
         The ~ `y' ~ setting ~ needs ~ to ~ be ~ set ~ to ~
         the ~ column ~ key ~ to ~ be ~ used ~ for ~
         the ~ plot ~ y ~ values, ~ either ~ in ~
         \token_to_str:N
         \DTLaction [ options={y={...}}, ~ ... ]
           { \l__datatool_action_tl } ~ or ~ in ~
         \token_to_str:N \DTLsetup { plot = { y= { ... } } }
       }
       \cs_set_eq:NN \__dataplot_do_plot:n \use_none:n
     }
     {
%    \end{macrocode}
%Check that there aren't more x than y variables:
%    \begin{macrocode}
       \int_compare:nNnT
         { \clist_count:N \l__dataplot_x_keys_clist }
          >
         { \seq_count:N \l__dataplot_y_keys_seq }
       {
         \__datatool_action_error:nn
         {
            Too ~ many ~ x ~ keys ~ listed ~ in ~
            x = { \l__dataplot_x_keys_clist }
         }
         { 
            The ~ number ~ of ~ x ~ keys ~ must ~ be ~ less ~ than ~ 
            or ~ equal ~ to ~ the ~ number ~ of ~ y ~ keys
         }
         \cs_set_eq:NN \__dataplot_do_plot:n \use_none:n
       }
     }
    \__dataplot_do_plot:n
     {
%    \end{macrocode}
% Initialise and do the plot.
%    \begin{macrocode}
       \__dataplot_init_and_do_plot:
%    \end{macrocode}
% Secondary return properties. Calculating the bounds may have
% trigger an error, in which case don't set the secondary return
% values.
%    \begin{macrocode}
       \__dataplot_do_plot:n
        {
         \tl_set:Nx \l__dataplot_content_tl
          {
            \exp_not:N \__datatool_put_return_action_decimal:nn
             { min-x } { \DTLminX }
            \exp_not:N \__datatool_put_return_action_decimal:nn
             { min-y } { \DTLminY }
            \exp_not:N \__datatool_put_return_action_decimal:nn
             { max-x } { \DTLmaxX }
            \exp_not:N \__datatool_put_return_action_decimal:nn
             { max-y } { \DTLmaxY }
            \exp_not:N \__datatool_put_return_action_int:nn
             { stream-count }
             { \int_use:N \l_dataplot_stream_index_int }
            \exp_not:N \__datatool_put_return_action_int:nn
             { x-count } { \dataplot_x_key_count: }
            \exp_not:N \__datatool_put_return_action_int:nn
             { y-count } { \dataplot_y_key_count: }
            \exp_not:N \__datatool_put_return_action_int:nn
             { name-count } { \dataplot_db_count: }
          }
        }
     }
  }
%    \end{macrocode}
% Primary return property and end group.
%    \begin{macrocode}
 \tl_put_left:Nx \l__dataplot_content_tl
   { 
     \exp_not:N \group_end:
     \exp_not:N \tl_set:Nn 
       \exp_not:N \l__datatool_action_return_tl
        { \int_use:N \l_dataplot_stream_index_int }
   }
 \l__dataplot_content_tl
}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__dataplot_set_style_from_clist:NN
{
  \seq_set_from_clist:NN #1 #2
%    \end{macrocode}
%The sequence must contain at least one item:
%    \begin{macrocode}
  \seq_if_empty:NT #1
   {
     \seq_put_right:Nn #1 { }
   }
}
%    \end{macrocode}
%
%\begin{macro}{\dtl@getbounds}
% Extract bounds function removed in version 3.0.
%See \cs{\_\_dataplot\_update\_bounds:nn}
%\changes{2.14}{2013-06-28}{changed \cs{FPifgt} to \cs{dtlifnumgt}}
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{definition}
%\cs{\_\_dataplot\_construct\_tick\_list:NNNN}\meta{min-fp}\meta{max-fp}\meta{min-gap-fp}\meta{seq}
%\end{definition}
%    \begin{macrocode}
\cs_new:Nn \__dataplot_construct_tick_list:NNNN
{
  \fp_compare:nTF { #1 < \c_zero_fp < #2 }
  {
%    \end{macrocode}
% Tick list straddles the origin.
%    \begin{macrocode}
    \fp_set:Nn \l__dataplot_width_fp { - ( #1 ) }
    \__dataplot_default_gap:NNN
      \l__dataplot_neg_gap_fp \l__dataplot_width_fp  #3
    \__dataplot_default_gap:NNN
      \l__dataplot_pos_gap_fp #2 #3
    \fp_set:Nn \l__dataplot_gap_fp
       { max ( \l__dataplot_neg_gap_fp, \l__dataplot_pos_gap_fp ) }
%    \end{macrocode}
% Don't construct a list if minimum gap is greater than plot width
%    \begin{macrocode}
    \fp_compare:nF { \l__dataplot_gap_fp > \l__dataplot_width_fp }
    {
      \__dataplot_construct_tick_list_over_zero:NNNN
       #1 #2 #4 \l__dataplot_gap_fp
    }
  }
  {
%    \end{macrocode}
% Tick list doesn't straddle the origin.
%    \begin{macrocode}
    \fp_set:Nn \l__dataplot_width_fp { #2 - #1 }
    \__dataplot_default_gap:NNN
      \l__dataplot_gap_fp \l__dataplot_width_fp #3
    \fp_compare:nTF { \l__dataplot_gap_fp < #3 }
    {
%    \end{macrocode}
% Don't construct a list if minimum gap is greater than plot width
%    \begin{macrocode}
      \fp_compare:nTF { #3 > \l__dataplot_width_fp }
      {
         \seq_put_right:NV #4 #1
         \seq_put_right:NV #4 #2
      }
      {
        \__dataplot_construct_tick_list_with_gap:NNNN
         #1 #2 #4 #3
      }
    }
    {
      \__dataplot_construct_tick_list_with_gap:NNNN
       #1 #2 #4 \l__dataplot_gap_fp
    }
  }
}
%    \end{macrocode}
%
%\cs{\_\_dataplot\_default\_gap:NNN} \meta{fp-var} \marg{extent} \marg{max
%gap}
%    \begin{macrocode}
\cs_new:Nn \__dataplot_default_gap:NNN
{
  \fp_set:Nn #1 { #2 / 10 }
  \fp_compare:nNnT { #1 } < { #3 }
   {
     \fp_set:Nn #1 { #2 / 5 }
     \fp_compare:nNnT { #1 } < { #3 }
     {
       \fp_set_eq:NN #1 #3 
     }
   }
  \fp_compare:nTF { #1 <= 0.1 }
   {
     \fp_set:Nn #1 { 0.1 }
   }
   {
     \fp_compare:nTF { #1 <= 0.5 }
      {
        \fp_set:Nn #1 { 0.5 }
      }
      {
        \fp_compare:nTF { #1 <= 0.5 }
         {
           \fp_set:Nn #1 { 0.5 }
         }
         {
           \fp_compare:nTF { #1 <= \c_one_fp }
            {
              \fp_set:NN #1 \c_one_fp
            }
            {
              \fp_compare:nTF { #1 <= 5 }
               {
                 \fp_set:Nn #1 { 5 }
               }
               {
                 \fp_compare:nTF { #1 <= 100 }
                  {
                    \fp_set:Nn #1 { 10 }
                  }
                  {
                    \fp_compare:nTF { #1 <= 1000 }
                     {
                       \fp_set:Nn #1 { 100 }
                     }
                     {
                       \fp_set:Nn #1 { 1000 * floor ( #1 / 1000 ) }
                     }
                  }
               }
            }
         }
      }
   }
}
%    \end{macrocode}
%
%\begin{macro}{\dtl@constructticklist}
%\begin{definition}
%\cs{dtl@constructticklist}\marg{min}\marg{max}\marg{min gap}\marg{list}
%\end{definition}
% Constructs a list of tick points between \meta{min} and \meta{max}
% and store in \meta{list} (a control sequence.)
%\changes{2.14}{2013-06-28}{replaced \cs{FPsub} with \cs{dtlsub} etc}
%\changes{3.0}{2025-03-03}{removed}
%
%Version 3.0: replaced with 
%\cs{\_\_dataplot\_construct\_tick\_list:NNNN}\meta{min-fp}\meta{max-fp}\meta{seq}\meta{min-gap-fp}
%\end{macro}
%
%\begin{definition}
%\cs{dataplot\_add\_end\_tick:NNN} \meta{fp var1} \meta{fp var2}
%\meta{gap fp} \meta{seq}
%\end{definition}
%Determine whether or not to add end tick. The first argument is the
%possible overshoot, the second argument is the calculated or
%provided maximum and the third is the tick gap.
%    \begin{macrocode}
\cs_new:Nn \dataplot_add_end_tick:NNNN
{
  \fp_compare:nNnT 
     { #1 - #2 } < { 0.1 * #3 }
   {
     \seq_put_right:NV #4 #2
   }
}
%    \end{macrocode}
%\begin{definition}
%\cs{\_\_dataplot\_construct\_tick\_list\_with\_gap:NNNN}
%\meta{min-fp}\meta{max-fp}\meta{seq}\meta{gap-fp}
%\end{definition}
% Constructs a list of tick points between \meta{min} and \meta{max}
% and store in \meta{list} (a control sequence) using the gap given
% by \meta{gap} where the gap is given in user co-ordinates.
%    \begin{macrocode}
\cs_new:Nn \__dataplot_construct_tick_list_with_gap:NNNN
{
  \fp_set:Nn \l__datatool_tmpa_fp
     { #4 * ( floor ( #1 / #4 ) + 1 ) }
  \fp_compare:nNnT 
     { #1 - \l__datatool_tmpa_fp } < { 0.5 * #4 }
   {
     \seq_put_right:NV #3 #1
   }
  \fp_while_do:nn { #1 < \l__datatool_tmpa_fp < #2 }
   {
     \seq_put_right:NV #3 \l__datatool_tmpa_fp
     \fp_add:Nn \l__datatool_tmpa_fp { #4 }
   }
  \fp_compare:nNnT 
     { \l__datatool_tmpa_fp - #2 } < { 0.5 * #4 }
   {
     \seq_put_right:NV #3 #2
   }
}
%    \end{macrocode}
%
%\begin{macro}{\dtl@constructticklistwithgap}
%Version 3.0: replaced \cs{dtl@constructticklistwithgap} with
% \cs{\_\_dataplot\_construct\_tick\_list\_with\_gap:NNNN}
%\changes{2.14}{2013-06-28}{replaced \cs{FPadd} with \cs{dtladd}}
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{definition}
%\cs{\_\_dataplot\_construct\_tick\_list\_over\_zero:NNNN}
%\meta{min-fp}\meta{max-fp}\meta{seq}\meta{gap-fp}
%\end{definition}
% Constructs a list of tick points between \meta{min} and \meta{max}
% and store in \meta{list} (a control sequence) using the gap given
% by \meta{gap} where the tick list straddles zero.
%    \begin{macrocode}
\cs_new:Nn \__dataplot_construct_tick_list_over_zero:NNNN
{
  \fp_compare:nNnF { #4 } < { \c_dataplot_smallest_gap_fp }
   {
     \seq_put_right:NV #3 \c_zero_fp
     \fp_set_eq:NN \l__datatool_tmpa_fp #4
     \fp_while_do:nn { \c_zero_fp < \l__datatool_tmpa_fp < #2 }
     {
       \seq_put_right:NV #3 \l__datatool_tmpa_fp
       \fp_add:Nn \l__datatool_tmpa_fp { #4 }
     }
     \dataplot_add_end_tick:NNNN
       \l__datatool_tmpa_fp #2 #4 #3
     \fp_compare:nF { #1 = \c_zero_fp }
     {
       \fp_set:Nn \l__datatool_tmpa_fp { - ( #4 ) }
       \fp_while_do:nn { #1 < \l__datatool_tmpa_fp < \c_zero_fp }
        {
          \seq_put_left:NV #3 \l__datatool_tmpa_fp
          \fp_sub:Nn \l__datatool_tmpa_fp { #4 }
        }
       \fp_compare:nNnT
          { #1 - \l__datatool_tmpa_fp } < { 0.5 * #4 }
        {
          \seq_put_left:NV #3 #1
        }
     }
   }
}
%    \end{macrocode}
%\end{macro}
%
%Construct minor list.
% Syntax: \meta{major tic seq}\meta{min fp}\meta{max fp}\meta{scale
% fp}\meta{minor tic seq}
%    \begin{macrocode}
\cs_new:Nn \__dataplot_construct_minor_ticks:NNNNN
{
%    \end{macrocode}
% Calculate minor gap, which will be stored in 
% \verb|\l__dataplot_gap_fp|
%    \begin{macrocode}
  \int_compare:nNnTF
    { \seq_count:N #1 } > { 3 }
   {
     \tl_set:Nx \l__dataplot_x_fp
       { \seq_item:Nn #1 { 2 } }
     \tl_set:Nx \l__dataplot_y_fp
       { \seq_item:Nn #1 { 3 } }
     \__dataplot_calc_minor_gap:NNN
       \l__dataplot_x_fp \l__dataplot_y_fp
       #4
   }
   {
     \int_compare:nNnTF
       { \seq_count:N #1 } > { \c_one_int }
      {
        \tl_set:Nx \l__dataplot_x_fp
         { \seq_item:Nn #1 { 1 } }
        \tl_set:Nx \l__dataplot_y_fp
         { \seq_item:Nn #1 { 2 } }
        \__dataplot_calc_minor_gap:NNN
         \l__dataplot_x_fp \l__dataplot_y_fp
         #4
      }
      {
        \__dataplot_calc_minor_gap:NNN
         #2 #3 #4
      }
   }
%    \end{macrocode}
% Previous tick
%    \begin{macrocode}
   \fp_set_eq:NN \l__dataplot_x_fp #2
   \seq_map_indexed_inline:Nn #1
    {
%    \end{macrocode}
% Current major tick
%    \begin{macrocode}
       \tl_set:Nn \l__dataplot_y_fp { ##2 }
       \int_compare:nNnT { ##1 } > { \c_one_int }
        {
%    \end{macrocode}
% Construct minor ticks between them:
%    \begin{macrocode}
          \int_compare:nNnTF { ##1 } = { 2 } 
           {
             \__dataplot_construct_reverse_tick_list:NNNN
              \l__dataplot_x_fp
              \l__dataplot_y_fp
              #5
              \l__dataplot_gap_fp
           }
           {
             \__dataplot_construct_tick_list_with_gap_excl:NNNN
              \l__dataplot_x_fp
              \l__dataplot_y_fp
              #5
              \l__dataplot_gap_fp
           }
       }
%    \end{macrocode}
% Previous major tick for next iteration:
%    \begin{macrocode}
       \fp_set_eq:NN \l__dataplot_x_fp \l__dataplot_y_fp
    }
%    \end{macrocode}
% Final ticks
%    \begin{macrocode}
   \fp_set_eq:NN \l__dataplot_y_fp #3
   \fp_compare:nNnT
    { \l__dataplot_min_minor_gap_fp }
    < 
    { \l__dataplot_y_fp - \l__dataplot_x_fp }
   {
     \__dataplot_construct_tick_list_with_gap_excl:NNNN
       \l__dataplot_x_fp
       \l__dataplot_y_fp
       #5
       \l__dataplot_gap_fp
   }
}
%    \end{macrocode}
%
%Calculate minor gap and stores the result in \verb|\l__dataplot_gap_fp|
%    \begin{macrocode}
\cs_new:Nn \__dataplot_calc_minor_gap:NNN
{
  \fp_set:Nn \l__dataplot_width_fp
   {
     ( #2 - #1 ) * #3
   }
  \fp_set:Nn \l__dataplot_gap_fp
   { \l__dataplot_width_fp / 10 }
  \fp_compare:nT
   { \l__dataplot_gap_fp < \l__dataplot_min_minor_gap_fp }
  {
    \fp_set:Nn \l__dataplot_gap_fp
     { \l__dataplot_width_fp / 4}
    \fp_compare:nT
     { \l__dataplot_gap_fp < \l__dataplot_min_minor_gap_fp }
    {
      \fp_set:Nn \l__dataplot_gap_fp
       { \l__dataplot_width_fp / 2}
      \fp_compare:nT
       { \l__dataplot_gap_fp < \l__dataplot_min_minor_gap_fp }
      {
        \fp_set_eq:NN \l__dataplot_gap_fp \l__dataplot_width_fp
      }
    }
  }
  \fp_set:Nn \l__dataplot_gap_fp 
   { \l__dataplot_gap_fp  / #3 }
}
%    \end{macrocode}
%
%\begin{macro}{\dtl@constructticklistwithgapz}
%Version 3.0: replaced \cs{dtl@constructticklistwithgapz} with
%\cs{\_\_dataplot\_construct\_tick\_list\_with\_gap\_excl:NNNN}
%\meta{min-fp}\meta{max-fp}\meta{seq}\meta{gap-fp}
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\dtl@constructminorticklist}
%Version 3.0: removed
%\begin{definition}
%\cs{dtl@constructminorticklist}\marg{min}\marg{max}\marg{scale factor}\marg{list}
%\end{definition}
% Constructs a list of minor tick points between \meta{min} and \meta{max}
% and append to \meta{list} (a control sequence.)
%\changes{2.14}{2013-06-28}{replaced \cs{FPsub} with \cs{dtlsub} etc}
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{definition}
%\cs{\_\_dataplot\_construct\_tick\_list\_with\_gap\_excl:NNNN}
%\meta{min-fp}\meta{max-fp}\meta{seq}\meta{gap-fp}
%\end{definition}
% Constructs a list of tick points between \meta{min} and \meta{max}
% and appends to \meta{seq} using the gap given
% by \meta{gap} where the gap is given in user co-ordinates.
% The end points are not included in the list.
%    \begin{macrocode}
\cs_new:Nn \__dataplot_construct_tick_list_with_gap_excl:NNNN
{
  \fp_compare:nTF
    { #1 < \c_zero_fp < #2 }
   {
     \__dataplot_construct_tick_list_over_zero:NNNN
      #1 #2 #3 #4
   }
   {
    \fp_compare:nNnF { #4 } < { \c_dataplot_smallest_gap_fp }
     {
       \fp_set:Nn \l__datatool_tmpa_fp { #1 + #4 }
       \fp_while_do:nn { #1 < \l__datatool_tmpa_fp < #2 }
       {
         \seq_put_right:NV #3 \l__datatool_tmpa_fp
         \fp_add:Nn \l__datatool_tmpa_fp { #4 }
       }
     }
  }
}
%    \end{macrocode}
%Syntax: \meta{min-fp}\meta{max-fp}\meta{seq}\meta{gap-fp}
%    \begin{macrocode}
\cs_new:Nn \__dataplot_construct_reverse_tick_list:NNNN
{
  \fp_compare:nNnF { #4 } < { \c_dataplot_smallest_gap_fp }
   {
     \fp_set:Nn \l__datatool_tmpa_fp { #2 - #4 }
     \fp_while_do:nn { #1 < \l__datatool_tmpa_fp < #2 }
     {
       \seq_put_left:NV #3 \l__datatool_tmpa_fp
       \fp_sub:Nn \l__datatool_tmpa_fp { #4 }
     }
   }
}
%    \end{macrocode}
%
%\begin{definition}
%\cs{\_\_dataplot\_construct\_tick\_list\_with\_gap\_incl:NNNN}
%\meta{min-fp}\meta{max-fp}\meta{seq}\meta{gap-fp}
%\end{definition}
% Constructs a list of tick points between \meta{min} and \meta{max}
% and appends to \meta{seq} using the gap given
% by \meta{gap} where the gap is given in user co-ordinates.
% The end points are included in the list if the gap is large enough.
%    \begin{macrocode}
\cs_new:Nn \__dataplot_construct_tick_list_with_gap_incl:NNNN
{
  \fp_compare:nTF
    { #1 < \c_zero_fp < #2 }
   {
     \__dataplot_construct_tick_list_over_zero:NNNN
      #1 #2 #3 #4
   }
   {
    \fp_compare:nNnF { #4 } < { \c_dataplot_smallest_gap_fp }
     {
       \fp_set:Nn \l__datatool_tmpa_fp { #1 + #4 }
       \seq_put_right:NV #3 #1
       \fp_while_do:nn { #1 < \l__datatool_tmpa_fp < #2 }
       {
         \seq_put_right:NV #3 \l__datatool_tmpa_fp
         \fp_add:Nn \l__datatool_tmpa_fp { #4 }
       }
       \fp_compare:nNnT 
         { \l__datatool_tmpa_fp - #2 } < { 0.1 * #4 }
        {
          \seq_put_right:NV #3 #2
        }
     }
  }
}
%    \end{macrocode}
%
%
%\begin{macro}{\dtl@constructticklistwithgapex}
%Version 3.0: removed
%\begin{definition}
%\cs{dtl@constructticklistwithgapex}\marg{min}\marg{max}\marg{list}\marg{gap}
%\end{definition}
% Constructs a list of tick points between \meta{min} and \meta{max}
% and store in \meta{list} (a control sequence) using the gap given
% by \meta{gap} where the gap is given in user co-ordinates.
% The end points are excluded from the list.
%\changes{2.14}{2013-06-28}{replaced \cs{FPadd} with \cs{dtladd} and
%changed third argument to minimum gap width (in data co-ordinates)}
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\DTLaddtoplotlegend}
%\begin{definition}
%\cs{DTLaddtoplotlegend}\marg{marker}\marg{line style}\marg{label}
%\end{definition}
% Adds entry to legend.
%\changes{2.15}{2013-07-10}{Used \cs{xdef} instead of \cs{edef} as
%may be scoped.}
%\changes{3.0}{2025-03-03}{switched to \cs{l\_dataplot\_legend\_tl} instead of
%\cs{dtl@legend}}
%    \begin{macrocode}
\newcommand*{\DTLaddtoplotlegend}[3]{%
 \tl_if_empty:NF \l_dataplot_legend_tl
 {
   \tl_put_right:Nn \l_dataplot_legend_tl { \\ }
 }
 \tl_clear:N \l__datatool_tmpc_tl
 \tl_if_empty:nF { #2 }
 {
   \tl_if_eq:nnF { #2 } { \relax }
   {
     \tl_put_right:Nn \l__datatool_tmpc_tl
      {
        \begin{pgfscope}
          #2
          \pgfpathmoveto{\pgfpoint{-10pt}{0pt}}
          \pgfpathlineto{\pgfpoint{10pt}{0pt}}
          \pgfusepath{stroke} 
        \end{pgfscope}
      }
    }
 }
 \tl_if_empty:nF { #1 }
 {
   \tl_if_eq:nnF { #1 } { \relax }
   {
     \tl_put_right:Nn \l__datatool_tmpc_tl { #1 }
   }
 }
 \tl_if_empty:NF \l__datatool_tmpc_tl
 {
   \tl_put_right:Nn \l_dataplot_legend_tl
     { \begin { pgfpicture } }
   \tl_put_right:NV \l_dataplot_legend_tl \l__datatool_tmpc_tl
   \tl_put_right:Nn \l_dataplot_legend_tl
     { \end { pgfpicture } }
   \tl_put_right:Nn \l_dataplot_legend_tl { & #3 }
 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{definition}
%\cs{dataplot\_get\_default\_legend:Nnnnn}
%\meta{tl-var}\marg{database index}\marg{database name}\marg{x key}\marg{y key}
%\end{definition}
%Get the default legend label (if legend labels not set).
%The \meta{tl-var} is the token list variable in which to store the
%legend label. The current x key index can be accessed with
%\cs{l\_dataplot\_x\_key\_int} and the current y key index
% can be accessed with \cs{l\_dataplot\_x\_key\_int}.
%    \begin{macrocode}
\cs_new:Nn \dataplot_get_default_legend:Nnnnn
{
  \int_compare:nNnTF 
   { \dataplot_x_key_count: } > { \c_one_int }
  {
    \tl_put_right:Nx #1
     {
       \exp_not:N \DTLplotlegendxy
        [ #2 ] { #3 }
        [ \int_use:N \l_dataplot_x_key_int ] { #4 }
        [ \int_use:N \l_dataplot_y_key_int ] { #5 }
     }
  }
  {
    \int_compare:nNnT 
     { \dataplot_y_key_count: } > { \c_one_int }
    {
      \tl_put_right:Nx #1
       {
         \exp_not:N \DTLplotlegendy
          [ #2 ] { #3 }
          [ \int_use:N \l_dataplot_y_key_int ] { #5 }
       }
    }
  }
 \tl_if_empty:NTF #1
  {
    \tl_set:Nx #1
     {
       \exp_not:N \DTLplotlegendname [ #2 ] { #3 }
     }
  }
  {
    \int_compare:nNnT
       { \dataplot_db_count: } > { \c_one_int }
     {
       \tl_put_left:Nx #1
        {
          \exp_not:N \DTLplotlegendname [ #2 ] { #3 }
          \exp_not:N \DTLplotlegendnamesep
        }
     }
  }
}
%    \end{macrocode}
%In the following commands, \meta{db index} is the index of the
%database name in the list supplied to \cs{DTLplot}, \meta{x index}
%is the index of the \meta{x key} in the "x" list, and
%\meta{y index} the index of the \meta{y key} in the "y" list.
%This is not the same as the column index (which can be obtained
%with \cs{dtlcolumnindex}\marg{db-name}\marg{key}).
%
%\begin{macro}{\DTLplotlegendxy}
%\begin{definition}
%\cs{DTLplotlegendxy}\oarg{db index}\marg{db-name}\oarg{x
%index}\marg{x key}\oarg{y index}\marg{y key}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%$x$ / $y$ label in legend.
%    \begin{macrocode}
\NewDocumentCommand \DTLplotlegendxy { O{0} m O{0} m O{0} m }
 {
   \DTLplotlegendx [ #1 ] { #2 } [ #3 ] { #4 }
   \DTLplotlegendxysep
   \DTLplotlegendy [ #1 ] { #2 } [ #5 ] { #6 }
 }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLplotlegendxysep}
%\changes{3.0}{2025-03-03}{new}
%Separator between $x$ and $y$ labels in legend.
%    \begin{macrocode}
\newcommand \DTLplotlegendxysep
 {
  \c_space_tl / \c_space_tl
 }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLplotlegendnamesep}
%\changes{3.0}{2025-03-03}{new}
%Separator after name in legend.
%    \begin{macrocode}
\newcommand \DTLplotlegendnamesep { ~ }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLplotlegendname}
%\begin{definition}
%\cs{DTLplotlegendxy}\oarg{db index}\marg{db-name}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%Database label in legend.
%    \begin{macrocode}
\NewDocumentCommand \DTLplotlegendname { O{0} m }
 {
   \prop_get:NnNTF \l_dataplot_legend_names_prop
      { #2 } \l_dataplot_legend_name_tl
    { \l_dataplot_legend_name_tl }
    { #2 }
 }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLplotlegendsetname}
%\changes{3.0}{2025-03-03}{new}
% Allow the user to supply a database name
%to legend text mapping.
%    \begin{macrocode}
\prop_new:N \l_dataplot_legend_names_prop
\tl_new:N \l_dataplot_legend_name_tl
\NewDocumentCommand \DTLplotlegendsetname { m m }
{
  \prop_put:Nnn \l_dataplot_legend_names_prop { #1 } { #2 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLplotlegendx}
%\begin{definition}
%\cs{DTLplotlegendx}\oarg{db index}\marg{db-name}\oarg{x index}\marg{x-key}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%$x$ label in legend.
%    \begin{macrocode}
\NewDocumentCommand \DTLplotlegendx { O{0} m O{0} m }
 {
%    \end{macrocode}
%If a mapping has been supplied for the x key, use that.
%    \begin{macrocode}
   \prop_get:NnNTF \l_dataplot_legend_xlabels_prop
      { #4 } \l_dataplot_legend_xlabel_tl
    { \l_dataplot_legend_xlabel_tl }
    {
      \DTLaction
        [ name = { #2 }, key = { #4 } ]
        { column ~ data }
      \DTLuse { header }
    }
 }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLplotlegendsetxlabel}
%\changes{3.0}{2025-03-03}{new}
% Allow the user to supply alternative text for the $x$ label
%to legend text mapping.
%    \begin{macrocode}
\prop_new:N \l_dataplot_legend_xlabels_prop
\tl_new:N \l_dataplot_legend_xlabel_tl
\NewDocumentCommand \DTLplotlegendsetxlabel { m m }
{
  \prop_put:Nnn \l_dataplot_legend_xlabels_prop { #1 } { #2 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLplotlegendy}
%\begin{definition}
%\cs{DTLplotlegendy}\oarg{db index}\marg{db-name}\oarg{y index}\marg{y-key}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \DTLplotlegendy { O{0} m O{0} m }
 {
%    \end{macrocode}
%If a mapping has been supplied for the y key, use that.
%    \begin{macrocode}
   \prop_get:NnNTF
      \l_dataplot_legend_ylabels_prop
        { #4 }
      \l_dataplot_legend_ylabel_tl
    { 
      \l_dataplot_legend_ylabel_tl
    }
    {
      \DTLaction
        [ name = { #2 }, key = { #4 } ]
        { column ~ data }
      \DTLuse { header }
    }
 }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\DTLplotlegendsetylabel}
%\changes{3.0}{2025-03-03}{new}
% Allow the user to supply alternative text for the $y$ label
%to legend text mapping.
%    \begin{macrocode}
\prop_new:N \l_dataplot_legend_ylabels_prop
\tl_new:N \l_dataplot_legend_ylabel_tl
\NewDocumentCommand \DTLplotlegendsetylabel { m m }
{
  \prop_put:Nnn \l_dataplot_legend_ylabels_prop { #1 } { #2 }
}
%    \end{macrocode}
%\end{macro}
%The following commands may be used in the above definition to
%access additional information.
%Get the total number of databases:
%    \begin{macrocode}
\cs_new:Nn \dataplot_db_count:
{
  \seq_count:N \l__dataplot_dbnames_seq 
}
%    \end{macrocode}
%Get the total number of x keys provided. NB this needs to count the 
%original clist not the sequence which has items popped from it.
%    \begin{macrocode}
\cs_new:Nn \dataplot_x_key_count:
{
  \clist_count:N \l__dataplot_x_keys_clist
}
%    \end{macrocode}
%Get the total number of y keys provided:
%    \begin{macrocode}
\cs_new:Nn \dataplot_y_key_count:
{
  \seq_count:N \l__dataplot_y_keys_seq
}
%    \end{macrocode}
%
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\iffalse
%    \begin{macrocode}
%</dataplot.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*person.sty>
%    \end{macrocode}
%\fi
%\section{person.sty}\label{sec:code:person}
%\subsection{Package Declaration}
% Package identification:
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
%    \end{macrocode}
% Rollback releases:
%    \begin{macrocode}
\DeclareRelease{v2.32}{2019-09-27}{person-2019-09-27.sty}
\DeclareCurrentRelease{v3.4.1}{2025-04-25}
%    \end{macrocode}
% Declare package:
%    \begin{macrocode}
\ProvidesPackage{person}[2025/04/25 v3.4.1 (NLCT)]
%    \end{macrocode}
%No longer requires the \sty{ifthen} package. However, \sty{ifthen}
%is automatically loaded by \sty{datatool-base}, so this will have
%no noticeable effect.
%This package only really requires \sty{datatool-base} not
%\sty{datatool}. Provide an option to omit loading \sty{datatool}.
%\begin{macro}{\@person@datatoolsty}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand\@person@datatoolsty{datatool}
%    \end{macrocode}
%\end{macro}
%
%Define shortcut commands.
%    \begin{macrocode}
\ExplSyntaxOn
\cs_new:Nn \__person_define_shortcuts:
{
  \newcommand \they { \peoplepronoun }
  \newcommand \They { \Peoplepronoun }
  \newcommand \them { \peopleobjpronoun }
  \newcommand \Them { \Peopleobjpronoun }
  \newcommand \their { \peoplepossadj }
  \newcommand \Their { \Peoplepossadj }
  \newcommand \theirs { \peopleposspronoun }
  \newcommand \Theirs { \Peopleposspronoun }
  \newcommand \you { \peoplepronounii }
  \newcommand \You { \Peoplepronounii }
  \newcommand \thee { \peopleobjpronounii }
  \newcommand \Thee { \Peopleobjpronounii }
  \newcommand \your { \peoplepossadjii }
  \newcommand \Your { \Peoplepossadjii }
  \newcommand \yours { \peopleposspronounii }
  \newcommand \Yours { \Peopleposspronounii }
  \newcommand \siblings { \peoplesibling }
  \newcommand \Siblings { \Peoplesibling }
  \newcommand \children { \peoplechild }
  \newcommand \Children { \Peoplechild }
  \newcommand \parents { \peopleparent }
  \newcommand \Parents { \Peopleparent }
  \cs_set:Nn \__person_define_shortcuts: { }
}
%    \end{macrocode}
%This will allow \sty{datatool-base} and (if also required)
%\sty{datatool} options to be passed to \sty{person}:
%    \begin{macrocode}
\keys_define:nn { datatool }
{
  base-only .code:n =
   {
     \tl_set:Nn \@person@datatoolsty {datatool-base}
   } ,
  base-only .value_forbidden:n = true ,
  datatool .code:n =
   {
     \tl_set:Nn \@person@datatoolsty {datatool}
   } ,
  datatool .value_forbidden:n = true ,
  shortcuts .code:n = { \__person_define_shortcuts: } ,
  shortcuts .value_forbidden:n = true
}
\ExplSyntaxOff
%    \end{macrocode}
%
% Process options:
%    \begin{macrocode}
\IfPackageLoadedTF{\@person@datatoolsty}
{
  \ProcessKeyOptions[datatool]
}
{
  \DeclareOption*{\PassOptionsToPackage{\CurrentOption}{\@person@datatoolsty}}
  \ProcessOptions
}
%    \end{macrocode}
%
%    \begin{macrocode}
\RequirePackage{\@person@datatoolsty} 
%    \end{macrocode}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
% Remove the package option keys so they can't be used with
% \cs{DTLsetup}.
%    \begin{macrocode}
\keys_define:nn { datatool }
{
  base-only .undefine: ,
  datatool .undefine: ,
  shortcuts .undefine: ,
}
%    \end{macrocode}
%Default behaviour is to locally define people. This allows the
%throwaway anon label to be used within a scoped context.
%    \begin{macrocode}
\bool_new:N \l__person_local_bool
\bool_set_true:N \l__person_local_bool
%    \end{macrocode}
%
%Define options specific to \sty{person} commands.
%    \begin{macrocode}
\keys_define:nn { datatool/person }
{
  local .bool_set:N = \l__person_local_bool ,
  global .bool_set_inverse:N = \l__person_local_bool ,
  shortcuts .code:n = { \__person_define_shortcuts: } ,
  shortcuts .value_forbidden:n = true
}
%    \end{macrocode}
% Allow these keys to be set in \verb|\DTLsetup{person={...}}|
%    \begin{macrocode}
\keys_define:nn { datatool }
{
  person .code:n = { \keys_set:nn { datatool/person } { #1 } } 
}
%    \end{macrocode}

%
%\subsection{Defining People}
%\begin{counter}{people}
% Keep count of the number of people who have been defined.
%This really should have been an internal variable, but retained for
%backward compatibility. The people label sequence can now simply be
%counted. (NB \cs{thepeople} was documented prior to v3.0.)
%    \begin{macrocode}
\newcounter{people}
%    \end{macrocode}
%\end{counter}
%\begin{macro}{\PersonTotalCount}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\newcommand* \PersonTotalCount
  { \int_use:c { c@people } }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\PersonMaleCount}
% Keep count of the number of male people who have been defined:
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\int_new:N \l__person_male_int
\newcommand* \PersonMaleCount
  { \int_use:N \l__person_male_int }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\PersonFemaleCount}
% Keep count of the number of female people who have been defined:
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\int_new:N \l__person_female_int
\newcommand* \PersonFemaleCount
  { \int_use:N \l__person_female_int }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\PersonNonBinaryCount}
% Keep count of the number of non-binary people who have been defined:
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\int_new:N \l__person_nonbinary_int
\newcommand* \PersonNonBinaryCount
  { \int_use:N \l__person_nonbinary_int }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\PersonUnknownCount}
% Keep count of the number of people who have been defined with
% the gender set to unknown:
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\int_new:N \l__person_unknown_int
\newcommand* \PersonUnknownGenderCount
  { \int_use:N \l__person_unknown_int }
%    \end{macrocode}
%\end{macro}
%The label scratch variable is a public one to allow the label to
%be referenced in the \cs{newperson} hooks.
%    \begin{macrocode}
\tl_new:N \l_person_label_tl
%    \end{macrocode}
%Scratch variables.
%    \begin{macrocode}
\tl_new:N \l__person_full_name_tl
\tl_new:N \l__person_forenames_tl
\tl_new:N \l__person_name_tl
\tl_new:N \l__person_gender_tl
\tl_new:N \l__person_title_tl
\tl_new:N \l__person_surname_tl
%    \end{macrocode}
%\begin{counter}{person}
%Version 3.0: Temporary counter \ctr{person} removed.
%\changes{3.0}{2025-03-03}{removed}
%\end{counter}
%
%Define deprecated command. The deprecated command is provided rather than defined to
%help avoid conflict.
%Syntax: \meta{old cmd}\meta{new cmd}
%    \begin{macrocode}
\cs_new:Nn \__person_define_deprecated_cmd:NN
{
 \providecommand #1 
  {
  \PackageWarning { person } 
   {
     \token_to_str:N #1 \c_space_tl ~ deprecated ~
     and ~ may ~ be ~ removed ~ in ~ future. ~ Use ~ 
     \token_to_str:N #2 \c_space_tl ~ 
     instead
   }
   #2 
  }
}
%    \end{macrocode}
%
%\begin{macro}{\@people@list}
% Keep a list of labels for each person who has been defined.
%Version 3.0: replaced \cs{@people@list} with:
%\changes{3.0}{2025-03-03}{replaced with \cs{l\_\_person\_people\_seq}}
%    \begin{macrocode}
\seq_new:N \l__person_people_seq
%    \end{macrocode}
%\end{macro}
%
%Internal constant gender identifiers. These are used to form token list
%variable names. The gender supplied when defining a person may be
%any of the defined labels, but it will be converted to the
%applicable constant value for internal use.
%
%\begin{macro}{\@male@label}
%Version 3.0: changed \cs{@male@label} to:
%\changes{3.0}{2025-03-03}{replaced with \cs{c\_person\_male\_label\_tl}}
%    \begin{macrocode}
\tl_const:Nn \c_person_male_label_tl { male }
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\@female@label}
%Version 3.0: changed \cs{@female@label} to :
%\changes{3.0}{2025-03-03}{replaced with \cs{c\_person\_female\_label\_tl}}
%    \begin{macrocode}
\tl_const:Nn \c_person_female_label_tl { female }
%    \end{macrocode}
%\end{macro}
%
%Non-binary:
%    \begin{macrocode}
\tl_const:Nn \c_person_nonbinary_label_tl { nonbinary }
%    \end{macrocode}
%
%Special case where the gender is unknown. This will usually be
%treated the same as non-binary.
%    \begin{macrocode}
\tl_const:Nn \c_person_unknown_label_tl { unknown }
%    \end{macrocode}
%
%Do something depending on whether none, one or multiple people
%defined.
%    \begin{macrocode}
\cs_new:Nn \__person_case:nnn
{
  \int_case:nnF { \seq_count:N \l__person_people_seq }
   {
     { \c_zero_int } { #1 }
     { \c_one_int } { #2 }
   }
   { #3 }
}
%    \end{macrocode}
%
%Set person label scratch variable:
%    \begin{macrocode}
\cs_new:Nn \person_set_label:n
{
  \__person_set_label:Nn \l_person_label_tl { #1 }
}
%    \end{macrocode}
%Supply token list variable:
%    \begin{macrocode}
\cs_new:Nn \__person_set_label:Nn
{
  \tl_set:Nx #1
   { \text_purify:n { \tl_trim_spaces:n { #2 } } }
}
%    \end{macrocode}
%
%\begin{macro}{\@get@firstperson}
%Version 3.0 removed \cs{@get@firstperson}
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%\begin{macro}{\@@get@firstperson}
%Version 3.0 removed \cs{@@get@firstperson}
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\malelabels}
% List of labels that can be used to indicate that a person is
% male (when defining a person using \cs{newperson}).
%Version 3.0: replaced \cs{malelabels} with an internal clist variable to reduce the
%possibility of a command name clash.
%\changes{3.0}{2025-03-03}{replaced with \cs{g\_person\_male\_label\_clist}}
%    \begin{macrocode}
\clist_new:N \g_person_male_label_clist
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\PersonSetMaleLabels}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \PersonSetMaleLabels { m } 
{
  \clist_gset:Ne \g_person_male_label_clist { #1 }
}
%    \end{macrocode}
%Initialise:
%    \begin{macrocode}
\PersonSetMaleLabels {Male,MALE,M,m}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\PersonAddMaleLabel}
% Adds a label to the list of male labels.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \PersonAddMaleLabel { m }
{
  \clist_gput_right:Ne \g_person_male_label_clist { #1 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\addmalelabel}
% Old command.
%\changes{3.0}{2025-03-03}{deprecated}
%    \begin{macrocode}
\__person_define_deprecated_cmd:NN
  \addmalelabel \PersonAddMaleLabel
%    \end{macrocode}
%\end{macro}
%
%
%\begin{macro}{\femalelabels}
% List of labels that can be used to indicate that a person is
% female (when defining a person using \cs{newperson}).
%Version 3.0: replaced \cs{femalelabels} with an internal clist variable to reduce the
%possibility of a command name clash.
%\changes{3.0}{2025-03-03}{replaced with \cs{g\_person\_female\_label\_clist}}
%    \begin{macrocode}
\clist_new:N \g_person_female_label_clist
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\PersonSetFemaleLabels}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \PersonSetFemaleLabels { m } 
{
  \clist_gset:Ne \g_person_female_label_clist { #1 }
}
%    \end{macrocode}
%Initialise:
%    \begin{macrocode}
\PersonSetFemaleLabels {Female,FEMALE,F,f}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\addfemalelabel}
% Adds a label to the list of female labels.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\__person_define_deprecated_cmd:NN
 \addfemalelabel \PersonAddFemaleLabel
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\PersonAddFemaleLabel}
% Adds a label to the list of female labels.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \PersonAddFemaleLabel { m }
{
  \clist_gput_right:Ne \g_person_female_label_clist { #1 }
}
%    \end{macrocode}
%\end{macro}
%
% List of labels that can be used to indicate that a person is
% non-binary (when defining a person using \cs{newperson}).
%    \begin{macrocode}
\clist_new:N \g_person_nonbinary_label_clist
%    \end{macrocode}
%\begin{macro}{\PersonSetNonBinaryLabels}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \PersonSetNonBinaryLabels { m } 
{
  \clist_gset:Ne \g_person_nonbinary_label_clist { #1 }
}
%    \end{macrocode}
%Initialise:
%    \begin{macrocode}
\PersonSetNonBinaryLabels 
{
 non-binary, Nonbinary, Non-Binary, NONBINARY, 
 NON-BINARY, N, n
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\PersonAddNonBinaryLabel}
% Adds a label to the list of non-binary labels.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \PersonAddNonBinaryLabel { m }
{
  \clist_gput_right:Ne \g_person_nonbinary_label_clist { #1 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\PersonIfMaleLabel}
% Determines if first argument is contained in the list of male
% labels. (One level expansion is performed on the first object
% in first argument.) This always first checks the constant
% \verb|\c_person_male_label_tl| so that it doesn't need to be included in
% the male labels list.
% If true does second argument, otherwise does third argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \PersonIfMaleLabel { m m m }
{
 \exp_args:NNo \tl_if_eq:NnTF \c_person_male_label_tl { #1 }
  { #2 }
  {
    \clist_if_in:NoTF \g_person_male_label_clist { #1 } { #2 } { #3 } 
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\ifmalelabel}
%Version 3.0: deprecated.
%\changes{3.0}{2025-03-03}{deprecated}
%    \begin{macrocode}
\__person_define_deprecated_cmd:NN
 \ifmalelabel \PersonIfMaleLabel
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\PersonIfFemaleLabel}
% Determines if first argument is contained in the list of female
% labels. (One level expansion is performed on the first object
% in first argument.) This always first checks the constant
% \verb|\c_person_female_label_tl| so that it doesn't need to be included in
% the \verb|\g_person_female_label_clist| list.
% If true does second argument, otherwise does third argument.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \PersonIfFemaleLabel { m m m }
{
 \exp_args:NNo \tl_if_eq:NnTF \c_person_female_label_tl { #1 }
  { #2 }
  {
    \clist_if_in:NoTF \g_person_female_label_clist { #1 } { #2 } { #3 } 
  }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\iffemalelabel}
%Version 3.0: deprecated.
%\changes{3.0}{2025-03-03}{deprecated}
%    \begin{macrocode}
\__person_define_deprecated_cmd:NN
 \iffemalelabel \PersonIfFemaleLabel
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\PersonIfNonBinaryLabel}
% Determines if first argument is contained in the list of
% non-binary gender labels. (One level expansion is performed on the first object
% in first argument.) This always first checks the constant
% \verb|\c_person_nonbinary_label_tl| so that it doesn't need to be included in
% the \verb|\g_person_nonbinary_label_clist| list. 
% If true, does second argument, otherwise does third argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \PersonIfNonBinaryLabel { m m m }
{
 \exp_args:NNo \tl_if_eq:NnTF
    \c_person_nonbinary_label_tl { #1 }
  { #2 }
  {
    \clist_if_in:NoTF \g_person_nonbinary_label_clist
      { #1 } { #2 } { #3 } 
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\newperson}
% Define a new person. The optional argument specifies a label
% with which to refer to that person. If omitted, \texttt{anon}
% is used. If more than one person is defined, the optional
% argument will be required to specify a unique label. The
% mandatory arguments are the person's full name, their 
% familiar name and their gender.
%
%Hook at the start of \cs{newperson}
%    \begin{macrocode}
\tl_new:N \g__person_new_init_tl
\cs_new:Nn \person_new_appto_start:n
{
 \tl_gput_right:N \g__person_new_init_tl { #1 }
}
%    \end{macrocode}
%Hook at the end of \cs{newperson}
%    \begin{macrocode}
\tl_new:N \g__person_new_finish_tl
\cs_new:Nn \person_new_appto_end:n
{
 \tl_gput_right:N \g__person_new_finish_tl { #1 }
}
%    \end{macrocode}
%Version 3.0 added a starred form which uses a key=value interface.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{added starred form}
%    \begin{macrocode}
\NewDocumentCommand{\newperson}{ s O{anon} }
{
  \person_set_label:n { #2 }
  \tl_clear:N \l__person_full_name_tl
  \tl_clear:N \l__person_forenames_tl
  \tl_clear:N \l__person_name_tl
  \tl_clear:N \l__person_gender_tl
  \tl_clear:N \l__person_title_tl
  \tl_clear:N \l__person_surname_tl
  \g__person_new_init_tl
  \IfBooleanTF { #1 }
  { \__person_new:n }
  { \__person_new:nnn }
}
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new:Nn \__person_new:nnn
{
  \person_if_exist:nTF { \l_person_label_tl }
  {
    \PackageError { person }
     {
        Person ~ ` \l_person_label_tl ' ~ has ~ already ~ been ~ defined
     } 
     {
       Each ~ defined ~ person ~ must ~ have ~ a ~ unique ~ label
     }
  }
  {
    \tl_if_empty:NTF \l_person_label_tl
    {
      \PackageError { person }
       { Empty ~ person ~ label ~ not ~ permitted}
       {
         You ~ need ~ to ~ supply ~ a ~ non-empty ~
         label ~ to ~ identify ~ the ~ person
       }
    }
    {
      \tl_set:Ne \l__person_full_name_tl { \tl_trim_spaces:n { #1 } }
      \tl_set:Ne \l__person_name_tl { \tl_trim_spaces:n { #2 } }
      \datatool_if_null_or_empty:nTF { #3 }
       {
         \tl_set_eq:NN \l__person_gender_tl \c_person_unknown_label_tl
       }
       {
         \tl_set:Ne \l__person_gender_tl { \tl_trim_spaces:n { #3 } }
       }
      \__person_new:
    }
  }
}
%    \end{macrocode}
%
%Starred form has key=value interface.
%First define keys.
%    \begin{macrocode}
\keys_define:nn { datatool/person }
{
  fullname .tl_set:N = \l__person_full_name_tl ,
  expand-once-fullname .code:n =
   { \tl_set:No \l__person_full_name_tl { #1 } } ,
  expand-fullname .tl_set_x:N = \l__person_full_name_tl ,
  forenames .tl_set:N = \l__person_forenames_tl ,
  expand-once-forenames .code:n =
   { \tl_set:No \l__person_forenames_tl { #1 } } ,
  expand-forenames .tl_set_x:N = \l__person_forenames_tl ,
  name .tl_set:N = \l__person_name_tl ,
  expand-once-name .code:n =
   { \tl_set:No \l__person_name_tl { #1 } },
  expand-name .tl_set_x:N = \l__person_name_tl ,
  gender .code:n =
   {
    \datatool_if_null_or_empty:nTF { #1 }
     {
       \tl_set_eq:NN \l__person_gender_tl \c_person_unknown_label_tl
     }
     { \tl_set:Ne \l__person_gender_tl { #1 } }
   },
  title .tl_set:N = \l__person_title_tl ,
  expand-once-title .code:n =
   { \tl_set:No \l__person_title_tl { #1 } },
  expand-title .tl_set_x:N = \l__person_title_tl ,
  surname .tl_set:N = \l__person_surname_tl ,
  expand-once-surname .code:n =
   { \tl_set:No \l__person_surname_tl { #1 } },
  expand-surname .tl_set_x:N = \l__person_surname_tl ,
}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__person_new:n
{
  \person_if_exist:nTF { \l_person_label_tl }
  {
    \PackageError { person }
     {
        Person ~ ` \l_person_label_tl ' ~ has ~ already ~ been ~ defined
     } 
     {
       Each ~ defined ~ person ~ must ~ have ~ a ~ unique ~ label
     }
  }
  {
    \tl_if_empty:NTF \l_person_label_tl
    {
      \PackageError { person }
       { Empty ~ person ~ label ~ not ~ permitted}
       {
         You ~ need ~ to ~ supply ~ a ~ non-empty ~
         label ~ to ~ identify ~ the ~ person
       }
    }
    {
      \keys_set:nn { datatool/person } { #1 }
      \__person_new:
    }
  }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \__person_new:
{
  \bool_lazy_or:nnTF 
      { \tl_if_empty_p:N \l__person_gender_tl }
      { \tl_if_eq_p:NN \l__person_gender_tl \c_person_unknown_label_tl }
   {
%    \end{macrocode}
%Unknown mostly behaves like non-binary but doesn't increment the
%nonbinary counter.
%    \begin{macrocode}
     \bool_if:NTF \l__person_local_bool
      {
        \tl_set:cV
         { person@ \l_person_label_tl @gender }
         \c_person_unknown_label_tl
      }
      {
        \tl_gset:cV
         { person@ \l_person_label_tl @gender }
         \c_person_unknown_label_tl
      }
   }
   {
     \exp_args:NV \PersonIfMaleLabel \l__person_gender_tl 
      {
        \bool_if:NTF \l__person_local_bool
         {
           \tl_set:cV
            { person@ \l_person_label_tl @gender }
            \c_person_male_label_tl
           \int_incr:N \l__person_male_int
         }
         {
           \tl_gset:cV
            { person@ \l_person_label_tl @gender }
            \c_person_male_label_tl
           \int_gincr:N \l__person_male_int
         }
      }
      {
        \exp_args:NV \PersonIfFemaleLabel \l__person_gender_tl 
         {
           \bool_if:NTF \l__person_local_bool
            {
              \tl_set:cV
               { person@ \l_person_label_tl @gender }
               \c_person_female_label_tl
              \int_incr:N \l__person_female_int
            }
            {
              \tl_gset:cV
               { person@ \l_person_label_tl @gender }
               \c_person_female_label_tl
              \int_gincr:N \l__person_female_int
            }
         }
         {
%    \end{macrocode}
%\changes{3.0}{2025-03-03}{added non-binary check}
%    \begin{macrocode}
            \exp_args:NV \PersonIfNonBinaryLabel \l__person_gender_tl
            {
              \bool_if:NTF \l__person_local_bool
               {
                \tl_set:cV
                 { person@ \l_person_label_tl @gender }
                 \c_person_nonbinary_label_tl
                \int_incr:N \l__person_nonbinary_int
               }
               {
                \tl_gset:cV
                 { person@ \l_person_label_tl @gender }
                 \c_person_nonbinary_label_tl
                \int_gincr:N \l__person_nonbinary_int
               }
            }
            {
              \tl_if_eq:NN \l__person_gender_tl \c_person_unknown_label_tl
              {
                \bool_if:NTF \l__person_local_bool
                 {
                  \tl_set:cV
                   { person@ \l_person_label_tl @gender }
                   \c_person_unknown_label_tl
                  \int_incr:N \l__person_unknown_int
                 }
                 {
                  \tl_gset:cV
                   { person@ \l_person_label_tl @gender }
                   \c_person_unknown_label_tl
                  \int_gincr:N \l__person_unknown_int
                 }
              }
              {
                \PackageError { person }
                 {
                   Unknown ~ gender ~ ` \l__person_gender_tl ' ~
                   for ~ person ~ ` \l_person_label_tl '
                 }
                 {
                   Known ~ gender ~ identifiers ~ are: ~
                   \clist_use:Nn \g_person_male_label_clist { , ~ }  ~
                   or ~ \clist_use:Nn \g_person_female_label_clist { , ~ } ~ 
                   or ~ \clist_use:Nn \g_person_nonbinary_label_clist { , ~ }
                   or ~ \c_person_unknown_label_tl
                 }
                \bool_if:NTF \l__person_local_bool
                 {
                   \tl_set:cV
                     { person@ \l_person_label_tl @gender }
                     \c_person_unknown_label_tl
                   \int_incr:N \l__person_unknown_int
                 }
                 {
                   \tl_gset:cV
                     { person@ \l_person_label_tl @gender }
                     \c_person_unknown_label_tl
                   \int_gincr:N \l__person_unknown_int
                 }
              }
            }
         }
       }
   }
  \tl_if_empty:NT \l__person_full_name_tl
   {
     \tl_if_empty:NTF \l__person_forenames_tl
      {
       \tl_if_empty:NTF \l__person_name_tl
        {
          \tl_if_empty:NTF \l__person_title_tl
           {
             \tl_set_eq:NN
               \l__person_full_name_tl
               \l__person_surname_tl
           }
           {
            \tl_set:Nx \l__person_full_name_tl
             {
               \exp_not:V \l__person_title_tl
               \exp_not:N \persontitlesurnamesep
               \exp_not:V \l__person_surname_tl
             }
           }
        }
        {
         \tl_if_empty:NTF \l__person_surname_tl
          {
            \tl_set_eq:NN
              \l__person_full_name_tl
              \l__person_name_tl
          }
          {
            \tl_set:Nx \l__person_full_name_tl
             {
              \exp_not:V \l__person_name_tl
              \c_space_tl
              \exp_not:V \l__person_surname_tl
             }
          }
        }
      }
      {
         \tl_if_empty:NTF \l__person_surname_tl
          {
            \tl_set_eq:NN
              \l__person_full_name_tl
              \l__person_forenames_tl
          }
          {
            \tl_set:Nx \l__person_full_name_tl
             {
              \exp_not:V \l__person_forenames_tl
              \c_space_tl
              \exp_not:V \l__person_surname_tl
             }
          }
      }
   }
  \tl_if_empty:NT \l__person_forenames_tl
   {
     \tl_set_eq:NN
       \l__person_forenames_tl
       \l__person_name_tl
   }
  \bool_if:NTF \l__person_local_bool
   {
     \tl_set_eq:cN
       { person@ \l_person_label_tl @name }
       \l__person_name_tl
     \tl_set_eq:cN
       { person@ \l_person_label_tl @surname }
       \l__person_surname_tl
     \tl_set_eq:cN
       { person@ \l_person_label_tl @title }
       \l__person_title_tl
     \tl_set_eq:cN
       { person@ \l_person_label_tl @fullname }
       \l__person_full_name_tl
     \tl_set_eq:cN
       { person@ \l_person_label_tl @forenames }
       \l__person_forenames_tl
     \seq_put_right:NV \l__person_people_seq \l_person_label_tl 
     \int_incr:N \c@people
   }
   {
     \tl_gset_eq:cN
       { person@ \l_person_label_tl @name }
       \l__person_name_tl
     \tl_gset_eq:cN
       { person@ \l_person_label_tl @surname }
       \l__person_surname_tl
     \tl_gset_eq:cN
       { person@ \l_person_label_tl @title }
       \l__person_title_tl
     \tl_gset_eq:cN
       { person@ \l_person_label_tl @fullname }
       \l__person_full_name_tl
     \tl_gset_eq:cN
       { person@ \l_person_label_tl @forenames }
       \l__person_forenames_tl
     \seq_gput_right:NV \l__person_people_seq \l_person_label_tl 
     \int_gincr:N \c@people
   }
  \g__person_new_finish_tl
}
%    \end{macrocode}
%
%Syntax: \marg{label}\marg{attribute name}\marg{value}
%    \begin{macrocode}
\cs_new:Nn \person_set_attribute:nnn
{
  \bool_if:NTF \l__person_local_bool
   {
     \tl_set_eq:cn { person@ #1 @ #2 } { #3 }
   }
   {
     \tl_gset_eq:cn { person@ #1 @ #2 } { #3 }
   }
}
\cs_generate_variant:Nn \person_set_attribute:nnn { nnV }
%    \end{macrocode}
%
%Syntax: \marg{person-label}\marg{attribute}
%    \begin{macrocode}
\cs_new:Nn \person_get_attribute:nn
{
  \tl_use:c { person@ #1 @ #2 }
}
%    \end{macrocode}
%Syntax: \meta{tl var} \marg{person-label}\marg{attribute}
%    \begin{macrocode}
\cs_new:Nn \person_get_attribute:Nnn
{
  \tl_seq_eq:Nc #1 { person@ #2 @ #3 }
}
%    \end{macrocode}
%
%Syntax: \marg{person-label}\marg{attribute}
%    \begin{macrocode}
\cs_new:Nn \person_unset_attribute:nn
{
  \bool_if:NTF \l__person_local_bool
   {
     \csundef { person@ #1 @ #2 }
   }
   {
     \csgundef { person@ #1 @ #2 }
   }
}
%    \end{macrocode}
%
%
%\subsection{Remove People}
%
%\begin{macro}{\removeperson}
% Removes person identified by their label from the list.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{moved existence check}
%    \begin{macrocode}
\NewDocumentCommand \removeperson { O{anon} }
{
  \person_set_label:n { #1 }
  \person_if_exist_or_err:nT { \l_person_label_tl }
  {
    \__person_remove:x { \l_person_label_tl }
  }
}
%    \end{macrocode}
%\end{macro}
%Internal commands don't check for existence.
%    \begin{macrocode}
\cs_new:Nn \__person_remove:
{
  \bool_if:NTF \l__person_local_bool
   {
    \seq_remove_all:NV \l__person_people_seq \l_person_label_tl
    \int_decr:N \c@people
%    \end{macrocode}
%If the gender is "unknown" there won't be an associated counter.
%    \begin{macrocode}
    \int_if_exist:cT
     { l__person_ \csuse{ person@ \l_person_label_tl @gender } _int }
     {
       \int_decr:c
        { l__person_ \csuse { person@ \l_person_label_tl @gender } _int }
     }
%    \end{macrocode}
% Undefine associated control sequences:
%    \begin{macrocode}
    \csundef { person@ \l_person_label_tl @name }
    \csundef { person@ \l_person_label_tl @fullname }
    \csundef { person@ \l_person_label_tl @forenames }
    \csundef { person@ \l_person_label_tl @gender }
    \csundef { person@ \l_person_label_tl @title }
    \csundef { person@ \l_person_label_tl @surname }
   }
   {
%    \end{macrocode}
% Globally remove label from list of people.
%    \begin{macrocode}
    \seq_gremove_all:NV \l__person_people_seq \l_person_label_tl
%    \end{macrocode}
% Decrement number of people:
%    \begin{macrocode}
    \int_gdecr:N \c@people
%    \end{macrocode}
% Decrement gender counter if it exists:
%    \begin{macrocode}
    \int_if_exist:cT
     { l__person_ \csuse{ person@ \l_person_label_tl @gender } _int }
     {
       \int_gdecr:c
        { l__person_ \csuse { person@ \l_person_label_tl @gender } _int }
     }
%    \end{macrocode}
% Undefine associated control sequences:
%    \begin{macrocode}
    \csgundef { person@ \l_person_label_tl @name }
    \csgundef { person@ \l_person_label_tl @fullname }
    \csgundef { person@ \l_person_label_tl @forenames }
    \csgundef { person@ \l_person_label_tl @gender }
    \csgundef { person@ \l_person_label_tl @title }
    \csgundef { person@ \l_person_label_tl @surname }
   }
  \g__person_remove_tl
}
\cs_new:Nn \__person_remove:n
{
  \person_set_label:n { #1 }
  \__person_remove:
}
\cs_generate_variant:Nn \__person_remove:n { x }
%    \end{macrocode}
%Hook for remove person:
%    \begin{macrocode}
\tl_new:N \g__person_remove_tl
\cs_new:Nn \person_remove_appto:n
{
 \tl_gput_right:N \g__person_remove_tl { #1 }
}
%    \end{macrocode}
%\begin{macro}{\removepeople}
% Removes the people listed.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \removepeople { m }
{
  \clist_map_function:nN { #1 } \__person_remove:n
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\removeallpeople}
% Removes everyone.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \removeallpeople { }
{
  \bool_if:NTF \l__person_local_bool
   {
    \seq_map_inline:Nn  \l__person_people_seq
     {
      \person_set_label:n { ##1 }
      \csundef{person@ \l_person_label_tl @name}
      \csundef{person@ \l_person_label_tl @fullname}
      \csundef{person@ \l_person_label_tl @forenames}
      \csundef{person@ \l_person_label_tl @gender}
      \csundef{person@ \l_person_label_tl @title}
      \csundef{person@ \l_person_label_tl @surname}
      \g__person_remove_tl
     }
    \int_zero:c { c@people }
    \int_zero:N \l__person_male_int
    \int_zero:N \l__person_female_int
    \int_zero:N \l__person_nonbinary_int
    \seq_clear:N \l__person_people_seq
   }
   {
    \seq_map_inline:Nn  \l__person_people_seq
     {
      \person_set_label:n { ##1 }
      \csgundef{person@ \l_person_label_tl @name}
      \csgundef{person@ \l_person_label_tl @fullname}
      \csgundef{person@ \l_person_label_tl @forenames}
      \csgundef{person@ \l_person_label_tl @gender}
      \csgundef{person@ \l_person_label_tl @title}
      \csgundef{person@ \l_person_label_tl @surname}
      \g__person_remove_tl
     }
    \int_gzero:c { c@people }
    \int_gzero:N \l__person_male_int
    \int_gzero:N \l__person_female_int
    \int_gzero:N \l__person_nonbinary_int
    \seq_gclear:N \l__person_people_seq
   }
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Conditionals and Loops}
%\begin{macro}{\ifpersonexists}
% If person whose label is given by the first argument exists, then
% do the second argument otherwise do third argument.
%    \begin{macrocode}
\newcommand{\ifpersonexists}[3]{
 \exp_args:Ne \person_if_exist:nTF
  { \tl_trim_spaces:n { #1 } }
  { #2 } { #3 }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\prg_new_conditional:Npnn \person_if_exist:n #1 { p, T, F, TF }
{
  \tl_if_exist:cTF { person@ #1 @name}
  { \prg_return_true: }
  { \prg_return_false: }
}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \person_if_exist_or_err:nT
{
 \person_if_exist:nTF
  { #1 } { #2 }
  {
    \PackageError { person }
     {
       Person ~ ` #1 ' ~ doesn't ~ exist
     }
     {
       Check ~ that ~ you ~ have ~ correctly ~ spelt ~ the ~ label
     }
  }
}
%    \end{macrocode}
%Case-command.
%Syntax:
% \meta{gender tl-var}
% \marg{invalid}
% \marg{male}
% \marg{female}
% \marg{non-binary}
% \marg{unknown}
%    \begin{macrocode}
\cs_new:Nn \person_gender_case:Nnnnnn
{
  \tl_if_eq:NNTF #1 \c_person_male_label_tl
   { #3 }
   {
    \tl_if_eq:NNTF #1 \c_person_female_label_tl
     { #4 }
     {
      \tl_if_eq:NNTF #1 \c_person_nonbinary_label_tl
       { #5 }
       {
         \tl_if_eq:NNTF #1 \c_person_unknown_label_tl
          { #6 } { #2 }
       }
     }
   }
}
%    \end{macrocode}
%As above but label supplied as a token list.
%    \begin{macrocode}
\cs_new:Nn \person_gender_case:nnnnnn
{
  \tl_if_eq:NnTF \c_person_male_label_tl { #1 }
   { #3 }
   {
    \tl_if_eq:NnTF \c_person_female_label_tl { #1 }
     { #4 }
     {
      \tl_if_eq:NnTF \c_person_nonbinary_label_tl { #1 }
       { #5 }
       {
         \tl_if_eq:NnTF \c_person_unknown_label_tl { #1 }
          { #6 } { #2 }
       }
     }
   }
}
\cs_generate_variant:Nn \person_gender_case:nnnnnn
 { xnnnnn }
%    \end{macrocode}
%Similar but issue an error for invalid case and treat as unknown:
%    \begin{macrocode}
\cs_new:Nn \person_gender_case:Nnnnn
{
  \person_gender_case:Nnnnnn #1
   {
     \PackageError { person }
      {
        Unrecognised ~ internal ~ gender ~ label ~ `#1'. ~
        Treating ~ as ~ ` \c_person_unknown_label_tl '
      }
      {
        Internal ~ gender ~ labels ~ must ~ be ~ one ~ of ~
        the ~ following: ` \c_person_male_label_tl ', ~
        ` \c_person_female_label_tl ', ~
        ` \c_person_nonbinary_label_tl ', ~
        ` \c_person_unknown_label_tl '
      }
     #5
   }
   { #2 } { #3 } { #4 } { #5 }
}
%    \end{macrocode}
%Gender label provided as a token list:
%    \begin{macrocode}
\cs_new:Nn \person_gender_case:nnnnn
{
  \person_gender_case:nnnnnn { #1 }
   {
     \PackageError { person }
      {
        Unrecognised ~ internal ~ gender ~ label ~ `#1'. ~
        Treating ~ as ~ ` \c_person_unknown_label_tl '
      }
      {
        Internal ~ gender ~ labels ~ must ~ be ~ one ~ of ~
        the ~ following: ` \c_person_male_label_tl ', ~
        ` \c_person_female_label_tl ', ~
        ` \c_person_nonbinary_label_tl ', ~
        ` \c_person_unknown_label_tl '
      }
     #5
   }
   { #2 } { #3 } { #4 } { #5 }
}
\cs_generate_variant:Nn  \person_gender_case:nnnnn
 { xnnnn }
%    \end{macrocode}
%Just need to know if the label is valid:
%    \begin{macrocode}
\cs_new:Nn \person_do_if_valid_gender:nT
{
  \person_gender_case:nnnnnn { #1 }
   {
     \PackageError { person }
      {
        Unrecognised ~ internal ~ gender ~ label ~ `#1' 
      }
      {
        Internal ~ gender ~ labels ~ must ~ be ~ one ~ of ~
        the ~ following: ` \c_person_male_label_tl ', ~
        ` \c_person_female_label_tl ', ~
        ` \c_person_nonbinary_label_tl ', ~
        ` \c_person_unknown_label_tl '
      }
   }
   { #2 } { #2 } { #2 } { #2 }
}
\cs_generate_variant:Nn  \person_do_if_valid_gender:nT
 { xT }
%    \end{macrocode}
%Test if all defined people the same gender.
%Syntax: \marg{all male}\marg{all female}\marg{all non-binary}\marg{other}
%    \begin{macrocode}
\cs_new:Nn \person_all_gender_case:nnnn
{
  \int_compare:nNnTF
    { \c@people } = { \l__person_male_int }
  { #1 }
  {
    \int_compare:nNnTF
      { \c@people } = { \l__person_female_int }
    { #2 }
    {
      \int_compare:nNnTF
        { \c@people } = { \l__person_nonbinary_int }
      { #3 } { #4 }
    }
  }
}
%    \end{macrocode}
%
%
%\begin{macro}{\PersonIfMale}
% If the person given by the label in the first argument is male,
% do the second argument, otherwise do the third argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \PersonIfMale { m m m }
{
  \person_set_label:n { #1 }
  \person_if_exist_or_err:nT { \l_person_label_tl  }
   {
     \tl_if_eq:cNTF
      { person@ \l_person_label_tl @gender }
       \c_person_male_label_tl
      {#2} {#3}
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\ifmale}
% If the person given by the label in the first argument is male,
% do the second argument, otherwise do the third argument.
%\changes{2.23}{2015-07-11}{bug fix: replaced \cs{@thisperson} with \#1}
%\changes{3.0}{2025-03-03}{deprecated}
%    \begin{macrocode}
\__person_define_deprecated_cmd:NN
 \ifmale \PersonIfMale
%    \end{macrocode}
%\end{macro}
%
%
%\begin{macro}{\PersonIfAllMale}
% If all people listed in first argument are male, do the second
% argument otherwise do the third argument. If the first argument
% is omitted, all defined people are checked.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \PersonIfAllMale { o m m }
{
 \IfNoValueTF { #1 }
 {
   \int_compare:nNnTF
     { \c@people } = { \l__person_male_int }
    { #2 } { #3 }
 }
 {
   \cs_set_eq:NN \__person_do:TF \cs_use_i:nn
   \clist_map_inline:nn { #1 }
    {
      \tl_if_eq:cNF
        { person@ \text_purify:n { ##1 } @gender}
        \c_person_male_label_tl
      { 
        \cs_set_eq:NN \__person_do:TF \cs_use_ii:nn
        \clist_map_break:
      }
    }
   \__person_do:TF { #2 } { #3 }
 }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\ifallmale}
%\changes{3.0}{2025-03-03}{deprecated}
%    \begin{macrocode}
\__person_define_deprecated_cmd:NN
 \ifallmale \PersonIfAllMale
%    \end{macrocode}
%\end{macro}
%
%
%\begin{macro}{\PersonIfFemale}
% If the person given by the label in the first argument is female,
% do the second argument, otherwise do the third argument.
%\changes{2.23}{2015-07-11}{bug fix: replaced \cs{@thisperson} with \#1}
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \PersonIfFemale { m m m }
{
  \person_set_label:n { #1 }
  \person_if_exist_or_err:nT { \l_person_label_tl }
   {
     \tl_if_eq:cNTF
      { person@ \l_person_label_tl @gender }
       \c_person_female_label_tl
      {#2} {#3}
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\iffemale}
% If the person given by the label in the first argument is female,
% do the second argument, otherwise do the third argument.
%\changes{2.23}{2015-07-11}{bug fix: replaced \cs{@thisperson} with \#1}
%\changes{3.0}{2025-03-03}{deprecated}
%    \begin{macrocode}
\__person_define_deprecated_cmd:NN
 \iffemale \PersonIfFemale
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\PersonIfAllFemale}
% If all people listed in first argument are female, do the second
% argument otherwise do the third argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \PersonIfAllFemale { o m m }
{
 \IfNoValueTF { #1 }
 {
   \int_compare:nNnTF
     { \c@people } = { \l__person_female_int }
    { #2 } { #3 }
 }
 {
   \cs_set_eq:NN \__person_do:TF \cs_use_i:nn
   \clist_map_inline:nn { #1 }
    {
      \tl_if_eq:cNF
        { person@ \text_purify:n { ##1 } @gender}
        \c_person_female_label_tl
      { 
        \cs_set_eq:NN \__person_do:TF \cs_use_ii:nn
        \clist_map_break:
      }
    }
   \__person_do:TF { #2 } { #3 }
 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\ifallfemale}
% If all people listed in first argument are female, do the second
% argument otherwise do the third argument.
%\changes{3.0}{2025-03-03}{deprecated}
%    \begin{macrocode}
\__person_define_deprecated_cmd:NN
 \ifallfemale \PersonIfAllFemale
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\PersonIfNonBinary}
% If the person given by the label in the first argument is non-binary,
% do the second argument, otherwise do the third argument. NB this does false for
% "unknown".
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \PersonIfNonBinary { m m m }
{
  \person_set_label:n { #1 }
  \person_if_exist_or_err:nT { \l_person_label_tl }
   {
     \tl_if_eq:cNTF
      { person@ \l_person_label_tl @gender }
       \c_person_nonbinary_label_tl
      { #2 } { #3 }
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\PersonIfAllNonBinary}
% If all people listed in first argument are nonbinary, do the second
% argument otherwise do the third argument. NB this does false for
% "unknown".
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \PersonIfAllNonBinary { o m m }
{
 \IfNoValueTF { #1 }
 {
   \int_compare:nNnTF
     { \c@people } = { \l__person_nonbinary_int }
    { #2 } { #3 }
 }
 {
   \cs_set_eq:NN \__person_do:TF \cs_use_i:nn
   \clist_map_inline:nn { #1 }
    {
      \tl_if_eq:cNF
        { person@ \text_purify:n { ##1 } @gender}
        \c_person_nonbinary_label_tl
      { 
        \cs_set_eq:NN \__person_do:TF \cs_use_ii:nn
        \clist_map_break:
      }
    }
   \__person_do:TF { #2 } { #3 }
 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\PersonIfUnknownGender}
% If the person given by the label in the first argument has the
% gender set to unknown,
% do the second argument, otherwise do the third argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \PersonIfUnknownGender { m m m }
{
  \person_set_label:n { #1 }
  \person_if_exist_or_err:nT { \l_person_label_tl }
   {
     \tl_if_eq:cNTF
      { person@ \l_person_label_tl @gender }
       \c_person_unknown_label_tl
      { #2 } { #3 }
   }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\PersonIfAllUnknownGender}
% If all people listed in first argument have the gender set to
% unknown, do the second argument otherwise do the third argument.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \PersonIfAllUnknownGender { o m m }
{
 \IfNoValueTF { #1 }
 {
   \int_compare:nNnTF
     { \c@people } = { \l__person_unknown_int }
    { #2 } { #3 }
 }
 {
   \cs_set_eq:NN \__person_do:TF \cs_use_i:nn
   \clist_map_inline:nn { #1 }
    {
      \tl_if_eq:cNF
        { person@ \text_purify:n { ##1 } @gender}
        \c_person_unknown_label_tl
      { 
        \cs_set_eq:NN \__person_do:TF \cs_use_ii:nn
        \clist_map_break:
      }
    }
   \__person_do:TF { #2 } { #3 }
 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\foreachpersonbreak}
%Break out of loops.
%    \begin{macrocode}
\newcommand{\foreachpersonbreak}
{ 
  \PackageError { person }
  {
    \token_to_str:N \foreachpersonbreak \c_space_tl ~
    can't ~ be ~ used ~ outside ~ of 
    \token_to_str:N \forallpeople \c_space_tl ~
    or \token_to_str:N \foreachperson
  }
  { }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\foreachperson}
%\begin{definition}
%\cs{foreachperson}(\meta{name cs},\meta{full name cs},\meta{gender cs},\meta{label cs})\cs{in}\marg{list}\cs{do}\marg{body}
%\end{definition}
% Iterates through list of people the \cs{in}\marg{list} is optional.
% If omitted, the list of all defined people is used.
%    \begin{macrocode}
\newcommand \foreachperson { \__person_foreach:wn }
%    \end{macrocode}
%\end{macro}
%
%    \begin{macrocode}
\cs_new:Npn \__person_foreach:wn ( #1 , #2 , #3 , #4 ) #5
{
  \tl_if_eq:nnTF { #5 } { \in }
   {
     \__person_foreach:NNNNnn #1 #2 #3 #4
   }
   {
     \renewcommand \foreachpersonbreak { \seq_break: }
     \seq_map_inline:Nn \l__person_people_seq
      {
        \__person_foreach_fn:NNNNnn #1 #2 #3 #4 { ##1 } { #5 }
      }
   }
}
%    \end{macrocode}
%List of labels provided:
%    \begin{macrocode}
\cs_new:Nn \__person_foreach:NNNNnn
{
  \renewcommand \foreachpersonbreak { \clist_break: }
  \clist_map_inline:nn { #5 }
   {
     \__person_foreach_fn:NNNNxn #1 #2 #3 #4
       { \text_purify:n { \tl_trim_spaces:n { ##1 } } } { #6 }
   }
}
%    \end{macrocode}
%Loop function.
%Syntax: \meta{name-tl-var} \meta{full-name-tl-var} \meta{gender-tl-var}
%\meta{label-tl-var} \marg{label} \marg{body}
%    \begin{macrocode}
\cs_new:Nn \__person_foreach_fn:NNNNnn
{
  \person_if_exist_or_err:nT { #5 }
   {
     \tl_set:Nn #4 { #5 }
     \tl_set_eq:Nc #1 { person@ #5 @name }
     \tl_set_eq:Nc #2 { person@ #5 @fullname }
     \tl_set:Nx #3 { \__person_language:nn { #5 } { gender } }
     #6
   }
}
\cs_generate_variant:Nn \__person_foreach_fn:NNNNnn
 { NNNNxn }
%    \end{macrocode}
%\begin{macro}{\@foreachperson}
%Version 3.0: removed \cs{@foreachperson}
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\forallpeople}
%\begin{definition}
%\cs{forallpeople}\oarg{list}\marg{label-cs}\marg{body}
%\end{definition}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \forallpeople { o m +m }
{
  \IfValueTF { #1 }
   {
     \renewcommand \foreachpersonbreak { \clist_break: }
     \clist_map_inline:nn { #1 }
      {
        \__person_set_label:Nn #2 { ##1 }
        \person_if_exist_or_err:nT { #2 }
         { #3 }
      }
   }
   {
     \renewcommand \foreachpersonbreak { \seq_break: }
     \seq_map_inline:Nn \l__person_people_seq
      {
        \tl_set:Nn #2 { ##1 }
%    \end{macrocode}
%The person should exist unless something unexpected has occurred.
%    \begin{macrocode}
        \person_if_exist_or_err:nT { #2 }
         { #3 }
      }
   }
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Predefined Words}\label{sec:code:peoplenames}
% These commands should be redefined if
% you are writing in another language, but note that these 
% are structured according to English grammar.
%
%Version 3.0 has renamed some commands to help avoid conflict.
%The old command names are retained for backward-compatibility but
%may be removed in a later version.
%
%\begin{macro}{\PersonSetLocalisation}
%\begin{definition}
%\cs{PersonSetLocalisation}\marg{gender}\marg{type}\marg{value}
%\end{definition}
%These are now token list variables in the form
%\verb|\g__person_|\meta{gender}\verb|_|\meta{type}\verb|_tl|
%and can be set with the following command. NB the gender label
% must be one of the internal labels (``male'', ``female'',
% ``nonbinary'' or ``unknown'').
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \PersonSetLocalisation
   { m m m }
{
 \person_do_if_valid_gender:nT { #1 }
  {
    \tl_gset:cn { g__person_ #1 _ #2 _tl } { #3 }
  }
}
%    \end{macrocode}
%\end{macro}
%Define new localisation token list variable:
%    \begin{macrocode}
\cs_new:Nn \person_new_localisation:nnn
{
 \person_do_if_valid_gender:nT { #1 }
  {
    \tl_new:c { g__person_ #1 _ #2 _tl }
    \tl_gset:cn { g__person_ #1 _ #2 _tl } { #3 }
  }
}
%    \end{macrocode}
%Get localisation token list variable.
%    \begin{macrocode}
\cs_new:Nn \person_get_localisation:nn
{
  \tl_if_exist:cTF {  g__person_ #1 _ #2 _tl }
   { \tl_use:c { g__person_ #1 _ #2 _tl } }
   {
     \tl_if_exist:cTF { g__person_unknown_ #2 _tl }
      {
        \tl_use:c { g__person_unknown_ #2 _tl }
      }
      {
       \tl_if_exist:cTF { g__person_nonbinary_ #2 _tl }
        {
          \tl_use:c { g__person_nonbinary_ #2 _tl }
        }
        {
          ??
          \NoCaseChange
           {
             \PackageWarning { person }
              {
                Unknown ~ localisation ~ combination: ~
                gender = ` #1 ' ~ and ~ type = ` #2 '
              }
           }
        }
      }
   }
}
\cs_generate_variant:Nn \person_get_localisation:nn { vn, Vn }
%    \end{macrocode}
%
%\subsubsection{Third person pronouns (subject)}
%
%    \begin{macrocode}
\cs_new:Nn \__person_deprecated_lang_cs:Nnnn
 {
   \tl_if_exist:NTF #1
    {
      \person_new_localisation:nnn { #2 } { #3 } { #4 }
    }
    {
      \newcommand* #1 { #4 }
      \person_new_localisation:nnn { #2 } { #3 } { #1 }
    }
 }
%    \end{macrocode}
%
%\begin{macro}{\malepronoun}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \malepronoun
  { male } { pronoun } { he }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\femalepronoun}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \femalepronoun
   { female } { pronoun } { she }
%    \end{macrocode}
%\end{macro}
%Non-binary pronoun:
%    \begin{macrocode}
\person_new_localisation:nnn { nonbinary } { pronoun } { they }
%    \end{macrocode}
%\begin{macro}{\pluralpronoun}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \pluralpronoun
  { unknown } { pluralpronoun } { they }
%    \end{macrocode}
%\end{macro}
%
%Pronouns (object).
%\begin{macro}{\maleobjpronoun}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \maleobjpronoun
  { male } { objpronoun } { him }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\femaleobjpronoun}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \femaleobjpronoun
  { female } { objpronoun } { her }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\person_new_localisation:nnn
  { nonbinary } { objpronoun } { them }
%    \end{macrocode}
%\begin{macro}{\pluralobjpronoun}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \pluralobjpronoun
  { unknown } { pluralobjpronoun } { them }
%    \end{macrocode}
%\end{macro}
%
%Pronouns (possessive adjective).
%\begin{macro}{\malepossadj}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \malepossadj
  { male } { possadj } { his }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\femalepossadj}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \femalepossadj
  { female } { possadj } { her }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\person_new_localisation:nnn
  { nonbinary } { possadj } { their }
%    \end{macrocode}
%\begin{macro}{\pluralpossadj}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \pluralpossadj
  { unknown } { pluralpossadj } { their }
%    \end{macrocode}
%\end{macro}
%
%Pronouns (possessive pronoun).
%\begin{macro}{\maleposspronoun}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \maleposspronoun
  { male } { posspronoun } { his }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\femaleposspronoun}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \femaleposspronoun
  { female } { posspronoun } { hers }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\person_new_localisation:nnn
  { nonbinary } { posspronoun } { theirs }
%    \end{macrocode}
%\begin{macro}{\pluralposspronoun}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \pluralposspronoun
  { unknown } { pluralposspronoun } { theirs }
%    \end{macrocode}
%\end{macro}
%
%\subsubsection{Second person}
%These are the same for all genders in English, but version 3.0 now
%provides support in the event of other languages.
%    \begin{macrocode}
\person_new_localisation:nnn { unknown } { pronoun2 } { you }
\person_new_localisation:nnn { unknown } { pluralpronoun2 } { you }
%    \end{macrocode}
%
%Objective pronoun.
%    \begin{macrocode}
\person_new_localisation:nnn
  { unknown } { objpronoun2 } { you }
\person_new_localisation:nnn
  { unknown } { pluralobjpronoun2 } { you }
%    \end{macrocode}
%Possessive adjective.
%    \begin{macrocode}
\person_new_localisation:nnn
  { unknown } { possadj2 } { your }
\person_new_localisation:nnn
  { unknown } { pluralpossadj2 } { your }
%    \end{macrocode}
%Possessive pronoun.
%    \begin{macrocode}
\person_new_localisation:nnn
  { unknown } { posspronoun2 } { yours }
\person_new_localisation:nnn
  { unknown } { pluralposspronoun2 } { yours }
%    \end{macrocode}
%
%\subsubsection{Relationship}
%
%\begin{macro}{\malechild}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \malechild
  { male } { child } { son }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\femalechild}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \femalechild
  { female } { child } { daughter }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\person_new_localisation:nnn
  { nonbinary } { child } { child }
%    \end{macrocode}
%\begin{macro}{\pluralchild}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \pluralchild
  { unknown } { pluralchild } { children }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\malechildren}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \malechildren
  { male } { pluralchild } { sons }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\femalechildren}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \femalechildren
  { female } { pluralchild } { daughters }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\person_new_localisation:nnn
  { nonbinary } { pluralchild } { children }
%    \end{macrocode}
%\begin{macro}{\maleparent}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \maleparent
  { male } { parent } { father }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\femaleparent}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \femaleparent
  { female } { parent } { mother }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\person_new_localisation:nnn
  { nonbinary } { parent } { parent }
%    \end{macrocode}
%\begin{macro}{\pluralparent}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \pluralparent
  { unknown } { pluralparent } { parents }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\malesibling}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \malesibling
  { male } { sibling } { brother }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\femalesibling}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \femalesibling
  { female } { sibling } { sister }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\person_new_localisation:nnn
  { nonbinary } { sibling } { sibling }
%    \end{macrocode}
%\begin{macro}{\pluralsibling}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \pluralsibling
  { unknown } { pluralsibling } { siblings }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\malesiblings}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \malesiblings
  { male } { pluralsibling } { brothers }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\femalesiblings}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \femalesiblings
  { female } { pluralsibling } { sisters }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\person_new_localisation:nnn
  { nonbinary } { pluralsibling } { siblings }
%    \end{macrocode}
%
%\subsubsection{Other}
%
%\begin{macro}{\andname}
%Version 3.0: removed definition of \cs{andname} (\sty{datatool-base}
%now provides \cs{DTLandname})
%\changes{3.0}{2025-03-03}{removed}
%\end{macro}
%
%\begin{macro}{\malename}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \malename
  { male } { gender } { male }
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\femalename}
%Old pre 3.0 command. This may be removed in future.
%    \begin{macrocode}
\__person_deprecated_lang_cs:Nnnn
 \femalename
  { female } { gender } { female }
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\person_new_localisation:nnn
  { nonbinary } { gender } { non-binary }
%    \end{macrocode}
%
%    \begin{macrocode}
\person_new_localisation:nnn
  { unknown } { gender } { unknown }
%    \end{macrocode}
%There are no plural versions of "gender".
%
%\subsection{Displaying Information (Name and Title)}
%
%\begin{macro}{\personsep}
% Separator to use between people (but not the between the last
% two).
%    \begin{macrocode}
\newcommand*{\personsep}{\DTLlistformatsep}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\personlastsep}
% Separator to use between last two people if the list contains more
% than two people.
%\changes{3.0}{2025-03-03}{use datatool-base separators}
%    \begin{macrocode}
\newcommand*{\personlastsep}{
 \DTLlistformatoxford
 \DTLlistformatlastsep
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\twopeoplesep}
% Separator to use when list only contains two people.
%\changes{3.0}{2025-03-03}{use datatool-base separators}
%    \begin{macrocode}
\newcommand*{\twopeoplesep}{\DTLlistformatlastsep}
%    \end{macrocode}
%\end{macro}
%List of people. The provided function should encapsulate the person
%label.
%    \begin{macrocode}
\cs_new:Nn \person_display_list:N
{
  \int_case:nnF { \seq_count:N \l__person_people_seq }
   {
     { \c_zero_int }
      {
        \PackageWarning { person }
         { No ~ people ~ defined }
      }
     { \c_one_int }
      {
        \exp_args:Nx #1
         { \seq_item:Nn \l__person_people_seq { \c_one_int } }
       }
     { 2 }
      {
        \exp_args:Nx #1
         { \seq_item:Nn \l__person_people_seq { \c_one_int } }
        \twopeoplesep
        \exp_args:Nx #1
         { \seq_item:Nn \l__person_people_seq { 2 } }
      }
   }
   {
     \seq_map_indexed_inline:Nn \l__person_people_seq
      {
        \int_case:nnF { ##1 }
         {
           { \seq_count:N \l__person_people_seq }
             { \personlastsep }
           { \c_one_int } { }
         }
         { \personsep }
        #1 { ##2 }
      }
   }
}
%    \end{macrocode}
%
%\begin{macro}{\personfullname}
% The person's full name can be displayed using 
% \cs{personfullname}\oarg{label}, where \meta{label} is the
% unique label used when defining that person. If \meta{label}
% is omitted, \texttt{anon} is used.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \personfullname { O{anon} }
{
 \person_set_label:n { #1 }
 \person_if_exist_or_err:nT { \l_person_label_tl }
  {
    \__person_full_name:n { \l_person_label_tl }
  }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \__person_full_name:n
{
  \tl_use:c { person@ #1 @fullname }
}
\cs_generate_variant:Nn \__person_full_name:n { x }
%    \end{macrocode}
%
%\begin{macro}{\peoplefullname}
% List all defined people's full names.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{added warning if no people}
%    \begin{macrocode}
\NewDocumentCommand \peoplefullname { }
{
  \person_display_list:N \__person_full_name:n
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\personforenames}
% The person's forenames can be displayed using 
% \cs{personforenames}\oarg{label}, where \meta{label} is the
% unique label used when defining that person. If \meta{label}
% is omitted, \texttt{anon} is used.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \personforenames { O{anon} }
{
 \person_set_label:n { #1 }
 \person_if_exist_or_err:nT { \l_person_label_tl }
  {
    \__person_forenames:n { \l_person_label_tl }
  }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \__person_forenames:n
{
  \tl_use:c { person@ #1 @forenames }
}
\cs_generate_variant:Nn \__person_forenames:n { x }
%    \end{macrocode}
%
%\begin{macro}{\peopleforenames}
% List all defined people's full names.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \peopleforenames { }
{
  \person_display_list:N \__person_forenames:n
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\personname}
% As \cs{personfullname}, but for the person's familiar or first name.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \personname { O{anon} }
{
 \person_set_label:n { #1 }
 \person_if_exist_or_err:nT { \l_person_label_tl }
  {
    \__person_name:n { \l_person_label_tl }
  }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \__person_name:n
{
  \tl_use:c { person@ #1 @name }
}
\cs_generate_variant:Nn \__person_name:n { x }
%    \end{macrocode}
%
%\begin{macro}{\peoplename}
% List all defined people's familiar names.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \peoplename { }
{
  \person_display_list:N \__person_name:n
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\personsurname}
% As \cs{personforenames}, but for the person's surname.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \personsurname { O{anon} }
{
 \person_set_label:n { #1 }
 \person_if_exist_or_err:nT { \l_person_label_tl }
  {
    \__person_surname:n { \l_person_label_tl }
  }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \__person_surname:n
{
  \tl_use:c { person@ #1 @surname }
}
\cs_generate_variant:Nn \__person_surname:n { x }
%    \end{macrocode}
%
%\begin{macro}{\peoplesurname}
% List all defined people's surnames.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \peoplesurname { }
{
  \person_display_list:N \__person_surname:n
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\persontitlesurname}
% The person's title and surname or full name can be displayed using 
% \cs{persontitlesurname}\oarg{label}, where \meta{label} is the
% unique label used when defining that person. If \meta{label}
% is omitted, \texttt{anon} is used.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \persontitlesurname { O{anon} }
{
 \person_set_label:n { #1 }
 \person_if_exist_or_err:nT { \l_person_label_tl }
  {
    \__person_title_surname:n { \l_person_label_tl }
  }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\cs_new:Nn \__person_title_surname:n
{
  \tl_if_empty:cTF { person@ #1 @surname }
  { \__person_full_name:n { #1 } }
  {
    \tl_if_empty:cTF { person@#1@title }
    { \__person_full_name:n { #1 } }
    {
     \tl_use:c { person@ #1 @title }
     \persontitlesurnamesep
     \tl_use:c { person@ #1 @surname}
    }
  }
}
%    \end{macrocode}
%
%\begin{macro}{\persontitlesurnamesep}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\ExplSyntaxOff
\newcommand{\persontitlesurnamesep}{\ }
\ExplSyntaxOn
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\peopletitlesurname}
% List all defined people's title and surname or full names. This iterates through all
% labels in \cs{@people@list}.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \peopletitlesurname { }
{
  \person_display_list:N \__person_title_surname:n
}
%    \end{macrocode}
%\end{macro}
%
%\subsection{Displaying Language-Sensitive Information}
%
%Display language text for a given person identified by their label.
%    \begin{macrocode}
\cs_new:Nn \__person_language:nn
{
  \person_get_localisation:vn
    { person@ #1 @gender } { #2 }
}
\cs_generate_variant:Nn \__person_language:nn { xn }
%    \end{macrocode}
%Sentence case:
%    \begin{macrocode}
\cs_new:Nn \__person_Language:nn
{
  \text_titlecase_first:n
   {
     \__person_language:nn { #1 } { #2 }
   }
}
\cs_generate_variant:Nn \__person_Language:nn { xn }
%    \end{macrocode}
%Common function for higher level user commands. No case-change:
%    \begin{macrocode}
\cs_new:Nn \person_language_text:nn
{
 \person_set_label:n { #1 }
 \person_if_exist_or_err:nT { \l_person_label_tl }
  {
    \__person_language:nn { \l_person_label_tl } { #2 }
  }
}
%    \end{macrocode}
%Sentence case:
%    \begin{macrocode}
\cs_new:Nn \person_Language_text:nn
{
 \person_set_label:n { #1 }
 \person_if_exist_or_err:nT { \l_person_label_tl }
  {
    \__person_Language:nn { \l_person_label_tl } { #2 }
  }
}
%    \end{macrocode}
%Plural, unless only one person defined. The argument is the language tag.
%    \begin{macrocode}
\cs_new:Nn \person_language_all_text:n
{
 \__person_case:nnn
  {
    \PackageWarning { person } { No ~ people ~ defined }
    \person_get_localisation:nn { unknown } { plural#1 }
  }
  {
    \exp_args:Nx \person_language_text:nn
      { \seq_item:Nn \l__person_people_seq { 1 } } { #1 }
  }
  { 
    \person_all_gender_case:nnnn
     {
       \person_get_localisation:nn { male } { plural#1 }
     }
     {
       \person_get_localisation:nn { female } { plural#1 }
     }
     {
       \person_get_localisation:nn { nonbinary } { plural#1 }
     }
     {
       \person_get_localisation:nn { unknown } { plural#1 }
     }
  }
}
%    \end{macrocode}
%Sentence case:
%    \begin{macrocode}
\cs_new:Nn \person_Language_all_text:n
{
 \__person_case:nnn
  {
    \PackageWarning { person } { No ~ people ~ defined }
    \text_titlecase_first:n
     {
       \person_get_localisation:nn { unknown } { plural#1 }
     }
  }
  {
    \exp_args:Nx \person_Language_text:nn
      { \seq_item:Nn \l__person_people_seq { 1 } } { #1 }
  }
  { 
    \person_all_gender_case:nnnn
     {
       \text_titlecase_first:n
        {
          \person_get_localisation:nn { male } { plural#1 }
        }
     }
     {
       \text_titlecase_first:n
        {
          \person_get_localisation:nn { female } { plural#1 }
        }
     }
     {
       \text_titlecase_first:n
        {
          \person_get_localisation:nn { nonbinary } { plural#1 }
        }
     }
     {
       \text_titlecase_first:n
        {
          \person_get_localisation:nn { unknown } { plural#1 }
        }
     }
  }
}
%    \end{macrocode}
%
%\subsubsection{Third-Person Reference}
%
%\begin{macro}{\personpronoun}
% Display the pronoun according to the person's gender.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \personpronoun { O{anon} }
{
  \person_language_text:nn { #1 } { pronoun }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Personpronoun}
% As above, but make the first letter uppercase.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{switched to LaTeX3 for case change}
%    \begin{macrocode}
\NewDocumentCommand \Personpronoun { O{anon} }
{
  \person_Language_text:nn { #1 } { pronoun }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\peoplepronoun}
%Pronoun for all defined people.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{added warning if no people defined}
%    \begin{macrocode}
\NewDocumentCommand \peoplepronoun { }
{
 \person_language_all_text:n { pronoun }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Peoplepronoun}
% As above, but first letter in upper case.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{switched to LaTeX3 for case change}
%\changes{3.0}{2025-03-03}{added warning if no people defined}
%    \begin{macrocode}
\NewDocumentCommand \Peoplepronoun { }
{
 \person_Language_all_text:n { pronoun }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\personobjpronoun}
% Display the objective pronoun according to the person's 
% gender.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \personobjpronoun { O{anon} }
{
  \person_language_text:nn { #1 } { objpronoun }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Personobjpronoun}
% As above, but make the first letter uppercase.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{switched to LaTeX3 for case change}
%    \begin{macrocode}
\NewDocumentCommand \Personobjpronoun { O{anon} }
{
  \person_Language_text:nn { #1 } { objpronoun }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\peopleobjpronoun}
%Objective pronoun for all defined people.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{added warning if no people defined}
%    \begin{macrocode}
\NewDocumentCommand \peopleobjpronoun { }
{
  \person_language_all_text:n { objpronoun }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\Peopleobjpronoun}
% As above, but first letter in upper case
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{added warning if no people defined}
%    \begin{macrocode}
\NewDocumentCommand \Peopleobjpronoun { }
{
  \person_Language_all_text:n { objpronoun }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\personpossadj}
% Display the possessive adjective according to the person's 
% gender.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \personpossadj { O{anon} }
{
  \person_language_text:nn { #1 } { possadj }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Personpossadj}
% As above, but make the first letter uppercase.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{switched to LaTeX3 for case change}
%    \begin{macrocode}
\NewDocumentCommand \Personpossadj { O{anon} }
{
  \person_Language_text:nn { #1 } { possadj }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\peoplepossadj}
% Possessive adjective for all defined people.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{added warning if no people defined}
%    \begin{macrocode}
\NewDocumentCommand \peoplepossadj { }
{
 \person_language_all_text:n { possadj }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\Peoplepossadj}
% As above, but first letter in upper case.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{switched to LaTeX3 for case change}
%\changes{3.0}{2025-03-03}{added warning if no people defined}
%    \begin{macrocode}
\NewDocumentCommand \Peoplepossadj { }
{
 \person_Language_all_text:n { possadj }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\personposspronoun}
% Display possessive pronoun according to the
% person's gender.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \personposspronoun { O{anon} }
{
  \person_language_text:nn { #1 } { posspronoun }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Personposspronoun}
% As above, but make the first letter uppercase.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{switched to LaTeX3 for case change}
%    \begin{macrocode}
\NewDocumentCommand \Personposspronoun { O{anon} }
{
  \person_Language_text:nn { #1 } { posspronoun }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\peopleposspronoun}
% Possessive pronoun for all defined people.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{added warning if no people defined}
%    \begin{macrocode}
\NewDocumentCommand \peopleposspronoun { }
{
 \person_language_all_text:n { posspronoun }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\Peopleposspronoun}
% As above, but first letter in upper case
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{switched to LaTeX3 for case change}
%\changes{3.0}{2025-03-03}{added warning if no people defined}
%    \begin{macrocode}
\NewDocumentCommand \Peopleposspronoun { }
{
 \person_Language_all_text:n { posspronoun }
}
%    \end{macrocode}
%\end{macro}
%
%\subsubsection{Second-Person Reference}
%
%\begin{macro}{\personpronounii}
% Display the pronoun according to the person's gender.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \personpronounii { O{anon} }
{
  \person_language_text:nn { #1 } { pronoun2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Personpronounii}
% As above, but make the first letter uppercase.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \Personpronounii { O{anon} }
{
  \person_Language_text:nn { #1 } { pronoun2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\peoplepronounii}
%Pronoun for all defined people.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \peoplepronounii { }
{
 \person_language_all_text:n { pronoun2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Peoplepronounii}
% As above, but first letter in upper case.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \Peoplepronounii { }
{
 \person_Language_all_text:n { pronoun2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\personobjpronounii}
% Display the objective pronoun according to the person's 
% gender.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \personobjpronounii { O{anon} }
{
  \person_language_text:nn { #1 } { objpronoun2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Personobjpronounii}
% As above, but make the first letter uppercase.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \Personobjpronounii { O{anon} }
{
  \person_Language_text:nn { #1 } { objpronoun2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\peopleobjpronounii}
%Objective pronoun for all defined people.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \peopleobjpronounii { }
{
  \person_language_all_text:n { objpronoun2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Peopleobjpronounii}
% As above, but first letter in upper case
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \Peopleobjpronounii { }
{
  \person_Language_all_text:n { objpronoun2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\personpossadjii}
% Display the possessive adjective according to the person's 
% gender.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \personpossadjii { O{anon} }
{
  \person_language_text:nn { #1 } { possadj2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Personpossadjii}
% As above, but make the first letter uppercase.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \Personpossadjii { O{anon} }
{
  \person_Language_text:nn { #1 } { possadj2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\peoplepossadjii}
% Possessive adjective for all defined people.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \peoplepossadjii { }
{
 \person_language_all_text:n { possadj2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Peoplepossadjii}
% As above, but first letter in upper case.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \Peoplepossadjii { }
{
 \person_Language_all_text:n { possadj2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\personposspronounii}
% Display possessive pronoun according to the
% person's gender.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \personposspronounii { O{anon} }
{
  \person_language_text:nn { #1 } { posspronoun2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Personposspronounii}
% As above, but make the first letter uppercase.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \Personposspronounii { O{anon} }
{
  \person_Language_text:nn { #1 } { posspronoun2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\peopleposspronounii}
% Possessive pronoun for all defined people.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \peopleposspronounii { }
{
 \person_language_all_text:n { posspronoun2 }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Peopleposspronounii}
% As above, but first letter in upper case
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \Peopleposspronounii { }
{
 \person_Language_all_text:n { posspronoun2 }
}
%    \end{macrocode}
%\end{macro}
%
%\subsubsection{Relationship Reference}
%
%\begin{macro}{\personchild}
% Display this person's relationship to their parent.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \personchild { O{anon} }
{
  \person_language_text:nn { #1 } { child }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Personchild}
% As above, but make first letter uppercase.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{switched to LaTeX3 for case change}
%    \begin{macrocode}
\NewDocumentCommand \Personchild { O{anon} }
{
  \person_Language_text:nn { #1 } { child }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\peoplechild}
% Child relationship for all. (That is, all defined people have the
% same parents.)
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{added warning if no people defined}
%    \begin{macrocode}
\NewDocumentCommand \peoplechild { }
{
  \person_language_all_text:n { child }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Peoplechild}
% As above but first letter is made uppercase.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{switched to LaTeX3 for case change}
%\changes{3.0}{2025-03-03}{added warning if no people defined}
%    \begin{macrocode}
\NewDocumentCommand \Peoplechild { }
{
  \person_Language_all_text:n { child }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\personparent}
% Display this person's relationship to their child (father /
% mother / parent).
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \personparent { O{anon} }
{
  \person_language_text:nn { #1 } { parent }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Personparent}
% As above, but make the first letter uppercase.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{switched to LaTeX3 for case change}
%    \begin{macrocode}
\NewDocumentCommand \Personparent { O{anon} }
{
  \person_Language_text:nn { #1 } { parent }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\peopleparent}
% The parental term for all defined people.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{added warning if no people defined}
%    \begin{macrocode}
\NewDocumentCommand \peopleparent { }
{
 \person_language_all_text:n { parent }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Peopleparent}
% As above, but make first letter uppercase.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{switched to LaTeX3 for case change}
%\changes{3.0}{2025-03-03}{added warning if no people defined}
%    \begin{macrocode}
\NewDocumentCommand \Peopleparent { }
{
  \person_Language_all_text:n { parent }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\personsibling}
% Display this person's relationship to their siblings.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \personsibling { O{anon} }
{
  \person_language_text:nn { #1 } { sibling }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Personsibling}
% As above but make first letter uppercase.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{switched to LaTeX3 for case change}
%    \begin{macrocode}
\NewDocumentCommand \Personsibling { O{anon} }
{
  \person_Language_text:nn { #1 } { sibling }
}
%    \end{macrocode}
%\end{macro}
%\begin{macro}{\peoplesibling}
% Sibling term for all defined people.
%\changes{3.0}{2025-03-03}{made robust}
%\changes{3.0}{2025-03-03}{added warning if no people defined}
%    \begin{macrocode}
\NewDocumentCommand \peoplesibling { }
{
  \person_language_all_text:n { sibling }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Peoplesibling}
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \Peoplesibling { }
{
  \person_Language_all_text:n { sibling }
}
%    \end{macrocode}
%\end{macro}
%
%
%
%\subsection{Extracting Information}
%
%\begin{macro}{\persongender}
% Displays the given person's gender using localisation.
%\changes{2.23}{2015-07-11}{bug fix: replaced \cs{ifpersonmale} with
%\cs{ifmale}}
%    \begin{macrocode}
\NewDocumentCommand \persongender { m }
{
  \person_language_text:nn { #1 } { gender }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\Persongender}
%As above but converts the first letter to uppercase.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \Persongender { m }
{
  \person_Language_text:nn { #1 } { gender }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\getpersongender}
% Gets person's gender name (language-sensitive) and stores in first argument which must be
% a control sequence.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \getpersongender { m m }
{
 \person_set_label:n { #2 }
 \person_if_exist_or_err:nT { \l_person_label_tl }
  {
    \tl_set:Ne #1
     { \__person_language:nn { \l_person_label_tl } { gender } }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\getpersongenderlabel}
% Gets person's internal gender label and stores in first argument which must be
% a control sequence.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \getpersongenderlabel { m m }
{
 \person_set_label:n { #2 }
 \person_if_exist_or_err:nT { \l_person_label_tl }
  {
    \tl_set:Nx #1
     { person@ \l_person_label_tl @gender }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\getpersonname}
% Gets person's name and stores in first argument which must be
% a control sequence.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \getpersonname { m m }
{
 \person_set_label:n { #2 }
 \person_if_exist_or_err:nT { \l_person_label_tl }
  {
    \tl_set_eq:Nc #1
     { person@ \l_person_label_tl @name }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\getpersonforenames}
% Gets person's forenames and stores in first argument which must be
% a control sequence.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \getpersonforenames { m m }
{
 \person_set_label:n { #2 }
 \person_if_exist_or_err:nT { \l_person_label_tl }
  {
    \tl_set_eq:Nc #1
     { person@ \l_person_label_tl @forenames }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\getpersonfullname}
% Gets person's full name and stores in first argument which must be
% a control sequence.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \getpersonfullname { m m }
{
 \person_set_label:n { #2 }
 \person_if_exist_or_err:nT { \l_person_label_tl }
  {
    \tl_set_eq:Nc #1
     { person@ \l_person_label_tl @fullname }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\getpersonsurname}
% Gets person's surname and stores in first argument which must be
% a control sequence.
%\changes{3.0}{2025-03-03}{new}
%    \begin{macrocode}
\NewDocumentCommand \getpersonsurname { m m }
{
 \person_set_label:n { #2 }
 \person_if_exist_or_err:nT { \l_person_label_tl }
  {
    \tl_set_eq:Nc #1
     { person@ \l_person_label_tl @surname }
  }
}
%    \end{macrocode}
%\end{macro}
%
%\begin{macro}{\getpersontitle}
% Gets person's title and stores in first argument which must be
% a control sequence.
%\changes{3.0}{2025-03-03}{made robust}
%    \begin{macrocode}
\NewDocumentCommand \getpersontitle { m m }
{
 \person_set_label:n { #2 }
 \person_if_exist_or_err:nT { \l_person_label_tl }
  {
    \tl_set_eq:Nc #1
     { person@ \l_person_label_tl @title }
  }
}
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
%\subsection{Localisation Support}
%\begin{macro}{\RequirePersonDialect}
%    \begin{macrocode}
\newcommand*{\RequirePersonDialect}[1]{%
 \TrackLangRequireDialect{person}{#1}%
} 
%    \end{macrocode}
%\end{macro}
%    \begin{macrocode}
\datatool@load@locales{%
  \AnyTrackedLanguages
  {%
    \ForEachTrackedDialect{\@dtl@thisdialect}%
    {%
      \RequirePersonDialect{\@dtl@thisdialect}%
    }%
  }%
  {}%
}
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</person.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*databib.bst>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
ENTRY
  { address
    author
    booktitle
    chapter
    edition
    editor
    howpublished
    institution
    journal
    key
    month
    note
    number
    organization
    pages
    publisher
    school
    series
    title
    type
    volume
    year
    date
    isbn
    issn
    eid
    doi
    pubmed
    url
    urldate
    abstract
    file
    eprints
    eprint
    eprinttype
    citations
  }
  {}
  { label }

INTEGERS { output.state before.all mid.sentence after.sentence after.block }

FUNCTION {init.state.consts}
{ #0 'before.all :=
  #1 'mid.sentence :=
  #2 'after.sentence :=
  #3 'after.block :=
}

STRINGS { s t }

FUNCTION {output.nonnull}
{ 's :=
          output.state before.all =
            {
       "%" * write$
               newline$
            }
    { newline$
              %add.period$ " " * write$
            }
  if$
  %    if$
  %    mid.sentence 'output.state :=
  %  }
  %if$
  s
}

FUNCTION {output}
{ duplicate$ empty$
    'pop$
    'output.nonnull
  if$
}

FUNCTION {output.check}
{ 't :=
  duplicate$ empty$
    { pop$ "empty " t * " in " * cite$ * warning$ }
    {
      output.nonnull
    }
  if$
}

FUNCTION {output.bibitem}
{ "\DTLnewbibrow" write$
  newline$
  "\DTLnewbibitem {CiteKey}{" write$
  cite$ write$
  "}%" write$
  newline$
  ""
  before.all 'output.state :=
}

FUNCTION {fin.entry}
{ "%" *
  write$
  newline$
}

FUNCTION {new.block}
{ output.state before.all =
    'skip$
    { after.block 'output.state := }
  if$
}

FUNCTION {new.sentence}
{ output.state after.block =
    'skip$
    { output.state before.all =
'skip$
{ after.sentence 'output.state := }
      if$
    }
  if$
}

FUNCTION {not}
{   { #0 }
    { #1 }
  if$
}

FUNCTION {and}
{   'skip$
    { pop$ #0 }
  if$
}

FUNCTION {or}
{   { pop$ #1 }
    'skip$
  if$
}

FUNCTION {new.block.checka}
{ empty$
    'skip$
    'new.block
  if$
}

FUNCTION {new.block.checkb}
{ empty$
  swap$ empty$
  and
    'skip$
    'new.block
  if$
}

FUNCTION {new.sentence.checka}
{ empty$
    'skip$
    'new.sentence
  if$
}

FUNCTION {new.sentence.checkb}
{ empty$
  swap$ empty$
  and
    'skip$
    'new.sentence
  if$
}

FUNCTION {field.or.null}
{ duplicate$ empty$
    { pop$ "" }
    'skip$
  if$
}

FUNCTION {emphasize}
{ duplicate$ empty$
    { pop$ "" }
    { "{\em " swap$ * "}" * }
  if$
}

FUNCTION {group}
{ duplicate$ empty$
    { pop$ "" }
    { "{" swap$ * "}" * }
  if$
}

INTEGERS { nameptr namesleft numnames }

FUNCTION {format.names}
{
  's :=
  #1 'nameptr :=
  s num.names$ 'numnames :=
  numnames 'namesleft :=
    { namesleft #0 > }
    {
      %s nameptr "{vv,}{ll,}{jj,}{ff}" format.name$ 't :=
      "{" *
      s nameptr "{vv}" format.name$ 't :=
       t * "}" *
      "{" *
      s nameptr "{ll}" format.name$ 't :=
       t * "}{" *
      s nameptr "{jj}" format.name$ 't :=
       t * "}" *
      "{" *
      s nameptr "{ff}" format.name$ 't :=
       t * "}" *
      s nameptr "" format.name$ 't :=
      namesleft #1 >
        { "," * }
        { }
      if$
      nameptr #1 >
{
          t *
}
't
      if$
      nameptr #1 + 'nameptr :=
      namesleft #1 - 'namesleft :=
    }
  while$
  "}" *
}

FUNCTION {format.authors}
{
  author empty$
    { "" }
    { author
      "\DTLnewbibitem {Author}{" write$
      format.names }
  if$
}

FUNCTION {format.editors}
{ editor empty$
    { "" }
    { editor
      "\DTLnewbibitem {Editor}{" write$
      format.names
    }
  if$
}

FUNCTION {format.title}
{ title empty$
    { "" }
    {
      "\DTLnewbibitem {Title}"
      title "t" change.case$ group *
    }
  if$
}

FUNCTION {format.howpublished}
{ howpublished empty$
    { "" }
    {
      howpublished
      "\DTLnewbibitem {HowPublished}" swap$ group *
    }
  if$
}

FUNCTION {format.organization}
{ organization empty$
    { "" }
    {
      organization
      "\DTLnewbibitem {Organization}" swap$ group *
    }
  if$
}

FUNCTION {format.institution}
{ institution empty$
    { "" }
    {
      institution
      "\DTLnewbibitem {Institution}" swap$ group *
    }
  if$
}

FUNCTION {format.key}
{ key empty$
    { "" }
    {
      key
      "\DTLnewbibitem {Key}" swap$ group *
    }
  if$
}

FUNCTION {format.note}
{ note empty$
    { "" }
    {
      note
      "\DTLnewbibitem {Note}" swap$ group *
    }
  if$
}

FUNCTION {format.isbn}
{ isbn empty$
    { "" }
    {
      isbn
      "\DTLnewbibitem {ISBN}" swap$ group *
    }
  if$
}

FUNCTION {format.issn}
{ issn empty$
    { "" }
    {
      issn
      "\DTLnewbibitem {ISSN}" swap$ group *
    }
  if$
}

FUNCTION {format.eid}
{ eid empty$
    { "" }
    {
      eid
      "\DTLnewbibitem {EID}" swap$ group *
    }
  if$
}

FUNCTION {format.doi}
{ doi empty$
    { "" }
    {
      doi
      "\DTLnewbibliteralitem {DOI}" swap$ group *
    }
  if$
}

FUNCTION {format.pubmed}
{ pubmed empty$
    { "" }
    {
      pubmed
      "\DTLnewbibitem {PubMed}" swap$ group *
    }
  if$
}

FUNCTION {format.citations}
{ citations empty$
    { "" }
    {
      citations
      "\DTLnewbibitem {Citations}" swap$ group *
    }
  if$
}

FUNCTION {format.abstract}
{ abstract empty$
    { "" }
    {
      abstract
      "\DTLnewbibitem {Abstract}" swap$ group *
    }
  if$
}

FUNCTION {format.url}
{ url empty$
    { "" }
    {
      url
      "\DTLnewbibliteralitem {Url}" swap$ group *
      output
      urldate empty$
       { "" }
       {
         urldate
         "\DTLnewbibitem {UrlDate}" swap$ group *
       }
      if$
    }
  if$
}

FUNCTION {format.file}
{ file empty$
    { "" }
    {
      file
      "\DTLnewbibliteralitem {File}" swap$ group *
    }
  if$
}

FUNCTION {format.eprints}
{ eprints empty$
    { 
      eprint empty$
       { "" }
       {
         eprint
         "\DTLnewbibliteralitem {Eprints}" swap$ group *
         eprinttype empty$
           { }
           {
             output
             eprinttype
            "\DTLnewbibitem {EprintType}" swap$ group *
           }
         if$
       }
      if$
    }
    {
      eprints
      "\DTLnewbibliteralitem {Eprints}" swap$ group *
      eprinttype empty$
        { }
        {
          output
          eprinttype
         "\DTLnewbibitem {EprintType}" swap$ group *
        }
      if$
    }
  if$
}

FUNCTION {format.extra}
{
  format.isbn output
  format.issn output
  format.eid output
  format.doi output
  format.pubmed output
  format.url output
  format.abstract output
  format.file output
  format.eprints output
  format.citations output
}

FUNCTION {format.address}
{ address empty$
    { "" }
    {
      address
      "\DTLnewbibitem {Address}" swap$ group *
    }
  if$
}

FUNCTION {format.school}
{ school empty$
    { "" }
    {
      school
      "\DTLnewbibitem {School}" swap$ group *
    }
  if$
}

FUNCTION {format.publisher}
{ publisher empty$
    { "" }
    {
      publisher
      "\DTLnewbibitem {Publisher}" swap$ group *
    }
  if$
}

FUNCTION {n.dashify}
{ 't :=
  ""
    { t empty$ not }
    { t #1 #1 substring$ "-" =
{ t #1 #2 substring$ "--" = not
    { "--" *
      t #2 global.max$ substring$ 't :=
    }
    {   { t #1 #1 substring$ "-" = }
{ "-" *
  t #2 global.max$ substring$ 't :=
}
      while$
    }
  if$
}
{ t #1 #1 substring$ *
  t #2 global.max$ substring$ 't :=
}
      if$
    }
  while$
}

FUNCTION {format.date}
{
 date empty$
  {
    year empty$
     {
       month empty$
         { "" }
         { "there's a month but no year in " cite$ * warning$
           "\DTLnewbibitem {Month}" *
            month group
         }
       if$
     }
     {
       month empty$
        {  }
        { "\DTLnewbibitem {Month}{" * month * "}" * }
       if$
       "\DTLnewbibitem {Year}{" * year * "}"
     }
    if$
  }
  {
    "\DTLnewbibitem {Date}{" * date * "}"
  }
 if$
}

FUNCTION {format.btitle}
{ title
  "\DTLnewbibitem {Title}{" swap$ *
  "}" *
}

FUNCTION {tie.or.space.connect}
{ duplicate$ text.length$ #3 <
    { "~" }
    { " " }
  if$
  swap$ * *
}

FUNCTION {either.or.check}
{ empty$
    'pop$
    { "can't use both " swap$ * " fields in " * cite$ * warning$ }
  if$
}

FUNCTION {format.bvolume}
{ volume empty$
    { "" }
    {
      "\DTLnewbibitem {Volume}{" volume * "}" *
      series empty$
'skip$
{
          "\DTLnewbibitem {Series}" * series group *
        }
      if$
      "volume and number" number either.or.check
    }
  if$
}

FUNCTION {format.number.series}
{ volume empty$
    { number empty$
{
          %series field.or.null group
           series empty$
             { "" }
             { "\DTLnewbibitem {Series}" * series group }
           if$
        }
{
          "\DTLnewbibitem {Number}" number group *
  series empty$
    { "there's a number but no series in " cite$ * warning$ }
    { "\DTLnewbibitem {Series}{" * series * "}" * }
  if$
}
      if$
    }
    { "" }
  if$
}

FUNCTION {format.edition}
{ edition empty$
    { "" }
    {
      "\DTLnewbibitem {Edition}"
      edition "l" change.case$ group *
    }
  if$
}

INTEGERS { multiresult }

FUNCTION {multi.page.check}
{ 't :=
  #0 'multiresult :=
    { multiresult not
      t empty$ not
      and
    }
    { t #1 #1 substring$
      duplicate$ "-" =
      swap$ duplicate$ "," =
      swap$ "+" =
      or or
{ #1 'multiresult := }
{ t #2 global.max$ substring$ 't := }
      if$
    }
  while$
  multiresult
}

FUNCTION {format.pages}
{ pages empty$
    { "" }
    { pages multi.page.check
{ "\DTLnewbibitem {Pages}" pages n.dashify
          group * }
{ "\DTLnewbibitem {Pages}" pages
           group *}
      if$
    }
  if$
}

FUNCTION {format.vol.num.pages}
{
  volume empty$
  { "" }
  {
    "\DTLnewbibitem {Volume}{" volume * "}" *
  }
  if$
  number empty$
    'skip$
    { "\DTLnewbibitem {Number}{" number * "}\relax " * *
      volume empty$
{ "there's a number but no volume in " cite$ * warning$ }
'skip$
      if$
    }
  if$
  pages empty$
    'skip$
    { duplicate$ empty$
{ pop$ format.pages }
{ "\DTLnewbibitem {Pages}" * pages n.dashify group * }
      if$
    }
  if$
}

FUNCTION {format.chapter.pages}
{ chapter empty$
    'format.pages
    { type empty$
{ "\DTLnewbibitem {Type}{chapter}" }
{ "\DTLnewbibitem {Type}" type "l" change.case$ group *}
      if$
      "\DTLnewbibitem {Chapter}{" * chapter * "}" *
      pages empty$
'skip$
{ format.pages * }
      if$
    }
  if$
}

FUNCTION {format.in.ed.booktitle}
{ booktitle empty$
    { "" }
    {
      "\DTLnewbibitem {BookTitle}" booktitle group *
      editor empty$
      {}
      {
         "\DTLnewbibitem {Editor}{" *
         editor format.names *
      }
      if$
    }
  if$
}

FUNCTION {empty.misc.check}
{ author empty$ title empty$ howpublished empty$
  month empty$ year empty$ note empty$
  and and and and and
    { "all relevant fields are empty in " cite$ * warning$ }
    'skip$
  if$
}

FUNCTION {format.thesis.type}
{ type empty$
    'skip$
    { pop$
      type "t" change.case$
      "\DTLnewbibitem {Type}" swap$ group *
    }
  if$
}

FUNCTION {format.tr.number}
{
  type empty$
    { "\techreportname " }
     'type
  if$
  number empty$
    { "t" change.case$ "\DTLnewbibitem {Type}" swap$ group *}
    { "\DTLnewbibitem {Type}" swap$ group *
      "\DTLnewbibitem {Number}" *
      number group * }
  if$
}

FUNCTION {format.article.crossref}
{ key empty$
    { journal empty$
{ "need key or journal for " cite$ * " to crossref " * crossref *
  warning$
  ""
}
{
          journal
          "\DTLnewbibitem {Journal}" swap$ group * 
        }
      if$
    }
    {
      ""
    }
  if$
  "\DTLnewbibitem {CrossRef}{" * crossref * "}" *
}

FUNCTION {format.crossref.editor}
{ format.editors
}

FUNCTION {format.book.crossref}
{ volume empty$
    { "empty volume in " cite$ * "'s crossref of " * crossref * warning$
    }
    {
      "\DTLnewbibitem {Volume}"
      volume group *
    }
  if$
  editor empty$
  editor field.or.null author field.or.null =
  or
    { key empty$
{ series empty$
    { "need editor, key, or series for " cite$ * " to crossref " *
      crossref * warning$
      "" *
    }
    {
              "\DTLnewbibitem {Series}{" * series *
              "}" *
            }
  if$
}
{ ""
        }
      if$
    }
    {
      format.crossref.editor *
    }
  if$
  "\DTLnewbibitem {CrossRef}{" * crossref * "}" *
}

FUNCTION {format.incoll.inproc.crossref}
{ editor empty$
  editor field.or.null author field.or.null =
  or
    { key empty$
{ booktitle empty$
    { "need editor, key, or booktitle for " cite$ * " to crossref " *
      crossref * warning$
      ""
    }
    { "\DTLnewbibitem {BookTitle}{" booktitle * "}" * }
  if$
}
{ "" }
      if$
    }
    {
       "\DTLnewbibitem {Editor}{" *
       editor format.names
    }
  if$
  "\DTLnewbibitem {CrossRef}{" * crossref * "}" *
}

FUNCTION {article}
{ output.bibitem
  "\DTLnewbibitem {EntryType}{article}%" write$
  newline$
  format.authors "author"
  output.check
  format.title "title" output.check
  new.block
  crossref missing$
    {
      journal empty$
      {
      }
      {
        "\DTLnewbibitem {Journal}{" * journal * "}" output
      }
      if$
      format.vol.num.pages output
    }
    { format.article.crossref output.nonnull
      format.pages output
    }
  if$
  format.date output
  new.block
  format.key output
  format.note output
  format.extra 
  fin.entry
}

FUNCTION {book}
{ output.bibitem
  "\DTLnewbibitem {EntryType}{book}%" write$
  newline$
  author empty$
    { format.editors "author and editor" output.check }
    { format.authors output.nonnull
      crossref missing$
{ "author and editor" editor either.or.check
        }
'skip$
      if$
    }
  if$
  new.block
  format.btitle "title" output.check
  crossref missing$
    { format.bvolume output
      new.block
      format.number.series output
      %new.sentence
      format.publisher "publisher" output.check
      format.address output
    }
    { new.block
      format.book.crossref output.nonnull
    }
  if$
  format.edition output
  format.date output
  new.block
  format.key output
  format.note output
  format.extra 
  fin.entry
}

FUNCTION {booklet}
{ output.bibitem
  "\DTLnewbibitem {EntryType}{booklet}%" write$
  newline$
  format.authors output
  new.block
  format.title "title" output.check
  howpublished address new.block.checkb
  format.howpublished output
  format.address output
  format.date output
  new.block
  format.key output
  format.note output
  format.extra 
  fin.entry
}

FUNCTION {inbook}
{ output.bibitem
  "\DTLnewbibitem {EntryType}{inbook}%" write$
  newline$
  author empty$
    { format.editors "author and editor" output.check }
    { format.authors output.nonnull
      crossref missing$
{ "author and editor" editor either.or.check }
'skip$
      if$
    }
  if$
  new.block
  format.btitle "title" output.check
  crossref missing$
    { format.bvolume output
      format.chapter.pages "chapter and pages" output.check
      new.block
      format.number.series output
      new.sentence
      format.publisher "publisher" output.check
      format.address output
    }
    { format.chapter.pages "chapter and pages" output.check
      new.block
      format.book.crossref output.nonnull
    }
  if$
  format.edition output
  format.date output
  new.block
  format.key output
  format.note output
  format.extra 
  fin.entry
}

FUNCTION {incollection}
{ output.bibitem
  "\DTLnewbibitem {EntryType}{incollection}%" write$
  newline$
  format.authors "author" output.check
  format.title "title" output.check
  crossref missing$
    { format.in.ed.booktitle "booktitle" output.check
      format.bvolume output
      format.number.series output
      format.chapter.pages output
      new.sentence
      format.publisher "publisher" output.check
      format.address output
      format.edition output
    }
    { format.incoll.inproc.crossref output.nonnull
      format.chapter.pages output
    }
  if$
  format.date output
  format.key output
  format.note output
  format.extra 
  fin.entry
}

FUNCTION {inproceedings}
{ output.bibitem
  "\DTLnewbibitem {EntryType}{inproceedings}%" write$
  newline$
  format.authors "author" output.check
  format.title "title" output.check
  crossref missing$
    { format.in.ed.booktitle "booktitle" output.check
      format.bvolume output
      format.number.series output
      format.pages output
      address empty$
{ %organization publisher new.sentence.checkb
  format.organization write$
  format.publisher output
}
{ format.address write$
  new.sentence
  format.organization output
  format.publisher output
}
      if$
    }
    {
      format.incoll.inproc.crossref output.nonnull
      format.pages output
    }
  if$
  format.date output
  format.key output
  format.note output
  format.extra 
  fin.entry
}

FUNCTION {conference} { inproceedings }

FUNCTION {manual}
{ output.bibitem
  "\DTLnewbibitem {EntryType}{manual}%" write$
  newline$
  author empty$
    { organization empty$
'skip$
{ format.organization output
  format.address output
}
      if$
    }
    { format.authors output }
  if$
  new.block
  format.btitle "title" output.check
  author empty$
    { organization empty$
{ address new.block.checka
  address output
}
'skip$
      if$
    }
    { %organization address new.block.checkb
      format.organization output
      format.address output
    }
  if$
  format.edition output
  format.date output
  new.block
  format.key output
  format.note output
  format.extra 
  fin.entry
}

FUNCTION {mastersthesis}
{ output.bibitem
  "\DTLnewbibitem {EntryType}{mastersthesis}%" write$
  newline$
  format.authors "author" output.check
  new.block
  format.title "title" output.check
  new.block
  "\DTLnewbibitem {Type}{\mscthesisname }"
  format.thesis.type output.nonnull
  format.school "school" output.check
  format.address output
  format.date output
  new.block
  format.key output
  format.note output
  format.extra 
  fin.entry
}

FUNCTION {misc}
{ output.bibitem
  "\DTLnewbibitem {EntryType}{misc}%" write$
  newline$
  format.authors output
  title howpublished new.block.checkb
  format.title output
  %howpublished new.block.checka
  format.howpublished output
  format.date output
  new.block
  format.key output
  format.note output
  format.extra 
  fin.entry
  empty.misc.check
}

FUNCTION {phdthesis}
{ output.bibitem
  "\DTLnewbibitem {EntryType}{phdthesis}%" write$
  newline$
  format.authors "author" output.check
  new.block
  format.btitle "title" output.check
  new.block
  "\DTLnewbibitem {Type}{\phdthesisname }"
  format.thesis.type output.nonnull
  format.school "school" output.check
  format.address output
  format.date output
  new.block
  format.key output
  format.note output
  format.extra 
  fin.entry
}

FUNCTION {proceedings}
{ output.bibitem
  "\DTLnewbibitem {EntryType}{proceedings}%" write$
  newline$
  editor empty$
    { format.organization output }
    { format.editors output.nonnull }
  if$
  new.block
  format.btitle "title" output.check
  format.bvolume output
  format.number.series output
  address empty$
    { editor empty$
{ publisher new.sentence.checka }
{ organization publisher new.sentence.checkb
  format.organization output
}
      if$
      format.publisher output
    }
    { format.address output
      new.sentence
      editor empty$
'skip$
{ format.organization output }
      if$
      format.publisher output
    }
  if$
  format.date output
  new.block
  format.key output
  format.note output
  format.extra 
  fin.entry
}

FUNCTION {techreport}
{ output.bibitem
  "\DTLnewbibitem {EntryType}{techreport}%" write$
  newline$
  format.authors "author" output.check
  new.block
  format.title "title" output.check
  new.block
  format.tr.number output.nonnull
  format.institution "institution" output.check
  format.address output
  format.date output
  new.block
  format.key output
  format.note output
  format.extra 
  fin.entry
}

FUNCTION {unpublished}
{ output.bibitem
  "\DTLnewbibitem {EntryType}{unpublished}%" write$
  newline$
  format.authors "author" output.check
  new.block
  format.title "title" output.check
  new.block
  format.key output
  format.note output
  format.extra 
  fin.entry
}

FUNCTION {default.type} { misc }

MACRO {jan} {"\DTLmonthname{01}"}

MACRO {feb} {"\DTLmonthname{02}"}

MACRO {mar} {"\DTLmonthname{03}"}

MACRO {apr} {"\DTLmonthname{04}"}

MACRO {may} {"\DTLmonthname{05}"}

MACRO {jun} {"\DTLmonthname{06}"}

MACRO {jul} {"\DTLmonthname{07}"}

MACRO {aug} {"\DTLmonthname{08}"}

MACRO {sep} {"\DTLmonthname{09}"}

MACRO {oct} {"\DTLmonthname{10}"}

MACRO {nov} {"\DTLmonthname{11}"}

MACRO {dec} {"\DTLmonthname{12}"}

MACRO {acmcs} {"\DTLacmcs "}

MACRO {acta} {"\DTLacta "}

MACRO {cacm} {"\DTLcacm "}

MACRO {ibmjrd} {"\DTLibmjrd "}

MACRO {ibmsj} {"\DTLibmsj "}

MACRO {ieeese} {"\DTLieeese "}

MACRO {ieeetc} {"\DTLieeetc "}

MACRO {ieeetcad} {"\DTLieeetcad "}

MACRO {ipl} {"\DTLipl "}

MACRO {jacm} {"\DTLjacm "}

MACRO {jcss} {"\DTLjcss "}

MACRO {scp} {"\DTLscp "}

MACRO {sicomp} {"\DTLsicomp "}

MACRO {tocs} {"\DTLtocs "}

MACRO {tods} {"\DTLtods "}

MACRO {tog} {"\DTLtog "}

MACRO {toms} {"\DTLtoms "}

MACRO {toois} {"\DTLtoois "}

MACRO {toplas} {"\DTLtoplas "}

MACRO {tcs} {"\DTLtcs "}

READ

STRINGS { longest.label }

INTEGERS { number.label longest.label.width }

FUNCTION {initialize.longest.label}
{ "" 'longest.label :=
  #1 'number.label :=
  #0 'longest.label.width :=
}

FUNCTION {longest.label.pass}
{ number.label int.to.str$ 'label :=
  number.label #1 + 'number.label :=
  label width$ longest.label.width >
    { label 'longest.label :=
      label width$ 'longest.label.width :=
    }
    'skip$
  if$
}

EXECUTE {initialize.longest.label}

ITERATE {longest.label.pass}

FUNCTION {begin.bib}
{ preamble$ empty$
    'skip$
    { preamble$ write$ newline$ }
  if$
}

EXECUTE {begin.bib}

EXECUTE {init.state.consts}

ITERATE {call.type$}

FUNCTION {end.bib}
{
  "\DTLbibsetlongestlabel{" longest.label  * "}" * write$ newline$
}

EXECUTE {end.bib}
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%</databib.bst>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*databar-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\subsection{Rollback v2.32 (databar-2019-09-27.sty)}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{databar}[2019/09/27 v2.32 (NLCT)]
\RequirePackage{xkeyval}
\RequirePackage{dataplot}[=v2.32]
\newif\ifDTLcolorbarchart
\DTLcolorbarcharttrue
\DeclareOption{color}{\DTLcolorbarcharttrue}
\DeclareOption{gray}{\DTLcolorbarchartfalse}
\newcommand*{\DTLbarXlabelalign}{left,rotate=-90}
\newcommand*{\DTLbarYticklabelalign}{right}
\define@boolkey{databar}[DTL]{verticalbars}[true]{%
\ifDTLverticalbars
 \def\DTLbarXlabelalign{left,rotate=-90}%
 \def\DTLbarYticklabelalign{right}
\else
 \def\DTLbarXlabelalign{right}%
 \def\DTLbarYticklabelalign{center}
\fi}
\DTLverticalbarstrue
\DeclareOption{vertical}{\DTLverticalbarstrue
 \def\DTLbarXlabelalign{left,rotate=-90}%
 \def\DTLbarYticklabelalign{right}
}
\DeclareOption{horizontal}{\DTLverticalbarsfalse
 \def\DTLbarXlabelalign{right}%
 \def\DTLbarYticklabelalign{center}
}
\ProcessOptions
\RequirePackage{datatool}[=v2.32]
\RequirePackage{tikz}
\newlength\DTLbarchartlength
\DTLbarchartlength=3in
\newlength\DTLbarwidth
\DTLbarwidth=1cm
\newlength\DTLbarlabeloffset
\setlength\DTLbarlabeloffset{10pt}
\newcommand*{\DTLBarXAxisStyle}{-}
\newcommand*{\DTLBarYAxisStyle}{-}
\newcounter{DTLbarroundvar}
\setcounter{DTLbarroundvar}{1}
\newcommand*{\DTLbardisplayYticklabel}[1]{#1}
\newcommand*{\DTLdisplaylowerbarlabel}[1]{#1}
\newcommand*{\DTLdisplaylowermultibarlabel}[1]{#1}
\newcommand*{\DTLdisplayupperbarlabel}[1]{#1}
\newcommand*{\DTLdisplayuppermultibarlabel}[1]{#1}
\newcommand*{\DTLbaratbegintikz}{}
\newcommand*{\DTLbaratendtikz}{}
\newif\ifDTLbarxaxis
\newif\ifDTLbaryaxis
\newif\ifDTLbarytics
\newcount\@dtl@barcount
\newcommand*{\DTLsetbarcolor}[2]{%
\expandafter\def\csname dtlbar@segcol\romannumeral#1\endcsname{#2}%
}
\newcommand*{\DTLgetbarcolor}[1]{%
\csname dtlbar@segcol\romannumeral#1\endcsname}
\newcommand*{\DTLdobarcolor}[1]{%
\expandafter\color\expandafter
{\csname dtlbar@segcol\romannumeral#1\endcsname}}
\newcommand*{\DTLdocurrentbarcolor}{%
\ifnum\dtlforeachlevel=0\relax
  \PackageError{databar}{Can't use
  \string\DTLdocurrentbarcolor\space outside
  \string\DTLbarchart}{}%
\else
  \expandafter\DTLdobarcolor\expandafter{%
  \csname c@DTLrow\romannumeral\dtlforeachlevel\endcsname}%
\fi}
\newcommand*{\DTLbaroutlinecolor}{black}
\newlength\DTLbaroutlinewidth
\DTLbaroutlinewidth=0pt
\ifDTLcolorbarchart
 \DTLsetbarcolor{1}{red}
 \DTLsetbarcolor{2}{green}
 \DTLsetbarcolor{3}{blue}
 \DTLsetbarcolor{4}{yellow}
 \DTLsetbarcolor{5}{magenta}
 \DTLsetbarcolor{6}{cyan}
 \DTLsetbarcolor{7}{orange}
 \DTLsetbarcolor{8}{white}
\else
 \DTLsetbarcolor{1}{black!15}
 \DTLsetbarcolor{2}{black!25}
 \DTLsetbarcolor{3}{black!35}
 \DTLsetbarcolor{4}{black!45}
 \DTLsetbarcolor{5}{black!55}
 \DTLsetbarcolor{6}{black!65}
 \DTLsetbarcolor{7}{black!75}
 \DTLsetbarcolor{8}{black!85}
\fi
\newcommand*{\DTLeverybarhook}{}
\define@key{databar}{max}{\def\DTLbarmax{#1}}
\define@key{databar}{length}{\DTLbarchartlength=#1\relax
}
\define@key{databar}{maxdepth}{%
\ifnum#1>0\relax
 \PackageError{databar}{depth must be zero or negative}{}%
\else
 \def\DTLnegextent{#1}%
\fi}
\define@choicekey{databar}{axes}[\var\nr]{both,x,y,none}{%
\ifcase\nr\relax
 % both
  \DTLbarxaxistrue
  \DTLbaryaxistrue
  \DTLbaryticstrue
\or
 % x only
  \DTLbarxaxistrue
  \DTLbaryaxisfalse
  \DTLbaryticsfalse
\or
 % y only
  \DTLbarxaxisfalse
  \DTLbaryaxistrue
  \DTLbaryticstrue
\or
 % neither
  \DTLbarxaxisfalse
  \DTLbaryaxisfalse
  \DTLbaryticsfalse
\fi
}
\define@key{databar}{variable}{%
 \def\DTLbarvariable{#1}%
}
\define@key{databar}{variables}{%
 \def\dtlbar@variables{#1}%
}
\define@key{databar}{barwidth}{\setlength{\DTLbarwidth}{#1}}
\define@key{databar}{barlabel}{%
\def\dtl@barlabel{#1}}
\def\dtl@barlabel{}
\define@key{databar}{multibarlabels}{%
\def\dtl@multibarlabels{#1}}
\def\dtl@multibarlabels{}
\define@key{databar}{groupgap}{\def\dtlbar@groupgap{#1}}
\def\dtlbar@groupgap{1}
\define@key{databar}{upperbarlabel}{%
\def\dtl@upperbarlabel{#1}}
\def\dtl@upperbarlabel{}
\define@key{databar}{uppermultibarlabels}{%
\def\dtl@uppermultibarlabels{#1}}
\def\dtl@uppermultibarlabels{}
\define@key{databar}{yticpoints}{%
\def\dtlbar@yticlist{#1}\DTLbaryticstrue\DTLbaryaxistrue}
\let\dtlbar@yticlist=\relax
\define@key{databar}{yticgap}{%
\def\dtlbar@yticgap{#1}\DTLbaryticstrue\DTLbaryaxistrue}
\let\dtlbar@yticgap=\relax
\define@key{databar}{yticlabels}{%
\def\dtlbar@yticlabels{#1}\DTLbaryticstrue\DTLbaryaxistrue}
\let\dtlbar@yticlabels=\relax
\define@key{databar}{ylabel}{%
\def\dtlbar@ylabel{#1}}
\let\dtlbar@ylabel=\relax
\newcommand*{\DTLbarchart}[4][\boolean{true}]{%
{%
  \undef\DTLbarvariable
  \undef\DTLbarmax
  \undef\DTLnegextent
  \disable@keys{databar}{variables,multibarlabels,%
    uppermultibarlabels,groupgap}%
  \setkeys{databar}{#2}%
  \ifundef{\DTLbarvariable}%
  {%
    \PackageError{databar}%
    {\string\DTLbarchart\space missing variable}%
    {You haven't use the "variable" key}%
  }%
  {%
    \ifundef{\DTLbarmax}%
    {%
      \@sDTLforeach[#1]{#3}{#4}{%
        \expandafter\DTLconverttodecimal\expandafter
          {\DTLbarvariable}{\dtl@barvar}%
        \ifundef{\DTLbarmax}%
        {%
          \let\DTLbarmax=\dtl@barvar
        }%
        {%
          \let\dtl@old=\DTLbarmax
          \dtlmax{\DTLbarmax}{\dtl@old}{\dtl@barvar}%
        }%
      }%
      \ifx\dtlbar@yticgap\relax
      \else
        \let\dtl@thistick=\dtlbar@yticgap
        \whiledo{\DTLisFPopenbetween{\dtl@thistick}{0}{\DTLbarmax}}%
        {%
          \dtladd{\dtl@thistick}{\dtl@thistick}{\dtlbar@yticgap}%
        }%
        \let\DTLbarmax=\dtl@thistick
      \fi
    }%
    {}%
    \ifundef{\DTLnegextent}%
    {%
      \def\DTLnegextent{0}%
      \@sDTLforeach[#1]{#3}{#4}{%
        \expandafter\DTLconverttodecimal\expandafter
          {\DTLbarvariable}{\dtl@barvar}%
        \let\dtl@old=\DTLnegextent
        \DTLmin{\DTLnegextent}{\dtl@old}{\dtl@barvar}%
      }%
      \ifx\dtlbar@yticgap\relax
      \else
        \ifthenelse{\DTLisFPlt{\DTLnegextent}{0}}%
        {%
        \edef\dtl@thistick{0}%
        \whiledo{\DTLisFPclosedbetween{\dtl@thistick}{\DTLnegextent}{0}}{%
          \dtlsub{\dtl@thistick}{\dtl@thistick}{\dtlbar@yticgap}%
        }%
        \let\DTLnegextent=\dtl@thistick
        }{}%
      \fi
    }%
    {}%
    \@dtl@tmpcount=\DTLbarchartlength
    \dtlsub{\dtl@extent}{\DTLbarmax}{\DTLnegextent}%
    \dtldiv{\dtl@unit}{\number\@dtl@tmpcount}{\dtl@extent}%
     \setlength{\dtl@yticlabelwidth}{0pt}%
     \ifDTLbarytics
       \ifx\dtlbar@yticlist\relax
         \ifx\dtlbar@yticgap\relax
           \@dtl@tmpcount=\DTLmintickgap
           \divide\@dtl@tmpcount by 65536\relax
           \dtldiv{\dtl@mingap}{\number\@dtl@tmpcount}{\dtl@unit}%
           \dtl@constructticklist\DTLnegextent\DTLbarmax
             \dtl@mingap\dtlbar@yticlist
         \else
           \dtl@constructticklistwithgapz
             \DTLnegextent\DTLbarmax\dtlbar@yticlist\dtlbar@yticgap
         \fi
       \fi
       \ifx\dtlbar@ylabel\relax
       \else
         \ifx\dtlbar@yticlabels\relax
           \@for\dtl@thislabel:=\dtlbar@yticlist\do{%
             \dtlround{\dtl@thislabel}{\dtl@thislabel}
                     {\c@DTLbarroundvar}%
             \ifDTLverticalbars
               \settowidth{\dtl@tmplength}{%
                  \DTLbardisplayYticklabel{\dtl@thislabel}}%
             \else
               \settoheight{\dtl@tmplength}{%
                  \DTLbardisplayYticklabel{\dtl@thislabel}}%
               \edef\@dtl@h{\the\dtl@tmplength}%
               \settodepth{\dtl@tmplength}{%
                  \DTLbardisplayYticklabel{\dtl@thislabel}}%
               \addtolength{\dtl@tmplength}{\@dtl@h}%
               \addtolength{\dtl@tmplength}{\baselineskip}%
             \fi
             \ifdim\dtl@tmplength>\dtl@yticlabelwidth
               \setlength{\dtl@yticlabelwidth}{\dtl@tmplength}%
             \fi
           }%
         \else
           \@for\dtl@thislabel:=\dtlbar@yticlabels\do{%
             \ifDTLverticalbars
               \settowidth{\dtl@tmplength}{%
                 \DTLbardisplayYticklabel{\dtl@thislabel}}%
             \else
               \settoheight{\dtl@tmplength}{%
                  \DTLbardisplayYticklabel{\dtl@thislabel}}%
               \edef\@dtl@h{\the\dtl@tmplength}%
               \settodepth{\dtl@tmplength}{%
                  \DTLbardisplayYticklabel{\dtl@thislabel}}%
               \addtolength{\dtl@tmplength}{\@dtl@h}%
               \addtolength{\dtl@tmplength}{\baselineskip}%
             \fi
             \ifdim\dtl@tmplength>\dtl@yticlabelwidth
               \setlength{\dtl@yticlabelwidth}{\dtl@tmplength}%
             \fi
           }%
         \fi
       \fi
     \fi
  \edef\DTLbarchartwidth{\expandafter\number\csname dtlrows@#3\endcsname}
  \begin{tikzpicture}
  \ifDTLverticalbars
    \pgfsetyvec{\pgfpoint{0pt}{\dtl@unit sp}}%
    \pgfsetxvec{\pgfpoint{\DTLbarwidth}{0pt}}%
  \else
    \pgfsetxvec{\pgfpoint{\dtl@unit sp}{0pt}}%
    \pgfsetyvec{\pgfpoint{0pt}{\DTLbarwidth}}%
  \fi
  \DTLbaratbegintikz
  \def\@dtl@start{0}%
  \@sDTLforeach[#1]{#3}{#4}{%
  \expandafter\let\expandafter\@dtl@bar
    \csname c@DTLrow\romannumeral\dtlforeachlevel\endcsname%
  \expandafter\DTLconverttodecimal\expandafter
    {\DTLbarvariable}{\dtl@variable}%
  \begin{scope}
   \DTLdocurrentbarcolor
   \ifDTLverticalbars
     \fill (\@dtl@start,0) -- (\@dtl@start,\dtl@variable)
        -- (\@dtl@bar,\dtl@variable) -- (\@dtl@bar,0) -- cycle;
   \else
     \fill (0,\@dtl@start) -- (\dtl@variable,\@dtl@start)
        -- (\dtl@variable,\@dtl@bar) -- (0,\@dtl@bar) -- cycle;
   \fi
  \end{scope}
  \begin{scope}
  \ifdim\DTLbaroutlinewidth>0pt
   \expandafter\color\expandafter{\DTLbaroutlinecolor}
   \ifDTLverticalbars
     \draw (\@dtl@start,0) -- (\@dtl@start,\dtl@variable)
        -- (\@dtl@bar,\dtl@variable) -- (\@dtl@bar,0) -- cycle;
   \else
     \draw (0,\@dtl@start) -- (\dtl@variable,\@dtl@start)
        -- (\dtl@variable,\@dtl@bar) -- (0,\@dtl@bar) -- cycle;
   \fi
  \fi
  \end{scope}
  \ifDTLverticalbars
    \edef\dtl@textopt{%
        at={\noexpand\pgfpointadd
             {\noexpand\pgfpointxy{\@dtl@start.5}{0}}
             {\noexpand\pgfpoint{0pt}{-\noexpand\DTLbarlabeloffset}}},
       \DTLbarXlabelalign
    }%
    \edef\DTLstartpt{\noexpand\pgfpointxy{\@dtl@start.5}{0}}%
  \else
    \edef\dtl@textopt{%
        at={\noexpand\pgfpointadd
             {\noexpand\pgfpointxy{0}{\@dtl@start.5}}
             {\noexpand\pgfpoint{-\noexpand\DTLbarlabeloffset}{0pt}}},
       \DTLbarXlabelalign
    }%
    \edef\DTLstartpt{\noexpand\pgfpointxy{0}{\@dtl@start.5}}%
  \fi
   \expandafter\pgftext\expandafter[\dtl@textopt]{%
     \DTLdisplaylowerbarlabel{\dtl@barlabel}}
  \ifDTLverticalbars
    \expandafter\DTLifnumlt\expandafter{\DTLbarvariable}{0}%
    {
      \edef\dtl@textopt{%
        at={\noexpand\pgfpointadd
             {\noexpand\pgfpointxy{\@dtl@start.5}{\dtl@variable}}
             {\noexpand\pgfpoint{0pt}{-\noexpand\DTLbarlabeloffset}}}
      }%
    }{%
      \edef\dtl@textopt{%
        at={\noexpand\pgfpointadd
             {\noexpand\pgfpointxy{\@dtl@start.5}{\dtl@variable}}
             {\noexpand\pgfpoint{0pt}{\noexpand\DTLbarlabeloffset}}}
      }%
    }
    \edef\DTLendpt{\noexpand\pgfpointxy{\@dtl@start.5}{\dtl@variable}}%
  \else
    \expandafter\DTLifnumlt\expandafter{\DTLbarvariable}{0}%
    {
      \edef\dtl@textopt{right,
        at={\noexpand\pgfpointadd
             {\noexpand\pgfpointxy{\dtl@variable}{\@dtl@start.5}}
             {\noexpand\pgfpoint{-\noexpand\DTLbarlabeloffset}{0pt}}}
      }%
    }{%
      \edef\dtl@textopt{left,
        at={\noexpand\pgfpointadd
             {\noexpand\pgfpointxy{\dtl@variable}{\@dtl@start.5}}
             {\noexpand\pgfpoint{\noexpand\DTLbarlabeloffset}{0pt}}}
      }%
    }
    \edef\DTLendpt{\noexpand\pgfpointxy{\dtl@variable}{\@dtl@start.5}}%
  \fi
   \expandafter\pgftext\expandafter[\dtl@textopt]{%
      \DTLdisplayupperbarlabel{\dtl@upperbarlabel}}
  \def\DTLmidpt{\pgfpointlineattime{0.5}{\DTLstartpt}{\DTLendpt}}%
    \DTLeverybarhook
    \edef\@dtl@start{\number\@dtl@bar}%
  }%
  \ifDTLbarxaxis
    \ifDTLverticalbars
      \expandafter\draw\expandafter[\DTLBarXAxisStyle]
        (0,0) -- (\DTLbarchartwidth,0);
    \else
      \expandafter\draw\expandafter[\DTLBarXAxisStyle]
        (0,0) -- (0,\DTLbarchartwidth);
    \fi
  \fi
  \ifDTLbaryaxis
    \ifDTLverticalbars
      \expandafter\draw\expandafter[\DTLBarYAxisStyle]
        (0,\DTLnegextent) -- (0,\DTLbarmax);
    \else
      \expandafter\draw\expandafter[\DTLBarYAxisStyle]
        (\DTLnegextent,0) -- (\DTLbarmax,0);
    \fi
  \fi
  \ifx\dtlbar@yticlist\relax
  \else
    \@for\dtl@thistick:=\dtlbar@yticlist\do{%
      \ifDTLverticalbars
        \pgfpathmoveto{\pgfpointxy{0}{\dtl@thistick}}
        \pgfpathlineto{
          \pgfpointadd{\pgfpointxy{0}{\dtl@thistick}}
                      {\pgfpoint{-\DTLticklength}{0pt}}}
      \else
        \pgfpathmoveto{\pgfpointxy{\dtl@thistick}{0}}
        \pgfpathlineto{
          \pgfpointadd{\pgfpointxy{\dtl@thistick}{0}}
                      {\pgfpoint{0pt}{-\DTLticklength}}}
      \fi
      \pgfusepath{stroke}
      \ifx\dtlbar@yticlabels\relax
         \dtlround{\dtl@thislabel}{\dtl@thistick}
                 {\c@DTLbarroundvar}%
      \else
         \dtl@chopfirst\dtlbar@yticlabels\dtl@thislabel\dtl@rest
         \let\dtlbar@yticlabels=\dtl@rest
      \fi
      \ifDTLverticalbars
        \edef\dtl@textopt{\DTLbarYticklabelalign,%
          at={\noexpand\pgfpointadd
               {\noexpand\pgfpointxy{0}{\dtl@thistick}}
               {\noexpand\pgfpoint{-\noexpand\DTLticklabeloffset}{0pt}},
          }}%
      \else
        \edef\dtl@textopt{\DTLbarYticklabelalign,
          at={\noexpand\pgfpointadd
               {\noexpand\pgfpointxy{\dtl@thistick}{0}}
               {\noexpand\pgfpoint{0pt}{-\noexpand\DTLticklabeloffset}}
          }}%
      \fi
      \expandafter\pgftext\expandafter[\dtl@textopt]{%
        \DTLbardisplayYticklabel{\dtl@thislabel}}
    }%
  \fi
  \ifx\dtlbar@ylabel\relax
  \else
    \addtolength{\dtl@yticlabelwidth}{\baselineskip}%
    \setlength{\dtl@tmplength}{0.5\DTLbarchartlength}
    \ifDTLverticalbars
      \pgftext[bottom,center,at={\pgfpointadd
          {\pgfpointxy{0}{\DTLnegextent}}%
          {\pgfpoint{-\dtl@yticlabelwidth}{\dtl@tmplength}}},
          rotate=90]{%
         \dtlbar@ylabel}
    \else
      \pgftext[bottom,center,at={\pgfpointadd
          {\pgfpointxy{\DTLnegextent}{0}}%
          {\pgfpoint{\dtl@tmplength}{-\dtl@yticlabelwidth}}}]{%
         \dtlbar@ylabel}
    \fi
  \fi
  \DTLbaratendtikz
  \end{tikzpicture}
  }%
}%
}
\newcommand*{\DTLmultibarchart}[4][\boolean{true}]{%
{\let\dtlbar@variables=\relax
\let\DTLbarmax=\relax
\let\DTLnegextent=\relax
\disable@keys{databar}{variable,upperbarlabel}%
\setkeys{databar}{#2}%
\ifx\dtlbar@variables\relax
  \PackageError{databar}{\string\DTLmultibarchart\space missing variables setting}{}%
\else
  \ifx\DTLbarmax\relax
    \@sDTLforeach[#1]{#3}{#4}{%
      \@for\DTLbarvariable:=\dtlbar@variables\do{%
        \expandafter\DTLconverttodecimal\expandafter
          {\DTLbarvariable}{\dtl@barvar}%
        \ifx\DTLbarmax\relax
          \let\DTLbarmax=\dtl@barvar
        \else
          \let\dtl@old=\DTLbarmax
          \dtlmax{\DTLbarmax}{\dtl@old}{\dtl@barvar}%
        \fi
      }%
    }%
    \ifx\dtlbar@yticgap\relax
    \else
      \let\dtl@thistick=\dtlbar@yticgap%
      \whiledo{\DTLisFPopenbetween{\dtl@thistick}{0}{\DTLbarmax}}{%
        \dtladd{\dtl@thistick}{\dtl@thistick}{\dtlbar@yticgap}%
      }%
      \let\DTLbarmax=\dtl@thistick
    \fi
  \fi
  \ifx\DTLnegextent\relax
    \def\DTLnegextent{0}%
    \@sDTLforeach[#1]{#3}{#4}{%
      \@for\DTLbarvariable:=\dtlbar@variables\do{%
        \expandafter\DTLconverttodecimal\expandafter
          {\DTLbarvariable}{\dtl@barvar}%
        \let\dtl@old=\DTLnegextent
        \DTLmin{\DTLnegextent}{\dtl@old}{\dtl@barvar}%
      }%
    }%
    \ifx\dtlbar@yticgap\relax
    \else
      \ifthenelse{\DTLisFPlt{\DTLnegextent}{0}}{%
      \edef\dtl@thistick{0}%
      \whiledo{\DTLisFPclosedbetween{\dtl@thistick}{\DTLnegextent}{0}}{%
        \dtlsub{\dtl@thistick}{\dtl@thistick}{\dtlbar@yticgap}%
      }%
      \let\DTLnegextent=\dtl@thistick
      }{}%
    \fi
  \fi
  \@dtl@tmpcount=\DTLbarchartlength
  \dtlsub{\dtl@extent}{\DTLbarmax}{\DTLnegextent}%
  \dtldiv{\dtl@unit}{\number\@dtl@tmpcount}{\dtl@extent}%
   \setlength{\dtl@yticlabelwidth}{0pt}%
   \ifDTLbarytics
     \ifx\dtlbar@yticlist\relax
       \ifx\dtlbar@yticgap\relax
         \@dtl@tmpcount=\DTLmintickgap
         \divide\@dtl@tmpcount by 65536\relax
         \dtldiv{\dtl@mingap}{\number\@dtl@tmpcount}{\dtl@unit}%
         \dtl@constructticklist\DTLnegextent\DTLbarmax
           \dtl@mingap\dtlbar@yticlist
       \else
         \dtl@constructticklistwithgapz
           \DTLnegextent\DTLbarmax\dtlbar@yticlist\dtlbar@yticgap
       \fi
     \fi
     \ifx\dtlbar@ylabel\relax
     \else
       \ifx\dtlbar@yticlabels\relax
         \@for\dtl@thislabel:=\dtlbar@yticlist\do{%
           \dtlround{\dtl@thislabel}{\dtl@thislabel}
                   {\c@DTLbarroundvar}%
           \ifDTLverticalbars
             \settowidth{\dtl@tmplength}{%
                \DTLbardisplayYticklabel{\dtl@thislabel}}%
           \else
             \settoheight{\dtl@tmplength}{%
                \DTLbardisplayYticklabel{\dtl@thislabel}}%
             \edef\@dtl@h{\the\dtl@tmplength}%
             \settodepth{\dtl@tmplength}{%
                \DTLbardisplayYticklabel{\dtl@thislabel}}%
             \addtolength{\dtl@tmplength}{\@dtl@h}%
             \addtolength{\dtl@tmplength}{\baselineskip}%
           \fi
           \ifdim\dtl@tmplength>\dtl@yticlabelwidth
             \setlength{\dtl@yticlabelwidth}{\dtl@tmplength}%
           \fi
         }%
       \else
         \@for\dtl@thislabel:=\dtlbar@yticlabels\do{%
           \ifDTLverticalbars
             \settowidth{\dtl@tmplength}{%
               \DTLbardisplayYticklabel{\dtl@thislabel}}%
           \else
             \settoheight{\dtl@tmplength}{%
                \DTLbardisplayYticklabel{\dtl@thislabel}}%
             \edef\@dtl@h{\the\dtl@tmplength}%
             \settodepth{\dtl@tmplength}{%
                \DTLbardisplayYticklabel{\dtl@thislabel}}%
             \addtolength{\dtl@tmplength}{\@dtl@h}%
             \addtolength{\dtl@tmplength}{\baselineskip}%
           \fi
           \ifdim\dtl@tmplength>\dtl@yticlabelwidth
             \setlength{\dtl@yticlabelwidth}{\dtl@tmplength}%
           \fi
         }%
       \fi
     \fi
   \fi
\dtl@xticlabelheight=0pt\relax
\@dtl@tmpcount=0\relax
\@for\dtl@thislabel:=\dtl@multibarlabels\do{%
  \advance\@dtl@tmpcount by 1\relax
  \settoheight{\dtl@tmplength}{\tikz\expandafter\pgftext\expandafter
    [\DTLbarXlabelalign]{\DTLdisplaylowerbarlabel{\dtl@thislabel}};}%
  \edef\@dtl@h{\the\dtl@tmplength}%
  \settodepth{\dtl@tmplength}{\tikz\expandafter\pgftext\expandafter
    [\DTLbarXlabelalign]{\DTLdisplaylowerbarlabel{\dtl@thislabel}};}%
  \addtolength{\dtl@tmplength}{\@dtl@h}%
  \addtolength{\dtl@tmplength}{\baselineskip}%
  \ifdim\dtl@tmplength>\dtl@xticlabelheight
    \setlength{\dtl@xticlabelheight}{\dtl@tmplength}%
  \fi
}
\@dtl@tmpcount=0\relax
\@for\dtl@this:=\dtlbar@variables\do{%
  \advance\@dtl@tmpcount by 1\relax
}%
\edef\DTLbargroupwidth{\number\@dtl@tmpcount}%
\edef\dtl@n{\expandafter\number\csname dtlrows@#3\endcsname}
\dtlmul{\dtl@tmpi}{\dtl@n}{\DTLbargroupwidth}
\dtlsub{\dtl@tmpii}{\dtl@n}{1}%
\dtlmul{\dtl@tmpii}{\dtl@tmpii}{\dtlbar@groupgap}%
\dtladd{\DTLbarchartwidth}{\dtl@tmpi}{\dtl@tmpii}
\begin{tikzpicture}
\ifDTLverticalbars
  \pgfsetyvec{\pgfpoint{0pt}{\dtl@unit sp}}%
  \pgfsetxvec{\pgfpoint{\DTLbarwidth}{0pt}}%
\else
  \pgfsetxvec{\pgfpoint{\dtl@unit sp}{0pt}}%
  \pgfsetyvec{\pgfpoint{0pt}{\DTLbarwidth}}%
\fi
\DTLbaratbegintikz
\def\@dtl@start{0}%
\@sDTLforeach[#1]{#3}{#4}{%
\@dtl@barcount = 1\relax
\let\dtl@multibar@labels=\dtl@multibarlabels
\let\dtl@uppermultibar@labels=\dtl@uppermultibarlabels
\dtlmul{\dtl@multimidpt}{\DTLbargroupwidth}{0.5}%
\dtladd{\dtl@multimidpt}{\dtl@multimidpt}{\@dtl@start}%
\@for\DTLbarvariable:=\dtlbar@variables\do{%
\dtladd{\@dtl@endpt}{\@dtl@start}{1}%
\expandafter\DTLconverttodecimal\expandafter
  {\DTLbarvariable}{\dtl@variable}%
\dtl@chopfirst\dtl@multibar@labels\dtl@thisbarlabel\dtl@rest
\let\dtl@multibar@labels=\dtl@rest
\dtl@chopfirst\dtl@uppermultibar@labels\dtl@thisupperbarlabel\dtl@rest
\let\dtl@uppermultibar@labels=\dtl@rest
\begin{scope}
 \expandafter\color\expandafter{\DTLgetbarcolor{\@dtl@barcount}}%
 \ifDTLverticalbars
   \fill (\@dtl@start,0) -- (\@dtl@start,\dtl@variable)
      -- (\@dtl@endpt,\dtl@variable) -- (\@dtl@endpt,0) -- cycle;
 \else
   \fill (0,\@dtl@start) -- (\dtl@variable,\@dtl@start)
      -- (\dtl@variable,\@dtl@endpt) -- (0,\@dtl@endpt) -- cycle;
 \fi
\end{scope}
\begin{scope}
\ifdim\DTLbaroutlinewidth>0pt
 \expandafter\color\expandafter{\DTLbaroutlinecolor}
 \ifDTLverticalbars
   \draw (\@dtl@start,0) -- (\@dtl@start,\dtl@variable)
      -- (\@dtl@endpt,\dtl@variable) -- (\@dtl@endpt,0) -- cycle;
 \else
   \draw (0,\@dtl@start) -- (\dtl@variable,\@dtl@start)
      -- (\dtl@variable,\@dtl@endpt) -- (0,\@dtl@endpt) -- cycle;
 \fi
\fi
\end{scope}
\dtladd{\@dtl@midpt}{\@dtl@start}{0.5}%
\ifDTLverticalbars
  \edef\dtl@textopt{%
      at={\noexpand\pgfpointadd
           {\noexpand\pgfpointxy{\@dtl@midpt}{0}}
           {\noexpand\pgfpoint{0pt}{-\noexpand\DTLbarlabeloffset}}},
     \DTLbarXlabelalign
  }%
  \edef\DTLstartpt{\noexpand\pgfpointxy{\@dtl@midpt}{0}}%
\else
  \edef\dtl@textopt{%
      at={\noexpand\pgfpointadd
           {\noexpand\pgfpointxy{0}{\@dtl@midpt}}
           {\noexpand\pgfpoint{-\noexpand\DTLbarlabeloffset}{0pt}}},
     \DTLbarXlabelalign
  }%
  \edef\DTLstartpt{\noexpand\pgfpointxy{0}{\@dtl@midpt}}%
\fi
 \expandafter\pgftext\expandafter[\dtl@textopt]{%
   \DTLdisplaylowermultibarlabel{\dtl@thisbarlabel}}
\ifDTLverticalbars
  \expandafter\DTLifnumlt\expandafter{\DTLbarvariable}{0}
  {
    \edef\dtl@textopt{%
      at={\noexpand\pgfpointadd
           {\noexpand\pgfpointxy{\@dtl@midpt}{\dtl@variable}}
           {\noexpand\pgfpoint{0pt}{-\noexpand\DTLbarlabeloffset}}}
    }%
  }{%
    \edef\dtl@textopt{%
      at={\noexpand\pgfpointadd
           {\noexpand\pgfpointxy{\@dtl@midpt}{\dtl@variable}}
           {\noexpand\pgfpoint{0pt}{\noexpand\DTLbarlabeloffset}}}
    }%
  }
  \edef\DTLendpt{\noexpand\pgfpointxy{\@dtl@midpt}{\dtl@variable}}%
\else
  \expandafter\DTLifnumlt\expandafter{\DTLbarvariable}{0}
  {
    \edef\dtl@textopt{right,
      at={\noexpand\pgfpointadd
           {\noexpand\pgfpointxy{\dtl@variable}{\@dtl@midpt}}
           {\noexpand\pgfpoint{-\noexpand\DTLbarlabeloffset}{0pt}}}
    }%
  }{%
    \edef\dtl@textopt{left,
      at={\noexpand\pgfpointadd
           {\noexpand\pgfpointxy{\dtl@variable}{\@dtl@midpt}}
           {\noexpand\pgfpoint{\noexpand\DTLbarlabeloffset}{0pt}}}
    }%
  }
  \edef\DTLendpt{\noexpand\pgfpointxy{\dtl@variable}{\@dtl@midpt}}%
\fi
 \expandafter\pgftext\expandafter[\dtl@textopt]{%
    \DTLdisplayuppermultibarlabel{\dtl@thisupperbarlabel}}
\def\DTLmidpt{\pgfpointlineattime{0.5}{\DTLstartpt}{\DTLendpt}}%
\DTLeverybarhook
  \dtladd{\@dtl@start}{\@dtl@start}{1}%
  \advance\@dtl@barcount by 1\relax
}%
\setlength{\dtl@tmplength}{\DTLbarlabeloffset}%
\addtolength{\dtl@tmplength}{\dtl@xticlabelheight}%
\ifDTLverticalbars
  \edef\dtl@textopt{%
      at={\noexpand\pgfpointadd
           {\noexpand\pgfpointxy{\dtl@multimidpt}{0}}
           {\noexpand\pgfpoint{0pt}{-\noexpand\dtl@tmplength}}},
     \DTLbarXlabelalign
  }%
\else
  \edef\dtl@textopt{%
      at={\noexpand\pgfpointadd
           {\noexpand\pgfpointxy{0}{\dtl@multimidpt}}
           {\noexpand\pgfpoint{-\noexpand\dtl@tmplength}{0pt}}},
     \DTLbarXlabelalign
  }%
\fi
 \expandafter\pgftext\expandafter[\dtl@textopt]{%
   \DTLdisplaylowerbarlabel{\dtl@barlabel}}
  \dtladd{\@dtl@start}{\@dtl@start}{\dtlbar@groupgap}%
}
\ifDTLbarxaxis
  \ifDTLverticalbars
    \expandafter\draw\expandafter[\DTLBarXAxisStyle]
      (0,0) -- (\DTLbarchartwidth,0);
  \else
    \expandafter\draw\expandafter[\DTLBarXAxisStyle]
      (0,0) -- (0,\DTLbarchartwidth);
  \fi
\fi
\ifDTLbaryaxis
  \ifDTLverticalbars
    \expandafter\draw\expandafter[\DTLBarYAxisStyle]
      (0,\DTLnegextent) -- (0,\DTLbarmax);
  \else
    \expandafter\draw\expandafter[\DTLBarYAxisStyle]
      (\DTLnegextent,0) -- (\DTLbarmax,0);
  \fi
\fi
\ifx\dtlbar@yticlist\relax
\else
  \@for\dtl@thistick:=\dtlbar@yticlist\do{%
    \ifDTLverticalbars
      \pgfpathmoveto{\pgfpointxy{0}{\dtl@thistick}}
      \pgfpathlineto{
        \pgfpointadd{\pgfpointxy{0}{\dtl@thistick}}
                    {\pgfpoint{-\DTLticklength}{0pt}}}
    \else
      \pgfpathmoveto{\pgfpointxy{\dtl@thistick}{0}}
      \pgfpathlineto{
        \pgfpointadd{\pgfpointxy{\dtl@thistick}{0}}
                    {\pgfpoint{0pt}{-\DTLticklength}}}
    \fi
    \pgfusepath{stroke}
    \ifx\dtlbar@yticlabels\relax
       \dtlround{\dtl@thislabel}{\dtl@thistick}
               {\c@DTLbarroundvar}%
    \else
       \dtl@chopfirst\dtlbar@yticlabels\dtl@thislabel\dtl@rest
       \let\dtlbar@yticlabels=\dtl@rest
    \fi
    \ifDTLverticalbars
      \edef\dtl@textopt{\DTLbarYticklabelalign,%
        at={\noexpand\pgfpointadd
             {\noexpand\pgfpointxy{0}{\dtl@thistick}}
             {\noexpand\pgfpoint{-\noexpand\DTLticklabeloffset}{0pt}},
        }}%
    \else
      \edef\dtl@textopt{\DTLbarYticklabelalign,
        at={\noexpand\pgfpointadd
             {\noexpand\pgfpointxy{\dtl@thistick}{0}}
             {\noexpand\pgfpoint{0pt}{-\noexpand\DTLticklabeloffset}}
        }}%
    \fi
    \expandafter\pgftext\expandafter[\dtl@textopt]{%
      \DTLbardisplayYticklabel{\dtl@thislabel}}
  }%
\fi
\ifx\dtlbar@ylabel\relax
\else
  \addtolength{\dtl@yticlabelwidth}{\baselineskip}%
  \setlength{\dtl@tmplength}{0.5\DTLbarchartlength}
  \ifDTLverticalbars
    \pgftext[bottom,center,at={\pgfpointadd
        {\pgfpointxy{0}{\DTLnegextent}}%
        {\pgfpoint{-\dtl@yticlabelwidth}{\dtl@tmplength}}},
        rotate=90]{%
       \dtlbar@ylabel}
  \else
    \pgftext[bottom,center,at={\pgfpointadd
        {\pgfpointxy{\DTLnegextent}{0}}%
        {\pgfpoint{\dtl@tmplength}{-\dtl@yticlabelwidth}}}]{%
       \dtlbar@ylabel}
  \fi
\fi
\DTLbaratendtikz
\end{tikzpicture}
\fi
}}
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</databar-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*databib-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\subsection{Rollback v2.32 (databib-2019-09-27.sty)}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{databib}[2019/09/27 v2.32 (NLCT)]
\RequirePackage{datatool}[=v2.32]
\newcommand*{\dtlbib@style}{plain}
\define@choicekey{databib.sty}{style}{plain,abbrv,alpha}{%
\def\dtlbib@style{#1}}
\ProcessOptionsX
\newcommand*{\DTLloadbbl}[3][\jobname.bbl]{%
\bibliographystyle{databib}%
\if@filesw
 \immediate\write\@auxout{\string\bibdata{#3}}%
\fi
\DTLnewdb{#2}%
\edef\DTLBIBdbname{#2}%
\@input@{#1}}
\newcommand*{\DTLnewbibrow}{\@DTLnewrow{\DTLBIBdbname}}
\newcommand*{\DTLnewbibitem}[2]{%
  \@DTLnewdbentry{\DTLBIBdbname}{#1}{#2}}
\providecommand*{\andname}{and}
\providecommand*{\ofname}{of}
\providecommand*{\inname}{in}
\providecommand*{\etalname}{et al.}
\providecommand*{\editorname}{editor}
\providecommand*{\editorsname}{editors}
\providecommand*{\volumename}{volume}
\providecommand*{\numbername}{number}
\providecommand*{\pagesname}{pages}
\providecommand*{\pagename}{page}
\providecommand*{\editionname}{edition}
\providecommand*{\techreportname}{Technical report}
\providecommand*{\mscthesisname}{Master's thesis}
\providecommand*{\phdthesisname}{PhD thesis}
\newcommand*{\DTLbibliography}[2][\boolean{true}]{%
  \begin{DTLthebibliography}[#1]{#2}%
  \DTLforeachbibentry[#1]{#2}{%
    \DTLbibitem \DTLformatbibentry \DTLendbibitem
  }%
  \end{DTLthebibliography}%
}
\newcommand*{\DTLformatbibentry}{%
  \@ifundefined{DTLformat\DBIBentrytype}%
  {%
    \PackageError{databib}{Don't know how to format bibliography
    entries of type `\DBIBentrytype'}{}%
  }%
  {%
    \dtl@message{[\DBIBcitekey]}%
    \DTLstartsentencefalse\DTLmidsentencefalse\DTLperiodfalse
     \csname DTLformat\DBIBentrytype\endcsname
  }%
}
\newcommand*{\gDTLformatbibentry}{%
  \@ifundefined{DTLformat\DBIBentrytype}%
  {%
    \PackageError{databib}{Don't know how to format bibliography
    entries of type `\DBIBentrytype'}{}%
  }%
  {%
    \dtl@message{[\DBIBcitekey]}%
    \global\DTLstartsentencefalse
    \global\DTLmidsentencefalse
    \global\DTLperiodfalse
     \csname DTLformat\DBIBentrytype\endcsname
  }%
}
\newcommand*{\DTLformatthisbibentry}[2]{%
  \edef\DBIBname{#1}%
  \edef\DBIBcitekey{#2}%
  \edtlgetrowforvalue{#1}{\dtlcolumnindex{#1}{CiteKey}}{\DBIBcitekey}%
  \dtl@gathervalues{#1}{\dtlcurrentrow}%
  \letcs{\DBIBentrytype}{@dtl@key@EntryType}%
  \DTLformatbibentry
}
\newcommand*{\DTLendbibitem}{}
\newlength\dtl@widest
\newcommand*{\DTLcomputewidestbibentry}[4]{%
\dtl@widest=0pt\relax
\let#4=\@empty
\DTLforeachbibentry[#1]{#2}{%
\settowidth{\dtl@tmplength}{#3}%
\ifdim\dtl@tmplength>\dtl@widest\relax
 \dtl@widest=\dtl@tmplength
 \protected@edef#4{#3}%
\fi
}%
}
\newcommand*{\DTLforeachbibentry}{%
\@ifstar\@sDTLforeachbibentry\@DTLforeachbibentry}
\newcommand*{\@DTLforeachbibentry}[3][\boolean{true}]{%
  \edef\DBIBname{#2}%
  \setcounter{DTLbibrow}{0}%
  \@DTLforeach{#2}{\DBIBcitekey=CiteKey,\DBIBentrytype=EntryType}%
  {%
    \dtl@gathervalues{#2}{\dtlcurrentrow}%
    \ifthenelse{#1}{\refstepcounter{DTLbibrow}#3}{}%
  }%
}
\newcommand*{\@sDTLforeachbibentry}[3][\boolean{true}]{%
  \edef\DBIBname{#2}%
  \setcounter{DTLbibrow}{0}%
  \@sDTLforeach{#2}{\DBIBcitekey=CiteKey,\DBIBentrytype=EntryType}%
  {%
    \dtl@gathervalues{#2}{\dtlcurrentrow}%
    \ifthenelse{#1}{\refstepcounter{DTLbibrow}#3}{}%
  }%
}
\newcommand{\gDTLforeachbibentry}{%
\@ifstar\@sgDTLforeachbibentry\@gDTLforeachbibentry}
\newcommand*{\@gDTLforeachbibentry}[3][\boolean{true}]{%
  \xdef\DBIBname{#2}%
  \global\c@DTLbibrow = 0\relax
  \@DTLforeach{#2}{\DBIBcitekey=CiteKey,\DBIBentrytype=EntryType}%
  {%
    \dtl@g@gathervalues{#2}{\dtlcurrentrow}%
    \ifthenelse{#1}%
    {%
      \refstepcounter{DTLbibrow}%
      \global\c@DTLbibrow=\c@DTLbibrow
      #3%
    }%
    {}%
  }%
}
\newcommand*{\@sgDTLforeachbibentry}[3][\boolean{true}]{%
  \xdef\DBIBname{#2}%
  \global\c@DTLbibrow = 0\relax
  \@sDTLforeach{#2}{\DBIBcitekey=CiteKey,\DBIBentrytype=EntryType}%
  {%
    \dtl@g@gathervalues{#2}{\dtlcurrentrow}%
    \ifthenelse{#1}%
    {%
      \refstepcounter{DTLbibrow}%
      \global\c@DTLbibrow=\c@DTLbibrow
      #3%
    }%
    {}%
  }%
}
\newcounter{DTLbibrow}
\def\theHDTLbibrow{\theHDTLrow.bib.\arabic{DTLbibrow}}%
\newcommand*{\DTLbibfield}[1]{\csname @dtl@key@#1\endcsname}
\newcommand*{\DTLbibfieldlet}[2]{%
  \letcs{#1}{@dtl@key@#2}%
}
\newcommand*{\DTLifbibfieldexists}[3]{%
\@ifundefined{@dtl@key@#1}{#3}{%
\expandafter\DTLifnull\csname @dtl@key@#1\endcsname
{#3}{#2}}}
\newcommand*{\DTLifanybibfieldexists}[3]{%
\@for\dtl@thisfield:=#1\do{%
\@ifundefined{@dtl@key@\dtl@thisfield}{}{%
\expandafter\DTLifnull\csname @dtl@key@\dtl@thisfield\endcsname
{}{%
\@endfortrue}}}%
\if@endfor
 #2%
\else
 #3%
\fi
\@endforfalse
}
\newif\ifDTLperiod
\newcommand*{\DTLcheckendsperiod}[1]{%
\dtl@checkendsperiod#1\@nil\relax}
\def\dtl@checkendsperiod#1#2{%
\def\@dtl@argi{#1}\def\@dtl@argii{#2}%
\def\@dtl@period{.}%
\ifx\@dtl@argi\@nnil
  \global\DTLperiodfalse
  \let\@dtl@donext=\relax
\else
  \ifx\@dtl@argii\@nnil
    \ifx\@dtl@argi\@dtl@period
      \global\DTLperiodtrue
    \else
      \global\DTLperiodfalse
    \fi
    \let\@dtl@donext=\@gobble
  \else
    \let\@dtl@donext=\dtl@checkendsperiod
  \fi
\fi
\@dtl@donext{#2}%
}
\newcommand*{\DTLcheckbibfieldendsperiod}[1]{%
\protected@edef\@dtl@tmp{\DTLbibfield{#1}}%
\expandafter\DTLcheckendsperiod\expandafter{\@dtl@tmp}}
\newif\ifDTLmidsentence
\newif\ifDTLstartsentence
\newcommand*{\DTLaddperiod}{\DTLmidsentencefalse\DTLperiodfalse
\DTLstartsentencetrue
\ifDTLperiod\else.\fi}
\newcommand*{\DTLaddcomma}{, \DTLmidsentencetrue
\DTLperiodfalse\DTLstartsentencefalse}
\newcommand*{\DTLstartsentencespace}{%
\ifDTLstartsentence\spacefactor=\sfcode`\.\relax\space
\fi\DTLstartsentencefalse}
\newcommand*{\DTLtwoand}{\ \andname\ }
\newcommand*{\DTLandlast}{, \andname\ }
\newcommand*{\DTLandnotlast}{, }
\newcount\@dtl@authorcount
\newcounter{DTLmaxauthors}
\setcounter{DTLmaxauthors}{10}
\newcommand*{\DTLformatauthorlist}{%
\DTLifbibfieldexists{Author}{%
\DTLstartsentencespace
\@dtl@authorcount=0\relax
\@for\@dtl@author:=\@dtl@key@Author\do{%
\advance\@dtl@authorcount by 1\relax}%
\@dtl@tmpcount=0\relax
\ifnum\@dtl@authorcount>\c@DTLmaxauthors
{%
  \@for\@dtl@author:=\@dtl@key@Author\do{%
  \advance\@dtl@tmpcount by 1\relax
  \ifnum\@dtl@tmpcount=1\relax
   \expandafter\DTLformatauthor\@dtl@author
  \else
    \ifnum\@dtl@tmpcount>\c@DTLmaxauthors
      \DTLandnotlast \etalname
      \expandafter\DTLcheckendsperiod\expandafter{\etalname}%
      \@endfortrue
    \else
      \DTLandnotlast \expandafter\DTLformatauthor\@dtl@author
    \fi
  \fi
  }%
}%
\else
  \@for\@dtl@author:=\@dtl@key@Author\do{%
  \advance\@dtl@tmpcount by 1\relax
  \ifnum\@dtl@tmpcount=1\relax
   \expandafter\DTLformatauthor\@dtl@author
  \else
    \ifnum\@dtl@tmpcount=\@dtl@authorcount
      \ifnum\@dtl@authorcount=2\relax
        \DTLtwoand
      \else
        \DTLandlast
      \fi
      \expandafter\DTLformatauthor\@dtl@author
    \else
      \DTLandnotlast \expandafter\DTLformatauthor\@dtl@author
    \fi
  \fi
  }%
\fi
}{}%
}
\newcounter{DTLmaxeditors}
\setcounter{DTLmaxeditors}{10}
\newcommand*{\DTLformateditorlist}{%
\DTLifbibfieldexists{Editor}{%
\DTLstartsentencespace
\@dtl@authorcount=0\relax
\@for\@dtl@author:=\@dtl@key@Editor\do{%
\advance\@dtl@authorcount by 1\relax}%
\@dtl@tmpcount=0\relax
\ifnum\@dtl@authorcount>\c@DTLmaxeditors
{%
  \@for\@dtl@author:=\@dtl@key@Editor\do{%
  \advance\@dtl@tmpcount by 1\relax
  \ifnum\@dtl@tmpcount=1\relax
   \expandafter\DTLformateditor\@dtl@author
  \else
    \ifnum\@dtl@tmpcount>\c@DTLmaxeditors
      \DTLandnotlast \etalname
      \expandafter\DTLcheckendsperiod\expandafter{\etalname}%
      \@endfortrue
    \else
      \DTLandnotlast \expandafter\DTLformateditor\@dtl@author
    \fi
  \fi
  }%
}%
\else
\@for\@dtl@author:=\@dtl@key@Editor\do{%
  \advance\@dtl@tmpcount by 1\relax
  \ifnum\@dtl@tmpcount=1\relax
   \expandafter\DTLformateditor\@dtl@author
  \else
    \ifnum\@dtl@tmpcount=\@dtl@authorcount
      \ifnum\@dtl@authorcount=2\relax
        \DTLtwoand
      \else
        \DTLandlast
      \fi
      \expandafter\DTLformateditor\@dtl@author
    \else
      \DTLandnotlast \expandafter\DTLformateditor\@dtl@author
    \fi
  \fi
  }%
\fi
,
\ifnum\@dtl@authorcount=1\relax
  \editorname
  \expandafter\DTLcheckendsperiod\expandafter{\editorname}%
\else
  \editorsname
  \expandafter\DTLcheckendsperiod\expandafter{\editorsname}%
\fi
}{}%
}
\newcommand*{\DTLformatsurnameonly}[4]{%
\DTLstartsentencespace
\def\@dtl@tmp{#1}%
\ifx\@dtl@tmp\@empty\else#1~\fi
#2%
\def\@dtl@tmp{#3}%
\ifx\@dtl@tmp\@empty
  \DTLcheckendsperiod{#2}%
\else
  , #3%
  \DTLcheckendsperiod{#3}%
\fi
}
\newcommand*{\DTLformatforenames}[1]{%
\DTLstartsentencespace
#1%
\DTLcheckendsperiod{#1}}
\newcommand*{\DTLformatabbrvforenames}[1]{%
\DTLstartsentencespace
\DTLstoreinitials{#1}{\@dtl@tmp}\@dtl@tmp
\expandafter\DTLcheckendsperiod\expandafter{\@dtl@tmp}}
\newcommand*{\DTLformatvon}[1]{%
\DTLstartsentencespace
\def\@dtl@tmp{#1}%
\ifx\@dtl@tmp\@empty
\else
  #1~%
\fi
}
\newcommand*{\DTLformatsurname}[1]{%
\DTLstartsentencespace
#1\DTLcheckendsperiod{#1}}
\newcommand*{\DTLformatjr}[1]{%
\DTLstartsentencespace
\def\@dtl@tmp{#1}%
\ifx\@dtl@tmp\@empty
\else
  , #1\DTLcheckendsperiod{#1}%
\fi
}
\newcommand*{\DTLformatcrossrefeditor}{%
\DTLifbibfieldexists{Editor}{%
\DTLstartsentencespace
\@dtl@authorcount=0\relax
\@for\@dtl@author:=\@dtl@key@Editor\do{%
\advance\@dtl@authorcount by 1\relax}%
{\@dtl@tmpcount=0\relax
\@for\@dtl@author:=\@dtl@key@Editor\do{%
\ifnum\@dtl@authorcount=1\relax
 \expandafter\DTLformatsurnameonly\@dtl@author
\else
 \advance\@dtl@tmpcount by 1\relax
 \ifnum\@dtl@tmpcount=1\relax
    \expandafter\DTLformatsurnameonly\@dtl@author
  \else
    \ifnum\@dtl@authorcount=2\relax
      \ \andname\ \expandafter\DTLformatsurnameonly\@dtl@author
    \else
      \ \etalname
      \expandafter\DTLcheckendsperiod\expandafter{\etalname}
    \fi
    \@endfortrue
  \fi
\fi
}}%
}{}%
}
\newcommand*{\DTLformatvolnumpages}{%
\DTLifbibfieldexists{Volume}{%
\DTLstartsentencespace
\DTLbibfield{Volume}\DTLperiodfalse}{}%
\DTLifbibfieldexists{Number}{%
\DTLstartsentencespace
(\DTLbibfield{Number})\DTLperiodfalse}{}%
\DTLifbibfieldexists{Pages}{%
\DTLifanybibfieldexists{Volume,Number}{:}{%
\DTLstartsentencespace
\protected@edef\@dtl@pages{0\DTLbibfield{Pages}}%
\DTLifnumerical{\@dtl@pages}{\pagename}{\pagesname}~}%
\DTLbibfield{Pages}\DTLperiodfalse}{}%
}
\newcommand*{\DTLformatbvolume}{%
\DTLifbibfieldexists{Volume}{%
\ifDTLmidsentence
  \volumename
\else
  \DTLstartsentencespace
  \expandafter\MakeUppercase\volumename
\fi
~\DTLbibfield{Volume}%
\DTLifbibfieldexists{Series}{\ \ofname\
{\em\DTLbibfield{Series}}\DTLcheckbibfieldendsperiod{Series}}{%
\DTLcheckbibfieldendsperiod{Volume}}%
}{}}
\newcommand*{\DTLformatchapterpages}{%
\DTLifbibfieldexists{Chapter}{%
\DTLifbibfieldexists{Type}{%
\DTLstartsentencespace
\DTLbibfield{Type}}{%
\DTLstartsentencespace
\chaptername}~\DTLbibfield{Chapter}%
\DTLifbibfieldexists{Pages}{\DTLaddcomma}{%
\DTLcheckbibfieldendsperiod{Chapter}}}{}%
\DTLstartsentencespace
\DTLformatpages}
\newcommand*{\DTLformatpages}{%
\DTLifbibfieldexists{Pages}{%
\DTLstartsentencespace
\protected@edef\@dtl@pages{0\DTLbibfield{Pages}}%
\DTLifnumerical{\@dtl@pages}{\pagename}{\pagesname}~%
\DTLbibfield{Pages}\DTLcheckbibfieldendsperiod{Pages}}{}%
}
\newcommand*{\DTLformatnumberseries}{%
\DTLifbibfieldexists{Volume}{}{%
\DTLifbibfieldexists{Number}{%
\ifDTLmidsentence
  \numbername
\else
  \DTLstartsentencespace
  \expandafter\MakeUppercase\numbername
\fi~\DTLbibfield{Number}%
\DTLifbibfieldexists{Series}{\ \inname\ \DTLbibfield{Series}%
\DTLcheckbibfieldendsperiod{Series}}{%
\DTLcheckbibfieldendsperiod{Number}}%
}{%
\DTLifbibfieldexists{Series}{%
\DTLstartsentencespace
\DTLbibfield{Series}%
\DTLcheckbibfieldendsperiod{Series}}{}}%
}%
}
\newcommand*{\DTLformatbookcrossref}{%
\DTLifbibfieldexists{Volume}{%
\ifDTLmidsentence
  \volumename
\else
  \DTLstartsentencespace
  \expandafter\MakeUppercase\volumename
\fi
~\DTLbibfield{Volume}\ \ofname\
}{%
\ifDTLmidsentence
  \inname
\else
  \DTLstartsentencespace
  \expandafter\MakeUppercase\inname
\fi\ }%
\DTLifbibfieldexists{Editor}{\DTLformatcrossrefeditor}{%
\DTLifbibfieldexists{Key}{%
\DTLbibfield{Key}}{%
\DTLifbibfieldexists{Series}{%
{\em\DTLbibfield{Series}}}{}%
}%
}%
~\DTLpcite{\DTLbibfield{CrossRef}}%
}
\newcommand*{\DTLformatincollproccrossref}{%
\DTLifbibfieldexists{Editor}{%
\ifDTLmidsentence
  \inname
\else
  \DTLstartsentencespace
  \expandafter\MakeUppercase\inname
\fi\
\DTLformatcrossrefeditor
}{%
\DTLifbibfieldexists{Key}{%
\ifDTLmidsentence
  \inname
\else
  \DTLstartsentencespace
  \expandafter\MakeUppercase\inname
\fi\ \DTLbibfield{Key}%
}{%
\DTLifbibfieldexists{BookTitle}{%
\ifDTLmidsentence
  \inname
\else
  \DTLstartsentencespace
  \expandafter\MakeUppercase\inname
\fi\ \DTLformatbooktitle{\DTLbibfield{BookTitle}}}{}%
}}%
~\DTLpcite{\DTLbibfield{CrossRef}}%
}
\newcommand*{\DTLformatinedbooktitle}{%
\DTLifbibfieldexists{BookTitle}{%
\ifDTLmidsentence
  \inname
\else
  \DTLstartsentencespace
  \expandafter\MakeUppercase\inname
\fi\
\DTLifbibfieldexists{Editor}{%
\DTLformateditorlist\DTLaddcomma \DTLformatbooktitle{\DTLbibfield{BookTitle}}%
\DTLcheckbibfieldendsperiod{BookTitle}%
}{\DTLformatbooktitle{\DTLbibfield{BookTitle}}%
\DTLcheckbibfieldendsperiod{BookTitle}%
}}{}}
\newcommand*{\DTLformatdate}{%
\DTLifbibfieldexists{Year}{%
\DTLifbibfieldexists{Month}{%
\protected@edef\@dtl@tmp{\DTLbibfield{Month}}%
\ifDTLmidsentence
  \@dtl@tmp
\else
  \DTLstartsentencespace
  \expandafter\MakeUppercase\@dtl@tmp
\fi\
\DTLmidsentencefalse}{}%
\DTLstartsentencespace
\DTLbibfield{Year}}{%
\DTLifbibfieldexists{Month}{%
\protected@edef\@dtl@tmp{\DTLbibfield{Month}}%
\ifDTLmidsentence
  \@dtl@tmp
\else
  \DTLstartsentencespace
  \expandafter\MakeUppercase\@dtl@tmp
\fi
\DTLcheckbibfieldendsperiod{Month}%
}{}}}
\newcommand*{\DTLformatarticlecrossref}{%
\DTLifbibfieldexists{Key}{%
\ifDTLmidsentence
 \inname
\else
 \DTLstartsentencespace
 \expandafter\MakeUppercase\inname
\fi
\ {\em\DTLbibfield{Key}}}{%
\DTLifbibfieldexists{Journal}{%
\ifDTLmidsentence
 \inname
\else
 \DTLstartsentencespace
 \expandafter\MakeUppercase\inname
\fi
\ {\em\DTLbibfield{Journal}}}{}}%
~\DTLpcite{\DTLbibfield{CrossRef}}%
}
\newrobustcmd*{\DTLpcite}[1]{%
  \protected@edef\@dtl@tmp{#1}%
  \cite{\@dtl@tmp}%
}
\newcommand*{\DTLbibfieldexists}[1]{%
\TE@throw\noexpand\dtl@testbibfieldexists{#1}%
\noexpand\if@dtl@condition}
\newcommand*{\dtl@testbibfieldexists}[1]{%
\DTLifbibfieldexists{#1}{\@dtl@conditiontrue}{\@dtl@conditionfalse}}
\newcommand*{\DTLbibfieldiseq}[2]{%
\TE@throw\noexpand\dtl@testbibfieldiseq{#1}{#2}%
\noexpand\if@dtl@condition}
\newcommand*{\dtl@testbibfieldiseq}[2]{%
\DTLifbibfieldexists{#1}{%
\expandafter\let\expandafter\@dtl@tmp\expandafter
 =\csname @dtl@key@#1\endcsname
\expandafter\toks@\expandafter{\@dtl@tmp}%
\@dtl@toks{#2}%
\edef\@dtl@docompare{\noexpand\dtlcompare{\noexpand\@dtl@tmpcount}%
{\the\toks@}{\the\@dtl@toks}}%
\@dtl@docompare
\ifnum\@dtl@tmpcount=0\relax
 \@dtl@conditiontrue
\else
 \@dtl@conditionfalse
\fi
}{%
\@dtl@conditionfalse}%
}
\newcommand*{\DTLbibfieldislt}[2]{%
\TE@throw\noexpand\dtl@testbibfieldislt{#1}{#2}%
\noexpand\if@dtl@condition}
\newcommand*{\dtl@testbibfieldislt}[2]{%
\DTLifbibfieldexists{#1}{%
\expandafter\let\expandafter\@dtl@tmp\expandafter
 =\csname @dtl@key@#1\endcsname
\expandafter\toks@\expandafter{\@dtl@tmp}%
\@dtl@toks{#2}%
\edef\@dtl@docompare{\noexpand\dtlcompare{\noexpand\@dtl@tmpcount}%
{\the\toks@}{\the\@dtl@toks}}%
\@dtl@docompare
\ifnum\@dtl@tmpcount=-1\relax
 \@dtl@conditiontrue
\else
 \@dtl@conditionfalse
\fi
}{%
\@dtl@conditionfalse}%
}
\newcommand*{\DTLbibfieldisle}[2]{%
\TE@throw\noexpand\dtl@testbibfieldisle{#1}{#2}%
\noexpand\if@dtl@condition}
\newcommand*{\dtl@testbibfieldisle}[2]{%
\DTLifbibfieldexists{#1}{%
\expandafter\let\expandafter\@dtl@tmp\expandafter
 =\csname @dtl@key@#1\endcsname
\expandafter\toks@\expandafter{\@dtl@tmp}%
\@dtl@toks{#2}%
\edef\@dtl@docompare{\noexpand\dtlcompare{\noexpand\@dtl@tmpcount}%
{\the\toks@}{\the\@dtl@toks}}%
\@dtl@docompare
\ifnum\@dtl@tmpcount<1\relax
 \@dtl@conditiontrue
\else
 \@dtl@conditionfalse
\fi
}{%
\@dtl@conditionfalse}%
}
\newcommand*{\DTLbibfieldisgt}[2]{%
\TE@throw\noexpand\dtl@testbibfieldisgt{#1}{#2}%
\noexpand\if@dtl@condition}
\newcommand*{\dtl@testbibfieldisgt}[2]{%
\DTLifbibfieldexists{#1}{%
\expandafter\let\expandafter\@dtl@tmp\expandafter
 =\csname @dtl@key@#1\endcsname
\expandafter\toks@\expandafter{\@dtl@tmp}%
\@dtl@toks{#2}%
\edef\@dtl@docompare{\noexpand\dtlcompare{\noexpand\@dtl@tmpcount}%
{\the\toks@}{\the\@dtl@toks}}%
\@dtl@docompare
\ifnum\@dtl@tmpcount=1\relax
 \@dtl@conditiontrue
\else
 \@dtl@conditionfalse
\fi
}{%
\@dtl@conditionfalse}%
}
\newcommand*{\DTLbibfieldisge}[2]{%
\TE@throw\noexpand\dtl@testbibfieldisge{#1}{#2}%
\noexpand\if@dtl@condition}
\newcommand*{\dtl@testbibfieldisge}[2]{%
\DTLifbibfieldexists{#1}{%
\expandafter\let\expandafter\@dtl@tmp\expandafter
 =\csname @dtl@key@#1\endcsname
\expandafter\toks@\expandafter{\@dtl@tmp}%
\@dtl@toks{#2}%
\edef\@dtl@docompare{\noexpand\dtlcompare{\noexpand\@dtl@tmpcount}%
{\the\toks@}{\the\@dtl@toks}}%
\@dtl@docompare
\ifnum\@dtl@tmpcount>-1\relax
 \@dtl@conditiontrue
\else
 \@dtl@conditionfalse
\fi
}{%
\@dtl@conditionfalse}%
}
\newcommand*{\DTLbibfieldcontains}[2]{%
\TE@throw\noexpand\dtl@testbibfieldcontains{#1}{#2}%
\noexpand\if@dtl@condition}
\newcommand*{\dtl@testbibfieldcontains}[2]{%
\DTLifbibfieldexists{#1}{%
\expandafter\let\expandafter\@dtl@tmp\expandafter
  =\csname @dtl@key@#1\endcsname
\expandafter\dtl@testifsubstring\expandafter{\@dtl@tmp}{#2}%
}{\@dtl@conditionfalse}}
\newenvironment{DTLthebibliography}[2][\boolean{true}]{%
\@dtl@tmpcount=0\relax
\@sDTLforeach[#1]{#2}{}{\advance\@dtl@tmpcount by 1\relax}%
\begin{thebibliography}{\number\@dtl@tmpcount}
}{\end{thebibliography}}
\newcommand*{\DTLmonthname}[1]{%
\dtl@monthname{#1}}
\newcommand*{\dtl@monthname}[1]{%
\ifcase#1%
\or January%
\or February%
\or March%
\or April%
\or May%
\or June%
\or July%
\or August%
\or September%
\or October%
\or November%
\or December%
\fi}
\newcommand*{\dtl@abbrvmonthname}[1]{%
\ifcase#1%
\or Jan.%
\or Feb.%
\or Mar.%
\or Apr.%
\or May%
\or June%
\or July%
\or Aug.%
\or Sept.%
\or Oct.%
\or Nov.%
\or Dec.%
\fi}
\newcommand*{\DTLbibitem}{\bibitem{\DBIBcitekey}}
\newcommand*{\DTLmbibitem}[1]{\bibitem{#1@\DBIBcitekey}}
\newcommand*{\DTLcustombibitem}[3]{%
  #1%
  \if@filesw
    \immediate\write\@auxout{\string\bibcite{#3}{#2}}%
  \fi
  \ignorespaces
}
\newcommand*{\DTLformatauthor}[4]{%
\DTLformatforenames{#4}
\DTLformatvon{#1}%
\DTLformatsurname{#2}%
\DTLformatjr{#3}}
\newcommand*{\DTLformateditor}[4]{%
\DTLformatforenames{#4}
\DTLformatvon{#1}%
\DTLformatsurname{#2}%
\DTLformatjr{#3}}
\newcommand*{\DTLformatedition}[1]{#1 \editionname}
\newcommand{\DTLformatarticle}{}
\newcommand{\DTLformatbook}{}
\newcommand{\DTLformatbooklet}{}
\newcommand{\DTLformatinbook}{}
\newcommand{\DTLformatincollection}{}
\newcommand{\DTLformatinproceedings}{}
\newcommand{\DTLformatmanual}{}
\newcommand{\DTLformatmastersthesis}{}
\newcommand{\DTLformatmisc}{}
\newcommand{\DTLformatphdthesis}{}
\newcommand{\DTLformatproceedings}{}
\newcommand{\DTLformattechreport}{}
\newcommand{\DTLformatunpublished}{}
\newcommand*{\DTLacmcs}{ACM Computing Surveys}
\newcommand*{\DTLacta}{Acta Informatica}
\newcommand*{\DTLcacm}{Communications of the ACM}
\newcommand*{\DTLibmjrd}{IBM Journal of Research and Development}
\newcommand*{\DTLibmsj}{IBM Systems Journal}
\newcommand*{\DTLieeese}{IEEE Transactions on Software Engineering}
\newcommand*{\DTLieeetc}{IEEE Transactions on Computers}
\newcommand*{\DTLieeetcad}{IEEE Transactions on Computer-Aided Design
of Integrated Circuits}
\newcommand*{\DTLipl}{Information Processing Letters}
\newcommand*{\DTLjacm}{Journal of the ACM}
\newcommand*{\DTLjcss}{Journal of Computer and System Sciences}
\newcommand*{\DTLscp}{Science of Computer Programming}
\newcommand*{\DTLsicomp}{SIAM Journal on Computing}
\newcommand*{\DTLtocs}{ACM Transactions on Computer Systems}
\newcommand*{\DTLtods}{ACM Transactions on Database Systems}
\newcommand*{\DTLtog}{ACM Transactions on Graphics}
\newcommand*{\DTLtoms}{ACM Transactions on Mathematical Software}
\newcommand*{\DTLtoois}{ACM Transactions on Office Information
Systems}
\newcommand*{\DTLtoplas}{ACM Transactions on Programming Languages
and Systems}
\newcommand*{\DTLtcs}{Theoretical Computer Science}
\newcommand{\dtlbst@plain}{%
  \renewenvironment{DTLthebibliography}[2][\boolean{true}]{%
  \@dtl@tmpcount=0\relax
  \@sDTLforeach[##1]{##2}{}{\advance\@dtl@tmpcount by 1\relax}%
  \begin{thebibliography}{\number\@dtl@tmpcount}%
  }{\end{thebibliography}}%
  \renewcommand*{\DTLbibitem}{\bibitem{\DBIBcitekey}}%
  \renewcommand*{\DTLmbibitem}[1]{\bibitem{##1@\DBIBcitekey}}%
  \renewcommand*{\DTLformatauthor}[4]{%
  \DTLformatforenames{##4}
  \DTLformatvon{##1}%
  \DTLformatsurname{##2}%
  \DTLformatjr{##3}}
  \renewcommand*{\DTLformateditor}[4]{%
  \DTLformatforenames{##4}
  \DTLformatvon{##1}%
  \DTLformatsurname{##2}%
  \DTLformatjr{##3}}
  \renewcommand*{\DTLformatedition}[1]{##1 \editionname}%
  \let\DTLmonthname\dtl@monthname
  \renewcommand*{\DTLacmcs}{ACM Computing Surveys}
  \renewcommand*{\DTLacta}{Acta Informatica}
  \renewcommand*{\DTLcacm}{Communications of the ACM}
  \renewcommand*{\DTLibmjrd}{IBM Journal of Research and Development}
  \renewcommand*{\DTLibmsj}{IBM Systems Journal}
  \renewcommand*{\DTLieeese}{IEEE Transactions on Software Engineering}
  \renewcommand*{\DTLieeetc}{IEEE Transactions on Computers}
  \renewcommand*{\DTLieeetcad}{IEEE Transactions on Computer-Aided Design
of Integrated Circuits}
  \renewcommand*{\DTLipl}{Information Processing Letters}
  \renewcommand*{\DTLjacm}{Journal of the ACM}
  \renewcommand*{\DTLjcss}{Journal of Computer and System Sciences}
  \renewcommand*{\DTLscp}{Science of Computer Programming}
  \renewcommand*{\DTLsicomp}{SIAM Journal on Computing}
  \renewcommand*{\DTLtocs}{ACM Transactions on Computer Systems}
  \renewcommand*{\DTLtods}{ACM Transactions on Database Systems}
  \renewcommand*{\DTLtog}{ACM Transactions on Graphics}
  \renewcommand*{\DTLtoms}{ACM Transactions on Mathematical Software}
  \renewcommand*{\DTLtoois}{ACM Transactions on Office Information
Systems}
  \renewcommand*{\DTLtoplas}{ACM Transactions on Programming Languages
and Systems}
  \renewcommand*{\DTLtcs}{Theoretical Computer Science}
  \renewcommand*{\DTLformatarticle}{%
    \DTLformatauthorlist
    \DTLifbibfieldexists{Author}{\DTLaddperiod}{}%
    \DTLifbibfieldexists{Title}{%
    \DTLstartsentencespace\DTLbibfield{Title}%
    \DTLcheckbibfieldendsperiod{Title}%
    \DTLaddperiod}{}%
    \DTLifbibfieldexists{CrossRef}{%
    \DTLformatarticlecrossref
    \DTLifbibfieldexists{Pages}{\DTLaddcomma}{}%
    \DTLformatpages
    \DTLaddperiod
    }{% no cross ref field
    \DTLifbibfieldexists{Journal}{\DTLstartsentencespace
    {\em\DTLbibfield{Journal}}%
    \DTLcheckbibfieldendsperiod{Journal}%
    \DTLifanybibfieldexists{Number,Volume,Pages,Month,Year}{%
    \DTLaddcomma}{\DTLaddperiod}}{}%
    \DTLformatvolnumpages
    \DTLifanybibfieldexists{Volume,Number,Pages}{%
    \DTLifanybibfieldexists{Year,Month}{\DTLaddcomma}{%
    \DTLaddperiod}%
    \DTLmidsentencefalse}{}%
    \DTLformatdate
    \DTLifanybibfieldexists{Year,Month}{\DTLaddperiod}{}%
    }%
    \DTLifbibfieldexists{Note}{\DTLstartsentencespace\DTLbibfield{Note}%
    \DTLcheckbibfieldendsperiod{Note}%
    \DTLaddperiod}{}%
  }
\renewcommand*{\DTLformatbook}{%
  \DTLifbibfieldexists{Author}%
  {%
    \DTLformatauthorlist\DTLaddperiod
  }%
  {%
    \DTLformateditorlist
    \DTLifbibfieldexists{Editor}%
    {%
      \DTLaddperiod
    }%
    {}%
  }%
  \DTLifbibfieldexists{Title}%
  {%
    \DTLstartsentencespace
    \DTLformatbooktitle{\DTLbibfield{Title}}%
    \DTLcheckbibfieldendsperiod{Title}%
  }%
  {}%
  \DTLifbibfieldexists{CrossRef}%
  {%
    \DTLifbibfieldexists{Title}{\DTLaddperiod}{}%
    \DTLformatbookcrossref
    \DTLifanybibfieldexists{Edition,Month,Year}%
    {\DTLaddcomma}%
    {\DTLaddperiod}%
  }%
  {%
    \DTLifbibfieldexists{Title}%
    {%
      \DTLifbibfieldexists{Volume}{\DTLaddcomma}{\DTLaddperiod}%
    }%
    {}%
    \DTLformatbvolume
    \DTLformatnumberseries
    \DTLifanybibfieldexists{Number,Series,Volume}{\DTLaddperiod}{}%
    \DTLifbibfieldexists{Publisher}%
    {%
      \DTLstartsentencespace
      \DTLbibfield{Publisher}%
      \DTLcheckbibfieldendsperiod{Publisher}%
      \DTLifbibfieldexists{Address}%
      {\DTLaddcomma}%
      {%
        \DTLifanybibfieldexists{Month,Year}%
        {\DTLaddcomma}%
        {\DTLaddperiod}%
      }%
    }%
    {}%
    \DTLifbibfieldexists{Address}%
    {%
      \DTLstartsentencespace
      \DTLbibfield{Address}%
      \DTLcheckbibfieldendsperiod{Address}%
      \DTLifanybibfieldexists{Month,Year}{\DTLaddcomma}{\DTLaddperiod}%
    }%
    {}%
  }%
  \DTLifbibfieldexists{Edition}%
  {%
    \protected@edef\@dtl@tmp{\DTLformatedition{\DTLbibfield{Edition}}}%
    \ifDTLmidsentence
     \@dtl@tmp
    \else
     \DTLstartsentencespace\expandafter\MakeUppercase\@dtl@tmp
    \fi
    \expandafter\DTLcheckendsperiod\expandafter{\@dtl@tmp}%
    \DTLifanybibfieldexists{Month,Year}{\DTLaddcomma}{\DTLaddperiod}%
  }%
  {}%
  \DTLformatdate
  \DTLifanybibfieldexists{Year,Month}{\DTLaddperiod}{}%
  \DTLifbibfieldexists{Note}%
  {%
    \DTLstartsentencespace
    \DTLbibfield{Note}%
    \DTLcheckbibfieldendsperiod{Note}%
    \DTLaddperiod
  }%
  {}%
}%
\renewcommand*{\DTLformatbooklet}{%
\DTLifbibfieldexists{Author}{%
\DTLformatauthorlist\DTLaddperiod}{}%
\DTLifbibfieldexists{Title}{\DTLstartsentencespace
\DTLbibfield{Title}%
\DTLcheckbibfieldendsperiod{Title}%
\DTLaddperiod}{}%
\DTLifbibfieldexists{HowPublished}{%
\DTLstartsentencespace\DTLbibfield{HowPublished}%
\DTLcheckbibfieldendsperiod{HowPublished}%
\DTLifanybibfieldexists{Address,Month,Year}{\DTLaddcomma
}{\DTLaddperiod}}{}%
\DTLifbibfieldexists{Address}{\DTLstartsentencespace
\DTLbibfield{Address}%
\DTLcheckbibfieldendsperiod{Address}%
\DTLifanybibfieldexists{Month,Year}{\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLformatdate
\DTLifanybibfieldexists{Year,Month}{\DTLaddperiod}{}%
\DTLifbibfieldexists{Note}{\DTLstartsentencespace\DTLbibfield{Note}%
\DTLcheckbibfieldendsperiod{Note}%
\DTLaddperiod}{}%
}%
\renewcommand*{\DTLformatinbook}{%
\DTLifbibfieldexists{Author}{%
\DTLformatauthorlist\DTLaddperiod}{%
\DTLifbibfieldexists{Editor}{\DTLformateditorlist\DTLaddperiod}{}}%
\DTLifbibfieldexists{Title}{%
\DTLstartsentencespace
{\em\DTLbibfield{Title}}%
\DTLcheckbibfieldendsperiod{Title}%
}{}%
\DTLifbibfieldexists{CrossRef}{%
\DTLifbibfieldexists{Title}{%
\DTLifbibfieldexists{Chapter}{\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLformatchapterpages
\DTLifanybibfieldexists{Chapter,Pages}{\DTLaddperiod}{}%
\DTLformatbookcrossref
}{% no cross ref
\DTLifbibfieldexists{Title}{%
\DTLifanybibfieldexists{Chapter,Volume}{\DTLaddcomma
}{\DTLaddperiod}}{}%
\DTLformatbvolume
\DTLifanybibfieldexists{Volume,Series}{%
\DTLifanybibfieldexists{Chapter,Pages}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLformatchapterpages
\DTLifanybibfieldexists{Chapter,Pages}{\DTLaddperiod}{}%
\DTLifbibfieldexists{Publisher}{%
\DTLstartsentencespace
\DTLbibfield{Publisher}%
\DTLcheckbibfieldendsperiod{Publisher}%
\DTLifbibfieldexists{Address}{\DTLaddcomma}{}}{}%
\DTLifbibfieldexists{Address}{%
\DTLstartsentencespace
\DTLbibfield{Address}%
\DTLcheckbibfieldendsperiod{Address}}{}%
}%
\DTLifanybibfieldexists{Edition,Month,Year}{\DTLaddcomma
}{\DTLaddperiod}%
\DTLifbibfieldexists{Edition}{%
\protected@edef\@dtl@tmp{\DTLformatedition{\DTLbibfield{Edition}}}%
\ifDTLmidsentence
 \@dtl@tmp
\else
 \DTLstartsentencespace
 \expandafter\MakeUppercase\@dtl@tmp
\fi
\expandafter\DTLcheckendsperiod\expandafter{\@dtl@tmp}%
\DTLifanybibfieldexists{Month,Year}{\DTLaddcomma
}{\DTLaddperiod}%
}{}%
\DTLformatdate
\DTLifanybibfieldexists{Month,Year}{\DTLaddperiod}{}%
\DTLifbibfieldexists{Note}{%
\DTLstartsentencespace
\DTLbibfield{Note}%
\DTLcheckbibfieldendsperiod{Note}%
\DTLaddperiod}{}%
}%
\renewcommand*{\DTLformatincollection}{%
\DTLifbibfieldexists{Author}{\DTLformatauthorlist\DTLaddperiod}{}%
\DTLifbibfieldexists{Title}{%
\DTLstartsentencespace
\DTLbibfield{Title}%
\DTLcheckbibfieldendsperiod{Title}%
\DTLaddperiod}{}%
\DTLifbibfieldexists{CrossRef}{%
\DTLformatincollproccrossref
\DTLifanybibfieldexists{Chapter,Pages}{\DTLaddcomma}{}%
\DTLformatchapterpages\DTLaddperiod
}{% no cross ref entry
\DTLformatinedbooktitle
\DTLifbibfieldexists{BookTitle}{%
\DTLifanybibfieldexists{Volume,Series,Chapter,Pages,Number}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLformatbvolume
\DTLifbibfieldexists{Volume}{%
\DTLifanybibfieldexists{Number,Series,Chapter,Pages}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLformatnumberseries
\DTLifanybibfieldexists{Number,Series}{%
\DTLifanybibfieldexists{Chapter,Pages}{\DTLaddcomma
}{\DTLaddperiod}}{}%
\DTLformatchapterpages
\DTLifanybibfieldexists{Chapter,Pages}{\DTLaddperiod}{}%
\DTLifbibfieldexists{Publisher}{%
\DTLstartsentencespace
\DTLbibfield{Publisher}%
\DTLcheckbibfieldendsperiod{Publisher}%
\DTLifanybibfieldexists{Address,Edition,Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLifbibfieldexists{Address}{%
\DTLstartsentencespace
\DTLbibfield{Address}%
\DTLcheckbibfieldendsperiod{Address}%
\DTLifanybibfieldexists{Edition,Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLifbibfieldexists{Edition}{%
\protected@edef\@dtl@tmp{\DTLformatedition{\DTLbibfield{Edition}}}%
\ifDTLmidsentence
 \@dtl@tmp
\else
 \DTLstartsentencespace
 \expandafter\MakeUppercase\@dtl@tmp
\fi
\expandafter\DTLcheckendsperiod\expandafter{\@dtl@tmp}%
\DTLifanybibfieldexists{Month,Year}{\DTLaddcomma
}{\DTLaddperiod}%
}{}%
\DTLformatdate
\DTLifanybibfieldexists{Month,Year}{\DTLaddperiod}{}%
}%
\DTLifbibfieldexists{Note}{%
\DTLstartsentencespace
\DTLbibfield{Note}%
\DTLcheckbibfieldendsperiod{Note}%
\DTLaddperiod}{}%
}%
\renewcommand*{\DTLformatinproceedings}{%
\DTLifbibfieldexists{Author}{\DTLformatauthorlist
\DTLaddperiod}{}%
\DTLifbibfieldexists{Title}{%
\DTLstartsentencespace
\DTLbibfield{Title}%
\DTLcheckbibfieldendsperiod{Title}%
\DTLaddperiod}{}%
\DTLifbibfieldexists{CrossRef}{%
\DTLformatincollproccrossref
\DTLifbibfieldexists{Pages}{\DTLaddcomma}{%
\DTLaddperiod}%
\DTLformatpages
\DTLifbibfieldexists{Pages}{\DTLaddperiod}{}%
}{% no cross ref
\DTLformatinedbooktitle
\DTLifbibfieldexists{BookTitle}{%
\DTLifanybibfieldexists{Volume,Series,Pages,Number,Address,%
Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLformatbvolume
\DTLifbibfieldexists{Volume}{%
\DTLifanybibfieldexists{Number,Series,Pages,Address,Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLformatnumberseries
\DTLifanybibfieldexists{Number,Series}{%
\DTLifanybibfieldexists{Pages,Address,Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLformatpages
\DTLifbibfieldexists{Pages}{%
\DTLifanybibfieldexists{Address,Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLifbibfieldexists{Address}{%
\DTLstartsentencespace
\DTLbibfield{Address}%
\DTLcheckbibfieldendsperiod{Address}%
\DTLifanybibfieldexists{Month,Year}{\DTLaddcomma}{%
\DTLaddperiod}%
\DTLformatdate
\DTLifanybibfieldexists{Month,Year}{\DTLaddperiod}{}%
\DTLifbibfieldexists{Organization}{%
\DTLstartsentencespace
\DTLbibfield{Organization}%
\DTLcheckbibfieldendsperiod{Organization}%
\DTLifbibfieldexists{Publisher}{\DTLaddcomma}{%
\DTLaddperiod}}{}%
\DTLifbibfieldexists{Publisher}{%
\DTLstartsentencespace
\DTLbibfield{Publisher}%
\DTLcheckbibfieldendsperiod{Publisher}%
\DTLaddperiod}{}%
}{%
\DTLifanybibfieldexists{Publisher,Organization}{%
\DTLaddperiod}{}%
\DTLifbibfieldexists{Organization}{%
\DTLstartsentencespace
\DTLbibfield{Organization}%
\DTLcheckbibfieldendsperiod{Organization}%
\DTLifanybibfieldexists{Publisher,Month,Year}{%
\DTLaddcomma}{}}{}%
\DTLifbibfieldexists{Publisher}{%
\DTLstartsentencespace
\DTLbibfield{Publisher}%
\DTLcheckbibfieldendsperiod{Publisher}%
\DTLifanybibfieldexists{Month,Year}{\DTLaddcomma}{%
\DTLaddperiod}}{}%
\DTLformatdate
\DTLifanybibfieldexists{Month,Year}{\DTLaddperiod}{}%
}%
}%
\DTLifbibfieldexists{Note}{%
\DTLstartsentencespace
\DTLbibfield{Note}%
\DTLcheckbibfieldendsperiod{Note}%
\DTLaddperiod}{}%
}%
\renewcommand*{\DTLformatmanual}{%
\DTLifbibfieldexists{Author}{\DTLformatauthorlist
\DTLaddperiod}{%
\DTLifbibfieldexists{Organization}{%
\DTLstartsentencespace
\DTLbibfield{Organization}%
\DTLcheckbibfieldendsperiod{Organization}%
\DTLifbibfieldexists{Address}{\DTLaddcomma \DTLbibfield{Address}%
\DTLcheckbibfieldendsperiod{Address}%
}{}%
\DTLaddperiod}{}%
}%
\DTLifbibfieldexists{Title}{%
\DTLstartsentencespace
{\em\DTLbibfield{Title}}%
\DTLcheckbibfieldendsperiod{Title}%
\DTLifbibfieldexists{Author}{%
\DTLifanybibfieldexists{Organization,Address}{%
\DTLaddperiod}{\DTLaddcomma}}{%
\DTLifanybibfieldexists{Organization,Address,Edition,Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}}{}%
\DTLifbibfieldexists{Author}{%
\DTLifbibfieldexists{Organization}{%
\DTLstartsentencespace
\DTLbibfield{Organization}%
\DTLcheckbibfieldendsperiod{Organization}%
\DTLifanybibfieldexists{Address,Edition,Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLifbibfieldexists{Address}{%
\DTLstartsentencespace
\DTLbibfield{Address}%
\DTLcheckbibfieldendsperiod{Address}%
\DTLifanybibfieldexists{Edition,Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
}{%
\DTLifbibfieldexists{Organization}{}{%
\DTLifbibfieldexists{Address}{%
\DTLstartsentencespace
\DTLbibfield{Address}%
\DTLcheckbibfieldendsperiod{Address}%
\DTLifanybibfieldexists{Edition,Month,Year}{\DTLaddcomma}{%
\DTLaddperiod}}{}}%
}%
\DTLifbibfieldexists{Edition}{%
\protected@edef\@dtl@tmp{\DTLformatedition{\DTLbibfield{Edition}}}%
\ifDTLmidsentence
 \@dtl@tmp
\else
 \DTLstartsentencespace
 \expandafter\MakeUppercase\@dtl@tmp
\fi
\expandafter\DTLcheckendsperiod\expandafter{\@dtl@tmp}%
\DTLifanybibfieldexists{Month,Year}{\DTLaddcomma}{%
\DTLaddperiod}}{}%
\DTLformatdate
\DTLifanybibfieldexists{Month,Year}{\DTLaddperiod}{}%
\DTLifbibfieldexists{Note}{%
\DTLstartsentencespace
\DTLbibfield{Note}%
\DTLcheckbibfieldendsperiod{Note}%
\DTLaddperiod}{}%
}%
\renewcommand*{\DTLformatmastersthesis}{%
\DTLifbibfieldexists{Author}{\DTLformatauthorlist\DTLaddperiod}{}%
\DTLifbibfieldexists{Title}{%
\DTLstartsentencespace
\DTLbibfield{Title}%
\DTLcheckbibfieldendsperiod{Title}%
\DTLaddperiod}{}%
\DTLifbibfieldexists{Type}{%
\DTLstartsentencespace
\DTLbibfield{Type}%
\DTLcheckbibfieldendsperiod{Type}%
\DTLifanybibfieldexists{School,Address,Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLifbibfieldexists{School}{%
\DTLstartsentencespace
\DTLbibfield{School}%
\DTLcheckbibfieldendsperiod{School}%
\DTLifanybibfieldexists{Address,Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLifbibfieldexists{Address}{%
\DTLstartsentencespace
\DTLbibfield{Address}%
\DTLcheckbibfieldendsperiod{Address}%
\DTLifanybibfieldexists{Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLformatdate
\DTLifanybibfieldexists{Month,Year}{\DTLaddperiod}{}%
\DTLifbibfieldexists{Note}{%
\DTLstartsentencespace
\DTLbibfield{Note}%
\DTLcheckbibfieldendsperiod{Note}%
\DTLaddperiod}{}%
}%
\renewcommand*{\DTLformatmisc}{%
\DTLifbibfieldexists{Author}{\DTLformatauthorlist\DTLaddperiod}{}%
\DTLifbibfieldexists{Title}{%
\DTLstartsentencespace
\DTLbibfield{Title}%
\DTLcheckbibfieldendsperiod{Title}%
\DTLifbibfieldexists{HowPublished}{\DTLaddperiod}{%
\DTLifanybibfieldexists{Month,Year}{\DTLaddcomma}{%
\DTLaddperiod}%
}%
\DTLmidsentencefalse}{}%
\DTLifbibfieldexists{HowPublished}{%
\DTLstartsentencespace
\DTLbibfield{HowPublished}%
\DTLcheckbibfieldendsperiod{HowPublished}%
\DTLifanybibfieldexists{Month,Year}{\DTLaddcomma}{%
\DTLaddperiod}}{}%
\DTLformatdate
\DTLifanybibfieldexists{Month,Year}{\DTLaddperiod}{}%
\DTLifbibfieldexists{Note}{%
\DTLstartsentencespace
\DTLbibfield{Note}%
\DTLcheckbibfieldendsperiod{Note}%
\DTLaddperiod}{}%
}%
\renewcommand*{\DTLformatphdthesis}{%
\DTLifbibfieldexists{Author}{\DTLformatauthorlist\DTLaddperiod}{}%
\DTLifbibfieldexists{Title}{%
\DTLstartsentencespace
{\em\DTLbibfield{Title}}%
\DTLcheckbibfieldendsperiod{Title}%
\DTLaddperiod}{}%
\DTLifbibfieldexists{Type}{%
\DTLstartsentencespace
\DTLbibfield{Type}%
\DTLcheckbibfieldendsperiod{Type}%
\DTLifanybibfieldexists{School,Address,Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLifbibfieldexists{School}{%
\DTLstartsentencespace
\DTLbibfield{School}%
\DTLcheckbibfieldendsperiod{School}%
\DTLifanybibfieldexists{Address,Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLifbibfieldexists{Address}{%
\DTLstartsentencespace
\DTLbibfield{Address}%
\DTLcheckbibfieldendsperiod{Address}%
\DTLifanybibfieldexists{Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLformatdate
\DTLifanybibfieldexists{Month,Year}{\DTLaddperiod}{}%
\DTLifbibfieldexists{Note}{%
\DTLstartsentencespace
\DTLbibfield{Note}%
\DTLcheckbibfieldendsperiod{Note}%
\DTLaddperiod}{}%
}%
\renewcommand*{\DTLformatproceedings}{%
\DTLifbibfieldexists{Editor}{%
\DTLformateditorlist\DTLaddperiod}{%
\DTLifbibfieldexists{Organization}{%
\DTLstartsentencespace
\DTLbibfield{Organization}%
\DTLcheckbibfieldendsperiod{Organization}%
\DTLaddperiod}{}}%
\DTLifbibfieldexists{Title}{%
\DTLstartsentencespace
{\em\DTLbibfield{Title}}%
\DTLcheckbibfieldendsperiod{Title}%
\DTLifanybibfieldexists{Volume,Number,Address,Editor,Publisher,%
Month,Year}{\DTLaddcomma}{\DTLaddperiod}%
}{}%
\DTLformatbvolume
\DTLifbibfieldexists{Volume}{%
\DTLifanybibfieldexists{Number,Address,Editor,Publisher,%
Month,Year}{\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLformatnumberseries
\DTLifbibfieldexists{Number}{%
\DTLifanybibfieldexists{Address,Editor,Publisher,%
Month,Year}{\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLifbibfieldexists{Address}{%
\DTLstartsentencespace
\DTLbibfield{Address}%
\DTLcheckbibfieldendsperiod{Address}%
\DTLifanybibfieldexists{Month,Year}{\DTLaddcomma}{\DTLaddperiod}%
\DTLformatdate
\DTLifanybibfieldexists{Month,Year}{\DTLaddperiod}{}%
\DTLifbibfieldexists{Editor}{\DTLifbibfieldexists{Organization}{%
\DTLstartsentencespace
\DTLbibfield{Organization}%
\DTLcheckbibfieldendsperiod{Organization}%
\DTLifbibfieldexists{Publisher}{%
\DTLaddcomma}{\DTLaddperiod}}{}}{}%
\DTLifbibfieldexists{Publisher}{%
\DTLstartsentencespace
\DTLbibfield{Publisher}%
\DTLcheckbibfieldendsperiod{Publisher}%
\DTLaddperiod
}{}%
}{% no address
\DTLifbibfieldexists{Editor}{%
\DTLifbibfieldexists{Organization}{%
\DTLstartsentencespace
\DTLbibfield{Organization}%
\DTLcheckbibfieldendsperiod{Organization}%
\DTLifanybibfieldexists{Publisher,Month,Year}{%
\DTLaddcomma}{\DTLaddperiod}}{}%
}{}%
\DTLifbibfieldexists{Publisher}{%
\DTLstartsentencespace
\DTLbibfield{Publisher}%
\DTLcheckbibfieldendsperiod{Publisher}%
\DTLifanybibfieldexists{Month,Year}{\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLformatdate
\DTLifanybibfieldexists{Month,Year}{\DTLaddperiod}{}%
}%
\DTLifbibfieldexists{Note}{%
\DTLstartsentencespace
\DTLbibfield{Note}%
\DTLcheckbibfieldendsperiod{Note}%
\DTLaddperiod}{}%
}%
\renewcommand*{\DTLformattechreport}{%
\DTLifbibfieldexists{Author}{\DTLformatauthorlist\DTLaddperiod}{}%
\DTLifbibfieldexists{Title}{%
\DTLstartsentencespace
\DTLbibfield{Title}%
\DTLcheckbibfieldendsperiod{Title}%
\DTLaddperiod}{}%
\DTLifbibfieldexists{Type}{%
\DTLstartsentencespace
\DTLbibfield{Type}%
\DTLcheckbibfieldendsperiod{Type}%
\DTLifbibfieldexists{Number}{~}{}}{}%
\DTLifbibfieldexists{Number}{%
\DTLstartsentencespace
\DTLbibfield{Number}%
\DTLcheckbibfieldendsperiod{Number}%
}{}%
\DTLifanybibfieldexists{Type,Number}{%
\DTLifanybibfieldexists{Institution,Address,Month,Year}{\DTLaddcomma
}{\DTLaddperiod}}{}%
\DTLifbibfieldexists{Institution}{%
\DTLstartsentencespace
\DTLbibfield{Institution}%
\DTLcheckbibfieldendsperiod{Institution}%
\DTLifanybibfieldexists{Address,Month,Year}{\DTLaddcomma
}{\DTLaddperiod}}{}%
\DTLifbibfieldexists{Address}{%
\DTLstartsentencespace
\DTLbibfield{Address}%
\DTLcheckbibfieldendsperiod{Address}%
\DTLifanybibfieldexists{Month,Year}{\DTLaddcomma
}{\DTLaddperiod}}{}%
\DTLformatdate
\DTLifanybibfieldexists{Month,Year}{\DTLaddperiod}{}%
\DTLifbibfieldexists{Note}{%
\DTLstartsentencespace
\DTLbibfield{Note}%
\DTLcheckbibfieldendsperiod{Note}%
\DTLaddperiod}{}%
}%
\renewcommand*{\DTLformatunpublished}{%
\DTLifbibfieldexists{Author}{\DTLformatauthorlist\DTLaddperiod}{}%
\DTLifbibfieldexists{Title}{%
\DTLstartsentencespace
\DTLbibfield{Title}%
\DTLcheckbibfieldendsperiod{Title}%
\DTLaddperiod}{}%
\DTLifbibfieldexists{Note}{%
\DTLstartsentencespace
\DTLbibfield{Note}%
\DTLcheckbibfieldendsperiod{Note}%
\DTLifanybibfieldexists{Month,Year}{\DTLaddcomma}{\DTLaddperiod}}{}%
\DTLformatdate
\DTLifanybibfieldexists{Month,Year}{\DTLaddperiod}{}%
}%
}
\newcommand*{\DTLformatbooktitle}[1]{\emph{#1}}
\newcommand{\dtlbst@abbrv}{%
\dtlbst@plain
\renewcommand*{\DTLformatauthor}[4]{%
\DTLformatabbrvforenames{##4}
\DTLformatvon{##1}%
\DTLformatsurname{##2}%
\DTLformatjr{##3}}
\renewcommand*{\DTLformateditor}[4]{%
\DTLformatabbrvforenames{##4}
\DTLformatvon{##1}%
\DTLformatsurname{##2}%
\DTLformatjr{##3}}
\let\DTLmonthname\dtl@abbrvmonthname
\renewcommand*{\DTLacmcs}{ACM Comput.\ Surv.}
\renewcommand*{\DTLacta}{Acta Inf.}
\renewcommand*{\DTLcacm}{Commun.\ ACM}
\renewcommand*{\DTLibmjrd}{IBM J.\ Res.\ Dev.}
\renewcommand*{\DTLibmsj}{IBM Syst.~J.}
\renewcommand*{\DTLieeese}{IEEE Trans. Softw.\ Eng.}
\renewcommand*{\DTLieeetc}{IEEE Trans.\ Comput.}
\renewcommand*{\DTLieeetcad}{IEEE Trans.\ Comput.-Aided Design
Integrated Circuits}
\renewcommand*{\DTLipl}{Inf.\ Process.\ Lett.}
\renewcommand*{\DTLjacm}{J.~ACM}
\renewcommand*{\DTLjcss}{J.~Comput.\ Syst.\ Sci.}
\renewcommand*{\DTLscp}{Sci.\ Comput.\ Programming}
\renewcommand*{\DTLsicomp}{SIAM J.~Comput.}
\renewcommand*{\DTLtocs}{ACM Trans.\ Comput.\ Syst.}
\renewcommand*{\DTLtods}{ACM Trans.\ Database Syst.}
\renewcommand*{\DTLtog}{ACM Trans.\ Gr.}
\renewcommand*{\DTLtoms}{ACM Trans.\ Math. Softw.}
\renewcommand*{\DTLtoois}{ACM Trans. Office Inf.\ Syst.}
\renewcommand*{\DTLtoplas}{ACM Trans.\ Prog. Lang.\ Syst.}
\renewcommand*{\DTLtcs}{Theoretical Comput.\ Sci.}
}
\newcommand{\dtlbst@alpha}{%
\dtlbst@plain
\renewenvironment{DTLthebibliography}[2][\boolean{true}]{%
\dtl@createalphabiblabels{##1}{##2}%
\begin{thebibliography}{\@dtl@widestlabel}%
}{\end{thebibliography}}%
\renewcommand*{\DTLbibitem}{%
\expandafter\bibitem\expandafter
 [\csname dtl@biblabel@\DBIBcitekey\endcsname]{\DBIBcitekey}}%
\renewcommand*{\DTLmbibitem}[1]{%
\expandafter\bibitem\expandafter
 [\csname dtl@biblabel@\DBIBcitekey\endcsname]{##1@\DBIBcitekey}}%
}
\newcommand*{\dtl@createalphabiblabels}[2]{%
\dtl@message{Creating bib labels}%
\begingroup
\gdef\@dtl@widestlabel{}%
\dtl@widest=0pt\relax
\DTLforeachbibentry[#1]{#2}{%
\dtl@message{\DBIBcitekey}%
\DTLifbibfieldexists{Author}{%
  \dtl@listgetalphalabel{\@dtl@thislabel}{\@dtl@key@Author}%
}{%
\DTLifbibfieldexists{Editor}{%
    \dtl@listgetalphalabel{\@dtl@thislabel}{\@dtl@key@Editor}%
 }{%
   \DTLifbibfieldexists{Key}{%
     \expandafter\dtl@get@firstthree\expandafter
       {\@dtl@key@Key}{\@dtl@thislabel}%
   }{%
     \DTLifbibfieldexists{Organization}{%
       \expandafter\dtl@get@firstthree\expandafter
         {\@dtl@key@Organization}{\@dtl@thislabel}%
   }{%
       \expandafter\dtl@get@firstthree\expandafter
         {\DBIBentrytype}{\@dtl@thislabel}%
   }%
   }}}%
\DTLifbibfieldexists{Year}{}{\DTLifbibfieldexists{CrossRef}{%
\DTLgetvalueforkey{\@dtl@key@Year}{Year}{#2}{CiteKey}{%
\@dtl@key@CrossRef}}{}}%
\DTLifbibfieldexists{Year}{%
\expandafter\dtl@get@yearsuffix\expandafter{\@dtl@key@Year}%
\expandafter\toks@\expandafter{\@dtl@thislabel}%
\expandafter\@dtl@toks\expandafter{\@dtl@year}%
\edef\@dtl@thislabel{\the\toks@\the\@dtl@toks}%
}{}%
\let\@dtl@s@thislabel=\@dtl@thislabel
\@onelevel@sanitize\@dtl@s@thislabel
\@ifundefined{c@biblabel@\@dtl@s@thislabel}{%
\newcounter{biblabel@\@dtl@s@thislabel}%
\setcounter{biblabel@\@dtl@s@thislabel}{1}%
\expandafter\edef\csname @dtl@bibfirst@\@dtl@s@thislabel\endcsname{%
\DBIBcitekey}%
\expandafter\global
\expandafter\let\csname dtl@biblabel@\DBIBcitekey\endcsname=
  \@dtl@thislabel
}{%
\expandafter\ifnum\csname c@biblabel@\@dtl@s@thislabel\endcsname=1\relax
 \expandafter\let\expandafter\@dtl@tmp
   \csname @dtl@bibfirst@\@dtl@s@thislabel\endcsname
 \expandafter\protected@xdef\csname dtl@biblabel@\@dtl@tmp\endcsname{%
   \@dtl@thislabel a}%
\fi
\stepcounter{biblabel@\@dtl@s@thislabel}%
\expandafter\protected@xdef\csname dtl@biblabel@\DBIBcitekey\endcsname{%
   \@dtl@thislabel\alph{biblabel@\@dtl@s@thislabel}}%
}%
\settowidth{\dtl@tmplength}{%
 \csname dtl@biblabel@\DBIBcitekey\endcsname}%
\ifdim\dtl@tmplength>\dtl@widest
 \dtl@widest=\dtl@tmplength
 \expandafter\global\expandafter\let\expandafter\@dtl@widestlabel
  \expandafter=\csname dtl@biblabel@\DBIBcitekey\endcsname
\fi
}%
\endgroup
}
\newcommand*{\dtl@listgetalphalabel}[2]{%
\@dtl@authorcount=0\relax
\@for\@dtl@author:=#2\do{%
\advance\@dtl@authorcount by 1\relax}%
\ifnum\@dtl@authorcount=1\relax
 \expandafter\dtl@getsinglealphalabel#2{#1}\relax
\else
  {%
  \xdef#1{}%
  \@dtl@tmpcount=0\relax
   \def\DTLafterinitials{}\def\DTLbetweeninitials{}%
   \def\DTLafterinitialbeforehyphen{}\def\DTLinitialhyphen{}%
   \@for\@dtl@author:=#2\do{%
     \expandafter\dtl@getauthorinitial\@dtl@author
     \expandafter\toks@\expandafter{\@dtl@tmp}%
     \expandafter\@dtl@toks\expandafter{#1}%
     \xdef#1{\the\@dtl@toks\the\toks@}%
     \advance\@dtl@tmpcount by 1\relax
     \ifnum\@dtl@tmpcount>2\relax\@endfortrue\fi
  }}%
\fi
}
\newcommand*{\dtl@getauthorinitial}[4]{%
\def\@dtl@vonpart{#1}%
\ifx\@dtl@vonpart\@empty
 \DTLstoreinitials{#2}{\@dtl@tmp}%
\else
 \DTLstoreinitials{#1 #2}{\@dtl@tmp}%
\fi}
\newcommand*{\dtl@getsinglealphalabel}[5]{%
\def\@dtl@vonpart{#1}%
\ifx\@dtl@vonpart\@empty
 \DTLifSubString{#2}{-}{%
   {\def\DTLafterinitials{}\def\DTLbetweeninitials{}%
    \def\DTLafterinitialbeforehyphen{}%
    \def\DTLinitialhyphen{}%
    \DTLstoreinitials{#2}{\@dtl@tmp}\global\let#5=\@dtl@tmp}%
   }{%
   \dtl@getfirstthree{#5}#2{}{}{}{}\@nil
 }
\else
 {\def\DTLafterinitials{}\def\DTLbetweeninitials{}%
  \def\DTLafterinitialbeforehyphen{}%
  \def\DTLinitialhyphen{}%
  \DTLstoreinitials{#1 #2}{\@dtl@tmp}\global\let#5=\@dtl@tmp}%
\fi
}
\def\dtl@getfirstthree#1#2#3#4#5\@nil{%
  \def#1{#2#3#4}%
}
\newcommand*{\dtl@get@firstthree}[2]{%
\dtl@getfirstthree#2#1{}{}{}{}{}\@nil}
\newcommand*{\dtl@get@yearsuffix}[1]{%
\dtl@getyearsuffix#1\@nil\relax\relax}

\def\dtl@getyearsuffix#1#2#3{%
\def\@dtl@argi{#1}\def\@dtl@argii{#2}%
\def\@dtl@argiii{#3}%
\ifx\@dtl@argi\@nnil
 \def\@dtl@year{}%
 \let\@dtl@donext=\relax
\else
  \ifx\@dtl@argii\@nnil
    \dtl@ifsingle{#1}{%
      \def\@dtl@year{#1}%
      \let\@dtl@donext=\relax
    }{%
      \def\@dtl@donext{\dtl@getyearsuffix#1#2#3}%
    }%
  \else
    \ifx\@dtl@argiii\@nnil
      \dtl@ifsingle{#1}{%
        \dtl@ifsingle{#2}{%
          \def\@dtl@year{#1#2}%
          \let\@dtl@donext=\relax
        }{%
          \def\@dtl@donext{\dtl@getyearsuffix#2#3}%
        }%
      }{%
        \def\@dtl@donext{\dtl@getyearsuffix#2#3}%
      }%
    \else
      \def\@dtl@donext{\dtl@getyearsuffix{#2}{#3}}%
    \fi
  \fi
\fi
\@dtl@donext
}
\newcommand*{\DTLbibliographystyle}[1]{%
\@ifundefined{dtlbst@#1}{\PackageError{databib}{Unknown
bibliography style `#1'}{}}{\csname dtlbst@#1\endcsname}}
\DTLbibliographystyle{\dtlbib@style}
\newcommand*{\DTLmultibibs}[1]{%
\@for\@dtl@af:=#1\do{%
\@ifundefined{dtl@aux@\@dtl@af}{%
\expandafter\newwrite\csname dtl@aux@\@dtl@af\endcsname
\expandafter\immediate
\expandafter\openout\csname dtl@aux@\@dtl@af\endcsname=\@dtl@af.aux
\expandafter\def\csname b@\@dtl@af @*\endcsname{}%
}{%
\PackageError{databib}{Can't create auxiliary file `\@dtl@af.aux',
\expandafter\string\csname dtl@aux@\@dtl@af\endcsname\space
already exists}{}}}}
\@onlypreamble{\DTLmultibibs}
\newcommand*{\DTLcite}{\@ifnextchar[{\@tempswatrue \dtl@citex
}{\@tempswafalse \dtl@citex[]}}
\def\dtl@citex[#1]#2#3{%
\leavevmode\let\@citea\@empty
\@cite{\@for\@citeb:=#3\do{\@citea
  \def\@citea{,\penalty \@m \ }%
  \edef\@citeb{\expandafter\@firstofone\@citeb\@empty}%
  \if@filesw
    \@ifundefined{dtl@aux@#2}{%
      \PackageError{databib}{multibib `#2' not defined}{%
      You need to define `#2' in \string\DTLmutlibibs}%
    }{%
      \expandafter\immediate
      \expandafter\write\csname dtl@aux@#2\endcsname{%
        \string\citation{\@citeb}}%
    }%
  \fi
  \@ifundefined{b@#2@\@citeb}{%
    \hbox{\reset@font\bfseries ?}%
    \G@refundefinedtrue
    \@latex@warning{Citation `\@citeb ' on page \thepage \space
      undefined}%
  }{%
    \@cite@ofmt{\csname b@#2@\@citeb \endcsname }%
  }%
}}{#1}%
}
\newcommand*{\DTLnocite}[2]{%
\@ifundefined{dtl@aux@#1}{%
  \PackageError{databib}{multibib `#1' not defined}{%
  You need to define `#1' in \string\DTLmutlibibs}%
}{%
  \@bsphack
  \ifx\@onlypreamble\document
    \@for\@citeb:=#2\do{%
      \edef\@citeb{\expandafter\@firstofone\@citeb}%
      \if@filesw
        \expandafter\immediate
        \expandafter\write\csname dtl@aux@#1\endcsname{%
          \string\citation{\@citeb}}%
      \fi
      \@ifundefined{b@#1@\@citeb}{%
        \G@refundefinedtrue
        \@latex@warning{Citation `\@citeb ' undefined}}{}%
    }%
  \else
    \@latex@error{Cannot be used in preamble}\@eha
  \fi
  \@esphack
}%
}
\newcommand*{\DTLloadmbbl}[3]{%
\@ifundefined{dtl@aux@#1}{%
  \PackageError{databib}{multibib `#1' not defined}{%
  You need to define `#1' in \string\DTLmutlibibs}%
}{%
  \if@filesw
    \expandafter\immediate\expandafter
      \write\csname dtl@aux@#1\endcsname{\string\bibstyle{databib}}%
    \expandafter\immediate\expandafter
      \write\csname dtl@aux@#1\endcsname{\string\bibdata{#3}}%
  \fi
  \DTLnewdb{#2}%
  \edef\DTLBIBdbname{#2}%
  \@input@{#1.bbl}%
}%
}
\newcommand*{\DTLmbibliography}[3][\boolean{true}]{%
\begin{DTLthebibliography}[#1]{#3}%
\DTLforeachbibentry[#1]{#3}{%
\DTLmbibitem{#2} \DTLformatbibentry \DTLendbibitem
}%
\end{DTLthebibliography}%
}
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</databib-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datagidx-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\subsection{Rollback v2.32 (datagidx-2019-09-27.sty)}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{datagidx}[2019/09/27 v2.32 (NLCT)]
\RequirePackage{datatool}[=v2.32]
\RequirePackage{etoolbox}
\RequirePackage{xkeyval}
\RequirePackage{mfirstuc}
\RequirePackage{xfor}
\RequirePackage{multicol}
\RequirePackage{textcase}
\RequirePackage{afterpage}
\newcommand*{\datagidx@columns}{2}
\newcommand*{\DTLgidxSetColumns}[1]{%
  \DTLifint{#1}%
  {%
    \def\datagidx@columns{#1}%
  }%
  {%
    \PackageError{datagidx}%
    {Number of columns must be an integer}%
    {%
      You have requested `#1' columns, which can't be parsed as a
      number%
    }%
  }%
}
\newcounter{DTLgidxChildCount}
\def\theHDTLgidxChildCount{\Label.\arabic{DTLgidxChildCount}}
\newcommand*{\DTLgidxChildCountLabel}{\theDTLgidxChildCount) }
\newcommand*{\DTLgidxChildStyle}[1]{#1}
\newcommand*{\datagidx@setchildstyle}[1]{%
  \ifcase#1\relax
    \renewcommand*{\DTLgidxChildStyle}[1]{##1}%
  \or
    \renewcommand*{\DTLgidxChildStyle}[1]{%
      \DTLgidxChildCountLabel
    }%
  \fi
}
\newcommand{\datagidx@foreachchild}{%
  \datagidx@sort@foreachchild
}
\newcommand*{\datagidx@setchildsort}[1]{%
  \ifcase#1\relax
    \renewcommand*{\datagidx@foreachchild}{%
       \datagidx@sort@foreachchild
    }%
  \or
    \renewcommand*{\datagidx@foreachchild}{%
       \datagidx@unsort@foreachchild
    }%
  \fi
}
\newcommand*{\DTLgidxPostName}{ }
\newcommand*{\DTLgidxPostChildName}{\DTLgidxPostName}
\newcommand*{\DTLgidxNameCase}[1]{#1}
\newcommand*{\datagidx@setnamecase}[1]{%
  \ifcase#1\relax
    \renewcommand*{\DTLgidxNameCase}[1]{##1}%
  \or
    \let\DTLgidxNameCase\MakeTextUppercase
  \or
    \let\DTLgidxNameCase\MakeTextLowercase
  \or
    \let\DTLgidxNameCase\xmakefirstuc
  \or
    \let\DTLgidxNameCase\xcapitalisewords
  \fi
}
\newcommand*{\DTLgidxNameFont}[1]{\textnormal{#1}}
\newcommand*{\DTLgidxPostDescription}{}
\newcommand*{\datagidx@setpostdesc}[1]{%
   \ifcase#1\relax
     \renewcommand*{\DTLgidxPostDescription}{}%
   \or
     \renewcommand*{\DTLgidxPostDescription}{.}%
   \fi
 }
\newcommand*{\DTLgidxPreLocation}{\enspace}
\newcommand*{\datagidx@setprelocation}[1]{%
   \ifcase#1\relax
     \renewcommand*{\DTLgidxPreLocation}{}%
   \or
     \renewcommand*{\DTLgidxPreLocation}{\enspace}%
   \or
     \renewcommand*{\DTLgidxPreLocation}{ }%
   \or
     \renewcommand*{\DTLgidxPreLocation}{\dotfill}%
   \or
     \renewcommand*{\DTLgidxPreLocation}{\hfill}%
   \fi
 }
\newcommand*{\DTLgidxLocation}{\dtldolocationlist}
\newcommand*{\datagidx@setlocation}[1]{%
  \ifcase#1\relax
    \renewcommand*{\DTLgidxLocation}{}%
  \or
    \renewcommand*{\DTLgidxLocation}{\dtldolocationlist}%
  \or
    \renewcommand*{\DTLgidxLocation}{\dtldofirstlocation}%
  \fi
}
\newcommand*{\DTLgidxSee}{%
  \DTLifnull{\See}%
  {}%
  {%
    \DTLgidxPreLocation
    \DTLgidxFormatSee{\seename}{\See}%
  }%
}
\newcommand*{\DTLgidxSeeAlso}{%
  \DTLifnull{\SeeAlso}%
  {}%
  {%
     \DTLgidxFormatSeeAlso{\seealsoname}{\SeeAlso}%
  }%
}
\newcommand*{\DTLgidxChildrenSeeAlso}{%
  \DTLgidxChildren
  \DTLgidxSeeAlso
}
\newcommand*{\datagidx@setsee}[1]{%
  \ifcase#1\relax
    \renewcommand*{\DTLgidxSee}{%
      \DTLifnull{\See}{}%
      {%
        , \DTLgidxFormatSee{\seename}{\See}%
      }%
    }%
  \or
    \renewcommand*{\DTLgidxSee}{%
      \DTLifnull{\See}{}
      {%
        \space(\DTLgidxFormatSee{\seename}{\See})%
      }%
    }%
  \or
    \renewcommand*{\DTLgidxSee}{%
      \DTLifnull{\See}{}%
      {%
        . \DTLgidxFormatSee{\xmakefirstuc{\seename}}{\See}%
      }%
    }%
  \or
    \renewcommand*{\DTLgidxSee}{%
      \DTLifnull{\See}{}
      {%
        \space\DTLgidxFormatSee{\seename}{\See}%
      }%
    }%
  \or
    \renewcommand*{\DTLgidxSee}{%
      \DTLifnull{\See}{}
      {%
        \DTLgidxFormatSee{\seename}{\See}%
      }%
    }%
  \or
    \renewcommand*{\DTLgidxSee}{%
      \DTLifnull{\See}{}
      {%
        ; \DTLgidxFormatSee{\seename}{\See}%
      }%
    }%
  \or
    \renewcommand*{\DTLgidxSee}{%
      \DTLifnull{\See}{}
      {%
        \DTLgidxPreLocation\DTLgidxFormatSee{\seename}{\See}%
      }%
    }%
  \fi
}
\newcommand*{\DTLgidxSymDescSep}{\space}
\newlength\datagidxsymbolwidth
\newlength\datagidxlocationwidth
\newcommand{\DTLgidxFormatDesc}[1]{#1}
\newcommand*{\DTLgidxSymbolDescription}{%
  \DTLgidxSymbolDescLeft
  \DTLgidxSymbolDescRight
}
\newcommand*{\DTLgidxSymbolDescLeft}{%
  \ifdefempty{\Symbol}{}{(\Symbol)\DTLgidxSymDescSep}%
}
\newcommand*{\DTLgidxSymbolDescRight}{%
  \ifdefempty{\Description}{}%
  {%
    \DTLgidxFormatDesc{\Description}\DTLgidxPostDescription
  }%
}
\newif\if@datagidxsymbolleft
\@datagidxsymbollefttrue
\newcommand*{\datagidx@formatsymdesc}[1]{%
   \ifcase#1\relax
      \renewcommand*{\DTLgidxSymbolDescLeft}{%
        \ifdefempty{\Symbol}{}{\Symbol}%
      }%
      \renewcommand*{\DTLgidxSymbolDescRight}{}%
      \@datagidxsymbollefttrue
   \or
      \renewcommand*{\DTLgidxSymbolDescLeft}{%
        \ifdefempty{\Description}{}%
        {%
           \DTLgidxFormatDesc{\Description}\DTLgidxPostDescription
        }%
      }%
      \renewcommand*{\DTLgidxSymbolDescRight}{}%
      \@datagidxsymbolleftfalse
   \or
      \renewcommand*{\DTLgidxSymbolDescLeft}{%
        \ifdefempty{\Symbol}{}{(\Symbol)\DTLgidxSymDescSep}%
      }%
      \renewcommand*{\DTLgidxSymbolDescRight}{%
        \ifdefempty{\Description}{}%
        {%
          \DTLgidxFormatDesc{\Description}\DTLgidxPostDescription
        }%
      }%
      \@datagidxsymbollefttrue
   \or
      \renewcommand*{\DTLgidxSymbolDescLeft}{%
        \ifdefempty{\Description}{}%
        {%
          \DTLgidxFormatDesc{\Description}%
          \DTLgidxPostDescription\DTLgidxSymDescSep
        }%
      }%
      \renewcommand*{\DTLgidxSymbolDescRight}{%
        \ifdefempty{\Symbol}{}{(\Symbol)}%
      }%
      \@datagidxsymbolleftfalse
   \or
      \renewcommand*{\DTLgidxSymbolDescLeft}{%
        \ifdefempty{\Symbol}{}{\Symbol\DTLgidxSymDescSep}%
      }%
      \renewcommand*{\DTLgidxSymbolDescRight}{%
        \ifdefempty{\Description}{}%
        {%
          \DTLgidxFormatDesc{\Description}%
          \DTLgidxPostDescription
        }%
      }%
      \@datagidxsymbollefttrue
   \or
      \renewcommand*{\DTLgidxSymbolDescLeft}{%
        \ifdefempty{\Description}{}%
        {%
          \DTLgidxFormatDesc{\Description}%
          \DTLgidxPostDescription\DTLgidxSymDescSep
        }%
      }%
      \renewcommand*{\DTLgidxSymbolDescRight}{%
        \ifdefempty{\Symbol}{}{\Symbol}%
      }%
      \@datagidxsymbolleftfalse
   \fi
 }
\newcommand*{\DTLgidxSetCompositor}[1]{%
 \undef\datagidx@docomplist
 \DeclareListParser{\datagidx@docomplist}{#1}%
 \def\datagidx@compositor{#1}%
}
\DTLgidxSetCompositor{.}
\newcommand*{\datagidx@do@sort}{\datagidx@sort}
\newcommand*{\datagidx@optimize@sort}{%
  \ifdef\datagidx@do@optimize@sort
  {%
    \datagidx@sort
  }%
  {%
    \protected@write\@auxout{}{%
      \string\gdef\string\datagidx@do@optimize@sort{}%
    }%
    \global\let\@datagidx@dorerun@warn@sort\@data@rerun@warn@sort
  }%
}
\newif\if@datagidx@warn
\@datagidx@warntrue
\newcommand*\@datagidx@dorerun@warn{}
\AtEndDocument{\if@datagidx@warn\@datagidx@dorerun@warn\fi}
\newcommand*\@datagidx@dorerun@warn@sort{}
\AtEndDocument{\if@datagidx@warn\@datagidx@dorerun@warn@sort\fi}
\newcommand*\@data@rerun@warn@sort{%
  \PackageWarningNoLine{datagidx}{Rerun required to sort the
   index/glossary databases}%
}
\newcommand*\@data@rerun@warn{%
  \PackageWarningNoLine{datagidx}{Rerun required to ensure the
   index/glossary location lists are up-to-date}%
}
\newcommand*{\datagidx@do@highopt@optimize}{%
  \renewcommand*{\datagidx@do@sort}{%
    \ifcsdef{datagidx@do@highopt@sort@\DTLgidxCurrentdb}%
    {%
      \csuse{datagidx@do@highopt@sort@\DTLgidxCurrentdb}%
    }%
    {%
    }%
    \bgroup
    \def\dtl@saverawdbhook{%
      \let\db@col@id@w\@datagidx@db@col@id@w
      \def\DTLgidxName{\string\DTLgidxName\space}%
      \def\DTLgidxMac{\string\DTLgidxMac\space}%
      \def\DTLgidxRank{\string\DTLgidxRank\space}%
      \def\DTLgidxParen{\string\DTLgidxParen\space}%
      \def\DTLgidxParticle{\string\DTLgidxParticle\space}%
      \def\DTLgidxOffice{\string\DTLgidxOffice\space}%
      \def\DTLgidxSaint{\string\DTLgidxSaint\space}%
      \def\DTLgidxPlace{\string\DTLgidxPlace\space}%
      \def\DTLgidxIgnore{\string\DTLgidxIgnore\space}%
      \def\DTLgidxNameNum{\string\DTLgidxNameNum\space}%
      \def\DTLgidxSubject{\string\DTLgidxSubject\space}%
    }%
    \DTLsaverawdb{\DTLgidxCurrentdb}{\datagidxhighoptfilename\DTLgidxCurrentdb}%
    \egroup
  }%
  \def\newgidx{\datagidx@highopt@newgidx}%
  \def\newterm{\datagidx@highopt@newterm}%
}
\def\@datagidx@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@{%
  \expandafter\@gobble\string\%^^J
  \string\db@col@id@w\space #1%
  \expandafter\@gobble\string\%^^J
  \string\db@col@id@end@\space
  \expandafter\@gobble\string\%^^J
  \string\db@col@elt@w\space
   \expandafter\ifnum\csname dtl@ci@\DTLgidxCurrentdb @Used\endcsname=#1\space
     0%
   \else
     \expandafter\ifnum\csname dtl@ci@\DTLgidxCurrentdb @Location\endcsname=#1\space
     \else
       \expandafter\ifnum\csname dtl@ci@\DTLgidxCurrentdb @CurrentLocation\endcsname=#1\space
       \else
         \expandafter\ifnum\csname dtl@ci@\DTLgidxCurrentdb @Sort\endcsname=#1\space
           \protect#2%
         \else
         #2%
         \fi
       \fi
     \fi
   \fi
  \expandafter\@gobble\string\%^^J
  \string\db@col@elt@end@\space
  \expandafter\@gobble\string\%^^J
  \string\db@col@id@w\space #3%
  \expandafter\@gobble\string\%^^J
  \string\db@col@id@end@\space
}
\newcommand*{\datagidx@do@highopt@update}[1]{}
\newcommand*{\datagidxhighoptfilename}[1]{\jobname-#1.gidx}
\define@choicekey{datagidx.sty}{optimize}[\val\nr]%
 {off,low,high}[high]%
 {%
   \ifcase\nr\relax
    \renewcommand*{\datagidx@do@sort}{\datagidx@sort}
   \or
    \renewcommand*{\datagidx@do@sort}{\datagidx@optimize@sort}
   \or
    \datagidx@do@highopt@optimize
   \fi
 }
\define@choicekey{datagidx.sty}{nowarn}[\val\nr]{true,false}[true]%
{%
  \ifcase\nr\relax
    \@datagidx@warnfalse
  \or
    \@datagidx@warntrue
  \fi
}
\define@choicekey{datatool.sty}{utf8}{true,false}[true]{%
  \setbool{@dtl@utf8}{#1}%
}
\define@key{datagidx.sty}{columns}%
{%
  \DTLgidxSetColumns{#1}%
}
\define@choicekey{datagidx.sty}{child}[\val\nr]%
 {named,noname}%
 {%
   \datagidx@setchildstyle\nr
 }
\define@choicekey{datagidx.sty}{namecase}[\val\nr]%
 {nochange,uc,lc,firstuc,capitalise}%
 {%
   \datagidx@setnamecase\nr
 }
\define@key{datagidx.sty}{namefont}%
{%
  \renewcommand*{\DTLgidxNameFont}[1]{{#1{##1}}}%
}
\define@key{datagidx.sty}{postname}
{%
  \renewcommand*{\DTLgidxPostName}{#1}%
}
\define@choicekey{datagidx.sty}{postdesc}[\val\nr]%
 {none,dot}%
 {%
   \datagidx@setpostdesc\nr
 }
\define@choicekey{datagidx.sty}{prelocation}[\val\nr]%
 {none,enspace,space,dotfill,hfill}%
 {%
   \datagidx@setprelocation\nr
 }
\define@choicekey{datagidx.sty}{location}[\val\nr]%
 {hide,list,first}%
 {\datagidx@setlocation\nr}
\define@choicekey{datagidx.sty}{see}[\val\nr]%
  {comma,brackets,dot,space,nosep,semicolon,location}%
  {\datagidx@setsee\nr}
\define@choicekey{datagidx.sty}{symboldesc}[\val\nr]%
 {symbol,desc,(symbol) desc,desc (symbol),symbol desc,desc symbol}%
 {\datagidx@formatsymdesc\nr}
\define@key{datagidx.sty}{compositor}%
{%
  \DTLgidxSetCompositor{#1}%
}%
\DeclareOptionX{final}{%
  \let\datagidxshowifdraft\@gobble
}
\let\datagidxshowifdraft\@gobble
\DeclareOptionX{draft}{%
  \let\datagidxshowifdraft\@firstofone
}
\define@choicekey{datagidx.sty}{verbose}[\val\nr]%
 {true,false}[true]%
 {%
   \csuse{dtlverbose\val}%
 }
\ProcessOptionsX
\DTLnewdb{datagidx}
\providecommand*{\seename}{see}
\providecommand*{\seealsoname}{see also}
\newcommand*{\DTLgidxSeeTagFont}[1]{\emph{#1}}
\newcommand*{\DTLgidxFormatSee}[2]{%
  \DTLgidxSeeTagFont{#1} \DTLgidxSeeList{#2}%
}
\newcommand*{\DTLgidxFormatSeeAlso}[2]{%
  \datagidxdoseealso
  {%
    \DTLgidxSeeTagFont{#1} \DTLgidxSeeList{#2}%
  }%
}
\newcommand*{\datagidxdoseealso}[1]{%
  \datagidxseealsostart
   #1%
  \datagidxseealsoend
}
\newcommand*{\DTLgidxSeeList}[1]{%
  \def\datagidx@sep{}%
  \@for\dtl@thislabel:=#1\do
  {%
    \ifx\@xfor@nextelement\@nnil
      \ifdefempty{\datagidx@sep}%
      {%
      }%
      {%
         \DTLidxSeeLastSep
      }%
    \else
      \datagidx@sep
      \let\datagidx@sep\DTLidxSeeSep
    \fi
    \DTLidxFormatSeeItem{\dtl@thislabel}%
  }%
}
\newcommand*{\DTLidxFormatSeeItem}[1]{%
  \DTLgidxFetchEntry{\datagidx@value}{#1}{Name}%
  \datagidxlink{#1}%
  {%
    \datagidx@value
  }%
}
\newcommand*{\DTLidxSeeSep}{, }
\newcommand*{\DTLidxSeeLastSep}{ \& }
\newcommand*{\DTLgidxDoSeeOrLocation}{%
  \DTLifnull\See
  {%
     \ifdefempty\Location
     {%
     }%
     {%
       \DTLgidxPreLocation
       \DTLgidxLocation
     }%
  }%
  {%
     \DTLgidxSee
  }%
}
\newcommand*{\datagidx@sortchildren}{%
  \def\datagidx@sortedlist{}%
  \@for\Label:=\Children\do
  {%
    \edef\do@getrow{%
      \noexpand\dtlgetrowforvalue
      {\DTLgidxCurrentdb}%
      {\dtlcolumnindex{\DTLgidxCurrentdb}{Label}}%
      {\Label}%
    }%
    \do@getrow
    \ifdefempty\datagidx@sortedlist
    {%
      \edef\datagidx@newsortedlist{{\number\dtlrownum}{\Label}}%
    }%
    {%
      \def\datagidx@newsortedlist{}%
      \@for\@datagidx@thisval:=\datagidx@sortedlist\do
      {%
        \edef\datagidx@thisidx{\expandafter\@firstoftwo\@datagidx@thisval}%
        \ifnum\datagidx@thisidx>\dtlrownum\relax
          \ifdefempty\datagidx@newsortedlist
          {%
            \eappto\datagidx@newsortedlist
            {%
              {\number\dtlrownum}{\Label},\@datagidx@thisval
            }%
          }%
          {%
            \eappto\datagidx@newsortedlist
            {%
              ,{\number\dtlrownum}{\Label},\@datagidx@thisval
            }%
          }%
          \@endfortrue
        \else
          \ifdefempty\datagidx@newsortedlist
          {%
            \edef\datagidx@newsortedlist{%
              \@datagidx@thisval
            }%
          }%
          {%
            \eappto\datagidx@newsortedlist
            {%
              ,\@datagidx@thisval
            }%
          }%
        \fi
      }%
      \if@endfor
        \ifdefempty\@forremainder
        {%
        }%
        {%
          \eappto\datagidx@newsortedlist{,\@forremainder}%
        }%
      \else
        \ifdefempty\datagidx@newsortedlist
        {%
          \edef\datagidx@newsortedlist{{\number\dtlrownum}{\Label}}%
        }%
        {%
          \eappto\datagidx@newsortedlist{,{\number\dtlrownum}{\Label}}%
        }%
      \fi
    }%
    \let\datagidx@sortedlist\datagidx@newsortedlist
    \@endforfalse
  }%
}
\newcommand{\datagidx@sort@foreachchild}[1]{%
  \datagidx@sortchildren
  \@for\@datagidx@thisval:=\datagidx@sortedlist\do
  {%
    \edef\Label{\expandafter\@secondoftwo\@datagidx@thisval}%
    #1%
  }%
}
\newcommand{\datagidx@unsort@foreachchild}[1]{%
  \@for\Label:=\Children\do
  {%
    #1%
  }%
}
\newcommand*{\DTLgidxChildren}{%
  \bgroup
    \DTLifnull\Children
    {}%
    {%
      \advance\datagidx@level by 1\relax
      \datagidxchildstart
      \let\Parent\Label
      \datagidx@foreachchild
      {%
        \edef\do@getrow{%
          \noexpand\dtlgetrowforvalue
          {\DTLgidxCurrentdb}%
          {\dtlcolumnindex{\DTLgidxCurrentdb}{Label}}%
          {\Label}%
        }%
        \do@getrow
        \dtlgetentryfromcurrentrow
          {\Location}%
          {\dtlcolumnindex{\DTLgidxCurrentdb}{Location}}%
        \dtlgetentryfromcurrentrow
          {\See}%
          {\dtlcolumnindex{\DTLgidxCurrentdb}{See}}%
        \dtlgetentryfromcurrentrow
          {\SeeAlso}%
          {\dtlcolumnindex{\DTLgidxCurrentdb}{SeeAlso}}%
        \DTLifnull\Location
        {%
          \DTLifnull\See
          {%
            \DTLifnull\SeeAlso
            {}%
            {%
              \datagidx@displaychild
            }%
          }%
          {%
            \datagidx@displaychild
          }%
        }%
        {%
           \datagidx@displaychild
        }%
      }%
      \datagidxchildend
    }%
  \egroup
}
\newcommand*{\datagidxgetchildfields}{%
  \dtlgetentryfromcurrentrow
    {\Name}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Name}}%
  \dtlgetentryfromcurrentrow
    {\Description}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Description}}%
  \dtlgetentryfromcurrentrow
    {\Symbol}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Symbol}}%
  \dtlgetentryfromcurrentrow
    {\Long}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Long}}%
  \dtlgetentryfromcurrentrow
    {\Short}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Short}}%
  \dtlgetentryfromcurrentrow
    {\Text}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Text}}%
  \dtlgetentryfromcurrentrow
    {\Plural}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Plural}}%
  \dtlgetentryfromcurrentrow
    {\Short}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Used}}%
  \dtlgetentryfromcurrentrow
    {\Children}%
    {\dtlcolumnindex{\DTLgidxCurrentdb}{Child}}%
}
\newcommand*{\datagidx@displaychild}{%
  \datagidxgetchildfields
  \datagidxchilditem
}
\ifdef{\chapter}
{%
  \newcommand*{\datagidx@heading}{\chapter*}
}%
{%
  \newcommand*{\datagidx@heading}{\section*}
}
\let\DTLgidxNoHeading\@gobble
\newcommand*{\datagidx@postheading}{}
\newcommand*{\datagidx@multicols}{multicols}
\newcommand*{\datagidx@sort}{%
  \dtlsort{Sort,FirstId}{\DTLgidxCurrentdb}{\dtlwordindexcompare}%
}
\providecommand{\@idxitem}{\par\hangindent 40\p@}
\newcommand*{\datagidxstart}%
{%
  \bgroup
  \setlength{\parindent}{0pt}%
  \setlength{\parskip}{0pt plus 0.3pt}%
  \let\item\@idxitem
}
\newcommand*{\datagidxend}{\egroup}
\newcommand*{\@datagidxtarget}[2]{%
  \ifdef\hypertarget
  {%
    \bgroup
      \let\glsadd\@gobble
      \settoheight\dimen@{#2}%
      \raisebox{\dimen@}{\hypertarget{#1}{}}%
    \egroup
  }%
  {%
  }%
  #2%
}
\newcommand*{\datagidxtarget}{\@datagidxtarget}
\newcommand*{\@datagidxlink}[2]{%
  \ifdef\hyperlink
  {%
    \hyperlink{#1}{#2}%
  }%
  {%
    #2%
  }%
}
\newcommand*{\datagidxlink}{\@datagidxlink}
\newcommand*{\DTLgidxEnableHyper}{%
  \let\datagidxtarget\@datagidxtarget
  \let\datagidxlink\@datagidxlink
}
\newcommand*{\DTLgidxDisableHyper}{%
  \let\datagidxtarget\@secondoftwo
  \let\datagidxlink\@secondoftwo
}
\newcommand*{\datagidxgroupsep}{}
\newcommand*{\datagidxgroupheader}{}
\newcommand*{\datagidxitem}{}%
\newcommand*{\datagidxchildstart}{}
\newcommand*{\datagidxchildend}{}
\newcommand*{\datagidxchilditem}{}%
\newcommand*{\datagidxseealsostart}{}
\newcommand*{\datagidxseealsoend}{}
\newcommand*{\datagidx@doifsymlocwidth}[3]{%
  \setlength{\dtl@tmplength}{\linewidth}%
  \addtolength{\dtl@tmplength}{-#1}%
  \settowidth{\dimen@}{#2}%
  \addtolength{\dtl@tmplength}{-\dimen@}%
  \addtolength{\dtl@tmplength}{-\datagidxsymbolwidth}%
  \addtolength{\dtl@tmplength}{-\datagidxlocationwidth}%
  \settowidth{\dimen@}{\DTLgidxPreLocation}%
  \addtolength{\dtl@tmplength}{-\dimen@}%
  \settowidth{\dimen@}{\DTLgidxSymDescSep}%
  \addtolength{\dtl@tmplength}{-\dimen@}%
  \if@datagidxsymbolleft
    \begin{minipage}[t]{\datagidxsymbolwidth}%
      \datagidxsymalign
      \let\DTLgidxSymDescSep\@empty
      \DTLgidxSymbolDescLeft
    \end{minipage}%
    \DTLgidxSymDescSep
    \begin{minipage}[t]{\dtl@tmplength}%
      \let\DTLgidxSymDescSep\@empty
      \DTLgidxSymbolDescRight
    \end{minipage}%
  \else
    \begin{minipage}[t]{\dtl@tmplength}%
      \let\DTLgidxSymDescSep\@empty
      \DTLgidxSymbolDescRight
    \end{minipage}%
    \DTLgidxSymDescSep
    \begin{minipage}[t]{\datagidxsymbolwidth}%
      \datagidxsymalign
      \let\DTLgidxSymDescSep\@empty
      \DTLgidxSymbolDescLeft
    \end{minipage}%
  \fi
  \DTLgidxPreLocation
  \begin{minipage}[t]{\datagidxlocationwidth}%
    \datagidxlocalign
    \let\DTLgidxPreLocation\@empty
    #3%
  \end{minipage}%
}
\newcommand*{\datagidx@doiflocwidth}[3]{%
  \setlength{\dtl@tmplength}{\linewidth}%
  \addtolength{\dtl@tmplength}{-#1}%
  \settowidth{\dimen@}{#2}%
  \addtolength{\dtl@tmplength}{-\dimen@}%
  \addtolength{\dtl@tmplength}{-\datagidxlocationwidth}%
  \settowidth{\dimen@}{\DTLgidxPreLocation}%
  \addtolength{\dtl@tmplength}{-\dimen@}%
  \begin{minipage}[t]{\dtl@tmplength}%
    \DTLgidxSymbolDescription
  \end{minipage}%
  \DTLgidxPreLocation
  \begin{minipage}[t]{\datagidxlocationwidth}%
    \datagidxlocalign
    \let\DTLgidxPreLocation\@empty
    #3%
  \end{minipage}%
}
\newcommand*{\datagidx@doifsymwidth}[3]{%
  \setlength{\dtl@tmplength}{\linewidth}%
  \addtolength{\dtl@tmplength}{-#1}%
  \settowidth{\dimen@}{#2}%
  \addtolength{\dtl@tmplength}{-\dimen@}%
  \addtolength{\dtl@tmplength}{-\datagidxsymbolwidth}%
  \settowidth{\dimen@}{\DTLgidxSymDescSep}%
  \addtolength{\dtl@tmplength}{-\dimen@}%
  \if@datagidxsymbolleft
    \begin{minipage}[t]{\datagidxsymbolwidth}%
      \datagidxsymalign
      \let\DTLgidxSymDescSep\@empty
      \DTLgidxSymbolDescLeft
    \end{minipage}%
    \DTLgidxSymDescSep
    \begin{minipage}[t]{\dtl@tmplength}%
      \let\DTLgidxSymDescSep\@empty
      \DTLgidxSymbolDescRight
      #3%
    \end{minipage}%
  \else
    \begin{minipage}[t]{\dtl@tmplength}%
      \let\DTLgidxSymDescSep\@empty
      \DTLgidxSymbolDescRight
    \end{minipage}%
    \DTLgidxSymDescSep
    \begin{minipage}[t]{\datagidxsymbolwidth}%
      \datagidxsymalign
      \let\DTLgidxSymDescSep\@empty
      \DTLgidxSymbolDescLeft
      #3%
    \end{minipage}%
  \fi
}
\newcommand*{\datagidxlocalign}{\raggedleft}
\newcommand*{\datagidxsymalign}{\centering}
\newcommand*{\datagidxsetstyle}[1]{%
  \ifcsdef{datagidx@style@#1}%
  {%
    \csuse{datagidx@style@#1}%
  }%
  {%
    \PackageError{datagidx}{Unknown style `#1'}{}%
  }%
}
\newcommand*{\datagidx@style@index}{%
  \renewcommand*{\datagidxstart}%
  {%
    \bgroup
    \setlength{\parindent}{0pt}%
    \setlength{\parskip}{0pt plus 0.3pt}%
    \raggedright
    \let\item\@idxitem
    \ifdim\datagidxsymbolwidth>0pt\relax
      \ifdim\datagidxlocationwidth>0pt\relax
        \def\datagidx@item@body{%
          \datagidx@doifsymlocwidth{0pt}%
           {\DTLgidxNameFont{\DTLgidxNameCase{\Name}}}%
           {%
             \DTLgidxDoSeeOrLocation
           }%
         }%
      \else
        \def\datagidx@item@body{%
          \datagidx@doiflocwidth{0pt}%
           {\DTLgidxNameFont{\DTLgidxNameCase{\Name}}}%
           {%
             \DTLgidxDoSeeOrLocation
           }%
         }%
      \fi
    \else
      \ifdim\datagidxlocationwidth>0pt\relax
        \def\datagidx@item@body{%
          \datagidx@doiflocwidth{0pt}%
           {\DTLgidxNameFont{\DTLgidxNameCase{\Name}}}%
           {%
             \DTLgidxDoSeeOrLocation
           }%
         }%
      \else
        \def\datagidx@item@body{%
          \DTLgidxSymbolDescription
          \DTLgidxDoSeeOrLocation
        }%
      \fi
    \fi
  }%
  \renewcommand*{\datagidxend}{\egroup}%
  \renewcommand*{\datagidxgroupsep}{\ifdatagidxshowgroups\indexspace\fi}%
  \renewcommand{\datagidxgroupheader}{%
    \ifdatagidxshowgroups
      \item
       \makebox[\linewidth]%
       {%
         \textbf{\DTLgidxGroupHeaderTitle{\datagidxcurrentgroup}}%
       }%
       \DTLpar\nobreak\@afterheading
    \fi
  }%
  \renewcommand*{\datagidxitem}{%
    \ifdefempty\datagidxprevgroup
    {%
      \datagidxgroupheader
    }%
    {%
      \ifdefequal\datagidxcurrentgroup\datagidxprevgroup
      {%
      }%
      {%
        \datagidxgroupsep
        \datagidxgroupheader
      }%
    }%
    \item
    \datagidxtarget{\Label}%
    {%
      \DTLgidxNameFont{\DTLgidxNameCase{\Name}}%
    }%
    \DTLgidxPostName
    \datagidx@item@body
    \DTLgidxChildrenSeeAlso
  }%
  \renewcommand*{\datagidxchildstart}%
  {%
    \bgroup
    \setlength{\parindent}{0pt}%
    \setlength{\parskip}{0pt plus 0.3pt}%
    \let\item\@idxitem
  }%
  \renewcommand*{\datagidxchildend}{\egroup}%
  \renewcommand*{\datagidxchilditem}{%
    \setlength{\dimen@}{\datagidxindent}%
    \multiply\dimen@ by \datagidx@level\relax
    \@idxitem\hspace*{\dimen@}%
    \refstepcounter{DTLgidxChildCount}%
    \datagidxtarget{\Label}%
    {%
      \DTLgidxChildStyle
      {%
        \DTLgidxNameFont{\DTLgidxNameCase{\Name}}%
        \DTLgidxPostChildName
      }%
    }%
    \DTLgidxSymbolDescription
    \DTLgidxDoSeeOrLocation
    \DTLgidxChildrenSeeAlso
  }%
  \renewcommand*{\datagidxseealsostart}%
  {%
    \bgroup
      \setlength{\parindent}{0pt}%
      \setlength{\parskip}{0pt plus 0.3pt}%
      \setlength{\dimen@}{\datagidxindent}%
      \advance\datagidx@level by 1\relax
      \multiply\dimen@ by \datagidx@level\relax
      \@idxitem\hspace*{\dimen@}%
  }%
  \renewcommand{\datagidxseealsoend}{\egroup}%
}
\datagidx@style@index
\newcommand*{\datagidx@style@indexalign}{%
  \renewcommand*{\datagidxstart}%
  {%
    \bgroup
    \setlength{\parindent}{0pt}%
    \setlength{\parskip}{0pt plus 0.3pt}%
    \setlength{\datagidxnamewidth}{0pt}%
    \DTLforeach*{\DTLgidxCurrentdb}%
      {\Name=Name,\Location=Location,\See=See,\SeeAlso=SeeAlso,%
       \Parent=Parent}%
    {%
       \DTLifnull{\Parent}%
       {%
         \datagidx@doifdisplayed
         {%
           \settowidth{\dimen@}{\DTLgidxNameFont{\DTLgidxNameCase{\Name}}}%
           \ifdim\dimen@>\datagidxnamewidth\relax
              \datagidxnamewidth=\dimen@\relax
           \fi
         }%
       }%
       {}%
    }%
    \settowidth{\dimen@}{\DTLgidxPostName}%
    \addtolength{\datagidxnamewidth}{\dimen@}%
    \setlength{\datagidxdescwidth}{\linewidth}%
    \addtolength{\datagidxdescwidth}{-\datagidxnamewidth}%
    \ifdim\datagidxsymbolwidth>0pt\relax
      \addtolength{\datagidxdescwidth}{-\datagidxsymbolwidth}%
      \settowidth{\dimen@}{\DTLgidxSymDescSep}%
      \addtolength{\datagidxdescwidth}{-\dimen@}%
    \fi
    \ifdim\datagidxlocationwidth>0pt\relax
      \addtolength{\datagidxdescwidth}{-\datagidxlocationwidth}%
      \settowidth{\dimen@}{\DTLgidxPreLocation}%
      \addtolength{\datagidxdescwidth}{-\dimen@}%
    \fi
    \ifdim\datagidxsymbolwidth>0pt\relax
      \ifdim\datagidxlocationwidth>0pt\relax
        \if@datagidxsymbolleft
          \def\datagidx@item@body{%
            \begin{minipage}[t]{\datagidxsymbolwidth}%
              \datagidxsymalign
              \let\DTLgidxSymDescSep\@empty
              \DTLgidxSymbolDescLeft
            \end{minipage}%
            \DTLgidxSymDescSep
            \begin{minipage}[t]{\datagidxdescwidth}%
              \let\DTLgidxSymDescSep\@empty
              \setlength{\parskip}{0pt plus 0.3pt}%
              \DTLgidxSymbolDescRight
            \end{minipage}%
            \DTLgidxPreLocation
            \begin{minipage}[t]{\datagidxlocationwidth}%
              \datagidxlocalign
              \let\DTLgidxPreLocation\@empty
              \DTLgidxDoSeeOrLocation
            \end{minipage}%
           }%
         \else
          \def\datagidx@item@body{%
            \begin{minipage}[t]{\datagidxdescwidth}%
              \let\DTLgidxSymDescSep\@empty
              \DTLgidxSymbolDescLeft
            \end{minipage}%
            \DTLgidxSymDescSep
            \begin{minipage}[t]{\datagidxsymbolwidth}%
              \datagidxsymalign
              \let\DTLgidxSymDescSep\@empty
              \setlength{\parskip}{0pt plus 0.3pt}%
              \DTLgidxSymbolDescRight
            \end{minipage}%
            \DTLgidxPreLocation
            \begin{minipage}[t]{\datagidxlocationwidth}%
              \datagidxlocalign
              \let\DTLgidxPreLocation\@empty
              \DTLgidxDoSeeOrLocation
            \end{minipage}%
           }%
         \fi
      \else
        \if@datagidxsymbolleft
          \def\datagidx@item@body{%
            \begin{minipage}[t]{\datagidxsymbolwidth}%
              \datagidxsymalign
              \let\DTLgidxSymDescSep\@empty
              \DTLgidxSymbolDescLeft
            \end{minipage}%
            \DTLgidxSymDescSep
            \begin{minipage}[t]{\datagidxdescwidth}%
              \let\DTLgidxSymDescSep\@empty
              \setlength{\parskip}{0pt plus 0.3pt}%
              \DTLgidxSymbolDescRight
              \DTLgidxDoSeeOrLocation
            \end{minipage}%
         }%
        \else
          \def\datagidx@item@body{%
            \begin{minipage}[t]{\datagidxdescwidth}%
              \let\DTLgidxSymDescSep\@empty
              \DTLgidxSymbolDescLeft
            \end{minipage}%
            \DTLgidxSymDescSep
            \begin{minipage}[t]{\datagidxsymbolwidth}%
              \datagidxsymalign
              \let\DTLgidxSymDescSep\@empty
              \setlength{\parskip}{0pt plus 0.3pt}%
              \DTLgidxSymbolDescRight
              \DTLgidxDoSeeOrLocation
            \end{minipage}%
         }%
        \fi
      \fi
    \else
      \ifdim\datagidxlocationwidth>0pt\relax
        \def\datagidx@item@body{%
          \begin{minipage}[t]{\datagidxdescwidth}%
           \setlength{\parskip}{0pt plus 0.3pt}%
           \DTLgidxSymbolDescription
          \end{minipage}%
          \DTLgidxPreLocation
          \begin{minipage}[t]{\datagidxlocationwidth}%
          \datagidxlocalign
           \let\DTLgidxPreLocation\@empty
           \DTLgidxDoSeeOrLocation
         }%
      \else
        \def\datagidx@item@body{%
          \begin{minipage}[t]{\datagidxdescwidth}%
           \setlength{\parskip}{0pt plus 0.3pt}%
           \DTLgidxSymbolDescription
           \DTLgidxDoSeeOrLocation
          \end{minipage}%
         }%
      \fi
    \fi
  }%
  \renewcommand*{\datagidxend}{\egroup}%
  \renewcommand*{\datagidxgroupsep}{}%
  \renewcommand*{\datagidxgroupheader}{}%
  \renewcommand*{\datagidxitem}{%
    \ifdefempty\datagidxprevgroup
    {%
      \datagidxgroupheader
    }%
    {%
      \ifdefequal\datagidxcurrentgroup\datagidxprevgroup
      {%
      }%
      {%
        \datagidxgroupsep
        \datagidxgroupheader
      }%
    }%
    \hangindent0pt\relax
    \parindent0pt\relax
    \makebox[\datagidxnamewidth][l]%
    {%
      \datagidxtarget{\Label}%
      {%
        \DTLgidxNameFont{\DTLgidxNameCase{\Name}}%
        \DTLgidxPostName
      }%
    }%
    \datagidx@item@body
    \par
    \DTLgidxChildrenSeeAlso
    \par
  }%
  \renewcommand*{\datagidxchildstart}%
  {%
    \bgroup
    \setlength{\dimen@}{\datagidxindent}%
    \multiply\dimen@ by \datagidx@level\relax
    \setlength{\dtl@tmplength}{\linewidth}%
    \addtolength{\dtl@tmplength}{-\dimen@}%
    \setlength{\parindent}{0pt}%
    \setlength{\parskip}{0pt plus 0.3pt}%
    \edef\item{\noexpand\parshape=1 \the\dimen@ \the\dtl@tmplength}%
    \setlength{\datagidxnamewidth}{0pt}%
    \DTLforeach*{\DTLgidxCurrentdb}%
      {\Name=Name,\Location=Location,\See=See,\SeeAlso=SeeAlso,%
       \Parent=Parent}%
    {%
       \DTLifnull{\Parent}%
       {%
         \datagidx@doifdisplayed
         {%
           \settowidth{\dimen@}%
           {%
             \DTLgidxChildStyle
             {%
               \DTLgidxNameFont{\DTLgidxNameCase{\Name}}%
             }%
           }%
           \ifdim\dimen@>\datagidxnamewidth\relax
              \datagidxnamewidth=\dimen@\relax
           \fi
         }%
       }%
       {}%
    }%
    \settowidth{\dimen@}{\DTLgidxChildStyle\DTLgidxPostChildName}%
    \addtolength{\datagidxnamewidth}{\dimen@}%
    \setlength{\datagidxdescwidth}{\dtl@tmplength}%
    \addtolength{\datagidxdescwidth}{-\datagidxnamewidth}%
  }%
  \renewcommand{\datagidxchildend}{\egroup}%
  \renewcommand*{\datagidxchilditem}{%
    \item
    \refstepcounter{DTLgidxChildCount}%
    \makebox[\datagidxnamewidth][l]%
    {%
      \datagidxtarget{\Label}%
      {%
        \DTLgidxChildStyle
        {%
          \DTLgidxNameFont{\DTLgidxNameCase{\Name}}%
          \DTLgidxPostChildName
        }%
      }%
    }%
    \begin{minipage}[t]{\datagidxdescwidth}%
     \setlength{\parskip}{0pt plus 0.3pt}%
     \DTLgidxSymbolDescription
     \DTLgidxDoSeeOrLocation
     \DTLgidxChildrenSeeAlso
    \end{minipage}%
    \par
  }%
}
\newlength\datagidxindent
\setlength\datagidxindent{10\p@}
\newlength\datagidxnamewidth
\newlength\datagidxdescwidth
\newcommand*{\datagidx@style@align}{%
  \renewcommand*{\datagidxstart}%
  {%
    \bgroup
    \setlength{\parindent}{0pt}%
    \setlength{\parskip}{0pt plus 0.3pt}%
    \setlength{\datagidxnamewidth}{0pt}%
    \DTLforeach*{\DTLgidxCurrentdb}%
      {\Name=Name,\Location=Location,\See=See,\SeeAlso=SeeAlso,%
       \Parent=Parent}%
    {%
       \DTLifnull{\Parent}%
       {%
         \datagidx@doifdisplayed
         {%
           \settowidth{\dimen@}{\DTLgidxNameFont{\DTLgidxNameCase{\Name}}}%
           \ifdim\dimen@>\datagidxnamewidth\relax
              \datagidxnamewidth=\dimen@\relax
           \fi
         }%
       }%
       {}%
    }%
    \settowidth{\dimen@}{\DTLgidxPostName}%
    \addtolength{\datagidxnamewidth}{\dimen@}%
    \setlength{\datagidxdescwidth}{\linewidth}%
    \addtolength{\datagidxdescwidth}{-\datagidxnamewidth}%
    \ifdim\datagidxsymbolwidth>0pt\relax
      \addtolength{\datagidxdescwidth}{-\datagidxsymbolwidth}%
      \settowidth{\dimen@}{\DTLgidxSymDescSep}%
      \addtolength{\datagidxdescwidth}{-\dimen@}%
    \fi
    \ifdim\datagidxlocationwidth>0pt\relax
      \addtolength{\datagidxdescwidth}{-\datagidxlocationwidth}%
      \settowidth{\dimen@}{\DTLgidxPreLocation}%
      \addtolength{\datagidxdescwidth}{-\dimen@}%
    \fi
    \ifdim\datagidxsymbolwidth>0pt\relax
      \ifdim\datagidxlocationwidth>0pt\relax
        \if@datagidxsymbolleft
          \def\datagidx@item@body{%
            \begin{minipage}[t]{\datagidxsymbolwidth}%
              \datagidxsymalign
              \let\DTLgidxSymDescSep\@empty
              \DTLgidxSymbolDescLeft
            \end{minipage}%
            \DTLgidxSymDescSep
            \begin{minipage}[t]{\datagidxdescwidth}%
              \let\DTLgidxSymDescSep\@empty
              \setlength{\parskip}{0pt plus 0.3pt}%
              \DTLgidxSymbolDescRight
            \end{minipage}%
            \DTLgidxPreLocation
            \begin{minipage}[t]{\datagidxlocationwidth}%
              \datagidxlocalign
              \let\DTLgidxPreLocation\@empty
              \DTLgidxDoSeeOrLocation
              \DTLgidxChildrenSeeAlso
            \end{minipage}%
           }%
         \else
          \def\datagidx@item@body{%
            \begin{minipage}[t]{\datagidxdescwidth}%
              \let\DTLgidxSymDescSep\@empty
              \DTLgidxSymbolDescLeft
            \end{minipage}%
            \DTLgidxSymDescSep
            \begin{minipage}[t]{\datagidxsymbolwidth}%
              \datagidxsymalign
              \let\DTLgidxSymDescSep\@empty
              \setlength{\parskip}{0pt plus 0.3pt}%
              \DTLgidxSymbolDescRight
            \end{minipage}%
            \DTLgidxPreLocation
            \begin{minipage}[t]{\datagidxlocationwidth}%
              \datagidxlocalign
              \let\DTLgidxPreLocation\@empty
              \DTLgidxDoSeeOrLocation
              \DTLgidxChildrenSeeAlso
            \end{minipage}%
           }%
         \fi
      \else
        \if@datagidxsymbolleft
          \def\datagidx@item@body{%
            \begin{minipage}[t]{\datagidxsymbolwidth}%
              \datagidxsymalign
              \let\DTLgidxSymDescSep\@empty
              \DTLgidxSymbolDescLeft
            \end{minipage}%
            \DTLgidxSymDescSep
            \begin{minipage}[t]{\datagidxdescwidth}%
              \let\DTLgidxSymDescSep\@empty
              \setlength{\parskip}{0pt plus 0.3pt}%
              \DTLgidxSymbolDescRight
              \DTLgidxDoSeeOrLocation
              \DTLgidxChildrenSeeAlso
            \end{minipage}%
         }%
        \else
          \def\datagidx@item@body{%
            \begin{minipage}[t]{\datagidxdescwidth}%
              \let\DTLgidxSymDescSep\@empty
              \DTLgidxSymbolDescLeft
            \end{minipage}%
            \DTLgidxSymDescSep
            \begin{minipage}[t]{\datagidxsymbolwidth}%
              \datagidxsymalign
              \let\DTLgidxSymDescSep\@empty
              \setlength{\parskip}{0pt plus 0.3pt}%
              \DTLgidxSymbolDescRight
              \DTLgidxDoSeeOrLocation
              \DTLgidxChildrenSeeAlso
           \end{minipage}%
         }%
        \fi
      \fi
    \else
      \ifdim\datagidxlocationwidth>0pt\relax
        \def\datagidx@item@body{%
          \begin{minipage}[t]{\datagidxdescwidth}%
           \setlength{\parskip}{0pt plus 0.3pt}%
           \DTLgidxSymbolDescription
          \end{minipage}%
          \DTLgidxPreLocation
          \begin{minipage}[t]{\datagidxlocationwidth}%
          \datagidxlocalign
           \let\DTLgidxPreLocation\@empty
           \DTLgidxDoSeeOrLocation
           \DTLgidxChildrenSeeAlso
          \end{minipage}%
         }%
      \else
        \def\datagidx@item@body{%
          \begin{minipage}[t]{\datagidxdescwidth}%
           \setlength{\parskip}{0pt plus 0.3pt}%
           \DTLgidxSymbolDescription
           \DTLgidxDoSeeOrLocation
           \DTLgidxChildrenSeeAlso
          \end{minipage}%
         }%
      \fi
    \fi
  }%
  \renewcommand*{\datagidxend}{\egroup}%
  \renewcommand*{\datagidxgroupsep}{\ifdatagidxshowgroups\indexspace\fi}%
  \renewcommand{\datagidxgroupheader}{%
    \ifdatagidxshowgroups
      \item
       \makebox[\linewidth]%
       {%
         \textbf{\DTLgidxGroupHeaderTitle{\datagidxcurrentgroup}}%
       }%
       \DTLpar\nobreak\@afterheading
    \fi
  }%
  \renewcommand*{\datagidxitem}{%
    \ifdefempty\datagidxprevgroup
    {%
      \datagidxgroupheader
    }%
    {%
      \ifdefequal\datagidxcurrentgroup\datagidxprevgroup
      {%
      }%
      {%
        \datagidxgroupsep
        \datagidxgroupheader
      }%
    }%
    \hangindent0pt\relax
    \parindent0pt\relax
    \makebox[\datagidxnamewidth][l]%
    {%
      \datagidxtarget{\Label}%
      {%
        \DTLgidxNameFont{\DTLgidxNameCase{\Name}}%
        \DTLgidxPostName
      }%
    }%
     \datagidx@item@body
    \par
  }%
  \renewcommand*{\datagidxchildstart}%
  {%
    \bgroup
    \setlength{\parindent}{0pt}%
    \setlength{\parskip}{0pt plus 0.3pt}%
    \setlength{\datagidxnamewidth}{0pt}%
    \DTLforeach*{\DTLgidxCurrentdb}%
      {\Name=Name,\Location=Location,\See=See,\SeeAlso=SeeAlso,%
       \Parent=Parent}%
    {%
       \DTLifnull{\Parent}%
       {%
         \datagidx@doifdisplayed
         {%
           \settowidth{\dimen@}%
           {%
             \DTLgidxChildStyle
             {%
               \DTLgidxNameFont{\DTLgidxNameCase{\Name}}%
             }%
           }%
           \ifdim\dimen@>\datagidxnamewidth\relax
              \datagidxnamewidth=\dimen@\relax
           \fi
         }%
       }%
       {}%
    }%
    \settowidth{\dimen@}{\DTLgidxChildStyle\DTLgidxPostChildName}%
    \addtolength{\datagidxnamewidth}{\dimen@}%
    \setlength{\datagidxdescwidth}{\linewidth}%
    \addtolength{\datagidxdescwidth}{-\datagidxnamewidth}%
  }%
  \renewcommand{\datagidxchildend}{\egroup}%
  \renewcommand*{\datagidxchilditem}{%
    \hangindent0pt\relax
    \parindent0pt\relax
    \refstepcounter{DTLgidxChildCount}%
    \makebox[\datagidxnamewidth][l]%
    {%
      \datagidxtarget{\Label}%
      {%
        \DTLgidxChildStyle
        {%
          \DTLgidxNameFont{\DTLgidxNameCase{\Name}}%
          \DTLgidxPostChildName
        }%
      }%
    }%
    \begin{minipage}[t]{\datagidxdescwidth}%
     \setlength{\parskip}{0pt plus 0.3pt}%
     \DTLgidxSymbolDescription
     \DTLgidxDoSeeOrLocation
     \DTLgidxChildrenSeeAlso
    \end{minipage}%
    \par
  }%
}
\newcommand*{\datagidx@style@gloss}{%
  \renewcommand*{\datagidxstart}%
  {%
    \bgroup
    \setlength{\parindent}{0pt}%
    \setlength{\parskip}{0pt plus 0.3pt}%
    \setlength{\datagidxnamewidth}{0pt}%
    \DTLforeach*{\DTLgidxCurrentdb}%
      {\Name=Name,\Location=Location,\See=See,\SeeAlso=SeeAlso,%
       \Parent=Parent}%
    {%
       \DTLifnull{\Parent}%
       {%
         \datagidx@doifdisplayed
         {%
           \settowidth{\dimen@}{\DTLgidxNameFont{\DTLgidxNameCase{\Name}}}%
           \ifdim\dimen@>\datagidxnamewidth\relax
              \datagidxnamewidth=\dimen@\relax
           \fi
         }%
       }%
       {}%
    }%
    \settowidth{\dimen@}{\DTLgidxPostName}%
    \addtolength{\datagidxnamewidth}{\dimen@}%
    \setlength{\datagidxdescwidth}{\linewidth}%
    \addtolength{\datagidxdescwidth}{-\datagidxnamewidth}%
  }%
  \renewcommand*{\datagidxend}{\egroup}%
  \renewcommand*{\datagidxgroupsep}{\ifdatagidxshowgroups\indexspace\fi}%
  \renewcommand{\datagidxgroupheader}{%
    \ifdatagidxshowgroups
      \item
       \makebox[\linewidth]%
       {%
         \textbf{\DTLgidxGroupHeaderTitle{\datagidxcurrentgroup}}%
       }%
       \DTLpar\nobreak\@afterheading
    \fi
  }%
  \renewcommand*{\datagidxitem}{%
    \ifdefempty\datagidxprevgroup
    {%
      \datagidxgroupheader
    }%
    {%
      \ifdefequal\datagidxcurrentgroup\datagidxprevgroup
      {%
      }%
      {%
        \datagidxgroupsep
        \datagidxgroupheader
      }%
    }%
    \hangindent0pt\relax
    \parindent0pt\relax
    \makebox[\datagidxnamewidth][l]%
    {%
      \datagidxtarget{\Label}%
      {%
        \DTLgidxNameFont{\DTLgidxNameCase{\Name}}%
        \DTLgidxPostName
      }%
    }%
    \begin{minipage}[t]{\datagidxdescwidth}%
     \setlength{\parskip}{0pt plus 0.3pt}%
     \@tempswatrue
     \ifdefempty{\Description}%
     {%
       \ifdefempty{\Symbol}%
       {%
         \ifdefempty{\Location}{\@tempswafalse}{}%
       }%
       {}%
     }%
     {}%
     \if@tempswa
       \DTLgidxSymbolDescription
       \DTLgidxDoSeeOrLocation
     \else
       \mbox{}%
     \fi
     \DTLgidxChildrenSeeAlso
    \end{minipage}%
    \par
  }%
  \renewcommand*{\datagidxchildstart}%
  {%
    \bgroup
    \def\datagidx@childsep{}%
    \setcounter{DTLgidxChildCount}{0}%
  }%
  \renewcommand{\datagidxchildend}{\DTLgidxPostChild\egroup}%
  \renewcommand*{\datagidxchilditem}{%
    \datagidx@childsep
    \refstepcounter{DTLgidxChildCount}%
    \datagidxtarget{\Label}%
    {%
      \DTLgidxChildStyle
      {%
        \DTLgidxNameFont{\DTLgidxNameCase{\Name}}%
        \DTLgidxPostChildName
      }%
    }%
    \DTLgidxSymbolDescription
    \DTLgidxDoSeeOrLocation
    \DTLgidxChildrenSeeAlso
    \let\datagidx@childsep\DTLgidxChildSep
  }%
}
\newcommand*{\DTLgidxChildSep}{ }
\newcommand*{\DTLgidxPostChild}{}
\ifdef\chapter
{%
  \newcommand\DTLgidxDictHead{%
    \chapter{\DTLgidxGroupHeaderTitle{\datagidxcurrentgroup}}%
  }%
}%
{%
  \newcommand\DTLgidxDictHead{%
    \section{\DTLgidxGroupHeaderTitle{\datagidxcurrentgroup}}%
  }%
}
\newcommand*{\DTLgidxCategoryNameFont}[1]{#1}
\newcommand*{\DTLgidxCategorySep}{\space}
\newcommand*{\DTLgidxSubCategorySep}{\space}
\newcommand*{\datagidxdictindent}{1em}
\newcommand{\DTLgidxDictPostItem}{\par}
\newcommand*{\datagidx@style@dict}{%
  \renewcommand*{\datagidxstart}%
  {%
    \bgroup
    \setlength{\parindent}{0pt}%
    \setlength{\parskip}{0pt plus 0.3pt}%
    \dimen@=\linewidth
    \advance\dimen@ by -\datagidxdictindent\relax
    \dtl@tmplength=\datagidxdictindent\relax
    \xdef\datagidxdictparshape{%
      \noexpand\parshape=2 0pt \the\linewidth\space
       \the\dtl@tmplength\space \the\dimen@\relax
    }%
    \datagidx@level=1\relax
    \raggedright
  }%
  \renewcommand*{\datagidxend}{\egroup}%
  \renewcommand*{\datagidxgroupsep}{}%
  \renewcommand{\datagidxgroupheader}{%
    \ifdatagidxshowgroups
      \datagidxend
      \datagidx@postend
      \DTLgidxDictHead
      \datagidx@prestart
      \datagidxstart
    \fi
  }%
  \renewcommand*{\datagidxitem}{%
    \ifdefempty\datagidxprevgroup
    {%
      \datagidxgroupheader
    }%
    {%
      \ifdefequal\datagidxcurrentgroup\datagidxprevgroup
      {%
      }%
      {%
        \datagidxgroupsep
        \datagidxgroupheader
      }%
    }%
    \datagidxdictparshape
    \datagidxtarget{\Label}%
    {%
      \DTLgidxNameFont{\DTLgidxNameCase{\Name}}%
    }%
    \DTLgidxPostName
    \let\datagidx@catsep\@empty
    \let\datagidx@subcatsep\@empty
    \DTLgidxSymbolDescription
    \DTLgidxChildrenSeeAlso
    \DTLgidxDictPostItem
  }%
  \renewcommand*{\datagidxchildstart}%
  {%
    \bgroup
  }%
  \renewcommand*{\datagidxchildend}{\egroup}%
  \renewcommand*{\datagidxchilditem}{%
    \ifnum\datagidx@level=2\relax
      \datagidx@catsep
      \let\datagidx@catsep\DTLgidxCategorySep
      \let\datagidx@subcapsep\@empty
      \datagidxtarget{\Label}%
      {%
        \DTLgidxChildStyle
        {%
          \DTLgidxCategoryNameFont{\DTLgidxNameCase{\Name}}%
          \DTLgidxPostChildName
        }%
      }%
      \setcounter{DTLgidxChildCount}{0}%
    \else
      \datagidx@subcatsep
      \let\datagidx@subcatsep\DTLgidxSubCategorySep
      \refstepcounter{DTLgidxChildCount}%
      \DTLgidxChildCountLabel
      \DTLgidxPostChildName
    \fi
    \DTLgidxSymbolDescription
    \DTLgidxDoSeeOrLocation
    \DTLgidxChildrenSeeAlso
  }%
  \renewcommand*{\datagidxseealsostart}%
  {%
    \bgroup
      \setlength{\parindent}{0pt}%
      \setlength{\parskip}{0pt plus 0.3pt}%
      \setlength{\dimen@}{\datagidxindent}%
      \advance\datagidx@level by 1\relax
      \multiply\dimen@ by \datagidx@level\relax
      \@idxitem\hspace*{\dimen@}%
  }%
  \renewcommand{\datagidxseealsoend}{\egroup}%
}
\newcommand*{\dtldofirstlocation}{%
  \@for\dtl@thisloc:=\Location\do{%
    \ifdefempty\dtl@thisloc
    {}%
    {%
       \expandafter\datagidx@getlocation\dtl@thisloc
       \datagidxlink{\datagidx@current@target}%
       {%
         \datagidx@formatlocation
            \datagidx@current@format\datagidx@current@locationstring
       }%
      \@endfortrue
    }%
  }%
}
\newcommand*{\datagidx@formatlocation}[2]{%
  \ifdefempty{#1}%
  {#2}%
  {%
    \ifcsdef{#1}%
    {%
      \csuse{#1}{#2}%
    }%
    {%
     \PackageWarning{datagidx}{Unknown format `#1'}%
      #2%
    }%
  }%
}
\newcommand*{\dtldolocationlist}{%
 \DTLifnull{\Location}%
 {}%
 {%
    \def\datagidx@prev@location{-1}%
    \def\datagidx@prev@locationstring{}%
    \def\datagidx@prev@format{}%
    \def\datagidx@prev@locationformat{}%
    \def\datagidx@prev@prefix{}%
    \def\datagidx@prev@target{}%
    \def\datagidx@location@sep{}%
    \def\datagidx@location@start{-1}%
    \expandafter\forcsvlist\expandafter\datagidx@parse@location
      \expandafter{\Location}%
    \do@prevlocation % tidy up loose ends
  }%
}
\newif\if@dtl@sequential
\newcommand*\datagidx@getlocdo[1]{%
  \ifdefempty\datagidx@current@location
  {}%
  {%
    \eappto\datagidx@current@prefix{%
      \datagidx@current@location\datagidx@compositor
    }%
  }%
  \def\datagidx@current@location{#1}%
}
\def\datagidx@getlocation[#1]#2#3{%
  \def\datagidx@current@locationstring{#2}%
  \bgroup
    \datagidx@escapelocationformat
    \xdef\datagidx@current@locationformat{#2}%
    \datagidx@clearlocationformat
    \xdef\datagidx@current@location{#2}%
  \egroup
  \let\datagidx@list\datagidx@current@location
  \def\datagidx@current@prefix{}%
  \def\datagidx@current@location{}%
  \let\do\datagidx@getlocdo
  \expandafter\datagidx@docomplist
   \expandafter{\datagidx@list}%
  \def\datagidx@current@format{#1}%
  \def\datagidx@current@target{#3}%
}
\newcommand*{\datagidx@parse@location}[1]{%
  \datagidx@getlocation#1\relax
  \ifdefequal{\datagidx@prev@locationstring}{\datagidx@current@locationstring}%
  {%
    \ifdefequal{\datagidx@prev@format}{\datagidx@current@format}%
    {%
    }%
    {%
      \ifdefempty{\datagidx@current@format}%
      {%
      }%
      {%
        \ifdefempty{\datagidx@prev@format}%
        {%
           \let\datagidx@prev@format\datagidx@current@format
        }%
        {%
           \PackageWarning{datagidx}%
           {%
              Conflicting location formats `\datagidx@prev@format' and
              `\datagidx@current@format' for location `\datagidx@current@location'%
           }%
        }%
      }%
    }%
  }%
  {%
    \@datagidx@parse@location
  }%
}
\newcommand*{\@datagidx@parse@location}{%
  \@dtl@sequentialtrue
  \ifdefequal{\datagidx@prev@format}{\datagidx@current@format}%
  {%
    \ifdefequal{\datagidx@prev@locationformat}{\datagidx@current@locationformat}%
    {%
      \ifdefequal{\datagidx@prev@prefix}{\datagidx@current@prefix}%
      {%
      }%
      {%
        \@dtl@sequentialfalse
      }%
    }%
    {%
      \@dtl@sequentialfalse
    }%
  }%
  {%
    \@dtl@sequentialfalse
  }%
  \if@dtl@sequential
    \ifnumequal{\datagidx@prev@location+1}{\datagidx@current@location}%
    {%
       \ifdefequal
         \datagidx@current@locationformat
         \datagidx@prev@locationformat
      {%
        \@dtl@sequentialtrue
      }%
      {%
             \@dtl@sequentialfalse
      }%
    }%
    {%
       \@dtl@sequentialfalse
    }%
  \fi
  \if@dtl@sequential
     \ifnumequal{\datagidx@location@start}{-1}%
     {%
       \let\datagidx@location@start\datagidx@prev@location
       \let\datagidx@location@startval\datagidx@prev@locationstring
       \let\datagidx@location@format\datagidx@prev@format
       \let\datagidx@location@target\datagidx@prev@target
     }%
     {%
     }%
  \else
    \do@prevlocation
  \fi
  \let\datagidx@prev@location\datagidx@current@location
  \let\datagidx@prev@format\datagidx@current@format
  \let\datagidx@prev@prefix\datagidx@current@prefix
  \let\datagidx@prev@locationformat\datagidx@current@locationformat
  \let\datagidx@prev@locationstring\datagidx@current@locationstring
  \let\datagidx@prev@target\datagidx@current@target
}
\newcommand*{\DTLgidxLocationSep}{, }
\newcommand*{\DTLgidxLocationF}[2]{%
  #1\DTLgidxLocationSep#2%
}
\newcommand*{\DTLgidxLocationFF}[2]{%
  #1--#2%
}
\newcommand*{\do@prevlocation}{%
  \ifnumequal{\datagidx@location@start}{-1}%
  {%
     \ifdefempty{\datagidx@prev@locationstring}%
     {}%
     {%
       \datagidx@location@sep
       \datagidxlink{\datagidx@prev@target}%
       {%
         \datagidx@formatlocation
           \datagidx@prev@format\datagidx@prev@locationstring
       }%
       \def\datagidx@location@sep{\DTLgidxLocationSep}%
     }%
  }%
  {%
    \datagidx@location@sep
    \do@locrange
    \def\datagidx@location@sep{\DTLgidxLocationSep}%
    \def\datagidx@location@start{-1}%
  }%
}
\newcommand*{\do@locrange}{%
  \ifnumgreater{\datagidx@prev@location}{\datagidx@location@start+1}%
  {%
     \DTLgidxLocationFF
       {%
         \datagidxlink{\datagidx@location@target}%
         {%
           \datagidx@formatlocation
             \datagidx@location@format\datagidx@location@startval
         }%
       }%
       {%
         \datagidxlink{\datagidx@prev@target}%
         {%
           \datagidx@formatlocation
             \datagidx@prev@format\datagidx@prev@locationstring
         }%
       }%
  }%
  {%
     \DTLgidxLocationF
       {%
         \datagidxlink{\datagidx@location@target}%
         {%
           \datagidx@formatlocation
             \datagidx@location@format\datagidx@location@startval
         }%
       }%
       {%
         \datagidxlink{\datagidx@prev@target}%
         {%
           \datagidx@formatlocation
             \datagidx@prev@format\datagidx@prev@locationstring
         }%
       }%
  }%
}
\newcommand*{\datagidx@defaultdatabase}{}
\newcommand*{\DTLgidxSetDefaultDB}[1]{%
  \renewcommand*{\datagidx@defaultdatabase}{#1}%
}
\define@key{newgloss}{heading}{\renewcommand*{\datagidx@heading}{#1}}
\define@key{newgloss}{postheading}{%
  \renewcommand*{\datagidx@postheading}{#1}%
}
\newif\ifdatagidxbalance
\datagidxbalancetrue
\define@choicekey{newgloss}{balance}[\val\nr]{true,false}[true]{%
 \ifcase\nr\relax
   \renewcommand*{\datagidx@multicols}{multicols}%
   \datagidxbalancetrue
 \or
   \renewcommand*{\datagidx@multicols}{multicols*}%
   \datagidxbalancefalse
 \fi
}
\define@key{newgloss}{sort}{\renewcommand*{\datagidx@sort}{#1}}
\newcommand*{\datagidx@style}{index}
\define@key{newgloss}{style}{\renewcommand*{\datagidx@style}{#1}}
\newif\ifdatagidxshowgroups
\newcommand*{\datagidx@showgroups}{false}
\define@choicekey{newgloss}{showgroups}{true,false}[true]%
{%
  \renewcommand{\datagidx@showgroups}{#1}%
}%
\ifundef\newgidx
{%
  \newcommand*{\newgidx}{\datagidx@newgidx}
}%
{}
\@onlypreamble\newgidx
\newcommand*{\datagidx@highopt@newgidx}[3][]{%
  \edef\datagidx@indexfilename{\datagidxhighoptfilename{#2}}%
  \IfFileExists{\datagidx@indexfilename}%
  {%
    \input{\datagidx@indexfilename}%
    \bgroup
      \setkeys{newgloss}{#1}%
      \datagidx@newgidx@update{#2}{#3}%
    \egroup
  }%
  {%
    \datagidx@newgidx[#1]{#2}{#3}%
  }%
}
\newcommand*{\loadgidx}[3][]{%
  \input{#2}%
  \bgroup
    \setkeys{newgloss}{sort={},#1}%
    \expandafter\datagidx@newgidx@update\expandafter
      {\dtllastloadeddb}{#3}%
  \egroup
  \edef\datagidx@defaultdatabase{\dtllastloadeddb}%
  \dtlforcolumn{\Label}{\dtllastloadeddb}{Label}%
  {%
     \csxdef{datagidxentry@\Label}{\dtllastloadeddb}%
  }%
}
\@onlypreamble\loadgidx
\newcommand*{\datagidx@newgidx}[3][]{%
\bgroup
  \setkeys{newgloss}{#1}%
  \ifdefempty{\datagidx@defaultdatabase}%
  {\xdef\datagidx@defaultdatabase{#2}}%
  {}%
  \DTLgnewdb{#2}%
  \DTLaddcolumn{#2}{Label}%
  \DTLaddcolumn{#2}{Location}%
  \DTLaddcolumn{#2}{CurrentLocation}%
  \DTLaddcolumn{#2}{FirstId}%
  \DTLaddcolumn{#2}{Name}%
  \DTLaddcolumn{#2}{Text}%
  \DTLaddcolumn{#2}{Parent}%
  \DTLaddcolumn{#2}{Child}%
  \DTLaddcolumn{#2}{Description}%
  \DTLaddcolumn{#2}{Used}%
  \DTLaddcolumn{#2}{Symbol}%
  \DTLaddcolumn{#2}{Long}%
  \DTLaddcolumn{#2}{Short}%
  \DTLaddcolumn{#2}{See}%
  \DTLaddcolumn{#2}{SeeAlso}%
  \datagidx@newgidx@update{#2}{#3}%
\egroup
}
\newcommand*{\datagidx@newgidx@update}[2]{%
  \DTLnewrow{datagidx}%
  \DTLnewdbentry{datagidx}{Glossary}{#1}%
  \DTLnewdbentry{datagidx}{Title}{#2}%
  {%
    \dtlexpandnewvalue
    \DTLnewdbentry{datagidx}{Heading}{\expandonce\datagidx@heading}%
    \DTLnewdbentry{datagidx}{PostHeading}{\expandonce\datagidx@postheading}%
    \DTLnewdbentry{datagidx}{MultiCols}{\expandonce\datagidx@multicols}%
    \DTLnewdbentry{datagidx}{Sort}{\expandonce\datagidx@sort}%
    \DTLnewdbentry{datagidx}{Style}{\expandonce\datagidx@style}%
    \DTLnewdbentry{datagidx}{ShowGroups}{\expandonce\datagidx@showgroups}%
  }%
}
\newcommand*{\newterm@label}{}
\define@key{newterm}{label}{\renewcommand*{\newterm@label}{#1}}
\newcommand*{\newterm@parent}{}
\define@key{newterm}{parent}{\renewcommand*{\newterm@parent}{#1}}
\newcommand*{\newterm@text}{}
\define@key{newterm}{text}{\renewcommand*{\newterm@text}{#1}}
\newcommand*{\newterm@description}{}
\define@key{newterm}{description}{%
  \renewcommand*{\newterm@description}{#1}%
}
\define@key{newterm}{plural}{\def\newterm@plural{#1}}
\newcommand*{\newterm@sort}{}
\define@key{newterm}{sort}{\renewcommand*{\newterm@sort}{#1}}
\newcommand*{\newterm@symbol}{}
\define@key{newterm}{symbol}{\renewcommand*{\newterm@symbol}{#1}}
\newcommand*{\newterm@database}{}
\define@key{newterm}{database}{\renewcommand*{\newterm@database}{#1}}
\newcommand*{\newterm@long}{}
\define@key{newterm}{long}{%
  \renewcommand*{\newterm@long}{#1}%
  \def\newterm@longplural{#1s}%
}
\newcommand*{\newterm@short}{}
\define@key{newterm}{short}{%
  \renewcommand*{\newterm@short}{#1}%
  \def\newterm@shortplural{#1s}%
}
\define@key{newterm}{longplural}{%
  \def\newterm@longplural{#1}%
}
\define@key{newterm}{shortplural}{%
  \def\newterm@shortplural{#1}%
}
\newcommand*{\newterm@see}{}
\define@key{newterm}{see}{%
  \renewcommand*{\newterm@see}{#1}%
}
\newcommand*{\newterm@seealso}{}
\define@key{newterm}{seealso}{%
  \renewcommand*{\newterm@seealso}{#1}%
}
\newcommand*{\newterm@defaultshook}{}
\newcommand*{\newterm@extrafields}{}
\newcommand*{\DTLgidxAssignList}{%
  \Name=Name,\Description=Description,\Used=Used,\Symbol=Symbol,%
  \Long=Long,\Short=Short,\LongPlural=LongPlural,\ShortPlural=ShortPlural,%
  \Location=Location,\See=See,\SeeAlso=SeeAlso,%
  \Text=Text,\Plural=Plural,\CurrentLocation=CurrentLocation,%
  \Label=Label,\Parent=Parent,\Children=Child,\FirstId=FirstId,\Sort=Sort%
}
\newcommand*{\datagidxtermkeys}{%
  name,description,symbol,long,short,see,seealso,text,plural,%
  label,parent,sort%
}
\newcommand*\@datagidx@fieldkey@Name{name}%
\newcommand*\@datagidx@fieldkey@Description{description}%
\newcommand*\@datagidx@fieldkey@Symbol{symbol}%
\newcommand*\@datagidx@fieldkey@Long{long}%
\newcommand*\@datagidx@fieldkey@Short{short}%
\newcommand*\@datagidx@fieldkey@See{see}%
\newcommand*\@datagidx@fieldkey@SeeAlso{seealso}%
\newcommand*\@datagidx@fieldkey@Text{text}%
\newcommand*\@datagidx@fieldkey@Plural{plural}%
\newcommand*\@datagidx@fieldkey@Label{label}%
\newcommand*\@datagidx@fieldkey@Parent{parent}%
\newcommand*\@datagidx@fieldkey@Sort{sort}%
\newcommand*{\newtermaddfield}[4][]{%
  \ifstrempty{#1}%
  {%
    \dtlforcolumn{\datagidx@thisidx}{datagidx}{Glossary}%
    {%
      \DTLaddcolumn{\datagidx@thisidx}{#2}%
    }%
  }%
  {%
    \@for\datagidx@thisidx:=#1\do
    {%
      \DTLaddcolumn{\datagidx@thisidx}{#2}%
    }%
  }%
  \expandafter\gdef\csname newterm@#3\endcsname{}%
  \define@key{newterm}{#3}%
  {%
    \expandafter\def\csname newterm@#3\endcsname{##1}%
  }%
  \gappto\newterm@defaultshook
  {%
    \expandafter\protected@edef\csname newterm@#3\endcsname{#4}%
  }%
  \gappto\newterm@extrafields
  {%
     \protected@edef\datagidx@value{\csname newterm@#3\endcsname}%
     \DTLnewdbentry{\newterm@database}{#2}{\expandonce\datagidx@value}%
  }%
  \xappto\DTLgidxAssignList
  {%
    ,\expandafter\noexpand\csname#2\endcsname=#2%
  }%
  \xappto\datagidxtermkeys{,#3}%
  \expandafter\xdef\csname @datagidx@fieldkey@#2\endcsname{#3}%
  \xappto\datagidxgetchildfields
  {%
    \noexpand\dtlgetentryfromcurrentrow
     {\expandafter\noexpand\csname#2\endcsname}%
     {\noexpand\dtlcolumnindex{\noexpand\DTLgidxCurrentdb}{#2}}%
  }%
}
\newcommand*{\newtermlabelhook}{}
\newcommand*{\DTLgidxNoFormat}[1]{#1}
\newcommand*{\DTLgidxGobble}[1]{}
\newcommand*{\DTLgidxStripBackslash}[1]{%
  \expandafter\@gobble\string#1%
}
\newcommand*{\DTLgidxName}[2]{%
  #1\space #2%
}
\newcommand*{\DTLgidxNameNum}[1]{\@Roman{#1}}
\newcommand*{\datagidx@namenum}[1]{\two@digits{#1}}
\newcommand*{\DTLgidxPlace}[2]{%
  #2%
}
\newcommand*{\DTLgidxSubject}[2]{%
  #2%
}
\newcommand*{\DTLgidxOffice}[2]{%
  #2 (#1)%
}
\newcommand*{\DTLgidxIgnore}[1]{#1}
\newcommand*{\DTLgidxMac}[1]{#1}
\newcommand*{\datagidx@mac}[1]{Mac}
\newcommand*{\DTLgidxSaint}[1]{#1}
\newcommand*{\datagidx@saint}[1]{Saint}
\newcommand*{\DTLgidxRank}[2]{#1~#2}
\newcommand*{\datagidx@rank}[2]{#2.}
\newcommand*{\DTLgidxParticle}[2]{#1~#2}
\newcommand*{\datagidx@particle}[2]{#2.}
\newcommand*{\datagidx@bothoftwo}[2]{#1#2}
\newcommand*{\datagidx@person}[2]{#2\noexpand\datatoolpersoncomma #1}
\newcommand*{\datagidx@place}[2]{#2\noexpand\datatoolplacecomma #1}
\newcommand*{\datagidx@subject}[2]{#2\noexpand\datatoolsubjectcomma #1}
\newcommand*{\datagidx@paren}[1]{\noexpand\datatoolparenstart #1}
\newcommand*{\datagidx@invert}[2]{#2, #1}
\newcommand*{\DTLgidxParen}[1]{\space(#1)}
\newcommand*{\datagidxwordifygreek}{%
  \def\alpha{alpha}%
  \def\beta{beta}%
  \def\gamma{gamma}%
  \def\delta{delta}%
  \def\epsilon{epsilon}%
  \def\varepsilon{epsilon}%
  \def\zeta{zeta}%
  \def\eta{eta}%
  \def\theta{theta}%
  \def\vartheta{theta}%
  \def\iota{iota}%
  \def\kappa{kappa}%
  \def\lambda{lambda}%
  \def\mu{mu}%
  \def\nu{nu}%
  \def\xi{xi}%
  \def\pi{pi}%
  \def\varpi{pi}%
  \def\rho{rho}%
  \def\varrho{rho}%
  \def\sigma{sigma}%
  \def\varsigma{sigma}%
  \def\tau{tau}%
  \def\upsilon{upsilon}%
  \def\phi{phi}%
  \def\varphi{phi}%
  \def\chi{chi}%
  \def\psi{psi}%
  \def\omega{omega}%
  \def\Gamma{Gamma}%
  \def\Delta{Delta}%
  \def\Theta{Theta}%
  \def\Lambda{Lambda}%
  \def\Xi{Xi}%
  \def\Pi{Pi}%
  \def\Sigma{Sigma}%
  \def\Upsilon{Upsilon}%
  \def\Phi{Phi}%
  \def\Psi{Psi}%
  \def\Omega{Omega}%
}
\newcommand{\datagidxextendedtoascii}{%
  \def\AE{AE}%
  \def\ae{ae}%
  \def\OE{OE}%
  \def\oe{oe}%
  \def\AA{AA}%
  \def\aa{aa}%
  \def\L{L}%
  \def\l{l}%
  \def\O{O}%
  \def\o{o}%
  \def\SS{SS}%
  \def\ss{ss}%
  \def\th{th}%
  \def\TH{TH}%
  \def\dh{dh}%
  \def\DH{DH}%
}
\newcommand*{\datagidxconvertchars}{%
  \let~\space
  \ifdef\andname
  {%
    \let\&\andname
  }%
  {%
    \def\&{\expandafter\@gobble\string\&}%
  }%
  \def\_{\string_}%
  \def\${\string$}%
  \def\#{\expandafter\@gobble\string\#}%
  \def\%{\expandafter\@gobble\string\%}%
  \def\{{\expandafter\@gobble\string\{}%
  \def\}{\expandafter\@gobble\string\}}%
}
\@ifl@t@r\fmtversion{2019/10/01}
{%
  \newcommand*{\datagidxstripaccents}{%
   \let\add@accent@\@secondoftwo
   \let\@text@composite@x\@secondoftwo
   \let\@tabacckludge\@secondoftwo
   \expandafter\def\csname \encodingdefault-cmd\endcsname##1##2##3{##3}%
   \expandafter\def\csname OT1-cmd\endcsname##1##2##3{##3}%
   \expandafter\def\csname T1-cmd\endcsname##1##2##3{##3}%
   \expandafter\def\csname PD1-cmd\endcsname##1##2##3{##3}%
   \def\IeC##1{\@gobbletwo##1}%
   \let\UTFviii@two@octets\UTFviii@two@octets@combine
  }%
}
{%
  \newcommand*{\datagidxstripaccents}{%
   \let\add@accent@\@secondoftwo
   \let\@text@composite@x\@secondoftwo
   \let\@tabacckludge\@secondoftwo
   \expandafter\def\csname \encodingdefault-cmd\endcsname##1##2##3{##3}%
   \expandafter\def\csname OT1-cmd\endcsname##1##2##3{##3}%
   \expandafter\def\csname T1-cmd\endcsname##1##2##3{##3}%
   \expandafter\def\csname PD1-cmd\endcsname##1##2##3{##3}%
   \def\IeC##1{\@gobbletwo##1}%
  }%
}
\providecommand{\newterm}{\datagidx@newterm}
\@onlypreamble\newterm
\newcommand{\datagidx@setfieldvalues}[2]{%
 \def\newterm@name{#2}%
 \renewcommand*\newterm@label{#2}%
 \renewcommand*\newterm@text{#2}%
 \undef\newterm@plural
 \renewcommand*{\newterm@description}{}%
 \renewcommand*{\newterm@sort}{#2}%
 \renewcommand*{\newterm@symbol}{}%
 \let\newterm@database\datagidx@defaultdatabase
 \renewcommand*{\newterm@short}{#2}%
 \undef\newterm@shortplural
 \renewcommand*{\newterm@long}{#2}%
 \undef\newterm@longplural
 \renewcommand*{\newterm@see}{}%
 \renewcommand*{\newterm@seealso}{}%
 \renewcommand*{\newterm@parent}{}%
 \let\datagidx@orgfield\field
 \def\field##1{\expandafter\noexpand\csname newterm@##1\endcsname}%
 \newterm@defaultshook
 \let\field\datagidx@orgfield
 \setkeys{newterm}{#1}%
 \bgroup
   \let\glsadd\@gobble
   \let\MakeUppercase\DTLgidxNoFormat
   \let\MakeTextUppercase\DTLgidxNoFormat
   \let\MakeLowercase\DTLgidxNoFormat
   \let\MakeTextLowercase\DTLgidxNoFormat
   \let\acronymfont\DTLgidxNoFormat
   \let\textrm\DTLgidxNoFormat
   \let\texttt\DTLgidxNoFormat
   \let\textsf\DTLgidxNoFormat
   \let\textsc\DTLgidxNoFormat
   \let\textbf\DTLgidxNoFormat
   \let\textmd\DTLgidxNoFormat
   \let\textit\DTLgidxNoFormat
   \let\textsl\DTLgidxNoFormat
   \let\emph\DTLgidxNoFormat
   \let\textsuperscript\DTLgidxNoFormat
  \datagidxconvertchars
   \let\ensuremath\DTLgidxNoFormat
   \let\DTLgidxParen\@gobble
   \let\DTLgidxName\@secondoftwo
   \let\DTLgidxPlace\datagidx@invert
   \let\DTLgidxSubject\datagidx@invert
   \let\DTLgidxOffice\@secondoftwo
   \let\DTLgidxParticle\datagidx@bothoftwo
   \datagidxwordifygreek
   \datagidxstripaccents
   \datagidxextendedtoascii
   \newtermlabelhook
   \protected@xdef\newterm@label{\newterm@label}%
   \let\DTLgidxName\datagidx@person
   \let\DTLgidxPlace\datagidx@place
   \let\DTLgidxSubject\datagidx@subject
   \let\DTLgidxOffice\datagidx@person
   \let\DTLgidxParen\datagidx@paren
   \let\DTLgidxMac\datagidx@mac
   \let\DTLgidxSaint\datagidx@saint
   \let\DTLgidxIgnore\@gobble
   \let\DTLgidxRank\datagidx@rank
   \let\DTLgidxParticle\datagidx@particle
   \let\DTLgidxNameNum\datagidx@namenum
   \protected@xdef\newterm@sort{\newterm@sort}%
 \egroup
}
\newcommand*{\datagidx@add@term}[1]{%
  \global\cslet{datagidxentry@\newterm@label}{\newterm@database}%
  \DTLnewrow{\newterm@database}%
  \DTLnewdbentry{\newterm@database}{Name}{#1}%
  \DTLnewdbentry{\newterm@database}{Used}{0}%
  {%
    \dtlexpandnewvalue
    \DTLnewdbentry{\newterm@database}{Text}{\expandonce\newterm@text}%
    \DTLnewdbentry{\newterm@database}{Description}{\expandonce\newterm@description}%
    \DTLnewdbentry{\newterm@database}{Label}{\expandonce\newterm@label}%
    \DTLnewdbentry{\newterm@database}{Sort}{\expandonce\newterm@sort}%
    \DTLnewdbentry{\newterm@database}{Symbol}{\expandonce\newterm@symbol}%
    \DTLnewdbentry{\newterm@database}{Short}{\expandonce\newterm@short}%
    \DTLnewdbentry{\newterm@database}{Long}{\expandonce\newterm@long}%
    \ifundef\newterm@plural
    {%
      \DTLnewdbentry{\newterm@database}{Plural}{\expandonce\newterm@text s}%
    }%
    {%
      \DTLnewdbentry{\newterm@database}{Plural}{\expandonce\newterm@plural}%
    }%
    \ifundef\newterm@shortplural
    {%
      \DTLnewdbentry{\newterm@database}{ShortPlural}{\expandonce\newterm@short s}%
    }%
    {%
      \DTLnewdbentry{\newterm@database}{ShortPlural}{\expandonce\newterm@shortplural}%
    }%
    \ifundef\newterm@longplural
    {%
      \DTLnewdbentry{\newterm@database}{LongPlural}{\expandonce\newterm@long s}%
    }%
    {%
      \DTLnewdbentry{\newterm@database}{LongPlural}{\expandonce\newterm@longplural}%
    }%
    \ifdefempty{\newterm@see}%
     {}%
     {\DTLnewdbentry{\newterm@database}{See}{\newterm@see}}%
    \ifdefempty{\newterm@seealso}%
     {}%
     {\DTLnewdbentry{\newterm@database}{SeeAlso}{\newterm@seealso}}%
    \newterm@extrafields
    \ifdefempty{\newterm@parent}%
     {}%
     {%
       \iftermexists{\newterm@parent}%
       {%
         \edef\newterm@parentdatabase{\csuse{datagidxentry@\newterm@parent}}%
         \ifthenelse{\equal{\newterm@parentdatabase}{\newterm@database}}
         {%
           \DTLnewdbentry{\newterm@database}{Parent}{\newterm@parent}%
           \datagidx@addchild{\newterm@database}{\newterm@parent}{\newterm@label}%
         }%
         {%
           \PackageError{datagidx}%
           {%
             Parent entry `\newterm@parent' must belong to the
             same database as child entry `\newterm@label'%
           }%
           {%
             Parent entry is in database
             `\newterm@parentdatabase' and child entry is in
             database `\newterm@database'%
           }%
         }%
       }%
       {%
         \PackageError{datagidx}%
         {%
            Can't assign parent to `\newterm@label':
            `\newterm@parent' doesn't exist%
         }%
         {}%
       }%
     }%
  }%
  \global\let\datagidxlastlabel\newterm@label
  \postnewtermhook
}%
\newcommand*{\postnewtermhook}{}
\newcommand*{\newtermfield}[1]{\csuse{newterm@#1}}
\newcommand{\ifnewtermfield}[3]{%
  \ifcsdef{newterm@#1}
  {%
    \ifcsempty{newterm@#1}{#3}{#2}%
  }%
  {%
    #3%
  }%
}
\newcommand{\datagidx@newterm}[2][]{%
  \datagidx@setfieldvalues{#1}{#2}%
  \DTLifdbexists{\newterm@database}%
  {%
    \iftermexists{\newterm@label}%
    {%
      \PackageError{datagidx}{Term `\newterm@label' already
        exists in database `\newterm@database'}{}%
    }%
    {%
       \datagidx@add@term{#2}%
    }%
  }%
  {%
    \PackageError{datagidx}%
    {Glossary/index data base `\newterm@database' doesn't exist}%
    {%
      You must define the glossary/index data base before you can
      add any terms to it.%
    }%
  }%
}
\newcommand{\datagidx@highopt@newterm}[2][]{%
  \datagidx@setfieldvalues{#1}{#2}%
  \DTLifdbexists{\newterm@database}%
  {%
    \edef\dtl@dogetrow{%
      \noexpand\dtlgetrowindex
      {\noexpand\dtl@rowidx}%
      {\newterm@database}%
      {%
        \dtlcolumnindex{\newterm@database}{Label}%
      }%
      {\newterm@label}}%
    \dtl@dogetrow
    \ifx\dtl@rowidx\dtlnovalue
       \datagidx@add@term{#2}%
       \csdef{datagidx@do@highopt@sort@\newterm@database}{\datagidx@sort}%
    \else
      \global\cslet{datagidxentry@\newterm@label}{\newterm@database}%
      \global\let\datagidxlastlabel\newterm@label
    \fi
  }%
  {%
    \PackageError{datagidx}%
    {Glossary/index data base `\newterm@database' doesn't exist}%
    {%
      You must define the glossary/index data base before you can
      add any terms to it.%
    }%
  }%
}
\newcommand*{\datagidx@addchild}[3]{%
  \edef\dtl@dogetrow{%
    \noexpand\dtlgetrowforvalue
    {#1}%
    {%
      \dtlcolumnindex{\newterm@database}{Label}%
    }%
    {#2}}%
  \dtl@dogetrow
  \dtlgetentryfromcurrentrow
    {\datagidx@child}%
    {\dtlcolumnindex{#1}{Child}}%
  \ifx\datagidx@child\dtlnovalue
    \edef\datagidx@child{#3}%
  \else
    \edef\datagidx@child{\datagidx@child,#3}%
  \fi
  \edef\do@update{\noexpand\dtlupdateentryincurrentrow
    {Child}{\datagidx@child}}%
  \do@update
  \dtlrecombine
}
\newcommand{\newacro}[3][]{%
  \newterm
    [%
      description={\capitalisewords{#3}},%
      short={\acronymfont{#2}},%
      long={#3},%
      text={\DTLgidxAcrStyle{#3}{\acronymfont{#2}}},%
      plural={\DTLgidxAcrStyle{#3s}{\acronymfont{#2s}}},%
      sort={#2},%
      #1%
    ]%
    {\MakeTextUppercase{#2}}%
}
\newcommand*{\acronymfont}[1]{#1}
\newcommand*{\DTLgidxAcrStyle}[2]{#1 (#2)}
\newcommand{\iftermexists}[3]{%
  \ifcsdef{datagidxentry@#1}{#2}{#3}%
}
\newcommand*{\datagidxdb}[1]{%
  \csuse{datagidxentry@#1}%
}
\newcommand*{\ifentryused}[3]{%
  \letcs{\newterm@database}{datagidxentry@#1}%
  \edef\dtl@dogetrow{%
    \noexpand\dtlgetrowforvalue
    {\newterm@database}%
    {%
      \dtlcolumnindex{\newterm@database}{Label}%
    }%
    {#1}}%
  \dtl@dogetrow
  \dtlgetentryfromcurrentrow
    {\datagidx@value}%
    {\dtlcolumnindex{\newterm@database}{Used}}%
  \ifnum\datagidx@value=1\relax
    #2%
  \else
    #3%
  \fi
}
\newcommand*{\glsreset}[1]{%
  \letcs{\newterm@database}{datagidxentry@#1}%
  \edef\do@getrow{%
    \noexpand\dtlgetrowforvalue
    {\newterm@database}%
    {\dtlcolumnindex{\newterm@database}{Label}}%
    {#1}%
  }%
  \do@getrow
  \dtlreplaceentryincurrentrow
    {0}{\dtlcolumnindex{\newterm@database}{Used}}%
  \dtlrecombine
}
\newcommand*{\glsunset}[1]{%
  \letcs{\newterm@database}{datagidxentry@#1}%
  \edef\do@getrow{%
    \noexpand\dtlgetrowforvalue
    {\newterm@database}%
    {\dtlcolumnindex{\newterm@database}{Label}}%
    {#1}%
  }%
  \do@getrow
  \dtlreplaceentryincurrentrow
    {1}{\dtlcolumnindex{\newterm@database}{Used}}%
  \dtlrecombine
}
\newcommand*{\glsresetall}[1]{%
  \def\datagidx@list{}%
  \dtlforcolumn{\datagidx@label}{#1}{Label}%
  {%
    \ifdefempty\datagidx@list
    {%
      \let\datagidx@list\datagidx@label
    }%
    {%
      \eappto\datagidx@list{,\datagidx@label}%
    }%
  }%
  \@for\datagidx@thislabel:=\datagidx@list\do
  {%
    \glsreset{\datagidx@thislabel}%
  }%
}
\newcommand*{\glsunsetall}[1]{%
  \def\datagidx@list{}%
  \dtlforcolumn{\datagidx@label}{#1}{Label}%
  {%
    \ifdefempty\datagidx@list
    {%
      \let\datagidx@list\datagidx@label
    }%
    {%
      \eappto\datagidx@list{,\datagidx@label}%
    }%
  }%
  \@for\datagidx@thislabel:=\datagidx@list\do
  {%
    \glsunset{\datagidx@thislabel}%
  }%
}
\newcount\datagidx@anchorcount
\newcommand*{\datagidx@formatanchor}[1]{%
  \ifnum#1<10000
   0%
    \ifnum#1<1000
     0%
      \ifnum#1<100
       0%
        \ifnum#1<10
         0%
        \fi
      \fi
    \fi
  \fi
  \number#1%
}
\newcommand*{\@datagidx@escloc}[2]{%
  \expandafter\string\csname#1\endcsname{\noexpand\number#2}%
}
\newcommand*{\datagidx@escapelocation}{%
  \def\@arabic{\@datagidx@escloc{@arabic}}%
  \def\@roman{\@datagidx@escloc{@roman}}%
  \def\@Roman{\@datagidx@escloc{@Roman}}%
  \def\@alph{\@datagidx@escloc{@alph}}%
  \def\@Alph{\@datagidx@escloc{@Alph}}%
}
\newcommand*{\datagidx@escapelocationformat}{%
  \def\@arabic##1{arabic}%
  \def\@roman##1{roman}%
  \def\@Roman##1{Roman}%
  \def\@alph##1{alph}%
  \def\@Alph##1{Alph}%
}
\newcommand*{\datagidx@clearlocationformat}{%
  \let\@arabic\@firstofone
  \let\@roman\@firstofone
  \let\@Roman\@firstofone
  \let\@alph\@firstofone
  \let\@Alph\@firstofone
}
\newcommand*{\DTLgidxAddLocationType}[1]{%
  \gappto\datagidx@escapelocation{%
    \expandafter\def\csname#1\endcsname{\@datagidx@escloc{#1}}%
  }%
  \gappto\datagidx@escapelocationformat{%
    \expandafter\def\csname#1\endcsname##1{#1}%
  }%
  \gappto\datagidx@clearlocationformat{%
    \expandafter\let\csname#1\endcsname\@firstofone
  }%
}
\@onlypreamble\DTLgidxAddLocationType
\newcommand*{\datagidx@target}[4]{%
  \global\advance\datagidx@anchorcount by 1\relax
  \edef\@datagidx@target{datagidx.\datagidx@formatanchor\datagidx@anchorcount}%
  \ifstrempty{#3}
  {%
    \datagidx@write@usedentry{#1}{}%
  }%
  {%
    \bgroup
      \datagidx@escapelocation
      \def\@arabic{\noexpand\@arabic}%
      \def\@roman{\noexpand\@roman}%
      \def\@Roman{\noexpand\@Roman}%
      \def\@alph{\noexpand\@alph}%
      \def\@Alph{\noexpand\@Alph}%
      \protected@edef\@datagidx@dowriteaux{%
         \noexpand\datagidx@write@usedentry{#1}%
              {[#2]{#3}{\@datagidx@target}}%
      }%
      \@datagidx@dowriteaux
    \egroup
  }%
  \ifdef\hypertarget
  {%
    \datagidxshowifdraft
    {%
      [\@datagidx@target]%
      \discretionary{}{}{}%
    }%
    \bgroup
      \let\glsadd\@gobble
      \settoheight\dimen@{#4}%
      \raisebox{\dimen@}%
      {%
        \datagidxtarget{\@datagidx@target}{}%
      }%
    \egroup
  }%
  {%
  }%
  \datagidxshowifdraft{[#1]\discretionary{}{}{}}%
  #4%
}
\DeclareRobustCommand*{\glsdispentry}[2]{%
  \DTLgidxFetchEntry{\datagidx@dispentryval}{#1}{#2}%
  \datagidx@dispentryval
}
\DeclareRobustCommand*{\Glsdispentry}[2]{%
  \DTLgidxFetchEntry{\datagidx@dispentryval}{#1}{#2}%
  \xmakefirstuc\datagidx@dispentryval
}
\newcommand*{\DTLgidxFetchEntry}[3]{%
  \ifcsdef{datagidxentry@#2}%
  {%
  \letcs{\newterm@database}{datagidxentry@#2}%
  \edef\do@getrow{%
    \noexpand\dtlgetrowforvalue
    {\newterm@database}%
    {\dtlcolumnindex{\newterm@database}{Label}}%
    {#2}%
  }%
  \do@getrow
  \dtlgetentryfromcurrentrow
    {#1}%
    {\dtlcolumnindex{\newterm@database}{#3}}%
  }%
  {%
    \PackageError{datagidx}{No term `#2' defined}{}%
  }%
}
\newcommand*{\datagidx@parse@formatlabel}[1]{%
  \datagidx@parse@format@label@#1\@endparse@formatlabel@
}
\newcommand*\datagidx@parse@format@label@{%
  \@ifnextchar[{\datagidx@parse@formatlabel@}{\datagidx@parse@formatlabel@[]}%
}
\def\datagidx@parse@formatlabel@[#1]#2\@endparse@formatlabel@{%
  \def\datagidx@format{#1}%
  \def\datagidx@label{#2}%
}
\newcommand*{\@datagidx@use@entry}[1]{%
  \ifcsundef{datagidxentry@\datagidx@label}
  {%
    \PackageError{datagidx}{Entry `\datagidx@label' doesn't exist}{}%
  }%
  {%
    \letcs{\newterm@database}{datagidxentry@\datagidx@label}%
    \edef\do@getrow{%
      \noexpand\dtlgetrowforvalue
      {\newterm@database}%
      {\dtlcolumnindex{\newterm@database}{Label}}%
      {\datagidx@label}%
    }%
    \do@getrow
    \dtlgetentryfromcurrentrow
      {\datagidx@id}%
      {\dtlcolumnindex{\newterm@database}{FirstId}}%
    \DTLifnull\datagidx@id
    {%
      \count@=\datagidx@anchorcount\relax
      \advance\count@ by 1\relax
      \dtlappendentrytocurrentrow{FirstId}{\datagidx@formatanchor\count@}%
    }%
    {}%
    \dtlreplaceentryincurrentrow
      {1}{\dtlcolumnindex{\newterm@database}{Used}}%
    \dtlgetentryfromcurrentrow
      {\datagidx@parent}%
      {\dtlcolumnindex{\newterm@database}{Parent}}%
    \dtlrecombine
    \datagidx@markparent{\newterm@database}{\datagidx@parent}%
    \datagidx@target{\datagidx@label}{\datagidx@format}%
      {\csuse{the\DTLgidxCounter}}{#1}%
  }%
}
\newcommand*{\DTLgidxCounter}{page}
\newcommand*{\datagidx@markparent}[2]{%
  \ifx#2\dtlnovalue
  \else
    \datagidx@target{#2}{}{}{}%
    \edef\do@getrow{%
      \noexpand\dtlgetrowforvalue
      {#1}%
      {\dtlcolumnindex{#1}{Label}}%
      {#2}}%
    \do@getrow
  \dtlgetentryfromcurrentrow
    {\datagidx@id}%
    {\dtlcolumnindex{\newterm@database}{FirstId}}%
  \DTLifnull\datagidx@id
  {%
    \dtlappendentrytocurrentrow{FirstId}{\datagidx@formatanchor\datagidx@anchorcount}%
  }%
  {}%
    \dtlgetentryfromcurrentrow
      {\datagidx@parent}%
      {\dtlcolumnindex{#1}{Parent}}%
  \dtlrecombine
    \datagidx@markparent{#1}{\datagidx@parent}%
  \fi
}
\newcommand*{\datagidx@write@usedentry}[2]{%
  \datagidx@do@highopt@update{#1}%
  \protected@write{\@auxout}{}%
    {%
      \string\datagidx@usedentry{#1}{#2}%
    }%
  \protected@edef\datagidx@do@usedentry{%
    \noexpand\datagidx@xusedentry{CurrentLocation}{#1}{#2}%
  }%
  \expandafter\ifstrequal\expandafter{\DTLgidxCounter}{page}%
  {%
     \expandafter\afterpage\expandafter{\datagidx@do@usedentry}%
  }%
  {
    \datagidx@do@usedentry
  }%
}
\newcommand*{\datagidx@xusedentry}[3]{%
  \protected@edef\@datagidx@do@xusedentry{%
     \noexpand\datagidx@usedentry[#1]{#2}{#3}%
  }%
  \@datagidx@do@xusedentry
}
\newcommand*{\datagidx@usedentry}[3][Location]{%
  \ifcsundef{datagidxentry@#2}%
  {%
    \PackageWarning{datagidx}{No term `#2' defined. Ignoring}%
  }%
  {%
    \letcs{\newterm@database}{datagidxentry@#2}%
    \edef\do@getrow{%
      \noexpand\dtlgetrowforvalue
      {\newterm@database}%
      {\dtlcolumnindex{\newterm@database}{Label}}%
      {#2}%
    }%
    \do@getrow
    \dtlgetentryfromcurrentrow
      {\datagidx@loc}%
      {\dtlcolumnindex{\newterm@database}{#1}}%
    \ifx\datagidx@loc\dtlnovalue
      \def\datagidx@loc{#3}%
      \dtlappendentrytocurrentrow{#1}{\expandonce\datagidx@loc}%
    \else
      \ifdefempty{\datagidx@loc}%
      {%
        \def\datagidx@loc{#3}%
      }%
      {%
        \ifstrempty{#3}%
        {}%
        {%
          \appto\datagidx@loc{,#3}%
        }%
      }%
      \expandafter\dtlreplaceentryincurrentrow\expandafter
        {\datagidx@loc}%
        {\dtlcolumnindex{\newterm@database}{#1}}%
    \fi
    \dtlrecombine
  }%
}
\newcommand*{\datagidx@save@loc}[2]{%
 \bgroup
  \datagidx@escapelocation
  \xdef\datagidx@tmp{#2}%
 \egroup
 \expandafter\xdef\csname datagidx@prev@loc@#1\endcsname{\datagidx@tmp}%
}
\newcommand*{\glsadd}[1]{%
  \NoCaseChange{\@glsadd{#1}}%
}
\DeclareRobustCommand*{\@glsadd}[1]{%
  \ifcsundef{datagidxentry@\datagidx@label}%
  {%
    \PackageError{datagidx}{Term `\datagidx@label' doesn't exist}{}%
  }%
  {%
    \datagidx@parse@formatlabel{#1}%
    \datagidx@target{\datagidx@label}{\datagidx@format}%
      {\csuse{the\DTLgidxCounter}}{}%
    \letcs{\newterm@database}{datagidxentry@\datagidx@label}%
    \edef\do@getrow{%
      \noexpand\dtlgetrowforvalue
      {\newterm@database}%
      {\dtlcolumnindex{\newterm@database}{Label}}%
      {\datagidx@label}%
    }%
    \do@getrow
    \dtlreplaceentryincurrentrow
      {1}{\dtlcolumnindex{\newterm@database}{Used}}%
    \dtlgetentryfromcurrentrow
      {\datagidx@id}%
      {\dtlcolumnindex{\newterm@database}{FirstId}}%
    \DTLifnull\datagidx@id
    {%
      \dtlappendentrytocurrentrow{FirstId}{\datagidx@formatanchor\datagidx@anchorcount}%
    }%
    {}%
    \dtlrecombine
  }%
}
\newcount\datagidx@count
\newcommand*{\glsaddall}[1]{%
  \DTLifdbexists{#1}%
  {%
     \edef\datagidx@rowcount{\number\DTLrowcount{#1}}%
     \datagidx@count=0\relax
     \loop
       \advance\datagidx@count by 1\relax
       \dtlgetrow{#1}{\datagidx@count}%
      \dtlgetentryfromcurrentrow
        {\datagidx@label}%
        {\dtlcolumnindex{#1}{Label}}%
    \bgroup
      \undef\hypertarget
      \datagidx@target{\datagidx@label}{}{}{}%
    \egroup
       \dtlreplaceentryincurrentrow
        {1}{\dtlcolumnindex{#1}{Used}}%
      \dtlgetentryfromcurrentrow
        {\datagidx@id}%
        {\dtlcolumnindex{#1}{FirstId}}%
      \DTLifnull\datagidx@id
      {%
        \dtlappendentrytocurrentrow{FirstId}{\datagidx@formatanchor\datagidx@anchorcount}%
      }%
      {}%
      \dtlrecombine
     \ifnum\datagidx@count<\datagidx@rowcount
     \repeat
  }%
  {%
    \PackageError{datagidx}{Database `#1' doesn't exist}{}%
  }%
}
\DeclareRobustCommand*{\glslink}[2]{%
  \datagidx@parse@formatlabel{#1}%
  \datagidxlink{\datagidx@label}%
  {%
    \@datagidx@use@entry{#2}%
  }%
}
\DeclareRobustCommand*{\useentry}[2]{%
  \datagidx@parse@formatlabel{#1}%
  \DTLgidxFetchEntry{\datagidx@value}{\datagidx@label}{#2}%
  \datagidxlink{\datagidx@label}%
  {%
    \@datagidx@use@entry{\datagidx@value}%
  }%
}
\DeclareRobustCommand*{\Useentry}[2]{%
  \datagidx@parse@formatlabel{#1}%
  \DTLgidxFetchEntry{\datagidx@value}{\datagidx@label}{#2}%
  \datagidxlink{\datagidx@label}%
  {%
    \@datagidx@use@entry{\xmakefirstuc{\datagidx@value}}%
  }%
}
\DeclareRobustCommand*{\USEentry}[2]{%
  \datagidx@parse@formatlabel{#1}%
  \DTLgidxFetchEntry{\datagidx@value}{\datagidx@label}{#2}%
  \datagidxlink{\datagidx@label}%
  {%
    \@datagidx@use@entry{\MakeTextUppercase{\datagidx@value}}%
  }%
}
\DeclareRobustCommand*{\useentrynl}[2]{%
  \datagidx@parse@formatlabel{#1}%
  \DTLgidxFetchEntry{\datagidx@value}{\datagidx@label}{#2}%
  \@datagidx@use@entry{\datagidx@value}%
}
\DeclareRobustCommand*{\Useentrynl}[2]{%
  \datagidx@parse@formatlabel{#1}%
  \DTLgidxFetchEntry{\datagidx@value}{\datagidx@label}{#2}%
  \@datagidx@use@entry{\xmakefirstuc{\datagidx@value}}%
}
\DeclareRobustCommand*{\USEentrynl}[2]{%
  \datagidx@parse@formatlabel{#1}%
  \DTLgidxFetchEntry{\datagidx@value}{\datagidx@label}{#2}%
  \@datagidx@use@entry{\MakeTextUppercase{\datagidx@value}}%
}
\DeclareRobustCommand*{\gls}[1]{\useentry{#1}{Text}}
\DeclareRobustCommand*{\glspl}[1]{\useentry{#1}{Plural}}
\DeclareRobustCommand*{\Gls}[1]{\Useentry{#1}{Text}}
\DeclareRobustCommand*{\Glspl}[1]{\Useentry{#1}{Plural}}
\DeclareRobustCommand*{\glsnl}[1]{\useentrynl{#1}{Text}}
\DeclareRobustCommand*{\glsplnl}[1]{\useentrynl{#1}{Plural}}
\DeclareRobustCommand*{\Glsnl}[1]{\Useentrynl{#1}{Text}}
\DeclareRobustCommand*{\Glsplnl}[1]{\Useentrynl{#1}{Plural}}
\DeclareRobustCommand*{\glssym}[1]{\useentry{#1}{Symbol}}
\DeclareRobustCommand*{\Glssym}[1]{\Useentry{#1}{Symbol}}
\newcommand*{\DTLgidxFormatAcr}[3]{%
  \DTLgidxAcrStyle{\glsdispentry{#1}{#2}}{\useentry{#1}{#3}}%
}
\newcommand*{\DTLgidxFormatAcrUC}[3]{%
  \DTLgidxAcrStyle{\Glsdispentry{#1}{#2}}{\useentry{#1}{#3}}%
}
\DeclareRobustCommand*{\acr}[1]{%
  \ifentryused{#1}%
  {\useentry{#1}{Short}}%
  {\DTLgidxFormatAcr{#1}{Long}{Short}}%
}
\DeclareRobustCommand*{\acrpl}[1]{%
  \ifentryused{#1}%
  {\useentry{#1}{ShortPlural}}%
  {\DTLgidxFormatAcr{#1}{LongPlural}{ShortPlural}}%
}
\DeclareRobustCommand*{\Acr}[1]{%
  \ifentryused{#1}%
  {\Useentry{#1}{Short}}%
  {\DTLgidxFormatAcrUC{#1}{Long}{Short}}%
}
\DeclareRobustCommand*{\Acrpl}[1]{%
  \ifentryused{#1}%
  {\Useentry{#1}{ShortPlural}}%
  {\DTLgidxFormatAcrUC{#1}{LongPlural}{ShortPlural}}%
}
\define@key{printterms}{database}{\renewcommand*{\newterm@database}{#1}}
\define@choicekey{printterms}{postdesc}[\val\nr]%
 {none,dot}%
 {%
   \datagidx@setpostdesc\nr
 }
\define@choicekey{printterms}{prelocation}[\val\nr]%
 {none,enspace,space,dotfill,hfill}%
 {%
   \datagidx@setprelocation\nr
 }
\define@choicekey{printterms}{location}[\val\nr]%
 {hide,list,first}%
 {\datagidx@setlocation\nr}
\define@choicekey{printterms}{symboldesc}[\val\nr]%
 {symbol,desc,(symbol) desc,desc (symbol),symbol desc,desc symbol}%
 {\datagidx@formatsymdesc\nr}
\define@key{printterms}{columns}%
{%
  \DTLgidxSetColumns{#1}%
}
\define@choicekey{printterms}{namecase}[\val\nr]%
 {nochange,uc,lc,firstuc,capitalise}%
 {%
   \datagidx@setnamecase\nr
 }
\define@key{printterms}{namefont}%
{%
  \renewcommand*{\DTLgidxNameFont}[1]{{#1{##1}}}%
}
\define@key{printterms}{postname}
{%
  \renewcommand*{\DTLgidxPostName}{#1}%
}
\define@choicekey{printterms}{see}[\val\nr]%
  {comma,brackets,dot,space,nosep,semicolon,location}%
  {\datagidx@setsee\nr}
\define@choicekey{printterms}{child}[\val\nr]%
 {named,noname}%
 {%
   \datagidx@setchildstyle\nr
 }
\define@key{printterms}{symbolwidth}%
{%
  \setlength{\datagidxsymbolwidth}{#1}%
}
\define@key{printterms}{locationwidth}%
{%
  \setlength{\datagidxlocationwidth}{#1}%
}
\define@choicekey{printterms}{childsort}[\val\nr]%
 {true,false}[true]%
 {%
   \datagidx@setchildsort\nr
 }
\define@choicekey{printterms}{showgroups}{true,false}[true]{%
 \appto\newterm@styles{showgroups={#1},}%
}
\define@key{printterms}{style}{\appto\newterm@styles{style={#1},}}
\define@key{printterms}{heading}{\appto\newterm@styles{heading={#1},}}
\define@key{printterms}{postheading}{%
  \appto\newterm@styles{postheading={#1},}%
}
\define@key{printterms}{sort}{\appto\newterm@styles{sort={#1},}}
\define@choicekey{printterms}{balance}[\val\nr]{true,false}[true]{%
  \ifcase\nr\relax
    \appto\newterm@styles{balance=true,}%
  \or
    \appto\newterm@styles{balance=false,}%
  \fi
}
\newcommand*{\printterms@condition}{\boolean{true}}
\define@key{printterms}{condition}{\renewcommand*{\printterms@condition}{#1}}
\newcommand{\printtermsstartpar}{\par}
\newcommand*{\printterms@setupmulticol}{%
 \ifdefempty\datagidx@postheading
 {%
   \edef\datagidx@prestart{%
    \noexpand\datagidx@heading{\noexpand\datagidx@title}%
    \noexpand\begin{\datagidx@multicols}{\datagidx@columns}%
   }%
 }%
 {%
   \edef\datagidx@prestart{%
    \noexpand\datagidx@heading{\noexpand\datagidx@title}%
    \noexpand\begin{\datagidx@multicols}{\datagidx@columns}%
    [\noexpand\datagidx@postheading]%
   }%
 }%
 \edef\datagidx@postend{%
  \noexpand\end{\datagidx@multicols}%
 }%
}
\newcommand*{\printterms@setuptwocol}{%
 \def\datagidx@prestart{%
   \twocolumn[\datagidx@heading{\datagidx@title}%
   \datagidx@postheading]}%
 \if@twocolumn
   \def\datagidx@postend{}%
 \else
   \def\datagidx@postend{\printtermsrestoreonecolumn}%
 \fi
}
\newcommand{\printtermsrestoreonecolumn}{\onecolumn}
\newcommand{\printterms}[1][]{%
\bgroup
  \let\newterm@database\datagidx@defaultdatabase
  \let\newterm@styles\@empty
  \setkeys{printterms}{#1}%
  \DTLifdbexists{\newterm@database}%
  {%
    \edef\DTLgidxCurrentdb{\newterm@database}%
    \edef\do@getrow{\noexpand\dtlgetrowforvalue
      {datagidx}%
      {\dtlcolumnindex{datagidx}{Glossary}}%
      {\newterm@database}%
    }%
    \do@getrow
    \dtlgetentryfromcurrentrow
      {\datagidx@title}%
      {\dtlcolumnindex{datagidx}{Title}}%
    \dtlgetentryfromcurrentrow
      {\datagidx@heading}%
      {\dtlcolumnindex{datagidx}{Heading}}%
    \dtlgetentryfromcurrentrow
      {\datagidx@postheading}%
      {\dtlcolumnindex{datagidx}{PostHeading}}%
    \dtlgetentryfromcurrentrow
      {\datagidx@multicols}%
      {\dtlcolumnindex{datagidx}{MultiCols}}%
    \dtlgetentryfromcurrentrow
      {\datagidx@sort}%
      {\dtlcolumnindex{datagidx}{Sort}}%
    \dtlgetentryfromcurrentrow
      {\datagidx@style}%
      {\dtlcolumnindex{datagidx}{Style}}%
    \dtlgetentryfromcurrentrow
      {\datagidx@showgroups}%
      {\dtlcolumnindex{datagidx}{ShowGroups}}%
    \edef\dtl@do@setkeys{\noexpand\setkeys{newgloss}{\expandonce\newterm@styles}}%
    \dtl@do@setkeys
    \ifnum\datagidx@columns>1\relax
       \ifnum\datagidx@columns=2\relax
        \ifdatagidxbalance
          \printterms@setupmulticol
        \else
          \printterms@setuptwocol
        \fi
       \else
          \printterms@setupmulticol
       \fi
    \else
       \def\datagidx@prestart{}%
       \def\datagidx@postend{}%
    \fi
    \let\@dtl@dbname\DTLgidxCurrentdb
    \csuse{datagidxshowgroups\datagidx@showgroups}%
    \datagidxsetstyle{\datagidx@style}%
    \def\datagidx@labellist{}%
    \ifnum\datagidx@columns=1\relax
      \datagidx@heading{\datagidx@title}%
      \datagidx@postheading
    \fi
    \datagidx@do@sort
    \datagidx@prestart
    \printtermsstartpar
    \datagidxstart
    \let\DTLgidxName\datagidx@invert
    \let\DTLgidxPlace\datagidx@invert
    \let\DTLgidxSubject\datagidx@invert
    \let\DTLgidxOffice\datagidx@invert
      \DTLgidxForeachEntry
      {%
         \datagidxitem
      }%
    \datagidxend
    \datagidx@postend
  }%
  {%
    \PackageError{datagidx}%
    {Glossary/index data base `\newterm@database' doesn't exist}%
    {%
      You must define the glossary/index data base before you can
      use it.%
    }%
  }%
\egroup
}
\def\datagidx@getgroup#1#2\datagidx@endgetgroup{%
  \dtl@setcharcode{#1}{\count@}%
  \dtlifintclosedbetween{\count@}{48}{57}%
  {%
    \gdef\datagidxcurrentgroup{Numbers}%
  }%
  {%
    \dtlifintclosedbetween{\count@}{97}{122}%
    {%
      \advance\count@ by -96\relax
      \xdef\datagidxcurrentgroup{\@Alph\count@}%
    }%
    {%
      \dtlifintclosedbetween{\count@}{65}{90}%
      {%
        \gdef\datagidxcurrentgroup{#1}%
      }%
      {%
        \gdef\datagidxcurrentgroup{Symbols}%
      }%
    }%
  }%
}
\newcommand*{\DTLgidxGroupHeaderTitle}[1]{%
  \ifcsdef{datagidx#1name}
  {%
    \csuse{datagidx#1name}%
  }%
  {%
    #1%
  }%
}
\newcommand{\DTLgidxForeachEntry}[1]{%
  \def\datagidxprevgroup{}%
  \edef\datagidx@doforeachentry{%
    \noexpand\DTLforeach*[\expandonce\printterms@condition]{\DTLgidxCurrentdb}%
     {\expandonce\DTLgidxAssignList}
  }%
  \datagidx@doforeachentry
  {%
    \DTLifnull{\Parent}%
    {%
      \DTLifnull\Location
      {%
         \DTLifnull\CurrentLocation
         {%
         }%
         {%
           \global\let\@datagidx@dorerun@warn\@data@rerun@warn
         }%
      }%
      {%
        \ifcsdef{datagidx@prev@loc@\Label}%
        {%
          \dtlgidx@checklocationchange
        }%
        {%
           \global\let\@datagidx@dorerun@warn\@data@rerun@warn
        }%
      }%
      \datagidx@doifdisplayed
      {%
        \edef\datagidx@dowrite{%
          \noexpand\protected@write\noexpand\@auxout{}%
          {%
            \string\datagidx@save@loc{\Label}{\CurrentLocation}%
          }%
        }%
        \datagidx@dowrite
        \datagidx@level=1\relax
        \expandafter\datagidx@getgroup\Sort{}\datagidx@endgetgroup
        #1%
        \global\let\datagidxprevgroup\datagidxcurrentgroup
      }%
    }%
    {}%
  }%
}
\newcommand*{\dtlgidx@checklocationchange}{%
  \protected@edef\@prev@location{%
     \csname datagidx@prev@loc@\Label\endcsname}%
  \@onelevel@sanitize\@prev@location
  \protected@edef\@cur@location{\CurrentLocation}%
  \@onelevel@sanitize\@cur@location
  \ifdefequal{\@prev@location}{\@cur@location}%
  {}%
  {%
    \global\let\@datagidx@dorerun@warn\@data@rerun@warn
  }%
}
\newcommand{\datagidx@doifdisplayed}[1]{%
  \DTLifnull{\Location}%
  {%
     \DTLifnull{\See}
     {%
       \DTLifnull{\SeeAlso}{}%
       {%
         #1%
       }%
     }%
     {%
       \@for\dtl@thislabel:=\See\do
       {%
           \iftermexists{\dtl@thislabel}%
           {%
             \ifentryused{\dtl@thislabel}%
             {%
               #1%
               \@endfortrue
             }%
             {}%
           }%
           {%
           }%
       }%
     }%
  }%
  {%
    #1%
  }%
}%
\newcount\datagidx@level
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</datagidx-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datapie-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\subsection{Rollback v2.32 (datapie-2019-09-27.sty)}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{datapie}[2019/09/27 v2.32 (NLCT)]
\RequirePackage{xkeyval}
\newif\ifDTLcolorpiechart
\DTLcolorpiecharttrue
\DeclareOption{color}{\DTLcolorpiecharttrue}
\DeclareOption{gray}{\DTLcolorpiechartfalse}
\define@boolkey{datapie}[DTL]{rotateinner}[true]{}
\define@boolkey{datapie}[DTL]{rotateouter}[true]{}
\DTLrotateinnerfalse
\DTLrotateouterfalse
\DeclareOption{rotateinner}{\DTLrotateinnertrue}
\DeclareOption{norotateinner}{\DTLrotateinnerfalse}
\DeclareOption{rotateouter}{\DTLrotateoutertrue}
\DeclareOption{norotateouter}{\DTLrotateouterfalse}
\ProcessOptions
\RequirePackage{datatool}[=v2.32]
\RequirePackage{tikz}
\newlength\DTLradius
\DTLradius=2cm
\newcommand*{\DTLinnerratio}{0.5}
\newcommand*{\DTLouterratio}{1.25}
\newcommand*\DTLcutawayratio{0.2}
\newcommand*{\DTLstartangle}{0}
\newlength\dtl@inneroffset
\dtl@inneroffset=\DTLinnerratio\DTLradius
\newlength\dtl@outeroffset
\dtl@outeroffset=\DTLouterratio\DTLradius
\newlength\dtl@cutawayoffset
\dtl@cutawayoffset=\DTLcutawayratio\DTLradius
\newcommand*{\dtl@piecutaways}{}
\def\dtl@innerlabel{\DTLpievariable}%
\def\dtl@outerlabel{}%
\newcounter{DTLpieroundvar}
\setcounter{DTLpieroundvar}{1}
\newcommand*{\DTLdisplayinnerlabel}[1]{#1}
\newcommand*{\DTLdisplayouterlabel}[1]{#1}
\newcommand*{\DTLpiepercent}{%
\ifnum\dtlforeachlevel=0\relax
  \PackageError{datapie}{Can't use
  \string\DTLpiepercent\space outside
  \string\DTLpiechart}{}%
\else
  \csname dtl@piepercent@\romannumeral\@dtl@seg\endcsname
\fi}
\newcommand*{\DTLpieatbegintikz}{}
\newcommand*{\DTLpieatendtikz}{}
\newcommand*{\DTLsetpiesegmentcolor}[2]{%
\expandafter\def\csname dtlpie@segcol\romannumeral#1\endcsname{#2}%
}
\newcommand*{\DTLgetpiesegmentcolor}[1]{%
\csname dtlpie@segcol\romannumeral#1\endcsname}
\newcommand*{\DTLdopiesegmentcolor}[1]{%
\expandafter\color\expandafter
{\csname dtlpie@segcol\romannumeral#1\endcsname}}
\newcommand*{\DTLdocurrentpiesegmentcolor}{%
\ifnum\dtlforeachlevel=0\relax
  \PackageError{datapie}{Can't use
  \string\DTLdocurrentpiesegmentcolor\space outside
  \string\DTLpiechart}{}%
\else
  \expandafter\DTLdopiesegmentcolor\expandafter{%
  \csname c@DTLrow\romannumeral\dtlforeachlevel\endcsname}%
\fi}
\newcommand*{\DTLpieoutlinecolor}{black}
\newlength\DTLpieoutlinewidth
\DTLpieoutlinewidth=0pt
\ifDTLcolorpiechart
 \DTLsetpiesegmentcolor{1}{red}
 \DTLsetpiesegmentcolor{2}{green}
 \DTLsetpiesegmentcolor{3}{blue}
 \DTLsetpiesegmentcolor{4}{yellow}
 \DTLsetpiesegmentcolor{5}{magenta}
 \DTLsetpiesegmentcolor{6}{cyan}
 \DTLsetpiesegmentcolor{7}{orange}
 \DTLsetpiesegmentcolor{8}{white}
\else
 \DTLsetpiesegmentcolor{1}{black!15}
 \DTLsetpiesegmentcolor{2}{black!25}
 \DTLsetpiesegmentcolor{3}{black!35}
 \DTLsetpiesegmentcolor{4}{black!45}
 \DTLsetpiesegmentcolor{5}{black!55}
 \DTLsetpiesegmentcolor{6}{black!65}
 \DTLsetpiesegmentcolor{7}{black!75}
 \DTLsetpiesegmentcolor{8}{black!85}
\fi
\define@key{datapie}{start}{\def\DTLstartangle{#1}}
\define@key{datapie}{radius}{\DTLradius=#1\relax
\dtl@inneroffset=\DTLinnerratio\DTLradius
\dtl@outeroffset=\DTLouterratio\DTLradius
\dtl@cutawayoffset=\DTLcutawayratio\DTLradius}
\define@key{datapie}{innerratio}{%
\def\DTLinnerratio{#1}%
\dtl@inneroffset=\DTLinnerratio\DTLradius}
\define@key{datapie}{outerratio}{%
\def\DTLouterratio{#1}%
\dtl@outeroffset=\DTLouterratio\DTLradius}
\define@key{datapie}{cutawayratio}{%
\def\DTLcutawayratio{#1}%
\dtl@cutawayoffset=\DTLcutawayratio\DTLradius}
\define@key{datapie}{inneroffset}{%
\dtl@inneroffset=#1}
\define@key{datapie}{outeroffset}{%
\dtl@outeroffset=#1}
\define@key{datapie}{cutawayoffset}{%
\dtl@cutawayoffset=#1}
\define@key{datapie}{cutaway}{%
\renewcommand*{\dtl@piecutaways}{#1}}
\define@key{datapie}{variable}{%
\def\DTLpievariable{#1}}
\define@key{datapie}{innerlabel}{%
\def\dtl@innerlabel{#1}}
\define@key{datapie}{outerlabel}{%
\def\dtl@outerlabel{#1}}
\newcommand*{\DTLpiechart}[4][\boolean{true}]{%
\bgroup
  \let\DTLpievariable=\relax
  \setkeys{datapie}{#2}%
  \ifx\DTLpievariable\relax
    \PackageError{datapie}%
    {\string\DTLpiechart\space missing variable}{}%
  \else
    \def\dtl@total{0}%
    \@sDTLforeach[#1]{#3}{#4}{%
      \let\dtl@oldtotal=\dtl@total
      \expandafter\DTLconverttodecimal\expandafter
        {\DTLpievariable}{\dtl@variable}%
      \dtladd{\dtl@total}{\dtl@variable}{\dtl@total}%
    }%
    \expandafter\DTLconverttodecimal\expandafter
      {\DTLstartangle}{\@dtl@start}%
    \@sDTLforeach[#1]{#3}{#4}{%
      \expandafter\DTLconverttodecimal\expandafter
        {\DTLpievariable}{\dtl@variable}%
      \dtl@computeangles
        {\csname c@DTLrow\romannumeral\dtlforeachlevel\endcsname}%
        {\dtl@variable}%
      \expandafter\@dtl@seg\expandafter=
        \csname c@DTLrow\romannumeral\dtlforeachlevel\endcsname%
      \dtlmul{\dtl@tmp}{\dtl@variable}{100}%
      \let\dtl@old=\dtl@tmp
      \dtldiv{\dtl@tmp}{\dtl@old}{\dtl@total}%
      \expandafter\dtlround
        \csname dtl@piepercent@\romannumeral\@dtl@seg\endcsname\dtl@tmp
        \c@DTLpieroundvar
    }%
    \@for\dtl@row:=\dtl@piecutaways\do{%
      \expandafter\@dtl@set@off\dtl@row-\relax
    }%
    \let\dtl@start=\DTLstartangle
    \begin{tikzpicture}
      \DTLpieatbegintikz
      \@sDTLforeach[#1]{#3}{#4}%
      {%
        \expandafter\@dtl@seg\expandafter=
        \csname c@DTLrow\romannumeral\dtlforeachlevel\endcsname%
        \edef\dtl@start{%
          \csname dtl@sang@\romannumeral\@dtl@seg\endcsname}%
        \edef\dtl@extent{%
          \csname dtl@angle@\romannumeral\@dtl@seg\endcsname}%
        \dtladd{\dtl@endangle}{\dtl@start}{\dtl@extent}%
        \edef\dtl@angle{%
           \csname dtl@cut@angle@\romannumeral\@dtl@seg\endcsname}%
        \let\dtl@old=\dtl@angle
        \dtl@truncatedecimal\dtl@angle
        \ifnum\dtl@angle>180\relax
          \dtlsub{\dtl@angle}{\dtl@old}{360}%
          \dtl@truncatedecimal\dtl@angle
        \fi
        \edef\dtl@cutlen{%
          \csname dtl@cut@len@\romannumeral\@dtl@seg\endcsname
        }%
        \edef\@dtl@shift{(\dtl@angle:\dtl@cutlen)}%
        \dtlmul{\dtl@angle}{\dtl@extent}{0.5}%
        \dtladd{\dtl@midangle}{\dtl@angle}{\dtl@start}%
        \begin{scope}[shift={\@dtl@shift}]%
        \fill[color=\DTLgetpiesegmentcolor\@dtl@seg] (0,0) --
        (\dtl@start:\DTLradius)
        arc (\dtl@start:\dtl@endangle:\DTLradius) -- cycle;
        \ifdim\DTLpieoutlinewidth>0pt\relax
          \draw[color=\DTLpieoutlinecolor,%
                line width=\DTLpieoutlinewidth]
          (0,0) -- (\dtl@start:\DTLradius)
          arc (\dtl@start:\dtl@endangle:\DTLradius) -- cycle;
        \fi
        \dtl@truncatedecimal\dtl@midangle
        \ifDTLrotateinner
          \dtlifnumopenbetween{\dtl@midangle}{90}{270}%
          {%
            \let\@dtl@next\@firstoftwo
          }%
          {%
             \dtlifnumlt{\dtl@midangle}{-90}%
             {\let\@dtl@next\@firstoftwo}%
             {\let\@dtl@next\@secondoftwo}%
          }%
          \@dtl@next
          {%
            \dtlsub{\dtl@labelangle}{\dtl@midangle}{180}%
            \dtl@truncatedecimal\dtl@labelangle
            \edef\dtl@innernodeopt{anchor=east,rotate=\dtl@labelangle}%
          }%
          {%
            \edef\dtl@innernodeopt{anchor=west,rotate=\dtl@midangle}%
          }%
        \else
          \edef\dtl@innernodeopt{anchor=center}%
        \fi
        \ifDTLrotateouter
           \dtlifnumopenbetween{\dtl@midangle}{90}{270}%
           {%
             \let\@dtl@next\@firstoftwo
           }%
           {%
              \dtlifnumlt{\dtl@midangle}{-90}%
              {\let\@dtl@next\@firstoftwo}%
              {\let\@dtl@next\@secondoftwo}%
           }%
           \@dtl@next
           {%
             \dtlsub{\dtl@labelangle}{\dtl@midangle}{180}%
             \dtl@truncatedecimal\dtl@labelangle
             \edef\dtl@outernodeopt{anchor=east,rotate=\dtl@labelangle}%
           }%
           {%
             \edef\dtl@outernodeopt{anchor=west,rotate=\dtl@midangle}%
           }%
        \else
          \dtlifnumeq{\dtl@midangle}{45}
          {%
            \let\@dtl@next\@firstoftwo
          }%
          {%
            \dtlifnumgt{\dtl@midangle}{315}
            {%
              \let\@dtl@next\@firstoftwo
            }%
            {%
              \dtlifnumopenbetween{\dtl@midangle}{-45}{45}%
              {%
                \let\@dtl@next\@firstoftwo
              }%
              {%
                \let\@dtl@next\@secondoftwo
              }%
            }%
          }%
          \@dtl@next
          {%
            \edef\dtl@outernodeopt{anchor=west}%
          }%
          {%
            \dtlifnumopenbetween{\dtl@midangle}{45}{135}%
            {%
              \let\@dtl@next\@firstoftwo
            }%
            {%
              \dtlifnumeq{\dtl@midangle}{135}%
              {%
                \let\@dtl@next\@firstoftwo
              }%
              {%
                \let\@dtl@next\@secondoftwo
              }%
            }%
            \@dtl@next
            {%
              \edef\dtl@outernodeopt{anchor=south}%
            }%
            {%
              \dtlifnumopenbetween{\dtl@midangle}{135}{225}%
              {%
                \let\@dtl@next\@firstoftwo
              }%
              {%
                \dtlifnumeq{\dtl@midangle}{225}%
                {%
                  \let\@dtl@next\@firstoftwo
                }%
                {%
                  \dtlifnumeq{\dtl@midangle}{-135}%
                  {%
                    \let\@dtl@next\@firstoftwo
                  }%
                  {%
                    \dtlifnumlt{\dtl@midangle}{-135}%
                    {%
                      \let\@dtl@next\@firstoftwo
                    }%
                    {%
                      \let\@dtl@next\@secondoftwo
                    }%
                  }%
                }%
              }%
              \@dtl@next
              {%
                \edef\dtl@outernodeopt{anchor=east}%
              }%
              {%
                \edef\dtl@outernodeopt{anchor=north}%
              }%
            }%
          }%
        \fi
        \edef\@dtl@dolabel{%
          \noexpand\draw (\dtl@midangle:\the\dtl@inneroffset)
             node[\dtl@innernodeopt]{%
           \noexpand\DTLdisplayinnerlabel{\noexpand\dtl@innerlabel}};
        }%
        \@dtl@dolabel
        \edef\@dtl@dolabel{%
          \noexpand\draw (\dtl@midangle:\the\dtl@outeroffset)
            node[\dtl@outernodeopt]{%
          \noexpand\DTLdisplayouterlabel{\noexpand\dtl@outerlabel}};
        }%
        \@dtl@dolabel
        \end{scope}%
      }%
      \DTLpieatendtikz
    \end{tikzpicture}%
  \fi
\egroup
}
\newcommand*{\dtl@computeangles}[2]{%
  \dtlifnumgt{\@dtl@start}{180}%
  {%
    \let\dtl@old=\@dtl@start
    \dtlsub{\@dtl@start}{\dtl@old}{360}%
  }%
  {}%
  \dtlifnumlt{\@dtl@start}{-180}%
  {%
    \let\dtl@old=\@dtl@start
    \dtladd{\@dtl@start}{\dtl@old}{360}%
  }%
  {}%
  \expandafter\edef\csname dtl@sang@\romannumeral#1\endcsname{%
     \@dtl@start}%
  \dtlmul{\dtl@angle}{360}{#2}%
  \let\dtl@old=\dtl@angle
  \dtldiv{\dtl@angle}{\dtl@old}{\dtl@total}%
  \expandafter\let\csname dtl@angle@\romannumeral#1\endcsname=\dtl@angle
  \let\dtl@old=\@dtl@start
  \dtladd{\@dtl@start}{\dtl@old}{\dtl@angle}%
  \expandafter\def\csname dtl@cut@angle@\romannumeral#1\endcsname{0}%
  \expandafter\def\csname dtl@cut@len@\romannumeral#1\endcsname{0cm}%
}
\def\@dtl@set@off#1-#2\relax{%
  \ifstrempty{#2}%
  {%
    \@@dtl@set@off{#1}%
  }%
  {%
    \@@dtl@set@offr#1-#2\relax
  }%
}
\newcommand*{\@@dtl@set@off}[1]{%
  \edef\dtl@old{\csname dtl@angle@\romannumeral#1\endcsname}%
  \dtlmul{\dtl@angle}{\dtl@old}{0.5}%
  \let\dtl@old=\dtl@angle
  \edef\dtl@sang{\csname dtl@sang@\romannumeral#1\endcsname}%
  \dtladd{\dtl@angle}{\dtl@old}{\dtl@sang}%
  \expandafter\edef\csname dtl@cut@angle@\romannumeral#1\endcsname{%
  \dtl@angle}%
  \expandafter\edef\csname dtl@cut@len@\romannumeral#1\endcsname{%
   \the\dtl@cutawayoffset}%
}
\newcount\@dtl@seg
\def\@@dtl@set@offr#1-#2-\relax{%
 \ifnum#1>#2\relax
   \PackageError{datapie}{Segment ranges must go in ascending order}{%
   Try #2-#1 instead of #1-#2}%
 \else
   \def\dtl@angle{0}%
   \@dtl@seg=#1\relax
   \whiledo{\not\(\@dtl@seg > #2\)}{%
    \let\dtl@old=\dtl@angle
    \edef\dtl@segang{\csname dtl@angle@\romannumeral\@dtl@seg\endcsname}%
    \dtladd{\dtl@angle}{\dtl@old}{\dtl@segang}%
    \advance\@dtl@seg by 1\relax
   }%
   \let\dtl@old=\dtl@angle
   \dtlmul{\dtl@angle}{\dtl@old}{0.5}%
   \edef\dtl@sang{\csname dtl@sang@\romannumeral#1\endcsname}%
   \let\dtl@old=\dtl@angle
   \dtladd{\dtl@angle}{\dtl@old}{\dtl@sang}%
   \@dtl@seg=#1\relax
   \whiledo{\not\(\@dtl@seg > #2\)}{%
   \expandafter
    \let\csname dtl@cut@angle@\romannumeral\@dtl@seg\endcsname
    =\dtl@angle
   \expandafter
    \edef\csname dtl@cut@len@\romannumeral\@dtl@seg\endcsname{%
    \the\dtl@cutawayoffset}
    \advance\@dtl@seg by 1\relax
   }%
 \fi
}
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</datapie-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*dataplot-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\subsection{Rollback v2.32 (dataplot-2019-09-27.sty)}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{dataplot}[2019/09/27 v2.32 (NLCT)]
\RequirePackage{xkeyval}
\RequirePackage{tikz}
\RequirePackage{datatool}[=v2.32]
\usetikzlibrary{plotmarks}
\usetikzlibrary{plothandlers}
\usetikzlibrary{calc}
\newcommand*{\DTLplotstream}[4][\boolean{true}]{%
  \@sDTLforeach[#1]{#2}{\dtl@x=#3,\dtl@y=#4}{%
    \DTLconverttodecimal{\dtl@x}{\dtl@decx}%
    \DTLconverttodecimal{\dtl@y}{\dtl@decy}%
    \pgfplotstreampoint{\pgfpointxy{\dtl@decx}{\dtl@decy}}%
  }%
}
\newcommand*{\DTLplotmarks}{%
  \pgfuseplotmark{o},%
  \pgfuseplotmark{x},%
  \pgfuseplotmark{+},%
  \pgfuseplotmark{square},%
  \pgfuseplotmark{triangle},%
  \pgfuseplotmark{diamond},%
  \pgfuseplotmark{pentagon},%
  \pgfuseplotmark{asterisk},%
  \pgfuseplotmark{star}%
}
\newcommand*{\DTLplotmarkcolors}{%
  red,%
  green,%
  blue,%
  yellow,%
  magenta,%
  cyan,%
  orange,%
  black,%
  gray}
\newcommand*{\DTLplotlines}{%
  \pgfsetdash{}{0pt},% solid line
  \pgfsetdash{{10pt}{5pt}}{0pt},%
  \pgfsetdash{{5pt}{5pt}}{0pt},%
  \pgfsetdash{{1pt}{5pt}}{0pt},%
  \pgfsetdash{{5pt}{5pt}{1pt}{5pt}}{0pt},%
  \pgfsetdash{{1pt}{3pt}}{0pt},%
}
\newcommand*{\DTLplotlinecolors}{%
  red,%
  green,%
  blue,%
  yellow,%
  magenta,%
  cyan,%
  orange,%
  black,%
  gray}
\newlength\DTLplotwidth
\setlength\DTLplotwidth{4in}
\newlength\DTLplotheight
\setlength\DTLplotheight{4in}
\newlength\DTLticklength
\setlength\DTLticklength{5pt}
\newlength\DTLminorticklength
\setlength\DTLminorticklength{2pt}
\newlength\DTLticklabeloffset
\setlength\DTLticklabeloffset{8pt}
\newlength\dtl@xticlabelheight
\newlength\dtl@yticlabelwidth
\newlength\DTLmintickgap
\setlength\DTLmintickgap{20pt}
\newlength\DTLminminortickgap
\setlength\DTLminminortickgap{5pt}
\newcounter{DTLplotroundXvar}
\setcounter{DTLplotroundXvar}{2}
\newcounter{DTLplotroundYvar}
\setcounter{DTLplotroundYvar}{2}
\newif\ifDTLxaxis
\DTLxaxistrue
\newcommand*{\DTLXAxisStyle}{-}
\newif\ifDTLyaxis
\DTLyaxistrue
\newcommand*{\DTLYAxisStyle}{-}
\newcommand*{\DTLmajorgridstyle}{color=gray,-}
\newcommand*{\DTLminorgridstyle}{color=gray,loosely dotted}
\newif\ifDTLxticsin
\DTLxticsintrue
\newif\ifDTLyticsin
\DTLyticsintrue
\newcount\dtl@legendsetting
\newlength\DTLlegendxoffset
\setlength\DTLlegendxoffset{10pt}
\newlength\DTLlegendyoffset
\setlength\DTLlegendyoffset{10pt}
\newcommand*{\DTLformatlegend}[1]{%
\setlength{\fboxrule}{1.1pt}%
\fcolorbox{black}{white}{#1}}
\newif\ifDTLshowmarkers
\DTLshowmarkerstrue
\newif\ifDTLshowlines
\DTLshowlinesfalse
\newcommand*{\DTLplotatbegintikz}{}
\newcommand*{\@dtlplothandlermark}[1]{%
  \pgfplothandlermark
  {%
    \pgfmathparse{1/\dtl@scale@x}%
    \pgftransformxscale{\pgfmathresult}%
    \pgfmathparse{1/\dtl@scale@y}%
    \pgftransformyscale{\pgfmathresult}%
   #1%
   }%
}
\newcommand*{\dtlplothandlermark}[1]{%
   \PackageWarning{dataplot}{\string\dtlplothandlermark\space
     found outside \string\DTLplot}%
  \pgfplothandlermark{#1}%
}
\newcommand*{\DTLplotatendtikz}{}
\define@key{dataplot}{x}{%
\def\dtl@xkey{#1}}
\define@key{dataplot}{y}{%
\def\dtl@ykey{#1}}
\define@key{dataplot}{markcolors}{%
\def\DTLplotmarkcolors{#1}}
\define@key{dataplot}{linecolors}{%
\def\DTLplotlinecolors{#1}}
\define@key{dataplot}{colors}{%
\def\DTLplotmarkcolors{#1}%
\def\DTLplotlinecolors{#1}}
\define@key{dataplot}{marks}{%
\def\DTLplotmarks{#1}}
\define@key{dataplot}{lines}{%
\def\DTLplotlines{#1}}
\define@key{dataplot}{width}{%
\setlength\DTLplotwidth{#1}}
\define@key{dataplot}{height}{%
\setlength\DTLplotheight{#1}}
\define@choicekey{dataplot}{style}[\val\nr]{both,lines,markers}{%
\ifcase\nr\relax
 \DTLshowlinestrue
 \DTLshowmarkerstrue
\or
 \DTLshowlinestrue
 \DTLshowmarkersfalse
\or
 \DTLshowmarkerstrue
 \DTLshowlinesfalse
\fi}
\define@choicekey{dataplot}{axes}[\val\nr]{both,x,y,none}[both]{%
\ifcase\nr\relax
 % both
 \DTLxaxistrue
 \DTLxticstrue
 \DTLyaxistrue
 \DTLyticstrue
\or % x
 \DTLxaxistrue
 \DTLxticstrue
 \DTLyaxisfalse
 \DTLyticsfalse
\or % y
 \DTLxaxisfalse
 \DTLxticsfalse
 \DTLyaxistrue
 \DTLyticstrue
\or % none
 \DTLxaxisfalse
 \DTLxticsfalse
 \DTLyaxisfalse
 \DTLyticsfalse
\fi
}
\define@boolkey{dataplot}[DTL]{box}[true]{}
\DTLboxfalse
\define@boolkey{dataplot}[DTL]{xtics}[true]{}
\DTLxticstrue
\define@boolkey{dataplot}[DTL]{ytics}[true]{}
\DTLyticstrue
\define@boolkey{dataplot}[DTL]{xminortics}[true]{%
\ifDTLxminortics \DTLxticstrue\fi}
\DTLxminorticsfalse
\define@boolkey{dataplot}[DTL]{yminortics}[true]{%
\ifDTLyminortics \DTLyticstrue\fi}
\DTLyminorticsfalse
\define@boolkey{dataplot}[DTL]{grid}[true]{}
\define@choicekey{dataplot}{xticdir}[\val\nr]{in,out}{%
\ifcase\nr\relax
 \DTLxticsintrue
\or
 \DTLxticsinfalse
\fi
}
\define@choicekey{dataplot}{yticdir}[\val\nr]{in,out}{%
\ifcase\nr\relax
 \DTLyticsintrue
\or
 \DTLyticsinfalse
\fi
}
\define@choicekey{dataplot}{ticdir}[\val\nr]{in,out}{%
\ifcase\nr\relax
 \DTLxticsintrue
 \DTLyticsintrue
\or
 \DTLxticsinfalse
 \DTLyticsinfalse
\fi
}
\define@key{dataplot}{bounds}{%
\def\dtl@bounds{#1}}
\let\dtl@bounds=\relax
\define@key{dataplot}{minx}{%
\def\dtl@minx{#1}}
\let\dtl@minx=\relax
\define@key{dataplot}{maxx}{%
\def\dtl@maxx{#1}}
\let\dtl@maxx=\relax
\define@key{dataplot}{miny}{%
\def\dtl@miny{#1}}
\let\dtl@miny=\relax
\define@key{dataplot}{maxy}{%
\def\dtl@maxy{#1}}
\let\dtl@maxy=\relax
\define@key{dataplot}{xticpoints}{%
\def\dtl@xticlist{#1}\DTLxticstrue\DTLxaxistrue}
\let\dtl@xticlist=\relax
\define@key{dataplot}{yticpoints}{%
\def\dtl@yticlist{#1}\DTLyticstrue\DTLyaxistrue}
\let\dtl@yticlist=\relax
\define@key{dataplot}{xticgap}{\def\dtl@xticgap{#1}%
\DTLxticstrue\DTLxaxistrue}
\let\dtl@xticgap=\relax
\define@key{dataplot}{yticgap}{\def\dtl@yticgap{#1}%
\DTLyticstrue\DTLyaxistrue}
\let\dtl@yticgap=\relax
\define@key{dataplot}{xticlabels}{%
\def\dtl@xticlabels{#1}\DTLxticstrue\DTLxaxistrue}
\let\dtl@xticlabels=\relax
\define@key{dataplot}{yticlabels}{%
\def\dtl@yticlabels{#1}\DTLyticstrue\DTLyaxistrue}
\let\dtl@yticlabels=\relax
\define@key{dataplot}{xlabel}{%
\def\dtl@xlabel{#1}}
\let\dtl@xlabel=\relax
\define@key{dataplot}{ylabel}{%
\def\dtl@ylabel{#1}}
\let\dtl@ylabel=\relax
\define@choicekey{dataplot}{legend}[\val\nr]{none,north,northeast,%
east,southeast,south,southwest,west,northwest}[northeast]{%
\dtl@legendsetting=\nr\relax
}
\define@key{dataplot}{legendlabels}{\def\dtl@legendlabels{#1}}
\newcommand*{\DTLplot}[3][\boolean{true}]{%
\bgroup
  \let\dtl@xkey=\relax
  \let\dtl@ykey=\relax
  \let\dtl@legendlabels=\relax
  \setkeys{dataplot}{#3}%
  \let\dtl@plotmarklist=\DTLplotmarks
  \let\dtl@plotlinelist=\DTLplotlines
  \let\dtl@plotmarkcolorlist=\DTLplotmarkcolors
  \let\dtl@plotlinecolorlist=\DTLplotlinecolors
  \def\dtl@legend{}%
  \ifx\dtl@legendlabels\relax
   \edef\dtl@legendlabels{#2}%
  \fi
  \ifx\dtl@xkey\relax
    \PackageError{dataplot}{Missing x setting for
      \string\DTLplot}{}%
  \else
    \ifx\dtl@ykey\relax
      \PackageError{dataplot}{Missing y setting for
      \string\DTLplot}{}%
    \else
      \ifx\dtl@bounds\relax
        \DTLcomputebounds[#1]{#2}{\dtl@xkey}{\dtl@ykey}
            {\DTLminX}{\DTLminY}{\DTLmaxX}{\DTLmaxY}%
        \ifx\dtl@minx\relax
        \else
           \let\DTLminX=\dtl@minx
        \fi
        \ifx\dtl@maxx\relax
        \else
           \let\DTLmaxX=\dtl@maxx
        \fi
        \ifx\dtl@miny\relax
        \else
           \let\DTLminY=\dtl@miny
        \fi
        \ifx\dtl@maxy\relax
        \else
           \let\DTLmaxY=\dtl@maxy
        \fi
      \else
        \expandafter\dtl@getbounds\dtl@bounds\@nil
      \fi
      \@dtl@tmpcount=\DTLplotwidth
      \divide\@dtl@tmpcount by 65536\relax
      \dtlsub{\dtl@dx}{\DTLmaxX}{\DTLminX}%
      \dtldiv{\dtl@scale@x}{\number\@dtl@tmpcount}{\dtl@dx}%
      \dtlmul{\dtl@offset@x}{-\dtl@scale@x}{\DTLminX}%
      \@dtl@tmpcount=\DTLplotheight
      \divide\@dtl@tmpcount by 65536\relax
      \dtlsub{\dtl@dy}{\DTLmaxY}{\DTLminY}%
      \dtldiv{\dtl@scale@y}{\number\@dtl@tmpcount}{\dtl@dy}%
      \dtlmul{\dtl@offset@y}{-\dtl@scale@y}{\DTLminY}%
     \ifDTLxtics
       \ifx\dtl@xticlist\relax
         \ifx\dtl@xticgap\relax
         \dtlsub{\dtl@mingap}{\number\DTLmintickgap}{\dtl@offset@x}%
         \dtldiv{\dtl@mingap}{\dtl@mingap}{\dtl@scale@x}%
         \dtldiv{\dtl@mingap}{\dtl@mingap}{65536}%
           \dtl@constructticklist\DTLminX\DTLmaxX
             \dtl@mingap\dtl@xticlist
         \else
           \DTLifFPopenbetween{0}{\DTLminX}{\DTLmaxX}{%
             \dtl@constructticklistwithgapz
               \DTLminX\DTLmaxX\dtl@xticlist\dtl@xticgap}{%
             \dtl@constructticklistwithgap
               \DTLminX\DTLmaxX\dtl@xticlist\dtl@xticgap}%
         \fi
       \fi
       \let\dtl@xminorticlist\@empty
       \ifDTLxminortics
         \let\dtl@prevtick=\relax
         \@for\dtl@nexttick:=\dtl@xticlist\do{%
           \ifx\dtl@prevtick\relax
           \else
             \dtl@constructminorticklist
                \dtl@prevtick\dtl@nexttick\dtl@scale@x\dtl@xminorticlist
           \fi
           \let\dtl@prevtick=\dtl@nexttick
         }%
       \fi
       \ifx\dtl@xticlabels\relax
         \settoheight{\dtl@xticlabelheight}{\dtl@xticlist}%
       \else
         \settoheight{\dtl@xticlabelheight}{\dtl@xticlabels}%
        \fi
     \else
       \setlength{\dtl@xticlabelheight}{0pt}%
     \fi
     \setlength{\dtl@yticlabelwidth}{0pt}%
     \ifDTLytics
       \ifx\dtl@yticlist\relax
         \ifx\dtl@yticgap\relax
         \dtlsub{\dtl@mingap}{\number\DTLmintickgap}{\dtl@offset@y}%
         \dtldiv{\dtl@mingap}{\dtl@mingap}{\dtl@scale@y}%
         \dtldiv{\dtl@mingap}{\dtl@mingap}{65536}%
         \dtl@constructticklist\DTLminY\DTLmaxY
           \dtl@mingap\dtl@yticlist
         \else
           \DTLifFPopenbetween{0}{\DTLminY}{\DTLmaxY}{%
             \dtl@constructticklistwithgapz
               \DTLminY\DTLmaxY\dtl@yticlist\dtl@yticgap}{%
             \dtl@constructticklistwithgap
               \DTLminY\DTLmaxY\dtl@yticlist\dtl@yticgap}%
         \fi
       \fi
       \let\dtl@yminorticlist\@empty
       \ifDTLyminortics
         \let\dtl@prevtick=\relax
         \@for\dtl@nexttick:=\dtl@yticlist\do{%
           \ifx\dtl@prevtick\relax
           \else
             \dtl@constructminorticklist
                \dtl@prevtick\dtl@nexttick\dtl@scale@y\dtl@yminorticlist
           \fi
           \let\dtl@prevtick=\dtl@nexttick
         }%
       \fi
       \ifx\dtl@ylabel\relax
       \else
         \ifx\dtl@yticlabels\relax
           \@for\dtl@thislabel:=\dtl@yticlist\do{%
             \dtlround{\dtl@thislabel}{\dtl@thislabel}
                     {\c@DTLplotroundYvar}%
             \settowidth{\dtl@tmplength}{\dtl@thislabel}%
             \ifdim\dtl@tmplength>\dtl@yticlabelwidth
               \setlength{\dtl@yticlabelwidth}{\dtl@tmplength}%
             \fi
           }%
         \else
           \@for\dtl@thislabel:=\dtl@yticlabels\do{%
             \settowidth{\dtl@tmplength}{\dtl@thislabel}%
             \ifdim\dtl@tmplength>\dtl@yticlabelwidth
               \setlength{\dtl@yticlabelwidth}{\dtl@tmplength}%
             \fi
           }%
         \fi
       \fi
     \fi
       \begin{tikzpicture}
       \pgfsetxvec{\pgfpoint{1pt}{0pt}}%
       \pgfsetyvec{\pgfpoint{0pt}{1pt}}%
   \begin{scope}
   \pgftransformcm{\dtl@scale@x}{0}{0}{\dtl@scale@y}%
     {\pgfpoint{\dtl@offset@x pt}{\dtl@offset@y pt}}%
       \let\dtlplothandlermark\@dtlplothandlermark
       \DTLplotatbegintikz
       \ifDTLbox
         \draw (\DTLminX,\DTLminY) -- (\DTLmaxX,\DTLminY) --
             (\DTLmaxX,\DTLmaxY) -- (\DTLminX,\DTLmaxY) --
               cycle;
       \else
          \ifDTLxaxis
            \expandafter\draw\expandafter[\DTLXAxisStyle]
            (\DTLminX,\DTLminY) -- (\DTLmaxX,\DTLminY);
          \fi
          \ifDTLyaxis
            \expandafter\draw\expandafter[\DTLYAxisStyle]
            (\DTLminX,\DTLminY) -- (\DTLminX,\DTLmaxY);
          \fi
       \fi
       \ifDTLgrid
          \ifDTLxminortics
            \@for\dtl@thistick:=\dtl@xminorticlist\do{%
              \expandafter\draw\expandafter[\DTLminorgridstyle]
             (\dtl@thistick,\DTLminY) -- (\dtl@thistick,\DTLmaxY);
            }%
          \fi
          \ifDTLyminortics
            \@for\dtl@thistick:=\dtl@yminorticlist\do{%
              \expandafter\draw\expandafter[\DTLminorgridstyle]
             (\DTLminX,\dtl@thistick) -- (\DTLmaxX,\dtl@thistick);
            }%
          \fi
          \@for\dtl@thistick:=\dtl@xticlist\do{%
            \expandafter\draw\expandafter[\DTLmajorgridstyle]
             (\dtl@thistick,\DTLminY) -- (\dtl@thistick,\DTLmaxY);
          }%
          \@for\dtl@thistick:=\dtl@yticlist\do{%
            \expandafter\draw\expandafter[\DTLmajorgridstyle]
             (\DTLminX,\dtl@thistick) -- (\DTLmaxX,\dtl@thistick);
          }%
       \fi
       \ifDTLxtics
         \dtlsub{\dtl@ticklength}{\number\DTLticklength}{-\dtl@offset@y}%
         \dtldiv{\dtl@ticklength}{\dtl@ticklength}{\dtl@scale@y}%
         \dtldiv{\dtl@ticklength}{\dtl@ticklength}{65536}%
         \addtolength\dtl@xticlabelheight{\DTLticklabeloffset}%
         \dtlsub{\dtl@ticlabeloffset}{\number\dtl@xticlabelheight}{-\dtl@offset@y}%
         \dtldiv{\dtl@ticlabeloffset}{\dtl@ticlabeloffset}{\dtl@scale@y}%
         \dtldiv{\dtl@ticlabeloffset}{\dtl@ticlabeloffset}{65536}%
         \@for\dtl@thistick:=\dtl@xticlist\do{%
           \let\dtl@thisticklabel\dtl@thistick
           \ifx\dtl@xticlabels\relax
             \dtlround{\dtl@thislabel}{\dtl@thistick}
                     {\c@DTLplotroundXvar}%
           \else
             \dtl@chopfirst\dtl@xticlabels\dtl@thislabel\dtl@rest
             \let\dtl@xticlabels=\dtl@rest
           \fi
           \ifDTLxticsin
             \draw (\dtl@thistick,\DTLminY) -- ++(0,\dtl@ticklength);
             \draw (\dtl@thistick,\DTLminY)
                 ++ (0,-\dtl@ticlabeloffset) node {\dtl@thislabel};
           \else
             \draw (\dtl@thistick,\DTLminY) -- ++(0,-\dtl@ticklength)
                 ++ (0,-\dtl@ticlabeloffset) node {\dtl@thislabel};
           \fi
           \ifDTLbox
             \ifDTLxticsin
               \draw (\dtl@thistick,\DTLmaxY) -- ++(0,-\dtl@ticklength);
             \else
             \draw (\dtl@thistick,\DTLmaxY) -- ++(0,\dtl@ticklength);
             \fi
           \fi
         }%
       \fi
      \ifx\dtl@xlabel\relax
      \else
         \dtladd{\dtl@x}{\number\baselineskip}{\dtl@offset@y}%
         \dtldiv{\dtl@x}{\dtl@x}{\dtl@scale@y}%
         \dtldiv{\dtl@x}{\dtl@x}{65536}%
         \dtladd{\dtl@ticlabeloffset}{\dtl@ticlabeloffset}{\dtl@x}%
         \dtlmul{\dtl@x}{\dtl@dx}{0.5}%
         \draw (\DTLminX,\DTLminY) ++(\dtl@x,-\dtl@ticlabeloffset)
            node[anchor=north] {\dtl@xlabel};
      \fi
      \ifDTLxminortics
         \dtlsub{\dtl@ticklength}{\number\DTLminorticklength}{-\dtl@offset@y}%
         \dtldiv{\dtl@ticklength}{\dtl@ticklength}{\dtl@scale@y}%
         \dtldiv{\dtl@ticklength}{\dtl@ticklength}{65536}%
         \@for\dtl@thistick:=\dtl@xminorticlist\do{%
           \ifDTLxticsin
             \draw (\dtl@thistick,\DTLminY) -- ++(0,\dtl@ticklength);
             \draw (\dtl@thistick,\DTLminY)
                 ++ (0,-\dtl@ticlabeloffset) node[anchor=north] {\dtl@thislabel};
           \else
             \draw (\dtl@thistick,\DTLminY) -- ++(0,-\dtl@ticklength)
                 ++ (0,-\dtl@ticlabeloffset) node[anchor=north] {\dtl@thislabel};
           \fi
           \ifDTLbox
             \ifDTLxticsin
               \draw (\dtl@thistick,\DTLmaxY) -- ++(0,-\dtl@ticklength);
             \else
             \draw (\dtl@thistick,\DTLmaxY) -- ++(0,\dtl@ticklength);
             \fi
           \fi
        }%
      \fi
      \ifDTLytics
         \dtlsub{\dtl@ticklength}{\number\DTLticklength}{-\dtl@offset@x}%
         \dtldiv{\dtl@ticklength}{\dtl@ticklength}{\dtl@scale@x}%
         \dtldiv{\dtl@ticklength}{\dtl@ticklength}{65536}%
         \dtladd{\dtl@ticlabeloffset}{\number\DTLticklabeloffset}{0}%
         \dtlsub{\dtl@ticlabeloffset}{\number\DTLticklabeloffset}{-\dtl@offset@x}%
         \dtldiv{\dtl@ticlabeloffset}{\dtl@ticlabeloffset}{\dtl@scale@x}%
         \dtldiv{\dtl@ticlabeloffset}{\dtl@ticlabeloffset}{65536}%
         \@for\dtl@thistick:=\dtl@yticlist\do{%
           \let\dtl@thisticklabel\dtl@thistick
           \ifx\dtl@yticlabels\relax
             \dtlround{\dtl@thislabel}{\dtl@thistick}
                     {\c@DTLplotroundXvar}%
           \else
             \dtl@chopfirst\dtl@yticlabels\dtl@thislabel\dtl@rest
             \let\dtl@yticlabels=\dtl@rest
           \fi
           \ifDTLyticsin
             \draw (\DTLminX,\dtl@thistick) -- ++(\dtl@ticklength,0);
             \draw (\DTLminX,\dtl@thistick)
                 ++ (-\dtl@ticlabeloffset,0) node[anchor=east] {\dtl@thislabel};
           \else
             \draw (\DTLminX,\dtl@thistick) -- ++(-\dtl@ticklength,0)
                 ++ (-\dtl@ticlabeloffset,0) node[anchor=east] {\dtl@thislabel};
           \fi
           \ifDTLbox
             \ifDTLyticsin
               \draw (\DTLmaxX,\dtl@thistick) -- ++(-\dtl@ticklength,0);
             \else
             \draw (\DTLmaxX,\dtl@thistick) -- ++(\dtl@ticklength,0);
             \fi
           \fi
         }%
      \fi
      \ifx\dtl@ylabel\relax
      \else
         \setlength{\dtl@tmplength}{\baselineskip}%
         \addtolength{\dtl@tmplength}{\dtl@yticlabelwidth}%
         \addtolength{\dtl@tmplength}{\DTLticklabeloffset}%
         \dtlsub{\dtl@ticlabeloffset}{\number\dtl@tmplength}{-\dtl@offset@x}%
         \dtldiv{\dtl@ticlabeloffset}{\dtl@ticlabeloffset}{\dtl@scale@x}%
         \dtldiv{\dtl@ticlabeloffset}{\dtl@ticlabeloffset}{65536}%
         \dtlmul{\dtl@y}{\dtl@dy}{0.5}%
         \draw (\DTLminX,\DTLminY) ++(-\dtl@ticlabeloffset,\dtl@y)
            node[rotate=90,anchor=south] {\dtl@ylabel};
      \fi
      \ifDTLyminortics
         \dtlsub{\dtl@ticklength}{\number\DTLminorticklength}{-\dtl@offset@x}%
         \dtldiv{\dtl@ticklength}{\dtl@ticklength}{\dtl@scale@x}%
         \dtldiv{\dtl@ticklength}{\dtl@ticklength}{65536}%
         \@for\dtl@thistick:=\dtl@yminorticlist\do{%
           \ifDTLyticsin
             \draw (\DTLminX,\dtl@thistick) -- ++(\dtl@ticklength,0);
           \else
             \draw (\DTLminX,\dtl@thistick) -- ++(-\dtl@ticklength,0);
           \fi
           \ifDTLbox
             \ifDTLyticsin
               \draw (\DTLmaxX,\dtl@thistick) -- ++(-\dtl@ticklength,0);
             \else
             \draw (\DTLmaxX,\dtl@thistick) -- ++(\dtl@ticklength,0);
             \fi
           \fi
        }%
      \fi
   \end{scope}
        \@for\dtl@thisdb:=#2\do{%
          \ifx\dtl@plotmarkcolorlist\@empty
            \let\dtl@plotmarkcolorlist=\DTLplotmarkcolors
          \fi
          \dtl@chopfirst\dtl@plotmarkcolorlist\dtl@thisplotmarkcolor
             \dtl@remainder
          \let\dtl@plotmarkcolorlist=\dtl@remainder
          \ifDTLshowmarkers
            \ifx\dtl@plotmarklist\@empty
               \let\dtl@plotmarklist=\DTLplotmarks
            \fi
            \dtl@chopfirst\dtl@plotmarklist\dtl@thisplotmark
               \dtl@remainder
            \let\dtl@plotmarklist=\dtl@remainder
            \ifx\dtl@thisplotmark\relax
              \let\dtl@mark=\relax
            \else
              \expandafter\toks@\expandafter{\dtl@thisplotmark}%
              \ifx\dtl@thisplotmarkcolor\@empty
                \edef\dtl@mark{\the\toks@}%
              \else
                \edef\dtl@mark{%
                    \noexpand\color{\dtl@thisplotmarkcolor}%
                  \the\toks@}%
              \fi
            \fi
          \else
            \let\dtl@mark=\relax
          \fi
          \ifx\dtl@plotlinecolorlist\@empty
            \let\dtl@plotlinecolorlist=\DTLplotlinecolors
          \fi
          \dtl@chopfirst\dtl@plotlinecolorlist\dtl@thisplotlinecolor
             \dtl@remainder
          \let\dtl@plotlinecolorlist=\dtl@remainder
          \ifDTLshowlines
            \ifx\dtl@plotlinelist\@empty
               \let\dtl@plotlinelist=\DTLplotlines
            \fi
            \dtl@chopfirst\dtl@plotlinelist\dtl@thisplotline
               \dtl@remainder
            \let\dtl@plotlinelist=\dtl@remainder
            \expandafter\ifx\dtl@thisplotline\relax
            \let\dtl@linestyle=\relax
          \else
            \expandafter\toks@\expandafter{\dtl@thisplotline}%
            \ifx\dtl@thisplotlinecolor\@empty
              \edef\dtl@linestyle{\the\toks@}%
            \else
              \edef\dtl@linestyle{%
                  \noexpand\color{\dtl@thisplotlinecolor}%
                \the\toks@}%
            \fi
          \fi
        \else
          \let\dtl@linestyle=\relax
        \fi
        \ifnum\dtl@legendsetting>0\relax
          \dtl@chopfirst\dtl@legendlabels\dtl@thislabel\dtl@rest
          \let\dtl@legendlabels=\dtl@rest
          \expandafter\toks@\expandafter{\dtl@mark}%
          \expandafter\@dtl@toks\expandafter{\dtl@linestyle}%
          \edef\dtl@addtolegend{\noexpand\DTLaddtoplotlegend
            {\the\toks@}{\the\@dtl@toks}{\dtl@thislabel}}%
          \dtl@addtolegend
        \fi
        \def\dtl@stream{\pgfplotstreamstart}%
        \@sDTLforeach[#1]{\dtl@thisdb}{\dtl@x=\dtl@xkey,%
            \dtl@y=\dtl@ykey}{%
          \DTLconverttodecimal{\dtl@x}{\dtl@decx}%
          \DTLconverttodecimal{\dtl@y}{\dtl@decy}%
          \ifthenelse{%
            \DTLisclosedbetween{\dtl@x}{\DTLminX}{\DTLmaxX}%
            \and
            \DTLisclosedbetween{\dtl@y}{\DTLminY}{\DTLmaxY}%
            }%
            {%
              \expandafter\toks@\expandafter{\dtl@stream}%
              \dtlmul{\dtl@decx}{\dtl@decx}{\dtl@scale@x}%
              \dtladd{\dtl@decx}{\dtl@decx}{\dtl@offset@x}%
              \dtlround{\dtl@decx}{\dtl@decx}{1}%
              \dtlmul{\dtl@decy}{\dtl@decy}{\dtl@scale@y}%
              \dtladd{\dtl@decy}{\dtl@decy}{\dtl@offset@y}%
              \dtlround{\dtl@decy}{\dtl@decy}{1}%
              \edef\dtl@stream{\the\toks@
                \noexpand\pgfplotstreampoint
                 {\noexpand\pgfpointxy{\dtl@decx}{\dtl@decy}}}%
          }{}%
        }%
        \expandafter\toks@\expandafter{\dtl@stream}%
        \edef\dtl@stream{\the\toks@\noexpand\pgfplotstreamend}%
        \ifx\dtl@linestyle\relax
        \else
          \begin{scope}
          \dtl@linestyle
          \pgfplothandlerlineto
          \dtl@stream
          \pgfusepath{stroke}
          \end{scope}
        \fi
        \ifx\dtl@mark\relax
        \else
          \begin{scope}
          \pgfplothandlermark{\dtl@mark}%
          \dtl@stream
          \pgfusepath{stroke}
          \end{scope}
        \fi
        }%
        \ifcase\dtl@legendsetting
      % none
        \or % north
         \dtlmul{\dtl@decx}{\dtl@dx}{0.5}%
         \dtladd{\dtl@decx}{\DTLminX}{\dtl@decx}%
         \dtlmul{\dtl@decx}{\dtl@decx}{\dtl@scale@x}%
         \dtladd{\dtl@decx}{\dtl@decx}{\dtl@offset@x}%
         \dtlmul{\dtl@decy}{\DTLmaxY}{\dtl@scale@y}%
         \dtladd{\dtl@decy}{\dtl@decy}{\dtl@offset@y}%
         \draw (\dtl@decx,\dtl@decy) ++(0,-\DTLlegendyoffset)
            node[anchor=north]
            {\DTLformatlegend
              {\begin{tabular}{cl}\dtl@legend\end{tabular}}%
            };
        \or % north east
         \dtlmul{\dtl@decx}{\DTLmaxX}{\dtl@scale@x}%
         \dtladd{\dtl@decx}{\dtl@decx}{\dtl@offset@x}%
         \dtlmul{\dtl@decy}{\DTLmaxY}{\dtl@scale@y}%
         \dtladd{\dtl@decy}{\dtl@decy}{\dtl@offset@y}%
         \draw (\dtl@decx,\dtl@decy) ++(-\DTLlegendxoffset,-\DTLlegendyoffset)
            node[anchor=north east]
            {\DTLformatlegend
              {\begin{tabular}{cl}\dtl@legend\end{tabular}}%
            };
        \or % east
         \dtlmul{\dtl@decy}{\dtl@dy}{0.5}%
         \dtladd{\dtl@decy}{\DTLminY}{\dtl@decy}%
         \dtlmul{\dtl@decy}{\dtl@decy}{\dtl@scale@y}%
         \dtladd{\dtl@decy}{\dtl@decy}{\dtl@offset@y}%
         \dtlmul{\dtl@decx}{\DTLmaxX}{\dtl@scale@x}%
         \dtladd{\dtl@decx}{\dtl@decx}{\dtl@offset@x}%
         \draw (\dtl@decx,\dtl@decy) ++(-\DTLlegendxoffset,0)
            node[anchor=east]
            {\DTLformatlegend
              {\begin{tabular}{cl}\dtl@legend\end{tabular}}%
            };
        \or % south east
         \dtlmul{\dtl@decx}{\DTLmaxX}{\dtl@scale@x}%
         \dtladd{\dtl@decx}{\dtl@decx}{\dtl@offset@x}%
         \dtlmul{\dtl@decy}{\DTLminY}{\dtl@scale@y}%
         \dtladd{\dtl@decy}{\dtl@decy}{\dtl@offset@y}%
         \draw (\dtl@decx,\dtl@decy) ++(-\DTLlegendxoffset,\DTLlegendyoffset)
            node[anchor=south east]
            {\DTLformatlegend
              {\begin{tabular}{cl}\dtl@legend\end{tabular}}%
            };
        \or % south
         \dtlmul{\dtl@decx}{\dtl@dx}{0.5}%
         \dtladd{\dtl@decx}{\DTLminX}{\dtl@decx}%
         \dtlmul{\dtl@decx}{\dtl@decx}{\dtl@scale@x}%
         \dtladd{\dtl@decx}{\dtl@decx}{\dtl@offset@x}%
         \dtlmul{\dtl@decy}{\DTLminY}{\dtl@scale@y}%
         \dtladd{\dtl@decy}{\dtl@decy}{\dtl@offset@y}%
         \draw (\dtl@decx,\dtl@decy) ++(0,\DTLlegendyoffset)
            node[anchor=south]
            {\DTLformatlegend
              {\begin{tabular}{cl}\dtl@legend\end{tabular}}%
            };
        \or % south west
         \dtlmul{\dtl@decx}{\DTLminX}{\dtl@scale@x}%
         \dtladd{\dtl@decx}{\dtl@decx}{\dtl@offset@x}%
         \dtlmul{\dtl@decy}{\DTLminY}{\dtl@scale@y}%
         \dtladd{\dtl@decy}{\dtl@decy}{\dtl@offset@y}%
         \draw (\dtl@decx,\dtl@decy) ++(\DTLlegendxoffset,\DTLlegendyoffset)
            node[anchor=south west]
            {\DTLformatlegend
              {\begin{tabular}{cl}\dtl@legend\end{tabular}}%
            };
        \or % west
         \dtlmul{\dtl@decy}{\dtl@dy}{0.5}%
         \dtladd{\dtl@decy}{\DTLminY}{\dtl@decy}%
         \dtlmul{\dtl@decy}{\dtl@decy}{\dtl@scale@y}%
         \dtladd{\dtl@decy}{\dtl@decy}{\dtl@offset@y}%
         \dtlmul{\dtl@decx}{\DTLminX}{\dtl@scale@x}%
         \dtladd{\dtl@decx}{\dtl@decx}{\dtl@offset@x}%
         \draw (\dtl@decx,\dtl@decy) ++(\DTLlegendxoffset,0)
            node[anchor=west]
            {\DTLformatlegend
              {\begin{tabular}{cl}\dtl@legend\end{tabular}}%
            };
        \or % north west
         \dtlmul{\dtl@decx}{\DTLminX}{\dtl@scale@x}%
         \dtladd{\dtl@decx}{\dtl@decx}{\dtl@offset@x}%
         \dtlmul{\dtl@decy}{\DTLmaxY}{\dtl@scale@y}%
         \dtladd{\dtl@decy}{\dtl@decy}{\dtl@offset@y}%
         \draw (\dtl@decx,\dtl@decy) ++(\DTLlegendxoffset,-\DTLlegendyoffset)
            node[anchor=north west]
            {\DTLformatlegend
              {\begin{tabular}{cl}\dtl@legend\end{tabular}}%
            };
        \fi
       \pgftransformcm{\dtl@scale@x}{0}{0}{\dtl@scale@y}%
         {\pgfpoint{\dtl@offset@x pt}{\dtl@offset@y pt}}%
       \let\dtlplothandlermark\@dtlplothandlermark
        \DTLplotatendtikz
       \end{tikzpicture}
    \fi
  \fi
\egroup
}
\def\dtl@getbounds#1,#2,#3,#4\@nil{%
\def\DTLminX{#1}%
\def\DTLminY{#2}%
\def\DTLmaxX{#3}%
\def\DTLmaxY{#4}%
\dtlifnumgt{\DTLminX}{\DTLmaxX}
{%
 \PackageError{dataplot}{Min X > Max X in bounds #1,#2,#3,#4}{%
 The bounds must be specified as minX,minY,maxX,maxY}%
}{}%
\dtlifnumgt{\DTLminY}{\DTLmaxY}
{%
 \PackageError{dataplot}{Min Y > Max Y in bounds #1,#2,#3,#4}{%
 The bounds must be specified as minX,minY,maxX,maxY}%
}{}%
}
\newcommand*{\dtl@constructticklist}[4]{%
  \DTLifFPopenbetween{0}{#1}{#2}%
  {%
    \dtlsub{\@dtl@width}{0}{#1}%
    \dtldiv{\@dtl@neggap}{\@dtl@width}{10}%
    \dtlifnumlt{\@dtl@neggap}{#3}%
    {%
      \edef\@dtl@neggap{#3}%
    }%
    {}%
    \dtldiv{\@dtl@posgap}{#2}{10}%
    \dtlifnumlt{\@dtl@posgap}{#3}%
    {%
      \edef\@dtl@posgap{#3}%
    }%
    {}%
    \dtlmax{\@dtl@gap}{\@dtl@neggap}{\@dtl@posgap}%
    \dtlifnumgt{\@dtl@gap}{\@dtl@width}%
    {}%
    {%
      \dtl@constructticklistwithgapz{#1}{#2}{#4}{\@dtl@gap}%
    }%
  }%
  {%
    \dtlsub{\@dtl@width}{#2}{#1}%
    \dtldiv{\@dtl@gap}{\@dtl@width}{10}%
    \dtlifnumlt{\@dtl@gap}{#3}%
    {%
      \dtlifnumgt{#3}{\@dtl@width}%
      {%
         \def#4{#1,#2}%
      }%
      {%
        \dtl@constructticklistwithgap{#1}{#2}{#4}{#3}%
      }
    }%
    {%
      \dtl@constructticklistwithgap{#1}{#2}{#4}{\@dtl@gap}%
    }%
  }%
}
\newcommand*{\dtl@constructticklistwithgap}[4]{%
\edef\@dtl@thistick{#1}%
\edef#3{#1}%
\dtladd{\@dtl@thistick}{\@dtl@thistick}{#4}%
\whiledo{\DTLisFPopenbetween{\@dtl@thistick}{#1}{#2}}{%
  \expandafter\toks@\expandafter{\@dtl@thistick}%
  \edef#3{#3,\the\toks@}%
  \dtladd{\@dtl@thistick}{\@dtl@thistick}{#4}%
}%
\expandafter\toks@\expandafter{#2}%
\edef#3{#3,\the\toks@}%
}
\newcommand*{\dtl@constructticklistwithgapz}[4]{%
  \edef\@dtl@thistick{0}%
  \edef#3{0}%
  \dtladd{\@dtl@thistick}{\@dtl@thistick}{#4}%
  \whiledo{\DTLisFPopenbetween{\@dtl@thistick}{0}{#2}}%
  {%
    \expandafter\toks@\expandafter{\@dtl@thistick}%
    \edef#3{#3,\the\toks@}%
    \dtladd{\@dtl@thistick}{\@dtl@thistick}{#4}%
  }%
  \expandafter\toks@\expandafter{#2}%
  \edef#3{#3,\the\toks@}%
  \dtlifnumeq{#1}{0}%
  {}%
  {%
    \edef\@dtl@thistick{0}%
    \dtlsub{\@dtl@thistick}{\@dtl@thistick}{#4}%
    \whiledo{\DTLisFPopenbetween{\@dtl@thistick}{#1}{0}}%
    {%
      \expandafter\toks@\expandafter{\@dtl@thistick}%
      \edef#3{\the\toks@,#3}%
      \dtlsub{\@dtl@thistick}{\@dtl@thistick}{#4}%
    }%
    \expandafter\toks@\expandafter{#1}%
    \edef#3{\the\toks@,#3}%
  }%
}
\newcommand*{\dtl@constructminorticklist}[4]{%
  \dtlsub{\@dtl@width}{#2}{#1}%
  \dtlmul{\@dtl@width}{\@dtl@width}{#3}%
  \dtldiv{\@dtl@gap}{\@dtl@width}{10}%
  \setlength\dtl@tmplength{\@dtl@gap sp}%
  \ifdim\dtl@tmplength<\DTLminminortickgap
    \dtldiv{\@dtl@gap}{\@dtl@width}{4}%
    \setlength\dtl@tmplength{\@dtl@gap sp}%
    \ifdim\dtl@tmplength<\DTLminminortickgap
      \dtldiv{\@dtl@gap}{\@dtl@width}{2}%
      \setlength\dtl@tmplength{\@dtl@gap sp}%
      \ifdim\dtl@tmplength<\DTLminminortickgap
        \let\@dtl@gap=\@dtl@width
      \fi
    \fi
  \fi
  \dtldiv{\@dtl@gap}{\@dtl@gap}{#3}%
  \dtl@constructticklistwithgapex{#1}{#2}{\dtl@tmp}{\@dtl@gap}%
  \ifx#4\@empty
    \let#4=\dtl@tmp
  \else
    \expandafter\toks@\expandafter{#4}%
    \edef#4{#4,\dtl@tmp}%
  \fi
}
\newcommand*{\dtl@constructticklistwithgapex}[4]{%
\edef\@dtl@thistick{#1}%
\let#3=\@empty
\dtladd{\@dtl@thistick}{\@dtl@thistick}{#4}%
\whiledo{\DTLisFPopenbetween{\@dtl@thistick}{#1}{#2}}{%
  \expandafter\toks@\expandafter{\@dtl@thistick}%
  \ifx#3\@empty
    \edef#3{\the\toks@}%
  \else
    \edef#3{#3,\the\toks@}%
  \fi
  \dtladd{\@dtl@thistick}{\@dtl@thistick}{#4}%
}%
}
\newcommand*{\DTLaddtoplotlegend}[3]{%
\def\dtl@legendline{}%
\ifx\relax#2\relax
\else
  \toks@{#2%
  \pgfpathmoveto{\pgfpoint{-10pt}{0pt}}%
  \pgfpathlineto{\pgfpoint{10pt}{0pt}}%
  \pgfusepath{stroke}}%
  \edef\dtl@legendline{\the\toks@}%
\fi
\ifx\relax#1\relax
\else
  \toks@{#1}%
  \expandafter\@dtl@toks\expandafter{\dtl@legendline}%
  \edef\dtl@legendline{\the\@dtl@toks\the\toks@}%
\fi
\expandafter\toks@\expandafter{\dtl@legendline}%
\ifx\dtl@legend\@empty
  \xdef\dtl@legend{\noexpand\tikz\the\toks@; \noexpand& #3}%
\else
  \expandafter\@dtl@toks\expandafter{\dtl@legend}%
  \xdef\dtl@legend{\the\@dtl@toks\noexpand\\%
    \noexpand\tikz\the\toks@; \noexpand& #3}%
\fi
}
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</dataplot-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datatool-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\subsection{Rollback v2.32 (datatool-2019-09-27.sty)}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{datatool}[2019/09/27 v2.32 (NLCT)]
\RequirePackage{xkeyval}
\RequirePackage{ifthen}
\RequirePackage{xfor}
\RequirePackage{substr}
\RequirePackage{etoolbox}
\newcommand*{\@dtl@separator}{,}
\newcommand*{\DTLsetseparator}[1]{%
  \renewcommand*{\@dtl@separator}{#1}%
  \@dtl@construct@lopoffs
}
\begingroup
\catcode`\^^I12
\gdef\DTLsettabseparator{%
  \catcode`\^^I12
  \DTLsetseparator{^^I}%
}
\gdef\DTLmaketabspace{%
  \catcode`\^^I10\relax
}
\endgroup
\begingroup
\catcode`\"12\relax
\gdef\@dtl@delimiter{"}
\endgroup
\newcommand*\DTLsetdelimiter[1]{%
  \renewcommand*{\@dtl@delimiter}{#1}%
  \@dtl@construct@lopoffs
}
\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}%
}
\define@key{datatool.sty}{separator}{%
  \DTLsetseparator{#1}%
}
\define@key{datatool.sty}{delimiter}{%
  \DTLsetdelimiter{#1}%
}
\define@boolkey{datatool.sty}[dtl]{verbose}[true]{}
\define@choicekey{datatool.sty}{math}[\val\nr]{fp,pgfmath}{%
  \renewcommand*\@dtl@mathprocessor{#1}%
}
\providecommand*{\@dtl@mathprocessor}{fp}
\newcommand*{\@dtl@set@options}{}
\define@choicekey{datatool.sty}{utf8}{true,false}[true]{%
  \renewcommand*{\@dtl@set@options}{\setbool{@dtl@utf8}{#1}}%
}
\ProcessOptionsX
\@dtl@construct@lopoffs
\RequirePackage{datatool-base}[=v2.32]
\@dtl@set@options
\DeclareRobustCommand{\DTLpar}{\par}
\newcommand*{\DTLnewdb}[1]{%
  \DTLifdbexists{#1}%
  {%
     \PackageError{datatool}{Database `#1' already exists}{}%
  }%
  {%
    \dtl@message{Creating database `#1'}%
    \expandafter\newtoks\csname dtldb@#1\endcsname
    \expandafter\newtoks\csname dtlkeys@#1\endcsname{}%
    \expandafter\newcount\csname dtlrows@#1\endcsname
    \expandafter\newcount\csname dtlcols@#1\endcsname
  }%
}
\newcommand*{\DTLcleardb}[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
    }%
    \csname dtldb@#1\endcsname{}%
    \csname dtlkeys@#1\endcsname{}%
    \csname dtlrows@#1\endcsname=0\relax
    \csname dtlcols@#1\endcsname=0\relax
  }%
  {%
    \PackageError{Can't clear database `#1':
       database doesn't exist}{}{}%
  }%
}
\newcommand*{\DTLdeletedb}[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{Can't delete database `#1':
       database doesn't exist}{}{}%
  }%
}
\newcommand*{\DTLgnewdb}[1]{%
  \DTLifdbexists{#1}%
  {%
     \PackageError{datatool}{Database `#1' already exists}{}%
  }%
  {%
    \dtl@message{Creating database `#1'}%
    \expandafter\global\expandafter\newtoks\csname dtldb@#1\endcsname
    \expandafter\global\expandafter\newtoks\csname dtlkeys@#1\endcsname{}%
    \expandafter\global\expandafter\newcount\csname dtlrows@#1\endcsname
    \expandafter\global\expandafter\newcount\csname dtlcols@#1\endcsname
  }%
}
\newcommand*{\DTLgdeletedb}[1]{%
  \DTLifdbexists{#1}%
  {%
    \dtlforeachkey(\@dtl@key,\@dtl@col,\@dtl@type,\@dtl@head)\in{#1}\do
    {%
      \expandafter\global\expandafter\let\csname dtl@ci@#1@\@dtl@key\endcsname\undefined
    }%
    \expandafter\global\expandafter\let\csname dtldb@#1\endcsname\undefined
    \expandafter\global\expandafter\let\csname dtlkeys@#1\endcsname\undefined
    \expandafter\global\expandafter\let\csname dtlrows@#1\endcsname\undefined
    \expandafter\global\expandafter\let\csname dtlcols@#1\endcsname\undefined
  }%
  {%
    \PackageError{Can't delete database `#1':
       database doesn't exist}{}{}%
  }%
}
\newcommand*{\DTLgcleardb}[1]{%
  \DTLifdbexists{#1}%
  {%
    \dtlforeachkey(\@dtl@key,\@dtl@col,\@dtl@type,\@dtl@head)\in{#1}\do
    {%
      \expandafter\global\expandafter\let\csname dtl@ci@#1@\@dtl@key\endcsname\undefined
    }%
    \expandafter\global\csname dtldb@#1\endcsname{}%
    \expandafter\global\csname dtlkeys@#1\endcsname{}%
    \expandafter\global\csname dtlrows@#1\endcsname=0\relax
    \expandafter\global\csname dtlcols@#1\endcsname=0\relax
  }%
  {%
    \PackageError{Can't clear database `#1':
       database doesn't exist}{}{}%
  }%
}
\newcommand*{\DTLrowcount}[1]{%
  \expandafter\number\csname dtlrows@#1\endcsname
}
\newcommand*{\DTLcolumncount}[1]{%
  \expandafter\number\csname dtlcols@#1\endcsname
}
\newcommand{\DTLifdbempty}[3]{%
  \DTLifdbexists{#1}%
    {\@DTLifdbempty{#1}{#2}{#3}}%
    {\PackageError{Can't check if database `#1' is empty:
       database doesn't exist}{}{}}%
}
\newcommand{\@DTLifdbempty}[3]{%
  \expandafter\ifnum\csname dtlrows@#1\endcsname=0\relax
    #2%
  \else
    #3%
  \fi
}
\newcommand*{\DTLnewrow}{%
  \@ifstar\@sDTLnewrow\@DTLnewrow
}
\newcommand*{\@DTLnewrow}[1]{%
\DTLifdbexists{#1}%
  {\@sDTLnewrow{#1}}%
  {\PackageError{datatool}{Can't add new row to database `#1':
     database doesn't exist}{}}%
}
\newcommand*{\@sDTLnewrow}[1]{%
  \global\advance\csname dtlrows@#1\endcsname by 1\relax
  \@dtl@toks@gput@right@cx{dtldb@#1}{%
     \noexpand\db@row@elt@w%
       \noexpand\db@row@id@w \number\csname dtlrows@#1\endcsname
       \noexpand\db@row@id@end@%
       \noexpand\db@row@id@w \number\csname dtlrows@#1\endcsname
       \noexpand\db@row@id@end@%
     \noexpand\db@row@elt@end@%
  }%
  \dtl@message{New row added to database `#1'}%
}
\newcount\dtlcolumnnum
\newcount\dtlrownum
\newcommand*{\DTLifhaskey}{\@ifstar\@sDTLifhaskey\@DTLifhaskey}
\newcommand{\@DTLifhaskey}[4]{%
   \DTLifdbexists{#1}%
   {%
     \@sDTLifhaskey{#1}{#2}{#3}{#4}%
   }%
   {%
     \PackageError{datatool}{Database `#1' doesn't exist}{}%
   }%
}
\newcommand{\@sDTLifhaskey}[4]{%
  \@ifundefined{dtl@ci@#1@#2}%
  {%
    #4%
  }%
  {%
    #3%
  }%
}
\newcommand*{\DTLgetcolumnindex}{%
  \@ifstar\@sdtl@getcolumnindex\@dtl@getcolumnindex
}
\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]{%
  \expandafter\let\expandafter#1\csname dtl@ci@#2@#3\endcsname
}
\newcommand*{\dtlcolumnindex}[2]{%
  \csname dtl@ci@#1@#2\endcsname
}
\newcommand*{\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
}
\def\DTLunsettype{}
\def\DTLstringtype{0}
\def\DTLinttype{1}
\def\DTLrealtype{2}
\def\DTLcurrencytype{3}
\newcommand*{\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
}
\newcommand*{\@dtl@getprops}[7]{%
  \def\@dtl@get@keydata##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{%
      \def#1{##2}% key
      \def#2{##3}% data type
      #3={##4}% header
      #4={##1}% before stuff
      #5={##5}% after stuff
    }%
  \@dtl@get@keydata#6\q@nil
}
\newtoks\@dtl@before
\newtoks\@dtl@after
\newtoks\@dtl@colhead
\newcommand*{\DTLaddcolumn}{%
   \@ifstar\@sDTLaddcolumn\@DTLaddcolumn
}
\newcommand{\@DTLaddcolumn}[2]{%
  \DTLifdbexists{#1}%
    {\@dtl@updatekeys{#1}{#2}{}}%
    {\PackageError{datatool}{Can't add new column to database `#1':
      database doesn't exist}{}}%
}
\newcommand{\s@DTLaddcolumn}[2]{%
  \@dtl@updatekeys{#1}{#2}{}%
}
\newcommand*{\@dtl@updatekeys}[3]{%
  \@sDTLifhaskey{#1}{#2}%
  {%
    \expandafter\dtlcolumnnum\expandafter
      =\dtlcolumnindex{#1}{#2}\relax
    \edef\@dtl@dogetprops{\noexpand\@dtl@getprops
      {\noexpand\@dtl@key}{\noexpand\@dtl@type}%
      {\noexpand\@dtl@colhead}{\noexpand\@dtl@before}%
      {\noexpand\@dtl@after}{\the\csname dtlkeys@#1\endcsname}%
      {\number\dtlcolumnnum}}%
    \@dtl@dogetprops
    \ifstrempty{#3}%
    {%
    }%
    {%
      \let\@dtl@oldtype\@dtl@type
      \@dtl@checknumerical{#3}%
      \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
      }%
      \ifx\@dtl@oldtype\@dtl@type
      \else
        \@dtl@toks@gconcat@middle@cx{dtlkeys@#1}%
        {\@dtl@before}%
        {%
          \noexpand\db@plist@elt@w% start of key block
            \noexpand\db@col@id@w \the\dtlcolumnnum
              \noexpand\db@col@id@end@% column index
            \noexpand\db@key@id@w #2\noexpand\db@key@id@end@% key id
            \noexpand\db@type@id@w \@dtl@type
              \noexpand\db@type@id@end@% data type
            \noexpand\db@header@id@w \the\@dtl@colhead
              \noexpand\db@header@id@end@% header
            \noexpand\db@col@id@w \the\dtlcolumnnum
              \noexpand\db@col@id@end@% column index
            \noexpand\db@plist@elt@end@% end of key block
        }%
        {\@dtl@after}%
      \fi
    }%
  }%
  {%
    \expandafter\global\expandafter\advance
      \csname dtlcols@#1\endcsname by 1\relax
    \dtlcolumnnum=\csname dtlcols@#1\endcsname\relax
    \expandafter\xdef\csname dtl@ci@#1@#2\endcsname{%
      \number\dtlcolumnnum}%
    \ifstrempty{#2}%
    {%
      \edef\@dtl@type{}% don't know data type yet
    }%
    {%
      \@dtl@checknumerical{#3}%
      \edef\@dtl@type{\number\@dtl@datatype}%
    }%
    \@dtl@toks@gput@right@cx{dtlkeys@#1}%
    {%
      \noexpand\db@plist@elt@w
      \noexpand\db@col@id@w \the\dtlcolumnnum
        \noexpand\db@col@id@end@
      \noexpand\db@key@id@w #2\noexpand\db@key@id@end@
      \noexpand\db@type@id@w \@dtl@type
        \noexpand\db@type@id@end@
      \noexpand\db@header@id@w #2\noexpand\db@header@id@end@
      \noexpand\db@col@id@w \the\dtlcolumnnum
        \noexpand\db@col@id@end@
      \noexpand\db@plist@elt@end@
    }%
  }%
}
\newcommand*{\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]{%
  \edef\@dtl@dogetprops{\noexpand\@dtl@getprops
    {\noexpand\@dtl@key}{\noexpand\@dtl@type}%
    {\noexpand\@dtl@colhead}{\noexpand\@dtl@before}%
    {\noexpand\@dtl@after}{\the\csname dtlkeys@#1\endcsname}%
    {\number#2}}%
  \@dtl@dogetprops
  \@dtl@colhead={#3}%
  \edef\@dtl@colnum{\number#2}\relax
  \@dtl@toks@gconcat@middle@cx{dtlkeys@#1}%
  {\@dtl@before}%
  {%
    \noexpand\db@plist@elt@w% start of block
      \noexpand\db@col@id@w \@dtl@colnum
        \noexpand\db@col@id@end@% index
      \noexpand\db@key@id@w \@dtl@key\noexpand\db@key@id@end@% key
      \noexpand\db@type@id@w \@dtl@type
        \noexpand\db@type@id@end@% data type
      \noexpand\db@header@id@w \the\@dtl@colhead
        \noexpand\db@header@id@end@% header
      \noexpand\db@col@id@w \@dtl@colnum
        \noexpand\db@col@id@end@% index
    \noexpand\db@plist@elt@end@% end of block
  }%
  {\@dtl@after}%
}
\newcommand*{\dtlexpandnewvalue}{%
  \def\@dtl@setnewvalue##1{\protected@edef\@dtl@tmp{##1}%
  \expandafter\@dtl@toks\expandafter{\@dtl@tmp}}%
}
\newcommand*{\dtlnoexpandnewvalue}{%
  \def\@dtl@setnewvalue##1{\@dtl@toks{##1}}%
}
\dtlnoexpandnewvalue
\newcommand{\DTLnewdbentry}{%
   \@ifstar\@sDTLnewdbentry\@DTLnewdbentry
}
\newcommand{\@DTLnewdbentry}[3]{%
  \DTLifdbexists{#1}%
    {\@sDTLnewdbentry{#1}{#2}{#3}}%
    {\PackageError{datatool}{Can't add new entry to database `#1':
      database doesn't exist}{}}%
}
\newcommand*{\@sDTLnewdbentry}[3]{%
  \@dtl@updatekeys{#1}{#2}{#3}%
  \expandafter\dtlcolumnnum\expandafter
    =\dtlcolumnindex{#1}{#2}\relax
  \edef\dtl@dogetrow{\noexpand\dtlgetrow{#1}%
    {\number\csname dtlrows@#1\endcsname}}%
  \dtl@dogetrow
  \edef\dtl@dogetentry{\noexpand\dtlgetentryfromcurrentrow
     {\noexpand\dtl@entry}{\number\dtlcolumnnum}%
  }%
  \dtl@dogetentry
  \ifx\dtl@entry\dtlnovalue
    \@dtl@setnewvalue{#3}%
    \@dtl@toks@gconcat@middle@cx{dtldb@#1}%
    {\dtlbeforerow}%
    {%
      \noexpand\db@row@elt@w%
      \noexpand\db@row@id@w \number\csname dtlrows@#1\endcsname
        \noexpand\db@row@id@end@%
      \the\dtlcurrentrow
      \noexpand\db@col@id@w \number\dtlcolumnnum
        \noexpand\db@col@id@end@%
          \noexpand\db@col@elt@w \the\@dtl@toks
            \noexpand\db@col@elt@end@%
      \noexpand\db@col@id@w \number\dtlcolumnnum
        \noexpand\db@col@id@end@%
      \noexpand\db@row@id@w \number\csname dtlrows@#1\endcsname
        \noexpand\db@row@id@end@%
      \noexpand\db@row@elt@end@%
    }%
    {\dtlafterrow}%
    \dtl@message{Added #2\space -> #3\space to database `#1'}%
  \else
    \PackageError{datatool}{Can't add entry with ID `#2' to
      current row of database `#1'}{There is already an entry with
      this ID in the current row}%
  \fi
}
\newcommand{\DTLifdbexists}[3]{%
  \@ifundefined{dtldb@#1}{#3}{#2}}
\newcommand*{\DTLassign}[3]{%
  \DTLifdbexists{#1}
  {%
    {%
      \dtlgetrow{#1}{#2}%
      \@dtl@assign{#3}{#1}%
    }%
  }%
  {%
    \PackageError{datatool}{Database `#1' doesn't exist}{}%
  }%
}
\newcommand*{\DTLassignfirstmatch}[4]{%
  \dtl@assignfirstmatch{#3}{#1}{#2}{#4}%
}
\newcommand*{\xDTLassignfirstmatch}[4]{%
  \protected@edef\@dtl@asg@value{\expandonce{#3}}%
  \expandafter\dtl@assignfirstmatch\expandafter
   {\@dtl@asg@value}{#1}{#2}{#4}%
}
\newcommand*{\dtl@assignfirstmatch}[4]{%
  \DTLifdbexists{#2}%
  {%
    {%
      \dtlgetrowindex{\dtl@asg@rowidx}{#2}{\dtlcolumnindex{#2}{#3}}{#1}%
      \ifx\dtl@asg@rowidx\dtlnovalue
        \PackageError{datatool}{No match found for
          \string\DTLassignfirstmatch{#2}{#3}{#1}{#4}}{}%
      \else
        \dtlgetrow{#2}{\dtl@asg@rowidx}%
        \@dtl@assign{#4}{#2}%
      \fi
    }%
  }%
  {%
    \PackageError{datatool}{Data base `#2' doesn't exist}{}%
  }%
}
\newcommand*{\@dtl@assign}[2]{%
  \ifstrempty{#1}{}%
  {%
    \@dtl@assigncmd#1,\@nil\@@{#2}%
  }%
}
\def\@dtl@assigncmd#1#2=#3,#4\@@#5{%
  \edef\@dtl@dbname{#5}%
  \@sDTLifhaskey{#5}{#3}%
  {%
    \edef\@dtl@dogetentry{%
      \noexpand\dtlgetentryfromcurrentrow
        {\noexpand#1}{\dtlcolumnindex{#5}{#3}}}%
    \@dtl@dogetentry
    \ifdefequal{#1}{\dtlnovalue}%
    {%
      \@@dtl@setnull{#1}{#3}%
    }%
    {}%
    \global\let#1=#1\relax
  }%
  {%
    \PackageError{datatool}{Can't assign \string#1\space: there
     is no key `#3' in data base `#5'}{}%
    \global\let#1\DTLstringnull
  }%
  \def\dtl@tmp{#4}%
  \ifx\@nnil\dtl@tmp
    \let\@dtl@next\@dtl@assigncmdnoop
  \else
    \let\@dtl@next\@dtl@assigncmd
  \fi
  \@dtl@next#4\@@{#5}%
}
\def\@dtl@assigncmdnoop#1\@@#2{}
\newcommand*{\@dtl@setnull}[2]{%
  \@sDTLifhaskey{\@dtl@dbname}{#2}%
  {%
    \@@dtl@setnull{#1}{#2}%
  }%
  {%
    \global\let#1=\DTLstringnull
  }%
}
\newcommand*{\@@dtl@setnull}[2]{%
  \@sdtlgetdatatype{\@dtl@type}{\@dtl@dbname}{#2}%
  \ifnum0\@dtl@type=0\relax
    \global\let#1=\DTLstringnull
  \else
    \global\let#1=\DTLnumbernull
  \fi
}
\newcommand*{\DTLstringnull}{\@dtlstringnull}
\newcommand*{\@dtlstringnull}{NULL}
\newcommand*{\DTLnumbernull}{\@dtlnumbernull}
\newcommand*{\@dtlnumbernull}{0}
\newcommand*{\DTLifnull}[3]{%
  \ifx#1\dtlnovalue
    #2%
  \else
    \ifx#1\DTLstringnull
      #2%
    \else
      \ifx#1\DTLnumbernull
        #2%
      \else
        #3%
      \fi
    \fi
  \fi
}
\newcommand*{\DTLifnullorempty}[3]{%
  \ifdefempty{#1}{#2}{\DTLifnull{#1}{#2}{#3}}%
}
\def\@dtlnovalue{Undefined Value}
\def\dtlnovalue{\@dtlnovalue}
\newcommand*{\DTLgetkeydata}{%
  \@ifstar\@sdtlgetkeydata\@dtlgetkeydata
}
\newcommand*{\@dtlgetkeydata}[5]{%
  \DTLifdbexists{#2}%
  {%
    \@sDTLifhaskey{#2}{#1}%
    {%
      \@sdtlgetkeydata{#1}{#2}{#3}{#4}{#5}%
    }%
    {%
      \PackageError{datatool}{Key `#1' not defined in database
        `#2'}{}%
    }%
  }%
  {%
    \PackageError{datatool}{Database `#2' doesn't exist}{}%
  }%
}
\newcommand*{\@sdtlgetkeydata}[5]{%
  \@sdtl@getcolumnindex{#3}{#2}{#1}%
  \edef\@dtl@dogetkeydata{\noexpand\@dtl@getprops
    {\noexpand\@dtl@key}{\noexpand#4}{\noexpand\@dtl@colhead}%
    {\noexpand\@dtl@before}{\noexpand\@dtl@after}%
    {\expandafter\the\csname dtlkeys@#2\endcsname}%
    {#3}}%
  \@dtl@dogetkeydata
  \edef#5{\the\@dtl@toks}%
}
\newcommand{\dtl@gathervalues}[3][key]{%
  \dtlforeachkey(\@dtl@key,\@dtl@col,\@dtl@type,\@dtl@head)\in{#2}\do
  {%
    \dtlgetentryfromrow{\@dtl@tmp}{\@dtl@col}{#3}%
    \ifx\@dtl@tmp\dtlnovalue
      \@dtl@setnull{\@dtl@tmp}{\@dtl@key}%
    \fi
    \expandafter\let\csname @dtl@#1@\@dtl@key\endcsname\@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}%
    \ifx\@dtl@tmp\dtlnovalue
      \@dtl@setnull{\@dtl@tmp}{\@dtl@key}%
    \fi
    \expandafter\global
      \expandafter\let\csname @dtl@#1@\@dtl@key\endcsname\@dtl@tmp
  }%
}
\newtoks\dtlcurrentrow
\newtoks\dtlbeforerow
\newtoks\dtlafterrow

\newcommand*{\dtlgetrow}[2]{%
  \dtlrownum=#2\relax
  \edef\dtldbname{#1}%
  \expandafter\toks@\expandafter=\csname dtldb@#1\endcsname
  \edef\@dtl@dogetrow{\noexpand\@dtlgetrow{\the\toks@}{\number#2}}%
  \@dtl@dogetrow
}
\newcommand{\edtlgetrowforvalue}[3]{%
  \protected@edef\@dtl@dogetrowforvalue{%
    \noexpand\dtlgetrowforvalue{#1}{#2}{#3}}%
  \@dtl@dogetrowforvalue
}
\newcommand{\DTLfetch}[4]{%
  \edtlgetrowforvalue{#1}{\dtlcolumnindex{#1}{#2}}{#3}%
  \dtlgetentryfromcurrentrow{\dtlcurrentvalue}{\dtlcolumnindex{#1}{#4}}%
  \dtlcurrentvalue
}
\newcommand*{\dtlgetrowforvalue}[3]{%
  \dtlgetrowindex{\dtl@rowidx}{#1}{#2}{#3}%
  \ifx\dtl@rowidx\dtlnovalue
    \PackageError{datatool}{No row found in database `#1' for
     column `\number#2' matching `#3'}{}%
  \else
    \dtlrownum=\dtl@rowidx\relax
    \edef\dtldbname{#1}%
    \expandafter\toks@\expandafter=\csname dtldb@#1\endcsname
    \edef\@dtl@dogetrow{\noexpand\@dtlgetrow{\the\toks@}{\dtl@rowidx}}%
    \@dtl@dogetrow
  \fi
}
\newcommand*{\@dtlgetrow}[2]{%
  \def\@dtl@getrow##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}}%
  \@dtl@getrow#1\q@nil
}
\newcommand*{\dtlrecombine}{%
   \@dtl@toks@gconcat@middle@cx{dtldb@\dtldbname}%
   {\dtlbeforerow}%
   {%
      \noexpand\db@row@elt@w
        \noexpand\db@row@id@w
          \number\dtlrownum
        \noexpand\db@row@id@end@
         \the\dtlcurrentrow
        \noexpand\db@row@id@w
          \number\dtlrownum
        \noexpand\db@row@id@end@
      \noexpand\db@row@elt@end@
   }%
   {\dtlafterrow}%
}
\newcommand{\dtlrecombineomitcurrent}{%
  \dtl@decrementrows{\dtlafterrow}{\dtlrownum}
  \csname dtldb@\dtldbname\endcsname=\dtlbeforerow
  \@dtl@toks@gput@right@cx{dtldb@\dtldbname}{\the\dtlafterrow}%
  \dtl@message{Removed row \number\dtlrownum\space in database
    `\dtldbname'}%
}
\newcommand*{\dtlsplitrow}[4]{%
  \def\@dtlsplitrow##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{\def#3{##1}\def#4{##3}}%
   \@dtlsplitrow#1\q@nil
}
\newcommand*{\dtlreplaceentryincurrentrow}[2]{%
  \edef\@dtl@do@splitrow{\noexpand\dtlsplitrow
   {\the\dtlcurrentrow}%
   {\number#2}%
   {\noexpand\@dtl@before@cs}%
   {\noexpand\@dtl@after@cs}}%
  \@dtl@do@splitrow
  \toks@{#1}%
  \edef\@dtl@stuff{%
    \expandonce\@dtl@before@cs
      \noexpand\db@col@id@w \number#2\noexpand
        \noexpand\db@col@id@end@% column id
      \noexpand\db@col@elt@w
        \the\toks@
      \noexpand\db@col@elt@end@
      \noexpand\db@col@id@w \number#2\noexpand
        \noexpand\db@col@id@end@% column id
    \expandonce\@dtl@after@cs
  }%
  \expandafter\dtlcurrentrow\expandafter{\@dtl@stuff}%
  \@sdtlgetkeyforcolumn{\@dtl@key}{\dtldbname}{#2}%
  \@dtl@updatekeys{\dtldbname}{\@dtl@key}{#1}%
  \dtl@message{Updated \@dtl@key\space -> #1\space in database
    `\dtldbname'}%
}
\newcommand*{\dtlremoveentryincurrentrow}[1]{%
  \edef\@dtl@do@splitrow{\noexpand\dtlsplitrow
   {\the\dtlcurrentrow}%
   {\number#1}%
   {\noexpand\@dtl@before@cs}%
   {\noexpand\@dtl@after@cs}}%
  \@dtl@do@splitrow
  \edef\@dtl@stuff{%
    \expandonce\@dtl@before@cs
    \expandonce\@dtl@after@cs
  }%
  \expandafter\dtlcurrentrow\expandafter{\@dtl@stuff}%
  \dtl@message{Removed entry from column \number#1\space\space in database
    `\dtldbname'}%
}
\newcommand*{\dtlswapentriesincurrentrow}[2]{%
  \dtlgetentryfromcurrentrow{\@dtl@entryI}{#1}%
  \dtlgetentryfromcurrentrow{\@dtl@entryII}{#2}%
  \expandafter\dtlreplaceentryincurrentrow\expandafter
   {\@dtl@entryII}{#1}%
  \expandafter\dtlreplaceentryincurrentrow\expandafter
   {\@dtl@entryI}{#2}%
}
\newcommand*{\dtlgetentryfromcurrentrow}[2]{%
  \dtlgetentryfromrow{#1}{#2}{\dtlcurrentrow}%
}
\newcommand*{\dtlgetentryfromrow}[3]{%
  \edef\@dtl@do@getentry{\noexpand\dtl@getentryfromrow
    {\noexpand#1}{\number#2}{\the#3}}%
  \@dtl@do@getentry
}
\newcommand*{\dtl@getentryfromrow}[3]{%
  \def\dtl@dogetentry##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{\def#1{##2}}%
  \dtl@dogetentry#3%
    \db@col@id@w #2\db@col@id@end@%
      \db@col@elt@w \@dtlnovalue\db@col@elt@end@%
    \db@col@id@w #2\db@col@id@end@%
    \q@nil
}
\newcommand*{\dtlappendentrytocurrentrow}[2]{%
      \@dtl@updatekeys{\dtldbname}{#1}{#2}%
      \expandafter\dtlcolumnnum\expandafter
        =\dtlcolumnindex{\dtldbname}{#1}\relax
      \edef\dtl@dogetentry{\noexpand\dtlgetentryfromcurrentrow
        {\noexpand\dtl@entry}{\number\dtlcolumnnum}%
      }%
      \dtl@dogetentry
      \ifx\dtl@entry\dtlnovalue
        \protected@edef\@dtl@tmp{#2}%
        \expandafter\@dtl@toks\expandafter{\@dtl@tmp}%
        \@dtl@toks@gput@right@cx{dtlcurrentrow}%
        {%
          \noexpand\db@col@id@w
            \number\dtlcolumnnum
          \noexpand\db@col@id@end@
          \noexpand\db@col@elt@w
            \the\@dtl@toks
          \noexpand\db@col@elt@end@
          \noexpand\db@col@id@w
             \number\dtlcolumnnum
          \noexpand\db@col@id@end@
        }%
         \dtl@message{Appended #1\space -> #2\space to database
           `\dtldbname'}%
      \else
        \PackageError{datatool}{Can't append entry to row:
          there is already an entry for key `#1' in this row}{}%
      \fi
}
\newcommand*{\dtlupdateentryincurrentrow}[2]{%
      \@dtl@updatekeys{\dtldbname}{#1}{#2}%
      \expandafter\dtlcolumnnum\expandafter
        =\dtlcolumnindex{\dtldbname}{#1}\relax
      \edef\dtl@dogetentry{\noexpand\dtlgetentryfromcurrentrow
        {\noexpand\dtl@entry}{\number\dtlcolumnnum}%
      }%
      \dtl@dogetentry
      \ifx\dtl@entry\dtlnovalue
        \protected@edef\@dtl@tmp{#2}%
        \expandafter\@dtl@toks\expandafter{\@dtl@tmp}%
        \@dtl@toks@gput@right@cx{dtlcurrentrow}%
        {%
          \noexpand\db@col@id@w
            \number\dtlcolumnnum
          \noexpand\db@col@id@end@
          \noexpand\db@col@elt@w
            \the\@dtl@toks
          \noexpand\db@col@elt@end@
          \noexpand\db@col@id@w
             \number\dtlcolumnnum
          \noexpand\db@col@id@end@
        }%
         \dtl@message{Appended #1\space -> #2\space to database
           `\dtldbname'}%
      \else
        \toks@{#2}%
        \edef\do@dtlreplaceincurrentrow{%
           \noexpand\dtlreplaceentryincurrentrow{\the\toks@}{\number\dtlcolumnnum}%
        }%
        \do@dtlreplaceincurrentrow
      \fi
}
\newcommand*{\DTLgetvalue}[4]{%
  \edef\dtl@dogetvalue{\noexpand\dtl@getvalue{\noexpand#1}{#2}%
    {\number#3}{\number#4}}%
  \dtl@dogetvalue
}
\newcommand*{\dtl@getvalue}[4]{%
  \def\@dtl@getvalue ##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
     ##4% stuff after value
     \q@nil{\def#1{##3}}%
  \toks@=\csname dtldb@#2\endcsname
  \expandafter\@dtl@getvalue\the\toks@% contents of data base
     \db@row@id@w #3\db@row@id@end@%
       \db@col@id@w #4\db@col@id@end@%
         \db@col@elt@w \@dtlnovalue\db@col@elt@end@% undefined value
     \q@nil
  \ifx#1\dtlnovalue
    \PackageError{datatool}{There is no element at (row=#3,\space
      column=#4) in database `#2'}{}%
  \fi
}
\newcommand*{\DTLgetlocation}[4]{%
  \def\@dtl@getlocation##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{\def#1{##4}\def#2{##2}}%
  \toks@=\csname dtldb@#3\endcsname
  \expandafter\@dtl@getlocation\the\toks@% contents of data base
    \db@col@elt@w #4\db@col@elt@end@% value
    \db@col@id@w \@dtlnovalue\db@col@id@end@% undefined column id
    \db@row@id@w \@dtlnovalue\db@row@id@end@% undefined row id
    \q@nil
  \ifx#1\dtlnovalue
    \PackageError{datatool}{There is no element `#4' in database `#3'}{}%
  \fi
}
\newcommand*{\DTLgetrowindex}[4]{%
  \toks@{#4}%
  \edef\dtl@dogetrowindex{\noexpand\@dtlgetrowindex{\noexpand#1}{#2}{\number#3}{\the\toks@}}%
  \dtl@dogetrowindex
  \ifx#1\dtlnovalue
    \PackageError{datatool}{There is no element `#4' for column
      \number#3\space in database `#2'}{}%
  \fi
}
\newcommand*{\dtlgetrowindex}[4]{%
  \toks@{#4}%
  \edef\dtl@dogetrowindex{\noexpand\@dtlgetrowindex{\noexpand#1}{#2}{\number#3}{\the\toks@}}%
  \dtl@dogetrowindex
}
\newcommand*{\xdtlgetrowindex}[4]{%
  \protected@edef\dtl@dogetrowindex{\noexpand\@dtlgetrowindex{\noexpand#1}{#2}{\number#3}{#4}}%
  \dtl@dogetrowindex
}
\newcommand*{\@dtlgetrowindex}[4]{%
  \def\@dtl@getrowindex##1% stuff before value
    \db@col@elt@w #4\db@col@elt@end@% value
    \db@col@id@w #3\db@col@id@end@% column id
    ##2% stuff after this column
    \db@row@id@w ##3\db@row@id@end@% row id
    ##4% stuff after row
    \q@nil{\def#1{##3}}%
  \toks@=\csname dtldb@#2\endcsname
  \expandafter\@dtl@getrowindex\the\toks@% contents of data base
    \db@col@elt@w #4\db@col@elt@end@% value
    \db@col@id@w #3\db@col@id@end@% column id
    \db@row@id@w \@dtlnovalue\db@row@id@end@% undefined row id
    \q@nil
}

\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{}
\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
}
\newcommand*{\dtlforcolumn}{\@ifstar\@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}{}%
  }%
}
\newcommand{\@sdtlforcolumn}[4]{%
    \toks@{#4}%
    \edef\@dtl@doforcol{\noexpand\dtl@forcolumn{\noexpand#1}%
      {\expandafter\the\csname dtldb@#2\endcsname}%
      {\dtlcolumnindex{#2}{#3}}{\the\toks@}%
    }%
    \@dtl@doforcol%
}
\newcommand*{\dtlforcolumnidx}{%
  \@ifstar\@sdtlforcolumnidx\@dtlforcolumnidx
}
\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{\@sdtlforcolumnidx}[4]{%
    \toks@{#4}%
    \edef\@dtl@doforcol{\noexpand\dtl@forcolumn{\noexpand#1}%
      {\expandafter\the\csname dtldb@#2\endcsname}%
      {\number#3}{\the\toks@}%
    }%
    \@dtl@doforcol
}
\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{DTLrowi}
\newcounter{DTLrowii}
\newcounter{DTLrowiii}
\newcounter{DTLrow}
\def\theHDTLrow{\arabic{DTLrow}}
\def\theHDTLrowi{\theHDTLrow.\arabic{DTLrowi}}
\def\theHDTLrowii{\theHDTLrowi.\arabic{DTLrowii}}
\def\theHDTLrowiii{\theHDTLrowii.\arabic{DTLrowiii}}
\newcount\dtl@rowi
\newcount\dtl@rowii
\newcount\dtl@rowiii
\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}
\newenvironment{DTLenvforeach}[3][\boolean{true}]%
{%
  \def\@dtlenvforeach@args{[#1]{#2}{#3}}%
  \long@collect@body\@do@dtlenvforeach
}%
{}
\newcommand{\@do@dtlenvforeach}[1]{%
  \expandafter\@DTLforeach\@dtlenvforeach@args{#1}%
}
\newenvironment{DTLenvforeach*}[3][\boolean{true}]%
{%
  \def\s@dtlenvforeach@args{[#1]{#2}{#3}}%
  \long@collect@body\@do@sdtlenvforeach
}%
{}
\newcommand{\@do@sdtlenvforeach}[1]{%
  \expandafter\@sDTLforeach\s@dtlenvforeach@args{#1}%
}
\newcommand*{\DTLforeach}{\@ifstar\@sDTLforeach\@DTLforeach}
\newcommand{\@DTLforeach}[4][\boolean{true}]{%
  \DTLifdbexists{#2}%
  {%
    \refstepcounter{DTLrow}%
    \global\c@DTLrow=\c@DTLrow\relax
    \xdef\@dtl@dbname{#2}%
    \global\advance\dtlforeachlevel by 1\relax
    \ifnum\dtlforeachlevel>3\relax
      \PackageError{datatool}{\string\DTLforeach\space nested too
        deeply}{Only 3 levels are allowed}%
    \else
       \@DTLifdbempty{#2}%
       {}%
       {%
        \expandafter\global
          \csname c@DTLrow\romannumeral\dtlforeachlevel\endcsname
            = 0\relax
        \expandafter\global\expandafter\let%
          \csname @dtl@iffirstrow\the\dtlforeachlevel\endcsname
          \DTLiffirstrow
        \gdef\DTLiffirstrow##1##2{%
          \expandafter\ifnum
           \csname c@DTLrow\romannumeral\dtlforeachlevel\endcsname
           =1 %space intended
            ##1%
          \else
            ##2%
          \fi}%
        \expandafter\global\expandafter\let%
          \csname @dtl@iflastrow\the\dtlforeachlevel\endcsname
          \DTLiflastrow
        \gdef\DTLiflastrow##1##2{%
          \expandafter\ifnum
           \csname c@DTLrow\romannumeral\dtlforeachlevel\endcsname
           =\csname dtlrows@#2\endcsname
            ##1%
          \else
            ##2%
          \fi}%
        \expandafter\global\expandafter\let%
          \csname @dtl@ifoddrow\the\dtlforeachlevel\endcsname
          \DTLifoddrow
        \gdef\DTLifoddrow##1##2{%
          \expandafter\ifodd
           \csname c@DTLrow\romannumeral\dtlforeachlevel\endcsname
            ##1%
          \else
            ##2%
          \fi}%
        \expandafter\global\expandafter\let
          \csname @dtl@dbname@\romannumeral\dtlforeachlevel\endcsname
            =\@dtl@dbname
        \expandafter\global\expandafter\let
          \csname @dtl@ro@\romannumeral\dtlforeachlevel\endcsname
            = 0\relax
        \dtlgforint
          \csname dtl@row\romannumeral\dtlforeachlevel\endcsname
          =1\to\csname dtlrows@#2\endcsname\step1\do
        {%
          \@dtl@tmpcount=
            \csname dtl@row\romannumeral\dtlforeachlevel\endcsname
          \edef\dtl@dogetrow{\noexpand\dtlgetrow{#2}%
            {\number\@dtl@tmpcount}}%
          \dtl@dogetrow
          \expandafter\global
            \csname @dtl@cur\romannumeral\dtlforeachlevel\endcsname
              = \dtlcurrentrow
          \expandafter\global
            \csname @dtl@prev\romannumeral\dtlforeachlevel\endcsname
              = \dtlbeforerow
          \expandafter\global
            \csname @dtl@next\romannumeral\dtlforeachlevel\endcsname
              = \dtlafterrow
          \ifblank{#3}{}{\@dtl@assign{#3}{#2}}%
          \ifthenelse{#1}%
          {%
            \refstepcounter{DTLrow\romannumeral\dtlforeachlevel}%
            \expandafter\edef\expandafter\DTLcurrentindex%
              \expandafter{%
                 \arabic{DTLrow\romannumeral\dtlforeachlevel}}%
            #4%
            \edef\@dtl@tmp{\expandafter\the
              \csname @dtl@cur\romannumeral
                \dtlforeachlevel\endcsname}%
            \ifx\@dtl@tmp\@nnil
              \expandafter\dtl@decrementrows\expandafter
                 {\csname @dtl@prev\romannumeral
                    \dtlforeachlevel\endcsname
                 }{\csname dtl@row\romannumeral
                   \dtlforeachlevel\endcsname}%
              \expandafter\dtl@decrementrows\expandafter
                 {\csname @dtl@next\romannumeral
                    \dtlforeachlevel\endcsname
                 }{\csname dtl@row\romannumeral
                   \dtlforeachlevel\endcsname}%
              \edef\@dtl@tmp{%
                \expandafter\the
                  \csname @dtl@prev\romannumeral
                    \dtlforeachlevel\endcsname
                \expandafter\the
                  \csname @dtl@next\romannumeral
                     \dtlforeachlevel\endcsname
                }%
              \expandafter\global\expandafter
                 \csname dtldb@#2\endcsname\expandafter{\@dtl@tmp}%
              \expandafter\global\expandafter
                \advance\csname dtlrows@#2\endcsname by -1\relax
              \expandafter\global\expandafter
                \advance\csname dtl@row\romannumeral
                   \dtlforeachlevel\endcsname by -1\relax
            \else
              \@dtl@before=\csname @dtl@prev\romannumeral
                \dtlforeachlevel\endcsname
              \@dtl@after=\csname @dtl@next\romannumeral
                \dtlforeachlevel\endcsname
              \@dtl@toks@gconcat@middle@cx{dtldb@#2}%
              {\@dtl@before}%
              {%
                \noexpand\db@row@elt@w%
                \noexpand\db@row@id@w \expandafter\number
                  \csname dtl@row\romannumeral
                    \dtlforeachlevel\endcsname
                \noexpand\db@row@id@end@%
                \expandafter\the
                  \csname @dtl@cur\romannumeral
                    \dtlforeachlevel\endcsname
                \noexpand\db@row@id@w \expandafter\number
                  \csname dtl@row\romannumeral
                    \dtlforeachlevel\endcsname
                \noexpand\db@row@id@end@%
                \noexpand\db@row@elt@end@%
                }%
              {\@dtl@after}%
            \fi
          }%
          {}%
         }%
        \expandafter\global\expandafter\let\expandafter\DTLiffirstrow
          \csname @dtl@iffirstrow\the\dtlforeachlevel\endcsname
        \expandafter\global\expandafter\let\expandafter\DTLiflastrow
          \csname @dtl@iflastrow\the\dtlforeachlevel\endcsname
        \expandafter\global\expandafter\let\expandafter\DTLifoddrow
          \csname @dtl@ifoddrow\the\dtlforeachlevel\endcsname
      }%
    \fi
    \global\advance\dtlforeachlevel by -1\relax
  }%
  {%
    \PackageError{datatool}{Database `#2' doesn't exist}{}%
  }%
}
\newcommand{\@sDTLforeach}[4][\boolean{true}]{%
  \DTLifdbexists{#2}%
  {%
    \refstepcounter{DTLrow}%
    \global\c@DTLrow=\c@DTLrow
    \xdef\@dtl@dbname{#2}%
    \global\advance\dtlforeachlevel by 1\relax
    \ifnum\dtlforeachlevel>3\relax
      \PackageError{datatool}{\string\DTLforeach\space nested too
        deeply}{Only 3 levels are allowed}%
    \else
       \@DTLifdbempty{#2}%
       {}%
       {%
        \expandafter\global
          \csname c@DTLrow\romannumeral\dtlforeachlevel\endcsname
            = 0\relax
        \expandafter\global\expandafter\let%
          \csname @dtl@iffirstrow\the\dtlforeachlevel\endcsname
          \DTLiffirstrow
        \gdef\DTLiffirstrow##1##2{%
          \expandafter\ifnum
           \csname c@DTLrow\romannumeral\dtlforeachlevel\endcsname
           =1 % space intended
            ##1%
          \else
            ##2%
          \fi}%
        \expandafter\global\expandafter\let%
          \csname @dtl@iflastrow\the\dtlforeachlevel\endcsname
          \DTLiflastrow
        \gdef\DTLiflastrow##1##2{%
          \expandafter\ifnum
           \csname c@DTLrow\romannumeral\dtlforeachlevel\endcsname
           =\csname dtlrows@#2\endcsname
            ##1%
          \else
            ##2%
          \fi}%
        \expandafter\global\expandafter\let%
          \csname @dtl@ifoddrow\the\dtlforeachlevel\endcsname
          \DTLifoddrow
        \gdef\DTLifoddrow##1##2{%
          \expandafter\ifodd
           \csname c@DTLrow\romannumeral\dtlforeachlevel\endcsname
            ##1%
          \else
            ##2%
          \fi}%
        \expandafter\gdef\csname @dtl@dbname@\romannumeral
          \dtlforeachlevel\endcsname{#2}%
        \expandafter\global\expandafter\let
          \csname @dtl@ro@\romannumeral\dtlforeachlevel\endcsname
            = 1\relax
        \@dtlforeachrow(\dtl@thisidx,\dtl@thisrow)\in{#2}\do%
        {%
          \csname dtl@row\romannumeral\dtlforeachlevel\endcsname
            = \dtl@thisidx\relax
          \expandafter\global
            \csname @dtl@cur\romannumeral\dtlforeachlevel\endcsname
              = \expandafter{\dtl@thisrow}%
          \ifblank{#3}{}
          {%
            \dtlcurrentrow=\expandafter{\dtl@thisrow}%
            \@dtl@assign{#3}{#2}%
          }%
          \ifthenelse{#1}%
          {%
            \refstepcounter{DTLrow\romannumeral\dtlforeachlevel}%
            \expandafter\edef\expandafter\DTLcurrentindex%
              \expandafter{%
                 \arabic{DTLrow\romannumeral\dtlforeachlevel}}%
            #4%
          }%
          {}%
         }%
        \expandafter\global\expandafter\let\expandafter\DTLiffirstrow
          \csname @dtl@iffirstrow\the\dtlforeachlevel\endcsname
        \expandafter\global\expandafter\let\expandafter\DTLiflastrow
          \csname @dtl@iflastrow\the\dtlforeachlevel\endcsname
        \expandafter\global\expandafter\let\expandafter\DTLifoddrow
          \csname @dtl@ifoddrow\the\dtlforeachlevel\endcsname
      }%
    \fi
    \global\advance\dtlforeachlevel by -1\relax
  }%
  {%
    \PackageError{datatool}{Database `#2' doesn't exist}{}%
  }%
}
\newcommand*{\@dtlifreadonly}[2]{%
  \expandafter\ifx
    \csname @dtl@ro@\romannumeral\dtlforeachlevel\endcsname1\relax
    #1%
  \else
    #2%
  \fi
}
\newcommand*{\DTLappendtorow}[2]{%
  \ifnum\dtlforeachlevel=0\relax
    \PackageError{datatool}{\string\DTLappendrow\space can only be
      used inside \string\DTLforeach}{}%
  \else
    \expandafter\let\expandafter\@dtl@thisdb
      \csname @dtl@dbname@\romannumeral\dtlforeachlevel\endcsname
    \@dtlifreadonly
    {%
      \PackageError{datatool}{\string\DTLappendtorow\space can't
       be used inside \DTLforeach*}{The starred version of
       \string\DTLforeach\space is read only}%
    }%
    {%
      \dtlrownum=
        \csname dtl@row\romannumeral\dtlforeachlevel\endcsname\relax
      \@dtl@updatekeys{\@dtl@thisdb}{#1}{#2}%
      \expandafter\dtlcolumnnum\expandafter
        =\dtlcolumnindex{\@dtl@thisdb}{#1}\relax
      \dtlcurrentrow =
        \csname @dtl@cur\romannumeral\dtlforeachlevel\endcsname
      \edef\dtl@dogetentry{\noexpand\dtlgetentryfromcurrentrow
        {\noexpand\dtl@entry}{\number\dtlcolumnnum}%
      }%
      \dtl@dogetentry
      \ifx\dtl@entry\dtlnovalue
        \protected@edef\@dtl@tmp{#2}%
        \expandafter\@dtl@toks\expandafter{\@dtl@tmp}%
        \@dtl@toks@gput@right@cx{@dtl@cur\romannumeral\dtlforeachlevel}%
        {%
          \noexpand\db@col@id@w \number\dtlcolumnnum
            \noexpand\db@col@id@end@
          \noexpand\db@col@elt@w \the\@dtl@toks
            \noexpand\db@col@elt@end@
          \noexpand\db@col@id@w \number\dtlcolumnnum
            \noexpand\db@col@id@end@
        }%
         \dtl@message{Appended #1\space -> #2\space to database
           `\@dtl@thisdb'}%
      \else
        \PackageError{datatool}{Can't append entry to row:
          there is already an entry for key `#1' in this row}{}%
      \fi
    }%
  \fi
}
\newcommand*{\DTLremoveentryfromrow}[1]{%
  \ifnum\dtlforeachlevel=0\relax
    \PackageError{datatool}{\string\DTLremoventryfromrow\space
      can only be used inside \string\DTLforeach}{}%
  \else
    \expandafter\let\expandafter\@dtl@thisdb
      \csname @dtl@dbname@\romannumeral\dtlforeachlevel\endcsname
    \@dtlifreadonly
    {%
      \PackageError{datatool}{\string\DTLremoveentryfromrow\space
        can't be used inside \string\DTLforeach*}{The starred
        version of \string\DTLforeach\space is read only}%
    }%
    {%
      \dtlrownum=
        \csname dtl@row\romannumeral\dtlforeachlevel\endcsname\relax
      \@DTLifhaskey{\@dtl@thisdb}{#1}%
      {%
        \@dtl@getcolumnindex{\thiscol}{\@dtl@thisdb}{#1}\relax
        \dtlcolumnnum=\thiscol\relax
        \dtlcurrentrow =
          \csname @dtl@cur\romannumeral\dtlforeachlevel\endcsname
        \edef\dtl@dogetentry{\noexpand\dtlgetentryfromcurrentrow
          {\noexpand\dtl@entry}{\number\dtlcolumnnum}%
        }%
        \dtl@dogetentry
        \ifx\dtl@entry\dtlnovalue
          \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'}%
        \else
          \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'}%
        \fi
      }%
      {%
        \PackageError{datatool}{Can't remove entry given by
          `#1' - no such key exists}{}%
      }%
    }%
  \fi
}
\newcommand*{\DTLreplaceentryforrow}[2]{%
  \ifnum\dtlforeachlevel=0\relax
    \PackageError{datatool}{\string\DTLreplaceentryforrow\space
      can only be used inside \string\DTLforeach}{}%
  \else
    \expandafter\let\expandafter\@dtl@thisdb
      \csname @dtl@dbname@\romannumeral\dtlforeachlevel\endcsname
    \@dtlifreadonly
    {%
      \PackageError{datatool}{\string\DTLreplaceentryforrow\space
        can't be used inside \string\DTLforeach*}{The starred version
        of \string\DTLforeach\space is read only}%
    }%
    {%
      \dtlrownum=
        \csname dtl@row\romannumeral\dtlforeachlevel\endcsname\relax
      \@DTLifhaskey{\@dtl@thisdb}{#1}%
      {%
        \@dtl@getcolumnindex{\thiscol}{\@dtl@thisdb}{#1}\relax
        \dtlcolumnnum=\thiscol\relax
        \dtlcurrentrow =
          \csname @dtl@cur\romannumeral\dtlforeachlevel\endcsname
        \edef\dtl@dogetentry{\noexpand\dtlgetentryfromcurrentrow
          {\noexpand\dtl@entry}{\number\dtlcolumnnum}%
        }%
        \dtl@dogetentry
        \ifx\dtl@entry\dtlnovalue
          \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'}%
        \else
          \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}%
          \@dtl@toks@gconcat@middle@cx
            {@dtl@cur\romannumeral\dtlforeachlevel}%
            {\@dtl@before}%
            {%
              \noexpand\db@col@id@w \number\dtlcolumnnum
                \noexpand\db@col@id@end@%
              \noexpand\db@col@elt@w \the\@dtl@toks
                \noexpand\db@col@elt@end@%
              \noexpand\db@col@id@w \number\dtlcolumnnum
                \noexpand\db@col@id@end@%
            }%
            {\@dtl@after}%
         \dtl@message{Updated #1\space -> #2\space in database
           `\@dtl@thisdb'}%
        \fi
      }%
      {%
         \PackageError{datatool}{Can't replace key `#1' - no such
           key in database `\@dtl@thisdb'}{}%
      }%
    }%
  \fi
}
\newcommand*{\DTLremovecurrentrow}{%
  \ifnum\dtlforeachlevel=0\relax
    \PackageError{datatool}{\string\DTLremovecurrentrow\space can
      only be used inside \string\DTLforeach}{}%
  \else
    \expandafter\let\expandafter\@dtl@thisdb
      \csname @dtl@dbname@\romannumeral\dtlforeachlevel\endcsname
    \@dtlifreadonly
    {%
      \PackageError{datatool}{\string\DTLreplaceentryforrow\space
        can't be used inside \string\DTLforeach*}{The starred version
        of \string\DTLforeach\space is read only}%
    }%
    {%
      \expandafter\global
        \csname @dtl@cur\romannumeral\dtlforeachlevel\endcsname
          ={\@nil}%
    }%
  \fi
}
\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
}
\newcommand{\DTLiffirstrow}[2]{%
  \PackageError{datatool}{\string\DTLiffirstrow\space can only
  be used inside \string\DTLforeach}{}%
}
\newcommand{\DTLiflastrow}[2]{%
  \PackageError{datatool}{\string\DTLiflastrow\space can only
  be used inside \string\DTLforeach}{}%
}
\newcommand{\DTLifoddrow}[2]{%
  \PackageError{datatool}{\string\DTLifoddrow\space can only
  be used inside \string\DTLforeach}{}%
}
\newcommand*{\dtlbetweencols}{}
\newcommand*{\dtlbeforecols}{}
\newcommand*{\dtlaftercols}{}
\newcommand*{\dtlstringalign}{l}
\newcommand*{\dtlintalign}{r}
\newcommand*{\dtlrealalign}{r}
\newcommand*{\dtlcurrencyalign}{r}
\newcommand*{\dtladdalign}[4]{%
  \ifnum#3=1\relax
    \protected@edef#1{\dtlbeforecols}%
  \else
    \protected@edef#1{#1\dtlbetweencols}%
  \fi
  \ifstrempty{#2}%
  {%
    \protected@edef#1{#1c}%
  }%
  {%
    \ifcase#2\relax
      \protected@edef#1{#1\dtlstringalign}%
    \or
      \protected@edef#1{#1\dtlintalign}%
    \or
      \protected@edef#1{#1\dtlrealalign}%
    \or
      \protected@edef#1{#1\dtlcurrencyalign}%
    \else
      \protected@edef#1{#1c}%
      \PackageError{datatool}{Unknown data type `#2'}{}%
    \fi
  }%
  \ifnum#3=#4\relax
    \protected@edef#1{#1\dtlaftercols}%
  \fi
}
\newcommand*{\dtlheaderformat}[1]{\null\hfil\textbf{#1}\hfil\null}
\newcommand*{\dtlstringformat}[1]{#1}
\newcommand*{\dtlintformat}[1]{#1}
\newcommand*{\dtlrealformat}[1]{#1}
\newcommand*{\dtlcurrencyformat}[1]{#1}
\newcommand*{\dtldisplaystarttab}{}
\newcommand*{\dtldisplayendtab}{}
\newcommand*{\dtldisplayafterhead}{}
\newcommand*{\dtldisplayvalign}{c}
\newcommand*{\dtldisplaystartrow}{}
\newcommand{\dtldisplaycr}{\tabularnewline}

\newcommand*{\DTLdisplaydb}[2][]{%
  \def\@dtl@doamp{\gdef\@dtl@doamp{&}}%
  \def\@dtl@resetdoamp{\gdef\@dtl@doamp{\gdef\@dtl@doamp{&}}}%
  \edef\@dtl@maxcols{\expandafter\number
    \csname dtlcols@#2\endcsname}%
  \DTLnumitemsinlist{#1}{\@dtl@tmp}%
  \dtlsub{\@dtl@maxcols}{\@dtl@maxcols}{\@dtl@tmp}%
  \dtlclip{\@dtl@maxcols}{\@dtl@maxcols}%
  \def\@dtl@tabargs{}%
  \dtlforeachkey(\@dtl@key,\@dtl@idx,\@dtl@type,\@dtl@head)%
    \in{#2}\do
  {%
    \expandafter\DTLifinlist\expandafter{\@dtl@key}{#1}%
    {}%
    {%
       \dtladdalign\@dtl@tabargs\@dtl@type\@dtl@idx\@dtl@maxcols
    }%
  }%
  \edef\@dtl@dobegintab{\noexpand\begin{tabular}[\dtldisplayvalign]{\@dtl@tabargs}}%
  \@dtl@dobegintab
  \dtldisplaystarttab
  \@dtl@resetdoamp
  \dtlforeachkey(\@dtl@key,\@dtl@idx,\@dtl@type,\@dtl@head)%
    \in{#2}\do
  {%
    \expandafter\DTLifinlist\expandafter{\@dtl@key}{#1}%
    {}%
    {%
      \@dtl@doamp
      \dtlheaderformat{\@dtl@head}%
    }%
  }%
  \\%
  \dtldisplayafterhead
  \@dtl@resetdoamp
  \@sDTLforeach{#2}{}{%
    \DTLiffirstrow{}{\dtldisplaycr\dtldisplaystartrow}%
    \@dtl@resetdoamp
    \DTLforeachkeyinrow{\@dtl@val}%
    {%
      \expandafter\DTLifinlist\expandafter{\dtlkey}{#1}%
      {}%
      {%
        \global\let\@dtl@val\@dtl@val
        \@dtl@doamp
        \@dtl@datatype=0\dtltype\relax
        \ifcase\@dtl@datatype
          \dtlstringformat\@dtl@val
        \or
          \dtlintformat\@dtl@val
        \or
          \dtlrealformat\@dtl@val
        \or
          \dtlcurrencyformat\@dtl@val
        \else
          \@dtl@val
        \fi
      }%
    }%
  }%
  \dtldisplayendtab
  \end{tabular}%
}
\define@key{displaylong}{caption}{\def\@dtl@cap{#1}}
\define@key{displaylong}{contcaption}{\def\@dtl@contcap{#1}}
\define@key{displaylong}{shortcaption}{\def\@dtl@shortcap{#1}}
\define@key{displaylong}{label}{\def\@dtl@label{#1}}
\define@key{displaylong}{foot}{\def\@dtl@foot{#1}}
\define@key{displaylong}{lastfoot}{\def\@dtl@lastfoot{#1}}
\define@key{displaylong}{omit}{\def\@dtl@omitlist{#1}}
\newcommand*{\@dtl@resetdostartrow}{%
  \gdef\@dtl@dostartrow{%
    \gdef\@dtl@dostartrow{\dtldisplaycr\dtldisplaystartrow}}%
}
\newcommand*{\DTLdisplaylongdb}[2][]{%
  \def\@dtl@cap{\@nil}%
  \def\@dtl@contcap{\@nil}%
  \def\@dtl@label{\@nil}%
  \def\@dtl@shortcap{\@dtl@cap}%
  \def\@dtl@foot{\@nil}%
  \def\@dtl@lastfoot{\@nil}%
  \def\@dtl@omitlist{}%
  \setkeys{displaylong}{#1}%
  \def\@dtl@doamp{\gdef\@dtl@doamp{&}}%
  \def\@dtl@resetdoamp{\gdef\@dtl@doamp{\gdef\@dtl@doamp{&}}}%
  \@dtl@resetdostartrow
  \edef\@dtl@maxcols{\expandafter\number
    \csname dtlcols@#2\endcsname}%
  \DTLnumitemsinlist{\@dtl@omitlist}{\@dtl@tmp}%
  \dtlsub{\@dtl@maxcols}{\@dtl@maxcols}{\@dtl@tmp}%
  \dtlclip{\@dtl@maxcols}{\@dtl@maxcols}%
  \def\@dtl@tabargs{}%
  \dtlforeachkey(\@dtl@key,\@dtl@idx,\@dtl@type,\@dtl@head)%
    \in{#2}\do
  {%
    \expandafter\DTLifinlist\expandafter{\@dtl@key}{\@dtl@omitlist}%
    {}%
    {%
      \dtladdalign\@dtl@tabargs\@dtl@type\@dtl@idx\@dtl@maxcols
    }%
  }%
  \edef\@dtl@dobegintab{\noexpand\begin{longtable}{\@dtl@tabargs}}%
  \@dtl@dobegintab
  \ifx\@dtl@foot\@nnil
  \else
    \@dtl@foot\endfoot
  \fi
  \ifx\@dtl@lastfoot\@nnil
  \else
    \@dtl@lastfoot\endlastfoot
  \fi
  \ifx\@dtl@cap\@nnil
    \@dtl@resetdoamp
    \dtldisplaystarttab
    \dtlforeachkey(\@dtl@key,\@dtl@idx,\@dtl@type,\@dtl@head)%
      \in{#2}\do
    {%
      \expandafter\DTLifinlist\expandafter{\@dtl@key}{\@dtl@omitlist}%
      {}%
      {%
        \@dtl@doamp{\dtlheaderformat{\@dtl@head}}%
      }%
    }%
    \@dtl@resetdoamp
    \@dtl@resetdostartrow
    \endhead\dtldisplayafterhead
  \else
    \caption[\@dtl@shortcap]{\@dtl@cap}%
    \ifx\@dtl@label\@nnil
    \else
      \label{\@dtl@label}%
    \fi
    \dtldisplaycr
  \dtldisplaystarttab
    \@dtl@resetdoamp
    \dtlforeachkey(\@dtl@key,\@dtl@idx,\@dtl@type,\@dtl@head)%
      \in{#2}\do
    {%
      \expandafter\DTLifinlist\expandafter{\@dtl@key}{\@dtl@omitlist}%
      {}%
      {%
        \@dtl@doamp{\dtlheaderformat{\@dtl@head}}%
      }%
    }%
    \@dtl@resetdoamp
    \dtldisplaycr\dtldisplayafterhead
    \endfirsthead
    \ifx\@dtl@contcap\@nnil
      \caption{\@dtl@cap}%
    \else
      \caption{\@dtl@contcap}%
    \fi
    \dtldisplaycr\dtldisplaystarttab
      \@dtl@resetdoamp
      \dtlforeachkey(\@dtl@key,\@dtl@idx,\@dtl@type,\@dtl@head)%
      \in{#2}\do
      {%
        \expandafter\DTLifinlist\expandafter{\@dtl@key}{\@dtl@omitlist}%
        {}%
        {%
          \@dtl@doamp{\dtlheaderformat{\@dtl@head}}%
        }%
      }%
      \@dtl@resetdoamp
      \@dtl@resetdostartrow
    \dtldisplaycr\dtldisplayafterhead
    \endhead
  \fi
  \@sDTLforeach{#2}{}{%
    \@dtl@dostartrow
    \@dtl@resetdoamp
    \DTLforeachkeyinrow{\@dtl@val}%
    {%
      \global\let\@dtl@val\@dtl@val
      \expandafter\DTLifinlist\expandafter{\dtlkey}{\@dtl@omitlist}%
      {}%
      {%
        \@dtl@doamp
        \@dtl@datatype=0\dtltype\relax
        \ifcase\@dtl@datatype
          \dtlstringformat\@dtl@val
        \or
          \dtlintformat\@dtl@val
        \or
          \dtlrealformat\@dtl@val
        \or
          \dtlcurrencyformat\@dtl@val
        \fi
      }%
    }%
  }%
  \dtldisplayendtab
  \end{longtable}%
}
\newcommand*{\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@
     \noexpand\db@row@elt@w%
     \noexpand\db@row@id@w \@dtl@rowAidx\noexpand\db@row@id@end@%
     \the\@dtl@toksB
     \noexpand\db@row@id@w \@dtl@rowAidx\noexpand\db@row@id@end@%
     \noexpand\db@row@elt@end@%
     \the\@dtl@toks}%
    \toks@=\expandafter{\@dtl@tmp}%
    \@dtl@toks=\expandafter{\@dtl@thirdpart}%
    \edef\@dtl@tmp{\the\toks@
     \noexpand\db@row@elt@w%
     \noexpand\db@row@id@w \@dtl@rowBidx\noexpand\db@row@id@end@%
     \the\@dtl@toksA
     \noexpand\db@row@id@w \@dtl@rowBidx\noexpand\db@row@id@end@%
     \noexpand\db@row@elt@end@%
    \the\@dtl@toks}%
    \expandafter\global\csname dtldb@#1\endcsname=\expandafter
      {\@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
      \advance\@dtl@tmpcount by -1\relax
      \toks@{#2}%
      \@dtl@toks=\expandafter{\@dtl@newlist}%
      \edef\@dtl@newlist{\the\@dtl@toks
        \noexpand\db@row@elt@w% row header
        \noexpand\db@row@id@w \number\@dtl@tmpcount
          \noexpand\db@row@id@end@% row id
         \the\toks@ % row contents
        \noexpand\db@row@id@w \number\@dtl@tmpcount
          \noexpand\db@row@id@end@% row id
        \noexpand\db@row@elt@end@% row end
      }%
    \else
      \toks@{#2}%
      \@dtl@toks=\expandafter{\@dtl@newlist}%
      \edef\@dtl@newlist{\the\@dtl@toks
        \noexpand\db@row@elt@w% row header
        \noexpand\db@row@id@w #1%
          \noexpand\db@row@id@end@% row id
         \the\toks@ % row contents
        \noexpand\db@row@id@w #3%
          \noexpand\db@row@id@end@% row id
        \noexpand\db@row@elt@end@% row end
      }%
    \fi
    \let\@dtl@donextdec=\@dtl@decrementrows
  \fi
  \@dtl@donextdec#4\@nil
}
\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}{}%
  }%
}
\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}%
      \edef\dtl@tmp{\the\dtlbeforerow \the\dtlafterrow}%
      \expandafter\global\csname dtldb@#1\endcsname
        =\expandafter{\dtl@tmp}%
      \expandafter\global\expandafter\advance
        \csname dtlrows@#1\endcsname by -1\relax
}
\newcommand*{\DTLsumforkeys}[1][\boolean{true}\and
 \DTLisnumerical{\DTLthisval}]{%
  \def\@dtl@cond{#1}%
  \@dtlsumforkeys
}
\newcommand*{\@dtlsumforkeys}[4][]{%
  \def#4{0}%
  \@for\@dtl@db@name:=#2\do{%
    \@sDTLforeach{\@dtl@db@name}%
    {#1}% assignment list
    {%
      \@for\@dtl@key:=#3\do{%
        \@sdtl@getcolumnindex{\@dtl@col}{\@dtl@db@name}{\@dtl@key}%
        \dtlcurrentrow=\expandafter{\dtl@thisrow}%
        \dtlgetentryfromrow{\DTLthisval}{\@dtl@col}{\dtlcurrentrow}%
        \expandafter\ifthenelse\expandafter{\@dtl@cond}%
          {\DTLadd{#4}{#4}{\DTLthisval}}{}%
      }%
    }%
  }%
}
\newcommand*{\DTLsumcolumn}[3]{%
  \def#3{0}%
  \DTLifdbexists{#1}%
  {%
     \@sDTLifhaskey{#1}{#2}%
     {%
       \@sdtlforcolumn{\DTLthisval}{#1}{#2}%
       {%
         \DTLadd{#3}{#3}{\DTLthisval}%
       }%
     }%
     {%
       \PackageError{datatool}{Key `#2' doesn't
         exist in database `#1'}{}%
     }%
  }%
  {%
    \PackageError{datatool}{Data base `#1' doesn't
      exist}{}%
  }%
}
\newcommand*{\DTLmeanforkeys}[1][\boolean{true}\and
 \DTLisnumerical{\DTLthisval}]{%
  \def\@dtl@cond{#1}%
  \@dtlmeanforkeys
}
\newcount\@dtl@elements
\newcommand*{\@dtlmeanforkeys}[4][]{%
  \def#4{0}%
  \@dtl@elements=0\relax
  \@for\@dtl@db@name:=#2\do{%
    \@sDTLforeach{\@dtl@db@name}%
    {#1}% assignment list
    {%
      \@for\@dtl@key:=#3\do{%
        \@sdtl@getcolumnindex{\@dtl@col}{\@dtl@db@name}{\@dtl@key}%
        \dtlcurrentrow=\expandafter{\dtl@thisrow}%
        \dtlgetentryfromrow{\DTLthisval}{\@dtl@col}{\dtlcurrentrow}%
        \expandafter\ifthenelse\expandafter{\@dtl@cond}%
        {%
          \DTLadd{#4}{#4}{\DTLthisval}%
          \advance\@dtl@elements by 1\relax
        }{}%
      }%
    }%
  }%
  \ifnum\@dtl@elements=0\relax
    \PackageError{datatool}{Unable to evaluate mean: no data}{}%
  \else
    \edef\@dtl@n{\number\@dtl@elements}%
    \DTLdiv{#4}{#4}{\@dtl@n}%
  \fi
}
\newcommand*{\DTLmeanforcolumn}[3]{%
  \def#3{0}%
  \@dtl@elements=0\relax
  \DTLifdbexists{#1}%
  {%
     \@sDTLifhaskey{#1}{#2}%
     {%
       \@sdtlforcolumn{\DTLthisval}{#1}{#2}%
       {%
         \DTLadd{#3}{#3}{\DTLthisval}%
         \advance\@dtl@elements by 1\relax
       }%
       \ifnum\@dtl@elements=0\relax
         \PackageError{datatool}{Can't compute mean for
          column `#2' in database `#1': no data}{}%
       \else
         \edef\@dtl@n{\number\@dtl@elements}%
         \DTLdiv{#3}{#3}{\@dtl@n}%
       \fi
     }%
     {%
       \PackageError{datatool}{Key `#2' doesn't
         exist in database `#1'}{}%
     }%
  }%
  {%
    \PackageError{datatool}{Data base `#1' doesn't
      exist}{}%
  }%
}
\newcommand*{\DTLvarianceforkeys}[1][\boolean{true}\and
 \DTLisnumerical{\DTLthisval}]{%
  \def\@dtl@cond{#1}%
  \@dtlvarianceforkeys
}
\newcommand*{\@dtlvarianceforkeys}[4][]{%
  \@dtlmeanforkeys[#1]{#2}{#3}{\dtl@mean}%
  \def#4{0}%
  \@dtl@elements=0\relax
  \@for\@dtl@db@name:=#2\do{%
    \@sDTLforeach{\@dtl@db@name}%
    {#1}% assignment list
    {%
      \@for\@dtl@key:=#3\do{%
        \@sdtl@getcolumnindex{\@dtl@col}{\@dtl@db@name}{\@dtl@key}%
        \dtlcurrentrow=\expandafter{\dtl@thisrow}%
        \dtlgetentryfromrow{\DTLthisval}{\@dtl@col}{\dtlcurrentrow}%
        \expandafter\ifthenelse\expandafter{\@dtl@cond}%
        {%
           \DTLsub{\dtl@diff}{\DTLthisval}{\dtl@mean}%
           \DTLmul{\dtl@diff}{\dtl@diff}{\dtl@diff}%
           \DTLadd{#4}{#4}{\dtl@diff}%
           \advance\@dtl@elements by 1\relax
        }{}%
      }%
    }%
  }%
  \ifnum\@dtl@elements=0\relax
    \PackageError{datatool}{Unable to evaluate variance: no data}{}%
  \else
    \edef\@dtl@n{\number\@dtl@elements}%
    \DTLdiv{#4}{#4}{\@dtl@n}%
  \fi
}
\newcommand*{\DTLvarianceforcolumn}[3]{%
  \DTLmeanforcolumn{#1}{#2}{\dtl@mean}%
  \def#3{0}%
  \@dtl@elements=0\relax
  \DTLifdbexists{#1}%
  {%
     \@sDTLifhaskey{#1}{#2}%
     {%
       \@sdtlforcolumn{\DTLthisval}{#1}{#2}%
       {%
         \DTLsub{\dtl@diff}{\DTLthisval}{\dtl@mean}%
         \DTLmul{\dtl@diff}{\dtl@diff}{\dtl@diff}%
         \DTLadd{#3}{#3}{\dtl@diff}%
         \advance\@dtl@elements by 1\relax
       }%
       \ifnum\@dtl@elements=0\relax
         \PackageError{datatool}{Can't compute variance for
          column `#2' in database `#1': no data}{}%
       \else
         \edef\@dtl@n{\number\@dtl@elements}%
         \DTLdiv{#3}{#3}{\@dtl@n}%
       \fi
     }%
     {%
       \PackageError{datatool}{Key `#2' doesn't
         exist in database `#1'}{}%
     }%
  }%
  {%
    \PackageError{datatool}{Data base `#1' doesn't
      exist}{}%
  }%
}
\newcommand*{\DTLsdforkeys}[1][\boolean{true}\and
\DTLisnumerical{\DTLthisval}]{%
  \def\@dtl@cond{#1}%
  \@dtlsdforkeys
}
\newcommand*{\@dtlsdforkeys}[4][]{%
  \@dtlvarianceforkeys[#1]{#2}{#3}{#4}%
  \DTLsqrt{#4}{#4}%
}
\newcommand*{\DTLsdforcolumn}[3]{%
  \DTLvarianceforcolumn{#1}{#2}{#3}%
  \DTLsqrt{#3}{#3}%
}
\newcommand*{\DTLminforkeys}[1][\boolean{true}\and
 \DTLisnumerical{\DTLthisval}]{%
  \def\@dtl@cond{#1}%
  \@dtlminforkeys
}
\newcommand*{\@dtlminforkeys}[4][]{%
  \def#4{}%
  \@for\@dtl@db@name:=#2\do{%
    \@sDTLforeach{\@dtl@db@name}%
    {#1}% assignment list
    {%
      \@for\@dtl@key:=#3\do{%
        \@sdtl@getcolumnindex{\@dtl@col}{\@dtl@db@name}{\@dtl@key}%
        \dtlcurrentrow=\expandafter{\dtl@thisrow}%
        \dtlgetentryfromrow{\DTLthisval}{\@dtl@col}{\dtlcurrentrow}%
        \expandafter\ifthenelse\expandafter{\@dtl@cond}%
        {%
          \ifdefempty{#4}%
          {%
            \let#4\DTLthisval
          }%
          {%
            \DTLmin{#4}{#4}{\DTLthisval}%
          }%
        }{}%
      }%
    }%
  }%
}
\newcommand*{\DTLminforcolumn}[3]{%
  \def#3{}%
  \DTLifdbexists{#1}%
  {%
     \@sDTLifhaskey{#1}{#2}%
     {%
       \@sdtlforcolumn{\DTLthisval}{#1}{#2}%
       {%
         \ifdefempty{#3}%
         {%
           \let#3\DTLthisval
         }%
         {%
           \DTLmin{#3}{#3}{\DTLthisval}%
         }%
       }%
     }%
     {%
       \PackageError{datatool}{Key `#2' doesn't
         exist in database `#1'}{}%
     }%
  }%
  {%
    \PackageError{datatool}{Data base `#1' doesn't
      exist}{}%
  }%
}
\newcommand*{\DTLmaxforkeys}[1][\boolean{true}\and
 \DTLisnumerical{\DTLthisval}]{%
  \def\@dtl@cond{#1}%
  \@dtlmaxforkeys
}
\newcommand*{\@dtlmaxforkeys}[4][]{%
  \def#4{}%
  \@for\@dtl@db@name:=#2\do{%
    \@sDTLforeach{\@dtl@db@name}%
    {#1}% assignment list
    {%
      \@for\@dtl@key:=#3\do{%
        \@sdtl@getcolumnindex{\@dtl@col}{\@dtl@db@name}{\@dtl@key}%
        \dtlcurrentrow=\expandafter{\dtl@thisrow}%
        \dtlgetentryfromrow{\DTLthisval}{\@dtl@col}{\dtlcurrentrow}%
        \expandafter\ifthenelse\expandafter{\@dtl@cond}%
        {%
          \ifdefempty{#4}%
          {%
            \let#4\DTLthisval
          }%
          {%
            \DTLmax{#4}{#4}{\DTLthisval}%
          }%
        }{}%
      }%
    }%
  }%
}
\newcommand*{\DTLmaxforcolumn}[3]{%
  \def#3{}%
  \DTLifdbexists{#1}%
  {%
     \@sDTLifhaskey{#1}{#2}%
     {%
       \@sdtlforcolumn{\DTLthisval}{#1}{#2}%
       {%
         \ifdefempty{#3}%
         {%
           \let#3\DTLthisval
         }%
         {%
           \DTLmax{#3}{#3}{\DTLthisval}%
         }%
       }%
     }%
     {%
       \PackageError{datatool}{Key `#2' doesn't
         exist in database `#1'}{}%
     }%
  }%
  {%
    \PackageError{datatool}{Data base `#1' doesn't
      exist}{}%
  }%
}
\newcommand*{\DTLcomputebounds}[8][\boolean{true}]{%
\let#5=\relax
\let#6=\relax
\let#7=\relax
\let#8=\relax
\@for\dtl@thisdb:=#2\do{%
  \@sDTLforeach[#1]{\dtl@thisdb}{\DTLthisX=#3,\DTLthisY=#4}{%
    \expandafter\DTLconverttodecimal\expandafter{\DTLthisX}{\dtl@decx}%
    \expandafter\DTLconverttodecimal\expandafter{\DTLthisY}{\dtl@decy}%
    \ifx#5\relax
      \let#5=\dtl@decx
      \let#6=\dtl@decy
      \let#7=\dtl@decx
      \let#8=\dtl@decy
    \else
      \dtlmin{#5}{#5}{\dtl@decx}%
      \dtlmin{#6}{#6}{\dtl@decy}%
      \dtlmax{#7}{#7}{\dtl@decx}%
      \dtlmax{#8}{#8}{\dtl@decy}%
    \fi
  }%
}%
}
\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
      }%
      {}%
    }%
  }%
}
\newtoks\@dtl@list
\newcommand*{\DTLsort}{\@ifstar\@sDTLsort\@DTLsort}
\newcommand{\@DTLsort}[3][]{%
  \dtlsort[#1]{#2}{#3}{\dtlcompare}%
}
\newcommand*{\@sDTLsort}[3][]{%
  \dtlsort[#1]{#2}{#3}{\dtlicompare}%
}
\newcommand{\dtlsort}[4][]{%
  \DTLifdbexists{#3}%
  {%
     \ifnum\DTLrowcount{#3}>100\relax
       \typeout{Sorting `#3' - this may take a while.}%
     \fi
    \edef\@dtl@replacementkeys{#1}%
    \def\@dtl@sortorder{}%
    \@for\@dtl@level:=#2\do
    {%
      \expandafter\@dtl@getsortdirection\@dtl@level=\relax
      \DTLifhaskey{#3}{\@dtl@key}%
      {%
         \ifdefempty\@dtl@sortorder
         {\let\@dtl@sortorder=\@dtl@level}%
         {\eappto\@dtl@sortorder{,\@dtl@level}}%
      }%
      {%
         \PackageError{datatool}%
         {%
            Can't sort on `\@dtl@level'.
            No such key `\@dtl@key' in database `#3'%
         }{}%
      }%
    }%
    \ifdefempty\@dtl@sortorder
    {%
       \PackageWarning{datatool}{No keys provided to sort database `#3'}%
    }%
    {%
      \let\@dtl@comparecs=#4%
      \dtl@sortdata{#3}%
    }%
  }%
  {%
    \PackageError{datatool}{Database `#3' doesn't exist}{}%
  }%
}
\newtoks\@dtl@rowa
\newtoks\@dtl@rowb
\newcommand*{\dtl@sortdata}[1]{%
  \def\@dtl@sortedlist{}%
  \edef\@dtl@dbname{#1}%
  \@dtlforeachrow(\@dtl@rowAnum,\@dtl@rowAcontents)\in\@dtl@dbname\do{%
    \@dtl@rowa=\expandafter{\@dtl@rowAcontents}%
    \def\@dtl@newlist{}%
  \@dtl@insertdonefalse
  \dtlrownum=0\relax
  \expandafter\@dtl@foreachrow\@dtl@sortedlist
    \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@%
    \@@{\@dtl@rowBnum}{\@dtl@rowBcontents}%
    {%
      \@dtl@rowb=\expandafter{\@dtl@rowBcontents}%
      \dtlrownum=\@dtl@rowBnum
      \if@dtl@insertdone
        \advance\dtlrownum by 1\relax
      \else
        \@dtl@sortcriteria{\@dtl@rowa}{\@dtl@rowb}%
        \ifnum\dtl@sortresult<0\relax
          \toks@=\expandafter{\@dtl@newlist}%
          \edef\@dtl@newlist{%
            \the\toks@
            \noexpand\db@row@elt@w%
             \noexpand\db@row@id@w \number\dtlrownum
             \noexpand\db@row@id@end@%
             \the\@dtl@rowa
             \noexpand\db@row@id@w \number\dtlrownum
             \noexpand\db@row@id@end@%
            \noexpand\db@row@elt@end@%
          }%
          \advance\dtlrownum by 1\relax
          \@dtl@insertdonetrue
        \fi
      \fi
      \toks@=\expandafter{\@dtl@newlist}%
      \edef\@dtl@newlist{\the\toks@
        \noexpand\db@row@elt@w%
         \noexpand\db@row@id@w \number\dtlrownum
         \noexpand\db@row@id@end@%
         \the\@dtl@rowb
         \noexpand\db@row@id@w \number\dtlrownum
         \noexpand\db@row@id@end@%
        \noexpand\db@row@elt@end@%
      }%
    }\q@nil
    \if@dtl@insertdone
    \else
      \advance\dtlrownum by 1\relax
      \toks@=\expandafter{\@dtl@newlist}%
      \edef\@dtl@newlist{\the\toks@
        \noexpand\db@row@elt@w%
         \noexpand\db@row@id@w \number\dtlrownum
         \noexpand\db@row@id@end@%
         \the\@dtl@rowa
         \noexpand\db@row@id@w \number\dtlrownum
         \noexpand\db@row@id@end@%
        \noexpand\db@row@elt@end@%
      }%
    \fi
    \let\@dtl@sortedlist=\@dtl@newlist
  }%
  \expandafter\global\csname dtldb@#1\endcsname=\expandafter
    {\@dtl@sortedlist}%
}
\newcommand{\@dtl@sortcriteria}[2]{%
  \@for\@dtl@level:=\@dtl@sortorder\do
  {%
    \expandafter\@dtl@getsortdirection\@dtl@level=\relax
    \let\@dtl@keya=\@dtl@key
    \let\@dtl@keyb=\@dtl@key
    \@sdtl@getcolumnindex{\@dtl@col}{\@dtl@dbname}{\@dtl@key}%
    \dtlgetentryfromrow{\@dtl@a}{\@dtl@col}{#1}%
    \dtlgetentryfromrow{\@dtl@b}{\@dtl@col}{#2}%
    \ifx\@dtl@a\dtlnovalue
      \@dtl@setnull{\@dtl@a}{\@dtl@key}%
    \fi
    \ifx\@dtl@b\dtlnovalue
      \@dtl@setnull{\@dtl@b}{\@dtl@key}%
    \fi
    \DTLifnull{\@dtl@a}%
    {%
      \@for\@dtl@keya:=\@dtl@replacementkeys\do{%
        \@sdtl@getcolumnindex{\@dtl@col}{\@dtl@dbname}{\@dtl@keya}%
        \dtlgetentryfromrow{\@dtl@a}{\@dtl@col}{#1}%
        \ifx\@dtl@a\dtlnovalue
          \@dtl@setnull{\@dtl@a}{\@dtl@key}%
        \fi
        \DTLifnull{\@dtl@a}{}{\@endfortrue}%
      }%
      \ifx\@dtl@keya\@nnil
        \let\@dtl@keya\@dtl@key
        \@dtl@setnull{\@dtl@a}{\@dtl@key}%
      \fi
    }%
    {}%
    \DTLifnull{\@dtl@b}%
    {%
      \@for\@dtl@keyb:=\@dtl@replacementkeys\do{%
        \@sdtl@getcolumnindex{\@dtl@col}{\@dtl@dbname}{\@dtl@keyb}%
        \dtlgetentryfromrow{\@dtl@b}{\@dtl@col}{#2}%
        \ifx\@dtl@b\dtlnovalue
          \@dtl@setnull{\@dtl@b}{\@dtl@key}%
        \fi
        \DTLifnull{\@dtl@b}{}{\@endfortrue}%
      }%
      \ifx\@dtl@keyb\@nnil
        \let\@dtl@keyb\@dtl@key
        \@dtl@setnull{\@dtl@b}{\@dtl@key}%
      \fi
    }%
    {}%
    \@dtl@toksA=\expandafter{\@dtl@a}%
    \@dtl@toksB=\expandafter{\@dtl@b}%
    \edef\@dtl@docompare{\noexpand\dtl@compare@
      {\@dtl@keya}{\@dtl@keyb}%
      {\noexpand\@dtl@toksA}{\noexpand\@dtl@toksB}}%
    \@dtl@docompare
    \ifnum\dtl@sortresult=0\relax
      \@endforfalse
    \else
      \@endfortrue
    \fi
  }%
  \multiply\dtl@sortresult by -\@dtl@sortdirection\relax
}
\def\@dtl@getsortdirection#1=#2\relax{%
  \def\@dtl@key{#1}%
  \def\@dtl@sortdirection{#2}%
  \ifdefempty{\@dtl@sortdirection}%
  {%
    \def\@dtl@sortdirection{-1}%
  }%
  {%
    \@dtl@get@sortdirection#2%
    \def\@dtl@dir{ascending}%
    \ifx\@dtl@sortdirection\@dtl@dir
      \def\@dtl@sortdirection{-1}%
    \else
      \def\@dtl@dir{descending}%
      \ifx\@dtl@sortdirection\@dtl@dir
        \def\@dtl@sortdirection{1}%
      \else
        \PackageError{datatool}{Invalid sort direction
        `\@dtl@sortdirection'}{The sort direction can only be
         one of `ascending' or `descending'}%
        \def\@dtl@sortdirection{-1}%
      \fi
    \fi
  }%
}
\def\@dtl@get@sortdirection#1={\def\@dtl@sortdirection{#1}}
\newtoks\@dtl@toksA
\newtoks\@dtl@toksB
\newcommand{\dtl@compare}[3]{%
  \dtl@compare@{#1}{#1}{#2}{#3}%
}
\newcommand{\dtl@compare@}[4]{%
  \DTLgetdatatype{\@dtl@typeA}{\@dtl@dbname}{#1}%
  \ifx\@dtl@typeA\DTLunsettype
    \let\@dtl@typeA\DTLstringtype
  \fi
  \DTLgetdatatype{\@dtl@typeB}{\@dtl@dbname}{#2}%
  \ifx\@dtl@typeB\DTLunsettype
    \let\@dtl@typeB\DTLstringtype
  \fi
  \@dtl@tmpcount=\@dtl@typeA\relax
  \multiply\@dtl@tmpcount by \@dtl@typeB\relax
  \ifnum\@dtl@tmpcount=0\relax
    \edef\@dtl@tmpcmp{%
      \noexpand\@dtl@comparecs{\noexpand\dtl@sortresult}%
        {\the#3}{\the#4}%
      }%
    \@dtl@tmpcmp
    \ifdtlverbose
      \edef\@dtl@a{\the#3}%
      \edef\@dtl@b{\the#4}%
    \fi
  \else
    \edef\@dtl@a{\the#3}%
    \edef\@dtl@b{\the#4}%
    \DTLifnumlt{\@dtl@a}{\@dtl@b}%
    {%
      \dtl@sortresult=-1\relax
    }%
    {%
      \DTLifnumgt{\@dtl@a}{\@dtl@b}%
      {%
        \dtl@sortresult=1\relax
      }%
      {%
        \dtl@sortresult=0\relax
      }%
    }%
  \fi
  \ifdtlverbose
    \@onelevel@sanitize\@dtl@a
    \@onelevel@sanitize\@dtl@b
    \dtl@message{`\@dtl@a' <=> `\@dtl@b' = \number\dtl@sortresult}%
  \fi
}
\newwrite\@dtl@write
\newcommand*{\DTLsavedb}[2]{%
  \DTLifdbexists{#1}%
  {%
    \openout\@dtl@write=#2\relax
    \def\@dtl@header{}%
    \dtlforeachkey(\@dtl@key,\@dtl@col,\@dtl@type,\@dtl@head)%
      \in{#1}\do
    {%
      \IfSubStringInString{\@dtl@separator}{\@dtl@key}%
      {%
        \ifdefempty{\@dtl@header}%
        {%
          \protected@edef\@dtl@header{%
            \@dtl@delimiter\@dtl@key\@dtl@delimiter}%
        }%
        {%
          \toks@=\expandafter{\@dtl@header}%
          \protected@edef\@dtl@header{%
            \the\toks@\@dtl@separator
            \@dtl@delimiter\@dtl@key\@dtl@delimiter}%
        }%
      }%
      {%
        \ifdefempty{\@dtl@header}%
        {%
          \protected@edef\@dtl@header{\@dtl@key}%
        }%
        {%
          \toks@=\expandafter{\@dtl@header}%
          \protected@edef\@dtl@header{\the\toks@
            \@dtl@separator\@dtl@key}%
        }%
      }%
    }%
    \protected@write\@dtl@write{}{\@dtl@header}%
    \@sDTLforeach{#1}{}%
    {%
      \def\@dtl@row{}%
      \DTLforeachkeyinrow{\@dtl@val}%
      {%
        \IfSubStringInString{\@dtl@separator}{\@dtl@val}%
        {%
          \ifdefempty{\@dtl@row}%
          {%
            \protected@edef\@dtl@row{%
              \@dtl@delimiter\@dtl@val\@dtl@delimiter}%
          }%
          {%
            \toks@=\expandafter{\@dtl@row}%
            \protected@edef\@dtl@row{\the\toks@\@dtl@separator
              \@dtl@delimiter\@dtl@val\@dtl@delimiter}%
          }%
        }%
        {%
          \ifdefempty{\@dtl@row}%
          {%
            \protected@edef\@dtl@row{\@dtl@val}%
          }%
          {%
            \toks@=\expandafter{\@dtl@row}%
            \protected@edef\@dtl@row{\the\toks@\@dtl@separator
              \@dtl@val}%
          }%
        }%
      }%
        \protected@write\@dtl@write{}{\@dtl@row}%
    }%
    \closeout\@dtl@write
  }%
  {%
    \PackageError{datatool}{Can't save database `#1': no such
      database}{}%
  }%
}
\newcommand*{\DTLsavetexdb}[2]{%
  \DTLifdbexists{#1}%
  {%
    \openout\@dtl@write=#2\relax
    \protected@write\@dtl@write{}{\string\DTLnewdb{#1}}%
    \@sDTLforeach{#1}{}%
    {%
      \protected@write\@dtl@write{}{\string\DTLnewrow*{#1}}%
      \DTLforeachkeyinrow{\@dtl@val}%
      {%
        \DTLifnull{\@dtl@val}%
        {\def\@dtl@val{}}%
        {}%
        \protected@write\@dtl@write{}{%
          \string\DTLnewdbentry*{#1}{\dtlkey}{\@dtl@val}}%
      }%
    }%
    \dtlforeachkey(\@dtl@k,\@dtl@c,\@dtl@t,\@dtl@h)\in{#1}\do
    {%
      \@onelevel@sanitize\@dtl@h
      \protected@write\@dtl@write{}{%
        \string\DTLsetheader*{#1}{\@dtl@k}{\@dtl@h}}%
    }%
    \protected@write{\@dtl@write}{}{\string\def\string\dtllastloadeddb{#1}}%
    \closeout\@dtl@write
  }%
  {%
    \PackageError{datatool}{Can't save database `#1': no such
      database}{}%
  }%
}
\newcommand*{\dtl@saverawdbhook}{}
\newcommand*{\DTLsaverawdb}[2]{%
  \DTLifdbexists{#1}%
  {%
    \openout\@dtl@write=#2\relax
    \protected@write{\@dtl@write}{}{%
      \string\DTLifdbexists{#1}\expandafter\@gobble\string\%^^J%
      {%
         \string\PackageError{datatool}{Database `#1' ^^Jalready exists}{}%
         \expandafter\@gobble\string\%^^J%
         \string\aftergroup\string\endinput
      }%
      {%
      }\expandafter\@gobble\string\%
    }%
    {%
      \def\db@row@elt@w{\expandafter\@gobble\string\%^^J\string\db@row@elt@w\space}%
      \def\db@row@elt@end@{\expandafter\@gobble\string\%^^J\string\db@row@elt@end@\space}%
      \def\db@row@id@w{\expandafter\@gobble\string\%^^J\string\db@row@id@w\space}%
      \def\db@row@id@end@{\expandafter\@gobble\string\%^^J\string\db@row@id@end@\space}%
      \def\db@col@elt@w{\expandafter\@gobble\string\%^^J\string\db@col@elt@w\space}%
      \def\db@col@elt@end@{\expandafter\@gobble\string\%^^J\string\db@col@elt@end@\space}%
      \def\db@col@id@w{\expandafter\@gobble\string\%^^J\string\db@col@id@w\space}%
      \def\db@col@id@end@{\expandafter\@gobble\string\%^^J\string\db@col@id@end@\space}%
      \def\db@plist@elt@w{\expandafter\@gobble\string\%^^J\string\db@plist@elt@w\space}%
      \def\db@plist@elt@end@{\expandafter\@gobble\string\%^^J\string\db@plist@elt@end@\space}%
      \def\db@key@id@w{\expandafter\@gobble\string\%^^J\string\db@key@id@w\space}%
      \def\db@key@id@end@{\expandafter\@gobble\string\%^^J\string\db@key@id@end@\space}%
      \def\db@type@id@w{\expandafter\@gobble\string\%^^J\string\db@type@id@w\space}%
      \def\db@type@id@end@{\expandafter\@gobble\string\%^^J\string\db@type@id@end@\space}%
      \def\db@header@id@w{\expandafter\@gobble\string\%^^J\string\db@header@id@w\space}%
      \def\db@header@id@end@{\expandafter\@gobble\string\%^^J\string\db@header@id@end@\space}%
      \protected@write{\@dtl@write}{}{\string\bgroup\string\makeatletter}%
      \protected@write{\@dtl@write}{}{%
        \string\dtl@message{Reconstructing database^^J`#1'}%
        \expandafter\@gobble\string\%}%
      \protected@write{\@dtl@write}{}{%
       \string\expandafter
       \string\global\string\expandafter^^J\string\newtoks
       \string\csname\space dtlkeys@#1\string\endcsname}%
      \protected@write{\@dtl@write}{}{%
        \string\expandafter
        \string\global^^J
        \string\csname\space dtlkeys@#1\string\endcsname
       =\expandafter\@gobble\string\{\expandafter\@gobble\string\%}%
      \expandafter\protected@xdef\csname dtl@rawwritedbkeys@#1\endcsname{%
        \the\csname dtlkeys@#1\endcsname}%
      \protected@write{\@dtl@write}{}{\csname dtl@rawwritedbkeys@#1\endcsname}%
      \protected@write{\@dtl@write}{}%
         {\expandafter\@gobble\string\}\expandafter\@gobble\string\%}%
      \dtl@saverawdbhook
      \protected@write{\@dtl@write}{}{%
       \string\expandafter\string\global
       \string\expandafter^^J\string\newtoks
         \string\csname\space dtldb@#1\string\endcsname}%
      \protected@write{\@dtl@write}{}{%
        \string\expandafter
        \string\global^^J\string\csname\space dtldb@#1\string\endcsname
       =\expandafter\@gobble\string\{\expandafter\@gobble\string\%}%
      \expandafter\protected@xdef\csname dtl@rawwritedb@#1\endcsname{\the\csname dtldb@#1\endcsname}%
      \protected@write{\@dtl@write}{}{\csname dtl@rawwritedb@#1\endcsname}%
      \protected@write{\@dtl@write}{}{\expandafter\@gobble\string\}\expandafter\@gobble\string\%}%
      \protected@write{\@dtl@write}{}{\string\expandafter\string\global^^J
       \string\expandafter\string\newcount
       \string\csname\space dtlrows@#1\string\endcsname}%
      \protected@write{\@dtl@write}{}{\string\expandafter\string\global^^J
       \string\csname\space dtlrows@#1\string\endcsname
        =\expandafter\number\csname dtlrows@#1\endcsname\string\relax}%
      \protected@write{\@dtl@write}{}{\string\expandafter\string\global^^J
       \string\expandafter\string\newcount
       \string\csname\space dtlcols@#1\string\endcsname}%
      \protected@write{\@dtl@write}{}{\string\expandafter\string\global^^J
       \string\csname\space dtlcols@#1\string\endcsname
        =\expandafter\number\csname dtlcols@#1\endcsname\string\relax}%
    \dtlforeachkey(\@dtl@key,\@dtl@col,\@dtl@type,\@dtl@head)\in{#1}\do
    {%
      \edef\dtl@tmp{%
        \string\expandafter^^J
        \string\gdef
        \string\csname\space dtl@ci@#1@\@dtl@key\string\endcsname
        {\csname dtl@ci@#1@\@dtl@key\endcsname}\expandafter\@gobble\string\%
      }%
      \expandafter\write\expandafter\@dtl@write\expandafter{\dtl@tmp}%
    }%
      \protected@write{\@dtl@write}{}{\string\egroup}%
    }%
    \protected@write{\@dtl@write}{}{\string\def\string\dtllastloadeddb{#1}}%
    \closeout\@dtl@write
  }%
  {%
    \PackageError{datatool}{Can't save database `#1': no such
      database}{}%
  }%
}
\newcommand*{\DTLprotectedsaverawdb}[2]{%
  \DTLifdbexists{#1}%
  {%
    \openout\@dtl@write=#2\relax
    \protected@write{\@dtl@write}{}{%
      \string\DTLifdbexists{#1}\expandafter\@gobble\string\%^^J%
      {%
         \string\PackageError{datatool}{Database `#1' ^^Jalready exists}{}%
         \expandafter\@gobble\string\%^^J%
         \string\aftergroup\string\endinput
      }%
      {%
      }\expandafter\@gobble\string\%
    }%
    {%
      \protected@write{\@dtl@write}{}{\string\bgroup\string\makeatletter}%
      \protected@write{\@dtl@write}{}{\string\dtl@message{Reconstructing database
        ^^J`#1'}\expandafter\@gobble\string\%}%
      \protected@write{\@dtl@write}{}{%
       \string\expandafter
       \string\global\string\expandafter^^J\string\newtoks
       \string\csname\space dtlkeys@#1\string\endcsname}%
      \protected@write{\@dtl@write}{}{%
        \string\expandafter
        \string\global^^J
        \string\csname\space dtlkeys@#1\string\endcsname
       =\expandafter\@gobble\string\{\expandafter\@gobble\string\%}%
      \edef\dtl@rawwrite@keys{\the\csname dtlkeys@#1\endcsname}%
      \@onelevel@sanitize\dtl@rawwrite@keys
      \expandafter\write\expandafter\@dtl@write\expandafter
         {\dtl@rawwrite@keys\expandafter\@gobble\string\}}%
      \protected@write{\@dtl@write}{}{%
       \string\expandafter\string\global
       \string\expandafter^^J\string\newtoks
         \string\csname\space dtldb@#1\string\endcsname}%
      \protected@write{\@dtl@write}{}{%
        \string\expandafter
        \string\global^^J\string\csname\space dtldb@#1\string\endcsname
       =\expandafter\@gobble\string\{\expandafter\@gobble\string\%}%
      \edef\dtl@rawwrite@db{\the\csname dtldb@#1\endcsname}%
      \@onelevel@sanitize\dtl@rawwrite@db
      \expandafter\write\expandafter\@dtl@write\expandafter
         {\dtl@rawwrite@db\expandafter\@gobble\string\}}%
      \protected@write{\@dtl@write}{}{\string\expandafter\string\global^^J
       \string\expandafter\string\newcount
       \string\csname\space dtlrows@#1\string\endcsname}%
      \protected@write{\@dtl@write}{}{\string\expandafter\string\global^^J
       \string\csname\space dtlrows@#1\string\endcsname
        =\expandafter\number\csname dtlrows@#1\endcsname\string\relax}%
      \protected@write{\@dtl@write}{}{\string\expandafter\string\global^^J
       \string\expandafter\string\newcount
       \string\csname\space dtlcols@#1\string\endcsname}%
      \protected@write{\@dtl@write}{}{\string\expandafter\string\global^^J
       \string\csname\space dtlcols@#1\string\endcsname
        =\expandafter\number\csname dtlcols@#1\endcsname\string\relax}%
    \dtlforeachkey(\@dtl@key,\@dtl@col,\@dtl@type,\@dtl@head)\in{#1}\do
    {%
      \edef\dtl@tmp{%
        \string\expandafter^^J
        \string\gdef
        \string\csname\space dtl@ci@#1@\@dtl@key\string\endcsname
        {\csname dtl@ci@#1@\@dtl@key\endcsname}\expandafter\@gobble\string\%
      }%
      \expandafter\write\expandafter\@dtl@write\expandafter{\dtl@tmp}%
    }%
      \protected@write{\@dtl@write}{}{\string\egroup}%
    }%
    \protected@write{\@dtl@write}{}{\string\def\string\dtllastloadeddb{#1}}%
    \closeout\@dtl@write
  }%
  {%
    \PackageError{datatool}{Can't save database `#1': no such
      database}{}%
  }%
}
\newcommand*{\DTLloaddbtex}[2]{%
  \IfFileExists{#2}%
  {%
    \input{#2}%
    \ifdef#1%
    {%
      \PackageError{datatool}{Command \string#1\space is already defined}%
      {}%
    }%
    {%
      \let#1\dtllastloadeddb
    }%
  }%
  {%
    \PackageError{datatool}{File `#2' doesn't exist.}{}%
  }%
}
\newread\@dtl@read
\newcount\dtl@entrycr
\define@boolkey{loaddb}[dtl]{noheader}[true]{}
\define@boolkey{loaddb}[dtl]{autokeys}[true]{}
\dtlautokeysfalse
\define@key{loaddb}{keys}{%
  \dtl@entrycr=0\relax
  \@for\@dtl@key:=#1\do
  {%
    \advance\dtl@entrycr by 1\relax
    \expandafter
      \edef\csname @dtl@inky@\romannumeral\dtl@entrycr\endcsname{%
        \@dtl@key}%
  }%
}
\define@key{loaddb}{headers}{%
  \dtl@entrycr=0\relax
  \@for\@dtl@head:=#1\do
  {%
    \advance\dtl@entrycr by 1\relax
    \toks@=\expandafter{\@dtl@head}%
    \expandafter
      \edef\csname @dtl@inhd@\romannumeral\dtl@entrycr\endcsname{%
        \the\toks@}%
  }%
}
\newcount{\dtl@omitlines}
\define@key{loaddb}{omitlines}{\dtl@omitlines=#1\relax}
\newcommand*{\dtldefaultkey}{Column}
\newcommand*{\@dtl@readline}[2]{%
  \begingroup
  \catcode\endlinechar=\active%
  \global\read#1 to #2%
  \endgroup%
  \ifx#2\empty%
  \else%
    \expandafter\@dtl@stripeol#2%
    \let#2\@dtl@strippedline%
  \fi%
}
\begingroup
\catcode\endlinechar=\active%
\gdef\@dtl@stripeol#1
{\gdef\@dtl@strippedline{#1}}
\endgroup
\newcommand*{\@dtl@readrawline}[2]{%
  \@dtl@rawread#1 to #2%
  \dtl@domappings\@dtl@line
}
\newif\ifDTLnewdbonload
\DTLnewdbonloadtrue
\newcommand*{\DTLloaddb}{%
  \let\@dtl@doreadline\@dtl@readline
  \@dtlloaddb
}
\newcommand*{\@dtlloaddb}[3][]{%
  \IfFileExists{#3}{%
  \begingroup
    \catcode`\"12\relax
     \dtlnoheaderfalse
     \setkeys{loaddb}{#1}%
    \openin\@dtl@read=#3%
    \dtl@message{Reading `#3'}%
    \loop
    \ifnum \dtl@omitlines > \z@
      \advance\dtl@omitlines by \m@ne
      \read\@dtl@read to \@dtl@line
    \repeat
    \ifDTLnewdbonload
     \DTLnewdb{#2}%
    \fi
    \ifeof\@dtl@read
      \PackageWarning{datatool}{File `#3' has no data}%
    \else
      \ifdtlnoheader
      \else
        \loop
          \@dtl@conditionfalse
          \ifeof\@dtl@read
          \else
           \@dtl@doreadline\@dtl@read\@dtl@line
            \ifdefempty{\@dtl@line}%
            {%
              \@dtl@conditiontrue
            }%
            {%
            }%
          \fi
        \if@dtl@condition
        \repeat
        \protected@edef\@dtl@lin@{%
           \@dtl@separator\@dtl@line\@dtl@separator}%
        \dtl@entrycr=0\relax
       \loop
        \expandafter\@dtl@lopoff\@dtl@lin@\to\@dtl@lin@\@dtl@key
        \advance\dtl@entrycr by 1\relax
        \ifdtlautokeys
          \csedef{@dtl@inky@\romannumeral\dtl@entrycr}%
             {\dtldefaultkey\number\dtl@entrycr}%
        \else
          \ifdefempty{\@dtl@key}%
          {%
            \edef\@dtl@key{\dtldefaultkey\number\dtl@entrycr}%
          }%
          {}%
        \fi
        \expandafter\@dtl@toks\expandafter{\@dtl@key}%
        \@ifundefined{@dtl@inky@\romannumeral\dtl@entrycr}%
        {%
          \expandafter
            \edef\csname @dtl@inky@\romannumeral
              \dtl@entrycr\endcsname{\the\@dtl@toks}%
        }%
        {%
          \@ifundefined{@dtl@inhd@\romannumeral\dtl@entrycr}%
          {%
            \expandafter
              \edef\csname @dtl@inhd@\romannumeral
                \dtl@entrycr\endcsname{\the\@dtl@toks}%
          }%
          {}%
        }%
        \ifx\@dtl@lin@\@dtl@separator
          \@dtl@conditionfalse
        \else
          \@dtl@conditiontrue
        \fi
       \if@dtl@condition
       \repeat
      \fi
       \ifeof\@dtl@read
          \ifdtlnoheader
            \PackageWarning{datatool}{No data in `#3'}%
          \else
            \PackageWarning{datatool}{Only header row found in `#3'}%
          \fi
       \else
         \@dtl@conditiontrue
         \loop
          \@dtl@doreadline\@dtl@read\@dtl@line
           \ifdefempty{\@dtl@line}%
           {%
           }%
           {%
             \@sDTLnewrow{#2}%
             \expandafter\@dtl@toks\expandafter{\@dtl@line}%
             \edef\@dtl@lin@{\@dtl@separator\the\@dtl@toks
               \@dtl@separator}%
             \dtl@entrycr=0\relax
             {%
               \@dtl@conditiontrue
               \loop
                 \expandafter\@dtl@lopoff\@dtl@lin@\to
                   \@dtl@lin@\@dtl@thisentry
                 \advance\dtl@entrycr by 1\relax
                 \@ifundefined{@dtl@inky@\romannumeral\dtl@entrycr}%
                 {%
                   \edef\@dtl@thiskey{\dtldefaultkey
                     \number\dtl@entrycr}%
                   \expandafter\let
                     \csname @dtl@inky@\romannumeral
                       \dtl@entrycr\endcsname\@dtl@thiskey
                 }%
                 {%
                   \edef\@dtl@thiskey{%
                     \csname @dtl@inky@\romannumeral
                       \dtl@entrycr\endcsname}%
                 }%
                 \expandafter\@dtl@toks\expandafter{\@dtl@thisentry}%
                 \edef\@do@dtlnewentry{\noexpand\@sDTLnewdbentry
                   {#2}{\@dtl@thiskey}{\the\@dtl@toks}}%
                 \@do@dtlnewentry
                 \ifx\@dtl@lin@\@dtl@separator
                   \@dtl@conditionfalse
                 \fi
               \if@dtl@condition
               \repeat
             }%
         }%
         \ifeof\@dtl@read \@dtl@conditionfalse\fi
         \if@dtl@condition
         \repeat
       \fi
    \fi
    \closein\@dtl@read
     \edef\@dtl@maxcols{\expandafter
       \number\csname dtlcols@#2\endcsname}%
     \dtlgforint\dtl@entrycr=1\to\@dtl@maxcols\step1\do
     {%
       \@ifundefined{@dtl@inhd@\romannumeral\dtl@entrycr}%
       {}%
       {%
          \expandafter\let\expandafter\@dtl@head
            \csname @dtl@inhd@\romannumeral\dtl@entrycr\endcsname
          \@dtl@toks=\expandafter{\@dtl@head}%
          \edef\@dtl@dosetheader{\noexpand\@dtl@setheaderforindex
            {#2}{\number\dtl@entrycr}{\the\@dtl@toks}}%
          \@dtl@dosetheader
       }%
     }%
  \endgroup
  }{%
    \PackageError{datatool}{Can't load database `#2' (file `#3'
    doesn't exist)}{}%
  }%
}
\newcommand*\DTLloadrawdb{%
  \let\@dtl@doreadline\@dtl@readrawline
  \@dtlloaddb
}
\begingroup
\catcode`\%=\active
\catcode`$=\active
\catcode`&=\active
\catcode`~=\active
\catcode`_=\active
\catcode`^=\active
\catcode`#=\active
\catcode`?=6\relax
\catcode`<=1\relax
\catcode`>=2\relax
\catcode`\{=\active
\catcode`\}=\active
\gdef\@dtl@rawread?1to?2<\relax
<<\catcode`\%=\active
\catcode`$=\active
\catcode`&=\active
\catcode`~=\active
\catcode`_=\active
\catcode`^=\active
\catcode`#=\active
\catcode`\{=\active
\catcode`\}=\active
\def%<\noexpand\%>\relax
\def$<\noexpand\$>\relax
\def&<\&>\relax
\def#<\#>\relax
\def~<\noexpand\textasciitilde>\relax
\def_<\noexpand\_>\relax
\def^<\noexpand\textasciicircum>\relax
\@dtl@activatebraces
\@dtl@doreadraw?1?2>>>
\gdef\@dtl@doreadraw?1?2<\relax
\begingroup\catcode\endlinechar=\active\global\read?1 to \dtl@tmp\endgroup
\expandafter\@dtl@stripeol\dtl@tmp
\let\dtl@tmp\@dtl@strippedline
\protected@xdef?2<\dtl@tmp>\relax
>
\endgroup
\begingroup
\catcode`\{=\active
\catcode`\}=\active
\catcode`<=1\relax
\catcode`>=2\relax
\gdef\@dtl@activatebraces<%
 \catcode`\{=\active
 \catcode`\}=\active
 \def{<\noexpand\{>%
 \def}<\noexpand\}>%
>%
\endgroup
\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
  }%
}
\newcommand*{\dtlshowdb}[1]{%
  \expandafter\showthe\csname dtldb@#1\endcsname
}
\newcommand*{\dtlshowdbkeys}[1]{%
  \expandafter\showthe\csname dtlkeys@#1\endcsname
}
\newcommand*{\dtlshowtype}[2]{%
  \DTLgetdatatype{\@dtl@type}{#1}{#2}\show\@dtl@type
}
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</datatool-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datatool-base-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\subsection{Rollback v2.32 (datatool-base-2019-09-27.sty)}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{datatool-base}[2019/09/27 v2.32 (NLCT)]
\RequirePackage{etoolbox}
\RequirePackage{amsmath}
\RequirePackage{xkeyval}
\RequirePackage{xfor}
\RequirePackage{ifthen}
\RequirePackage{substr}[2009/10/20]
\ifundef{\ifdtlverbose}
{
  \define@boolkey{datatool-base.sty}[dtl]{verbose}[true]{}
}%
{}
\define@choicekey{datatool-base.sty}{math}[\val\nr]{fp,pgfmath}{%
  \renewcommand*\@dtl@mathprocessor{#1}%
}
\define@boolkey{datatool-base.sty}[@dtl@]{utf8}[true]{}
\ifdef\UTFviii@two@octets
{\booltrue{@dtl@utf8}}%
{\boolfalse{@dtl@utf8}}
\newcommand*{\dtlenableUTFviii}{\booltrue{@dtl@utf8}}
\newcommand*{\dtldisableUTFviii}{\boolfalse{@dtl@utf8}}
\providecommand*{\@dtl@mathprocessor}{fp}
\ProcessOptionsX
\RequirePackage{datatool-\@dtl@mathprocessor}[=v2.32]
\newcommand*{\dtl@message}[1]{%
  \ifdtlverbose\typeout{#1}\fi
}
\newtoks\@dtl@toks
\newcount\@dtl@tmpcount
\newlength\dtl@tmplength
\newcommand{\dtl@ifsingle}[3]{%
  \def\@dtl@arg{#1}%
  \ifdefempty{\@dtl@arg}%
  {%
    #3%
  }%
  {%
    \@dtl@ifsingle#1\@nil{#2}{#3}%
  }%
}
\def\@dtl@ifsingle#1#2\@nil#3#4{%
  \def\dtl@sg@arg{#2}%
  \ifdefempty{\dtl@sg@arg}%
  {%
   #3%
  }%
  {%
   #4%
  }%
}
\newcommand{\dtl@ifsingleorUTFviii}[3]{%
  \ifbool{@dtl@utf8}
  {%
    \def\@dtl@arg{#1}%
    \ifdefempty{\@dtl@arg}%
    {%
      #3%
    }%
    {%
      \expandafter\dtl@if@two@octets#1\relax\relax\dtl@end@if@two@octets
      {%
        \dtl@getfirst@UTFviii#1\@nil\end@dtl@getfirst@UTFviii
        \ifdefempty\dtl@rest{#2}{#3}%
      }%
      {%
        \@dtl@ifsingle#1\@nil{#2}{#3}%
      }%
    }%
  }%
  {%
    \dtl@ifsingle{#1}{#2}{#3}%
  }%
}%
\newcommand{\dtlifintopenbetween}[5]{%
  \ifnum#1>#2\relax
    \ifnum#1<#3\relax
      #4%
    \else
      #5%
    \fi
  \else
    #5%
  \fi
}
\newcommand{\dtlifintclosedbetween}[5]{%
  \dtlifintopenbetween{#1}{#2}{#3}{#4}%
  {%
    \ifnum#1=#2\relax
      #4%
    \else
      \ifnum#1=#3\relax
        #4%
      \else
        #5%
      \fi
    \fi
  }%
}
\long\def\long@collect@body#1{%
  \@envbody{\@xp#1\@xp{\the\@envbody}}%
  \edef\process@envbody{\the\@envbody\@nx\end{\@currenvir}}%
  \@envbody\@emptytoks \def\begin@stack{b}%
  \begingroup
  \@xp\let\csname\@currenvir\endcsname\long@collect@@body
  \edef\process@envbody{\@xp\@nx\csname\@currenvir\endcsname}%
  \process@envbody
}
\long\def\long@addto@envbody#1{%
  \toks@{#1}%
  \edef\@dtl@tmp{\the\@envbody\the\toks@}%
  \global\@envbody\@xp{\@dtl@tmp}%
}
\long\def\long@collect@@body#1\end#2{%
  \protected@edef\begin@stack{%
    \long@push@begins#1\begin\end \@xp\@gobble\begin@stack
  }%
  \ifx\@empty\begin@stack
    \endgroup
    \@checkend{#2}%
    \long@addto@envbody{#1}%
  \else
    \long@addto@envbody{#1\end{#2}}%
  \fi
  \process@envbody
}
\long\def\long@push@begins#1\begin#2{%
  \ifx\end#2\else b\@xp\long@push@begins\fi
}
\newcommand*{\DTLifinlist}[4]{%
  \def\@dtl@doifinlist##1,#1,##2\end@dtl@doifinlist{%
     \def\@before{##1}%
     \def\@after{##2}%
  }%
  \expandafter\@dtl@doifinlist\expandafter,#2,#1,\@nil
    \end@dtl@doifinlist
  \ifx\@after\@nnil
    #4%
  \else
    #3%
  \fi
}
\newif\ifDTLlistskipempty
\DTLlistskipemptytrue
\newrobustcmd{\DTLlistelement}[2]{%
  \begingroup
    \@dtl@tmpcount=0\relax
    \@for\@dtl@element:=#1\do{%
     \ifDTLlistskipempty
       \ifdefempty{\@dtl@element}%
       {}%
       {%
         \advance\@dtl@tmpcount by 1\relax%
         \ifnum\@dtl@tmpcount=#2 \@dtl@element\@endfortrue\fi
       }%
     \else
       \advance\@dtl@tmpcount by 1\relax%
       \ifnum\@dtl@tmpcount=#2 \@dtl@element\@endfortrue\fi
     \fi
    }%
    \if@endfor \else\@dtl@listelement@outofrange{#2}\fi
  \endgroup
}
\newrobustcmd{\DTLfetchlistelement}[3]{%
 \begingroup
   \@dtl@tmpcount=0\relax
   \@for\@dtl@element:=#1\do{%
    \ifDTLlistskipempty
      \ifdefempty{\@dtl@element}%
      {}%
      {%
        \advance\@dtl@tmpcount by 1\relax%
        \ifnum\@dtl@tmpcount=#2 \@endfortrue\fi
      }%
    \else
      \advance\@dtl@tmpcount by 1\relax%
      \ifnum\@dtl@tmpcount=#2 \@endfortrue\fi
    \fi
   }%
   \if@endfor \else\def\@dtl@element{\@dtl@listelement@outofrange{#2}}\fi
   \edef\x{%
     \endgroup
     \noexpand\def\noexpand#3{\expandonce\@dtl@element}%
   }\x
}
\newcommand{\@dtl@listelement@outofrange}[1]{%
  \PackageWarning{datatool-base}{List index `\number#1' out of range}%
}
\newrobustcmd{\DTLnumitemsinlist}[2]{%
  \@dtl@tmpcount=0\relax
  \@for\@dtl@element:=#1\do{%
     \ifDTLlistskipempty
       \ifdefempty{\@dtl@element}%
       {}%
       {\advance\@dtl@tmpcount by 1\relax}%
     \else
       \advance\@dtl@tmpcount by 1\relax
     \fi
  }%
  \edef#2{\number\@dtl@tmpcount}%
}
\newcommand*{\dtl@choplast}[3]{%
  \let#2\@empty
  \let#3\@empty
  \@for\@dtl@element:=#1\do{%
    \ifdefempty{#3}%
    {%
    }%
    {%
      \ifdefempty{#2}%
      {%
        \expandafter\toks@\expandafter{#3}%
        \edef#2{{\the\toks@}}%
      }%
      {%
        \expandafter\toks@\expandafter{#3}%
        \expandafter\@dtl@toks\expandafter{#2}%
        \edef#2{\the\@dtl@toks,{\the\toks@}}%
      }%
    }%
    \let#3=\@dtl@element%
  }%
}
\newcommand*{\dtl@chopfirst}[3]{%
  \let#2=\@empty
  \let#3=\@empty
  \@for\@dtl@element:=#1\do{%
    \let#2=\@dtl@element
    \@endfortrue
  }%
  \if@endfor
   \let#3=\@forremainder
  \fi
  \@endforfalse
}
\newcommand{\dtl@sortlist}[2]{%
\def\@dtl@sortedlist{}%
\@for\@dtl@currentrow:=#1\do{%
\expandafter\dtl@insertinto\expandafter
  {\@dtl@currentrow}{\@dtl@sortedlist}{#2}%
\@endforfalse}%
\let#1=\@dtl@sortedlist
}
\newcommand{\dtl@insertinto}[3]{%
  \def\@dtl@newsortedlist{}%
  \@dtl@insertdonefalse
  \@for\dtl@srtelement:=#2\do{%
    \if@dtl@insertdone
      \expandafter\toks@\expandafter{\dtl@srtelement}%
      \edef\@dtl@newstuff{{\the\toks@}}%
    \else
      \expandafter#3\expandafter{\dtl@srtelement}{#1}%
      \ifnum\dtl@sortresult<0\relax
        \expandafter\toks@\expandafter{\dtl@srtelement}%
        \@dtl@toks{#1}%
        \edef\@dtl@newstuff{{\the\@dtl@toks},{\the\toks@}}%
        \@dtl@insertdonetrue
      \else
        \expandafter\toks@\expandafter{\dtl@srtelement}%
        \edef\@dtl@newstuff{{\the\toks@}}%
      \fi
    \fi
    \ifdefempty{\@dtl@newsortedlist}%
    {%
      \expandafter\toks@\expandafter{\@dtl@newstuff}%
      \edef\@dtl@newsortedlist{\the\toks@}%
    }%
    {%
      \expandafter\toks@\expandafter{\@dtl@newsortedlist}%
      \expandafter\@dtl@toks\expandafter{\@dtl@newstuff}%
      \edef\@dtl@newsortedlist{\the\toks@,\the\@dtl@toks}%
    }%
    \@endforfalse
  }%
  \ifdefempty{\@dtl@newsortedlist}%
  {%
    \@dtl@toks{#1}%
    \edef\@dtl@newsortedlist{{\the\@dtl@toks}}%
  }%
  {%
    \if@dtl@insertdone
    \else
      \expandafter\toks@\expandafter{\@dtl@newsortedlist}%
      \@dtl@toks{#1}%
      \edef\@dtl@newsortedlist{\the\toks@,{\the\@dtl@toks}}%
    \fi
  }%
  \global\let#2=\@dtl@newsortedlist
}
\newcommand{\dtlsortlist}[2]{%
\def\@dtl@sortedlist{}%
\@for\@dtl@currentrow:=#1\do{%
\expandafter\dtlinsertinto\expandafter
  {\@dtl@currentrow}{\@dtl@sortedlist}{#2}%
\@endforfalse}%
\let#1=\@dtl@sortedlist
}
\newcommand{\dtlinsertinto}[3]{%
  \def\@dtl@newsortedlist{}%
  \@dtl@insertdonefalse
  \@for\dtl@srtelement:=#2\do{%
    \expandafter\DTLifSubString\expandafter{\dtl@srtelement}{,}
    {%
      \expandafter\toks@\expandafter{\dtl@srtelement}%
      \edef\dtl@srtelement{{\the\toks@}}%
    }%
    {%
    }
    \if@dtl@insertdone
      \let\@dtl@newstuff\dtl@srtelement
    \else
      \expandafter#3\expandafter\dtl@sortresult
        \expandafter{\dtl@srtelement}{#1}%
      \ifnum\dtl@sortresult>0\relax
        \DTLifSubString{#1}{,}%
        {%
          \@dtl@toks{{#1}}%
        }%
        {%
          \@dtl@toks{#1}%
        }%
        \expandafter\toks@\expandafter{\dtl@srtelement}%
        \edef\@dtl@newstuff{\the\@dtl@toks,\the\toks@}%
        \@dtl@insertdonetrue
      \else
        \expandafter\toks@\expandafter{\dtl@srtelement}%
        \edef\@dtl@newstuff{{\the\toks@}}%
        \let\@dtl@newstuff\dtl@srtelement
      \fi
    \fi
    \ifdefempty{\@dtl@newsortedlist}%
    {%
      \expandafter\toks@\expandafter{\@dtl@newstuff}%
      \edef\@dtl@newsortedlist{\the\toks@}%
    }%
    {%
      \expandafter\toks@\expandafter{\@dtl@newsortedlist}%
      \expandafter\@dtl@toks\expandafter{\@dtl@newstuff}%
      \edef\@dtl@newsortedlist{\the\toks@,\the\@dtl@toks}%
    }%
    \@endforfalse
  }%
  \ifdefempty{\@dtl@newsortedlist}%
  {%
    \DTLifSubString{#1}{,}%
    {%
      \@dtl@toks{{#1}}%
    }%
    {%
      \@dtl@toks{#1}%
    }%
    \edef\@dtl@newsortedlist{\the\@dtl@toks}%
  }%
  {%
    \if@dtl@insertdone
    \else
      \DTLifSubString{#1}{,}%
      {%
        \@dtl@toks{{#1}}%
      }%
      {%
        \@dtl@toks{#1}%
      }%
      \expandafter\toks@\expandafter{\@dtl@newsortedlist}%
      \edef\@dtl@newsortedlist{\the\toks@,\the\@dtl@toks}%
    \fi
  }%
  \global\let#2=\@dtl@newsortedlist
}
\newcommand*{\edtlinsertinto}[3]{%
  \protected@edef\dtl@srtelement{#1}%
  \expandafter\dtlinsertinto\expandafter{\dtl@srtelement}{#2}{#3}%
}
\newif\if@dtl@insertdone
\newcount\dtl@sortresult
\newcommand*{\DTLlistformatsep}{, }
\newcommand*{\DTLlistformatoxford}{}
\ifdef\andname
{\newcommand*{\DTLandname}{\andname}}
{\newcommand*{\DTLandname}{\&}}
\newcommand*{\DTLlistformatlastsep}{ \DTLandname\space}
\newcommand*{\DTLlistformatitem}[1]{#1}
\newcommand*{\@dtl@formatlist@handler}[1]{%
  \@dtl@formatlist@itemsep
  \@dtl@formatlist@lastitem
  \renewcommand{\@dtl@formatlist@lastitem}{%
    \renewcommand{\@dtl@formatlist@itemsep}{%
      \DTLlistformatsep
      \renewcommand*{\@dtl@formatlist@prelastitemsep}{%
        \DTLlistformatoxford}}%
    \renewcommand{\@dtl@formatlist@prelastitem}{%
      \@dtl@formatlist@prelastitemsep
       \DTLlistformatlastsep}%
    \DTLlistformatitem{#1}%
  }%
}%
\newrobustcmd*{\DTLformatlist}{%
 \@ifstar{\s@dtlformatlist}{\@dtlformatlist}%
}
\newcommand*{\s@dtlformatlist}[1]{%
  \def\@dtl@formatlist@itemsep{}%
  \def\@dtl@formatlist@lastitem{}%
  \def\@dtl@formatlist@prelastitem{}%
  \def\@dtl@formatlist@prelastitemsep{}%
  \@for\@dtl@formatlist@item:=#1\do{%
    \ifDTLlistskipempty
      \ifdefempty{\@dtl@formatlist@item}%
      {}%
      {\expandafter\@dtl@formatlist@handler\expandafter{\@dtl@formatlist@item}}%
    \else
      \expandafter\@dtl@formatlist@handler\expandafter{\@dtl@formatlist@item}%
    \fi
  }%
  \@dtl@formatlist@prelastitem\@dtl@formatlist@lastitem
}
\newcommand*{\@dtlformatlist}[1]{{\s@dtlformatlist{#1}}}
\newcommand{\@dtl@toks@gput@right@cx}[2]{%
  \def\@dtl@toks@name{#1}%
  \edef\@dtl@stuff{#2}%
  \global\csname\@dtl@toks@name\endcsname\expandafter
    \expandafter\expandafter{\expandafter\the
    \csname\expandafter\@dtl@toks@name\expandafter\endcsname\@dtl@stuff}%
}
\newcommand{\@dtl@toks@gconcat@middle@cx}[4]{%
  \def\@dtl@toks@name{#1}%
  \edef\@dtl@stuff{#3}%
  \global\csname\@dtl@toks@name\endcsname\expandafter\expandafter
    \expandafter\expandafter\expandafter
    \expandafter\expandafter{\expandafter\expandafter\expandafter
    \the\expandafter\expandafter\expandafter#2%
    \expandafter\@dtl@stuff\the#4}%
}
\newcount\@dtl@numgrpsepcount
\newcommand*{\@dtl@decimal}{.}
\newcommand*{\@dtl@numbergroupchar}{,}
\newcommand*{\DTLsetnumberchars}[2]{%
  \renewcommand*{\@dtl@numbergroupchar}{#1}%
  \renewcommand*{\@dtl@decimal}{#2}%
  \@dtl@construct@getnums
  \@dtl@construct@stripnumgrpchar{#1}%
}
\edef\@dtl@construct@getintfrac#1{%
  \noexpand\def\noexpand\@dtl@getintfrac##1#1##2\noexpand\relax{%
    \noexpand\@dtl@get@intpart{##1}%
    \noexpand\def\noexpand\@dtl@fracpart{##2}%
    \noexpand\ifdefempty{\noexpand\@dtl@fracpart}
    {%
      \noexpand\def\noexpand\@dtl@fracpart{0}%
    }%
    {%
      \noexpand\@dtl@getfracpart##2\noexpand\relax
      \noexpand\@dtl@choptrailingzeroes{\noexpand\@dtl@fracpart}%
    }%
  }%
  \noexpand\def\noexpand\@dtl@getfracpart##1#1\noexpand\relax{%
    \noexpand\def\noexpand\@dtl@fracpart{##1}%
  }%
  \noexpand\def\noexpand\DTLconverttodecimal##1##2{%
    \noexpand\dtl@ifsingle{##1}%
    {%
      \noexpand\expandafter\noexpand\toks@\noexpand\expandafter{##1}%
      \noexpand\edef\noexpand\@dtl@tmp{\noexpand\the\noexpand\toks@}%
    }%
    {%
      \noexpand\def\noexpand\@dtl@tmp{##1}%
    }%
    \noexpand\@dtl@standardize@currency\noexpand\@dtl@tmp
    \noexpand\ifdefempty{\noexpand\@dtl@org@currency}%
    {%
    }%
    {%
      \noexpand\let\noexpand\@dtl@currency\noexpand\@dtl@org@currency
    }%
    \noexpand\expandafter
      \noexpand\@dtl@getintfrac\noexpand\@dtl@tmp#1\noexpand\relax
    \noexpand\edef##2{\noexpand\@dtl@intpart.\noexpand\@dtl@fracpart}%
  }%
}
\newcommand*{\@dtl@construct@getnums}{%
  \expandafter\@dtl@construct@getintfrac\expandafter{\@dtl@decimal}%
}
\newcommand*{\@dtl@get@intpart}[1]{%
  \@dtl@tmpcount=1\relax
  \def\@dtl@intpart{#1}%
  \ifx\@dtl@intpart\@empty
    \def\@dtl@intpart{0}%
  \else
    \def\@dtl@intpart{}%
    \@dtl@get@int@part#1.\relax%
  \fi
  \ifnum\@dtl@tmpcount<0\relax
    \edef\@dtl@intpart{-\@dtl@intpart}%
  \fi
  \@dtl@strip@numgrpchar{\@dtl@intpart}%
}
\def\@dtl@get@int@part#1#2\relax{%
  \def\@dtl@argi{#1}%
  \def\@dtl@argii{#2}%
  \ifx\protect#1\relax%
    \let\@dtl@get@nextintpart=\@dtl@get@int@part
  \else
    \expandafter\ifx\@dtl@argi\$%
      \let\@dtl@get@nextintpart=\@dtl@get@int@part
    \else
      \ifx-#1%
        \multiply\@dtl@tmpcount by -1\relax
        \let\@dtl@get@nextintpart=\@dtl@get@int@part
      \else
        \if\@dtl@argi+%
          \let\@dtl@get@nextintpart=\@dtl@get@int@part
        \else
          \def\@dtl@intpart{#1}%
          \ifx.\@dtl@argii
            \let\@dtl@get@nextintpart=\@gobble
          \else
            \let\@dtl@get@nextintpart=\@dtl@get@next@intpart
          \fi
        \fi
      \fi
    \fi
  \fi
  \@dtl@get@nextintpart#2\relax
}
\def\@dtl@get@next@intpart#1.\relax{%
  \edef\@dtl@intpart{\@dtl@intpart#1}%
}
\newcommand*{\@dtl@choptrailingzeroes}[1]{%
  \def\@dtl@tmpcpz{}%
  \expandafter\@dtl@chop@trailingzeroes#1\@nil%
  \let#1=\@dtl@tmpcpz
}
\def\@dtl@chop@trailingzeroes#1#2\@nil{%
  \dtlifnumeq{#2}{0}%
  {%
    \edef\@dtl@tmpcpz{\@dtl@tmpcpz#1}%
    \let\@dtl@chopzeroesnext=\@dtl@gobbletonil
  }%
  {%
    \edef\@dtl@tmpcpz{\@dtl@tmpcpz#1}%
    \let\@dtl@chopzeroesnext=\@dtl@chop@trailingzeroes
  }%
  \@dtl@chopzeroesnext#2\@nil
}
\def\@dtl@gobbletonil#1\@nil{}
\newcommand*{\dtl@truncatedecimal}[1]{%
  \expandafter\@dtl@truncatedecimal#1.\@nil#1%
}
\def\@dtl@truncatedecimal#1.#2\@nil#3{%
  \def#3{#1}%
}
\newcommand*{\@dtl@strip@numgrpchar}[1]{%
  \def\@dtl@stripped{}%
  \edef\@dtl@do@stripnumgrpchar{%
    \noexpand\@@dtl@strip@numgrpchar#1\@dtl@numbergroupchar
    \noexpand\relax
  }%
  \@dtl@do@stripnumgrpchar
  \let#1=\@dtl@stripped
}
\edef\@dtl@construct@stripnumgrpchar#1{%
  \noexpand\def\noexpand\@@dtl@strip@numgrpchar##1#1##2\noexpand\relax{%
    \noexpand\expandafter\noexpand\toks@\noexpand\expandafter
      {\noexpand\@dtl@stripped}%
    \noexpand\edef\noexpand\@dtl@stripped{%
      \noexpand\the\noexpand\toks@
      ##1%
    }%
    \noexpand\def\noexpand\@dtl@tmp{##2}%
    \noexpand\ifx\noexpand\@dtl@tmp\noexpand\@empty
      \noexpand\let\noexpand\@dtl@next=\noexpand\relax
    \noexpand\else
      \noexpand\let\noexpand\@dtl@next=\noexpand\@@dtl@strip@numgrpchar
    \noexpand\fi
    \noexpand\@dtl@next##2\noexpand\relax
  }%
}
\newcommand*{\DTLdecimaltolocale}[2]{%
  \edef\@dtl@tmpdtl{#1}%
  \expandafter\@dtl@decimaltolocale\@dtl@tmpdtl.\relax
  \dtlifnumeq{\@dtl@fracpart}{0}%
  {%
    \edef#2{\@dtl@intpart}%
  }%
  {%
    \edef#2{\@dtl@intpart\@dtl@decimal\@dtl@fracpart}%
  }%
}
\def\@dtl@decimaltolocale#1.#2\relax{%
  \@dtl@decimaltolocaleint{#1}%
  \def\@dtl@fracpart{#2}%
  \ifdefempty\@dtl@fracpart
  {%
    \def\@dtl@fracpart{0}%
  }%
  {%
    \@dtl@decimaltolocalefrac#2\relax
  }%
}
\def\@dtl@decimaltolocaleint#1{%
  \@dtl@tmpcount=0\relax
  \@dtl@countdigits#1.\relax
  \@dtl@numgrpsepcount=\@dtl@tmpcount\relax
  \divide\@dtl@numgrpsepcount by 3\relax
  \multiply\@dtl@numgrpsepcount by 3\relax
  \advance\@dtl@numgrpsepcount by -\@dtl@tmpcount\relax
  \ifnum\@dtl@numgrpsepcount<0\relax
    \advance\@dtl@numgrpsepcount by 3\relax
  \fi
  \def\@dtl@intpart{}%
  \@dtl@decimal@to@localeint#1.\relax
}
\def\@dtl@countdigits#1#2\relax{%
  \advance\@dtl@tmpcount by 1\relax
  \ifx.#2\relax
    \let\@dtl@countnext=\@gobble
  \else
    \let\@dtl@countnext=\@dtl@countdigits
  \fi
  \@dtl@countnext#2\relax
}
\def\@dtl@decimal@to@localeint#1#2\relax{%
  \advance\@dtl@numgrpsepcount by 1\relax
  \ifx.#2\relax
    \edef\@dtl@intpart{\@dtl@intpart#1}%
    \let\@dtl@localeintnext=\@gobble
  \else
    \ifnum\@dtl@numgrpsepcount=3\relax
      \edef\@dtl@intpart{\@dtl@intpart#1\@dtl@numbergroupchar}%
      \@dtl@numgrpsepcount=0\relax
    \else
      \ifnum\@dtl@numgrpsepcount>3\relax
        \@dtl@numgrpsepcount=0\relax
      \fi
      \edef\@dtl@intpart{\@dtl@intpart#1}%
    \fi
    \let\@dtl@localeintnext=\@dtl@decimal@to@localeint
  \fi
  \@dtl@localeintnext#2\relax
}
\def\@dtl@decimaltolocalefrac#1.\relax{%
  \count@=0\relax
  \@dtl@digitcount#1\relax
  \ifnum\count@>9\relax
    \@dtl@chopexcessfrac#1000000000\@nil
  \else
    \def\@dtl@fracpart{#1}%
  \fi
}
\newcommand*{\@dtl@chopexcessfrac}[9]{%
  \def\@dtl@fracpart{#1#2#3#4#5#6#7#8#9}%
  \@dtl@gobbletonil
}
\newcommand*{\@dtl@digitcount}[1]{%
  \ifx\relax#1\relax
    \let\@dtl@digitcountnext\relax
  \else
    \advance\count@ by \@ne
    \let\@dtl@digitcountnext\@dtl@digitcount
  \fi
  \@dtl@digitcountnext
}
\newcommand*{\DTLdecimaltocurrency}[2]{%
  \edef\@dtl@tmpdtl{#1}%
  \expandafter\@dtl@decimaltolocale\@dtl@tmpdtl.\relax
  \dtl@truncatedecimal\@dtl@tmpdtl
  \@dtl@tmpcount=\@dtl@tmpdtl\relax
  \expandafter\@dtl@toks\expandafter{\@dtl@currency}%
  \dtlifnumeq{\@dtl@fracpart}{0}%
  {%
    \ifnum\@dtl@tmpcount<0\relax
      \@dtl@tmpcount = -\@dtl@tmpcount\relax
      \edef#2{-\the\@dtl@toks\the\@dtl@tmpcount\@dtl@decimal00}%
    \else
      \edef#2{\the\@dtl@toks\@dtl@intpart\@dtl@decimal00}%
    \fi
  }%
  {%
    \ifnum\@dtl@tmpcount<0\relax
      \@dtl@tmpcount = -\@dtl@tmpcount\relax
      \ifnum\@dtl@fracpart<10\relax
        \edef#2{%
          -\the\@dtl@toks\number\@dtl@tmpcount
          \@dtl@decimal\@dtl@fracpart0%
        }%
      \else
        \edef#2{%
          -\the\@dtl@toks\number\@dtl@tmpcount
          \@dtl@decimal\@dtl@fracpart
        }%
      \fi
    \else
      \ifnum\@dtl@fracpart<10\relax
        \edef#2{\the\@dtl@toks\@dtl@intpart\@dtl@decimal\@dtl@fracpart0}%
      \else
        \edef#2{\the\@dtl@toks\@dtl@intpart\@dtl@decimal\@dtl@fracpart}%
      \fi
    \fi
  }%
}
\@dtl@construct@getnums
\expandafter\@dtl@construct@stripnumgrpchar\expandafter
  {\@dtl@numbergroupchar}
\newcommand*{\@dtl@currencies}{\$,\pounds}
\newcommand*{\DTLnewcurrencysymbol}[1]{%
  \expandafter\toks@\expandafter{\@dtl@currencies}%
  \@dtl@toks{#1}%
  \edef\@dtl@currencies{\the\@dtl@toks,\the\toks@}%
}
\AtBeginDocument{%
  \@ifundefined{texteuro}{}{\DTLnewcurrencysymbol{\texteuro}}%
  \@ifundefined{textdollar}{}{\DTLnewcurrencysymbol{\textdollar}}%
  \@ifundefined{textstirling}{}{\DTLnewcurrencysymbol{\textstirling}}%
  \@ifundefined{textyen}{}{\DTLnewcurrencysymbol{\textyen}}%
  \@ifundefined{textwon}{}{\DTLnewcurrencysymbol{\textwon}}%
  \@ifundefined{textcurrency}{}{\DTLnewcurrencysymbol{\textcurrency}}%
  \@ifundefined{euro}{}{\DTLnewcurrencysymbol{\euro}}%
  \@ifundefined{yen}{}{\DTLnewcurrencysymbol{\yen}}%
}
\newcommand{\@dtl@standardize@currency}[1]{%
  \def\@dtl@org@currency{}%
  \@for\@dtl@thiscurrency:=\@dtl@currencies\do{%
    \expandafter\toks@\expandafter{\@dtl@thiscurrency}%
    \edef\@dtl@dosubs{\noexpand\DTLsubstitute{\noexpand#1}%
     {\the\toks@}{\noexpand\$}}%
    \@dtl@dosubs
    \ifdefempty{\@dtl@replaced}%
    {%
    }%
    {%
      \let\@dtl@org@currency=\@dtl@replaced
      \@endfortrue
    }%
  }%
  \@endforfalse
}
\newcommand*{\@dtl@currency}{\$}
\newcommand*{\DTLsetdefaultcurrency}[1]{%
  \renewcommand*{\@dtl@currency}{#1}%
}
\newcommand*{\DTLadd}[3]{%
  \DTLconverttodecimal{#2}{\@dtl@numi}%
  \DTLconverttodecimal{#3}{\@dtl@numii}%
  \dtladd{\@dtl@tmp}{\@dtl@numi}{\@dtl@numii}%
  \ifdefempty{\@dtl@replaced}%
  {%
    \DTLdecimaltolocale{\@dtl@tmp}{#1}%
  }%
  {%
    \DTLdecimaltocurrency{\@dtl@tmp}{#1}%
  }%
}
\newcommand*{\DTLgadd}[3]{%
  \DTLadd{\@dtl@tmpii}{#2}{#3}%
  \global\let#1=\@dtl@tmpii
}
\newcommand*{\DTLaddall}[2]{%
  \def\@dtl@sum{0}%
  \@for\dtl@thisval:=#2\do{%
    \expandafter\DTLconverttodecimal\expandafter{\dtl@thisval}{\@dtl@num}%
    \dtladd{\@dtl@sum}{\@dtl@sum}{\@dtl@num}%
  }%
  \ifdefempty{\@dtl@replaced}%
  {%
    \DTLdecimaltolocale{\@dtl@sum}{#1}%
  }%
  {%
    \DTLdecimaltocurrency{\@dtl@sum}{#1}%
  }%
}
\newcommand*{\DTLgaddall}[2]{%
  \DTLaddall{\@dtl@tmpi}{#2}%
  \global\let#1=\@dtl@tmpi
}
\newcommand*{\DTLsub}[3]{%
  \DTLconverttodecimal{#2}{\@dtl@numi}%
  \DTLconverttodecimal{#3}{\@dtl@numii}%
  \dtlsub{\@dtl@tmp}{\@dtl@numi}{\@dtl@numii}%
  \ifdefempty{\@dtl@replaced}%
  {%
    \DTLdecimaltolocale{\@dtl@tmp}{#1}%
  }%
  {%
    \DTLdecimaltocurrency{\@dtl@tmp}{#1}%
  }%
}
\newcommand*{\DTLgsub}[3]{%
  \DTLsub{\@dtl@tmpii}{#2}{#3}%
  \global\let#1=\@dtl@tmpii
}
\newcommand*{\DTLmul}[3]{%
  \let\@dtl@thisreplaced=\@empty
  \DTLconverttodecimal{#2}{\@dtl@numi}%
  \ifdefempty{\@dtl@replaced}%
  {%
  }%
  {%
    \let\@dtl@thisreplaced=\@dtl@replaced
  }%
  \DTLconverttodecimal{#3}{\@dtl@numii}%
  \ifdefempty{\@dtl@replaced}%
  {%
  }%
  {%
    \let\@dtl@thisreplaced=\@dtl@replaced
  }%
  \dtlmul{\@dtl@tmp}{\@dtl@numi}{\@dtl@numii}%
  \ifdefempty{\@dtl@thisreplaced}%
  {%
    \DTLdecimaltolocale{\@dtl@tmp}{#1}%
  }%
  {%
    \DTLdecimaltocurrency{\@dtl@tmp}{#1}%
  }%
}
\newcommand*{\DTLgmul}[3]{%
 \DTLmul{\@dtl@tmpii}{#2}{#3}%
 \global\let#1=\@dtl@tmpii
}
\newcommand*{\DTLdiv}[3]{%
  \let\@dtl@thisreplaced=\@empty
  \DTLconverttodecimal{#2}{\@dtl@numi}%
  \ifdefempty{\@dtl@replaced}%
  {%
  }%
  {%
    \let\@dtl@thisreplaced=\@dtl@replaced
  }%
  \DTLconverttodecimal{#3}{\@dtl@numii}%
  \dtldiv{\@dtl@tmp}{\@dtl@numi}{\@dtl@numii}%
  \ifdefempty{\@dtl@thisreplaced}%
  {%
    \DTLdecimaltolocale{\@dtl@tmp}{#1}%
  }%
  {%
    \ifdefequal{\@dtl@thisreplaced}{\@dtl@replaced}%
    {%
      \DTLdecimaltolocale{\@dtl@tmp}{#1}%
    }%
    {%
      \DTLdecimaltocurrency{\@dtl@tmp}{#1}%
    }%
  }%
}
\newcommand*{\DTLgdiv}[3]{%
  \DTLdiv{\@dtl@tmpii}{#2}{#3}%
  \global\let#1=\@dtl@tmpii
}
\newcommand*{\DTLabs}[2]{%
  \DTLconverttodecimal{#2}{\@dtl@numi}%
  \dtlabs{\@dtl@tmp}{\@dtl@numi}%
  \ifdefempty{\@dtl@replaced}%
  {%
    \DTLdecimaltolocale{\@dtl@tmp}{#1}%
  }%
  {%
    \DTLdecimaltocurrency{\@dtl@tmp}{#1}%
  }%
}
\newcommand*{\DTLgabs}[2]{%
  \DTLabs{\@dtl@tmpii}{#2}%
  \global\let#1=\@dtl@tmpii
}
\newcommand*{\DTLneg}[2]{%
  \DTLconverttodecimal{#2}{\@dtl@numi}%
  \dtlneg{\@dtl@tmp}{\@dtl@numi}%
  \ifdefempty{\@dtl@replaced}%
  {%
    \DTLdecimaltolocale{\@dtl@tmp}{#1}%
  }%
  {%
    \DTLdecimaltocurrency{\@dtl@tmp}{#1}%
  }%
}
\newcommand*{\DTLgneg}[2]{%
  \DTLneg{\@dtl@tmpii}{#2}%
  \global\let#1=\@dtl@tmpii
}
\newcommand*{\DTLsqrt}[2]{%
  \DTLconverttodecimal{#2}{\@dtl@numi}%
  \dtlroot{\@dtl@tmpi}{\@dtl@numi}{2}%
  \ifdefempty{\@dtl@replaced}%
  {%
    \DTLdecimaltolocale{\@dtl@tmpi}{#1}%
  }%
  {%
    \DTLdecimaltocurrency{\@dtl@tmpi}{#1}%
  }%
}
\newcommand*{\DTLgsqrt}[2]{%
\DTLsqrt{\@dtl@tmpii}{#2}%
\global\let#1=\@dtl@tmpii
}
\newcommand*{\DTLmin}[3]{%
 \DTLconverttodecimal{#2}{\@dtl@numi}%
 \DTLconverttodecimal{#3}{\@dtl@numii}%
 \dtlifnumlt{\@dtl@numi}{\@dtl@numii}%
 {%
   \dtl@ifsingle{#2}%
   {\let#1=#2}%
   {\def#1{#2}}%
 }%
 {%
  \dtl@ifsingle{#3}%
  {\let#1=#3}%
  {\def#1{#3}}%
 }%
}
\newcommand*{\DTLgmin}[3]{%
  \DTLmin{\@dtl@tmpii}{#2}{#3}%
  \global\let#1=\@dtl@tmpii
}
\newcommand*{\DTLminall}[2]{%
  \let\@dtl@min=\@empty
  \@for\dtl@thisval:=#2\do{%
    \expandafter\DTLconverttodecimal\expandafter{\dtl@thisval}{\@dtl@num}%
    \ifdefempty{\@dtl@min}%
    {%
      \let\@dtl@min=\@dtl@num
    }%
    {%
      \dtlmin{\@dtl@min}{\@dtl@min}{\@dtl@num}%
    }%
  }%
  \ifdefempty{\@dtl@replaced}%
  {%
    \DTLdecimaltolocale{\@dtl@min}{#1}%
  }%
  {%
    \DTLdecimaltocurrency{\@dtl@min}{#1}%
  }%
}
\newcommand*{\DTLgminall}[2]{%
  \DTLminall{\@dtl@tmpi}{#2}%
  \global\let#1=\@dtl@tmpi
}
\newcommand*{\DTLmax}[3]{%
  \DTLconverttodecimal{#2}{\@dtl@numi}%
  \DTLconverttodecimal{#3}{\@dtl@numii}%
  \dtlmax{\@dtl@tmp}{\@dtl@numi}{\@dtl@numii}%
  \dtlifnumgt{\@dtl@numi}{\@dtl@numii}%
  {%
   \dtl@ifsingle{#2}%
   {\let#1=#2}%
   {\def#1{#2}}%
  }%
  {%
   \dtl@ifsingle{#3}%
   {\let#1=#3}%
   {\def#1{#3}}%
  }%
}
\newcommand*{\DTLgmax}[3]{%
  \DTLmax{\@dtl@tmpii}{#2}{#3}%
  \global\let#1=\@dtl@tmpii
}
\newcommand*{\DTLmaxall}[2]{%
  \let\@dtl@max=\@empty
  \@for\dtl@thisval:=#2\do{%
    \expandafter\DTLconverttodecimal\expandafter{\dtl@thisval}{\@dtl@num}%
    \ifdefempty{\@dtl@max}%
    {%
      \let\@dtl@max\@dtl@num
    }%
    {%
      \dtlmax{\@dtl@max}{\@dtl@max}{\@dtl@num}%
    }%
  }%
  \ifdefempty{\@dtl@replaced}%
  {%
    \DTLdecimaltolocale{\@dtl@max}{#1}%
  }%
  {%
    \DTLdecimaltocurrency{\@dtl@max}{#1}%
  }%
}
\newcommand*{\DTLgmaxall}[2]{%
\DTLmaxall{\@dtl@tmpi}{#2}%
\global\let#1=\@dtl@tmpi
}
\newcommand*{\DTLmeanforall}[2]{%
  \def\@dtl@mean{0}%
  \def\@dtl@n{0}%
  \@for\dtl@thisval:=#2\do{%
    \expandafter\DTLconverttodecimal\expandafter{\dtl@thisval}{\@dtl@num}%
    \dtladd{\@dtl@mean}{\@dtl@mean}{\@dtl@num}%
    \dtladd{\@dtl@n}{\@dtl@n}{1}%
  }%
  \dtldiv{\@dtl@mean}{\@dtl@mean}{\@dtl@n}%
  \ifdefempty{\@dtl@replaced}%
  {%
    \DTLdecimaltolocale{\@dtl@mean}{#1}%
  }%
  {%
    \DTLdecimaltocurrency{\@dtl@mean}{#1}%
  }%
}
\newcommand*{\DTLgmeanforall}[2]{%
  \DTLmeanforall{\@dtl@tmpi}{#2}%
  \global\let#1=\@dtl@tmpi
}
\newcommand*{\DTLvarianceforall}[2]{%
  \def\@dtl@mean{0}%
  \def\@dtl@n{0}%
  \let\@dtl@decvals=\@empty
  \@for\dtl@thisval:=#2\do{%
    \expandafter\DTLconverttodecimal\expandafter{\dtl@thisval}{\@dtl@num}%
    \ifdefempty{\@dtl@decvals}%
    {%
     \let\@dtl@decvals=\@dtl@num
    }%
    {%
      \expandafter\toks@\expandafter{\@dtl@decvals}%
      \edef\@dtl@decvals{\the\toks@,\@dtl@num}%
    }%
    \dtladd{\@dtl@mean}{\@dtl@mean}{\@dtl@num}%
    \dtladd{\@dtl@n}{\@dtl@n}{1}%
  }%
  \dtldiv{\@dtl@mean}{\@dtl@mean}{\@dtl@n}%
  \def\@dtl@var{0}%
  \@for\@dtl@num:=\@dtl@decvals\do{%
    \dtlsub{\@dtl@diff}{\@dtl@num}{\@dtl@mean}%
    \dtlmul{\@dtl@diff}{\@dtl@diff}{\@dtl@diff}%
    \dtladd{\@dtl@var}{\@dtl@var}{\@dtl@diff}%
  }%
  \dtldiv{\@dtl@var}{\@dtl@var}{\@dtl@n}%
  \ifdefempty{\@dtl@replaced}%
  {%
    \DTLdecimaltolocale{\@dtl@var}{#1}%
  }%
  {%
    \DTLdecimaltocurrency{\@dtl@var}{#1}%
  }%
}
\newcommand*{\DTLgvarianceforall}[2]{%
  \DTLvarianceforall{\@dtl@tmpi}{#2}%
  \global\let#1=\@dtl@tmpi
}
\newcommand*{\DTLsdforall}[2]{%
  \def\@dtl@mean{0}%
  \def\@dtl@n{0}%
  \let\@dtl@decvals=\@empty
  \@for\dtl@thisval:=#2\do{%
    \expandafter\DTLconverttodecimal\expandafter{\dtl@thisval}{\@dtl@num}%
    \ifdefempty{\@dtl@decvals}%
    {%
     \let\@dtl@decvals=\@dtl@num
    }%
    {%
     \expandafter\toks@\expandafter{\@dtl@decvals}%
     \edef\@dtl@decvals{\the\toks@,\@dtl@num}%
    }%
    \dtladd{\@dtl@mean}{\@dtl@mean}{\@dtl@num}%
    \dtladd{\@dtl@n}{\@dtl@n}{1}%
  }%
  \dtldiv{\@dtl@mean}{\@dtl@mean}{\@dtl@n}%
  \def\@dtl@sd{0}%
  \@for\@dtl@num:=\@dtl@decvals\do{%
    \dtlsub{\@dtl@diff}{\@dtl@num}{\@dtl@mean}%
    \dtlmul{\@dtl@diff}{\@dtl@diff}{\@dtl@diff}%
    \dtladd{\@dtl@sd}{\@dtl@sd}{\@dtl@diff}%
  }%
  \dtldiv{\@dtl@sd}{\@dtl@sd}{\@dtl@n}%
  \dtlroot{\@dtl@sd}{\@dtl@sd}{2}%
  \ifdefempty{\@dtl@replaced}%
  {%
    \DTLdecimaltolocale{\@dtl@sd}{#1}%
  }%
  {%
    \DTLdecimaltocurrency{\@dtl@sd}{#1}%
  }%
}
\newcommand*{\DTLgsdforall}[2]{%
  \DTLsdforall{\@dtl@tmpi}{#2}%
  \global\let#1=\@dtl@tmpi
}
\newcommand*{\DTLround}[3]{%
  \DTLconverttodecimal{#2}{\@dtl@numi}%
  \dtlround{\@dtl@tmp}{\@dtl@numi}{#3}%
  \ifdefempty{\@dtl@replaced}%
  {%
    \DTLdecimaltolocale{\@dtl@tmp}{#1}%
  }%
  {%
    \DTLdecimaltocurrency{\@dtl@tmp}{#1}%
  }%
}
\newcommand*{\DTLground}[3]{%
  \DTLround{\@dtl@tmpii}{#2}{#3}%
  \global\let#1=\@dtl@tmpii
}
\newcommand*{\DTLtrunc}[3]{%
  \DTLconverttodecimal{#2}{\@dtl@numi}%
  \dtltrunc{\@dtl@tmp}{\@dtl@numi}{#3}%
  \ifdefempty{\@dtl@replaced}%
  {%
    \DTLdecimaltolocale{\@dtl@tmp}{#1}%
  }%
  {%
    \DTLdecimaltocurrency{\@dtl@tmp}{#1}%
  }%
}
\newcommand*{\DTLgtrunc}[3]{%
  \DTLtrunc{\@dtl@tmpii}{#2}{#3}%
  \global\let#1=\@dtl@tmpii
}
\newcommand*{\DTLclip}[2]{%
  \DTLconverttodecimal{#2}{\@dtl@numi}%
  \dtlclip{\@dtl@tmp}{\@dtl@numi}%
  \ifdefempty{\@dtl@replaced}%
  {%
    \DTLdecimaltolocale{\@dtl@tmp}{#1}%
  }%
  {%
    \DTLdecimaltocurrency{\@dtl@tmp}{#1}%
  }%
}
\newcommand*{\DTLgclip}[3]{%
  \DTLclip{\@dtl@tmpii}{#2}%
  \global\let#1=\@dtl@tmpii
}
\newcommand*\DTLinitials[1]{%
  \def\dtl@initialscmd{}%
  \dtl@subnobrsp{#1}{\dtl@string}%
  \DTLsubstituteall{\dtl@string}{~}{ }%
  \DTLsubstituteall{\dtl@string}{\ }{ }%
  \DTLsubstituteall{\dtl@string}{\space}{ }%
  \expandafter\dtl@initials\dtl@string{} \@nil%
  \dtl@initialscmd
}%
\edef\dtl@construct@subnobrsp{%
  \noexpand\def\noexpand\@dtl@subnobrsp##1\noexpand\protect
  \expandafter\noexpand\csname nobreakspace \endcsname ##2{%
  \noexpand\toks@{##1}%
  \noexpand\expandafter\noexpand\@dtl@toks\noexpand\expandafter{%
  \noexpand\@dtl@string}%
  \noexpand\edef\noexpand\@dtl@string{\noexpand\the\noexpand\@dtl@toks
  \noexpand\the\noexpand\toks@}%
  \noexpand\def\noexpand\@dtl@tmp{##2}%
  \noexpand\ifx\noexpand\@dtl@tmp\noexpand\@nnil
    \noexpand\let\noexpand\@dtl@subnobrspnext=\noexpand\relax
  \noexpand\else
    \noexpand\toks@{ }%
    \noexpand\expandafter\noexpand\@dtl@toks\noexpand\expandafter{%
    \noexpand\@dtl@string}%
    \noexpand\edef\noexpand\@dtl@string{\noexpand\the\noexpand\@dtl@toks
    \noexpand\the\noexpand\toks@}%
    \noexpand\let\noexpand\@dtl@subnobrspnext=\noexpand\@dtl@subnobrsp
  \noexpand\fi
  \noexpand\@dtl@subnobrspnext
  }%
  \noexpand\def\noexpand\dtl@subnobrsp##1##2{%
  \noexpand\def\noexpand\@dtl@string{}%
  \noexpand\@dtl@subnobrsp ##1\noexpand\protect\expandafter\noexpand
  \csname nobreakspace \endcsname \noexpand\@nil
  \noexpand\let##2=\noexpand\@dtl@string
  }%
}
\dtl@construct@subnobrsp
\newcommand*{\DTLstoreinitials}[2]{%
  \def\dtl@initialscmd{}%
  \dtl@subnobrsp{#1}{\dtl@string}%
  \DTLsubstituteall{\dtl@string}{~}{ }%
  \DTLsubstituteall{\dtl@string}{\ }{ }%
  \DTLsubstituteall{\dtl@string}{\space}{ }%
  \expandafter\dtl@initials\dtl@string{} \@nil
  \let#2=\dtl@initialscmd
}
\def\dtl@initials#1#2 #3{%
  \dtl@ifsingle{#1}%
  {%
    \ifcat\noexpand#1\relax\relax
       \def\@dtl@donextinitials{\@dtl@initials#2 {#3}}%
    \else
       \def\@dtl@donextinitials{\@dtl@initials#1#2 {#3}}%
    \fi
  }%
  {%
    \def\@dtl@donextinitials{\@dtl@initials{#1}#2 {#3}}%
  }%
  \@dtl@donextinitials
}
\def\@dtl@initials#1#2 #3{%
  \dtl@initialshyphen#2-{}\dtl@endhyp
  \expandafter\@dtl@toks\expandafter{\dtl@initialscmd}%
  \toks@{#1}%
  \ifdefempty{\dtl@inithyphen}%
  {%
  }%
  {%
    \edef\dtl@initialscmd{\the\@dtl@toks\the\toks@}%
    \expandafter\@dtl@toks\expandafter{\dtl@initialscmd}%
    \expandafter\toks@\expandafter{\dtl@inithyphen}%
  }%
  \def\dtl@tmp{#3}%
  \ifx\@nnil\dtl@tmp
   \edef\dtl@initialscmd{\the\@dtl@toks\the\toks@\DTLafterinitials}%
   \let\dtl@initialsnext=\@gobble
  \else
   \edef\dtl@initialscmd{\the\@dtl@toks\the\toks@\DTLbetweeninitials}%
   \let\dtl@initialsnext=\dtl@initials
  \fi
  \dtl@initialsnext{#3}%
}
\def\dtl@initialshyphen#1-#2#3\dtl@endhyp{%
  \def\dtl@inithyphen{#2}%
  \ifdefempty{\dtl@inithyphen}%
  {%
  }%
  {%
   \edef\dtl@inithyphen{%
     \DTLafterinitialbeforehyphen\DTLinitialhyphen#2}%
  }%
}
\newcommand*{\DTLafterinitials}{.}
\newcommand*{\DTLbetweeninitials}{.}
\newcommand*{\DTLafterinitialbeforehyphen}{.}
\newcommand*{\DTLinitialhyphen}{-}
\newcommand*{\DTLifAllUpperCase}[3]{%
  \protected@edef\dtl@tuc{#1}%
  \expandafter\dtl@testifuppercase\dtl@tuc\@nil\relax
  \if@dtl@condition#2\else#3\fi
}
\def\dtl@testifuppercase#1#2{%
  \def\dtl@argi{#1}%
  \def\dtl@argii{#2}%
  \def\dtl@tc@rest{}%
  \ifx\dtl@argi\@nnil
    \let\dtl@testifuppernext=\@nnil
  \else
    \ifx#1\protect
      \let\dtl@testifuppernext=\dtl@testifuppercase
    \else
      \ifx\uppercase#1\relax
        \@dtl@conditiontrue
        \def\dtl@tc@rest{}%
        \let\dtl@testifuppernext=\relax
      \else
        \edef\dtl@tc@arg{\string#1}%
        \expandafter\dtl@test@ifuppercase\dtl@tc@arg\end
        \ifx\dtl@argii\@nnil
          \let\dtl@testifuppernext=\@dtl@gobbletonil
        \fi
      \fi
    \fi
  \fi
  \ifx\dtl@testifuppernext\relax
   \edef\dtl@dotestifuppernext{%
     \noexpand\dtl@testifuppercase}%
  \else
   \ifx\dtl@testifuppernext\@nnil
     \edef\dtl@dotestifuppernext{#2}%
   \else
     \expandafter\toks@\expandafter{\dtl@tc@rest}%
     \@dtl@toks{#2}%
     \edef\dtl@dotestifuppernext{%
       \noexpand\dtl@testifuppernext\the\toks@\the\@dtl@toks}%
   \fi
  \fi
  \dtl@dotestifuppernext
}
\def\dtl@test@ifuppercase#1#2\end{%
  \def\dtl@tc@rest{#2}%
  \IfSubStringInString{\string\MakeUppercase}{#1#2}%
  {%
     \@dtl@conditiontrue
     \def\dtl@tc@rest{}%
     \let\dtl@testifuppernext=\relax
  }%
  {%
    \IfSubStringInString{\string\MakeTextUppercase}{#1#2}%
    {%
       \@dtl@conditiontrue
       \def\dtl@tc@rest{}%
       \let\dtl@testifuppernext=\relax
    }%
    {%
      \edef\dtl@uccode{\the\uccode`#1}%
      \edef\dtl@code{\number`#1}%
      \ifnum\dtl@code=\dtl@uccode\relax
        \@dtl@conditiontrue
        \let\dtl@testifuppernext=\dtl@testifuppercase
      \else
        \ifnum\dtl@uccode=0\relax
          \@dtl@conditiontrue
          \let\dtl@testifuppernext=\dtl@testifuppercase
        \else
          \@dtl@conditionfalse
          \let\dtl@testifuppernext=\@dtl@gobbletonil
        \fi
      \fi
    }%
  }%
}
\newcommand*{\DTLifAllLowerCase}[3]{%
  \protected@edef\dtl@tlc{#1}%
  \expandafter\dtl@testiflowercase\dtl@tlc\@nil\relax
  \if@dtl@condition#2\else#3\fi
}
\def\dtl@testiflowercase#1#2{%
  \def\dtl@argi{#1}%
  \def\dtl@argii{#2}%
  \ifx\dtl@argi\@nnil
    \let\dtl@testiflowernext=\@nnil
  \else
    \ifx#1\protect
      \let\dtl@testiflowernext=\dtl@testiflowercase
    \else
      \ifx\lowercase#1\relax
        \@dtl@conditiontrue
        \def\dtl@tc@rest{}%
        \let\dtl@testiflowernext=\relax
      \else
        \edef\dtl@tc@arg{\string#1}%
        \expandafter\dtl@test@iflowercase\dtl@tc@arg\end
        \ifx\dtl@argii\@nnil
          \let\dtl@testiflowernext=\@dtl@gobbletonil
        \fi
      \fi
    \fi
  \fi
  \ifx\dtl@testiflowernext\relax
   \edef\dtl@dotestiflowernext{%
     \noexpand\dtl@testiflowercase}%
  \else
   \ifx\dtl@testiflowernext\@nnil
     \edef\dtl@dotestiflowernext{#2}%
   \else
     \expandafter\toks@\expandafter{\dtl@tc@rest}%
     \@dtl@toks{#2}%
     \edef\dtl@dotestiflowernext{%
       \noexpand\dtl@testiflowernext\the\toks@\the\@dtl@toks}%
   \fi
  \fi
  \dtl@dotestiflowernext
}
\def\dtl@test@iflowercase#1#2\end{%
  \def\dtl@tc@rest{#2}%
  \IfSubStringInString{\string\MakeLowercase}{#1#2}%
  {%
    \@dtl@conditiontrue
    \def\dtl@tc@rest{}%
    \let\dtl@testiflowernext=\relax
  }%
  {%
    \IfSubStringInString{\string\MakeTextLowercase}{#1#2}%
    {%
      \@dtl@conditiontrue
      \def\dtl@tc@rest{}%
      \let\dtl@testiflowernext=\relax
    }%
    {%
      \edef\dtl@lccode{\the\lccode`#1}%
      \edef\dtl@code{\number`#1}%
      \ifnum\dtl@code=\dtl@lccode\relax
        \@dtl@conditiontrue
        \let\dtl@testiflowernext=\dtl@testiflowercase
      \else
        \ifnum\dtl@lccode=0\relax
          \@dtl@conditiontrue
          \let\dtl@testiflowernext=\dtl@testiflowercase
        \else
          \@dtl@conditionfalse
          \let\dtl@testiflowernext=\@dtl@gobbletonil
        \fi
      \fi
    }%
  }%
}
\newcommand{\DTLsubstitute}[3]{%
  \expandafter\DTLsplitstring\expandafter
    {#1}{#2}{\@dtl@beforepart}{\@dtl@afterpart}%
  \ifdefempty{\@dtl@replaced}%
  {%
  }%
  {%
    \def#1{}%
    \expandafter\@dtl@toks\expandafter{\@dtl@beforepart}%
    \expandafter\toks@\expandafter{#1}%
    \protected@edef#1{\the\toks@\the\@dtl@toks#3}%
    \expandafter\@dtl@toks\expandafter{\@dtl@afterpart}%
    \expandafter\toks@\expandafter{#1}%
    \edef#1{\the\toks@\the\@dtl@toks}%
  }%
}
\newcommand*{\DTLsplitstring}[4]{%
  \def\dtl@splitstr##1#2##2\@nil{%
    \def#3{##1}%
    \def#4{##2}%
    \ifdefempty{#4}%
    {%
      \let\@dtl@replaced=\@empty
    }%
    {%
      \def\@dtl@replaced{#2}%
      \dtl@split@str##2\@nil
    }%
  }%
  \def\dtl@split@str##1#2\@nil{\def#4{##1}}%
  \dtl@splitstr#1#2\@nil
}
\newcommand{\DTLsubstituteall}[3]{%
  \def\@dtl@splitsubstr{}%
  \let\@dtl@afterpart=#1\relax
  \@dtl@dosubstitute{#2}{#3}%
  \expandafter\toks@\expandafter{\@dtl@splitsubstr}%
  \expandafter\@dtl@toks\expandafter{\@dtl@afterpart}%
  \long\edef#1{\the\toks@\the\@dtl@toks}%
}
\def\@dtl@dosubstitute#1#2{%
  \expandafter\DTLsplitstring\expandafter
   {\@dtl@afterpart}{#1}{\@dtl@beforepart}{\@dtl@afterpart}%
  \expandafter\toks@\expandafter{\@dtl@splitsubstr}%
  \expandafter\@dtl@toks\expandafter{\@dtl@beforepart}%
  \edef\@dtl@splitsubstr{\the\toks@\the\@dtl@toks}%
  \ifdefempty{\@dtl@replaced}%
  {%
    \let\@dtl@dosubstnext=\@dtl@dosubstitutenoop
  }%
  {%
    \expandafter\toks@\expandafter{\@dtl@splitsubstr}%
    \@dtl@toks{#2}%
    \edef\@dtl@splitsubstr{\the\toks@\the\@dtl@toks}%
    \let\@dtl@dosubstnext=\@dtl@dosubstitute
  }%
  \@dtl@dosubstnext{#1}{#2}%
}
\def\@dtl@dosubstitutenoop#1#2{}
\newif\if@dtl@condition
\newcount\@dtl@datatype
\newcommand{\@dtl@checknumerical}[1]{%
  \@dtl@numgrpsepfalse
  \dtl@ifsingle{#1}%
  {%
    \expandafter\toks@\expandafter{#1}%
    \edef\@dtl@tmp{\the\toks@}%
  }%
  {%
    \def\@dtl@tmp{#1}%
  }%
  \ifdefempty\@dtl@tmp
  {%
    \@dtl@datatype=0\relax
  }%
  {%
    \@dtl@tmpcount=0\relax
    \@dtl@datatype=0\relax
    \@dtl@numgrpsepcount=2\relax
    \@dtl@standardize@currency\@dtl@tmp
    \ifdefempty{\@dtl@org@currency}%
    {%
    }%
    {%
      \let\@dtl@currency\@dtl@org@currency
    }%
    \expandafter\@dtl@checknumericalstart\@dtl@tmp\@nil\@nil
  }%
  \ifnum\@dtl@numgrpsepcount>-1\relax
    \if@dtl@numgrpsep
      \ifnum\@dtl@numgrpsepcount=3\relax
      \else
        \@dtl@datatype=0\relax
      \fi
    \fi
  \fi
}
\newcommand*{\@dtl@protect}{\protect}
\newcommand*{\@dtl@minus}{-}
\newcommand*{\@dtl@plus}{+}
\newcommand*{\@dtl@dollar}{\$}
\def\@dtl@checknumericalstart#1#2\@nil\@nil{%
  \def\@dtl@tmp{#1}%
  \ifx\@dtl@tmp\@dtl@protect
    \@dtl@checknumericalstart#2\@nil\@nil\relax
  \else
    \ifx\@dtl@tmp\@dtl@minus
      \def\@dtl@tmp{#2}%
      \ifdefempty{\@dtl@tmp}%
      {%
        \@dtl@datatype=0\relax
      }%
      {%
        \ifnum\@dtl@datatype=0\relax
          \@dtl@datatype=1\relax
        \fi
        \@dtl@checknumericalstart#2\@nil\@nil\relax
      }%
    \else
      \ifx\@dtl@tmp\@dtl@plus
        \def\@dtl@tmp{#2}%
        \ifdefempty{\@dtl@tmp}%
        {%
          \@dtl@datatype=0\relax
        }%
        {%
          \ifnum\@dtl@datatype=0\relax
            \@dtl@datatype=1\relax
          \fi
          \@dtl@checknumericalstart#2\@nil\@nil\relax
        }%
      \else
        \def\@dtl@tmp{#1}%
        \ifx\@dtl@tmp\@dtl@dollar
          \def\@dtl@tmp{#2}%
          \ifdefempty{\@dtl@tmp}%
          {%
            \@dtl@datatype=0\relax
          }%
          {%
            \@dtl@datatype=3\relax
            \@dtl@checknumericalstart#2\@nil\@nil\relax
          }%
        \else
          \ifdefempty{\@dtl@tmp}%
          {%
            \@dtl@datatype=0\relax
          }%
          {%
            \ifnum\@dtl@datatype=0\relax
              \@dtl@datatype=1\relax
            \fi
            \@dtl@checknumericalloop#1#2\@nil\@nil\relax
          }%
        \fi
      \fi
    \fi
  \fi
}
\newif\if@dtl@numgrpsep
\newcommand*{\@dtl@ifDigitOrDecimalSep}[3]{%
  \ifnum 9<1\noexpand#1\relax
    #2%
  \else
    \expandafter\ifx\@dtl@decimal#1\relax
      #2%
    \else
      #3%
    \fi
  \fi
}
\def\@dtl@checknumericalloop#1#2\@nil{%
\def\@dtl@tmp{#1}%
\ifx\@nnil\@dtl@tmp\relax
 \let\@dtl@chcknumnext=\@dtl@checknumericalnoop%
\else
  \@dtl@ifDigitOrDecimalSep{#1}{%
  \let\@dtl@chcknumnext=\@dtl@checknumericalloop%
  \expandafter\ifx\@dtl@decimal#1\relax
     \if@dtl@numgrpsep
       \ifnum\@dtl@numgrpsepcount=3\relax
        \@dtl@numgrpsepcount=-1\relax
       \else
        \@dtl@datatype=0\relax
        \let\@dtl@chcknumnext=\@dtl@checknumericalnoop
       \fi
     \else
       \@dtl@numgrpsepcount=-1\relax
     \fi
   \else
     \ifnum\@dtl@numgrpsepcount=-1\relax
     \else
       \advance\@dtl@numgrpsepcount by 1\relax
     \fi
   \fi
}{%
\ifx\@dtl@numbergroupchar\@dtl@tmp\relax
  \@dtl@numgrpseptrue
  \ifnum\@dtl@numgrpsepcount<3\relax
    \@dtl@datatype=0\relax
    \let\@dtl@chcknumnext=\@dtl@checknumericalnoop
  \else
     \@dtl@numgrpsepcount=0\relax
  \fi
\else
  \@dtl@datatype=0\relax
  \let\@dtl@chcknumnext=\@dtl@checknumericalnoop
\fi
}%
  \ifx\@dtl@decimal\@dtl@tmp\relax
   \ifnum\@dtl@datatype<3\relax
     \@dtl@datatype=2\relax
   \fi
   \advance\@dtl@tmpcount by 1\relax
   \ifnum\@dtl@tmpcount>1\relax
     \@dtl@datatype=0\relax
     \let\@dtl@chcknumnext=\@dtl@checknumericalnoop%
   \fi
  \fi
\fi
\@dtl@chcknumnext#2\@nil
}
\def\@dtl@checknumericalnoop#1\@nil#2{}
\newcommand{\DTLifnumerical}[3]{%
\@dtl@checknumerical{#1}%
\ifnum\@dtl@datatype=0\relax#3\else#2\fi
}
\newcommand{\DTLifreal}[3]{%
  \@dtl@checknumerical{#1}%
  \ifnum\@dtl@datatype=2\relax #2\else #3\fi
}
\newcommand{\DTLifint}[3]{%
  \@dtl@checknumerical{#1}%
  \ifnum\@dtl@datatype=1\relax #2\else #3\fi
}
\newcommand{\DTLifstring}[3]{%
  \@dtl@checknumerical{#1}%
  \ifnum\@dtl@datatype=0\relax #2\else #3\fi
}
\newcommand{\DTLifcurrency}[3]{%
  \@dtl@checknumerical{#1}%
  \ifnum\@dtl@datatype=3\relax #2\else #3\fi
}
\newcommand*{\DTLifcurrencyunit}[4]{%
  \@dtl@checknumerical{#1}%
  \ifnum\@dtl@datatype=3\relax
    \ifthenelse{\equal{\@dtl@org@currency}{#2}}{#3}{#4}%
  \else
    #4%
  \fi
}
\newcommand{\DTLifcasedatatype}[5]{%
  \@dtl@checknumerical{#1}%
  \ifcase\@dtl@datatype
   #2% string
  \or
   #3% integer
  \or
   #4% number
  \or
   #5% currency
  \fi
}
\newcommand*{\dtl@testbothnumerical}[2]{%
  \dtl@ifsingle{#1}{%
   \edef\@dtl@tmp{#1}}{%
   \def\@dtl@tmp{#1}}%
  \expandafter\@dtl@checknumerical\expandafter{\@dtl@tmp}%
  \edef\@dtl@firsttype{\number\@dtl@datatype}%
  \dtl@ifsingle{#2}{%
   \edef\@dtl@tmp{#2}}{%
   \def\@dtl@tmp{#2}}%
  \expandafter\@dtl@checknumerical\expandafter{\@dtl@tmp}%
  \multiply\@dtl@datatype by \@dtl@firsttype\relax
  \ifnum\@dtl@datatype>0\relax
   \@dtl@conditiontrue
  \else
   \@dtl@conditionfalse
  \fi
}
\newcommand*{\DTLifnumlt}[4]{%
  \DTLconverttodecimal{#1}{\@dtl@numi}%
  \DTLconverttodecimal{#2}{\@dtl@numii}%
  \dtlifnumlt{\@dtl@numi}{\@dtl@numii}%
  {%
    #3%
  }%
  {%
    #4%
  }%
}
\newif\ifdtlcompareskipcs
\dtlcompareskipcsfalse
\newcommand*{\dtlcompare}[3]{%
  \dtl@subnobrsp{#2}{\@dtl@argA}%
  \dtl@subnobrsp{#3}{\@dtl@argB}%
  \ifdefempty{\@dtl@argA}%
  {%
    \ifdefempty{\@dtl@argB}%
    {%
      #1=0\relax
    }%
    {%
      #1=-1\relax
    }%
  }%
  {%
    \ifdefempty{\@dtl@argB}%
    {%
      #1=1\relax
    }%
    {%
      \dtl@setwordbreaksnohyphens{\@dtl@argA}{\@dtl@wordbreak}%
      \let\@dtl@argA\dtl@string
      \dtl@setwordbreaksnohyphens{\@dtl@argB}{\@dtl@wordbreak}%
      \let\@dtl@argB\dtl@string
      \expandafter\dtl@getfirst\@dtl@argA\end@dtl@getfirst
      \let\dtl@firstA=\dtl@first
      \let\dtl@restA=\dtl@rest
      \expandafter\dtl@getfirst\@dtl@argB\end@dtl@getfirst
      \let\dtl@firstB=\dtl@first
      \let\dtl@restB=\dtl@rest
      \expandafter\dtl@ifsingleorUTFviii\expandafter{\dtl@firstA}%
      {%
        \expandafter\dtl@ifsingleorUTFviii\expandafter{\dtl@firstB}%
        {%
          \expandafter\dtl@setcharcode\expandafter{\dtl@firstA}{\dtl@codeA}%
          \expandafter\dtl@setcharcode\expandafter{\dtl@firstB}{\dtl@codeB}%
          \let\dtl@donextcompare\@firstofone
          \ifdtlcompareskipcs
            \ifnum\dtl@codeA=0\relax
              \ifnum\dtl@codeB=0\relax
                 \edef\dtl@donext{%
                   \noexpand\dtlcompare
                   {\noexpand#1}{\expandonce\dtl@restA}{\expandonce\dtl@restB}}%
                 \dtl@donext
                 \let\dtl@donextcompare\@gobble
              \else
                 \edef\dtl@donext{%
                   \noexpand\dtlcompare
                   {\noexpand#1}{\expandonce\dtl@restA}{\expandonce\@dtl@argB}}%
                 \dtl@donext
                 \let\dtl@donextcompare\@gobble
              \fi
            \else
              \ifnum\dtl@codeB=0\relax
                 \edef\dtl@donext{%
                   \noexpand\dtlcompare
                   {\noexpand#1}{\expandonce\@dtl@argA}{\expandonce\dtl@restB}}%
                 \dtl@donext
                 \let\dtl@donextcompare\@gobble
              \fi
            \fi
          \fi
          \dtl@donextcompare
          {%
            \ifnum\dtl@codeA=-1\relax
              \ifnum\dtl@codeB=-1\relax
                 \edef\dtl@donext{%
                   \noexpand\dtlcompare
                   {\noexpand#1}{\expandonce\dtl@restA}{\expandonce\dtl@restB}}%
                 \dtl@donext
              \else
                 \edef\dtl@donext{%
                   \noexpand\dtlcompare
                     {\noexpand#1}%
                     {\expandonce\dtl@restA}%
                     {\expandonce\dtl@firstB\expandonce\dtl@restB}}%
                 \dtl@donext
              \fi
            \else
              \ifnum\dtl@codeB=-1\relax
                 \edef\dtl@donext{%
                   \noexpand\dtlcompare
                     {\noexpand#1}%
                     {\expandonce\dtl@firstA\expandonce\dtl@restA}%
                     {\expandonce\dtl@restB}}%
                 \dtl@donext
              \else
                \ifnum\dtl@codeA<\dtl@codeB
                  #1=-1\relax
                \else
                  \ifnum\dtl@codeA>\dtl@codeB
                    #1=1\relax
                  \else
                     \ifdefempty{\dtl@restA}%
                     {%
                       \ifdefempty{\dtl@restB}%
                       {%
                         #1=0\relax
                       }%
                       {%
                         #1=-1\relax
                       }%
                     }%
                     {%
                       \ifdefempty{\dtl@restB}%
                       {%
                         #1=1\relax
                       }%
                       {%
                         \protected@edef\dtl@donext{%
                           \noexpand\dtlcompare
                             {\noexpand#1}{\dtl@restA}{\dtl@restB}}%
                         \dtl@donext
                       }%
                     }%
                  \fi
                \fi
              \fi
            \fi
          }%
        }%
        {%
          \edef\dtl@donext{%
            \noexpand\dtlcompare
              {\noexpand#1}%
              {\expandonce\dtl@firstA\expandonce\dtl@restA}%
              {\expandonce\dtl@firstB\expandonce\dtl@restB}}%
          \dtl@donext
        }%
      }%
      {%
        \edef\dtl@donext{%
          \noexpand\dtlcompare
            {\noexpand#1}%
            {\expandonce\dtl@firstA\expandonce\dtl@restA}%
            {\expandonce\dtl@firstB\expandonce\dtl@restB}}%
        \dtl@donext
      }%
    }%
  }%
}
\def\dtl@if@two@octets#1#2\dtl@end@if@two@octets#3#4{%
  \ifbool{@dtl@utf8}
  {%
    \ifx\UTFviii@two@octets#1\relax
     #3%
    \else
     #4%
    \fi
  }%
  {%
    #4%
  }%
}
\def\dtl@getfirst@UTFviii#1#2#3\end@dtl@getfirst@UTFviii{%
  \def\dtl@first{#1#2}%
  \ifx\@nil#3\relax
    \def\dtl@rest{}%
  \else
    \expandafter\def\expandafter\dtl@rest\expandafter{\@dtl@firsttonil#3}%
  \fi
}
\def\@dtl@firsttonil#1\@nil{#1}
\def\dtl@getfirst#1#2\end@dtl@getfirst{%
  \def\dtl@first{#1}%
  \ifdefempty{\dtl@first}%
  {%
    \def\dtl@rest{#2}%
  }%
  {%
    \ifbool{@dtl@utf8}
    {%
      \expandafter\dtl@if@two@octets#1#2\relax\dtl@end@if@two@octets
      {%
        \dtl@getfirst@UTFviii#1#2\@nil\end@dtl@getfirst@UTFviii
      }%
      {%
        \dtl@ifsingle{#1}{\def\dtl@rest{#2}}{\dtl@getfirst#1#2\end@dtl@getfirst}%
      }%
    }%
    {%
      \dtl@ifsingle{#1}{\def\dtl@rest{#2}}{\dtl@getfirst#1#2\end@dtl@getfirst}%
    }%
  }%
}%
\newcount\dtl@codeA
\newcount\dtl@codeB
\newcommand*{\dtl@setcharcode}[2]{%
  \ifstrempty{#1}%
  {%
    #2=-1\relax
  }%
  {%
    \ifx\@dtl@wordbreak#1\relax
     #2=`\ \relax
   \else
     \ifcat\noexpand#1\relax
       #2=0\relax
     \else
       \expandafter\dtl@if@two@octets#1\relax\relax\dtl@end@if@two@octets
       {%
        \dtlsetUTFviiicharcode{#1}{#2}%
       }%
       {%
         \dtlsetcharcode{#1}{#2}%
       }%
     \fi
   \fi
  }%
}
\newcommand*{\dtlsetcharcode}[2]{#2=`#1\relax}
\newcommand*{\dtlsetlccharcode}[2]{#2=\lccode`#1\relax}
\newcommand*\dtlsetUTFviiicharcode[2]{\dtlsetdefaultUTFviiicharcode{#1}{#2}}
\newcommand*\dtlsetUTFviiilccharcode[2]{\dtlsetdefaultUTFviiilccharcode{#1}{#2}}
\newcommand*\dtlsetdefaultUTFviiicharcode[2]{%
 \ifboolexpr
 {
      test {\ifstrequal{#1}{À}}
   or test {\ifstrequal{#1}{Á}}
   or test {\ifstrequal{#1}{Á}}
   or test {\ifstrequal{#1}{Ã}}
   or test {\ifstrequal{#1}{Ä}}
 }%
 {%
   #2=`A\relax
 }%
 {%
   \ifstrequal{#1}{Ç}%
   {%
     #2=`C\relax
   }%
   {%
     \ifboolexpr
     {
          test {\ifstrequal{#1}{È}}
       or test {\ifstrequal{#1}{É}}
       or test {\ifstrequal{#1}{Ê}}
       or test {\ifstrequal{#1}{Ë}}
     }%
     {%
       #2=`E\relax
     }%
     {%
       \ifboolexpr
       {
            test {\ifstrequal{#1}{Ì}}
         or test {\ifstrequal{#1}{Í}}
         or test {\ifstrequal{#1}{Î}}
         or test {\ifstrequal{#1}{Ï}}
       }%
       {%
         #2=`I\relax
       }%
       {%
         \ifstrequal{#1}{Ñ}%
         {%
           #2=`N\relax
         }%
         {%
           \ifboolexpr
           {
                test {\ifstrequal{#1}{Ò}}
             or test {\ifstrequal{#1}{Ó}}
             or test {\ifstrequal{#1}{Ô}}
             or test {\ifstrequal{#1}{Õ}}
             or test {\ifstrequal{#1}{Ö}}
           }%
           {%
             #2=`O\relax
           }%
           {%
             \ifboolexpr
             {
                  test {\ifstrequal{#1}{Ù}}
               or test {\ifstrequal{#1}{Ú}}
               or test {\ifstrequal{#1}{Û}}
               or test {\ifstrequal{#1}{Ü}}
             }%
             {%
               #2=`U\relax
             }%
             {%
               \ifstrequal{#1}{Ý}%
               {%
                 #2=`Y\relax
               }%
               {%
                 \ifboolexpr
                 {
                      test {\ifstrequal{#1}{à}}
                   or test {\ifstrequal{#1}{á}}
                   or test {\ifstrequal{#1}{á}}
                   or test {\ifstrequal{#1}{ã}}
                   or test {\ifstrequal{#1}{ä}}
                 }%
                 {%
                   #2=`a\relax
                 }%
                 {%
                   \ifstrequal{#1}{ç}%
                   {%
                     #2=`c\relax
                   }%
                   {%
                     \ifboolexpr
                     {
                          test {\ifstrequal{#1}{è}}
                       or test {\ifstrequal{#1}{é}}
                       or test {\ifstrequal{#1}{ê}}
                       or test {\ifstrequal{#1}{ë}}
                     }%
                     {%
                       #2=`e\relax
                     }%
                     {%
                       \ifboolexpr
                       {
                            test {\ifstrequal{#1}{ì}}
                         or test {\ifstrequal{#1}{í}}
                         or test {\ifstrequal{#1}{î}}
                         or test {\ifstrequal{#1}{ï}}
                       }%
                       {%
                         #2=`i\relax
                       }%
                       {%
                         \ifstrequal{#1}{ñ}%
                         {%
                           #2=`n\relax
                         }%
                         {%
                           \ifboolexpr
                           {
                                test {\ifstrequal{#1}{ò}}
                             or test {\ifstrequal{#1}{ó}}
                             or test {\ifstrequal{#1}{ô}}
                             or test {\ifstrequal{#1}{õ}}
                             or test {\ifstrequal{#1}{ö}}
                           }%
                           {%
                             #2=`o\relax
                           }%
                           {%
                             \ifboolexpr
                             {
                                  test {\ifstrequal{#1}{ù}}
                               or test {\ifstrequal{#1}{ú}}
                               or test {\ifstrequal{#1}{û}}
                               or test {\ifstrequal{#1}{ü}}
                             }%
                             {%
                               #2=`u\relax
                             }%
                             {%
                               \ifstrequal{#1}{ý}%
                               {%
                                 #2=`y\relax
                               }%
                               {%
                                 #2=64\relax
                               }%
                             }%
                           }%
                         }%
                       }%
                     }%
                   }%
                 }%
               }%
             }%
           }%
         }%
       }%
     }%
   }%
 }%
}
\newcommand*\dtlsetdefaultUTFviiilccharcode[2]{%
 \ifboolexpr
 {
      test {\ifstrequal{#1}{à}}
   or test {\ifstrequal{#1}{á}}
   or test {\ifstrequal{#1}{á}}
   or test {\ifstrequal{#1}{ã}}
   or test {\ifstrequal{#1}{ä}}
   or test {\ifstrequal{#1}{À}}
   or test {\ifstrequal{#1}{Á}}
   or test {\ifstrequal{#1}{Á}}
   or test {\ifstrequal{#1}{Ã}}
   or test {\ifstrequal{#1}{Ä}}
 }%
 {%
   #2=`a\relax
 }%
 {%
   \ifboolexpr
   {
        test {\ifstrequal{#1}{ç}}
     or test {\ifstrequal{#1}{Ç}}
   }
   {%
     #2=`c\relax
   }%
   {%
     \ifboolexpr
     {
          test {\ifstrequal{#1}{è}}
       or test {\ifstrequal{#1}{é}}
       or test {\ifstrequal{#1}{ê}}
       or test {\ifstrequal{#1}{ë}}
       or test {\ifstrequal{#1}{È}}
       or test {\ifstrequal{#1}{É}}
       or test {\ifstrequal{#1}{Ê}}
       or test {\ifstrequal{#1}{Ë}}
     }%
     {%
       #2=`e\relax
     }%
     {%
       \ifboolexpr
       {
            test {\ifstrequal{#1}{ì}}
         or test {\ifstrequal{#1}{í}}
         or test {\ifstrequal{#1}{î}}
         or test {\ifstrequal{#1}{ï}}
         or test {\ifstrequal{#1}{Ì}}
         or test {\ifstrequal{#1}{Í}}
         or test {\ifstrequal{#1}{Î}}
         or test {\ifstrequal{#1}{Ï}}
       }%
       {%
         #2=`i\relax
       }%
       {%
         \ifboolexpr
         {
              test {\ifstrequal{#1}{ñ}}
           or test {\ifstrequal{#1}{Ñ}}
         }
         {%
           #2=`n\relax
         }%
         {%
           \ifboolexpr
           {
                test {\ifstrequal{#1}{ò}}
             or test {\ifstrequal{#1}{ó}}
             or test {\ifstrequal{#1}{ô}}
             or test {\ifstrequal{#1}{õ}}
             or test {\ifstrequal{#1}{ö}}
             or test {\ifstrequal{#1}{Ò}}
             or test {\ifstrequal{#1}{Ó}}
             or test {\ifstrequal{#1}{Ô}}
             or test {\ifstrequal{#1}{Õ}}
             or test {\ifstrequal{#1}{Ö}}
           }%
           {%
             #2=`o\relax
           }%
           {%
             \ifboolexpr
             {
                  test {\ifstrequal{#1}{ù}}
               or test {\ifstrequal{#1}{ú}}
               or test {\ifstrequal{#1}{û}}
               or test {\ifstrequal{#1}{ü}}
               or test {\ifstrequal{#1}{Ù}}
               or test {\ifstrequal{#1}{Ú}}
               or test {\ifstrequal{#1}{Û}}
               or test {\ifstrequal{#1}{Ü}}
             }%
             {%
               #2=`u\relax
             }%
             {%
               \ifboolexpr
               {
                    test {\ifstrequal{#1}{ý}}
                 or test {\ifstrequal{#1}{Ý}}
               }%
               {%
                 #2=`y\relax
               }%
               {%
                 #2=96\relax
               }%
             }%
           }%
         }%
       }%
     }%
   }%
 }%
}
\newcommand*{\dtl@setlccharcode}[2]{%
  \ifstrempty{#1}%
  {%
   #2=-1\relax
  }%
  {%
   \ifx#1\@dtl@wordbreak\relax
     #2=`\ \relax
   \else
     \ifcat\noexpand#1\relax%
       #2=0\relax
     \else
       \expandafter\dtl@if@two@octets#1\relax\relax\dtl@end@if@two@octets
       {%
        \dtlsetUTFviiilccharcode{#1}{#2}%
       }%
       {%
         \dtlsetlccharcode{#1}{#2}%
       }%
       \ifnum#2=0\relax
         #2=`#1\relax
       \fi
     \fi
   \fi
  }%
}
\newcommand*{\dtlicompare}[3]{%
  \dtl@subnobrsp{#2}{\@dtl@argA}%
  \dtl@subnobrsp{#3}{\@dtl@argB}%
  \ifdefempty{\@dtl@argA}%
  {%
    \ifdefempty{\@dtl@argB}%
    {%
      #1=0\relax
    }%
    {%
      #1=-1\relax
    }%
  }%
  {%
    \ifdefempty{\@dtl@argB}%
    {%
      #1=1\relax
    }%
    {%
      \dtl@setwordbreaksnohyphens{\@dtl@argA}{\@dtl@wordbreak}%
      \let\@dtl@argA\dtl@string
      \dtl@setwordbreaksnohyphens{\@dtl@argB}{\@dtl@wordbreak}%
      \let\@dtl@argB\dtl@string
      \expandafter\dtl@getfirst\@dtl@argA\end@dtl@getfirst
      \let\dtl@firstA=\dtl@first
      \let\dtl@restA=\dtl@rest
      \expandafter\dtl@getfirst\@dtl@argB\end@dtl@getfirst
      \let\dtl@firstB=\dtl@first
      \let\dtl@restB=\dtl@rest
      \expandafter\dtl@ifsingleorUTFviii\expandafter{\dtl@firstA}%
      {%
        \expandafter\dtl@ifsingleorUTFviii\expandafter{\dtl@firstB}%
        {%
          \expandafter\dtl@setlccharcode\expandafter{\dtl@firstA}{\dtl@codeA}%
          \expandafter\dtl@setlccharcode\expandafter{\dtl@firstB}{\dtl@codeB}%
          \let\dtl@donextcompare\@firstofone
          \ifdtlcompareskipcs
            \ifnum\dtl@codeA=0\relax
              \ifnum\dtl@codeB=0\relax
                 \edef\dtl@donext{%
                   \noexpand\dtlicompare
                   {\noexpand#1}{\expandonce\dtl@restA}{\expandonce\dtl@restB}}%
                 \dtl@donext
                 \let\dtl@donextcompare\@gobble
              \else
                 \edef\dtl@donext{%
                   \noexpand\dtlicompare
                   {\noexpand#1}{\expandonce\dtl@restA}{\expandonce\@dtl@argB}}%
                 \dtl@donext
                 \let\dtl@donextcompare\@gobble
              \fi
            \else
              \ifnum\dtl@codeB=0\relax
                 \edef\dtl@donext{%
                   \noexpand\dtlicompare
                   {\noexpand#1}{\expandonce\@dtl@argA}{\expandonce\dtl@restB}}%
                 \dtl@donext
                 \let\dtl@donextcompare\@gobble
              \fi
            \fi
          \fi
          \dtl@donextcompare
          {%
            \ifnum\dtl@codeA=-1\relax
              \ifnum\dtl@codeB=-1\relax
                 \edef\dtl@donext{%
                   \noexpand\dtlicompare{\noexpand#1}%
                   {\expandonce\dtl@restA}{\expandonce\dtl@restB}}%
                 \dtl@donext
              \else
                 \edef\dtl@donext{%
                   \noexpand\dtlicompare
                     {\noexpand#1}%
                     {\expandonce\dtl@restA}%
                     {\expandonce\dtl@firstB\expandonce\dtl@restB}}%
                 \dtl@donext
              \fi
            \else
              \ifnum\dtl@codeB=-1\relax
                 \edef\dtl@donext{%
                   \noexpand\dtlicompare
                    {\noexpand#1}%
                    {\expandonce\dtl@firstA\expandonce\dtl@restA}%
                    {\expandonce\dtl@restB}}%
                 \dtl@donext
              \else
                \ifnum\dtl@codeA<\dtl@codeB
                  #1=-1\relax
                \else
                  \ifnum\dtl@codeA>\dtl@codeB
                    #1=1\relax
                  \else
                     \ifdefempty{\dtl@restA}%
                     {%
                       \ifdefempty{\dtl@restB}%
                       {%
                         #1=0\relax
                       }%
                       {%
                         #1=-1\relax
                       }%
                     }%
                     {%
                       \ifdefempty{\dtl@restB}%
                       {%
                         #1=1\relax
                       }%
                       {%
                         \edef\dtl@donext{%
                           \noexpand\dtlicompare
                             {\noexpand#1}%
                             {\expandonce\dtl@restA}%
                             {\expandonce\dtl@restB}}%
                         \dtl@donext
                       }%
                     }%
                  \fi
                \fi
              \fi
            \fi
          }%
        }%
        {%
          \edef\dtl@donext{%
            \noexpand\dtlicompare
              {\noexpand#1}%
              {\expandonce\dtl@firstA\expandonce\dtl@restA}%
              {\expandonce\dtl@firstB\expandonce\dtl@restB}}%
          \dtl@donext
        }%
      }%
      {%
        \edef\dtl@donext{%
          \noexpand\dtlicompare
            {\noexpand#1}%
            {\expandonce\dtl@firstA\expandonce\dtl@restA}%
            {\expandonce\dtl@firstB\expandonce\dtl@restB}}%
        \dtl@donext
      }%
    }%
  }%
}
\newcommand*{\dtlwordindexcompare}[3]{%
  \@dtldictcompare{#1}{#2}{#3}{\@dtl@wordbreak}%
}
\newcommand*{\dtlletterindexcompare}[3]{%
  \@dtldictcompare{#1}{#2}{#3}{}%
}
\newcommand*{\@dtldictcompare}[4]{%
  \dtl@subnobrsp{#2}{\@dtl@argA}%
  \dtl@subnobrsp{#3}{\@dtl@argB}%
  \ifdefempty{\@dtl@argA}%
  {%
    \ifdefempty{\@dtl@argB}%
    {%
      #1=0\relax
    }%
    {%
      #1=-1\relax
    }%
  }%
  {%
    \ifdefempty{\@dtl@argB}%
    {%
      #1=1\relax
    }%
    {%
      \expandafter\DTLsplitstring\expandafter
        {\@dtl@argA}{\datatoolpersoncomma}{\@dtl@beforepart}{\@dtl@afterpart}%
      \ifdefempty{\@dtl@replaced}%
      {%
        \expandafter\DTLsplitstring\expandafter
          {\@dtl@argA}{\datatoolplacecomma}{\@dtl@beforepart}{\@dtl@afterpart}%
        \ifdefempty{\@dtl@replaced}%
        {%
          \expandafter\DTLsplitstring\expandafter
            {\@dtl@argA}{\datatoolsubjectcomma}{\@dtl@beforepart}{\@dtl@afterpart}%
          \ifdefempty{\@dtl@replaced}%
          {%
            \expandafter\DTLsplitstring\expandafter
              {\@dtl@argA}{\datatoolparenstart}{\@dtl@beforepart}{\@dtl@afterpart}%
            \ifdefempty{\@dtl@replaced}%
            {%
              \def\@dtl@A@comma{0}%
              \let\@dtl@A@before\@dtl@argA
              \def\@dtl@A@after{}%
            }%
            {%
              \let\@dtl@A@comma\@dtl@replaced
              \let\@dtl@A@before\@dtl@beforepart
              \let\@dtl@A@after\@dtl@afterpart
            }%
          }%
          {%
            \let\@dtl@A@comma\@dtl@replaced
            \let\@dtl@A@before\@dtl@beforepart
            \let\@dtl@A@after\@dtl@afterpart
          }%
        }%
        {%
          \let\@dtl@A@comma\@dtl@replaced
          \let\@dtl@A@before\@dtl@beforepart
          \let\@dtl@A@after\@dtl@afterpart
        }%
      }%
      {%
        \let\@dtl@A@comma\@dtl@replaced
        \let\@dtl@A@before\@dtl@beforepart
        \let\@dtl@A@after\@dtl@afterpart
      }%
      \expandafter\DTLsplitstring\expandafter
        {\@dtl@argB}{\datatoolpersoncomma}{\@dtl@beforepart}{\@dtl@afterpart}%
      \ifdefempty{\@dtl@replaced}%
      {%
        \expandafter\DTLsplitstring\expandafter
          {\@dtl@argB}{\datatoolplacecomma}{\@dtl@beforepart}{\@dtl@afterpart}%
        \ifdefempty{\@dtl@replaced}%
        {%
          \expandafter\DTLsplitstring\expandafter
            {\@dtl@argB}{\datatoolsubjectcomma}{\@dtl@beforepart}{\@dtl@afterpart}%
          \ifdefempty{\@dtl@replaced}%
          {%
            \expandafter\DTLsplitstring\expandafter
              {\@dtl@argB}{\datatoolparenstart}{\@dtl@beforepart}{\@dtl@afterpart}%
            \ifdefempty{\@dtl@replaced}%
            {%
              \def\@dtl@B@comma{0}%
              \let\@dtl@B@before\@dtl@argB
              \def\@dtl@B@after{}%
            }%
            {%
              \let\@dtl@B@comma\@dtl@replaced
              \let\@dtl@B@before\@dtl@beforepart
              \let\@dtl@B@after\@dtl@afterpart
            }%
          }%
          {%
            \let\@dtl@B@comma\@dtl@replaced
            \let\@dtl@B@before\@dtl@beforepart
            \let\@dtl@B@after\@dtl@afterpart
          }%
        }%
        {%
          \let\@dtl@B@comma\@dtl@replaced
          \let\@dtl@B@before\@dtl@beforepart
          \let\@dtl@B@after\@dtl@afterpart
        }%
      }%
      {%
        \let\@dtl@B@comma\@dtl@replaced
        \let\@dtl@B@before\@dtl@beforepart
        \let\@dtl@B@after\@dtl@afterpart
      }%
      \expandafter\dtl@ifcasechargroup\@dtl@A@before\dtl@end@ifcasechargroup
       {\def\@dtl@A@chargroup{2}}%
       {\def\@dtl@A@chargroup{1}}%
       {\def\@dtl@A@chargroup{0}}%
      \expandafter\dtl@ifcasechargroup\@dtl@B@before\dtl@end@ifcasechargroup
       {\def\@dtl@B@chargroup{2}}%
       {\def\@dtl@B@chargroup{1}}%
       {\def\@dtl@B@chargroup{0}}%
      \ifnum\@dtl@A@chargroup<\@dtl@B@chargroup
        #1=-1\relax
      \else
        \ifnum\@dtl@A@chargroup>\@dtl@B@chargroup
          #1=1\relax
        \else
          \ifcase\@dtl@A@chargroup
            \edef\dtl@donext{%
              \noexpand\dtlcompare
                {\noexpand#1}%
                {\expandonce\@dtl@A@before}%
                {\expandonce\@dtl@B@before}}%
            \dtl@donext
          \or
            \ifnum\@dtl@A@before<\@dtl@B@before\relax
              #1=-1\relax
            \else
              \ifnum\@dtl@A@before>\@dtl@B@before\relax
                #1=1\relax
              \else
                #1=0\relax
              \fi
            \fi
          \or
            \@dtlwordindexcompare{#1}{\@dtl@A@before}{\@dtl@B@before}
              {\dtlicomparewords}{#4}%
            \ifnum#1=0\relax
              \let\@org@dtl@person@comma\datatoolpersoncomma
              \let\@org@dtl@place@comma\datatoolplacecomma
              \let\@org@dtl@subject@comma\datatoolsubjectcomma
              \let\@org@dtl@paren@start\datatoolparenstart
              \def\datatoolpersoncomma{3}%
              \def\datatoolplacecomma{2}%
              \def\datatoolsubjectcomma{1}%
              \def\datatoolparenstart{-1}%
              \ifnum\@dtl@A@comma>\@dtl@B@comma\relax
                #1=-1\relax
              \else
                \ifnum\@dtl@A@comma<\@dtl@B@comma\relax
                  #1=1\relax
                \else
                 \@dtlwordindexcompare{#1}{\@dtl@B@before}{\@dtl@A@before}
                   {\dtlcomparewords}{#4}%
                  \ifnum#1=0\relax
                    \@dtlwordindexcompare{#1}{\@dtl@A@after}{\@dtl@B@after}
                      {\dtlicomparewords}{#4}%
                  \fi
                \fi
              \fi
              \let\datatoolpersoncomma\@org@dtl@person@comma
              \let\datatoolplacecomma\@org@dtl@place@comma
              \let\datatoolsubjectcomma\@org@dtl@subject@comma
              \let\datatoolparenstart\@org@dtl@paren@start
            \fi
          \fi
        \fi
      \fi
    }%
  }%
}%
\newcommand*{\datatoolpersoncomma}{,\space}
\newcommand*{\datatoolplacecomma}{,\space}
\newcommand*{\datatoolsubjectcomma}{,\space}
\newcommand*{\datatoolparenstart}{\space}
\newcommand*{\@dtlwordindexcompare}[5]{%
  \dtl@setwordbreaks{#2}{#5}%
  \let#2\dtl@string
  \dtl@setwordbreaks{#3}{}%
  \let#3\dtl@string
  \edef\@dtl@do@compare{%
    \noexpand#4{\noexpand#1}%
      {\expandonce#2}{\expandonce#3}%
  }%
  \@dtl@do@compare
}
\newcommand*{\@dtl@dict@compare}[4]{%
  \ifdefempty{#2}%
  {%
    \ifdefempty{#3}%
    {%
      #1=0\relax
    }%
    {%
      #1=-1\relax
    }%
  }%
  {%
    \ifdefempty{#3}%
    {%
      #1=1\relax
    }%
    {%
      \expandafter\dtl@grabword#2\@dtl@endgrabword\dtl@A@first\dtl@A@remain
      \expandafter\dtl@grabword#3\@dtl@endgrabword\dtl@B@first\dtl@B@remain
      \edef\@dtl@do@compare{%
        \noexpand#4{\noexpand#1}%
          {\expandonce\dtl@A@first}{\expandonce\dtl@B@first}%
      }%
      \@dtl@do@compare
      \ifnum#1=0\relax
        \@dtl@dict@compare{#1}{\dtl@A@remain}{\dtl@B@remain}{#4}%
      \fi
    }%
  }%
}
\def\dtl@grabword#1\@dtl@wordbreak#2\@dtl@endgrabword#3#4{%
  \def#3{#1}%
  \def#4{#2}%
}
\newcommand{\dtlicomparewords}[3]{%
  \dtlicompare{#1}{#2}{#3}%
}
\newcommand{\dtlcomparewords}[3]{%
  \dtlcompare{#1}{#2}{#3}%
}
\newcommand*{\dtl@setwordbreaks}[2]{%
  \expandafter\dtl@subnobrsp\expandafter{#1}{\dtl@string}%
  \DTLsubstituteall{\dtl@string}{~}{#2}%
  \DTLsubstituteall{\dtl@string}{\ }{#2}%
  \DTLsubstituteall{\dtl@string}{\space}{#2}%
  \DTLsubstituteall{\dtl@string}{-}{#2}%
 \toks@{#2}%
 \edef\dtl@do@setwordbreaks{%
   \noexpand\@dtl@setwordbreaks{\the\toks@}\expandonce\dtl@string\space\noexpand\@nil}%
 \def\dtl@string{}%
 \dtl@do@setwordbreaks
}
\def\@dtl@setwordbreaks#1#2 #3{%
  \def\dtl@tmp{#3}%
  \ifx\@nnil\dtl@tmp
    \let\@dtl@setwordbreaks@next\@gobbletwo
    \appto\dtl@string{#2}%
  \else
    \let\@dtl@setwordbreaks@next\@dtl@setwordbreaks
    \appto\dtl@string{#2#1}%
  \fi
  \@dtl@setwordbreaks@next{#1}#3%
}
\newcommand*{\dtl@setwordbreaksnohyphens}[2]{%
  \expandafter\dtl@subnobrsp\expandafter{#1}{\dtl@string}%
  \DTLsubstituteall{\dtl@string}{~}{#2}%
  \DTLsubstituteall{\dtl@string}{\ }{#2}%
  \DTLsubstituteall{\dtl@string}{\space}{#2}%
 \toks@{#2}%
 \edef\dtl@do@setwordbreaks{%
   \noexpand\@dtl@setwordbreaks{\the\toks@}\expandonce\dtl@string\space\noexpand\@nil}%
 \def\dtl@string{}%
 \dtl@do@setwordbreaks
}
\newcommand*{\@dtl@wordbreak}{ }
\def\dtl@ifcasechargroup#1#2\dtl@end@ifcasechargroup#3#4#5{%
 \expandafter\dtl@if@two@octets#1#2\relax\relax\dtl@end@if@two@octets
 {%
   \dtl@getfirst@UTFviii#1#2\@nil\end@dtl@getfirst@UTFviii
   \expandafter\dtlsetUTFviiilccharcode\expandafter{\dtl@first}{\count@}%
   \ifnum\count@<`a\relax #5\else#3\fi
 }%
 {%
  \dtlifcasechargroup{#1}%
   {#3}%
   {%
     \DTLifint{#1#2}
     {%
       #4%
     }%
     {%
       #3%
     }%
   }%
   {#5}%
 }%
}
\newcommand*{\dtlifcasechargroup}[4]{%
  \count@=`#1\relax
  \dtlifintclosedbetween{\number\count@}{48}{57}%
  {%
    #3%
  }%
  {%
    \dtlifintclosedbetween{\number\count@}{97}{122}%
    {%
      #2%
    }%
    {%
      \dtlifintclosedbetween{\number\count@}{65}{90}%
      {%
        #2%
      }%
      {%
        #4%
      }%
    }%
  }%
}
\newcommand*{\dtlparsewords}[2]{%
  \dtl@subnobrsp{#1}{\dtl@string}%
  \DTLsubstituteall{\dtl@string}{~}{ }%
  \DTLsubstituteall{\dtl@string}{\ }{ }%
  \DTLsubstituteall{\dtl@string}{\space}{ }%
  \DTLsubstituteall{\dtl@string}{-}{ }%
  \let\dtl@parsewordshandler#2\relax
  \edef\dtl@donext{%
     \noexpand\@dtl@parse@words\expandonce\dtl@string\space\noexpand\@nil}%
  \dtl@donext
}
\def\@dtl@parse@words#1 #2{%
  \def\dtl@tmp{#2}%
  \ifx\@nnil\dtl@tmp
   \let\parse@wordsnext=\@gobble
  \else
   \let\parse@wordsnext=\@dtl@parse@words
  \fi
  \dlt@parsewordshandler{#1}%
  \parse@wordsnext#2%
}
\newcommand*{\DTLifstringlt}{\@ifstar\@sDTLifstringlt\@DTLifstringlt}
\newcommand*{\@DTLifstringlt}[4]{%
  \protected@edef\@dtl@tmpcmp{%
    \noexpand\dtlcompare{\noexpand\@dtl@tmpcount}{#1}{#2}}%
  \@dtl@tmpcmp
  \ifnum\@dtl@tmpcount<0\relax
    #3%
  \else
    #4%
  \fi
}
\newcommand*{\@sDTLifstringlt}[4]{%
  \protected@edef\@dtl@tmpcmp{%
    \noexpand\dtlicompare{\noexpand\@dtl@tmpcount}{#1}{#2}}%
  \@dtl@tmpcmp
  \ifnum\@dtl@tmpcount<0\relax
    #3%
  \else
    #4%
  \fi
}
\newcommand*{\DTLiflt}{\@ifstar\@sDTLiflt\@DTLiflt}
\newcommand*{\@DTLiflt}[4]{%
  \dtl@testbothnumerical{#1}{#2}%
  \if@dtl@condition
   \DTLifnumlt{#1}{#2}{#3}{#4}%
  \else
   \@DTLifstringlt{#1}{#2}{#3}{#4}%
  \fi
}
\newcommand*{\@sDTLiflt}[4]{%
  \dtl@testbothnumerical{#1}{#2}%
  \if@dtl@condition
   \DTLifnumlt{#1}{#2}{#3}{#4}%
  \else
   \@sDTLifstringlt{#1}{#2}{#3}{#4}%
  \fi
}
\newcommand*{\DTLifnumgt}[4]{%
  \DTLconverttodecimal{#1}{\@dtl@numi}%
  \DTLconverttodecimal{#2}{\@dtl@numii}%
  \dtlifnumgt{\@dtl@numi}{\@dtl@numii}%
  {%
    #3%
  }%
  {%
    #4%
  }%
}
\newcommand*{\DTLifstringgt}{\@ifstar\@sDTLifstringgt\@DTLifstringgt}
\newcommand*{\@DTLifstringgt}[4]{%
  \protected@edef\@dtl@tmpcmp{%
    \noexpand\dtlcompare{\noexpand\@dtl@tmpcount}{#1}{#2}}%
  \@dtl@tmpcmp
  \ifnum\@dtl@tmpcount>0\relax
    #3%
  \else
    #4%
  \fi
}
\newcommand*{\@sDTLifstringgt}[4]{%
  \protected@edef\@dtl@tmpcmp{%
    \noexpand\dtlicompare{\noexpand\@dtl@tmpcount}{#1}{#2}}%
  \@dtl@tmpcmp
  \ifnum\@dtl@tmpcount>0\relax
    #3%
  \else
    #4%
  \fi
}
\newcommand*{\DTLifgt}{\@ifstar\@sDTLifgt\@DTLifgt}
\newcommand*{\@DTLifgt}[4]{%
  \dtl@testbothnumerical{#1}{#2}%
  \if@dtl@condition
   \DTLifnumgt{#1}{#2}{#3}{#4}%
  \else
   \@DTLifstringgt{#1}{#2}{#3}{#4}%
  \fi
}
\newcommand*{\@sDTLifgt}[4]{%
  \dtl@testbothnumerical{#1}{#2}%
  \if@dtl@condition
   \DTLifnumgt{#1}{#2}{#3}{#4}%
  \else
   \@sDTLifstringgt{#1}{#2}{#3}{#4}%
  \fi
}
\newcommand*{\DTLifnumeq}[4]{%
  \DTLconverttodecimal{#1}{\@dtl@numi}%
  \DTLconverttodecimal{#2}{\@dtl@numii}%
  \dtlifnumeq{\@dtl@numi}{\@dtl@numii}%
  {%
    #3%
  }%
  {%
    #4%
  }%
}
\newcommand*{\DTLifstringeq}{\@ifstar\@sDTLifstringeq\@DTLifstringeq}
\newcommand*{\@DTLifstringeq}[4]{%
  \protected@edef\@dtl@tmpcmp{%
    \noexpand\dtlcompare{\noexpand\@dtl@tmpcount}{#1}{#2}}%
  \@dtl@tmpcmp
  \ifnum\@dtl@tmpcount=0\relax
    #3%
  \else
    #4%
  \fi
}
\newcommand*{\@sDTLifstringeq}[4]{%
  \protected@edef\@dtl@tmpcmp{%
    \noexpand\dtlicompare{\noexpand\@dtl@tmpcount}{#1}{#2}}%
  \@dtl@tmpcmp
  \ifnum\@dtl@tmpcount=0\relax
    #3%
  \else
    #4%
  \fi
}
\newcommand*{\DTLifeq}{\@ifstar\@sDTLifeq\@DTLifeq}
\newcommand*{\@DTLifeq}[4]{%
  \dtl@testbothnumerical{#1}{#2}%
  \if@dtl@condition
   \DTLifnumeq{#1}{#2}{#3}{#4}%
  \else
   \@DTLifstringeq{#1}{#2}{#3}{#4}%
  \fi
}
\newcommand*{\@sDTLifeq}[4]{%
  \dtl@testbothnumerical{#1}{#2}%
  \if@dtl@condition
   \DTLifnumeq{#1}{#2}{#3}{#4}%
  \else
   \@sDTLifstringeq{#1}{#2}{#3}{#4}%
  \fi
}
\newcommand*{\DTLifSubString}[4]{%
  \protected@edef\@dtl@dotestifsubstring{\noexpand\dtl@testifsubstring
  {#1}{#2}}%
  \@dtl@dotestifsubstring
  \if@dtl@condition
   #3%
  \else
   #4%
  \fi
}
\newcommand*{\dtl@testifsubstring}[2]{%
  \dtl@subnobrsp{#1}{\@dtl@argA}%
  \dtl@subnobrsp{#2}{\@dtl@argB}%
  \dtl@setwordbreaksnohyphens{\@dtl@argA}{\@dtl@wordbreak}%
  \let\@dtl@argA\dtl@string
  \dtl@setwordbreaksnohyphens{\@dtl@argB}{\@dtl@wordbreak}%
  \let\@dtl@argB\dtl@string
  \edef\dtl@donext{%
    \noexpand\@dtl@testifsubstring{\expandonce\@dtl@argA}{\expandonce\@dtl@argB}}%
  \dtl@donext
}
\newcommand*{\@dtl@testifsubstring}[2]{%
  \def\@dtl@subs@argA{#1}%
  \def\@dtl@subs@argB{#2}%
  \ifdefempty{\@dtl@subs@argB}%
  {%
    \@dtl@conditiontrue
  }%
  {%
    \ifdefempty{\@dtl@subs@argA}%
    {%
      \@dtl@conditionfalse
    }%
    {%
      \@dtl@teststartswith{#1}{#2}%
      \if@dtl@condition
      \else
        \dtl@getfirst#1\end@dtl@getfirst
        \expandafter\dtl@ifsingle\expandafter{\dtl@first}%
        {%
          \expandafter\@dtl@testifsubstring\expandafter{\dtl@rest}{#2}%
        }%
        {%
          \protected@edef\@dtl@donext{\noexpand\@dtl@testifsubstring
             {\expandonce\dtl@first\expandonce\dtl@rest}{\expandonce\@dtl@subs@argB}}%
          \@dtl@donext
        }%
      \fi
    }%
  }%
}
\newcommand*{\DTLifStartsWith}[4]{%
  \@dtl@conditionfalse
  \protected@edef\@dtl@tmp{\noexpand\dtl@teststartswith{#1}{#2}}%
  \@dtl@tmp
  \if@dtl@condition
   #3%
  \else
   #4%
  \fi
}
\newcommand*{\dtl@teststartswith}[2]{%
  \dtl@subnobrsp{#1}{\@dtl@argA}%
  \dtl@subnobrsp{#2}{\@dtl@argB}%
  \dtl@setwordbreaksnohyphens{\@dtl@argA}{\@dtl@wordbreak}%
  \let\@dtl@argA\dtl@string
  \dtl@setwordbreaksnohyphens{\@dtl@argB}{\@dtl@wordbreak}%
  \let\@dtl@argB\dtl@string
  \edef\dtl@donext{%
    \noexpand\@dtl@teststartswith{\expandonce\@dtl@argA}{\expandonce\@dtl@argB}}%
  \dtl@donext
}

\newcommand*{\@dtl@teststartswith}[2]{%
  \def\@dtl@argA{#1}%
  \def\@dtl@argB{#2}%
  \ifdefempty{\@dtl@argA}%
  {%
    \ifdefempty{\@dtl@argB}%
    {%
      \@dtl@conditiontrue
    }%
    {%
      \@dtl@conditionfalse
    }%
  }%
  {%
    \ifdefempty{\@dtl@argB}%
    {%
      \@dtl@conditiontrue
    }%
    {%
      \expandafter\dtl@getfirst\@dtl@argA\end@dtl@getfirst
      \let\dtl@firstA=\dtl@first
      \let\dtl@restA=\dtl@rest
      \expandafter\dtl@getfirst\@dtl@argB\end@dtl@getfirst
      \let\dtl@firstB=\dtl@first
      \let\dtl@restB=\dtl@rest
      \expandafter\dtl@ifsingle\expandafter{\dtl@firstA}%
      {%
        \expandafter\dtl@ifsingle\expandafter{\dtl@firstB}%
        {%
          \expandafter\dtl@setcharcode\expandafter{\dtl@firstA}{\dtl@codeA}%
          \expandafter\dtl@setcharcode\expandafter{\dtl@firstB}{\dtl@codeB}%
          \ifnum\dtl@codeA=-1\relax
            \ifnum\dtl@codeB=-1\relax
               \protected@edef\dtl@donext{%
                 \noexpand\@dtl@teststartswith{\expandonce\dtl@restA}{\expandonce\dtl@restB}}%
               \dtl@donext
            \else
               \protected@edef\dtl@donext{%
                 \noexpand\@dtl@teststartswith
                   {\expandonce\dtl@restA}{\expandonce\dtl@firstB\expandonce\dtl@restB}}%
               \dtl@donext
            \fi
          \else
            \ifnum\dtl@codeB=-1\relax
               \protected@edef\dtl@donext{%
                 \noexpand\@dtl@teststartswith
                   {\expandonce\dtl@firstA\expandonce\dtl@restA}{\expandonce\dtl@restB}}%
               \dtl@donext
            \else
              \ifnum\dtl@codeA=\dtl@codeB
                \protected@edef\dtl@donext{%
                  \noexpand\@dtl@teststartswith{\expandonce\dtl@restA}{\expandonce\dtl@restB}}%
                \dtl@donext
              \else
                \@dtl@conditionfalse
              \fi
            \fi
          \fi
        }%
        {%
          \protected@edef\dtl@donext{%
            \noexpand\@dtl@teststartswith
              {\expandonce\dtl@firstA\expandonce\dtl@restA}%
              {\expandonce\dtl@firstB\expandonce\dtl@restB}}%
          \dtl@donext
        }%
      }%
      {%
        \protected@edef\dtl@donext{%
          \noexpand\@dtl@teststartswith
            {\expandonce\dtl@firstA\expandonce\dtl@restA}%
            {\expandonce\dtl@firstB\expandonce\dtl@restB}}%
      }%
    }%
  }%
}
\newcommand*{\DTLifnumclosedbetween}[5]{%
 \DTLconverttodecimal{#1}{\@dtl@numi}%
 \DTLconverttodecimal{#2}{\@dtl@numii}%
 \DTLconverttodecimal{#3}{\@dtl@numiii}%
 \DTLifFPclosedbetween{\@dtl@numi}{\@dtl@numii}{\@dtl@numiii}{#4}{#5}%
}
\newcommand*{\DTLifstringclosedbetween}{%
  \@ifstar\@sDTLifstringclosedbetween\@DTLifstringclosedbetween
}
\newcommand*{\@DTLifstringclosedbetween}[5]{%
  \protected@edef\@dtl@tmpcmp{%
    \noexpand\dtlcompare{\noexpand\@dtl@tmpcount}{#1}{#2}}%
  \@dtl@tmpcmp
  \let\@dtl@dovalue\relax
  \ifnum\@dtl@tmpcount<0\relax
    \def\@dtl@dovalue{#5}%
  \fi
  \ifx\@dtl@dovalue\relax
    \protected@edef\@dtl@tmpcmp{%
      \noexpand\dtlcompare{\noexpand\@dtl@tmpcount}{#1}{#3}}%
    \@dtl@tmpcmp
    \ifnum\@dtl@tmpcount>0\relax
      \def\@dtl@dovalue{#5}%
    \else
      \def\@dtl@dovalue{#4}%
    \fi
  \fi
  \@dtl@dovalue
}
\newcommand*{\@sDTLifstringclosedbetween}[5]{%
  \protected@edef\@dtl@tmpcmp{%
    \noexpand\dtlicompare{\noexpand\@dtl@tmpcount}{#1}{#2}}%
  \@dtl@tmpcmp
  \let\@dtl@dovalue\relax
  \ifnum\@dtl@tmpcount<0\relax
    \def\@dtl@dovalue{#5}%
  \fi
  \ifx\@dtl@dovalue\relax
    \protected@edef\@dtl@tmpcmp{%
      \noexpand\dtlicompare{\noexpand\@dtl@tmpcount}{#1}{#3}}%
    \@dtl@tmpcmp
    \ifnum\@dtl@tmpcount>0\relax
      \def\@dtl@dovalue{#5}%
    \else
      \def\@dtl@dovalue{#4}%
    \fi
  \fi
  \@dtl@dovalue
}
\newcommand*{\DTLifclosedbetween}{%
  \@ifstar\@sDTLifclosedbetween\@DTLifclosedbetween
}
\newcommand*{\@DTLifclosedbetween}[5]{%
  \dtl@testbothnumerical{#2}{#3}%
  \if@dtl@condition
    \dtl@ifsingle{#1}{%
      \edef\@dtl@tmp{#1}}{%
      \def\@dtl@tmp{#1}}%
    \expandafter\@dtl@checknumerical\expandafter{\@dtl@tmp}%
    \ifnum\@dtl@datatype>0\relax
      \DTLifnumclosedbetween{#1}{#2}{#3}{#4}{#5}%
    \else
      \@DTLifstringclosedbetween{#1}{#2}{#3}{#4}{#5}%
    \fi
  \else
    \@DTLifstringclosedbetween{#1}{#2}{#3}{#4}{#5}%
  \fi
}
\newcommand*{\@sDTLifclosedbetween}[5]{%
  \dtl@testbothnumerical{#2}{#3}%
  \if@dtl@condition
    \dtl@ifsingle{#1}{%
      \edef\@dtl@tmp{#1}}{%
      \def\@dtl@tmp{#1}}%
    \expandafter\@dtl@checknumerical\expandafter{\@dtl@tmp}%
    \ifnum\@dtl@datatype>0\relax
      \DTLifnumclosedbetween{#1}{#2}{#3}{#4}{#5}%
    \else
      \@sDTLifstringclosedbetween{#1}{#2}{#3}{#4}{#5}%
    \fi
  \else
    \@sDTLifstringclosedbetween{#1}{#2}{#3}{#4}{#5}%
  \fi
}
\newcommand*{\DTLifnumopenbetween}[5]{%
 \DTLconverttodecimal{#1}{\@dtl@numi}%
 \DTLconverttodecimal{#2}{\@dtl@numii}%
 \DTLconverttodecimal{#3}{\@dtl@numiii}%
 \DTLifFPopenbetween{\@dtl@numi}{\@dtl@numii}{\@dtl@numiii}{#4}{#5}%
}
\newcommand*{\DTLifstringopenbetween}{%
  \@ifstar\@sDTLifstringopenbetween\@DTLifstringopenbetween
}
\newcommand*{\@DTLifstringopenbetween}[5]{%
  \protected@edef\@dtl@tmpcmp{%
    \noexpand\dtlcompare{\noexpand\@dtl@tmpcount}{#1}{#2}}%
  \@dtl@tmpcmp
  \let\@dtl@dovalue\relax
  \ifnum\@dtl@tmpcount>0\relax
  \else
    \def\@dtl@dovalue{#5}%
  \fi
  \ifx\@dtl@dovalue\relax
    \protected@edef\@dtl@tmpcmp{%
      \noexpand\dtlcompare{\noexpand\@dtl@tmpcount}{#1}{#3}}%
    \@dtl@tmpcmp
    \ifnum\@dtl@tmpcount<0\relax
      \def\@dtl@dovalue{#4}%
    \else
      \def\@dtl@dovalue{#5}%
    \fi
  \fi
  \@dtl@dovalue
}
\newcommand*{\@sDTLifstringopenbetween}[5]{%
  \protected@edef\@dtl@tmpcmp{%
    \noexpand\dtlicompare{\noexpand\@dtl@tmpcount}{#1}{#2}}%
  \@dtl@tmpcmp
  \let\@dtl@dovalue\relax
  \ifnum\@dtl@tmpcount>0\relax
  \else
    \def\@dtl@dovalue{#5}%
  \fi
  \ifx\@dtl@dovalue\relax
    \protected@edef\@dtl@tmpcmp{%
      \noexpand\dtlicompare{\noexpand\@dtl@tmpcount}{#1}{#3}}%
    \@dtl@tmpcmp
    \ifnum\@dtl@tmpcount<0\relax
      \def\@dtl@dovalue{#4}%
    \else
      \def\@dtl@dovalue{#5}%
    \fi
  \fi
  \@dtl@dovalue
}
\newcommand*{\DTLifopenbetween}{%
  \@ifstar\@sDTLifopenbetween\@DTLifopenbetween
}
\newcommand*{\@DTLifopenbetween}[5]{%
  \dtl@testbothnumerical{#2}{#3}%
  \if@dtl@condition
    \dtl@ifsingle{#1}{%
      \edef\@dtl@tmp{#1}}{%
      \def\@dtl@tmp{#1}}%
    \expandafter\@dtl@checknumerical\expandafter{\@dtl@tmp}%
    \ifnum\@dtl@datatype>0\relax
      \DTLifnumopenbetween{#1}{#2}{#3}{#4}{#5}%
    \else
      \@DTLifstringopenbetween{#1}{#2}{#3}{#4}{#5}%
    \fi
  \else
    \@DTLifstringopenbetween{#1}{#2}{#3}{#4}{#5}%
  \fi
}
\newcommand*{\@sDTLifopenbetween}[5]{%
  \dtl@testbothnumerical{#2}{#3}%
  \if@dtl@condition
    \dtl@ifsingle{#1}{%
      \edef\@dtl@tmp{#1}}{%
      \def\@dtl@tmp{#1}}%
    \expandafter\@dtl@checknumerical\expandafter{\@dtl@tmp}%
    \ifnum\@dtl@datatype>0\relax
      \DTLifnumopenbetween{#1}{#2}{#3}{#4}{#5}%
    \else
      \@sDTLifstringopenbetween{#1}{#2}{#3}{#4}{#5}%
    \fi
  \else
    \@sDTLifstringopenbetween{#1}{#2}{#3}{#4}{#5}%
  \fi
}
\let\DTLifFPopenbetween\dtlifnumopenbetween
\let\DTLifFPclosedbetween\dtlifnumclosedbetween
\newcommand*{\dtl@testlt}[2]{%
  \DTLiflt{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
\newcommand*{\DTLislt}[2]{%
  \TE@throw\noexpand\dtl@testlt{#1}{#2}\noexpand\if@dtl@condition
}
\newcommand*{\dtl@testiclt}[2]{%
  \@sDTLiflt{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
\newcommand*{\DTLisilt}[2]{%
  \TE@throw\noexpand\dtl@testiclt{#1}{#2}\noexpand\if@dtl@condition
}
\newcommand*{\dtl@testgt}[2]{%
  \DTLifgt{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
\newcommand*{\DTLisgt}[2]{%
  \TE@throw\noexpand\dtl@testgt{#1}{#2}\noexpand\if@dtl@condition
}
\newcommand*{\dtl@testicgt}[2]{%
  \@sDTLifgt{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
\newcommand*{\DTLisigt}[2]{%
  \TE@throw\noexpand\dtl@testicgt{#1}{#2}\noexpand\if@dtl@condition
}
\newcommand*{\dtl@testeq}[2]{%
  \DTLifeq{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
\newcommand*{\DTLiseq}[2]{%
  \TE@throw\noexpand\dtl@testeq{#1}{#2}\noexpand\if@dtl@condition
}
\newcommand*{\dtl@testiceq}[2]{%
  \@sDTLifeq{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
\newcommand*{\DTLisieq}[2]{%
  \TE@throw\noexpand\dtl@testiceq{#1}{#2}\noexpand\if@dtl@condition
}
\newcommand*{\DTLisSubString}[2]{%
  \TE@throw\noexpand\dtl@testifsubstring{#1}{#2}%
  \noexpand\if@dtl@condition
}
\newcommand*{\DTLisPrefix}[2]{%
  \TE@throw\noexpand\dtl@teststartswith{#1}{#2}%
  \noexpand\if@dtl@condition
}
\newcommand*{\DTLisinlist}[2]{%
  \TE@throw\noexpand\dtl@testinlist{#1}{#2}%
  \noexpand\if@dtl@condition
}
\newcommand*{\dtl@testinlist}[2]{%
  \DTLifinlist{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
\newcommand*{\dtl@testnumclosedbetween}[3]{%
  \DTLifnumclosedbetween{#1}{#2}{#3}%
    {\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
\newcommand*{\DTLisnumclosedbetween}[3]{%
  \TE@throw\noexpand\dtl@testnumclosedbetween{#1}{#2}{#3}%
  \noexpand\if@dtl@condition
}
\newcommand*{\dtl@testnumopenbetween}[3]{%
  \DTLifnumopenbetween{#1}{#2}{#3}%
    {\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
\newcommand*{\DTLisnumopenbetween}[3]{%
  \TE@throw\noexpand\dtl@testnumopenbetween{#1}{#2}{#3}%
  \noexpand\if@dtl@condition
}
\newcommand*{\dtl@testclosedbetween}[3]{%
  \DTLifclosedbetween{#1}{#2}{#3}%
    {\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
\newcommand*{\DTLisclosedbetween}[3]{%
  \TE@throw\noexpand\dtl@testclosedbetween{#1}{#2}{#3}%
  \noexpand\if@dtl@condition
}
\newcommand*{\dtl@testiclosedbetween}[3]{%
  \@sDTLifclosedbetween{#1}{#2}{#3}%
    {\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
\newcommand*{\DTLisiclosedbetween}[3]{%
  \TE@throw\noexpand\dtl@testiclosedbetween{#1}{#2}{#3}%
  \noexpand\if@dtl@condition
}
\newcommand*{\dtl@testopenbetween}[3]{%
  \DTLifopenbetween{#1}{#2}{#3}%
    {\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
\newcommand*{\DTLisopenbetween}[3]{%
  \TE@throw\noexpand\dtl@testopenbetween{#1}{#2}{#3}%
  \noexpand\if@dtl@condition
}
\newcommand*{\dtl@testiopenbetween}[3]{%
  \@sDTLifopenbetween{#1}{#2}{#3}%
    {\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
\newcommand*{\DTLisiopenbetween}[3]{%
  \TE@throw\noexpand\dtl@testiopenbetween{#1}{#2}{#3}%
  \noexpand\if@dtl@condition
}
\let\DTLisFPclosedbetween\DTLisnumclosedbetween
\newcommand*{\dtl@testFPopenbetween}[3]{%
  \DTLifFPopenbetween{#1}{#2}{#3}%
    {\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
\newcommand*{\DTLisFPopenbetween}[3]{%
  \TE@throw\noexpand\dtl@testFPopenbetween{#1}{#2}{#3}%
  \noexpand\if@dtl@condition
}
\newcommand*{\dtl@testFPislt}[2]{%
  \dtlifnumlt{#1}{#2}%
  {%
    \@dtl@conditiontrue
  }%
  {%
    \@dtl@conditionfalse
  }%
}
\newcommand*{\DTLisFPlt}[2]{%
  \TE@throw\noexpand\dtl@testFPislt{#1}{#2}%
  \noexpand\if@dtl@condition
}
\newcommand*{\dtl@testFPisgt}[2]{%
  \dtlifnumgt{#1}{#2}%
  {%
    \@dtl@conditiontrue
  }%
  {%
   \@dtl@conditionfalse
  }%
}
\newcommand*{\DTLisFPgt}[2]{%
  \TE@throw\noexpand\dtl@testFPisgt{#1}{#2}%
  \noexpand\if@dtl@condition
}
\newcommand*{\dtl@testFPiseq}[2]{%
 \dtlifnumeq{#1}{#2}%
 {%
   \@dtl@conditiontrue
 }%
 {%
  \@dtl@conditionfalse
 }%
}
\newcommand*{\DTLisFPeq}[2]{%
  \TE@throw\noexpand\dtl@testFPiseq{#1}{#2}%
  \noexpand\if@dtl@condition
}
\newcommand*{\dtl@testFPislteq}[2]{%
 \dtlifnumlt{#1}{#2}%
 {%
   \@dtl@conditiontrue
 }%
 {%
   \@dtl@conditionfalse
 }%
 \if@dtl@condition
 \else
  \dtl@testFPiseq{#1}{#2}%
 \fi
}
\newcommand*{\DTLisFPlteq}[2]{%
 \TE@throw\noexpand\dtl@testFPislteq{#1}{#2}%
 \noexpand\if@dtl@condition
}
\newcommand*{\dtl@testFPisgteq}[2]{%
 \dtlifnumgt{#1}{#2}%
 {%
   \@dtl@conditiontrue
 }%
 {%
   \@dtl@conditionfalse
 }%
 \if@dtl@condition
 \else
  \dtl@testFPiseq{#1}{#2}%
 \fi
}
\newcommand*{\DTLisFPgteq}[2]{%
  \TE@throw\noexpand\dtl@testFPisgteq{#1}{#2}%
  \noexpand\if@dtl@condition}
\newcommand*{\dtl@teststring}[1]{%
  \DTLifstring{#1}{\@dtl@conditiontrue}{\@dtl@conditionfalse}}
\newcommand*{\DTLisstring}[1]{%
  \TE@throw\noexpand\dtl@teststring{#1}\noexpand\if@dtl@condition}
\newcommand*{\dtl@testnumerical}[1]{%
  \DTLifnumerical{#1}{\@dtl@conditiontrue}{\@dtl@conditionfalse}%
}
\newcommand*{\DTLisnumerical}[1]{%
  \TE@throw\noexpand\dtl@testnumerical{#1}\noexpand\if@dtl@condition}
\newcommand*{\dtl@testint}[1]{%
  \DTLifint{#1}{\@dtl@conditiontrue}{\@dtl@conditionfalse}}
\newcommand*{\DTLisint}[1]{%
  \TE@throw\noexpand\dtl@testint{#1}\noexpand\if@dtl@condition}
\newcommand*{\dtl@testreal}[1]{%
  \DTLifreal{#1}{\@dtl@conditiontrue}{\@dtl@conditionfalse}}
\newcommand*{\DTLisreal}[1]{%
  \TE@throw\noexpand\dtl@testreal{#1}\noexpand\if@dtl@condition}
\newcommand*{\dtl@testcurrency}[1]{%
  \DTLifcurrency{#1}{\@dtl@conditiontrue}{\@dtl@conditionfalse}}
\newcommand*{\DTLiscurrency}[1]{%
  \TE@throw\noexpand\dtl@testcurrency{#1}\noexpand\if@dtl@condition}
\newcommand*{\dtl@testcurrencyunit}[2]{%
  \DTLifcurrencyunit{#1}{#2}{\@dtl@conditiontrue}{\@dtl@conditionfalse}}
\newcommand*{\DTLiscurrencyunit}[2]{%
  \TE@throw\noexpand\dtl@testcurrencyunit{#1}{#2}%
  \noexpand\if@dtl@condition
}
\newcommand*{\dtlbreak}{%
  \PackageError{datatool}{Can't break out of anything}{}%
}
\long\def\dtlforint#1=#2\to#3\step#4\do#5{%
  \let\@dtl@orgbreak\dtlbreak
  \def\@dtl@endloophook{}%
  \def\dtlbreak{\def\@dtl@endloophook{#1=#3}}%
  #1=#2\relax
  \ifnum#4<0\relax
    \whiledo{\(#1>#3\)\TE@or\(#1=#3\)}%
    {%
      #5%
      \@dtl@endloophook
      \advance#1 by #4\relax
    }%
  \else
    \whiledo{\(#1<#3\)\TE@or\(#1=#3\)}%
    {%
      #5%
      \@dtl@endloophook
      \advance#1 by #4\relax
    }%
  \fi
  \let\dtlbreak\@dtl@orgbreak
}
\newcount\@dtl@foreach@level
\long\def\dtlgforint#1=#2\to#3\step#4\do#5{%
  \global#1=#2\relax
  \global\advance\@dtl@foreach@level by 1\relax
  \expandafter\global\expandafter
    \let\csname @dtl@endhook@\the\@dtl@foreach@level\endcsname
    \relax
  \expandafter\global\expandafter
    \let\csname @dtl@break@\the\@dtl@foreach@level\endcsname
    \dtlbreak
  \gdef\dtlbreak{\expandafter
     \gdef\csname @dtl@endhook@\the\@dtl@foreach@level\endcsname{%
       #1=#3}}%
  \ifnum#4<0\relax
    \whiledo{\(#1>#3\)\TE@or\(#1=#3\)}%
    {%
      #5%
      \csname @dtl@endhook@\the\@dtl@foreach@level\endcsname
      \global\advance#1 by #4\relax
    }%
  \else
    \whiledo{\(#1<#3\)\TE@or\(#1=#3\)}%
    {%
      #5%
      \csname @dtl@endhook@\the\@dtl@foreach@level\endcsname
      \global\advance#1 by #4\relax
    }%
  \fi
  \expandafter\global\expandafter\let\expandafter\dtlbreak
    \csname @dtl@break@\the\@dtl@foreach@level\endcsname
  \global\advance\@dtl@foreach@level by -1\relax
}
\newenvironment{dtlenvgforint}[1]%
{%
  \def\@dtlenvgforint@arg{#1}%
  \long@collect@body\@do@dtlenvgforint
}%
{}
\newcommand{\@do@dtlenvgforint}[1]{%
  \expandafter\dtlgforint\@dtlenvgforint@arg\do{#1}%
}
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</datatool-base-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datatool-fp-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\subsection{Rollback v2.32 (datatool-fp-2019-09-27.sty)}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{datatool-fp}[2019/09/27 v2.32 (NLCT)]
\RequirePackage{xkeyval}
\RequirePackage{fp}
\RequirePackage{datatool-base}[=v2.32]
\define@choicekey{datatool-fp}{verbose}[\val\nr]{true,false}[true]{%
  \ifcase\nr\relax
    \FPmessagestrue
  \or
    \FPmessagesfalse
  \fi
}
\let\ifFPmessages\ifdtlverbose
\ProcessOptionsX
\providecommand*{\@dtl@mathprocessor}{fp}
\newcommand*{\dtlifnumeq}[4]{%
  \FPifeq{#1}{#2}%
    #3%
  \else
    #4%
  \fi
}
\let\ifdtlverbose\ifFPmessages
\newcommand*{\dtlifnumlt}[4]{%
  \FPiflt{#1}{#2}%
   #3%
  \else
   #4%
  \fi
}
\newcommand*{\dtlifnumgt}[4]{%
  \FPifgt{#1}{#2}%
   #3%
  \else
   #4%
  \fi
}
\newcommand*{\dtlifnumopenbetween}[5]{%
  \let\@dtl@dovalue\relax
  \dtlifnumgt{#1}{#2}%
  {}%
  {%
    \def\@dtl@dovalue{#5}%
  }%
  \dtlifnumlt{#1}{#3}%
  {%
    \ifx\@dtl@dovalue\relax
      \def\@dtl@dovalue{#4}%
    \fi
  }%
  {%
    \def\@dtl@dovalue{#5}%
  }%
  \@dtl@dovalue
}
\newcommand*{\dtlifnumclosedbetween}[5]{%
  \let\@dtl@dovalue\relax
  \dtlifnumgt{#1}{#2}%
  {}%
  {%
    \dtlifnumeq{#1}{#2}%
    {%
      \def\@dtl@dovalue{#4}%
    }%
    {%
      \def\@dtl@dovalue{#5}%
    }%
  }%
  \dtlifnumlt{#1}{#3}%
  {%
    \ifx\@dtl@dovalue\relax
      \def\@dtl@dovalue{#4}%
    \fi
  }%
  {%
    \dtlifnumeq{#1}{#3}%
    {%
      \def\@dtl@dovalue{#4}%
    }%
    {%
      \def\@dtl@dovalue{#5}%
    }%
  }%
  \@dtl@dovalue
}
\newcommand*{\dtladd}[3]{%
  \FPadd{#1}{#2}{#3}%
}
\newcommand*{\dtlsub}[3]{%
  \FPsub{#1}{#2}{#3}%
}
\newcommand*{\dtlmul}[3]{%
  \FPmul{#1}{#2}{#3}%
}
\newcommand*{\dtldiv}[3]{%
  \FPdiv{#1}{#2}{#3}%
}
\newcommand*{\dtlroot}[2]{%
  \FProot{#1}{#2}%
}
\newcommand*{\dtlround}[3]{%
  \FPround{#1}{#2}{#3}%
}
\newcommand*{\dtltrunc}[3]{%
  \FPtrunc{#1}{#2}{#3}%
}
\newcommand*{\dtlclip}[2]{%
  \FPclip{#1}{#2}%
}
\newcommand*{\dtlmin}[3]{%
  \FPmin{#1}{#2}{#3}%
}
\newcommand*{\dtlmax}[3]{%
  \FPmax{#1}{#2}{#3}%
}
\newcommand*{\dtlabs}[2]{%
  \FPabs{#1}{#2}%
}
\newcommand*{\dtlneg}[2]{%
  \FPneg{#1}{#2}%
}
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</datatool-fp-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*datatool-pgfmath-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\subsection{Rollback v2.32 (datatool-pgfmath-2019-09-27.sty)}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{datatool-pgfmath}[2019/09/27 v2.32 (NLCT)]
\RequirePackage{xkeyval}
\RequirePackage{pgfrcs,pgfkeys,pgfmath}
\ProcessOptionsX
\providecommand*{\@dtl@mathprocessor}{pgfmath}
\newcommand*{\dtlifnumeq}[4]{%
  \def\@dtl@truepart{#3}%
  \def\@dtl@falsepart{#4}%
  \pgfmathifthenelse{\number0#1==\number0#2}%
    {"\noexpand\@dtl@truepart"}{"\noexpand\@dtl@falsepart"}%
  \pgfmathresult
}
\RequirePackage{datatool-base}[=v2.32]
\newcommand*{\dtlifnumlt}[4]{%
  \def\@dtl@truepart{#3}%
  \def\@dtl@falsepart{#4}%
  \pgfmathifthenelse{\number0#1 < \number0#2}%
    {"\noexpand\@dtl@truepart"}{"\noexpand\@dtl@falsepart"}%
  \pgfmathresult
}
\newcommand*{\dtlifnumgt}[4]{%
  \def\@dtl@truepart{#3}%
  \def\@dtl@falsepart{#4}%
  \pgfmathifthenelse{\number0#1 > \number0#2}%
    {"\noexpand\@dtl@truepart"}{"\noexpand\@dtl@falsepart"}%
  \pgfmathresult
}
\newcommand*{\dtlifnumopenbetween}[5]{%
  \def\@dtl@truepart{#4}%
  \def\@dtl@falsepart{#5}%
  \pgfmathifthenelse
    {(\number0#2 < \number0#1) && (\number0#1 < \number0#3)}%
    {"\noexpand\@dtl@truepart"}{"\noexpand\@dtl@falsepart"}%
  \pgfmathresult
}
\newcommand*{\dtlifnumclosedbetween}[5]{%
  \def\@dtl@truepart{#4}%
  \def\@dtl@falsepart{#5}%
  \pgfmathifthenelse
    {(\number0#2 <= \number0#1) && (\number0#1 <= \number0#3)}
    {"\noexpand\@dtl@truepart"}{"\noexpand\@dtl@falsepart"}%
  \pgfmathresult
}
\newcommand*{\dtladd}[3]{%
  \pgfmathadd{#2}{#3}%
  \let#1\pgfmathresult
}
\newcommand*{\dtlsub}[3]{%
  \pgfmathsubtract{#2}{#3}%
  \let#1\pgfmathresult
}
\newcommand*{\dtlmul}[3]{%
  \pgfmathmultiply{#2}{#3}%
  \let#1\pgfmathresult
}
\newcommand*{\dtldiv}[3]{%
  \pgfmathdivide{#2}{#3}%
  \let#1\pgfmathresult
}
\newcommand*{\dtlroot}[2]{%
  \pgfmathsqrt{#2}%
  \let#1\pgfmathresult
}
\newcommand*{\dtlround}[3]{%
  \ifnum#3=0\relax
    \pgfmathparse{int(round(#2))}%
    \let#1\pgfmathresult
  \else
    \pgfmathparse{int(10^#3)}%
    \let\dtl@tmpshift\pgfmathresult
    \pgfmathparse{int(floor(#2))}%
    \let\dtl@int@round\pgfmathresult
    \pgfmathparse{int(round((#2-\dtl@int@round) * \dtl@tmpshift))}%
    \@dtl@tmpcount=0\relax
    \expandafter\@dtl@countdigits\pgfmathresult.\relax
    \advance\@dtl@tmpcount by -#3\relax
    \def\@dtl@intpart{}%
    \def\@dtl@fracpart{}%
    \expandafter\@dtl@gatherintfrac\pgfmathresult\relax
    \edef\@dtl@intpart{\number\numexpr\dtl@int@round
      +\number0\@dtl@intpart}%
    \edef#1{\@dtl@intpart.\@dtl@fracpart}%
  \fi
}
\newcommand*{\@dtl@gatherintfrac}[1]{%
  \ifx\relax#1\relax
  \else
    \advance\@dtl@tmpcount by -1\relax
    \ifnum\@dtl@tmpcount<0\relax
      \edef\@dtl@fracpart{\@dtl@fracpart#1}%
    \else
      \edef\@dtl@intpart{\@dtl@intpart#1}%
    \fi
    \expandafter\@dtl@gatherintfrac
  \fi
}
\newcommand*{\dtltrunc}[3]{%
  \ifnum#3=0\relax
    \pgfmathparse{int(floor(#2))}%
    \let#1\pgfmathresult
  \else
    \pgfmathparse{int(10^#3)}%
    \let\dtl@tmpshift\pgfmathresult
    \pgfmathparse{int(floor(#2))}%
    \let\dtl@int@trunc\pgfmathresult
    \pgfmathparse{int(floor((#2-\dtl@int@trunc) * \dtl@tmpshift))}%
    \@dtl@tmpcount=0\relax
    \expandafter\@dtl@countdigits\pgfmathresult.\relax
    \advance\@dtl@tmpcount by -#3\relax
    \def\@dtl@intpart{}%
    \def\@dtl@fracpart{}%
    \expandafter\@dtl@gatherintfrac\pgfmathresult\relax
    \edef\@dtl@intpart{\number\numexpr\dtl@int@trunc
      +\number0\@dtl@intpart}%
    \edef#1{\@dtl@intpart.\@dtl@fracpart}%
  \fi
}
\newcommand*{\dtlclip}[2]{%
 \edef#1{#2}%
}
\newcommand*{\dtlmin}[3]{%
  \pgfmathmin{#2}{#3}%
  \let#1\pgfmathresult
}
\newcommand*{\dtlmax}[3]{%
  \pgfmathmax{#2}{#3}%
  \let#1\pgfmathresult
}
\newcommand*{\dtlabs}[2]{%
  \pgfmathabs{#2}%
  \let#1\pgfmathresult
}
\newcommand*{\dtlneg}[2]{%
  \pgfmathmul{-1}{#2}%
  \let#1\pgfmathresult
}
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</datatool-pgfmath-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\iffalse
%    \begin{macrocode}
%<*person-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\subsection{Rollback v2.32 (person-2019-09-27.sty)}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{person}[2019/09/27 v2.32 (NLCT)]
\RequirePackage{ifthen}
\RequirePackage{datatool}[=v2.32]
\newcounter{people}
\newcounter{person}
\newcommand*{\@people@list}{,}
\newcommand*{\@get@firstperson}[1]{%
  \expandafter\@@get@firstperson\@people@list,\@nil{#1}}
  \def\@@get@firstperson,#1,#2\@nil#3{%
  \def#3{#1}%
}
\newcommand*{\malelabels}{male,Male,MALE,M,m}
\newcommand*{\addmalelabel}[1]{%
  \expandafter\@dtl@toksA\expandafter{\malelabels}%
  \expandafter\@dtl@toksB\expandafter{#1}%
  \edef\malelabels{\the\@dtl@toksA,\the\@dtl@toksB}%
}
\newcommand*{\addfemalelabel}[1]{%
  \expandafter\@dtl@toksA\expandafter{\femalelabels}%
  \expandafter\@dtl@toksB\expandafter{#1}%
  \edef\femalelabels{\the\@dtl@toksA,\the\@dtl@toksB}%
}
\newcommand*{\femalelabels}{female,Female,FEMALE,F,f}
\newcommand{\ifmalelabel}[3]{%
   \expandafter\DTLifinlist\expandafter{#1}{\malelabels}{#2}{#3}%
}
\newcommand{\iffemalelabel}[3]{%
   \expandafter\DTLifinlist\expandafter{#1}{\femalelabels}{#2}{#3}%
}
\newcommand*{\newperson}[4][anon]{%
  \@ifundefined{person@#1@name}%
  {%
    \ifmalelabel{#4}%
    {%
      \expandafter\gdef\csname person@#1@gender\endcsname{male}%
    }%
    {%
      \iffemalelabel{#4}%
      {%
        \expandafter\gdef\csname person@#1@gender\endcsname{female}%
      }%
      {%
         \PackageError{person}{Unknown gender `#4' for person
         `#1'}{Allowed gender labels are: \malelabels\space or
         \femalelabels}%
         \@namedef{person@#1@gender}{other}%
      }%
    }%
    \expandafter
      \protected@xdef\csname person@#1@fullname\endcsname{#2}%
    \expandafter
      \protected@xdef\csname person@#1@name\endcsname{#3}%
    \protected@xdef\@people@list{\@people@list#1,}%
    \stepcounter{people}%
  }%
  {%
    \PackageError{person}{Person `#1' has already been defined}{}%
  }%
}
\newcommand*{\removeperson}[1][anon]{%
  \edef\@person@label{#1}%
  \expandafter\@removeperson\expandafter{\@person@label}%
}
\newcommand*{\@removeperson}[1]{%
   \ifpersonexists{#1}%
   {%
     \def\@remove@person##1,#1,##2\@nil{%
       \def\@prsn@pre{##1}\def\@prsn@post{##2}}%
     \expandafter\@remove@person\@people@list\@nil
     \xdef\@people@list{\@prsn@pre,\@prsn@post}%
     \addtocounter{people}{-1}%
     \expandafter\global\expandafter
       \let\csname person@#1@name\endcsname\undefined
     \expandafter\global\expandafter
       \let\csname person@#1@fullname\endcsname\undefined
     \expandafter\global\expandafter
       \let\csname person@#1@gender\endcsname\undefined
   }%
   {%
     \PackageError{person}{Can't remove person `#1': no such
     person}{}%
   }%
}
\newcommand*{\removepeople}[1]{%
  \@for\@thisperson:=#1\do{%
    \ifx\@thisperson\@empty
    \else
      \expandafter\removeperson\expandafter[\@thisperson]%
    \fi
  }%
}
\newcommand*{\removeallpeople}{%
  \@for\@thisperson:=\@people@list\do{%
    \expandafter\global\expandafter
      \let\csname person@\@thisperson @name\endcsname\undefined
    \expandafter\global\expandafter
      \let\csname person@\@thisperson @fullname\endcsname\undefined
    \expandafter\global\expandafter
      \let\csname person@\@thisperson @gender\endcsname\undefined
  }%
  \setcounter{people}{0}%
  \gdef\@people@list{,}%
}
\newcommand{\ifpersonexists}[3]{%
  \@ifundefined{person@#1@name}{#3}{#2}%
}
\newcommand{\ifmale}[3]{%
   \ifpersonexists{#1}%
   {%
      \edef\@gender{\csname person@#1@gender\endcsname}%
      \ifx\@gender\@male@label
        #2%
      \else
        #3%
      \fi
   }%
   {%
      \PackageError{person}{Person `#1' doesn't exist.}{}%
   }%
}
\def\@male@label{male}
\newcommand{\ifallmale}[3][\@people@list]{%
  \@for\@thisperson:=#1\do{%
    \ifpersonexists{\@thisperson}%
    {%
      \edef\@gender{\csname person@\@thisperson @gender\endcsname}%
      \ifx\@gender\@male@label
      \else
        \@endfortrue
      \fi
    }%
    {%
      \PackageError{person}{Person `#1' doesn't exist.}{}%
    }%
  }%
  \if@endfor
    #3%
  \else
    #2%
  \fi
}
\newcommand{\iffemale}[3]{%
   \ifpersonexists{#1}%
   {%
      \edef\@gender{\csname person@#1@gender\endcsname}%
      \ifx\@gender\@female@label
        #2%
      \else
        #3%
      \fi
   }%
   {%
      \PackageError{person}{Person `#1' doesn't exist.}{}%
   }%
}
\def\@female@label{female}
\newcommand{\ifallfemale}[3][\@people@list]{%
  \@for\@thisperson:=#1\do{%
    \edef\@gender{\csname person@\@thisperson @gender\endcsname}%
    \ifx\@gender\@female@label
    \else
      \@endfortrue
    \fi
  }%
  \if@endfor
    #3%
  \else
    #2%
  \fi
}
\def\foreachperson(#1,#2,#3,#4)#5{%
  \ifx#5\in
    \def\@do@foreachperson{\@foreachperson(#1,#2,#3,#4)#5}%
  \else
    \def\@do@foreachperson{%
      \@foreachperson(#1,#2,#3,#4)\in\@people@list#5}%
  \fi
  \@do@foreachperson
}
\long\def\@foreachperson(#1,#2,#3,#4)\in#5\do#6{%
  \@for#4:=#5\do{%
    \ifx#4\@empty
    \else
       \ifpersonexists{#4}%
       {%
         \expandafter
           \let\expandafter#1\csname person@#4@name\endcsname
         \expandafter
           \let\expandafter#2\csname person@#4@fullname\endcsname
         \expandafter
           \let\expandafter#3\csname person@#4@gender\endcsname
         \ifx#3\@male@label
           \let#3\malename
         \else
           \ifx#3\@female@label
             \let#3\femalename
           \fi
         \fi
         #6%
       }%
       {%
         \PackageError{person}{Person `#4' doesn't exist}{}%
       }%
     \fi
  }%
}
\newcommand*{\malepronoun}{he}
\newcommand*{\femalepronoun}{she}
\newcommand*{\pluralpronoun}{they}
\newcommand*{\maleobjpronoun}{him}
\newcommand*{\femaleobjpronoun}{her}
\newcommand*{\pluralobjpronoun}{them}
\newcommand*{\malepossadj}{his}
\newcommand*{\femalepossadj}{her}
\newcommand*{\pluralpossadj}{their}
\newcommand*{\maleposspronoun}{his}
\newcommand*{\femaleposspronoun}{hers}
\newcommand*{\pluralposspronoun}{theirs}
\newcommand*{\malechild}{son}
\newcommand*{\femalechild}{daughter}
\newcommand*{\pluralchild}{children}
\newcommand*{\malechildren}{sons}
\newcommand*{\femalechildren}{daughters}
\newcommand*{\maleparent}{father}
\newcommand*{\femaleparent}{mother}
\newcommand*{\pluralparent}{parents}
\newcommand*{\malesibling}{brother}
\newcommand*{\femalesibling}{sister}
\newcommand*{\pluralsibling}{siblings}
\newcommand*{\malesiblings}{brothers}
\newcommand*{\femalesiblings}{sisters}
\providecommand*{\andname}{and}
\newcommand*{\malename}{male}
\newcommand*{\femalename}{female}
\newcommand*{\personsep}{, }
\newcommand*{\personlastsep}{\space\andname\space}
\newcommand*{\twopeoplesep}{\space\andname\space}
\newcommand*{\personfullname}[1][anon]{%
  \@ifundefined{person@#1@fullname}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \csname person@#1@fullname\endcsname
  }%
}
\newcommand*{\peoplefullname}{%
  \setcounter{person}{1}%
  \@for\@thisperson:=\@people@list\do{%
    \ifthenelse{\equal{\@thisperson}{}}%
    {}%
    {%
      \personfullname[\@thisperson]%
      \stepcounter{person}%
      \ifnum\c@people=1\relax
      \else
        \ifnum\c@person=\c@people
          \ifnum\c@people=2\relax
            \twopeoplesep
          \else
            \personlastsep
          \fi
        \else
           \ifnum\c@person<\c@people
             \personsep
           \fi
        \fi
      \fi
    }%
  }%
}
\newcommand*{\personname}[1][anon]{%
  \@ifundefined{person@#1@name}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \csname person@#1@name\endcsname
  }%
}
\newcommand*{\peoplename}{%
  \setcounter{person}{1}%
  \@for\@thisperson:=\@people@list\do{%
    \ifthenelse{\equal{\@thisperson}{}}%
    {}%
    {%
      \personname[\@thisperson]%
      \stepcounter{person}%
      \ifnum\c@people=1\relax
      \else
        \ifnum\c@person=\c@people
          \ifnum\c@people=2\relax
            \twopeoplesep
          \else
            \personlastsep
          \fi
        \else
           \ifnum\c@person<\c@people
             \personsep
           \fi
        \fi
      \fi
    }%
  }%
}
\newcommand*{\personpronoun}[1][anon]{%
  \@ifundefined{person@#1@gender}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \edef\@gender{\csname person@#1@gender\endcsname}%
    \csname\@gender pronoun\endcsname
  }%
}
\newcommand*{\Personpronoun}[1][anon]{%
  \@ifundefined{person@#1@gender}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \edef\@gender{\csname person@#1@gender\endcsname}%
    \expandafter\expandafter\expandafter
    \MakeUppercase\csname\@gender pronoun\endcsname
  }%
}
\newcommand*{\peoplepronoun}{%
  \ifnum\c@people>1\relax
     \pluralpronoun
  \else
     \@get@firstperson{\@thisperson}%
     \personpronoun[\@thisperson]%
  \fi
}
\newcommand*{\Peoplepronoun}{%
  \ifnum\c@people>1\relax
     \expandafter\MakeUppercase\pluralpronoun
  \else
     \@get@firstperson{\@thisperson}%
     \Personpronoun[\@thisperson]%
  \fi
}
\newcommand*{\personobjpronoun}[1][anon]{%
  \@ifundefined{person@#1@gender}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \edef\@gender{\csname person@#1@gender\endcsname}%
    \csname\@gender objpronoun\endcsname
  }%
}
\newcommand*{\Personobjpronoun}[1][anon]{%
  \@ifundefined{person@#1@gender}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \edef\@gender{\csname person@#1@gender\endcsname}%
    \expandafter\expandafter\expandafter
    \MakeUppercase\csname\@gender objpronoun\endcsname
  }%
}
\newcommand*{\peopleobjpronoun}{%
  \ifnum\c@people>1\relax
     \pluralobjpronoun
  \else
     \@get@firstperson{\@thisperson}%
     \personobjpronoun[\@thisperson]%
  \fi
}
\newcommand*{\Peopleobjpronoun}{%
  \ifnum\c@people>1\relax
     \expandafter\MakeUppercase\pluralobjpronoun
  \else
     \@get@firstperson{\@thisperson}%
     \Personobjpronoun[\@thisperson]%
  \fi
}
\newcommand*{\personpossadj}[1][anon]{%
  \@ifundefined{person@#1@gender}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \edef\@gender{\csname person@#1@gender\endcsname}%
    \csname\@gender possadj\endcsname
  }%
}
\newcommand*{\Personpossadj}[1][anon]{%
  \@ifundefined{person@#1@gender}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \edef\@gender{\csname person@#1@gender\endcsname}%
    \expandafter\expandafter\expandafter
    \MakeUppercase\csname\@gender possadj\endcsname
  }%
}
\newcommand*{\peoplepossadj}{%
  \ifnum\c@people>1\relax
     \pluralpossadj
  \else
     \@get@firstperson{\@thisperson}%
     \personpossadj[\@thisperson]%
  \fi
}
\newcommand*{\Peoplepossadj}{%
  \ifnum\c@people>1\relax
     \expandafter\MakeUppercase\pluralpossadj
  \else
     \@get@firstperson{\@thisperson}%
     \Personpossadj[\@thisperson]%
  \fi
}
\newcommand*{\personposspronoun}[1][anon]{%
  \@ifundefined{person@#1@gender}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \edef\@gender{\csname person@#1@gender\endcsname}%
    \csname\@gender posspronoun\endcsname
  }%
}
\newcommand*{\Personposspronoun}[1][anon]{%
  \@ifundefined{person@#1@gender}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \edef\@gender{\csname person@#1@gender\endcsname}%
    \expandafter\expandafter\expandafter
    \MakeUppercase\csname\@gender posspronoun\endcsname
  }%
}
\newcommand*{\peopleposspronoun}{%
  \ifnum\c@people>1\relax
     \pluralposspronoun
  \else
     \@get@firstperson{\@thisperson}%
     \personposspronoun[\@thisperson]%
  \fi
}
\newcommand*{\Peopleposspronoun}{%
  \ifnum\c@people>1\relax
     \expandafter\MakeUppercase\pluralposspronoun
  \else
     \@get@firstperson{\@thisperson}%
     \Personposspronoun[\@thisperson]%
  \fi
}
\newcommand*{\personchild}[1][anon]{%
  \@ifundefined{person@#1@gender}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \edef\@gender{\csname person@#1@gender\endcsname}%
    \csname\@gender child\endcsname
  }%
}
\newcommand*{\Personchild}[1][anon]{%
  \@ifundefined{person@#1@gender}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \edef\@gender{\csname person@#1@gender\endcsname}%
    \expandafter\expandafter\expandafter\MakeUppercase
      \csname\@gender child\endcsname
  }%
}
\newcommand*{\peoplechild}{%
  \ifnum\c@people>1\relax
    \ifallmale
      {\malechildren}%
      {\ifallfemale{\femalechildren}{\pluralchild}}%
  \else
    \@get@firstperson{\@thisperson}%
    \personchild[\@thisperson]%
 \fi
}
\newcommand*{\Peoplechild}{%
  \ifnum\c@people>1\relax
    \ifallmale
      {\expandafter\MakeUppercase\malechildren}%
      {\ifallfemale
        {\expandafter\MakeUppercase\femalechildren}
        {\expandafter\MakeUppercase\pluralchild}}%
  \else
    \@get@firstperson{\@thisperson}%
    \Personchild[\@thisperson]%
 \fi
}
\newcommand*{\personparent}[1][anon]{%
  \@ifundefined{person@#1@gender}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \edef\@gender{\csname person@#1@gender\endcsname}%
    \csname\@gender parent\endcsname
  }%
}
\newcommand*{\Personparent}[1][anon]{%
  \@ifundefined{person@#1@gender}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \edef\@gender{\csname person@#1@gender\endcsname}%
    \expandafter\expandafter\expandafter\MakeUppercase
       \csname\@gender parent\endcsname
  }%
}
\newcommand*{\peopleparent}{%
  \ifnum\c@people>1\relax
     \pluralparent
  \else
     \@get@firstperson{\@thisperson}%
     \personparent[\@thisperson]%
  \fi
}
\newcommand*{\Peopleparent}{%
  \ifnum\c@people>1\relax
     \expandafter\MakeUppercase\pluralparent
  \else
     \@get@firstperson{\@thisperson}%
     \Personparent[\@thisperson]%
  \fi
}
\newcommand*{\personsibling}[1][anon]{%
  \@ifundefined{person@#1@gender}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \edef\@gender{\csname person@#1@gender\endcsname}%
    \csname\@gender sibling\endcsname
  }%
}
\newcommand*{\Personsibling}[1][anon]{%
  \@ifundefined{person@#1@gender}%
  {%
    \PackageError{person}{Person `#1' has not been defined}{}%
  }%
  {%
    \edef\@gender{\csname person@#1@gender\endcsname}%
    \expandafter\expandafter\expandafter\MakeUppercase
      \csname\@gender sibling\endcsname
  }%
}
\newcommand*{\peoplesibling}{%
  \ifnum\c@people>1\relax
    \ifallmale
      {\malesiblings}%
      {\ifallfemale{\femalesiblings}{\pluralsibling}}%
  \else
    \@get@firstperson{\@thisperson}%
    \personsibling[\@thisperson]%
 \fi
}
\newcommand*{\persongender}[1]{%
   \ifmale{#1}{\malename}{\femalename}%
}
\newcommand*{\getpersongender}[2]{%
   \ifmale{#2}{\let#1\malename}{\let#1\femalename}%
}
\newcommand*{\getpersonname}[2]{%
   \ifpersonexists{#2}%
   {%
     \expandafter\let\expandafter#1\csname person@#2@name\endcsname
   }%
   {%
      \PackageError{person}{Person `#2' doesn't exist}{}%
   }%
}
\newcommand*{\getpersonfullname}[2]{%
   \ifpersonexists{#2}%
   {%
     \expandafter
       \let\expandafter#1\csname person@#2@fullname\endcsname
   }%
   {%
      \PackageError{person}{Person `#2' doesn't exist}{}%
   }%
}
%    \end{macrocode}
%\iffalse
%    \begin{macrocode}
%</person-2019-09-27.sty>
%    \end{macrocode}
%\fi
%\Finale
\endinput
