Обнаружение нижнего индекса в аргументе команды

Обнаружение нижнего индекса в аргументе команды

Я определил команду 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}}}}}

Однако, к моему удивлению, эта команда вставляет скобки только иногда. В частности, в приведенном выше примере этого не происходит. Почему это происходит и что я могу сделать, чтобы это исправить?

EDIT: Похоже, проблема в том, что \isinне разворачивает определения команд. @egreg уже предоставил ответ, который позволяет мне проверять вложенные вызовы, \lowно не работает для аргументов, содержащих другие команды с индексами. Есть ли у кого-нибудь решение, которое работает для произвольных аргументов?

решение1

В некоторых случаях подача заявления \protected@edefи \@onelevel@sanitizeпроверка на «stringified» _могут сработать:

\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так, что \macroвыдает последовательность токенов символов категории кода 12(другие), которая выглядит как последовательность токенов, которая была бы "выдана" до \macroприменения \@onelevel@sanitize. Это почти как переопределение \macroтого, что вы получаете, применяя \stringк каждому токену \macroопределения .

\protected@edefопределяет макрос, но перед этим он расширяет все расширяемые токены текста определения, за исключением тех, которые определены через \DeclareRobustCommandтокен или предшествуются ему \protect. Можно сказать: \protected@edef«разворачивает» определения токенов, содержащихся в его тексте определения, перед фактическим выполнением назначения.

\@tempaпредставляет собой макрос-скачок, который определяется с помощью для \protected@edefрасширения до аргумента #1со всеми определениями в #1«развернутом» виде.

Тест \ifthenelse{\isin...}не обнаруживает _, что вложены в фигурные скобки, поскольку фигурные скобки обычно имеют специальную функцию. Поэтому \@onelevel@sanitizeприменяется для превращения всех токенов, а значит и фигурных скобок, в обычные безвредные символы-токены категории кода 12(другие), которые не мешают тесту \ifthenelse{\isin...}.


А вот процедура, которая не проверяет (строчное) подчеркивание кода категории 12 (другое) черезпятнадцатьтогда's \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}

Это производит:

введите описание изображения здесь

PS: Конечно, в особых случаях следует добавлять ручные решения, но в любом случае, я уверен, что в вашей команде есть исключения для многих случаев.

Связанный контент