我试图将一段文本放入 LaTeX3 字符串中,以便稍后写入文件中(在写入之前,我还对该字符串进行了其他处理,例如文本替换、字符串连接……),但此文件可能不是是 LaTeX 文档,但是任意的文本/代码,因此我想精确保留输入,包括换行符、空格、制表符和许多字符,例如_{}$\
:
Hello % This is a comment that should not be removed
I want precisely this spacing, and I want to be allowed to use any text, like 1 + (1*2), héhé,
or:
def mycode(my_variable_with_underscore="hey"): # This is a comment (with a single sharp)
print(my_variable_with_underscore)
or latex macros like $\array{a & b\\c & d}$. And ideally unbalanced {} but I heard it was difficult
(maybe by replacing special tokens like \MYCLOSINGBRACE and \MYOPENINGBRACE in the final string?).
现在,我尝试玩\tl_rescan:nn
,尝试遵循这个答案但没能取得多大成功。
梅威瑟:
\documentclass[options]{article}
\ExplSyntaxOn
\iow_new:N \g_my_write
\cs_generate_variant:Nn \iow_now:Nn { NV }
%% Creates a new category table
%% Inspired by https://github.com/latex3/latex3/blob/0b851165c1dba9625f7ab80bb5c4cbd27f3e9af7/l3kernel/l3cctab.dtx
\cctab_const:Nn \c_my_active_cctab
{
\cctab_select:N \c_initex_cctab
\int_set:Nn \tex_endlinechar:D { -1 }
\int_step_inline:nnn { 0 } { 127 }
{ \char_set_catcode_active:n {#1} }
}
\NewDocumentCommand{\myExactWrite}{m}{
\str_new:N \g_my_string
\iow_open:Nn \g_my_write {my-output.txt}
% === Fails: the final string contains \tl_rescan code:
% \str_gset:Nn \g_my_string {\tl_rescan:nn {\cctab_select:N \c_my_active_cctab} { #1 }}
% === Fails: I get errors about missing $
% \def\mygset{\str_gset:Nn}
% \def\mystring{\g_my_string}
% \tl_rescan:nn {\cctab_select:N \c_my_active_cctab} { \mygset \g_my_string {#1} }
% In my code the write might arrives much later, even in another function, hence the use of a string
% === See that the input is not the expected one:
\str_set:Nn \g_my_string {#1}
\iow_now:NV \g_my_write \g_my_string
\iow_close:N \g_my_write
}
\ExplSyntaxOff
\usepackage{verbatim}
\begin{document}
%%% Level 1
\myExactWrite{
Hello % This is a comment that should not be removed
I want precisely this spacing, and I want to be allowed to use any text
}
% %% Level 2
% \myExactWrite{
% Hello % This is a comment that should not be removed
% I want precisely this spacing, and I want to be allowed to use any text, like 1 + (1*2), héhé,
% or:
% def mycode(my_variable_with_underscore="hey"): # This is a comment (with a single sharp)
% print(my_variable_with_underscore)
% or latex macros like $\array{a & b\\c & d}$. And ideally unbalanced {} but I heard it was difficult
% (maybe by replacing special tokens like \MYCLOSINGBRACE and \MYOPENINGBRACE in the final string?).
% }
The content of the file is:
\verbatiminput{my-output.txt}
\end{document}
答案1
需要考虑的一些有关 TeX 和 LaTeX 的事实:
TeX 中的所有编程实际上都是基于所谓的标记 (token)。
在 TeX 中,标记可以是控制序列标记或显式字符标记(具有属性类别和字符代码)。
Token 的产生方式如下:
- 通过让 TeX 读取 .tex 输入文件的行并对其进行标记,或者(在交互模式下)让 TeX 读取在控制台上输入的行并对其进行标记。
(\scantokens
可用于模拟将标记未扩展地写入文件,然后通过 . 加载该文件\input
) - 在扩展阶段,通过扩展可扩展的标记,例如宏或类似的东西等等。
\the⟨register or parameter⟩
- 使用基于 LuaTeX 的 TeX 引擎,通过让 Lua 后端将标记推送到标记流中,例如通过
token.put_next(token.create(...)...)
。
基于 TeX 中的宏编程(因此专注于标记!)的机制无法从由于标记化 .tex-input 的这些部分而产生的标记中精确地重新创建 .tex-input 的任意部分,因为在通过宏处理标记的阶段,无法获得有关宏处理的标记如何产生的信息。因此,如果这些标记是由于 TeX 读取/预处理 .tex 文件的行并对其进行标记而产生的,那么也无法获得有关这些 .tex-input 行是什么样子的信息。
.tex-input 的部分内容只能在标记化时通过调整 catcode-régime 来确保重新创建,并且
\endlinechar
- 在预处理和标记化过程中,不会丢弃任何源字符,
- 源中的每个字符仅产生一个(字符)标记,你可以通过该标记的属性(字符代码)推断出 .tex 输入文件中的相应字符,
- 有关行尾/换行的信息不会丢失。
(预处理过程中不丢弃任何字符是至关重要的一点,因为在任何情况下,.tex 输入行右端的空格字符都会被丢弃。有关预处理的更多详细信息,请参见下文。
除此之外,但这些事情可以通过在标记化之前调整 catcode-régime 来处理,以防 TeX 没有收集控制序列标记名称的第一个字符,- 类别代码 9 的字符被删除。
- 如果 TeX 的读取装置处于 S(跳过空白)或 N(换行)状态,则删除类别代码为 10(空格)的字符。
(后者就是为什么在正常的 catcode 制度下,您可以使用空格字符和水平制表符(在正常的 catcode 制度下,它们的类别代码为 10)来缩进代码。) - 当阅读设备处于状态 M(行中间)时,类别代码为 10(空格)的字符被标记为显式空格标记,即,标记为类别 10(空格)和字符代码 32 的显式字符标记。如果字符具有类别代码 10(空格),则无论相关字符在 TeX 的内部字符编码方案中的代码点编号是多少,生成的字符标记都具有字符代码 32。
- 在对第 5 类(行尾)字符进行标记时产生什么标记取决于读取设备的状态。
- 同一行中类别代码 5(行尾)字符之后的字符将被删除。
- 删除第 14 类(注释)的字符以及同一行上的后续字符。
- TeX 不喜欢遇到类别代码 15 的字符(无效)。...
)
- 通过让 TeX 读取 .tex 输入文件的行并对其进行标记,或者(在交互模式下)让 TeX 读取在控制台上输入的行并对其进行标记。
(未扩展的)写入控制序列标记的结果取决于整数参数 的值。应用、、、、
\escapechar
时也是如此。\string
\detokenize
\scantokens
\meaning
\show
当(未扩展)写入控制字标记时,TeX 会附加一个空格字符。即使在 .tex 输入文件中没有空格。例如,在正常的 catcode 机制下,输入
\TeX\TeX
被标记为两个控制字标记\TeX
。未扩展写入它们会产生字符序列\TeX␣\TeX␣
— ␣ 表示空格字符。当(未扩展)写入控制符号标记时,TeX 不会附加空格字符。这也适用于\scantokens
和\detokenize
。显式空间代币(在收集未限定的宏参数的第一个标记时,类别 10(空格)和字符代码 32 的显式字符标记)被删除。
哈希,即类别 6(参数)的显式字符标记,在写入文本文件或屏幕时会加倍。这也适用于
\scantokens
和\detokenize
。当 LaTeX 读取一行 .tex 输入时,在标记化之前会进行一些预处理:
字符从计算机平台的字符表示方案转换为 TeX 的内部字符表示方案,对于传统的 TeX 引擎而言,该方案为 ASCII,而对于基于 LuaTeX 和基于 XeTeX 的 TeX 引擎而言,该方案为 unicode,其中 ASCII 是其严格子集。
行右端的所有空格字符(以及基于某些 Web2C 版本的 TeX 实现中的所有水平制表符)都将被删除。没有办法绕过行尾空格的删除,即使切换到逐字模式也不行。(切换到逐字模式意味着暂时改变 catcode 机制,这反过来又会影响标记化,进而发生后.tex-input 的行是经过预处理的。
在行的右端附加一个字符,其在 TeX 的内部字符表示方案中的代码点等于整数参数的值
\endlinechar
。
如果\endlinechar
的值超出了 TeX 引擎内部字符表示方案中可用的代码点范围,则不会在行的右端附加任何字符。
通常 的值\endlinechar
是 13,表示回车符。通常回车符的类别代码是 5(行尾),这意味着 TeX 在标记化过程中遇到它时会采取以下行为:如果 TeX 正在收集控制序列标记名称的第一个字符,TeX 将在标记流中插入一个控制符号标记,其名称由回车符组成,也就是所谓的“控制回车”。
如果 TeX 没有收集控制序列标记名称的第一个字符,TeX 会删除该行的剩余字符,并且如果读取设备处于状态 S(跳过空格),则不会将任何标记附加到标记流;如果读取设备处于状态 M(行中间),则确实会将空格标记(字符代码 32,类别 10(空格))附加到标记流;如果读取设备处于状态 N(新行),则确实会将控制字标记附加
\par
到标记流,无论 的当前含义是什么\par
。
这就是为什么空行通常会产生\par
:像任何行一样,空行会附加行尾字符,通常是回车符。当遇到该回车符时,该行的字符没有产生其他标记,因此读取设备的状态为 N,同时遇到类别 5(行尾)的字符。因此 TeX 会将控制字标记附加\par
到标记流。
当 TeX 将字符标记写入文件时,根据所使用的底层 TeX 引擎(传统的 (pdf)TeX/XeTeX/LuaTeX)以及字符翻译的设置(那些您可以在 -notation 中指定要写入哪些字符的 .tcx 文件),字符翻译会发生,因此对于某些 TeX 引擎,回车符(这被认为有些特殊)以-notation
^^
写为;而对于其他引擎,回车符则写为相应的 ASCII 字节/utf8 字节序列。^^
^^M
\newlinechar
当 TeX 将标记写入文件或屏幕时,不会写入字符代码等于整数参数数量的显式字符标记,但会将其作为在另一行开头继续写入的指令。通常\newlinechar
具有表示换行符的值 10(ASCII 和 unicode 中的代码点 10;^^J
在^^
符号中;J
是拉丁字母表中的第10 个字母)。当 LaTeX 切换到 verbatim-catcode-régime 时,同样使用
+v
-argument-type,水平制表符的类别代码(ASCII 和 unicode 中的代码点 9,\^^I
在 TeX 的^^
-notation 中,而 I 是拉丁字母表中的第9 个字母)保持不变。即,在 verbatim-catcode-régime 中,水平制表符的类别代码为 10(空格),这反过来意味着在 verbatim-catcode-régime 中,水平制表符被标记为显式空格标记(字符代码 32,类别 10(空格)),这反过来意味着它们不是写为水平制表符,而是写为空格字符。当在 verbatim-catcode-régime 下将内容标记化以写入外部文本文件时,可以通过将水平制表符的类别代码切换为 12(其他)来解决此问题。当 LaTeX 切换到逐字编码模式时,回车符将获得类别代码 12(其他),因此在逐字模式下,由于 -mechanism 而附加到 .tex 输入行的回车符
\endlinechar
将被标记为类别 12(其他)的普通字符标记。
在编写此类普通回车符标记时,根据所使用的引擎和有效的字符转换,它们可能以^^
-notation 形式^^M
或相应的 ASCII 字节/utf8 字节序列形式编写。通常,由于机制的原因,构成 TeX 输入预处理行的字符集中的回车符仅出现在右端
\endlinechar
。因此,在书写时说\newlinechar=\endlinechar
/就可以完全避免输入回车符。\tex_newlinechar:D=\tex_endlinechar:D
这反过来意味着您需要知道写作的时间,当写作立即发生/就\immediate
/而言时,这很容易\tex_immediate:D
,但当写作被延迟到输出例程发出另一页时,这并不容易。
但是这样在写作时回车符不会明确写出(无论是作为 ASCII 字节/utf-8 字节序列还是 - 符号^^
),但它们只是表示写作应在另一行的开头继续。这样,它们会触发需要在计算机平台上安装的 TeX 触发的任何平台特定操作,以便在另一行的开头继续写作。正如 projectmbc 指出的那样,而不是说
\newlinechar=\endlinechar
/\tex_newlinechar:D=\tex_endlinechar:D
,你可以考虑在字符串中用-characters替换所有^^M
-characters^^J
因为这也将确保在将字符串写入文件时保留正确的换行符——在\str_replace_all:Nxx \g_my_string { \iow_char:N \^^M } { \iow_char:N \^^J }
通过某种变体进行写入之前\iow_now:Nn
。我认为这是一种更好的方法,因为可以随时进行替换,因此这种技术也可以在延迟写入时使用。expl3
\iow_now:Nn
及其变体的一个问题是,这些命令在内部通过\int_set:Nn
确保在写入时\newlinechar
表示换行符。这使得\newlinechar=\endlinechar
在写入时很难拥有。您可以\int_set:Nn
通过将其重新定义为一个只吞噬其参数的宏来暂时中和,但这将是一个丑陋的黑客行为。我建议改用 TeX 原语\tex_immediate:D \tex_write:D
。
如果必须解释的话我可能会这样做:
\documentclass{article}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Some margin-adjustments so that the verbatim input fits on the page:
% These adjustments are sloppy and only for this example.
% E.g., parameters for \marginpar are not adjusted as \marginpar
% is not used with this example.
\oddsidemargin=1cm
\textwidth=\paperwidth
\advance\textwidth-2\oddsidemargin
\advance\oddsidemargin-1in
\evensidemargin=\oddsidemargin
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\ExplSyntaxOn
\iow_new:N \g_my_write
\str_new:N \g_my_string
\NewDocumentCommand{\myExactWrite}{}{
\group_begin:
% +v-type-argument/verbatim-mode does not do this, so let's
% turn horizontal tab from space to other and then fetch
% the +v-argument by calling another macro. (Otherwise
% horizontal-tabs will be written as space characters.)
\char_set_catcode_other:N \^^I
%
\tex_newlinechar:D=\tex_endlinechar:D
% projectmbc suggested replacing all ^^M by ^^J instead.
% I think that would be a better approach because this can also
% be done in combination with delayed-writing.
\iow_open:Nn \g_my_write {my-output.txt}
\myInnerExactWrite
}
\NewDocumentCommand{\myInnerExactWrite}{+v}{
\str_set:Nn \g_my_string {#1}
%\str_show:N \g_my_string
\exp_args:NnV \use:n {\tex_immediate:D \tex_write:D \g_my_write} \g_my_string
\group_end:
\iow_close:N \g_my_write
}
\ExplSyntaxOff
\usepackage{verbatim}
\begin{document}
%%% Level 1
\myExactWrite{
Hello % This is a comment that should not be removed
I want precisely this spacing, and I want to be allowed to use any text
}
% %% Level 2
\myExactWrite{
Hello % This is a comment that should not be removed
I want precisely this spacing, and I want to be allowed to use any text, like 1 + (1*2), héhé,
or:
def mycode(my_variable_with_underscore="hey"): # This is a comment (with a single sharp)
print(my_variable_with_underscore)
or latex macros like $\array{a & b\\c & d}$. And ideally unbalanced {} but I heard it was difficult
(maybe by replacing special tokens like \MYCLOSINGBRACE and \MYOPENINGBRACE in the final string?).
}
\noindent
The content of the file is:
\verbatiminput{my-output.txt}
\end{document}
意识到:
答案2
我从未使用过 Expl3,也不知道如何使用,所以这是我用纯 (e-)TeX 编写的答案。它似乎运行得相当好。
\newwrite\myWrite
% \verbatimwrite{<file name>}{<text ...>}
\def\verbatimwrite#1{\bgroup%
% Begin setting up verbatim
\catcode`\^^M=12\relax%
\def\do##1{\catcode`##1=12\relax}%
\dospecials%
% Allow capture of next parameter
\catcode`\{=1\relax \catcode`\}=2\relax%
\verbatimwriteA{#1}%
}
\long\def\verbatimwriteA#1#2{%
% Finish setting up verbatim
\catcode`\{=12\relax \catcode`\}=12\relax%
% Dont expand EOF token
\everyeof={\noexpand}%
% Newlines in \write
\newlinechar=`\^^M\relax%
% All the \write-ing stuff
\immediate\openout\myWrite #1\relax%
\immediate\write\myWrite{\scantokens{#2}}%
\immediate\closeout\myWrite%
\egroup}
\verbatimwrite{test.txt}{
Hello % This is a comment that should not be removed
I want precisely this spacing, and I want to be allowed to use any text
}
\verbatimwrite{test1.txt}{
Hello % This is a comment that should not be removed
I want precisely this spacing, and I want to be allowed to use any text, like 1 + (1*2), héhé,
or:
def mycode(my_variable_with_underscore="hey"): # This is a comment (with a single sharp)
print(my_variable_with_underscore)
or latex macros like $\array{a & b\\c & d}$. And ideally unbalanced {} but I heard it was difficult
(maybe by replacing special tokens like \MYCLOSINGBRACE and \MYOPENINGBRACE in the final string?).
}
此代码确实具有将文本后面的换行符{
添加到文件中的效果。您可以在之前添加某种测试,该#2
测试吞噬字符,检查它是否是^^M
,如果不是,则将其放回流中。
如果您想要允许不平衡的括号,可以使用构造来实现这一点,但过程会稍微复杂一些,即\beginverbwrite...\endverbwrite
逐字逐句地设置所有内容\beginverbwrite
,然后吞噬标记并将其附加到标记列表中。每次吞噬 a 时,您都必须检查\
以下字符是否为endverbwrite
,这将不得不将标记列表写入文件并逐字结束。您不能将文本作为参数传递给宏,因为您不知道参数何时结束,因此需要使用构造\begin...\end
。
这有点复杂,但我想我可能有一些代码可以修改一下来实现这一点。如果你感兴趣,告诉我,我会试着做这件事(不过这可能需要一些时间,我可能无法立即开始)。
答案3
根据 @davidcarlisle 的评论,逐字逐句地阅读这些项目乍一看似乎没问题。你能检查/确认吗?
使用 expl3+v
参数类型 (=multi-par verbatim)。
很大程度上取决于字体。
平均能量损失
\documentclass[options]{article}
\ExplSyntaxOn
\iow_new:N \g_my_write
\cs_generate_variant:Nn \iow_now:Nn { NV }
\str_new:N \g_my_string
\NewDocumentCommand{\myExactWrite}{+v}{
\iow_open:Nn \g_my_write {my-output.txt}
% In my code the write might arrives much later, even in another function, hence the use of a string
% === See that the input is not the expected one:
\str_set:Nn \g_my_string {#1}
\iow_now:NV \g_my_write \g_my_string
\iow_close:N \g_my_write
}
\ExplSyntaxOff
\usepackage{verbatim}
\begin{document}
%%% Level 1
\myExactWrite{
Hello % This is a comment that should not be removed
I want precisely this spacing, and I want to be allowed to use any text
}
% %% Level 2
\myExactWrite{
Hello % This is a comment that should not be removed
I want precisely this spacing, and I want to be allowed to use any text, like 1 + (1*2), héhé,
or:
def mycode(my_variable_with_underscore="hey"): # This is a comment (with a single sharp)
print(my_variable_with_underscore)
or latex macros like $\array{a & b\\c & d}$. And ideally unbalanced {} but I heard it was difficult
(maybe by replacing special tokens like \MYCLOSINGBRACE and \MYOPENINGBRACE in the final string?).
}
The content of the file is:
\verbatiminput{my-output.txt}
\end{document}
更正:+v
属于xparse
(现在是内核的一部分),而不是 expl3。
-parameterv
命令可以使用两个相同的字符作为参数分隔符(如\verb
does),或者一{}
对。
因此,使用
答案4
我找到了另一个基于 xsim 包(或其轻量级子集 xsimverb)的解决方案(当然有点脏,但至少代码在概念上更简单,而且非常有弹性,因为它适用于非平衡括号并且可以处理吞噬)。这个想法是使用xsimverb
写入文件,然后我们使用 LaTeX3 命令读取该文件并将它们放入字符串中。
请注意,它将在宏内部失败,但这是预料之中的,因为一旦我们处理非乳胶代码,外部宏将删除所有注释等……所以在这种情况下我宁愿出现错误,而不是奇怪地删除某些字符。
\documentclass{article}
\usepackage{verbatim}
\usepackage{xsimverb}
\ExplSyntaxOn
\cs_generate_variant:Nn \iow_now:Nn { NV }
\iow_new:N \g_robExt_write
\ior_new:N \g_robExt_read_ior
\NewDocumentEnvironment{robExtNamedTemplate}{}{\XSIMfilewritestart*{test.tmp}}{
\XSIMfilewritestop
\ior_open:Nn \g_robExt_read_ior {test.tmp}
\str_gclear:N \g_robExt_mystring
%% Loop on all lines of the file:
\ior_str_map_inline:Nn \g_robExt_read_ior {
\str_gput_right:Nx \g_robExt_mystring {\tl_to_str:N{##1}^^J}
}
}
\NewDocumentCommand{\saveStringAndPrintFile}{O{}}{
\message{E}
\iow_open:Nn \g_robExt_write {test-out.tex}
\message{F}
\iow_now:NV \g_robExt_write \g_robExt_mystring
\message{G}
\iow_close:N \g_robExt_write
\message{H}
\verbatiminput{test-out.tex}
}
\ExplSyntaxOff
\begin{document}
\begin{robExtNamedTemplate}
# This is a comment
def my_function():
a = {}
a = {b}
return a+b % 2
\end{robExtNamedTemplate}
\saveStringAndPrintFile
\end{document}