5

This question is based on Peter Grill's answer to "Table including rows of a master table"..

The function PrintDTLTable was in Peter's original answer. I wanted a version that would run over all rows, so I motified this slightly to PrintDTLTableDefault, which does not have a row id argument. Both PrintDTLTable and PrintDTLTableDefault work as expected.

I think it makes more sense to combine these into a single function, so I tried using ifthenelse and this gave PrintDTLTableCombined. This gives the error

ERROR: Argument of \isempty has an extra }.

I don't see any obvious syntax or usage errors. As usual with LaTeX, the error could easily be something completely unrelated to the error message. I don't have to use ifthenelse; something else would be fine.

\documentclass{article}
\usepackage{array}
\usepackage{multirow}
\usepackage{datatool}
\usepackage{longtable}
\usepackage{filecontents}
\usepackage{xifthen}
\newcommand{\colhead}[1]{\multicolumn{1}{>{\bfseries}l}{#1}}
\newcounter{tabenum}\setcounter{tabenum}{0}
\newcommand{\nextnuml}[1]{\refstepcounter{tabenum}\thetabenum.\label{#1}}

\begin{filecontents*}{foo.dat}
  Hammer001,   Hammer,    1 ,  0 , 1 , 10 , 1 , \multirow{2}{2in}{light (add some words here to wrap around)}\\
  Hammer002,   Hammer,    2 ,  0 , 1 , 10 , 1 , heavy
  Hammer003,   Hammer,    3 ,  0 , 1 , 10 , 1 , really heavy
  Longsword001,Longsword, 1 , -1 , 2 , 75 , 2 , one-handed
  Longsword002,Longsword, 2 , -1 , 2 , 75 , 2 , two-handed
  Longsword003,Longsword, 3 , -1 , 2 , 75 , 2 , three-handed
\end{filecontents*}

\newcommand{\PrintDTLTable}[2]{%
  % #1 = database to search
  % #2 = list of rowIDs
  \begin{tabular}{c c c c c c c p{3.0cm}}
    & \colhead{Label} & \colhead{Cost} & \colhead{Weight} & \colhead{PropA} & \colhead{PropB} & \colhead{PropC} & \colhead{Description}\\\hline
    \DTLforeach[\DTLisSubString{#2}{\RowID}]{#1}{%
      \RowID=RowID,%
      \Label=Label,%
      \Cost=Cost,%
      \Weight=Weight,%
      \PropA=PropA,%
      \PropB=PropB,%
      \PropC=PropC,%
      \Description=Description%
    }{%
      \nextnuml{\RowID} & \Label &\Cost & \Weight & \PropA & \PropB & \PropC & \Description \\
    }%
  \end{tabular}
}%

\newcommand{\PrintDTLTableDefault}[1]{%
  % #1 = database to search
  \begin{longtable}{c c c c c c c p{3.0cm}}
    & \colhead{Label} & \colhead{Cost} & \colhead{Weight} & \colhead{PropA} & \colhead{PropB} & \colhead{PropC} & \colhead{Description}\\\hline
    \DTLforeach[\boolean{true}]
    {#1}{%
      \RowID=RowID,%
      \Label=Label,%
      \Cost=Cost,%
      \Weight=Weight,%
      \PropA=PropA,%
      \PropB=PropB,%
      \PropC=PropC,%
      \Description=Description%
    }{%
      \nextnuml{\RowID} & \Label &\Cost & \Weight & \PropA & \PropB & \PropC & \Description \\
    }%
  \end{longtable}
}%

\newcommand{\PrintDTLTableCombined}[2][]{%
 % #1 = list of rowIDs
 % #2 = database to search
  \begin{longtable}{c c c c c c c p{3.0cm}}
    & \colhead{Label} & \colhead{Cost} & \colhead{Weight} & \colhead{PropA} & \colhead{PropB} & \colhead{PropC} & \colhead{Description}\\\hline
    \DTLforeach
    [\ifthenelse{\isempty{#1}}{\boolean{true}}{\DTLisSubString{#1}{\RowID}}]
    {#2}{%
      \RowID=RowID,%
      \Label=Label,%
      \Cost=Cost,%
      \Weight=Weight,%
      \PropA=PropA,%
      \PropB=PropB,%
      \PropC=PropC,%
      \Description=Description%
    }{%
      \nextnuml{\RowID} & \Label &\Cost & \Weight & \PropA & \PropB & \PropC & \Description \\
    }%
  \end{longtable}
}%

\begin{document}
% \DTLsetseparator{&}% Define separator of the data
\DTLloaddb[noheader,keys={RowID,Label,Cost,Weight,PropA,PropB,PropC,Description}]{myDB}{foo.dat}

%\PrintDTLTable{myDB}{Hammer001,Hammer003,Longsword003}
%\PrintDTLTableDefault{myDB}
\PrintDTLTableCombined{myDB}

This is a reference to ~\ref{Hammer003}.

\end{document}

UPDATE: @Werner's macro works for me. However, I'm still would like to know what made my attempt fail; maybe it would be instructive. LaTeX is frustrating for a beginner, partly because it frequently seems impossible to debug for a non-expert.

4
  • I can't see why using \multirow and not the simpler \multicolumn{1}{p{2in}}{...}
    – egreg
    Jan 9 '14 at 17:36
  • @egreg: The \multirow thing is a detail I should probably have left out. I've thought of using p, but why do I need to wrap it in \multicolumn{1}? Jan 9 '14 at 17:40
  • If it's an "exceptional" cell, you need \multicolumn{1} for overriding the standard alignment for the column. Or just specify the whole column as p{2in}. Note that with the \multicolumn approach, the rules automatically take care of the width, which doesn't happen with \multirow.
    – egreg
    Jan 9 '14 at 17:42
  • @FaheemMitha: I've added an explanation of the problem you ran into.
    – Werner
    Jan 11 '14 at 3:43
4

It works when you perform the "check for empty arguments" using

% http://tex.stackexchange.com/q/308/5764
\makeatletter
\def\ifemptyarg#1{%
  \if\relax\detokenize{#1}\relax % H. Oberdiek
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi}
\makeatother

instead of the xifthen \ifthenelse:

\newcommand{\PrintDTLTableCombined}[2][]{%
 % #1 = list of rowIDs
 % #2 = database to search
  \begin{longtable}{c c c c c c c p{3.0cm}}
    & \colhead{Label} & \colhead{Cost} & \colhead{Weight} & \colhead{PropA} & \colhead{PropB} & \colhead{PropC} & \colhead{Description}\\\hline

    \DTLforeach
    [\ifemptyarg{#1}{\boolean{true}}{\DTLisSubString{#1}{\RowID}}]
    {#2}{%
      \RowID=RowID,%
      \Label=Label,%
      \Cost=Cost,%
      \Weight=Weight,%
      \PropA=PropA,%
      \PropB=PropB,%
      \PropC=PropC,%
      \Description=Description%
    }{%
      \nextnuml{\RowID} & \Label &\Cost & \Weight & \PropA & \PropB & \PropC & \Description \\
    }%
  \end{longtable}
}%

Checking for an empty argument is sometimes problematic, since some uses could require an assignment, which is not allowed.


An analysis of why your approach does not work stems from how datatool evaluates conditionals. Inside the bowels of \DTLforeach[#1]{#2}{#3}{#4} is a test

\ifthenelse{#1}{<true>}{<false>}

The datatool user guide supports this (section 5.4 Iterating Through a Database, p 50):

\DTLforeach[<condition>]{<db name>}{<assign list>}{<text>}

The optional argument <condition> is a condition in the form allowed by \ifthenelse. This includes the commands provided by the ifthen package (such as \not, \and, \or), as well as the commands described in section 2.2. The default value of <condition> is \boolean{true}.

So, effectively what you're trying to implement is a nested conditional that resembles

\ifthenelse{\ifthenelse{<condition>}{<trueA>}{<falseA>}}{<trueB>}{<falseB>}

A minimal example that replicates the problem is given by

\documentclass{article}
\usepackage{xifthen}% http://ctan.org/pkg/xifthen
\begin{document}
\ifthenelse{\ifthenelse{\boolean{true}}{\boolean{true}}{\boolean{false}}}{A}{B}
\end{document}

While the above should (logically) yield A as output (since the nested condition should yield \boolean{true}, which evaluated is true and should output A), you receive the error

! Argument of \boolean has an extra }.
<inserted text> 
                \par 
l.4 ...an{true}}{\boolean{true}}{\boolean{false}}}
                                                  {A}{B}

There's no other way around it but to use a different method for deriving a result to condition on, since you are using the condition to decide whether/not to print a row. That is, you can't evaluate the condition outside \DTLforeach.

2
  • 2
    Instead of ifthen, I suggest to use etoolbox: \ifstrempty{string}{<case for empty>}{<case for non empty>}
    – egreg
    Jan 9 '14 at 17:46
  • @egreg The \ifstrempty works too, thanks. Jan 9 '14 at 19:48

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.