我正在尝试获取一个可以接受可变数量的 csv 文件作为参数的函数,然后将它们连接到单个数据库中Latex 的数据工具。数据库都是统一格式(相同的列),只需要附加行即可。
我的尝试是制作一个受此启发的递归函数博客文章。
\documentclass{article}
\makeatletter
\newcommand{\recursive}[1]{#1 \@ifnextchar\bgroup{\recursive}{}}
\makeatother
\begin{document}
\recursive{lorem ipsum}\par
\recursive{A}{B}{C}{1}\par
\recursive{1}{2}{3}{4}{5}{6}{7}
\end{document}
<dbX>
但我无法唯一地命名数据库,因此出现错误。也许可以通过在参数中加载数据库、将内容添加到当前主数据库<dbY>
并清除<dbX>
以便可以重复该过程来工作?
\documentclass{article}
\usepackage{datatool}
\DTLsetseparator{;}
%dbA
\begin{filecontents*}{databaseA.csv}
first; second
A; B
C; D
E; F
\end{filecontents*}
%dbB
\begin{filecontents*}{databaseB.csv}
first; second
G; H
I; J
K; L
M; N
\end{filecontents*}
%dbC
\begin{filecontents*}{databaseC.csv}
first; second
O; P
\end{filecontents*}
\makeatletter
\newcommand{\recursive}[1]{\DTLloadrawdb{dbA}{#1}\@ifnextchar\bgroup{\recursive}{}}
\makeatother
\recursive{databaseA.csv}{databaseB.csv}{databaseC.csv}
挑战的第二部分是如何将行附加到数据库中。还有更多复杂的例子如何连接额外的列和行。但我希望有一个不太复杂的命令,只需循环遍历所有行并将它们附加到一行中,而不是逐个值地添加,甚至更好的是,只需在一个命令中合并两个数据库。
答案1
在包 datatool 的手册中只需阅读有关\DTLnewdbonloadtrue
和的内容\DTLnewdbonloadfalse
:
默认情况下,在加载文件中给出的数据之前,
\DTLloaddb
会创建一个名为 的新数据库。⟨db name⟩
⟨filename⟩
如果你想附加数据,
\DTLnewdbonloadfalse
在使用之前使用\DTLloaddb
。您可以使用
\DTLnewdbonloadtrue
\documentclass{article}
\usepackage{datatool}
\DTLsetseparator{;}
%dbA
\begin{filecontents*}{databaseA.csv}
first; second
A; B
C; D
E; F
\end{filecontents*}
%dbB
\begin{filecontents*}{databaseB.csv}
first; second
G; H
I; J
K; L
M; N
\end{filecontents*}
%dbC
\begin{filecontents*}{databaseC.csv}
first; second
O; P
\end{filecontents*}
\makeatletter
\newcommand\LoadNoMoreDatabases{\LoadNoMoreDatabases}%
\newcommand\LoadDatabases[1]{\DTLnewdbonloadtrue\LoadDatabasesLoop{\DTLnewdbonloadfalse}{#1}}
\newcommand\LoadDatabasesLoop[3]{%
% #1 tokens to execute after \DTLloadrawdb
% #2 name of database
% #3 name of csv-file or end-marker for the loop.
\ifx\LoadNoMoreDatabases#3\expandafter\@secondoftwo\else\expandafter\@firstoftwo\fi
{\DTLloadrawdb{#2}{#3}#1\LoadDatabasesLoop{}{#2}}%
{\DTLnewdbonloadtrue}%
}%
\makeatother
% Syntax of the mechanism:
%
% \LoadDatabases{<database to create>}{<csv-file 1>}{<csv-file 2>}...{<csv-file k>}\LoadNoMoreDatabases
\LoadDatabases{db}{databaseA.csv}{databaseB.csv}{databaseC.csv}\LoadNoMoreDatabases
\begin{document}
\DTLdisplaydb{db}
\end{document}
以上确实
\DTLnewdbonloadtrue
\DTLloadrawdb{⟨database to create⟩}{⟨csv-file 1⟩}%
\DTLnewdbonloadfalse
\DTLloadrawdb{⟨database to create⟩}{⟨csv-file 2⟩}%
\DTLloadrawdb{⟨database to create⟩}{⟨csv-file 3⟩}%
...
\DTLloadrawdb{⟨database to create⟩}{⟨csv-file k-1⟩}%
\DTLloadrawdb{⟨database to create⟩}{⟨csv-file k⟩}%
\DTLnewdbonloadtrue
您可以轻松地使用\clist_map_inline:nn
expl3 的 l3clist 包来创建一个宏,您可以在其中传递文件名的逗号列表。
\documentclass{article}
\usepackage{xparse}
\usepackage{datatool}
\DTLsetseparator{;}
%dbA
\begin{filecontents*}{databaseA.csv}
first; second
A; B
C; D
E; F
\end{filecontents*}
%dbB
\begin{filecontents*}{databaseB.csv}
first; second
G; H
I; J
K; L
M; N
\end{filecontents*}
%dbC
\begin{filecontents*}{databaseC.csv}
first; second
O; P
\end{filecontents*}
\ExplSyntaxOn
\NewDocumentCommand\LoadDatabases{mm}{
\DTLnewdb{#1} % <- create the new empty database
\DTLnewdbonloadfalse % <- let's append to that database
\clist_map_inline:nn {#2} {\DTLloadrawdb{#1}{##1}} % <- have a sequence of calls `\DTLloadrawdb
\DTLnewdbonloadtrue % <- switch back to \DTLload(raw)db not appending to an existing database but to creating databases anew.
}
\ExplSyntaxOff
\LoadDatabases{db}{databaseA.csv, databaseB.csv, databaseC.csv}
\begin{document}
\DTLdisplaydb{db}
\end{document}
代码解释:
2021-08-27 发行LaTeX3 接口在章节中说22.5 逗号列表映射:
\clist_map_inline:Nn
\clist_map_inline:cn
\clist_map_inline:nn
更新时间:2012-06-29
\clist_map_inline:Nn ⟨comma list⟩ {⟨inline function⟩}
适用于存储在 中的
⟨inline function⟩
每个 。 应由接收的代码组成。从左到右返回 。⟨item⟩
⟨comma list⟩
⟨inline function⟩
⟨item⟩
#1
⟨items⟩
我觉得这个解释的最后一句话有点不准确,因为返回的不仅仅是项目。返回的 token 序列数量与非空白项目的数量相同。⟨逗号列表⟩. 每个 token 序列都由一组 token 组成,这些 token 被称为⟨内联函数⟩,其中在该分类中,序列#1
被由相应项目组成的那些标记所替换。
定义后
\NewDocumentCommand\LoadDatabases{mm}{
\DTLnewdb{#1}
\DTLnewdbonloadfalse
\clist_map_inline:nn {#2} {\DTLloadrawdb{#1}{##1}}
\DTLnewdbonloadtrue
}
序列\LoadDatabase{db}{databaseA.csv, databaseB.csv, databaseC.csv}
产量:
\DTLnewdb{db}
\DTLnewdbonloadfalse
\clist_map_inline:nn {databaseA.csv, databaseB.csv, databaseC.csv} {\DTLloadrawdb{db}{#1}}
\DTLnewdbonloadtrue
(在扩展过程中,\LoadDatabases
两个连续的哈希值##
会##1
折叠成一个哈希值#
。)
\clist_map_inline:nn
的⟨逗号列表⟩是:databaseA.csv, databaseB.csv, databaseC.csv
\clist_map_inline:nn
的⟨内联函数⟩是:\DTLloadrawdb{db}{#1}
在⟨内联函数⟩ #1
表示⟨逗号列表⟩,即.csv 文件的名称。
因此\clist_map_inline:nn
得到的结果类似
\DTLloadrawdb{db}{databaseA.csv}%
\DTLloadrawdb{db}{databaseB.csv}%
\DTLloadrawdb{db}{databaseC.csv}%
答案2
以下是使用包 (v3.0) 的方法readarray
。我将每个文件读入其自己的数组,然后开始合并数组,从而消除除第一个数组之外的所有数组的标题行。
最终结果的\arrayjoined
大小为[9,2]
。可以访问单个元素,例如,\arrayjoined[5,2]
获取第 5 行、第 2 列的数据条目。
%dbA
\begin{filecontents*}{databaseA.csv}
first; second
A; B
C; D
E; F
\end{filecontents*}
%dbB
\begin{filecontents*}{databaseB.csv}
first; second
G; H
I; J
K; L
M; N
\end{filecontents*}
%dbC
\begin{filecontents*}{databaseC.csv}
first; second
O; P
\end{filecontents*}
\documentclass{article}
\usepackage{readarray}[2021-08-08]
\begin{document}
\readarraysepchar{;}
\readdef{databaseA.csv}\dbA
\readarray\dbA\arrayA[-,2]
\readdef{databaseB.csv}\dbB
\readarray\dbB\arrayB[-,2]
\readdef{databaseC.csv}\dbC
\readarray\dbC\arrayC[-,2]
\initarray\arrayjoined[\the\numexpr
\arrayAROWS+\arrayBROWS+\arrayCROWS-2\relax,2]
\mergearray\arrayC\arrayjoined[\the\numexpr
\arrayAROWS+\arrayBROWS-1\relax,1]
\mergearray\arrayB\arrayjoined[\arrayAROWS,1]
\mergearray\arrayA\arrayjoined[1,1]
\typesetarray\arrayjoined
\end{document}
如果允许完全扩展文件内容,下面的方法可能更简单……在这里我使用宏来连接三组文件数据,同时剥离不必要的标题。
%dbA
\begin{filecontents*}{databaseA.csv}
first; second
A; B
C; D
E; F
\end{filecontents*}
%dbB
\begin{filecontents*}{databaseB.csv}
first; second
G; H
I; J
K; L
M; N
\end{filecontents*}
%dbC
\begin{filecontents*}{databaseC.csv}
first; second
O; P
\end{filecontents*}
\newcommand\stripheader[1]{\expandafter\stripheaderaux#1\relax}
\def\stripheaderaux#1;#2;#3\relax{#3}
\documentclass{article}
\usepackage{readarray}[2021-08-08]
\begin{document}
\readarraysepchar{;}
\readdef{databaseA.csv}\dbA
\readdef{databaseB.csv}\dbB
\readdef{databaseC.csv}\dbC
\edef\dbjoined{\dbA\stripheader\dbB\stripheader\dbC}
\readarray\dbjoined\arrayjoined[-,2]
\typesetarray\arrayjoined
\end{document}
注意:如果希望“调节”输出\typesetarray
以用于tabular
格式,那么只需要调节它,如下
\renewcommand\typesetcolsepchar{&}
\renewcommand\typesetrowsepchar{\\}
\begin{tabular}{ll}
\typesetarray\arrayjoined
\end{tabular}
答案3
我设法自己完成了一些工作,所以这是我最好的尝试。也许其他人会找到更优雅的解决方案。
\documentclass{article}
\usepackage{datatool}
\DTLsetseparator{;}
\DTLnewdb{db}
\makeatletter
\newcommand{\AddAllDBs}[1]{
\DTLloadrawdb{tmpDB}{#1}
\DTLforeach*{tmpDB}{\DBfirst=first,\DBsecond=second}{
\DTLnewrow{db}
{\let\DTLnewdbentry\relax
\protected@xdef\insertnewdbentry{%
\DTLnewdbentry{db}{first}{\DBfirst}%
\DTLnewdbentry{db}{second}{\DBsecond}%
}}\insertnewdbentry
}
\DTLdeletedb{tmpDB}
\@ifnextchar\bgroup{\AddAllDBs}{}
}
\makeatother
\begin{document}
\AddAllDBs{databaseA.csv}{databaseB.csv}{databaseC.csv}
\DTLdisplaydb{db}
\end{document}