3

I have a csv table and I want to input specific cells. For instance, my csv is project.csv:

       , obj, resources, wp
task1.1, 1  ,   2k     , 1
task2.1, 2  ,   3k     , 2 
task3.1, 3  ,   4k     , 3

Ideally, I would like to input specific elements from it, for instance by referring to it by its column and row name. For instance:

Resources for the first task from Work Package \input{project.csv[task1.1, wp]} are \input{project.csv[task1.1, resources]} USD 
4
  • Something like this: tex.stackexchange.com/a/474404/134574? Commented Nov 10, 2021 at 19:40
  • Are you able to change the CSV so it has a header for the tasks column, like task?
    – Werner
    Commented Nov 10, 2021 at 20:14
  • @Phelype, being able to refer to it using the col/rowname would make it easier, but yes something like that is what I was looking for. Thanks.
    – augusto
    Commented Nov 10, 2021 at 21:16
  • @Werner, yes, I can modify the csv to add headers for all the columns
    – augusto
    Commented Nov 10, 2021 at 21:19

2 Answers 2

4

Use datatool's \DTLfetch{<db name>}{<source col>}{<source val>}{<target col>} to return the value associated with <target col> where <source col> is <source val> in <db name> (it acts like a lookup-up table).

enter image description here

\documentclass{article}

\begin{filecontents*}[overwrite]{tasks.csv}
task,obj,resources,wp
task1.1,1,2k,1
task2.1,2,3k,2 
task3.1,3,4k,3
\end{filecontents*}

\usepackage{datatool}

\begin{document}

% Load the database; later referenced as tasks
\DTLloaddb
  [keys={task,obj,resources,wp}]% <options>
  {tasks}% <db name>
  {tasks.csv}% <filename>

Resources for the first task from Work Package 
\DTLfetch{tasks}{task}{task1.1}{wp} %\input{project.csv[task1.1, wp]} 
are 
\DTLfetch{tasks}{task}{task1.1}{resources} %\input{project.csv[task1.1, resources]} 
USD.

\end{document}

Note that spaces in the CSV file are returned, so that'll depend on how you manage spaces within the output (from \DTLfetch). I've removed them, since typical CSV exports will exclude them.

3
  • fantastic, easy and simple, thanks!
    – augusto
    Commented Nov 11, 2021 at 19:02
  • Additional related question, if there are multiple matches for a given row/column combination, how can I retrieve them all? For instance, if the csv has one extra column (obj) with all its values being the same (1) \begin{filecontents*}[overwrite]{tasks.csv} task,obj,resources,wp,obj task1.1,1,2k,1,1 task2.1,2,3k,2,1 task3.1,3,4k,3,1 \end{filecontents*} How can I do something like \DTLfetch{tasks}{objective}{1}{task} Where the output is both tasks task1.1, task1.2, task1.3
    – augusto
    Commented Nov 11, 2021 at 21:17
  • 1
    @augusto: Not with \DTLfetch (which only returns the first occurence/match). You'd have to process the entire database and perform a check for each record if you want to return more than one result.
    – Werner
    Commented Nov 12, 2021 at 6:31
2

Here's an expl3 implementation from scratch. Use

\ReadCSV { <filename> } \command

to read the .csv file to <filename>, and store it to be used in \command, then use:

\command{<row>,<column>}

to get the item corresponding to <row> and <column> (accessed by name).

The \command is expandable, so you can safely use it in captions, section headings, etc.

Additionally, you can use

\IterateCSVRows \command {<code>}
% and
\IterateCSVCols \command {<code>}

to iterate over the rows and columns of the read CSV file. In the <code>, #1 is the row or column name for the current iteration, so you can use \command{#1,col} or \command{row,#1} in <code>. If you nest both, the inner command has to use ##1 for the row or column name. The example below shows how to reconstruct the .csv as a table using these commands.

\documentclass{article}
\begin{filecontents*}[overwrite]{project.csv}
task   , obj, resources, wp
task1.1, 1  ,   2k     , 1
task2.1, 2  ,   3k     , 2
task3.1, 3  ,   4k     , 3
\end{filecontents*}
\ExplSyntaxOn
\tl_new:N \l__augusto_tmpa_tl
\tl_new:N \l__augusto_csv_name_tl
\ior_new:N \l__augusto_csv_ior
\seq_new:N \l__augusto_tmpa_seq
\int_new:N \l__augusto_csv_rows_int
\bool_new:N \l__augusto_csv_read_as_string_bool
\cs_generate_variant:Nn \seq_set_from_clist:Nn { NV }
\cs_generate_variant:Nn \prop_put_if_new:Nnn { cVx }
\cs_new_protected:Npn \augusto_read_csv:NnN #1 #2 #3
  {
    \bool_set_eq:NN \l__augusto_csv_read_as_string_bool #1
    \ior_open:NnTF \l__augusto_csv_ior {#2}
      {
        \cs_if_exist:NTF #3
          { \msg_error:nnn { augusto } { command-exists } {#3} }
          { \exp_args:Nf \__augusto_read_csv:nN { \cs_to_str:N #3 } #3 }
      }
      { \msg_error:nnn { augusto } { file-not-found } {#2} }
  }
\msg_new:nnn { augusto } { file-not-found }
  { File~'#1'~not~found. }
\msg_new:nnn { augusto } { command-exists }
  { Command~'#1'~already~defined. }
\cs_new_protected:Npn \__augusto_read_csv:nN #1 #2
  {
    \NewExpandableDocumentCommand #2 { m }
      { \__augusto_get_csv_item:nn {##1} {#1} }
    \tl_set:Nn \l__augusto_csv_name_tl {#1}
    \bool_if:NTF \l__augusto_csv_read_as_string_bool
      { \ior_str_get:NN } { \ior_get:NN }
          \l__augusto_csv_ior \l__augusto_tmpa_tl
    \seq_set_from_clist:NV \l__augusto_tmpa_seq \l__augusto_tmpa_tl
    \seq_pop:NN \l__augusto_tmpa_seq \l__augusto_tmpa_tl
    \seq_new:c { g__augusto_csv_#1_cols_seq }
    \seq_map_inline:Nn \l__augusto_tmpa_seq
      {
        \seq_new:c { g__augusto_csv_#1_##1_seq }
        \seq_gput_right:cn { g__augusto_csv_#1_cols_seq } {##1}
      }
    \prop_new:c { g__augusto_csv_#1_rows_prop }
    \int_zero:N \l__augusto_csv_rows_int
    \bool_if:NTF \l__augusto_csv_read_as_string_bool
      { \ior_str_map_inline:Nn } { \ior_map_inline:Nn }
          \l__augusto_csv_ior
        {
          \int_incr:N \l__augusto_csv_rows_int
          \seq_set_split:Nnn \l__augusto_tmpa_seq { , } {##1}
          \seq_pop:NN \l__augusto_tmpa_seq \l__augusto_tmpa_tl
          \prop_put_if_new:cVx { g__augusto_csv_#1_rows_prop }
            \l__augusto_tmpa_tl { \int_use:N \l__augusto_csv_rows_int }
          \seq_mapthread_function:cNN
            { g__augusto_csv_#1_cols_seq } \l__augusto_tmpa_seq
            \__augusto_add_csv_row:nn
        }
  }
\cs_new_protected:Npn \augusto_map_csv_rows:Nn #1 #2
  { \prop_map_inline:cn { g__augusto_csv_ \cs_to_str:N #1 _rows_prop } {#2} }
\cs_new_protected:Npn \augusto_map_csv_cols:Nn #1 #2
  { \seq_map_inline:cn { g__augusto_csv_ \cs_to_str:N #1 _cols_seq } {#2} }
\cs_new_protected:Npn \__augusto_add_csv_row:nn #1 #2
  {
    \seq_gput_right:cn
      { g__augusto_csv_ \l__augusto_csv_name_tl _#1_seq }
      {#2}
  }
\cs_new:Npn \__augusto_get_csv_item:nn #1
  { \__augusto_get_csv_item:wn #1 , \q_nil , \s_stop }
\cs_new:Npn \__augusto_get_csv_item:wn #1 , #2 , #3 \s_stop #4
  {
    \quark_if_nil:nTF {#2}
      { \msg_expandable_error:nnn { augusto } { invalid-item } {#4} }
      {
        \exp_args:Nff \__augusto_get_item:nnn
          { \tl_trim_spaces:n {#1} } { \tl_trim_spaces:n {#2} } {#4}
      }
  }
\cs_new:Npn \__augusto_get_item:nnn #1 #2 #3
  {
    \exp_args:Ncc \__augusto_get_item:NNnn
      { g__augusto_csv_#3_#2_seq } { g__augusto_csv_#3_rows_prop }
      {#1} {#2}
  }
\cs_new:Npn \__augusto_get_item:NNnn #1 #2 #3 #4
  {
    \seq_if_exist:NTF #1
      {
        \prop_if_in:NnTF #2 {#3}
          { \seq_item:Nn #1 { \prop_item:Nn #2 {#3} } }
          { \use_ii:nn }
      }
      { \use_ii:nn }
    \use_none:n
      {
        \msg_expandable_error:nnn
          { augusto } { invalid-item } {#3,#4}
      }
  }
\msg_new:nnn { augusto } { invalid-item }
  { Invalid~item~'#1'. }
\NewDocumentCommand \ReadCSV { s m m }
  {
    \IfBooleanTF {#1}
      { \augusto_read_csv:NnN \c_true_bool }
      { \augusto_read_csv:NnN \c_false_bool }
        {#2} #3
  }
\NewDocumentCommand \IterateCSVRows { m +m }
  { \augusto_map_csv_rows:Nn #1 {#2} }
\NewDocumentCommand \IterateCSVCols { m +m }
  { \augusto_map_csv_cols:Nn #1 {#2} }
\ExplSyntaxOff
\begin{document}

\ReadCSV{project.csv}\project

Resources for the first task from Work Package \project{task1.1, wp}
are \project{task1.1, resources} USD

\begin{tabular}{*4c}
\IterateCSVCols \project { & #1 } \\
\IterateCSVRows \project
  {%
    #1
    \IterateCSVCols \project
      { & \project{#1,##1} }
    \\
  }
\end{tabular}

\end{document}
6
  • How can I loop through \project to build a table with custom row? Commented Apr 20, 2023 at 12:28
  • It seems there is a glitch. If a value is missing in a source csv file, the command \project{id,col} will grab and display the value of the next row. Commented Apr 21, 2023 at 20:48
  • @SergeiZinovyev Bug fixed (replaced \seq_set_from_clist:Nn, which discards empty items, by \seq_set_split:Nnn). I added iterator functions \IterateCSVRows and \IterateCSVCols and and example of how to rebuild the CSV as a table Commented Apr 27, 2023 at 2:24
  • Thank you for your changes. It works now. One more question. While iterating through project lines I use str_if_eq:nnTF, but it always executes False branch: \str_if_eq:nnTF {\project{#1,resources}} {2k} {small}{big}. I think it is related to expanding. How can I use string comparison? Commented Apr 27, 2023 at 15:34
  • @SergeiZinovyev Use \str_if_eq:eeTF to expand the arguments Commented Apr 27, 2023 at 15:59

You must log in to answer this question.

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