--- title: "crossrefenum" subtitle: "Smart typesetting of enumerated cross-references for various TeX formats" author: Bastien Dumont email: bastien [dot] dumont [at] posteo [dot] net date: "2025/08/07" version: 1.2 lang: en-US toc: true license: true --- _crossrefenum_ lets TeX manage the formatting of bunches of cross-references for you. It features: * Automatic collapsing of references; * Support for references by various criteria: * page and note number; * in ConTeXt: line number; * in LaTeX, when used in conjunction with _reledmac_: edpage and edline. * Handling of references combining two criteria (e.g. by page and note number); * Extension mechanisms to add support to other types of references without modifying the internal macros. Note that sorting is not supported. I assume that users know in what order the labels they refer to appear in their document. It is written in Plain TeX as much as possible in order to make it compatible with a wide array of formats. For the moment, it works out of the box with ConTeXt and LaTeX. Its only (optional) dependency is [`expkv`](https://ctan.org/pkg/expkv-bundle), but it may require other packages to work properly in some formats (e.g. [`zref`](https://ctan.org/pkg/zref) in LaTeX). The file `main-test.pdf` provides a showcase of the abilities of _crossrefenum_. ## Loading To load _crossrefenum_, provided that `crossrefenum.tex`, `crossrefenum.sty` and `t-crossrefenum.tex` are installed in a directory where TeX will find them (presumably under the `tex/generic/crossrefenum` directory of one of your _texmf_ trees), you can do: * `\input{crossrefenum}` (generic); * `\usepackage{crossrefenum}` (LaTeX; must be called after _nameref_ if you use _hyperref_`\kern1.5pt`{=context}); * `\usemodule[crossrefenum]` (ConTeXt). ## Summary {#summary} * [`\crossrefenum`](#macro-crossrefenum): the main macro * [`\crfnmsetup`](#crfnmsetup-macro): configuration macro (default of per type) ```{=context} \vskip\medskipamount ``` +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |**Configuration |**Configuration macro (with `\def`)** |**Meaning** |**Example** |**Page** | |key (with | | | | | |`\crfnmsetup`)** | | | | | +=================+===============================================+=====================+==============+==============================+ |`` _is a single type_ |`page` | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`sg` |`\crfnm` |Singular prefix |`{p. }` |`\at[prefixes]`{=context} | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`pl` |`\crfnms` |Plural prefix |`{pp. }` |`\at[prefixes]`{=context} | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`delimiter` |`\crfnmEnumDelim` |Delimiter between |`{, }` |`\at[delimiters]`{=context} | | | |references | | | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`before last |`\crfnmBeforeLastInEnum` |Delimiter before the |`{ and }` |`\at[delimiters]`{=context} | |reference` | |last reference | | | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`range separator`|`\crfnmRangeSep` |Separator between the|`{\tt |`\at[range-sep]`{=context} | | | |two values in a range|`{=context} – | | | | | |`}`{=context} | | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`collapsable?` |`\crfnmCollapsable` |Should consecutive |`yes` or `no` |`\at[collapsable]`{=context} | | | |numbers (e.g. 2, 3, | | | | | |4) be merged into a | | | | | |range? | | | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`` _is a double type_ |`pagenote` | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`subtypes |`\crfnmSubtypesSep` |Separator between the|`{, }` |`\at[subtypes-sep]`{=context} | |separator` | |two types of | | | | | |references in a | | | | | |double reference | | | | | |(e.g. between the | | | | | |page and note numbers| | | | | |in a reference to a | | | | | |note including the | | | | | |page number) | | | | | | | | | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`print prefix of |`\crfnmPrintFirstPrefix` |Should the numbers of|`always` or |`\at[fst-pref-dbl]`{=context} | |first subtype` | |the first subtype be |`once` | | | | |prefixed always or | | | | | |only for the first | | | | | |reference in an | | | | | |enumeration? | | | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`group subtypes?`|`\crfnmGroupSubtypes` |Sould all the values |`yes` or `no` |`\at[group-subt]`{=context} | | | |for each subtype be | | | | | |printed separately? | | | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`order` |`\crfnmOrder` |Whether the subtypes |`normal` or |`\at[order]`{=context} | | | |are printed in the |`inverted` | | | | |same order as in the | | | | | |name of the double | | | | | |type | | | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`` _is a single type; the following options apply when it is used as the second |`page` | |subtype of a double type_ | | | | | | | | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`formatting when |`\crfnmFormatInSecond` |`{}` or a macro that |`\textbf` |`\at[fmt-sec-subt]`{=context} | |second subtype` | |takes the prefixes | | | | | |and numbers as its | | | | | |argument | | | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`print prefix |`\crfnmPrintPrefixInSecond` |Should the prefix be |`yes` or `no` |`\at[rep-pref-dbl]`{=context} | |when second | |printed? | | | |subtype?` | | | | | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`delimiter when |`\crfnmEnumDelimInSecond` |Delimiter between |`{, }` |`\at[delim-sec-dbl]`{=context}| |second subtype` | |references | | | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`before last |`\crfnmBeforeLastInSecond` |Delimiter before the |`{ and }` |`\at[delim-sec-dbl]`{=context}| |reference when | |last reference | | | |second subtype` | | | | | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ |`continuous |`\crfnmNumberingContinuousAcrossDocument`|Is the numbering for |`yes` or `no` |`\at[numb-contin]`{=context} | |numbering?` | |this type continuous | | | | | |(i.e. not reset at | | | | | |every | | | | | |page/chapter/etc.)? | | | +-----------------+-----------------------------------------------+---------------------+--------------+------------------------------+ ## Basic invocation {#macro-crossrefenum} The macro `\crossrefenum` has the following syntax: > \\crossrefenum[\][\]{\} * _type_ is the type of the references. Built-in possible values are: * For LaTeX and ConTeXt: `page`, `note`, `pagenote`; * For ConTeXt only: `line`, `pageline`; * For LaTeX with _reledmac_: `edpage`, `edline`, `edpageline`. * _print prefix?_ indicates whether the prefix (like “p. ”) should be printed or not: true if set to `withprefix` or `yes`; * _enumeration_ is a group containing one or more _single_ labels (e.g. `mylabel`) or _ranges_ (e.g. `lbl-begin to lbl-end`). Ranges cannot be used with `note` and `pagenote` types. Two syntaxes are supported: lists of groups and comma-delimited lists (see below). _type_ and _print prefix?_ are optional. _type_ defaults to `page` and _print prefix_ to `withprefix`. Here are some valid invocations with the comma-delimited syntax: * `\crossrefenum[note][withprefix]{lblone, lbltwo, lblthree}` * `\crossrefenum[edline][noprefix]{lblone, lbltwo, lblthree}` * `\crossrefenum[page]{lblone to lbltwo, lblthree}` * `\crossrefenum[noprefix]{lblone, lbltwo, lblthree}` (_type_ defaults to `page`) * `\crossrefenum[note]{lblone, lbltwo, lblthree}` (_print prefix?_ defaults to `withprefix`) * `\crossrefenum{lblone, lbltwo, lblthree}` (_type_ defaults to `page` and _print prefix?_ defaults to `withprefix`) * `\crossrefenum{only-one}` The same invocations with the group-based syntax: * `\crossrefenum[note][withprefix]{{lblone}{lbltwo}{lblthree}}` * `\crossrefenum[edline][noprefix]{{lblone}{lbltwo}{lblthree}}` * `\crossrefenum[page]{{lblone to lbltwo}{lblthree}}` * `\crossrefenum[noprefix]{{lblone}{lbltwo}{lblthree}}` * `\crossrefenum[note]{{lblone}{lbltwo}{lblthree}}` * `\crossrefenum{{lblone}{lbltwo}{lblthree}}` * `\crossrefenum{{only-one}}` (even if the enumeration is limited to one item, it can be inside its own group) ## Customization {#crfnmsetup-macro} There are two configuration interfaces: one based on key-value lists, the other on (re)defining macros. These two interfaces can be used concurrently. If you are not interested in using the key-value interface or prefer not to depend on the `expkv` package, you can deactivate it by defining `\crfnmNoKV` to anything other than `\relax` before loading `crossrefenum`. After having explained the general principles, we will present the options related to single types before we turn to the options specific to double types and their subtypes. For each configuration option, I will show first how to use the key-value interface using `\crfnmsetup`, and second how to do the same thing with the low-level macros. ### General principles {#specialize-config-macros} In this manual, “default” means “not type-specific”. In this respect, “default” settings may be set be the user. The key-value interface has the following syntax: > \\crfnmsetup[<“default” or type>]{ \ = \, <…> } _type_ is a single or a double type (e.g. `page` or `pagenote`). The spaces around the equal signs and the commas are optional and ignored; if a value contains leading or trailing spaces, it must be put inside a group (e.g. `delimiter = {, }`). Under the hood, `\crfnmsetup` (re)defines the low-level configuration macros, which you can also manipulate directly. When successive calls to `\crfnmsetup` contradict one another, the last one prevails, except that type-specific settings always have precedence over the default ones. In the following subsections, I will generally present the low-level macros corresponding to the default settings, which have `Default` in their name. If you want to redefine a macro for a specific type, simply replace `Default` with the (capitalized) name of the type (e.g. `\crfnmPageEnumDelim` instead of `\crfnmDefaultEnumDelim`). Setting a value in the key-value interface or a macro to `\relax` will cause `\crossrefenum` to fall back to the default value. To set a configuration option to an absence of operation, use an empty group (e.g. `\def\crfnmPageEnumDelim{}` if you really don't want any delimiter between page numbers while the default delimiter is a comma followed by a space). Unless specified otherwise, the examples in the following subsections correspond to the built-in configuration. ### Prefixes, delimiters and separators You can define the [singular and plural prefixes]{#prefixes} printed before the value of the reference like this: ```{.tex} \crfnmsetup[page]{sg={p.~}, pl={pp.~}} \crfnmsetup[note]{sg={n.~}, pl={nn.~}} \crfnmsetup[edpage]{sg={p.~}, pl={pp.~}} \crfnmsetup[edline]{sg={l.~}, pl={ll.~}} ``` `\noindentation`{=context} which almost amounts to the built-in configuration: ```{.tex} \def\crfnmPage{p.~} \def\crfnmPages{pp.~} \def\crfnmNote{n.~} \def\crfnmNotes{nn.~} \let\crfnmEdpage\crfnmPage \let\crfnmEdpages\crfnmPages \def\crfnmEdline{l.~} \def\crfnmEdlines{ll.~} ``` `\noindentation`{=context} (it would have been more accurate to write `\crfnmsetup[edpage]{sg=\crfnmPage, pl=\crfnmPages}`). The [delimiters]{#delimiters} printed respectively between the successive references in an enumeration and before the last one are set so: ```{.tex} \crfnmsetup[default]{ delimiter = {, }, before last reference = { and } } ``` `\noindentation`{=context} which amounts to: ```{.tex} \def\crfnmDefaultEnumDelim{, } \def\crfnmDefaultBeforeLastInEnum{ and } ``` The [separator in a range]{#range-sep} is set like this: ```{.tex} \crfnmsetup[default]{range separator = –} ``` `\noindentation`{=context} which amounts to: ```{.tex} \def\crfnmDefaultRangeSep{–} ``` ### Collapsable and non-collapsable types {#collapsable} The configuration option `collapsable?` and the macro `\crfnmDefaultCollapsable` define if ranges are allowed. The built-in configuration corresponds to: ```{.tex} \crfnmsetup[default]{collapsable?=yes} \crfnmsetup[note]{collapsable?=no} ``` `\noindentation`{=context} which amounts to: ```{.tex} \def\crfnmDefaultCollapsable{yes} \def\crfnmNoteCollapsable{no} ``` Thus, unless you change that, a reference to consecutive notes is formatted like “nn. 3, 4 and 5”, not like “nn. 3–5”. Ranges are not accepted in the argument of `\crossrefenum` for non-collapsable types. This extends to double types that include a non-collapsable type (such as `pagenote` in the built-in configuration). ### Double types You can set like this [the separator between the two values in a double reference]{#subtypes-sep} (e.g. the page and the note numbers in a `pagenote` reference): ```{.tex} \crfnmsetup[default]{subtypes separator={, }} ``` `\noindentation`{=context} which amounts to: ```{.tex} \def\crfnmDefaultSubtypesSep{, } ``` When more than one reference is cited in an enumeration, [you may not want the first prefix to be repeated every time]{#fst-pref-dbl} (e.g. you may prefer “pp. 5, n. 2; 7, n. 4” to “p. 5, n. 2; p. 7, n. 4”). In this case, set `print prefix of first subtype` or `\crfnmDefaultPrintFirstPrefix` to `once`. Otherwise you will get: ```{.tex} \crfnmsetup[default]{ print prefix of first subtype = always } ``` `\noindentation`{=context} which amounts to: ```{.tex} \def\crfnmDefaultPrintFirstPrefix{always} ``` If you want to [format the second subtype]{#fmt-sec-subt} in a special way (e.g. in superscript), set the key `formatting when second subtype` either to `{}` (no formatting) or to a macro which will take the reference number and all its affixes as its only argument (e.g. `\textsuperscript`). Alternatively, you can define `\crfnmDefaultFormatInSecond` with one argument. What `\crossrefenum` comes with is: ```{.tex} \crfnmsetup[default]{formatting when second subtype = {}} ``` `\noindentation`{=context} which amounts to: ```{.tex} \def\crfnmDefaultFormatInSecond#1{#1} ``` [If you don't want any prefix to be printed in the second term of a double reference]{#rep-pref-dbl}, set `print prefix when second subtype?` or `\crfnmDefaultPrintPrefixInSecond` to `no` (built-in: yes). Here is how you can print the line number in superscript when it comes after the corresponding page number: ```{.tex} \crfnmsetup[edline]{ formatting when second subtype = \textsuperscript, print prefix when second subtype? = no } ``` `\noindentation`{=context} which amounts to: ```{.tex} \def\crfnmEdlineFormatInSecond#1{\textsuperscript{#1}} \def\crfnmEdlinePrintPrefixInSecond{no} ``` After that, `\crossrefenum[edpageline]{mylabel}` may return “p. 5^10^”, whereas `\crossrefenum[edline]{mylabel}` would return “l. 10”. You can specify a [specific delimiter for the second part of double references]{#delim-sec-dbl} and a specific string to be printed before the last reference of the second subtype in a double reference (e.g. the last reference to a line in “p. 5, l. 10, 13, 16”, which is “16”). For instance, you may want to use the word “and” before the last note number if the reference type is a simple one (`note`) and a comma if it is comes in second in a double reference (e.g. in `pagenote`). To achieve this, you can set `delimiter when second subtype` (= `\crfnmDefaultEnumDelimInSecond`) and `before last reference when second subtype` (= `\crfnmDefaultBeforeLastInSecond`). In the built-in configuration, there is no difference between a simple type used alone and the same simple type taken as the second subtype of a double type, but we could imagine the following: ```{.tex} \crfnmsetup[note]{ delimiter = {; }, before last reference = { and }, delimiter when second subtype = {, }, before last reference when second subtype = {, } } \crossrefenum[note]{lbl1, lbl2, lbl3} = \crossrefenum[pagenote]{lbl1, lbl2, lbl3} ``` `\noindentation`{=context} which may yield: “n. 1; 2 and 5 = p. 8, n. 1, 2, 5”. [When citing a range, the two parts of the reference]{#group-subt} can be either split (e.g. “p. 5, l. 3 – p. 7, l. 44”) or grouped (“p. 5–7, l. 3–44”). This is controlled via `group subtypes?` (= `\crfnmDefaultGroupSubtypes`), which can be set to `yes` or `no`. This works only with [collapsable types](#collapsable): ```{.tex} \crfnmsetup[default]{group subtypes? = no} ``` `\noindentation`{=context} which amounts to: ```{.tex} \def\crfnmDefaultGroupSubtypes{no} ``` To know if a reference to “p. 6, l. 34” should be merged with “p. 7, l. 35”, _crossrefenum_ needs to know if the lineation is [continuous]{#numb-contin} (in this case, these lines are consecutive) or per page (they are not, so they should not be merged). You can set accordingly `continuous numbering?` (= `\crfnmDefaultNumberingContinuousAcrossDocument`)[^line-numbering] to `yes` (built-in) or `no`. Note that _crossrefenum_ cannot merge a reference to the last line of a page and the first line of the following page if the lineation is not continuous. [^line-numbering]: In this case, you could set more specifically `\crfnmLineNumberingContinuousAcrossDocument` or `\crfnmEdlineNumberingContinuousAcrossDocument` or use `\crfnmsetup` with `[line]` and `[edline]`. In the built-in configuration, the [order of the subtypes]{#order} in the name of a subtype (e.g. “page” and “note” in “pagenote”) determines by default the order in which they are printed (e.g. “p. 6, n. 2” instead of “n. 2, p. 6”). If you want to change this, set `order` (= `\crfnmDefaultOrder`) to `inverted` (built-in: `normal`). ## How to extend crossrefenum with other types and formats {#extending} Adding support for new types consists in defining the related macros in your preamble. Here is a commented example that would add support for references to lines in ConTeXt if this feature were not already included in _crossrefenum_. I suppose that the labels are inserted in the document using the standard ConTeXt macros, i.e. `\someline` for line references and `\pagereference` for page references. ```{.tex} % Register the types. Take care about capitalization! %% Set the names of the types and their corresponding macros: %% the control sequence should always be lowercase %% and the name of the type capitalized. \def\crfnm@line{Line} \def\crfnm@pageline{Pageline} %% Add them to the lists of known types. \crfnm@declareType[simple][\crfnm@line] \crfnm@declareType[double][\crfnm@pageline] ``` ```{.tex} %% For the double types, set the primary and the secondary type. %% The primary type corresponds to the widest typographic unit %% (“page” for “pagenote”, “section” for “sectionpage”...). \let\crfnm@PagelinePrimary\crfnm@page \let\crfnm@PagelineSecondary\crfnm@line % Define the macro used to typeset the reference number. \def\crfnm@LineRef#1{\in[lr:b:#1]} % Define the macro used by \crossrefenum internally % to retrieve the raw reference number without typesetting it. % This macro must be purely expandable. % If you want to support multiple formats, % see how the macro \crfnm@case is used in crossrefenum.tex. % Note: the Lua function get_raw_ref_number is defined % in crossrefenum.tex for ConTeXt. \def\crfnm@getLineNumber#1{\directlua{get_raw_ref_number('lr:b:#1', 'linenumber')}} % Define all specific configuration options in the regular way. % Instead of the following, you can use \crfnmsetup. ``` ```{.tex} %% Required \def\crfnmLine{l.~} \def\crfnmLines{ll.~} %% If it differs from the defaults. \def\crfnmLineCollapsable{yes} \def\crfnmLineBeforeLastInSecond{, } \def\crfnmPagelineSubtypesSep{} \def\crfnmPagelinePrintFirstPrefix{once} \def\crfnmLineFormatInSecond#1{\crfnmSuperscript{#1}} \def\crfnmLinePrintPrefixInSecond{no} ``` Adding support for additional formats is far less trivial, not least because most of them do not provide a straightforward way to get reference numbers via purely expandable macros, which is required for _crossrefenum_ to perform its calculations. If you have wishes or hints about this, please contact me. ## Compatibility issues With LaTeX, the _nameref_ package (required by _hyperref_`\kern1.5pt`{=context}) must be loaded explicitely before _crossrefenum_. However, if the links in the output of `\crossrefenum` (not the numbers) point to a wrong location, you may wish to patch the macro to disable _hyperref_ locally: ``` {.tex} \makeatletter \let\oldcrfnm@enum\crfnm@enum \def\crfnm@enum[#1][#2]#3{% \begin{NoHyper}\oldcrfnm@enum[#1][#2]{#3}\end{NoHyper}% } \makeatother ```