更新时间 2019-01-14

更新时间 2019-01-14

使用以下 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 机制。按时间顺序排列如下:

  1. tabu当读取(标记化)包的代码时,catcodes 生效;
  2. tabu当环境的前言被标记时,catcodes 生效;
  3. 执行时 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 。\MakeShortVerbtabu|

\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:NnnLaTeX3 中的实现中找到,也可以在 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 @@}

相关内容