1

I'm playing with the very intersting zref and I'm stumbling at one point, thanks to my insufficient expansion-fu, and perhaps someone may find this problem easier.

zref writes labels to the aux file with \protected@write (not \immediate) so that the labels get written there in the order the objects to which these labels refer to get shipped out (as far as I understand, if this presumption is wrong, just let me know, and disregard what follows). And I think I could use the "shipout order" of the labels to infer (at least an approximation of) the relative position of different labels on the page (which one comes before, which comes after). So I'm trying to retrieve the sequence of labels in the order which they occur in the aux file.

The information zref stores in the aux file goes under a macro named \zref@newlabel, which is defined as:

\def\ZREF@RefPrefix{Z@R}
\ZREF@Robust\edef\zref@newlabel{%
  \noexpand\@newl@bel{\ZREF@RefPrefix}%
}

(As far as my reading goes, for a current TeXLive, which is what I have here, \ZREF@Robust will be \protected).

My thought is that I could use (patch/hook/redefine) this macro to do what it is intended to do, and also feed a (L3) clist with the information I want.

However, \zref@newlabel is one of those "commands that look ahead" mentioned in ltcmdhooks documentation. Indeed, \@newl@bel receives 3 arguments, and \zref@newlabel just passes the first, the following two will be scanned ahead. As far as I can tell, that means using ltcmdhooks is not viable for the case. (Edit: As I found out later, ltcmdhooks is not viable for still another reason, the hooks are not actually available when the .aux file is read, they come only after \begin{document}).

I've tried to redefine it, but unfortunately, I can't control expansion appropriately, so the question here is how could I make such a redefinition properly.

This is a MWE which illustrates the situation, and includes my failed attempt:

\documentclass{book}

\usepackage[user]{zref}
\usepackage{hyperref}% just for \phantomsection

\usepackage{econlipsum}

\ExplSyntaxOn
\makeatletter

\clist_new:N \g__auxfile_labelseq_clist

% That's more or less what I'd like to do, but... I can't make it work.
% \cs_gset_protected:Npn \zref@newlabel #1#2 {
%   \clist_gput_right:Nn \g__zrefcheck_auxfile_labelseq_clist {#1}
%   \exp_not:N \@newl@bel {\ZREF@RefPrefix} {#1} {#2}
% }

\makeatother
\ExplSyntaxOff

\begin{document}

\chapter{Chapter 1}
\zlabel{cha:chapter-1}

\econ[1]

\begin{figure}[b]
  \centering
  \caption{Figure 1}
  \zlabel{fig:figure-1}
\end{figure}

A reference to the figure ``above''~\zref{fig:figure-1}%
\phantomsection\zlabel{fig:figure-1:reference}.

\end{document}
16
  • sounds a bit odd. Why do you need to know the order? And why can't you store the info in the label, e.g. with a new counter? Jul 8, 2021 at 20:50
  • I'm trying to get "the order in which the object to which the label refers to gets shipped out", if I just add a counter which is stepped in \zlabel (is that what you are suggesting?), I'd have the order in which the labels occur in the source. If you check the aux of the MWE, the label to fig:figure-1:reference occurs before the one fig:figure-1. This is inverted relative to the sequence they occur in the document, because of the float, of course. And it is that inversion I'm trying to capture. Is there any other way you know of?
    – gusbrs
    Jul 8, 2021 at 20:55
  • Btw, @UlrikeFischer, zref is indeed pretty cool, thanks again for the tip. I do miss the hyperref support, but I'm having lots of fun with it.
    – gusbrs
    Jul 8, 2021 at 20:57
  • Ah, complementing the first comment. I did try to hook into \ZREF@label by stepping a counter in the second argument of \protected@write (the same way \ZREF@label does it's stuff) to try to capture the "shipout sequence". And added that counter to a starred version of \zref@newprop*{shipoutseq}. However, for reasons I do not understand, all the steps of the counter would run once per page "en block", so that I missed precisely the information I wanted, which is the relative position within the page.
    – gusbrs
    Jul 8, 2021 at 21:06
  • 1
    well you are missing \makeatletter, and you are overdoing in the definitions. Also I would use a seq and not a clist. something like \cs_gset_protected:Npn \zref@newlabel #1#2 { \seq_gput_right:Nn \g__zrefcheck_auxfile_labelseq_seq {#1} \@newl@bel {Z@R} {#1} {#2}}. Jul 8, 2021 at 22:11

2 Answers 2

1

Ulrike's suggestion in the comments (How to retrieve order of zref's labels in the aux file) was good, and indeed works, but it involves hard-coding \ZREF@RefPrefix in the redefinition. While this should be indeed harmless in this case, I still put some further thought on the matter, and eventually came to the following:

\seq_new:N \g__zrefcheck_auxfile_labelseq_seq

\ZREF@Robust\edef\zref@newlabel#1{
  \noexpand\seq_gput_right:Nn \noexpand\g__zrefcheck_auxfile_labelseq_seq {#1}
  \noexpand\@newl@bel{\ZREF@RefPrefix}{#1}
}

As far as I can tell, this would behave in a really equivalent way as the original (plus the added code, of course). (It seems I am learning something in the process ;-)

However, in the meantime, I found out I'm certainly not the only one interested in the list of existing labels. At least two packages on CTAN also retrieve this information: crossreftools and listlbls. The first one constructs a special toc (with corresponding file) for the purpose, while the latter reads the .aux file directly.

listlbls was based on an answer by David Carlisle (List of all labels with hyperlinks) here on TeX.SX. I thought this was a good approach, because it does not require redefining any internals or additional infrastructure. The package, alas, is made to work with regular labels, not those of zref, and also does not really expose the information in a retrievable way (as far as I could tell). So I cooked an adapted version of it (a poor man's version at that):

\seq_new:N \g__zrefcheck_auxfile_labelseq_seq

\tl_set:Nn \g_tmpa_tl { \c_sys_jobname_str .aux }
\file_if_exist:nT { \g_tmpa_tl }
  {
    \ior_open:Nn \g_tmpa_ior { \g_tmpa_tl }
    \group_begin:
      \ior_map_inline:Nn \g_tmpa_ior
        {
          \tl_set:Nn \l_tmpa_tl { \zref@newlabel }
          \tl_set:Nx \l_tmpb_tl { \tl_item:nn {#1} { 1 } }
          \tl_if_eq:NNT \l_tmpa_tl \l_tmpb_tl
            {
              \tl_set:Nx \l_tmpa_tl { \tl_item:nn {#1} { 2 } }
              \seq_gput_right:NV \g__zrefcheck_auxfile_labelseq_seq \l_tmpa_tl
            }
        }
    \group_end:
    \ior_close:N \g_tmpa_ior
  }
1

You can patch \zref@newlabel to count labels, to add number and name of a label to an expl3-sequence, to add the number of a label as value of the property labelnumber to the label itself, and to do a \@newl@bel with different prefix for mapping from the number of a zref-label to its name:

\errorcontextlines=10000

\documentclass{article}
\usepackage[user]{zref}
\usepackage{atveryend}

\makeatletter
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
\zref@newprop{labelnumber}{0}%
\newcommand*\labelcount{0}%
\ZREF@Robust\edef\zref@newlabel{%
  \xdef\noexpand\labelcount{\noexpand\number\numexpr\noexpand\labelcount+1\relax}%
  \noexpand\expandafter\noexpand\AddElementAndCallNewlabel\noexpand\expandafter{\noexpand\labelcount}{\ZREF@RefPrefix}%
}
\makeatother
\ExplSyntaxOn
\makeatletter
\NewDocumentCommand\AddElementAndCallNewlabel {mmmm} {
   % #1 = number of label
   % #2 \@newl@bel-prefix
   % #3 name of label
   % #4 property-list of label
   \__MyStuff_AddElement:nn{#1}{#3}
   \@newl@bel{MYmapping}{#1}{#3}
   \@newl@bel{#2}{#3}{#4\labelnumber{#1}}
}
\newcommand\GetLabelNameFromLabelNumber[2]{
   % #1 = number of label
   % #2 = tokens in case no label name is mapped to number of label
   \romannumeral
   \cs_if_exist:cTF{MYmapping@#1}
                   { \exp_args:Nc \use_ii_i:nn 
                                  {MYmapping@#1}
                                  {\exp_args:No \use_ii_i:nn}{\UD@stopromannumeral} }
                   {\UD@stopromannumeral#2}
}
% Error-message in case no label name is mapped to number of label;
% like zref@refused, but for prefix MYmapping instead of \ZREF@RefPrefix=Z@R:
\NewDocumentCommand\LabelNumberNotMapped{m}{
   % #1 = term denoting number of label
  \protect\G@refundefinedtrue\@latex@warning {No~zref-label~is~mapped~to~number~`#1'~on~page~\thepage}
}
\makeatother
\seq_new:N \g__zrefcheck_auxfile_labelseq_seq
\cs_new:Nn \__MyStuff_AddElement:nn {
  \seq_gput_right:Nn \g__zrefcheck_auxfile_labelseq_seq {{#1}{#2}}
}
\NewDocumentCommand \ListOfZrefLabels {} {%
  \seq_map_function:NN \g__zrefcheck_auxfile_labelseq_seq \__MyStuff_PrintElement:n
}
\cs_new:Nn \__MyStuff_PrintElement:n {
  \__MyStuff_PrintElement:nn #1
}
\cs_new:Nn \__MyStuff_PrintElement:nn{
  #1.\nobreakspace#2,~
}
\AfterLastShipout{\xdef\labelcount{0}\cs_set:Nn \__MyStuff_AddElement:nn {}}%
\ExplSyntaxOff

\makeatletter

% For the sake of having fun define a loop which prints the names of all labels defined
% before and after the label specified as argument:

\@ifdefinable\gobbletorelax{\long\def\gobbletorelax#1\relax{}}%
\@ifdefinable\delivertorelax{\long\def\delivertorelax#1\relax{#1}}%
\newcommand\UD@CheckWhetherRelax[1]{%
  \ifcat$\detokenize\expandafter{\gobbletorelax#1\relax}$%
  \expandafter\@secondoftwo\else\expandafter\@firstofone\fi
  {%
     \ifcat$\detokenize\expandafter{\romannumeral\delivertorelax\UD@stopromannumeral#1}$%
     \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
     {\@firstoftwo}%
  }%
  {\@secondoftwo}%
}%
\newcommand\AllPreceedingAndFollowingLabels[2]{%
   \romannumeral
   \zref@ifrefundefined{#1}{\UD@stopromannumeral#2}{%
     \AllSubSequentLabelsLoop{#1}{%
        \AllSubSequentLabelsLoop{#1}%
                                {\expandafter\UD@stopromannumeral\@firstofone}%
                                {+}%
     }{-}{\textbf{#1}}%
   }%
}%
\newcommand\jointhem[4]{\AllSubSequentLabelsLoop{#1}{#2}{#3}{#4, #1}}%
\newcommand\jointhemexchanged[4]{\AllSubSequentLabelsLoop{#1}{#2}{#3}{#1, #4}}%
\newcommand\AllSubSequentLabelsLoop[4]{%
  % #1 = name of label.
  % #2 = tokens to aplly to the list of label-names gathered so far when the loop is done.
  % #3 = +/- ; marker for obtaining name of following/previous label.
  % #4 = list of label-names gathered so far.
  \expandafter\expandafter\expandafter\UD@CheckWhetherRelax
  \expandafter\expandafter\expandafter{%
    \GetLabelNameFromLabelNumber{%
      \number\numexpr\zref@extractdefault{#1}{labelnumber}{-2}#31\relax
    }{\relax}%
  }%
  {#2{#4}}%
  {%
    \ifx#3-%
    \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
    {\expandafter\expandafter\expandafter\jointhemexchanged}%
    {\expandafter\expandafter\expandafter\jointhem}%
    \expandafter\expandafter\expandafter{%
      \GetLabelNameFromLabelNumber{%
        \number\numexpr\zref@extractdefault{#1}{labelnumber}{-2}#31\relax
      }{\relax}%
    }{#2}{#3}{#4}%
  }%
}%

\makeatother

\begin{document}

\hrulefill

Some text.

\hrulefill

\ListOfZrefLabels

\hrulefill

\makeatletter

LabelUndef has number \zref@extractdefault{LabelUndef}{labelnumber}{\zref@refused{LabelUndef}\textsf{-not available-}}%

LabelA has number \zref@extractdefault{LabelA}{labelnumber}{\zref@refused{LabelA}\textsf{-not available-}}%

LabelB has number \zref@extractdefault{LabelB}{labelnumber}{\zref@refused{LabelB}\textsf{-not available-}}%

LabelC has number \zref@extractdefault{LabelC}{labelnumber}{\zref@refused{LabelC}\textsf{-not available-}}%

LabelD has number \zref@extractdefault{LabelD}{labelnumber}{\zref@refused{LabelD}\textsf{-not available-}}%

LabelE has number \zref@extractdefault{LabelE}{labelnumber}{\zref@refused{LabelE}\textsf{-not available-}}%

\hrulefill

The name of label number 0 is 
\GetLabelNameFromLabelNumber{0}{\LabelNumberNotMapped{0}\textsf{-not available-}}

The name of label number 1 is 
\GetLabelNameFromLabelNumber{1}{\LabelNumberNotMapped{1}\textsf{-not available-}}

The name of label number 2 is 
\GetLabelNameFromLabelNumber{2}{\LabelNumberNotMapped{2}\textsf{-not available-}}

The name of label number 3 is 
\GetLabelNameFromLabelNumber{3}{\LabelNumberNotMapped{3}\textsf{-not available-}}

The name of label number 4 is 
\GetLabelNameFromLabelNumber{4}{\LabelNumberNotMapped{4}\textsf{-not available-}}

The name of label number 5 is 
\GetLabelNameFromLabelNumber{5}{\LabelNumberNotMapped{5}\textsf{-not available-}}

The name of label number 6 is 
\GetLabelNameFromLabelNumber{6}{\LabelNumberNotMapped{6}\textsf{-not available-}}

The name of label number W is 
\GetLabelNameFromLabelNumber{W}{\LabelNumberNotMapped{W}\textsf{-not available-}}

The name of the label which was placed after LabelC is
\GetLabelNameFromLabelNumber{%
  \number\numexpr\zref@extractdefault{LabelC}{labelnumber}{-2}+1\relax
}{\LabelNumberNotMapped{%
    <Number of LabelC>+1
  }%
  \textsf{-not available-}%
}

The name of the label which was placed after LabelE is
\GetLabelNameFromLabelNumber{%
  \number\numexpr\zref@extractdefault{LabelE}{labelnumber}{-2}+1\relax
}{\LabelNumberNotMapped{%
    <Number of LabelE>+1
  }%
  \textsf{-not available-}%
}


\hrulefill

\AllPreceedingAndFollowingLabels{LabelUndef}{\zref@refused{LabelUndef}\textsf{-not available-}}%

\AllPreceedingAndFollowingLabels{LabelA}{\zref@refused{LabelA}\textsf{-not available-}}%

\AllPreceedingAndFollowingLabels{LabelB}{\zref@refused{LabelB}\textsf{-not available-}}%

\AllPreceedingAndFollowingLabels{LabelC}{\zref@refused{LabelC}\textsf{-not available-}}%

\AllPreceedingAndFollowingLabels{LabelD}{\zref@refused{LabelD}\textsf{-not available-}}%

\AllPreceedingAndFollowingLabels{LabelE}{\zref@refused{LabelE}\textsf{-not available-}}%

\hrulefill

\zref@labelbylist{LabelA}{main}%

\zref@labelbylist{LabelB}{main}%

\zref@labelbylist{LabelC}{main}%

\zref@labelbylist{LabelD}{main}%

\zref@labelbylist{LabelE}{main}%

\end{document}

enter image description here

1
  • Cool! Thank you! So you got hooked on the problem too. ;-) On my side, I'm post processing the sequence to a proplist, to be able to query it, but this approach of yours is nice too. I did go with reading the .aux file though, since not having to tamper with the internals is a big plus here, in my view.
    – gusbrs
    Jul 13, 2021 at 2:40

You must log in to answer this question.

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