3

This question led to a new feature in a package:
datatool

\DTLgdeletedb is available in (v2.13) and later.


I am trying to use a \foreach loop to iterate over a few items. For each iteration of the steps of \ExtractData are:

  1. Load the data into mydb.
  2. Extract the appropriate data.
  3. Delete mydb.

This works just fine outside of a \foreach. But if used within a \foreach, it appears that the database is not deleted, and any subsequent attempt to create a new database with the same name fails with:

Package datatool Error: Database `myDB' already exists.

Notes:

  • The MWE example below, shows that that database access is working fine outside of a \foreach, and within the very first invocation within a \foreach.
  • The failure occurs only if an attempt is made to use \ExtractData after a use within the \foreach. So uncommenting any of the commented blocks will show the failure.
  • Before someone asks why I close and re-open the same db, in my actual usage, the data file opened is different for each member of the \foreach. Just the name of the data base is the same. I guess I could create a new name each time, but I'd really like to know what the actual problem is.

Reference:

Code:

\documentclass{article}

\usepackage{filecontents}
\begin{filecontents*}{foo.dat}
    Smith, John, 1977-05-04
    Doe,   Jane, 1980-05-04
\end{filecontents*}

\usepackage{xstring}
\usepackage{pgffor}
\usepackage{datatool}

\newcommand{\ExtractData}[3]{% #1=file name #2=row, #3=col
    \DTLloaddb[noheader,keys={LastName, FirstName, DOB}]{myDB}{#1}%
    \IfEq{\DTLrowcount{myDB}}{0}{%
        \def\Value{NO VALUE}
    }{%
        \DTLgetvalue{\Value}{myDB}{#2}{#3}%
    }%
    \DTLdeletedb{myDB}%
    \Value%
}

\begin{document}
\section{Works}
\ExtractData{foo.dat}{1}{2}
\ExtractData{foo.dat}{1}{1}

\section{Works in foreach (one element)}
\foreach \x in {2}{
    \ExtractData{foo.dat}{1}{\x}
}

\section{Does NOT work subsequent to foreach (one element)}

%\ExtractData{foo.dat}{1}{1}

%\foreach \x in {1}{
%   \ExtractData{foo.dat}{1}{\x}
%}

\section{Does NOT works in foreach (more than one element)}
%\foreach \x in {2,1}{
%   \ExtractData{foo.dat}{1}{\x}\par\noindent
%}
\end{document}
3
  • 1
    It's the usual problem: \foreach works in a group, so the \DTLdeletedb inside the \foreach deletes the database only locally.
    – egreg
    Commented Apr 14, 2012 at 9:50
  • @egreg: I did think of that but since \DTLloaddb and \DTLdeletedb were within the same scope I though they would either both load/delete locally or both load/delete globally so did not think that that was the problem. Commented Apr 14, 2012 at 17:28
  • AFAIK, each step of \foreach is run in a distinct group.
    – egreg
    Commented Apr 14, 2012 at 17:33

2 Answers 2

4

\foreach works inside a group, so the deletion of the database is only local. Define a "global" deletion command \DTLgdeletedb and use that.

\makeatletter
\newcommand*{\DTLgdeletedb}[1]{%
  \DTLifdbexists{#1}%
  {%
    \dtlforeachkey(\@dtl@key,\@dtl@col,\@dtl@type,\@dtl@head)\in{#1}\do
    {%
      \global\expandafter\let\csname dtl@ci@#1@\@dtl@key\endcsname\undefined
    }%
    \global\expandafter\let\csname dtldb@#1\endcsname\undefined
    \global\expandafter\let\csname dtlkeys@#1\endcsname\undefined
    \global\expandafter\let\csname dtlrows@#1\endcsname\undefined
    \global\expandafter\let\csname dtlcols@#1\endcsname\undefined
  }%
  {%
    \PackageError{Can't delete database `#1':
       database doesn't exist}{}{}%
  }%
}
\makeatother

It's just the same as the \DTLdeletedb command, but with \global prepended. I'm afraid that heavy use of this approach can pollute the save stack; be warned.

1
  • 1
    I'll add this (and a global version of \DTLcleardb) to the next version of datatool. (You can track the progress of this feature using my feature tracker.) Commented Dec 10, 2012 at 8:51
3

I can't answer this question but I want to mention that the loop \@for works well:

\documentclass{article}

\usepackage{filecontents}
\begin{filecontents*}{foo.dat}
    Smith, John, 1977-05-04
    Doe,   Jane, 1980-05-04
\end{filecontents*}

\usepackage{xstring}
\usepackage{pgffor}
\usepackage{datatool}

\newcommand{\ExtractData}[3]{% #1=file name #2=row, #3=col
    \DTLloaddb[noheader,keys={LastName, FirstName, DOB}]{myDB}{#1}%
    \IfEq{\DTLrowcount{myDB}}{0}{%
        \def\Value{NO VALUE}
    }{%
        \DTLgetvalue{\Value}{myDB}{#2}{#3}%
    }%
    \DTLdeletedb{myDB}%
    \Value%
}
\makeatletter
\let\myloop\@for
\makeatother
\begin{document}
\section{Works}
\ExtractData{foo.dat}{1}{2}
\ExtractData{foo.dat}{1}{1}

\section{Works in foreach (one element)}

\myloop\xx:={2}\do{%
  \ExtractData{foo.dat}{1}{\xx}%
  }

\section{Does NOT work subsequent to foreach (one element)}

\ExtractData{foo.dat}{1}{1}

\myloop\xx:={1}\do{%
   \ExtractData{foo.dat}{1}{\xx}%
}

\section{Does NOT works in foreach (more than one element)}
\myloop\xx:={2,1}\do{%
   \ExtractData{foo.dat}{1}{\xx}\par\noindent%
}
\end{document}
1
  • Thanks for this. Will keep the @for loop in mind next time I get into trouble with a \foreach, which I am sure will happen. :-) Commented Apr 14, 2012 at 17:35

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .