2

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? 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
    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
    Nov 10, 2021 at 21:16
  • @Werner, yes, I can modify the csv to add headers for all the columns
    – augusto
    Nov 10, 2021 at 21:19

2 Answers 2

2

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
    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
    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
    Nov 12, 2021 at 6:31
1

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.

\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
\seq_new:N \l__augusto_tmp_cols_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_clear:N \l__augusto_tmp_cols_seq
    \seq_map_inline:Nn \l__augusto_tmpa_seq
      {
        \seq_new:c { g__augusto_csv_#1_##1_seq }
        \seq_put_right:Nn \l__augusto_tmp_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_from_clist:Nn \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:NNN
            \l__augusto_tmp_cols_seq \l__augusto_tmpa_seq
            \__augusto_add_csv_row:nn
        }
  }
\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
  }
\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

\end{document}

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.