There's a clue to what's happening in the makeindex
log file, which contains the warning:
## Warning (input = test.idx, line = 2; output = test.ind, line = 7):
-- Conflicting entries: multiple encaps for the same page under same key.
This occurs when an entry is indexed multiple times on the same page with different encaps (the command that encapsulates the page number). Here's an example that illustrates this:
\documentclass{report}
\usepackage{makeidx}
\makeindex
\newcommand{\zzz}[1]{#1}
\newcommand{\aaa}[1]{#1}
\begin{document}
foo\index{foo|textbf}
\index{foo|zzz}
\index{foo|emph}
\index{foo|aaa}
\index{foo}
\printindex
\end{document}
This results in four multiple encap warnings. The ind
file contains:
\begin{theindex}
\item foo, 1, \aaa{1}, \emph{1}, \textbf{1}, \zzz{1}
\end{theindex}
makeindex
has listed each of the five foo
entries for page one, but has sorted them in the order: no encap (\index{foo}
) followed by the remaining entries that have been ordered alphabetically according to the encap value.
Now back to your MWE without hyperref
:
\documentclass{report}
\usepackage{makeidx}
\makeindex
\begin{document}
foo\index{foo}\index{foo|seealso{bar}}
foo\index{foo}
bar\index{bar}
\printindex
\end{document}
Here the first and third foo
index don't have an encap (\index{foo}
), so makeindex
can combine them into an unencapsulated entry for page 1, but the second foo
index has an encap (\index{foo|seealso{bar}}
). The encap value being seealso{bar}
, so the .ind
file contains the line:
\item foo, 1, \seealso{bar}{1}
Again, the entry without an encap is listed first. Now let's add hyperref
:
\documentclass{report}
\usepackage{makeidx}
\makeindex
\usepackage{hyperref}
\begin{document}
foo\index{foo}\index{foo|seealso{bar}}
foo\index{foo}
bar\index{bar}
\printindex
\end{document}
When hyperref
is added, hyperref
adds an encap to \index{foo}
behind the scenes. You can see this by inspecting the .idx
file, which contains:
\indexentry{foo|hyperpage}{1}
\indexentry{foo|hyperindexformat{\seealso{bar}}}{1}
\indexentry{foo|hyperpage}{1}
Again there's a multiple encaps warning and makeindex
again sorts the conflicting entries alphabetically according the encap, so the .ind
file now contains:
\item foo, \hyperindexformat{\seealso{bar}}{1}, \hyperpage{1}
This is what's causing the change in order. The simplest solution is to avoid the multiple encap clash by moving the cross-referenced entries to another part of the document that has a different page number. (Since \seealso
ignores its second argument, the actual page number is irrelevant.) For example:
\documentclass{report}
\usepackage{makeidx}
\makeindex
\usepackage{hyperref}
\begin{document}
foo\index{foo}
foo\index{foo}
bar\index{bar}
\clearpage
\index{foo|seealso{bar}}
\printindex
\end{document}
This gets rid of the warning and moves the cross-reference to the end of the location list.

Edit: Another possibility is to switch off hyperref
's hyperindex
option and create a makeindex
style file to wrap the location list in a command that can add hyperlinks to the page numbers. For example:
\documentclass{report}
\usepackage{filecontents}
\usepackage{makeidx}
\usepackage[hyperindex=false]{hyperref}
\makeindex
\begin{filecontents}{\jobname.ist}
delim_0 ", \\locationlist\{"
delim_1 ", \\locationlist\{"
delim_2 ", \\locationlist\{"
delim_t "\}"
delim_n ","
encap_prefix "\\encap\{\\"
encap_infix "\{"
encap_suffix "\}\}"
\end{filecontents}
\makeatletter
\newcommand*{\encap}[1]{#1}
\def\@location#1#2\end@location{%
\ifx\encap#1\relax
#2%
\else
\hyperpage{#1#2}%
\fi
}
\newcommand*{\locationlist}[1]{%
\def\locationsep{}%
\@for\location:=#1\do{%
\locationsep
\expandafter\@location\location\end@location
\def\locationsep{, }%
}%
}
\makeatother
\begin{document}
foo\index{foo}\index{foo|seealso{bar}}
foo\index{foo}
bar\index{bar}
\clearpage
bar\index{bar}
\clearpage
bar\index{bar}
\printindex
\end{document}
If the file is called test.tex
, then this needs:
pdflatex test
makeindex -s test.ist -o test.ind test.idx
pdflatex test

This applies \hyperpage
to the plain locations but not to the encaps. If you have other encaps (for example, \index{foo|textbf}
) you'll need a wrapper command, for example:
\documentclass{report}
\usepackage{filecontents}
\usepackage{makeidx}
\usepackage[hyperindex=false]{hyperref}
\makeindex
\begin{filecontents}{\jobname.ist}
delim_0 ", \\locationlist\{"
delim_1 ", \\locationlist\{"
delim_2 ", \\locationlist\{"
delim_t "\}"
delim_n ","
encap_prefix "\\encap\{\\"
encap_infix "\{"
encap_suffix "\}\}"
\end{filecontents}
\makeatletter
\newcommand*{\encap}[1]{#1}
\def\@location#1#2\end@location{%
\ifx\encap#1\relax
#2%
\else
\hyperpage{#1#2}%
\fi
}
\newcommand*{\locationlist}[1]{%
\def\locationsep{}%
\@for\location:=#1\do{%
\locationsep
\expandafter\@location\location\end@location
\def\locationsep{, }%
}%
}
\makeatother
\newcommand*{\primaryidx}[1]{\textbf{\hyperpage{#1}}}
\begin{document}
foo\index{foo}\index{foo|seealso{bar}}
foo\index{foo}
bar\index{bar}
\clearpage
bar\index{bar}
\clearpage
bar\index{bar}
foo\index{foo|primaryidx}
\printindex
\end{document}
This produces:

Note that the "see also" part isn't at the end of the list as it's associated with page 1. (The analogous see
key in glossaries
fakes page Z
to counteract this issue.) This problem will also occur without hyperref
.
One way to get around this (if you don't want to move your \index
commands to the end of the document) is to modify the location parser so that it saves the cross-reference and moves it to the end of the list:
\documentclass{report}
\usepackage{filecontents}
\usepackage{makeidx}
\usepackage[hyperindex=false]{hyperref}
\makeindex
\begin{filecontents}{\jobname.ist}
delim_0 ", \\locationlist\{"
delim_1 ", \\locationlist\{"
delim_2 ", \\locationlist\{"
delim_t "\}"
delim_n ","
encap_prefix "\\encap\{\\"
encap_infix "\{"
encap_suffix "\}\}"
\end{filecontents}
\makeatletter
\newcommand*{\encap}[1]{#1}
\def\@ifseealso#1#2\end@ifseealso#3#4{%
\ifx\seealso#1\relax
#3%
\else
#4%
\fi
}
\def\@location#1#2\end@location{%
\ifx\encap#1\relax
\@ifseealso#2\relax\end@ifseealso
{\def\crossref{\locationsep #2}}
{\locationsep #2}%
\else
\locationsep
\hyperpage{#1#2}%
\fi
}
\newcommand*{\locationlist}[1]{%
\def\locationsep{}%
\def\crossref{}%
\@for\location:=#1\do{%
\expandafter\@location\location\end@location
\def\locationsep{, }%
}%
\crossref
}
\makeatother
\newcommand*{\primaryidx}[1]{\textbf{\hyperpage{#1}}}
\begin{document}
foo\index{foo}\index{foo|seealso{bar}}
foo\index{foo}
bar\index{bar}
\clearpage
bar\index{bar}
\clearpage
bar\index{bar}
foo\index{foo|primaryidx}
\printindex
\end{document}
