5

I would like to define new commands and be able to change the output from a csv-file. Let's say the csv-file is semicolon seperated:

Name; Score
Max; 12
Peter; 32
Anna; 27

Now I would like to have the following in LaTeX:

\newcommand{\Max}{12}
\newcommand{\Peter}{32}
\newcommand{\Anna}{27}

1. Basic soulution: Only the numbers (= 12, 32, and 27) should be read from the csv-file. The command names (= \Max, \Peter, and \Anna) can be hardcoded.

2. Advanced solution: Loop through all rows of the csv-file (optional: beginning with the second row) and create new commands based on the names in the first column (= command name) and the score in the second column (= desired output of each command name).

How do I achieve any of the solutions above? There are a lot of examples of reading csv-files and creating a table out of them in LaTeX, but I haven't found anything that solves my problem.

Thanks in advance!


Update: Added minimal working example (MWE) inspired by the following website: https://www.uweziegenhagen.de/?p=3100

The following code loops through my csv file and gives me the names and the scores. It is not a solution to my problem, but it could be part of it:

\documentclass{article}

\usepackage{datatool}

\DTLsetseparator{;}
\DTLloaddb{scores}{test.csv}

\begin{document}
    \DTLforeach{scores}{%
        \name=Name,\score=Score}{%
        \name\score\\}
\end{document}

Now when I try to solve my problem, I fail to add the new command names programatically (\Max, \Peter, and \Anna). I get the error: Command \textbackslash already defined. \newcommand{\textbackslash \name}{\score}}. If this would work it would be the "advanced solution" which would have been very nice:

\documentclass{article}

\usepackage{datatool}

\DTLsetseparator{;}
\DTLloaddb{scores}{test.csv}

\DTLforeach{scores}{%
    \name=Name,\score=Score}{%
    \newcommand{\textbackslash \name}{\score}}

\begin{document}
    \Max
    \Peter
    \Anna
\end{document}
6
  • Hmmm.... are you aware of osl.ugr.es/CTAN/macros/latex/contrib/csvsimple/csvsimple.pdf ? It does not exactly what you ask, but could be another solution.
    – Rmano
    Commented Apr 20, 2021 at 9:46
  • I think I have not yet worked with the csvsimple package, but maybe my problem could be solved using it. I would have to take a deeper look into it.
    – owmal
    Commented Apr 20, 2021 at 9:50
  • You can loop over the row of a .cvs file by using macros bound to the column's value. But I suspect this is a XY problem --- why don't you put a simple minimal working example (MWE) showing what you want to achieve?
    – Rmano
    Commented Apr 20, 2021 at 9:54
  • Thanks for the comment! I have now added a minimal working example and a code which gives me an error. The problem is that I fail to programmatically add the command name in the for loop with a backslash.
    – owmal
    Commented Apr 20, 2021 at 11:17
  • The pgfplotstable package is another way (besides filecontents) to combine a csv with execuaibles. Commented Apr 20, 2021 at 14:46

3 Answers 3

1
\documentclass{article}
\usepackage{readarray}
\begin{filecontents*}[overwrite]{mydata.txt}
Name; Score
Max; 12
Peter; 32
Anna; 27
\end{filecontents*}

\begin{document}
\readarraysepchar{,}
\setsepchar{,/;}
\readdef{mydata.txt}\datadef
\ignoreemptyitems
\readlist*\datarray{\datadef}
\foreachitem\z\in\datarray[]{%
  \ifnum\zcnt=1\else
    \expandafter\xdef\csname\datarray[\zcnt,1]\endcsname
      {\datarray[\zcnt,2]}%
  \fi
}

The score for Max is \Max.

But Peter, with a score of \Peter, beats him.

The person from line 4 in the file, whose name I can't remember,
  is \datarray[4,1].  Her score is \datarray[4,2].
Oh, Anna?  Great!  I can just use \Anna.
\end{document}

enter image description here

4
  • Great approach! This requires the file to be a txt-file, but generally it is very easy to convert a csv file to a txt file and for my case your code is more than sufficient for me. Thank you very much! A small side note: the for loop and array part can also be inserted in the preambel (before \begin{document}) which I personally find better.
    – owmal
    Commented Apr 20, 2021 at 11:55
  • 1
    @owmal If you would prefer csv input, that is easy to arrange. I was just following your question's example. Would your csv file be the same, except semicolons changed to commas? (That is to say, a csv file is a certain kind of text file). Why, just change the two instances of .txt to .csv and recompile. Commented Apr 20, 2021 at 11:59
  • Sorry, your code actually works just fine with csv files aswell. Also with semicolon separator. No problem at all, I was using the wrong file name and thought it would only work with txt files. Thanks again! I think this is a great answer to my problem!
    – owmal
    Commented Apr 20, 2021 at 12:10
  • @owmal If you want to use actual commas as your in-file separators, then change these two lines as follows: \readarraysepchar{\\} \setsepchar{\\/,}. This is a nested parsing. First, into lines with "\\" as separator, then within each line, with "," as item separator. Commented Apr 20, 2021 at 12:12
3

Here is the code which does not need any additional package. Only TeX primitives are used.

\documentclass{article}

\begin{document}

\def\readrow #1;#2;{\ifx^#2^\else 
   \expandafter\xdef\csname #1\endcsname{#2}%
   \expandafter\readrow\fi}
\def\startread #1;#2;{\readrow} % skip first row
\begingroup
   \endlinechar=`; \everyeof={;;} \catcode`\@=11
   \expandafter \startread \@@input mydata.txt %
\endgroup % 

The score for Max is \Max.

But Peter, with a score of \Peter, beats him.

I can just use \Anna.
\end{document}
2
  • That is a nice solution aswell. Is there any way to use it for csv-files (if not, it's not a big deal)? Also here a small side note: the \def and \begingroup / \endgroup part can also be inserted in the preambel (before \begin{document}) which I personally prefer.
    – owmal
    Commented Apr 20, 2021 at 16:26
  • All macro packages which are able to read csv files, are based on this principle shown here in my example. For example scancsv.tex from my package olsak-misc.
    – wipet
    Commented Apr 20, 2021 at 19:04
0

You can read the file line by line (discarding the first one), split the line at the semicolon and do the definition:

\begin{filecontents*}{\jobname.dat}
Name; Score
Max; 12
Peter; 32
Anna; 27
\end{filecontents*}

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\makecommands}{m}
 {
  \owmal_makecommands:n { #1 }
 }

\ior_new:N \g_owmal_makecommands_ior
\seq_new:N \l__owmal_makecommands_line_seq

\cs_new_protected:Nn \owmal_makecommands:n
 {
  \ior_open:Nn \g_owmal_makecommands_ior { #1 }
  \ior_get:NN \g_owmal_makecommands_ior \l_tmpa_tl % discard the first line
  \ior_map_inline:Nn \g_owmal_makecommands_ior
   {
    \seq_set_split:Nnn \l__owmal_makecommands_line_seq { ; } { ##1 }
    \cs_new:cpx
     { \seq_item:Nn \l__owmal_makecommands_line_seq { 1 } } % name of command
     { \seq_item:Nn \l__owmal_makecommands_line_seq { 2 } } % text
   }
 }

\ExplSyntaxOff

\makecommands{\jobname.dat}

\begin{document}

The score for Max is \Max

The score for Peter is \Peter

The score for Anna is \Anna

\end{document}

enter image description here

On the other hand, you can avoid to define commands and store the scores in a property list indexed by the names.

\begin{filecontents*}{\jobname.dat}
Name; Score
Max; 12
Peter; 32
Anna; 27
\end{filecontents*}

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\readscores}{m}
 {
  \owmal_scores_read:n { #1 }
 }
\NewExpandableDocumentCommand{\getscore}{m}
 {
  \prop_item:Nn \g_owmal_scores_prop { #1 }
 }

\ior_new:N \g_owmal_scores_ior
\seq_new:N \l__owmal_scores_line_seq
\prop_new:N \g_owmal_scores_prop

\cs_new_protected:Nn \owmal_scores_read:n
 {
  \ior_open:Nn \g_owmal_scores_ior { #1 }
  \ior_get:NN \g_owmal_scores_ior \l_tmpa_tl % discard the first line
  \ior_map_inline:Nn \g_owmal_scores_ior
   {
    \seq_set_split:Nnn \l__owmal_scores_line_seq { ; } { ##1 }
    \prop_gput:Nxx \g_owmal_scores_prop
     { \seq_item:Nn \l__owmal_scores_line_seq { 1 } } % name
     { \seq_item:Nn \l__owmal_scores_line_seq { 2 } } % score
   }
 }

\ExplSyntaxOff

\readscores{\jobname.dat}

\begin{document}

The score for Max is \getscore{Max}

The score for Peter is \getscore{Peter}

The score for Anna is \getscore{Anna}

\end{document}

You must log in to answer this question.

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