5

I'd like to store some basic information on people in a file, and want to be able for LaTeX to process this information. More specifically, I'd like to store something like:

id:john1; fname: John; lname: Doe; mail: example@doe.com
id:harry1; fname: Harry; lname: Potter; mail: harry@hogwarts.com

etc. Is this possible in LaTeX, i.e. is there a way to 1) make this list and 2) place this information in a document by something like \data{john1}{fname}?

konewka
  • 377
  • The readarray package might help: http://tex.stackexchange.com/questions/198026/getting-data-dynamically-into-latex-from-a-spreadsheet/198030#198030 – Steven B. Segletes Dec 10 '14 at 14:33
  • I store such informations in bibfiles and retrieve them with biblatex. Another possibility is datatools. – Ulrike Fischer Dec 10 '14 at 14:57
  • and pgfplotstable too. – percusse Dec 10 '14 at 15:54
  • I don't understand why here is accepted solution which doesn't implement the reading data from external file. My understanding were that the external file is supposed. If no, then my solution contains only six lines. Compare this with 37 lines and one external package (next thousands lines) used in the accepted solution. – wipet Dec 10 '14 at 16:14
  • You're absolutely right, I've been messing around a little more and found your solution to be nicer to work with! – konewka Dec 10 '14 at 16:28

2 Answers2

5

You can read the file by \read primitive. The data are stored in control sequences \base:id:name. The \data macro simply expands this control sequence.

\newread\basein
\def\readbase #1 {\bgroup \endlinechar=-1 \openin\basein=#1 \readbaseA}
\def\readbaseA{\ifeof\basein \egroup \else
   \read\basein to\tmp
   \ifx\tmp\empty \else \expandafter\base\tmp; :.; \fi
   \expandafter\readbaseA \fi
}
\def\base id:#1; {\def\baseid{#1}\baseA}
\def\baseA #1:#2#3; {\ifx\end#1\end\else
   \expandafter\gdef\csname base:\baseid:#1\endcsname{#2#3}%
   \expandafter\baseA\fi
}
\def\data#1#2{\csname base:#1:#2\endcsname}

\readbase base.txt  % reading the file

\data{john1}{fname}
wipet
  • 74,238
2

A self contained implementation:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\storedata}{mm}
 {% pass control to an inner function
  \konewka_store_data:nn { #1 } { #2 }
 }

\DeclareExpandableDocumentCommand{\getdata}{mm}
 {% just retrieve the property from the appropriate property list
  \prop_item:cn { g_konewka_data_#1_prop } { #2 }
 }

\seq_new:N \l__konewka_data_temp_seq
\seq_new:N \l__konewka_field_temp_seq

\cs_new_protected:Nn \konewka_store_data:nn
 {
  % create a property list for an ID
  \prop_new:c { g_konewka_data_#1_prop }
  % split the second argument
  \seq_set_split:Nnn \l__konewka_data_temp_seq { ; } { #2 }
  % process each field
  \seq_map_inline:Nn \l__konewka_data_temp_seq
   {
    \__konewka_process_field:nn { #1 } { ##1 }
   }
 }

% we need a colon with the appropriate category code    
\group_begin:
\char_set_lccode:nn { `? } { `: }
\tl_to_lowercase:n
 {
  \group_end:
  \tl_const:Nn \c_konewka_colon_tl { ? }
 } 

% split the field at the colon and store the property    
\cs_new_protected:Nn \__konewka_process_field:nn
 {
  \seq_set_split:NVn \l__konewka_data_field_seq \c_konewka_colon_tl { #2 }
  \prop_gput:cxx { g_konewka_data_#1_prop }
   { \seq_item:Nn \l__konewka_data_field_seq { 1 } }
   { \seq_item:Nn \l__konewka_data_field_seq { 2 } }
 }
\cs_generate_variant:Nn \seq_set_split:Nnn { NV }
\cs_generate_variant:Nn \prop_gput:Nnn { cxx }

\ExplSyntaxOff

\storedata{john1}{fname: John; lname: Doe; mail: example@doe.com}
\storedata{harry1}{fname: Harry; lname: Potter; mail: harry@hogwarts.com}

\begin{document}

\begin{tabular}{cccc}
Id & fname & lname & mail \\
\hline
\texttt{john1} & \getdata{john1}{fname} & \getdata{john1}{lname} & \getdata{john1}{mail} \\
\texttt{harry1} & \getdata{harry1}{fname} & \getdata{harry1}{lname} & \getdata{harry1}{mail} \\
\end{tabular}

\end{document}

enter image description here


A version that also allows reading the data from a file. Here the file is added with filecontents, but can be any, so long as the format is as shown. Spaces around colons and semicolons will be ignored.

\begin{filecontents*}{\jobname.csv}
id:john1; fname: John; lname: Doe; mail: example@doe.com
id:harry1; fname: Harry; lname: Potter; mail: harry@hogwarts.com
\end{filecontents*}

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\storedata}{mm}
 {% pass control to an inner function
  \konewka_add_id:n { #1 }
  \konewka_store_data:nn { #1 } { #2 }
 }

\NewDocumentCommand{\readdata}{m}
 {
  \konewka_read_data:n { #1 }
 }

\DeclareExpandableDocumentCommand{\getdata}{mm}
 {% just retrieve the property from the appropriate property list
  \prop_item:cn { g_konewka_data_#1_prop } { #2 }
 }

\NewDocumentCommand{\listIDs}{}
 {% just an example
  \seq_use:Nn \g_konewka_id_seq { ,~ }
 }

%%% variables

% we need a colon with the appropriate category code    
\tl_const:Nx \c_konewka_colon_tl { \tl_to_str:n {:} }
% other variables
\seq_new:N \g_konewka_id_seq

\tl_new:N \l__konewka_id_tl
\seq_new:N \l__konewka_data_temp_seq
\seq_new:N \l__konewka_field_temp_seq
\ior_new:N \g__konewka_read_data_stream

%%% variants of kernel functions
\cs_generate_variant:Nn \seq_set_split:Nnn { NV , NVV }
\cs_generate_variant:Nn \prop_gput:Nnn { cxx }

%%% our functions

% add the new id to a sequence for possible later usage
\cs_new_protected:Nn \konewka_add_id:n
 {
  \seq_gput_right:Nn \g_konewka_id_seq { #1 }
 }
\cs_generate_variant:Nn \konewka_add_id:n { V }

% the inner function for \storedata
\cs_new_protected:Nn \konewka_store_data:nn
 {
  % create a property list for an ID
  \prop_new:c { g_konewka_data_#1_prop }
  % split the second argument at semicolons
  \seq_set_split:Nnn \l__konewka_data_temp_seq { ; } { #2 }
  % populate the property list
  \__konewka_process_entry:n { #1 }
 }

% the inner function for \readdata
\cs_new_protected:Nn \konewka_read_data:n
 {
  \ior_open:Nn \g__konewka_read_data_stream { #1 }
  \ior_map_inline:Nn \g__konewka_read_data_stream
   {
    % split a line into fields
    \seq_set_split:Nnn \l__konewka_data_temp_seq { ; } { ##1 }
    % retrieve the first field (ID)
    \seq_pop_left:NN \l__konewka_data_temp_seq \l__konewka_id_tl
    % split at colon and set the ID to the second part
    \seq_set_split:NVV \l__konewka_data_field_seq \c_konewka_colon_tl \l__konewka_id_tl
    \tl_set:Nx \l__konewka_id_tl { \seq_item:Nn \l__konewka_data_field_seq { 2 } }
    % add the id to the list
    \konewka_add_id:V \l__konewka_id_tl
    % populate the property list
    \__konewka_process_entry:V \l__konewka_id_tl
   }
 }

% auxiliary function
\cs_new_protected:Nn \__konewka_process_entry:n
 {
  \seq_map_inline:Nn \l__konewka_data_temp_seq
   {
    \seq_set_split:NVn \l__konewka_data_field_seq \c_konewka_colon_tl { ##1 }
    \prop_gput:cxx { g_konewka_data_#1_prop }
     { \seq_item:Nn \l__konewka_data_field_seq { 1 } }
     { \seq_item:Nn \l__konewka_data_field_seq { 2 } }
   }
 }
\cs_generate_variant:Nn \__konewka_process_entry:n { V }

\ExplSyntaxOff

\readdata{\jobname.csv}
\storedata{uthor1}{fname: Algernon; lname: Uthor; mail: a.uthor@tex.org}
\storedata{riter1}{fname: Walter; lname: Riter; mail: w.riter@latex.org}

\begin{document}

\begin{tabular}{cccc}
Id & fname & lname & mail \\
\hline
\texttt{john1} & \getdata{john1}{fname} & \getdata{john1}{lname} & \getdata{john1}{mail} \\
\texttt{harry1} & \getdata{harry1}{fname} & \getdata{harry1}{lname} & \getdata{harry1}{mail} \\
\texttt{uthor1} & \getdata{uthor1}{fname} & \getdata{uthor1}{lname} & \getdata{uthor1}{mail} \\
\texttt{riter1} & \getdata{riter1}{fname} & \getdata{riter1}{lname} & \getdata{riter1}{mail} \\
\end{tabular}

\medskip

The IDs are: \listIDs

\end{document}

enter image description here

egreg
  • 1,121,712