我正在尝试创建一个宏,它采用可变数量的参数并创建一个表,其中每个参数占一行;因此假设该命令被调用dynamictable
,像这样调用它
\dynamictable{One & Two}{Three & Four}...{N-1 & N}
应产生:
One Two
Three Four
...
N-1 N
我的灵感来自 这篇博文以及传递性tex.stackexchange 上此问题的答案。然而,我只能生成一行下面的 MWE 没有错误 - 它几乎不是很动态!
\documentclass{minimal}
\usepackage{supertabular}
\makeatletter
\newcommand{\dynamictable}[1]{%
\begin{supertabular}[l]{c l}
#1 \checknextline
\end{supertabular}
}
\newcommand{\checknextline}{\@ifnextchar\bgroup{\gobblenextline}{\\}}
\newcommand{\gobblenextline}[1]{#1\@ifnextchar\bgroup{\gobblenextline}{}}
% This attempt gives error: Misplaced alignment character &.
%\newcommand{\gobblenextline}[1]{#1\\\@ifnextchar\bgroup{\gobblenextline}{}}
\makeatother
\begin{document}
\dynamictable{(1,1) & (1,2)}\par
% Error: Misplaced alignment character &.
%\dynamictable{(1,1) & (1,2)}{(2,1) & (2,2)}\par
\end{document}
问题:如何修复上述示例以生成多行?任何建议都非常感谢!
答案1
作为egreg 指出\dynamictable
,与使用以 结尾行的标准表格标记相比,这种接受“任意数量参数”的命令给我们带来的好处\\
并不十分明显。但让我们把它当作练习来研究一下。:-)
你的代码会发生什么情况
您收到的错误是由于您的\checknextline
调用后紧接着\end{supertabular}
。因此,在\dynamictable
吞噬并插入第一个参数之后(1,1) & (1,2)
,\checknextline
发现下一个标记是\end
from \end{supertabular}
;因为它不\let
等于 a \bgroup
,所以\checknextline
插入\\
并且不递归。因此,supertabular
环境完成,然后 TeX 在输入流中找到第二个“参数”。事实上,对于 TeX 来说,这不是一个参数,而是一个带括号的组。TeX 在&
这个组内找到了 ,由于我们不再位于任何对齐内(tabular
& 朋友),所以类别代码为 4(对齐选项卡)的这个字符标记无效,从而给出错误:
./orig.tex:22: Misplaced alignment tab character &.
l.22 \dynamictable{(1,1) & (1,2)}{(2,1) &
如何修复
我们supertabular
现在先不考虑这个游戏,它只会让事情变得复杂。第一次尝试修复可能是(虽然很丑陋,但说明了原因):
\documentclass{article}
\makeatletter
\newcommand{\dynamictable}[1]{%
\begin{tabular}[l]{c l}
#1 \checknextline
}
\newcommand{\checknextline}{\@ifnextchar\bgroup{\gobblenextline}{\\}}
\newcommand{\gobblenextline}[1]{#1\@ifnextchar\bgroup{\gobblenextline}{}}
\makeatother
\begin{document}
\dynamictable{(1,1) & (1,2)}{(2,1) & (2,2)}
\end{tabular}
\end{document}
失败原因:
./a.tex:16: Extra alignment tab has been changed to \cr.
<recently read> \endtemplate
l.16 \dynamictable{(1,1) & (1,2)}{(2,1) & (2,2)}
因为\checknextline
调用\gobblenextline
会插入其#1
(第二个“参数”)而不添加任何\\
。因此 TeX 在对齐的同一行上找到两个对齐制表符(&
),此时看起来像:
(1,1) & (1,2)(2,1) & (2,2)
这与您指定的前言不匹配(至少需要 3 列),因此出现错误。\\
可以使用以下命令插入相应的内容:
\newcommand{\checknextline}{\@ifnextchar\bgroup{\gobblenextline}{}}
\newcommand{\gobblenextline}[1]{\\ #1\@ifnextchar\bgroup{\gobblenextline}{}}
文档主体如下:
\begin{document}
\dynamictable{(1,1) & (1,2)}{(2,1) & (2,2)}
\end{tabular}
\end{document}
这完美地工作。
但这不是漂亮的语法,因此下一步,可以创建一个\end{tabular}
在末尾插入其自身的环境,而不是必须在后面跟上命令\end{tabular}
:
\documentclass{article}
\makeatletter
\newenvironment{dynamictable}[1]{%
\begin{tabular}[l]{c l}
#1\checknextline
}{%
\end{tabular}%
}
\newcommand{\checknextline}{\@ifnextchar\bgroup{\gobblenextline}{}}
\newcommand{\gobblenextline}[1]{\\ #1\@ifnextchar\bgroup{\gobblenextline}{}}
\makeatother
\begin{document}
\begin{dynamictable}{(1,1) & (1,2)}{(2,1) & (2,2)}
\end{dynamictable}
\end{document}
这可行,但是:
可以通过在我们的自定义环境中使用
\tabular
and\endtabular
而不是\begin{tabular}
and来改进错误报告\end{tabular}
(这样,如果在处理dynamictable
环境时出现错误,则会报告为发生在该环境内部,而不是tabular
最终用户未明确使用的环境中);将第一个括号组作为环境的参数似乎没有什么用;表格内容也可以属于环境主体。
通过实施这些小改进和针对环境的常规空白处理,我们获得:
\documentclass{article}
\makeatletter
\newenvironment{dynamictable}{%
\tabular[l]{c l}
\checknextline
}{%
\unskip
\endtabular
\ignorespacesafterend
}
\newcommand{\checknextline}{\@ifnextchar\bgroup{\gobblenextline}{}}
\newcommand{\gobblenextline}[1]{#1\@ifnextchar\bgroup{\\ \gobblenextline}{}}
\makeatother
\begin{document}
\begin{dynamictable}
{(1,1) & (1,2)}
{(2,1) & (2,2)}
\end{dynamictable}
\end{document}
\@ifnextchar
具有在向前查找“下一个字符”时忽略空格标记的属性,这使得我们可以将两个括号组放在不同的行上以提高可读性。
唉,这不适用于supertabular
(我还没有检查为什么),但似乎这个包已经被longtable
许多用例所取代,并且上面描述的方法确实适用于longtable
:
\documentclass{article}
\usepackage{longtable}
\makeatletter
\newenvironment{dynamictable}{%
\longtable[l]{c l}
\checknextline
}{%
\unskip
\endlongtable
\ignorespacesafterend
}
\newcommand{\checknextline}{\@ifnextchar\bgroup{\gobblenextline}{}}
\newcommand{\gobblenextline}[1]{#1\@ifnextchar\bgroup{\\ \gobblenextline}{}}
\makeatother
\begin{document}
\begin{dynamictable}
{(1,1) & (1,2)}
{(2,1) & (2,2)}
\end{dynamictable}
\end{document}
(与上一张截图相同)
保留命令语法
正如 Skillmon 所指出的,可以保留命令的语法,\dynamictable
而不是使其成为环境(我相信我更喜欢环境语法,但您当然可以自由选择您喜欢的任何一种)。为此,所需要做的就是从内部插入适当的\endtabular
或,当它们检测到没有其他支撑组跟随时:\endlongtable
\checknextline
\gobblenextline
\documentclass{article}
\usepackage{longtable}
\makeatletter
\newcommand{\dynamictable}{%
\longtable[l]{c l}
\checknextline
}
\newcommand{\checknextline}{%
\@ifnextchar\bgroup{\gobblenextline}{\endlongtable}%
}
\newcommand{\gobblenextline}[1]{%
#1\@ifnextchar\bgroup{\\ \gobblenextline}{\endlongtable}%
}
\makeatother
\begin{document}
\dynamictable
{(1,1) & (1,2)}
{(2,1) & (2,2)}
\end{document}
当然,你也可以将主体写成:
\dynamictable{(1,1) & (1,2)}{(2,1) & (2,2)}
如果需要的话。如果您使用此方法,则可以使用\relax
来终止表格,并确保不要错误地将以下左括号作为另一行的开头:
\dynamictable{(1,1) & (1,2)}{(2,1) & (2,2)}\relax
{\bfseries This doesn't belong to the table.}
话虽如此,此时您应该意识到我们又回到了使用结束分隔符,只是可读性不如\end{dynamictable}
。因此,我仍然更喜欢基于环境的方法。
答案2
以下抓取参数,直到下一个标记不是左括号。此外,它需要可变数量的列并将它们全部居中。您可以使用可选参数来明确指定表格前言。这个使用tabular
但我认为您可以轻松地将其更改为另一个表格环境:
\documentclass[]{article}
\usepackage{array}
\makeatletter
\newcolumntype{\RepeatCols}{!{\d@llarend&\span\@gobbletwo}}
\newcommand*\dynamictable@grab
{%
\futurelet\dynamictable@tok\dynamictable@grab@a
}
\newcommand*\dynamictable@grab@a
{%
\ifx\dynamictable@tok\@sptoken
\dynamictable@grab@eatspace
\fi
\dynamictable@grab@b
}
\newcommand*\dynamictable@grab@eatspace[1]
{%
\def\dynamictable@grab@eatspace\fi##1#1{\fi\dynamictable@grab}%
}
\dynamictable@grab@eatspace{ }
\newcommand*\dynamictable@grab@b
{%
\ifx\dynamictable@tok\bgroup
\dynamictable@grab@c
\fi
\end{tabular}%
}
\newif\ifdynamictable@first
\def\dynamictable@grab@c\fi\end#1#2%
{%
\fi
\ifdynamictable@first\else\\\fi #2
\dynamictable@grab
}
\newcommand\dynamictable[1][c \RepeatCols c]
{%
\begin{tabular}{#1}%
\dynamictable@firsttrue
\dynamictable@grab
}
\makeatother
\begin{document}
\dynamictable{(1,1) & (1,2)}\par
% Error: Misplaced alignment character &.
\dynamictable{(1,1) & (1,2)}{(2,1) & (2,2)}\par
\dynamictable{(1,1) & (1,2) & (1,3)}{(2,1) & (2,2) & (2,3)}\par
\end{document}
以下是另一种实现,它接受任意数量的参数,但用额外的括号括起来。
\documentclass[]{article}
\usepackage{array}
\makeatletter
\newcolumntype{\RepeatCols}{!{\d@llarend&\span\@gobbletwo}}
\begingroup
\xdef\dynamictable@mark
{%
\unexpanded\expandafter
{%
\csname dynamictable Error: mark expanded.\endcsname
\dynamictable@mark
}%
}
\endgroup
\long\def\dynamictable@end\fi\@firstofone#1%
{%
\fi
\end{tabular}%
}
\newcommand\dynamictable@iterator[1]
{%
\ifx\dynamictable@mark#1%
\dynamictable@end
\fi
\@firstofone{#1\\\dynamictable@iterator}%
}
\newcommand\dynamictable[2][c \RepeatCols c]
{%
\begin{tabular}{#1}%
\dynamictable@iterator#2\dynamictable@mark
}
\makeatother
\begin{document}
\dynamictable{{(1,1) & (1,2)}}\par
% Error: Misplaced alignment character &.
\dynamictable{{(1,1) & (1,2)}{(2,1) & (2,2)}}\par
\dynamictable{{(1,1) & (1,2) & (1,3)}{(2,1) & (2,2) & (2,3)}}\par
\end{document}
(两个代码版本的输出)