让我解释一下我的问题。我有一个简单的 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)上测试。
一些说明:
你需要莱雅(LibYAML 与 Lua 的绑定)Lua 库,以及Lua POSIX库。这些对应于 Debian 和衍生产品中的 Lua 库 lua-yaml 和 lua-posix。根据您的操作系统/发行版,这些可能适用于您的操作系统/发行版,否则您必须安装它们。
包含该
luapackageloader
包很重要,否则将找不到外部 Lua 包。如您所见,我在 中添加了 Lua 的额外搜索路径util.lua
,但如果没有该包,它们将被忽略。请参阅texdoc luapackageloader
了解更多信息。习惯上,在将 Lua 函数暴露给 TeX 时,会为其添加一个 TeX 包装器(在本例中为
\readYAML
),该包装器通常位于 .sty 文件中。但在本例中,为了简单起见,我仅将其包含在 TeX 文件中。为简单起见,我使用内置命令将
sr.yaml
(YAML 文件) 和util.lua
(LUA 文件) 包含在 TeX 文件中 。这将创建文件和 ,前提是使用了选项。sr.tex
filecontents
sr.yaml
util.lua
-shell-escape
lualatex
您需要使用以下方式调用
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: