
Estoy intentando definir un comando que tome dos argumentos, uno de los cuales es opcional. Si no se proporciona el argumento opcional, me gustaría que se utilice el argumento obligatorio para el argumento opcional.
Por ejemplo:
\necommand{\foo}[2][#2]{#1 foo(#2)}
que devolvería:
IN: \foo[hello]{hi} OUT: hello foo(hi)
IN: \foo{hi} OUT: hi foo(hi)
Idealmente, sería bueno mantenerlo simple y usar LaTeX estándar sin paquetes. ¡Gracias de antemano!
Respuesta1
El enfoque clásico para esto es utilizar \@dblarg
:
\documentclass{article}
\makeatletter
\newcommand{\foo}{\@dblarg\ah@foo}
\def\ah@foo[#1]#2{#1 foo(#2)}
\makeatother
\begin{document}
No optional argument: \foo{xyz}
Optional argument: \foo[abc]{xyz}
\end{document}
Con xparse
es más fácil:
\documentclass{article}
\usepackage{xparse}
\NewDocumentCommand{\foo}{om}{%
\IfNoValueTF{#1}{#2}{#1} foo(#2)%
}
\begin{document}
No optional argument: \foo{xyz}
Optional argument: \foo[abc]{xyz}
\end{document}
Siempre que necesite el argumento opcional, escriba \IfNoValueTF{#1}{#2}{#1}
.
Hay una forma mucho más sencilla si ha xparse
publicado el 10/02/2017 (o posterior): un argumento opcional O
puede tomar como predeterminado cualquiera de los argumentos obligatorios:
\documentclass{article}
\usepackage{xparse}
\NewDocumentCommand{\foo}{ O{#2} m }{%
#1 foo(#2)%
}
\begin{document}
No optional argument: \foo{xyz}
Optional argument: \foo[abc]{xyz}
\end{document}
Entonces estamos diciendo \foo
que, si falta el argumento opcional (primera llamada), el argumento obligatorio #2
también debe usarse como valor para #1
. En la segunda llamada, se proporciona el argumento opcional, por lo que se sustituye por #1
.
Respuesta2
Esto es bastante fácil con \NewDocumentCommand
from xparse
, verificando si el argumento opcional (o) se proporcionó o no con \IfValueTF{#1}{}{}
.
Sin embargo, no es tan fácil con \newcommand
. Defina un comando sin argumentos, diga \foobar
y verifique \@ifnextchar[{}{}
si el siguiente carácter es a [
y bifurquese en un comando que use []{}
argumentos y otro que solo use el argumento obligatorio, es decir {}
. Esta técnica se llama "argumentos en movimiento". De esta manera no necesita otros paquetes adicionales y aplica las funciones principales de LaTeX. El único punto "complicado" es utilizar el \makeatletter...\makeatother
par.
\@ifnextchar[
busca [
y, si se encuentra, el carácter básicamente se "desplaza" hacia atrás de modo que \foobar@opt
pueda encontrarlo nuevamente como inicio de un comando con un argumento opcional (en realidad, [
se almacena en una macro temporal y se expande para la true
rama si [
se ha encontrado)
\documentclass{article}
\usepackage{xparse}
\makeatletter
\newcommand{\foobar}{%
\@ifnextchar[{\foobar@opt}{\foobar@noopt}
}{}
\newcommand{\foobar@opt}[2][]{%
#1 foo(#2)%
}
\newcommand{\foobar@noopt}[1]{%
#1 foo(#1)%
}
\makeatother
\NewDocumentCommand{\foo}{om}{%
\IfValueTF{#1}{%
#1 foo(#2)%
}{%
#2 foo(#2)%
}%
}
\begin{document}
\foo[hello]{hi}
\foo{hi}
\foobar[hello]{hi}
\foobar{hi}
\end{document}
Respuesta3
Puede proporcionar un valor predeterminado para el argumento opcional y utilizarlo para condicionar si se proporcionó o no en primer lugar.
\documentclass{article}
\newcommand{\foo}[2][\relax]{%
\ifx\relax#1\relax #2\else #1\fi
~foo(#2)%
}
\begin{document}
\foo[hello]{hi}
\foo[]{hi}
\foo{hi}
\end{document}
Arriba especifiqué que el argumento opcional será predeterminado \relax
si no se proporciona, y luego verificaré \ifx\relax#1\relax
en cualquier lugar #2
o #1
. \ifx
Compara los siguientes dos tokens para determinar su equivalencia.
Respuesta4
Prefiero la respuesta de egreg, es decir, la sugerencia de usar \@dblarg
.
Werner sugirió buscar un "valor predeterminado" en términos de \ifx
comparación.
Verificar un valor predeterminado es algo diferente de verificar si se proporcionó un argumento opcional, ya que el caso en que el argumento opcional no se proporciona en absoluto no se distingue del caso en que el argumento opcional se proporciona explícitamente con el valor predeterminado.
\ifx
-la comparación implica que
- cualquiera de las comparaciones debe realizarse de forma no ampliable definiendo y
\ifx
comparando macros temporales - o
\ifx
-comparison se aplicará directamente al token que forma el valor predeterminado y a los tokens realmente proporcionados como argumento opcional.
En el último caso \ifx
, la comparación impone algunas restricciones sobre lo que se puede utilizar como valor predeterminado y no es completamente "impermeable":
- Los valores predeterminados no pueden consistir en varios tokens.
- Como
\ifx
la comparación compara tokens individuales, no argumentos macro,\ifx
la comparación se puede superar de varias maneras erróneas en casos extremos donde los argumentos constan de más de un token. - Una
\ifx
comparación también puede ser superada por el caso límite de argumentos que contienen desequilibrados\else
o\fi
. \ifx
-Las comparaciones se pueden superar mediante el uso de tokens de secuencia de control o tokens de personajes activos\let
iguales al token de valor predeterminado.
Si sigue la ruta de verificar un valor predeterminado de forma expandible, es decir, sin definir algunas macros temporales y \ifx
compararlas, sugiero
ya sea teniendo "vacío" como valor predeterminado y verificando el vacío:
\documentclass{article} \makeatletter %%========================================================================= %% Paraphernalia: %% \UD@firstoftwo, \UD@secondoftwo %%......................................................................... \newcommand\UD@firstoftwo[2]{#1}% \newcommand\UD@secondoftwo[2]{#2}% %%------------------------------------------------------------------------- %% 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> %% %% A concern in his posting is that the argument is hit with \string %% after some expansions which in edge cases might result in unbalancing %% surrounding \if..\fi-constructs if the macro is used inside of such %% \if..\fi-constructs. %% %% That challenging concern sickened me. ;-) %% %% Therefore I decided to implerment a variant where this cannot happen %% as expansion is forced by \romannumeral: %% %% After the first expansion-step, \string is not applied yet. %% After the second expansion-step, any possibly disturbing remainders %% are already removed due to \romannumeral-expansion. %% %% No eTeX- or whatsoever extensions. No \if.. .Only \romannumeral, %% digit 0, space token for terminating \romannumeral-expansion, %% \string, \expandafter, \UD@firstoftwo, \UD@secondoftwo, {, }. %% %% May 20, 2016 %% %% Ulrich Diez (e-mail: [email protected]) %% \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}% }% \newcommand{\foo}[2][]{% \UD@CheckWhetherNull{#1}{#2}{#1}~foo(#2)% }% \makeatother \parindent=0ex \parskip=\bigskipamount \begin{document} Empty optional argument or no optional argument will be given---% \verb|\foo{hi}|:\\ \foo{hi} Empty optional argument or no optional argument will be given---% \verb|\foo[]{hi}|:\\ \foo[]{hi} Empty optional argument or no optional argument will be given---% \verb|\foo[{}]{hi}|:\\ \foo[{}]{hi} A nice optional argument will be given---% \verb|\foo[hello]{hi}|:\\ \foo[hello]{hi} \end{document}
o comprobando el valor predeterminado mediante macros que procesan argumentos delimitados:
\documentclass{article} \makeatletter %%========================================================================= %% Paraphernalia: %% \UD@firstoftwo, \UD@secondoftwo %%......................................................................... \newcommand\UD@firstoftwo[2]{#1}% \newcommand\UD@secondoftwo[2]{#2}% %%------------------------------------------------------------------------- %% 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> %% %% A concern in his posting is that the argument is hit with \string %% after some expansions which in edge cases might result in unbalancing %% surrounding \if..\fi-constructs if the macro is used inside of such %% \if..\fi-constructs. %% %% That challenging concern sickened me. ;-) %% %% Therefore I decided to implerment a variant where this cannot happen %% as expansion is forced by \romannumeral: %% %% After the first expansion-step, \string is not applied yet. %% After the second expansion-step, any possibly disturbing remainders %% are already removed due to \romannumeral-expansion. %% %% No eTeX- or whatsoever extensions. No \if.. .Only \romannumeral, %% digit 0, space token for terminating \romannumeral-expansion, %% \string, \expandafter, \UD@firstoftwo, \UD@secondoftwo, {, }. %% %% May 20, 2016 %% %% Ulrich Diez (e-mail: [email protected]) %% \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 contains no exclamation-mark on top-brace-level: %%......................................................................... %% \UD@CheckWhetherNoExclamationMark{<Argument which is to be checked>}% %% {<Tokens to be delivered in case that %% argument which is to be checked does not contain !>}% %% {<Tokens to be delivered in case that %% argument which is to be checked does contain !>}% \long\def\UD@RemoveToExclamationMark#1!{}% \long\def\UD@CheckWhetherNoExclamationMark#1{% \expandafter\UD@CheckWhetherNull\expandafter{\UD@RemoveToExclamationMark#1!}% }% %%------------------------------------------------------------------------- %% Fork depending on some tokens: %%......................................................................... %%\\CheckWhetherDefault{<Argument which is to be checked>}% %% {<Tokens to be delivered in case argument is "Default value">}% %% {<Tokens to be delivered in case argument is not "Default value">}% %% %% In case <Argument which is to be checked> is neither "case 1" nor %% "case 2" the phrase "Error: Unknown parameter ``<Argument which is %% to be checked>'' to \CheckWhetherDefault." will be delivered. %% \newcommand\@CheckWhetherDefault{}% \long\def\@CheckWhetherDefault% #1!!Default value!#2#3!!!!{#2}% \newcommand\CheckWhetherDefault[1]{% \romannumeral0% \UD@CheckWhetherNoExclamationMark{#1}{% \@CheckWhetherDefault !#1!Default value!{\UD@firstoftwo}% <- #1 is empty. !!#1!{\UD@firstoftwo}% <- #1 = Default value !!Default value!{\UD@secondoftwo}% <- #1 = something else without exclamation mark !!!!% }{\UD@secondoftwo}% <- #1 = something else with exclamation mark }% \makeatother \newcommand{\foo}[2][Default value]{% \CheckWhetherDefault{#1}{#2}% {#1}% ~foo(#2)% }% \parindent=0ex \parskip=\bigskipamount \begin{document} ``Default value'' or empty optional argument or no optional argument will be given---% \verb|\foo{hi}|:\\ \foo{hi} ``Default value'' or empty optional argument or no optional argument will be given---% \verb|\foo[Default value]{hi}|:\\ \foo[Default value]{hi} ``Default value'' or empty optional argument or no optional argument will be given---% \verb|\foo[]{hi}|:\\ \foo[]{hi} ``Default value'' or empty optional argument or no optional argument will be given---% \verb|\foo[{}]{hi}|:\\ \foo[{}]{hi} A nice optional argument will be given---% \verb|\foo[hello]{hi}|:\\ \foo[hello]{hi} \end{document}
También puedes seguir la aburrida ruta no expandible de definir y \ifx
comparar macros temporales:
\documentclass{article}
\makeatletter
\newcommand{\foo}[2][Default value]{%
\begingroup
\def\mytempa{#1}%
\def\mytempb{Default value}%
\ifx\mytempa\mytempb
\expandafter\endgroup\expandafter\@secondoftwo
\else
\expandafter\@firstofone
\fi
{%
\def\mytempb{}%
\expandafter\endgroup\ifx\mytempa\mytempb
\expandafter\@secondoftwo
\else
\expandafter\@firstoftwo
\fi
{#1}%
}{#2}~foo(#2)%
}%
\makeatother
\parindent=0ex
\parskip=\bigskipamount
\begin{document}
``Default value'' or empty optional argument or no optional argument will be given---%
\verb|\foo{hi}|:\\
\foo{hi}
``Default value'' or empty optional argument or no optional argument will be given---%
\verb|\foo[Default value]{hi}|:\\
\foo[Default value]{hi}
``Default value'' or empty optional argument or no optional argument will be given---%
\verb|\foo[]{hi}|:\\
\foo[]{hi}
``Default value'' or empty optional argument or no optional argument will be given---%
\verb|\foo[{}]{hi}|:\\
\foo[{}]{hi}
A nice optional argument will be given---%
\verb|\foo[hello]{hi}|:\\
\foo[hello]{hi}
\end{document}
Como dije antes: en situaciones comunes/habituales definitivamente prefiero el \@dblarg
-thing a cualquiera de estos enfoques.