\chapter{表格}

表格通常用于呈现一些成行成列的数据，但是不要对此有刻板印象，有时一些非常规的页面布局，若基于表格实现，也许颇具奇效。\CONTEXT\ 有三种表格，从易到难，依次是 tabulate，table 和 xtable，详见文档 \cite[tables]，本章仅讲述 tabulate，而且我也建议你应该先掌握 tabulate，待其难以满足需求时，再考虑学习另外两种。

\section{基本格式}

以下代码构造了一个 2 行 3 列的表格\index[tabulate]{\type{tabulate} 环境}，第 1 列居左（\type{l}），第 2 列居中（\type{c}），第 3 列居右（\type{r}）。

\startexample
\starttabulate[|l|c|r|]
\NC 1 \NC 2 \NC 3 \NR
\NC 4 \NC 5 \NC 6 \NR
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\getexample}

\noindent\boxquote{\type{|l|c|r|}}是表格各列的格式声明，你可以不指定各列的对齐方式，例如\boxquote{\type{||||}}，但是格式声明不能省略，否则表格的排版结果可能会变得非常诡异。\tex{NC} 用于构造新的单元格，\type{\NR} 用于构造一个新行。

\section{边线}

将 \type{\NC} 替换为 \type{\VL}，用 \type{\HL} 可以画出单元格的左右边线。例如

\startexample
\starttabulate[|l|c|r|]
\HL
\VL 1 \VL 2 \VL 3\VL\NR
\HL
\VL 4 \VL 5 \VL 6\VL\NR
\HL
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\getexample}

上例表格的竖线像是被横线打断了，实际上是因为 \type{tabulate} 环境构造的表格，各行之间默认存在一定间距导致的。若想消除该现象，只需将单元格之间的纵向间距参数 \type{distance} 设为 0 尺度，例如 0mm，也可以设成 \type{none}。例如

\startexample
\starttabulate[|l|c|r|][distance=0mm]
\HL
\VL 1 \VL 2 \VL 3 \VL\NR
\HL
\VL 4 \VL 5 \VL 6 \VL\NR
\HL
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\getexample}

\section{对齐的假象}

在上文的所有示例里，第 1 列和第 3 列的对齐实际上存在假象。即使将它们也设为居中对齐，它们的位置似乎依然不变，见下例，第 1 列依然是居左的，第 3 列依然是居右的。即使将第 1 列设为 \type{r}，将第 3 列设为 \type{l}，结果依然一样。甚至，即使你将第 2 列设为 \type{l} 或 \type{r}，结果它依然是居中的。

\startexample
\starttabulate[|c|l|c|]
\VL 1 \VL 2 \VL 3 \VL\NR
\VL 4 \VL 5 \VL 6 \VL\NR
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\getexample}

事实上，\type{tabulate} 单元格内容的对齐，并不以单元格空间亦即一个矩形框为基准，而是以这列内容本身为基准。例如，如果某列设为 \type{l}，仅仅是意味着这列内容是左对齐的。上述例子之所以看不到这种效果，是因为单元格的内容过于简单，下例让单元格的内容变得长短不一，便可呈现该效果。

\startexample
\starttabulate[|r|c|l|]
\VL 1 \VL 2 \VL 3.14159 \VL\NR
\VL 3.14159 \VL 5 \VL 6 \VL\NR
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\getexample}

造成假象的另一个原因是，\type{tabulate} 各列内容之间默认存在间距，而该间距并不属于单元格。你应该将表格的竖线理解成有一些潜在的宽度的线，该宽度值可通过 \type{unit} 参数予以设定，下例将其设为 0 长度，结果虽然丑陋，但如果你真的希望单元格的内容真正位于单元格的左侧、中央还是右侧，则必须如此设定。

\startexample
\starttabulate[|r|c|l|][unit=0mm]
\VL 1 \VL 2 \VL 3.14159 \VL\NR
\VL 3.14159 \VL 5 \VL 6 \VL\NR
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\getexample}

在上例的基础上，我们只要设定各列宽度，便可以得到你预想的的对齐效果，见下例。设定列宽的格式声明是 \type{w(尺寸)}，可与对齐声明联合使用。

\startexample
\starttabulate[|rw(2cm)|cw(1cm)|lw(1.5cm)|][unit=0mm]
\VL 1 \VL 2 \VL 3.14159 \VL\NR
\VL 3.14159 \VL 5 \VL 6 \VL\NR
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\getexample}

\section{三线表}

\type{tabulate} 环境主要是为科技文献排版中常用的三线表设计的，这是造成第一节中那些对齐假象的最根本的原因。在三线表中，是不需要表格竖线的，故而默认的 \type{unit} 参数值，不会给你造成任何不适，见下例。

\startexample
\starttabulate[|c|c|c|]
\HL
\NC 甲 \NC 乙 \NC 丙 \NR
\HL
\NC 一 \NC 二 \NC 三 \NR
\NC one \NC two \NC three \NR
\HL
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\getexample}

不过，三线表的顶线和底线，粗度要比表格内的线要粗一些。虽然 \tex{HL} 不能设定粗细，但 \type{tabulate} 环境提供了 \tex{TL} 和 \tex{BL}，可分别用于绘制指定粗细以及颜色的表格顶线和底线，T 表示 Top，B 表示 Bottom，这两种横线命令的用法如下：

\startexample
\starttabulate[|c|c|c|]
\TL[3]
\NC 甲 \NC 乙 \NC 丙 \NR
\HL
\NC 一 \NC 二 \NC 三 \NR
\NC one \NC two \NC three \NR
\BL[3,darkred]
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\getexample}

如果表格还需要定制更多横线的粗细， \type{tabulate} 环境还提供了 \tex{FL}、\tex{ML} 和 \tex{LL}，分别用于构造表格顶线之下的第一条横线，中间的横线以及最后一条横线。因而除了默认粗度的 \tex{HL} 之外，共有 5 种可设定粗细的线性备用，对于三线表而言，这些线足够用了，实际上我们只需要两种线，一种是 \tex{HL}，另一种是可设定粗细的，T、F、M、L、B 只是希望横线具有语义。

可设定横线粗细的命令，设定线的粗细的数字参数，其值是表格线默认粗度的倍数。实际上，还有一个数字参数，其值也是表格线默认粗度的倍数，用于构造虚线，即虚线的空白间隔是表格线默认粗度的倍数，见下例。

\startexample
\starttabulate[|c|c|c|]
\TL[3]
\NC 甲 \NC 乙 \NC 丙 \NR
\FL[1, 2, blue]
\NC 一 \NC 二 \NC 三 \NR
\NC one \NC two \NC three \NR
\BL[3,darkred]
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\getexample}

\section{段落成列}

单元格里，可以放置含有段落的内容，格式声明为 \type{p} 或 \type{p(尺寸)}，后者可限定段落的宽度。段落本身以及段落所能包含的任何内容，皆能嵌入在单元格内。下例在单元格内嵌入了一个列表。

\startexample
\starttabulate[|c|p(4cm)|][align=normal]
\TL[3]
\NC 列表 \NC
         \startitemize
         \item 中午，晒十五分钟太阳
         \item 晚上，看流浪地球\,\Romannumerals{2}
         \stopitemize \NR
\BL[3]
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\getexample}

若段落的格式声明未提供宽度，单元格的宽度会尽可能地占满页面空间。例如，如果表格只有 1 列，则该列的宽度会占满正文宽度，如果表格有 2 列，则这两列平分正文宽度，见下例。

\startexample
\starttabulate[|p|p|]
\TL[3]
\NC 天下皆知美之为美，斯恶已；皆知善之为善，斯不善已。
\VL 道冲而用之或不盈。渊兮似万物之宗。
\NR
\BL[3]
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\null}
\getexample

不过，上例有诡异之处，最右侧单元的内容没有顶格放置，而是下沉到单元底部了。具体原因不明，而且只有列格式为段落时，才会出现该问题。解决方法很简单，只需在 \tex{NR} 之间添加 \tex{NC}，见下例。

\startexample
\starttabulate[|p|p|]
\TL[3]
\NC 天下皆知美之为美，斯恶已；皆知善之为善，斯不善已。
\VL 道冲而用之或不盈。渊兮似万物之宗。
\NC\NR  % 关键改动之处
\BL[3]
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\null}
\getexample

{\bf 为稳妥起见，在使用表格时，建议在每个 \tex{NR} 之前都添加 \tex{NC}，\CONTEXT\ 官方文档也是如此写法。}

\section{像插图那样}

虽然我还没有介绍如何在文档中插入图片，但是表格可以像插图那样出现于文档，通常居中放置，并配备标题，\tex{placetable} 命令可做此事，见下例。不过，该例也有诡异之处，即使右侧列格式是段落，\tex{NR} 之前没有放置 \tex{NC}，但是在 \tex{placetable} 的作用下，右侧单元内容的位置变得正常了。

\mainlanguage[en]
\startexample
\placetable[here][]{道德经里的句子}{
  \starttabulate[|p|p|]
  \TL[3]
  \NC 天下皆知美之为美，斯恶已；皆知善之为善，斯不善已。
  \VL 道冲而用之或不盈。渊兮似万物之宗。
  \NR
  \BL[3]
  \stoptabulate
}
\stopexample
\simpleexample[option=TEX]{\null}
\getexample

\CONTEXT\ 默认的表格标题的位置以及序号前缀皆不合中文文档的常见格式，存在以下问题：

\startitemize[packed]
\item 序号的前缀应该\boxquote{表}，而非 \boxquote{Table}；
\item 序号部分不应该是粗体，且字号应当比正文小一号；
\item 标题中的文字过于蓬松，且应当位于表格上方。
\stopitemize

以下代码能够让表格标题的样式符合中文排版惯例。希望你还没有忘记 \in[style] 节所讲的内容与样式分离，你应当将这些设定放置于文档的样式文件。

\startTEX
% 开启中文界面
% 将「Table」和「Figure」等序号前缀切换为「表」和「图」等。
\mainlanguage[cn]/BTEX\index[mainlanguage]{\tex{mainlanguage}}/ETEX

% 将表格标题改为顶部放置
\setupcaption
  [table][headstyle=normal, style=small, align=center, location=top]
% 上述设定亦可写成
% \setupcaption
%   [table]
%   [headstyle=\rm, style=\tfx, align=center, location=top]
\stopTEX

\noindent \tex{setupcaption}\index[setupcaption]{\tex{setupcaption}} 的 \type{headstyle} 参数用于设定表格标题的序号部分（包括前缀）的样式，其值为 \type{normal} 时，表示使用字族中的正体，等效于 \tex{rm}；该参数的默认值是 bold，即粗体。\type{style} 参数用于设定标题的正体风格，其值 \type{small} 表示用小号字，等效于 \tex{tfx}。\type{align} 参数，在 \in[essay] 节已经见过了，应当将其设为 \type{center} 方能避免标题汉字之间的粘连被意外激活而导致文字蓬松。\type{location} 参数用于设定标题在表格上方（top）还是在底部（bottom)，还是在其他位置。上述样式设定产生的效果见表 \in[table-cn-style]。

\mainlanguage[cn]
\setupcaption
  [table]
  [headstyle=normal, style=\tfx, align=center, location=top]

\placetable[here][table-cn-style]{道德经里的句子}{
  \starttabulate[|p|p|]
  \TL[3]
  \NC 天下皆知美之为美，斯恶已；皆知善之为善，斯不善已。
  \VL 道冲而用之或不盈。渊兮似万物之宗。
  \NR
  \BL[3]
  \stoptabulate
}

\tex{placetable} 命令需要 4 个参数，其形式可抽象为

\startTEX
\placefigure[位置][引用]{标题}{\starttabulate ... \stoptabulate}
\stopTEX

上例将\boxquote{位置}设定为 \type{here}，意思是就在 \tex{placetable} 出现的位置插入表格，但并不是强制的，若需要强制，应将 \type{here} 换成 \type{force}。

也许你会有所不解，表格出现的位置还需要设定吗？它难道不是理所应当出现在 \tex{placetable} 所在的位置吗？答案是，\CONTEXT\ 的表格和插图是浮动的，排版引擎会尽量为它们寻找合适的安放空间，而 \type{here} 和 \type{force} 可以禁止排版引擎的自作聪明。\boxquote{位置}参数也可以设定为 \type{left} 和 \type{right}，分别将表格放置于页面的左侧和右侧。

至于 \tex{placetable} 的\boxquote{引用}，其幕后的机制和用法与 \in[itemcite] 节介绍的列表项引用相似。你可以为表格设定一个引用名，例如 \type{foo}，然后在正文里可以用 \tex{in[foo]} 以序号的形式引用该表格，例如

\startTEX
如表 \in[foo] 所示，... ... ...\par
\placefigure[here][foo]{标题}{\starttabulate ... \stoptabulate}
\stopTEX

\noindent 如果一个表格不需要任何引用，则可像上例，将此项空置即可。

如果你不需要表格带有标题，但是又想利用 \tex{placetable} 的功能，可在该命令的第 1 个参数添加 \type{none}，然后将表格标题置空，例如

\blank[halfline]

\placetable[right,none][]{}{
  \starttabulate[|c|c||c]
  \NC 2 \NC 9 \NC 4 \NR
  \NC 3 \NC 5 \NC 7 \NR
  \NC 6 \NC 1 \NC 8 \NR
  \stoptabulate
}

\startTEX
\placetable[right,none][]{}{
  \starttabulate[|c|c||c]
    \NC 2 \NC 9 \NC 4 \NR
    \NC 3 \NC 5 \NC 7 \NR
    \NC 6 \NC 1 \NC 8 \NR
  \stoptabulate
}
\stopTEX

\section{横列表}

也许你是一位教师，经常用 \CONTEXT\ 排版试卷。试卷的选择题，选项较短时，通常是水平排列的四项，序号是 A、B、C、D。你可以用 \type{itemize} 的分列\index[hengxlist]{横向列表}功能来完成该任务，例如

\startexample
\startitemize[A, columns, four]
\item 花非花 \item 雾非雾 \item 夜半来 \item 天明去
\stopitemize
\stopexample
\simpleexample[option=TEX]{\null}
\getexample

若你不知 \type{itemize} 环境具有上述用法，也可以用表格解决这个问题。例如

\startexample
\starttabulate[|p|p|p|p|]
\NC A. 花非花 \NC B. 雾非雾 \NC C. 夜半来 \NC D. 天明去 \NC\NR
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\null}

\noindent 虽然表格方案并不比列表更好，但是在略微复杂一些的情况里，例如当你需要将 A 和 B 项放在一行，C 和 D 项放在下一行时，列表方案就会失效，它会将 A 和 B 放在一列，C 和 D 放在一列，见下例。

\startexample
\startitemize[A, nowhite, columns, two]
\item 花非花 \item 雾非雾 \item 夜半来 \item 天明去
\stopitemize
\stopexample
\simpleexample[option=TEX]{\null}
\getexample
\blank[halfline]

\noindent 之所以如此，是因为 \type{itemize} 环境是纵向优先。要解决这个问题，列表方案必须拆解为两个单行的列表，让第 2 个列表的序号续接第 1 个，见下例。

\startexample
\startitemize[A, nowhite, columns, two]
\item 花非花 \item 雾非雾
\stopitemize
\startitemize[A, nowhite, columns, two, continue]  
\item 夜半来 \item 天明去
\stopitemize
\stopexample
\simpleexample[option=TEX]{\null}
\getexample
\blank[halfline]

对于上述问题，在表格方案里，就很简单，只需要构造两行两列的表格即可，如下

\startTEX
\starttabulate[|p|p|]
\NC A. 花非花 \NC B. 雾非雾 \NC\NR
\NC C. 夜半来 \NC D. 天明去 \NC\NR
\stoptabulate
\stopTEX

\section{样式设定}

\type{tabulate} 环境的第 2 个参数可以设定表格的样式，例如上文设定过 \type{distance} 和 \type{unit} 的值。如果你的所有表格需要使用同一类样式，可以用 \tex{setuptabulate} 命令\index[setuptabulate]{\tex{setuptabulate}}统一设定，无需每个表格都作设定。例如

\startexample
\setuptabulate[distance=0mm, unit=0mm, rulethickness=4pt]
\starttabulate[|p|p|]
\HL
\NC A. 花非花 \NC B. 雾非雾 \NC\NR
\NC C. 夜半来 \NC D. 天明去 \NC\NR
\HL
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\null}
\start \getexample\stop

\noindent \type{rulethickness} 用于设定表格线条的粗度。

如果你不想让表格的样式设定影响所有表格，而是想让它仅仅影响某类表格，你可以用 \tex{definetabulate}\index[definetabulate]{\tex{definetabulate}} 定义自己的表格类型，然后只为该类型的表格设定样式。例如

\startexample
\definetabulate[Options][|p|p|]
\setuptabulate[Options][rulethickness=4pt]
\startOptions
\HL
\NC A. 花非花 \NC B. 雾非雾 \NC\NR
\NC C. 夜半来 \NC D. 天明去 \NC\NR
\HL
\stopOptions
\stopexample
\simpleexample[option=TEX]{\null}
\getexample

\subject{结语}

本文并未介绍 \type{tabulate} 环境的一切用法，例如单元格跨列、表格分页以及前景和背景颜色设置等主题皆未涉及，如果你需要了解这部分功能，可阅读文档 \cite[tabulation]。\type{tabulate} 环境有很多局限性，例如你无法让单元格跨越多行，也难以让单元格的内容能在竖直方向居于单元格空间的中部。如果你遇到了它的这些局限，就意味着你应该学习 \type{table} 环境了，见文档 \cite[natual-table]。
