6

I want to make sure that the references are in in the order of the appearance of floats. I.e. I do not want to change the order of the floats but I want to avoid the situation:

... shown in figure 2. ...displayed in figure 1.

How can I issue a warning if a figure or table is referenced for the first time that has a higher number than than the following? Of course one would need to veto the case

...displayed in figure 1. ... shown in figure 2. ...displayed in figure 1.

  • Are you using hyperref? – Werner Aug 27 '13 at 17:01
  • @Werner: Yes, I'm using hyperref. – Somebody Aug 28 '13 at 11:25
11
+100

I'd suggest to check the number of the figure you're referencing at every reference, see if it's the highest yet and if not, check if its been referenced before. Otherwise: throw an error.

Consider the following document:

\documentclass{scrartcl}

\usepackage{hyperref}
\usepackage[demo]{graphicx}

\begin{document}

\section{Figures}\label{sec}

\begin{figure}
\includegraphics[width=4cm,height=2cm]{}
\caption{first figure}
\label{first}
\end{figure}

\autoref{first}
\autoref{second}\autoref{sec}\autoref{first}

\begin{figure}
\includegraphics[width=2cm,height=4cm]{}
\caption{second figure}
\label{second}
\end{figure}

\end{document}

This results in

compilation of code

If I read your question correctly, this should be ok but removing the first \autoref{first} should lead to an error.

To achieve this, note how hyperref treats labels: For every label, a command \r@<labelname> is defined consisting of 5 bits of information, in this case (for the label first):

> \r@first=macro:
->{1}{1}{first figure}{figure.1}{}.

Important for us: the fourth group consists of <counter name>.<counter value>. This can be extracted by defining, e.g.,

\def\parse#1#2#3#4#5{\@parse#4\@nil}
\def\@parse#1.#2\@nil{\def\current@type{#1}\def\current@number{#2}}

Now calling

\expandafter\parse\r@first

saves figure in \current@type and 1 in \current@number. Note, however, that \r@<label> is not defined in the first LaTeX run, as its defined by \newlabel which lives in the aux file. Therefore, any use of it should always be wrapped in a test to see if the command is defined!

Next, we introduce a counter to keep track of figures that have been cited:

\newcounter{highestyet}
\setcounter{highestyet}{0}

Every call of \autoref should check the current value against this value. Let's collect everything we want to do at every \autoref in some command with one argument for the name of the label we're referencing:

First we should check if \r@#1 is defined. If yes, we use our \parse macro to extract the counter name and value, otherwise we set them to 0 to avoid errors:

  \ifcsname r@#1\endcsname%
     \expandafter\expandafter\expandafter\parse%
         \csname r@#1\endcsname%
  \else\def\current@type{0}\def\current@number{0}\fi%

The etoolbox package provides the test \ifdefstring that we can use to see if the counter name is figure. In that case, we can use \ifnumgreater to compare the current value to the highest seen yet. If \current@number is greater than highestyet we replace it. If it is smaller, we check if this figure has been cited already. The idea is to have a boolean for each figure number that is set to true once it's been referenced. For this we can use the \providebool command (again from etoolbox) which stops us from having to worry whether a boolean is (un)defined before checking its value. In case it's false, we give a warning. Otherwise, nothing happens:

  \ifdefstring{\current@type}{figure}{%
    \ifnumgreater{\current@number}{\value{highestyet}}
      {\setcounter{highestyet}{\current@number}}
      {\ifnumgreater{\value{highestyet}}{\current@number}
        {\expandafter\providebool{figure@\current@number @isrefd}%
         \expandafter\ifbool{figure@\current@number @isrefd}
            {}{Warning!}}{}}%
    \expandafter\providebool{figure@\current@number @isrefd}%
    \expandafter\booltrue{figure@\current@number @isrefd}%
  }{}%

Ok, that's it! If we call this macro \stuff, all we need to do is to append \stuff to \autoref (or the reference command of your choice ... note, however, that the structure of hyperref's \newlabel command was explicitly used in the parsing macro, if you use any other labeling packages, you might want to check that they don't redefine that!).

However, patching \autoref directly is dificult. Taking a closer look (\show and \xshowcmd from xpatch are your friend here ...), \autoref actually calls \HyRef@autoref, which is the command that actually works with the argument, the label name, that we are interested in (note that the label is stored in #2!):

> \autoref =macro:
->\leavevmode \@ifstar {\HyRef@autoref \@gobbletwo }{\HyRef@autoref \hyper@@link }.
> \HyRef@autoref=macro:
#1#2->\begingroup \Hy@safe@activestrue \expandafter \HyRef@autosetref \csname r@#2\endcsname {#2}{#1}\endgroup .

We can simply patch this, using \xapptocmd from the xpatch package:

\xapptocmd{\HyRef@autoref}{\stuff{#2}}{}{}

In fact, in this case the \apptocmd command from etoolbox also does the trick, in case you're working on an older system with no l3 (and hence no xpatch) support.

The same holds for any other commands that you want to respect this behaviour: for example, when using the cleveref package (which should be loaded after hyperref!), one can check that all variants of \cref (e.g. \Cref) end up calling \@cref, which takes the command called and the name of the label as arguments and stores the latter again in #2. So, as above, one should add

\xapptocmd{\@cref}{\stuff{#2}}{}{}

to the file (the same remark as above applies to the x). As they use the same counters, the commands will also cooperate, i.e. a previous reference using \autocite will also be detected by a following \cref, etc.

Now, the full document

\documentclass{scrartcl}

\usepackage[demo]{graphicx}
\usepackage{xpatch}
\usepackage{hyperref}

\makeatletter
\def\parse#1#2#3#4#5{\@parse#4\@nil}
\def\@parse#1.#2\@nil{\def\current@type{#1}\def\current@number{#2}}

\newcounter{highestyet}
\setcounter{highestyet}{0}

\def\stuff#1{%
  \ifcsname r@#1\endcsname%
     \expandafter\expandafter\expandafter\parse%
         \csname r@#1\endcsname%
  \else\def\current@type{0}\def\current@number{0}\fi%
  \ifdefstring{\current@type}{figure}{%
    \ifnumgreater{\current@number}{\value{highestyet}}
      {\setcounter{highestyet}{\current@number}}
      {\ifnumgreater{\value{highestyet}}{\current@number}
        {\expandafter\providebool{figure@\current@number @isrefd}%
         \expandafter\ifbool{figure@\current@number @isrefd}
            {}{Warning!}}{}}%
    \expandafter\providebool{figure@\current@number @isrefd}%
    \expandafter\booltrue{figure@\current@number @isrefd}%
  }{}%
}

\xapptocmd{\HyRef@autoref}{\stuff{#2}}{}{}
\makeatother

\begin{document}

\section{Figures}\label{sec}

\begin{figure}
\includegraphics[width=4cm,height=2cm]{}
\caption{first figure}
\label{first}
\end{figure}

%\autoref{first}
\autoref{second}\autoref{sec}\autoref{first}

\begin{figure}
\includegraphics[width=2cm,height=4cm]{}
\caption{second figure}
\label{second}
\end{figure}

\end{document}

compiles to

compilation of second document

If you remove the comment in the line %\autoref{first}, the result is the same as above (i.e. the warning disappears).

In practice you may want to replace "Warning!" by something like

\GenericWarning{}{Warning! Figure \current@number\space referenced after
     figure \thehighestyet\space on page \thepage\space without being
     referenced before}%

to produce

Warning! Figure 1 referenced after figure 2 on page 1 without being referenced 
before on input line 45.

(see also Expansion of commands in errmessage)

As a final note: of course this approach works for more counters than figure. For example, to also include tables, one needs to distinguish the highestyet counters, i.e. call them something like highestyet@figure and highestyet@table and add an

\ifdefstring{\current@type}{table}

check into the false part of \ifdefstring{\current@type}{figure} in the macro \stuff. This should include the same code as above with the appropriate counters at the appropriate places.

| improve this answer | |
  • Great! How to make it work with cref (and Cref) from cleveref for figures and tables? – Somebody Aug 30 '13 at 13:33
  • 1
    @Somebody see edited post. – Jonathan Aug 30 '13 at 17:15
  • Hi, may I ask why \autoref{second}\autoref{first} throws a warning, but \autoref{second} alone does not? It seems the warning is thrown by \autoref{first} if 1. first is referenced for the first time, and 2. a higher one has been referenced before. But this means \autoref{second} does not throw a warning if first is forgotten and never referenced - is that correct? Is this intended? – bers Mar 20 '17 at 16:19
  • Next question, can this be used with \ref? – bers Mar 20 '17 at 16:23
  • @bers yes and yes ;-) Sometimes things are numbered and not referenced; to get this behaviour, just add a test checking if the difference of current@number and highestyet is more than 1, i.e. if you skipped someone. To use it with \ref just adapt the \parse macro and the name of the command you stick \stuff to... – Jonathan Mar 20 '17 at 16:46
0

This is just an updated version of Jonathans code, it contains a small bugfix for users of the caption package and is extended for tables.

This also serves as an example on how to use this feature with the fancyref package.

\documentclass{scrartcl}

\usepackage[demo]{graphicx}
\usepackage{xpatch}
\usepackage{fancyref}
\usepackage{hyperref}
\usepackage{caption}


\makeatletter
\def\parse#1#2#3#4#5{\@parse#4\@nil}

%%Begin fix for caption package: %%
\def\@parse#1.#2\@nil{
  \def\current@type{#1}
  \ifdefstring{\current@type}{figure}{\@@parse#2\@nil}
    {\ifdefstring{\current@type}{table}{\@@parse#2\@nil}}
    {}
  }
\def\@@parse#1.#2\@nil{\def\current@number{#2}}
%%End fix for caption package: %%
%If you ar not using \usepackage{caption} then simply use this line instead:
%\def\@parse#1.#2\@nil{\def\current@type{#1}\def\current@number{#2}}


\newcounter{highestfigureyet}
\setcounter{highestfigureyet}{0}

\newcounter{highesttableyet}
\setcounter{highesttableyet}{0}


\def\checkfloatreforder#1{%
  \ifcsname r@#1\endcsname%
     \expandafter\expandafter\expandafter\parse%
         \csname r@#1\endcsname%      
  \else\def\current@type{0}\def\current@number{0}\fi%
  \ifdefstring{\current@type}{figure}{%
    \ifnumgreater{\current@number}{\value{highestfigureyet}}
      {\setcounter{highestfigureyet}{\current@number}}
      {\ifnumgreater{\value{highestfigureyet}}{\current@number}
        {\expandafter\providebool{figure@\current@number @isrefd}%
         \expandafter\ifbool{figure@\current@number @isrefd}
            {}{\GenericWarning{}{Warning! Figure \current@number\space referenced after
     figure \thehighestfigureyet\space on page \thepage\space without being referenced before}}}{}}%
    \expandafter\providebool{figure@\current@number @isrefd}%
    \expandafter\booltrue{figure@\current@number @isrefd}%
  }{%
    \ifdefstring{\current@type}{table}{%
      \ifnumgreater{\current@number}{\value{highesttableyet}}
        {\setcounter{highesttableyet}{\current@number}}
        {\ifnumgreater{\value{highesttableyet}}{\current@number}
          {\expandafter\providebool{table@\current@number @isrefd}%
           \expandafter\ifbool{table@\current@number @isrefd}
              {}{\GenericWarning{}{Warning! Table \current@number\space referenced after
     table \thehighesttableyet\space on page \thepage\space without being referenced before}}}{}}%
      \expandafter\providebool{table@\current@number @isrefd}%
      \expandafter\booltrue{table@\current@number @isrefd}%
      }{}  
  }%
}

\let\old@fref\fref
\renewcommand*{\fref}[1]{\old@fref{#1}
\checkfloatreforder{#1}}

\makeatother


\begin{document}

\section{Figures}\label{sec:figures}

\begin{figure}
\includegraphics[width=4cm,height=2cm]{}
\caption{first figure}
\label{fig:first}
\end{figure}

\fref{fig:second} in \fref{sec:figures} and \fref{fig:first}


\begin{figure}
\includegraphics[width=2cm,height=4cm]{}
\caption{second figure}
\label{fig:second}
\end{figure}

\section{Tables}\label{sec:tables}

\begin{table}
\includegraphics[width=4cm,height=2cm]{}
\caption{first table}
\label{tab:first}
\end{table}

\fref{tab:second} in \fref{sec:tables} and \fref{tab:first}


\begin{table}
\includegraphics[width=2cm,height=4cm]{}
\caption{second table}
\label{tab:second}
\end{table}

\end{document}

Reason for all this is, that the caption package changes the string you have to parse from:

  • {1}{1}{first figure\relax }{figure.1}{} changes to

    {1}{1}{first figure\relax}{figure.caption.1}{}

    this also applies to tables

  • List item labels of sections remain unchanged:

    {1}{1}{Figures}{section.1}{} remains

    {1}{1}{Figures}{section.1}{}

| 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.