1

Following up this answer, how to extend the code of its second method to store each row value columns in a list or array and be able to index into it?

In other words, I need another macro (along with \getValue) that stores all the values of the columns of the same row (except the first column that has the row name) in the following way:

\getRow{<row name located in the first column>}{<list/array name of the whole row value columns>}

For example, consider the following file

\begin{filecontents*}{test.csv}
First Parameter , 7 , 9  ,  ,
Third Parameter , 5 , 10 ,  ,
Fifth Parameter , 3 , 6  ,  , 44
\end{filecontents*}
  • I can have the value of 44 by first storing the row of Fifth Parameter in \myList by \getRow{Fifth Parameter}{\myList}. Then \myList[4] will get me the value 44.

  • The first value of the row (e.g. 3) can be accessed by \myList or \myList[1].

1
  • I believe this type of retrieval mechanism is built into datatool (not sure)...
    – Werner
    Commented Dec 14, 2020 at 22:04

1 Answer 1

2

I've added a \getRow command to the “Second method” of the existing code, as you asked. Its syntax is:

\getRow[*] {\macro} {key} {label}
  • The star form performs a global assignment to \macro (otherwise, the assignment is local).

  • The label must identify a file you've previously read using \ReadCSV (it corresponds to the first mandatory argument of \ReadCSV).

  • The key is used to select a particular row from that file (it has same meaning as with \getValue and \CSVItem).

After such a \getRow call, \macro is safe to use in expansion-only contexts (e.g., inside \edef, \write, \num from siunitx, etc.): it wraps the desired value using the \unexpanded e-TeX primitive, just like \tl_item:nn does.

Negative indices are supported and count from the end:

  • \macro[-1] expands to the last item of the row stored in \macro;

  • \macro[-2] expands to its predecessor;

  • etc.

As a special case, \macro[non-empty] expands to the number of non-empty items in the row stored inside \macro.

\begin{filecontents*}{test.csv}
Third Parameter  , 7 , 9          ,
First Parameter  , 5 , {foo, bar} ,
Second Parameter , 3 , 6          , 44
\end{filecontents*}

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
% Step 1: reading the file
\ior_new:N \l__diaa_csv_ior
\bool_new:N \l__diaa_csv_str_bool
\seq_new:N \l__diaa_csv_tmp_seq

% str mode (bool/star), key column, label, value columns, file
\NewDocumentCommand \ReadCSV { s O{1} m O{} m }
  {
    \IfBooleanTF {#1}
      { \bool_set_true:N \l__diaa_csv_str_bool }
      { \bool_set_false:N \l__diaa_csv_str_bool }
    \diaa_csv_read:nnnn {#3} {#2} {#4} {#5}
  }

% label, key column, value columns, file
\cs_new_protected:Npn \diaa_csv_read:nnnn #1 #2 #3 #4
  {
    \tl_if_blank:nTF {#3}       % Detect number of columns and use 2 to last
      {
        \ior_open:NnTF \l__diaa_csv_ior {#4}
          {
            \bool_if:NTF \l__diaa_csv_str_bool
              { \ior_str_get:NN }
              { \ior_get:NN }
              \l__diaa_csv_ior \l_tmpa_tl

            \ior_close:N \l__diaa_csv_ior
            \seq_set_split:NnV \l_tmpa_seq { , } \l_tmpa_tl
            \seq_clear:N \l__diaa_csv_tmp_seq
            \int_step_inline:nnn { 2 } { \seq_count:N \l_tmpa_seq }
              { \seq_put_right:Nn \l__diaa_csv_tmp_seq {##1} }
          }
          { \msg_error:nnn { diaa } { file-not-found } {#4} }
      }
      { \seq_set_split:Nnn \l__diaa_csv_tmp_seq { , } {#3} } % explicit columns

    \ior_open:NnTF \l__diaa_csv_ior {#4}
      {
        \prop_new:c { g__diaa_csv_#1_prop }
        \__diaa_csv_read:nn {#1} {#2}
        \ior_close:N \l__diaa_csv_ior
      }
      { \msg_error:nnn { diaa } { file-not-found } {#4} }
  }

\msg_new:nnn { diaa } { file-not-found }
  { File~`#1'~not~found. }

\cs_generate_variant:Nn \prop_put:Nnn { cxV }

% label, key column
\cs_new_protected:Npn \__diaa_csv_read:nn #1 #2
  {
    \bool_if:NTF \l__diaa_csv_str_bool
      { \ior_str_map_inline:Nn }
      { \ior_map_inline:Nn }
        \l__diaa_csv_ior
        {
          \seq_set_split:Nnn \l_tmpa_seq { , } {##1} % split one CSV row
          \tl_clear:N \l_tmpa_tl
          \seq_map_inline:Nn \l__diaa_csv_tmp_seq
            {
              \tl_put_right:Nx \l_tmpa_tl { { \seq_item:Nn \l_tmpa_seq {####1} } }
            }

          \prop_put:cxV { g__diaa_csv_#1_prop }
            { \seq_item:Nn \l_tmpa_seq {#2} }
            \l_tmpa_tl
        }
  }

% Step 2: getting the values
% star → global assignment, macro or tl var, value column, key, label
\NewDocumentCommand \getValue { s m O{1} m m }
  {
    \IfBooleanTF {#1} { \tl_gset:Nx } { \tl_set:Nx }
      #2 { \diaa_csv_item:nnn {#4} {#3} {#5} }
  }

% key, value column, label
\NewExpandableDocumentCommand \CSVItem { m O{1} m }
  { \diaa_csv_item:nnn {#1} {#2} {#3} }

\cs_generate_variant:Nn \tl_item:nn { f }

% key, value column, label
\cs_new:Npn \diaa_csv_item:nnn #1 #2 #3
  {
    \tl_item:fn { \prop_item:cn { g__diaa_csv_#3_prop } {#1} } {#2}
  }

% star → global assignment, macro, key, label
\NewDocumentCommand \getRow { s m m m }
  {
    \prop_get:cnN { g__diaa_csv_#4_prop } {#3} \l_tmpa_tl
    \IfBooleanTF {#1} { \cs_gset_nopar:Npx } { \cs_set_nopar:Npx } #2 [ ##1 ]
      {
        \exp_not:N \str_if_eq:nnTF {##1} { non-empty }
          {
            \exp_not:N \__diaa_nb_nonempty_items_in_row:nw { 0 }
            \exp_not:V \l_tmpa_tl
            \exp_not:n { \q_recursion_tail \q_recursion_stop }
          }
          { \exp_not:N \tl_item:nn { \exp_not:V \l_tmpa_tl } {##1} }
      }
  }

\cs_new:Npn \__diaa_nb_nonempty_items_in_row:nw #1#2
  {
    \quark_if_recursion_tail_stop_do:nn {#2} { \int_eval:n {#1} }
    \tl_if_empty:nTF {#2}
      { \__diaa_nb_nonempty_items_in_row:nw {#1} }
      { \__diaa_nb_nonempty_items_in_row:nw { #1 + 1 } }
  }
\ExplSyntaxOff

\begin{document}

% Use default column for the key (1). The second empty optional argument (list
% of value columns) means we want to autodetect the value columns; then, the
% first column is for keys and all other columns are used as value columns.
\ReadCSV{mydata}{test.csv}

\getValue\rdPar{Second Parameter}{mydata}
\rdPar                         % 3

\getValue\rdPar[2]{Second Parameter}{mydata}
\rdPar                         % 6

\getValue\rdPar[3]{Second Parameter}{mydata}
\rdPar                         % 44

\getValue\rdPar{Third Parameter}{mydata}
\rdPar                         % 7

\edef\rdPar{\CSVItem{First Parameter}{mydata}}%
\rdPar                         % 5

\edef\rdPar{\CSVItem{First Parameter}[2]{mydata}}%
\rdPar                         % foo, bar

\edef\rdPar{\CSVItem{First Parameter}[3]{mydata}}%
\ifx\rdPar\empty
  \textlangle empty\textrangle
\else
  \rdPar
\fi

\getRow{\RowA}{First Parameter}{mydata}
\getRow{\RowB}{Second Parameter}{mydata}
\getRow{\RowC}{Third Parameter}{mydata}

``\RowA[1]'', ``\RowA[2]'', ``\RowA[3]'' ($\RowA[non-empty]$ non-empty)\par
``\RowB[1]'', ``\RowB[2]'', ``\RowB[3]'' ($\RowB[non-empty]$ non-empty)\par
``\RowC[1]'', ``\RowC[2]'', ``\RowC[3]'' ($\RowC[non-empty]$ non-empty)

{% Enter a group and perform a global assignment
  \getRow*{\globallyDefined}{First Parameter}{mydata}%
}% Leave the group
``\globallyDefined[1]'', ``\globallyDefined[2]'', ``\globallyDefined[3]''

\edef\zzz{abc'\RowA[2]'def}
%\show\zzz % prints \zzz=macro:->abc'foo, bar'def.
\edef\zzz{\RowA[non-empty]}
%\show\zzz % prints \zzz=macro:->2.

\end{document}

enter image description here

18
  • You are my latex savior for long time :). I am on my phone but I can't hold this question till tomorrow XD: do you mean that I can use something like \SI{\fpeval{myList[1]^2 * \mylist[2]}} without problems? Since this is what I actually need to do.
    – Diaa
    Commented Dec 15, 2020 at 0:31
  • Another feature I hope you consider since it was missing in the old code: i would like to get a warning in my latex editor when the macro handled by \getValue has an empty value (i.e. the accessed cell in the CSV file is empty) and an error halting the compilation when I try to index into a non-existent row or column.
    – Diaa
    Commented Dec 15, 2020 at 0:38
  • Regarding your first comment, if you add \usepackage{siunitx} and \usepackage{xfp} to my example, then $d = \SI{\fpeval{\RowB[1]^2 * \RowB[2]}}{\meter}$ typesets d = 54 m (because (3^2)×6 = 54). Regarding your second comment, these could be added later (no time now, sorry, have to go to bed).
    – frougon
    Commented Dec 15, 2020 at 0:45
  • I highly appreciate your consideration and swift help.
    – Diaa
    Commented Dec 15, 2020 at 9:15
  • If you don't mind me extending my wish list along with warning and error XD: How can I get the number of non-empty elements in the list/array? Also, how can I robustly access the last element by using something like \myList[end] or \myList[-1] without having to explicitly write its index?
    – Diaa
    Commented Dec 15, 2020 at 10:24

You must log in to answer this question.

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