1

I use the "local labels" hack from this answer to automatically prefix labels in order to be able to reuse the same labels in different places of the same document.


Background: Why?

There are two documents (doc1.tex & doc2.tex) whose content is outside of my control. I need to create a third document (main.tex) that includes the contents of the two other documents. Identical labels in doc1.tex and doc2.tex lead to duplicated labels in main.tex.

Full (non-working) example (I'm loading cleveref because I use it in my actual code – not sure if it is relevant here):

% file main.tex:
\documentclass{scrartcl}
\usepackage{hyperref}
\usepackage{cleveref}
% "doc1":
\begin{filecontents}{doc1.tex}
\documentclass{scrartcl}
\begin{document}
\input{doc1_body}
\end{document}
\end{filecontents}
\begin{filecontents}{doc1_body.tex}
\section{About Foo} \label{sec:foo}
\ref{sec:foo}
\end{filecontents}

% "doc2" (structurally identical to "doc1"):
\begin{filecontents}{doc2.tex}
\documentclass{scrartcl}
\begin{document}
\input{doc2_body}
\end{document}
\end{filecontents}
\begin{filecontents}{doc2_body.tex}
\section{About Foo} \label{sec:foo} % same label as in doc2_body.tex
\ref{sec:foo}
\end{filecontents}

\begin{document}
% cannot modify doc1_body or doc1_body!
\input{doc1_body}
\input{doc2_body} % Label `sec:foo' multiply defined.
\end{document}

The "local labels" hack automatically prefixes labels in order to be able to reuse the same labels in different places of the same document.

MWE, using the files generated by the filecontents environments in the first example:

\documentclass{scrartcl}
\usepackage{hyperref}
\usepackage{cleveref}

%%% Automatically prefix labels %%%
% https://tex.stackexchange.com/a/82115/37118
\makeatletter
\AtBeginDocument{%
\let\origref\ref
\let\origlabel\label
\newcommand\locallabels[1]{%
  \renewcommand\label[1]{\origlabel{#1##1}}%
  \renewcommand\ref[1]{\origref{#1##1}}%
}}
\makeatother

\begin{document}
\locallabels{part1:}
\input{doc1_body} % contains \ref{sec:foo}

\locallabels{part2:}
\input{doc2_body}  % contains \ref{sec:foo}
\end{document}

This works great for \ref: \ref{sec:foo} in doc1_body.tex prints "1" whereas it prints "2" in doc2_body.tex.

Problem: How to achieve the same for the starred version \ref* as well, such that \ref* can be used in section commands, e.g.: \section{More on \ref*{sec:foo}}?

The patched version of \ref* should work as expected when using hyperref: Print the same output as \ref but without the hyperlink.


What I've tried:

  • Initially, I tried to naively add \renewcommand\ref*[1]{\origref*{#1##1}}, but of course patching commands with starred versions is not that simple …
  • Then, I tried to follow the advice in this answer and adapt it to my problem. (I'm aware that there are various ways to redefine commands with starred variants, but the linked answer seems to be very close to my use case).

Building on this, I came up with the following solution:

\makeatletter
\AtBeginDocument{%
\let\origref\ref
\let\origlabel\label
\newcommand*{\newrefstar}[1]{}
\newcommand*{\newref}[1]{}
\newcommand\locallabels[1]{%
  \renewcommand\label[1]{\origlabel{#1##1}}%
  \renewcommand*{\ref}{\@ifstar\newrefstar\newref}%
  \renewcommand*{\newrefstar}[1]{\origref*{#1##1}}%
  \renewcommand*{\newref}[1]{\hyperref[#1##1]{\newrefstar{##1}}}%
}}
\makeatother

Replacing the code in my initial MWE's preamble with this code (almost) works: I can use \ref*{sec:foo} just like \ref{sec:foo}. A full MWE is provided here (for the sake of keeping this question short). However, the document crashes as soon as I use \ref* within \section.


Question: How can I fix the code above such that \section{\ref*{sec:foo}} works? Full non-working example.

In a comment, Ulrike Fischer pointed out that this may lead to issues in the table of contents and bookmarks. Is this unavoidable or can \ref* be substituted by its intended output before it is written to the TOC file?

Please note that I cannot change doc1.tex or doc2.tex (or their contents) from the initial example. So just avoiding duplicate labels, or altering the ref and label commands within these documents, is not feasible. This is why I use the "local labels" trick and try to adapt it.

6
  • well you can make it robust, but you will run into problems in the table of contents and the bookmarks -- they won't know which prefix to use as they are processed in other parts of the document. Oct 25, 2021 at 14:50
  • @UlrikeFischer This sounds ... problematic. Maybe this is a naive question, but could this be fixed by making the \ref* expand before it is written to the TOC file?
    – CL.
    Oct 26, 2021 at 9:35
  • The xr package replaces \include with \externaldocument which can add a prefix to labels automatically. Oct 26, 2021 at 13:00
  • @JohnKormylo Thanks for your reply. I'm aware of the xr package. But just using \externaldocument instead of \include is not a solution in my case. To clarify, consider 2 documents: doc1.tex (with everything except for the preamble in doc1_body.tex) and doc2.tex (with everything except for the preamble in doc2_body.tex). I need to combine doc1 and doc2 by including doc1_body.tex and doc2_body.tex into the same "master" document. My goal is not having to modify the two included documents, even if the same label occurs in both.
    – CL.
    Oct 26, 2021 at 14:43
  • My best advise is avoid altering the primitive tags like \label, \ref etc. You can create your own named tag and define as you wish...
    – MadyYuvi
    Oct 27, 2021 at 8:47

1 Answer 1

1
+100

I'd do a single redefinition of the relevant commands. Note you will also need to redefine in similar ways the cleveref specific commands you need.

% "doc1":
\begin{filecontents}{doc1_body.tex}
\section{About Foo} \label{sec:foo}
\ref{sec:foo} \ref*{sec:foo}
\end{filecontents}

% "doc2" (structurally identical to "doc1"):
\begin{filecontents}{doc2_body.tex}
\section{About Foo} \label{sec:foo} % same label as in doc2_body.tex
\ref{sec:foo} \ref*{sec:foo}
\section{About \ref{sec:foo}}
\end{filecontents}

% file main.tex:
\documentclass{scrartcl}
\usepackage{hyperref}
\usepackage{cleveref}

\makeatletter
\newcommand{\locallabels}[1]{\gdef\local@labels{#1}}
\locallabels{}% initialize
\AtBeginDocument{%
  \NewCommandCopy\KEPT@ref\ref
  \NewCommandCopy\KEPT@label\label
  \RenewExpandableDocumentCommand{\ref}{sm}{%
    \IfBooleanTF{#1}{\KEPT@ref*{\local@labels #2}}{\KEPT@ref{\local@labels #2}}%
  }%
  \RenewDocumentCommand{\label}{m}{\KEPT@label{\local@labels #1}}
}

\begin{document}
% cannot modify doc1_body or doc1_body!
\tableofcontents
\locallabels{part1:}
\input{doc1_body}
\locallabels{part2:}
\input{doc2_body} % Label `sec:foo' multiply defined.
\end{document}

enter image description here

Note that there is no problem with the table of contents, because the .toc file will have

\contentsline {section}{\numberline {1}About Foo}{1}{section.1}%
\contentsline {section}{\numberline {2}About Foo}{1}{section.2}%
\contentsline {section}{\numberline {3}About \KEPT@ref {part2:sec:foo}}{1}{section.3}%
\providecommand \tocbasic@end@toc@file {}\tocbasic@end@toc@file
4
  • Thanks a lot, this is great! Just one small caveat: while the TOC is flawless, I do get "??" in the bookmarks. Is it possible to fix this?
    – CL.
    Nov 1, 2021 at 16:29
  • @CL. The bookmarks are a very different beast, I'm afraid.
    – egreg
    Nov 1, 2021 at 16:38
  • 1
    You could try \pdfstringdefDisableCommands{\def\ref#1{\expandafter\HyPsd@ref\expandafter{\local@labels#1}}} for the bookmarks (not much tested). Nov 5, 2021 at 15:55
  • @UlrikeFischer Indeed, this astonishing piece of black magic seems to work like a charm. Thank you very much!
    – CL.
    Nov 6, 2021 at 22:40

You must log in to answer this question.

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