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}

4 Answers 4

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

7
  • @ 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
    Commented Jan 28, 2019 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. Commented Jan 28, 2019 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
    Commented Jan 28, 2019 at 20:39
  • @TonyTan Let me think about your request, but perhaps look at the listofitems manual as well: ctan.org/pkg/listofitems Commented Jan 28, 2019 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} Commented Jan 29, 2019 at 2:38
3

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

You must log in to answer this question.

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