使用 write18 时如何处理宏扩展和‘TeX 容量超出’?

使用 write18 时如何处理宏扩展和‘TeX 容量超出’?

这个问题让我抓狂。自从 tex SE 上友好而博学的人指导我以正确的方式进行多次字符串替换,我一直在尝试将这些知识融入我的代码中 — 但目前还没有成功。困难在于:

  1. 字符串替换功能使用\noexpandarg/ \expandafter,它与所有功能配合得不是\write18很好,并且
  2. \write18and/or中存在一个令人讨厌的情况\input,会导致 (La)TeX 抛出一条TeX capacity exceeded, sorry消息。

让我们看看下面的 M(W)E — 它目前并没有真正发挥作用,因为我用它curl来处理本地服务器,而这里没有介绍。对于那些感兴趣的人:此代码适用于CoffeeXeLaTeX,尝试使用 JavaScript(以及 CoffeeScript)使 TeX 可编写脚本。除非您在指定的地址拥有 Web 服务器,否则无法成功运行下面的代码;我意识到这种依赖关系对于我想要演示的效果完全是偶然的,我愿意重写该示例,以便我们可以从 M(W)E 中取出括号。话虽如此,我相信你们中的许多人在阅读此代码时会惊呼“啊哈!初学者的错误!”:

\documentclass[a4paper]{article}
\usepackage{xstring}
\usepackage[usenames,dvipsnames,svgnames,table]{xcolor}


% -----------------------------------------------------------------
% URL escaping simplified;
% as per https://tex.stackexchange.com/questions/153215/how-to-do-multiple-string-replacements
\newcommand{\urlescapestep}[2]{%
  \expandafter\StrSubstitute\expandafter{\x}{#1}{#2}[\x]%
  }
\newcommand{\urlescape}[1]{{%
  \noexpandarg%
  \StrSubstitute{#1}{\%}{\%25}[\x]%
  \urlescapestep{/}{\%2F}%
  \urlescapestep{a}{A}% just for this test; imagine useful stuff here
  \x}}

\newcommand{\escapeONE}[1]{*#1*}

\newcommand{\escapeTWO}[1]{\StrSubstitute{#1}{a}{A}}

% -----------------------------------------------------------------
% Execute a command with `\write18`:
\newcommand{\CXtempoutroute}{/tmp/CXtempout.tex}

\newcommand{\exec}[1]{%
  \immediate\write18{#1 > "\CXtempoutroute"}\input{\CXtempoutroute}}

% -----------------------------------------------------------------
% `curl` commands using the string replacement commands:
\newcommand{\curlPlain}[4]{%
  \exec{curl --silent --show-error #1 #2/#3#4}}

\newcommand{\curlUrlescape}[4]{%
  \exec{curl --silent --show-error #1 #2/#3\urlescape{#4}}}

\newcommand{\curlONE}[4]{%
  \exec{curl --silent --show-error #1 #2/#3\escapeONE{#4}}}

\newcommand{\curlTWO}[4]{%
  \exec{curl --silent --show-error #1 #2/#3\escapeTWO{#4}}}

% -----------------------------------------------------------------
\begin{document}

First, let's show our three string escaping mechanisms all work under
normal circumstances:

\verb#\urlescape#: \urlescape{abc}

\verb#\escapeONE#: \escapeONE{abc}

\verb#\escapeTWO#: \escapeTWO{abc}

These do work and turn \verb#abc# into \verb#Abc#, \verb#*abc*#, and \verb#Abc#, respectively.

Now let's use the various \verb#curl*# methods:

works: \verb#\curlPlain{}{127.0.0.1:8910}{foobar.tex/helo/}{abc}#: 
  \curlPlain{}{127.0.0.1:8910}{foobar.tex/helo/}{abc}

works: \verb#\curlONE{}{127.0.0.1:8910}{foobar.tex/helo/}{abc}#: 
  \curlONE{}{127.0.0.1:8910}{foobar.tex/helo/}{abc}

throws: \verb#\curlTWO{}{127.0.0.1:8910}{foobar.tex/helo/}{abc}#: 
  \curlTWO{}{127.0.0.1:8910}{foobar.tex/helo/}{abc}

\end{document}

目前的代码抛出了TeX capacity exceeded, sorry [text input levels=15],这似乎表明 TeX 递归调用自身太多次。

在过去无数的时光里,我还遇到了一些TeX capacity exceeded, sorry [input stack size=5000]可怕的use of StrSubstitute doesn't match its definition错误,这取决于我把这些神奇的、、\edef东西放在哪里。\noexpand\expandafter

我完全承认我当时在做货物崇拜编程,但后来我也拿出了我 1990 年台湾版的 TeX Book。我读了贝希托尔斯海姆,我经常在互联网上搜索,Google 现在想给我发一张黄金客户卡。我删除了实验代码并重新开始,这样我就可以用一些有意义的代码发布这个问题;我可能会尝试重建一些间歇性的尝试。

到目前为止我发现,当 TeX 向文件写入内容时,它会推迟写入,因为它假设“大多数情况下这就是你想要的”(我不确定我是否同意这一点;为什么不写\postpone\write18如果你真的想让它以这种方式工作,为什么不写呢?)。你可以让它行动现在\immediate由于 TeX 中没有诱饵,所以这不确切地您认为它应该是什么,它只是“在大多数情况下起着类似的作用”。

作为一名程序员,我习惯于在事件循环中处理递归函数调用和延迟的异步代码执行。奇怪的是,当 TeX 抱怨时,这些知识对我没什么帮助:

  • 上面的代码中递归调用发生在哪里?我找不到它们。

  • 为什么我stack size exceeded使用时有时会收到无害输入文件的错误\input(我相信)?

  • \noexpand为什么/的存在会\expandafter破坏\write18

  • 我应该如何解决这些症状(除了阅读实现代码直至参数中每个命令的最后一次扩展write18)?

  • 所有这些是否在某种程度上与强健/脆弱命令和“可移动参数”问题相关?

我觉得自己有理由问这个复杂而巨大的问题,因为在摆弄了几个月的 TeX(并设法产生了输出,所以值得一试)之后,我感觉这些是 TeX 复杂而阴暗的部分,游客不喜欢去,没有便捷的交通,只有最坚强的人才能让它工作。这大概就是我提出 CoffeeXeLaTeX 的动机。

答案1

上一个问题中的命令\urlescape仅适用于印刷一个“纯净”的 URL。您需要一个不同的版本才能使用它\write;请注意,生成文字 %并不是\%

\documentclass[a5paper]{article}
\usepackage{xstring}

\newcommand{\urlescapestep}[2]{%
  \expandafter\StrSubstitute\expandafter{\x}{#1}{#2}[\x]%
}

% In the following group endlines do not produce spaces
% and `%'  becomes a printable character
\begingroup
\endlinechar=-1
\catcode`\%=12
\gdef\urlescape#1{{
  \noexpandarg
  \StrSubstitute{#1}{%}{%25}[\x]
  \urlescapestep{/}{%2F}
  \urlescapestep{\&}{%26}
  \urlescapestep{ }{%20}
  \urlescapestep{\$}{%24}
  \urlescapestep{+}{%2b}
  \urlescapestep{,}{%2c}
  \urlescapestep{:}{%3a}
  \urlescapestep{;}{%3b}
  \urlescapestep{?}{%3f}
  \urlescapestep{@}{%40}
  \urlescapestep{"}{%22}
  \urlescapestep{<}{%3c}
  \urlescapestep{>}{%3e}
  \urlescapestep{\#}{%23}
  \urlescapestep{\{}{%7b}
  \urlescapestep{\}}{%7d}
  \urlescapestep{|}{%7c}
  \urlescapestep{\^}{%5e}
  \urlescapestep{\~}{%7e}
  \urlescapestep{[}{%5b}
  \urlescapestep{]}{%5d}
  \urlescapestep{\`}{%60}
  \global\let\urlescapecurrent=\x}}
\endgroup

\newcommand{\curlUrlescape}[4]{%
  \urlescape{#4}%
  \exec{curl --silent --show-error #1 #2/#3\urlescapecurrent}%
}

A temporary version just for testing
\newcommand{\exec}[1]{%
  \typeout{I would use^^J^^J%
  #1%
  ^^J^^Jin \string\immediate\string\write18}%
}

\begin{document}

% a foolish URL, just for testing
\curlUrlescape{}{127.0.0.1:8910}{foobar.tex/helo/}{abc|][}

\end{document}

以下是日志文件中打印的内容:

I would use

curl --silent --show-error  127.0.0.1:8910/foobar.tex/helo/abc%7c%5d%5b

in \immediate\write18

答案2

您的问题是不熟悉 TeX 的人经常遇到的问题:您没有考虑到什么是可扩展的,什么是不可扩展的。\write原语以与 相同的方式进行扩展\edef。其工作方式\StrSubstitute不是可扩展:你不能\edef在或类似结构内使用它\write。相反,您必须首先使用“名称”(宏)的可选参数进行赋值。以您的演示中适用的部分为例:

\makeatletter
\newcommand{\curlTWO}[4]{%
  \StrSubstitute{#4}{a}{A}[\@tempa]
  \exec{curl --silent --show-error #1 #2/#3\@tempa}}
\makeatother

注意,这做了替换外部可扩展上下文(\exec作为的包装器\write),并且我使用\@tempa它作为宏来保存替换的字符串。

更一般地,如果你想编写 (La)TeX 程序,你理解扩展上下文。一些 TeX 流程要求可扩展性,以及其他堵塞它(作业,,\relax...)。

相关内容