在命令中使用 &

在命令中使用 &

使用这个很好的答案我设法创建了一个\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&#2#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&#2#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

相关内容