为 \DTLforeach 的键分配列表分配一个变量

为 \DTLforeach 的键分配列表分配一个变量

请考虑以下 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, ...)评估参数abc等)。 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必须先展开一次,然后才能使用这些展开的结果来定义。正如我所说,使用或类型参数(例如,如果传递整数变量,效果会更好;这就是我在这里选择它的原因)相对容易做到:\myKeyTwomyKeyThree\myDefineAssignList\myKeyAssignListexpl3oVVexpl3

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

输出与上面相同。

相关内容