我目前正在尝试更新一些 LaTeX 代码。我想\newcommand
用替换\NewDocumentCommand
。大多数情况下它都能正常工作,但以下示例存在问题:
\documentclass{article}
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}
\newcommand{\coords}{(1,1)} % works
%\NewDocumentCommand{\coords}{}{(1,1)} % does not work
\begin{document}
\begin{tikzpicture}
\begin{axis}
\addplot coordinates {(0,0) \coords};
\end{axis}
\end{tikzpicture}
\end{document}
它\newcommand
工作正常,但\NewDocumentCommand
使用时出现错误
Incomplete \ifx; all text was ignored after line 11.
\newcommand
和之间有什么区别\NewDocumentCommand
,会导致错误?如果我理解正确的话,\NewDocumentCommand
总是声明健壮的命令。这是这里的问题吗?
我该如何使用\NewDocumentCommand
?(或者类似的东西,允许使用自定义分隔符定义具有多个可选参数的命令)
答案1
pgfplots
<coordinates list>
当它遇到来自输入的控制序列时,进行一步扩展\addplot ... coordinates {<coordinates list>}
,参见\pgfplots@foreach@plot@coord@ITERATE@
。
这看起来不错,并且恰好适合 定义的命令\NewExpandableDocumentCommand
,但一般来说,一步扩展是不够的。当宏(或其替换文本的扩展)包含类似 的条件时\ifx
,一步扩展将留下不完整的\ifxxx
。
\documentclass{article}
\begin{document}
% show result of one-step expansion of `\ifx\a\b true\else false\fi`
\ttfamily
\detokenize\expandafter{\ifx\a\b true\else false\fi} % leave "false\fi"
\end{document}
不幸的是,\NewExpandableDocumentCommand
-defined 命令确实使用条件。在 OP 的例子中,\NewExpandableDocumentCommand{\coords}{}{(1,1)}
第一个遇到的条件是\token_if_eq_charcode:NNT
。
由于<coordinates list>
忽略空格,并且坐标不会以数字、+
或开头-
,因此进行扩展似乎是安全的\romannumeral
。下面使用的解释\romannumeral-`\0
可在此站点上找到。
\documentclass{article}
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}
\usepackage{xpatch}
\makeatletter
% originally defined in pgfplotscoordprocessing.code.tex
\xpatchcmd\pgfplots@foreach@plot@coord@ITERATE@
{
\if\noexpand\pgfplots@foreach@plot@coord@ITERATE@tok\noexpand\anymacro
% Ah. #1 has the same character (!) code as \anymacro,
% that means it is a macro! Expand it:
\def\pgfplots@loop@next{%
\expandafter\pgfplots@foreach@plot@coord@ITERATE#1}%
}{%
\if\noexpand\pgfplots@foreach@plot@coord@ITERATE@tok\noexpand\anymacro
\def\pgfplots@loop@next{%
\expandafter\pgfplots@foreach@plot@coord@ITERATE\romannumeral-`\0#1}%
}
{}{\PatchFailed}
\makeatother
%\newcommand{\coords}{(1,1)} % works
\NewExpandableDocumentCommand{\coords}{}{(1,1)} % works now
\begin{document}
\begin{tikzpicture}
\begin{axis}
\addplot coordinates {(0,0) \coords};
\end{axis}
\end{tikzpicture}
\end{document}
PS:我很好奇为什么您想要\coord
通过xparse
(或者,因为它现在是内核的一部分,模块ltcmd
)命令来定义,当\def
或\newcommand
已经满足要求时。
答案2
\NewDocumentCommand
定义\protected
宏。因此,它们不会在 、 等内部展开\edef
。\write
定义的宏\NewExpandableDocumentCommand
没有此属性,因此在这里尝试它是一个自然的想法……但这不起作用,正如评论中提到的那样。找出原因可能很复杂,我将把这个问题留给专家!
但是,当你无法在“敏感的地方”(通常是仅扩展的环境)做你想做的事情时,这里有一个基本上总是有效的策略:
你做“复杂的工作”前TeX 到达敏感的地方。你应该选择一个 TeX 执行扩展的地方和执行;这样,您可以使用完成任务所需的所有工具(包括
\NewDocumentCommand
或任何其他不可扩展的工具)。从这个地方,你将结果存储在一个宏中(在中,类似函数
expl3
中的“结果宏”几乎总是第一个参数;我保留这个惯例,这有助于记忆)。*_set_*
\seq_set_split:Nnn
您将“结果宏”(单个控制序列标记)放在“敏感位置”。扩展一次宏是一个非常基本的操作;这在发生扩展的每个上下文中都有效。
让我们将此策略应用到您的问题中:
\documentclass[tikz,border=2mm]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}
\NewDocumentCommand{\prepareStuff}{ m O{#3} m }{%
\def#1{(#2,#3)}% you could compute things here using \edef or \cs_new:Npx
}
\begin{document}
\begin{tikzpicture}
\prepareStuff{\coords}{1}
\begin{axis}
\addplot coordinates {(0,0) \coords};
\end{axis}
\end{tikzpicture}
\end{document}
当然,您可以\prepareStuff{\coords}[1]{1}
在这里实现相同的效果(表明您可以根据要求使用可选参数,或任何\NewDocumentCommand
允许的内容)并将此调用放在其他地方,例如之前\begin{document}
。
注意:如果您想要使用相同的“结果宏”多次调用,我使用\def
inside (此处为)。对于不合理的情况,您当然可以使用。\prepareStuff
\prepareStuff
\coords
\newcommand
\def
此外,当地的定义(其效果仅限于当前 TeX 组);如果您想要一个全局的,请改用\gdef
。