3

Consider the following example: we have a command \mycmd, defined in some pacakge as:

\DeclareRobustCommand{\mycmd}{%
    <original source code>
}

we want to patch this command, in the sense of pre- and appending code. These may contain anything, including \if \else constructs wrapping the original code. The direct way to do this would be of course to originally defining the command this way. If the command already exists we could patch it by deleting it first using etoolbox's \undef:

\undef{\mycmd}
\DeclareRobustCommand{\mycmd}{%
    <pre code>
    <original source code>
    <post code>
}

My question:

It seems that etoolbox's facilities (\appto, \apptocmd, etc.) do not work in all circumstances.

Is there any way to achieve this patching without having to literally copy paste the original source code (but with 100% the same behaviour in all circumstances)?

This is sort of a follow up question to Safe way of redefining \- only for math mode

  • 4
    It depends a bit on what you want to patch. Most of the time you can use \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... – Phelype Oleinik Feb 21 at 13:29
  • @PhelypeOleinik This example is out of the scope of this question. I am restricting it clearly to pre- and appending code. Although that code should, as mentioned, be allowed to contain anything including wrapping the original source code with \if \else. I updated the title to make it more clear. – Hyperplane Feb 21 at 13:38
  • That's why it was an example ;-) Looking at your linked question, this should do the job, with 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 {...}. – Phelype Oleinik Feb 21 at 13:44
  • @PhelypeOleinik oh nice, I wasn't aware of this package. Although it says in the documentation that it isn't compatible with xparse defined commands, which is a bummer. – Hyperplane Feb 21 at 13:51
  • xpatch uses the patching mechanism from etoolbox, but adds extra checking in case the command is robust (\DeclareRobustCommand) or contains optional arguments. And xparse-defined commands can be patched, yes (they are macros like any other), but they should not, that's why the author of xpatch didn't implement that. – Phelype Oleinik Feb 21 at 13:53
5

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.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.