在 LaTeX3 字符串中输入**精确**的输入字符串以写入文件

在 LaTeX3 字符串中输入**精确**的输入字符串以写入文件

我试图将一段文本放入 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 的字符(无效)。...
  • (未扩展的)写入控制序列标记的结果取决于整数参数 的值。应用、、、、\escapechar时也是如此。\string\detokenize\scantokens\meaning\show

  • 当(未扩展)写入控制字标记时,TeX 会附加一个空格字符。即使在 .tex 输入文件中没有空格。例如,在正常的 catcode 机制下,输入\TeX\TeX被标记为两个控制字标记\TeX。未扩展写入它们会产生字符序列\TeX␣\TeX␣— ␣ 表示空格字符。当(未扩展)写入控制符号标记时,TeX 不会附加空格字符。这也适用于\scantokens\detokenize

  • 显式空间代币(在收集未限定的宏参数的第一个标记时,类别 10(空格)和字符代码 32 的显式字符标记)被删除。

  • 哈希,即类别 6(参数)的显式字符标记,在写入文本文件或屏幕时会加倍。这也适用于\scantokens\detokenize

  • 当 LaTeX 读取一行 .tex 输入时,在标记化之前会进行一些预处理:

    1. 字符从计算机平台的字符表示方案转换为 TeX 的内部字符表示方案,对于传统的 TeX 引擎而言,该方案为 ASCII,而对于基于 LuaTeX 和基于 XeTeX 的 TeX 引擎而言,该方案为 unicode,其中 ASCII 是其严格子集。

    2. 行右端的所有空格字符(以及基于某些 Web2C 版本的 TeX 实现中的所有水平制表符)都将被删除。没有办法绕过行尾空格的删除,即使切换到逐字模式也不行。(切换到逐字模式意味着暂时改变 catcode 机制,这反过来又会影响标记化,进而发生.tex-input 的行是经过预处理的。

    3. 在行的右端附加一个字符,其在 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 的评论,逐字逐句地阅读这些项目乍一看似乎没问题。你能检查/确认吗?

解释3动词

使用 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命令可以使用两个相同的字符作为参数分隔符(如\verbdoes),或者一{}对。

因此,使用

答案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}

相关内容