0

I try to obtain alternate colored rows (just like the table is shown in the figure) using the datatool package and the \rowcolor command. I am following the datatool user manual example for striped tables, but it is not enterely clear. The issue for me is how to color the first row, because the manual does not really deal with that.

It looks like \rowcolor, \DTLifoddrow, \DTLiffirstrow and \DTLiflastrow has themselves certain rules which I can't understand fully.

Is there a more compact way to do this? I mean, why it is not possible to call all the \rowcolor statements inside the loop?

\documentclass{article}

\usepackage[table]{xcolor}
\usepackage{datatool}

\begin{filecontents*}{data.csv}
position
first
second
third
fourth
\end{filecontents*}

\DTLloaddb{rows}{data.csv}

\begin{document}

\begin{tabular}{c}
\hline
\rowcolor{orange} % <- redundant? really needed?
\DTLforeach*{rows}{\word=position}{
    \word%
    \DTLiflastrow{}{\\\DTLifoddrow{\rowcolor{gray}}{\rowcolor{orange}}}%
    }
\\ \hline

\end{tabular}

\end{document}

enter image description here

1 Answer 1

1

Since your goal seems to be to reduce redundancy, I suggest using something like this, or the more flexible variant given below:

\RequirePackage{filecontents}
\begin{filecontents*}{data.csv}
position
first
second
third
fourth
\end{filecontents*}

\documentclass{article}
\usepackage[table]{xcolor}
\usepackage{datatool}

\colorlet{OddRowColor}{orange}%
\colorlet{EvenRowColor}{gray!20}%

\DTLloaddb{rows}{data.csv}

\begin{document}

\begin{tabular}{c}
\rowcolor{OddRowColor}%
\DTLforeach*{rows}{\word=position}{%
  \word
  \\ % ending the row, \noalign can thus be used here
  \DTLiflastrow{}{% This is the color for the *next* row, hence the odd/even
                  % shift compared to what datatool just processed
    \DTLifoddrow{\rowcolor{EvenRowColor}}{\rowcolor{OddRowColor}}%
  }%
}
\end{tabular}

\end{document}

Screenshot

Note regarding filecontents: if your LaTeX2e release is from October 2019 or more recent, it has features that render the filecontents package obsolete. In this case, you can replace the part preceding \documentclass with the following (see here for details, in particular the LaTeX2e News Issue 30):

\begin{filecontents}[noheader,overwrite]{data.csv}
position
first
second
third
fourth
\end{filecontents}

Back to our document. If you find the markup too verbose in the tabular, you can define a macro like this:

\newcommand*{\myApplyAlternatingColorsForTable}{%
  \DTLiflastrow{}{% This is the color for the *next* row, hence the odd/even
                  % shift compared to what datatool just processed
    \DTLifoddrow{\rowcolor{EvenRowColor}}{\rowcolor{OddRowColor}}%
  }%
}

Then your tabular can look like:

\begin{tabular}{c}
\rowcolor{OddRowColor}%
\DTLforeach*{rows}{\word=position}{%
  \word
  \\ % ending the row, \noalign can thus be used here
  \myApplyAlternatingColorsForTable
}
\end{tabular}

It's tempting to make \myApplyAlternatingColorsForTable accept the two colors as parameters, but this would miss the first tabular row which also needs its color to be given. So, to get one step further, you would need to define macros before the table that expand to the desired colors, like this:

\RequirePackage{filecontents}
\begin{filecontents*}{data.csv}
position
first
second
third
fourth
\end{filecontents*}

\documentclass{article}
\usepackage[table]{xcolor}
\usepackage{datatool}

\newcommand*{\myApplyAlternatingColorsForTable}{%
  \DTLiflastrow{}{% This is the color for the *next* row, hence the odd/even
                  % shift compared to what datatool just processed
    \DTLifoddrow{\rowcolor{\myColorForEvenRows}}%
                {\rowcolor{\myColorForOddRows}}%
  }%
}

\newcommand*{\mySetColorsForNextTable}[2]{%
  \def\myColorForOddRows{#1}%
  \def\myColorForEvenRows{#2}%
}

\newcommand*{\myApplyColorToFirstRow}{%
  \rowcolor{\myColorForOddRows}%
}

\DTLloaddb{rows}{data.csv}

\begin{document}

\mySetColorsForNextTable{orange}{gray!20}%
\begin{tabular}{c}
\myApplyColorToFirstRow
\DTLforeach*{rows}{\word=position}{%
  \word
  \\ % ending the row, \noalign can thus be used here
  \myApplyAlternatingColorsForTable
}
\end{tabular}

\end{document}

What you probably wanted to do is this:

\begin{tabular}{c}
\DTLforeach*{rows}{\word=position}{%
    \DTLifoddrow{\rowcolor{orange}}{\rowcolor{gray}}%
    \word
    \\
}
\end{tabular}

This doesn't work because of the way \rowcolor and TeX alignments such as \halign (like the tabular environment) work. \rowcolor uses a TeX primitive called \noalign, which can only appear at the beginning of the first line or after a \cr or \crcr (which is used internally by tabular to end a tabular row, when you use \\). When looking for \noalign or \omit (which is special in the same respect as \noalign for this discussion), TeX expands tokens and stops at the first non-expandable, non-space token (cf. TeXbook p. 240).

When using \rowcolor at the beginning or end of a line, the first non-expandable, non-space token found is \noalign, therefore it works fine. But when using something even simpler than the above non-working code:

\begin{tabular}{c}
\DTLforeach*{rows}{\word=position}{%
    \rowcolor{blue}%
    \word
    \\
}
\end{tabular}

you get an error like so:

./thefile.tex:23: Misplaced \noalign.
\rowcolor ->\noalign 
                     {\ifnum 0=`}\fi \global \let \CT@do@color \CT@@[email protected] }

This is because \DTLforeach is expanded as part of the aforementioned process during which TeX expands tokens looking for \noalign or \omit, and this stops at a non-expandable, non-space token way before \rowcolor got a chance to be expanded and yield its \noalign. Indeed, in order to check if its star form is used, \DTLforeach starts by calling \new@ifnextchar, which contains a \let in its expansion, and \let is precisely a non-expandable, non-space token. At this point, using \noalign before the next \cr or \crcr,1 becomes invalid. TeX then processes the start template of the next column (what you can define with >{...} when using the array package), then the entry contents before getting to the column's end template. While processing the entry contents, TeX expands the \rowcolor macro following \DTLforeach*{rows}{\word=position}{, and this expansion yields the \noalign we already mentioned. Unfortunately, it is too late for \noalign to be valid here, because of the \let obtained from the expansion of \DTLforeach. \noalign will only be valid to see again after the next \cr or \crcr.

Are \DTLiflastrow and \DTLifoddrow safe?

You may wonder why \DTLiflastrow and \DTLifoddrow appear to work in our example, where \DTLforeach* doesn't. Well... because you've been lucky! Update: we are explaining a bug present in datatool 2018/12/07 v2.31; in the meantime, it has been fixed in datatool 2019/09/27 v2.32.

Look at this definition:

 \gdef\DTLiflastrow##1##2{%
   \expandafter\ifnum
    \csname c@DTLrow\romannumeral\dtlforeachlevel\endcsname
    =\csname dtlrows@#2\endcsname\relax
     ##1%
   \else
     ##2%
   \fi}%

There was a little bug (or, let's say, undesirable behavior): it works better without the \relax. In order to trigger the bug (only present in datatool < 2.32), try to use \rowcolor in the 〈true〉 clause of \DTLiflastrow, you'll get a “Misplaced \noalign” error, just as we explained. So, with “old” datatool (e.g., 2018/12/07 v2.31), \DTLiflastrow can only contain \noalign material in the 〈false〉 clause. You weren't hit by the bug because TeX skips over the \relax without seeing it when the \DTLiflastrow test is false (i.e., when it is looking for the \else token), and you were only using \noalign material in the 〈false〉 clause. But the problem is easy to correct, and the fix is now in datatool 2019/09/27 v2.32: since \dtlrows@DBNAME is a \countdef token, it is a 〈number〉 according to TeX's grammar, therefore the \relax wasn't needed. If one removes the \relax (beware, there are two places that do \gdef\DTLiflastrow in datatool.sty, one for \DTLforeach and one for \DTLforeach*), then one can manually append, say, a green line containing abc to the tabular, like this:

\RequirePackage{filecontents}
\begin{filecontents*}{data.csv}
position
first
second
third
fourth
\end{filecontents*}

\documentclass{article}
\usepackage[table]{xcolor}
\usepackage{datatool}

\newcommand*{\myApplyAlternatingColorsForTable}{%
  \DTLiflastrow{\rowcolor{green}}{%
    \DTLifoddrow{\rowcolor{\myColorForEvenRows}}%
                {\rowcolor{\myColorForOddRows}}%
  }%
}

\newcommand*{\mySetColorsForNextTable}[2]{%
  \def\myColorForOddRows{#1}%
  \def\myColorForEvenRows{#2}%
}

\newcommand*{\myApplyColorToFirstRow}{%
  \rowcolor{\myColorForOddRows}%
}

\DTLloaddb{rows}{data.csv}

\begin{document}

\mySetColorsForNextTable{orange}{gray!20}%
\begin{tabular}{c}
\myApplyColorToFirstRow
\DTLforeach*{rows}{\word=position}{%
  \word
  \\ % ending the row, \noalign can thus be used here
  \myApplyAlternatingColorsForTable
}
abc
\end{tabular}

\end{document}

Screenshot

\DTLifoddrow didn't have this bug (we would have noticed since we used both its 〈true〉 and 〈false〉 clauses), but \DTLiffirstrow had it for both \DTLforeach and \DTLforeach* in datatool < 2.32. All this has been fixed in datatool 2019/09/27 v2.32. The above example works out of the box with that version.


Footnote

  1. End-of-line primitives for TeX alignments (\halign and \valign), internally used by tabular, array and friends.
12
  • Maybe I was no clear. I want no headers. I just would to know why it is not possible to call all the \rowcolor statements inside the loop.
    – e_moro
    Commented May 24, 2019 at 9:27
  • This is because \rowcolor uses \noalign and thus can only be called in specific places, I'll explain that in the answer.
    – frougon
    Commented May 24, 2019 at 9:44
  • I have updated my answer with detailed explanations and uncovered a bug in \DTLiflastrow when using \noalign material such as \rowcolor in the 〈true〉 clause (\DTLiffirstrow has the same bug, maybe also other tests). I've explained how to fix the bug, but of course, it should be reported to the package author (my version of datatool.sty is the last to this date).
    – frougon
    Commented May 24, 2019 at 14:03
  • I have submitted a report on datatool's bug tracker. According to Nicola's site, it may need a few days to appear because of spam filtering measures.
    – frougon
    Commented May 24, 2019 at 15:08
  • Excellent explanation. I understand the main ideas, despite my limited knowledge of low level TeX (I'm just a curious end user of LaTeX). Congratulations for finding the bug!
    – e_moro
    Commented May 27, 2019 at 7:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .