5

How can I read columns from a external csv file using a foreach statement? e.g. if the content of the csv file is as follows:

{0,0,0},black,
{1,1,0},yellow,
{1,0,1},pink

And I would like to use it as in the following code.

\documentclass{standalone}
\usepackage{tikz}
\begin{document}
  \foreach \code/\col in {%
  {0,0,0}/black,
  {1,1,0}/yellow,
  {1,0,1}/pink
  }{
   \definecolor{tempcolor}{rgb}{\code}
   \textcolor{tempcolor}{\col};
  }
  \end{document}
3

As long as it is ensured that each line of your .csv-file is exactly of pattern

{⟨rgb-specification⟩},⟨name of color⟩,

and that there are no spaces at the commas that separate the {⟨rgb-specification⟩} from the ⟨name of color⟩, you can:

  • use the catchfile-package for getting the content of the .csv-file into a temporary macro.
  • Then can apply some replacement-routine, e.g., of the xstring package, to the tokens that form the expansion/replacement-text of that temporary macro.
  • After that you can "feed" the expansion of the temporary macro to \foreach.

\documentclass{standalone}
\usepackage{tikz}
\usepackage{xstring}
\usepackage{catchfile}

\newcommand\PassFirstToSecond[2]{#2{#1}}

% !!! This will produce a text-file colorlist.csv for you.!!!
% !!! Make sure it won't override another already existing!!!
% !!! file of same name !!!
\begin{filecontents*}{colorlist.csv}
{0,0,0},black,
{1,1,0},yellow,
{1,0,1},pink
\end{filecontents*}

\begin{document}

\CatchFileDef{\tempa}{colorlist.csv}{}%

% Due to LaTeX's reading-apparatus which has that nice `\endlinechar`-thingie,
% reading and tokenizing colorlist.csv yields that each end of a line of
% colorlist.csv will result in the insertion of an explicit space token into
% the token stream.
% Thus the comma-character-tokens that stem from the commas at the ends of lines
% will be trailed by explicit space tokens while the comma-character-tokens that
% stem from commas not at the ends of lines don't have these trailing space tokens.
% The ending of the last line of colorlist.csv does also yield the insertion
% of an explicit space token.
% Anything nested inside curly braces is protected from being replaced by
% \StrSubstitute.

% Let's replace the commas at line endings by //:
\expandafter\StrSubstitute\expandafter{\tempa}{, }{//}[\tempa]%
%\show\tempa
% Let's replace the commas not at line endings by /:
\expandafter\StrSubstitute\expandafter{\tempa}{,}{/}[\tempa]%
%\show\tempa
% Let's replace the line endings (they are now denoted by //) by commas:
\expandafter\StrSubstitute\expandafter{\tempa}{//}{,}[\tempa]%
%\show\tempa
% Let's remove all remaining space-tokens - this does remove
% the space that came into being due to the end of the last
% line of colorlist.csv
\expandafter\StrSubstitute\expandafter{\tempa}{ }{}[\tempa]%
%\show\tempa


\expandafter\PassFirstToSecond\expandafter{\tempa}{\foreach\code/\col in}{%
  \definecolor{tempcolor}{rgb}{\code}%
  \textcolor{tempcolor}{\col};
}%

\end{document}

enter image description here

3

This assumes that the rows in the CSV file have the same number of nonempty items (trailing commas allowed).

We read one row at a time and pass it for processing; the row is split at commas (ignoring empty lines), the items so obtained are braced and passed as arguments to the macro specified in the second argument to \readcsv. Such a macro should have the exact number of arguments as the columns in the CSV file.

I provide two examples.

\begin{filecontents*}{\jobname.csv}
{0,0,0},black,
{0,1,1},cyan,
{1,0,1},pink
\end{filecontents*}

\begin{filecontents*}{\jobname2.csv}
{0,0,0},black,nero
{0,1,1},cyan,ciano
{1,0,1},pink,rosa
\end{filecontents*}

\documentclass{article}
\usepackage{xcolor}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\readcsv}{mm}
 {% #1 = filename, #2 = macro to apply to each row
  \tonytan_readcsv:nn { #1 } { #2 }
 }

\ior_new:N \g_tonytan_readcsv_stream
\seq_new:N \l__tonytan_readcsv_temp_seq
\tl_new:N \l__tonytan_readcsv_temp_tl

\cs_new_protected:Nn \tonytan_readcsv:nn
 {
  \ior_open:Nn \g_tonytan_readcsv_stream { #1 }
  \ior_map_inline:Nn \g_tonytan_readcsv_stream
   {
    \tonytan_readcsv_generic:Nn #2 { ##1 }
   }
  \ior_close:N \g_tonytan_readcsv_stream
 }
\cs_new_protected:Nn \tonytan_readcsv_generic:Nn
 {
  \seq_set_from_clist:Nn \l__tonytan_readcsv_temp_seq { #2 }
  \tl_set:Nx \l__tonytan_readcsv_temp_tl
   {
    \seq_map_function:NN \l__tonytan_readcsv_temp_seq \__tonytan_readcsv_brace:n
   }
  \exp_last_unbraced:NV #1 \l__tonytan_readcsv_temp_tl
 }
\cs_new:Nn \__tonytan_readcsv_brace:n { {#1} }
\ExplSyntaxOff

\newcommand{\showcolor}[2]{\textcolor[rgb]{#1}{#2}\par}

\newcommand{\showcolorx}[3]{\textcolor[rgb]{#1}{#2} (#3)\par}

\begin{document}

\readcsv{\jobname.csv}{\showcolor}

\readcsv{\jobname2.csv}{\showcolorx}

\end{document}

enter image description here

3

Here, I use readarray to get the file into a \def and then listofitems (included in readarray) to parse it and loop over it.

\documentclass{standalone}
\usepackage{tikz,readarray,filecontents}
\begin{filecontents*}{mydata.csv}
{0,0,0},black,
{1,1,0},yellow,
{1,0,1},pink
\end{filecontents*}
\begin{document}
  \readarraysepchar{\\}%
  \readdef{mydata.csv}\mydata%
  \ignoreemptyitems%
  \setsepchar{\\/,}%
  \readlist\mylist{\mydata}%
  \foreachitem\x\in\mylist{%
    \def\tmp{\definecolor{tempcolor}{rgb}}%
    \expandafter\expandafter\expandafter\tmp\mylist[\xcnt,1]%
    \textcolor{tempcolor}{\mylist[\xcnt,2]};%
  }
  \end{document}

enter image description here

  • @ Steven B. Segletes I accepted Steven's code as answer due to its simplicity and its resistance to irregularity of CSV list (e.g. empty spaces or lines). Thank you all! – Tony Tan Jan 28 at 18:50
  • 1
    @TonyTan If you want surrounding spaces in the data to be ignored, you can change the \readlist to \readlist*. Then, leading/trailing blanks are excised from the parsed data. – Steven B. Segletes Jan 28 at 19:35
  • Is there easy way to assign \mylist[\xcnt,2] to col and \mylist[\xcnt,1] to code in foreach statement as I did in my original post? or even better, can I put the header in the CSV file? I can not find a clue from the readarray manual. – Tony Tan Jan 28 at 20:39
  • @TonyTan Let me think about your request, but perhaps look at the listofitems manual as well: ctan.org/pkg/listofitems – Steven B. Segletes Jan 28 at 20:55
  • 1
    @TonyTan If your own answer is not a duplicate of others, post away. In listofitems, the way to skip the header is \foreachitem\x\in\<listname>{\ifnum\xcnt=1\else...<your loop code here>...\fi} – Steven B. Segletes Jan 29 at 2:38
2

After playing with it for a while, I think that datatool is a better tool for what I need. Thanks everyone for helping!

\documentclass{standalone}
\usepackage{tikz,datatool,filecontents}
\begin{filecontents*}{mydata.csv}
   code,text,
  {0,0,0},black,
  {1,1,0},yellow,
  {1,0,1},pink
\end{filecontents*}
\DTLsetseparator{,}
\DTLloaddb{myfile}{mydata.csv}
\begin{document}
\DTLforeach*
   {myfile}
   {\code=code,\text=text}% assignment
   {% Stuff to do at each iteration:
   \expandafter\expandafter\definecolor{tempcolor}{rgb}{\code}
   \textcolor{tempcolor}{\text} 
 }
 \end{document}

enter image description here

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.