定义 TikZ 矩阵的快捷方式的问题

定义 TikZ 矩阵的快捷方式的问题

为了昨天回答了一个问题我想定义一个在 中创建矩阵的快捷方式tikzpicture。作为一个最小示例,请考虑

\newcommand\mymatrix[1]{
    \begin{tikzpicture}
        \matrix[matrix of math nodes] {#1};
    \end{tikzpicture}%
}

然后将其用作

\mymatrix{
    a & b \\
    e & f \\
}

但是 LaTeX 会抱怨Package pgfbasematrix Error: Single ampersand used with wrong catcode行的右括号\amatrix{}。为什么会出现这种情况?我该如何避免这种情况?

答案1

TikZ 无法将参数中给出的 & 符号转换为活动字符。你可以

使用\pgfmatrixnextcell而不是 & 并可能为其定义一个快捷方式,

或者使用 & 符号替换选项:

\newcommand\mymatrix[1]{
    \begin{tikzpicture}
        \matrix[ampersand replacement=\&,matrix of math nodes] {#1};
    \end{tikzpicture}%
}
\mymatrix{
    a \& b \\
    e \& f \\
}

答案2

摘自手册第 179 页(重点添加,纠正了一个小但重要的错误):

尽管 TikZ 似乎使用&来分隔单元格,但 pgf 实际上使用不同的命令来分隔单元格,即命令\pgfmatrixnextcell,使用普通&字符通常会失败。发生的情况是,TikZ 将 & 设为活动字符,然后将该字符定义为等于\pgfmatrixnextcell。在大多数情况下,这会很好地工作,但有时&无法将其设为活动字符;例如因为 矩阵用于某些宏的参数\pgfmatrixnextcell 或者矩阵包含包含正常 {tabular} 环境的节点。在这种情况下,您可以使用以下选项来避免每次都输入:

/tikz/ampersand replacement= macro name or empty          (no default)

如果提供了宏名,则该宏将被定义为等于\pgfmatrixnextcell矩阵内部,并且&不会被激活。例如,您可以说ampersand replacement=\&,然后使用\&来分隔列,如以下示例所示:

\tikz
\matrix [ampersand replacement=\&]
{
\draw (0,0) circle (4mm); \& \node[rotate=10] {Hello}; \\
\draw (0.2,0) circle (2mm); \& \fill[red] (0,0) circle (3mm); \\
};

大概存在一些极其复杂的方法可以在宏参数中再次打开 & 符号的活动性质,但上述方法似乎是最干净的方法。

答案3

虽然前面的答案提供了一种完全可以满足您的要求的方法,但它们确实有一个缺点,那就是您需要编写\&或任何其他替换来代替通常的替换&(然后您不能在参数中将该特定宏用于其他目的)。 在大多数情况下,这很好,但假设有人想保留常规语法,没有什么可以阻止他们&自己使字符处于活动状态,从而复制 TikZ 的通常行为。 这甚至不太难:

\newcommand\mymatrix{%
    \begingroup%
    \long\def\tmp##1{%
        \endgroup%
        \begin{tikzpicture}
            \matrix[matrix of math nodes] {##1};
        \end{tikzpicture}
    }%
    \catcode`&=13
    \tmp%
}

我们可以根据需要使用它:

\mymatrix{
    a & b \\
    e & f \\
}

那么这里发生了什么?当 TeX 读入宏的参数时,它会使用当前有效的类别代码,并且该代码会保留在生成的标记序列中。撇开技术细节不谈,在简单的方法中,我们最终得到的标记是{name: &, catcode: 4}TikZ 期望得到的标记{name: &, catcode: 13},其中 13 表示活动字符。因此,要创建&TikZ 期望的活动标记,我们需要更改类别代码读取相应的字符。(事后也可以用它们的活动版本替换这些字符,但是如果我们一开始就能让 TeX 引擎正确地解析它们,那么这将变得不必要地复杂。)

因此,请注意,\mymatrix定义似乎不接受任何参数。(尽管如此,我们仍然可以按照预期使用它。)当 TeX 遇到宏时,它会不是但继续解析“参数”,就像其他情况一样。相反,由于不需要参数,因此立即扩展定义,并将结果放在处理队列的前面。

随着 TeX 的继续,我们首先用 开始一个新组,以\begingroup确保任何更改都保持局部性,特别是类别代码更改(参见下一段)。然后我们定义临时宏\tmp,它将作为我们的代理来处理以下块。但在调用它之前,的类别代码&会更改为 13,即它处于活动状态。然后我们调用\tmp,按照定义,它需要一个参数,因此 TeX 会像\mymatrix接受一个参数一样读取下一部分,但是现在 &处于应有的活跃状态。

一旦 TeX 读入参数并将其转换为标记(保留&为活动),它就会扩展\tmp其定义并开始处理生成的标记序列。我们要做的第一件事就是用 关闭之前启动的组\endgroup。因此,类别代码&会变回其原始值,并且我们所做的更改不会影响除参数解析方式之外的任何其他内容。(顺便说一句,这也会删除宏,\tmp因为它也是在组内定义的;无论如何,我们不再需要它了,它已经扩展了。)

最后,剩下的就是启动tikzpicture环境并\matrix使用提供的参数进行调用。此时,字符序列已被解析为标记序列,被&解释为活动的,并且该标记序列已替换为##1,因此我们一切顺利,TikZ 不会再抱怨。

答案4

灵感来自这个很好的答案egreg,我想到了一种更强大的方法来使用 LaTeX3 语法。还要注意的是,我刚刚看到这个答案,这可能更简单。

\documentclass[]{article}
\usepackage{tikz}
\usetikzlibrary{matrix}

% ExplSyntax does not allow spaces, so it is easier to define this function here and map it to an expl3 function later:
\NewDocumentCommand\mymatrixcontext{m}{
  \begin{tikzpicture}
    \matrix[matrix of math nodes] {#1};
  \end{tikzpicture}%
}

\ExplSyntaxOn
%%% Map the \mymatrixcontext in an expl3 function
\cs_set_eq:NN \my_matrix_context:n \mymatrixcontext
% Generate a variant \my_matrix_context:nV that accepts a variable
\cs_generate_variant:Nn \my_matrix_context:n { V }

\NewDocumentCommand\mymatrix{m}{
  % Store the tokens of the content into a new variable
  \tl_set:Nn \l_tmpa_tl { #1 }
  % Replace the catcode of the tokens "&" with an active (\cA) token
  % Read more in https://ctan.math.illinois.edu/macros/latex/contrib/l3kernel/interface3.pdf
  \regex_replace_all:nnN { \& } { \cA\& } \l_tmpa_tl
  % Call our context function on this new token list.
  \my_matrix_context:V \l_tmpa_tl
}

\ExplSyntaxOff


\begin{document}

\mymatrix{
  A & B\\
}

\end{document}

相关内容