我正在使用 Archlinux 和 texlive。在 2022-01-02 将我的安装更新为最新的 Archlinux texlive-packets 后,LuaLaTeX 的某些功能似乎发生了重大变化。例如,tex.sprint(STRING)
在 的文件名参数中不再起作用\lstinputlisting{FILENAME}
。
更新之前,以下 MWE 正在运行:
\documentclass{minimal}
\usepackage{luacode}
\usepackage[procnames]{listings}
\begin{filecontents*}{before-file1_test_label.lst}
\LaTeX~macros work
\end{filecontents*}
\newcommand{\lstSHAName}[2]{before-\directlua{
local string = "#1"
tex.print(string .. '_test')
}_#2.lst}
\begin{document}
%\lstinputlisting{\lstSHAName{file1}{label}} % was working before, now crashes
\lstinputlisting{before-file1_test_label.lst} % still works, but requires setting path manually
\end{document}
如果我使用新版本的 texlive 进行编译,编译将停止并显示以下错误消息:
! Missing \endcsname inserted.
<to be read again>
global
l.16 \lstinputlisting{\lstSHAName{file1}{label}}
更让人怀疑问题与更新有关的是,我的同事在 Windows 上使用 MikTeX 仍然可以毫无问题地编译旧代码。
解决方法
这个想法是使用tex.sprint(STRING)
LuaLaTeX 的功能来打印\lstinputlisting
带有所有选项的命令,然后让 LaTeX 执行它。
\documentclass{minimal}
\usepackage{luacode}
\usepackage[procnames]{listings}
\begin{filecontents*}{before-file1_test_label.lst}
\LaTeX~macros work
\end{filecontents*}
\newcommand{\printListing}[3]{%
\directlua{
local strA = "#1"
local strB = "#2"
local strC = "#3"
local string = "\\lstinputlisting[caption={" .. strC .. "}]{before-" .. strA .. "_test_" .. strB .. ".lst}"
tex.sprint(string)
}
}
\begin{document}
\printListing{file1}{label}{Test caption} % works
%\printListing{file1}{label}{Test caption with \LaTeX~macro} % throws an error due to expansion
\printListing{file1}{label}{Test caption with \\LaTeX{} macro} % works, but tilde would have also to be escaped differently
\end{document}
传递 LaTeX 宏\directlua
而不进行转义(第 22 行)会导致错误
! Undefined control sequence.
\S@10 ->\gdef \tf@size
{10}\gdef \sf@size {7}\gdef \ssf@size {5}
l.22
手动转义在某种程度上是可行的。但是,最初的想法是为没有经验的 LaTeX 用户提供一个包装器宏。必须知道如何在 LaTeX 中转义,以便 lua 能够正确处理它,这违背了最初的目的。
问题
- 在将文件路径传输到 之前,可以通过类型转换/扩展/转义来解决此问题吗
\lstinputlisting
?如果可以,该如何解决(无法使其工作;总是出现相同的错误)? - 有没有一种解决方法,
expl3
可以在将参数传递给之前对参数进行预处理\directlua
,以便 lua 不会扩展宏(例如,传递\\LaTeX
而不是\LaTeX
)?
已安装 texlive 包
我认为与该问题相关的软件包:
- texlive-bin 2021.5945-1,构建日期:2021-12-27
- texlive-core 2021.61403-1,构建日期:2021-12-27
- texlive-formats-extra 2021.57972-1,构建日期:2021-04-06
- texlive-latexextra 2021.61405-1,构建日期:2021-12-27
答案1
1.
一般情况下,不会。根据 TeX 解析规则,如果你有
\lstinputlisting{\lstSHAName{file1}{label}}
然后 TeX 将\lstinputlisting
首先扩展并将参数{\lstSHAName{file1}{label}}
(删除括号后)传递给它,并且不能保证\lstinputlisting
宏会扩展该参数。
但是仍然可以重新定义\lstinputlisting
(可能很脆弱并且会干扰包装listings
!)。
或者你也可以做类似的事情
\injectlstSHAName{
\lstinputlisting{\lstSHAName{file1}{label}}
}
外部宏读取参数(如果您想允许用户在内部使用 catcode 更改命令,则逐字读取),执行适当的替换,然后打印回内容。
2.
一种方法是逐字读取参数(参见下面代码的第 1 部分)。
另一种方式是detokenize
it。在这种情况下,无论如何它都是一样的,因为标题被当作参数来抓取(即\lstinputlisting[caption={a {\verb+123+} b}]{before-file1_test_label.lst}
无论如何都行不通。cprotect
但是,仍然可以让它与 一起工作。)
还有一种方法是根本不通过 Lua 进行往返,而是将其保留在 TeX 中。这种方法的优点是保留了标记 catcode,以防用户知识渊博并将奇怪的 catcode 组合传递到命令中。
%! TEX program = lualatex
\documentclass{minimal}
\usepackage{luacode}
\usepackage[procnames]{listings}
\begin{filecontents*}{before-file1_test_label.lst}
\LaTeX~macros work
\end{filecontents*}
\begin{document}
% ======== method 1
\NewDocumentCommand{\printListing}{mmv}{%
\directlua{
local strA = "#1"
local strB = "#2"
local strC = "\luaescapestring{#3}"
local string = "\\lstinputlisting[caption={" .. strC .. "}]{before-" .. strA .. "_test_" .. strB .. ".lst}"
tex.sprint(string)
}%
}
\printListing{file1}{label}{Test caption}
\printListing{file1}{label}{Test caption with \LaTeX~macro}
% ======== method 2
\RenewDocumentCommand{\printListing}{mmm}{%
\directlua{
local strA = "#1"
local strB = "#2"
local strC = "\luaescapestring{\detokenize{#3}}"
local string = "\\lstinputlisting[caption={" .. strC .. "}]{before-" .. strA .. "_test_" .. strB .. ".lst}"
tex.sprint(string)
}%
}
\printListing{file1}{label}{Test caption with \LaTeX~macro}
% ======== method 3
\RenewDocumentCommand{\printListing}{mmm}{%
\lstinputlisting[caption={#3}]{before-#1_test_#2.lst}
}
\printListing{file1}{label}{Test caption with \LaTeX~macro}
\end{document}
事实证明方法 3 是最简单的。