2

I am creating a document in which in the first chapter I define some concepts – each with its own dedicated paragraph (something like: concept one, concept two, concept three, etc.) – and in all the other chapters I analyse things applying the concepts that I have defined in the first chapter.

I would like to add at the end of each “concept paragraph” something like “To see this concept applied, please see pp. 13, 19, 55, ...”

The usual way to do that is by adding labels to various parts of the document and add a \pageref to each of them under the appropriate “concept paragraph”. However I would like to automatize everything by reversing the approach and in the other chapters doing something like

\applyconcept{concept-three}As you can see, “concept three” applies to this case ...

...

\applyconcept{concept-four}As you can see, “concept four” applies to this case ...

etc.

so that each time the \applyconcept macro is invoked it populates the appropriate “concept paragraph” in the first chapter.

What I am trying to do is basically creating separate indices. Unfortunately the usual packages for indices are not as customizable as I would like, therefore I have tried to hack multind.sty and amsmidx.sty in order to rename the \index macro into \applyconcept, but I found out that not even the original \index macro is really suitable for my case.

Is there anyone able to tell me how to implement a very basic index from scratch?

4 Answers 4

1

You can use my rdfref package for such things. It isn't unfortunately on CTAN, so you will need to install it yourself.

It is inspired by RDF, so you can declare objects for which you can set various properties. In your case, each use of the \applyconcept should create a new object, for which it should set the concept name and page number as properties.

You can then define another command, named for example as \conceptpages, which will search for all objects with the given concept name, and print the page numbers where they were used.

I've created a simple package, applyconcept.sty, which implements these commands with the help of rdfref:

\ProvidesPackage{applyconcept}
\RequirePackage{rdfref-user}
\RequirePackage{rdfref-query}
\RequirePackage{hyperref}

\newcommand\applyconcept[1]{%
  % define a new object, with anonymous name
  \BlankNode%
  % set basic object properties
  % this identifies the current object as the con:concept type
  \AddProperty{rdf:type}{con:concept}%
  % save the concept name, so it can be later looked up
  \AddProperty{con:name}{#1}%
  % save the page number for this concept
  \AddPropertyEx{doc:pageNo}{\thepage}%
  % define target for hyperlink, using the current object
  % name as a label.
  \hypertarget{\CurrentObject}{}%
}

\newcommand\conceptpages[1]{%
  % we will want to separate page numbers with commans, but not the first one
  \def\currentseparator{}%
  % process all objects with the con:name property equal to the parameter
  % it executes the code for each object that matches the condition
  \Bind{?obj}{con:name}{#1}{%
    % the object name is saved in the ?obj variable, we can get it's value and save it to a macro 
    % for a simpler access:
    \edef\currobject{\GetVal{?obj}}% 
    % print the page number with link to the page%
    \currentseparator\hyperlink{\currobject}{\GetProperty{\currobject}{doc:pageNo}}%%
    % the subsequent pages will be separated using comma and space
    \gdef\currentseparator{,\space}%
  }%
}


\endinput

It defines the commands like this:

\newcommand\applyconcept[1]{%
  % define a new object, with anonymous name
  \BlankNode%
  % set basic object properties
  % this identifies the current object as the con:concept type
  \AddProperty{rdf:type}{con:concept}%
  % save the concept name, so it can be later looked up
  \AddProperty{con:name}{#1}%
  % save the page number for this concept
  \AddPropertyEx{doc:pageNo}{\thepage}%
  % define target for hyperlink, using the current object
  % name as a label.
  \hypertarget{\CurrentObject}{}%
}

First, it defines a new anonymous object using the \BlankNode command, and then it adds various properties using the \AddProperty or \AddPropertyEx (for expanded value).

The rdf:type property should be used for all objects, to identify the object type. In RDF, properties are named as prefix:name, so I stick with this, even if it is not really necessary for our use.

The con:name property is the concept name. It is necessary to save it, in order to be able to use the query language to search for all usages of this concept.

I've also added the \hypertarget command, so it will be possible to make hyperlink exactly to the place where concept was defined. The \CurrentObject command holds the current object name, usually something like _:blank1 for anonymous objects. We will get the object name in the query, so we will be able to link to this place.

Now the querying command:

\newcommand\conceptpages[1]{%
  % we will want to separate page numbers with commans, but not the first one
  \def\currentseparator{}%
  % process all objects with the con:name property equal to the parameter
  % it executes the code for each object that matches the condition
  \Bind{?obj}{con:name}{#1}{%
    % the object name is saved in the ?obj variable, we can get it's value and save it to a macro 
    % for a simpler access:
    \edef\currobject{\GetVal{?obj}}% 
    % print the page number with link to the page%
    \currentseparator\hyperlink{\currobject}{\GetProperty{\currobject}{doc:pageNo}}%%
    % the subsequent pages will be separated using comma and space
    \gdef\currentseparator{,\space}%
  }%
}

The \currentseparator command is used for correct handling of separating commas, so it is not really interesting.

The \Bind command searches for a query. It takes four parameters:

\Bind{object}{property}{value}{code to be executed}

In our case, we know that we want to search for objects that have property con:name with value passed as an argument (for instance concept-three). The ?obj used in the place of the object indicates that the object is unknown. The \Bind command will loop over all objects, test if they have the given property and value, and execute the code when they matches. We can then get the object name using the \GetVal{?obj} command.

We then can use the object name for two tasks: for the hyperlink, and to get the page number. For easier use, I've saved the object name in the \currobject command. We can then use the following command to print the hyperlink and the page number:

\hyperlink{\currobject}{\GetProperty{\currobject}{doc:pageNo}}

The package can be used like this:

\documentclass{article}
\usepackage{applyconcept}
\begin{document}

\applyconcept{concept-three}As you can see, “concept three” applies to this case ...


\applyconcept{concept-four}As you can see, “concept four” applies to this case ...

\newpage

\applyconcept{concept-three}Apply concept three again.

To see this concept applied, please see pp.~\conceptpages{concept-three}.

\end{document}

This is the result:

enter image description here

1
  • Thank you, michal.h21. I did not know about the rdfref package.
    – madmurphy
    Aug 11, 2022 at 5:16
1

Emphasizing the "very basic" requirement, the following code implements \applyconcept.

The idea is to use macros \conceptone, \concepttwo etc, and every time \applyconcept is called the current page number is added to the previous contents of that macro.

At the end you create a label for each concept with the full contents of the concept macros. This label is then used for \ref in the introductory paragraphs.

In terms of code the basic idea is:

\def\someconcept{\someconcept,\thepage}

However, this is a recursive definition. To avoid a loop, the definition should be expanded, i.e., the second occurrence of \someconcept should be replaced by the actual value to avoid the recursion. For this you can use \edef (expanded def). In this case we want global scope, for which \xdef is used (global expanded def):

\xdef\someconcept{\someconcept,\thepage}

Now this should go into a macro definition for \applyconcept. Within this definition, the argument #1 should be used within the \xdef instead of the name \someconcept above. To make the argument #1 a valid input for \xdef, it needs to be transformed into a control sequence. This is done with \csname [name of the macro] \endcsname. So:

\xdef\csname#1\endcsname{\csname#1\endcsname, \thepage}

One last step is to make sure \xdef does not try to define \csname, i.e., that the definition is delayed until \csname is finished constructing the macro name. This is done with \expandafter:

\expandafter\xdef\csname#1\endcsname{\csname#1\endcsname, \thepage}

One detail is that the first mention of a concept should not contain the previous contents and the comma. To keep the code clean we can use \ifcsvoid from the etoolbox package, which checks if a control sequence exists and is not empty, and allows for different code to be executed when this is or is not the case.

Finally the labels need to be populated. This is done by defining \@currentlabel. When this macro is set, all the subsequent regular \label statements take the value of \@currentlabel.

Full MWE:

\documentclass{article}
\usepackage{etoolbox}
\def\applyconcept#1{%
\ifcsvoid{#1}{%
\expandafter\xdef\csname#1\endcsname{\thepage}%
}{%
\expandafter\xdef\csname#1\endcsname{\csname#1\endcsname, \thepage}%
}%
}
\begin{document}
This is concept one. To see this concept applied, please see pp. \ref{conceptonelabel}.

This is concept four. To see this concept applied, please see pp. \ref{conceptfourlabel}.
\setcounter{page}{13}
\par This is page \thepage. \applyconcept{conceptone}As you can see, ``concept one'' applies here.
\setcounter{page}{15}
\par This is page \thepage. \applyconcept{conceptfour}As you can see, ``concept four'' applies here.
\setcounter{page}{19}
\par This is page \thepage. \applyconcept{conceptone}As you can see, ``concept one'' applies here.
\setcounter{page}{26}
\par This is page \thepage. \applyconcept{conceptfour}As you can see, ``concept four'' applies here.
\setcounter{page}{55}
\par This is page \thepage. \applyconcept{conceptone}As you can see, ``concept one'' applies here.

\makeatletter
\def\@currentlabel{\conceptone}\label{conceptonelabel}
\def\@currentlabel{\conceptfour}\label{conceptfourlabel}
\makeatother
\end{document}

Result:

enter image description here

Hoqwever, what you probably want is a glossary, for which the glossaries package can be used:

\documentclass{article}
\usepackage[sort=def, nopostdot]{glossaries}

\makeglossaries

\newglossaryentry{conceptone}
{
    name=Concept one,
    description={This is an important concept. It is the first one, and therefore the most important of all concepts. See pages:}
}
\newglossaryentry{conceptfour}
{
    name=Concept four,
    description={This is not a very important concept. It is only the fourth concept, the first three are clearly more important. See pages:}
}

\begin{document}
\printglossary[type=main,title={Definition of concepts}]
\setcounter{page}{13}
\par This is page \thepage. As you can see, \gls{conceptone} applies here.
\newpage
\setcounter{page}{15}
\par This is page \thepage. As you can see, \gls{conceptfour} applies here.
\newpage
\setcounter{page}{19}
\par This is page \thepage. As you can see, \gls{conceptone} applies here.
\newpage
\setcounter{page}{26}
\par This is page \thepage. As you can see, \gls{conceptfour} applies here.
\newpage
\setcounter{page}{55}
\par This is page \thepage. As you can see, \gls{conceptone} applies here.
\end{document}

enter image description here

1
1

Here's an implementation.

The concept environment sets up a clist variable with name depending on the argument (the conventional name of the concept, not necessarily the exact name).

Such clist is populated by the \applyconcept command. At end document we set another clist and save it in the .aux file, so at the next run it can be used at the end of the relevant concept environment.

The needed labels are self-generated by \applyconcept.

\documentclass{book}
\usepackage{lipsum}

\ExplSyntaxOn
\NewDocumentEnvironment{concept}{m}
 {
  \clist_new:c { g__madmurphy_concept_in_#1_clist }
  \seq_gput_right:Nn \g__madmurphy_concept_list_seq { #1 }
 }
 {
  To~see~this~concept~applied,~please~see~pages~\__madmurphy_concept_show:n { #1 }.
  \par\addvspace{\topsep}
 }

\NewDocumentCommand{\applyconcept}{m}
 {
  \int_gincr:N \g__madmurphy_concept_int
  \par
  \label{concept @ \int_to_arabic:n { \g__madmurphy_concept_int }}
  \clist_gput_right:cx { g__madmurphy_concept_in_#1_clist }
   {
    \exp_not:N \pageref{concept @ \int_to_arabic:n { \g__madmurphy_concept_int }}
   }
 }

\int_new:N \g__madmurphy_concept_int
\seq_new:N \g__madmurphy_concept_list_seq

\cs_new:Nn \__madmurphy_concept_show:n
 {
  \clist_use:cnnn { g__madmurphy_concept_out_#1_clist } { ~and~ } { ,~ } { ,~and~ }
 }

\AtEndDocument
 {
  \iow_now:cn { @auxout } { \ExplSyntaxOn }
  \seq_map_function:NN \g__madmurphy_concept_list_seq \__madmurphy_concept_write:n
  \iow_now:cn { @auxout } { \ExplSyntaxOff }
 }

\cs_new_protected:Nn \__madmurphy_concept_write:n
 {
  \iow_now:cx { @auxout }
   {
    \clist_clear_new:c { g__madmurphy_concept_out_#1_clist }
   }
  \iow_now:cx { @auxout }
   {
    \clist_gset:cn { g__madmurphy_concept_out_#1_clist }
     {
      \clist_use:cn { g__madmurphy_concept_in_#1_clist } { , }
     }
   }
 }

\ExplSyntaxOff

\begin{document}

\chapter{Concepts}

\begin{concept}{gnu}
The first concept we introduce is \emph{gnu}.
Let's describe it with nonsense words.
Let's describe it with nonsense words.
Let's describe it with nonsense words.
Let's describe it with nonsense words.
\end{concept}

\begin{concept}{gnat}
The second concept we introduce is \emph{gnat}. 
Let's describe it with nonsense words.
Let's describe it with nonsense words.
Let's describe it with nonsense words.
Let's describe it with nonsense words.
\end{concept}

\chapter{Some title}

\applyconcept{gnu}
\lipsum[1]

\clearpage

\applyconcept{gnat}
\lipsum[2]

\chapter{Some other title}

\applyconcept{gnat}
\lipsum[3]

\clearpage

\applyconcept{gnu}
\lipsum[4]

\ExplSyntaxOn

\end{document}

enter image description here

1
0

Thank you everyone for the good answers. I tried to find my own solution, and this is what I came up with (I made the package available on GitHub as well).

My solution relies on only one dependency (the hyperref package) and offers two simple macros: \whereapplies and \hereapplies – the latter has also a starred version (\hereapplies*).

EDIT: After finding a small bug and asking for help on another thread I have adopted David Carlisle's bugfix. The code below has been updated accordingly.

hereapplies.sty:

%  -*- Mode: latex; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-

%
% hereapplies.sty
%
% A LaTeX package for cross-linking applications of concepts
%
% https://github.com/madmurphy/hereapplies.sty
%
% Version 0.2.0
%
% Copyright (c) 2022 madmurphy <madmurphy333@gmail.com>
%
% **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 <http://www.gnu.org/licenses/>.
%
%
%
% Example usage:
%
%     \documentclass{article}
%
%     \usepackage{hereapplies}
%
%     \begin{document}
%
%     \title{Some title}
%     \author{Some author}
%
%     \maketitle
%
%     This is concept one. To see this concept applied, please
%     see \whereapplies{conceptone}.
%
%     This is concept four. To see this concept applied, please
%     see \whereapplies{conceptfour}.
%
%     \newpage
%
%     \hereapplies{conceptone}This is page \thepage. As you can see, ``concept
%     one'' applies here.\newpage
%
%     \hereapplies{conceptfour}This is page \thepage. As you can see,
%     ``concept four'' applies here.\newpage
%
%     \hereapplies{conceptone}This is page \thepage. As you can see, ``concept
%     one'' applies here.\newpage
%
%     \hereapplies{conceptfour}This is page \thepage. As you can see,
%     ``concept four'' 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{appl:conceptone:myref}.
%
%     \end{document}
%
%
\ProvidesPackage{hereapplies}
\RequirePackage{hyperref}
%
%
%
%         PRIVATE ENVIRONMENT
%         ===================
%
%
% Assign a unique number to each applicable concept
\newcounter{@ha@concept@counter}
%
%
% Macro `\@ha@newapplicable{concept_name}`
% *****************************************************************************
%
% Initialize a new applicable concept
%
% Thie macro is for internal purposes only. When invoked it sets up the helper
% macros, counters and auxiliary files needed for keeping track of a concept.
% If the concept was already initialized this macro will be no op.
%
\newcommand{\@ha@newapplicable}[1]{%
    % Was this concept already initialized?
    \expandafter\ifx\csname @ha@concept@#1@cursor\endcsname\relax%
        % The concept was never initialized
        % Move the counter to the current id
        \stepcounter{@ha@concept@counter}%
        % Count the unnamed occurrences
        \newcounter{@ha@concept@#1@unnamed@counter}%
        % The content of this macro will be saved in the .haN file
        \expandafter\def\csname @ha@concept@#1@output\endcsname{\textbf{??}}%
        % The last page that applies
        \expandafter\def\csname @ha@concept@#1@cursor\endcsname{-1}%
        % Store the id of the current concept
        \expandafter\edef\csname @ha@concept@#1@id\endcsname{\arabic{@ha@concept@counter}}%
        % Initialize the .haN file
        % The previous solution generated unwanted whitespaces:
        %\@starttoc{ha\csname @ha@concept@#1@id\endcsname}%
        % I am thankful to David Carlisle for the following line:
        {\endlinechar=\m@ne\@starttoc{ha\csname @ha@concept@#1@id\endcsname}}%
        % Store all the occurrences when the document reaches the end
        \AtEndDocument{%
            % Set the .haN file as output
            \addtocontents{ha\csname @ha@concept@#1@id\endcsname}{%
                % Check that there are occurrences
                \expandafter\ifcsname @ha@concept@#1@cache\endcsname%
                    % There are occurrences - write "p./pp. ..." to the output
                    \gdef\expandafter\protect\csname @ha@concept@#1@output\endcsname{%
                        \csname @ha@concept@#1@preamble\endcsname\csname @ha@concept@#1@cache\endcsname%
                    }%
                \else%
                    % There are no occurrences - write "??" to the output
                    \gdef\expandafter\protect\csname @ha@concept@#1@output\endcsname{%
                        \textbf{??}%
                    }%
                \fi%
            }%
        }%
    \fi%
}
%
%
% Macro: `\starred@hereapplies[occurrence_name]{concept_name}`
% *****************************************************************************
%
% Equivalent to `\hereapplies*`
%
% Thie macro is for internal purposes - but nothing forbids invoking it
% directly.
%
\newcommand{\starred@hereapplies}[2][]{%
    % Make sure that the concept has been initialized
    \@ha@newapplicable{#2}%
    \ifx&#1&%
        % The macro has been called with only one argument
        % Assign a unique number to the unnamed occurrence
        \stepcounter{@ha@concept@#2@unnamed@counter}%
        % Call `\starred@hereapplies` again (recursion), but this time with 2 arguments
        \edef\tmp{\noexpand\starred@hereapplies[__unnamed\arabic{@ha@concept@#2@unnamed@counter}__]{#2}}\tmp%
    \else%
        % The macro has been called with two arguments
        % Assign a label to this occurrence
        \label{appl:#2:#1}%
        % If the cursor already points to the current page do nothing
        \unless\ifnum\csname @ha@concept@#2@cursor\endcsname=\thepage%
            % Make the cursor point to the current page
            \expandafter\edef\csname @ha@concept@#2@cursor\endcsname{\thepage}%
            % Is this the first occurrence?
            \expandafter\ifcsname @ha@concept@#2@cache\endcsname%
                % This is *not* the first occurrence
                % Use "pp." for the preamble when there are multiple occurrences
                \expandafter\def\csname @ha@concept@#2@preamble\endcsname{pp.~}%
                % Populate the cache
                \expandafter\g@addto@macro\csname @ha@concept@#2@cache\endcsname{, \pageref{appl:#2:#1}}%
            \else%
                % This is the first occurrence
                % Use "p." for the preamble when there is only one occurrence
                \expandafter\def\csname @ha@concept@#2@preamble\endcsname{p.~}%
                % Initialize the cache
                \expandafter\def\csname @ha@concept@#2@cache\endcsname{\pageref{appl:#2:#1}}%
            \fi%
        \fi%
    \fi%
}
%
%
%
%         PUBLIC ENVIRONMENT
%         ===================
%
%
% Macro: `\hereapplies[occurrence_name]{concept_name}`
% *****************************************************************************
%
% Notify the document that here a particular concept applies
%
% If the `occurrence_name` argument is passed, a new label will be created in
% the form `appl:[concept_name]:[occurrence_name]`. Without passing the
% `occurrence_name` argument, an opaque name will be assigned to the label.
%
% The starred version of this macro (`\hereapplies*`) will not invoke the
% `\phantomsection` directive before generating the label.
%
\newcommand{\hereapplies}{%
    % Check if a star is present in the invocation of the command
    \@ifstar{\starred@hereapplies}{\phantomsection\relax\starred@hereapplies}%
}
%
%
% Macro: `\whereapplies{concept_name}`
% *****************************************************************************
%
% Print all the applications of a concept in the form "p./pp. ..."
%
\newcommand{\whereapplies}[1]{%
    % Make sure that the applicable concept is initialized
    \@ha@newapplicable{#1}%
    % Print all the applications of the concept
    \csname @ha@concept@#1@output\endcsname%
}

example.tex (thnx @marijn for the hackable example):

\documentclass{article}

\usepackage{hereapplies}

\begin{document}

\title{Some title}
\author{Some author}

\maketitle

This is concept one. To see this concept applied, please
see \whereapplies{conceptone}.

This is concept four. To see this concept applied, please
see \whereapplies{conceptfour}.

\newpage

\hereapplies{conceptone}This is page \thepage. As you can see, ``concept
one'' applies here.\newpage

\hereapplies{conceptfour}This is page \thepage. As you can see,
``concept four'' applies here.\newpage

\hereapplies{conceptone}This is page \thepage. As you can see, ``concept
one'' applies here.\newpage

\hereapplies{conceptfour}This is page \thepage. As you can see,
``concept four'' 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{appl:conceptone:myref}.

\end{document}

Result:

Example of hereapplies.sty

--madmurphy

You must log in to answer this question.

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