% ^^A ================================== 元注释 ================================== % \iffalse meta-comment % % 文件:tabular2.dtx % % 版权 (C) 2023-2026 By Ms_yam % % 它可以在 LaTeX 项目公共许可(LPPL)1.3c 及之后的任意版本(随你的意见)下分发或修改。 % 这个许可的最新版本在如下文件中: % % https://www.latex-project.org/lppl.txt % % 本文件的 LPPL 维护状态为 "maintained"。 % % 本文件的当前维护者是 Ms_yam。 % % \fi % % ^^A ================================== 安装信息 ================================== % \iffalse %<*install> \begingroup \input docstrip.tex \keepsilent \askforoverwritefalse \preamble \endpreamble \postamble \endpostamble \generate{ \file{tabular2.ins} {\from{\jobname.dtx}{install}} \file{tabular2.sty} {\from{\jobname.dtx}{package}} \let\MetaPrefix\relax \def\MetaPrefix{} \nopreamble\nopostamble \file{README.md} {\from{\jobname.dtx}{readme}} } \ifx\documentclass\@undefined \endbatchfile \fi \endgroup % % \fi % % ^^A ================================== 文档驱动 ================================== % \iffalse %<*driver> \documentclass[show-notes]{l3doc} \usepackage{tabular2} % ^^A 添加中文支持及设置超级链接 \usepackage[UTF8,heading]{ctex} \hypersetup{ colorlinks, linkcolor=blue, % hyperindex, pdfstartview=FitH, plainpages=false, % backref, } \usepackage{enumitem} \setlist{noitemsep,topsep=0pt,parsep=0pt,partopsep=0pt} % ^^A 引用待办包 \usepackage[nothing]{todo} % ^^A 汉化 l3doc 的部分定义 \DeclareDocumentEnvironment { texnote } { } { \endgraf \vspace{0.5em} % ^^A 3mm => 0.5em \small\textbf{\TeX{} 黑客笔记:} % ^^A \TeX~hackers~note: } { \vspace{0.5em} } % ^^A 3mm => 0.5em \ExplSyntaxOn \NewDocumentCommand {\ttt} { m } { \texttt{\tl_to_str:n{#1}} } \ExplSyntaxOff % ^^A 索引排除 \DoNotIndex{ \+, \-, \., \\, \ , ., .., \c, \d, \s, \A, \Z } \DoNotIndex{ \begin, \end, \par, \newline, \baselineskip, \noindent, \rule, \textwidth, \textheight, \hsize, \hspace, \parindent, \vfill, \vskip, \centering, \raggedleft, \raggedright, \thefootnote, \footnote, \footnotemark, \footnotetext } \DoNotIndex{ \NeedsTeXFormat, \RequirePackage, \ProvidesExplPackage, \ProcessKeyOptions, \NewDocumentCommand, \NewDocumentEnvironment, \ExplSyntaxOff, \IfBooleanF, \IfNoValueTF } \DoNotIndex{ \group_begin:, \group_end: } \DoNotIndex{ \cs_new:Nn, \cs_new:Npn, \cs_set:Nn, \cs_set_eq:NN, \cs_set_eq:Nc, \cs_gset_eq:NN, \cs_if_free:NT, \cs_if_exist:NTF, \cs_if_exist:cF, \cs_generate_variant:Nn, \exp_args:Ne, \use:N, \use:c } \DoNotIndex{ \prg_new_conditional:Nnn, \prg_generate_conditional_variant:Nnn, \prg_replicate:nn, \prg_return_true:, \prg_return_false: } \DoNotIndex{ \msg_new:nnn, \msg_set:nnn, \msg_error:nnn, \msg_error:nnV } \DoNotIndex{ \bool_new:N, \bool_new:c, \bool_gset_eq:NN, \bool_gset_eq:Nc, \bool_gset_eq:cN, \bool_set_eq:NN, \bool_gset_true:N, \bool_gset_false:N, \bool_set_true:N, \bool_set_false:N, \bool_if:NT, \bool_if:NF, \bool_if:NTF, \bool_if:cTF, \bool_if:nTF, \bool_show:N, \bool_log:N, \bool_until_do:nn, \c_true_bool } \DoNotIndex{ \tl_new:N, \tl_const:Nn, \tl_clear:N, \tl_set_eq:NN, \tl_set:Nn, \tl_set:NV, \tl_set:Ne, \tl_set:No, \tl_if_empty:NT, \tl_if_empty:NF, \tl_if_empty:NTF, \tl_if_empty:nT, \tl_if_empty:nF, \tl_if_empty:nTF, \tl_if_eq:NnTF, \tl_if_eq:VnT, \tl_if_in:NnT, \tl_put_left:Nn, \tl_put_left:NV, \tl_put_right:Nn, \tl_put_right:NV, \tl_replace_all:Nnn, \tl_to_str:N, \tl_to_str:n } \DoNotIndex{ \str_new:N, \str_const:Nn, \str_const:Ne, \str_clear:N, \str_set:Ne, \str_put_right:Nn, \str_if_empty:nTF, \str_if_eq:nnT, \str_if_eq:VnT, \str_if_eq:VVT, \str_if_eq:nnTF, \str_if_eq:VnTF, \str_if_in:NnF, \str_if_in:nnTF, \str_case:Vn, \str_case:nnF, \str_case:VnF, \str_case:NnF, \str_case:NnTF, \str_case_e:nnF, \str_map_inline:nn } \DoNotIndex{ \char_generate:nn } \DoNotIndex{ \regex_const:Nn, \regex_extract_all:NnN, \regex_extract_all:NnNF, \regex_extract_all:nnNF, \regex_match:nnTF } \DoNotIndex{ \int_new:N, \int_new:c, \int_gset_eq:NN, \int_gset_eq:Nc, \int_gset_eq:cN, \int_set_eq:NN, \int_set_eq:Nc, \int_zero:N, \int_gzero:N, \int_incr:N, \int_gincr:N, \int_add:Nn, \int_add:cn, \int_gset:Nn, \int_gset:cn, \int_set:Nn, \int_if_zero:nTF, \int_if_zero:nT, \int_if_zero:nF, \int_case:nn, \int_compare:nNnT, \int_compare:nNnF, \int_compare:nNnTF, \int_step_inline:nn, \int_step_inline:nnn, \int_step_inline:nnnn, \int_max:nn, \int_use:N, \int_to_Alph:n, \int_use:N, \int_use:c, \int_show:N, \int_log:N } \DoNotIndex{ \dim_new:N, \dim_const:Nn, \dim_zero:N, \dim_set_eq:NN, \dim_gset:Nn, \dim_set:Nn, \dim_add:Nn, \dim_sub:Nn, \dim_max:nn, \dim_eval:n, \dim_compare:nNnT, \dim_compare:nNnTF, \dim_use:N } \DoNotIndex{ \seq_new:N, \seq_new:c, \seq_gset_eq:Nc, \seq_gset_eq:cN, \seq_set_eq:NN, \seq_gclear:N, \seq_clear:N, \seq_set_split:Nnn, \seq_set_split:NVn, \seq_set_split:NnV, \seq_set_split:NVV, \seq_if_empty_p:N, \seq_if_empty:NT, \seq_if_empty:NTF, \seq_count:N, \seq_gput_left:Nn, \seq_gput_left:NV, \seq_gput_right:Nn, \seq_gput_right:Ne, \seq_gput_right:NV, \seq_gpop_left:NN, \seq_pop_left:NN, \seq_pop_left:NNT, \seq_pop_left:NNTF, \seq_put_right:Nn, \seq_put_right:NV, \seq_gset_item:Nnn, \seq_gset_item:Nne, \seq_gset_item:NnV, \seq_set_item:Nnn, \seq_get_left:NN, \seq_get_right:NN, \seq_item:Nn, \seq_map_function:NN, \seq_map_inline:Nn, \seq_show:N, \seq_log:N, \c_empty_seq } \DoNotIndex{ \prop_new:N, \prop_new:c, \prop_const_from_keyval:Nn, \prop_gset_eq:Nc, \prop_gset_eq:cN, \prop_gclear:N, \prop_clear:N, \prop_gset_eq:NN, \prop_if_empty:NTF, \prop_if_in:NVTF, \prop_gput:Nnn, \prop_gput:Nne, \prop_gput:Nen, \prop_gput:NVn, \prop_gput:NVe, \prop_gput:NnV, \prop_gput:NeV, \prop_put:Nnn, \prop_put:NnV, \prop_put:Nen, \prop_put_if_new:Nnn, \prop_gput_if_new:NnV, \prop_gpop:NnN, \prop_gpop:NeN, \prop_gpop:NnNT, \prop_get:NnN, \prop_get:NVN, \prop_get:NnNF, \prop_get:NnNTF, \prop_get:NVNTF, \prop_get:NnNT, \prop_if_in:NnT, \prop_if_in:NnTF, \prop_map_inline:Nn, \prop_map_inline:cn, \prop_map_inline:nn, \prop_show:N, \prop_log:N } \DoNotIndex{ \scan_new:N } \DoNotIndex{ \clist_new:N, \clist_if_in:nVTF, \clist_item:Nn, \clist_item:nn, \clist_item:Vn, \clist_map_inline:nn } \DoNotIndex{ \keys_define:nn, \keys_set:nn, \l_keys_choice_tl } \DoNotIndex{ \box_new:N, \box_ht:N, \box_dp:N, \box_set_dp:Nn, \box_wd:N, \box_use:N, \hbox:n, \hbox_set:Nn, \vbox_set:Nn, \vbox_set_top:Nn, \vbox_set_to_ht:Nnn, \box_set_ht:Nn } \DoNotIndex{ \color_stroke:n, \color_stroke:e } \DoNotIndex{ \draw_begin:, \draw_end:, \draw_box_use:N, \draw_box_use:Nn, \draw_linewidth:n, \draw_set_linewidth:n, \draw_set_linewidth:e, \draw_set_linewidth:V, \draw_dash_pattern:nn, \draw_set_dash_pattern:nn, \draw_set_dash_pattern:en, \draw_set_dash_pattern:Vn, \draw_path_moveto:n, \draw_path_lineto:n, \draw_path_rectangle:nn, \draw_path_use_clear:n } \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % ^^A ================================ RADME ME ================================ % \iffalse %<*readme> ^^A README # 基本说明 Description 本宏包是一个实验性的表格排版解决方案。它采用一套全新的表格输入输出接口, 使得输入输出的方式多样化且更加可读。代码层上采用 expl3 语法,完全摆脱了对传 统的底层表格命令的依赖。 This package is an experimental solution for table typesetting. It features a completely new set of input and output interfaces for tables, which diversifies the methods of input and output and enhances readability. At the code level, it is written using the expl3 syntax, completely eliminating its reliance on traditional low-level table commands. # 许可 License 版权 Copyright (C) 2023-2025 By Ms_yam 本宏包可以在 LaTeX 项目公共许可(LPPL)1.3c 及之后的任意版本(随你的意见)下分发或修改。 这个许可的最新版本在如下文件中: https://www.latex-project.org/lppl.txt It may be distributed and/or modified under the conditions of the LaTeX Project Public License (LPPL), either version 1.3c of this license or (at your option) any later version. The latest version of this license is in the file https://www.latex-project.org/lppl.txt 本文件的 LPPL 维护状态为 "maintained"。 This file has LPPL maintenance status "maintained". 本文件的当前维护者是 Ms_yam。 The current maintainer of this file is Ms_yam. 本宏包由以下文件组成: tabular2.dtx(源代码)、 tabular2.ins、 tabular2.sty、 tabular2.pdf、 tabular2-sample.tex(源代码)、 tabular2-sample.pdf 和 README.md (本文件)。 This package consists of the source file tabular2.dtx, the install file tabular2.ins, the drived files tabular2.sty, tabular2.pdf, the sample files tabular2-sample.tex, tabular2-sample.pdf and README.md(this file). # 编译 Compilation 本宏包设计为单文件包,你可以直接使用以下命令编译: This package is designed as a single-file package and can be compiled directly with the following commands: ```sh # 提取宏包文件 xetex tabular2.dtx # 使用 XeLaTeX 编译文档 xelatex tabular2.dtx makeindex -s gglo.ist -o tabular2.gls tabular2.glo makeindex -s gind.ist -o tabular2.ind tabular2.idx xelatex tabular2.dtx xelatex tabular2.dtx ``` 示例代码则直接编译(两次)即可: The sample code is compiled directly (twice): ```sh xelatex tabular2-sample.tex xelatex tabular2-sample.tex ``` # 备注 Remark * 目前属于开发阶段,变更比较频繁,部分文档暂不提供英文版,敬请谅解; * 英文版本描述由 AI 翻译,如有差异,请以中文版本为准。 * It is currently in the development stage, the change is frequent, some documents will not be provided in the English version, please understand. * The English version description is translated by AI. In case of any discrepancies, please refer to the Chinese version as the standard. % % \fi % % ^^A ================================== 文档内容 ================================== % % \changes{v0.1}{2025-08-09}{内部测试} % \changes{v0.2}{2025-08-09}{初版发行} % \changes{v0.3}{2025-09-03}{添加独立的行列名设置功能} % \changes{v0.3}{2025-09-03}{添加按行或按列输入功能} % \changes{v0.3}{2025-09-03}{添加对无表头的表格的支持} % \changes{v0.3}{2025-09-03}{修复表格保存、恢复、查看命令} % \changes{v0.3}{2025-09-03}{整理优化内部接口} % \changes{v0.4}{2026-03-11}{调整宏包组织结构及规范命名} % \changes{v0.4}{2026-03-11}{修改表头的数据结构并大幅重构内部函数} % % \title{^^A % tabular2\thanks{原本计划叫 \pkg{xtable}/\pkg{xtabular},但与其它宏冲突;^^A % 后受 R 的 \pkg{ggplot2} 的启发,用于表示这是一个全新的 tabular。} : ^^A % 一个实验性的表格排版方案^^A % } % % \author{Ms\_yam ~(\href{mailto:Ms_yam@163.com}{Ms\_yam@163.com})^^A % } % \date{\zhdigits*{2026}年\zhnumber{03}月\zhnumber{16}日} % % \maketitle % % ^^A ---------------------------------- 文档部分 ---------------------------------- % % \begin{documentation} % % \begin{abstract} % 本宏包是一个实验性的表格排版解决方案。 % 它采用一套全新的表格输入输出接口,使得输入输出的方式多样化且更加可读。 % 代码层上采用 \pkg{expl3} 语法 |+| 盒子 |+| \pkg{l3draw} % \footnote{此宏包当前仍被官方标注为“实验性”宏包,因此本宏包存在未及时响应 \pkg{l3draw} 宏包变更而带来异常的风险。} 绘图, % 完全摆脱了对传统的底层表格命令的依赖 % \footnote{但仍然使用了部分底层命令,如 \ttt{\hsize} 等。如有更好的 \pkg{expl3} 实现方法,欢迎指正。}。 % % \end{abstract} % % {\small\tableofcontents} % % % \done \todo[Debug] { 修复由于测量单元格自然尺寸导致的脚注命令重复计数的问题 } % % \section{用户文档} % % 与传统表格不同,本模块将表格的数据录入与渲染输出彻底分开,前者用于设置表格数据(内容及内容样式) % \footnote{原计划只涉及内容, 但由于宽高与对齐影响单元格大小,所以把这两部分放在前面。如有更好的意见,欢迎指点。}, % 后者确认渲染条件(边框、颜色等)。 % % 为方便定位,本宏包为表格提供两种定位方式:数字坐标与行列名。 % 数字坐标从 $1$ 开始计数(不含表头 |Header|\footnote{在设计上,表头的坐标为 $0$,且行列表头(如果有)的默认内容为行列名。}); % 行列名则可由用户指定的 % \footnote{当行列名为纯数字时,定位按数字坐标直接处理。即使第 $1$ 列的列名为 $3$,$3$ 也始终指向第 $3$ 列。}, % 但不能是以下名称之一:|header|、|title|、|first|、|last|。 % % 本宏包直接支持脚注命令 \cs{footnote} 与 \cs{footnotemark} ,但不建议混合使用这两个命令 % \footnote{前者是通过使用 \cs{footnotemark} + \cs{footnotetext} 命令实现的,混合使用有乱序风险。}。 % % % \subsection{数据输入} % % \subsubsection{录入环境} % % \begin{function}{xtable} % \begin{syntax} % \ttt{\begin}\{xtable\} \oarg{选项} % ~~嵌套的环境与命令... % \ttt{\end}\{xtable\} % \end{syntax} % 一个表格输入环境,它本身不能直接输入,也不会渲染输出表格。 % 它会先初始化一个空表,然后执行嵌套的数据输入环境与命令, % 最后做一些必要的计算。 % \begin{texnote} % 所有表格数据,必须通过嵌套在本环境中的环境与命令来输入。 % \end{texnote} % \end{function} % \todo[方向]{ 思考应当支持什么选项 } % % \subsubsection{行列名设置} % % \done\todo[功能] { 添加独立的行列名设置功能 } % % 本宏包可以单独设置表格的行名与列名,以便设计表格的结构及方便后续需要坐标的命令与环境定位。 % % \begin{function}{\excelcolname} % \begin{syntax} % \cs{excelcolname} \oarg{n} % \end{syntax} % 将前 \meta{n} 列的列名设置为 $A$、$B$、\dots (类似 Excel 列名),默认 \meta{n} = $26$。 % 本命令不会影响表格的大小。 % \end{function} % % % \begin{function}{\rowname, \colname} % \begin{syntax} % \cs{rowname} (\meta{i}) \oarg{分隔符} \Arg{名称列表} % \cs{colname} (\meta{i}) \oarg{分隔符} \Arg{名称列表} % \end{syntax} % 从第 \meta{i} 行/列开始依次设置行名/列名。其中,名称由 \meta{名称列表} 指定,后者使用 \meta{分隔符} 分隔。 % 默认 \meta{i} = $1$,\meta{分隔符} = “$,$”。本命令会调整表格大小,使其可容纳 \meta{名称列表} 中的全部的行名/列名。 % \end{function} % % % \subsubsection{内容录入} % % \begin{function}{data} % \begin{syntax} % \ttt{\begin}\{data\} \oarg{选项} % ~~A,~B,~C |\\|\\ % ~~1,~2,~3... % \ttt{\end}\{data\} % \end{syntax} % 表格内容录入环境。 % \begin{texnote} % 本宏包只定义了其在 \ttt{xtable} 环境中的作用,对环境外不作干涉。【下同】 % \end{texnote} % \end{function} % % 标准输入以 “|\\|” 为行分隔符 % \footnote{在标准输入中,如果单元格里也有换行,请使用 \ttt{\newline}。}, % 以指定的分隔符(默认为 “|,|”)为列分隔符,不做任何其它特殊处理。 % 这种输入方式简单易解码,处理速度快,因此推荐作为主要输入方式。 % % % 以下是一个类似之前的表格输入方式的示例:\label{标准输入} % % \begin{minipage}{0.6\textwidth} % \begin{verbatim} % % \begin{xtable} % % loc 用于指定基准位置,sep 用于指定分隔符 % \begin{data}[loc={2,1}, sep={&}] % Name & Sex & Age\\ % John & man & 20 % \end{data} % \end{xtable} % % \end{verbatim} % \end{minipage} % \begin{minipage}{0.29\textwidth} % \begin{xtable} % \begin{data}[loc={2,1}, sep={&}] % Name & Sex & Age\\ % John & man & 20 % \end{data} % \end{xtable} % \rendertable % \end{minipage} % % % CSV 作为最传统的输入方式,本宏包也添加了支持\footnote{标准输入其实就是 % 一个特殊的 CSV 格式,但因其不需要考虑特殊字符,处理效率显著强于后者。}。 % 由于 \TeX 读取普通显示字符时,换行符会被忽略,因此在输入时也使用 “|\\|” 作为换行。 % \todo[功能]{ 处理 CSV 格式中的特殊字符 } % % 此外,本宏包还有限地支持 JSON 数据源,其中最主要的限制就是不允许嵌套。 % 以下是一个 JSON 的输入示例。\label{JSON 格式} \todo[功能]{ 处理 JSON 格式中的特殊字符 } % % \begin{minipage} {0.6\textwidth} % \begin{verbatim} % % \begin{xtable} % % format 用于指定输入的格式,也可以直接使用 json 选项 % \begin{data}[format=json] % [ % { % "Name": "张三", % "Age": 30, % "City": "北京" % }, % { % "Name": "李四", % "Age": 25, % "City": "上海" % } % ] % \end{data} % \end{xtable} % \end{verbatim} % \end{minipage} % \begin{minipage} {0.29\textwidth} % \begin{xtable} % \begin{data}[json] % [ % { % "Name": "张三", % "Age": 30, % "City": "北京" % }, % { % "Name": "李四", % "Age": 25, % "City": "上海" % } % ] % \end{data} % \end{xtable} % \rendertable % \end{minipage} % % % % \begin{function}{\file} % \begin{syntax} % \cs{file} \oarg{选项} \Arg{文件名} % \end{syntax} % 导入数据文件,规则与 \ttt{data} 类似。【此功能尚未实现】 % \begin{texnote} % 文件中的换行符无需处理,其他待确认。 % \end{texnote} % \end{function} % \todo[功能]{ 实现文件导入及细节描述 } % % % \begin{function}{\row, \col, \cell} % \begin{syntax} % \cs{row} (\meta{行坐标},\meta{列坐标}) \oarg{分隔符} \Arg{内容} % \cs{col} (\meta{行坐标},\meta{列坐标}) \oarg{分隔符} \Arg{内容} % \cs{cell}(\meta{行坐标},\meta{列坐标}) \Arg{内容} % \end{syntax} % 按行/列/单元格的方式设定表格的内容。 % 对于行与列,若坐标缺省,则自动在末尾新增行或列;若只提供一个维度,则另一个维度自动补充为 $1$。 % \begin{texnote} % \meta{行坐标}、\meta{列坐标} 坐标支持数据坐标或行名/列名,若行名/列名不存在,则自动添加到行尾/列尾。【下同】 % \end{texnote} % \end{function} % \done\todo[功能]{ 添加按行/列输入的命令\ttt{\row}、\ttt{\col} } % % % % \begin{function}{\savetable, \loadtable} % \begin{syntax} % \cs{savetable} \Arg{名称} % \cs{loadtable} \Arg{名称} % \end{syntax} % 将当前的表格数据保存到指定 \meta{名称} 中, % 或加载之前保存的名为 \meta{名称} 的表 % \footnote{它会替代当前表的全部内容,所以应当在输入其它数据之前加载。}。 % \end{function} % % \subsubsection{格式设置} % % \begin{function}{\rowheight, \colwidth} % \begin{syntax} % \cs{rowheight}\oarg{全局样式} \Arg{样式列表} % \cs{colwidth} \oarg{全局样式} \oarg{总宽度} \Arg{样式列表} % \end{syntax} % 设置表格的行高与列宽样式,样式支持 |auto|、|same|、|fill|(仅列宽)、 % |samefill|(仅列宽) 及指定数值。 % \meta{样式列表} 若干个 \meta{[坐标=]样式} 组成,并以逗号分隔。 % % 如果省略坐标,则坐标为 “前一个坐标 |+1|”。如果是 \meta{样式列表} 第 $1$ 个, % 则默认为 |header|(有 |Header|) 或 $1$ (没有 |Header|)。 % \begin{texnote} % 样式 |auto| 表示自然尺寸;|same| 则表示同组所有行/列的值相同 % \footnote{同时兼容所有行/列的自然尺寸,即同组自然尺寸的最大值。}; % |fill| 则会根据总宽度来补偿,即为 “自然尺寸+补偿值” % \footnote{补偿值的目的是用于保证总宽度,即为控制总宽度,需要使用缩放这些列的宽度。}; % |samefill| 等于 |same|\footnote{但与纯 $same$ 不同组,即它们的宽度是另外一个值。} + |fill|。 % 所有 |fill| 的补偿值是一样的。样式支持缩写为首字母。 % \end{texnote} % \end{function} % % % \begin{function}{\rowalign, \colalign} % \begin{syntax} % \cs{rowalign} \oarg{全局样式} \Arg{样式列表} % \cs{colalign} \oarg{全局样式} \Arg{样式列表} % \end{syntax} % 设置表格的行与列对齐方式;行对齐(垂直对齐)样式支持 |t|、|m|、|b|, % 列对齐(水平对齐)样式支持 |l|、|c|、|r|。设置方式与 \cs{rowheight} 类似。 % \begin{texnote} % 当 \meta{样式列表} 不含索引时,可以省略逗号,即类似 “|llcrl|” 一样输入。 % \end{texnote} % \end{function} % % % \subsubsection{合并单元格} % % % \done \todo[功能]{ 添加合并单元格功能 } % % \begin{function}{\mergecell} % \begin{syntax} % \cs{mergecell} \oarg{数据角标} \Arg{对角坐标1} \Arg{对角坐标2} \oarg{行列对齐方式} % \end{syntax} % 合并 \meta{对角坐标1} 与 \meta{对角坐标2} 之间的单元格。 % 合并的后的单元格内容可以采用这个区域中的任何一个角的单元格内容; % \meta{数据角标} 用来指定实际使用哪个角,默认为 $ul$。 % 合单单元格的对齐方式可由 \meta{行列对齐方式} 定义义,默认为 |mc|。 % \begin{texnote} % 计算单元格排版尺寸时,会跳过合并单元格,即合并单元格的尺寸由其所在的行与列决定。 % 默认常规单元格的对齐方式由其所在的行与列中指定,无法专门指定。 % 如果确实需要单独指定,可以本命令合并一个 $1*1$ 的单元格,再指定该合并单元格的对齐方式。 % \end{texnote} % \end{function} % % % \subsection{渲染输出} % % \begin{function}{\printtable} % \begin{syntax} % \cs{printtable}[*] \oarg{缩进量} % \end{syntax} % 逐行打印表格,输出的表格不带格式(如边框与背景色),但会处理基本的对齐。 % \begin{texnote} % 逐行输出,每行是一个水平盒子,因此可以正常分页。 % 另外,此命令是本宏包中渲染表格最快的命令。 % \end{texnote} % \end{function} % % % 以下章节 \ref{JSON 格式} 中的 JSON 输入示例的输出效果:(缩进量为 |4em|)\\ % \printtable[4em] % % 由于使用了水平盒子 |+ \\|,其副作用是在嵌套环境中可能报错 “There's no line here to end.”。 % 要在类似环境中使用本命令输出表格,请使用星号版本,区别在于它不会输出 |\\|。 % % \begin{verbatim} % \begin{table}[!htp] % \centering % \caption{嵌套的 \cs{printtable}} % \parbox[t]{\textwidth}{\printtable*} % \label{tab:嵌套的printtable} % \end{table} % \end{verbatim} % 以上示例展示如何在表格浮动体中使用 \cs{printtable},其效果如表 \ref{tab:嵌套的printtable}。 % % \begin{table}[!htp] % \centering % \caption{嵌套的 \cs{printtable}} % \parbox[t]{\textwidth}{\printtable*} % \label{tab:嵌套的printtable} % \end{table} % % % \begin{function}{\rendertable, \hrule, \vrule} % \begin{syntax} % \cs{rendertable} [booktabs, rule=\Arg{中间的线}] % \cs{rendertable} [grid, rule = % ~~\{ % ~~~~\cs{hrule} \oarg{默认值} \Arg{边框列表} % ~~~~\cs{vrule} \oarg{默认值} \Arg{边框列表} % ~~\} ] % \end{syntax} % 按预定义的渲染方式渲染表格。功能开发中,目前参数支持有限。\done \todo[优化]{ 更新边框命令及说明 } % 前面介绍输入时显示的表格就是使用本命令(无参数)渲染的。 % \begin{texnote} % 在渲染逻辑上,它是通过 \pkg{l3draw} 绘制的,即它是一个整体。 % 可以直接嵌套在其它环境中(如表 \ref{tab:rendertable[booktabs]}、 % 表 \ref{tab:rendertable[grid]}),但它不会分页。 % \end{texnote} % \end{function} % % \begin{xtable} % \begin{data}[loc={2,1},sep={&}] % Name & Sex & Age\\ % John & man & 18\\ % Leon & man & 20\\ % Lily & woman & 21 % \end{data} % \cell(1,Sex){man\\woman\\unknown} % \cell(1,3){18\~{}25} % \colwidth[a]{a} % \rowalign[m]{} % \colalign{l,c,r} % \end{xtable} % % % \begin{table}[!htp] % \begin{minipage}{0.45\textwidth} % \centering % \caption{\cs{rendertable}\texttt{[booktabs]}} % \rendertable[booktabs] % \label{tab:rendertable[booktabs]} % \end{minipage} % \begin{minipage}{0.45\textwidth} % \centering % \caption{\cs{rendertable}\texttt{[grid]}} % \rendertable[grid, rule = % { % \hrule{0.7pt,midrule,3=dash,bottomrule} % \vrule{0.7pt,2=dotted} % }] % \label{tab:rendertable[grid]} % \end{minipage} % \end{table} % % \subsection{选项参数} % % \subsubsection{宏包选项} % % % \begin{function}{xtable/package/en} % \begin{syntax} % en % \end{syntax} % 使用英文错误消息。 % \end{function} % % \begin{function}{xtable/package/cellsep} % \begin{syntax} % cellsep = {0.5em} % \end{syntax} % 设置表格的单元格之间的间距。 % \end{function} % % % \begin{function}{xtable/package/margin} % \begin{syntax} % margin = \{0.4em, 0.6ex\} % \end{syntax} % 设置表格的单元格的边距。 % \end{function} % % % \begin{function}{xtable/package/vspace} % \begin{syntax} % vspace = \{0.5ex, 0ex\} % \end{syntax} % 设置表格的上下边距。暂时只有顶边距生效。\todo[优化]{更新 vspace 的实现与说明} % \end{function} % % % \begin{function}{xtable/package/minwidth, xtable/package/lineskip} % \begin{syntax} % minwidth = {1.5em}, lineskip = {3ex} % \end{syntax} % 设置单元格的最小宽度及行基线间距。 % \end{function} % % % \subsubsection{设置命令} % % \begin{function}{\rulepatternset} % \begin{syntax} % \cs{rulepatternset} \Arg{名称} \Arg{定义} % \end{syntax} % 将指定 \meta{名称} 的线型映射到指定 \meta{定义}。 % 线型定义的方式由一系列长度值组成,即 |{线, 空, ...}|。 % \end{function} % % \begin{function}{\rulewdset} % \begin{syntax} % \cs{rulewdset} \Arg{名称} \Arg{尺寸} % \end{syntax} % 将指定 \meta{名称} 的线宽映射到指定 \meta{尺寸}。 % \end{function} % % \begin{function}{\rulestyleset} % \begin{syntax} % \cs{rulestyleset} \Arg{名称} \Arg{线型} \Arg{线宽} \Arg{颜色} % \end{syntax} % 将指定 \meta{名称} 的线样式映射到指定 \meta{线型}、\meta{线宽} 及 \meta{颜色}。 % \meta{线型}、\meta{线宽} 可以是已定义的名称或字面值。 % \end{function} % % \subsection{调试} % % 本小节的命令主要用于输出一些表格的内部状态数据,以供调试使用。 % % \begin{function}{\showtable} % \begin{syntax} % \cs{showtable} % \end{syntax} % 以文本形式显示当前表格的数据。效果如下: % \end{function} % % \showtable % % \begin{function}{\logtable} % \begin{syntax} % \cs{logtable} % \end{syntax} % 在日志中显示表格的核心数据。 % \end{function} % % % \section{关键逻辑} % % \subsection{单元格大小} % % \subsubsection{间距} % % 对于无边框打印表格,列间距由 |cellsep| 设置,表格四周无空隙。 % 行间距由 \LaTeX 自行按普通行处理。 % % 对于有边框的渲染表格,单元格的边距由 |margin| 确认。 % 相当于行列间有 $2$ 倍的边距),表格四周有单倍的边距。 % % \subsubsection{宽度} % % 宽度的计算步骤如下: % \begin{enumerate} % \item 计算所有列的自然宽度(该列中所有单元格的自然宽度的最大值); % \item 将固定宽度的列的宽度设置为指定值; % \item 将 |same| 与 |samefill| 的宽度设置为同组最大值; % \item 计算当前总宽度与目标总宽度的差异,计算补偿值; % \item 将所有 |fill| 与 |samefill| 的列宽度加上补偿值。 % \end{enumerate} % % 注:列宽度不包含边距,但总宽度有计算。 % % % \subsubsection{高度} % % 高度的计算步骤如下: % \begin{enumerate} % \item 计算所有单元格的首行填充值(用于平衡居中对齐时的基线问题); % \item 计算所有行的自然高度与深度(该行中所有单元格的自然高度与浓度的最大值); % \item 将固定高度的列的高度设置为(指定值-深度); % \item 将 |same| 的高度与深度设置为同组最大值。 % \end{enumerate} % % 图 \ref{fig:表格渲染逻辑} 展示了基本的计算逻辑。\\ % % \begin{figure}[htp] % \centering % \ExplSyntaxOn % \draw_begin: % \draw_set_linewidth:n {1pt} ^^A 边框 % \draw_path_rectangle:nn {0,0} {2cm,1cm} % \draw_path_rectangle:nn {2cm,0cm} {3cm,1cm} % \draw_path_rectangle:nn {0cm,1cm} {2cm,1cm} % \draw_path_rectangle:nn {2cm,1cm} {3cm,1cm} % \draw_path_use_clear:n { stroke } % \color_fill:n { black!40 } ^^A 最终尺寸 % \draw_path_rectangle:nn {0cm + 2pt, 0cm + 2pt} {2cm - 4pt, 1cm - 4pt} % \draw_path_rectangle:nn {2cm + 2pt, 0cm + 2pt} {3cm - 4pt, 1cm - 4pt} % \draw_path_rectangle:nn {0cm + 2pt, 1cm + 2pt} {2cm - 4pt, 1cm - 4pt} % \draw_path_rectangle:nn {2cm + 2pt, 1cm + 2pt} {3cm - 4pt, 1cm - 4pt} % \draw_path_use_clear:n { fill } % \color_fill:n { yellow!80!black } ^^A 自然尺寸 % \draw_path_rectangle:nn {0cm + 5pt, 0cm + 3pt} {2cm - 10pt, 1cm - 6pt} % \draw_path_rectangle:nn {2cm + 5pt, 0cm + 3pt} {3cm - 10pt, 1cm - 6pt} % \draw_path_rectangle:nn {0cm + 5pt, 1cm + 3pt} {2cm - 10pt, 1cm - 6pt} % \draw_path_rectangle:nn {2cm + 5pt, 1cm + 3pt} {3cm - 10pt, 1cm - 6pt} % \draw_path_use_clear:n { fill } % \color_fill:n { green!50!black } ^^A 实际内容 % \draw_path_rectangle:nn {0cm + 10pt, 0cm + 6pt} {2cm - 20pt, 1cm - 12pt} % \draw_path_rectangle:nn {2cm + 2pt, 0cm + 7pt} {3cm - 40pt, 1cm - 14pt} % \draw_path_rectangle:nn {0cm + 15pt, 1cm + 11pt} {2cm - 30pt, 1cm - 13pt} % \draw_path_rectangle:nn {2cm + 2pt, 1cm + 6pt} {3cm - 60pt, 1cm - 8pt} % \draw_path_use_clear:n { fill } % \color_select:n {black} ^^A 对齐方式 % \hbox_set:Nn \l_tmpa_box {居中对齐} % \draw_box_use:Nn \l_tmpa_box % {1cm - (\box_wd:N \l_tmpa_box)/2, 2.2cm} % \hbox_set:Nn \l_tmpa_box {左对齐} % \draw_box_use:Nn \l_tmpa_box % {3.5cm - (\box_wd:N \l_tmpa_box)/2, 2.2cm} % \hbox_set:Nn \l_tmpa_box {上下居中} % \draw_box_use:Nn \l_tmpa_box % {-0.4 - \box_wd:N \l_tmpa_box, 0.5cm - (\box_ht:N \l_tmpa_box)/2} % \hbox_set:Nn \l_tmpa_box {顶对齐} % \draw_box_use:Nn \l_tmpa_box % {-0.4 - \box_wd:N \l_tmpa_box, 1.5cm - (\box_ht:N \l_tmpa_box)/2} % \vbox_set:Nn \l_tmpa_box ^^A 图例 % { % \hbox:n % { % \draw_begin: % \draw_set_linewidth:n {1pt} % \draw_path_rectangle:nn {0,0} {0.5cm-1pt, 0.3cm-1pt} % \draw_path_use_clear:n { stroke } % \draw_end: % \ 线宽与间隙(白色) % } % \hbox:n % { \color_fill:n { black!40 } \rule{0.5cm}{0.3cm}~最终尺寸} % \hbox:n % { \color_fill:n { yellow!80!black } \rule{0.5cm}{0.3cm}~自然尺寸 } % \hbox:n % { \color_fill:n { green!50!black } \rule{0.5cm}{0.3cm}~实际内容 } % } % \draw_box_use:Nn \l_tmpa_box {5.5cm, 0cm} % \draw_end: % \ExplSyntaxOff % \caption{表格渲染逻辑} \label{fig:表格渲染逻辑} % \end{figure} % % % % \end{documentation} % % % ^^A ---------------------------------- 实现部分 ---------------------------------- % % \newpage % % \begin{implementation} % % \section{基础定义} % % 本宏包的开发测试环境使用的 \LaTeX2e 的版本为 <2023-11-01> , % L3 编程层的版本为 <2024-02-20>。 % \footnote{同时使用以下环境验证与打包:\LaTeX2e 的版本为 <2025-11-01> , % L3 编程层的版本为 <2026-01-19>。} % % 本节实现本宏包的初始化及选项定义,并定义一些公共的变量与函数。 % 它们完全不依赖表格的设置,只为表格的实现提供功能。 % % \begin{macrocode} %<*package> %<@@=xtable> \NeedsTeXFormat{LaTeX2e}[2023-11-01] \ProvidesExplPackage{tabular2}{2026-03-16}{0.4} {A new table implementation base on expl3} \RequirePackage{l3draw}[2024-02-20] % \end{macrocode} % % \subsection{通用变体} % % 定义一些必要的系统函数的变体。 % \begin{macrocode} \cs_generate_variant:Nn \msg_error:nnn { nnV } \cs_generate_variant:Nn \str_const:Nn { Ne } \cs_generate_variant:Nn \clist_item:nn { Vn } \cs_generate_variant:Nn \seq_set_split:Nnn { NVn, NVV } \cs_generate_variant:Nn \seq_set_item:Nnn { NnV, Nne } \cs_generate_variant:Nn \seq_gset_item:Nnn { NnV, Nne } \cs_generate_variant:Nn \prop_gput:Nnn { Nne, Nen, Nee, NeV, NVe, NVV } \cs_generate_variant:Nn \prop_gpop:NnN { NeN } \cs_generate_variant:Nn \color_stroke:n { e } \cs_generate_variant:Nn \regex_extract_all:NnN { NVN } % \end{macrocode} % % % \subsection{\pkg{l3draw} 函数} % % 在 2025-06-30 发布的新版 \pkg{l3draw} 中,将部分设置函数的函数名添加了 |set| 部分。 % 但考虑到部分用户可能并未升级到此版本,故增加此部分以兼容旧版本。 % \begin{macrocode} \cs_if_free:NT \draw_set_linewidth:n { \cs_gset_eq:NN \draw_set_linewidth:n \draw_linewidth:n } \cs_if_free:NT \draw_set_dash_pattern:nn { \cs_gset_eq:NN \draw_set_dash_pattern:nn \draw_dash_pattern:nn } % \end{macrocode} % % 定义 \pkg{l3draw} 函数的变体。 % \begin{macrocode} \cs_generate_variant:Nn \draw_set_linewidth:n { V, e } \cs_generate_variant:Nn \draw_set_dash_pattern:nn { Vn, en } % \end{macrocode} % % % \subsection{消息定义} % % 本小节定义一些常见的错误消息。 % % \begin{macrocode} \msg_new:nnn {xtable} {unknown_row_name} {未知的行名称:<#1>} \msg_new:nnn {xtable} {unknown_col_name} {未知的列名称:<#1>} \msg_new:nnn {xtable} {unknown_input} {未知的输入:<#1>} \msg_new:nnn {xtable} {unknown_format} {未知的格式:<#1>} \msg_new:nnn {xtable} {unknown_cell} {未知的单元格数据 @<#1>} \msg_new:nnn {xtable} {merged_cell} {已经合并的单元格 @<#1>} \msg_new:nnn {xtable} {unmerged_cell} {未合并的单元格 @<#1>} \msg_new:nnn {xtable} {outside_xtable} {<#1> 应当在 xtable 环境中使用} \msg_new:nnn {xtable} {outside_render} {<#1> 应当在 \rendertable 命令中使用} \msg_new:nnn {xtable} {unsaved_table} {未保存的表:<#1>} % \end{macrocode} % % 添加英文支持。 % \begin{macrocode} \cs_new:Nn \@@_en_msg_support: { \msg_set:nnn {xtable} {unknown_row_name} {Unknown row name: <##1>} \msg_set:nnn {xtable} {unknown_col_name} {Unknown column name: <##1>} \msg_set:nnn {xtable} {unknown_input} {Unknown input: <##1>} \msg_set:nnn {xtable} {unknown_format} {Unknown format: <##1>} \msg_set:nnn {xtable} {unknown_cell} {Unknown cell data @<##1>} \msg_set:nnn {xtable} {merged_cell} {Already merged cell @<##1>} \msg_set:nnn {xtable} {unmerged_cell} {Unmerged cell @<##1>} \msg_set:nnn {xtable} {outside_xtable} {<##1> should be used inside xtable environment} \msg_set:nnn {xtable} {outside_render} {<##1> should be used inside \rendertable command} \msg_set:nnn {xtable} {unsaved_table} {Unsaved table: <##1>} } % \end{macrocode} % % % \subsection{宏包选项} % % 本小节定义宏包的选项及对应的变量。 % 选项是唯一修改这些变量的地方。 % % \begin{variable}{\g_@@_cell_sep_dim, \g_@@_row_margin_dim, \g_@@_col_margin_dim} % 行列间距与边距。 % \begin{macrocode} \dim_new:N \g_@@_cell_sep_dim % 单元格间的水平间距(用于打印表格) \dim_new:N \g_@@_row_margin_dim % 行边距与列边路(用于渲染表格) \dim_new:N \g_@@_col_margin_dim % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_above_space_dim, \g_@@_below_space_dim, % \g_@@_cell_wd_min_dim, \g_@@_cell_lineskip_dim} % 表格上下边空、单元格最小宽度及单元格内行间距。 % \begin{macrocode} \dim_new:N \g_@@_above_space_dim % 表格上下边空 \dim_new:N \g_@@_below_space_dim \dim_new:N \g_@@_cell_wd_min_dim % 单元格的最小宽度 \dim_new:N \g_@@_cell_lineskip_dim % 多行单元格的行距 % \end{macrocode} % \end{variable} % % % \begin{variable}{xtable/package} % 定义宏包选项。 % \begin{macrocode} \keys_define:nn { xtable / package } { en .code:n = { \@@_en_msg_support: }, cellsep .dim_gset:N = \g_@@_cell_sep_dim, cellsep .initial:n = { 0.5em }, margin .code:n = { \dim_gset:Nn \g_@@_row_margin_dim { \clist_item:nn {#1} {2} } \dim_gset:Nn \g_@@_col_margin_dim { \clist_item:nn {#1} {1} } }, margin .initial:n = { 0.4em, 0.6ex }, vspace .code:n = { \dim_gset:Nn \g_@@_above_space_dim { \clist_item:nn {#1} {1} } \dim_gset:Nn \g_@@_below_space_dim { \clist_item:nn {#1} {2} } }, vspace .initial:n = { 0.5ex, -1ex }, minwidth .dim_gset:N = \g_@@_cell_wd_min_dim, minwidth .initial:n = { 1.5em }, lineskip .dim_gset:N = \g_@@_cell_lineskip_dim, lineskip .initial:n = { 3ex } } \ProcessKeyOptions [ xtable / package ] % \end{macrocode} % \end{variable} % % % % \subsection{通用变量} % % 本小节定义一些通用的变量,供整个宏包中使用。 % % \subsubsection{全局常量} % % \begin{variable}{\c_@@_space_str, \c_@@_escape_str, \c_@@_lbrace_str, \c_@@_rbrace_str} % 定义一些不易直接输入的字符常量。 % \begin{macrocode} \str_const:Ne \c_@@_space_str { \char_generate:nn {32} {10} } % \str_const:Ne \c_@@_escape_str { \char_generate:nn {92} {12} } % \ \str_const:Ne \c_@@_lbrace_str { \char_generate:nn {123} {12} } % { \str_const:Ne \c_@@_rbrace_str { \char_generate:nn {125} {12} } % } % \end{macrocode} % \end{variable} % % % \begin{variable}{\s_@@_mark} % 定义内部标记。 % \begin{macrocode} \scan_new:N \s_@@_mark % \end{macrocode} % \end{variable} % % % \begin{variable}{\c_@@_std_ht_dim, \c_@@_std_dp_dim} % 单行文字的标准高度与深度。 % \begin{macrocode} \dim_const:Nn \c_@@_std_ht_dim {1.91ex} % 约等于汉字的高度,字母高度{1.67ex} \dim_const:Nn \c_@@_std_dp_dim {0.48ex} % 约等于字母的深度 % \end{macrocode} % \end{variable} % % % \begin{variable}{\c_@@_merge_loc_prop, \c_@@_merge_align_prop} % 合并单元格设置映射表。 % \begin{macrocode} \prop_const_from_keyval:Nn \c_@@_merge_loc_prop { ul = ul, ur = ur, dl = dl, du = du, lu = ul, ru = ur, ld = dl, ud = du } \prop_const_from_keyval:Nn \c_@@_merge_align_prop { tl = {t, l}, tc = {t, c}, tr = {t, r}, ml = {m, l}, mc = {m, c}, mr = {m, r}, bl = {b, l}, bc = {b, c}, br = {b, r}, lt = {t, l}, ct = {t, c}, rt = {t, r}, lm = {m, l}, cm = {m, c}, rm = {m, r}, lb = {b, l}, cb = {b, c}, rb = {b, r}, l = {m, l}, c = {m, c}, r = {m, r}, t = {t, c}, m = {m, c}, b = {b, c} } % \end{macrocode} % \end{variable} % % % % % \subsubsection{专用变量} % % 本小节定义一些专用的变量,以供缓存或函数之间传递特定的数据。 % 使用这些变量的函数应当在文档中声明。 % % % \begin{variable}{\l_@@_row_loc_int, \l_@@_col_loc_int} % 用于存储(行/列的)数字坐标信息。 % \begin{macrocode} \int_new:N \l_@@_row_loc_int \int_new:N \l_@@_col_loc_int % \end{macrocode} % \end{variable} % % % \begin{variable}{\l_@@_is_box_bool, \l_@@_data_tl} % 用于存储单元格内容及状态的变量。 % \begin{macrocode} \bool_new:N \l_@@_is_box_bool \tl_new:N \l_@@_data_tl % \end{macrocode} % \end{variable} % % % \begin{variable}{\l_@@_style_tl, \l_@@_row_align_tl, \l_@@_col_align_tl} % 用于存储通用样式与对齐样式专用的凭据表。 % \begin{macrocode} \tl_new:N \l_@@_style_tl \tl_new:N \l_@@_row_align_tl \tl_new:N \l_@@_col_align_tl % \end{macrocode} % \end{variable} % % % \begin{variable}{\l_@@_merge_id_tl, \l_@@_merge_seq} % 用于存储合并单元格索引的凭据表和内容的序列。 % \begin{macrocode} \tl_new:N \l_@@_merge_id_tl \seq_new:N \l_@@_merge_seq % \end{macrocode} % \end{variable} % % % \begin{variable}{\l_@@_wd_dim, \l_@@_ht_dim, \l_@@_dp_dim, \l_@@_ufill_dim, \l_@@_dfill_dim} % 用于存储宽高深等尺寸信息。 % \begin{macrocode} \dim_new:N \l_@@_wd_dim \dim_new:N \l_@@_ht_dim \dim_new:N \l_@@_dp_dim \dim_new:N \l_@@_ufill_dim \dim_new:N \l_@@_dfill_dim % \end{macrocode} % \end{variable} % % % % % \subsubsection{缓存变量} % % 本小节定义一些缓存的变量,以供函数内保存数据,但不应用于函数间传递数据。 % 使用这些变量的函数应当在文档中声明。 % % \begin{variable}{\l_@@_cachea_int, \l_@@_cacheb_int} % 用于缓存整数。 % \begin{macrocode} \int_new:N \l_@@_cachea_int \int_new:N \l_@@_cacheb_int % \end{macrocode} % \end{variable} % % % \begin{variable}{\l_@@_cachea_dim, \l_@@_cacheb_dim} % 用于缓存尺寸信息。 % \begin{macrocode} \dim_new:N \l_@@_cachea_dim \dim_new:N \l_@@_cacheb_dim % \end{macrocode} % \end{variable} % % % \begin{variable}{\l_@@_cachea_tl, \l_@@_cacheb_tl} % 用于缓存的凭据表。 % \begin{macrocode} \tl_new:N \l_@@_cachea_tl \tl_new:N \l_@@_cacheb_tl % \end{macrocode} % \end{variable} % % % \begin{variable}{\l_@@_cachea_seq, \l_@@_cacheb_seq} % 用于缓存的属性表。 % \begin{macrocode} \seq_new:N \l_@@_cachea_seq \seq_new:N \l_@@_cacheb_seq % \end{macrocode} % \end{variable} % % % \subsubsection{临时变量} % % 本小节定义一些临时变量\footnote{不使用系统提供的临时变量,以防止和其它宏包冲突。}, % 以供函数内部使用\footnote{不应当使用这些变量在函数之间传递数据。}。 % 可以随意使用这些变量而无需当在文档中声明 % \footnote{应当假定调用任何函数都会修改这些变量的值。}。 % % \begin{variable}{\l_@@_tmpa_int, \l_@@_tmpb_int} % 整数变量。 % \begin{macrocode} \int_new:N \l_@@_tmpa_int \int_new:N \l_@@_tmpb_int % \end{macrocode} % \end{variable} % % % \begin{variable}{\l_@@_tmpa_dim, \l_@@_tmpb_dim} % 长度变量。 % \begin{macrocode} \dim_new:N \l_@@_tmpa_dim \dim_new:N \l_@@_tmpb_dim % \end{macrocode} % \end{variable} % % % \begin{variable}{\l_@@_tmpa_tl, \l_@@_tmpb_tl, \l_@@_tmpa_str, \l_@@_tmpb_str} % 凭据表与字符串。 % \begin{macrocode} \tl_new:N \l_@@_tmpa_tl \tl_new:N \l_@@_tmpb_tl \str_new:N \l_@@_tmpa_str \str_new:N \l_@@_tmpb_str % \end{macrocode} % \end{variable} % % % \begin{variable}{\l_@@_tmpa_seq, \l_@@_tmpb_seq, \l_@@_tmpa_prop, \l_@@_tmpb_prop} % 序列与属性表。 % \begin{macrocode} \seq_new:N \l_@@_tmpa_seq \seq_new:N \l_@@_tmpb_seq \prop_new:N \l_@@_tmpa_prop \prop_new:N \l_@@_tmpb_prop % \end{macrocode} % \end{variable} % % % \begin{variable}{\l_@@_tmpa_box, \l_@@_tmpb_box} % 盒子对象。 % \begin{macrocode} \box_new:N \l_@@_tmpa_box \box_new:N \l_@@_tmpb_box % \end{macrocode} % \end{variable} % % \subsection{通用函数} % % 本小节定义一些通用的函数,供整个宏包中使用。 % 本小节只对外提供功能,不提供变量,也依赖外部变量。 % % \subsubsection{常规设置} % % \begin{macro}{\@@_init_seq:Nnn, \@@_ginit_seq:Nnn} % 初始化序列 |#1|,使其元素个数为 |#2|,且元素内容均为 |#3|。 % \begin{macrocode} \cs_new:Nn \@@_init_seq:Nnn { \seq_clear:N #1 \prg_replicate:nn {#2} { \seq_put_right:Nn #1 {#3} } } \cs_new:Nn \@@_ginit_seq:Nnn { \seq_gclear:N #1 \prg_replicate:nn {#2} { \seq_gput_right:Nn #1 {#3} } } \cs_generate_variant:Nn \@@_init_seq:Nnn {Nne} % \end{macrocode} % \end{macro} % % % % \begin{macro}{\@@_int_set_max:Nn, \@@_int_gset_max:Nn} % 将变量(|#1|)的值与指定值(|#2|)中的最大值赋值给变量。 % \begin{macrocode} \cs_new:Nn \@@_int_set_max:Nn { \int_set:Nn #1 { \int_max:nn {#1} {#2} } } \cs_new:Nn \@@_int_gset_max:Nn { \int_gset:Nn #1 { \int_max:nn {#1} {#2} } } \cs_generate_variant:Nn \@@_int_gset_max:Nn {cn} % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_dim_set_max:NN, \@@_dim_set_max:Nn} % 将变量(|#1|)的值与指定值(|#2|)中的最大值赋值给变量。 % \begin{macrocode} \cs_new:Nn \@@_dim_set_max:NN { \dim_set:Nn #1 { \dim_max:nn {#1} {#2} } } \cs_new:Nn \@@_dim_set_max:Nn { \dim_set:Nn #1 { \dim_max:nn {#1} {#2} } } % \end{macrocode} % \end{macro} % % % \subsubsection{脚注函数} % % \changes{v0.4}{2026-03-11}{添加对脚注的支持} % % \begin{variable}{\g_@@_footnote_prop} % 表格脚注支持专用变量。 % \begin{macrocode} \prop_new:N \g_@@_footnote_prop % \end{macrocode} % \end{variable} % % % \begin{macro}{\@@_origin_footnote:, \@@_origin_footnotemark:} % 备份系统脚注函数。 % \begin{macrocode} \cs_gset_eq:NN \@@_origin_footnote: \footnote \cs_gset_eq:NN \@@_origin_footnotemark: \footnotemark % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_footnote:, \@@_footnotemark:} % 只保留占位符的脚注函数。 % \begin{macrocode} \NewDocumentCommand \@@_footnote: { O{} m } { \@@_origin_footnotemark:[99] } \NewDocumentCommand \@@_footnotemark: { O{} } { \@@_origin_footnotemark:[99] } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_footnote_support:, \@@_footnote_print:} % 脚注标签与内容拆分实现的脚注函数。 % \begin{macrocode} \NewDocumentCommand \@@_footnote_support: { o m } { \IfNoValueTF {#1} { \@@_origin_footnotemark: \prop_gput:Nen \g_@@_footnote_prop {\thefootnote} {#2} } { \@@_origin_footnotemark:[#1] \prop_gput:Nnn \g_@@_footnote_prop {#1} {#2} } } \cs_new:Nn \@@_footnote_print: { \prop_map_inline:Nn \g_@@_footnote_prop { \footnotetext[##1] {##2} } } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\@@_disable_sys_func:, \@@_support_sys_func:, \@@_restore_sys_func:} % 切换脚注函数。 % \begin{macrocode} \cs_new:Nn \@@_disable_sys_func: { \prop_gclear:N \g_@@_footnote_prop \cs_set_eq:NN \footnote \@@_footnote: \cs_set_eq:NN \footnotemark \@@_footnotemark: } \cs_new:Nn \@@_support_sys_func: { \prop_gclear:N \g_@@_footnote_prop \cs_set_eq:NN \footnote \@@_footnote_support: } \cs_new:Nn \@@_restore_sys_func: { \@@_footnote_print: \cs_set_eq:NN \footnote \@@_origin_footnote: \cs_set_eq:NN \footnotemark \@@_origin_footnotemark: } % \end{macrocode} % \end{macro} % % % \subsubsection{临时函数} % % \begin{macro}{\@@_tmpa_cs:, \@@_tmpb_cs:} % 两个临时函数,用于临时保存函数的定义。 % \begin{macrocode} \cs_new:Nn \@@_tmpa_cs: {} \cs_new:Nn \@@_tmpb_cs: {} % \end{macrocode} % \end{macro} % % % \subsection{边框线样式} % % 本小节提供边框线的设置及应用功能。样式是全局设置,可以在表格渲染中应用样式。 % 本小节函数不依赖外部变量,外面的函数也不应当修改本小节的变量。 % % % \subsubsection{专用变量} % % \begin{variable}{\g_@@_rule_pattern_prop, \g_@@_rule_wd_prop, \g_@@_rule_style_prop} % 用于全局存储表格线型、线宽与样式。 % 样式的值为\{线型, 线宽, 颜色\}(分隔符为 \cs{s_@@_mark})。 % \begin{macrocode} \prop_new:N \g_@@_rule_pattern_prop \prop_new:N \g_@@_rule_wd_prop \prop_new:N \g_@@_rule_style_prop % \end{macrocode} % \end{variable} % % % \begin{variable}{\l_@@_rule_pattern_tl, \l_@@_rule_wd_tl, \l_@@_rule_color_tl, \l_@@_rule_dim, % \l_@@_rule_style_tl, \l_@@_rule_style_seq} % 用于临时存储表格线型、线宽与样式。 % \begin{macrocode} \tl_new:N \l_@@_rule_pattern_tl \tl_new:N \l_@@_rule_wd_tl \tl_new:N \l_@@_rule_color_tl \tl_new:N \l_@@_rule_style_tl \dim_new:N \l_@@_rule_dim \seq_new:N \l_@@_rule_style_seq % \end{macrocode} % \end{variable} % % % \subsubsection{样式设置} % % \begin{macro}{\rulepatternset, \rulewdset, \rulestyleset} % 设置边框线的线宽、线型与样式。 % \begin{macrocode} \NewDocumentCommand {\rulepatternset} { m m } { \prop_gput:Nnn \g_@@_rule_pattern_prop {#1} {#2} } \NewDocumentCommand {\rulewdset} { m m } { \prop_gput:Nnn \g_@@_rule_wd_prop {#1} {#2} } \NewDocumentCommand {\rulestyleset} { m m m m } { \prop_get:NnNF \g_@@_rule_pattern_prop {#2} \l_@@_rule_pattern_tl { \tl_set:Nn \l_@@_rule_pattern_tl {#2} } \prop_get:NnNF \g_@@_rule_wd_prop {#3} \l_@@_rule_wd_tl { \tl_set:Nn \l_@@_rule_wd_tl {#3} } \prop_gput:Nne \g_@@_rule_style_prop {#1} { \l_@@_rule_pattern_tl \s_@@_mark \l_@@_rule_wd_tl \s_@@_mark #4} } \rulepatternset {soild} {} \rulepatternset {dotted}{0.6em, 0.15em} \rulepatternset {dash} {0.6em, 0.15em, 0.05em, 0.15em} \rulewdset {normal} {1pt} \rulewdset {thick} {1.5pt} \rulewdset {thin} {0.75pt} \rulewdset {toprule} {0.08em} \rulewdset {midrule} {0.05em} \rulewdset {cmidrule} {0.03em} \rulewdset {bottomrule} {0.08em} \rulestyleset {normal} {soild} {normal} {black} \rulestyleset {dotted} {dotted} {normal} {black} \rulestyleset {dash} {dash} {normal} {black} \rulestyleset {bold} {soild} {thick} {black} % \end{macrocode} % \end{macro} % % % \subsubsection{应用样式} % % \begin{macro}{\@@_set_rule_pattern:n, \@@_set_rule_wd:n, \@@_set_rule_style:n} % 设置当前线型、线宽及样式。 % \begin{macrocode} \cs_new:Nn \@@_set_rule_pattern:n { \prop_get:NnNTF \g_@@_rule_pattern_prop {#1} \l_@@_rule_pattern_tl { \draw_set_dash_pattern:Vn \l_@@_rule_pattern_tl {0pt} } { \draw_set_dash_pattern:nn {#1} {0pt} } } \cs_new:Nn \@@_set_rule_wd:n { \prop_get:NnNTF \g_@@_rule_wd_prop {#1} \l_@@_rule_wd_tl { \draw_set_linewidth:V \l_@@_rule_wd_tl } { \draw_set_linewidth:n {#1} } } \cs_new:Nn \@@_set_rule_style:n { \prop_get:NnNTF \g_@@_rule_style_prop {#1} \l_@@_rule_style_tl { \seq_set_split:NnV \l_@@_rule_style_seq {\s_@@_mark} \l_@@_rule_style_tl \draw_set_dash_pattern:en { \seq_item:Nn \l_@@_rule_style_seq {1} } {0pt} \draw_set_linewidth:e { \seq_item:Nn \l_@@_rule_style_seq {2} } \color_stroke:e { \seq_item:Nn \l_@@_rule_style_seq {3} } } { \@@_set_rule_pattern:n {soild} \@@_set_rule_wd:n {#1} \color_stroke:e {black} } } \cs_generate_variant:Nn \@@_set_rule_style:n {V} % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_parse_rule_style:n} % 解析边框线样式,并将结果分别保存在 \cs{l_@@_rule_pattern_tl}、 % \cs{l_@@_rule_wd_tl} 与 \cs{l_@@_rule_color_tl} 中。 % \begin{macrocode} \cs_new:Nn \@@_parse_rule_style:n { \prop_get:NnNTF \g_@@_rule_style_prop {#1} \l_@@_rule_style_tl { \seq_set_split:NnV \l_@@_rule_style_seq {\s_@@_mark} \l_@@_rule_style_tl \tl_set:Ne \l_@@_rule_pattern_tl { \seq_item:Nn \l_@@_tmpa_seq {1} } \tl_set:Ne \l_@@_rule_wd_tl { \seq_item:Nn \l_@@_tmpa_seq {2} } \tl_set:Ne \l_@@_rule_color_tl { \seq_item:Nn \l_@@_tmpa_seq {3} } \dim_set:Nn \l_@@_rule_dim { \l_@@_rule_wd_tl } } { \prop_get:NnNF \g_@@_rule_wd_prop {#1} \l_@@_rule_wd_tl { \tl_set:Nn \l_@@_rule_wd_tl {#1} } \dim_set:Nn \l_@@_rule_dim { \l_@@_rule_wd_tl } \tl_if_empty:NT \l_@@_rule_pattern_tl { \tl_set:Nn \l_@@_rule_pattern_tl { soild } } \tl_if_empty:NT \l_@@_rule_color_tl { \tl_set:Nn \l_@@_rule_color_tl { black } } } } \cs_generate_variant:Nn \@@_parse_rule_style:n {V} % \end{macrocode} % \end{macro} % % % % \section{表格核心} % % 本节定义了存储、设置及读取表格的位置、内容与格式的核心变量与函数。 % 录入一个表格,应当先设置好内容,再设置格式,否则可能会报错。 % % \subsection{核心存储} % % 本小节定义一些用于存储表格数据与格式的变量。 % 在表格的内部存储中,统一使用数字坐标\footnote{数据从 $1$ 开始,行/列表头的坐标为 $0$。}为索引(键); % 行名与列名仅用于输入接口。 % % % \subsubsection{行列属性} % % \begin{variable}{\g_@@_row_header_bool, \g_@@_col_header_bool} % 是否包含行列表头(|Header|)。 % \begin{macrocode} \bool_new:N \g_@@_row_header_bool \bool_new:N \g_@@_col_header_bool % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_row_count_int, \g_@@_col_count_int} % 最大行列数(不统计 |Header|)。 % \begin{macrocode} \int_new:N \g_@@_row_count_int \int_new:N \g_@@_col_count_int % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_row_name_prop, \g_@@_col_name_prop} % 存储行名与列名与其数字坐标的映射关系(|name -> number|)。 % 唯二不使用数字坐标为索引的(集合)变量(包含 |Header|)。 % \begin{macrocode} \prop_new:N \g_@@_row_name_prop \prop_new:N \g_@@_col_name_prop % \end{macrocode} % \end{variable} % % % \begin{variable}{\g_@@_row_align_seq, \g_@@_col_align_seq} % 行列的对齐方式(包含 |Header|)。 % \begin{macrocode} \seq_new:N \g_@@_row_align_seq \seq_new:N \g_@@_col_align_seq % \end{macrocode} % \end{variable} % % % \begin{variable}{\g_@@_row_ht_style_seq, \g_@@_col_wd_style_seq} % 行高与列宽样式(包含 |Header|)。 % \begin{macrocode} \seq_new:N \g_@@_row_ht_style_seq \seq_new:N \g_@@_col_wd_style_seq % \end{macrocode} % \end{variable} % % % \subsubsection{单元格内容} % % % \begin{variable}{\c_@@_cell_content_tl, \c_@@_cell_formula_tl, \c_@@_cell_box_tl} % 定义单元格的类型。 % \begin{macrocode} \tl_const:Nn \c_@@_cell_content_tl {content} \tl_const:Nn \c_@@_cell_formula_tl {formula} \tl_const:Nn \c_@@_cell_box_tl {box} % \end{macrocode} % \end{variable} % % % \begin{variable}{\g_@@_cell_data_prop} % 定义存储的单元格内容的属性表(包含 |Header|)。\\ % 单元格内容:|row,col| |->| |{type, content, formula}|(分隔符为 \cs{s_@@_mark})。 % \begin{macrocode} \prop_new:N \g_@@_cell_data_prop % \end{macrocode} % \end{variable} % % % \subsubsection{合并单元格} % % % \begin{variable}{\g_@@_merge_info_seq, \g_@@_merge_align_seq, \g_@@_merge_ref_prop} % 合并单元格引用映射表、信息表及对齐样式表。\\ % 引用映射表:|row,col| |->| |merge_id|(在信息表及样式表中的位置);\\ % 信息表:|{start_row, start_col, end_row, end_col, mapping_cell}|;\\ % 对齐样式表:|{row_align, col_aligin}|。 % \begin{macrocode} \seq_new:N \g_@@_merge_info_seq \seq_new:N \g_@@_merge_align_seq \prop_new:N \g_@@_merge_ref_prop % \end{macrocode} % \end{variable} % % % \subsection{行列定位} % % % \subsubsection{行列名设置} % % % \begin{macro}{\@@_set_excel_col_names:n} % 设置 Excel 风格的列名,|#1| 为终止列数。 % \begin{macrocode} \cs_new:Nn \@@_set_excel_col_names:n { \int_step_inline:nn {#1} { \prop_gput:Nen \g_@@_col_name_prop { \int_to_Alph:n {##1} } {##1} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_set_row_name:nN, \@@_set_col_name:nN} % 设置行名/列名: |#1| 为行名/列名,|#2| 为数字列坐标。 % \begin{macrocode} \cs_new:Nn \@@_set_row_name:nN { \prop_gput:Nne \g_@@_row_name_prop {#1} {\int_use:N #2} \@@_int_gset_max:Nn \g_@@_row_count_int {#2} } \cs_new:Nn \@@_set_col_name:nN { \prop_gput:Nne \g_@@_col_name_prop {#1} {\int_use:N #2} \@@_int_gset_max:Nn \g_@@_col_count_int {#2} } \cs_generate_variant:Nn \@@_set_row_name:nN {VN} \cs_generate_variant:Nn \@@_set_col_name:nN {VN} % \end{macrocode} % \end{macro} % % % % \subsubsection{行列名解析} % % % \begin{macro}{\@@_get_line_loc:NnNF} % 返回指定行名、列名对应的数字坐标。 % |#1|:名称属性表,|#2|:名称,|#3|:结果变量,|#4| 查找失败时动作。 % \begin{macrocode} \prg_new_conditional:Nnn \@@_get_line_loc:NnN { T, F, TF } { \regex_match:nnTF {^-?[0-9]+$} {#2} { \int_set:Nn #3 {#2} \prg_return_true: } { \prop_get:NnNTF #1 {#2} \l_@@_tmpa_tl { \int_set:Nn #3 { \l_@@_tmpa_tl } \prg_return_true: } { \prg_return_false: } } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_parse_row_loc:n, \@@_parse_col_loc:n} % 解析坐标(支持数字和名称混合),如果解析失败则报错,|#1|:行名/列名。 % 解析的坐标保存在 \cs{l_@@_row_loc_int} 或 \cs{l_@@_col_loc_int} 中。 % \begin{macrocode} \cs_new:Nn \@@_parse_row_loc:n { \@@_get_line_loc:NnNF \g_@@_row_name_prop {#1} \l_@@_row_loc_int { \msg_error:nnn {xtable} {unknown_row_name} {#1} } } \cs_new:Nn \@@_parse_col_loc:n { \@@_get_line_loc:NnNF \g_@@_col_name_prop {#1} \l_@@_col_loc_int { \msg_error:nnn {xtable} {unknown_col_name} {#1} } } \cs_generate_variant:Nn \@@_parse_row_loc:n { e, V } \cs_generate_variant:Nn \@@_parse_col_loc:n { e, V } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_parse_new_row_loc:n, \@@_parse_new_col_loc:n} % 解析坐标(支持数字和名称混合),如果解析失败则尝试建立映射,|#1|:行名/列名。 % 坐标保存在 \cs{l_@@_row_loc_int} 或 \cs{l_@@_col_loc_int} 中。 % \begin{macrocode} \cs_new:Nn \@@_parse_new_row_loc:n { \@@_get_line_loc:NnNF \g_@@_row_name_prop {#1} \l_@@_row_loc_int { \int_if_zero:nT { \l_@@_row_loc_int } { \int_set:Nn \l_@@_row_loc_int { \g_@@_row_count_int + 1 } } \@@_set_row_name:nN {#1} \l_@@_row_loc_int } } \cs_new:Nn \@@_parse_new_col_loc:n { \@@_get_line_loc:NnNF \g_@@_col_name_prop {#1} \l_@@_col_loc_int { \int_if_zero:nT { \l_@@_col_loc_int } { \int_set:Nn \l_@@_col_loc_int { \g_@@_col_count_int + 1 } } \@@_set_col_name:nN {#1} \l_@@_col_loc_int } } \cs_generate_variant:Nn \@@_parse_new_row_loc:n { e, V } \cs_generate_variant:Nn \@@_parse_new_col_loc:n { e, V } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_parse_coord:n, \@@_parse_new_coord:n} % 解析坐标(支持数字和名称混合),|#1|:(行名, 列名)。 % 解析的坐标保存在 \cs{l_@@_row_loc_int} 与 \cs{l_@@_col_loc_int} 中。 % 两个版本的区别在于如果解析失败如何处理。 % \begin{macrocode} \cs_new:Nn \@@_parse_coord:n { \@@_parse_row_loc:e { \clist_item:nn {#1} {1} } \@@_parse_col_loc:e { \clist_item:nn {#1} {2} } } \cs_new:Nn \@@_parse_new_coord:n { \@@_parse_new_row_loc:e { \clist_item:nn {#1} {1} } \@@_parse_new_col_loc:e { \clist_item:nn {#1} {2} } } % \end{macrocode} % \end{macro} % % % \subsection{内容处理} % % % \subsubsection{初始化与保存} % % % \begin{macro}{\@@_table_init:} % 将初始化为空表。 % \begin{macrocode} \cs_new:Nn \@@_table_init: { \int_gzero:N \g_@@_row_count_int \int_gzero:N \g_@@_col_count_int \seq_gclear:N \g_@@_row_align_seq \seq_gclear:N \g_@@_col_align_seq \seq_gclear:N \g_@@_row_ht_style_seq \seq_gclear:N \g_@@_col_wd_style_seq \seq_gclear:N \g_@@_merge_info_seq \seq_gclear:N \g_@@_merge_align_seq \prop_gclear:N \g_@@_row_name_prop \prop_gclear:N \g_@@_col_name_prop \prop_gclear:N \g_@@_cell_data_prop \prop_gclear:N \g_@@_merge_ref_prop % \end{macrocode} % \begin{macrocode} \prop_gput:Nnn \g_@@_row_name_prop {header} {0} \prop_gput:Nnn \g_@@_col_name_prop {title} {0} \bool_gset_false:N \g_@@_row_header_bool \bool_gset_true:N \g_@@_col_header_bool } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\@@_table_save:n} % 保存表格。 \done\todo[Debug] { 修复表格保存命令报错的 Bug } % \begin{macrocode} \cs_new:Nn \@@_table_save:n { \cs_if_exist:cF { g_@@_row_count_#1_int } { \bool_new:c { g_@@_row_header_#1_bool } \bool_new:c { g_@@_col_header_#1_bool } \int_new:c { g_@@_row_count_#1_int } \int_new:c { g_@@_col_count_#1_int } \seq_new:c { g_@@_row_align_#1_seq } \seq_new:c { g_@@_col_align_#1_seq } \seq_new:c { g_@@_row_ht_style_#1_seq } \seq_new:c { g_@@_col_wd_style_#1_seq } \seq_new:c { g_@@_merge_info_#1_seq } \seq_new:c { g_@@_merge_align_#1_seq } \prop_new:c { g_@@_row_name_#1_prop } \prop_new:c { g_@@_col_name_#1_prop } \prop_new:c { g_@@_cell_data_#1_prop } \prop_new:c { g_@@_merge_ref_#1_prop } } \bool_gset_eq:cN { g_@@_row_header_#1_bool } \g_@@_row_header_bool \bool_gset_eq:cN { g_@@_col_header_#1_bool } \g_@@_col_header_bool \int_gset_eq:cN { g_@@_row_count_#1_int } \g_@@_row_count_int \int_gset_eq:cN { g_@@_col_count_#1_int } \g_@@_col_count_int \seq_gset_eq:cN { g_@@_row_align_#1_seq } \g_@@_row_align_seq \seq_gset_eq:cN { g_@@_col_align_#1_seq } \g_@@_col_align_seq \seq_gset_eq:cN { g_@@_row_ht_style_#1_seq } \g_@@_row_ht_style_seq \seq_gset_eq:cN { g_@@_col_wd_style_#1_seq } \g_@@_col_wd_style_seq \seq_gset_eq:cN { g_@@_merge_info_#1_seq } \g_@@_merge_info_seq \seq_gset_eq:cN { g_@@_merge_align_#1_seq } \g_@@_merge_align_seq \prop_gset_eq:cN { g_@@_row_name_#1_prop } \g_@@_row_name_prop \prop_gset_eq:cN { g_@@_col_name_#1_prop } \g_@@_col_name_prop \prop_gset_eq:cN { g_@@_cell_data_#1_prop } \g_@@_cell_data_prop \prop_gset_eq:cN { g_@@_merge_ref_#1_prop } \g_@@_merge_ref_prop } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_table_restore:n} % 恢复表格。 % \begin{macrocode} \cs_new:Nn \@@_table_restore:n { \cs_if_exist:cF { g_@@_row_count_#1_int } { \msg_error:nnn {xtable} {unsaved_table} {#1} } \bool_gset_eq:Nc \g_@@_row_header_bool { g_@@_row_header_#1_bool } \bool_gset_eq:Nc \g_@@_col_header_bool { g_@@_col_header_#1_bool } \int_gset_eq:Nc \g_@@_row_count_int { g_@@_row_count_#1_int } \int_gset_eq:Nc \g_@@_col_count_int { g_@@_col_count_#1_int } \seq_gset_eq:Nc \g_@@_row_align_seq { g_@@_row_align_#1_seq } \seq_gset_eq:Nc \g_@@_col_align_seq { g_@@_col_align_#1_seq } \seq_gset_eq:Nc \g_@@_row_ht_style_seq { g_@@_row_ht_style_#1_seq } \seq_gset_eq:Nc \g_@@_col_wd_style_seq { g_@@_col_wd_style_#1_seq } \seq_gset_eq:Nc \g_@@_merge_info_seq { g_@@_merge_info_#1_seq } \seq_gset_eq:Nc \g_@@_merge_align_seq { g_@@_merge_align_#1_seq } \prop_gset_eq:Nc \g_@@_row_name_prop { g_@@_row_name_#1_prop } \prop_gset_eq:Nc \g_@@_col_name_prop { g_@@_col_name_#1_prop } \prop_gset_eq:Nc \g_@@_cell_data_prop { g_@@_cell_data_#1_prop } \prop_gset_eq:Nc \g_@@_merge_ref_prop { g_@@_merge_ref_#1_prop } } % \end{macrocode} % \end{macro} % % % \subsubsection{内容设置} % % % \begin{macro}{\@@_set_cell:nnnn} % 设置单元格数据。|#1| 数字坐标,|#2| 类型,|#3| 内容,|#4| 公式。 % \begin{macrocode} \cs_new:Nn \@@_set_cell:nnnn { \prop_gput:Nnn \g_@@_cell_data_prop {#1} {#2 \s_@@_mark #3 \s_@@_mark #4} \@@_int_gset_max:Nn \g_@@_row_count_int { \clist_item:nn{#1}{1} } \@@_int_gset_max:Nn \g_@@_col_count_int { \clist_item:nn{#1}{2} } } \cs_generate_variant:Nn \@@_set_cell:nnnn { nVnn, eVnn } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_set_cell:nn, \@@_set_cell_formula:nn, \@@_set_cell_box:nn} % 设置单元格内容。|#1| 数字坐标,|#2| 内容/公式/盒子。 % 若内容为空,则直接清空。 % \begin{macrocode} \cs_new:Nn \@@_set_cell:nn { \tl_if_empty:nTF {#2} { \prop_gpop:NeN \g_@@_cell_data_prop {#1} \l_@@_tmpa_tl } { \@@_set_cell:nVnn {#1} \c_@@_cell_content_tl {#2} {} } } \cs_new:Nn \@@_set_cell_formula:nn { \@@_set_cell:nVnn {#1} \c_@@_cell_formula_tl {} {#2} } \cs_new:Nn \@@_set_cell_box:nn { \@@_set_cell:nVnn {#1} \c_@@_cell_box_tl {#2} {} } \cs_generate_variant:Nn \@@_set_cell:nn { en, nV, eV } \cs_generate_variant:Nn \@@_set_cell_formula:nn { en } \cs_generate_variant:Nn \@@_set_cell_box:nn { en } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_set_cell:NNn, \@@_set_cell_formula:NNn, \@@_set_cell_box:NNn} % 设置单元格内容。|#1#2| 数字坐标,|#3| 内容/公式/盒子。 % 若内容为空,则直接清空。 % \begin{macrocode} \cs_new:Nn \@@_set_cell:NNn { \@@_set_cell:en { \int_use:N #1, \int_use:N #2 } {#3} } \cs_new:Nn \@@_set_cell_formula:NNn { \@@_set_cell_formula:en { \int_use:N #1, \int_use:N #2 } {#3} } \cs_new:Nn \@@_set_cell_box:NNn { \@@_set_cell_box:en { \int_use:N #1, \int_use:N #2 } {#3} } \cs_generate_variant:Nn \@@_set_cell:NNn { NNV } \cs_generate_variant:Nn \@@_set_cell_formula:NNn { NNV } \cs_generate_variant:Nn \@@_set_cell_box:NNn { NNV } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_set_cell:n, \@@_set_cell_formula:n, \@@_set_cell_box:n} % 设置单元格内容/公式/盒子。坐标使用 \cs{l_@@_row_loc_int} 与 \cs{l_@@_col_loc_int}。 % \begin{macrocode} \cs_new:Nn \@@_set_cell:n { \@@_set_cell:NNn \l_@@_row_loc_int \l_@@_col_loc_int {#1} } % \end{macrocode} % \begin{macrocode} \cs_new:Nn \@@_set_cell_formula:n { \@@_set_cell_formula:NNn \l_@@_row_loc_int \l_@@_col_loc_int {#1} } % \end{macrocode} % \begin{macrocode} \cs_new:Nn \@@_set_cell_box:n { \@@_set_cell_box:NNn \l_@@_row_loc_int \l_@@_col_loc_int {#1} } % \end{macrocode} % \end{macro} % % % \subsubsection{内容查询} % % \begin{macro}{\@@_parse_cell:n} % 获取指定坐标的单元格的内容。 % 结果保存在 \cs{l_@@_data_tl}(内容)与 \cs{l_@@_cell_is_box_bool}(是否盒子)中。 % \begin{macrocode} \cs_new:Nn \@@_parse_cell:n { \bool_set_false:N \l_@@_is_box_bool \prop_get:NnNTF \g_@@_cell_data_prop {#1} \l_@@_tmpa_tl { \seq_set_split:NnV \l_@@_tmpa_seq {\s_@@_mark} \l_@@_tmpa_tl \tl_set:Ne \l_@@_tmpa_tl { \seq_item:Nn \l_@@_tmpa_seq {1} } \tl_set:Ne \l_@@_data_tl { \seq_item:Nn \l_@@_tmpa_seq {2} } \str_if_eq:VVT \l_@@_tmpa_tl \c_@@_cell_box_tl { \bool_set_true:N \l_@@_is_box_bool } } { \tl_clear:N \l_@@_data_tl } % 空单元格 } \cs_generate_variant:Nn {\@@_parse_cell:n} {V, e} % \end{macrocode} % \end{macro} % % % \subsection{修改表格} % % \changes{v0.4}{2026-03-11}{添加修改表格的功能} % % \subsubsection{表格重组} % % \begin{macro}{\@@_table_restructure:nn} % 重新选择数据替换原数据表。 % |#1|: 新的行/列逗号分隔列表,|#2|: 行还是列。 % \begin{macrocode} \cs_new:Nn \@@_table_restructure:nn { \group_begin: \prop_clear:N \l_@@_tmpa_prop % new -> old \prop_clear:N \l_@@_tmpb_prop % old -> new \prop_clear:N \l_@@_cachea_prop % new name prop \prop_clear:N \l_@@_cacheb_prop % new data prop \seq_set_split:Nnn \l_@@_tmpa_seq {,} {#1} % new col list % \end{macrocode} % 确认映射表与新的行/列数。 % \begin{macrocode} \int_gset:cn {g_@@_#2_count_int} { \seq_count:N \l_@@_tmpa_seq } \prop_gput:Nnn \l_@@_tmpa_prop {0} {0} \prop_gput:Nnn \l_@@_tmpb_prop {0} {0} \int_step_inline:nn { \seq_count:N \l_@@_tmpa_seq } { \tl_set:Ne \l_@@_tmpa_tl { \seq_item:Nn \l_@@_tmpa_seq {##1} } \prop_gput:NnV \l_@@_tmpa_prop {##1} \l_@@_tmpa_tl \prop_gput:NVn \l_@@_tmpb_prop \l_@@_tmpa_tl {##1} } % \end{macrocode} % 更新名称属性表。 % \begin{macrocode} \prop_map_inline:cn { g_@@_#2_name_prop } { \prop_get:NnNTF \l_@@_tmpb_prop {##2} \l_@@_tmpa_tl { \prop_put:NnV \l_@@_cachea_prop {##1} \l_@@_tmpa_tl } { \int_compare:nNnT {##2} = {0} { \prop_put:Nnn \l_@@_cachea_prop {##1} {##2} } } } \prop_gset_eq:cN { g_@@_#2_name_prop } \l_@@_cachea_prop % \end{macrocode} % 更新实际数据。 % \begin{macrocode} \str_if_eq:nnTF {#2} {row} { \cs_set_eq:NN \@@_tmpa_cs: \l_@@_tmpa_str } { \cs_set_eq:NN \@@_tmpa_cs: \l_@@_tmpb_str } \prop_map_inline:nn \g_@@_cell_data_prop { \str_set:Ne \l_@@_tmpa_str { \clist_item:nn {##1} {1} } \str_set:Ne \l_@@_tmpb_str { \clist_item:nn {##1} {2} } \prop_get:NnNT \l_@@_tmpb_prop \@@_tmpa_cs: \@@_tmpa_cs: { \prop_put:Nen \l_@@_cacheb_prop { \l_@@_tmpa_str, \l_@@_tmpb_str } {##2} } } \prop_gset_eq:NN \g_@@_cell_data_prop \l_@@_cacheb_prop \group_end: } % \end{macrocode} % \end{macro} % % % \subsubsection{移动表格} % % \begin{macro}{\@@_data_move:nnn} % 整行或整列移动单元格。 % |#1|: 起始位置,|#2|: 移动数量,|#3|: 行还是列。 % \begin{macrocode} \cs_new:Nn \@@_data_move:nnn { \prop_clear:N \l_@@_tmpa_seq % new data prop \str_if_eq:nnTF {#3} {row} { \cs_set_eq:NN \@@_tmpa_cs: \l_@@_tmpa_int % 临时行索引 \cs_set_eq:NN \@@_tmpb_cs: \l_@@_row_count_int % 行数 } { \cs_set_eq:NN \@@_tmpa_cs: \l_@@_tmpb_int % 临时列索引 \cs_set_eq:NN \@@_tmpb_cs: \l_@@_col_count_int % 列数 } % \end{macrocode} % 更新实际数据。 % \begin{macrocode} \prop_map_inline:nn \g_@@_cell_data_prop { \int_set:Nn \l_@@_tmpa_int { \clist_item:nn {##1} {1} } \int_set:Nn \l_@@_tmpb_int { \clist_item:nn {##1} {2} } \int_compare:nNnTF {\@@_tmpa_cs:} < {#1} % 如果有数据,说明其它单元格移动到此位置 { \prop_put_if_new:Nnn \l_@@_tmpa_seq {##1} {##2} } { \int_add:Nn \@@_tmpa_cs: {#2} \@@_int_gset_max:Nn \@@_tmpb_cs: { \@@_tmpa_cs: } \prop_put:Nen \l_@@_tmpa_seq { \int_use:N \l_@@_tmpa_int, \int_use:N \l_@@_tmpb_int } {##2} } } \prop_gset_eq:NN \g_@@_cell_data_prop \l_@@_tmpa_seq } % \end{macrocode} % \end{macro} % % % \subsubsection{移动单元格} % % % \begin{macro}{\@@_cell_move_by_row:nnn, \@@_cell_move_by_col:nnn} % 移动单元格。 % |#1|: 起始行,|#2|: 起始列,|#3|: 移动数量。 % \begin{macrocode} \cs_new:Nn \@@_cell_move_by_row:nnn { \int_compare:nNnTF {#3} < {0} { \int_step_inline:nnn {#1} {\g_@@_row_count_int} } { \int_step_inline:nnnn {\g_@@_row_count_int} {#1} {-1} } { % 此内容为映射函数的最后一个参数 \prop_gpop:NnNT \g_@@_cell_data_prop {##1,#2} \l_@@_tmpa_tl { \int_set:Nn \l_@@_tmpa_int {##1 + #3} \prop_gput:NeV \g_@@_cell_data_prop { \int_use:N \l_@@_tmpa_int, #2 } \l_@@_tmpa_tl \@@_int_gset_max:Nn \l_@@_row_count_int { \l_@@_tmpa_int } } } } % \end{macrocode} % \begin{macrocode} \cs_new:Nn \@@_cell_move_by_col:nnn { \int_compare:nNnTF {#3} < {0} { \int_step_inline:nnn {#2} {\g_@@_col_count_int} } { \int_step_inline:nnnn {\g_@@_col_count_int} {#2} {-1} } { % 此内容为映射函数的最后一个参数 \prop_gpop:NnNT \g_@@_cell_data_prop {#1,##1} \l_@@_tmpa_tl { \int_set:Nn \l_@@_tmpa_int {##1 + #3} \prop_gput:NeV \g_@@_cell_data_prop { #1, \int_use:N \l_@@_tmpa_int } \l_@@_tmpa_tl \@@_int_gset_max:Nn \l_@@_col_count_int { \l_@@_tmpa_int } } } } % \end{macrocode} % \end{macro} % % % \subsection{格式处理} % % \subsubsection{样式设置} % % \begin{macro}{\@@_set_line_style:NNNn} % 设置行/列样式。|#1|: 行样式名,|#2|: 样式内容,|#3|: 默认值,|#4|:行或列。 % 本命令会修改变量 \cs{l_@@_cachea_int}、\cs{l_@@_row_loc_int} 及 \cs{l_@@_col_loc_int} 的值。 % \begin{macrocode} \cs_new:Nn \@@_set_line_style:NNnn { \bool_if:cTF { \str_if_eq:nnTF {#4} {row} { g_@@_col_header_bool } { g_@@_row_header_bool } } { \int_set:Nn \l_@@_tmpa_int {0} } { \int_set:Nn \l_@@_tmpa_int {1} } \@@_int_gset_max:cn { g_@@_#4_count_int } { \seq_count:N #1 - 1 + \l_@@_tmpa_int } \@@_ginit_seq:Nnn #1 { \int_use:c { g_@@_#4_count_int } + 1 } {#3} \cs_set_eq:Nc \@@_tmpa_cs: { @@_parse_#4_loc:V } \seq_map_inline:Nn #2 { \str_if_in:nnTF {##1} {=} { \seq_set_split:Nnn \l_@@_tmpa_seq {=} {##1} \seq_get_left:NN \l_@@_tmpa_seq \l_@@_tmpa_tl \seq_get_right:NN \l_@@_tmpa_seq \l_@@_tmpb_tl \@@_tmpa_cs: \l_@@_tmpa_tl \int_set_eq:Nc \l_@@_tmpa_int { l_@@_#4_loc_int } } { \tl_set:Nn \l_@@_tmpb_tl {##1} } \seq_gset_item:NnV #1 {\l_@@_tmpa_int + 1} \l_@@_tmpb_tl \int_incr:N \l_@@_tmpa_int } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_set_row_align:Nn, \@@_set_col_align:Nn} % 设置行高样式。|#1|: 样式内容,|#2|: 默认值。 % \begin{macrocode} \cs_new:Nn \@@_set_row_align:Nn { \@@_set_line_style:NNnn \g_@@_row_align_seq #1 {#2} {row} } \cs_new:Nn \@@_set_col_align:Nn { \@@_set_line_style:NNnn \g_@@_col_align_seq #1 {#2} {col} } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\@@_set_row_ht_style:Nnn, \@@_set_col_wd_style:Nnn} % 设置行高的样式。|#1|: 样式内容,|#2|: 默认值,|#3|: 备用。 % \begin{macrocode} \cs_new:Nn \@@_set_row_ht_style:Nnn { \@@_set_line_style:NNnn \g_@@_row_ht_style_seq #1 {#2} {row} \int_step_inline:nn { \seq_count:N \g_@@_row_ht_style_seq } { \tl_set:Ne \l_@@_tmpa_tl { \seq_item:Nn \g_@@_row_ht_style_seq {##1} } \tl_replace_all:Nnn \l_@@_tmpa_tl {auto} {a} \tl_replace_all:Nnn \l_@@_tmpa_tl {same} {s} \seq_gset_item:NnV \g_@@_row_ht_style_seq {##1} \l_@@_tmpa_tl } \seq_gput_right:Nn \g_@@_row_ht_style_seq {#3} } % \end{macrocode} % 设置列宽的样式。|#1|: 样式内容,|#2|: 默认值,|#3|: 总宽。 % \begin{macrocode} \cs_new:Nn \@@_set_col_wd_style:Nnn { \@@_set_line_style:NNnn \g_@@_col_wd_style_seq #1 {#2} {col} \int_step_inline:nn { \seq_count:N \g_@@_col_wd_style_seq } { \tl_set:Ne \l_@@_tmpa_tl { \seq_item:Nn \g_@@_col_wd_style_seq {##1} } \tl_replace_all:Nnn \l_@@_tmpa_tl {auto} {a} \tl_replace_all:Nnn \l_@@_tmpa_tl {same} {s} \tl_replace_all:Nnn \l_@@_tmpa_tl {fill} {f} \seq_gset_item:NnV \g_@@_col_wd_style_seq {##1} \l_@@_tmpa_tl } \seq_gput_right:Nn \g_@@_col_wd_style_seq {#3} } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_insure_style:} % 确保样式有设置好。 % \begin{macrocode} \cs_new:Nn \@@_insure_style: { \seq_if_empty:NT \g_@@_row_align_seq { \@@_set_row_align:Nn \c_empty_seq {b} } \seq_if_empty:NT \g_@@_col_align_seq { \@@_set_col_align:Nn \c_empty_seq {c} } \seq_if_empty:NT \g_@@_row_ht_style_seq { \@@_set_row_ht_style:Nnn \c_empty_seq {a} {} } \seq_if_empty:NT \g_@@_col_wd_style_seq { \@@_set_col_wd_style:Nnn \c_empty_seq {a} {\textwidth} } } % \end{macrocode} % \end{macro} % % % % \subsubsection{样式查询} % % % \begin{macro}{\@@_parse_row_align:n, \@@_parse_col_align:n} % 解析行列的对齐样式。|#1|: 数字坐标,结果保存在 % \cs{l_@@_row_align_tl} 与 \cs{l_@@_col_align_tl} 中。 % \begin{macrocode} \cs_new:Nn \@@_parse_row_align:n { \tl_set:Ne \l_@@_row_align_tl { \seq_item:Nn \g_@@_row_align_seq {#1 + 1} } } \cs_new:Nn \@@_parse_col_align:n { \tl_set:Ne \l_@@_col_align_tl { \seq_item:Nn \g_@@_col_align_seq {#1 + 1} } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_parse_row_ht_style:n, \@@_parse_col_wd_style:n} % 解析行高/列宽的样式。|#1|: 数字坐标,结果保存在 \cs{l_@@_style_tl} 中。 % \begin{macrocode} \cs_new:Nn \@@_parse_row_ht_style:n { \tl_set:Ne \l_@@_style_tl { \seq_item:Nn \g_@@_row_ht_style_seq {#1 + 1} } } \cs_new:Nn \@@_parse_col_wd_style:n { \tl_set:Ne \l_@@_style_tl { \seq_item:Nn \g_@@_col_wd_style_seq {#1 + 1} } } % \end{macrocode} % \end{macro} % % % \subsection{合并单元格} % % \changes{v0.4}{2026-03-11}{添加对合并单元格的支持} % % \subsubsection{合并设置} % % % \begin{macro}{\@@_check_merge_range:nnnn} % 检查指定区域内的所有单元格是否都未被合并。 % |#1|: 起始行, |#2|: 起始列, |#3|: 结束行, |#4|: 结束列。 % 如果有合并,则报错。 % \begin{macrocode} \cs_new:Nn \@@_check_merge_range:nnnn { \int_step_inline:nnn {#1} {#3} { \int_step_inline:nnn {#2} {#4} { \prop_if_in:NnT \g_@@_merge_ref_prop {##1,####1} { \msg_error:nnn {xtable} {merged_cell} {##1,####1} } } } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_set_merge_cell:nnnnn} % 合并单元格。 % |#1|: 起始行, |#2|: 起始列, |#3|: 结束行, |#4|: 结束列, |#5|: 合并后取哪个单元格的内容。 % \begin{macrocode} \cs_new:Nn \@@_set_merge_cell:nnnnn { \@@_check_merge_range:nnnn {#1} {#2} {#3} {#4} \int_set:Nn \l_@@_tmpa_int { \seq_count:N \g_@@_merge_info_seq + 1 } \prop_get:NnNF \c_@@_merge_align_prop {#5} \l_@@_tmpa_tl { \tl_set:Nn \l_@@_tmpa_tl {} } \seq_gput_right:Ne \g_@@_merge_info_seq { #1 \s_@@_mark #2 \s_@@_mark #3 \s_@@_mark #4 \s_@@_mark \str_case:VnF \l_@@_tmpa_tl { {ul} { #1, #2 } {ur} { #3, #2 } {dl} { #1, #4 } {dr} { #3, #4 } } { #1, #2 } } \int_step_inline:nnn {#1} {#3} { \int_step_inline:nnn {#2} {#4} { \prop_gput:Nne \g_@@_merge_ref_prop {##1,####1} { \int_use:N \l_@@_tmpa_int } } } } \cs_generate_variant:Nn \@@_set_merge_cell:nnnnn { VVVVn } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_set_merge_align:n} % 指定当前合并单元格的对齐格式。 % \begin{macrocode} \cs_new:Nn \@@_set_merge_align:n { \int_set:Nn \l_@@_tmpa_int { \seq_count:N \g_@@_merge_align_seq + 1 } \int_set:Nn \l_@@_tmpb_int { \seq_count:N \g_@@_merge_info_seq - 1 } \int_step_inline:nnn { \l_@@_tmpa_int } { \l_@@_tmpb_int } { \seq_gput_right:Nn \g_@@_merge_align_seq {m, c} } \prop_get:NnNF \c_@@_merge_align_prop {#1} \l_@@_tmpa_tl { \tl_set:Nn \l_@@_tmpa_tl {m, c} } \seq_gput_right:NV \g_@@_merge_align_seq \l_@@_tmpa_tl } % \end{macrocode} % \end{macro} % % % % \subsubsection{合并查询} % % % \begin{macro}{\@@_merge_if:n} % 确认指定单元格是否已合并。|#1|: 数字坐标。 % \begin{macrocode} \prg_new_conditional:Nnn \@@_merge_if:n { p, T, F, TF } { \prop_if_in:NnTF \g_@@_merge_ref_prop {#1} { \prg_return_true: } { \prg_return_false: } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_parse_merge_count:} % 解析当前合并单元格数量,结果直接留在输入流中。 % \begin{macrocode} \cs_new:Nn \@@_parse_merge_count: { \seq_count:N \g_@@_merge_info_seq } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\@@_parse_merge_id:n} % 解析指定坐标所处的合并单元格的索引。修改变量 \cs{l_@@_merge_id_tl}。 % \begin{macrocode} \cs_new:Nn \@@_parse_merge_id:n { \prop_get:NnNF \g_@@_merge_ref_prop {#1} \l_@@_merge_id_tl { \msg_error:nnn {xtable} {unmerged_cell} {#1} } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_parse_merge:n} % 解析指定索引的合并单元格的内容。 % 修改变量 \cs{l_@@_merge_seq}、\cs{l_@@_data_tl} 及 \cs{l_@@_is_box_bool}。 % \begin{macrocode} \cs_new:Nn \@@_parse_merge:n { \tl_set:Ne \l_@@_tmpa_tl { \seq_item:Nn \g_@@_merge_info_seq {#1} } \seq_set_split:NnV \l_@@_merge_seq {\s_@@_mark} \l_@@_tmpa_tl \@@_parse_cell:e { \seq_item:Nn \l_@@_merge_seq {5} } } \cs_generate_variant:Nn \@@_parse_merge:n { V } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_parse_merge_align:n} % 解析指定索引的合并单元格的内容。 % \cs{l_@@_row_align_tl} 与 \cs{l_@@_col_align_tl} 中。 % \begin{macrocode} \cs_new:Nn \@@_parse_merge_align:n { \tl_set:Ne \l_@@_tmpa_tl { \seq_item:Nn \g_@@_merge_align_seq {#1} } \tl_set:Ne \l_@@_row_align_tl { \clist_item:Vn \l_@@_tmpa_tl {1} } \tl_set:Ne \l_@@_col_align_tl { \clist_item:Vn \l_@@_tmpa_tl {2} } } % \end{macrocode} % \end{macro} % % % \subsection{表头填充} % % \begin{macro}{\@@_header_fill:} % 当表格的表头为空时,尝试用行列名替代。 % \begin{macrocode} \cs_new:Nn \@@_header_fill: { \prop_clear:N \l_@@_tmpa_prop \prop_map_inline:Nn \g_@@_row_name_prop { \prop_put:Nnn \l_@@_tmpa_prop {##2, 0} {##1} } \prop_map_inline:Nn \g_@@_col_name_prop { \prop_put:Nnn \l_@@_tmpa_prop {0, ##2} {##1} } \prop_map_inline:Nn \l_@@_tmpa_prop { \tl_set:Nn \l_@@_tmpa_tl { \s_@@_mark ##2 \s_@@_mark } \tl_put_left:NV \l_@@_tmpa_tl \c_@@_cell_content_tl \prop_gput_if_new:NnV \g_@@_cell_data_prop {##1} \l_@@_tmpa_tl } \int_step_inline:nn { \g_@@_row_count_int } { \tl_set:No \l_@@_tmpa_tl {\c_@@_cell_content_tl \s_@@_mark ##1 \s_@@_mark } \prop_gput_if_new:NnV \g_@@_cell_data_prop {##1, 0} \l_@@_tmpa_tl } } % \end{macrocode} % \end{macro} % % % \subsection{调试} % % % \begin{macro}{\showtable} % 显示表存储的内容,使用变量 \cs{l_@@_data_tl}。\todo[优化]{ 显示表格的更多内容信息 } % \begin{macrocode} \NewDocumentCommand \showtable {} { % \end{macrocode} % 输出列标题。 % \begin{macrocode} \prop_if_empty:NTF \g_@@_cell_data_prop {当前表格内容为空。} {当前表格内容如下:} % \end{macrocode} % 输出内容。 % \begin{macrocode} \int_step_inline:nnn {0} { \g_@@_row_count_int } { \\<##1>~ \int_step_inline:nnn {0} { \g_@@_col_count_int } { \@@_parse_cell:n {##1, ####1} ,~\l_@@_data_tl } ; } } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\logtable} % 在日志中显示表格的核心数据。\done\todo[Debug] { 修复 \cs{logtable} 实际调用成 show 函数的 Bug } % \begin{macrocode} \NewDocumentCommand \logtable {} { \int_log:N \g_@@_row_count_int \int_log:N \g_@@_col_count_int \seq_log:N \g_@@_row_align_seq \seq_log:N \g_@@_col_align_seq \seq_log:N \g_@@_row_ht_style_seq \seq_log:N \g_@@_col_wd_style_seq \seq_log:N \g_@@_merge_info_seq \seq_log:N \g_@@_merge_align_seq \prop_log:N \g_@@_row_name_prop \prop_log:N \g_@@_col_name_prop \prop_log:N \g_@@_cell_data_prop \prop_log:N \g_@@_merge_ref_prop \bool_log:N \g_@@_row_header_bool \bool_log:N \g_@@_col_header_bool } % \end{macrocode} % \end{macro} % % % \section{输入接口} % % 本节作为数据输入的对外接口,负责将表格内容从用户层转到存储层,但不涉及渲染相关计算. % % \subsection{变量与选项} % % % \subsubsection{正则常量} % % % \begin{variable} {\c_@@_csv_row_regex, \c_@@_csv_cell_regex} % 用于解析 CSV 中的行的正则表达式。 % 其中单元格表达式每个匹配生成三个项目,它们分别是所有匹配,常规字段值,引号字段值。 % \begin{macrocode} \regex_const:Nn \c_@@_csv_row_regex { (?:(?:[^"\c{\\}]+|"(?:""|[^"])+")+?)(?:\c{\\}) } \regex_const:Nn \c_@@_csv_cell_regex { (?:([^"\c{\\},]*)|"((?:""|[^"])+)")(?:,|\c{\\}) } % \end{macrocode} % \end{variable} % % % \begin{variable} {\c_@@_json_cell_regex} % 用于解析 JSON 中的属性值的正则表达式。 % 每个匹配生成四个项目,它们分别是所有匹配,名称,非字符值(数值或真假),字符值。 % \begin{macrocode} \regex_const:Nn \c_@@_json_cell_regex { "((?:[^"\\]|\\.)+)" \s*:\s* (?: ([\+\-]?(?:\d*\.)?\d+|true|false) | "((?:[^"\\]|\\.)+)" ) \s*[,]? } % \end{macrocode} % \end{variable} % % % \begin{variable} {\c_@@_row_ht_regex, \c_@@_col_wd_regex} % 用于判定某项内容是否为行高样式/列宽样式。 % \begin{macrocode} \regex_const:Nn \c_@@_row_ht_regex { \A(?:.*=)?(?: (auto|same|a|s)| ([0-9.]+(?:pt|ex|em|mm|cm|in|cc|dd|pc)) )\Z } \regex_const:Nn \c_@@_col_wd_regex { \A(?:.*=)?(?: (auto|same|fill|samefill|a|s|f|sf)| ([0-9.]+(?:pt|ex|em|mm|cm|in|cc|dd|pc)) )\Z } % \end{macrocode} % \end{variable} % % % % \subsubsection{状态参数} % % \begin{variable} {\l_@@_input_bool} % 当前是否处于输入环境。 % \begin{macrocode} \bool_new:N \l_@@_input_bool \bool_set_false:N \l_@@_input_bool % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_check_input_env:n} % 检测是否处理输入环境。如果不是,则报错。 % \begin{macrocode} \cs_new:Nn \@@_check_input_env:n { \bool_if:NF \l_@@_input_bool { \msg_error:nnn {xtable} {outside_xtable} {#1} } } % \end{macrocode} % \end{macro} % % \begin{variable} {\l_@@_parse_status_int, \l_@@_esc_status_bool, \l_@@_quote_status_bool} % 用于保存解析过程中的状态。 % \begin{macrocode} \int_new:N \l_@@_parse_status_int \bool_new:N \l_@@_esc_status_bool \bool_new:N \l_@@_quote_status_bool % \end{macrocode} % \end{variable} % % % \subsubsection{局部变量} % % \begin{variable} {\l_@@_row_input_seq, \l_@@_cell_input_seq} % 用于保存原始的输入数据。 % \begin{macrocode} \seq_new:N \l_@@_row_input_seq \seq_new:N \l_@@_cell_input_seq % \end{macrocode} % \end{variable} % % % \subsubsection{局部选项} % % % \begin{variable}{\l_@@_input_format_tl, \l_@@_input_sep_tl, % \l_@@_input_row_header_bool, \l_@@_input_col_header_bool} % 输入选项变量。 % \begin{macrocode} \tl_new:N \l_@@_input_format_tl \tl_new:N \l_@@_input_sep_tl \bool_new:N \l_@@_input_row_header_bool \bool_new:N \l_@@_input_col_header_bool % \end{macrocode} % \end{variable} % % % % \begin{macro}{xtable/input} % 输入选项。 % \begin{macrocode} \keys_define:nn { xtable / input } { format .choices:nn = { inner, csv, json } { \tl_set_eq:NN \l_@@_input_format_tl \l_keys_choice_tl }, format .initial:n = {inner}, csv .code:n = { \tl_set:Nn \l_@@_input_format_tl { csv } }, json .code:n = { \tl_set:Nn \l_@@_input_format_tl { json } }, title .bool_set:N = \l_@@_input_col_header_bool, header .bool_set:N = \l_@@_input_col_header_bool, header .initial:n = {true}, rowheader .bool_set:N = \l_@@_input_row_header_bool, rowheader .initial:n = {false}, sep .tl_set:N = \l_@@_input_sep_tl, sep .initial:n = {,}, loc .code:n = { \@@_parse_coord:n {#1} }, loc .initial:n = {1,1} } % \end{macrocode} % \end{macro} % % % \subsection{数据解析} % % \subsubsection{标准格式} % % % \begin{macro}{\@@_parse_input:n} % 解析标准输入的表格数据(|#1|),并将其添加到指定位置。 % 起始行由 \cs{l_@@_row_loc_int} 指定;起始列由 \cs{l_@@_col_loc_int} 指定。 % \begin{macrocode} \cs_new:Nn \@@_parse_input:n { % \end{macrocode} % 准备阶段。 % \begin{macrocode} \seq_set_split:Nnn \l_@@_row_input_seq {\\} {#1} \cs_set:Nn \@@_tmpa_cs: % 更新单元格序列 { \tl_replace_all:Nnn \l_@@_tmpa_tl {\newline} {\\} \seq_set_split:NVV \l_@@_cell_input_seq \l_@@_input_sep_tl \l_@@_tmpa_tl } \cs_set:Nn \@@_tmpb_cs: % 填充一行数据 { \int_set_eq:NN \l_@@_tmpb_int \l_@@_col_loc_int \seq_map_inline:Nn \l_@@_cell_input_seq { \@@_set_cell:NNn \l_@@_tmpa_int \l_@@_tmpb_int {####1} \int_incr:N \l_@@_tmpb_int } } % \end{macrocode} % 处理标题行。 % \begin{macrocode} \bool_if:NT \l_@@_input_col_header_bool { \int_zero:N \l_@@_tmpa_int \seq_pop_left:NN \l_@@_row_input_seq \l_@@_tmpa_tl \@@_tmpa_cs: \bool_if:NT \l_@@_input_row_header_bool { \seq_pop_left:NNT \l_@@_cell_input_seq \l_@@_tmpb_tl { \@@_set_cell:nV {0, 0} \l_@@_tmpb_tl } } \@@_tmpb_cs: \int_set_eq:NN \l_@@_tmpb_int \l_@@_col_loc_int \seq_map_inline:Nn \l_@@_cell_input_seq { \@@_set_col_name:nN {##1} \l_@@_tmpb_int \int_incr:N \l_@@_tmpb_int } } % \end{macrocode} % 处理正常数据行。 % \begin{macrocode} \int_set_eq:NN \l_@@_tmpa_int \l_@@_row_loc_int \seq_map_inline:Nn \l_@@_row_input_seq { \tl_set:Nn \l_@@_tmpa_tl {##1} \@@_tmpa_cs: \bool_if:NT \l_@@_input_row_header_bool { \seq_pop_left:NNT \l_@@_cell_input_seq \l_@@_tmpb_tl { \@@_set_cell:eV {\l_@@_tmpa_int, 0} \l_@@_tmpb_tl } } \@@_tmpb_cs: \int_incr:N \l_@@_tmpa_int } } % \end{macrocode} % \end{macro} % % % \subsubsection{CSV 数据} % % % 解析 CSV 数据使用的思路是,先用正则表达式解析出行,再用正则表达式解析每个字段。 % % \done\todo[Debug] { 修复 CSV 正则表达式与代码不匹配的Bug } % % \begin{macro}{\@@_parse_csv:n} % 解析 CSV 数据(|#1|),并将其添加到指定位置。 % 起始行由 \cs{l_@@_row_loc_int} 指定;起始列由 \cs{l_@@_col_loc_int} 指定。 % \begin{macrocode} \cs_new:Nn \@@_parse_csv:n { \seq_clear:N \l_@@_row_input_seq \regex_extract_all:NnN \c_@@_csv_row_regex {#1\\} \l_@@_row_input_seq \int_set_eq:NN \l_@@_tmpa_int \l_@@_row_loc_int \seq_map_inline:Nn \l_@@_row_input_seq { \int_set_eq:NN \l_@@_tmpb_int \l_@@_col_loc_int \seq_clear:N \l_@@_tmpa_seq \seq_clear:N \l_@@_cell_input_seq \regex_extract_all:NnN \c_@@_csv_cell_regex {##1} \l_@@_tmpa_seq \int_step_inline:nnnn {2} {3} {\seq_count:N \l_@@_tmpa_seq} { \tl_set:Ne \l_@@_tmpa_tl { \seq_item:Nn \l_@@_tmpa_seq {####1} } \tl_set:Ne \l_@@_tmpb_tl { \seq_item:Nn \l_@@_tmpa_seq {####1+1} } \tl_put_right:NV \l_@@_tmpa_tl \l_@@_tmpb_tl \seq_put_right:NV \l_@@_cell_input_seq \l_@@_tmpa_tl } \bool_if:NTF \l_@@_input_col_header_bool { % 填充表头(设置列名) \seq_map_inline:Nn \l_@@_cell_input_seq { \@@_set_col_name:nN {####1} \l_@@_tmpb_int \int_incr:N \l_@@_tmpb_int } \bool_set_false:N \l_@@_input_col_header_bool } { % 填充数据 \seq_map_inline:Nn \l_@@_cell_input_seq { \@@_set_cell:NNn \l_@@_tmpa_int \l_@@_tmpb_int {####1} \int_incr:N \l_@@_tmpb_int } \int_incr:N \l_@@_tmpa_int } } } % \end{macrocode} % \end{macro} % % % % \subsubsection{JSON 数据} % % 本宏包解析 JSON 数据的思路:先将行依次解析到一个中转序列(逐字符处理) % 再依次解析每一行数据(正则表达式)。 % % 逐字符解析完整 JSON ,并将解析状态分为:初始模式、行间模式、行内模式。 % 整个解析过程都在这三个状态中切换。 % % \begin{macro}{\@@_parse_json_auxa:n} % 拆分行时用到的辅助函数,处理初始模式。 % 当前字符(|#1|)为 |[|,则表示数据开始,切换到行间模式; % 当前字符为 |{|,则表示一行数据开始,直接切换到行内模式。 % \begin{macrocode} \cs_new:Nn \@@_parse_json_auxa:n { \str_case_e:nnF {#1} { {\c_@@_space_str} {} % 空白,跳过 {[} % 数组开始 { \int_set:Nn \l_@@_parse_status_int {1} } {\c_@@_lbrace_str} % 行开始 { \int_set:Nn \l_@@_parse_status_int {2} \str_clear:N \l_@@_tmpa_str } } { \msg_error:nnn {xtable} {unknown_input} {#1} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_parse_json_auxb:n} % 拆分行时用到的辅助函数,处理行间模式。 % 当前字符(|#1|)为 |]|,则表示数据结束,切换回初始模式; % 当前字符为 |{|,则表示一行数据开始,切换到行内模式。 % 忽略逗号与空白。 % \begin{macrocode} \cs_new:Nn \@@_parse_json_auxb:n { \str_case_e:nnF {#1} { {\c_@@_space_str} {} % 空白,跳过 {,} {} % 行分隔,跳过 {]} % 数组结束 { \int_set:Nn \l_@@_parse_status_int {0} } {\c_@@_lbrace_str} % 行开始 { \int_set:Nn \l_@@_parse_status_int {2} \str_clear:N \l_@@_tmpa_str } } { \msg_error:nnn {xtable} {unknown_input} {#1} } } % \end{macrocode} % \end{macro} % \begin{macro}{\@@_parse_json_auxc:n} % 拆分行时用到的辅助函数,处理行内模式。|#1| 为当前字符。 % 行内模式又有两个子状态:转义态与引用态。 % 如果当前状态为转义态,则恢复为非转义态;否则,可以用 |\| 切换为转义态。 % 如果当前状态为引用态(且非转义态),则可用 |"| 切换为非引用态; % 否则,可以使用 |"| 切换为引用态,或使用 |}| 结束当前行,并进入行间模式。 % \begin{macrocode} \cs_new:Nn \@@_parse_json_auxc:n { \bool_if:NTF \l_@@_esc_status_bool % 转义状态 { \bool_set_false:N \l_@@_esc_status_bool } { \str_if_eq:VnTF \c_@@_escape_str {#1} % 即将转义 { \bool_set_true:N \l_@@_esc_status_bool } { \bool_if:NTF \l_@@_quote_status_bool % 引用状态 { \str_if_eq:nnT {"} {#1} % 结束引用 { \bool_set_false:N \l_@@_quote_status_bool } } { \str_if_eq:nnTF {"} {#1} % 即将引用 { \bool_set_true:N \l_@@_quote_status_bool } { \str_if_eq:VnT \c_@@_rbrace_str {#1} % 行结束 { \int_set:Nn \l_@@_parse_status_int {1} \seq_put_right:NV \l_@@_row_input_seq \l_@@_tmpa_str } } } } } \str_put_right:Nn \l_@@_tmpa_str {#1} } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_parse_json_row:n} % 解析 JSON 中的单行数据(|#1|),并将其添加表中。 % 使用 \cs{l_@@_row_loc_int} 与 \cs{l_@@_col_loc_int} 来传递下一位置的坐标。 % 使用缓存变量 \cs{l_@@_cachea_tl} 与 \cs{l_@@_cacheb_tl}。 % \begin{macrocode} \cs_new:Nn \@@_parse_json_row:n { \regex_extract_all:NnN \c_@@_json_cell_regex {#1} \l_@@_cell_input_seq \bool_until_do:nn { \seq_if_empty_p:N \l_@@_cell_input_seq } { \seq_pop_left:NN \l_@@_cell_input_seq \l_@@_tmpa_tl % 丢弃 \seq_pop_left:NN \l_@@_cell_input_seq \l_@@_cachea_tl \seq_pop_left:NN \l_@@_cell_input_seq \l_@@_tmpa_tl \seq_pop_left:NN \l_@@_cell_input_seq \l_@@_tmpb_tl \tl_if_empty:NTF \l_@@_tmpa_tl { \tl_set_eq:NN \l_@@_cacheb_tl \l_@@_tmpb_tl } { \tl_set_eq:NN \l_@@_cacheb_tl \l_@@_tmpa_tl } \int_set_eq:NN \l_@@_cachea_int \l_@@_col_loc_int \@@_parse_new_col_loc:V \l_@@_cachea_tl \int_set_eq:NN \l_@@_tmpa_int \l_@@_col_loc_int \int_compare:nNnTF {\l_@@_cachea_int} = {\l_@@_col_loc_int} { \int_incr:N \l_@@_col_loc_int } { \int_set_eq:NN \l_@@_col_loc_int \l_@@_cachea_int } \@@_set_cell:NNV \l_@@_row_loc_int \l_@@_tmpa_int \l_@@_cacheb_tl } \int_incr:N \l_@@_row_loc_int } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_parse_json:n} % 解析 JSON 数据(|#1|),并将其添加到指定位置。 % 起始行由 \cs{l_@@_row_loc_int} 指定;列位置则由列名指定。 % 如果列名不存在,先依次将列名映射到指定位置(\cs{l_@@_col_loc_int} )及其后。 % \begin{macrocode} \cs_new:Nn \@@_parse_json:n { \int_zero:N \l_@@_parse_status_int \bool_set_false:N \l_@@_esc_status_bool \bool_set_false:N \l_@@_quote_status_bool \str_clear:N \l_@@_tmpa_str \seq_clear:N \l_@@_row_input_seq \str_map_inline:nn {#1} % 解析 JSON 中的行 { \int_case:nn { \l_@@_parse_status_int } { {0} { \@@_parse_json_auxa:n {##1} } % 开始 {1} { \@@_parse_json_auxb:n {##1} } % 行间状态 {2} { \@@_parse_json_auxc:n {##1} } % 行内状态 } } \seq_map_function:NN \l_@@_row_input_seq \@@_parse_json_row:n } % \end{macrocode} % \end{macro} % % \subsection{数据录入} % % % \begin{macro}{\loadtable, \savetable} % 保存与加载表格。 % \begin{macrocode} \NewDocumentCommand {\loadtable} { m } { \@@_check_input_env:n {\loadtable} \@@_table_restore:n {#1} } \NewDocumentCommand {\savetable} { m } { \@@_check_input_env:n {\savetable} \@@_table_save:n {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{\excelcolname} % 设置 Excel 风格列名。 % \begin{macrocode} \NewDocumentCommand {\excelcolname} { O{26} } { \@@_check_input_env:n {\excelcolname} \@@_set_excel_col_names:n {#1} } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\@@_line_name:Nnnn, \rowname, \colname} % 设置行名与列名。 % \begin{macrocode} \cs_new:Nn \@@_line_name:Nnnn { \int_set:Nn \l_@@_tmpa_int {#2} \seq_set_split:Nnn \l_@@_cell_input_seq {#3} {#4} \seq_map_inline:Nn \l_@@_cell_input_seq { #1 {##1} \l_@@_tmpa_int \int_incr:N \l_@@_tmpa_int } } % \end{macrocode} % |#1| 位置,|#2| 分隔符,|#3| 名称列表。 % \begin{macrocode} \NewDocumentCommand {\rowname} { D(){1} O{,} m } { \@@_check_input_env:n {\rowname} \@@_line_name:Nnnn \@@_set_row_name:nN {#1} {#2} {#3} } \NewDocumentCommand {\colname} { D(){1} O{,} m } { \@@_check_input_env:n {\colname} \@@_line_name:Nnnn \@@_set_col_name:nN {#1} {#2} {#3} } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_line:nnnn, \@@_row:nnn, \@@_col:nnn} % 设置某一行/列数据。使用变量 \cs{l_@@_row_loc_int} 与 \cs{l_@@_col_loc_int}。 % \begin{macrocode} \cs_new:Nn \@@_line:nnnn { % \end{macrocode} % 解析位置。 % \begin{macrocode} \int_set:Nn \l_@@_row_loc_int {1} \int_set:Nn \l_@@_col_loc_int {1} \str_if_empty:nTF {#1} { \int_set_eq:Nc \l_@@_tmpa_int { g_@@_#4_count_int } \int_add:cn { l_@@_#4_loc_int } { \l_@@_tmpa_int } } { \str_if_in:nnTF {#1} {,} { \@@_parse_new_coord:n {#1} } { \use:c { @@_parse_new_#4_loc:n } {#1} } } % \end{macrocode} % 遍历添加数据。 % \begin{macrocode} \str_if_eq:nnTF {#4} {row} { \cs_set_eq:NN \@@_tmpa_cs: \l_@@_col_loc_int } { \cs_set_eq:NN \@@_tmpa_cs: \l_@@_row_loc_int } \seq_set_split:Nnn \l_@@_cell_input_seq {#2} {#3} \seq_map_inline:Nn \l_@@_cell_input_seq { \@@_set_cell:n {##1} \int_incr:N \@@_tmpa_cs: } } % \end{macrocode} % 定义内部名称。|#1| 位置,|#2| 分隔符,|#3| 名称列表。 % \begin{macrocode} \NewDocumentCommand {\@@_row:nnn} { D(){} O{,} m } { \@@_line:nnnn {#1} {#2} {#3} {row} } \NewDocumentCommand {\@@_col:nnn} { D(){} O{,} m } { \@@_line:nnnn {#1} {#2} {#3} {col} } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_cell:nn} % 用于输入单元格的数据。使用变量 \cs{l_@@_row_loc_int} 与 \cs{l_@@_col_loc_int}。 % \begin{macrocode} \NewDocumentCommand {\@@_cell:nn} { r() m } { \int_zero:N \l_@@_row_loc_int \int_zero:N \l_@@_col_loc_int \@@_parse_new_coord:n {#1} \@@_set_cell:n {#2} } % \end{macrocode} % \end{macro} % % % \begin{environment}{@@_data:} % 用于输入表格内容的环境。 % \begin{macrocode} \NewDocumentEnvironment{@@_data:}{O{} +b} { \keys_set:nn { xtable/input } {#1} \str_case:VnF \l_@@_input_format_tl { {inner} { \@@_parse_input:n {#2} } {csv} { \@@_parse_csv:n {#2} } {json} { \@@_parse_json:n {#2} } } { \msg_error:nnV {xtable} {unknown_format} \l_@@_input_format_tl } } {} % \end{macrocode} % \end{environment} % % \begin{macro}{., ..,\row, \col, \cell} % \begin{environment}{xtable, data} % 用于输入表格的环境。 % \begin{macrocode} \NewDocumentEnvironment{xtable}{ O{} } { \@@_table_init: \keys_set:nn { xtable/input } {#1} \bool_gset_eq:NN \g_@@_row_header_bool \l_@@_input_row_header_bool \bool_gset_eq:NN \g_@@_col_header_bool \l_@@_input_col_header_bool \cs_set_eq:NN \data \@@_data: \cs_set_eq:NN \enddata \end_@@_data: \cs_set_eq:NN \row \@@_row:nnn \cs_set_eq:NN \col \@@_col:nnn \cs_set_eq:NN \cell \@@_cell:nn \bool_set_true:N \l_@@_input_bool } { \bool_set_false:N \l_@@_input_bool \@@_insure_style: \@@_header_fill: } % \end{macrocode} % \end{environment} % \end{macro} % % \subsection{格式设置} % % % \begin{macro}{\@@_line_align:nnnn, \rowalign, \colalign} % 设置行与列的对齐方式。|#1| 默认值,|#2| 格式列表,|#3| 正则表达式,|#4| 行/列。 % \begin{macrocode} \cs_new:Nn \@@_line_align:nnnn { \regex_extract_all:nnNF {#3} {#1#2} \l_@@_tmpa_seq { \msg_error:nnn {xtable} {unknown_format} {[#1],{#2}} } \str_if_in:nnTF {#2} {=} { \seq_set_split:Nnn \l_@@_cell_input_seq {,} {#2} } { \tl_set:Nn \l_@@_tmpa_tl {#2} \tl_replace_all:Nnn \l_@@_tmpa_tl {,} {} \seq_set_split:NnV \l_@@_cell_input_seq {} \l_@@_tmpa_tl } \use:c { @@_set_#4_align:Nn } \l_@@_cell_input_seq {#1} } % \end{macrocode} % \begin{macrocode} \NewDocumentCommand {\rowalign} { O{b} m } { \@@_check_input_env:n {\rowalign} \@@_line_align:nnnn {#1} {#2} {^(?:(?:.*=)?[tmb],?)*$} {row} } \NewDocumentCommand {\colalign} { O{c} m } { \@@_check_input_env:n {\colalign} \@@_line_align:nnnn {#1} {#2} {^(?:(?:.*=)?[lcr],?)*$} {col} } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_line_size:nnnn, \rowheight, \colwidth} % 设置行高与列宽。|#1| 默认值,|#2| 总尺寸,|#3| 格式列表,|#4| 行高/列宽。 % \begin{macrocode} \cs_new:Nn \@@_line_size:nnnn { \seq_clear:N \l_@@_cell_input_seq \seq_set_split:Nnn \l_@@_cell_input_seq {,} {#3} \cs_set_eq:Nc \@@_tmpa_cs: { c_@@_#4_regex } \regex_extract_all:NnNF \@@_tmpa_cs: {#1} \l_@@_tmpa_seq { \msg_error:nnn {xtable} {unknown_format} {#1} } \seq_map_inline:Nn \l_@@_cell_input_seq { \regex_extract_all:NnNF \@@_tmpa_cs: {##1} \l_@@_tmpa_seq { \msg_error:nnn {xtable} {unknown_format} {##1} } } \use:c { @@_set_#4_style:Nnn } \l_@@_cell_input_seq {#1} {#2} } % \end{macrocode} % \begin{macrocode} \NewDocumentCommand {\rowheight} { O{auto} O{\textheight} m } { \@@_check_input_env:n {\rowheight} \@@_line_size:nnnn {#1} {#2} {#3} { row_ht } } \NewDocumentCommand {\colwidth} { O{auto} O{\textwidth} m } { \@@_check_input_env:n {\colwidth} \@@_line_size:nnnn {#1} {#2} {#3} { col_wd } } % \end{macrocode} % \end{macro} % % % \subsection{合并单元格} % % \begin{macro}{\mergecell} % 合并单元格。 % \begin{macrocode} \NewDocumentCommand {\mergecell} { O{ul} r() r() O{mc} } { \@@_check_input_env:n {\mergecell} \cs_new:Npn \@@_tmapa_cs: ##1##2 { \int_set_eq:NN \l_@@_tmpa_int ##1 \int_set_eq:NN ##1 ##2 \int_set_eq:NN ##2 \l_@@_tmpa_int } % \end{macrocode} % \begin{macrocode} \@@_check_input_env:n {\mergecell} \@@_parse_coord:n {#3} \int_set_eq:NN \l_@@_cachea_int \l_@@_row_loc_int \int_set_eq:NN \l_@@_cacheb_int \l_@@_col_loc_int \@@_parse_coord:n {#2} \int_compare:nNnT {\l_@@_row_loc_int} > {\l_@@_cachea_int} { \@@_tmapa_cs: \l_@@_row_loc_int \l_@@_cachea_int } \int_compare:nNnT {\l_@@_col_loc_int} > {\l_@@_cacheb_int} { \@@_tmapa_cs: \l_@@_col_loc_int \l_@@_cacheb_int } \@@_set_merge_cell:VVVVn \l_@@_row_loc_int \l_@@_col_loc_int \l_@@_cachea_int \l_@@_cacheb_int {#1} \@@_set_merge_align:n {#4} } % \end{macrocode} % \end{macro} % % % \section{中间计算} % % 本节内容为渲染输出准备,它计算一些表格相关的数据 。 % % % \subsection{中间变量} % % % \begin{variable}{\g_@@_row_sep_seq, \g_@@_col_sep_seq} % 用于存储间隙信息(包含 |Header|),在渲染前需要首先刷新此变量的值。 % \begin{macrocode} \seq_new:N \g_@@_row_sep_seq \seq_new:N \g_@@_col_sep_seq % \end{macrocode} % \end{variable} % % % \begin{variable}{\g_@@_row_ht_seq, \g_@@_row_dp_seq, \g_@@_col_wd_seq} % 用于行/列的存储高、深及宽信息(包含|Header|),在渲染前需要刷新此变量的值。 % 如果列宽有使用填充样式,则需要刷新列间隙后再刷新列宽。 % \begin{macrocode} \seq_new:N \g_@@_row_ht_seq \seq_new:N \g_@@_row_dp_seq \seq_new:N \g_@@_col_wd_seq % \end{macrocode} % \end{variable} % % % \begin{variable}{\g_@@_cell_ht_prop, \g_@@_cell_ufill_prop, \g_@@_cell_dfill_prop} % 单元格填充值(补偿首行字高及未行字深)及填充后高度 % \footnote{此高度可能比行高要小,因为行高是该行中所有单元格高度中最大的值。},在渲染前需要刷新此变量的值。 % \begin{macrocode} \prop_new:N \g_@@_cell_ht_prop \prop_new:N \g_@@_cell_ufill_prop \prop_new:N \g_@@_cell_dfill_prop % \end{macrocode} % \end{variable} % % % \begin{variable}{\g_@@_merge_wd_seq, \g_@@_merge_ht_seq, \g_@@_merge_dp_seq} % 合并单元格的净宽、净高、净深及上下填充值,在渲染前需要刷新此变量的值。 % \begin{macrocode} \seq_new:N \g_@@_merge_wd_seq \seq_new:N \g_@@_merge_ht_seq \seq_new:N \g_@@_merge_dp_seq \seq_new:N \g_@@_merge_ufill_seq \seq_new:N \g_@@_merge_dfill_seq % \end{macrocode} % \end{variable} % % % % % \begin{variable}{\g_@@_merge_range_wd_seq, \g_@@_merge_range_ht_seq, \g_@@_merge_range_dp_seq} % 合并单元格的排版尺寸,在渲染前需要刷新此变量的值。 % \begin{macrocode} \seq_new:N \g_@@_merge_range_wd_seq \seq_new:N \g_@@_merge_range_ht_seq \seq_new:N \g_@@_merge_range_dp_seq % \end{macrocode} % \end{variable} % % % \begin{variable}{\g_@@_row_loc_seq, \g_@@_col_loc_seq} % 用于存储表格的行列坐标(图形坐标)信息(包含 |Header|)。 % 需要在渲染条件确认后再刷新。 % \begin{macrocode} \seq_new:N \g_@@_row_loc_seq \seq_new:N \g_@@_col_loc_seq % \end{macrocode} % \end{variable} % % % \subsection{行列尺寸} % % % \subsubsection{边距设置} % % % \begin{macro}{\@@_set_row_sep:nn, \@@_set_col_sep:nn} % 设置列边距(内容到内容的距离)。|#1|:最左边与最右边的边距,|#2|:中间的边距。 % \begin{macrocode} \cs_new:Nn \@@_set_row_sep:nn { \seq_gclear:N \g_@@_row_sep_seq \prg_replicate:nn {\g_@@_row_count_int} { \seq_gput_right:Nn \g_@@_row_sep_seq {#2} } \seq_gput_left:Nn \g_@@_row_sep_seq {#1} \seq_gput_right:Nn \g_@@_row_sep_seq {#1} \bool_if:NF \g_@@_col_header_bool { \seq_gset_item:Nnn \g_@@_row_sep_seq {1} { 0pt } \seq_gset_item:Nnn \g_@@_row_sep_seq {2} { #1 } } } \cs_new:Nn \@@_set_col_sep:nn { \seq_gclear:N \g_@@_col_sep_seq \prg_replicate:nn {\g_@@_col_count_int} { \seq_gput_right:Nn \g_@@_col_sep_seq {#2} } \seq_gput_left:Nn \g_@@_col_sep_seq {#1} \seq_gput_right:Nn \g_@@_col_sep_seq {#1} \bool_if:NF \g_@@_row_header_bool { \seq_gset_item:Nnn \g_@@_col_sep_seq {1} { 0pt } \seq_gset_item:Nnn \g_@@_col_sep_seq {2} { #1 } } } \cs_generate_variant:Nn \@@_set_row_sep:nn { ne, en, ee } \cs_generate_variant:Nn \@@_set_col_sep:nn { ne, en, ee } % \end{macrocode} % \end{macro} % % % \subsubsection{宽度计算} % % % \begin{macro}{\@@_measure_cell_wd:n} % 测量单元格内容(|#1|)的自然宽度,结果保存在 \cs{l_@@_wd_dim} 中。 % 顺便会更新首行的高度补偿\footnote{高度与 \cs{c_@@_std_ht_dim} 的差距值(为正),如果高度更大,则为零。} % 与未行的深度补偿\footnote{深度与 \cs{c_@@_std_dp_dim} 的差距值(为正),如果深度度更大,则为零。}, % 结果保存在 \cs{l_@@_ufill_dim} \cs{l_@@_dfill_dim} 中。 % \begin{macrocode} \cs_new:Nn \@@_measure_cell_wd:n { \dim_zero:N \l_@@_ufill_dim \dim_zero:N \l_@@_dfill_dim \dim_set_eq:NN \l_@@_wd_dim \g_@@_cell_wd_min_dim \tl_if_empty:nF {#1} { \bool_set_true:N \l_@@_tmpa_bool % top line mark \seq_set_split:Nnn \l_@@_tmpa_seq {\\} {#1} \seq_map_inline:Nn \l_@@_tmpa_seq % each line { \hbox_set:Nn \l_@@_tmpa_box {##1} \@@_dim_set_max:Nn \l_@@_wd_dim { \box_wd:N \l_@@_tmpa_box } \bool_if:NT \l_@@_tmpa_bool { \@@_dim_set_max:Nn \l_@@_ufill_dim { \c_@@_std_ht_dim - \box_ht:N \l_@@_tmpa_box } \bool_set_false:N \l_@@_tmpa_bool } \dim_zero:N \l_@@_dfill_dim % clear above line \@@_dim_set_max:Nn \l_@@_dfill_dim { \c_@@_std_dp_dim - \box_dp:N \l_@@_tmpa_box } } } } \cs_generate_variant:Nn \@@_measure_cell_wd:n { V, e } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_measure_cell_box_wd:n} % 测量单元格盒子(|#1|)的宽度,结果保存在 \cs{l_@@_wd_dim} 中。 % 同样会会更新首行的高度补偿与未行的深度补偿, % 结果保存在 \cs{l_@@_ufill_dim} \cs{l_@@_dfill_dim} 中。 % \begin{macrocode} \cs_new:Nn \@@_measure_cell_box_wd:n { \dim_zero:N \l_@@_ufill_dim \dim_zero:N \l_@@_dfill_dim \dim_set_eq:NN \l_@@_wd_dim \g_@@_cell_wd_min_dim \tl_if_empty:nF {#1} { \hbox_set:Nn \l_@@_tmpa_box {#1} \@@_dim_set_max:Nn \l_@@_wd_dim { \box_wd:N \l_@@_tmpa_box } \@@_dim_set_max:Nn \l_@@_ufill_dim { \c_@@_std_ht_dim - \box_ht:N \l_@@_tmpa_box } \@@_dim_set_max:Nn \l_@@_dfill_dim { \c_@@_std_dp_dim - \box_dp:N \l_@@_tmpa_box } } } \cs_generate_variant:Nn \@@_measure_cell_box_wd:n { V, e } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_measure_col_wd:n} % 测量表格的列(|#1|)的宽度,结果保存在 \cs{l_@@_wd_dim} 中; % 同时会记录填充值(使用变量 \cs{l_@@_ufill_dim} 和 \cs{l_@@_dfill_dim} 中转)。 % 本函数会使用 \cs{l_@@_data_tl} 变量。 % \footnote{本函数需要确认 \cs{l_@@_tmpa_dim} 不会被调用的函数修改。} % \begin{macrocode} \cs_new:Nn \@@_measure_col_wd:n { \dim_zero:N \l_@@_tmpa_dim % max width \bool_if:NTF \g_@@_col_header_bool { \int_set:Nn \l_@@_tmpa_int {0} } { \int_set:Nn \l_@@_tmpa_int {1} } \int_step_inline:nnn {\l_@@_tmpa_int} {\g_@@_row_count_int} { \@@_merge_if:nF {##1, #1} { \@@_parse_cell:n {##1, #1} \bool_if:NTF \l_@@_is_box_bool { \@@_measure_cell_box_wd:V \l_@@_data_tl } { \@@_measure_cell_wd:V \l_@@_data_tl } \@@_dim_set_max:NN \l_@@_tmpa_dim \l_@@_wd_dim \prop_gput:Nne \g_@@_cell_ufill_prop {##1,#1} {\dim_use:N \l_@@_ufill_dim} \prop_gput:Nne \g_@@_cell_dfill_prop {##1,#1} {\dim_use:N \l_@@_dfill_dim} } } \dim_set_eq:NN \l_@@_wd_dim \l_@@_tmpa_dim } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_calc_col_wd:} % 计算表格各列的宽度,如果有需要调整的列宽,必须刷新出列边距,否则计算会失真。 % 本命令会大量修改通用变量的值,调用时需要注意。 % \done\todo[Debug] { 修复宽度样式为填充时长宽计算异常的 Bug } % \begin{macrocode} \cs_new:Nn \@@_calc_col_wd: { % \end{macrocode} % 初始化。 % \begin{macrocode} \dim_zero:N \l_@@_cachea_dim % 相同宽度的列的最大列宽 \dim_zero:N \l_@@_cacheb_dim % 相同宽度且填充的列的最大列宽 \seq_clear:N \l_@@_cachea_seq % 相同宽度的列的索引序列 \seq_clear:N \l_@@_cacheb_seq % 相同宽度且填充的列列的索引序列 \seq_gclear:N \g_@@_col_wd_seq % 列宽序列 \prop_gclear:N \g_@@_cell_ufill_prop \prop_gclear:N \g_@@_cell_dfill_prop % \end{macrocode} % 自然宽度循环,并统计相同宽度的列的索引及最大宽度。 % \begin{macrocode} \@@_disable_sys_func: \bool_if:NTF \g_@@_row_header_bool { \int_step_inline:nnn { 0 } { \g_@@_col_count_int } } { \seq_gput_right:Nn \g_@@_col_wd_seq { 0pt } \int_step_inline:nnn { 1 } { \g_@@_col_count_int } } { % \int_step_inline:nnn 的参数三 \@@_measure_col_wd:n {##1} \@@_parse_col_wd_style:n {##1} \clist_if_in:nVTF {a,s,f,sf} \l_@@_style_tl { \seq_gput_right:Ne \g_@@_col_wd_seq { \dim_use:N \l_@@_wd_dim } \tl_if_eq:VnT \l_@@_style_tl {s} { \seq_put_right:Nn \l_@@_cachea_seq {##1} \@@_dim_set_max:NN \l_@@_cachea_dim \l_@@_wd_dim } \tl_if_eq:VnT \l_@@_style_tl {sf} { \seq_put_right:Nn \l_@@_cacheb_seq {##1} \@@_dim_set_max:NN \l_@@_cacheb_dim \l_@@_wd_dim } } { \seq_gput_right:NV \g_@@_col_wd_seq \l_@@_style_tl } } \@@_restore_sys_func: % \end{macrocode} % 相同宽度循环:根据索引直接使用最大宽度替换。 % \begin{macrocode} \seq_map_inline:Nn \l_@@_cachea_seq { \seq_gset_item:Nne \g_@@_col_wd_seq {##1+1} { \dim_use:N \l_@@_cachea_dim } } \seq_map_inline:Nn \l_@@_cacheb_seq { \seq_gset_item:Nne \g_@@_col_wd_seq {##1+1} { \dim_use:N \l_@@_cacheb_dim } } % \end{macrocode} % 填充循环。可填充长度 |=| 总宽 |-| 单元格间距 |-| 单元格所需宽度。 % \begin{macrocode} \int_zero:N \l_@@_cachea_int % 填充列的个数 \seq_clear:N \l_@@_cachea_seq % 填充列的索引序列 \dim_set:Nn \l_@@_wd_dim { \seq_item:Nn \g_@@_col_wd_style_seq {-1} } \seq_map_inline:Nn \g_@@_col_sep_seq { \dim_sub:Nn \l_@@_wd_dim {##1} } \int_step_inline:nnn { 0 } { \g_@@_col_count_int } { \dim_sub:Nn \l_@@_wd_dim { \seq_item:Nn \g_@@_col_wd_seq {##1+1} } \@@_parse_col_wd_style:n {##1} \tl_if_in:NnT \l_@@_style_tl {f} { \int_incr:N \l_@@_cachea_int \seq_put_right:Nn \l_@@_cachea_seq {##1} } } \int_if_zero:nF { \l_@@_cachea_int } { \dim_set:Nn \l_@@_cachea_dim { \l_@@_wd_dim / \l_@@_cachea_int } } \seq_map_inline:Nn \l_@@_cachea_seq { \dim_sub:Nn \l_@@_wd_dim { \seq_item:Nn \g_@@_col_wd_seq {##1+1} } \dim_add:Nn \l_@@_wd_dim { \l_@@_cachea_dim } \seq_gset_item:Nne \g_@@_col_wd_seq {##1+1} { \dim_use:N \l_@@_wd_dim } } } % \end{macrocode} % \end{macro} % % % \subsubsection{高度计算} % % % % \begin{macro}{\@@_measure_cell_ht:nnn} % 测量单元格内容在限制宽度内的高度尺寸。|#1|: 垂直对齐方式,|#2|: 宽度,|#3| 内容; % 变量 \cs{l_@@_ufill_dim} 和 \cs{l_@@_dfill_dim} 用于传递需要垂直填充的值。 % 结果保存在 \cs{l_@@_ht_dim} 和 \cs{l_@@_dp_dim} 中。 % \begin{macrocode} \cs_new:Nn \@@_measure_cell_ht:nnn { \dim_set_eq:NN \l_@@_ht_dim \c_@@_std_ht_dim \dim_set_eq:NN \l_@@_dp_dim \c_@@_std_dp_dim \tl_if_empty:nF {#3} { \str_if_eq:nnTF {#1} {t} { \vbox_set_top:Nn } { \vbox_set:Nn } \l_@@_tmpa_box { \baselineskip=\g_@@_cell_lineskip_dim \hsize=#2 \parindent=0pt \noindent #3 } \@@_dim_set_max:Nn \l_@@_ht_dim { \box_ht:N \l_@@_tmpa_box + \l_@@_ufill_dim } \@@_dim_set_max:Nn \l_@@_dp_dim { \box_dp:N \l_@@_tmpa_box + \l_@@_dfill_dim } } } \cs_generate_variant:Nn \@@_measure_cell_ht:nnn { VeV } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_measure_row_ht:n} % 测量表格的行(|#1|)的高度,结果保存在 \cs{l_@@_ht_dim} 与 \cs{l_@@_dp_dim} 中, % 同时会缓存高度数据备用。 % 本函数会修改变量 \cs{l_@@_ufill_dim}、 \cs{l_@@_dfill_dim} 和 \cs{l_@@_row_align_tl} 的值。 % \footnote{本函数需要确认 \cs{l_@@_tmpa_dim} 与 \cs{l_@@_tmpb_dim} 不会被调用的函数修改。} % \begin{macrocode} \cs_new:Nn \@@_measure_row_ht:n { \dim_zero:N \l_@@_tmpa_dim \dim_zero:N \l_@@_tmpb_dim \@@_parse_row_align:n {#1} \bool_if:NTF \g_@@_row_header_bool { \int_set:Nn \l_@@_tmpa_int {0} } { \int_set:Nn \l_@@_tmpa_int {1} } \int_step_inline:nnn { \l_@@_tmpa_int } { \g_@@_col_count_int } { \@@_merge_if:nF {#1, ##1} { \@@_parse_cell:n {#1, ##1} \@@_parse_cell_fill:n {#1, ##1} \@@_measure_cell_ht:VeV \l_@@_row_align_tl { \seq_item:Nn \g_@@_col_wd_seq { ##1 + 1 } } \l_@@_data_tl \prop_gput:Nne \g_@@_cell_ht_prop {#1,##1} { \dim_use:N \l_@@_ht_dim } \@@_dim_set_max:NN \l_@@_tmpa_dim \l_@@_ht_dim \@@_dim_set_max:NN \l_@@_tmpb_dim \l_@@_dp_dim } } \dim_set_eq:NN \l_@@_ht_dim \l_@@_tmpa_dim \dim_set_eq:NN \l_@@_dp_dim \l_@@_tmpb_dim } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_calc_row_ht:} % 计算表格各行的高度。本命令会大量修改通用变量的值,调用时需要注意。 % \done\todo[Debug]{ 修复多行顶对齐时,相同行高居中对齐失效的 Bug } % \begin{macrocode} \cs_new:Nn \@@_calc_row_ht: { \dim_zero:N \l_@@_cachea_dim \dim_zero:N \l_@@_cacheb_dim \seq_clear:N \l_@@_cachea_seq \seq_gclear:N \g_@@_row_ht_seq \seq_gclear:N \g_@@_row_dp_seq \prop_gclear:N \g_@@_cell_ht_prop % \end{macrocode} % 自然高度循环,并统计相同高度的列的索引及最大高度。 % \begin{macrocode} \@@_disable_sys_func: \bool_if:NTF \g_@@_col_header_bool { \int_step_inline:nnn { 0 } { \g_@@_row_count_int } } { \seq_gput_right:Nn \g_@@_row_ht_seq { 0pt } \seq_gput_right:Nn \g_@@_row_dp_seq { 0pt } \int_step_inline:nnn { 1 } { \g_@@_row_count_int } } { % \int_step_inline:nnn 的参数三 \@@_measure_row_ht:n {##1} \@@_parse_row_ht_style:n {##1} \clist_if_in:nVTF {a,s} \l_@@_style_tl { \seq_gput_right:Ne \g_@@_row_ht_seq { \dim_use:N \l_@@_ht_dim } \seq_gput_right:Ne \g_@@_row_dp_seq { \dim_use:N \l_@@_dp_dim } \tl_if_eq:VnT \l_@@_style_tl {s} { \seq_put_right:Nn \l_@@_cachea_seq {##1} \@@_dim_set_max:NN \l_@@_cachea_dim \l_@@_ht_dim \@@_dim_set_max:NN \l_@@_cacheb_dim \l_@@_dp_dim } } { \seq_gput_right:Ne \g_@@_row_ht_seq { \dim_eval:n { \l_@@_style_tl - \l_@@_dp_dim } } \seq_gput_right:Ne \g_@@_row_dp_seq { \dim_use:N \l_@@_dp_dim } } } \@@_restore_sys_func: % \end{macrocode} % 相同高度循环:根据索引直接使用最大高度替换。 % \begin{macrocode} \seq_map_inline:Nn \l_@@_cachea_seq { \@@_parse_row_align:n {##1} \str_if_eq:VnTF \l_@@_row_align_tl {t} { \dim_set:Nn \l_@@_ht_dim { \seq_item:Nn \g_@@_row_ht_seq {##1+1} } \dim_set:Nn \l_@@_dp_dim { \l_@@_cachea_dim + \l_@@_cacheb_dim - \l_@@_ht_dim } \seq_gset_item:Nne \g_@@_row_dp_seq {##1+1} { \dim_use:N \l_@@_dp_dim } } { \dim_set:Nn \l_@@_dp_dim { \seq_item:Nn \g_@@_row_dp_seq {##1+1} } \dim_set:Nn \l_@@_ht_dim { \l_@@_cachea_dim + \l_@@_cacheb_dim - \l_@@_dp_dim } \seq_gset_item:Nne \g_@@_row_ht_seq {##1+1} { \dim_use:N \l_@@_ht_dim } } } } % \end{macrocode} % \end{macro} % % % \subsubsection{查询尺寸} % % \begin{macro}{\@@_parse_cell_size:n} % 查询单元格的排版尺寸。|#1| 为数字坐标。 % 结果保存在 \cs{l_@@_wd_dim}、\cs{l_@@_ht_dim} 及 \cs{l_@@_dp_dim} 中。 % \begin{macrocode} \cs_new:Nn \@@_parse_cell_size:n { \int_set:Nn \l_@@_tmpa_int { \clist_item:nn {#1} {1} + 1 } \int_set:Nn \l_@@_tmpb_int { \clist_item:nn {#1} {2} + 1 } \dim_set:Nn \l_@@_wd_dim { \seq_item:Nn \g_@@_col_wd_seq { \l_@@_tmpb_int } } \dim_set:Nn \l_@@_ht_dim { \seq_item:Nn \g_@@_row_ht_seq { \l_@@_tmpa_int } } \dim_set:Nn \l_@@_dp_dim { \seq_item:Nn \g_@@_row_dp_seq { \l_@@_tmpa_int } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_parse_cell_fill:n} % 查询单元格的首行高度与深度填充值。|#1| 为数字坐标。 % 结果保存在 \cs{l_@@_ufill_dim} 与 \cs{l_@@_dfill_dim} 中。 % \begin{macrocode} \cs_new:Nn \@@_parse_cell_fill:n { \prop_get:NnNF \g_@@_cell_ufill_prop {#1} \l_@@_tmpa_tl { \tl_set:Nn \l_@@_tmpa_tl {0pt} } \prop_get:NnNF \g_@@_cell_dfill_prop {#1} \l_@@_tmpb_tl { \tl_set:Nn \l_@@_tmpb_tl {0pt} } \dim_set:Nn \l_@@_ufill_dim {\l_@@_tmpa_tl} \dim_set:Nn \l_@@_dfill_dim {\l_@@_tmpb_tl} } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_parse_cell_ht:n} % 查询单元格的实际高度。|#1| 为数字坐标。 % 结果保存在 \cs{l_@@_ht_dim} 中。 % \begin{macrocode} \cs_new:Nn \@@_parse_cell_ht:n { \prop_get:NnN \g_@@_cell_ht_prop {#1} \l_@@_tmpa_tl \dim_set:Nn \l_@@_ht_dim {\l_@@_tmpa_tl} } % \end{macrocode} % \end{macro} % % % \subsection{合并单元格} % % \subsubsection{尺寸计算} % % \begin{macro}{\@@_measure_merge_size:} % 查询单元格的实际高度。 % 使用单元格格内容及高宽深及填充值等变量 。 % \begin{macrocode} \cs_new:Nn \@@_measure_merge_size: { \seq_gclear:N \g_@@_merge_wd_seq \seq_gclear:N \g_@@_merge_ht_seq \seq_gclear:N \g_@@_merge_dp_seq \seq_gclear:N \g_@@_merge_dfill_seq \seq_gclear:N \g_@@_merge_ufill_seq \int_step_inline:nn { \@@_parse_merge_count: } { \@@_parse_merge:n {##1} % 计算宽度 \bool_if:NTF \l_@@_is_box_bool { \@@_measure_cell_box_wd:V \l_@@_data_tl } { \@@_measure_cell_wd:V \l_@@_data_tl } \seq_gput_right:Ne \g_@@_merge_wd_seq { \dim_use:N \l_@@_wd_dim } \seq_gput_right:Ne \g_@@_merge_dfill_seq { \dim_use:N \l_@@_dfill_dim } \seq_gput_right:Ne \g_@@_merge_ufill_seq { \dim_use:N \l_@@_ufill_dim } % 计算高度 \@@_parse_merge_align:n {##1} \@@_measure_cell_ht:VeV \l_@@_row_align_tl { \dim_use:N \l_@@_wd_dim } \l_@@_data_tl \seq_gput_right:Ne \g_@@_merge_ht_seq { \dim_use:N \l_@@_ht_dim } \seq_gput_right:Ne \g_@@_merge_dp_seq { \dim_use:N \l_@@_dp_dim } } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_calc_merge_size:} % 计算单元格的排版尺寸。 % 使用单元格格内容及高宽深及填充值等变量 。 % \begin{macrocode} \cs_new:Nn \@@_calc_merge_size: { \seq_gclear:N \g_@@_merge_range_wd_seq \seq_gclear:N \g_@@_merge_range_ht_seq \seq_gclear:N \g_@@_merge_range_dp_seq \@@_disable_sys_func: \@@_measure_merge_size: \@@_restore_sys_func: \int_step_inline:nn { \@@_parse_merge_count: } { \@@_parse_merge:n {##1} \@@_parse_merge_align:n {##1} % \end{macrocode} % 计算宽度。 % \begin{macrocode} \int_zero:N \l_@@_tmpa_int \int_step_inline:nnn { \seq_item:Nn \l_@@_merge_seq {2} } { \seq_item:Nn \l_@@_merge_seq {4} } { \dim_add:Nn \l_@@_wd_dim { \seq_item:Nn \g_@@_col_sep_seq { ####1 + 1 } } \int_if_zero:nT { \l_@@_tmpa_int } { % 置后清零,以排除第一列左边的间隙 \dim_zero:N \l_@@_wd_dim \int_incr:N \l_@@_tmpa_int } \dim_add:Nn \l_@@_wd_dim { \seq_item:Nn \g_@@_col_wd_seq { ####1 + 1 } } } \seq_gput_right:Ne \g_@@_merge_range_wd_seq { \dim_use:N \l_@@_wd_dim } % \end{macrocode} % 计算高度与深度。 % \begin{macrocode} \int_zero:N \l_@@_tmpa_int \int_step_inline:nnn { \seq_item:Nn \l_@@_merge_seq {1} } { \seq_item:Nn \l_@@_merge_seq {3} } { \dim_add:Nn \l_@@_tmpa_dim { \seq_item:Nn \g_@@_row_sep_seq { ####1 + 1 } } \int_if_zero:nT { \l_@@_tmpa_int } { % 置后清零,以排除第一行上边的间隙 \dim_zero:N \l_@@_tmpa_dim \int_incr:N \l_@@_tmpa_int \dim_set:Nn \l_@@_ht_dim % 首行 { \seq_item:Nn \g_@@_row_ht_seq { ####1 + 1 } } } \dim_set:Nn \l_@@_dp_dim % 未列 { \seq_item:Nn \g_@@_row_dp_seq { ####1 + 1 } } \dim_add:Nn \l_@@_tmpa_dim { \seq_item:Nn \g_@@_row_ht_seq { ####1 + 1 } } \dim_add:Nn \l_@@_tmpa_dim { \seq_item:Nn \g_@@_row_dp_seq { ####1 + 1 } } } \str_if_eq:VnTF \l_@@_row_align_tl {t} { \seq_gput_right:Ne \g_@@_merge_range_ht_seq { \dim_use:N \l_@@_ht_dim } \seq_gput_right:Ne \g_@@_merge_range_dp_seq { \dim_eval:n { \l_@@_tmpa_dim - \l_@@_ht_dim } } } { \seq_gput_right:Ne \g_@@_merge_range_dp_seq { \dim_use:N \l_@@_dp_dim } \seq_gput_right:Ne \g_@@_merge_range_ht_seq { \dim_eval:n { \l_@@_tmpa_dim - \l_@@_dp_dim } } } } } % \end{macrocode} % \end{macro} % % % % \subsubsection{尺寸查询} % % % \begin{macro}{\@@_parse_merge_size:n} % 查询合并单元格的排版尺寸,|#1| 为索引。 % 结果保存在 \cs{l_@@_wd_dim}、 \cs{l_@@_ht_dim} 与 \cs{l_@@_dp_dim} 中。 % \begin{macrocode} \cs_new:Nn \@@_parse_merge_size:n { \dim_set:Nn \l_@@_wd_dim { \seq_item:Nn \g_@@_merge_range_wd_seq {#1} } \dim_set:Nn \l_@@_ht_dim { \seq_item:Nn \g_@@_merge_range_ht_seq {#1} } \dim_set:Nn \l_@@_dp_dim { \seq_item:Nn \g_@@_merge_range_dp_seq {#1} } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_parse_merge_fill:n} % 查询单元格的首行高度与深度填充值,|#1| 为索引。 % 结果保存在 \cs{l_@@_ufill_dim} 与 \cs{l_@@_dfill_dim} 中。 % \begin{macrocode} \cs_new:Nn \@@_parse_merge_fill:n { \dim_set:Nn \l_@@_dfill_dim { \seq_item:Nn \g_@@_merge_dfill_seq {#1} } \dim_set:Nn \l_@@_ufill_dim { \seq_item:Nn \g_@@_merge_ufill_seq {#1} } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_parse_merge_ht:n} % 查询单元格的首行高度与深度填充值,|#1| 为索引。 % 结果保存在 \cs{l_@@_ht_dim} 中。 % \begin{macrocode} \cs_new:Nn \@@_parse_merge_ht:n { \dim_set:Nn \l_@@_ht_dim { \seq_item:Nn \g_@@_merge_ht_seq {#1} } } % \end{macrocode} % \end{macro} % % % \subsection{计算坐标} % % \begin{macro}{\@@_calc_coord:} % 计算表格的(边框线所处位置的)实际输出坐标,其受有无 |Header| 的影响。 % 本命令使用 \cs{l_@@_wd_dim} 与 \cs{l_@@_ht_dim} 变量。 % \begin{macrocode} \cs_new:Nn \@@_calc_coord: { \dim_zero:N \l_@@_wd_dim \seq_gclear:N \g_@@_col_loc_seq \seq_gput_right:NV \g_@@_col_loc_seq \l_@@_wd_dim \bool_if:NTF \g_@@_row_header_bool { \int_set:Nn \l_@@_tmpa_int {0} } { \int_set:Nn \l_@@_tmpa_int {1} \seq_gput_right:NV \g_@@_col_loc_seq \l_@@_wd_dim } \dim_add:Nn \l_@@_wd_dim { ( \seq_item:Nn \g_@@_col_sep_seq { \l_@@_tmpa_int + 1 } ) / 2 } \int_step_inline:nnn { \l_@@_tmpa_int } { \g_@@_col_count_int } { \dim_add:Nn \l_@@_wd_dim { ( \seq_item:Nn \g_@@_col_sep_seq { ##1 + 1 } ) / 2 } \dim_add:Nn \l_@@_wd_dim { \seq_item:Nn \g_@@_col_wd_seq { ##1 + 1 } } \dim_add:Nn \l_@@_wd_dim { ( \seq_item:Nn \g_@@_col_sep_seq { ##1 + 2 } ) / 2 } \seq_gput_right:NV \g_@@_col_loc_seq \l_@@_wd_dim } \dim_add:Nn \l_@@_wd_dim { ( \seq_item:Nn \g_@@_col_sep_seq {-1} ) / 2 } \seq_gset_item:NnV \g_@@_col_loc_seq {-1} \l_@@_wd_dim % \end{macrocode} % 为方便计算,$Y$ 坐标采用负坐标。 % \begin{macrocode} \dim_zero:N \l_@@_ht_dim \seq_gclear:N \g_@@_row_loc_seq \seq_gput_right:NV \g_@@_row_loc_seq \l_@@_ht_dim \bool_if:NTF \g_@@_col_header_bool { \int_set:Nn \l_@@_tmpa_int {0} } { \int_set:Nn \l_@@_tmpa_int {1} \seq_gput_right:NV \g_@@_row_loc_seq \l_@@_ht_dim } \dim_sub:Nn \l_@@_ht_dim { ( \seq_item:Nn \g_@@_row_sep_seq { \l_@@_tmpa_int + 1 } ) / 2 } \int_step_inline:nnn { \l_@@_tmpa_int } { \g_@@_row_count_int } { \dim_sub:Nn \l_@@_ht_dim { ( \seq_item:Nn \g_@@_row_sep_seq { ##1+1 } ) / 2 } \dim_sub:Nn \l_@@_ht_dim { \seq_item:Nn \g_@@_row_ht_seq { ##1+1 } } \dim_sub:Nn \l_@@_ht_dim { \seq_item:Nn \g_@@_row_dp_seq { ##1+1 } } \dim_sub:Nn \l_@@_ht_dim { ( \seq_item:Nn \g_@@_row_sep_seq { ##1+2 } ) / 2 } \seq_gput_right:NV \g_@@_row_loc_seq \l_@@_ht_dim } \dim_sub:Nn \l_@@_ht_dim { (\seq_item:Nn \g_@@_row_sep_seq {-1})/2 } \seq_gset_item:NnV \g_@@_row_loc_seq {-1} \l_@@_ht_dim } % \end{macrocode} % \end{macro} % % % \section{渲染输出} % % % \subsection{通用内容} % % \subsubsection{局部变量} % % % % \begin{variable}{\l_@@_x_dim, \l_@@_y_dim} % 用于存储绘图坐标信息。 % \begin{macrocode} \dim_new:N \l_@@_x_dim \dim_new:N \l_@@_y_dim % \end{macrocode} % \end{variable} % % % \begin{variable}{\l_@@_offset_x_dim, \l_@@_offset_y_dim} % 两个偏移值变量。 % \begin{macrocode} \dim_new:N \l_@@_offset_x_dim \dim_new:N \l_@@_offset_y_dim % \end{macrocode} % \end{variable} % % % \begin{variable}{\l_@@_expand_start_dim, \l_@@_expand_end_dim} % 两个扩展值变量。 % \begin{macrocode} \dim_new:N \l_@@_expand_start_dim \dim_new:N \l_@@_expand_end_dim % \end{macrocode} % \end{variable} % % % % \begin{variable}{\g_@@_row_rule_seq, \g_@@_col_rule_seq} % 用于存储行列边框线。需要考虑是否有 |Header|。 % \begin{macrocode} \seq_new:N \g_@@_row_rule_seq \seq_new:N \g_@@_col_rule_seq % \end{macrocode} % \end{variable} % % % % \begin{variable}{\l_@@_style_seq} % 用于存储样式序列。 % \begin{macrocode} \seq_new:N \l_@@_style_seq % \end{macrocode} % \end{variable} % % % \begin{variable}{\l_@@_cell_box} % 用于存储单元格内容的盒子。 % \begin{macrocode} \box_new:N \l_@@_cell_box % \end{macrocode} % \end{variable} % % % % % \subsubsection{渲染函数} % % \begin{macro}{\@@_draw_hrule:n} % 绘制表格指定的水平线。使用 \cs{l_@@_expand_start_dim} 与 % \cs{l_@@_expand_end_dim} 扩展边界。 % \begin{macrocode} \cs_new:Nn \@@_draw_hrule:n { \draw_path_moveto:n { \seq_item:Nn \g_@@_col_loc_seq {1} - \l_@@_expand_start_dim, \seq_item:Nn \g_@@_row_loc_seq {#1} } \draw_path_lineto:n { \seq_item:Nn \g_@@_col_loc_seq {-1} + \l_@@_expand_end_dim, \seq_item:Nn \g_@@_row_loc_seq {#1} } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_draw_vrule:n} % 绘制表格指定的垂直线。使用 \cs{l_@@_expand_start_dim} 与 % \cs{l_@@_expand_end_dim} 扩展边界。 % \begin{macrocode} \cs_new:Nn \@@_draw_vrule:n { \draw_path_moveto:n { \seq_item:Nn \g_@@_col_loc_seq {#1}, \seq_item:Nn \g_@@_row_loc_seq {1} + \l_@@_expand_start_dim } \draw_path_lineto:n { \seq_item:Nn \g_@@_col_loc_seq {#1}, \seq_item:Nn \g_@@_row_loc_seq {-1} - \l_@@_expand_end_dim } } % \end{macrocode} % \end{macro} % % % \subsection{打印表格} % % \begin{macro}{\@@_print_data:nnn} % 使用垂直盒子打印输出指定数据。|#1|: 垂直对齐方式, |#2|: 水平对齐方式,|#3|: 内容。 % 使用变量 \cs{l_@@_wd_dim}、\cs{l_@@_ht_dim} 和\cs{l_@@_dp_dim} 的值。 % \begin{macrocode} \cs_new:Nn \@@_print_data:nnn { \str_if_in:nnTF {tmb} {#1} { \tl_set:Nn \l_@@_tmpb_tl {#1} } { \tl_set:Nn \l_@@_tmpb_tl {b} } \str_case:nnF {#2} { {l} { \tl_set:Nn \l_@@_tmpa_tl {\raggedright} } {c} { \tl_set:Nn \l_@@_tmpa_tl {\centering} } {r} { \tl_set:Nn \l_@@_tmpa_tl {\raggedleft} } } { \tl_set:Nn \l_@@_tmpa_tl {} } \tl_if_empty:nT {#3} { \tl_put_right:Nn \l_@@_tmpa_tl {\rule{1pt}{0pt}} } \str_case:Vn \l_@@_tmpb_tl { {t} { \vbox_set_top:Nn \l_@@_tmpa_box { \baselineskip=\g_@@_cell_lineskip_dim \hsize=\l_@@_wd_dim \parindent=0pt \l_@@_tmpa_tl #3 \vfill \par } } % \end{macrocode} % 如果是居中对齐,则额外使用 \cs{l_@@_cachea_dim} 与 \cs{l_@@_ufill_dim} % 传递单元格高度及填充量。 % \begin{macrocode} {m} { \dim_set:Nn \l_@@_tmpa_dim { (\l_@@_ht_dim - \l_@@_cachea_dim)/2 + \l_@@_ufill_dim } \vbox_set_to_ht:Nnn \l_@@_tmpa_box {\l_@@_ht_dim} { \baselineskip=\g_@@_cell_lineskip_dim \hsize=\l_@@_wd_dim \parindent=0pt \vskip \l_@@_tmpa_dim \l_@@_tmpa_tl #3 \vfill \par } } {b} { \dim_set:Nn \l_@@_tmpa_dim { \l_@@_ht_dim - \l_@@_cachea_dim + \l_@@_ufill_dim } \vbox_set:Nn \l_@@_tmpa_box { \baselineskip=\g_@@_cell_lineskip_dim \hsize=\l_@@_wd_dim \parindent=0pt \vskip \l_@@_tmpa_dim \l_@@_tmpa_tl #3 \par } } } \dim_compare:nNnT {\box_dp:N \l_@@_tmpa_box} < {\l_@@_dp_dim} { \box_set_dp:Nn \l_@@_tmpa_box {\l_@@_dp_dim} } \box_use:N \l_@@_tmpa_box } \cs_generate_variant:Nn \@@_print_data:nnn {VVV} % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_print_merge_data:nn} % 打印合并单元格,修改变量 \cs{l_@@_merge_seq}。 % \begin{macrocode} \cs_new:Nn \@@_print_merge_data:nn { \@@_parse_merge_id:n {#1, #2} \@@_parse_merge:V \l_@@_merge_id_tl \tl_set:Ne \l_@@_tmpa_tl { \seq_item:Nn \l_@@_merge_seq {2} } \str_if_eq:VnT \l_@@_tmpa_tl {#2} { % 只有合并单元格的第一列输出 \@@_parse_merge_size:n { \l_@@_merge_id_tl } \@@_parse_merge_fill:n { \l_@@_merge_id_tl } \tl_set:Ne \l_@@_tmpa_tl { \seq_item:Nn \l_@@_merge_seq {1} } \str_if_eq:VnTF \l_@@_tmpa_tl {#1} { % 如果是首行,则完整输出 \vbox_set_top:Nn \l_@@_tmpa_box { \baselineskip=\g_@@_cell_lineskip_dim \hsize=\l_@@_wd_dim \parindent=0pt \centering \l_@@_data_tl \vfill \par } \@@_parse_cell_size:n {#1, #2} \box_set_ht:Nn \l_@@_tmpa_box { \l_@@_ht_dim - \l_@@_dfill_dim } \box_set_dp:Nn \l_@@_tmpa_box { \l_@@_dp_dim } \box_use:N \l_@@_tmpa_box } { % 如果是非首行,则只输出占位符 \exp_args:Ne \rule { \dim_use:N \l_@@_wd_dim } { 0pt } } \hspace{\g_@@_cell_sep_dim} } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\printtable} % 打印表格。 % \begin{macrocode} \NewDocumentCommand \printtable { s O{0em} } { \@@_set_row_sep:ne {0pt} { \dim_use:N \g_@@_cell_sep_dim } \@@_set_col_sep:ne {0pt} { \dim_use:N \g_@@_cell_sep_dim } \@@_calc_col_wd: \@@_calc_row_ht: \@@_calc_merge_size: \bool_if:NTF \g_@@_col_header_bool { \int_set:Nn \l_@@_cachea_int {0} } { \int_set:Nn \l_@@_cachea_int {1} } \bool_if:NTF \g_@@_row_header_bool { \int_set:Nn \l_@@_cacheb_int {0} } { \int_set:Nn \l_@@_cacheb_int {1} } \int_step_inline:nnn { \l_@@_cachea_int } { \g_@@_row_count_int } { \@@_support_sys_func: \hbox:n { \dim_zero:N \l_@@_tmpa_dim \hspace{#2} \int_step_inline:nnn { \l_@@_cacheb_int } {\g_@@_col_count_int} { \@@_merge_if:nTF {##1, ####1} { \@@_print_merge_data:nn {##1} {####1} } { \@@_parse_cell:n {##1, ####1} \@@_parse_cell_fill:n {##1, ####1} \@@_parse_cell_ht:n {##1, ####1} \dim_set_eq:NN \l_@@_cachea_dim \l_@@_ht_dim \@@_parse_cell_size:n {##1, ####1} \@@_parse_row_align:n {##1} \@@_parse_col_align:n {####1} \@@_print_data:VVV \l_@@_row_align_tl \l_@@_col_align_tl \l_@@_data_tl \hspace{\g_@@_cell_sep_dim} } } } \IfBooleanF {#1} { \int_compare:nNnT {##1} < {\g_@@_row_count_int} {\\} } \@@_restore_sys_func: } } % \end{macrocode} % \end{macro} % % % \subsection{渲染表格} % % % \subsubsection{渲染内容} % % % \begin{macro}{\@@_render_data:nnn} % 使用垂直盒子渲染指定数据。|#1|: 垂直对齐方式, |#2|: 水平对齐方式,|#3|: 内容。 % 使用变量 \cs{l_@@_wd_dim},渲染好的盒子存储在 \cs{l_@@_cell_box} 中。 % \begin{macrocode} \cs_new:Nn \@@_render_data:nnn { \str_case:nnF{#2} { {l} { \tl_set:Nn \l_@@_tmpa_tl { \raggedright } } {c} { \tl_set:Nn \l_@@_tmpa_tl { \centering } } {r} { \tl_set:Nn \l_@@_tmpa_tl { \raggedleft } } } { \tl_set:Nn \l_@@_tmpa_tl {} } \tl_if_empty:nTF {#3} { \tl_set:Nn \l_@@_tmpb_tl { \rule{1pt}{0pt} } } { \tl_set:Nn \l_@@_tmpb_tl {#3} } \str_if_eq:nnTF {#1} {t} { \vbox_set_top:Nn } { \vbox_set:Nn } \l_@@_cell_box { \baselineskip=3ex \hsize=\l_@@_wd_dim \parindent=0pt \l_@@_tmpa_tl \l_@@_tmpb_tl \par } } \cs_generate_variant:Nn \@@_render_data:nnn {VVV} % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_draw_cell:nn} % 绘制指定单元格,|#1#2| 为数字坐标;如果指定单元格已合并,则绘制空内容。 % 使用 \cs{l_@@_x_dim} 与 \cs{l_@@_y_dim} 的坐标信息。 % \begin{macrocode} \cs_new:Nn \@@_draw_cell:nn { \@@_parse_cell_fill:n {#1, #2} \@@_parse_cell_size:n {#1, #2} \@@_parse_row_align:n {#1} \@@_parse_col_align:n {#2} \@@_merge_if:nTF {#1, #2} { \bool_set_false:N \l_@@_is_box_bool \tl_set:Nn \l_@@_data_tl {} } { \@@_parse_cell:n {#1, #2} } \@@_render_data:VVV % 获取包含渲染内容的盒子 \l_@@_row_align_tl \l_@@_col_align_tl \l_@@_data_tl \tl_if_eq:NnTF \l_@@_row_align_tl {m} { \dim_set:Nn \l_@@_tmpb_dim { \box_ht:N \l_@@_cell_box + \l_@@_ufill_dim } \dim_set:Nn \l_@@_tmpa_dim { \l_@@_y_dim - \l_@@_tmpb_dim } \dim_sub:Nn \l_@@_tmpa_dim { (\l_@@_ht_dim - \l_@@_tmpb_dim) / 2 } } { \dim_set:Nn \l_@@_tmpa_dim { \l_@@_y_dim - \l_@@_ht_dim } } \draw_box_use:Nn \l_@@_cell_box { \l_@@_x_dim, \l_@@_tmpa_dim } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_draw_merge:n} % 绘制指定单元格,|#1#2| 为数字坐标;如果指定单元格已合并,则绘制空内容。 % 使用 \cs{l_@@_x_dim} 与 \cs{l_@@_y_dim} 的坐标信息。 % \begin{macrocode} \cs_new:Nn \@@_draw_merge:n { \@@_parse_merge:n {#1} \int_set:Nn \l_@@_tmpa_int { \seq_item:Nn \l_@@_merge_seq {1} } \int_set:Nn \l_@@_tmpb_int { \seq_item:Nn \l_@@_merge_seq {2} } \dim_set:Nn \l_@@_y_dim { \seq_item:Nn \g_@@_row_loc_seq { \l_@@_tmpa_int + 1 } } \dim_set:Nn \l_@@_x_dim { \seq_item:Nn \g_@@_col_loc_seq { \l_@@_tmpb_int + 1 } } \dim_add:Nn \l_@@_x_dim { \seq_item:Nn \g_@@_col_sep_seq { \l_@@_tmpb_int + 1 } / 2 } \dim_sub:Nn \l_@@_y_dim { \c_@@_std_dp_dim / 3 } \dim_sub:Nn \l_@@_y_dim { \seq_item:Nn \g_@@_row_sep_seq { \l_@@_tmpa_int + 1 } / 2 } \@@_parse_merge_size:n {#1} \@@_parse_merge_fill:n {#1} \@@_parse_merge_ht:n {#1} \@@_parse_merge_align:n {#1} \@@_render_data:VVV % 获取包含渲染内容的盒子 \l_@@_row_align_tl \l_@@_col_align_tl \l_@@_data_tl \tl_if_eq:NnTF \l_@@_row_align_tl {m} { \dim_set:Nn \l_@@_tmpb_dim { \box_ht:N \l_@@_cell_box + \l_@@_ufill_dim } \dim_set:Nn \l_@@_tmpa_dim { \l_@@_y_dim - \l_@@_tmpb_dim } \dim_sub:Nn \l_@@_tmpa_dim { (\l_@@_ht_dim - \l_@@_tmpb_dim) / 2 } } { \dim_set:Nn \l_@@_tmpa_dim { \l_@@_y_dim - \l_@@_ht_dim } } \draw_box_use:Nn \l_@@_cell_box { \l_@@_x_dim, \l_@@_tmpa_dim } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_draw_cells:} % 绘制所有单元格,使用 \cs{l_@@_x_dim} 与 \cs{l_@@_y_dim} 的存储内容的左上角坐标信息。 % \begin{macrocode} \cs_new:Nn \@@_draw_cells: { \dim_set:Nn \l_@@_y_dim { -\c_@@_std_dp_dim / 3 } % 补偿 \bool_if:NTF \g_@@_col_header_bool { \int_set:Nn \l_@@_tmpa_int {0} } { \int_set:Nn \l_@@_tmpa_int {1} } \int_step_inline:nnn {\l_@@_tmpa_int} {\g_@@_row_count_int} { \dim_sub:Nn \l_@@_y_dim { \seq_item:Nn \g_@@_row_sep_seq {##1+1} } \dim_zero:N \l_@@_x_dim \bool_if:NTF \g_@@_row_header_bool { \int_set:Nn \l_@@_tmpb_int {0} } { \int_set:Nn \l_@@_tmpb_int {1} } \int_step_inline:nnn { \l_@@_tmpb_int } { \g_@@_col_count_int } { \dim_add:Nn \l_@@_x_dim { \seq_item:Nn \g_@@_col_sep_seq {####1 + 1} } \@@_draw_cell:nn {##1} {####1} \dim_add:Nn \l_@@_x_dim { \l_@@_wd_dim } } \dim_sub:Nn \l_@@_y_dim { \l_@@_ht_dim + \l_@@_dp_dim } } \int_step_inline:nn { \@@_parse_merge_count: } { \@@_draw_merge:n {##1} } } % \end{macrocode} % \end{macro} % % % \subsubsection{渲染边框-三线表} % % \done\todo[功能]{ 处理无表头时的渲染情况 } % \todo[功能]{ 三线表标题自动合并 } % \begin{macro}{\@@_draw_booktabs:n} % 绘制三线表的边框线。 % \begin{macrocode} \cs_new:Nn \@@_draw_booktabs:n { \dim_zero:N \l_@@_expand_start_dim \dim_zero:N \l_@@_expand_end_dim \@@_set_rule_style:n {toprule} \@@_draw_hrule:n {1} \draw_path_use_clear:n { stroke } \bool_if:NT \g_@@_col_header_bool { \@@_set_rule_style:n {midrule} \@@_draw_hrule:n {2} \draw_path_use_clear:n { stroke } } \@@_set_rule_style:n {bottomrule} \@@_draw_hrule:n {-1} \draw_path_use_clear:n { stroke } \@@_set_rule_style:n {cmidrule} \clist_map_inline:nn {#1} { \@@_draw_hrule:n {##1+2} } \draw_path_use_clear:n { stroke } } \cs_generate_variant:Nn \@@_draw_booktabs:n { V } % \end{macrocode} % \end{macro} % % % \subsubsection{渲染边框-网格线} % % % \begin{macro}{\@@_row_rule:nn} % 设置网格线风格的行网格线。|#1| 为默认值,|#2| 为线列表。 % \begin{macrocode} \NewDocumentCommand {\@@_row_rule:nn} { O{} m } { \tl_set:Nn \l_@@_style_tl {#1} \tl_if_empty:nTF {#2} { \seq_clear:N \l_@@_style_seq } { \seq_set_split:Nnn \l_@@_style_seq {,} {#2} \seq_get_left:NN \l_@@_style_seq \l_@@_tmpa_tl \str_if_in:NnF \l_@@_tmpa_tl {=} { \seq_pop_left:NN \l_@@_style_seq \l_@@_style_tl } } \@@_set_line_style:NNnn \g_@@_row_rule_seq \l_@@_style_seq {#1} {row} \bool_if:NF \g_@@_col_header_bool { \seq_gpop_left:NN \g_@@_row_rule_seq \l_@@_tmpa_tl } \seq_gput_left:NV \g_@@_row_rule_seq \l_@@_style_tl } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_col_rule:nn} % 设置网格线风格的列网格线。|#1| 为默认值,|#2| 为线列表。 % \begin{macrocode} \NewDocumentCommand {\@@_col_rule:nn} { O{} m } { \tl_set:Nn \l_@@_style_tl {#1} \tl_if_empty:nTF {#2} { \seq_clear:N \l_@@_style_seq } { \seq_set_split:Nnn \l_@@_style_seq {,} {#2} \seq_get_left:NN \l_@@_style_seq \l_@@_tmpa_tl \str_if_in:NnF \l_@@_tmpa_tl {=} { \seq_pop_left:NN \l_@@_style_seq \l_@@_style_tl } } \@@_set_line_style:NNnn \g_@@_col_rule_seq \l_@@_style_seq {#1} {col} \seq_gput_left:NV \g_@@_col_rule_seq \l_@@_style_tl } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_calc_grid_expand:N} % 计算网格线的扩展长度\footnote{用于平衡线宽造成的转角过渡问题。}。 % \begin{macrocode} \cs_new:Nn \@@_calc_grid_expand:N { \seq_if_empty:NTF #1 { \seq_set_split:Nnn \l_@@_tmpa_seq {,} {0pt} } { \seq_set_eq:NN \l_@@_tmpa_seq #1 } \seq_get_left:NN \l_@@_tmpa_seq \l_@@_style_tl \tl_if_empty:NTF \l_@@_style_tl { \dim_zero:N \l_@@_expand_start_dim } { \@@_parse_rule_style:V \l_@@_style_tl \dim_set:Nn \l_@@_expand_start_dim { \l_@@_rule_dim/2 } } \seq_get_right:NN \l_@@_tmpa_seq \l_@@_style_tl \tl_if_empty:NTF \l_@@_style_tl { \dim_zero:N \l_@@_expand_end_dim } { \@@_parse_rule_style:V \l_@@_style_tl \dim_set:Nn \l_@@_expand_end_dim { \l_@@_rule_dim/2 } } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_draw_grid:n} % 绘制网格线型表格。 % \begin{macrocode} \cs_new:Nn \@@_draw_grid:n { \group_begin: \cs_set_eq:NN \hrule \@@_row_rule:nn \cs_set_eq:NN \vrule \@@_col_rule:nn #1 \group_end: \@@_calc_grid_expand:N \g_@@_row_rule_seq \int_step_inline:nn { \seq_count:N \g_@@_col_loc_seq } { \tl_set:Ne \l_@@_style_tl { \seq_item:Nn \g_@@_col_rule_seq {##1} } \tl_if_empty:NF \l_@@_style_tl { \@@_set_rule_style:V \l_@@_style_tl \@@_draw_vrule:n {##1} \draw_path_use_clear:n { stroke } } } \@@_calc_grid_expand:N \g_@@_col_rule_seq \int_step_inline:nn {\seq_count:N \g_@@_row_loc_seq} { \tl_set:Ne \l_@@_style_tl { \seq_item:Nn \g_@@_row_rule_seq {##1} } \tl_if_empty:NF \l_@@_style_tl { \@@_set_rule_style:V \l_@@_style_tl \@@_draw_hrule:n {##1} \draw_path_use_clear:n { stroke } } } } \cs_generate_variant:Nn \@@_draw_grid:n { V } % \end{macrocode} % \end{macro} % % % \subsubsection{渲染边框-全边框} % % \todo[优化]{ 提供内外边框的设置接口 } % \done \todo[优化]{ 适应合并单元格的框架 } % \begin{macro}{\@@_draw_cell_rule:nn} % 绘制指定单元格边框。 % \begin{macrocode} \cs_new:Nn \@@_draw_cell_rule:nn { \dim_set:Nn \l_@@_x_dim { \seq_item:Nn \g_@@_col_loc_seq {#2+1} } \dim_set:Nn \l_@@_tmpa_dim { \seq_item:Nn \g_@@_col_loc_seq {#2+2} } \dim_set:Nn \l_@@_y_dim { \seq_item:Nn \g_@@_row_loc_seq {#1+2} } \dim_set:Nn \l_@@_tmpb_dim { \seq_item:Nn \g_@@_row_loc_seq {#1+1} } \dim_sub:Nn \l_@@_tmpa_dim { \l_@@_x_dim } \dim_sub:Nn \l_@@_tmpb_dim { \l_@@_y_dim } \draw_path_rectangle:nn { \l_@@_x_dim, \l_@@_y_dim } { \l_@@_tmpa_dim, \l_@@_tmpb_dim } \draw_path_use_clear:n { draw } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_draw_merge_rule:n} % 绘制指定单元格边框。 % \begin{macrocode} \cs_new:Nn \@@_draw_merge_rule:n { \@@_parse_merge:n {#1} \dim_set:Nn \l_@@_x_dim { \seq_item:Nn \g_@@_col_loc_seq { \seq_item:Nn \l_@@_merge_seq {2} + 1 } } \dim_set:Nn \l_@@_tmpa_dim { \seq_item:Nn \g_@@_col_loc_seq { \seq_item:Nn \l_@@_merge_seq {4} + 2 } } \dim_set:Nn \l_@@_y_dim { \seq_item:Nn \g_@@_row_loc_seq { \seq_item:Nn \l_@@_merge_seq {3} + 2 } } \dim_set:Nn \l_@@_tmpb_dim { \seq_item:Nn \g_@@_row_loc_seq { \seq_item:Nn \l_@@_merge_seq {1} + 1 } } \dim_sub:Nn \l_@@_tmpa_dim { \l_@@_x_dim } \dim_sub:Nn \l_@@_tmpb_dim { \l_@@_y_dim } \draw_path_rectangle:nn { \l_@@_x_dim, \l_@@_y_dim } { \l_@@_tmpa_dim, \l_@@_tmpb_dim } \draw_path_use_clear:n { draw } } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\@@_draw_cell_rule:} % 绘制单元格边框。 % \begin{macrocode} \cs_new:Nn \@@_draw_cell_rules: { \int_step_inline:nnn { \bool_if:NTF \g_@@_col_header_bool {0} {1} } { \g_@@_row_count_int } { \int_step_inline:nnn { \bool_if:NTF \g_@@_row_header_bool {0} {1} } { \g_@@_col_count_int } { \@@_merge_if:nF {##1, ####1} { \@@_draw_cell_rule:nn {##1} {####1} } } } \int_step_inline:nn { \@@_parse_merge_count: } { \@@_draw_merge_rule:n {##1} } } % \end{macrocode} % \end{macro} % % % \subsubsection{渲染选项} % % \changes{v0.4}{2026-03-11}{将渲染函数的参数改为键值接口} % % \begin{variable}{\l_@@_render_multicol_bool, \l_@@_render_longtable_bool, % \l_@@_render_format_tl, \l_@@_render_rule_tl} % 输入选项变量。 % \begin{macrocode} \bool_new:N \l_@@_render_multicol_bool \bool_new:N \l_@@_render_longtable_bool \tl_new:N \l_@@_render_format_tl \tl_new:N \l_@@_render_rule_tl % \end{macrocode} % \end{variable} % % % \begin{macro}{xtable/render} % 渲染选项。 % \begin{macrocode} \keys_define:nn { xtable / render } { format .choices:nn = { border, grid, booktabs, custom } { \tl_set_eq:NN \l_@@_render_format_tl \l_keys_choice_tl }, format .initial:n = {border}, booktabs .code:n = { \tl_set:Nn \l_@@_render_format_tl { booktabs } }, grid .code:n = { \tl_set:Nn \l_@@_render_format_tl { grid } }, border .code:n = { \tl_set:Nn \l_@@_render_format_tl { border } }, custom .code:n = { \tl_set:Nn \l_@@_render_format_tl { custom } }, multicol .bool_set:N = \l_@@_render_multicol_bool, multicol .default:n = {true}, multicol .initial:n = {false}, longtable .bool_set:N = \l_@@_render_longtable_bool, longtable .default:n = {true}, longtable .initial:n = {false}, rule .tl_set:N = \l_@@_render_rule_tl, rule .initial:n = {} } % \end{macrocode} % \end{macro} % % % % \begin{macro}{xtable/render} % 清空渲染选项。 % \begin{macrocode} \cs_new:Nn \@@_clear_render_options: { \tl_clear:N \l_@@_render_rule_tl \bool_set_false:N \l_@@_render_multicol_bool \bool_set_false:N \l_@@_render_longtable_bool } % \end{macrocode} % \end{macro} % % % \subsubsection{渲染表格} % % \todo[功能]{ 添加并排输出功能 } % % \begin{macro}{\@@_set_table_sep:n} % 设置表格边框序列。 % \begin{macrocode} \cs_new:Nn \@@_set_table_sep:n { \@@_set_row_sep:ee { \dim_use:N \g_@@_row_margin_dim } { \dim_eval:n { \g_@@_row_margin_dim * 2 } } \@@_set_col_sep:ee { \bool_if:nTF {#1} { \dim_use:N \g_@@_col_margin_dim } { 0pt } } { \dim_eval:n { \g_@@_col_margin_dim * 2 } } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_rendertable_pre:, \@@_rendertable_post:} % 渲染表格的准备函数与善后的函数。 % 其中,准备函数由 \cs{rendertable} 直接调用,而善后函数则由所有绘制表格的函数调用。 % \begin{macrocode} \cs_new:Nn \@@_rendertable_pre: { \@@_set_table_sep:n {\c_true_bool} \@@_calc_col_wd: \@@_calc_row_ht: \@@_calc_merge_size: \@@_calc_coord: \draw_begin: \vbox_set_to_ht:Nnn \l_@@_tmpa_box {\g_@@_above_space_dim} {} \draw_box_use:N \l_@@_tmpa_box } \cs_new:Nn \@@_rendertable_post: { \@@_support_sys_func: \@@_draw_cells: \draw_end: \@@_restore_sys_func: \@@_clear_render_options: } % \end{macrocode} % \end{macro} % % % \begin{macro}{\rendertable} % 绘制表格。 % \begin{macrocode} \NewDocumentCommand \rendertable { O{} } { \keys_set:nn { xtable/render } {#1} \@@_rendertable_pre: \str_case:NnF \l_@@_render_format_tl { {booktabs} { \@@_draw_booktabs:V \l_@@_render_rule_tl } {grid} { \@@_draw_grid:V \l_@@_render_rule_tl } } { \@@_draw_cell_rules: } \@@_rendertable_post: } % \end{macrocode} % \end{macro} % % % \begin{macrocode} % % \end{macrocode} % % % \end{implementation} % % \todo[Debug]{ 查找 Package multicol: Error saving partial page. 的原因 } % \onecolumn % % \PrintChanges % \PrintIndex % \todos % \endinput