以下宏,完整的 MWE 可以在以下位置找到未修改的源代码,
\def\SourceCode{%
\begingroup%
\endlinechar`\^^J%
\catcode`\\=12\catcode`\^^M=12\catcode`\#=12\catcode`\~=12\catcode`\%=12\catcode`\^=12\catcode`\_=12\catcode`\@=12\catcode`\ =12\catcode`\|=12%
\SourceCodeAux}%
\def\SourceCodeAux#1#2{\endgroup\directlua{print("\luaescapestring{#1}")}}%
存在一个问题,即最后一个终止符}
之后不能有任何其他内容,在它的行上:
\begin{itemize}
\item 2
\SourceCode{
{this
is
a
test}
}\end{itemize} % < This line is the problem, } \end{itemize} works but produces protected space in pdf.
不管用除非 \end{itemize}
移动到下一行或者在之间放置一个空格} \end{itemize}
(然后在 pdf 中放置一个受保护的空间)
问题似乎是宏如何处理最后一个字符,但我不太明白发生了什么。有什么办法可以让这种}\end{itemize}
情况奏效吗?
我有点困惑 \def\SourceCodeAux#1#2 的第二个参数的作用。它是必需的,但我不确定它实际上是如何工作的。我已经用其他东西替换了它,可以开始
}\end{itemize}
工作,但最终在 pdf 中出现了受保护的空格或其他字符。我的想法是,如果我可以在最后一个换行符之后立即在流中插入一个换行符,}
那么 TeX 就会看到}\^^M\end{itemize}
并允许它工作。无论如何,也许有人可以告诉我到底发生了什么。
这是我所看到的宏:
启动新组来本地定义 catcode(因此 catcode 的修改不会影响宏中的内容以外的任何内容)
修改 endlinechar 和 catcodes,使宏调用后的标记不再是“特殊”的
调用一个接受两个参数的辅助宏。第一个参数最终将成为标记流(源代码参数“传递”给原始宏调用)。第二个参数将成为源代码中的最后一个标记。(在问题案例中,恰好是
\
被“意外”拾取并破坏\end{itemize}
(tex 将其视为end{itemize}
)调用 endgroup 使 catcodes 恢复正常,然后对第一个“参数”进行处理。(我不太清楚宏如何知道何时停止处理标记,但我猜这与使用 #2 的一些技巧有关,这也是导致问题的原因)
以下是完整的 MWE:
\documentclass{book}
\usepackage{luatex}
\directlua{tex.enableprimitives('',tex.extraprimitives())}
\begin{document}
\def\SourceCode{%
\begingroup%
\endlinechar`\^^J%
\catcode`\\=12\catcode`\^^M=12\catcode`\#=12\catcode`\~=12\catcode`\%=12\catcode`\^=12\catcode`\_=12\catcode`\@=12\catcode`\ =12\catcode`\|=12%
\SourceCodeAux}%
\def\SourceCodeAux#1#2{\endgroup\directlua{print("\luaescapestring{#1}")}}%
\begin{itemize}
\item 1
\SourceCode{
{this
is
a sucessful
test}
}
\end{itemize}
\iftrue
\begin{itemize}
\item 2
\SourceCode{
{this
is
a failed
test}
}\end{itemize}
\fi
\end{document}
答案1
问题是 TeX 添加了行尾符号 (EOL,\endlinechar
) 字符当它读完整行时,而不是在处理行尾时。这意味着实际\SourceCode
内容之后的任何代码的最后一行的 EOL 仍然是^^J
(打印为大写 Ohm)。#2
包含 是为了吞噬后面的 EOL,假定后面跟着。如果不是,则吞噬下一个逐字字符,并且 EOL^^J
仍在那里。
解决方案是使用清理宏,该宏读取行的其余部分并删除,^^J
同时重新插入源代码行的其他代码。请注意,正常的 EOL 会插入一个空格,该空格包含在清理宏中,但在所有情况下可能并不总是完全相同。此外,如果该行中有注释掩盖了 EOL 字符,这将中断并出现 TeX 错误。
一般来说,将多行逐字环境或宏的第一行和最后一行与普通材料混合在一起是个坏主意,因此在后面加一个换行符}
实际上是一个非常好的主意。
\documentclass{book}
\usepackage{luatex}
\directlua{tex.enableprimitives('',tex.extraprimitives())}
\begin{document}
\def\SourceCode{%
\begingroup%
\endlinechar`\^^J%
\catcode`\\=12\catcode`\^^M=12\catcode`\#=12\catcode`\~=12\catcode`\%=12\catcode`\^=12\catcode`\_=12\catcode`\@=12\catcode`\ =12\catcode`\|=12%
\SourceCodeAux}%
\def\SourceCodeAux#1{\endgroup\directlua{print("\luaescapestring{#1}")}\SourceCodeCleanup}%
\def\SourceCodeCleanup#1^^J{#1 }%
\begin{itemize}
\item 1
\SourceCode{
{this
is
a successful
test}
}
\end{itemize}
\iftrue
\begin{itemize}
\item 2
\SourceCode{
{this
is
now also a successful
test}
}\end{itemize}
\fi
\end{document}
答案2
我从问题的“线索”中知道这xparse
可能不是你想要的,但在这里使用逐字参数来获得你指示的输出是没有问题的
\documentclass{article}
\usepackage{luatex}
\directlua{tex.enableprimitives('',tex.extraprimitives())}
\usepackage{xparse}
\NewDocumentCommand{\SourceCode}{+v}{\directlua{print("\luaescapestring{#1}")}}
\begin{document}
\begin{itemize}
\item 2
\SourceCode{
{this
is
a
test}
}\end{itemize}
\end{document}
演示了如何使用它来处理^^M
TeX 中的(行尾)(上面的演示在 Lua 级别打印输出):
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{xparse}
\ExplSyntaxOn
\char_set_catcode_other:n { `\^^M }
\NewDocumentCommand { \SourceCode } { +v }
{
\tl_set:Nn \l_tmpa_tl {#1}
\tl_replace_all:Nnn \l_tmpa_tl { ^^M } { \par }
\ttfamily { \l_tmpa_tl }
}
\char_set_catcode_end_line:n { `\^^M }
\ExplSyntaxOff
\begin{document}
\begin{itemize}
\item 2
\SourceCode{
{this is
is
a
test}
}\end{itemize}
\end{document}
这是必要的,因为每行的逐字抓取都包括^^M
在每行的末尾。您可以通过显示抓取的标记的分析来看到这一点:
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand { \SourceCode } { +v }
{ \tl_show_analysis:n {#1} }
\ExplSyntaxOff
\begin{document}
\begin{itemize}
\item 2
\SourceCode{
{this is
is
a
test}
}\end{itemize}
\end{document}
(对于 TeX Live 2017 之前的版本,您还需要\usepackage{l3tl-analysis}
才能\usepackage{xparse}
正常工作。)