Short answer: there is no “definitive way”.
How (and if it is possible) to patch (or prepend or append to) a command depends on how it's defined (\def
, \newcommand
, \DeclareRobustCommand
, \NewDocumentCommand
), if it takes arguments, what are the contents of the command, and what you want to patch in that command.
Macro without arguments: The simplest case is if the command doesn’t take arguments. In this case the macro is (in expl3
parlance) a “token list”, and you can just expand the macro and do whatever with its contents. Everything inside that token list is (unsurprisingly) a token, so you can traverse it, query its contents, add and remove tokens at your will. Prepending and appending in this case is trivial (see the definition of \g@addto@macro
in latex.ltx
), and the difficulty of patching something in the middle again depends on what you want to replace.
Macro with arguments: A trickier case is when the macro has arguments. In this case you cannot simply expand it because when you expand it tries to grab arguments, and you don’t have these here (and if you do, you lose the macro parameters inside the definition, which you probably don’t want).
In this case you have to use a proper patching command (like etoolbox
’s \patchcmd
). The process of patching consists in turning the entire definition into a string (using \meaning
), separating the <parameter text>
from the <replacement text>
, doing the replacement, and then rescanning the string and redoing the definition using \scantokens
, hoping nothing goes wrong in this process (see an explanation of that here).
The problem with patching is the \scantokens
part, which assumes one single catcode regime, and depending how the macro was defined, that is not the case. Take for example LaTeX 2ε’s \rem@pt
macro. If you do \meaning\rem@pt
you get \rem@pt=macro:#1.#2pt->#1\ifnum #2>\z@ .#2\fi
(suppose you just want to replace the .
for a ,
: seems easy enough). The problem with patching this one is that in #1.#2pt
, the p
and t
are catcode-12 tokens, whereas the p
and t
in \rem@pt
must be catcode-11 so that you can actually do the definition. \patchcmd
will refuse to do anything here unless (it's not impossible, but you have to be careful). There are cases where this is outright impossible with \patchcmd
(try patching anything into the definition of \end
in a LaTeX older than 01-10-2019).
How it is defined (and optional arguments): Supposing that the macro has a well-behaved catcode regime, and that you can patch it using \patchcmd
. When you define \newcommand\foo[1][]{a#1b}
the actual definition of \foo
is not in \foo
, but in \\foo
(two backslashes in the name), so you have to patch the latter instead. This case is quite easy: load xpatch
and use \xpatchcmd
(or \xpretocmd
or \xapptocmd
) instead. xpatch
will do the thinking for you and will figure out that it needs to patch \\foo
instead (and will use etoolbox
’s \patchcmd
for that). If the command was defined with xparse
then xpatch
won’t help either (these commands can be patched, but they are not meant to, so xpatch
doesn’t even try to support that).
What you want to patch: You mentioned unbalanced \if
...\fi
in your question, but that’s actually not a problem. Unbalanced conditionals are problematic inside other conditionals, which is not the case when patching. The problem here is unbalanced {
...}
, since the patching macros assume balanced braces. Depending on what you want to do you can work-around (look here for an example), but it's really in a case-by-case basis.
l3regex
to the rescue: What gets closest to a “definitive” way to patch is using l3regex
(and regexpatch
). The regexpatch
package uses the LaTeX3 regular expression engine (l3regex
) to match and replace tokens set under different catcode settings so that you can patch the trickier stuff. Using \regexpatchcmd\command{<search-regex>}{<replace-regex>}{<true code>}{<false code>}
will look for the <search-regex>
in the definition of the \command
and will replace by the <replace-regex>
. The package, same as xpatch
, takes care of robust commands and commands with optional arguments. Take a look at the l3regex
and regexpatch
documentation for the syntax of the regular expressions and the patching commands.
\pretocmd
to prepend and\apptocmd
to append to a macro, and\patchcmd
to patch some part of it. Though some times patching is tricker (example), and some times it's not at all possible. So again, it depends...\if
\else
. I updated the title to make it more clear.xpatch
loaded:\xpretocmd\-{\ifmmode \mycmd \else}{}{\ERROR}\xapptocmd\-{\fi}{}{\ERROR}
. Patching with unbalanced\if...\fi
is usually not a problem. What can be a complication is ubalanced{...}
.xparse
defined commands, which is a bummer.xpatch
uses the patching mechanism frometoolbox
, but adds extra checking in case the command is robust (\DeclareRobustCommand
) or contains optional arguments. Andxparse
-defined commands can be patched, yes (they are macros like any other), but they should not, that's why the author ofxpatch
didn't implement that.