Custom definition of \ref causes problem inside argument of \caption

Custom definition of \ref causes problem inside argument of \caption

I am trying to implement a custom version of the \ref command. I found out that in order for me being able to redefine this command I need to do so via \AtBeginDocument as otherwise my change seemed to get overwritten by something else.

It works now as expected except when using \ref inside a \caption which will produce the following error:

Argument of \@caption has an extra }. \caption{Test here \ref{sec:Section}}
Paragraph ended before \@caption was complete. \caption{Test here \ref{sec:Section}}

Here is a MWE reproducing the problem:

\documentclass{article} 
\usepackage{xstring}

\AtBeginDocument{%
    \let\refCopy\ref
    \renewcommand{\ref}[1]{%
        \IfBeginWith{#1}{eq:}{
            (\refCopy{#1})
        }{
        \refCopy{#1}
    }
}
}

\setcounter{errorcontextlines}{999}

\begin{document}
    \section{Section}
    \label{sec:Section}

    \begin{figure}
        \caption{Test here \ref{sec:Section}}
    \end{figure}
\end{document}

If I remove my custom implementation of \ref the document compiles just fine.

What is causing this problem and how can I avoid it?

답변1

Your (re)definition of \ref makes it a so-called "fragile" command. This matters if \ref is used in the argument of a "moving command" -- such as \caption. You need to change

\caption{Test here \ref{sec:Section}}

to

\caption{Test here \protect\ref{sec:Section}}

Moreover, you need to make sure that no spurious whitespace is introduced before or after the cross-referencing call-out. I suggest you change

    \renewcommand{\ref}[1]{%
        \IfBeginWith{#1}{eq:}{
            (\refCopy{#1})
        }{
        \refCopy{#1}
    }
}

to

    \renewcommand{\ref}[1]{%
        \IfBeginWith{#1}{eq:}{%
            (\refCopy{#1})%
        }{%
        \refCopy{#1}%
    }%
}%

Can you spot the six new instances of %?


Addendum: As @AlanMunn has pointed out in a comment, the \protect directives aren't needed if one loads the etoolbox package and replaces \renewcommand{\ref}... with \renewrobustcmd{\ref}....

Formatting the code as

\renewcommand{\ref}[1]{%
  \IfBeginWith{#1}{eq:}{%
    (\refCopy{#1})%
  }{%
    \refCopy{#1}%
  }%
}%

may make it more readable, as the levels are more evident.

관련 정보