3

I am trying to automatically convert \caption{...\label{...}...} to \caption{...}\label{...}. This is what I have tried:

\documentclass{article}
\usepackage{showlabels}
\newcommand{\storelabel}[1]{\gdef\storedlabel{#1}}
\let\oldcaption\caption
\renewcommand{\caption}[1]{%
    \let\storedlabel\undefined
    \oldcaption{%
        \def\label##1{%
            \protect\storelabel{##1}%
        }%
        #1%
    }%
    \ifdefined\storedlabel%
        \label{\storedlabel}%
    \else%
    \fi%
}
\begin{document}
    \begin{table}
        \caption{no label}
        foo
    \end{table}
    \begin{table}
        \caption{\label{mylabel}label inside caption (front)}
        foo
    \end{table}
    \begin{table}
        \caption{label inside caption (back) \label{mylabel}}
        foo
    \end{table}
    \begin{table}
        \caption{label directly after caption}
        \label{mylabel}
        foo
    \end{table}
    \begin{table}
        \caption{label at end of table (I'm OK with that one)}
        foo
        \label{mylabel}
    \end{table}
\end{document}

The pdf file looks correct, too, but I get this error message (five times, once per call of \caption):

! Illegal parameter number in definition of \reserved@a.
<to be read again> 
                   1
l.20        \caption{no label}

You meant to type ## instead of #, right?
Or maybe a } was forgotten somewhere earlier, and things
are all screwed up? I'm going to assume that you meant ##.

If I replace \storelabel{##1} by \storelabel{mylabel}, the pdf file looks the same and the error message is gone. But this is not what I want, obviously.

Update: This MWE nicely shows the same error, although it is unrelated to my original goal of moving \label out of \caption and thereby moving the showlabels labels.

\documentclass{article}
\begin{document}
\begin{table}
    \caption{\def\foo[#1]{#1}}
\end{table}
\end{document}
bers
  • 5,404
  • 1
    \caption has an (moving) optional argument which gets lost here -- I fear that your approach screws up the whole labelling system –  Mar 24 '16 at 18:03
  • 2
    Instead of all this trickery, why not simply use your editor or a tool like sed to make the changes in fact? (Aside from the fun the intellectual challenge may provide, for actual documents, it may be better to subscribe to the philosophy that just because TeX can do something doesn't mean you should use TeX to do it.) – jon Mar 24 '16 at 18:05
  • 1
    The basic error (in my point of view) is that you try to redefine \caption with an argument -- it's in fact an moving argument. If the caption package` is loaded, those whole redefinitions would break again (even if they would work right now) –  Mar 24 '16 at 18:06
  • @jon the reason is a table macro from a document class that I use (iopart, to be exact), which uses \label inside \caption. @ChristianHupfer I agree for my particular application; but my redefinition of \caption could be replaced by an appropriate xpatchcmd that takes optional and moving arguments into account . Anyway, there seems to be something else I am doing wrong: look at the first short MWE, for example, where I reproduce the same error without any redefinition of \caption. It does not even have to involve \label, as the last short MEW now shows. – bers Mar 24 '16 at 18:33
  • Your update has you \def-ing `\protect? – jon Mar 24 '16 at 19:31
  • Seems so :) I was trying to reduce the number of errors (3) to only those regarding the use of #1 within \caption. I assume now, by the way, that this problem is mainly robustness (or lack thereof), and should simply be avoided as in my answer below: https://tex.stackexchange.com/questions/60353/are-commands-defined-by-newcommand-robust – bers Mar 24 '16 at 19:40
  • 1
    By the way: I looked at iopart.cls. Although its guidelines tell us to put \label in \caption, there is nothing in the class code that requires you to do so (and is 'wrong' from a LaTeX point of view -- see the LaTeX2e manual) unless you use the \Table macro. But that is a dirty hack that you should probably avoid anyway since it defines a table of 16 columns and then lets you input a table of fewer columns by relying on \\ to prematurely end each row. – jon Mar 24 '16 at 20:01

2 Answers2

1

In my point of view, it's better to catch the \label and let it store the label name to \storedlabel in \caption and then shift the real \label to the end of \@caption, after the \@makecaption has been issued.

The redefinition of \label will break cleveref's \label[...]{foo} style!

\documentclass{article}
\usepackage{showlabels}
\usepackage{xparse}
\usepackage{letltxmacro}

\usepackage{xpatch}

\usepackage{cleveref} % Just for testing!

\makeatletter

\LetLtxMacro\latex@@origlabel\label

\xpatchcmd{%
  \caption
}{%
  \refstepcounter\@captype%
}{%
  \let\storedlabel\undefined%
  \refstepcounter\@captype
  \begingroup
  \renewcommand{\label}[1]{%
     \gdef\storedlabel{##1}%  Catch the  the label but doing nothing except of storing it!
   }
   \endgroup
 }{\typeout{Successfully patched caption}}{\typeout{Failed in patching caption}}


\xpatchcmd{\@caption}{%
  \@makecaption{\csname fnum@#1\endcsname}{\ignorespaces #3}\par
  \endgroup}{%
  \@makecaption{\csname fnum@#1\endcsname}{\ignorespaces #3}\par
  \endgroup%
  \@ifundefined{storedlabel}{}{%  Transfer the label to this place
    \label@@origlabel{\storedlabel}}%
}{\typeout{Success}}{\typeout{Failure!}}
\makeatother

\begin{document}
  \begin{table}
        \caption{no label}
        foo
    \end{table}
    \begin{table}
        \caption{\label{othermylabel}label inside caption (front)}
        foo
    \end{table}
    \begin{table}
        \caption{label inside caption (back) \label{mylabel}}
        foo
    \end{table}
    \begin{table}
        \caption{label directly after caption}
        \label{mylabelfoo}
        foo
    \end{table}
    \begin{table}
      \caption{label at end of table (I'm OK with that one)}
      foo
      \label{mylabelfoobar}
    \end{table}

Now some referencing: \cref{othermylabel} and \ref{mylabel} and \ref{mylabelfoo} and \ref{mylabelfoobar}
\end{document}

enter image description here

  • I may not have been clear enough about my goal, which is to move all showlabels labels to the right margin. Your table 2 still looks like the original version using no patch at all, with the label within the table caption instead of to its right. – bers Mar 24 '16 at 19:07
  • @bers: Look into your question: There's no single word indicating that you want to move the showlabels stuff! Thanks for wasting my time :-( –  Mar 24 '16 at 19:08
  • I am sorry this could be misunderstand. I did not see any other reason to move \label out of \caption instead of showlabels because this is the first time that I have ever been seeing a difference between the two. Probably there are more cases, but I did not think of that. Anyway, your try was very helpful to me as you will see in my updated answer. – bers Mar 24 '16 at 19:23
0

This works:

\documentclass{article}
%\usepackage{caption}
\usepackage{showlabels}
\usepackage{xpatch}
\def\storelabel#1{\gdef\storedlabel{#1}}
\makeatletter
\patchcmd{\@caption}{#3}{\global\let\storedlabel\undefined\let\label\storelabel#3}{}{err}
\apptocmd{\@caption}{\ifdefined\storedlabel\label{\storedlabel}\else\fi}{}{err}
\makeatother
\begin{document}
    \listoftables
    \clearpage
    \begin{table}
        \caption{no label}
        foo
    \end{table}
    \begin{table}
        \caption{\label{mylabel}label inside caption (front)}
        foo
    \end{table}
    \begin{table}
        \caption{label inside caption (back) \label{mylabel}}
        foo
    \end{table}
    \begin{table}
        \caption{label directly after caption}
        \label{mylabel}
        foo
    \end{table}
    \begin{table}
        \caption{label at end of table (I'm OK with that one)}
        foo
        \label{mylabel}
    \end{table}
\end{document}

The key differences are

  • \def\label##1{...} -> \let\label\storelabel, avoiding the use of the argument ##1 within \caption
  • Inspired by and thanks to @ChristianHupfer, the use of xpatch to selectively patch \@caption instead of redefining \caption. It even works with the caption package and with \listoftables now.

His comment still stands: \label with an optional argument fails. But I do not need that.

bers
  • 5,404