Eu defini um comando low
que adiciona um subscrito a um argumento:
\newcommand{\low}[1]{{#1}_{l_{\mathcal{A}}}}
No entanto, se o próprio argumento de low
contém subscritos (por exemplo, no caso de \low{\low{\Sigma}}
), não é fácil ver que os subscritos introduzidos pelo exterior low
pertencem a todo o argumento e não apenas ao primeiro subscrito. Portanto, eu gostaria de introduzir automaticamente parênteses se o argumento de low
contiver um subscrito (assim \low{\low{\Sigma}}
deveria ser parecido com \low{(\low{\Sigma})}
.
Para conseguir isso, tentei o seguinte usando o pacote xifthen:
\newcommand{\low}[1]{\ifthenelse{\isin{_}{#1}}{{(#1)}_{l_{\mathcal{A}}}}{{#1}_{l_{\mathcal{A}}}}}
No entanto, para minha surpresa, este comando só às vezes insere os parênteses. Em particular, isso não acontece no exemplo dado acima. Por que isso acontece e o que posso fazer para corrigir isso?
EDIT: Parece que o problema é que \isin
não desenrola as definições dos comandos. @egreg já forneceu uma resposta que me permite verificar chamadas aninhadas, \low
mas não funciona para argumentos que contêm outros comandos com subscritos. Alguém tem uma solução que funcione para argumentos arbitrários?
Responder1
Em alguns casos, aplicar \protected@edef
e \@onelevel@sanitize
antes de verificar se há um "stringificado" _
pode funcionar:
\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
faz com que o próximo token - se expansível - seja expandido exatamente uma vez antes do próximo token ser expandido, se expansível. (La)TeX considera o trabalho \expandafter
concluído quando a expansão do próximo token é concluída. Portanto, você pode usar cadeias/sequências para \expandafter
fazer com que o (La)TeX "pule" sobre k tokens para primeiro expandir o (k+1)-ésimo token.
\@onelevel@sanitize\macro
altera a definição de \macro
para que \macro
exiba uma sequência de tokens de caracteres do código de categoria 12 (outro) que se parece com a sequência de tokens que teria sido "cuspida" antes \macro
de aplicar \@onelevel@sanitize
. É quase como redefinir \macro
o que você obtém aplicando \string
a definição de cada token \macro
.
\protected@edef
define uma macro, mas antes de fazê-lo, expande todos os tokens expansíveis do texto de definição, exceto aqueles que são definidos \DeclareRobustCommand
ou precedidos pelo token \protect
. Você pode dizer: \protected@edef
"desenrola" as definições dos tokens contidos em seu texto de definição antes de realmente executar a atribuição.
\@tempa
é uma macro de rascunho que é definida por meio de \protected@edef
para expandir o argumento #1
com todas as definições em #1
"desenrolado".
O \ifthenelse{\isin...}
-test não descobre _
que estão aninhados entre chaves, pois as chaves geralmente têm uma função especial. Portanto, \@onelevel@sanitize
é aplicado para transformar todos os tokens, e portanto também as chaves, em tokens de caracteres comuns e inofensivos de código de categoria 12 (outros) que não perturbem o \ifthenelse{\isin...}
teste.
E aqui está uma rotina que não verifica o sublinhado (stringificado) do código de categoria 12 (outro) viaxiftão's \ifthenelse{\isin...}
-thingie mas verifica tokens do código de categoria 8 (subscrito) sem stringificação.
A rotina chama a si mesma recursivamente ao examinar os tokens que formam o argumento.
A rotina ainda não expande o argumento – isso ainda precisa ser feito via \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}
Esteja ciente de que esta rotina não tem como objetivo detectar _
-tokens de caracteres (stringificados) do código de categoria 12 (outros), mas detectar todos os tokens de caracteres (sejam eles explícitos ou implícitos) do código de categoria 8 (subscrito).
Responder2
Uma idéia (não uma solução direta) é colocar o argumento dentro de uma caixa e verificar sua altura com a altura de um caractere que você supõe não ser muito alto para precisar de parênteses, mas não muito curto para adicionar parênteses ao seu \Sigma.
E adivinhe qual será o nosso argumento padrão: \Sigma
... P
O código (contendo alguns testes) é este:
\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}
Isso produz:
PS:É claro que soluções manuais devem ser adicionadas em casos especiais, mas de qualquer forma, no seu comando, tenho certeza de que você teria exceções para muitos casos.