模仿 LaTeX 的“目录”功能

模仿 LaTeX 的“目录”功能

我需要在较低级别模仿(=复制) LaTeX 的“目录”功能。

基本上,我想学习如何.aux在第一次运行中将条目写入文件LaTeX并在第二次运行中将它们包括在内。

我该怎么做,或者在哪里可以阅读有关此内容?

答案1

相关例程是\@starttoc和,\addcontentsline它们又使用\addtocontents\@writefile

我尝试解释该程序(toc此处代表tocloflot其他可能的“个人”缩写list of somethings

来自latex.ltx(LaTeX 核心文件)

\def\@starttoc#1{%
  \begingroup
    \makeatletter
    \@input{\jobname.#1}%
    \if@filesw
      \expandafter\newwrite\csname tf@#1\endcsname
      \immediate\openout \csname tf@#1\endcsname \jobname.#1\relax
    \fi
    \@nobreakfalse
  \endgroup}
\def\addcontentsline#1#2#3{%
  \addtocontents{#1}{\protect\contentsline{#2}{#3}{\thepage}}}
\long\def\addtocontents#1#2{%
  \protected@write\@auxout
      {\let\label\@gobble \let\index\@gobble \let\glossary\@gobble}%
      {\string\@writefile{#1}{#2}}}
\def\contentsline#1{\csname l@#1\endcsname}

我将把\@starttoc解释移到最后。

我们看到,\addtocontents除了使用 的格式之外,\contentsline实际上是\string\@writefile,即该字符串被写入.aux文件中。(旁注:\label\indexglossary在文件中被“吞噬”等“禁用” .aux

在第二次编译运行中,\@writefile最终将其内容设置为相关toc文件(即,,,.toc等).lof.lot

在我们找到命令之前的几行\@writefile,都有#1作为toc签名的。

\long\def\@writefile#1#2{%
  \@ifundefined{tf@#1}\relax
    {\@temptokena{#2}%
     \immediate\write\csname tf@#1\endcsname{\the\@temptokena}%
    }%
}

现在任何ToC- 相关文件都绑定到名为\tf@toc\tf@lof等的文件句柄,即\@writefile尝试写入文件句柄\tf@#1(您记得#1toc- 扩展名) - 它首先检查是否存在,如果存在,则将真实内容存储到写入的\tf@#1令牌中- 文件句柄宏调用必须以这种方式构造,因为它具有可变性()。\@temptokena\csname tf@#1\endcsname#1 as part of the name

现在回到\@starttoc

只要没有\@starttoc{toc}给出命令,文件句柄\tf@#1就不存在,因此文件\@writefile中的命令.aux不执行任何操作。

但首先,读取现有的\jobname.#1(即格式化的文件),除非未指定,否则会生成句柄,并且旧的将被“擦除”并再次写入,以便在后续运行中可能出现新的条目。toc\nofiles\tf@#1\jobname.#1

每个\tableof...\listof...命令使用\@starttoc{toc}等,至少在标准类的版本中,例如来自article.cls

\newcommand\tableofcontents{%
    \section*{\contentsname
        \@mkboth{%
           \MakeUppercase\contentsname}{\MakeUppercase\contentsname}}%
    \@starttoc{toc}%
    }

命令\section*在这里无关紧要——\@starttoc重要的是特征!

回到\@writefile

关键工具是 ,\@writefile它有两个参数。基本上你可以将任何东西写入.aux\@writefile从而创建真正的“内容”

以下是一个小示例文档 ( sampletoc.tex)

\documentclass{book}


\begin{document}
\tableofcontents
\chapter{One}
\section{First}
\section{Second}
\chapter{Two}
\section{First}
\section{Second}

\end{document}

...及其对应的sampletoc.aux文件

\relax 
\@writefile{toc}{\contentsline {chapter}{\numberline {1}One}{3}}
\@writefile{lof}{\addvspace {10\p@ }}
\@writefile{lot}{\addvspace {10\p@ }}
\@writefile{toc}{\contentsline {section}{\numberline {1.1}First}{3}}
\@writefile{toc}{\contentsline {section}{\numberline {1.2}Second}{3}}
\@writefile{toc}{\contentsline {chapter}{\numberline {2}Two}{5}}
\@writefile{lof}{\addvspace {10\p@ }}
\@writefile{lot}{\addvspace {10\p@ }}
\@writefile{toc}{\contentsline {section}{\numberline {2.1}First}{5}}
\@writefile{toc}{\contentsline {section}{\numberline {2.2}Second}{5}}

很明显,这\contentsline是按字面意思写的.toc,而章节和页码是从\addcontentsline使用时开始使用的(即\thesection等被扩展)

现在,程序如下:

  • 复制\@starttoc命令latex.ltx并将文件句柄名称更改为其他名称,例如\mypersonaltoc
  • 复制\@writefile并重命名为,然后\@mywritefile更改tf@#1mypersonaltoc(不带\\
  • 称呼\addtocontents{}{your content}

或者定义您自己的toc扩展,并在适当的地方johnB使用\addtocontents{johnB}{your content}和。\@starttoc{johnB}

答案2

除了 Christian 的精彩回答之外,我想提一下.aux文件中书写的一些更微妙的方面。

LaTeX 为此定义了一个文件句柄,即\@auxout,并提供了在文件中“安全”写入的功能\protected@write。此功能与普通的功能\write主要有以下三个不同之处:

  1. 强命令或\protected 命令是逐字写出来的(嗯,差不多,但这是一个小细节);

  2. 该命令有一个附加参数,因此必须将其调用为

    \protected@write<handle>{<settings>}{<general text>}
    

    (此处括号表示明确的);

  3. 代码在一个组中执行,其中\thepage设置为\relax,除了<settings>其中可以指定要逐字传递给底层\write命令的其他命令(这将在输出例程期间扩展它们)。

注意,底层\write会进行扩展,所以要逐字写出的命令前面必须加上\protect(或\string)。

\@auxout需要考虑的写入的其他方面是,.aux文件首先作为例程的一部分被读取\begin{document},但是在一个隐式组中,因此如果文件中的某些内容应该对文档产生影响,那么它必须是一个全局声明。

因此,如果运行以下测试文件

\documentclass{article}

\begin{document}

\makeatletter
\protected@write\@auxout{}{\protect\newcommand\protect\mytest{This is a test macro}}
\makeatother

\ifdefined\mytest
  Defined%
\else
  Undefined%
\fi

\end{document}

.aux文件将包含以下行

\newcommand \mytest {This is a test macro}

但每次运行时仍会打印“Undefined”。更改\protect\newcommand\gdef(不需要,\protect因为它不可扩展),第二次运行 LaTeX 时将打印“Defined”,因为文件.aux将包含

\gdef \mytest {This is a test macro}

.aux文件也是作为例程的一部分被读入的,\end{document}而这正是\@writefile函数执行其任务的地方。在第一次读取.aux文件时\begin{document}\@writefile被(本地)设置为\@gobbletwo

.aux特别是在文件上层找到的宏必须定义。例如,LaTeX 写成

\bibstyle{plain}
\bibdata{test}

发现

\bibliographystyle{plain}
\bibliography{test}

在文件中。latex.ltx然而

% latex.ltx, lines 6292-6293
\let\bibdata=\@gobble
\let\bibstyle=\@gobble

因此基本上文件中的两个命令.aux被忽略,因为它们仅用于 BibTeX。

答案3

尽管您提到了 TOC 功能,但您指出您的目标是:

基本上,我想学习如何在第一次运行 LaTeX 时将条目写入 .aux 文件并在第二次运行中包含它们。

我将重点讨论这个问题,而不是实际 TOC 功能的奥秘。这是一个与目录相关的简单用例:如果有图表列表,则命令将生成图表列表,否则不执行任何操作。以下是完整的实现:

\documentclass{article}

\makeatletter
\g@addto@macro\figure{\@ifundefined{so@therearefigures}
  {\immediate\write\@mainaux{\string\gdef\string\so@list@figures{1}}%
   \newcommand\@therearefigures{1}} {}}

\newcommand\autolistfigures{\@ifundefined{so@list@figures} {} {\listoffigures}}
\makeatother

\begin{document}
\autolistfigures

\section{The text}
\begin{figure}
\caption{There is a figure here}
\end{figure}

\end{document}

解释

为了能够检查是否有图形,我们挂钩了图形环境,以便它在\gdef\so@list@figures{1}第一次制作图形时将宏定义写入辅助文件。(我使用 TeX 命令,\gdef因为它不关心其参数是否已定义)。为了检测它是否已经在之前的调用中完成此操作,{figure}还定义(并检查)了第二个宏\@therearefigures

\g@addto@macro\figure{\@ifundefined{so@therearefigures}
  {\immediate\write\@mainaux{\string\gdef\string\so@list@figures{1}}%
   \newcommand\@therearefigures{1}} {}}

这就是全部内容;当重新运行 latex 时,将读入辅助文件并执行所有定义。我们的实现\autolistfigures只是检查是否\so@list@figures已定义。

\newcommand\autolistfigures{\@ifundefined{so@list@figures} {} {\listoffigures}}

请注意,我们写出的宏(\so@list@figures)必须与我们用来控制写入的宏(\so@therearefigures)不同;如果我们使用相同的宏,即使 LaTeX 文件不再包含任何图形,它也会将自身写回到辅助文件中!

相关内容