使用这个很好的答案我设法创建了一个\zx{A & B}
可以接受&
char 的函数(将其提供给某种 tikz 矩阵,因为我更愿意避免使用\&
而不是&
... 请注意,即使根本没有使用 tikz 也会出现错误)。它运行良好......除了在环境中align
。在这种情况下,即使\zx{A}
(不使用任何&
)中断。有什么想法可以解决这个问题吗?
平均能量损失
\documentclass[]{article}
\usepackage{amsmath}
\usepackage{tikz-cd}
\newcommand\zx{%
\begingroup% To avoid ampersand issues https://tex.stackexchange.com/a/611535/116348
\NewDocumentCommand{\tmpZX}{O{}+m}{%
\endgroup%
\begin{tikzcd}[##1] ##2 \end{tikzcd}
% Problem is not specific to tikzcd, and also appear if you use instead:
% Hello (##1) ##2 :)
% But just make sure to remove & and \rar since they only make sense in tikzcd.
}%
\catcode`&=13%
\tmpZX%
}
\begin{document}
\subsection{Level 1: without inside.}
Works:
Hello \zx{A} and \zx{A \rar & B}.
\noindent Works:
\begin{equation}
\zx{A} = \zx{A \rar & B}
\end{equation}
\noindent Fails:
% \begin{align}
% \zx{A}
% \end{align}
\end{document}
答案1
无需进行间接操作:
\documentclass[]{article}
\usepackage{amsmath}
\usepackage{tikz-cd}
\usepackage{lipsum}
\NewDocumentCommand{\zx}{O{}+m}{%
\begingroup\catcode`\&=\active
\begin{tikzcd}[#1] \scantokens{#2} \end{tikzcd}
\endgroup
}
\begin{document}
Hello \zx{A} and \zx{A \rar & B}.
\lipsum[1][1-2]
\begin{equation}
\zx{A} = \zx{A \rar & B}
\end{equation}
\lipsum[1][4-6]
\begin{align}
\zx{A} &= \zx{A \rar & B} \\
x &= y
\end{align}
\lipsum[2][1-3]
\end{document}
从最后一个例子可以看出,&
无论在 内还是在 外, 在任何情况下都可以工作\zx
。
以稍微不同(且更为强大)的方式:
\documentclass[]{article}
\usepackage{amsmath}
\usepackage{tikz-cd}
\usepackage{lipsum}
\ExplSyntaxOn
\NewDocumentCommand{\zx}{O{}+m}{%
\tl_rescan:nn { \char_set_catcode_active:N \& } { \begin{tikzcd}[#1] #2 \end{tikzcd} }
}
\ExplSyntaxOff
\begin{document}
Hello \zx{A} and \zx{A \rar & B}.
\lipsum[1][1-2]
\begin{equation}
\zx{A} = \zx{A \rar & B}
\end{equation}
\lipsum[1][4-6]
\begin{align}
\zx{A} &= \zx{A \rar & B} \\
x &= y
\end{align}
\lipsum[2][1-3]
\end{document}
答案2
如果您喜欢一种没有的复杂方式,我可以提供一个 由 -expansion 驱动的\scantokens
宏机制,它以递归方式替换类别 4(对齐选项卡)的显式 -character-tokens。
\ReplaceAnd{⟨replacement for &4⟩}{⟨tokens where &4 shall be replaced by ⟨replacement for &4⟩⟩}
\romannumeral
&
作为副作用,任何匹配的 1 类(开始组)和 2 类(结束组)的显式字符标记对⟨tokens where & 4 应替换为 ⟨replacement for & 4 ⟩⟩被一对匹配的显式字符标记和所替换。由于和 通常是类别代码分别为 1 和 2 的唯一字符,因此这通常无关紧要。{1
}2
{
}
Active-& 可以用作⟨替换 & 4 ⟩.
构成tikzcd
可以用作⟨tokens where & 4 应替换为 ⟨replacement for & 4 ⟩⟩。
\errorcontextlines=10000
\makeatletter
%%///////// Code for \ReplaceAnd //////////////////////////////////////////////
%% Syntax:
%% -------
%%
%% \ReplaceAnd{<replacement for &>}%
%% {<tokens where & shall be replaced by <replacement for &>>}%
%%
%% The result is delivered after two expansion-steps/by two "hits"
%% with \expandafter.
%%
%% As a side-effect any matching pair of explicit character tokens
%% of category code 1 and 2 is replaced by a matching pair of
%% explicit character-tokens {_1 and }_2.
%%
%%=============================================================================
%% PARAPHERNALIA:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond, \UD@Exchange,
%% \UD@removespace, \UD@stopromannumeral, \UD@CheckWhetherNull,
%% \UD@CheckWhetherBrace, \UD@CheckWhetherLeadingExplicitSpace,
%% \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@removespace{\UD@Exchange{ }{\def\UD@removespace}{}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
\expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument is blank (empty or only spaces):
%%-----------------------------------------------------------------------------
%% -- Take advantage of the fact that TeX discards space tokens when
%% "fetching" _un_delimited arguments: --
%% \UD@CheckWhetherBlank{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked is blank>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not blank>}%
\newcommand\UD@CheckWhetherBlank[1]{%
\romannumeral\expandafter\expandafter\expandafter\UD@secondoftwo
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo#1{}{}}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has a leading
%% explicit catcode-1-character-token>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked does not have a
%% leading explicit catcode-1-character-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
\romannumeral\expandafter\UD@secondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@firstoftwo}{%
\expandafter\UD@stopromannumeral\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingExplicitSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked> does have a
%% leading explicit space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked> does not have a
%% a leading explicit space-token>}%
\newcommand\UD@CheckWhetherLeadingExplicitSpace[1]{%
\romannumeral\UD@CheckWhetherNull{#1}%
{\expandafter\UD@stopromannumeral\UD@secondoftwo}%
{%
% Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
% and thus do not disturb when the test is carried out within \halign/\valign/
% tabular-environment:
\expandafter\UD@firstoftwo\expandafter{%
\expandafter\expandafter\expandafter\UD@stopromannumeral
\romannumeral\expandafter\UD@secondoftwo
\string{\UD@CheckWhetherLeadingExplicitSpaceB.#1 }{}%
}{}%
}%
}%
\@ifdefinable\UD@CheckWhetherLeadingExplicitSpaceB{%
\long\def\UD@CheckWhetherLeadingExplicitSpaceB#1 {%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\expandafter\expandafter\expandafter\UD@stopromannumeral
\expandafter\expandafter\expandafter}%
\expandafter\UD@secondoftwo\expandafter{\string}%
}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%% \UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% \UD@ExtractFirstArg{{AB}CDE} yields {{AB}}
%%
%% Due to \romannumeral-expansion the result is delivered after two
%% expansion-steps/after "hitting" \UD@ExtractFirstArg with \expandafter
%% twice.
%%
%% \UD@ExtractFirstArg's argument must not be blank.
%% This case can be cranked out via \UD@CheckWhetherBlank before calling
%% \UD@ExtractFirstArg.
%%
%% Use frozen-\relax as delimiter for speeding things up.
%% Frozen-\relax is chosen because David Carlisle pointed out in
%% <https://tex.stackexchange.com/a/578877>
%% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be
%% affected by \uppercase/\lowercase.
%%
%% \UD@ExtractFirstArg's argument may contain frozen-\relax:
%% The only effect is that internally more iterations are needed for
%% obtaining the result.
%%.............................................................................
\@ifdefinable\UD@RemoveTillFrozenrelax{%
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}%
{\long\def\UD@RemoveTillFrozenrelax#1#2}{{#1}}%
}%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral\expandafter
\UD@PassFirstToSecond\expandafter{\romannumeral
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}{\UD@stopromannumeral#1}%
}{%
\UD@stopromannumeral\romannumeral\UD@ExtractFirstArgLoop
}%
}{%
\newcommand\UD@ExtractFirstArg[1]%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@stopromannumeral#1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillFrozenrelax#1}}%
}%
%====================================================================
\@ifdefinable\UD@AndInstance{\long\def\UD@AndInstance#1#3!{#2}}%
\newcommand\UD@ReplaceAndInstance[2]{%
% #1 a single token to examine
% #2 replacement for &
\UD@AndInstance#1{\UD@stopromannumeral#2}&{\UD@stopromannumeral#1}!%
}%
\newcommand\UD@AndReplaceloop[3]{%
% #1 replacement for &
% #2 tokens forming the result gathered so far
% #3 remaining token list to process
\UD@CheckWhetherNull{#3}{\UD@stopromannumeral#2}{%
\expandafter\UD@Exchange\expandafter{%
\romannumeral
\UD@CheckWhetherLeadingExplicitSpace{#3}{%
\expandafter\UD@PassFirstToSecond
\expandafter{\UD@removespace#3}{\UD@stopromannumeral{#2 }}%
}{%
\expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#3}{%
\expandafter\expandafter\expandafter\UD@stopromannumeral
\expandafter\expandafter\expandafter{%
\expandafter\UD@Exchange\expandafter{%
\romannumeral
\UD@CheckWhetherBrace{#3}{%
\expandafter\UD@stopromannumeral\expandafter{\romannumeral
\expandafter\expandafter\expandafter\UD@PassFirstToSecond
\UD@ExtractFirstArg{#3}{\UD@AndReplaceloop{#1}{}}%
}%
}{%
\expandafter\expandafter\expandafter\UD@ReplaceAndInstance
\UD@ExtractFirstArg{#3}{#1}%
}%
}%
{#2}%
}%
}%
}%
}{\UD@AndReplaceloop{#1}}%
}%
}%
\newcommand\ReplaceAnd[2]{%
% #1 replacement for &
% #2 tokens where to replace & by replacement for &
\romannumeral\UD@AndReplaceloop{#1}{}{#2}%
}%
%%///////// End of code for \ReplaceAnd //////////////////////////////////////
\makeatother
\documentclass[]{article}
\usepackage{amsmath}
\usepackage{tikz-cd}
\begingroup
\makeatletter
\catcode`\&=13 %
\@firstofone{%
\endgroup
\NewDocumentCommand{\zx}{o+m}{%
\ReplaceAnd{&}{\IfNoValueTF{#1}{\begin{tikzcd}}{\begin{tikzcd}[{#1}]}#2\end{tikzcd}}%
}%
}%
\begin{document}
\subsection{Level 1: without inside.}
Works:
Hello \zx{A} and \zx{A \rar & B}.
\noindent Works:
\begin{equation}
\zx{A} = \zx{A \rar & B}
\end{equation}
\noindent Works:
\begin{align}
\zx{A} &= \zx{A \rar & B}\\
\zx{UV} &= \zx{UV \rar & UW}
\end{align}
\end{document}
如果你喜欢极端情况和奇怪的场景:
与\scantokens
-method不同\ReplaceAnd
,-method 不会重复哈希值,也不会在控制字标记后面产生空格 - 下面是一个结合了替换方法和\scantokens
-method 的例子,以便你可以看到边缘情况下的细微差别:
\errorcontextlines=10000
\makeatletter
%%///////// Code for \ReplaceAND //////////////////////////////////////////////
%% Syntax:
%% -------
%%
%% \ReplaceAnd{<replacement for &>}%
%% {<tokens where & shall be replaced by <replacement for &>>}%
%%
%% The result is delivered after two expansion-steps/by two "hits"
%% with \expandafter.
%%
%% As a side-effect any matching pair of explicit character tokens
%% of category code 1 and 2 is replaced by a matching pair of
%% explicit character-tokens {_1 and }_2.
%%
%%=============================================================================
%% PARAPHERNALIA:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond, \UD@Exchange,
%% \UD@removespace, \UD@stopromannumeral, \UD@CheckWhetherNull,
%% \UD@CheckWhetherBrace, \UD@CheckWhetherLeadingExplicitSpace,
%% \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@removespace{\UD@Exchange{ }{\def\UD@removespace}{}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
\expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument is blank (empty or only spaces):
%%-----------------------------------------------------------------------------
%% -- Take advantage of the fact that TeX discards space tokens when
%% "fetching" _un_delimited arguments: --
%% \UD@CheckWhetherBlank{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked is blank>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not blank>}%
\newcommand\UD@CheckWhetherBlank[1]{%
\romannumeral\expandafter\expandafter\expandafter\UD@secondoftwo
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo#1{}{}}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has a leading
%% explicit catcode-1-character-token>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked does not have a
%% leading explicit catcode-1-character-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
\romannumeral\expandafter\UD@secondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@firstoftwo}{%
\expandafter\UD@stopromannumeral\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingExplicitSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked> does have a
%% leading explicit space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked> does not have a
%% a leading explicit space-token>}%
\newcommand\UD@CheckWhetherLeadingExplicitSpace[1]{%
\romannumeral\UD@CheckWhetherNull{#1}%
{\expandafter\UD@stopromannumeral\UD@secondoftwo}%
{%
% Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
% and thus do not disturb when the test is carried out within \halign/\valign/
% tabular-environment:
\expandafter\UD@firstoftwo\expandafter{%
\expandafter\expandafter\expandafter\UD@stopromannumeral
\romannumeral\expandafter\UD@secondoftwo
\string{\UD@CheckWhetherLeadingExplicitSpaceB.#1 }{}%
}{}%
}%
}%
\@ifdefinable\UD@CheckWhetherLeadingExplicitSpaceB{%
\long\def\UD@CheckWhetherLeadingExplicitSpaceB#1 {%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\expandafter\expandafter\expandafter\UD@stopromannumeral
\expandafter\expandafter\expandafter}%
\expandafter\UD@secondoftwo\expandafter{\string}%
}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%% \UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% \UD@ExtractFirstArg{{AB}CDE} yields {{AB}}
%%
%% Due to \romannumeral-expansion the result is delivered after two
%% expansion-steps/after "hitting" \UD@ExtractFirstArg with \expandafter
%% twice.
%%
%% \UD@ExtractFirstArg's argument must not be blank.
%% This case can be cranked out via \UD@CheckWhetherBlank before calling
%% \UD@ExtractFirstArg.
%%
%% Use frozen-\relax as delimiter for speeding things up.
%% Frozen-\relax is chosen because David Carlisle pointed out in
%% <https://tex.stackexchange.com/a/578877>
%% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be
%% affected by \uppercase/\lowercase.
%%
%% \UD@ExtractFirstArg's argument may contain frozen-\relax:
%% The only effect is that internally more iterations are needed for
%% obtaining the result.
%%.............................................................................
\@ifdefinable\UD@RemoveTillFrozenrelax{%
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}%
{\long\def\UD@RemoveTillFrozenrelax#1#2}{{#1}}%
}%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral\expandafter
\UD@PassFirstToSecond\expandafter{\romannumeral
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}{\UD@stopromannumeral#1}%
}{%
\UD@stopromannumeral\romannumeral\UD@ExtractFirstArgLoop
}%
}{%
\newcommand\UD@ExtractFirstArg[1]%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@stopromannumeral#1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillFrozenrelax#1}}%
}%
%====================================================================
\@ifdefinable\UD@AndInstance{\long\def\UD@AndInstance#1#3!{#2}}%
\newcommand\UD@ReplaceAndInstance[2]{%
% #1 a single token to examine
% #2 replacement for &
\UD@AndInstance#1{\UD@stopromannumeral#2}&{\UD@stopromannumeral#1}!%
}%
\newcommand\UD@AndReplaceloop[3]{%
% #1 replacement for &
% #2 tokens forming the result gathered so far
% #3 remaining token list to process
\UD@CheckWhetherNull{#3}{\UD@stopromannumeral#2}{%
\expandafter\UD@Exchange\expandafter{%
\romannumeral
\UD@CheckWhetherLeadingExplicitSpace{#3}{%
\expandafter\UD@PassFirstToSecond
\expandafter{\UD@removespace#3}{\UD@stopromannumeral{#2 }}%
}{%
\expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#3}{%
\expandafter\expandafter\expandafter\UD@stopromannumeral
\expandafter\expandafter\expandafter{%
\expandafter\UD@Exchange\expandafter{%
\romannumeral
\UD@CheckWhetherBrace{#3}{%
\expandafter\UD@stopromannumeral\expandafter{\romannumeral
\expandafter\expandafter\expandafter\UD@PassFirstToSecond
\UD@ExtractFirstArg{#3}{\UD@AndReplaceloop{#1}{}}%
}%
}{%
\expandafter\expandafter\expandafter\UD@ReplaceAndInstance
\UD@ExtractFirstArg{#3}{#1}%
}%
}%
{#2}%
}%
}%
}%
}{\UD@AndReplaceloop{#1}}%
}%
}%
\newcommand\ReplaceAnd[2]{%
% #1 replacement for &
% #2 tokens where to replace & by replacement for &
\romannumeral\UD@AndReplaceloop{#1}{}{#2}%
}%
%%///////// End of code for \ReplaceAnd //////////////////////////////////////
\makeatother
\documentclass[]{article}
\usepackage{amsmath}
\usepackage{tikz-cd}
\begingroup
\makeatletter
\catcode`\&=13 %
\@firstofone{%
\endgroup
\NewDocumentCommand{\zx}{o+m}{%
\ReplaceAnd{&}{\IfNoValueTF{#1}{\begin{tikzcd}}{\begin{tikzcd}[{#1}]}#2\end{tikzcd}}%
}%
}%
\NewDocumentCommand{\zxE}{O{}+m}{%
\begingroup\catcode`\&=\active
\begin{tikzcd}[#1]\scantokens{#2}\end{tikzcd}%
\endgroup
}
\makeatletter
\newcommand\Q{\futurelet\QQ\QQQ}
\newcommand\QQQ{%
\text{\texttt{\string\Q}(\ifx\QQ\@sptoken\else no \fi space token behind \texttt{\string\Q})}%
}%
\makeatother
\begin{document}
\noindent\verb|\ReplaceAnd|-method:
\begin{verbatim}
\begin{align}
\zx{\string#A}&=\zx{\string#A \rar & \string#B}\\
\zx{\catcode`\Q=12 \Q!}&=\zx{UV \rar & UW}
\end{align}
\end{verbatim}
\begin{align}
\zx{\string#A}&=\zx{\string#A \rar & \string#B}\\
\zx{\catcode`\Q=12 \Q!}&=\zx{UV \rar & UW}
\end{align}
\noindent\hrule\hfill\bigskip
\noindent\verb|\scantokens|-method:
\begin{verbatim}
\begin{align}
\zxE{\string#A}&=\zxE{\string#A \rar & \string#B}\\
\zxE{\catcode`\Q=12 \Q!}&=\zxE{UV \rar & UW}
\end{align}
\end{verbatim}
\begin{align}
\text{You get: ! You can't use `macro paramete}&\text{r character \string#' in math mode.}\\
%\zxE{\string#A}&=\zxE{\string#A \rar & \string#B}\\
\zxE{\catcode`\Q=12 \Q!}&=\zxE{UV \rar & UW}
\end{align}
\end{document}
\scantokens
关于在那些边缘情况/奇怪场景中使用方法产生的相当意外的效果/结果:
“3.7 输入处理”一节eTeX 手册说\scantokens
:
该命令
\scantokens{...}
吸收未扩展标记列表,将其转换为字符串(该字符串被视为外部文件),然后开始从该“伪文件”读取。 [...] 特别是,当前换行符的每次出现都被解释为新行的开始,并且输入字符将照常转换为标记。 [...]
这里的关键点是“将其转换为字符串”。此转换由伪造未展开的标记列表并将结果字符(不是标记!)视为形成外部文件,该文件的字符将像往常一样由 TeX 的眼睛读取和预处理(删除出现在行右端的所有空格字符,附加一个字符,该字符在 TeX 的内部字符编码方案中的代码点号等于整数参数的值\endlinechar
),并且构成该预处理结果的字符由 TeX 的嘴根据需要进行标记,即,每当食道(进行扩展的地方)和/或胃(进行分配和构建盒子/页面等的地方)需要更多标记时。
当将第 6 类(参数)的显式字符标记(即 hashes( #
))写入未扩展的文件时,TeX 会将其加倍。
除了“哈希加倍”之外,标记的未扩展写入就像应用于\string
该标记并写入结果的不可扩展字符标记序列,并且如果所讨论的标记是控制字标记,则附加空格字符。 (如果所讨论的标记是控制符号标记或显式字符标记,则不附加空格字符。)
反过来,使用\string
控制序列标记意味着传递一个前置转义字符,该字符在 TeX 内部字符表示方案中的代码点编号等于整数参数的值\escapechar
。通常该值为 92,表示反斜杠,但可以更改!作为应用于无名控制序列的特殊情况,它通过扩展或在 .tex 输入的一行末尾放置反斜杠而\string
产生,同时具有非正值,传递,不附加空格字符。\csname\endcsname
\endlinechar
⟨character according to \escapechar⟩csname⟨character according to \escapechar⟩endcsname
所有这些事情都可能给你带来麻烦\scantokens
。
例如, 在“写入伪文件”时将\zxE{\string#A}
的哈希值加倍 会导致序列中的哈希值加倍,并且将空格字符附加到来自未扩展写入控制字标记的内容中,以便要像往常一样处理的伪文件包含字符。\scantokens
\string#A
\string⟨space-character⟩##A
这是针对一个文件进行的。读取并预处理该文件的第一行将产生字符。
\string⟨space-character⟩##A⟨character according to \endlinechar⟩
标记化产生控制字标记\string
。当 TeX 的读取装置在标记化控制字标记后切换到状态 S(跳过空白)时,后续⟨空格字符⟩被跳过,根本不会产生任何标记。然后第一个哈希被标记化并产生一个明确的#
字符标记,类别为 6(参数)。 \string
应用于该字符标记,进而产生一个明确的#
字符标记,类别为 12(其他)。这会进入胃部并像在数学模式下排版任何普通字符一样进行排版。然后第二个哈希被标记化并产生一个明确的#
字符标记,类别为 6(参数)。这会通过食道并到达胃部并导致“!您不能在数学模式下使用‘宏参数字符 #’。”错误。
通过\zxE{\catcode`\Q=12 \Q!}
\scantokens
' 创建伪文件会导致伪文件由以下字符组成:
\catcode⟨space-character⟩`\Q⟨space-character⟩=12 \Q⟨space-character⟩!
读取并预处理该文件的第一行得到以下字符
\catcode⟨space-character⟩`\Q⟨space-character⟩=12 \Q⟨space-character⟩!⟨character according to \endlinechar⟩
当逐个标记这些字符并处理生成的标记时,该字符\Q
被分配类别代码 12(其他)。因此,最后一个\Q
是控制符号标记,而不是控制字标记。由于它不是控制字标记,因此阅读器不会切换到状态 S(跳过空白),而是切换到状态 M(行中),随后⟨空格字符⟩不会被跳过,但会产生类别代码为 10(空格)和字符代码为 32 的明确字符标记。这反过来又可以由\futurelet
定义背后的机制检测到\Q
。