Generating a table from key-value pairs
Here's a version without key-values (first), the only thing that has to be done manually is the adapt the number of columns here.
\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}
Update
\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}
You don't really want to use \protected@xappto
, but rather \xappto
and \expandonce
.
(I also fixed the end-of-line protections.)
\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}
An expl3
implementation, where I use (or abuse, perhaps) the key-value interface.
\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}
The sequence keeps track of the column headers; in the token list variable I store the rows.
Of course, like in the original implementation, the order must be strict or items would be misplaced.
A solution, that uses package kvsetkeys
for parsing the comma and key value lists. The table specification, the header row and the body are first constructed in token registers. Then the table is composed.
Since all assignments are kept local in a group, \MakeTable
can be nested.
Package booktabs
provides \midrule
for a nicer line below the table header row.
Package alphalph
provides \AlphAlph
for alphabetic numbering of more than 26 rows.
Full example:
\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}