10

Two days I am looking for a solution but I have no more idea. This is my problem.

I'm trying to create a macro to automatically define new other macros to write references in a document. I want to create \createrefmacro which take one parameter (for example Figure). The \createrefmacro will create a new macro called \refFigure which will take one argument (the label of the reference) and an optional argument (I need an optional argument).

This is an MWE which should reproduce my problem (my original macro contains more details and the MWE may seem stupid!).

\documentclass{article}
  \makeatletter
  \def\createrefmacro#1{%
    \edef\macro{\csname ref#1\endcsname}%
    \edef\macro@i{\csname ref#1@i\endcsname}%
    \expandafter\gdef\macro{%
      \expandafter\@testopt\macro@i{}%
    }%
    \expandafter\gdef\macro@i[##1]##2{#1~\ref{##2}}%
  }
  \makeatother
  \createrefmacro{Figure}
  \createrefmacro{Table}
\begin{document}
  \begin{table}[htp]
    \caption{First table}\label{tab1}
  \end{table}
  \begin{figure}[htp]
    \caption{First figure}\label{fig1}
  \end{figure}
  \begin{figure}[htp]
    \caption{Second figure}\label{fig2}
  \end{figure}
  \refFigure{fig1}\par
  \refTable{tab1}\par
  \refFigure{fig2}\par
\end{document}

With \createrefmacro{Figure} and \createrefmacro{Table}, I create the \refFigure and the \refTable macros. I write a small document with only 2 figures and 1 table then I try to insert corresponding references in the document. However, the result is not what I expect as can be seen below

enter image description here

I tried few tricks with no success:

  1. Replace \gdef with \xdef:
    • In \macro but the compilation produce an error
    • In \macro@i but the problem still happens
  2. Use (plenty of) \expandafter to force the expansion of #1 in \macro@i but I don't really know how and where to use it in this particular case.

I know the existence of packages like varioref and the \labelformat macro which should do the job in a better way. However, I need a solution which does not use external packages because of submissions as a scientific article (and I'm also very curious of the solution of this tricky problem!).

2 Answers 2

13

My first bet would be on

\newcommand\createrefmacro[1]{%
  \expandafter\newcommand\csname ref#1\endcsname[2][]{#1~\ref{##2}}}

If I load xpatch for looking at the real meaning of \refFigure after

\createrefmacro{Figure}

I get the following answer:

> \\refFigure=\long macro:
[#1]#2->Figure~\ref {#2}.

that is exactly what you wish to obtain with the (non working) code you proposed. A working version would be

\makeatletter
\def\createrefmacro#1{%
  \expandafter\def\csname ref#1\expandafter\endcsname\expandafter{%
    \expandafter\@testopt\csname ref#1@i\endcsname{}%
  }%
  \expandafter\def\csname ref#1@i\endcsname[##1]##2{#1~\ref{##2}}%
}
\makeatother

In this way \createrefmacro would first execute

 \expandafter\def\csname refFigure\expandafter\endcsname\expandafter{\expandafter\@testopt\csname refFigure@i\endcsname{}}

where the \expandafter at the end of the first \csname...\endcsname pair would trigger the formation of the token \refFigure@i and so this would bring you

\def\refFigure{\@testopt\refFigure@i{}}

Then

\expandafter\def\csname refFigure@i\endcsname[#1]#2{Figure~\ref{#2}}

would be equivalent to

\def\refFigure@i[#1]#2{Figure~\ref{#2}}

But the first solution is obviously cleaner.

Analysis

Let's see where your code fails:

\def\createrefmacro#1{%
  \edef\macro{\csname ref#1\endcsname}%
  \edef\macro@i{\csname ref#1@i\endcsname}%
  \expandafter\gdef\macro{%
    \expandafter\@testopt\macro@i{}%
  }%
  \expandafter\gdef\macro@i[##1]##2{#1~\ref{##2}}%
}

First of all, \gdef is useless, as probably you won't issue \createrefmacro inside a group, but it does no harm. Assume that you give

\createrefmacro{Figure}

Then

\edef\macro{\csname refFigure\endcsname}

will be equivalent to

\def\macro{\refFigure}

(because \refFigure is still undefined, so it will be equivalent to \relax and stop expansion). Similarly

\edef\macro@i{\csname refFigure@i\endcsname}

will be the same as

\def\macro@i{\refFigure@i}

Now TeX will see

\expandafter\gdef\macro{\expandafter\@testopt\macro@i{}}

that would become

\gdef\refFigure{\expandafter\@testopt\macro@i{}}

and here is the main problem: the \expandafter will not come into action. So, with a subsequent \createrefmacro{Table}, the meaning of \macro@i would be \refTable@i! There is a possibility with \expandafter, let's look at it:

\expandafter\expandafter\expandafter\gdef\expandafter\macro\expandafter{%
  \expandafter\@testopt\macro@i{}}

Can you see it? The first \expandafter will expand the third, thus the fourth, the fifth and the sixth, leaving

\expandafter\gdef\macro{\@testopt\refFigure@i{}}

which eventually becomes

\gdef\refFigure{\@testopt\refFigure@i{}}

as desired. The last job would become

\expandafter\gdef\macro@i[#1]#2{Figure~\ref{#2}}

(because #1 is replaced by Figure and ## becomes #), so this would do the correct

\gdef\refFigure@i[#1]#2{Figure~\ref{#2}}

Of course, what #1 should do remains unexplained in your code (but you did say this). However, using directly \csname...\endcsname is much handier and doesn't require \macro and \macro@i.

2
  • Thank for you this fast answer. As I answer to David Carlisle, I will first try his cleaner solution. However, if I don't success, your answer will be very helpful to continue in finding a complete solution for my problem (and at least, understand a bit more TeX/LaTeX!). Commented Mar 31, 2012 at 23:18
  • 1
    I just read your second part (the analysis) and it is very helpful to understand my mistakes (very pedagogical). I think I still need to learn how to use \expandafter, this powerful and dangerous macro. Commented Apr 1, 2012 at 9:06
10

I'm not quite sure what you want to do with the default argument but this seems to work for your MWE

\documentclass{article}

  \def\createrefmacro#1{%
    \expandafter\newcommand\csname ref#1\endcsname[2][default]{#1~\ref{##2}}}


  \createrefmacro{Figure}
  \createrefmacro{Table}

\begin{document}
  \begin{table}[htp]
    \caption{First table}\label{tab1}
  \end{table}
  \begin{figure}[htp]
    \caption{First figure}\label{fig1}
  \end{figure}
  \begin{figure}[htp]
    \caption{Second figure}\label{fig2}
  \end{figure}
  \refFigure{fig1}\par
  \refTable{tab1}\par
  \refFigure{fig2}\par
\end{document}
1
  • Thank you for this fast answer. I first have macros defined like you propose but I changed them because of another problem. However, I made so many modifications since this old version... I will now try to clean my code and see if your solution can do the job. Commented Mar 31, 2012 at 23:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .