逐字编写环境内容时为空组 {}(但使用扩展宏)

逐字编写环境内容时为空组 {}(但使用扩展宏)

在回答这个问题,我注意到{}在其他逐字上下文中关于空组存在一些棘手的问题。

假设您逐字逐句地捕获环境的内容(如该问题所示),但\{}仍保留其正常的 catcode,以便宏可以正常工作。然后环境的内容将写入文件。任何{}未被宏使用的空组都将作为文字写入文件{}有没有什么办法可以防止这种情况发生?在这种情况下,如何才能让宏尽可能正常运行?

在这些条件下,宏不会消耗后面的空格,因为空格是文字,因此用 . 分隔宏的结尾是合乎逻辑的,{}只是这会导致文字{}.

最小示例

下面的示例同时使用了verbatimfancyvrb。在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}

相关内容