请考虑以下 MWE-1:
\documentclass{article}
\usepackage{longtable}
\usepackage{datatool}
\newcommand*{\myKeyOne}{ID}
\newcommand*{\myKeyTwo}{First-Name}
\newcommand*{\myKeyThree}{Last-Name}
\newcommand*{\myHeaderOne}{\myKeyOne}
\newcommand*{\myHeaderTwo}{\myKeyTwo}
\newcommand*{\myHeaderThree}{\myKeyThree}
%There are two external files, my-CSV-File-A.txt and my-CSV-File-B.txt
%The contents of my-CSV-File-A.txt are
%ID,First-Name,Last-Name
%1,First-Name-One,Last-Name-One
%2,First-Name-Two,Last-Name-Two
%3,First-Name-Three,Last-Name-Three
%4,First-Name-Four,Last-Name-Four
%5,First-Name-Five,Last-Name-Five
%The contents of my-CSV-File-B.txt are
%ID,First-Name,Last-Name
%6,First-Name-Six,Last-Name-Six
%7,First-Name-Seven,Last-Name-Seven
%8,First-Name-Eight,Last-Name-Eight
%9,First-Name-Nine,Last-Name-Nine
%10,First-Name-OneZero,Last-Name-OneZero
%For MWE purposes, only 5 entries are shown in each file after the header/keys line. In reality, each file can contain up to 1000 lines.
%Using datatooltk, the following .dbtex files are generated:
%
%my-CSV-File-A.dbtex
%my-CSV-File-B.dbtex
%
%The corresponding database names are the same as the filenames.
%Load the databases:
\input{my-CSV-File-A.dbtex}%
\input{my-CSV-File-B.dbtex}%
\begin{document}
%Now, present the databases in tabular format:
This is for database {my-CSV-File-A}.
\begin{longtable}{ccc}%
\hline%
\myHeaderOne&\myHeaderTwo&\myHeaderThree%
\tabularnewline%
\hline%
\endfirsthead%
\hline%
\myHeaderOne&\myHeaderTwo&\myHeaderThree%
\tabularnewline%
\hline%
\endhead%
\DTLforeach*{my-CSV-File-A}%
{%
\myColumnOneValue=\myKeyOne,%
\myColumnTwoValue=\myKeyTwo,%
\myColumnThreeValue=\myKeyThree%
}%
{%
\myColumnOneValue&\myColumnTwoValue&\myColumnThreeValue%
\tabularnewline%
\hline%
}%
\end{longtable}
This is for database {my-CSV-File-B}.
\begin{longtable}{ccc}%
\hline%
\myHeaderOne&\myHeaderTwo&\myHeaderThree%
\tabularnewline%
\hline%
\endfirsthead%
\hline%
\myHeaderOne&\myHeaderTwo&\myHeaderThree%
\tabularnewline%
\hline%
\endhead%
\DTLforeach*{my-CSV-File-B}%
{%
\myColumnOneValue=\myKeyOne,%
\myColumnTwoValue=\myKeyTwo,%
\myColumnThreeValue=\myKeyThree%
}%
{%
\myColumnOneValue&\myColumnTwoValue&\myColumnThreeValue%
\tabularnewline%
\hline%
}%
\end{longtable}
\end{document}
MWE-1 没有问题。它编译得很好。现在,假设我有许多具有相同标题/键的数据库/表,并且假设还有许多标题/键,并且有数千个行条目。有些表是排序的结果,有些是改组的结果,也许有些是过滤的结果。因此,它们都具有相同的标题/键,也许我希望显示它们。为了最大限度地减少错误,我考虑对标题/键和行使用变量。
现在考虑 MWE-2:
\documentclass{article}
\usepackage{longtable}
\usepackage{datatool}
\newcommand*{\myKeyOne}{ID}
\newcommand*{\myKeyTwo}{First-Name}
\newcommand*{\myKeyThree}{Last-Name}
\newcommand*{\myHeaderOne}{\myKeyOne}
\newcommand*{\myHeaderTwo}{\myKeyTwo}
\newcommand*{\myHeaderThree}{\myKeyThree}
\newcommand*{\myHeader}{\myHeaderOne&\myHeaderTwo&\myHeaderThree}
\newcommand*{\myRow}{\myColumnOneValue&\myColumnTwoValue&\myColumnThreeValue}
\newcommand*{\myKeyAssignList}%
{%
\myColumnOneValue=\myKeyOne,%
\myColumnTwoValue=\myKeyTwo,%
\myColumnThreeValue=\myKeyThree%
}%
\input{my-CSV-File-A.dbtex}%
\input{my-CSV-File-B.dbtex}%
\begin{document}
\begin{longtable}{ccc}%
\hline%
\myHeader%
\tabularnewline%
\hline%
\endfirsthead%
\hline%
\myHeader%
\tabularnewline%
\hline%
\endhead%
\DTLforeach*{my-CSV-File-A}%
{\myKeyAssignList}%
{%
\myRow%
\tabularnewline%
\hline%
}%
\end{longtable}
\begin{longtable}{ccc}%
\hline%
\myHeader%
\tabularnewline%
\hline%
\endfirsthead%
\hline%
\myHeader%
\tabularnewline%
\hline%
\endhead%
\DTLforeach*{my-CSV-File-B}%
{\myKeyAssignList}%
{%
\myRow%
\tabularnewline%
\hline%
}%
\end{longtable}
\end{document}
看上去引入\myHeader
和\myRow
是可以的,但是引入\myKeyAssignList
导致编译不通过。
如果我使用函数来代替,例如,
\newcommand*{\myLongTableFunction}[5]%
{%
\begin{longtable}{#2}%
\hline%
#3%
\tabularnewline%
\hline%
\endfirsthead%
\hline%
#3%
\tabularnewline%
\hline%
\endhead%
\DTLforeach*{#1}%
{#4}%
{%
#5%
\tabularnewline%
\hline%
}%
\end{longtable}
}%
并调用以下命令:
\myLongTableFunction{my-CSV-File-A}{ccc}{\myHeader}{\myKeyAssignList}{\myRow}%
\myLongTableFunction{my-CSV-File-B}{ccc}{\myHeader}{\myKeyAssignList}{\myRow}%
它无法编译。
经过多次反复尝试,我发现错误出在\myKeyAssignList
。看来我无法将键分配列表“转换”为变量。
有什么方法可以为的键分配列表分配一个变量吗\DTLforeach
?
答案1
您的问题表明了您在清晰度和适度抽象方面的努力,这很好。不过,如果您能将其缩短并附上一个完整示例(如下面的示例),我将不胜感激,并且将来您可能会获得更多读者。例如,看看我如何使用:
\begin{filecontents*}{file-A.csv}
...
\end{filecontents*}
将您的数据库文件与 LaTeX 文档一起包含在内(这些文件是在您编译文档时编写的,默认情况下它们并不存在;有一个overwrite
选项可以在真正需要时使用)。
我认为您的主要问题在于您认为 TeX 宏的工作方式与大多数编程语言中的函数类似,其中评估表达式(例如f(a, b, c, ...)
评估参数a
、b
、c
等)。前 f
被调用。TeX 是一种宏语言,不能以这种方式工作;宏参数只是取代在宏展开时,用于#1
、等占位符。例如,使用以下定义:#2
\newcommand*{\zzz}[1]{abc#1def}
一个扩展步骤的\zzz{1+1}
收益abc1+1def
,和一个扩展步骤的\zzz{\what\ever}
收益abc\what\ever def
(在内部,后面没有空格\ever
:这只是“用户级语法”,以清楚地表明控制序列名称在之后停止ever
)。这是纯文本替换,无论我们是否可以“计算”1+1
或扩展\what\ever
。这种扩展不会自动发生。当需要遵守特定宏的语法时(在您的例子中:\DTLforeach*
),您需要自己处理扩展。
一些命令会自动对某些参数进行扩展,这就是为什么这似乎并不总是需要的原因。但除了在expl3
文档中,这一事实很少有文档记录,因此您需要:
明确处理扩展以自己准备参数,或者;
检查您正在使用的代码以确定它是否以及如何扩展其参数;
尝试通过反复试验来确定这些信息(效果不太好)。
当只需要对第一个参数进行一个扩展步骤时,\expandafter
这项工作就变得简单了:
\expandafter\someMacro\expandafter{\myMacro}{other arguments...}
当第一个参数只需要两个扩展步骤时,可以稍微修改一下:
\expandafter\someMacro
\expandafter\expandafter\expandafter{\myMacro}{other arguments...}
但可读性明显下降。当需要更多扩展步骤或需要扩展除第一个参数以外的其他参数时,其他技术更为实用(\edef
,在中间宏中交换参数,标记列表寄存器......)。该expl3
语言对这个问题有一个非常优雅和通用的解决方案:函数变体。阅读它这里如果您有兴趣,请阅读下面的第二个示例;在这篇文章中添加更多细节会让我们走得太远。
在您的情况下,您希望将这些标记作为第二个强制参数传递\DTLforeach*
:
\myColumnOneValue=ID,\myColumnTwoValue=First-Name,\myColumnThreeValue=Last-Name
\myKeyAssignList
这与传递单个标记(即使\myKeyAssignList
扩展到上述标记)完全不同。因此,我定义了一个包装器宏,\myDtlIteration
如下所示:
\newcommand*{\myDtlIteration}[2]{%
\DTLforeach*{#2}{#1}%
{%
\myRow
\tabularnewline
\hline
}%
}
在里面longtable
,我展开\myKeyAssignList
前包装器宏本身被扩展。包装器宏接收一步扩展\myKeyAssignList
作为其第一个参数。因此,包装器宏的#1
正是所需的标记\myColumnOneValue=ID,\myColumnTwoValue=First-Name, etc.
;因此,这就是\DTLforeach*
将作为其第二个强制参数的内容。
有了这些解释和下面代码中的注释,我相信你应该明白了。请注意,如果最后一个标记是,即名称仅包含字母(az 和 AZ)的控制序列标记,则无需%
在行末使用。\controlword
\begin{filecontents*}{file-A.csv}
ID,First-Name,Last-Name
1,First-Name-One,Last-Name-One
2,First-Name-Two,Last-Name-Two
3,First-Name-Three,Last-Name-Three
4,First-Name-Four,Last-Name-Four
5,First-Name-Five,Last-Name-Five
\end{filecontents*}
\begin{filecontents*}{file-B.csv}
ID,First-Name,Last-Name
6,First-Name-Six,Last-Name-Six
7,First-Name-Seven,Last-Name-Seven
8,First-Name-Eight,Last-Name-Eight
9,First-Name-Nine,Last-Name-Nine
10,First-Name-OneZero,Last-Name-OneZero
\end{filecontents*}
\documentclass{article}
\usepackage{longtable}
\usepackage{datatool}
\DTLloaddb{my-db-A}{file-A.csv}
\DTLloaddb{my-db-B}{file-B.csv}
\newcommand*{\myKeyOne}{ID}
\newcommand*{\myKeyTwo}{First-Name}
\newcommand*{\myKeyThree}{Last-Name}
% Beware, this doesn't “store” the current values of \myKeyOne, \myKeyTwo and
% \myKeyThree! You would need to expand them before executing the definition
% for that.
\newcommand*{\myHeaderOne}{\myKeyOne}
\newcommand*{\myHeaderTwo}{\myKeyTwo}
\newcommand*{\myHeaderThree}{\myKeyThree}
% Ditto: \myHeaderOne, \myHeaderTwo and \myHeaderThree aren't expanded now.
\newcommand*{\myHeader}{\myHeaderOne&\myHeaderTwo&\myHeaderThree}
% Ditto
\newcommand*{\myRow}{\myColumnOneValue&\myColumnTwoValue&\myColumnThreeValue}
\newcommand*{\myDefineAssignList}[4]{%
\newcommand*{#1}% use \def#1{...}% if you want to be able to overwrite #1
{%
\myColumnOneValue=#2,%
\myColumnTwoValue=#3,%
\myColumnThreeValue=#4%
}%
}
% If you have more than 8 columns, we'll need a different syntax (e.g.,
% \myDefineAssignList{\myKeyAssignList}{<comma-list>}).
\myDefineAssignList{\myKeyAssignList}{ID}{First-Name}{Last-Name}
% Use this to check the result:
% \show\myKeyAssignList
\newcommand*{\myDtlIteration}[2]{%
\DTLforeach*{#2}{#1}%
{%
\myRow
\tabularnewline
\hline
}%
}
\begin{document}
\begin{longtable}{ccc}%
\hline
\myHeader
\tabularnewline
\hline
\endfirsthead
\hline
\myHeader
\tabularnewline
\hline
\endhead
\expandafter\myDtlIteration\expandafter{\myKeyAssignList}{my-db-A}%
\end{longtable}
\begin{longtable}{ccc}%
\hline
\myHeader
\tabularnewline
\hline
\endfirsthead
\hline
\myHeader
\tabularnewline
\hline
\endhead
\expandafter\myDtlIteration\expandafter{\myKeyAssignList}{my-db-B}%
\end{longtable}
\end{document}
如果你确实想使用:
\myDefineAssignList{\myKeyAssignList}{\myKeyOne}{\myKeyTwo}{\myKeyThree}
代替:
\myDefineAssignList{\myKeyAssignList}{ID}{First-Name}{Last-Name}
然后,为了安全起见,传递给的三个参数和\myKeyOne
必须先展开一次,然后才能使用这些展开的结果来定义。正如我所说,使用或类型参数(例如,如果传递整数变量,效果会更好;这就是我在这里选择它的原因)相对容易做到:\myKeyTwo
myKeyThree
\myDefineAssignList
\myKeyAssignList
expl3
o
V
V
expl3
\usepackage{xparse}
(...)
\ExplSyntaxOn
\cs_new_protected:Nn \beethov_define_assign_list:Nnnn
{
% Use \cs_new:Nn if you want to have an error when #1 is already defined
\cs_set:Npn #1
{
\myColumnOneValue=#2,
\myColumnTwoValue=#3,
\myColumnThreeValue=#4
}
}
\cs_generate_variant:Nn \beethov_define_assign_list:Nnnn { NVVV }
\NewDocumentCommand \myDefineAssignList { m m m m }
{
\beethov_define_assign_list:NVVV #1 #2 #3 #4
}
\ExplSyntaxOff
\myDefineAssignList{\myKeyAssignList}{\myKeyOne}{\myKeyTwo}{\myKeyThree}
使用此技术的完整代码:
\begin{filecontents*}{file-A.csv}
ID,First-Name,Last-Name
1,First-Name-One,Last-Name-One
2,First-Name-Two,Last-Name-Two
3,First-Name-Three,Last-Name-Three
4,First-Name-Four,Last-Name-Four
5,First-Name-Five,Last-Name-Five
\end{filecontents*}
\begin{filecontents*}{file-B.csv}
ID,First-Name,Last-Name
6,First-Name-Six,Last-Name-Six
7,First-Name-Seven,Last-Name-Seven
8,First-Name-Eight,Last-Name-Eight
9,First-Name-Nine,Last-Name-Nine
10,First-Name-OneZero,Last-Name-OneZero
\end{filecontents*}
\documentclass{article}
\usepackage{xparse}
\usepackage{longtable}
\usepackage{datatool}
\DTLloaddb{my-db-A}{file-A.csv}
\DTLloaddb{my-db-B}{file-B.csv}
\newcommand*{\myKeyOne}{ID}
\newcommand*{\myKeyTwo}{First-Name}
\newcommand*{\myKeyThree}{Last-Name}
% Beware, this doesn't “store” the current values of \myKeyOne, \myKeyTwo and
% \myKeyThree! You would need to expand them before executing the definition
% for that.
\newcommand*{\myHeaderOne}{\myKeyOne}
\newcommand*{\myHeaderTwo}{\myKeyTwo}
\newcommand*{\myHeaderThree}{\myKeyThree}
% Ditto: \myHeaderOne, \myHeaderTwo and \myHeaderThree aren't expanded now.
\newcommand*{\myHeader}{\myHeaderOne&\myHeaderTwo&\myHeaderThree}
% Ditto
\newcommand*{\myRow}{\myColumnOneValue&\myColumnTwoValue&\myColumnThreeValue}
\ExplSyntaxOn
\cs_new_protected:Nn \beethov_define_assign_list:Nnnn
{
% Use \cs_new:Npn if you want to have an error when #1 is already defined.
\cs_set:Npn #1
{
\myColumnOneValue=#2,
\myColumnTwoValue=#3,
\myColumnThreeValue=#4
}
}
\cs_generate_variant:Nn \beethov_define_assign_list:Nnnn { NVVV }
\NewDocumentCommand \myDefineAssignList { m m m m }
{
\beethov_define_assign_list:NVVV #1 #2 #3 #4
}
\ExplSyntaxOff
% If you have more than 8 columns, we'll need a different syntax (e.g.,
% \myDefineAssignList{\myKeyAssignList}{<comma-list>}).
\myDefineAssignList{\myKeyAssignList}{\myKeyOne}{\myKeyTwo}{\myKeyThree}
% Use this to check the result:
% \show\myKeyAssignList
\newcommand*{\myDtlIteration}[2]{%
\DTLforeach*{#2}{#1}%
{%
\myRow
\tabularnewline
\hline
}%
}
\begin{document}
\begin{longtable}{ccc}%
\hline
\myHeader
\tabularnewline
\hline
\endfirsthead
\hline
\myHeader
\tabularnewline
\hline
\endhead
\expandafter\myDtlIteration\expandafter{\myKeyAssignList}{my-db-A}%
\end{longtable}
\begin{longtable}{ccc}%
\hline
\myHeader
\tabularnewline
\hline
\endfirsthead
\hline
\myHeader
\tabularnewline
\hline
\endhead
\expandafter\myDtlIteration\expandafter{\myKeyAssignList}{my-db-B}%
\end{longtable}
\end{document}
输出与上面相同。