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}?

5
  • The readarray package might help: tex.stackexchange.com/questions/198026/… Dec 10, 2014 at 14:33
  • I store such informations in bibfiles and retrieve them with biblatex. Another possibility is datatools. Dec 10, 2014 at 14:57
  • and pgfplotstable too.
    – percusse
    Dec 10, 2014 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, 2014 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, 2014 at 16:28

2 Answers 2

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}
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

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.