使用 datatool 为 csv 文件中的每一行动态定义宏会产生错误结果

使用 datatool 为 csv 文件中的每一行动态定义宏会产生错误结果

请参见以下示例。我以为这会非常简单。但处理似乎被延迟(或分块进行),从而产生了错误的结果。

\documentclass{article}

\usepackage{filecontents}

\usepackage{datatool}

\begin{document}

\begin{filecontents}{\jobname.csv}
a, A, 100
b, B, 200
c, C, 300
d, D, 400
e, E, 500
f, F, 600
g, G, 700
h, H, 800
i, I, 900
\end{filecontents}


% Load database
\DTLloaddb[noheader]{\jobname}{\jobname.csv}

% Define macros
\DTLforeach{\jobname}{\Tag=Column1, \N=Column2, \V=Column3}{%
  \expandafter\def\csname\Tag\endcsname{\N}
  \expandafter\def\csname\Tag Value\endcsname{\V}}


\begin{enumerate}
\item \b, \bValue               % Should give B, 200
\item \d, \dValue               % D, 400
\item \i, \iValue               % I, 900
\end{enumerate}

\end{document}

查看产生的结果。

在此处输入图片描述

我怎样才能立即处理?

答案1

您需要使用\edef,否则\N\V将打印分配给它们的最后一个值。

无论如何,使用时应非常小心\def;例如,您正在重新定义\a\b\c,它们是排版重音字母的重要 LaTeX 命令。

我建议采用不同的策略:首先在组内处理数据库并使用\xdef(全局扩展定义),但使用可以使用“外部”命令恢复的“内部命令”。

\begin{filecontents}{\jobname.csv}
a, A, 100
b, B, 200
c, C, 300
d, D, 400
e, E, 500
f, F, 600
g, G, 700
h, H, 800
i, I, 900
\end{filecontents}

\documentclass{article}
\usepackage{datatool}

\newcommand{\getrowN}[1]{\csname masroor@row@#1@N\endcsname}
\newcommand{\getrowV}[1]{\csname masroor@row@#1@V\endcsname}

% Load database
\DTLloaddb[noheader]{\jobname}{\jobname.csv}

% Define macros
\begingroup
\DTLforeach{\jobname}{\Tag=Column1, \N=Column2, \V=Column3}{%
  \expandafter\xdef\csname masroor@row@\Tag@N\endcsname{\N}%
  \expandafter\xdef\csname masroor@row@\Tag@V\endcsname{\V}%
}
\endgroup

\begin{document}

\begin{enumerate}
\item \getrowN{b}, \getrowV{b}  % Should give B, 200
\item \getrowN{d}, \getrowV{d}  % D, 400
\item \getrowN{i}, \getrowV{i}  % I, 900
\end{enumerate}

\end{document}

在此处输入图片描述

通过使用组,您使用 或 的事实不会\def在组外传播,并且如果这些命令已经定义,它们将在 处恢复其行为。\Tag\N\V\endgroup

\b或 则有所不同\c,因为您在文档中需要它们。这就是使用\getrowN\getrowV来检索值的原因。

答案2

你几乎就成功了。

但...

问题 1:

\Tag\N和的定义\V将随着 -loop 的每次迭代而被覆盖\DTLforeach

因此这些宏需要在循环的每次迭代中展开。

\csname触发扩展事物(因此在您的示例中触发扩展\Tag),直到找到匹配的\endcsname

但是需要\N一些\V额外的步骤来确保在循环的每次迭代中没有标记\N/没有标记\V但是有标记的扩展/在定义文本内\N有标记的扩展。\V

\csname我稍微修改了你的例子,以表明在触发扩展的基础上,你可以使用\expandafter它来“欺骗”LaTeX 进行\N扩展\V

\documentclass{article}

\usepackage{filecontents}

\usepackage{datatool}

\begin{document}

\begin{filecontents}{\jobname.csv}
a, A, 100
b, B, 200
c, C, 300
d, D, 400
e, E, 500
f, F, 600
g, G, 700
h, H, 800
i, I, 900
\end{filecontents}


% Load database
\DTLloaddb[noheader]{\jobname}{\jobname.csv}

% Define macros
\DTLforeach{\jobname}{\Tag=Column1, \N=Column2, \V=Column3}{%
  \expandafter\def\csname\Tag\expandafter\endcsname\expandafter{\N}%
  \expandafter\def\csname\Tag Value\expandafter\endcsname\expandafter{\V}%
}%


\begin{enumerate}
\item \a, \aValue
\item \b, \bValue
\item \c, \cValue
\item \d, \dValue
\item \e, \eValue
\item \f, \fValue
\item \g, \gValue
\item \h, \hValue
\item \i, \iValue
\end{enumerate}

\end{document}

问题 2:

\a、、和已在 LaTeX 2ε 内核中定义。 - loop可能\b会覆盖已存在的定义。\c\d\i
\DTLforeach

因此我再次修改了你的例子:

  • 每个要定义的命令都有名称前缀MY
  • 而不是使用\defnow \newcommand*。如果覆盖了已经定义的内容,则会引发错误消息。
  • 该宏的用处\name如下所示:

    \name\newcommand{macro}...\newcommand\macro...
    \name\string{macro}\string\macro
    \name\name\let{macroA}={macroB}\name\let\macroA={macroB}\let\macroA=\macroB

 

\documentclass{article}

\newcommand\exchange[2]{#2#1}
\csname @ifdefinable\endcsname\name{\long\def\name#1#{\romannumeral0\innername{#1}}}%
\newcommand\innername[2]{%
  \expandafter\exchange\expandafter{\csname#2\endcsname}{ #1}%
}%

\usepackage{filecontents}

\usepackage{datatool}

\begin{document}

\begin{filecontents}{\jobname.csv}
a, A, 100
b, B, 200
c, C, 300
d, D, 400
e, E, 500
f, F, 600
g, G, 700
h, H, 800
i, I, 900
\end{filecontents}

% Load database
\DTLloaddb[noheader]{\jobname}{\jobname.csv}

% Define macros
\DTLforeach{\jobname}{\Tag=Column1, \N=Column2, \V=Column3}{%
  \name\expandafter\newcommand\expandafter*\expandafter{MY\Tag}\expandafter{\N}%
  \name\expandafter\newcommand\expandafter*\expandafter{MY\Tag Value}\expandafter{\V}%
}%

\begin{enumerate}
\item \MYa, \MYaValue
\item \MYb, \MYbValue
\item \MYc, \MYcValue
\item \MYd, \MYdValue
\item \MYe, \MYeValue
\item \MYf, \MYfValue
\item \MYg, \MYgValue
\item \MYh, \MYhValue
\item \MYi, \MYiValue
\end{enumerate}

\end{document}

除此之外,我还可以提供一个宏\ExtractKthArg,如果列表中存在第 K 个参数,则从非分隔参数列表中提取第 K 个参数。

您可以\DTLforeach为数据库的每一行定义一个宏,该宏扩展为表示该行列中条目的非分隔参数列表,并用于\ExtractKthArg检索该行的单个元素/列条目:

\documentclass{article}

\makeatletter
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Code for \ExtractKthArg
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond, 
%%    \UD@CheckWhetherNull,
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@PassFirstBehindThirdToSecond[3]{#2{#3}{#1}}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%=============================================================================
%% Extract K-th inner undelimited argument:
%%
%% \ExtractKthArg{<integer K>}%
%%               {<list of non-delimited args>}%
%%               {<tokens in case K is not positive>}%
%%               {<tokens in case list has less than K args>}%
%% 
%% In case K is not positive:
%%   Does deliver <tokens in case K is not positive>.
%% In case K is positive but there is no K-th argument in <list of non-delimited args> : 
%%   Does deliver <tokens in case list has less than K args>.
%% In case there is a K-th argument in <list of non-delimited args> : 
%%   Does deliver that K-th argument with one level of braces removed.
%%
%% Due to \romannumeral0-expansion the result is delivered after two
%% expansion-steps/after two "hits" with \expandafter.
%%
%% Examples:
%%
%%   \ExtractKthArg{0}{ABCDE}{K not positive}{No Kth Element in list} yields: K not positive
%%
%%   \ExtractKthArg{3}{ABCDE}{K not positive}{No Kth Element in list} yields:  C
%%
%%   \ExtractKthArg{3}{AB{CD}E}{K not positive}{No Kth Element in list} yields:  CD
%%
%%   \ExtractKthArg{4}{{001}{002}{003}{004}{005}}{K not positive}{No Kth Element in list} yields: 004
%%
%%   \ExtractKthArg{6}{{001}{002}{003}}{K not positive}{No Kth Element in list} yields: No Kth Element in list
%% 
%%=============================================================================
\newcommand\ExtractKthArg[1]{%
  % #1: <integer number K>
  \romannumeral0%
  \expandafter\UD@ExtractKthArgCheck
  \expandafter{\romannumeral\number\number#1 000}%
}%
\newcommand\UD@ExtractKthArgCheck[4]{%
  % #1: <Amount of letters "m" corresponding to number K>
  % #2: <list of non-delimited args>
  % #3: <tokens in case K is not positive>
  % #4: <tokens in case list has less than K args>
  \UD@CheckWhetherNull{#1}{ #3}{%
    \expandafter\UD@ExtractKthArgLoop\expandafter{\UD@firstoftwo{}#1}{#2}{#4}%
  }%
}%
\newcommand\UD@ExtractKthArgLoop[3]{%
  % #1: <remaining amount of letters "m">
  % #2: <(remaining) list of non-delimited args>
  % #3: <tokens in case list has less than K args>
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo#2{}.}{ #3%
  }{%
    \UD@CheckWhetherNull{#1}{%
      \UD@ExtractFirstArgLoop{#2\UD@SelDOm}%
    }{%
      \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#2}%
      {\expandafter\UD@ExtractKthArgLoop\expandafter{\UD@firstoftwo{}#1}}%
      {#3}%
    }%
  }%
}%
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  % #1: <(remaining) list of non-delimited args>
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\UD@firstoftwo{\expandafter}{} \UD@secondoftwo{}#1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%% End of code for \ExtractKthArg.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\makeatother

\usepackage{filecontents}

\usepackage{datatool}

\begin{document}

\begin{filecontents}{\jobname.csv}
a, A, 100
b, B, 200
c, C, 300
d, D, 400
e, E, 500
f, F, 600
g, G, 700
h, H, 800
i, I, 900
\end{filecontents}

% Load database
\DTLloaddb[noheader]{\jobname}{\jobname.csv}

\makeatletter
% Define macro for each row:
\DTLforeach{\jobname}{\Tag=Column1, \N=Column2, \V=Column3}{%
   \expandafter\@gobble\string{%
      \expandafter\UD@PassFirstBehindThirdToSecond\expandafter{\V}{%
        \expandafter\UD@PassFirstBehindThirdToSecond\expandafter{\N}{%
          \expandafter\UD@PassFirstBehindThirdToSecond\expandafter{\Tag}{%
            \UD@secondoftwo{}%
          }%
        }%
      }%
      {\expandafter\newcommand\csname Row\Tag%
      \expandafter\expandafter\expandafter\endcsname
      \expandafter\expandafter\expandafter{\expandafter\@gobble\string}}%
   }%
   %\expandafter\show\csname Row\Tag\endcsname
}%
\newcommand\GenericGetElementOfRow[4]{%
  % #1 = Entry in field "\Tag=Column 1" (, the primary key for rows)
  % #2 = Column-Number (beginning with 1)
  % #3 = <Tokens in case row is undefined>
  % #4 = <Tokens in case row is defined but does not have an element in column #2>
  \@ifundefined{Row#1}{%
    #3%
  }{%
    \expandafter\expandafter\expandafter\UD@PassFirstToSecond
    \expandafter\expandafter\expandafter{\csname Row#1\endcsname}%
    {\ExtractKthArg{#2}}%
    {}{#4}%
  }%
}%
\newcommand\GetTagofRow[1]{%
  % #1 = Entry in field "\Tag=Column 1" (, the primary key for rows)
  \GenericGetElementOfRow{#1}{1}{%
    % Tokens in case row is undefined:
    \nfss@text{\reset@font\bfseries??}%
  }{%
    % Tokens in case row defined but does not have K-th element:
    \nfss@text{\reset@font\bfseries??}%
  }%
}%
\newcommand\GetNofRow[1]{%
  % #1 = Entry in field "\Tag=Column 1" (, the primary key for rows)
  \GenericGetElementOfRow{#1}{2}{%
    % Tokens in case row is undefined:
    \nfss@text{\reset@font\bfseries??}%
  }{%
    % Tokens in case row defined but does not have K-th element:
    \nfss@text{\reset@font\bfseries??}%
  }%
}%
\newcommand\GetVofRow[1]{%
  % #1 = Entry in field "\Tag=Column 1" (, the primary key for rows)
  \GenericGetElementOfRow{#1}{3}{%
    % Tokens in case row is undefined:
    \nfss@text{\reset@font\bfseries??}%
  }{%
    % Tokens in case row defined but does not have K-th element:
    \nfss@text{\reset@font\bfseries??}%
  }%
}%
\makeatother

\begin{itemize}
\item[\GetTagofRow{a}:] \GetNofRow{a}, \GetVofRow{a}
\item[\GetTagofRow{b}:] \GetNofRow{b}, \GetVofRow{b}
\item[\GetTagofRow{c}:] \GetNofRow{c}, \GetVofRow{c}
\item[\GetTagofRow{d}:] \GetNofRow{d}, \GetVofRow{d}
\item[\GetTagofRow{e}:] \GetNofRow{e}, \GetVofRow{e}
\item[\GetTagofRow{f}:] \GetNofRow{f}, \GetVofRow{f}
\item[\GetTagofRow{g}:] \GetNofRow{g}, \GetVofRow{g}
\item[\GetTagofRow{h}:] \GetNofRow{h}, \GetVofRow{h}
\item[\GetTagofRow{i}:] \GetNofRow{i}, \GetVofRow{i}
\item[\GetTagofRow{j}:] \GetNofRow{j}, \GetVofRow{j}
\end{itemize}

\end{document}

相关内容