从 LaTeX 读取 YAML 字段

从 LaTeX 读取 YAML 字段

让我解释一下我的问题。我有一个简单的 YAML 文件,其中自动生成了一系列实验,如下所示

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

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

我想做的是在编译期间以某种方式自动从文件中检索值并将其插入文本和/或表格中。例如,我希望执行以下操作:

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

第一个问题:您认为这是可能的吗?您对如何实现这一点有什么建议吗?

顺便说一句,如果这可能有帮助,我的工作流程基于 LuaLaTeX。谢谢!

答案1

虽然不是完整的 YAML 解析器,但应该足以满足您的特定需求。我以前\jobname只是不破坏我的文件,您可以使用自己的文件。

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

在此处输入图片描述

您可能需要将读取阶段与数据检索阶段分离,以获得可扩展的宏。

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

如果你添加,之前\ExplSyntaxOff

\NewDocumentCommand{\printYAML}{mm}
 {% #1 = YAML file, #2 = property
  \begin{itemize}
  \prop_map_inline:cn { g_sr_readyaml_#1_prop }
   {
    \str_if_in:nnT { ##1 } { #2@ } { \item \__sr_readyaml_entry:n { ##1 }:~##2 }
   }
  \end{itemize}
 }
\cs_new:Nn \__sr_readyaml_entry:n
 {
  \__sr_readyaml_entry:w #1 \q_stop
 }
\cs_new:Npn \__sr_readyaml_entry:w #1 @ #2 \q_stop { #2 }

然后调用

\printYAML{\jobname}{error-metric1}

将打印

在此处输入图片描述

答案2

请参阅下面的示例莱雅,在 Debian 11(bullseye)上测试。

一些说明:

  1. 你需要莱雅(LibYAML 与 Lua 的绑定)Lua 库,以及Lua POSIX库。这些对应于 Debian 和衍生产品中的 Lua 库 lua-yaml 和 lua-posix。根据您的操作系统/发行版,这些可能适用于您的操作系统/发行版,否则您必须安装它们。

  2. 包含该luapackageloader包很重要,否则将找不到外部 Lua 包。如您所见,我在 中添加了 Lua 的额外搜索路径 util.lua,但如果没有该包,它们将被忽略。请参阅 texdoc luapackageloader了解更多信息。

  3. 习惯上,在将 Lua 函数暴露给 TeX 时,会为其添加一个 TeX 包装器(在本例中为\readYAML),该包装器通常位于 .sty 文件中。但在本例中,为了简单起见,我仅将其包含在 TeX 文件中。

  4. 为简单起见,我使用内置命令将sr.yaml(YAML 文件) 和util.lua(LUA 文件) 包含在 TeX 文件中 。这将创建文件和 ,前提是使用了选项。sr.texfilecontentssr.yamlutil.lua-shell-escapelualatex

  5. 您需要使用以下方式调用lualatex -shell-escape sr.tex

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %% sr.tex
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \documentclass[12pt]{standalone}
    \usepackage{luapackageloader} % Required for Lua to look in standard
                                  % Lua paths.
    \begin{filecontents}[overwrite,noheader]{sr.yaml}
    error-metric1:
        method1: fee
        method2: fi
        method3: fum
    
    error-metric2:
        method1: foo
        method2: bar
        method3: baz
    \end{filecontents}
    
    \begin{filecontents}[overwrite,noheader]{util.lua}
    package.cpath="/usr/local/lib/lua/5.3/?.so;/usr/lib/x86_64-linux-gnu/lua/5.3/?.so;\z
    /usr/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/loadall.so;./?.so"..package.cpath
    package.path="/usr/local/share/lua/5.3/?.lua;/usr/local/share/lua/5.3/?/init.lua;\z
    /usr/local/lib/lua/5.3/?.lua;/usr/local/lib/lua/5.3/?/init.lua;\z
    /usr/share/lua/5.3/?.lua;/usr/share/lua/5.3/?/init.lua;./?.lua"..package.path
    
    local function loadYAML(config)
       local lyaml = require "lyaml"
       local posix = require "posix"
       local s
       if posix.stat(config) ~= nil then
          local f = io.open(config)
          s = f:read("*all")
          f:close()
          return lyaml.load (s)
       end
    end
    
    local function readYAML(config, keyA, keyB)
       tex.sprint(loadYAML(config)[keyA][keyB])
    end
    
    return {readYAML=readYAML}
    \end{filecontents}
    
    \directlua{util_lualib = require "util"}
    \NewDocumentCommand{\readYAML}{m m m}
    {
      \directlua{util_lualib.readYAML("\luaescapestring{#1}",
      "\luaescapestring{#2}", "\luaescapestring{#3}")}%
    }
    
    \begin{document}
    The error rate for the method1 is 
    \readYAML{sr.yaml}{error-metric1}{method1}.
    \end{document}
    
    %%% Local Variables:
    %%% mode: latex
    %%% TeX-engine: luatex
    %%% TeX-master: t
    %%% End:
    

在此处输入图片描述

相关内容