2

I am trying to build a variable size table programmatically. All the logic works separate from the table commands, and the table commands work separate from the programmatic logic, but somehow when I put them together I get errors I don't understand.

I have created a minimal working example showing how it works and removing everything I thought was extraneous. It is shown below, giving an example of how the input data is stored/retreived, what the table is supposed to look like, and where it hangs up:

\documentclass{article}
\usepackage{tabularx}

\newcount\tmpnum \newcounter{tmpind} \newcounter{basesize}
\setcounter{tmpind}{0} \setcounter{basesize}{15} %% for 5 lines of three columns in empty table; table should expand if data count exceeds this size

\newif\iftestEmptyBool \newcommand{\testEmpty}[1]{\setbox0=\hbox{#1}\ifdim\wd0=0pt \testEmptyBooltrue\else \testEmptyBoolfalse\fi}

\def\storedata#1#2{\tmpnum=0 \edef\tmp{\string#1}\storedataA#2\end\expandafter\def\csname data:\tmp:0\endcsname{\tmpcnt}}
\def\storedataA#1{\advance\tmpnum by1
   \ifx\end#1\else
      \expandafter\def\csname data:\tmp:\the\tmpnum\endcsname{#1}%
      \expandafter\storedataA\fi
}

\def\getdata[#1]#2{\csname data:\string#2:#1\endcsname}

\def\funcOne{\addtocounter{tmpind}{1}\testEmpty{\getdata[\thetmpind]\myData}\iftestEmptyBool\else\funcOne\fi}

\def\funcTwo{
\ifnum\the\value{tmpind}>0
  \ifnum\the\value{tmpind}<\the\value{basesize}\fi%&&\\ \large - & \large - & \large - \\ \hline\fi%
  \ifnum\the\value{tmpind}=\the\value{basesize}\addtocounter{tmpind}{-3} \fi%&&\\ \getdata[\thetmpind-2]\myData & \getdata[\thetmpind-1]\myData &  \getdata[\thetmpind]\myData \\ \hline\fi%
  \addtocounter{basesize}{-3}
  \funcTwo
\fi
}

\newcommand{\buildTable}{\funcOne\addtocounter{tmpind}{-1}\begin{tabularx}{\linewidth}{|c|c|X|c|}\hline Column 1 &  Column2 & Column3\\&&\\ \hline\funcTwo\end{tabularx}}

\begin{document}
\storedata\myData{{data1}{data2}{data3}{data4}{data5}{data6}}\funcOne\addtocounter{tmpind}{-1}The number of stored items is \thetmpind.

\vspace{10pt}Example of how it should look:

\begin{tabularx}{\linewidth}{|c|c|X|c|} \hline
Column 1 &  Column2 & Column3\\ &&\\ \hline
&&\\ \large - & \large - & \large - \\ \hline
&&\\ \large - & \large - & \large - \\ \hline
&&\\ \large - & \large - & \large - \\ \hline
&&\\ \getdata[4]\myData & \getdata[5]\myData &  \getdata[6]\myData \\ \hline
&&\\ \getdata[1]\myData & \getdata[2]\myData &  \getdata[3]\myData \\ \hline
\end{tabularx}
\vspace{10pt}How it does look:

\buildTable
\end{document}

The commented sections at the end of the two \ifnum lines in \funcTwo can be put back by deleting the \fi% at the end of the active code in those rows to reproduce the fault.

If anybody has an idea of why this is happening and how to get around it, that would be totally awesome! :)

1

In case anyone else is struggling with a similar problem, I am posting the solution I found (even though it indicates I was missing a crucial understanding of something simple):

In my example I had a spelled out table showing what I wanted:

\vspace{10pt}Example of how it should look:

\begin{tabularx}{\linewidth}{|c|c|X|c|} \hline
Column 1 &  Column2 & Column3\\ &&\\ \hline
&&\\ \large - & \large - & \large - \\ \hline
&&\\ \large - & \large - & \large - \\ \hline
&&\\ \large - & \large - & \large - \\ \hline
&&\\ \getdata[4]\myData & \getdata[5]\myData &  \getdata[6]\myData \\ \hline
&&\\ \getdata[1]\myData & \getdata[2]\myData &  \getdata[3]\myData \\ \hline
\end{tabularx}

In this table, the \getdata[?]\mydata commands hard reference particular counter/index values.

In my \buildTable command, I was using a function \funcTwo where I tried to use relative indexing:

\def\funcTwo{
\ifnum\the\value{tmpind}>0
  \ifnum\the\value{tmpind}<\the\value{basesize} &&\\ \large - & \large - & \large - \\ \hline\fi%
  \ifnum\the\value{tmpind}=\the\value{basesize}\addtocounter{tmpind}{-3} &&\\ \getdata[\thetmpind-2]\myData & \getdata[\thetmpind-1]\myData &  \getdata[\thetmpind]\myData \\ \hline\fi%
  \addtocounter{basesize}{-3}
  \funcTwo
\fi
}

Apparently you can't do that in tex/latex (and given some of the things I've read that makes sense, but I hadn't consciously considered it before). By replacing the relative references with verbose \addtocounter calls, I achieved the same effect.

\def\funcTwo{
\ifnum\the\value{tmpind}>0
  \ifnum\the\value{tmpind}<\the\value{basesize}\addtocounter{basesize}{-3}\emptyRow\fi
  \ifnum\the\value{tmpind}=\the\value{basesize}\addtocounter{basesize}{-3}\filledRow\fi
  \funcTwo
\fi}

\newcommand{\emptyRow}{%
&&\\ \large - & \large - & \large - \\ \hline}
\newcommand{\filledRow}{%
&&\\  \addtocounter{tmpind}{-2}\getdata[\thetmpind]\myData 
& \addtocounter{tmpind}{1}\getdata[\thetmpind]\myData 
&  \addtocounter{tmpind}{1}\getdata[\thetmpind]\myData
\addtocounter{tmpind}{-3} \\ \hline}

This produces the same result as the hard coded table, but adjusts based on the amount of data present. :)

| improve this answer | |
1

It's a common problem: a conditional cannot straddle two (or more) alignment cells.

Here's a simpler implementation in xparse:

\documentclass{article}
\usepackage{xparse}
\usepackage{tabularx}

\newcounter{basesize}
\setcounter{basesize}{15}

\ExplSyntaxOn

% \storedata{<name>}{item1,item2,...}
\NewDocumentCommand{\storedata}{mm}
 {
  \seq_clear_new:c { l_cooper_data_#1_seq }
  \seq_set_from_clist:cn { l_cooper_data_#1_seq } { #2 }
 }

% \buildTable{<name>}
\NewDocumentCommand{\buildTable}{m}
 {
  \cooper_buildtable:n { #1 }
 }

% variables
\seq_new:N \l_cooper_reverse_seq
\tl_new:N \l_cooper_tablebody_tl
\int_new:N \l_cooper_column_int

% internal function
\cs_new_protected:Nn \cooper_buildtable:n
 {
  % use a temporary sequence
  \seq_set_eq:Nc \l_cooper_reverse_seq { l_cooper_data_#1_seq }
  % if it has less elements than stated in basesize fill with `--`
  \int_compare:nT { \seq_count:c { l_cooper_data_#1_seq } < \value{basesize} }
   {
    \prg_replicate:nn { \value{basesize} - \seq_count:c { l_cooper_data_#1_seq } }
     {
      \seq_put_right:Nn \l_cooper_reverse_seq { -- }
     }
   }
  % reverse the sequence
  \seq_reverse:N \l_cooper_reverse_seq
  % start preparing the body
  \tl_clear:N \l_cooper_tablebody_tl
  \int_zero:N \l_cooper_column_int
  % fill three rows and then go to a new one
  \seq_map_inline:Nn \l_cooper_reverse_seq
   {
    \int_compare:nT { \l_cooper_column_int = 0 }
     {
      \tl_put_right:Nn \l_cooper_tablebody_tl { && \\ }
     }
    \int_incr:N \l_cooper_column_int
    \int_compare:nTF { \l_cooper_column_int = 3 }
     {
      \tl_put_right:Nn \l_cooper_tablebody_tl { ##1 \\\hline }
      \int_zero:N \l_cooper_column_int
     }
     {
      \tl_put_right:Nn \l_cooper_tablebody_tl { ##1 & }
     }
   }
  % make the table
  \begin{tabularx}{\linewidth}{|c|c|X|}
  \hline
  Column 1 & Column2 & Column3 \\ &&\\
  \hline
  \tl_use:N \l_cooper_tablebody_tl
  \end{tabularx}
}
\ExplSyntaxOff

\begin{document}

\storedata{myData}{data1,data2,data3,data4,data5,data6}

\noindent
\buildTable{myData}

\end{document}

enter image description here

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.