¿Utilizando el argumento obligatorio para el argumento opcional?

¿Utilizando el argumento obligatorio para el argumento opcional?

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}

ingrese la descripción de la imagen aquí

Con xparsees 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 xparsepublicado el 10/02/2017 (o posterior): un argumento opcional Opuede 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 \fooque, si falta el argumento opcional (primera llamada), el argumento obligatorio #2tambié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 \NewDocumentCommandfrom 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 \foobary 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...\makeatotherpar.

\@ifnextchar[busca [y, si se encuentra, el carácter básicamente se "desplaza" hacia atrás de modo que \foobar@optpueda encontrarlo nuevamente como inicio de un comando con un argumento opcional (en realidad, [se almacena en una macro temporal y se expande para la truerama 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}

ingrese la descripción de la imagen aquí

Respuesta3

Puede proporcionar un valor predeterminado para el argumento opcional y utilizarlo para condicionar si se proporcionó o no en primer lugar.

ingrese la descripción de la imagen aquí

\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 \relaxsi no se proporciona, y luego verificaré \ifx\relax#1\relaxen cualquier lugar #2o #1. \ifxCompara 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 \ifxcomparació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 \ifxcomparando 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 \ifxla comparación compara tokens individuales, no argumentos macro, \ifxla comparación se puede superar de varias maneras erróneas en casos extremos donde los argumentos constan de más de un token.
  • Una \ifxcomparación también puede ser superada por el caso límite de argumentos que contienen desequilibrados \elseo \fi.
  • \ifx-Las comparaciones se pueden superar mediante el uso de tokens de secuencia de control o tokens de personajes activos \letiguales 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 \ifxcompararlas, 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}
    

    ingrese la descripción de la imagen aquí

  • 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}
    

    ingrese la descripción de la imagen aquí

También puedes seguir la aburrida ruta no expandible de definir y \ifxcomparar 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}

ingrese la descripción de la imagen aquí

Como dije antes: en situaciones comunes/habituales definitivamente prefiero el \@dblarg-thing a cualquiera de estos enfoques.

información relacionada