2

I have a csv file that contains the following:

"N","n","M"
200,100,50

How can I read it with LaTeX such that \N, \n_trees and \M then returns respectively 200, 100 and 50 ? Or some other internal name, like \myarray{N}, \myarray{n}, \myarray{M} or any other syntax that would allow me to use these values in my text.

2
  • Check out the readarray package: ctan.org/pkg/readarray – Steven B. Segletes Jan 16 '20 at 12:41
  • Not really. It does part of the job, but my csv is realy a kind of "key-values" pairs; Furthermore, readarray allows to read an array into the document, i want to read it in the header so i can assign it's values to macros – lrnv Jan 16 '20 at 12:54
4

Here's one alternative LaTeX3 property lists to store and retrieve the values. Use \ReadCSVArray{<file name>} to read the .csv file, and then use \ArrayItem{<key>} to retrieve the <value> associated with the key. \ArrayItem is expandable so that if the <value> is numeric, then you can use in numerical expressions in \fpeval (for example). The code assumes that all keys are surrounded by double quotes.

enter image description here

\documentclass{article}
\begin{filecontents*}[overwrite]{mycsv.csv}
"N","n","M"
200,100,50
\end{filecontents*}
\usepackage{xparse}
\usepackage{xfp}
\ExplSyntaxOn
\ior_new:N \l__lrnv_temp_ior
\str_new:N \l__lrnv_tmp_str
\seq_new:N \l__lrnv_key_seq
\seq_new:N \l__lrnv_value_seq
\prop_new:N \l__lrnv_array_prop
\cs_new_protected:Npn \__lrnv_csv_to_prop:n #1
  {
    \__lrnv_input_csv:n {#1}
    \seq_mapthread_function:NNN \l__lrnv_key_seq \l__lrnv_value_seq
      \__lrnv_set_prop:nn
  }
\cs_new_protected:Npn \__lrnv_set_prop:nn #1 #2
  { \prop_put:Nnn \l__lrnv_array_prop {#1} {#2} }
\cs_new_protected:Npn \__lrnv_input_csv:n #1
  {
    \ior_open:Nn \l__lrnv_temp_ior {#1}
    \__lrnv_read_csv_row_to_seq:NN \l__lrnv_temp_ior \l__lrnv_key_seq
    \__lrnv_read_csv_row_to_seq:NN \l__lrnv_temp_ior \l__lrnv_value_seq
    \ior_close:N \l__lrnv_temp_ior
  }
\cs_new_protected:Npn \__lrnv_read_csv_row_to_seq:NN #1 #2
  {
    \ior_str_get:NN #1 \l__lrnv_tmp_str
    \seq_set_from_clist:NV #2 \l__lrnv_tmp_str
  }
\cs_generate_variant:Nn \seq_set_from_clist:Nn { NV }
\msg_new:nnn { lrnv } { file-not-found }
  { File~`#1'~not~found. }
\NewDocumentCommand \ReadCSVArray { m }
  {
    \file_if_exist:nTF {#1}
      { \__lrnv_csv_to_prop:n {#1} }
      { \msg_error:nnn { lrnv } { file-not-found } {#1} }
  }
\NewExpandableDocumentCommand \ArrayItem { m }
  { \prop_item:Nn \l__lrnv_array_prop { "#1" } }
\ExplSyntaxOff
\begin{document}
\ReadCSVArray{mycsv.csv}
$N=\ArrayItem{N}$, $n=\ArrayItem{n}$, and $M=\ArrayItem{M}$
(Total: \fpeval{\ArrayItem{N}+\ArrayItem{n}+\ArrayItem{M}}).
\end{document}
3

A slightly (more compact) version with expl3:

\begin{filecontents*}{\jobname.csv}
"N","n","M"
200,100,50
\end{filecontents*}

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\readcsvfile}{m}
 {% #1 is the file name
  \lrnv_readcsvfile:n { #1 }
 }
\NewExpandableDocumentCommand{\getcsvitem}{m}
 {
  \prop_item:Nn \l__lrnv_readcsvfile_prop { #1 }
 }
\NewExpandableDocumentCommand{\getcsvitembynumber}{m}
 {
  \seq_item:Nn \l__lrnv_readcsvfile_body_seq { #1 }
 }

\ior_new:N \g__lrnv_readcsvfile_ior
\tl_new:N \l__lrnv_readcsvfile_tl
\seq_new:N \l__lrnv_readcsvfile_head_seq
\seq_new:N \l__lrnv_readcsvfile_body_seq
\prop_new:N \l__lrnv_readcsvfile_prop
\cs_generate_variant:Nn \prop_put:Nnn { Nnf }

\cs_new_protected:Nn \lrnv_readcsvfile:n
 {
  % open the file
  \ior_open:Nn \g__lrnv_readcsvfile_ior { #1 }
  % get the first line
  \ior_get:NN \g__lrnv_readcsvfile_ior \l__lrnv_readcsvfile_tl
  % remove the quotes
  \tl_replace_all:Nnn \l__lrnv_readcsvfile_tl { " } { }
  % populate a sequence splitting at commas
  \seq_set_split:NnV \l__lrnv_readcsvfile_head_seq { , } \l__lrnv_readcsvfile_tl
  % get the second line
  \ior_get:NN \g__lrnv_readcsvfile_ior \l__lrnv_readcsvfile_tl
  % populate the a sequence splitting at commas
  \seq_set_split:NnV \l__lrnv_readcsvfile_body_seq { , } \l__lrnv_readcsvfile_tl
  % close the file
  \ior_close:N \g__lrnv_readcsvfile_ior
  % clear the property list
  \prop_clear:N \l__lrnv_readcsvfile_prop
  % populate the property list: property from first sequence, value from second sequence
  \seq_indexed_map_inline:Nn \l__lrnv_readcsvfile_head_seq
   {
    \prop_put:Nnf \l__lrnv_readcsvfile_prop { ##2 } { \seq_item:Nn \l__lrnv_readcsvfile_body_seq { ##1 } }
   }
 }

\cs_new_protected:Nn \__lrnv_readcsvfile_read:
 {
 }

\ExplSyntaxOff

\begin{document}

\readcsvfile{\jobname.csv}

\getcsvitem{N} -- \getcsvitembynumber{1}

\getcsvitem{n} -- \getcsvitembynumber{2}

\getcsvitem{M} -- \getcsvitembynumber{3}

\end{document}

enter image description here

1

Although an accepted answer is posted, I would also offer the option to use DataTool. I kept the raw \DTLfetch command exposed. it could be encapsulated in its own macro to achieve your objective.

\documentclass{article}

% datatool does the database work
\usepackage{datatool}

% the file contents

\begin{filecontents*}{values.csv}
color, N, n_trees, M
red, 200, 100, 50
green, 300, 10, 49
blue, 400, 1, 48
\end{filecontents*}

% load the database to the document

\DTLloaddb{values}{values.csv}

\begin{document}

% examples

The trees for red is: \DTLfetch{values}{color}{red}{n_trees}.

The M for blue is: \DTLfetch{values}{color}{blue}{M}.

The color that had 1 tree is: \DTLfetch{values}{n_trees}{1}{color}.

\end{document}

See also a different example at this link.

Not the answer you're looking for? Browse other questions tagged or ask your own question.