没有空间容纳新的 \count 和 datatool

没有空间容纳新的 \count 和 datatool

这个问题引出了包中的一个新功能:
datatool


更新日期:2013-1-19

MWE 与datatool使用 的新版本 (v2.13) 软件包配合良好\DTLnewdbonloadfalse。但在实际使用中,我收到了以下消息:

失控参数?\db@plist@elt@w \db@col@id@w 1\db@col@id@end@ \db@key@id@w Chapter\db@key@id@end@ \ETC.

!扫描 \@dtl@get@keydata 的使用时文件结束。

大约 16172 DB 之后\DTLloaddb——因为我只计算了反复已加载。

将尝试为此创建一个 MWE,但万一其他人能明显看出问题所在,我想在尝试彻底解决这个问题时添加这些信息。


背景:

我正在生成一个文档,用于跟踪由大量文件组成的项目组件的进度。对于每个这些文件中,生成一个小型数据库并通过包裹datatool。该数据库包含相关文件的列表以及它们的位置以及它们是什么类型的文件。

虽然这与下面的 MWE 无关,但这里有一小段输出摘录来说明我实际上想要实现的目标:

在此处输入图片描述

从中我可以知道该文件

  • 15-4-34 有一些进展(以蓝色文本显示)
  • 15-4-35 和 15-4-36 取得了重大进展(突出显示)。
  • 15-4-37 已经没有进展完全没有(红色部分)

每个文件都有链接,因此我只需单击它们即可打开查看,并且可以知道哪个团队成员取得了进展。在文档的末尾,我会制作一份总结报告,这样我就可以知道我在项目中的进展情况。还会​​捕获许多其他信息,但这足以让我们了解实际用例。

关联文件列表包含在运行时为项目的每个组件生成的两个小型 CSV 文件中。例如,如果文件存在,我会检查是否有相应的 PDF,以及是否有任何相关图表已完成,或者文件中.tex是否有表明该文件仍未完成的内容,等等。\todonote.tex

问题:

在处理该文档时,我收到以下错误消息:

No room for a new \count .

\et@xchk ...lse \errmessage {No room for a new #2}

根据下面列出的第一个参考文献中 egreg 的回答,造成这种情况的两个潜在原因有三个:

  1. 包裹太多
  2. 编程错误,例如\newdimen在命令的定义中说错话。

在彻底搜索我的代码之后编程错误,我确定罪魁祸首是包裹datatool声明了一个\newcountwithin \DTLnewdb

\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
  }%
}

它似乎是按照\DTLdeletedb以下方式发布的:

    \expandafter\let\csname dtlrows@#1\endcsname\undefined
    \expandafter\let\csname dtlcols@#1\endcsname\undefined

然而,这似乎并没有真正达到预期的效果,即如 MWE 所示,将计数器返回到可用的计数器列表以供后续使用。

笔记:

  • MWE 需要文件MyData.csv。但是,我将其注释掉\usepackage{filecontents}以防止覆盖此文件,从而防止意外删除。
  • 下面的 MWE 的输出并不重要,只是为了创建 PDF。

问题:

  • 有没有办法解决这个问题,或者也许有更好的方法来使用datatool包来解决这样的事情?

参考:

代码:

\documentclass{article}
\usepackage{datatool}
\usepackage{pgffor}

%\usepackage{filecontents}% Commented to prevent overwriting MyData.csv
\begin{filecontents*}{MyData.csv}
    Directory, Color
    ../dirB,    red
    ../dirC,    yellow
\end{filecontents*}

\begin{document}

\foreach \x in {1,...,10000} {%             Read DB numerous times
    \DTLifdbexists{MyDB}{\DTLdeletedb{MyDB}}{}%
    \DTLloadrawdb[keys={Directory,Color}]{MyDB}{MyData.csv}%
}%

\par\noindent\DTLdisplaydb{MyDB}%           Some output to have a PDF

\foreach \x in {1,...,10000} {%             Read DB numerous times
    \typeout{count=\x}%
    \DTLifdbexists{MyDB}{\DTLdeletedb{MyDB}}{}%
    \DTLloadrawdb[keys={Directory,Color}]{MyDB}{MyData.csv}%
}%

\end{document}

答案1

Joseph 已经解释了问题出现的原因。下面是一个可能的解决方法。我定义了一组宏,设置了两个令牌寄存器池和计数寄存器池,在\DTLdbnewdb调用时从中选择新的,在发出时重新插入到池中\DTLdeletedb

可以增加池的大小,使用以下宏将其限制为同时打开五个数据库,这应该足够了。

\usepackage{datatool}
\makeatletter

%%% Auxiliary macros
\def\pool@getnext#1{\expandafter\pool@getnext@aux\expandafter#1#1\@nil}
\def\pool@getnext@aux#1#2#3\@nil{\def#1{#3}\gdef\pool@temp{#2}}

\expandafter\def\expandafter\pool@strip@count\string\count#1\@nil{#1}
\expandafter\def\expandafter\pool@strip@toks\string\toks#1\@nil{#1}

\def\loop@add#1#2{\xdef#1{#1{\expandafter#2\meaning\pool@temp\@nil}}}

%%% Start with empty lists
\def\pool@count@list{}
\def\pool@toks@list{}
%%% Fill the lists; only 5 simultaneously open databases are allowed
\count@=\z@
\loop\ifnum\count@<10 % use 2n, where n is the maximum number of open db
  \advance\count@\@ne
  \newcount\pool@temp
  \loop@add\pool@count@list\pool@strip@count
  \newtoks\pool@temp
  \loop@add\pool@toks@list\pool@strip@toks
\repeat

%%% Allocation macros

\newcommand\pool@newcount[1]{%
  \ifx\pool@count@list\@empty
    \PackageError{datatool}{Count pool exhausted}{Too many open databases}
  \else
    \pool@getnewcount{#1}%
  \fi}

\newcommand\pool@getnewcount[1]{%
  \pool@getnext\pool@count@list
  \global\expandafter\countdef\csname#1\endcsname=\pool@temp
  \csname#1\endcsname=\z@
}
\newcommand\pool@newtoks[1]{%
  \ifx\pool@toks@list\@empty
    \PackageError{datatool}{Toks pool exhausted}{Too many open databases}
  \else
    \pool@getnewtoks{#1}%
  \fi}

\newcommand\pool@getnewtoks[1]{%
  \pool@getnext\pool@toks@list
  \global\expandafter\toksdef\csname#1\endcsname=\pool@temp
  \csname#1\endcsname={}%
}

\newcommand\pool@releasecount[1]{%
  \expandafter\let\expandafter\pool@temp\csname#1\endcsname
  \loop@add\pool@count@list\pool@strip@count
  \global\expandafter\let\csname#1\endcsname\@undefined}

\newcommand\pool@releasetoks[1]{%
  \expandafter\let\expandafter\pool@temp\csname#1\endcsname
  \loop@add\pool@toks@list\pool@strip@toks
  \global\expandafter\let\csname#1\endcsname\@undefined}

%%% Patch datatool

\renewcommand*{\DTLnewdb}[1]{%
  \DTLifdbexists{#1}%
  {%
     \PackageError{datatool}{Database `#1' already exists}{}%
  }%
  {%
    \dtl@message{Creating database `#1'}%
    \pool@newtoks{dtldb@#1}%
    \pool@newtoks{dtlkeys@#1}%
    \pool@newcount{dtlrows@#1}%
    \pool@newcount{dtlcols@#1}%
  }%
}

\renewcommand*{\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
    }%
    \pool@releasetoks{dtldb@#1}%
    \pool@releasetoks{dtlkeys@#1}%
    \pool@releasecount{dtlrows@#1}%
    \pool@releasecount{dtlcols@#1}%
  }%
  {%
    \PackageError{Can't delete database `#1':
       database doesn't exist}{}{}%
  }%
}
\makeatother

该池使用通常的\newtoks\newcount宏进行初始化,因此寄存器将被保留并且不会被其他包使用。

不幸的是,这似乎增加了执行时间。我会尝试看看是否可以减少。


另一种方式是修补宏,datatool以便如果数据库关闭并且其符号名称被重用,它们会重用计数器:

\makeatletter
\renewcommand*{\DTLnewdb}[1]{%
  \DTLifdbexists{#1}%
  {%
     \PackageError{datatool}{Database `#1' already exists}{}%
  }%
  {%
    \dtl@message{Creating database `#1'}%
    \global\expandafter\let\csname dtldb@#1@active\endcsname\@empty
    \@ifundefined{dtldb@#1@used}
      {\expandafter\newtoks\csname dtldb@#1\endcsname
       \expandafter\newtoks\csname dtlkeys@#1\endcsname
       \expandafter\newcount\csname dtlrows@#1\endcsname
       \expandafter\newcount\csname dtlcols@#1\endcsname}
      {\csname dtldb@#1\endcsname{}\csname dtlkeys@#1\endcsname{}%
       \csname dtlrows@#1\endcsname=\z@\csname dtlcols@#1\endcsname=\z@}
  }%
}
\renewcommand*{\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
    }%
    \global\expandafter\let\csname dtldb@#1@used\endcsname\@empty
    \global\expandafter\let\csname dtldb@#1@active\endcsname\relax
  }%
  {%
    \PackageError{Can't delete database `#1':
       database doesn't exist}{}{}%
  }%
}
\renewcommand{\DTLifdbexists}[3]{%
  \@ifundefined{dtldb@#1@active}{#3}{#2}}
\makeatother

还必须进行修补,\DTLifdbexists因为原始版本依赖于分配令牌寄存器之一的事实。因此,我定义\dtldb@MyDB@active数据库何时处于活动状态,并在删除时取消定义;当符号名称为“new”时,将分配并\dtldb@MyDB@used定义寄存器,这样,如果重复使用相同的名称,则不会分配新寄存器,并且会清除现有寄存器。

答案2

TeX 的寄存器可以通过名称或数字寻址。为了跟踪分配给名称的数字,必须有一个分配例程,它\newcount提供了一个接口。因此,当你这样做时

\newcount\foo

你定义\foo\count<number>,并标记<number>为“已采取”。如果你随后这样做

\let\foo\undefined

你有空\foo,但是不是将其标记<number>为可用。

LaTeX 分配例程不提供释放寄存器编号的机制,因此没有

\deletecount

或类似。原则上可以编写一个允许这种可能性的分配例程,但是,e-TeX 允许我们使用 32k 寄存器,并且 TeX 是一种排版系统,而不是通用编程语言。因此,从来都没有必要编写这样的分配例程。


请注意,仅使用 e-TeX 无法访问大多数类型的 32k 寄存器。您需要一个了解它们的分配例程,最容易实现的是使用

\usepackage{etex}

这在这里没有帮助,因为有 20k 个文件加载操作,并且datatool每个表使用两个计数,因此需要 40k 个!

答案3

我刚刚将 v2.13 上传到 CTAN。它可能需要一天左右的时间才能在镜像和发行版中传播,但一旦可用,您就可以使用下面的代码。最好的解决方案是只定义一次数据库(在循环之前)并在每次循环时清除它。这意味着您不会不断分配新的寄存器。新版本允许您调整\DTLloaddb和的行为,以便它们附加到命名的数据库而不是创建新的数据库。这是通过条件和打开和关闭它的命令(和)\DTLloadrawdb来完成的:\ifDTLnewdbload\DTLnewdbloadtrue\DTLnewdbloadfalse

\documentclass{article}
\usepackage{datatool}
\usepackage{pgffor}

\usepackage{filecontents}% Commented to prevent overwriting MyData.csv
\begin{filecontents*}{MyData.csv}
    Directory, Color
    ../dirB,    red
    ../dirC,    yellow
\end{filecontents*}

\begin{document}

\DTLnewdb{MyDB}
\DTLnewdbonloadfalse % don't let \DTLloaddb create a new database 

\foreach \x in {1,...,10000}{%             Read DB numerous times
    \typeout{count=\x}%
    \DTLcleardb{MyDB}% clear database
    \DTLloadrawdb[keys={Directory,Color}]{MyDB}{MyData.csv}%
}%

\par\noindent\DTLdisplaydb{MyDB}%           Some output to have a PDF

\foreach \x in {1,...,10000}{%             Read DB numerous times
    \typeout{count=\x}%
    \DTLcleardb{MyDB}% clear database
    \DTLloadrawdb[keys={Directory,Color}]{MyDB}{MyData.csv}%
}%

\end{document}

相关内容