当 \newcommand 不起作用时,\NewDocumentCommand 在 pgfplots 中不起作用

当 \newcommand 不起作用时,\NewDocumentCommand 在 pgfplots 中不起作用

我目前正在尝试更新一些 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没有此属性,因此在这里尝试它是一个自然的想法……但这不起作用,正如评论中提到的那样。找出原因可能很复杂,我将把这个问题留给专家!

但是,当你无法在“敏感的地方”(通常是仅扩展的环境)做你想做的事情时,这里有一个基本上总是有效的策略:

  1. 你做“复杂的工作”TeX 到达敏感的地方。你应该选择一个 TeX 执行扩展的地方执行;这样,您可以使用完成任务所需的所有工具(包括\NewDocumentCommand或任何其他不可扩展的工具)。

  2. 从这个地方,你将结果存储在一个宏中(在中,类似函数expl3中的“结果宏”几乎总是第一个参数;我保留这个惯例,这有助于记忆)。*_set_*\seq_set_split:Nnn

  3. 您将“结果宏”(单个控制序列标记)放在“敏感位置”。扩展一次宏是一个非常基本的操作;这在发生扩展的每个上下文中都有效。

让我们将此策略应用到您的问题中:

\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}

注意:如果您想要使用相同的“结果宏”多次调用,我使用\definside (此处为)。对于不合理的情况,您当然可以使用。\prepareStuff\prepareStuff\coords\newcommand

\def此外,当地的定义(其效果仅限于当前 TeX 组);如果您想要一个全局的,请改用\gdef

相关内容