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.

Faheem Mitha
  • 7,778
  • I can't see why using \multirow and not the simpler \multicolumn{1}{p{2in}}{...} – egreg Jan 09 '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}? – Faheem Mitha Jan 09 '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 09 '14 at 17:42
  • @FaheemMitha: I've added an explanation of the problem you ran into. – Werner Jan 11 '14 at 03:43

1 Answers1

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.

Werner
  • 603,163