开始尝试使用自定义命令,并希望能够帮助理解 LaTeX 黑客代码

开始尝试使用自定义命令,并希望能够帮助理解 LaTeX 黑客代码

这个问题可能一下子要回答很多,但我在一个旧帖子中看到了一些代码(粘贴在下面),但我真的不明白它是如何工作的。如果有人能仔细阅读代码并解释一下,我将不胜感激?(特别是 in 是如何#工作\def\Assign#1的,\ifcsname以及 是\global如何操作的)

\documentclass{article}

\makeatletter
\def\Assign#1{% if \DP@#1 is defined append to it otherwise create it
  \ifcsname DP@#1\endcsname
    \edef\DP@tmp{\csname DP@#1\endcsname,\@currentlabel}%
  \else
    \edef\DP@tmp{\@currentlabel}%
  \fi
  \global\expandafter\let\csname DP@#1\endcsname\DP@tmp
  \typeout{ASSIGN: \csname DP@#1\endcsname}
}
\def\Assigned#1{%
  % \expandafter\def\csname Assigned#1\endcsname{\csname DP@#1\endcsname}
  \typeout{ASSIGNED: \csname DP@#1\endcsname}
}
\makeatother

\begin{document}
\begin{enumerate}
  \item Apples \Assign{fred}  
  \item Oranges \Assign{julie}
  \item Bananas \Assign{fred}
  \item Mangos \Assign{julie}
  \item Strawberries \Assign{julie}
\end{enumerate}

\Assigned{fred}   % should print 1,3  
\Assigned{julie}  % should print 2,4,5
\end{document}

答案1

\def\foo#1{...#1...}是定义单参数宏的原始 TeX 方式。

在 LaTeX 环境中,代码应该具有

\newcommand*{\Assign}[1]{...}

\newcommand*{\Assigned}[1]{...}

以检查宏是否已经定义,并且不会对奇怪的结果感到惊讶。

代码的目的是创建一个用逗号分隔的数字列表数组,每个项目都与一个名称相关联,并包含分配给此人的任务列表。

任务编号是通过 获得的\@currentlabel,它扩展为最后一个用 步进的计数器的表示\refstepcounter;在 内enumerate\@currentlabel将扩展为当前项目编号(带有前缀,对于第一级enumerate环境,该前缀为空)。

项目列表是逐步构建的,并存储在名为 的宏中\DP@<name>;对于用户fred,宏是\DP@fred。但是,\DP@#1由于 TeX 读取输入并将其拆分为标记的方式,使用 在代码中不起作用,因此\csname DP@#1\endcsname必须使用 来形成正确的宏名称。

\ifcsname...\endcsname如果由此产生的控制序列具有定义,则条件返回 true \csname...\endcsname,否则返回 false。代码使用此方法测试是否\DP@<name>存在:在第一种情况下,下一个项目编号将附加在逗号后,否则仅附加编号。

例如,第一个\Assign{fred}将定义\DP@fred1;第二个将附加,3

代码以间接的方式执行此操作,使用临时宏\DP@tmp(这是一个糟糕的选择,因为如果其中一个用户名是tmp混乱就会随之而来)。

在进行宏定义之前,对替换文本进行\edef\DP@tmp{...}完全扩展,以完成预期的工作。

接下来,\DP@<name>被赋予与临时宏相同的含义;这必须全局完成,因为本地宏定义会在环境结束时被遗忘enumerate

为控制序列分配与另一个标记相同含义的原始指令是\let;如果以 为前缀\global,则分配是全局的。但是在这种情况下,我们需要使用\csname来形成受让人,但是

\global\let\csname DP@#1\endcsname\DP@tmp

不会起作用,因为它会\csname与 相同D。控制序列标记必须在\let进入操作之前形成,因此\let必须在 之前\expandafter。根据 TeX 规则,\global前缀会递归扩展下一个标记,直到找到可以合法添加前缀的东西(\let\def以及一些其他原始命令)。因此

\global\expandafter\let\csname DP@#1\endcsname\DP@tmp

实际上,使用临时宏的中间步骤并不是真正需要的:

\makeatletter
\newcommand*{\Assign}[1]{% if \DP@#1 is defined append to it otherwise create it
  \ifcsname DP@#1\endcsname
    \expandafter\xdef\csname DP@#1\endcsname{\csname DP@#1\endcsname,\@currentlabel}%
  \else
    \expandafter\xdef\csname DP@#1\endcsname{\@currentlabel}%
  \fi
  \typeout{ASSIGN: \csname DP@#1\endcsname}
}
\newcommand*{\Assigned}[1]{%
  % \expandafter\def\csname Assigned#1\endcsname{\csname DP@#1\endcsname}
  \typeout{ASSIGNED: \csname DP@#1\endcsname}
}
\makeatother

也可以这样做,并避免与名为 的用户可能出现的问题tmp

定义中的注释\Assigned行将生成一个“用户级”宏\Assigned<name>。如果取消注释,则调用\Assigned{fred}将在日志文件和控制台中打印任务列表,之后\Assignedfred相当于\DP@fred

实现相同目标的更好方法是

\global\expandafter\let\csname Assigned#1\expandafter\endcsname\csname DP@#1\endcsname

我添加\global它的原因与之前相同。

相关内容