5

I would like to access the value of a record using a key.

Let's say an external file with two fields delimited b comma is as follows:

A, Cycle A Morning Summer
B, Cycle A2 Night
C, Not a defined 1st cycle
..., .....
Z, Last cycle

With a macro I would like to pass the key value and print the second column \printcycle{A01} % I should get "Cycle A morning summer"

I saw this example (songs package: how to store gtab definitions in an external file?) but I do not know how to retrieve only the text value from the second field. I get both the key and its value

\begin{filecontents*}{tabs.txt}
A, Cycle A Morning Summer
B, Cycle A2 Night
Z, Not a defined 1st cycle
\end{filecontents*}

\documentclass{book}
\usepackage{xparse}
\ExplSyntaxOn
\ior_new:N \g_fedelibre_get_tabs_ior
\prop_new:N \g_fedelibre_tabs_prop

\cs_new_protected:Npn \fedelibre_add_to_prop:w #1,#2!!
{
\prop_gput:Nnn \g_fedelibre_tabs_prop {#1} {#2}
}
\ior_open:Nn \g_fedelibre_get_tabs_ior {tabs.txt} 
\ior_str_map_inline:Nn \g_fedelibre_get_tabs_ior
{
\fedelibre_add_to_prop:w #1!!
} 
\DeclareDocumentCommand{ \fedetab }{ v }
{
\prop_get:NnNTF \g_fedelibre_tabs_prop {#1} \l_tmpa_tl
    {
\fedelibre_gtab:nV {#1} \l_tmpa_tl
}
{
   \msg_term:n {Maybe~ you~ have~ mispelled~ your~ tab!}
}
}
   \cs_set_eq:NN \fedelibre_gtab:nn \textit
\cs_generate_variant:Nn \fedelibre_gtab:nn { nV }
\ExplSyntaxOff
\begin{document}
    \fedetab{A} 

\end{document}
  • Your "external file" example is much different from what you write in tabs.txt. Why is that and how should it be handled? – Werner Feb 10 '17 at 6:11
  • @werner I was trying to make a workable example. I made some edits to make it little bit more clear. – hpcolos Feb 10 '17 at 7:15
3

You can read the contents of the file using \read. If you want to put the entries into \pgfkeys the first problem you have it that pgf keys need to be initialised. You can get around this by defining an unknown handler:

\usepackage{pgfkeys}
\pgfkeys{/cycles/.is family, cycles,
  % allow arbitrary unknown keys and set with \pgfkeyssetvalue
  .unknown/.code={\pgfkeyssetvalue{\pgfkeyscurrentpath/\pgfkeyscurrentname}{#1}},
}

In fact, this is all you for the definition of \pgfkeys{/cycles}. What this does is set the value of a key that is unknown. As a bonus, you can use \pgfkeysifdefined to test if a key is defined, so the \printcycle command can be defined as:

\newcommand\printcycle[1]{% print the key if it is defined and ??? otherwise
    \pgfkeysifdefined{/cycles/#1}{\pgfkeysvalueof{/cycles/#1}}{???}%
}

Notice that ??? is printed if the key is not known (i.e. the key is not in the data file).

Now it remains to read in the file and put the data into \pgfkeys{/cycles}. I used \SplitList from the xparse package to split an input line like A01, Cycle A Morning Summer into a key and its value and then there are some annoying expansion issues into order to get the keys to play nice - and you have to check for empty lines and lines consisting of \par as well, so this complicates the reading a little.

The code below defines a macro \ReadCycles for reading the data. The MWe produces:

enter image description here

Here is the full code:

\RequirePackage{filecontents}% write the data file
\begin{filecontents}{cycles.csv}
A01,Cycle A Morning Summer
A02,Cycle A2 Night
AC1,Not a defined 1st cycle
Z01,Last cycle
\end{filecontents}

\documentclass{article}
\usepackage{xparse}% mainly for \SplitList
\usepackage{pgfkeys}
\pgfkeys{/cycles/.is family, cycles,
  % allow arbitrary unknown keys and set with \pgfkeyssetvalue
  .unknown/.code={\pgfkeyssetvalue{\pgfkeyscurrentpath/\pgfkeyscurrentname}{#1}},
}
\newcommand\printcycle[1]{% print the key if it is defined and ??? otherwise
    \pgfkeysifdefined{/cycles/#1}{\pgfkeysvalueof{/cycles/#1}}{???}%
}

% split input line into key-value pair
\NewDocumentCommand{\AddCycle}{ >{\SplitList{,}} m }{%
  \AddCycleValue #1
}
% put a key-value pair into \pgfkeys{/cycles}
\newcommand\AddCycleValue[2]{\expandafter\pgfkeys\expandafter{/cycles,#1=#2}}

% \ReadCycles{filename} keys the keys in <filename> into \pgfkeys{/cycles}
\newread\cyclefile% file handler
\def\apar{\par}% \ifx\par won't work but \ifx\apar will
\newcommand\ReadCycles[1]{% read file into [\pgfkeys{/cycles}
  \openin\cyclefile=#1% open file for reading
  \loop\unless\ifeof\cyclefile% loop until end of file
    \read\cyclefile to \cycleline% read line from file
    \ifx\cycleline\apar% test for \par
    \else%
      \ifx\cycleline\empty\relax% skip over empty lines/comments
      \else\expandafter\AddCycle\expandafter{\cycleline}%
      \fi%
    \fi%
  \repeat% end of file reading loop
  \closein\cyclefile% close input file
}
\ReadCycles{cycles.csv}% read the file

\begin{document}

  A01 is \printcycle{A01}

  A02 is \printcycle{A02}

  Z01 is \printcycle{Z01}

  Z02 is \printcycle{Z02}

\end{document}

Btw, for consistency with pgfkeys, I would use = instead of , in the data file:

A01=Cycle A Morning Summer
A02=Cycle A2 Night
AC1=Not a defined 1st cycle
Z01=Last cycle

For this you just have to use \SplitList{=} above. Better still, you could replace the two macros \AddCycle and \AddCycleValue with the single macro

\newcommand\AddCycle[1]{\expandafter\pgfkeys\expandafter{/cycles, #1}}

So you would no longer need xparse.

  • Very clever. From my limited knowldge of low level programming with Latex, this solution is more readable and as fast as the solution proposed by egreg (although no official benchmark was used). I am curious to know if the cycles.csv is created on RAM memory or on the hard drive. – hpcolos Feb 10 '17 at 17:24
  • @hpcolos The filecontents environment writes its contents to a file on the hard drive. In your real application you should just save the file rather than using filecontents because filecontent will rewrite the file every time latex is run. The main reason, I believe, that people use it on TeX.SX is to make minimal working examples self-contained because it lets you create any external files that are needed when latex is run. – Andrew Feb 10 '17 at 20:27
3

Some changes are in order. The file should be read in with \ior_map_inline:Nn and not \ior_str_map_inline:Nn; also, the reading should be done outside the scope of \ExplSyntaxOn.

\begin{filecontents*}{\jobname.txt}
A, Cycle A Morning Summer
B, Cycle A2 Night
Z, Not a defined 1st cycle
\end{filecontents*}

\documentclass{book}
\usepackage{xparse}

\ExplSyntaxOn
\ior_new:N \g_hpcolos_get_tabs_stream
\prop_new:N \g_hpcolos_tabs_prop

\cs_new_protected:Nn \hpcolos_add_to_prop:n
 {
  \prop_gput:Nff \g_hpcolos_tabs_prop
   { \clist_item:nn { #1 } { 1 } }
   { \clist_item:nn { #1 } { 2 } }
 }
\cs_generate_variant:Nn \prop_gput:Nnn { Nff }

\NewDocumentCommand{\readtitlefile}{m}
 {
  \ior_open:Nn \g_hpcolos_get_tabs_stream { #1 }
  \ior_map_inline:Nn \g_hpcolos_get_tabs_stream
   {
    \hpcolos_add_to_prop:n { ##1 }
   }
  \ior_close:N \g_hpcolos_get_tabs_stream
 }
\DeclareExpandableDocumentCommand{ \gettitle }{ m }
 {
  \prop_item:Nn \g_hpcolos_tabs_prop { #1 }
 }
\ExplSyntaxOff

\readtitlefile{\jobname.txt}

\begin{document}

\gettitle{A}

\gettitle{B}

\gettitle{Z}

\end{document}

enter image description here

  • I marked your response as my favorite solution because compilation is faster than using the datatool without overwhelming the system with so many packages. Where can I learn more about using latex programming? I do have ample background in object oriented programming with modern languages. The typesetting macro commands is straight forward, but Latex3 programming looks to me like a blackbox. I have not been able to find useful learning tools. I already looked at ctan.sharelatex.com/tex-archive/macros/latex/contrib/l3kernel/…. I''ll look for more useful tutorials – hpcolos Feb 10 '17 at 17:05
2

I'd be inclined to use datatool for this:

\RequirePackage{filecontents}
\begin{filecontents}{cycles.csv}
A01, Cycle A Morning Summer
A02, Cycle A2 Night
AC1, Not a defined 1st cycle
..., ...
Z01, Last cycle
\end{filecontents}

\documentclass{article}
\usepackage{datatool}
\DTLloaddb[noheader, keys={key,value}]{cycles}{cycles.csv}
\newcommand{\printcycle}[1]{\DTLfetch{cycles}{key}{#1}{value}}

\begin{document}

A01: \printcycle{A01}

Z01: \printcycle{Z01}

AC1: \printcycle{AC1}

\end{document}
  • Although it works (and I will give you the vote for a solution) I would like to see if someone can improve the low level functions in my example as the datatool you propose I use seems to slows down the compilation of a big book with about 1000 entries I search for key values – hpcolos Feb 10 '17 at 7:05

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.