我有一个代码函数,需要使用临时文件来保存部分代码,这些代码稍后需要在其他代码中使用。我将内容简化到最低限度以表示下面的问题。但命令\immediate\write
无法正确处理参数值中的斜杠。如何确保安全并将所有内容视为字符串?
\documentclass{article}
\usepackage[utf8]{inputenc}
\immediate\newwrite\matchtwoleft
\immediate\openout\matchtwoleft=matchtwoleft.aux
\newcommand{\match}[1]{%
\immediate\write\matchtwoleft{#1}%
}
\begin{document}
%this works
\match{an expos\string\'e}
%this doesn't
\match{an expos\'e}
\end{document}
文件 cat matchtwoleft.aux 的输出
an expos\'e
an expos\unhbox \voidb@x \bgroup \let \unhbox \voidb@x \setbox \@tempboxa \hbox {e\global \mathchardef \accent@spacefactor \spacefactor }\accent 19 e\egroup \spacefactor \accent@spacefactor
知道如何才能使 \match 命令的代码对于斜线来说是安全的吗?
答案1
您需要“受保护的写入”。内核不提供此功能,但添加它很容易。
\documentclass{article}
\usepackage[utf8]{inputenc}
\newwrite\matchtwoleft
\immediate\openout\matchtwoleft=\jobname-write.dat
\makeatletter
\long\def\protected@iwrite#1#2#3{%
\begingroup
%\let\thepage\relax % useless here
#2%
\let\protect\@unexpandable@protect
\edef\reserved@a{\immediate\write#1{#3}}%
\reserved@a
\endgroup
\if@nobreak\ifvmode\nobreak\fi\fi
}
\newcommand{\match}[1]{%
\protected@iwrite\matchtwoleft{}{#1}%
}
\makeatother
\begin{document}
%this works
\match{an expos\string\'e}
%this doesn't
\match{an expos\'e -- exposé -- gar\c con -- garçon}
\immediate\closeout\matchtwoleft
\end{document}
书面文件将包含
an expos\'e
an expos\'e -- exposé -- gar\c con -- garçon
答案2
在提供一些示例之前,我先来补充一些关于在 LaTeX 中创建和写入外部文本文件的评论。
关于
\immediate\newwrite\matchtwoleft
\write
- 在传统 TeX 中,文本文件的句柄编号从 0 到 15。
\newwrite
只是一种为特定句柄分配名称(就控制序列的名称而言)的方法\write
,无论当前是否有文本文件(通过\openout
或\immediate\openout
)与该文件相关联\write
并打开以进行写入。
因此您不需要\immediate
使用\newwrite
。基本上\newwrite
只有 (通过\chardef
) 定义一个控制序列来产生一个表示\write
句柄编号的数字。\chardef
定义表示句柄编号的控制序列的操作\write
将立即执行,而 的操作则\immediate
没有效果。
\newwrite
只是一种根据控制序列的名称为句柄编号分配名称的方法\write
,从而确保您不会尝试使用超过\write
可用数量的句柄,也不会错误地处理\write
已经用于其他目的的句柄。
正如您在问题附带的代码中所展示的那样,可以通过以下方式将名称(根据通过定义的控制序列的名称\chardef
)分配给-handle\write
\newwrite
) 。
将文件分配给\write
-handle 并重新创建该文件(如果存在则销毁它的先前实例)并打开它进行写入是通过 完成的\openout
。
通过 写入文件\write
。
关闭文件并删除\write
-handle 和文件之间的关联是通过\closeout
(或通过结束 (La)TeX-run)来完成的。
在写入外部文本文件时,(La)TeX 存在四个主要问题:
问题 1:当 (La)TeX 遇到相应的\write
指令时,是否应立即进行写入,还是应延迟写入,直到 (La)TeX 即将创建的页面被传送到输出文件 = .dvi 文件或 .pdf 文件时才进行写入?
如果在\openout
/ \write
/前面\closeout
加上\immediate
,那么相应的操作不会延迟,直到将 (La)TeX 即将创建的页面传送到 output-file=.dvi-file 或 .pdf-file 时遇到这些指令,而是立即执行相应的操作。
拥有用于延迟/不延迟\write
操作等的方法非常重要:原因是:由于输出例程的异步性,诸如页码之类的信息只有在将页面传送到输出文件时才可靠地可用。将页面传送到输出文件的时间是文本短语最终所在的页码唯一可靠可用的时刻。在之前,您无法可靠地预测该短语是否会出现在 LaTeX 即将创建的页面上,或者它是否会出现在该页面的下一页面上。例如,在将引用标签的信息写入 .aux 文件时,其中还包含用于\pageref
可靠预测页码的内容非常重要。因此,这些东西不能立即写入,而\immediate
应该以延迟的方式写入,即在将相关页面传送到输出文件时写入。
问题 2:可扩展令牌何时应被扩展?它们是否应该被扩展?
可\immediate\write
扩展的令牌会立即扩展,并且写入也会立即进行,而不是延迟到页面传送到输出文件为止。
随着延迟写入,可扩展标记的扩展也会延迟,直到将页面交付到输出文件。这意味着可能存在一个问题:可扩展标记可能会在遇到语句\write
和实际写入之间的时间跨度内重新定义。
在 LaTeX 2ε 中,此问题通常通过以下方式处理\protected@write
: \protected@write
内部应用于\edef
立即扩展的内容,然后应用(延迟)\write
。\edef
以考虑 LaTeX 2ε 的 -机制的方式应用,\protect
从而可以防止扩展不应扩展的标记。
问题 3:在写入文件时,(La)TeX 有何特殊之处?
在读取/预处理/标记 .tex 输入文件之后,一切都与 (La)TeX 中所谓的标记有关。
因此,诸如此类的东西\write
实际上并不适用于 .tex 输入文件的字符,而是适用于在读取/预处理/标记 .tex 输入文件期间或由于可扩展标记的扩展而产生的标记。
特点是:
- 当 (La)TeX 写入不带扩展的控制字标记时(控制字标记不可扩展,被
\noexpand
或阻止扩展\protect
,控制字标记是\the
标记寄存器内容的扩展的一部分,或其他),您将始终获得一个字符,其在 (La)TeX 内部字符编码方案中的代码点号等于整数参数 的值\escapechar
。通常该参数的值为 92,因此通常您会得到一个反斜杠。然后您会得到一个字符序列,它表示该控制字标记的名称。然后您会得到一个空格字符。即使您没有在 .tex 输入文件中相应序列后面输入一个空格字符,您也会得到该空格字符。换句话说:附加空格是未扩展写入控制字标记操作的一部分。 - 使用控制符号标记时,情况会有所不同:附加空格不是未扩展写入控制符号标记的操作的一部分。
- 当 (La)TeX 写入类别代码为 6(参数)的显式字符标记时,您将获得相应的字符两次。 (通常,哈希,,
#
属于类别代码 6,因此从 .tex 输入文件中标记哈希字符通常会产生这样的字符标记 - 这就是为什么这被称为“哈希加倍”。) - 当 (La)TeX 在写入过程中遇到不可扩展的显式字符标记,而该标记在 (La)TeX 的内部字符编码方案中的代码点号等于整数参数的值时
\newlinechar
,该字符将不会被写入,但会导致 (La)TeX 开始写入另一行。反过来,在文本文件中开始另一行的编码取决于平台。 ^^
- 字符符号可能会转换为普通符号,反之亦然。
您可以通过让 (La)TeX 从 .tex 输入文件中读取内容并在 verbatim-category-code-régime 下对内容进行标记来防止 (La)TeX 执行此类奇怪的操作,这样所有内容都会被标记为某种普通的字符标记。
有时这是需要的。有时,当需要编写不能重复的哈希值、不平衡的花括号或 - 字符%
等时,这会很方便。
\input
问题 4:无法同时打开文件进行写入和读取(例如通过)
这可能是一个问题:假设您有指令将条目写入索引或词汇表的辅助文件。一方面,您希望能够将此类指令放置在文档的整个源代码中。另一方面,您希望该辅助文件提供的信息在 LaTeX 运行期间可用,即使在最后,因为索引/词汇表将放在文档的末尾。这似乎是矛盾的,因为只要打开文件进行写入,该文件提供的信息就无法读取。
在 LaTeX 2ε 中,您可以通过\@starttoc
--\addtocontents
机制处理此类问题。
如果您说,那么如果存在,则将输入\@starttoc{foo}
文件。然后通过 — 分配一个新的 -handle,即:相应 -handle 的编号将通过通过 — 定义的控制序列提供,并且通过该-handle 分配文件,该文件的先前实例(如果存在)将因此被销毁,并且将重新创建并打开以供写入。\jobname.foo
\write
\newwrite
\write
\tf@foo
\chardef
\immediate\openout
\write
\jobname.foo
如果您说\addtocontents{foo}{bla bla}
,则在延迟方式下,但会立即执行\edef
扩展,并遵循 LaTeX 2ε 的\protect
机制,将指令写入 .aux 文件:
\@writefile{foo}{bla bla}
。
在 LaTeX 运行结束时处理 .aux 文件时,\@writefile
将执行以下指令:如果\write
在此期间已分配并打开相应的句柄以供写入,例如由于\@starttoc{foo}
,bla bla
将立即写入(不扩展)文件\jobname.foo
。
打开\write
(La)TeX 运行结束时,
这样,您可以在整个 LaTeX 文档的源代码中提供写入外部文件的指令,同时仍使该文件可用,直到(可能在该源代码结束之前的某个地方)说\@starttoc
。 (假定在处理相应的指令后,不再需要相关文件\@starttoc
。)通常“隐藏”在或或之\@starttoc
类的命令定义中。\tableofcontents
\listoffigures
\listoftables
如果你对一种不需要分配很多资源的做事方式感兴趣\write
同时分配多个句柄的方式来做事感兴趣/如果你对一种写入比\write
可用句柄更多的文件的方式感兴趣,scrwfile 软件包你可能会感兴趣
\@starttoc
修改该包后,不会立即分配\write
-handle 并打开相应的文件进行写入。(实际上没有必要立即打开文件进行写入,因为无论如何,写入该文件将在 LaTeX 运行结束时进行,即\@writefile
处理 .aux 文件及其 -directives 时。)相反,\write
在处理 .aux 文件及其 -directives 时,会在 LaTeX 运行结束时分配外部文件的 -handle \@writefile
:
假设需要通过\addtocontents
-→ -\@writefile
指令编写 49 个外部文件,而实际上只有大约 12 个\write
-句柄可用。scrwfile 软件包\write
将为前 12 个文件分配句柄,然后处理\@ritefile
句柄,然后处理.aux 文件的指令。然后scrwfile 软件包将重新分配这些\write
句柄以写入接下来的 12 个文件,然后\@ritefile
再次处理 .aux 文件的指令。等等,直到所有外部文件都写入完毕。
我强烈建议不要使用 LaTeX 本身也使用的文件扩展名。
这样可以降低错误地覆盖仍需要的文件的风险。
因此,在下面的示例中,在 LaTeX 运行期间/结束时写入的外部文本文件将不会被调用matchtwoleft.aux
,但将被调用matchtwoleft.mxt
或⟨jobname⟩.mxt
(mxt 是米耶訂閱延伸)。
在以下示例中,verbatim
-package 已加载。这只是为了使\verbatiminput
-command 可用。而
-command\verbatiminput
则仅用于在 LaTeX 的输出文件(.dvi 文件或 .pdf 文件)中显示外部文本文件。
如果你需要立即写入并进行一定程度的扩展,但在写入外部文件时进行扩展以防止出现扩展,我建议这样做:
文件text.tex
:
\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{verbatim}
\makeatletter
\newcommand{\match}[1]{%
\@bsphack
\begingroup
\let\label\relax
\let\index\relax
\let\glossary\relax
\let\protect\noexpand
% \protect=\noexpand will ensure that commands defined in terms
% of \DeclareRobustCommand or \newcommand with optional argument
% or prefixed with \protect will not be expanded.
\immediate\write\matchtwoleft{#1}%
\endgroup
\@esphack
}%
\makeatother
\begin{document}
\noindent
This is the content of the file \texttt{matchtwoleft.mxt}---spaces are displayed as ``\texttt{\char32}'':
\IfFileExists{matchtwoleft.mxt}{\verbatiminput*{matchtwoleft.mxt}}{}%
\newwrite\matchtwoleft
\immediate\openout\matchtwoleft=matchtwoleft.mxt
% Be aware that the file matchtwoleft.mxt is destroyed now and gets written anew.
% Thus it is not available throughout the entire LaTeX-run.
\match{an expos\'e
an expos\'e
an expos\'e}
\end{document}
如果您希望能够给出写入外部文件的指令但不破坏其内容,您可以执行类似于 LaTeX 2ε 内核的\@starttoc
/\addtocontents
机制的操作,该机制将指令写入 .aux 文件,从而导致在 LaTeX 运行结束时写入与之关联的外部文件。@writefile{⟨write-handle⟩}{⟨stuff-to-write⟩}
⟨stuff-to-write⟩
⟨write-handle⟩
如果您希望 .aux-file-route 具有立即写入功能并具有一定程度的扩展,但对于在写入外部文件时扩展时会出现的问题具有扩展预防功能,我建议使用以下变体\addtocontents
:
文件text.tex
:
\makeatletter
\newcommand\myaddtocontents[2]{%
\protected@write\@auxout{%
\let\label\relax
\let\index\relax
\let\glossary\relax
\begingroup
\def\write{\noexpand\endgroup\let\noexpand\protect\noexpand\noexpand\immediate\noexpand\write}%
}{\string\@writefile{#1}{#2}}%
}%
\makeatother
\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{verbatim}
\makeatletter
%%================= Code for infrastructure for maintaining .mxt-file==========
\newcommand\creatematchfile{%
\begingroup
\let\saved@input=\@input
\def\@input##1{\let\@input=\saved@input}%
\@starttoc{mxt}%
\endgroup
}%
\newcommand\printandcreatematchfile{%
\IfFileExists{\jobname.mxt}{\verbatiminput*{\jobname.mxt}}{}%
\creatematchfile
}%
\newcommand\match[1]{%
\@bsphack
\myaddtocontents{mxt}{#1}%
\@esphack
}%
%%============== End of code for infrastructure for maintaining .mxt-file======
\makeatother
\begin{document}
\noindent
This is the content of the file \texttt{\jobname.mxt}---spaces are displayed as ``\texttt{\char32}'':
\printandcreatematchfile
\match{an expos\'e
an expos\'e
an expos\'e}
\end{document}
如果要直接从 .tex 输入文件中逐字复制内容并“逐字”书写,不进行宏扩展等操作,则可以让 LaTeX 通过 的xparse
逐字参数之一来处理这些内容:
文件text.tex
:
\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{xparse}
\usepackage{verbatim}
\makeatletter
\NewDocumentCommand{\match}{}{%
\@bsphack
\begingroup
\newlinechar=\endlinechar
\catcode\endlinechar=12 %
\catcode`\^^I=12 %
\matchinternal
}%
\NewDocumentCommand{\matchinternal}{+v}{%
\immediate\write\matchtwoleft{#1}%
\endgroup
\@esphack
}%
\makeatother
\begin{document}
\noindent
This is the content of the file \texttt{matchtwoleft.mxt}---spaces are displayed as ``\texttt{\char32}'':
\IfFileExists{matchtwoleft.mxt}{\verbatiminput*{matchtwoleft.mxt}}{}%
\newwrite\matchtwoleft
\immediate\openout\matchtwoleft=matchtwoleft.mxt
\match{an expos\'e
an expos\'e
an expos\'e}
\end{document}
除此之外,我还可以提供 LaTeX 2ε \@starttoc
/\addtocontents
机制的变体,该机制也会在 verbatim-catcode-régime 下读取参数。
在这里我只使用xparse
我自己的东西,因为xparse
它不允许您传递 verbatim-delimiter,但\@starttoc
/\addtocontents
机制是一个两阶段过程(阶段 1:\@writefile
在处理 .tex 文件时将指令写入 .aux 文件,阶段 2:在处理 .aux 文件及其指令时写入外部文件\@writefile
),其中第二阶段也需要 verbatim-delimiter:
文件text.tex
:
\makeatletter
%%======================Code for \UDcollectverbarg=============================
%% \UDcollectverbarg{^^M-replacement}{<mandatory 1>}{<mandatory 2>}|<verbatim arg>|
%%
%% reads <verbatim arg> under verbatim-catcode-regime and delivers:
%%
%% <mandatory 1>{<mandatory 2>{<verbatim arg>}{|<verbatim arg>|}}
%%
%% Instead of verbatim-delimiter | the <verbatim arg> can be nested in braces.
%% You cannot use percent or spaces or horizontal tab as verbatim-delimiter.
%%
%% You can use <mandatory 1> for nesting calls to \UDcollectverbarg.
%% <mandatory 2> gets the <verbatim arg> twice: Once without verbatim-delimiters/braces,
%% once surrounded by verbatim-delimiters/braces.
%% Reason: When you feed it to \scantokens you don't need the verbatim-delimiters.
%% When you use it for writing to temporary files and reading back,
%% you need them.
%%=============================================================================
%% 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>}%
%%
%% Due to \romannumeral0-expansion the result is delivered after two
%% expansion-steps/after two "hits" by \expandafter.
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
%%
\long\def\UD@CheckWhetherNull#1{%
\romannumeral0\expandafter\@secondoftwo\string{\expandafter
\@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
\@secondoftwo\string}\@firstoftwo\expandafter{} \@secondoftwo}%
{\@firstoftwo\expandafter{} \@firstoftwo}%
}%
%%=============================================================================
\begingroup
\@makeother\^^M%
\@firstofone{%
\endgroup%
\newcommand\UDEndlreplace[2]{\romannumeral0\@UDEndlreplace{#2}#1^^M\relax{}}%
\@ifdefinable\@UDEndlreplace{%
\long\def\@UDEndlreplace#1#2^^M#3\relax#4#5{%
\UD@CheckWhetherNull{#3}%
{ #5{#4#2}}{\@UDEndlreplace{#1}#3\relax{#4#2#1}{#5}}%
}%
}%
}%
\newcommand\UDcollectverbarg[3]{%
\begingroup
\let\do\@makeother % <- this and the next line switch to
\dospecials % verbatim-category-code-régime.
\catcode`\{=1 % <- give opening curly brace the usual catcode so a
% curly-brace-balanced argument can be gathered in
% case of the first thing of the verbatimized-argument
% being a curly opening brace.
\catcode`\ =10 % <- give space and horizontal tab the usual catcode so \UD@collectverbarg
\catcode`\^^I=10 % cannot catch a space or a horizontal tab as its 4th undelimited argument.
% (Its 4th undelimited argument denotes the verbatim-
% syntax-delimiter in case of not gathering a
% curly-brace-nested argument.)
\catcode`\%=14 % <- make percent comment.
\kernel@ifnextchar\bgroup
{% seems a curly-brace-nested argument is to be caught:
\catcode`\}=2 % <- give closing curly brace the usual catcode also.
\UD@collectverbarg{#1}{#2}{#3}{}%
}{% seems an argument with verbatim-syntax-delimiter is to be caught:
\do\{% <- give opening curly brace the verbatim-catcode again.
\UD@collectverbarg{#1}{#2}{#3}%
}%
}%
\newcommand\UD@collectverbarg[4]{%
\do\ % <- Now that \UD@collectverbarg has the delimiter or
\do\^^I% emptiness in its 4th arg, give space and horizontal tab
% the verbatim-catcode again.
\do\^^M% <- Give the carriage-return-character the verbatim-catcode.
\do\%% <- Give the percent-character the verbatim-catcode.
\long\def\@tempb##1#4{%
\def\@tempb{##1}%
\UD@CheckWhetherNull{#4}{%
\def\@tempc{{##1}}%
}{%
\def\@tempc{#4##1#4}%
}%
\@onelevel@sanitize\@tempb % <- Turn characters into their "12/other"-pendants.
% This may be important with things like the
% inputenc-package which may make characters
% active/which give them catcode 13(active).
\expandafter\UDEndlreplace\expandafter{\@tempb}{#1}{\def\@tempb}% <- this starts
% the loop for replacing endline-characters.
\@onelevel@sanitize\@tempc
\expandafter\UDEndlreplace\expandafter{\@tempc}{#1}{\def\@tempc}%
\expandafter\expandafter\expandafter\UD@@collectverbarg% <- this "spits out the result.
\expandafter\expandafter\expandafter{%
\expandafter\@tempb\expandafter}%
\expandafter{\@tempc}{#2}{#3}%
}%
\@tempb
}%
\newcommand\UD@@collectverbarg[4]{%
\endgroup
#3{#4{#1}{#2}}%
}%
%%================= End of code for \UDcollectverbarg =========================
%%
%%================= Code for writing verbatim-args via aux-file================
\newcommand\UD@verbargs@addtocontents[2]{%
% #1 - \@starttoc-\write-handle
% #2 - Things to do after writing to .aux, e.g., \@esphack.
\UDcollectverbarg{^^J}{\@firstofone}{\UD@@verbargs@addtocontents{#1}{#2}}%
}%
\newcommand\UD@@verbargs@addtocontents[4]{%
\immediate\write\@auxout{\string\UD@verbarg@writefile{#1}#4}%
#2%
}%
\newcommand\UD@verbarg@writefile[1]{%
\UDcollectverbarg{^^J}{\@firstofone}{\UD@verbarg@@writefile{#1}}%
}%
\newcommand\UD@verbarg@@writefile[3]{%
\@writefile{#1}{#2}%
}%
%%================= End of code for writing verbatim-args via aux-file=========
%%
\makeatother
\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{verbatim}
\makeatletter
%%================= Code for infrastructure for maintaining .mxt-file==========
\newcommand\creatematchfile{%
\begingroup
\let\saved@input=\@input
\def\@input##1{\let\@input=\saved@input}%
\@starttoc{mxt}%
\endgroup
}%
\newcommand\printandcreatematchfile{%
\IfFileExists{\jobname.mxt}{\verbatiminput*{\jobname.mxt}}{}%
\creatematchfile
}%
\newcommand\match{%
\@bsphack
\UD@verbargs@addtocontents{mxt}{\@esphack}%
}%
%%============== End of code for infrastructure for maintaining .mxt-file======
\makeatother
\begin{document}
\noindent
This is the content of the file \texttt{\jobname.mxt}---spaces are displayed as ``\texttt{\char32}'':
\printandcreatematchfile
\match{an expos\'e
an expos\'e
an expos\'e}
\match|annother expos\'e
another expos\'e
another expos\'e|
\end{document}