3

Let me explain my problem. I have a simple YAML file automatically generated with a series of experiments that looks like as follows

error-metric1:
    method1: value
    method2: value
    method3: value

error-metric2:
    method1: value
    method2: value
    method3: value

What I would like to do is to - somehow - retrieve values automatically from the file during compilation and insert them in text and/or tables. For example, I'm looking to do something like:

The error rate for the method1 is \readYAML{file}{error-metric1}{method1}

First question: do you think it is possible? Do you have any suggestions on how it can be done?

BTW, if this might help, my workflow is based on LuaLaTeX. Thanks!

  • Welcome to TeX.SX! – Bobyandbob Aug 9 '18 at 9:52
  • Will each metric always have the same number of methods, or might the number of methods vary with the metric? Also, are the words "error-metric" and "method" standard words that actually appear in the file, or are you only using them as placeholder words? – Steven B. Segletes Aug 9 '18 at 10:49
2

Not a complete YAML parser, but this should be sufficient for your specific need. I used \jobname just not to clobber my files, you can use your own files.

\begin{filecontents*}{\jobname.yaml}
error-metric1:
    method1: 1
    method2: 2
    method3: 3

error-metric2:
    method1: 4
    method2: 5
    method3: 6
\end{filecontents*}

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\readYAML}{mmm}
 {% #1 = file (no .yaml extension)
  % #2 = section
  % #3 = key
  \sr_readyaml:nnn { #1 } { #2 } { #3 }
 }

\tl_new:N \l__sr_readyaml_currsection_tl
\seq_new:N \l__sr_readyaml_line_seq
\ior_new:N \g_sr_readyaml_stream

\cs_generate_variant:Nn \seq_set_split:Nnn { NV }
\cs_generate_variant:Nn \prop_gput:Nnn { cxx }
\prg_generate_conditional_variant:Nnn \tl_if_blank:n { f } { TF }

\cs_new_protected:Nn \sr_readyaml:nnn
 {
  \prop_if_exist:cF { g_sr_readyaml_#1_prop }
   {
    \__sr_readyaml_read:n { #1 }
   }
  \prop_item:cn { g_sr_readyaml_#1_prop } { #2@#3 }
 }

\cs_new_protected:Nn \__sr_readyaml_read:n
 {
  \prop_new:c { g_sr_readyaml_#1_prop }
  \ior_open:Nn \g_sr_readyaml_stream { #1.yaml }
  \ior_str_map_inline:Nn \g_sr_readyaml_stream
   {
    \__sr_readyaml_line:nn { #1 } { ##1 }
   }
  \ior_close:N \g_sr_readyaml_stream
 }

\cs_new_protected:Nn \__sr_readyaml_line:nn
 {% #1 is the file name, #2 the current line
  \tl_if_blank:nF { #2 }
   {
    \seq_set_split:NVn \l__sr_readyaml_line_seq \c_colon_str { #2 }
    \tl_if_blank:fTF { \seq_item:Nn \l__sr_readyaml_line_seq { 2 } }
     {% nothing after the colon, set the current section
      \tl_set:Nx \l__sr_readyaml_currsection_tl { \seq_item:Nn \l__sr_readyaml_line_seq { 1 } }
     }
     {% value after the colon, add to the property list
      \prop_gput:cxx
       % prop name
       { g_sr_readyaml_#1_prop }
       % key
       { \l__sr_readyaml_currsection_tl @ \seq_item:Nn \l__sr_readyaml_line_seq { 1 } }
       % value
       { \seq_item:Nn \l__sr_readyaml_line_seq { 2 } }
     }
   }
 }

\ExplSyntaxOff

\begin{document}

\readYAML{\jobname}{error-metric1}{method1} \par
\readYAML{\jobname}{error-metric1}{method2} \par
\readYAML{\jobname}{error-metric1}{method3} \par
\readYAML{\jobname}{error-metric2}{method1} \par
\readYAML{\jobname}{error-metric2}{method2} \par
\readYAML{\jobname}{error-metric2}{method3} \par

\end{document}

enter image description here

You might want to split off the reading phase from the data retrieving phase, in order to get an expandable macro.

\begin{filecontents*}{\jobname.yaml}
error-metric1:
    method1: 1
    method2: 2
    method3: 3

error-metric2:
    method1: 4
    method2: 5
    method3: 6
\end{filecontents*}

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\readYAML}{m}
 {% #1 = file (no .yaml extension)
  \sr_readyaml:n { #1 }
 }
\NewExpandableDocumentCommand{\getfromYAML}{mmm}
 {% #1 = file (no .yaml extension)
  % #2 = section
  % #3 = key
  \prop_item:cn { g_sr_readyaml_#1_prop } { #2@#3 }
 }

\tl_new:N \l__sr_readyaml_currsection_tl
\seq_new:N \l__sr_readyaml_line_seq
\ior_new:N \g_sr_readyaml_stream

\cs_generate_variant:Nn \seq_set_split:Nnn { NV }
\cs_generate_variant:Nn \prop_gput:Nnn { cxx }
\prg_generate_conditional_variant:Nnn \tl_if_blank:n { f } { TF }

\cs_new_protected:Nn \sr_readyaml:n
 {
  \prop_if_exist:cF { g_sr_readyaml_#1_prop }
   {
    \__sr_readyaml_read:n { #1 }
   }
 }

\cs_new_protected:Nn \__sr_readyaml_read:n
 {
  \prop_new:c { g_sr_readyaml_#1_prop }
  \ior_open:Nn \g_sr_readyaml_stream { #1.yaml }
  \ior_str_map_inline:Nn \g_sr_readyaml_stream
   {
    \__sr_readyaml_line:nn { #1 } { ##1 }
   }
  \ior_close:N \g_sr_readyaml_stream
 }

\cs_new_protected:Nn \__sr_readyaml_line:nn
 {% #1 is the file name, #2 the current line
  \tl_if_blank:nF { #2 }
   {
    \seq_set_split:NVn \l__sr_readyaml_line_seq \c_colon_str { #2 }
    \tl_if_blank:fTF { \seq_item:Nn \l__sr_readyaml_line_seq { 2 } }
     {% nothing after the colon, set the current section
      \tl_set:Nx \l__sr_readyaml_currsection_tl { \seq_item:Nn \l__sr_readyaml_line_seq { 1 } }
     }
     {% value after the colon, add to the property list
      \prop_gput:cxx
       % prop name
       { g_sr_readyaml_#1_prop }
       % key
       { \l__sr_readyaml_currsection_tl @ \seq_item:Nn \l__sr_readyaml_line_seq { 1 } }
       % value
       { \seq_item:Nn \l__sr_readyaml_line_seq { 2 } }
     }
   }
 }

\ExplSyntaxOff

\begin{document}

% read the file
\readYAML{\jobname}

\getfromYAML{\jobname}{error-metric1}{method1} \par
\getfromYAML{\jobname}{error-metric1}{method2} \par
\getfromYAML{\jobname}{error-metric1}{method3} \par
\getfromYAML{\jobname}{error-metric2}{method1} \par
\getfromYAML{\jobname}{error-metric2}{method2} \par
\getfromYAML{\jobname}{error-metric2}{method3} \par

\end{document}

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.