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}

enter image description here

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}

enter image description here


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}

enter image description here

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.

enter image description here


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{&#1}%
}

\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{&#2}%
}
\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}

Result