How do I use the ampersand (&) inside a foreach or conditional (or other group/environment) when building tables?
You're missing the fact that \foreach
executes each cycle in a group, so when finishing it the change to \@tabtoks
is undone.
Add \global
in the relevant places (and also \arraybackslash
in the specification for the last column, but this is another problem).
\documentclass[10pt,a4paper]{article}
\usepackage{tabularx}
\usepackage{tikz}
\newcommand{\nRows}{5}
\makeatletter
\newtoks\@tabtoks
%%% assignments to \@tabtoks must be global, because they are done in \foreach
\newcommand\addtabtoks[1]{\global\@tabtoks\expandafter{\the\@tabtoks#1}}
%%% variable should always be operated on always locally or always globally
\newcommand*\resettabtoks{\global\@tabtoks{}}
\newcommand*\printtabtoks{\the\@tabtoks}
\makeatother
\begin{document}
\resettabtoks
\foreach \r in {1,...,\nRows}{
\addtabtoks{\textbf{column1} & two & three & four & five \\\hline}%
}
\noindent\begin{tabularx}{\textwidth}{
@{}>{\raggedright}p{2.2cm}
@{}>{\raggedright}X
@{}>{\raggedright}X
@{}>{\raggedright}X
@{}>{\raggedright\arraybackslash}X
@{}
}
\printtabtoks
\end{tabularx}
\end{document}
A more complex example
Suppose we want each table row to depend on the loop index; then some change has to be made. The new \addtabtoks
macro takes an optional argument that, if expressed, should be the command representing the index in the \foreach
loop; the mandatory argument, in this case, should be a one parameter macro, like in the code below; the parameter should be the loop index.
\documentclass[10pt,a4paper]{article}
\usepackage{tabularx}
\usepackage{tikz}
\newcommand{\nRows}{5}
\makeatletter
\newtoks\@tabtoks
%%% assignments to \@tabtoks must be global, because they are done in \foreach
\newcommand\addtabtoks[2][]{%
\if\relax\detokenize{#1}\relax
% no index, just append the second argument
\global\@tabtoks\expandafter{\the\@tabtoks#2}%
\else
% we assume the second argument is a one parameter macro
\global\@tabtoks\expandafter{\the\expandafter\@tabtoks\expandafter#2\expandafter{#1}}%
\fi
}
%%% variable should always be operated on always locally or always globally
\newcommand*\resettabtoks{\global\@tabtoks{}}
\newcommand*\printtabtoks{\the\@tabtoks}
\makeatother
% define a one parameter macro for making the row depending on the current index
\newcommand{\tablerow}[1]{%
\textbf{Row #1} & two & three & four & five \\\hline
}
\begin{document}
\resettabtoks
\foreach \r in {1,...,\nRows}{%
\addtabtoks{\textbf{column1} & two & three & four & five \\\hline}%
}
\noindent\begin{tabularx}{\textwidth}{
@{}>{\raggedright}p{2.2cm}
@{}>{\raggedright}X
@{}>{\raggedright}X
@{}>{\raggedright}X
@{}>{\raggedright\arraybackslash}X
@{}
}
\hline
\printtabtoks
\end{tabularx}
\bigskip
\resettabtoks
\foreach \r in {1,...,\nRows}{%
\addtabtoks[\r]{\tablerow}%
}
\noindent\begin{tabularx}{\textwidth}{
@{}>{\raggedright}p{2.2cm}
@{}>{\raggedright}X
@{}>{\raggedright}X
@{}>{\raggedright}X
@{}>{\raggedright\arraybackslash}X
@{}
}
\hline
\printtabtoks
\end{tabularx}
\end{document}
Presumably you are doing this because you want the rows of the table to depend on the counter in the loop. In this case you will need a way to add expanded objects to the list of tokens. Here I add an \eaddtabtoks
macro in addition to egreg's corrections to the global assignments.
\documentclass[10pt,a4paper]{article}
\usepackage{tabularx,etoolbox}
\usepackage{tikz}
\newcommand{\nRows}{5}
\makeatletter
\newtoks\@tabtoks
\newcommand\addtabtoks[1]{\global\@tabtoks\expandafter{\the\@tabtoks#1}}
\newcommand\eaddtabtoks[1]{\edef\mytmp{#1}\expandafter\addtabtoks\expandafter{\mytmp}}
\newcommand*\resettabtoks{\global\@tabtoks{}}
\newcommand*\printtabtoks{\the\@tabtoks}
\makeatother
\begin{document}
\resettabtoks
\foreach \r in {1,...,\nRows}{
\addtabtoks{\textbf{column 1} &}
\eaddtabtoks{row \r}
\addtabtoks{ & three & four & five \\}%
}
\begin{tabularx}{\textwidth}{@{}>{\raggedright\arraybackslash}p{2.2cm}*{4}{@{}>{\raggedright\arraybackslash}X}@{}}
\printtabtoks
\end{tabularx}%
\end{document}
If your are open to using (here) another loop than \foreach
then there are options, like \xintFor
from package xinttools. The syntax is a bit different, and using \xintFor
at this place does not exclude using \foreach
at others...
Update adds a second method for people loving the bleeding edge.
\documentclass[10pt,a4paper]{article}
\usepackage{tabularx}
\usepackage{tikz}
\usepackage{xinttools}
\newcommand{\nRows}{5}
% \usepackage{tabu} % alternative to tabularx
\begin{document}
\begin{tabularx}{\textwidth}{@{}>{\raggedright}p{2.2cm}@{}>{\raggedright}X@{}>{\raggedright}X@{}>{\raggedright}X@{}>{\raggedright\arraybackslash}X@{}}
\xintFor* #1 in {\xintSeq {1}{\nRows}}
\do
{(\textsl{row #1}) \textbf{column1} & two & three & four & five
\\ }
\end{tabularx}
% Note with a tabu environment of package tabu, \arraybackslash is not needed
% \begin{tabu} to \textwidth{@{}>{\raggedright}p{2.2cm}@{}>{\raggedright}X@{}>{\raggedright}X@{}>{\raggedright}X@{}>{\raggedright}X@{}}
% \xintFor* #1 in {\xintSeq {1}{\nRows}}
% \do
% {(\textsl{row #1}) \textbf{column1} & two & three & four & five
% \\ }
% \end{tabu}
\end{document}
Here is an idiosyncratic way which relies on an expandable loop (\xintFor
is not expandable despite the fact it enjoys tabulars), and without counters! (\xintFor
does not use counters either). \xintiloopindex
can not be within braces; and in this example we need to hide the tabulations, for the \xintiloopindex
mechanism to work, because non-expandable material is encountered before.
\documentclass[10pt,a4paper]{article}
\usepackage{tabularx}
\usepackage{tikz}
\usepackage{xinttools}
\newcommand{\nRows}{5}
\def\TAB{&}
\begin{document}
\begin{tabularx}{\textwidth}{@{}>{\raggedright}p{2.2cm}@{}>{\raggedright}X@{}>{\raggedright}X@{}>{\raggedright}X@{}>{\raggedright\arraybackslash}X@{}}
\xintiloop [1+1]
(\bgroup\slshape row \xintiloopindex\egroup )% \xintiloopindex can not be
% within braces...
\textbf{column1} \TAB two \TAB three \TAB four \TAB five \\
\ifnum\nRows>\xintiloopindex\space
\repeat
\end{tabularx}
\end{document}