5

I have a CSV file with key-value pairs, and I want to be able to read the file into latex, and generate macros to access the values. Here is my current attempt with datatool:

\documentclass{article}
\usepackage{datatool,filecontents}

\newcommand{\loadmap}[2][map]{%
    \DTLloaddb{#1}{#2}%
    \DTLforeach{#1}{\Key=Key,\Value=Value}{%
        \expandafter\def\csname #1\Key\endcsname{\Value}}}

\newcommand{\loadmaptest}[2][map]{%
    \DTLloaddb{#1}{#2}%
    \DTLforeach{#1}{\Key=Key,\Value=Value}{\Key,\Value\\}}

\begin{document}
\begin{filecontents}{testdata.csv}
    Key,Value
    firstkey,0.002
    secondkey,1.0
    thirdkey,4
\end{filecontents}  

\loadmap{testdata.csv}
\mapfirstkey,\mapsecondkey,\mapthirdkey\\

\loadmaptest[second]{testdata.csv}

\end{document}

You will see that the output is something like:

4,4,4
firstkey,0.002
secondkey,1.0
thirdkey,4

So, while the DTLforeach is correctly iterating through the CSV file when directly displaying the results, the generation of the macros is incorrect. In particular, it correctly generates the macro names as \mapfirstkey, etc. but the value for all of them is the last value in the keys. I suspect this has something to do with me incorrectly expanding macros...

Any ideas?

  • 1
    Use \edef instead \def. – esdd Jun 21 '17 at 17:43
  • @esdd Easy-peasy. Thank you so much. If you post as a solution I will accept it. – jlperla Jun 21 '17 at 17:45
  • @esdd Actually, another issue is that the keys can't have numbers in them. Is there a trick to have the \edef accept numbers? – jlperla Jun 21 '17 at 17:47
  • \expandafter\edef\csname macro123\endcsname{definition}, or am I missing the point of your question? – Steven B. Segletes Jun 21 '17 at 18:19
3

You have to expand \Value, otherwise the defined macros would contain it and not its value.

If you plan to use non alphabetic characters in the keys, I suggest to implement a \usevar command.

\begin{filecontents*}{\jobname.csv}
Key,Value
firstkey,0.002
secondkey,1.0
thirdkey,4
\end{filecontents*}

\documentclass{article}
\usepackage{datatool}

\newcommand{\loadmap}[2][map]{%
  \DTLloaddb{#1}{#2}%
  \DTLforeach{#1}{\Key=Key,\Value=Value}{%
    \expandafter\def\csname#1\Key\expandafter\endcsname\expandafter{\Value}%
  }%
}

\newcommand{\usevar}[2]{\csname #1#2\endcsname}

\begin{document}

\loadmap{\jobname.csv}

\usevar{map}{firstkey}, \usevar{map}{secondkey}, \usevar{map}{thirdkey}  

\end{document}

With the help of xparse and expl3 this can be made more efficient.

Note that the \usevar command can use an optional argument, but it remains fully expandable.

\begin{filecontents*}{\jobname.csv}
Key,Value
firstkey,0.002
secondkey,1.0
thirdkey,4
\end{filecontents*}

\documentclass{article}
\usepackage{datatool}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\loadmap}{O{map}m}
 {
  \jlperla_keyvalue_assign:nn { #1 } { #2 }
 }
\NewExpandableDocumentCommand{\usevar}{O{map}m}
 {
  \prop_item:cn { l_jlperla_keyvalue_#1_prop } { #2 }
 }

\tl_new:N \l__jlperla_keyvalue_key_tl
\tl_new:N \l__jlperla_keyvalue_Value_tl

\cs_new_protected:Nn \jlperla_keyvalue_assign:nn
 {
  \prop_clear_new:c { l_jlperla_keyvalue_#1_prop }
  \DTLloaddb{#1}{#2}
  \DTLforeach{#1}
   {
    \l__jlperla_keyvalue_key_tl = Key,
    \l__jlperla_keyvalue_Value_tl = Value
   }
   {
    \prop_put:cVV
     { l_jlperla_keyvalue_#1_prop }
     \l__jlperla_keyvalue_key_tl
     \l__jlperla_keyvalue_Value_tl
   }
 }
\cs_generate_variant:Nn \prop_put:Nnn { cVV }
\ExplSyntaxOff

\begin{document}

\loadmap{\jobname.csv}

\usevar{firstkey}, \usevar[map]{secondkey}, \usevar{thirdkey}

\end{document}

enter image description here

  • Looks great. Though I can't figure out how to get the expl3 engine working with Miktex, so I will have to go with the easier one. – jlperla Jun 21 '17 at 20:33
4

Replace \def by \edef:

\documentclass{article}
\usepackage{datatool,filecontents}

\newcommand{\loadmap}[2][map]{%
    \DTLloaddb{#1}{#2}%
    \DTLforeach{#1}{\Key=Key,\Value=Value}{%
        \expandafter\edef\csname #1\Key\endcsname{\Value}}}% <- changed

\newcommand{\loadmaptest}[2][map]{%
    \DTLloaddb{#1}{#2}%
    \DTLforeach{#1}{\Key=Key,\Value=Value}{\Key,\Value\\}}

\begin{document}
\begin{filecontents}{testdata.csv}
    Key,Value
    firstkey,0.002
    secondkey,1.0
    thirdkey,4
\end{filecontents}  

\loadmap{testdata.csv}
\noindent
\mapfirstkey,\mapsecondkey,\mapthirdkey\\
\loadmaptest[second]{testdata.csv}

\end{document}

Result:

enter image description here


Because of a comment: While it is possible to use numbers in the key you have to use `\csname ...\endcsname in the code. So I do not recommend to use the following example. Maybe you can find better names for your keys.

\documentclass{article} \usepackage{datatool,filecontents}

\newcommand{\loadmap}[2][map]{%
    \DTLloaddb{#1}{#2}%
    \DTLforeach{#1}{\Key=Key,\Value=Value}{%
        \expandafter\edef\csname #1\csname Key\endcsname\endcsname{\Value}}}

\newcommand{\loadmaptest}[2][map]{%
    \DTLloaddb{#1}{#2}%
    \DTLforeach{#1}{\Key=Key,\Value=Value}{\Key,\Value\\}}

\begin{document}
\begin{filecontents}{testdata.csv}
    Key,Value
    firstkey,0.002
    2key,1.0
    thirdkey,4
\end{filecontents}  

\loadmap{testdata.csv}
\noindent
\mapfirstkey,\csname map2key\endcsname ,\mapthirdkey\\
\loadmaptest[second]{testdata.csv}

\end{document}

Result:

enter image description here

3

An approach with readarray instead of datatool.

\documentclass{article}
\usepackage{readarray}[2016-11-07]
\usepackage{filecontents}
\begin{filecontents*}{myfile.dat}
    Key,Value
    firstkey,0.002
    secondkey,1.0
    thirdkey,4
    4key, 77
\end{filecontents*}
\newcounter{mycount}
\newcommand\assigndat[1]{%
  \readarraysepchar{,}%
  \readdef{#1}\mydata%
  \readarray*\mydata\myarray[-,\ncols]%
  \setcounter{mycount}{0}%
  \whiledo{\themycount<\nrows}{%
    \stepcounter{mycount}%
    \edef\tmp{\myarray[\themycount,1]}%
    \expandafter\expandafter\expandafter\xdef\expandafter\csname\tmp\endcsname{%
      \myarray[\themycount,2]}%
    }%
}
\newcommand\theKey[1]{\myarray[#1,1]}
\newcommand\theValue[1]{\csname #1\endcsname}
\begin{document}
\assigndat{myfile.dat}
\theKey{1}: \theValue{\theKey{1}} or \theValue{Key}\par
\theKey{2}: \theValue{\theKey{2}} or \theValue{firstkey}\par
\theKey{3}: \theValue{\theKey{3}} or \theValue{secondkey}\par
\theKey{4}: \theValue{\theKey{4}} or \theValue{thirdkey}\par
\theKey{5}: \theValue{\theKey{5}} or \theValue{4key}
\end{document}

enter image description here

With the pgffor package loaded, one could loop through all the keys in one fell swoop as such:

\foreach\x in {2,...,\myarrayROWS}{\theKey{\x}: \theValue{\theKey{\x}}\par}

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.