如何在宏定义中保留实际输入的换行符/行尾符(十六进制 0x0a)

如何在宏定义中保留实际输入的换行符/行尾符(十六进制 0x0a)

我正在使用filecontents包,并希望将其包含在宏中,以便分解一些复杂的处理。问题是,后面\begin{filecontents}除了空格和实际的尾随换行符外什么都不用跟,不幸的是,TeX/LaTex 处理宏定义的方式吸收了换行符。这是 M(not)WE :

\documentclass{article} 
\usepackage{filecontents}
\thispagestyle{empty}

\newcommand{\cf}[2]{%
% next-line-compulsorily-ends-only-with-spaces-then-newline
\begin{filecontents}{prefix-#1.tex}
factored-complex-processing-of{#2}
\end{filecontents}
}
\show\cf
%
\cf{suffix1}{somefirststuff}
%
\begin{filecontents}{prefix-suffix2.tex}
complex-processing-of{somenextstuff}
\end{filecontents}

\begin{document}

some-package-macro-processing-using-suffix-number{1,2}

Equivalent to:

\input{prefix-suffix1.tex}
\input{prefix-suffix2.tex}

\end{document}

\begin{filecontents}由于吸收了行尾不存在的换行符\newcommand{\cf},因此 TeX/LaTeX 不会理解此命令并将其余部分处理为要输出的文本,这会导致错误Missing \begin{document}

我曾尝试寻找涉及\def、、、等\edef的变通方法,但没有找到任何有用的东西。\noexpand\catcode

那么有没有办法将这个实际输入的换行符/行尾符保留在宏定义中?

答案1

\documentclass{article}
\usepackage{verbatim}
\usepackage{filecontents}
\thispagestyle{empty}

\newcommand\exchange[2]{#2#1}

% The integer-parameter \newlinechar usually has the value 10.
% This means if at the time of writing to text-file (La)TeX encounters a
% character whose code-point's number in (La)TeX's internal character-encoding
% (which either is ASCII or is UTF-8)  is 10, it will not write that character
% but will write a linebreak. 10 denotes the Line Feed-character and using
% (La)TeX's ^^-notation you can also write it as ^^J.

\begingroup
\newcommand\cf[2]{%
  \endgroup
  \newcommand{\cf}[2]{%
    \scantokens{%
      #1{prefix-##1.tex}^^J%
      factored-complex-processing-of{##2}^^J%
      #2%
    }%
  }%
}%
\expandafter\exchange\expandafter{\expandafter{\string\end{filecontents}}}%
{\expandafter\cf\expandafter{\string\begin{filecontents}}}%

\cf{suffix1}{ somefirststuff\LaTeX}%

\begin{filecontents}{prefix-suffix2.tex}
factored-complex-processing-of{ somenextstuff\LaTeX}
\end{filecontents}

\begin{document}

\noindent prefix-suffix1.tex is:

\verbatiminput{prefix-suffix1.tex}

\noindent\hrulefill\null

\noindent prefix-suffix2.tex is:

\verbatiminput{prefix-suffix2.tex}

\noindent\hrulefill\null

Do you see the subtle difference?

In prefix-suffix1.tex there is a space behind the phrase \verb|\LaTeX|.

In prefix-suffix2.tex there is no space behind the phrase \verb|\LaTeX|.

The reason is:

prefix-suffix1.tex comes from the \verb|\cf|-command.

The tokens forming the second argument of \verb|\cf| were
tokenized under normal catcode-r\'egime. Thus the phrase \verb|\LaTeX| got
tokenized as control-word-token. When \verb|\scantokens| did its simulated
unexpanded-writing-part, that control-word-token got written unexpanded
with a trailing space as \LaTeX{} always writes control-word-tokens with
a trailing space.\\
Besides this, hashes of catcode 6 will be doubled at writing time also.

If you don't like such side-effects, you need to have  \LaTeX{} read the
second argument of \verb|\cf| under verbatim-catcode-r\'egime. How to do
that is exhibited in the other example.

\noindent\hrulefill\null

\noindent\verb|\input{prefix-suffix1.tex}| yields:

\noindent\input{prefix-suffix1.tex}

\noindent\hrulefill\null

\noindent\verb|\input{prefix-suffix2.tex}| yields:

\noindent\input{prefix-suffix2.tex}

\end{document}

在此处输入图片描述

请注意\scantokens{<stuff>},由 e-TeX 扩展提供的 类似于

\newtoks\myscratchtoks
\newwrite\myscratchwrite
...
\immediate\openout\myscratchwrite temp.tex\relax
\myscratchtoks{<stuff>}%
\immediate\write\myscratchwrite{\the\myscratchtoks}%
\immediate\closeout\myscratchwrite
\input temp.tex %\@@@input with LaTeX 2e.

我说“类似”是因为它并不完全相同:\scantokens它本身是可扩展的,而上面的代码片段则不可扩展。

例如,\scantokens你可以做

\expandafter\def\expandafter\temp\scantokens{{definition text}}

当调整上述代码片段时,必须执行以下操作:

\newtoks\myscratchtoks
\newwrite\myscratchwrite
...
\immediate\openout\myscratchwrite temp.tex\relax
\myscratchtoks{{definition text}}%
\immediate\write\myscratchwrite{\the\myscratchtoks}%
\immediate\closeout\myscratchwrite
\expandafter\def\expandafter\temp\input temp.tex %\@@@input with LaTeX 2e.

这两种方法都会发生将标记未扩展写入/模拟未扩展写入文本文件的情况。

请注意,当发生标记的未扩展写入时,catcode 6 的字符标记将加倍,即,#catcode 6 的哈希值将加倍。

请注意,当发生未扩展的标记写入时,控制字标记将带有尾随空格。

如果您不想要这些效果,您需要转变\cf为一个在 verbatimized-catcode-conditions 下读取其内容的宏。如果这样做,\cf则不得通过其他宏获取其提供的参数,而必须通过从 tex-input-file 读取和标记来获取它们。即,与 -command 相同的限制适用\verb

我可以提供一个例程\UDcollectverbarg

\documentclass{article}
\usepackage{verbatim}
\usepackage{filecontents}
\thispagestyle{empty}

\makeatletter
\newcommand\UD@firstofone[1]{#1}%
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@exchange[2]{#2#1}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
%%<-------------------- Code for \UDcollectverbarg -------------------->
%% 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>}%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%......................................................................
\begingroup
\catcode`\^^M=12 %
\UD@firstofone{%
  \endgroup%
  \newcommand\UDEndlreplace[2]{\romannumeral0\@UDEndlreplace{#2}#1^^M\relax{}}%
  \newcommand*\@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.)
  \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.
  \long\def\@tempb##1#4{%
    %\edef\@tempb{##1}%
    \def\@tempb{##1}%
    \@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.
    \expandafter\UD@@collectverbarg\expandafter{\@tempb}{#2}{#3}% <- this "spits 
                               %    out the result.
  }%
  \@tempb
}%
\newcommand\UD@@collectverbarg[3]{%
  \endgroup
  #2{#3{#1}}%
}%
%%<---------------- End of code for \UDcollectverbarg ----------------->

% The integer-parameter \newlinechar usually has the value 10.
% This means if at the time of writing to text-file (La)TeX encounters a
% character whose code-point's number in (La)TeX's internal character-encoding
% (which either is ASCII or is UTF-8)  is 10, it will not write that character
% but will write a linebreak. 10 denotes the Line Feed-character and using
% (La)TeX's ^^-notation you can also write it as ^^J.
\newcommand{\cf}[1]{%
  \UDcollectverbarg{^^J}{\UD@firstofone}{\innercf{#1}}%
}%
\begingroup
\newcommand\innercf[2]{%
  \endgroup
  \newcommand\innercf[2]{%
    \scantokens{%
      #1{prefix-##1.tex}^^J%
      factored-complex-processing-of{##2}^^J%
      #2%
    }%
  }%
}%
\UDcollectverbarg{^^J}{%
  \UDcollectverbarg{^^J}{\UD@firstofone}%
}{\innercf}|\begin{filecontents}||\end{filecontents}|%


\cf{suffix1}{ somefirststuff\string#\LaTeX}%

\begin{filecontents}{prefix-suffix2.tex}
factored-complex-processing-of{ somenextstuff\string#\LaTeX}
\end{filecontents}

\begin{document}

\noindent prefix-suffix1.tex is:

\verbatiminput{prefix-suffix1.tex}

\noindent\hrulefill\null

\noindent prefix-suffix2.tex is:

\verbatiminput{prefix-suffix2.tex}

\noindent\hrulefill\null

\noindent\verb|\input{prefix-suffix1.tex}| yields:

\noindent\input{prefix-suffix1.tex}

\noindent\hrulefill\null

\noindent\verb|\input{prefix-suffix2.tex}| yields:

\noindent\input{prefix-suffix2.tex}

\end{document}

在此处输入图片描述

如果您希望在将\cf第二个参数写入文件之前对其进行完全评估/扩展,您可以应用\protected@edef\@onelevel@sanitize

\documentclass{article}
\usepackage{verbatim}
\usepackage{filecontents}
\thispagestyle{empty}

\newcommand\exchange[2]{#2#1}

% The integer-parameter \newlinechar usually has the value 10.
% This means if at the time of writing to text-file (La)TeX encounters a
% character whose code-point's number in (La)TeX's internal character-encoding
% (which either is ASCII or is UTF-8)  is 10, it will not write that character
% but will write a linebreak. 10 denotes the Line Feed-character and using
% (La)TeX's ^^-notation you can also write it as ^^J.

\newenvironment{localscope}{}{}%

\begingroup
\makeatletter
\newcommand\cf[2]{%
  \endgroup
  \newcommand{\cf}[2]{%
    \begin{localscope}%
    \protected@edef\UD@tempa{##2}%
    \@onelevel@sanitize\UD@tempa
    \expandafter\exchange\expandafter{%
    \expandafter\scantokens\expandafter{%
      \romannumeral0\expandafter\exchange\expandafter{\expandafter{\UD@tempa}}{ %
      #1{prefix-##1.tex}^^J%
      factored-complex-processing-of}^^J%
      #2%
    }}%
    {\end{localscope}}%
  }%
}%
\expandafter\exchange\expandafter{\expandafter{\string\end{filecontents}}}%
{\expandafter\cf\expandafter{\string\begin{filecontents}}}%

\newcommand\firststage{\secondstage}
\newcommand\secondstage{\thirdstage}
\newcommand\thirdstage{Expanded. \LaTeX}

\cf{suffix1}{ somefirststuff \firststage}%

\begin{filecontents}{prefix-suffix2.tex}
factored-complex-processing-of{ somenextstuff \firststage}
\end{filecontents}

\begin{document}

\noindent prefix-suffix1.tex is:

\verbatiminput{prefix-suffix1.tex}

\noindent\hrulefill\null

\noindent prefix-suffix2.tex is:

\verbatiminput{prefix-suffix2.tex}

\noindent\hrulefill\null

\noindent\verb|\input{prefix-suffix1.tex}| yields:

\noindent\input{prefix-suffix1.tex}

\noindent\hrulefill\null

\noindent\verb|\input{prefix-suffix2.tex}| yields:

\noindent\input{prefix-suffix2.tex}

\end{document}

在此处输入图片描述

相关内容