[Tex/LaTex] Make index entries refer to something other than the page numbers

imakeidxindexing

For a given book which is a daily devotional, I have no page numbers at all, but each section corresponds to a day in the year, with one day per page. I generate an index of Bible references in the end of the book, and I link the Bible references to the page where they are quoted. But since there are no page numbers in the book, the index must contain the day (i.e. the secion, e.g. "13 Mai" instead of 151) instead of the page number.

So far, my (ugly) solution was to output the section name and page in the logs with:

 \typeout{Date on page \thepage:\thesection:\chaphead}%

and then use a shell script to parse the logs and modify the generated index (.ind file) with sed:

#!/bin/bash                                                                     

NAME="$1"                                                                       

while read -r page day month; do                                                
   bsmonth=$(sed -e 's@\\@\\\\@g' <<<$month)                                    
   sed -i "s@textrm{\([^,]*, \)\?$page\(, [^,]*\)\?}@textrm{\1\\\\hyperlink{page.$page}{\\\\mbox{\\\\scshape\\\\makedate{$day}~$bsmonth}}\2}@g" $NAME.ind
done < <(sed -n 's@Date on page \([0-9]\+\):\([0-9]\+\):\(.*\)@\1 \2 \3@p' $NAME.log)

I would call LaTeX this way:

%.pdf: %.tex            
    TEXINPUTS=$(TEXINPUTS) pdflatex -shell-escape -interaction=batchmode $< 
    # Modify index to use dates instead of pages                            
    ./index_dates.sh $*                                                     
    # No -shell-escape to prevent re-creation of index                      
    TEXINPUTS=$(TEXINPUTS) pdflatex -interaction=batchmode $<   

I'm using imakeidx so the index is automatically regenerated when I compile the document. In TeXLive 2009, not passing -shell-escape prevented imakeidx from rebuilding the index, and thus from erasing the changes made by index_dates.sh, so it worked fine (although still ugly).

Now in TeXLive 2010, the -shell-escape trick doesn't work, and before I switch to another ugly trick, I'd like to think of rewriting this piece of code in proper TeX. Can you give me some hint as to how to do it properly?

  • Should I generate dynamic macros such as:

    \def\pagemonth@\csname \thepage{\thesection~\chaphead}
    

and then modify the index? If so, how do I modify the index?

  • Should I write these macros to a separate file and import it later on (and how to do that)?

  • Should I hook into imakeidx (and how)?

  • Should I replace imakeidx with another package that would allow this more easily?

  • Should I switch to another index builder (such as xindy) and what parameters would I pass it?

  • Should I add a "style" for page numbers in the index which would actually be a call to a macro replacing with my content?

Best Answer

Here is a possible solution. I show it with sections, it should be easy to adapt to your setting.

(1) I define a counter and step it each time a new section is started with the \Section command, which defines a unique command holding the section's title and whose name depends on the value of the counter.

(2) I get copies of the relevant indexing commands and patch them: \Index requires \@Index which in turn requires \@Wrindex; \@Wrindex is the heart of the process, because it writes the entry in the .idx file. So I patch it in such a way that it immediately writes the current value of the counter. Here \protected@iwrite is the same as \protected@write, but does an \immediate\write instead of the usual delayed \write, since we don't need the page number which is known only during the output routine.

(3) The real \Index command is a wrapper around a useful function of makeindex, usually employed for "Seeā€¦".

(4) We pass makeindex the -s raphink option, where raphink.ist is a style file containing only

delim_0 ""

that will not add a comma after an entry.

Now a command such as \Index{Veni Sancte Spiritus} in the "January 1st" section will write

\indexentry{Veni Sancte Spiritus|transform}{1}

in the .idx file (assuming that 1 is the current value of the counter). If we write also \Index{Veni Sancte Spiritus} on Pentecost, we'll have an entry such as

\indexentry{Veni Sancte Spiritus|transform}{150}

Processing with makeindex -s raphink (done automatically by imakeidx) will give the following in the .ind file:

\item Veni Sancte Spiritus \transform{1, 150}

The macro \transform reads the comma separated list and splits it using then the commands \csname cursec1\endcsname and \csname cursec150\endcsname that contain the correct section titles.

The implementation

Here are the macros in a sample document.

\begin{filecontents*}{raphink.ist}
delim_0 ""
\end{filecontents*}
\documentclass[a4paper]{report}
\usepackage{etoolbox}
\newcounter{indexcnt}
\newcommand\Section[1]{%
  \stepcounter{indexcnt}%
  \expandafter\gdef\csname cursec\theindexcnt\endcsname{#1}%
  \section{#1}}

\usepackage{imakeidx}
\makeindex[options=-s raphink]

\makeatletter
\let\@Index\@index
\patchcmd{\@Index}{\@wrindex}{\@Wrindex}{}{}
\let\@Wrindex\@wrindex
\patchcmd{\@Wrindex}{\thepage}{\theindexcnt}{}{}
\let\xIndex\index
\patchcmd{\xIndex}{\@index}{\@Index}{}{}
\patchcmd{\xIndex}{\@index}{\@Index}{}{}
\let\protected@iwrite\protected@write
\patchcmd{\protected@iwrite}{\write}{\immediate\write}{}{}
\patchcmd{\@Wrindex}{\protected@write}{\protected@iwrite}{}{}
\makeatother

\newcommand{\Index}[1]{\xIndex{#1|transform}}
\newcommand{\transform}[1]{\forcsvlist\decodesec{#1}}
\newcommand{\decodesec}[1]{, \csname cursec#1\endcsname}

\begin{document}

\chapter{Christmas}

\Section{January 1st}

...
Veni Sancte Spiritus\Index{Veni Sancte Spiritus}
...

\chapter{Easter}

\Section{Pentecost}

...
Veni Sancte Spiritus\Index{Veni Sancte Spiritus}
...


\printindex

\end{document}