% -*- Mode: latex; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- % % % hereapplies.sty % % A LaTeX package for cross-linking concepts to their applications % % https://github.com/madmurphy/hereapplies.sty % % Version 0.5.0 % % Copyright (C) 2022 madmurphy % % **Here Applies** is free software: you can redistribute it and/or modify it % under the terms of the GNU Affero General Public License as published by the % Free Software Foundation, either version 3 of the License, or (at your % option) any later version. % % **Here Applies** is distributed in the hope that it will be useful, but % WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or % FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License % for more details. % % You should have received a copy of the GNU Affero General Public License % along with this program. If not, see . % % % % Example usage: % % \documentclass{article} % % \usepackage{hereapplies} % % \begin{document} % % \title{Some title} % % \author{Some author} % % \maketitle % % This is concept one. To find this concept applied, please % see \whereapplies{conceptOne}. % % This is concept two. To find this concept applied, please % see \whereapplies{conceptTwo}.\newpage % % \hereapplies{conceptOne}This is page \thepage. As you can see, ``concept % one'' applies here.\newpage % % \hereapplies{conceptTwo}This is page \thepage. As you can see, ``concept % two'' applies here.\newpage % % \hereapplies{conceptOne,conceptTwo}This is page \thepage. As you % can see, both ``concept one'' and ``concept two'' apply here.\newpage % % \hereapplies{conceptTwo}This is page \thepage. As you can see, ``concept % two'' applies here.\newpage % % \hereapplies[myref]{conceptOne}This is page \thepage. As you can % see, ``concept one'' applies here. This point in the document is % labeled \texttt{myref}. % % \end{document} % % \ProvidesPackage{hereapplies} \RequirePackage{hyperref} \RequirePackage{refcount} % % % % PRIVATE ENVIRONMENT % =================== % % % Assign a unique number to each identifier \newcounter{@ha@identifier@counter} % Assign a unique number to each unnamed occurrence of an identifier \newcounter{@ha@unlabeled@counter} % % % Macro `\@ha@makeoccurrencelist{hypermacro}{labels}` % ***************************************************************************** % % Generate the list of page numbers with page range support % % This macro is for internal purposes only. When invoked, it scans the % comma-separated list of labels provided (`labels`) and checks which labels % refer to duplicate page numbers and which page numbers can be grouped % together. % % The `hypermacro` argument is the macro from the `hyperref` package that will % process the label name. This should be either `T@pageref` -- equivalent to % `\pageref` -- or `@pagerefstar` -- equivalent to `\pageref*`. % % The `labels` must be a comma-separated list of labels. % \newcommand{\@ha@makeoccurrencelist}[2]{% % Reset the current page number \def\@ha@tmp@@currp{-1}% % Reset the current range offset \def\@ha@tmp@@prangeoffs{-1}% % Iterate through the `labels` argument \@for\@ha@tmp@@thislabel:=#2\do{% % Store the page number associated with this label \edef\@ha@tmp@@nextp{\getpagerefnumber{\@ha@tmp@@thislabel}}% % Check that we are not on the same page as in the last iteration \unless\ifnum\@ha@tmp@@currp=\@ha@tmp@@nextp% % This is not the same page as in the last iteration % Is this the first page in which this identifier appears? \unless\ifnum\@ha@tmp@@currp=-1% % This is not the first page in which this identifier appears % Does this page follow immediately the previous label's page? \ifnum\numexpr\@ha@tmp@@currp+1=\@ha@tmp@@nextp% % This page follows immediately the previous label's page % Are these the first contiguous pages that we encounter? \ifnum\@ha@tmp@@prangeoffs=-1% % These are the first contiguous pages we encounter % Store the first page number of the pair \edef\@ha@tmp@@prangeoffs{\@ha@tmp@@currp}% % Store the first label of the pair \edef\@ha@tmp@@currrangelbl{\@ha@tmp@@currlbl}% \fi% \else% % This page is far from the previous label's page % Was the previous page part of a contiguous range? \ifnum\@ha@tmp@@prangeoffs=-1% % The previous page was a standalone page % Print "[p], " {\csname #1\endcsname{\@ha@tmp@@currlbl}, }% \else% % The previous page was part of a contiguous range % Print "[p--q], " {\csname #1\endcsname{\@ha@tmp@@currrangelbl}--\csname #1\endcsname{\@ha@tmp@@currlbl}, }% % Reset the current range offset \def\@ha@tmp@@prangeoffs{-1}% \fi% \fi% \fi% % Prepare the next page number \edef\@ha@tmp@@currp{\@ha@tmp@@nextp}% % Prepare the next label \edef\@ha@tmp@@currlbl{\@ha@tmp@@thislabel}% \fi% }% % Print the last page number % Is there at least one page to print? \unless\ifnum\@ha@tmp@@currp=-1% % There is at least one page to print % Was the previous page part of a contiguous range? \ifnum\@ha@tmp@@prangeoffs=-1% % The previous page was a standalone page % Print "[p], " {\csname #1\endcsname{\@ha@tmp@@currlbl}}% \else% % The previous page was part of a contiguous range % Print "[p--q], " {\csname #1\endcsname{\@ha@tmp@@currrangelbl}--\csname #1\endcsname{\@ha@tmp@@currlbl}}% \fi% \fi% } % % % Macro `\@ha@newidentifier{identifier}` % ***************************************************************************** % % Initialize a new identifier % % This macro is for internal purposes only. When invoked, it sets up the helper % macros, counters and auxiliary files needed for keeping track of an % identifier. If the identifier was already initialized the macro will be no % op. % % The `identifier` argument may contain only Latin letters and the "at" sign % (`@`). This string remains confined within the internal scope of the package % and does not create conflicts with possible macros or labels of the same % name. % \newcommand{\@ha@newidentifier}[1]{% % Was this identifier already initialized? \unless\ifcsname @ha@prop@@count@#1\endcsname% % The identifier was never initialized % Set the output to "??" - it will be updated by the .haN file \expandafter\gdef\csname @ha@prop@@uoutput@#1\endcsname{\textbf{??}}% % Set the starred output to "??" - it will be updated by the .haN file \expandafter\gdef\csname @ha@prop@@soutput@#1\endcsname{\textbf{??}}% % Use "p." for the preamble when there is only one occurrence \expandafter\gdef\csname @ha@prop@@preamble@#1\endcsname{p.~}% % Increase the counter of identifiers \stepcounter{@ha@identifier@counter}% % Store the current value of the counter of identifiers \expandafter\xdef\csname @ha@prop@@count@#1\endcsname{\the@ha@identifier@counter}% % Initialize the .haN file % Previous versions had this line, which created unwanted whitespaces: %\@starttoc{ha\csname @ha@prop@@count@#1\endcsname}% % I am thankful to David Carlisle for the following replacement: {\endlinechar=\m@ne\@starttoc{ha\csname @ha@prop@@count@#1\endcsname}}% % Store all the occurrences when the document reaches the end \AtEndDocument{% % Make sure that there are occurrences \ifcsname @ha@prop@@labels@#1\endcsname% % There are occurrences % Set the .haN file as output \addtocontents{ha\csname @ha@prop@@count@#1\endcsname}{% % Write "p./pp. \pageref..." to the output \gdef\expandafter\protect\csname @ha@prop@@uoutput@#1\endcsname{% % Preamble (either "p." or "pp.") \csname @ha@prop@@preamble@#1\endcsname% % `\T@pageref` is a synonym of `\pageref` \expandafter\protect\csname @ha@makeoccurrencelist\endcsname{T@pageref}{\csname @ha@prop@@labels@#1\endcsname}% }% % Write "p./pp. \pageref*..." to the starred output \gdef\expandafter\protect\csname @ha@prop@@soutput@#1\endcsname{% % Preamble (either "p." or "pp.") \csname @ha@prop@@preamble@#1\endcsname% % `\@pagerefstar` is a synonym of `\pageref*` \expandafter\protect\csname @ha@makeoccurrencelist\endcsname{@pagerefstar}{\csname @ha@prop@@labels@#1\endcsname}% }% }% \fi% }% \fi% } % % % Macro: `\@ha@getpropat{field}{identifier}` % ***************************************************************************** % % Get the value of an identifier's property % % This macro is for internal purposes (but nothing forbids invoking it % directly). If the identifier was never initialized the macro will initialize % it. % % Possible values for the `field` argument are: `count`, `labels`, `preamble`, % `soutput`, `uoutput`. % % The `identifier` argument may contain only Latin letters and the "at" sign % (`@`). This string remains confined within the internal scope of the package % and does not create conflicts with possible macros or labels of the same % name. % \newcommand{\@ha@getpropat}[2]{% % Make sure that the identifier is initialized \@ha@newidentifier{#2}% % Print the identifier's property \csname @ha@prop@@#1@#2\endcsname% } % % % Macro: `\starred@labeled@hereapplies{label}{identifiers}` % ***************************************************************************** % % Equivalent to `\hereapplies*`, but the two arguments are both mandatory % % This macro is for internal purposes (but nothing forbids invoking it % directly). See the documentation of `\hereapplies` for more information. % \newcommand{\starred@labeled@hereapplies}[2]{% % Assign a label to this occurrence \label{#1}% % Iterate through the comma-separated list `identifiers` \@for\@ha@tmp@@id:=#2\do{% % Make sure that the identifier is initialized {\edef\tmp{\noexpand\@ha@newidentifier{\@ha@tmp@@id}}\tmp}% % Is this the first time this identifier is mentioned? \ifcsname @ha@prop@@labels@\@ha@tmp@@id\endcsname% % This is *not* the first time \expandafter\g@addto@macro\csname @ha@prop@@labels@\@ha@tmp@@id\endcsname{,#1}% % Use "pp." for the preamble when there are multiple occurrences \expandafter\gdef\csname @ha@prop@@preamble@\@ha@tmp@@id\endcsname{pp.~}% \else% % This is the first time \expandafter\gdef\csname @ha@prop@@labels@\@ha@tmp@@id\endcsname{#1}% \fi% }% } % % % Macro: `\starred@hereapplies[label]{identifiers}` % ***************************************************************************** % % Equivalent to `\hereapplies*` % % This macro is for internal purposes (but nothing forbids invoking it % directly). See the documentation of `\hereapplies` for more information. % \newcommand{\starred@hereapplies}[2][]{% % Check whether the macro has been called with one or two arguments \ifx&% % The macro has been called with only one argument % Assign a unique number to the unnamed occurrence \stepcounter{@ha@unlabeled@counter}% % Call `\starred@hereapplies` with an opaque label {\edef\tmp{\noexpand\starred@labeled@hereapplies{% hereapplies:unnamed\the@ha@unlabeled@counter}{#2}% }\tmp}% \else% % The macro has been called with two arguments \starred@labeled@hereapplies{#1}{#2}% \fi% } % % % % PUBLIC ENVIRONMENT % =================== % % % Macro: `\hereapplies[label]{identifiers}` % ***************************************************************************** % % Notify the document that one or more identifiers apply to a particular point % and add a label to it % % If the optional argument is passed the label created will be named % accordingly, otherwise an opaque name will be assigned to it. This argument % may contain only what is legal for `\pageref`. % % The `identifiers` argument must be a comma-separated list of identifiers. % Each of these may contain only Latin letters and the "at" sign (`@`). These % strings remain confined within the internal scope of the package and do not % create conflicts with possible macros or labels of the same names. % % The starred version of this macro (`\hereapplies*`) does not invoke the % `\phantomsection` directive. % \newcommand{\hereapplies}{% % Check if a star is present in the invocation of the command \@ifstar{\starred@hereapplies}{\phantomsection\relax\starred@hereapplies}% } % % % Macro: `\whereapplies{identifier}` % ***************************************************************************** % % Print all the occurrences of an identifier in the form "p. ..." or "pp. ..." % with page range support % % The `identifier` argument may contain only Latin letters and the "at" % sign (`@`). This string remains confined within the internal scope of the % package and does not create conflicts with possible macros or labels of the % same name. % % If the same `identifier` is not passed to `\hereapplies` at least once % throughout the document, `\whereapplies` will print "??". % % The starred version of this macro (`\whereapplies*`) will use `\pageref*` % instead of `\pageref` for generating the page list. % \newcommand{\whereapplies}{% % Check if a star is present in the invocation of the command \@ifstar{\@ha@getpropat{soutput}}{\@ha@getpropat{uoutput}}% }