使用以下 MWE
\documentclass{article}
\usepackage{verbatim}
\usepackage{tabu}
\makeatletter
\renewcommand\verbatim@processline
{%
\begin{tabu}to\textwidth{|l|X[-1,l]|}
foo&\the\verbatim@line
\end{tabu}
}
\makeatother
\begin{document}
\verbatiminput{test.tex}%
\end{document}
我收到一些错误信息:
! Missing number, treated as zero.
<to be read again>
\NC@list
l.16 \verbatiminput{test.tex}
%^^M
! Incompatible glue units.
<to be read again>
\NC@list
l.16 \verbatiminput{test.tex}
%^^M
等等以及不想要的输出:
这是一个已知问题吗?有简单的解决方法吗?还是我应该向软件包维护者报告?
编辑
在答案中,事实证明问题是由于\scantokens
在 catcodes 被重新定义时应用而引起\verbatiminput
的。由于这些信息可能对其他软件包作者有用,因此我想提请大家注意。
答案1
更新时间 2019-01-14
与 Bruno 的答案中的代码等效的补丁已在 tabu 2.9 中应用,并已提交给 ctan,因此不需要此答案中建议的解决方法。
tabu
在解析前导码时使用\scantokens
,这意味着它会拾取本地逐字设置并出错。由于参数只是\def\:{|}
用正常的 catcode 读取它们。此外,您需要一个\par
,否则所有内容都会出现在一行上。
\documentclass{article}
\usepackage{verbatim}
\usepackage{tabu}
\makeatletter
\renewcommand\verbatim@processline
{{\let\scantokens\@firstofone
\begin{tabu}to\textwidth{|l|X[-1,l]|}%
foo&\the\verbatim@line%
\end{tabu}%
}\par}
\makeatother
\begin{document}
%\tracingall
\verbatiminput{test.tex}%
\end{document}
正如 Bruno 的回答中的评论讨论中所指出的那样,\scantokens
这里的禁用只是对逐字使用特殊情况的部分修复。
在诸如此类的代码中,有几种 catcode 机制发挥作用。
在用户宏中保存数组前导时有效的 catcode。在表体期间有效的 catcode(在本例中为逐字设置)读取禁忌内部时有效的 catcode。
禁用 scantokens 仅当第一个和最后一个相同时才有效,这是通常的情况。scantokens 的禁忌用法尝试使用 \scantokens 来规范化前言,但这假定前言已在执行表格时使用有效的 catcodes 保存,而如果表格前言存储在宏中而不是仅内联在文档中,则情况并非如此。
理想情况下,表格前导解析代码应该与 catcode 无关(即|
无论使用什么 catcode 都可以接受为垂直规则规范),或者如果它使用扫描标记,它应该规范化全部的具有安全 catcode 机制的数组前导
答案2
更新时间 2019-01-14
在 tabu 2.9 中已经应用了等效补丁,并已提交给 ctan。
编辑:经过评论讨论,事实证明我低估了这个问题,并且大卫的答案比我的更接近正确答案。
这里涉及三种 catcode 机制。按时间顺序排列如下:
tabu
当读取(标记化)包的代码时,catcodes 生效;tabu
当环境的前言被标记时,catcodes 生效;- 执行时 catcodes 生效
tabu
(此处,在 verbatim 环境中)。
该tabu
包 (v2.8) 假设 catcode 机制 2 和 3 是相同的(但它误用了\scantokens
,而 只能与 结合使用\everyeof
— 请参阅下面的相应代码 —)。具体而言,它尝试使用机制|
3 中由 分隔的宏来解析前导码(其 catcodes 属于机制 2)。当像问题中那样使用时,tabu
前导码会在早期保存(使用正常的 catcodes),并且tabu
当逐字 catcodes 生效时执行 。在这种情况下,catcode 机制 2 实际上与 catcode 机制 1 一致,因此 David 建议禁用\scantokens
是正确的,因为tabu
然后使用由机制 1 分隔的宏来解析前导码|
。
但是,一般来说,如果三个 catcode 机制不同,则两种解决方案都可能失败,例如,如果|
被声明为 verbatim 的简写字符,就会发生这种情况。在这种情况下,最简单的方法是使用 David 的建议,同时确保在读取禁忌包代码时使用适当的类别代码对前言进行标记,从而使用正常的类别代码。例如,从下面的代码中tabu
删除\DeleteShortVerb
(以及后续的)行将失败,因为无法识别前言中的active 。\MakeShortVerb
tabu
|
\documentclass{article}
\usepackage{verbatim}
\usepackage{tabu}
\usepackage{shortvrb}
\MakeShortVerb{\|}
\begin{document}
We first input the file \jobname.tex with
|\verbatiminput{\jobname.tex}|:
\verbatiminput{\jobname.tex}%
Then redefine |\verbatim@processline|
%
\makeatletter
\DeleteShortVerb{\|}
\renewcommand\verbatim@processline
{{\let\scantokens\@firstofone
\begin{tabu}to\textwidth{|[5pt]l|X[-1,l]|}%
foo&\the\verbatim@line%
\end{tabu}%
}\par}
\MakeShortVerb{\|}
\makeatother
%
and input the file again with the same command:
\verbatiminput{\jobname.tex}%
\end{document}
完全正确的修复方法是彻底改变tabu
序言的解析方式,用一种方法替换当前方法(来自 LaTeXe到*
),该方法从左到右读取序言中的字符,忽略它们的 catcode,检查它们是否是“原始”列类型或应该扩展为其他类型,检查这些列类型的参数,完成后,转到序言中的下一个标记。array
\newcolumntype
eTeX 原语\scantokens
的使用非常棘手,而 tabu 滥用了它(而且在很多地方)。这显然是 tabu 的一个错误,可以修复。
而不是
\scantokens{\def\:{|}} % bad
这是有风险的,因为\def\:
也要重新扫描(还有牙套),最好这样做
\everyeof{\noexpand}
\edef\:{\expandafter\noexpand\scantokens{|}}
即只将需要重新扫描的部分放在括号组中。确保\edef
展开\scantokens
,并将设置\everyeof
为\noexpand
可防止末尾的文件结束标记\scantokens
造成破坏。额外的\expandafter\noexpand
构造仅用于支持当前处于活动状态的情况。宏参数字符或开始或结束组标记的|
情况会破坏该代码,但这可能是不可避免的。当然,要正确使用,还需要注意(tabu 所做的),以及(如果设置为),因此,针对您的情况的正确修复是|
\scantokens
\endlinechar
\newlinechar
|
\renewcommand{\tabu@textbar}[1]%
{%
\begingroup
\newlinechar \m@ne % I'm just paranoid.
\endlinechar \m@ne
\everyeof{\noexpand}%
\edef\:{\expandafter\noexpand\scantokens{|}}%
\expandafter
\endgroup
\expandafter #1%
\:%
}
现在,在我的解决方案中,我利用了 tabu 的作者只想在这里重新扫描单个字符这一事实。当重新扫描完整的标记列表时,他应该怎么做?嗯,这更棘手,因为 TeX 总是在每个文件(包括文件\scantokens
)的末尾插入一个标记,它充当一个\outer
“东西”,例如,防止出现在一个文件中的宏将其参数放在另一个文件中。答案可以在\tl_set_rescan:Nnn
LaTeX3 中的实现中找到,也可以在 Heiko Oberdiek 的一个包中找到(不知道是哪一个,欢迎参考)。构建一个在重新扫描时不能出现的标记(例如,两个@
具有不同 catcode 的标记),并将其设置为文件结束标记。然后定义一个带有由该标记分隔的参数的宏,以收集重新扫描的标记列表。例如,
\def\tabu@tmp#1%
{%
\long\def\tabu@gdef@rescan@##1#1%
{\expandafter{##1}}%
\long\def\tabu@gdef@rescan##1##2%
{%
\begingroup
\newlinechar\m@ne
\endlinechar\m@ne
\everyeof{#1\noexpand}%
\xdef##1%
{%
\unexpanded
\expandafter\tabu@gdef@rescan@
\expandafter\empty
\scantokens{##2}%
}%
\endgroup
}%
}
\expandafter\tabu@tmp\expandafter{\string @@}