He definido un comando low
que agrega un subíndice a un argumento:
\newcommand{\low}[1]{{#1}_{l_{\mathcal{A}}}}
Sin embargo, si el argumento de low
contiene subíndices en sí (por ejemplo, en el caso de \low{\low{\Sigma}}
), no es fácil ver que los subíndices introducidos por el exterior low
pertenecen a todo el argumento y no solo al primer subíndice. Por lo tanto, me gustaría introducir paréntesis automáticamente si el argumento de low
contiene un subíndice (así \low{\low{\Sigma}}
debería verse así \low{(\low{\Sigma})}
).
Para lograr esto intenté lo siguiente usando el paquete xifthen:
\newcommand{\low}[1]{\ifthenelse{\isin{_}{#1}}{{(#1)}_{l_{\mathcal{A}}}}{{#1}_{l_{\mathcal{A}}}}}
Sin embargo, para mi sorpresa, este comando sólo algunas veces inserta los paréntesis. En particular, no lo hace en el ejemplo dado anteriormente. ¿Por qué sucede esto y qué puedo hacer para solucionarlo?
EDITAR: Parece que el problema es que \isin
no desenrolla las definiciones de los comandos. @egreg ya proporcionó una respuesta que me permite verificar si hay llamadas anidadas \low
pero no funciona con argumentos que contienen otros comandos con subíndices. ¿Alguien tiene una solución que funcione para argumentos arbitrarios?
Respuesta1
En algunos casos, aplicar \protected@edef
y \@onelevel@sanitize
antes de comprobar si hay un "stringified" _
puede 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
hace que el siguiente token, si es expandible, se expanda exactamente una vez antes de que el siguiente token se expanda si es expandible. (La)TeX considera que el trabajo está \expandafter
terminado cuando se realiza la expansión del penúltimo token. Por lo tanto, puede usar cadenas/secuencias de \expandafter
para que (La)TeX "salte" sobre k tokens para expandir primero el (k+1)-ésimo token.
\@onelevel@sanitize\macro
cambia la definición de \macro
para que \macro
escupe una secuencia de tokens de caracteres del código de categoría 12 (otro) que se parece a la secuencia de tokens que habría sido "escupido" antes \macro
de aplicar \@onelevel@sanitize
. Es casi como redefinir \macro
lo que se obtiene al aplicar la definición \string
a cada token \macro
.
\protected@edef
define una macro pero antes de hacerlo, expande todos los tokens expandibles del texto de definición, excepto aquellos que están definidos mediante \DeclareRobustCommand
el token o están precedidos por él \protect
. Se podría decir: \protected@edef
"desenrolla" las definiciones de los tokens contenidos en su texto de definición antes de realizar la asignación.
\@tempa
es una macro borrador que se define mediante \protected@edef
la expansión del argumento #1
con todas las definiciones #1
"desenrolladas".
La \ifthenelse{\isin...}
prueba no encuentra _
que estén anidados entre llaves, ya que las llaves suelen tener una función especial. Por lo tanto, \@onelevel@sanitize
se aplica para convertir todas las fichas, y por tanto también las llaves, en fichas de caracteres ordinarias e inofensivas del código de categoría 12 (otros) que no perturben la \ifthenelse{\isin...}
prueba.
Y aquí hay una rutina que no verifica el guión bajo (en cadena) del código de categoría 12 (otro) a través dexifentos's \ifthenelse{\isin...}
-thingie pero busca tokens del código de categoría 8 (subíndice) sin cadena.
La rutina se llama a sí misma de forma recursiva al examinar los tokens que forman el argumento.
La rutina aún no amplía el argumento; esto aún debe hacerse mediante \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}
Tenga en cuenta que esta rutina no tiene como objetivo detectar _
tokens de caracteres (encadenados) del código de categoría 12 (otros), sino detectar todos los tokens de caracteres (sean explícitos o implícitos) del código de categoría 8 (subíndice).
Respuesta2
Una idea (no una solución sencilla) es colocar el argumento dentro de un cuadro y verificar su altura con la altura de un carácter que supone que no es demasiado alto para necesitar paréntesis, pero tampoco demasiado corto para agregar paréntesis a su \Sigma.
Y adivina cuál será nuestro argumento por defecto: \Sigma
... P
El código (que contiene algunas pruebas) es 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}
Eso produce:
PD: Por supuesto, se deben agregar soluciones manuales en casos especiales, pero de todos modos, en su comando, estoy seguro de que tendrá excepciones para muchos casos.