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 在垂直模式下发现它时,它会切换到水平模式以开始一个新段落)。因此,从输入流中消耗了的扩展,其中表示活动的行尾字符(它是活动的,因为上面使用了 )。从输入流中删除这些标记后,将插入 的替换文本,并用 替换:\everypar
t
tempor incididunt
t
\tabline
tempor incididunt•
•
\obeylines
\tabline
tempor 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 消化来自 时才会发生S
,See Note
即,在\bfseries
完全处理之后。此时,TeX 切换到水平模式,因为 是S
〈水平命令〉;它插入缩进框(这里不可见,因为\parindent
是0pt
),然后插入存储在 中的标记\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}
脚注
- 例如 〈letter〉、〈otherchar〉 或
\unhbox
来自 的扩展\leavevmode
,以及其他可能性(参见 TeXbook 第 283 页)。