T-F-S / csvsimple

A LaTeX package for lightweight CSV file processing.
http://www.ctan.org/pkg/csvsimple
LaTeX Project Public License v1.3c
24 stars 5 forks source link

head to column names = false #16

Closed dmyersturnbull closed 2 years ago

dmyersturnbull commented 2 years ago

I'm trying to write a variant of csvautotabular that tolerates macros inside the header. This came up using siunitx in a column -- e.g. "Output radiant flux (\unit{\milli\watt}), as well as textgreek and acro.

From what I can tell, the issue has to do with csvsimple-l3 assigning macros for columns. The documentation lists head to column names = false, but that doesn't seem to solve this issue.

FYI, this is an incredible package. Thanks for maintaining it.

Minimal working example

\documentclass{article}
\RequirePackage{booktabs}
\RequirePackage{csvsimple-l3}

\begin{filecontents*}{animals.csv}
Animal,Length\:(cm)
kitten,1.3
puppy,3.2
\end{filecontents*}

\begin{document}

% This won't work:
%\csvautotabular[]{animals.csv}

% Trying to patch with head to column names :
\csvreader[
  tabular={ll},
  table head = {\csvlinetotablerow \\\midrule},
  table foot = {\bottomrule},
  head to column names = false
]{animals.csv}{}{\csvlinetotablerow}

\end{document}
! Missing \endcsname inserted.
<to be read again> 
\protect 
l.24 ]{animals.csv}{}{\csvlinetotablerow}

The control sequence marked <to be read again> should
not appear between \csname and \endcsname.

[1

{/usr/local/texlive/2021/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./output.aux))

Original macro, for reference

\RequirePackage{booktabs}
\RequirePackage{tabularray}
\RequirePackage{csvsimple-l3}
\RequirePackage{caption}
\RequirePackage{hypcap}

\newsavebox{\tablebox}   % so we can add a footer

\NewDocumentCommand{\gettsv}{m}{
  \begin{lrbox}{\tablebox}
    \csvreader[
       tabularray,    % let's try this out
       table head = {\csvlinetotablerow \\\midrule},
       table foot = {\bottomrule},
       separator=tab,
       head to column names = false
    ]{#1}{}{\csvlinetotablerow}
  \end{lrbox}
}

\NewDocumentCommand{\tablefoot}{m}{
  \usebox{\tablebox}\\[2ex]
  \parbox{\wd\tablebox}{\small#1}
}
T-F-S commented 2 years ago

You cannot use \csvlinetotablerow inside the table head key.

What you could do is the following, using no head to make the head line a normal data line:

\documentclass{article}
\RequirePackage{booktabs}
\RequirePackage{csvsimple-l3}

\begin{filecontents*}{animals.csv}
Animal,Length\:(cm)
kitten,1.3
puppy,3.2
\end{filecontents*}

\begin{document}

\csvreader[
  tabular={ll},
  table head = {\toprule},
  table foot = {\bottomrule},
  no head,
  late after first line = \\\midrule,
]{animals.csv}{}{\csvlinetotablerow}

\end{document}

Currently, you will miss \midrule in the output, because of https://github.com/T-F-S/csvsimple/issues/17 I will address this issue soon.

lvjr commented 2 years ago

In the original code, OP tried to typeset the table with tabularray, but it seems I can not use \csvlinetotablerow with tabularray, even if I put it after \csvexpval:

\documentclass{article}

\RequirePackage{tabularray}
\RequirePackage{csvsimple-l3}

\begin{filecontents*}{animals.csv}
Animal,Length\:(cm)
kitten,1.3
puppy,3.2
\end{filecontents*}

\begin{document}

\csvreader[
  tabularray={ll},
  no head,
]{animals.csv}{}{
  \csvexpval\csvlinetotablerow
}

\end{document}

Are there any mistakes in the code above?

T-F-S commented 2 years ago

Yes, \csvlinetotablerow cannot be use for tabularray, because this uses Data Collection and cannot absorb this macro which actually contains a loop.

You could use the following:

\documentclass{article}

\RequirePackage{tabularray}
\RequirePackage{csvsimple-l3}

\begin{filecontents*}{animals.csv}
Animal,Length\:(cm)
kitten,1.3
puppy,3.2
\end{filecontents*}

\begin{document}

\csvreader[
  tabularray={ll},
  no head,
]{animals.csv}{}{%
  \csvexpval\csvcoli & \csvexpval\csvcolii
}

\end{document}

Alternatively, a loop could be used to add data, but not inside the command which is expanded. The next example uses after filter for such a loop:

\documentclass{article}

\RequirePackage{tabularray}
\RequirePackage{csvsimple-l3}

\ExplSyntaxOn
\NewDocumentCommand \csvcollectlinetotablerow { }
  {
    \bool_set_false:N \l_tmpa_bool
    \seq_map_inline:Nn \g__csvsim_line_seq
      {
        \bool_if:NTF \l_tmpa_bool
          {
            \tl_gput_right:Nn \g__csvsim_collect_tl { & ##1 }
          }
          {
            \tl_gput_right:Nn \g__csvsim_collect_tl { ##1 }
            \bool_set_true:N \l_tmpa_bool
          }
      }
  }
\ExplSyntaxOff

\begin{filecontents*}{animals.csv}
Animal,Length\:(cm)
kitten,1.3
puppy,3.2
\end{filecontents*}

\begin{document}

\csvreader[
  tabularray={ll},
  no head,
  after filter=\csvcollectlinetotablerow,
]{animals.csv}{}{}

\end{document}

Maybe, it would not hurt to have \csvcollectlinetotablerow as an official macro?

lvjr commented 2 years ago

I think it may be simpler to write the function above as follows:

\NewDocumentCommand \csvcollectlinetotablerow { }
  {
    \tl_gput_right:Nx \g__csvsim_collect_tl { \seq_use:Nn \g__csvsim_line_seq { & } }
  }

What do you think about defining \csvlinetotablerow as a tl variable (like \csvcoli, \csvcolii, etc ):

\tl_set:Nx \csvlinetotablerow { \seq_use:Nn \g__csvsim_line_seq { & } }

Then tabularray can use \csvexpval\csvlinetotablerow inside the body of \csvreader and tabular can still use \csvlinetotablerow.

T-F-S commented 2 years ago

Using

\RenewExpandableDocumentCommand \csvlinetotablerow { }
  {
    \seq_use:Nn \g__csvsim_line_seq { & }
  }

seems to work.

Also, for tabularray, this seems to work without \csvexpval (because expansion is needed here)

lvjr commented 2 years ago

Great. And it would be perfect if we can also use other predefined macros (\csvcoli, \csvcolii, etc) without \csvexpval.

T-F-S commented 2 years ago

Tests with the expandable \csvlinetotablerow showed no problems. I will add this for the next version.

But, \csvcoli etc. will still need \csvexpval, because they contain the pure data and I intend to keep it like that (for backward compatibility as well as for principle).

lvjr commented 2 years ago

In csvsimple-l3.sty, I tried to replace

\tl_gset:cn {csvcol \int_to_roman:n \g__csvsim_col_int}{##1}

with

\tl_gset:cn {csvcol \int_to_roman:n \g__csvsim_col_int}{ \exp_not:n {##1} }

And the following test showed no problems of backward compatibility:

\documentclass{article}

\RequirePackage{tabularray}
\RequirePackage{csvsimple-l3}

\begin{filecontents*}[overwrite]{animals.csv}
\def\abc{xyz}\abc,Length
kitten,1.3
puppy ,3.2
\end{filecontents*}

\begin{document}

\csvreader[
  tabularray={ll},
  no head,
]{animals.csv}{}{
  \csvcoli & \csvcolii
}

\csvreader[
  tabularray={ll},
  no head,
]{animals.csv}{}{
  \csvexpval\csvcoli & \csvexpval\csvcolii
}

\csvreader[
  tabular={ll},
  no head,
]{animals.csv}{}{
  \csvcoli & \csvcolii
}

\end{document}
T-F-S commented 2 years ago

For many, maybe most, applications, a wrapping \exp_not:n will not hurt, but not for all.

The following example fails, if \exp_not:n is added:

\documentclass{article}

\RequirePackage{csvsimple-l3,xcolor}

\ExplSyntaxOn

\cs_set_protected_nopar:Npn \__csvsim_scan_line:
  {
    \int_gzero:N \g__csvsim_col_int
    \seq_gset_split:NVV \g__csvsim_line_seq \g__csvsim_separator_tl \csvline
    \seq_map_inline:Nn \g__csvsim_line_seq
      {
        \int_gincr:N \g__csvsim_col_int
        %\tl_gset:cn {csvcol \int_to_roman:n \g__csvsim_col_int}{##1}
        \tl_gset:cn {csvcol \int_to_roman:n \g__csvsim_col_int}{ \exp_not:n {##1} }
      }
    \int_compare:nNnT \g__csvsim_colmax_int < \g__csvsim_col_int
      {
        \int_gset_eq:NN \g__csvsim_colmax_int \g__csvsim_col_int
      }
    \int_compare:nNnT \g_csvsim_columncount_int < \c_one_int
      {
        \int_gset_eq:NN \g_csvsim_columncount_int \g__csvsim_col_int
      }
  }

\NewDocumentCommand \maptoken { m }
  {
    \tl_map_inline:Nn #1
      {
        \fbox{##1}
      }
  }

\ExplSyntaxOff

\begin{filecontents*}{valtest.csv}
type,value
A,\textit{5}
B,2
AB,4
ABC,3
\end{filecontents*}

\begin{document}

\csvreader[
  tabular={ll},
  head to column names,
]{valtest.csv}{}{%
  \maptoken{\csvcoli}
  &
  \ifcsvstrequal{\csvcoli}{B}{\color{red}\bfseries}{} \csvcolii
}

\end{document}

I want to have \csvcoli containing the content of the first column and nothing added. The legacy csvsimple has some drawbacks here and the L3 version is more advanced in this matter (and should continue like that).

T-F-S commented 2 years ago

For the version 2.3.0 released today, the expandable \csvlinetotablerow is implemented. Also, column names detection=false can be set to switch off column names detection.

Finally, you may like the following new example from the documentation: grafik

lvjr commented 2 years ago

Great, the example looks very nice!