为了昨天回答了一个问题我想定义一个在 中创建矩阵的快捷方式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}