我想从逗号分隔的键值对列表(例如)自动生成一个表{{x=1,y=2},{x=3,y=1},{x=2,y=3}}
。在每个“行”中,“键”始终相同(它们成为表中的列标题),但值可以更改。给定这个特定的键值对列表,以下代码将生成以下内容:
这正是我想要的。不幸的是,稍微复杂一点的值输入(例如){{x=1,y=$\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}
会破坏我的代码。我的代码确实生成了预期的输出:
但在执行此操作之前我收到了编译错误,例如:
! \tableRows 定义中的参数编号非法。\crcr l.40 ...\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}
有没有更好/更强大的方法来做到这一点?
这是我的代码:
\documentclass{article}
\usepackage{xparse,etoolbox}
\newcounter{tableRow} % for counting the number of rows in the table
\newcounter{tableCol} % for counting the number of columns
\newcommand\tableRows{} % to hold the contents of the table
\newcommand\tableHeader{} % to hold the header of the table
\newcommand\MakeTable[1]{
\setcounter{tableRow}{0} % initialise
\setcounter{tableCol}{1}
\renewcommand\tableRows{}
\renewcommand\tableHeader{}
\forcsvlist\ProcessRow{#1}% generate table
\begin{tabular}{*{\arabic{tableCol}}{c}}
\tableHeader\\\hline\tableRows\\
\end{tabular}
}
\makeatletter
\newcommand\ProcessRow[1]{% generate table rows using \ProcessEntry
\addtocounter{tableRow}{1}
\protected@xappto\tableRows{\Alph{tableRow}}% row label in table
\forcsvlist\ProcessEntry{#1}
\protected@xappto\tableRows{\\}
}
% need to extract key-value pairs from input of the form: key=val
\newcommand\ExtractKeyValuePair[2]{\def\Key{#1}\def\Value{#2}}
\DeclareDocumentCommand\ProcessEntry{>{\SplitArgument{1}{=}}m}{% add entries to row
\ExtractKeyValuePair#1% key-value pairs --> \Key and \Value
\ifnum\value{tableRow}=1%
\addtocounter{tableCol}{1}
\protected@xappto\tableHeader{&\Key}
\fi
\protected@xappto\tableRows{&\Value}
}
\begin{document}
\MakeTable{{x=1,y=2},{x=3,y=1},{x=2,y=3}}
% \MakeTable fails on this example
\MakeTable{{x=1,y=$\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}
\end{document}
\protected@xappto
为了生成表格的行,我使用电子工具箱包。最初我尝试使用 token,但遇到了扩展问题,这无疑是由于我的无知。我不清楚为什么我需要\protected@xappto
而不是\xappto
,但如果我使用 ,这\xappto
两个示例的代码都会失败。
我提取键值对的方式也感觉有点 OTT:我使用了一些\SplitArgument
技巧\DeclareDocumentCommand
解析包来执行此操作。
编辑
事实证明,我的代码大部分都有效,但我有点不走运,因为这\overrightarrow
是我在实际代码中使用的第一个例子,我的 MWE 就是从这个例子中提炼出来的。我的 MWE 的问题在于它是\overrightarrow
一个脆弱的命令。我可以通过添加以下行来修复 MWE 中的编译错误:
\usepackage{fixltx2e}
\MakeRobust{\overrightarrow}
然而,这并不是一个完整的修复,因为必然会有其他脆弱的命令会破坏我的代码...Christian 的生成表而不是存储表的方法可能是最好的解决方案。
答案1
一种解决方案,使用包kvsetkeys
来解析逗号和键值列表。首先在令牌寄存器中构建表格规范、标题行和正文。然后组成表格。
由于所有分配均保存在本地组中,因此\MakeTable
可以嵌套。
该包在表格标题行下方booktabs
提供了\midrule
更美观的线条。
该包alphalph
提供\AlphAlph
超过 26 行的字母编号。
完整示例:
\documentclass{article}
\usepackage{booktabs}
\usepackage{kvsetkeys}
\usepackage{alphalph}
\makeatletter
% Resources
\newcount\TableRow
\newtoks\TableSpecification
\newtoks\TableHeader
\newtoks\TableBody
% Helper marco \AddToToks{<token register>}{<appended contents>}
\newcommand{\AddToToks}[2]{#1\expandafter{\the#1#2}}
% Main macro \MakeTable{...}
\newcommand{\MakeTable}[1]{%
\begingroup
\MakeTableSpecificationAndHeader{#1}%
\MakeTableBody{#1}%
\edef\TableBegin{%
\noexpand\begin{tabular}{\TableSpecification}%
}%
%\TableBegin
\begin{tabular}{\the\TableSpecification}%
\the\TableHeader
\midrule
\the\TableBody
\end{tabular}%
\endgroup
}
\newcommand{\MakeTableSpecificationAndHeader}[1]{%
\TableSpecification{l}%
\TableHeader{}%
\AnalyzeFirstRow{#1}%
\AddToToks\TableHeader{\tabularnewline}%
}
\newcommand{\AnalyzeFirstRow}[1]{%
\comma@parse{#1}\FirstRowProcessor
}
\newcommand{\FirstRowProcessor}[1]{%
\kv@parse{#1}\FirstRowCellsProcessor
\comma@break
}
\newcommand{\FirstRowCellsProcessor}[2]{%
\AddToToks\TableSpecification{c}%
\AddToToks\TableHeader{}%
}
\newcommand{\MakeTableBody}[1]{%
\TableRow=0 %
\TableBody{}%
\comma@parse{#1}\TableRowProcessor
}
\newcommand*{\TableRowProcessor}[1]{%
\advance\TableRow by 1 %
\edef\TableNumber{\AlphAlph{\TableRow}}%
\expandafter\AddToToks\expandafter\TableBody\expandafter{\TableNumber}%
\kv@parse{#1}\TableRowCellsProcessor
\AddToToks\TableBody{\tabularnewline}%
}
% Simplified implementation, which requires, that all keys of
% a row are given and have the correct order.
\newcommand{\TableRowCellsProcessor}[2]{%
\AddToToks\TableBody{}%
}
\makeatother
\begin{document}
\MakeTable{{x=1,y=2},{x=3,y=1},{x=2,y=3}}
\medskip
\MakeTable{{x=1,y=$\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}
\medskip
\MakeTable{
{
xy=\MakeTable{{x=1, y=1}, {x=2, y=2}},
yz=foo,
},
{
xy=bar,
yz=\MakeTable{{y=4, z=5}, {y=6, z=7}},
},
}
\end{document}
答案2
这是一个没有键值的版本(第一个),唯一需要手动完成的就是调整此处的列数。
\documentclass{article}
\usepackage{xparse,etoolbox}
\newcounter{tableRow} % for counting the number of rows in the table
\newcounter{tableCol} % for counting the number of columns
\newcommand\tableRows{} % to hold the contents of the table
\newcommand\tableHeader{} % to hold the header of the table
\ExplSyntaxOn
\clist_new:N \g_andrew_clist
\int_new:N \l_columncounter_int
\int_new:N \g_list_count
\NewDocumentCommand{\ProcessRow}{m}{%
\clist_gset:Nn \g_andrew_clist {#1}%
\int_gset:Nn \g_list_count {\clist_count:N \g_andrew_clist}
\int_gset:Nn \l_columncounter_int {\c_one}
\stepcounter{tableRow}%
\Alph{tableRow} &
\prg_replicate:nn { \g_list_count }{%
\int_compare:nNnTF { \l_columncounter_int } < { \g_list_count }{%
\clist_item:Nn \g_andrew_clist {\l_columncounter_int} &
}{
\clist_item:Nn \g_andrew_clist {\int_use:N \l_columncounter_int }
}
\int_gincr:N \l_columncounter_int
}
}
\newcommand\MakeTable[2][3]{%
\clist_set:Nn \l_tmpa_clist {#2}
\setcounter{tableRow}{0} % initialise
\setcounter{tableCol}{#1}
\begin{tabular}{*{\arabic{tableCol}}{c}}
& x & y \tabularnewline
\hline
\clist_map_inline:Nn \l_tmpa_clist {%
\ProcessRow{##1} \tabularnewline
}
\end{tabular}
}
\begin{document}
\ExplSyntaxOff
\MakeTable{ {1,2}, {5,6}, {3,{$\overrightarrow{AB}$}}}
\MakeTable[4]{ {1,2,3}, {4,5,6}, {7,8,{$\overrightarrow{AB}$}}}
\end{document}
更新
\documentclass{article}
\usepackage{xparse,etoolbox}
\usepackage{l3regex}
\newcounter{tableRow} % for counting the number of rows in the table
\newcounter{tableCol} % for counting the number of columns
\ExplSyntaxOn
\clist_new:N \g_andrew_argument_clist
\clist_new:N \g_andrew_clist
\clist_new:N \l_andrew_data_clist
\seq_new:N \l_andrew_header_seq
\int_new:N \l_columncounter_int
\int_new:N \g_list_count
\NewDocumentCommand{\ProcessRow}{m}{%
\clist_gset:Nn \g_andrew_clist {#1}%
\int_gset:Nn \g_list_count {\clist_count:N \g_andrew_clist}
\int_gset:Nn \l_columncounter_int {\c_one}
\stepcounter{tableRow}%
\Alph{tableRow} &
\prg_replicate:nn { \g_list_count }{%
\int_compare:nNnTF { \l_columncounter_int } < { \g_list_count }{%
\clist_item:Nn \g_andrew_clist {\l_columncounter_int} &
}{
\clist_item:Nn \g_andrew_clist {\int_use:N \l_columncounter_int }
}
\int_gincr:N \l_columncounter_int
}
}
\NewDocumentCommand{\ProcessHeader}{m}{%
\clist_gset_eq:NN \g_andrew_clist #1%
\int_gset:Nn \g_list_count {\clist_count:N \g_andrew_clist}
\int_gset:Nn \l_columncounter_int {\c_one}
& %
\prg_replicate:nn { \g_list_count }{%
\int_compare:nNnTF { \l_columncounter_int } < { \g_list_count }{%
\clist_item:Nn \g_andrew_clist {\l_columncounter_int} &
}{
\clist_item:Nn \g_andrew_clist {\int_use:N \l_columncounter_int }
}
\int_gincr:N \l_columncounter_int
}
}
\newcommand\MakeTable[1]{%
\clist_set:Nn \g_andrew_argument_clist {#1} % Store the full clist first
\clist_set:Nx \l_tmpb_clist {\clist_item:Nn \g_andrew_argument_clist {1}} % Extract the first line to get the header descriptions
\int_set:Nn \l_tmpa_int {\clist_count:N \l_tmpb_clist}
\int_incr:N \l_tmpa_int
\seq_clear:N \l_andrew_header_seq
\tl_set:Nn \l_tmpa_tl {#1} % Grab the argument again
\regex_replace_all:nnN { \w+= }{ } \l_tmpa_tl % Replace the x= values with nothing
\clist_set:NV \l_andrew_data_clist {\l_tmpa_tl} %making a new clist again
% Get the headers
\clist_map_inline:Nn \l_tmpb_clist { %
\tl_set:Nn \l_tmpa_tl {##1}
\seq_clear:N \l_tmpb_seq
\seq_set_split:NnV \l_tmpb_seq {=} {\l_tmpa_tl}
\seq_gput_right:Nx \l_andrew_header_seq {\seq_item:Nn \l_tmpb_seq {1}}
}
\clist_set_from_seq:NN \l_tmpb_clist \l_andrew_header_seq
\setcounter{tableRow}{0} % initialise
\setcounter{tableCol}{\int_use:N \l_tmpa_int}
\begin{tabular}{*{\int_eval:n{\l_tmpa_int+1}}{c}}
\ProcessHeader{\l_tmpb_clist} \tabularnewline % Header frist
\hline
\clist_map_inline:Nn \l_andrew_data_clist {%
\ProcessRow{##1} \tabularnewline
}
\end{tabular}
}
\ExplSyntaxOff
\begin{document}
\MakeTable{ {x=1,Foo=2}, {5,6}, {3,{$\overrightarrow{AB}$}}}
\MakeTable{ {x=1,y=2,z=3,u=4}, {x=4,y=5,z=6,u=10}, {x=7,y=8,{z=$\overrightarrow{AB}$},u=14}}
\end{document}
答案3
您实际上并不想使用\protected@xappto
,而是使用\xappto
和\expandonce
。
(我还修复了行尾保护。)
\documentclass{article}
\usepackage{xparse,etoolbox}
\newcounter{tableRow} % for counting the number of rows in the table
\newcounter{tableCol} % for counting the number of columns
\newcommand\tableRows{} % to hold the contents of the table
\newcommand\tableHeader{} % to hold the header of the table
\newcommand\MakeTable[1]{%
\setcounter{tableRow}{0}% initialise
\setcounter{tableCol}{1}%
\renewcommand\tableRows{}%
\renewcommand\tableHeader{}%
\forcsvlist\ProcessRow{#1}% generate table
\begin{tabular}{*{\arabic{tableCol}}{c}}
\tableHeader\\\hline\tableRows\\
\end{tabular}%
}
\newcommand\ProcessRow[1]{% generate table rows using \ProcessEntry
\addtocounter{tableRow}{1}%
\xappto\tableRows{\Alph{tableRow}}% row label in table
\forcsvlist\ProcessEntry{#1}%
\gappto\tableRows{\\}%
}
% need to extract key-value pairs from input of the form: key=val
\newcommand\ExtractKeyValuePair[2]{\def\Key{#1}\def\Value{#2}}
\DeclareDocumentCommand\ProcessEntry{>{\SplitArgument{1}{=}}m}{% add entries to row
\ExtractKeyValuePair#1% key-value pairs --> \Key and \Value
\ifnum\value{tableRow}=1
\addtocounter{tableCol}{1}%
\xappto\tableHeader{&\expandonce{\Key}}%
\fi
\xappto\tableRows{&\expandonce{\Value}}%
}
\begin{document}
\MakeTable{{x=1,y=2},{x=3,y=1},{x=2,y=3}}
\MakeTable{{x=1,y=$\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}
\end{document}
一种expl3
实现,其中我使用(或者可能滥用)键值接口。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\MakeTable}{m}
{
\andrew_maketable_main:n { #1 }
}
\seq_new:N \l__andrew_maketable_cols_seq
\tl_new:N \l__andrew_maketable_body_tl
\int_new:N \l__andrew_maketable_rows_int
\keys_define:nn { andrew/maketable }
{
unknown .code:n = { \__andrew_maketable_add:n { #1 } }
}
\cs_new_protected:Nn \andrew_maketable_main:n
{
\seq_clear:N \l__andrew_maketable_cols_seq
\tl_clear:N \l__andrew_maketable_body_tl
\int_zero:N \l__andrew_maketable_rows_int
\clist_map_inline:nn { #1 }
{
\__andrew_maketable_add_row:n { ##1 }
}
\tl_put_left:Nx \l__andrew_maketable_body_tl
{ & \seq_use:Nn \l__andrew_maketable_cols_seq { & } \exp_not:n { \\ \hline } }
\begin{tabular}{ c *{ \seq_count:N \l__andrew_maketable_cols_seq } { c } }
\tl_use:N \l__andrew_maketable_body_tl
\end{tabular}
}
\cs_new_protected:Nn \__andrew_maketable_add_row:n
{
\int_incr:N \l__andrew_maketable_rows_int
\tl_put_right:Nx \l__andrew_maketable_body_tl
{
\int_to_Alph:n { \l__andrew_maketable_rows_int }
}
\keys_set:nn { andrew/maketable } { #1 }
\tl_put_right:Nn \l__andrew_maketable_body_tl { \\ }
}
\cs_new_protected:Nn \__andrew_maketable_add:n
{
\seq_if_in:NxF \l__andrew_maketable_cols_seq { \l_keys_key_tl }
{
\seq_put_right:Nx \l__andrew_maketable_cols_seq { \l_keys_key_tl }
}
\tl_put_right:Nn \l__andrew_maketable_body_tl { & #1 }
}
\ExplSyntaxOff
\begin{document}
\MakeTable{{x=1,y=2},{x=3,y=1},{x=2,y=3}}
\MakeTable{{x=1,y=$\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}
\MakeTable{ {x=1,y=2,z=3,u=4}, {x=4,y=5,z=6,u=10}, {x=7,y=8,z=$\overrightarrow{AB}$,u=14}}
\end{document}
该序列跟踪列标题;在标记列表变量中我存储行。
当然,和原来的实现一样,顺序必须严格,否则物品就会放错。
答案4
使用包的方法新工具。
与其他实现一样,顺序必须严格。如果希望密钥顺序任意,则需要更复杂的方法。
\documentclass{article}
\usepackage{xinttools}
% helper utilities
\newcommand*\AndrewExtractKey {}
\def\AndrewExtractKey #1#2=#3,{#1#2}
\newcommand*\AndrewExtractValue {}
\def\AndrewExtractValue #1=#2#3,{#2#3}
\makeatletter
\newcommand*\JohnDoeAlph [1]{\@Alph{#1\relax}}
\makeatother
% Main command
\newcommand*\MakeTable [1]{%
\fdef\AndrewTableKeys{\xintCSVtoList{#1}}%
%
\fdef\AndrewTableKeysFirstRow{\xintNthElt{1}{\AndrewTableKeys}}%
\fdef\AndrewTableKeysNbColumns
{\xintNthElt{0}{\xintCSVtoList{\AndrewTableKeysFirstRow}}}%
%
\gdef\AndrewTableKeysRowCount{1}%
\begin{tabular}{c*{\AndrewTableKeysNbColumns}c}
\xintFor ##1 in {\AndrewTableKeysFirstRow}\do
{&\AndrewExtractKey ##1,}\\
\hline
\xintFor* ##1 in {\AndrewTableKeys}\do
{%
\JohnDoeAlph{\AndrewTableKeysRowCount}%
\xdef\AndrewTableKeysRowCount{\the\numexpr\AndrewTableKeysRowCount+1}%
\xintFor ##2 in {##1}\do{&\AndrewExtractValue ##2,}\\%
}%
\end{tabular}
}
\begin{document}
\MakeTable{{x=1,y=2},{x=3,y=1},{x=2,y=3}}
\MakeTable{{x=1,y=$\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}
\MakeTable{ {x=1,y=2,z=3,u=4}, {x=4,y=5,z=6,u=10}, {x=7,y=8,z=$\overrightarrow{AB}$,u=14}}
\end{document}