我需要在较低级别模仿(=复制) LaTeX 的“目录”功能。
基本上,我想学习如何.aux
在第一次运行中将条目写入文件LaTeX
并在第二次运行中将它们包括在内。
我该怎么做,或者在哪里可以阅读有关此内容?
答案1
相关例程是\@starttoc
和,\addcontentsline
它们又使用\addtocontents
和\@writefile
。
我尝试解释该程序(toc
此处代表toc
、lof
和lot
其他可能的“个人”缩写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
、\index
和glossary
在文件中被“吞噬”等“禁用” .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
(您记得#1
有toc
- 扩展名) - 它首先检查是否存在,如果存在,则将真实内容存储到写入的\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@#1
为mypersonaltoc
(不带\\
) - 称呼
\addtocontents{}{your content}
或者定义您自己的toc
扩展,并在适当的地方johnB
使用\addtocontents{johnB}{your content}
和。\@starttoc{johnB}
答案2
除了 Christian 的精彩回答之外,我想提一下.aux
文件中书写的一些更微妙的方面。
LaTeX 为此定义了一个文件句柄,即\@auxout
,并提供了在文件中“安全”写入的功能\protected@write
。此功能与普通的功能\write
主要有以下三个不同之处:
强命令或
\protect
ed 命令是逐字写出来的(嗯,差不多,但这是一个小细节);该命令有一个附加参数,因此必须将其调用为
\protected@write<handle>{<settings>}{<general text>}
(此处括号表示明确的);
代码在一个组中执行,其中
\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 文件不再包含任何图形,它也会将自身写回到辅助文件中!