% \iffalse meta-comment % % Copyright (c) 2026 David Purton % % 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} % % \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 % % \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} % % \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} % % \end{macrocode}