0

Everything works fine if I do not use \PassOptionsToPackage{french}{babel}.

My document class abntex2 already loads babel, then, to load the french language I need to use \PassOptionsToPackage{french}{babel} before my document class.

% \PassOptionsToPackage{french}{babel}

\documentclass[english,12pt,a4paper,twoside]{abntex2}
\usepackage{caption,xpatch,listings}

\makeatletter
\tracingpatches
\newcommand{\specialcodelistingcontentsline}[1]{\contentsline{lstlisting}}

\newenvironment{code}{
  \captionsetup{type=listing}
  \xshowcmd\addcontentsline

  \xpatchcmd{\addcontentsline}
  {\contentsline}
  {\specialcodelistingcontentsline}
  {}
  {\FAILEDPATCHSPECIALCONTENTSLINE}
}{}
\makeatother
\begin{document}

\begin{code}
Hi.
\end{code}

\end{document}

Here on this log, we can see the patch was successful:

> \addcontentsline=macro:
#1#2#3->\begingroup \let \label \@gobble \ifx \@currentHref \@empty \Hy@Warning
 {No destination for bookmark of \string \addcontentsline ,\MessageBreak destin
ation is added}\phantomsection \fi \expandafter \ifx \csname toclevel@#2\endcsn
ame \relax \begingroup \def \Hy@tempa {#1}\ifx \Hy@tempa \Hy@bookmarkstype \Hy@
WarningNoLine {bookmark level for unknown #2 defaults to 0}\else \Hy@Info {book
mark level for unknown #2 defaults to 0}\fi \endgroup \expandafter \gdef \csnam
e toclevel@#2\endcsname {0}\fi \edef \Hy@toclevel {\csname toclevel@#2\endcsnam
e }\Hy@writebookmark {\csname the#2\endcsname }{#3}{\@currentHref }{\Hy@tocleve
l }{#1}\ifHy@verbose \begingroup \def \Hy@tempa {#3}\@onelevel@sanitize \Hy@tem
pa \let \temp@online \on@line \let \on@line \@empty \Hy@Info {bookmark\temp@onl
ine :\MessageBreak thecounter {\csname the#2\endcsname }\MessageBreak text {\Hy
@tempa }\MessageBreak reference {\@currentHref }\MessageBreak toclevel {\Hy@toc
level }\MessageBreak type {#1}}\endgroup \fi \addtocontents {#1}{\protect \cont
entsline {#2}{#3}{\thepage }{\@currentHref }}\endgroup .
<recently read> \addcontentsline

l.23 \begin{code}

?
[debug] tracing \patchcmd on input line 23
[debug] analyzing '\addcontentsline'
[debug] ++ control sequence is defined
[debug] ++ control sequence is a macro
[debug] ++ macro can be retokenized cleanly
[debug] ++ search pattern found in replacement text
[debug] ++ patching possible
[debug] == retokenizing macro now

However, after uncommenting the \PassOptionsToPackage{french}{babel} on the minimal example, results in a failed patch:

> \addcontentsline=macro:
#1#2#3->\begingroup \let \label \@gobble \ifx \@currentHref \@empty \Hy@Warning
 {No destination for bookmark of \string \addcontentsline ,\MessageBreak destin
ation is added}\phantomsection \fi \expandafter \ifx \csname toclevel@#2\endcsn
ame \relax \begingroup \def \Hy@tempa {#1}\ifx \Hy@tempa \Hy@bookmarkstype \Hy@
WarningNoLine {bookmark level for unknown #2 defaults to 0}\else \Hy@Info {book
mark level for unknown #2 defaults to 0}\fi \endgroup \expandafter \gdef \csnam
e toclevel@#2\endcsname {0}\fi \edef \Hy@toclevel {\csname toclevel@#2\endcsnam
e }\Hy@writebookmark {\csname the#2\endcsname }{#3}{\@currentHref }{\Hy@tocleve
l }{#1}\ifHy@verbose \begingroup \def \Hy@tempa {#3}\@onelevel@sanitize \Hy@tem
pa \let \temp@online \on@line \let \on@line \@empty \Hy@Info {bookmark\temp@onl
ine :\MessageBreak thecounter {\csname the#2\endcsname }\MessageBreak text {\Hy
@tempa }\MessageBreak reference {\@currentHref }\MessageBreak toclevel {\Hy@toc
level }\MessageBreak type {#1}}\endgroup \fi \addtocontents {#1}{\protect \cont
entsline {#2}{#3}{\thepage }{\@currentHref }}\endgroup .
<recently read> \addcontentsline

l.23 \begin{code}

?
[debug] tracing \patchcmd on input line 23
[debug] analyzing '\addcontentsline'
[debug] ++ control sequence is defined
[debug] ++ control sequence is a macro
[debug] -- macro cannot be retokenized cleanly
[debug] -> the macro may have been defined under a category
[debug]    code regime different from the current one
[debug] -> the replacement text may contain special control
[debug]    sequence tokens formed with \csname...\endcsname;
[debug] -> the replacement text may contain carriage return,
[debug]    newline, or similar characters
! Undefined control sequence.
<argument> \FAILEDPATCHSPECIALCONTENTSLINE

Update

As pointed out by @Phelype Oleinik, changing the catcode of : temporarily to 12 fixed the issue. However, I cannot manage to restore the catcode correctly.

The following does not work because I used \begingroup to restore the catcode, but it also restores/nullifies the patch I just did to \addcontentlines

\PassOptionsToPackage{french}{babel}
\documentclass[english,12pt,a4paper,twoside]{abntex2}
\usepackage{caption,xpatch,listings}

\makeatletter
\tracingpatches
\newcommand{\specialcodelistingcontentsline}[1]{\contentsline{lstlisting}}

\newenvironment{code}{
  \captionsetup{type=listing}
  \xshowcmd\addcontentsline

  \begingroup
    \catcode`\:=12
    \xpatchcmd{\addcontentsline}
    {\contentsline}
    {\specialcodelistingcontentsline}
    {}
    {\FAILEDPATCHSPECIALCONTENTSLINE}
  \endgroup
  % Here the catcode is restored, however, my patch is also undone
}{}
\makeatother
\begin{document}
\begin{code}
Hi.
\end{code}
\end{document}

Related:

  1. Temporarily changing catcode of %
  2. Active character and delimited argument
user
  • 4,745
  • The definition of \addcontentsline contains a : which, at the time the macro was defined, was a catcode-12 token. The patch fails because babel-french makes : an active character, so when rescanning the definition etoolbox detects that the patching is not possible there. Temporarily setting \catcode`\:=12 should work... – Phelype Oleinik Nov 04 '19 at 03:54
  • @PhelypeOleinik, Thanks, it worked! – user Nov 04 '19 at 04:06
  • @PhelypeOleinik, actually, my answer does not work because I need to restore the catcode after changing it, if and only if it was not changed to active. – user Nov 04 '19 at 04:59

1 Answers1

0

After finding this other answer Save and restore the makeatletter/makeatother state, I managed to set and restore the colon catcode with this:

\PassOptionsToPackage{french}{babel}
\documentclass[english,12pt,a4paper,twoside]{abntex2}
\usepackage{caption,xpatch,listings}
\makeatletter
\tracingpatches
\newcommand{\specialcodelistingcontentsline}[1]{\contentsline{lstlisting}}

\newenvironment{code}{
  \captionsetup{type=listing}
  \def\makesafecoloncatcoderestore##1{%
      \edef##1{\catcode`:=\the\catcode`:\relax}%
  }
  \makesafecoloncatcoderestore\restorecodecoloncatcode
  \catcode`\:=12
  \xpatchcmd{\addcontentsline}
  {\contentsline}
  {\specialcodelistingcontentsline}
  {\message{^^J^^JCould YES patch the contentsline environment!^^J^^J^^J^^J^^J^^J}}
  {\FAILEDPATCHSPECIALCONTENTSLINE}
  % \xshowcmd\addcontentsline
  \restorecodecoloncatcode
}{}

\makeatother
\begin{document}
\begin{code}
Hi.
\end{code}
\end{document}

Related:

  1. Temporarily changing catcode of %
  2. Active character and delimited argument
  3. \xpatch fails if \PassOptionsToPackage{french}{babel} is used
user
  • 4,745
  • Patching a command in the document body where catcodes are often unclear (and again and again at every occurrence of your environment) looks like rather bad coding. Why don't you create a dedicated command in the preamble? – Ulrike Fischer Nov 04 '19 at 07:20
  • @UlrikeFischer, I am patching the command in the preamble. And I am not doing renew command \addcontentsline because I do not know how to create a new one 100% equivalent. – user Nov 04 '19 at 16:58
  • No, you are patching the command when you execute the code environment and this happens in the document. https://en.wikipedia.org/wiki/XY_problem – Ulrike Fischer Nov 04 '19 at 17:09
  • You are right. I forgot to think about the runtime of the code. Indeed, I am changing catcodes inside the body of the document. But I do so in a very brief moment, as I immediately change it back, after patching the command for my code environment. – user Nov 04 '19 at 19:27