TransWikia.com

Keeping default optional argument when adding to command

TeX - LaTeX Asked on March 2, 2021

TeX FAQ’s "Patching existing commands" gives a method to add to existing commands that have an optional argument:

documentclass{article}
usepackage{letltxmacro}

newcommand{rough}[2][default]{...}

LetLtxMacro{OldRough}{rough}

renewcommand{rough}[2][newdef]{mumbleOldRough[{#1}]{#2}}

However, it assumes that you want a new default argument "newdef". If the default argument is to be kept, you may change "newdef" to "default", but that’s only possible if you have internal knowledge of the previously defined command. Furthermore, if the that value is modified, you have to change it in your patching as well.

Is it possible to add to an existing command with optional arguments in a way that the default argument is kept, even if you don’t know what that argument is?

Note: The argument has to still be usable inside the new definition.


Edit: After trying the suggested xapptocmd and xapptocmd I was still unable to reproduce the desired result in the command I’m working with, clevelref‘s label.

I would expect the code below to write "aaa", and then apply the label, which is not the case, even when replaced by things such as math, the contents are still not present in the final document:

documentclass[]{article}

usepackage{cleveref,xpatch}

xpretocmdlabel{aaa}{}{}

begin{document}
label{example}
end{document}

This gives me the impression that it’s not being correctly appended.

Also, when trying to access the second argument of label, TeX throws the error Illegal parameter number in definition of etb@resrvda. xpretocmdlabel{#1#2}:

documentclass[]{article}

usepackage{cleveref,xpatch}

xpretocmdlabel{#1#2}{}{}

begin{document}
end{document}

I presume it is because it is detecting the standard definition of label, which only has a single parameter, instead of cleverf‘s one.

How may these be corrected?

3 Answers

What does LaTeX do when processing the definition of a command with an optional argument? Say we have

newcommand{test}[2][default]{Optional: #1; mandatory: #2.}

With a rather long sequence of steps that's not relevant for the discussion, the basic working is to do

deftest{@protected@testopttesttest{default}}
expandafterdefcsnamestringtestendcsname[#1]#2{Optional: #1; mandatory: #2.}

In the first line, test represents a single token with a backslash in its name, which can be obtained by csnamestringtestendcsname.

The macro @protected@testopt branches according to whether protect is relax or not and either calls @testopt (removing the else...fi part) or @x@protecttest (which removes everything up to fi and only leaves protecttest). The macro @testopt checks for a following [ and does the right thing.

So, if you want to patch the real replacement text of such a command you need to do

expandafterpatchcmdcsnamestringtestendcsname
  {<search>}
  {<replace>}
  {<success>}{<fail>}

It would be worse if you have

DeclareRobustCommand{test}[2][default]{Optional: #1; mandatory: #2.}

because in this case you'd need to do

expandafterpatchcmdcsnamestringtestspaceendcsname
  {<search>}
  {<replace>}
  {<success>}{<fail>}

For no optional argument, in the case of DeclareRobustCommand{test}[<n>]{...} the call should be

expandafterpatchcmdcsname test endcsname
  {<search>}
  {<replace>}
  {<success>}{<fail>}

This is the reason why I wrote the xpatch package, which is able to test how the command was defined to begin with and calls patchcmd with the right incantation. It also supports newrobustcmd from etoolbox and has built-in tools for patching internals of biblatex.

So you can simply do

usepackage{xpatch}

xpatchcmd{test}
  {<search>}
  {<replace>}
  {<success>}{<fail>}

and it will work in all cases.

What if you want to patch the default value for the optional argument? Even without knowing what the default value is?

documentclass{article}
usepackage{etoolbox}

makeatletter
defreplaceoptionalargument#1#2#3#4{%
  ifcsnamestring#1endcsname
    @replaceoptionalargument#1{#2}{#3}%
  else
    #4%
  fi
}
def@replaceoptionalargument#1#2#3{%
  edef#1{%
    unexpandedexpandafterexpandafterexpandafter{%
      expandafter@@replaceoptionalargument#1{#2}%
    }%
  }%
  #3%
}
def@@replaceoptionalargument#1#2#3#4#5{#1#2#3{#5}}
makeatother

newcommand{test}[2][default]{}

replaceoptionalargumenttest{new}{message{YES}}{message{NO}}

Addition about patching label

When you're trying to patch a command, you have to know in detail how it's defined. For instance, if you do showlabel in the preamble of your test document, you'll be presented the following information

> label=macro:
#1->@bsphack protected@write @auxout {}{string newlabel {#1}{{@currentlabel }{thepage }}}@esphack .

which is the standard LaTeX definition. Indeed, cleveref delays the redefinition at begin document. Good, let's issue showlabel after begin{document}, getting

> label=macro:
->@ifnextchar [label@optarg label@noarg .

which shows that label is not redefined like

renewcommand{label}[2][<default>]{...}

so your attempt will fail because label has no argument at all. Why does the developer do that way? Because there is no default value for the optional argument and different code has to be executed with or without the optional argument.

Correct answer by egreg on March 2, 2021

The xpatch package can patch commands containing optional arguments. The following changes the definition without changing the optional argument's value (and can access the parameters using #1 or #2):

documentclass[]{article}

usepackage{xpatch}

newcommandfoo[2][default]
  {%
    Normal definition. Optional argument: #1. Mandatory argument: #2.%
  }

begin{document}
foo{abc}

xpatchcmdfoo{Normal}{Patched}{}{}
foo{abc}

xpretocmdfoo{Optional=#1. }{}{}
foo{abc}

xapptocmdfoo{ Mandatory=#2.}{}{}
foo{abc}
end{document}

Answered by Skillmon on March 2, 2021

showrough reveals:

> rough=macro:
->@protected@testopt rough rough {newdef }.
l.12 showrough

rough is just a wrapper for launching the LaTeX 2e-kernel's mechanism for detecting the presence of an optional argument while taking the protection-mechanism into account.

Internally the macro rough is called for processing the arguments, thus that's the macro that needs to be patched:

documentclass{article}

newcommand{rough}[2][default]{... s.th. with #1 and #2 ...}

expandaftershowcsnamestringroughendcsname

% > rough=long macro:
% [#1]#2->... s.th. with #1 and #2 ....
% <recently read> rough 

%------------------------------------------------------------

newcommandPassFirstToSecond[2]{#2{#1}}%
newcommandExchange[2]{#2#1}%

expandafterPassFirstToSecondexpandafter{%
  romannumeral
  expandafterexpandafterexpandafterExchange
  expandafterexpandafterexpandafter{%
    csnamestringroughendcsname[{#1}]{#2}%
  }{0 mumbleatbegin}%
  mumbleatend%
}{%
  longexpandafterdefcsnamestringroughendcsname[#1]#2%
}%

expandaftershowcsnamestringroughendcsname

% > rough=long macro:
% [#1]#2->mumbleatbegin ... s.th. with #1 and #2 ...mumbleatend .
% <recently read> rough 

stop

Answered by Ulrich Diez on March 2, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP