% \iffalse meta-comment
%
% Copyright (c) 2026 David Purton <dcpurton@marshwiggle.net>
%
% This work may be distributed and/or modified under the conditions of
% the LaTeX Project Public License, either version 1.3c of this license
% or (at your option) any later version. The latest version of this
% license is in
%    http://www.latex-project.org/lppl.txt
% and version 1.3c or later is part of all distributions of LaTeX
% version 2005/12/01 or later.
%
%<*driver>
\RequirePackage{pdfmanagement}
\documentclass[a4paper]{l3doc}
\usepackage[T1]{fontenc}
\usepackage{microtype}
\usepackage{mlmodern}
\usepackage[font=small, skip=6pt]{caption}
\usepackage{xcolor}
\usepackage{listings}
\usepackage{sblidx}

\AddToHook{env/macrocode/before}{%
  \addvspace{\medskipamount}}

\AddToHook{env/macro/before}{%
  \addvspace{\medskipamount}}

\begin{document}
\DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
% \title{The \pkg{sblidx} Package}
% \author{David Purton\thanks{Email: \url{dcpurton@marshwiggle.net}}}
% \date{2026-04-04 v1.0}
%
% \maketitle
%
% \begin{abstract}
%   \pkg{sblidx} provides a \LaTeX\ package for creating indices in a style
%   recommended by the Society of Biblical Literature as outlined in the
%   \emph{Society of Biblical Literature Handbook of Style} and
%   \emph{Preparing Indices}.\footnote{See \emph{The SBL Handbook of Style:
%   For Biblical Studies and Related Disciplines}, 2nd ed.\@ (SBL Press,
%   2014); SBL Publications, \emph{Preparing Indices},
%   \url{https://www.sbl-site.org/wp-content/uploads/2024/05/Indexing_SBL.pdf}.}
%   This includes producing an index of ancient sources with the help of the
%   \pkg{bibleref-sbl} package, an index of modern authors with the help of
%   the \pkg{biblatex-sbl} package and an index of subjects. Page ranges are
%   automatically compressed and when indexed entries appear in footnotes they
%   are indicated with n.\ and nn.
% \end{abstract}
%
% \tableofcontents
%
% \section{Introduction}
%
% The Society of Biblical Literature has specific requirements for indexes.
% Typically there are separate indices for subjects, modern authors and
% ancient sources. And these different indices are laid out in a slightly
% different way. Additionally page ranges are compressed and index references
% in footnotes are indicated with n.\ and nn. This package attempts to handle
% these variations with as much automation as possible.
%
% A subject index is automatically enabled and indices of modern authors and
% ancient sources can optionally be added. Modern authors are indexed when
% they are cited using the \pkg{biblatex-sbl} package and books of the Bible
% are indexed using the \pkg{bibleref-sbl} package. The different format of
% these three indices as required by the SBL is handled automatically.
%
% \subsection{Usage}
%
% A simple example document is shown in Figure \ref{exampleusage} and the
% output is shown in Figure \ref{exampleoutput}.
%
% \textbf{Note:} You should load \pkg{sblidx} \emph{after} you load
% \pkg{biblatex}.
%
% \lstset{
%   language={[LaTeX]TeX},
%   morekeywords={\ibibleverse, \SBLPrintIndices, \autocite},
%   basicstyle=\small\ttfamily,
%   keywordstyle=\color{blue!70!black},
%   frame=single,
%   tabsize=2,
% }
%
% \begin{figure}[b!]
% \iffalse
%<*example>
% \fi
    \begin{lstlisting}
\documentclass{article}
\begin{filecontents}{\jobname.bib}
@book{talbert:1992,
  author = {Talbert, Charles H.},
  title = {Reading John},
  subtitle = {A Literary and Theological Commentary on the Fourth
              Gospel and the Johannine Epistles},
  location = {New York},
  publisher = {Crossroad},
  date = {1992}
}
\end{filecontents}
\usepackage[style=sbl]{biblatex}
\addbibresource{\jobname.bib}
\usepackage[subject, ancient sources, modern authors]{sblidx}
\begin{document}
A topic\index{topic}. A reference to \ibibleverse{John}(1:1)
\autocite{talbert:1992}. Another topic.\footnote{with an index
entry in the footnote.\index{another topic}.} And an example of a
cross reference in an index.\index{entry|see{topic}}
\SBLPrintIndices
\end{document}
    \end{lstlisting}
% \iffalse
%</example>
% \fi
%   \caption{Example \LaTeX\ source showing subject, ancient sources and
%     modern authors indices.}
%   \label{exampleusage}
% \end{figure}
%
% \begin{figure}
%   \centering
%   \framebox[0.5\linewidth]{%
%     \parbox{\dimexpr 0.5\linewidth-2\fboxsep-2\fboxrule}{%
%       \setlength{\parskip}{0pt}\small
%       {\large\bfseries Ancient Sources Index\par}
%       \medskip
%       \textbf{New Testament}\par
%       \quad John\par
%       \qquad 1:1\hfill 1\par
%       \bigskip
%       {\large\bfseries Modern Authors Index\par}
%       \medskip
%       Talbert, Charles H.\hfill 1 n.~1\par
%       \bigskip
%       {\large\bfseries Subject Index\par}
%       \medskip
%       another topic, 1 n.~2\par
%       \smallskip
%       entry. \emph{See} topic\par
%       \smallskip
%       topic, 1
%     }%
%   }
%   \caption{Example output showing subject, ancient sources and modern
%     authors indices.}
%   \label{exampleoutput}
% \end{figure}
%
% \subsection{Bug Reports and Feature Requests}
%
% Bug reports and feature requests can be made at the \pkg{sbltex} GitHub
% repository. See \url{https://github.com/dcpurton/sbltex}.
%
% \section{Package Options}
%
% \begin{function}{ancient sources}
%   \begin{syntax}
%     ancient sources = true \textbar\ false\hfill Default: false
%   \end{syntax}
%   Enable an index of ancient sources. Bible books (including
%   deutero-canonical books) are indexed in SBL style use the
%   \pkg{bibleref-sbl}, \pkg{bibleref-parse} and \pkg{bibleref} packages.
% \end{function}
%
% \begin{function}{ancient sources title}
%   \begin{syntax}
%     ancient sources title = \meta{title}\hfill Default: Ancient Sources Index
%   \end{syntax}
%   Set the title of the ancient sources index.
% \end{function}
%
% \begin{function}{modern authors}
%   \begin{syntax}
%     modern authors = true \textbar\ false\hfill Default: false
%   \end{syntax}
%   Enable an index of modern authors. Modern authors are indexed when cited
%   using \pkg{biblatex-sbl}.
% \end{function}
%
% \begin{function}{modern authors title}
%   \begin{syntax}
%     modern authors title = \meta{title}\hfill Default: Modern Authors Index
%   \end{syntax}
%   Set the title of the modern authors index.
% \end{function}
%
% \begin{function}{subject}
%   \begin{syntax}
%     subject = true \textbar\ false\hfill Default: true
%   \end{syntax}
%   Enable an index of subjects. Subjects are indexed using |makeindex| and
%   the standard \cs{index} macro.
%   using \pkg{biblatex-sbl}.
% \end{function}
%
% \begin{function}{subject title}
%   \begin{syntax}
%     subject title = \meta{title}\hfill Default: Subject Index
%   \end{syntax}
%   Set the title of the subject index.
% \end{function}
%
% \section{Commands}
%
% \begin{function}{\SBLFootnoteIndex}
%   \begin{syntax}
%     \cs{SBLFootnotes} \oarg{name} \marg{entry}
%   \end{syntax}
%   \cs{index} is defined to this macro within footnotes so that note numbers
%   are automatically included unless some other formatting is requested by
%   the user. \meta{name} is the name of the raw index file and \meta{entry}
%   should be formatted according to syntax required by |makeindex|. In
%   general this macro should not be called directly as it automatically
%   includes the current value of the |footnote| counter.
% \end{function}
%
% \begin{function}{\SBLPageWithNote}
%   \begin{syntax}
%     \cs{SBLPageWithNote} \marg{comma separated list of notes} \marg{page}
%   \end{syntax}
%   Output page with note number using n.\ or nn.\ depending on how many notes
%   there are. When |\index| appears in a footnote, this command is called
%   automatically using the |makeindex|
%   \verb+\index{gnat|SBLPageWithNote{\thefootnote}}+ syntax.
% \end{function}
%
% \begin{function}{\SBLPrintIndices}
%   \begin{syntax}
%     \cs{SBLPrintIndices}
%   \end{syntax}
%   Print the indices. This command prints one or more indices depending on
%   which are enabled with the |ancient sources|, |modern authors| and
%   |subject| options.
% \end{function}
%
% \begin{function}{\SBLProcessIndexPages}
%   \begin{syntax}
%     \cs{SBLProcessIndexPages} \marg{list of pages}
%   \end{syntax}
%   Process a list of pages produced by |makeindex|, compressing page ranges
%   and note ranges as needed according to SBL style. This function is called
%   by the required custom index style file |sblidx.ist|.
% \end{function}
%
% \begin{function}{\SBLSetMaxRomanPage}
%   \begin{syntax}
%     \cs{SetMaxRomanPage} \marg{page}
%   \end{syntax}
%   Set the maximum Roman page number (as an integer, not in Roman format) in
%   the document in case automatic detection fails. This should be set before
%   the indices are printed.
% \end{function}
%
% \begin{function}{\see, \seealso}
%   \begin{syntax}
%     \cs{see} \Arg{index entry} \marg{page number} \\
%     \cs{seealso} \Arg{index entry} \marg{page number}
%   \end{syntax}
%   \cs{see} and \cs{seealso} are used for index cross-references. These are
%   slightly redefined from the default as SBL style requires capital letters
%   for \emph{See} and \emph{See also}.  They are called in the standard way
%   for the \cs{index} command:
%
%   \begin{center}
%     \begin{tabular}{ll||l}
%       Page 2: & \verb+\index{at}+ & at, 2 \\
%       Page 2: & \verb+\index{at!bat|see{bat, at}}+ & \quad bat. \emph{See} bat, at
%     \end{tabular}
%   \end{center}
% \end{function}
%
% \section{Implementation}
%
% \setlength{\parindent}{0em}
%
% \subsection{Custom Index Style File}
%
%    \begin{macrocode}
%<*indexstyle>
%    \end{macrocode}
%
% Custom index style file which processes a list of indexed pages with the
% macro \cs{ProcessIndexPages} and removes the default comma delimiter before
% the first page number for an entry. SBL only uses a comma for the Subject
% Index.
%
%    \begin{macrocode}
delim_0 "\\SBLProcessIndexPages{"
delim_1 "\\SBLProcessIndexPages{"
delim_2 "\\SBLProcessIndexPages{"
delim_t "}"
%    \end{macrocode}
%
%    \begin{macrocode}
%</indexstyle>
%    \end{macrocode}
%
% \subsection{Main Package}
%
%    \begin{macrocode}
%<*package>
%<@@=sblidx>
%    \end{macrocode}
%
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesExplPackage{sblidx}{2026-04-04}{1.0}
  {Society of Biblical Literature Indices (DCP)}
%    \end{macrocode}
%
% Load \pkg{imakeidx} and \pkg{idxlayout} with appropriate options. Most
% layout is controlled with \pkg{idxlayout}, but \pkg{imakeidx} provides an
% interface for setting the index title and the convenience of running
% |makeindex| inline.
%
%    \begin{macrocode}
\RequirePackage { imakeidx }
\RequirePackage { idxlayout }
\idxlayout
  {
    , columnsep    = 0.5in
    , font         = small
    , hangindent   = 0.25in
    , initsep      = \bigskipamount
    , itemlayout   = relhang
    , subindent    = 0.25in
    , subsubindent = 0.5in
    , totoc
  }
%    \end{macrocode}
%
% \subsubsection{Define and Process Package Options}
%
%    \begin{macrocode}
\keys_define:nn { sblidx }
  {
    , ancient~sources       .bool_set:N = \l_@@_ancient_sources_bool
    , ancient~sources~title .tl_set:N   = \l_@@_ancient_sources_title_tl
    , ancient~sources~title .initial:n  = Ancient~Sources~Index
    , modern~authors        .bool_set:N = \l_@@_modern_authors_bool
    , modern~authors~title  .tl_set:N   = \l_@@_modern_authors_title_tl
    , modern~authors~title  .initial:n  = Modern~Authors~Index
    , subject               .bool_set:N = \l_@@_subject_bool
    , subject               .initial:n  = true
    , subject~title         .tl_set:N   = \l_@@_subject_title_tl
    , subject~title         .initial:n  = Subject~Index
  }
%    \end{macrocode}
%
%    \begin{macrocode}
\ProcessKeyOptions
%    \end{macrocode}
%
% \subsubsection{Set Up Indices}
%
% The delimiter between the indexed entry and the first page is dependent on
% the index type, so it is set dynamically in \cs{SBLPrintIndices}.
%
%    \begin{macrocode}
\tl_const:Nn \c_@@_delim_tl { , \c_space_tl }
\tl_new:N \l_@@_delim_first_tl
\tl_const:Nn \c_@@_delim_see_tl { . \c_space_tl }
%    \end{macrocode}
%
% Set up code and call \cs{makeindex} for each required index.
%
%    \begin{macrocode}
\bool_if:NT \l_@@_subject_bool
  {
    \makeindex
      [
        , title   = \l_@@_subject_title_tl
        , options = -s ~ sblidx.ist ~ -q
      ]
  }
%    \end{macrocode}
%
%    \begin{macrocode}
\bool_if:NT \l_@@_ancient_sources_bool
  {
    \RequirePackage { bibleref-parse }
    \RequirePackage { bibleref-sbl }
    \makeindex
      [
        name    = \jobname-ancient-sources ,
        title   = \l_@@_ancient_sources_title_tl ,
        options = -s ~ sblidx.ist ~ -q
      ]
    \cs_set_nopar:Npn \biblerefindex
      {
        \index [ \jobname-ancient-sources ]
      }
  }
%    \end{macrocode}
%
%    \begin{macrocode}
\bool_if:NT \l_@@_modern_authors_bool
  {
    \RequirePackage { biblatex }
    \ExecuteBibliographyOptions { indexing = cite }
    \makeindex
      [
        name    = \jobname-modern-authors ,
        title   = \l_@@_modern_authors_title_tl ,
        options = -s ~ sblidx.ist ~ -q
      ]
    \DeclareIndexNameFormat { default }
      {
        \usebibmacro { index:name }
          { \index [ \jobname-modern-authors ] }
          { \namepartfamily }
          { \namepartgiven }
          { \namepartprefix }
          { \namepartsuffix }
      }
  }
%    \end{macrocode}
%
% Set up \cs{footnote} so that \cs{index} includes the footnote number when
% indexing an entry in a footnote.
%
% \begin{macro}{\@@_set_up_footnotes:}Footnote set up code.
%    \begin{macrocode}
\cs_new_protected:Nn \@@_set_up_footnotes:
  {
    \cs_set_eq:NN \@sblidx@index \index
    \bool_if:NT \l_@@_ancient_sources_bool
      {
        \cs_set_nopar:Npn \bvidxpgformat
          { SBLPageWithNote { \thefootnote } }
      }
    \cs_set_nopar:Npn \index { \SBLFootnoteIndex }
  }
%    \end{macrocode}
% \end{macro}
%
% For new footnote code, the set up can be injected with hooks. Otherwise the
% \cmd{footnote} command must be redefined. Note that in the latter case if
% you or some other package again redefines \cs{footnote} after loading
% \pkg{sblidx} automatically including note numbers with indexed entries in
% footnotes will not work.
%
%    \begin{macrocode}
\IfDocumentMetadataTF
  {
    \hook_gput_code:nnn { fntext } { sblidx } { \@@_set_up_footnotes: }
  }
  {
    \cs_set_eq:NN \@@_footnote: \footnote
    \RenewDocumentCommand { \footnote } { o+m }
      {
        \group_begin:
          \@@_set_up_footnotes:
          \tl_if_novalue:nTF {#1}
            { \@@_footnote: {#2} }
            { \@@_footnote: [#1] {#2} }
        \group_end:
      }
  }
%    \end{macrocode}
%
% \begin{macro}{\see, \seealso}
%   SBL want \emph{See} and \emph{See also} to begin a new sentence so
%   redefine \cs{see} and \cs{seealso} to capitalise the first letter of
%   \emph{See}.
%
%    \begin{macrocode}
\cs_set_nopar:Npn \see #1 #2
  {
    \emph { \text_titlecase_first:n { \seename } }
    \c_space_tl #1
  }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_set_nopar:Npn \seealso #1 #2
  {
    \emph { \text_titlecase_first:n { \alsoname } }
    \c_space_tl #1
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\SBLFootnoteIndex} \oarg{name} \marg{entry}
%
%   \medskip
%
%   \cs{index} is defined to this macro within footnotes so that note numbers
%   are automatically included unless some other formatting is requested by
%   the user. \meta{name} is the name of the raw index file and \meta{entry}
%   should be formatted according to syntax required by |makeindex|.
%
%    \begin{macrocode}
\NewDocumentCommand { \SBLFootnoteIndex } { om }
  {
    \tl_if_novalue:nTF {#1}
      {
        \str_if_in:nnTF {#2} { | }
          { \@sblidx@index {#2} }
          {
            \@sblidx@index
              { #2 | SBLPageWithNote { \thefootnote } }
          }
      }
      {
        \str_if_in:nnTF {#2} { | }
          { \@sblidx@index [#1] {#2} }
          {
            \@sblidx@index [ #1 ]
              { #2 | SBLPageWithNote { \thefootnote } }
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\SBLPageWithNote} \marg{comma separated list of notes}
%     \marg{page}
%
%   \medskip
%
%   Output page with note number using n.\ or nn.\ depending on how many notes
%   there are. When |\index| appears in a footnote, this command is called
%   automatically using the |makeindex|
%   \verb+\index{gnat|SBLPageWithNote{\thefootnote}}+ syntax. It allows for a
%   comma separated list of notes because the command |\SBLProcessIndexPages|
%   combines indexed entries in footnotes on the same page into one command
%   with the format |\SBLPageWithNote{x,y,z}{page}|.
%
%    \begin{macrocode}
\cs_new_protected:Npn \SBLPageWithNote #1 #2
  {
    #2
    \c_space_tl
    \int_compare:nNnTF { \clist_count:n {#1} } > { \c_one_int }
      { nn. }
      { n. }
    \nobreakspace
    \@@_compress_note_list:n {#1}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\SBLPrintIndices}
%   Print the indices. This command prints one or more indices depending on
%   which are enabled with the |ancient sources|, |modern authors| and
%   |subject| options.
%
%    \begin{macrocode}
\cs_new_protected:Npn \SBLPrintIndices
  {
    \@@_set_up_hyperref:
    \bool_if:NT \l_@@_ancient_sources_bool
      {
        \group_begin:
          \idxlayout
            {
              subindent    = 0pt ,
              subsubindent = 0.25in ,
              hangindent   = 0.25in
            }
          \cs_set_nopar:Npn \@idxitem
            { \indexspace \bfseries }
          \cs_set_nopar:Npn \subitem
            { \indexspace \normalfont }
          \tl_set:Nn \l_@@_delim_first_tl { \quad \hfill }
          \printindex [ \jobname-ancient-sources ]
        \group_end:
      }
    \bool_if:NT \l_@@_modern_authors_bool
      {
        \group_begin:
          \tl_set:Nn \l_@@_delim_first_tl { \quad \hfill }
          \printindex [ \jobname-modern-authors ]
        \group_end:
      }
    \bool_if:NT \l_@@_subject_bool
      {
        \group_begin:
          \tl_set:Nn \l_@@_delim_first_tl { , \c_space_tl }
          \printindex
        \group_end:
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\SBLProcessIndexPages} \marg{list of pages}
%
%   \medskip
%
%   Process a list of pages produced by |makeindex|, compressing page ranges
%   and note ranges as needed according to SBL style. This function is called
%   by the required custom index style file |sblidx.ist|.
%
%    \begin{macrocode}
\cs_new_protected:Npn \SBLProcessIndexPages #1
  {
    \@@_process_index_pages:n {#1}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\SBLSetMaxRomanPage} \marg{page}
%
%   \medskip
%
%   Set the maximum Roman page number (as an integer, not in Roman format) in
%   the document in case automatic detection fails. This should be set before
%   the indices are printed.
%
%    \begin{macrocode}
\cs_new_protected:Npn \SBLSetMaxRomanPage #1
  {
    \@@_set_max_roman_page:n {#1}
  }
%    \end{macrocode}
% \end{macro}
%
% \subsubsection{Page and Note Compression Code}
%
% The following code handles compression of page numbers and note numbers in
% indices according to SBL requirements.
%
% \begin{macro}{\@@_set_max_roman_page:n} \marg{number}
%
%   \medskip
%
%   Set the maximum Roman page number used in the front matter. This is needed
%   for incrementing page numbers.
%
%    \begin{macrocode}
\int_new:N   \g_@@_max_roman_page_int
\int_gset:Nn \g_@@_max_roman_page_int { 1000 }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_set_max_roman_page:n
  {
    \int_gset:Nn \g_@@_max_roman_page_int {#1}
  }
%    \end{macrocode}
%
%   Although not completely reliable, try to set this automatically when
%   |\mainmatter| is called.
%
%    \begin{macrocode}
\hook_gput_code:nnn { cmd / mainmatter / before } { sblidx }
  {
    \int_gset_eq:NN \g_@@_max_roman_page_int \c@page
    \legacy_if:nT { @twoside }
      {
        \int_if_odd:nT { \c@page }
          { \int_gincr:N \g_@@_max_roman_page_int }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_compress_range:n} \marg{number range}
%
%   \medskip
%
%   Compress a number range according to SBL style. Non-Arabic numbers and
%   non-ranges are returned unaltered.
%
%    \begin{macrocode}
\int_new:N \l_@@_range_start_int
\int_new:N \l_@@_range_end_int
\int_new:N \l_@@_range_length_int
\int_new:N \l_@@_range_end_position_int
\seq_new:N \l_@@_range_seq
\tl_new:N  \l_@@_compressed_range_tl
\tl_new:N  \l_@@_compressed_range_end_tl
\tl_new:N  \l_@@_range_start_tl
\tl_new:N  \l_@@_range_end_tl
%    \end{macrocode}
%
%    \begin{macrocode}
\regex_const:Nn \c_@@_range_regex { \A ( .*? ) -- ( .*? ) \Z }
\regex_const:Nn \c_@@_number_range_regex { \A ( \d+ ) -- ( \d+ ) \Z }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_compress_range:n
  {
    \@@_get_compressed_range_end:n {#1}
    \tl_if_empty:NTF \l_@@_compressed_range_end_tl
      {#1}
      {
        \tl_set:Ne \l_@@_compressed_range_tl
          {
            \l_@@_range_start_tl
            --
            \l_@@_compressed_range_end_tl
          }
        \tl_use:N \l_@@_compressed_range_tl
      }
  }
%    \end{macrocode}
%
%   |\@@_get_compressed_range_end:n| finds the compressed end value for an
%   Arabic number range and stores it in |\l_@@_compressed_range_end_tl|.
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_get_compressed_range_end:n
  {
    \tl_clear:N \l_@@_compressed_range_end_tl
%    \end{macrocode}
%
%   Only Arabic number ranges are compressed
%
%    \begin{macrocode}
    \seq_set_regex_extract_once:NNn
      \l_@@_range_seq \c_@@_number_range_regex {#1}
    \int_compare:nNnTF
      { \seq_count:N \l_@@_range_seq } = { 3 }
      {
        \int_set:Nn \l_@@_range_start_int
          { \seq_item:Nn \l_@@_range_seq { 2 } }
        \int_set:Nn \l_@@_range_end_int
          { \seq_item:Nn \l_@@_range_seq { 3 } }
%    \end{macrocode}
%
%   First number must be greater than 100.
%
%    \begin{macrocode}
        \int_compare:nNnT
          { \l_@@_range_start_int } > { 100 }
          {
%    \end{macrocode}
%
%   First number must not be divisible by 100.
%
%    \begin{macrocode}
            \int_compare:nNnT
              {
                \int_mod:nn
                  { \l_@@_range_start_int }
                  { 100 }
              }
              >
              { \c_zero_int }
              {
%    \end{macrocode}
%
%   Pages must be the same number of digits.
%
%    \begin{macrocode}
                \int_set:Nn \l_@@_range_length_int
                  {
                    \tl_count:e { \int_use:N \l_@@_range_start_int }
                  }
                \int_compare:nNnT
                  { \l_@@_range_length_int }
                  =
                  {
                    \tl_count:e { \int_use:N \l_@@_range_end_int }
                  }
                  {
%    \end{macrocode}
%
%   Main compression code. Step through each digit of the start number in the
%   range until the corresponding digit in the end number is different
%   \emph{or} there are only two digits left. The remaining digits of the end
%   number is the compressed value.
%
%    \begin{macrocode}
                    \tl_set:No \l_@@_range_start_tl
                      { \int_use:N \l_@@_range_start_int }
                    \tl_set:No \l_@@_range_end_tl
                      { \int_use:N \l_@@_range_end_int }
                    \int_zero:N \l_@@_range_end_position_int
                    \tl_map_inline:Nn \l_@@_range_start_tl
                      {
                        \int_incr:N \l_@@_range_end_position_int
                        \int_compare:nNnT
                          { \l_@@_range_end_position_int + 1 }
                          =
                          { \l_@@_range_length_int }
                          {
                            \tl_set:Ne
                              \l_@@_compressed_range_end_tl
                              {
                                \tl_range:Nnn
                                  \l_@@_range_end_tl
                                  { \l_@@_range_end_position_int }
                                  { \l_@@_range_length_int }
                              }
                            \tl_map_break:
                          }
                        \tl_if_eq:neF
                          {##1}
                          {
                            \tl_item:Nn
                              \l_@@_range_end_tl
                              { \l_@@_range_end_position_int }
                          }
                          {
                            \tl_set:Ne
                              \l_@@_compressed_range_end_tl
                              {
                                \tl_range:Nnn
                                  \l_@@_range_end_tl
                                  { \l_@@_range_end_position_int }
                                  { \l_@@_range_length_int }
                              }
                            \tl_map_break:
                          }
                      }
%    \end{macrocode}
%
%   Remove leading zeroes from compressed range.
%
%    \begin{macrocode}
                    \tl_regex_replace_once:Nnn
                      \l_@@_compressed_range_end_tl
                      { \A 0+ }
                      { }
                  }
              }
          }
      }
      {
%    \end{macrocode}
%
%   Check for non-Arabic range.
%
%    \begin{macrocode}
        \seq_set_regex_extract_once:NNn
          \l_@@_range_seq \c_@@_range_regex {#1}
        \seq_if_empty:NF \l_@@_range_seq
          {
            \tl_set:Ne \l_@@_range_start_tl
              { \seq_item:Nn \l_@@_range_seq { 2 } }
            \tl_set:Ne \l_@@_range_end_tl
              { \seq_item:Nn \l_@@_range_seq { 3 } }
            \tl_set_eq:NN
              \l_@@_compressed_range_end_tl \l_@@_range_end_tl
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_compress_note_range:n, \@@_compress_page_range:n,
%     \@@_hyper_compress_page_rage:n} \marg{number range}
%
%   \medskip
%
%   Compress a note or page range according to SBL style. Non-Arabic numbers
%   and non-ranges are returned unaltered. \pkg{hyperref} is supported for
%   page numbers by |\@@_hyper_compress_page_range:n|.
%
%    \begin{macrocode}
\cs_set:Nn \@@_compress_note_range:n
  { \@@_compress_range:n {#1} }
\cs_set:Nn \@@_compress_page_range:n
  { \@@_compress_range:n {#1} }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_hyper_compress_page_range:n
  {
    \@@_get_compressed_range_end:n {#1}
    \tl_if_empty:NTF \l_@@_compressed_range_end_tl
      {
        \hyperlink { page. #1 } {#1}
      }
      {
        \tl_set:Ne \l_@@_compressed_range_tl
          {
            \exp_not:N \hyperlink
              { page. \l_@@_range_start_tl }
              { \l_@@_range_start_tl }
            --
            \exp_not:N \hyperlink
              { page. \l_@@_range_end_tl }
              { \l_@@_compressed_range_end_tl }
          }
        \tl_use:N \l_@@_compressed_range_tl
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_do_page:n, \@@_hyper_do_page:n} \marg{page}
%
%   Create a hyperlink for \meta{page}. This does nothing if \pkg{hyperref} is
%   not loaded.
%
%    \begin{macrocode}
\cs_new:Nn \@@_do_number:n {#1}
\cs_new:Nn \@@_do_note:n {#1}
\cs_new:Nn \@@_do_page:n {#1}
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new:Nn \@@_hyper_do_page:n
  {
    \exp_not:N \hyperlink { page. #1 } {#1}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_compress_list:nN} \marg{sorted numbers}
%     \meta{compressed list}
%
%   \medskip
%
%   Format a comma separated list of page or note numbers to use SBL style
%   compressed ranges when there are three or more consecutive numbers and
%   store the result in the \meta{compressed list} |clist| variable.
%
%    \begin{macrocode}
\clist_new:N \l_@@_uncompressed_clist
\tl_new:N    \l_@@_start_tl
\tl_new:N    \l_@@_previous_tl
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_compress_list:nN
  {
    \clist_set:Nn \l_@@_uncompressed_clist {#1}
    \clist_clear:N #2
    \clist_pop:NNT \l_@@_uncompressed_clist \l_@@_start_tl
      {
        \tl_set_eq:NN \l_@@_previous_tl \l_@@_start_tl
        \clist_map_inline:Nn \l_@@_uncompressed_clist
          {
            \@@_page_compare:eNeTF
              {##1}
              =
              { \@@_page_incr:V \l_@@_previous_tl }
              {
                \tl_set:Nn \l_@@_previous_tl {##1}
              }
              {
                \@@_append_compressed_list:VVN
                  \l_@@_start_tl \l_@@_previous_tl #2
                \tl_set:Nn \l_@@_start_tl {##1}
                \tl_set:Nn \l_@@_previous_tl {##1}
              }
          }
        \@@_append_compressed_list:VVN
          \l_@@_start_tl \l_@@_previous_tl #2
      }
  }
%    \end{macrocode}
%
%   |\@@_append_compressed_list:nnN| and |\@@_append_compressed_list:VVN| are
%   helper functions called by |\@@_compress_list:nN| to append the next
%   number or number range to the current list when building a compressed
%   list.
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_append_compressed_list:nnN
  {
    \@@_page_compare:eNeTF {#2} > { \@@_page_incr:n {#1} }
      {
        \clist_put_right:Ne #3
          { \@@_do_compress_range:n { #1 -- #2 } }
      }
      {
        \clist_put_right:Nn #3
          {
            \@@_do_number:n {#1}
          }
        \tl_if_eq:nnF {#1} {#2}
          {
            \clist_put_right:Nn #3
              {
                \@@_do_number:n {#2}
              }
          }
      }
  }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_generate_variant:Nn \@@_append_compressed_list:nnN { VVN }
%    \end{macrocode}
%
%   |\@@_do_compress_range:n| needs to be set to either
%   |\@@_compress_note_range:n| or |\@@_compress_page_range:n| before calling
%   to ensure only page numbers are hyperlinked when \pkg{hyperref} is loaded.
%
%    \begin{macrocode}
\cs_set:Nn \@@_do_compress_range:n
  { \@@_compress_range:n {#1} }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_compress_note_list:n} \marg{sorted note numbers}
%
%   \medskip
%
%   Format a comma separated list of note numbers to use SBL style compressed
%   ranges when there are three or more consecutive numbers.
%
%    \begin{macrocode}
\clist_new:N \l_@@_compressed_clist
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_compress_note_list:n
  {
    \cs_set:Nn \@@_do_compress_range:n
      { \@@_compress_note_range:n {##1} }
    \cs_set:Nn \@@_do_number:n
      { \@@_do_note:n {##1} }
    \@@_compress_list:nN {#1} \l_@@_compressed_clist
    \clist_use:Nnnn
      \l_@@_compressed_clist
      { ~ and ~ } { , ~ } { , ~ and ~ }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_compress_page_list:nN, \@@_compress_page_list:VN}
%     \marg{sorted page numbers} \meta{list of compressed page}
%
%   \medskip
%
%   Format a comma separated list of page numbers to use SBL style compressed
%   ranges when there are three or more consecutive numbers.
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_compress_page_list:nN
  {
    \cs_set:Nn \@@_do_compress_range:n
      { \@@_compress_page_range:n {##1} }
    \cs_set:Nn \@@_do_number:n
      { \@@_do_page:n {##1} }
    \@@_compress_list:nN {#1} \l_@@_compressed_clist
    \tl_set:Ne
      #2
      {
        \clist_use:Nn \l_@@_compressed_clist { \c_@@_delim_tl }
      }
  }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_generate_variant:Nn \@@_compress_page_list:nN { VN }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_page_compare:nNnTF, \@@_page_compare:VNVT,
%     \@@_page_compare:eNeTF} \marg{first page number} \meta{relation}
%     \marg{second page number} \marg{true code} \marg{false code}
%
%   \medskip
%
%   Compare pages taking into account Roman and Arabic page numbering systems.
%   Note that this macro only takes into account a standard book with the
%   front matter numbered with Roman numbers and the main matter numbered with
%   Arabic numbers. The relations supported are |=|, |>| and |<|. |T| and
%   |TF| variants are available.
%
%    \begin{macrocode}
\int_new:N \l_@@_first_int
\int_new:N \l_@@_second_int
%    \end{macrocode}
%
%    \begin{macrocode}
\prg_new_protected_conditional:Npnn \@@_page_compare:nNn #1 #2 #3
  { T, TF }
  {
%    \end{macrocode}
%
%   Offset Arabic page numbers by |\g_@@_max_roman_page_int| so they are
%   always greater than any Roman page number.
%
%    \begin{macrocode}
    \regex_if_match:nnTF { \A \d+ \Z } {#1}
      {
        \int_set:Nn \l_@@_first_int
          { #1 + \g_@@_max_roman_page_int }
      }
      {
        \int_set:Nn \l_@@_first_int
          { \exp_args:Ne \int_from_roman:n {#1} }
      }
    \regex_if_match:nnTF { \A \d+ \Z } {#3}
      {
        \int_set:Nn \l_@@_second_int
          { #3 + \g_@@_max_roman_page_int }
      }
      {
        \int_set:Nn \l_@@_second_int
          { \exp_args:Ne \int_from_roman:n {#3} }
      }
%    \end{macrocode}
%
%   Now we just need a simple integer comparison.
%
%    \begin{macrocode}
    \if_int_compare:w \l_@@_first_int #2 \l_@@_second_int
      \prg_return_true:
    \else:
      \prg_return_false:
    \fi:
  }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_generate_variant:Nn \@@_page_compare:nNnT  { VNVT }
\cs_generate_variant:Nn \@@_page_compare:nNnTF { eNeTF }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_page_incr:n} \marg{page number}
%
%   \medskip
%
%   Increment a page number taking into account whether it is Roman or Arabic,
%   leaving the result on the input stream. Trying to increment something
%   other than a Roman or positive Arabic number results in an empty token
%   list. This function is fully expandable, but as a result the test for an
%   Arabic page number checks if the first character is a digit and otherwise
%   assumes the input is roman.
%
%    \begin{macrocode}
\cs_new:Nn \@@_page_incr:n
  {
    \exp_args:Ne \@@_page_incr:nn { \str_head:n {#1} } {#1}
  }
%    \end{macrocode}
%
% |\@@_page_incr:nn| is a helper function that allows |\@@_page_incr:n| to be
% fully expandable.
%
%    \begin{macrocode}
\cs_new:Nn \@@_page_incr:nn
  {
    \str_case:nnTF {#1}
      {
        { 0 } { } { 1 } { } { 2 } { } { 3 } { } { 4 } { }
        { 5 } { } { 6 } { } { 7 } { } { 8 } { } { 9 } { }
      }
      {
        \int_eval:n { #2 + 1 }
      }
      {
        \int_compare:nNnTF
          { \int_from_roman:n {#2} } = { \g_@@_max_roman_page_int }
          { 1 }
          { \int_to_roman:n { \int_from_roman:n {#2} + 1 } }
      }
  }
%    \end{macrocode}
%  
%    \begin{macrocode}
\cs_generate_variant:Nn \@@_page_incr:n { V }
%    \end{macrocode}
% \end{macro}
%
% \subsubsection{Index Processing Code}
%
% The following code processes page numbers and notes for an index entry and
% produces an index entry in SBL style.
%
% \begin{macro}{\@@_set_up_hyperref:}
%
%   The \pkg{hyperref} package changes the format of the index, so needs
%   special handling at a number of points.
%
%    \begin{macrocode}
\bool_new:N  \l_@@_hyperref_bool
\regex_new:N \l_@@_page_regex
\regex_new:N \l_@@_page_encap_regex
\regex_new:N \l_@@_see_also_regex
\regex_new:N \l_@@_page_with_note_regex
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_set_up_hyperref:
  {
    \cs_if_exist:NTF \hyperxindexformat
      {
        \bool_set_true:N \l_@@_hyperref_bool
        \cs_set:Nn \@@_compress_page_range:n
          { \@@_hyper_compress_page_range:n {##1} }
        \cs_set:Nn \@@_do_page:n
          { \@@_hyper_do_page:n {##1} }
        \regex_set:Nn \l_@@_page_regex
          { \A \c{ hyperpage } \cB\{ ( .*? ) \cE\} \Z }
        \regex_set:Nn \l_@@_page_encap_regex
          {
            \A \c{ hyperxindexformat } \cB\{ ( \c{ .* } .* ) \cE\}
              \cB\{ ( .*? ) \cE\} \Z
          }
        \regex_set:Nn \l_@@_see_also_regex
          {
            \A \c{ hyperxindexformat }
              \cB\{ ( [ \c{ see } \c{ seealso } ] .* ) \cE\}
              \cB\{ .*? \cE\} \Z
          }
        \regex_set:Nn \l_@@_page_with_note_regex
          {
            \A \c{ hyperxindexformat }
              \cB\{ \c{ SBLPageWithNote } \cB\{ ( .*? ) \cE\} \cE\}
              \cB\{ ( .*? ) \cE\} \Z
          }
      }
      {
        \cs_set:Nn \@@_compress_page_range:n
          { \@@_compress_range:n {##1} }
        \cs_set:Nn \@@_do_page:n {##1}
        \regex_set:Nn \l_@@_page_encap_regex
          { \A ( \c{ .* } .* ) \cB\{ ( .*? ) \cE\} \Z }
        \regex_set:Nn \l_@@_see_also_regex
          { \A ( [ \c{ see } \c{ seealso } ] .* ) \cB\{ .*? \cE\} \Z }
        \regex_set:Nn \l_@@_page_with_note_regex
          {
            \A \c{ SBLPageWithNote }
              \cB\{ ( .*? ) \cE\} \cB\{ ( .*? ) \cE\} \Z
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\l_@@_page_data_seq}
%
% \medskip
%
% Create a data structure for storing and manipulating page data. These can
% then added to the |\l_@@_page_data_seq| sequence. All terms should always be
% specified and their values should be braced. E.g.,
% |page={1},note={},encap={}|.
%
%    \begin{macrocode}
\seq_new:N       \l_@@_page_data_seq
\tl_new:N        \l_@@_page_tl
\tl_new:N        \l_@@_note_tl
\cs_new_nopar:Nn \@@_encap: { \exp_not:n {} }
%    \end{macrocode}
%
%    \begin{macrocode}
\keys_define:nn { sblidx / pagedata }
  {
    , page  .tl_set:N = \l_@@_page_tl
    , note  .tl_set:N = \l_@@_note_tl
    , encap .code:n   = \cs_set_nopar:Nn \@@_encap:
                          { \exp_not:n {#1} }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_sort_page_data:N} \meta{page data sequence}
%
%   \medskip
%
%   Sort the specified page data sequence by page number. Encapsulated page
%   numbers sort ahead of plain page numbers which sort ahead of page numbers
%   with notes.
%
%    \begin{macrocode}
\bool_new:N \l_@@_sort_returned_bool
\cs_set_nopar:Nn \@@_empty_encap: { \exp_not:n {} }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_sort_page_data:N
  {
    \seq_sort:Nn #1
      {
        \bool_set_false:N \l_@@_sort_returned_bool
        \keys_set:nn { sblidx / pagedata } {##1}
        \tl_set_eq:NN \l_tmpa_tl \l_@@_page_tl
        \tl_set_eq:NN \l_tmpb_tl \l_@@_note_tl
        \cs_set_eq:NN \@@_tmpa: \@@_encap:
        \keys_set:nn { sblidx / pagedata } {##2}
%    \end{macrocode}
%
%   Page is primary sort key.
%
%    \begin{macrocode}
        \@@_page_compare:VNVT \l_tmpa_tl > \l_@@_page_tl
          {
            \bool_set_true:N \l_@@_sort_returned_bool
            \sort_return_swapped:
          }
        \@@_page_compare:VNVT \l_tmpa_tl < \l_@@_page_tl
          {
            \bool_set_true:N \l_@@_sort_returned_bool
            \sort_return_same:
          }
        \tl_if_eq:NNT \l_tmpa_tl \l_@@_page_tl
          {
%    \end{macrocode}
%
%   If the page is the same, then calculate a sorting weight for each item.
%
%    \begin{macrocode}
            \@@_page_data_weight:NNN
              \l_tmpb_tl \@@_tmpa: \l_tmpa_int
            \@@_page_data_weight:NNN
              \l_@@_note_tl \@@_encap: \l_tmpb_int
            \int_compare:nNnT { \l_tmpa_int } < { \l_tmpb_int }
              {
                \bool_set_true:N \l_@@_sort_returned_bool
                \sort_return_same:
              }
            \int_compare:nNnT { \l_tmpa_int } > { \l_tmpb_int }
              {
                \bool_set_true:N \l_@@_sort_returned_bool
                \sort_return_swapped:
              }
%    \end{macrocode}
%
%   If the sorting weight indicates both items have a note then sort by note.
%
%    \begin{macrocode}
            \int_compare:nNnT { \l_tmpa_int } = { \l_tmpb_int }
              {
                \bool_set_true:N \l_@@_sort_returned_bool
                \int_compare:nNnTF { \l_tmpa_int } = { 2 }
                  {
                    \tl_if_empty:NTF \l_@@_note_tl
                      { \int_zero:N \l_tmpa_int }
                      { \int_set:Nn \l_tmpa_int { \l_@@_note_tl } }
                    \tl_if_empty:NTF \l_tmpb_tl
                      { \int_zero:N \l_tmpb_int }
                      { \int_set:Nn \l_tmpb_int { \l_tmpb_tl } }
                    \int_compare:nNnTF { \l_tmpa_int } < { \l_tmpb_int }
                      { \sort_return_swapped: }
                      { \sort_return_same: }
                  }
                  { \sort_return_same: }
              }
          }
%    \end{macrocode}
%
%   Make sure we return for a cases where |\l_@@_page_tl| is a roman numeral
%   range.
%
%    \begin{macrocode}
        \bool_if:NF \l_@@_sort_returned_bool
          { \sort_return_same: }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_page_data_weight:NNN} \meta{note} \meta{encap}
%     \meta{weight}
%
%   \medskip
%
%   Return a sorting weight depending on the presence and value of \meta{note}
%   and \meta{encap}. Set the \meta{weight} integer variable to |0| if
%   \meta{note} is an empty token list and \meta{encap} is |\@@_empty_encap:|,
%   |1| if there is a \meta{note} and \meta{encap} is |\@@_empty_encap:| and
%   |2| if \meta{note} is an empty token list and \meta{encap} is not
%   |\@@_empty_encap:|.
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_page_data_weight:NNN
  {
    \cs_if_eq:NNTF #2 \@@_empty_encap:
      {
        \tl_if_empty:NTF #1
          {
            \int_set:Nn #3 { \c_one_int }
          }
          {
            \int_set:Nn #3 { 2 }
          }
      }
      { \int_zero:N #3 }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_page_data_to_tl:N} \meta{page data sequence}
%     \meta{token list}
%
%   \medskip
%
%   Convert a page data sequence to a token list applying compression and
%   encapsulation. The page data sequence must already be sorted and
%   consolidated.
%
%    \begin{macrocode}
\bool_new:N \l_@@_item_added_bool
\tl_new:N   \l_@@_compressed_page_tl
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_page_data_to_tl:N
  {
    \tl_clear:N \l_tmpa_tl
    \seq_map_inline:Nn #1
      {
        \bool_set_false:N \l_@@_item_added_bool
        \keys_set:nn { sblidx / pagedata } {##1}
        \@@_compress_page_list:VN
          \l_@@_page_tl
          \l_@@_compressed_page_tl
        \cs_if_eq:NNF \@@_encap: \@@_empty_encap:
          {
%    \end{macrocode}
%
% Test if the encap is |\see| or |\seealso| and if it is insert a period
% instead of a comma.
%
%    \begin{macrocode}
            \tl_set:Ne \l_tmpb_tl { \@@_encap: }
            \tl_if_in:NnTF \l_tmpb_tl { \see }
              {
                \tl_put_right:NV \l_tmpa_tl \c_@@_delim_see_tl
              }
              {
                \tl_if_in:NnTF \l_tmpb_tl { \seealso }
                  {
                    \tl_put_right:NV \l_tmpa_tl \c_@@_delim_see_tl
                  }
                  {
                    \tl_if_empty:NTF \l_tmpa_tl
                      { \tl_set:NV \l_tmpa_tl \l_@@_delim_first_tl }
                      { \tl_put_right:NV \l_tmpa_tl \c_@@_delim_tl }
                  }
              }
            \tl_put_right:Ne \l_tmpa_tl
              {
                \@@_encap: { \l_@@_compressed_page_tl }
              }
            \bool_set_true:N \l_@@_item_added_bool
          }
        \bool_if:NF \l_@@_item_added_bool
          {
            \tl_if_empty:NF \l_@@_note_tl
              {
                \tl_if_empty:NTF \l_tmpa_tl
                  { \tl_set:NV \l_tmpa_tl \l_@@_delim_first_tl }
                  { \tl_put_right:NV \l_tmpa_tl \c_@@_delim_tl }
                \tl_put_right:Ne \l_tmpa_tl
                  {
                    \SBLPageWithNote
                      { \l_@@_note_tl }
                      { \l_@@_compressed_page_tl }
                  }
                \bool_set_true:N \l_@@_item_added_bool
              }
          }
        \bool_if:NF \l_@@_item_added_bool
          {
            \tl_if_empty:NTF \l_tmpa_tl
              { \tl_set:NV \l_tmpa_tl \l_@@_delim_first_tl }
              { \tl_put_right:NV \l_tmpa_tl \c_@@_delim_tl }
            \tl_put_right:Ne \l_tmpa_tl
              { \l_@@_compressed_page_tl }
          }
      }
%    \end{macrocode}
%
% Put formatted index pages on to the input stream.
%
%    \begin{macrocode}
    \tl_use:N \l_tmpa_tl
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\g_@@_status_code_int}
%
%   The functions below set a status code so that the result of the most
%   recent function can be tested.
%
%    \begin{macrocode}
\bool_new:N \l_@@_stepping_page_bool
\int_new:N  \g_@@_status_code_int
\tl_new:N   \l_@@_end_tl
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_range_to_clist:nN, \@@_range_to_clist:eN}
%     \marg{range} \meta{list}
%
%   \medskip
%
%   Convert a range of the form \meta{integer}|--|\meta{integer} to a comma
%   separated list of integers, leaving the result in \meta{list} |clist|
%   variable. If the form is not found the input is added unaltered to
%   \meta{list}. A status code of |0| indicates that a range was found and
%   successfully added to \meta{list}.
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_range_to_clist:nN
  {
    \seq_set_regex_extract_once:NNn
      \l_tmpb_seq \c_@@_range_regex {#1}
    \seq_if_empty:NTF \l_tmpb_seq
      {
        \clist_set:Nn #2 {#1}
        \int_gset:Nn \g_@@_status_code_int { \c_one_int }
      }
      {
        \clist_clear:N #2
        \bool_set_true:N \l_@@_stepping_page_bool
        \tl_set:Ne \l_@@_page_tl { \seq_item:Nn \l_tmpb_seq { 2 } }
        \tl_set:Ne \l_@@_end_tl { \seq_item:Nn \l_tmpb_seq { 3 } }
        \bool_do_while:nn { \l_@@_stepping_page_bool }
          {
            \clist_put_right:NV #2 \l_@@_page_tl
            \tl_set:Ne \l_@@_page_tl
              { \@@_page_incr:V \l_@@_page_tl }
            \@@_page_compare:VNVT \l_@@_page_tl = \l_@@_end_tl
              {
                \clist_put_right:NV #2 \l_@@_page_tl
                \bool_set_false:N \l_@@_stepping_page_bool
              }
          }
        \int_gzero:N \g_@@_status_code_int
      }
  }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_generate_variant:Nn \@@_range_to_clist:nN { eN }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_check_page:n} \marg{index item}
%
%   \medskip
%
%   Assume an \meta{index item} is a plain page or range and append it the
%   page to the \meta{page data} sequence. A status code of |0| indicates a
%   page or range was successfully appended to \meta{page data}.
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_check_page:n
  {
    \tl_clear:N \l_tmpa_tl
    \bool_if:NTF \l_@@_hyperref_bool
      {
        \seq_set_regex_extract_once:NNn
          \l_tmpa_seq \l_@@_page_regex {#1}
        \seq_if_empty:NF \l_tmpa_seq
          {
            \tl_set:Ne \l_tmpa_tl { \seq_item:Nn \l_tmpa_seq { 2 } }
          }
      }
      {
        \tl_set:Nn \l_tmpa_tl {#1}
      }
    \tl_if_empty:NTF \l_tmpa_tl
      {
        \int_gset:Nn \g_@@_status_code_int { \c_one_int }
      }
      {
        \clist_set:NV \l_tmpa_clist \l_tmpa_tl
        \clist_map_inline:Nn \l_tmpa_clist
          {
            \@@_range_to_clist:nN {##1} \l_tmpb_clist
            \clist_map_inline:Nn \l_tmpb_clist
              {
                \seq_put_right:Ne \l_@@_page_data_seq
                  {
                    page  = {####1} ,
                    note  = { } ,
                    encap = { }
                  }
              }
          }
        \int_gzero:N \g_@@_status_code_int
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_check_page_encap:n} \marg{index item}
%
%   \medskip
%
%   Check an \meta{index item} if it is encapsulated. If it is then append the
%   page and encap function to the \meta{page data} sequence. A status code of
%   |0| indicates an encapsulated page was found and successfully appended to
%   \meta{page data}.
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_check_page_encap:n
  {
    \seq_set_regex_extract_once:NNn
      \l_tmpa_seq \l_@@_page_encap_regex {#1}
    \seq_if_empty:NTF \l_tmpa_seq
      {
        \int_gset:Nn \g_@@_status_code_int { \c_one_int }
      }
      {
        \@@_range_to_clist:eN
          { \seq_item:Nn \l_tmpa_seq { 3 } }
          \l_tmpa_clist
        \clist_map_inline:Nn \l_tmpa_clist
          {
            \seq_put_right:Ne \l_@@_page_data_seq
              {
                page  = {##1} ,
                note  = { } ,
                encap = { \seq_item:Nn \l_tmpa_seq { 2 } }
              }
          }
        \int_gzero:N \g_@@_status_code_int
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_check_see_also:n} \marg{index item}
%
%   \medskip
%
%   Check an \meta{index item} if it use |\see| or |\seealso|. If it does then
%   append to the \meta{page data} sequence with a large page number to ensure
%   correct sorting. A status code of |0| indicates |\see| or |\seealso| was
%   found and successfully appended to \meta{page data}.
%
%    \begin{macrocode}
\int_new:N \l_@@_see_also_int
\int_set:Nn \l_@@_see_also_int { 100000 }
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_check_see_also:n
  {
    \seq_set_regex_extract_once:NNn
      \l_tmpa_seq \l_@@_see_also_regex {#1}
    \seq_if_empty:NTF \l_tmpa_seq
      {
        \int_gset:Nn \g_@@_status_code_int { \c_one_int }
      }
      {
        \int_incr:N \l_@@_see_also_int
        \seq_put_right:Ne \l_@@_page_data_seq
          {
            page  = { \int_use:N \l_@@_see_also_int } ,
            note  = { } ,
            encap = { \seq_item:Nn \l_tmpa_seq { 2 } }
          }
        \int_gzero:N \g_@@_status_code_int
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_check_page_with_note:n} \marg{index item}
%
%   \medskip
%
%   Check an \meta{index item} if it is in the form
%   \cs{SBLPageWithNote}\marg{note}\marg{page}. If it is then append the page
%   and note to the \meta{page data} sequence. A status code of |0| indicates
%   a page with a note was found and successfully appended to \meta{page
%   data}.
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_check_page_with_note:n
  {
    \seq_set_regex_extract_once:NNn
      \l_tmpa_seq \l_@@_page_with_note_regex {#1}
    \seq_if_empty:NTF \l_tmpa_seq
      {
        \int_gset:Nn \g_@@_status_code_int { \c_one_int }
      }
      {
        \seq_put_right:Ne \l_@@_page_data_seq
          {
            page  = { \seq_item:Nn \l_tmpa_seq { 3 } } ,
            note  = { \seq_item:Nn \l_tmpa_seq { 2 } } ,
            encap = { }
          }
        \int_gzero:N \g_@@_status_code_int
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_consolidate_page_data:N} \meta{page data}
%
%   \medskip
%
%   Loop through a \meta{page data} sequence combining consecutive pages into
%   single terms, notes into single pages and removing unneeded pages.
%
%    \begin{macrocode}
\bool_new:N  \l_@@_put_page_data_bool
\clist_new:N \l_@@_notes_clist
\clist_new:N \l_@@_pages_clist
%    \end{macrocode}
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_consolidate_page_data:N
  {
    \keys_set:ne { sblidx / pagedata }
      {
        \seq_item:Nn #1 { \c_one_int }
      }
    \clist_set:NV \l_@@_pages_clist \l_@@_page_tl
    \clist_set:NV \l_@@_notes_clist \l_@@_note_tl
    \cs_set_eq:NN \@@_encap_previous: \@@_encap:
    \seq_clear:N \l_tmpa_seq
    \int_step_inline:nnn { 2 } { \seq_count:N #1 }
      {
        \keys_set:ne { sblidx / pagedata }
          { \seq_item:Nn #1 {##1} }
        \tl_if_eq:eeTF
          { \l_@@_page_tl }
          { \clist_item:Nn \l_@@_pages_clist { -1 } }
          {
            \clist_if_empty:NF \l_@@_notes_clist
              {
                \clist_put_right:NV
                  \l_@@_notes_clist \l_@@_note_tl
              }
          }
          {
            \bool_set_false:N \l_@@_put_page_data_bool
%    \end{macrocode}
%
% If the |encap| changes then add current data to page data sequence.
%
%    \begin{macrocode}
            \cs_if_eq:NNF \@@_encap: \@@_encap_previous:
              { \bool_set_true:N \l_@@_put_page_data_bool }
%    \end{macrocode}
%
% If there are notes in the list then add current data to page data sequence.
%
%    \begin{macrocode}
            \clist_if_empty:NF \l_@@_notes_clist
              { \bool_set_true:N \l_@@_put_page_data_bool }
%    \end{macrocode}
%
% If the current item has a note then add current data to page data sequence.
%
%    \begin{macrocode}
            \tl_if_empty:NF \l_@@_note_tl
              { \bool_set_true:N \l_@@_put_page_data_bool }
%    \end{macrocode}
%
% Either add data to the page data sequence or append the current page to a
% page list.
%
%    \begin{macrocode}
            \bool_if:NTF \l_@@_put_page_data_bool
              {
                \seq_put_right:Ne \l_tmpa_seq
                  {
                    page  = { \clist_use:N \l_@@_pages_clist } ,
                    note  = { \clist_use:N \l_@@_notes_clist } ,
                    encap = { \@@_encap_previous: }
                  }
                \clist_set:NV \l_@@_pages_clist \l_@@_page_tl
                \clist_set:NV \l_@@_notes_clist \l_@@_note_tl
                \cs_set_eq:NN \@@_encap_previous: \@@_encap:
              }
              {
                \clist_put_right:NV
                  \l_@@_pages_clist \l_@@_page_tl
              }
          }
      }
    \seq_put_right:Ne \l_tmpa_seq
      {
        page  = { \clist_use:N \l_@@_pages_clist } ,
        note  = { \clist_use:N \l_@@_notes_clist } ,
        encap = { \@@_encap_previous: }
      }
    \seq_set_eq:NN #1 \l_tmpa_seq
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_process_index_pages:n} \marg{index pages}
%
%   \medskip
%
%   Process a list of pages produced by |makeindex|, compressing page ranges
%   and note ranges as needed according to SBL style.
%
%    \begin{macrocode}
\cs_new_protected:Nn \@@_process_index_pages:n
  {
    \seq_clear:N \l_@@_page_data_seq
    \int_set:Nn \l_@@_see_also_int { 100000 }
    \clist_map_inline:nn { #1 }
      {
        \@@_check_page_with_note:n {##1}
        \int_compare:nNnF { \g_@@_status_code_int } = { \c_zero_int }
          { \@@_check_see_also:n {##1} }
        \int_compare:nNnF { \g_@@_status_code_int } = { \c_zero_int }
          { \@@_check_page_encap:n {##1} }
        \int_compare:nNnF { \g_@@_status_code_int } = { \c_zero_int }
          { \@@_check_page:n {##1} }
      }
    \@@_sort_page_data:N \l_@@_page_data_seq
    \@@_consolidate_page_data:N \l_@@_page_data_seq
    \@@_page_data_to_tl:N \l_@@_page_data_seq
  }
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
\endinput
%    \end{macrocode}
%
%    \begin{macrocode}
%</package>
%    \end{macrocode}
