![Gerando uma tabela a partir de pares de valores-chave](https://rvso.com/image/330737/Gerando%20uma%20tabela%20a%20partir%20de%20pares%20de%20valores-chave.png)
Quero gerar automaticamente uma tabela a partir de uma lista separada por vírgulas de pares de valores-chave, como {{x=1,y=2},{x=3,y=1},{x=2,y=3}}
. Em cada "linha" as "chaves" são sempre as mesmas (tornam-se os cabeçalhos das colunas da tabela), mas os valores podem mudar. Dada esta lista específica de pares de valores-chave, o código abaixo produz o seguinte:
que é exatamente o que eu quero. Infelizmente, entradas um pouco mais complicadas para os valores, como {{x=1,y=$\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}
, quebram meu código. Meu código gera a saída esperada:
mas antes disso, recebo erros de compilação, como:
! Número de parâmetro ilegal na definição de \tableRows. \crcr l.40 ...\overrightarrow{AB}$},{x=3,y=1},{x=2,y=3}}
Existe uma maneira melhor/mais robusta de fazer isso?
Aqui está meu 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 gerar as linhas da tabela que utilizo \protected@xappto
a partir doetoolboxpacote. Inicialmente tentei usar tokens, mas tive problemas de expansão, sem dúvida por minha ignorância. Não está claro para mim por que preciso \protected@xappto
de , em vez de \xappto
, mas se eu usar \xappto
o código falhará em ambos os exemplos.
A maneira como extraio os pares de valores-chave também parece um pouco OTT: estou usando alguns truques com \SplitArgument
e \DeclareDocumentCommand
a partir doxparsepacote para fazer isso.
EDITAR
Acontece que meu código funciona principalmente e tive um pouco de azar, pois \overrightarrow
foi um dos primeiros exemplos que usei no código real do qual meu MWE foi destilado. O problema com meu MWE é que \overrightarrow
é umfrágilcomando. Posso corrigir o erro de compilação no MWE adicionando as linhas:
\usepackage{fixltx2e}
\MakeRobust{\overrightarrow}
Esta não é uma solução completa, entretanto, porque certamente haverá outros comandos frágeis que quebrarão meu código... A abordagem de Christian de gerar a tabela, em vez de armazená-la, é provavelmente a melhor solução.
Responder1
Uma solução que usa pacote kvsetkeys
para analisar as listas de vírgulas e valores-chave. A especificação da tabela, a linha do cabeçalho e o corpo são primeiro construídos em registradores de token. Então a tabela é composta.
Como todas as atribuições são mantidas localmente em um grupo, \MakeTable
podem ser aninhadas.
O pacote booktabs
fornece \midrule
uma linha melhor abaixo da linha do cabeçalho da tabela.
O pacote alphalph
fornece \AlphAlph
numeração alfabética de mais de 26 linhas.
Exemplo 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{}%
}
\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}
Responder2
Aqui está uma versão sem valores-chave (primeiro), a única coisa que precisa ser feita manualmente é adaptar o número de colunas aqui.
\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}
Atualizar
\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}
Responder3
Você realmente não quer usar \protected@xappto
, mas sim \xappto
and \expandonce
.
(Também consertei as proteções de fim de linha.)
\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}
Uma expl3
implementação, onde uso (ou abuso, talvez) a interface de valor-chave.
\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}
A sequência controla os cabeçalhos das colunas; na variável da lista de tokens eu armazeno as linhas.
É claro que, como na implementação original, a ordem deve ser rigorosa ou os itens serão perdidos.
Responder4
Uma abordagem usando pacotexinttools.
como em outras implementações, a ordem deve ser rigorosa. Uma abordagem mais complicada é necessária se quisermos uma ordem arbitrária para as chaves.
\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}