你的代码会发生什么情况

你的代码会发生什么情况

我正在尝试创建一个宏,它采用可变数量的参数并创建一个表,其中每个参数占一行;因此假设该命令被调用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发现下一个标记是\endfrom \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}

这可行,但是:

  • 可以通过在我们的自定义环境中使用\tabularand\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}

在此处输入图片描述

(两个代码版本的输出)

相关内容