Generando una tabla a partir de pares clave-valor

Generando una tabla a partir de pares clave-valor

Quiero generar automáticamente una tabla a partir de una lista separada por comas de pares clave-valor, como {{x=1,y=2},{x=3,y=1},{x=2,y=3}}. En cada "fila", las "claves" son siempre las mismas (se convierten en los encabezados de las columnas de la tabla), pero los valores pueden cambiar. Dada esta lista particular de pares clave-valor, el siguiente código produce lo siguiente:

ingrese la descripción de la imagen aquí

que es exactamente lo que quiero. Desafortunadamente, una entrada ligeramente más complicada para los valores, como {{x=1,y=$\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}, rompe mi código. Mi código genera el resultado esperado:

ingrese la descripción de la imagen aquí

pero antes de hacer esto me salen errores de compilación como:

! Número de parámetro ilegal en la definición de \tableRows. \crcr l.40 ...\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}

¿Existe una forma mejor o más sólida de hacer esto?

Aquí está mi código:

\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}

Para generar las filas de la tabla utilizo \protected@xapptodesde elcaja de herramientaspaquete. Inicialmente intenté usar tokens pero tuve problemas de expansión, sin duda debido a mi ignorancia. No tengo claro por qué necesito \protected@xappto, en lugar de \xappto, pero si uso \xapptoel código falla con ambos ejemplos.

La forma en que extraigo los pares clave-valor también parece un poco OTT: estoy usando algunos trucos con \SplitArgumenty\DeclareDocumentCommand desde elxparsepaquete para hacer esto.

EDITAR

Resulta que mi código funciona en su mayor parte y tuve un poco de mala suerte porque \overrightarrowfue uno de los primeros ejemplos que utilicé en el código real del que se destila mi MWE. El problema con mi MWE es que\overrightarrow es unfrágildominio. Puedo corregir el error de compilación en MWE agregando las líneas:

\usepackage{fixltx2e}
\MakeRobust{\overrightarrow}

Sin embargo, esta no es una solución completa porque seguramente habrá otros comandos frágiles que romperán mi código... El enfoque de Christian de generar la tabla, en lugar de almacenarla, es probablemente la mejor solución.

Respuesta1

Una solución que utiliza el paquete kvsetkeyspara analizar las listas de comas y valores clave. La especificación de la tabla, la fila del encabezado y el cuerpo se construyen primero en registros de token. Luego se compone la mesa.

Dado que todas las asignaciones se mantienen locales en un grupo, \MakeTablese pueden anidar.

El paquete booktabsproporciona \midruleuna línea más bonita debajo de la fila del encabezado de la tabla.

El paquete alphalphproporciona \AlphAlphnumeración alfabética de más de 26 filas.

Ejemplo completo:

\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}

Resultado

Respuesta2

Aquí hay una versión sin valores-clave (primero), lo único que debe hacerse manualmente es adaptar el número de columnas aquí.

\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}

ingrese la descripción de la imagen aquí

Actualizar

\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}

ingrese la descripción de la imagen aquí

Respuesta3

Realmente no deseas utilizar \protected@xappto, sino más bien \xapptoy \expandonce.

(También arreglé las protecciones de final de línea).

\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}

ingrese la descripción de la imagen aquí

Una expl3implementación en la que uso (o abuso, tal vez) la interfaz clave-valor.

\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}

La secuencia realiza un seguimiento de los encabezados de las columnas; en la variable de lista de tokens almaceno las filas.

Por supuesto, como en la implementación original, el orden debe ser estricto o los artículos se extraviarían.

ingrese la descripción de la imagen aquí

Respuesta4

Un enfoque utilizando el paquete.herramientas xint.

Como en otras implementaciones, el orden debe ser estricto. Se necesita un enfoque más complicado si se quiere un orden arbitrario para las claves.

\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}

cita en bloque

información relacionada