在回答这个问题,我注意到{}
在其他逐字上下文中关于空组存在一些棘手的问题。
假设您逐字逐句地捕获环境的内容(如该问题所示),但\
、{
和}
仍保留其正常的 catcode,以便宏可以正常工作。然后环境的内容将写入文件。任何{}
未被宏使用的空组都将作为文字写入文件{}
。 有没有什么办法可以防止这种情况发生?在这种情况下,如何才能让宏尽可能正常运行?
在这些条件下,宏不会消耗后面的空格,因为空格是文字,因此用 . 分隔宏的结尾是合乎逻辑的,{}
只是这会导致文字{}
.
最小示例
下面的示例同时使用了verbatim
和fancyvrb
。在verbatim
中,每行都被逐字逐句地捕获(存储在 中\verbatim@line
),然后在 中重新标记\verbatim@processline
。实际上,只处理一次每行会更有效率,因此使用 fancyvrb
会更好。但两者都产生相同的输出,并且verbatim
出于演示目的,该示例更容易破解。
\documentclass{article}
\usepackage{verbatim}
\usepackage{fancyvrb}
\newwrite\outfile
\makeatletter
\newenvironment{verbatimoutexp}[1]%
{\immediate\openout\outfile=#1%
\def\verbatim@processline{%
\begingroup
% Redefine escapes so they write to file in literal fashion
\edef\{{\@charlb}%
\edef\}{\@charrb}%
\edef\\{\@backslashchar}
\let\textbackslash\@backslashchar
% Retokenize with new catcodes
\everyeof{\noexpand}%
\endlinechar-1\relax
\let\do\@makeother\dospecials%
\catcode`\\=0%
\catcode`\{=1%
\catcode`\}=2%
\xdef\verbatim@line@retok{\expandafter\scantokens\expandafter{\the\verbatim@line}}%
\endgroup
\immediate\write\outfile{\verbatim@line@retok}}%
\@bsphack
\let\do\@makeother\dospecials
\catcode`\^^M\active
\verbatim@start}%
{\@esphack
\immediate\closeout\outfile}
\makeatother
\begin{document}
\def\mymacro{MACRO}
\begin{verbatimoutexp}{test1.txt}
\mymacro{}FollowingText
\end{verbatimoutexp}
\begin{VerbatimOut}[commandchars=\\\{\}]{test2.txt}
\mymacro{}FollowingText
\end{VerbatimOut}
\end{document}
输出(两个文件相同):
MACRO{}FollowingText
期望输出:
MACROFollowingText
答案1
我只需采用\*
或任何其他合适的单字符宏命令名称并将其定义为空,然后您就可以执行以下操作:
\documentclass{article}
\usepackage{verbatim}
\usepackage{fancyvrb}
\newwrite\outfile
\makeatletter
\newenvironment{verbatimoutexp}[1]%
{\immediate\openout\outfile=#1%
\def\verbatim@processline{%
\begingroup
% Redefine escapes so they write to file in literal fashion
\edef\{{\@charlb}%
\edef\}{\@charrb}%
\edef\\{\@backslashchar}
\let\textbackslash\@backslashchar
% Retokenize with new catcodes
\everyeof{\noexpand}%
\endlinechar-1\relax
\let\do\@makeother\dospecials%
\catcode`\\=0%
\catcode`\{=1%
\catcode`\}=2%
\xdef\verbatim@line@retok{\expandafter\scantokens\expandafter{\the\verbatim@line}}%
\endgroup
\immediate\write\outfile{\verbatim@line@retok}}%
\@bsphack
\let\do\@makeother\dospecials
\catcode`\^^M\active
\verbatim@start}%
{\@esphack
\immediate\closeout\outfile}
\makeatother
\begin{document}
\def\mymacro{MACRO}
\def\*{}
\begin{verbatimoutexp}{test1.txt}
\mymacro\*FollowingText
\end{verbatimoutexp}
\begin{VerbatimOut}[commandchars=\\\{\}]{test2.txt}
\mymacro\*FollowingText
\end{VerbatimOut}
\end{document}
答案2
我是从错误的角度来看待这个问题的。我不应该试图改变空组的行为{}
,特别是当写入文件时,而应该寻找将其完全删除的方法。我希望这是实现我想要的唯一方法,但如果有其他或更好的选择,我会把这个问题留到更长时间。
在示例中,重新标记后verbatim
,我们得到了文字文本、用括号分隔的组以及可能无法扩展的宏。我们需要删除带有 catcode 1 和 2 的括号。对任何无法扩展的宏发出警告可能也不错,因为如果使用宏将文本插入到原本逐字的代码中,我们很可能只想要完全可扩展的宏。
可以通过迭代捕获行中的每个文字字符/括号组/不可扩展宏,然后重新组装捕获的元素,从行中剥离括号。由于括号仍然具有其正常的 catcode,因此在捕获过程中会将其删除。处理嵌套括号组稍微复杂一些。本质上,需要重复剥离括号的过程,直到输出不再与输入不同。
一旦删除括号,就只剩下文字字符和不可扩展的宏。如果这些宏不受欢迎,可以通过再次逐个迭代并使用 检查宏来生成警告\ifcat
。
\documentclass{article}
\usepackage{verbatim}
\usepackage{fancyvrb}
\newwrite\outfile
\makeatletter
\newenvironment{verbatimoutexp}[1]%
{\immediate\openout\outfile=#1%
\def\verbatim@processline{%
\begingroup
% Redefine escapes so they write to file in literal fashion
\edef\{{\@charlb}%
\edef\}{\@charrb}%
\edef\\{\@backslashchar}
\let\textbackslash\@backslashchar
% Retokenize with new catcodes
\everyeof{\noexpand}%
\endlinechar-1\relax
\let\do\@makeother\dospecials%
\catcode`\\=0%
\catcode`\{=1%
\catcode`\}=2%
\xdef\verbatim@line@retok{\expandafter\scantokens\expandafter{\the\verbatim@line}}%
\endgroup
\let\verbatim@line@retok@final\empty
\let\verbatim@line@retok@last\verbatim@line@retok
\expandafter\gobble@retok@braces\verbatim@line@retok\@nil
\expandafter\check@retok@unexpandable\verbatim@line@retok@final\@nil
\immediate\write\outfile{\verbatim@line@retok@final}}%
\@bsphack
\let\do\@makeother\dospecials
\catcode`\^^M\active
\verbatim@start}%
{\@esphack
\immediate\closeout\outfile}
\def\gobble@retok@braces#1#2\@nil{%
\g@addto@macro{\verbatim@line@retok@final}{#1}%
\if\relax\detokenize{#2}\relax
\ifx\verbatim@line@retok@last\verbatim@line@retok@final
\else
\let\verbatim@line@retok@last\verbatim@line@retok@final
\let\verbatim@line@retok@final\empty
\expandafter\gobble@retok@braces\verbatim@line@retok@last\@nil
\fi
\else
\gobble@retok@braces#2\@nil
\fi}
\def\check@retok@unexpandable#1#2\@nil{%
\ifcat#1\relax
\PackageWarning{}{Verbatim text contained unexpandable macro \string#1}%
\else
\if\relax\detokenize{#2}\relax
\else
\check@retok@unexpandable#2\@nil
\fi
\fi}
\makeatother
\begin{document}
\def\mymacro{MACRO}
\begin{verbatimoutexp}{test.txt}
{}\mymacro{{{}{}{}{{{}}}}}{{FollowingText}} \{ \} \\ \textbackslash
\end{verbatimoutexp}
\VerbatimInput{test.txt}
\begin{verbatimoutexp}{test.txt}
\mymacro{}\relax FollowingText\ignorespaces asfd\ignorespaces{}sadf
\end{verbatimoutexp}
\VerbatimInput{test.txt}
\end{document}