诊断

诊断

xltabular我有以下代码,它可以按预期工作,但如代码中所述,当宏开始构建一行时除外:

\documentclass{article}

\usepackage[papersize={5.5in,8.5in},margin=0.6in,bottom=0.7in]{geometry}
\usepackage{array}
\usepackage{multicol}
\usepackage{xparse}
\usepackage{xltabular}
\usepackage{etoolbox}

%% https://tex.stackexchange.com/questions/487572/obeylines-and-gappto-from-etoolbox
%% Collect the body of the xltabular in \tabbody:
\begingroup
    \lccode`~=`\^^M
    \lowercase{%
\endgroup
    \def\tabline#1~{%
        \xappto{\tabbody}{\unexpanded{#1\\\hline}}~
    }
}

\makeatletter
\NewDocumentEnvironment{listit}{}{%
    \gdef\tabbody{}
    \noindent
    \begin{minipage}{\textwidth}
        \raggedcolumns
        \begin{multicols}{2}
            \col@number\@ne
            \mathchardef\LT@end@pen=0 %
            \begingroup
                \offinterlineskip
                \everypar={\tabline}
                \obeylines
}{%
        \end{multicols}
    \end{minipage}
}
\makeatother

\NewDocumentCommand{\explain}{}{%
            \endgroup
            \vspace{-\baselineskip}
            \begin{xltabular}{\linewidth}{X}
                \hline\hline
                \tabbody
                \hline
            \end{xltabular}
}

\begin{document}

\begin{listit}
Lorem ipsum dolor
sit amet, consectetur
adipiscing elit
sed do eiusmod
tempor incididunt
\bfseries See Note
ut labore
et dolore
magna aliqua.
Ut enim ad
\explain

Note: \verb+\bfseries+ by itself is ignored. \verb+\textbf{See Note}+ produces \verb+Extra \fi+ error.
\end{listit}

\end{document}

在此处输入图片描述

使用\leavevmode\bfseries See Note会产生预期的输出。为什么这是必要的?

我错过了什么?

答案1

诊断

问题在于,你的行\tabbody是由宏收集的\tabline,宏由 插入\everypar。但是,这些\everypar标记仅在段落开始时插入,并且\bfseries在垂直模式下使用不会导致 TeX 开始一个段落(即切换到水平模式)。因此,当设计为由 读取的特殊行之一以\tabline开头\bfseries时,TeX 在找到 时仍处于垂直模式\bfseries(这是在行尾变为 之后\par)。它会扩展它,并且只是后来当 〈水平命令〉1导致切换到水平模式时,它会插入 ,\tabline它将收集行的剩余部分。但当这种情况发生时,已经太晚了,已经完全展开和消化,它不会被插入的\bfseries抓取为 的一部分。#1\tabline

例如:假设 TeX 正在收集以下行:

tempor incididunt
\bfseries See Note

tempor incididunt是当段落以from开头时\tabline通过 插入的宏的第一个参数(是一个 〈字母〉 ,因此是一个 〈水平命令〉 ;当 TeX 在垂直模式下发现它时,它会切换到水平模式以开始一个新段落)。因此,从输入流中消耗了的扩展,其中表示活动的行尾字符(它是活动的,因为上面使用了 )。从输入流中删除这些标记后,将插入 的替换文本,并用 替换:\everyparttempor incididuntt\tablinetempor incididunt•\obeylines\tablinetempor incididunt#1

\xappto{\tabbody}{\unexpanded{tempor incididunt\\\hline}}•

一旦\xappto完全处理完毕,TeX 就会找到刚刚插入的 •,它已被制作成\let- 等同\par\obeylines

\obeylines:
macro:->\catcode `\^^M\active \let ^^M\par 

因此,当这个 • 标记被消化后,TeX 会结束段落并切换到垂直模式。输入流中的下一个标记是\bfseries。这是一个宏,因此它会被扩展。我传递了其扩展的详细信息 (\protect\bfseries 第二个\bfseries名称末尾有一个空格,等等。)。重要的是,这\bfseries不会开始一个新段落(其扩展名不包含任何 〈水平命令〉)。这仅当 TeX 消化来自 时才会发生SSee Note即,在\bfseries完全处理之后。此时,TeX 切换到水平模式,因为 是S〈水平命令〉;它插入缩进框(这里不可见,因为\parindent0pt),然后插入存储在 中的标记\everypar,在您的情况下为单个\tabline,并恢复对输入流的正常处理。刚插入的\tabline标记被扩展,它被抓取See Note作为第一个参数(您会看到,\bfseries不存在于该参数中,它已经在我们身后了),使用后面的活动行尾字符(因为它是宏的 〈参数文本〉 的一部分),然后插入替换文本,See Note替换为#1

\xappto{\tabbody}{\unexpanded{See Note\\\hline}}•

(请注意,此\xappto调用不会将 附加到\bfseries\tabbody它肯定已丢失\tabbody)并且该过程继续,正如我们刚才解释的那样。

建议的解决方案

我建议不要依赖\everypar。相反,我们可以使环境定义最内层的组中的行尾字符处于活动状态listit,并将活动行尾字符重新定义为等于\let\tabline参见我的\listit@obeylines宏)。这样,特殊部分中的每个行尾字符都等同于一个\tabline标记,并在下一行之前展开。这样,它#1就会抓取所有内容,直到下一个行尾,因此不会丢失任何命令。

\par此方法甚至允许您在收集的文本中使用标记\tabbody(见Paragraph break here:\par But...下面的示例)。当然,它需要一种方法来停止特殊收集过程。考虑到您的示例,我决定以 开头的行\explain标记此过程的结束(参见 \listit@checknext)。当然,如果您愿意,可以使用不同的结束标记。

\documentclass{article}
\usepackage{multicol}
\usepackage{xparse}
\usepackage{xltabular}
\usepackage{etoolbox}

\makeatletter

%% https://tex.stackexchange.com/questions/487572/obeylines-and-gappto-from-etoolbox
%% Collect the body of the xltabular in \tabbody:
\begingroup
    \lccode`~=`\^^M
    \lowercase{%
\endgroup
    \long\def\tabline#1~{%
        \xappto{\tabbody}{\unexpanded{#1\\\hline}}%
        \futurelet\next\listit@checknext
    }
    \newcommand*{\listit@obeylines}{\catcode`~=\active \let~=\tabline}
}

\newcommand*{\listit@checknext}{%
   \ifx\next\explain
     \let\next=\relax
   \else
     \let\next=\tabline
   \fi
   \next
}

\NewDocumentEnvironment{listit}{}{%
    \gdef\tabbody{}
    \noindent
    \begin{minipage}{\textwidth}
        \raggedcolumns
        \begin{multicols}{2}
            \col@number\@ne
            \mathchardef\LT@end@pen=0 %
            \begingroup
                \listit@obeylines
}{%
        \end{multicols}
    \end{minipage}
}
\makeatother

\NewDocumentCommand{\explain}{}{%
            \endgroup
            \vspace{-\baselineskip}
            \begin{xltabular}{\linewidth}{X}
                \hline\hline
                \tabbody
                \hline\noalign{\vskip 4pt}%
            \end{xltabular}
}

\begin{document}

\begin{listit}
Lorem ipsum dolor
sit amet, consectetur
adipiscing elit
sed do eiusmod
tempor incididunt
\bfseries See Note
ut labore
\textbf{See Note}
magna aliqua.
Paragraph break here:\par But we remain in the same ``line.''
\explain

Note: \verb+\bfseries+ is not ignored anymore. You can easily see that
\verb+\textbf{See Note}+ works fine too.
\end{listit}

\end{document}

截屏


脚注

  1. 例如 〈letter〉、〈otherchar〉 或\unhbox来自 的扩展\leavevmode,以及其他可能性(参见 TeXbook 第 283 页)。

相关内容