检测命令参数中的下标

检测命令参数中的下标

low我定义了一个为参数添加下标的命令:

\newcommand{\low}[1]{{#1}_{l_{\mathcal{A}}}}

但是,如果的参数low本身包含下标(例如的情况\low{\low{\Sigma}}),则不容易看出外部引入的下标属于整个参数,而不仅仅是第一个下标。因此,如果的参数包含下标,low我希望自动引入括号(因此应该看起来像)。low\low{\low{\Sigma}}\low{(\low{\Sigma})}

为了实现这一点,我使用 xifthen 包尝试了以下操作:

\newcommand{\low}[1]{\ifthenelse{\isin{_}{#1}}{{(#1)}_{l_{\mathcal{A}}}}{{#1}_{l_{\mathcal{A}}}}}

然而令我惊讶的是,这个命令只是偶尔插入括号。特别是在上面给出的例子中,它没有这样做。为什么会发生这种情况?我该怎么做才能解决这个问题?

编辑:问题似乎在于\isin没有展开命令的定义。@egreg 已经提供了一个答案,允许我检查嵌套调用\low,但对于包含其他带下标的命令的参数不起作用。有人有适用于任意参数的解决方案吗?

答案1

在某些情况下,应用\protected@edef\@onelevel@sanitize在检查“字符串化”之前_可能会起作用:

\documentclass{article}
\usepackage{amsmath}
\usepackage{xifthen}

\makeatletter    
\DeclareRobustCommand\DetectUnderscore[1]{%
  \begingroup
  \protected@edef\@tempa{#1}%
  \@onelevel@sanitize\@tempa
  \expandafter\expandafter\expandafter\endgroup
  \expandafter\expandafter\expandafter\ifthenelse
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\expandafter\isin
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\string_%
  \expandafter}%
  \expandafter{%
  \@tempa}}{{(#1)}}{{#1}}%
}%
\newcommand\low[1]{%
  \DetectUnderscore{#1}%
  _{l_{\mathcal{A}}}%
}
\makeatother

\begin{document}

\[
\low{\Sigma} \qquad 
\low{\low{\Sigma}} \qquad
\low{\low{\low{\Sigma}}} \qquad
\]
\[
\low{\Sigma_b} \qquad 
\low{\low{\Sigma_b}} \qquad
\low{\low{\low{\Sigma_b}}} \qquad
\]
\[
\low{{\Sigma_b}} \qquad
\low{b_{\low{c_{\low{\Sigma_d}}}}} \qquad
(\Sigma_b)_{l_\mathcal{A}}
\]
\[
\low{\low{1} + 2}
\]    
\end{document}

在此处输入图片描述

\expandafter导致下一个标记(如果可扩展)在下一个标记扩展之前(如果可扩展)只扩展一次。 (La)TeX 在下\expandafter一个标记扩展完成后认为 的工作已完成。因此,您可以使用 的链/序列让\expandafter(La)TeX “跳过” k 个标记,首先扩展第 (k+1) 个标记。

\@onelevel@sanitize\macro更改 的定义\macro,以便输出类别代码为 12(其他) 的字符标记序列,该序列看起来像在应用 之前\macro“输出”的标记序列。这几乎就像重新定义通过应用的定义的每个标记而获得的结果。\macro\@onelevel@sanitize\macro\string\macro

\protected@edef定义一个宏,但在执行此操作之前,它会扩展定义文本的所有可扩展标记,除了通过标记定义\DeclareRobustCommand或在标记前面的标记\protect。您可能会说:\protected@edef在实际执行分配之前,会“展开”其定义文本中包含的标记的定义。

\@tempa是一个临时宏,它通过展开到具有“展开”中的所有定义的\protected@edef参数来定义。#1#1

-test\ifthenelse{\isin...}不会发现_嵌套在花括号中的字符,因为花括号通常具有特殊功能。因此,\@onelevel@sanitize它用于将所有标记(包括花括号)转换为类别代码为 12(其他)的普通无害字符标记,这些标记不会干扰测试\ifthenelse{\isin...}


下面是一个例程,它不检查类别代码 12(其他)的(字符串化)下划线西弗森\ifthenelse{\isin...}-thingie 但会检查类别代码 8(下标)的标记是否未经字符串化。

在检查构成参数的标记时,例程会递归调用自身。

该例程仍然没有扩展参数——这仍然必须通过来完成\protected@edef

\documentclass{article}
\usepackage{amsmath}

\makeatletter
%%-----------------------------------------------------------------------------
%% Paraphernalia ;-) :
%%.............................................................................
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@exchange[2]{#2#1}%
\newcommand\UD@removespace{}\UD@firstoftwo{\def\UD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%%   \UD@ExtractFirstArg{ABCDE} yields  {A}
%%
%%   \UD@ExtractFirstArg{{AB}CDE} yields  {AB}
%%
%% !!! The argument of \UD@ExtractFirstArg must not be empty. !!!
%% You can check for emptiness via \UD@CheckWhetherNull before applying
%% \UD@ExtractFirstArg.
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
  \romannumeral0%
  \UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  { #1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% 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]{%
  \romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% 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 leading
%%                        catcode-1-token>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has no leading
%%                        catcode-1-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@firstoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is a
%%                               space-token>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is not
%%                               a space-token>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
  \romannumeral0\UD@CheckWhetherNull{#1}%
  {\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
  {\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\newcommand\UD@CheckWhetherLeadingSpaceB{}%
\long\def\UD@CheckWhetherLeadingSpaceB#1 {%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#1{}}%
  {\UD@exchange{\UD@firstoftwo}}{\UD@exchange{\UD@secondoftwo}}%
  {\UD@exchange{ }{\expandafter\expandafter\expandafter\expandafter
   \expandafter\expandafter\expandafter}\expandafter\expandafter
   \expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%.............................................................................
%% Check whether brace-balanced argument starts with a token of
%% category code 8 (subscript)
%%.............................................................................
%% \UD@CheckWhetherFirstTokenHasCatcodeSubscript{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that 
%%                         <argument which is to be checked> has a first
%%                         token of catcode 8>}%
%%                      {<Tokens to be delivered in case that
%%                         <argument which is to be checked> does not have
%%                         a first token of catcode 8>}%
%%
\newcommand\UD@CheckWhetherFirstTokenHasCatcodeSubscript[1]{%
  \romannumeral0%
  \UD@CheckWhetherNull{#1}{\UD@exchange{ }{\expandafter}\UD@secondoftwo}{%
    \UD@CheckWhetherBrace{#1}{\UD@exchange{ }{\expandafter}\UD@secondoftwo}{%
      \UD@CheckWhetherLeadingSpace{#1}{\UD@exchange{ }{\expandafter}\UD@secondoftwo}{%
        \expandafter\expandafter\expandafter\UD@@CheckWhetherFirstTokenHasCatcodeSubscript
        \UD@ExtractFirstArg{#1}%
      }%
    }%
  }%
}%
\newcommand\UD@@CheckWhetherFirstTokenHasCatcodeSubscript[1]{%
  \expandafter\ifcat_#1%
  \expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
  {\UD@exchange{ }{\expandafter}\UD@firstoftwo}%
  {\UD@exchange{ }{\expandafter}\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument does contain underscore/tokens of 
%% category code 8 (subscript), no matter if nested in braces or not.
%%
%% \UD@CheckWhetherSubscriptTokens{<Argument which is to be checked>}%
%%                  {<Tokens to be delivered in case that 
%%                     <argument which is to be checked> contains
%%                     some token(s) of catcode 8 (subscript)>}%
%%                  {<Tokens to be delivered in case that
%%                     <argument which is to be checked> contains
%%                     no token of catcode 8 (subscript)>}%
%%
%%-----------------------------------------------------------------------------
\newcommand\UD@CheckWhetherSubscriptTokens{\romannumeral0\UD@@CheckWhetherSubscriptTokens}%
\newcommand\UD@@CheckWhetherSubscriptTokens[3]{%
  \UD@CheckWhetherNull{#1}{ #3}{%
    \UD@CheckWhetherLeadingSpace{#1}{%
      \expandafter\UD@@CheckWhetherSubscriptTokens\expandafter{\UD@removespace#1}{#2}{#3}%
    }{%
      \UD@CheckWhetherBrace{#1}{%
        \expandafter\expandafter\expandafter\UD@CheckWhetherSubscriptTokens
        \UD@ExtractFirstArg{#1}{ #2}{%
          \expandafter\UD@@CheckWhetherSubscriptTokens\expandafter{\UD@firstoftwo{}#1}{#2}{#3}%
        }%
      }{%
        \UD@CheckWhetherFirstTokenHasCatcodeSubscript{#1}{ #2}{%
           \expandafter\UD@@CheckWhetherSubscriptTokens\expandafter{\UD@firstoftwo{}#1}{#2}{#3}%
        }%
      }%
    }%
  }%
}%

\DeclareRobustCommand\UD@CallUD@CheckWhetherSubscriptTokensOnExpansion[1]{%
  \begingroup
  \protected@edef\@tempa{#1}%
  \expandafter\endgroup
  \expandafter\UD@CheckWhetherSubscriptTokens\expandafter{\@tempa}{{(#1)}}{{#1}}%
}%

\newcommand\low[1]{%
  \UD@CallUD@CheckWhetherSubscriptTokensOnExpansion{#1}_{l_{\mathcal{A}}}%
}
\makeatother

\begin{document}

% Let`s use | and \myunderscore in the same way as _ :

\catcode`\|=8

\let\myunderscore=_

\[
\low{\Sigma} \qquad 
\low{\low{\Sigma}} \qquad
\low{\low{\low{\Sigma}}} \qquad
\]
\[
\low{\Sigma\myunderscore b} \qquad 
\low{\low{\Sigma\myunderscore b}} \qquad
\low{\low{\low{\Sigma|b}}} \qquad
\]
\[
\low{{\Sigma_b}} \qquad
\low{b_{\low{c_{\low{\Sigma|d}}}}} \qquad
(\Sigma_b)_{l_\mathcal{A}}
\]
\[
\low{\low{1} + 2}
\]    
\end{document}

在此处输入图片描述

请注意,此例程的目的不是检测_类别代码 12(其他)的(字符串化)字符标记,而是检测类别代码 8(下标)的所有字符标记(无论是显式的还是隐式的)。

答案2

一个想法(不是直接的解决方案)是将参数放在一个盒子里,并检查它的高度与一个字符的高度,你认为这个字符的高度不会太高而不需要括号,但也不会太短而无法在你的 \Sigma 中添加括号。

猜猜我们的默认参数是什么:\Sigma...P

代码(包含一些测试)如下:

\documentclass{article}
\def\DefLowArg{$\Sigma$}
\let\oldDefLowArg\DefLowArg
\newsavebox{\myAbox}
\newsavebox{\myBbox}
\newcommand{\low}[2][\DefLowArg]{\savebox\myAbox{\vbox{#1}}\savebox\myBbox{\vbox{\ensuremath{#2}}}
\ifdim\dimexpr\ht\myAbox+\dp\myAbox<\dimexpr\ht\myBbox+\dp\myBbox\relax
\left({#2}\right)_{l_{\mathcal{A}}}
\else {#2}_{l_{\mathcal{A}}}\fi
}
\begin{document}

\[\low{\low{\Sigma}}\]

\[\low{\Sigma}\]
\[
\low{\sum_{i=3}^5 F(x)}
\]

\[
\low{\frac{F(x)}{x+5}}
\]

\[\low{F_x}\]

\[\low[1/4]{F(x)}\]

\[\low{x^2}\]

\[
\low{G_x}
\]

These commands may be should add without parentheses

\[
\low{g(z)}
\]
\[
\low{F(x)}
\]

{\bfseries Solution 1 Add an tall optional argument in the command like: \verb|\low[/]{F(x)}|}

\[
\low[/]{g(z)}
\]
\[
\low[/]{F(x)}
\]

{\bfseries Solution 2 Change the Default argument \verb|\DefLowArg| to something tall enough (return with \verb|\let\DefLowArg\oldDefLowArg|):}

\xdef\DefLowArg{/}

\[
\low{g(z)}
\]
\[
\low{F(x)}
\]
\let\DefLowArg\oldDefLowArg


{\bfseries And back to default}

\[
\low{F(X)}
\]

\end{document}

得出的结果为:

在此处输入图片描述

附言:当然,特殊情况下应该添加手动解决方案,但无论如何,在您的命令中,我相信很多情况都会有例外。

相关内容