Implementation and documentation of \crfnmsetup

This commit is contained in:
Bastien Dumont
2025-08-06 08:38:39 +02:00
parent d09410db10
commit eabdf91580
7 changed files with 1210 additions and 267 deletions

View File

@ -25,4 +25,8 @@ The test suite now uses _lualatex_.
The last argument of `\crossrefenum` can now be a comma-delimited list
instead of a list of groups. Lists of groups are still supported and will remain so.
Fixed a small error in an example in the manual.
The new macro `\crfnmsetup` now provides a key-value configuration interface,
thanks to Jonathan P. Spratte's [`expkv`](https://ctan.org/pkg/expkv-bundle) package.
The dependency to `expkv` can be removed by deactivating the key-value interface.
Various improvements in the manual.

View File

@ -31,6 +31,9 @@ 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.
It only depends on [`expkv`](https://ctan.org/pkg/expkv-bundle)
(but this dependency can be removed by deactivating
the key-value configuration interface in favor of the native configuration macros).
## List of files

View File

@ -30,7 +30,9 @@ 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 dependency is [`simplekv`](https://ctan.org/pkg/simplekv).
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_.
@ -86,14 +88,69 @@ The same invocations with the group-based syntax:
## Customization
Customizing is done by redefining configuration macros.
We describe the general mechanism first.
Macros for double types are introduced at the end of this section.
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_\]{ _key1_ = _value1_, _key2_ = _value2_, … }
_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
Every simple type has two macros corresponding to the singular and plural prefixes
printed before the value of the reference. By default, they are set to:
You can define the singular and plural 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.~}
@ -106,14 +163,31 @@ printed before the value of the reference. By default, they are set to:
\def\crfnmEdlines{ll.~}
```
Between successive items in an enumeration, `\crossrefenum` calls `\crfnmDefaultEnumDelim` or `\crfnmDefaultBeforeLastInEnum`. By default, they are set to:
`\noindentation`{=context} (it would have been more accurate to write
`\crfnmsetup[edpage]{sg=\crfnmPage, pl=\crfnmPages}`).
The 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 beginning and the end of a range are separated by `\crfnmDefaultRangeSep`. By default:
The separator in a range is set like this:
```{.tex}
\crfnmsetup[default]{range separator = }
```
`\noindentation`{=context} which amounts to:
```{.tex}
\def\crfnmDefaultRangeSep{}
@ -121,24 +195,37 @@ The beginning and the end of a range are separated by `\crfnmDefaultRangeSep`. B
### Collapsable and non-collapsable types {#collapsable-types}
The macro `\crfnmDefaultCollapsable` defines if ranges are allowed.
The default configuration is:
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, a reference to consecutive notes is formatted like _nn. 3, 4 and 5_, not like _nn. 35_.
Thus, unless you change that, a reference to consecutive notes
is formatted like “nn. 3, 4 and 5”, not like “nn. 35”.
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 default configuration).
(such as `pagenote` in the built-in configuration).
### Double types
Two subtypes in a double type (e.g. page and note number for `pagenote`)
are separated by `\crfnmDefaultSubtypesSep`. Default:
You can set like this the separator between the two values in a double reference
(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{, }
@ -146,51 +233,96 @@ are separated by `\crfnmDefaultSubtypesSep`. Default:
When more than one reference is cited in an enumeration,
you may not want the first prefix to be repeated every time
(e.g. you could prefer “pp. 5, n. 2; 7, n. 4” over “p. 5, n. 2; p. 7, n. 4”).
In that case, set `\crfnmDefaultPrintFirstPrefix` to `once`.
Default is:
(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 part of the reference in a special way (e.g. in superscript),
use `\crfnmDefaultFormatInSecond`,
which takes one argument which corresponds to the reference number and all its affixes.
Default is:
If you want to format the second subtype 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,
set `\crfnmDefaultPrintPrefixInSecond` to `no` (default is yes).
For instance:
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}
\crossrefenum[edpageline]{{mylabel}}
```
`\noindentation`{=context} may return “p. 5^10^”, while `\crossrefenum[edline]{{mylabel}}` would return “l. 10”.
For the second part of such an enumeration (e.g. “l. 10” in “p. 5, l. 10”),
you can specify a specific delimiter
and a specific string to be printed before the last reference:
for instance, you may want to use the word “and”
before the last note number if the reference type is a simple one (`note`),
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 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 should redefine `\crfnmDefaultEnumDelimInSecond`
and `\crfnmDefaultBeforeLastInSecond`.
By default, these macros fall back respectively
on `\crfnmDefaultEnumDelim` and `\crfnmDefaultBeforeLastInEnum`.
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 can
either be split (e.g. “p. 5, l. 3 p. 7, l. 44”)
be either split (e.g. “p. 5, l. 3 p. 7, l. 44”)
or grouped (“p. 57, l. 344”).
This is controlled via `\crfnmDefaultGroupSubtypes`, which can be set to `yes` or `no`.
This works only with [collapsable types](#collapsable-types).
Default is:
This is controlled via `group subtypes?` (= `\crfnmDefaultGroupSubtypes`),
which can be set to `yes` or `no`.
This works only with [collapsable types](#collapsable-types):
```{.tex}
\crfnmsetup[default]{group subtypes? = no}
```
`\noindentation`{=context} which amounts to:
```{.tex}
\def\crfnmDefaultGroupSubtypes{no}
@ -200,39 +332,29 @@ To know if a reference to “p. 6, l. 34” should be merged with “p. 7, l.
_crossrefenum_ needs to know if the lineation is
continuous (in this case, these lines are consecutive)
or per page (they are not, so they should not be merged).
You can set accordingly
`\crfnmDefaultNumberingContinuousAcrossDocument`[^line-numbering] to `yes` (default) or `no`.
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`:
see [the following subsection](#specialize-config-macros).
By default, the number of the first subtype in the name of the double type
(e.g. “page” in “pagenote”) is always displayed first.
If you want to change this, set `\crfnmDefaultOrder` to `inverted` (defaults to `normal`).
### Specific values for given types {#specialize-config-macros}
If you want to override some of these macros for a specific type,
simply replace `Default` in its name with the (capitalized) name of the type
(e.g. `\def\crfnmPageEnumDelim{; }`).
Setting one of these macros to `\relax` will cause `\crossrefenum`
to use the corresponding default macro instead.
If you want a specific macro to be set to nothing,
use an empty group (e.g. `\def\crfnmPageEnumDelim{}`).
or `\crfnmEdlineNumberingContinuousAcrossDocument`
or use `\crfnmsetup` with `[line]` and `[edline]`.
In the built-in configuration, the order of the subtypes 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_.
We suppose that the labels are inserted in the document using the standard ConTeXt macros,
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}
@ -269,13 +391,14 @@ i.e. `\someline` for line references and `\pagereference` for page references.
\def\crfnm@getLineNumber#1{\directlua{get_raw_ref_number('lr:b:#1', 'linenumber')}}
% Define all specific configuration options in the regular way.
%% Required
\def\crfnmLine{l.~}
\def\crfnmLines{ll.~}
% 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{, }

View File

@ -6,6 +6,7 @@
\def\crfnmresetpage{\setnumber[userpage][1]}
\def\smaller{\setupbodyfont[script]}
\let\crfnmsc\sc
\def\textit##1{{\it ##1}}
}
\crfnm@latex: {
\def\crfnmlbl##1{{\bf [##1]}\label{##1}}

View File

@ -451,6 +451,66 @@
{withprefix}
{p. 1, n. 1, p. 2, n. 3 and p. 4, n. 3}
\crfnmheader{Key-value configuration interface}
Heavily modified setup for {\tt page}, {\tt note} and {\tt pagenote} with \crfnmverbatim{\crfnmsetup}
\crfnmsetup[page]{
sg = {page },
pl = {pages },
delimiter = {; },
before last reference = { AND },
range separator = { to },
collapsable? = yes
}
\crfnmsetup[note]{
sg = {note },
pl = {notes },
print prefix when second subtype? = no,
delimiter when second subtype = +,
before last reference when second subtype = { And },
formatting when second subtype = \textit % single-arg macro
}
\crfnmsetup[pagenote]{
delimiter = {; },
before last reference = { AND },
range separator = { to },
subtypes separator = { in },
print prefix of first subtype = once,
group subtypes? = no,
continuous numbering? = no
}
\crfnmtestenum{Simple type}
{matthaeus-14, matthaeus-025, matthaeus-0223}
{page}
{withprefix}
{pages 2; 4 AND 6}
\crfnmtestenum{Simple type with a range}
{{matthaeus-2 to matthaeus-15}{matthaeus-16}}
{page}
{withprefix}
{pages 1 to 2}
\crfnmtestenum{Double type}
{note-Aminadab-undecim, note-generationes-quis, note-congregans-conteram, note-Israhel-mittam, note-regem-Theman}
{pagenote}
{withprefix}
{pages 1 in \textit{1}; 2 in \textit{3} AND \textit{4} in \textit{1+2 And 3}}
\crfnmsetup[page]{
collapsable? = no
}
\crfnmtestenum{Pages are not collapsable anymore!}
{{matthaeus-2}{matthaeus-15}{matthaeus-22}{matthaeus-025}}
{page}
{withprefix}
{pages 1; 2; 3 AND 4}
\iflatex{\end{english}}
\ifcontext{
\language[latin]

File diff suppressed because it is too large Load Diff

View File

@ -506,6 +506,40 @@
%%% Initialization: Key-value configuration interface %%%
\expandafter\ifx\csname crfnmNoKV\endcsname\relax
\crfnm@case[\fmtname]
\crfnm@context: {\usemodule[expkv-def]}
\crfnm@latex: {\usepackage{expkv-def}}
\fmtname: {\input{expkv-def}}
\crfnm@endCases
\def\crfnmsetup[#1]#2{%
% #1 = a reference type or "default"
\ekvdefinekeys{crfnm@type@#1}{
code sg = \expandafter\def\csname crfnm\crfnm@capitalize #1\endcsname{##1},
code pl = \expandafter\def\csname crfnm\crfnm@capitalize #1s\endcsname{##1},
code delimiter = \expandafter\def\csname crfnm\crfnm@capitalize #1EnumDelim\endcsname{##1},
code before last reference = \expandafter\def\csname crfnm\crfnm@capitalize #1BeforeLastInEnum\endcsname{##1},
code range separator = \expandafter\def\csname crfnm\crfnm@capitalize #1RangeSep\endcsname{##1},
code collapsable? = \expandafter\def\csname crfnm\crfnm@capitalize #1Collapsable\endcsname{##1},
code subtypes separator = \expandafter\def\csname crfnm\crfnm@capitalize #1SubtypesSep\endcsname{##1},
code print prefix of first subtype = \expandafter\def\csname crfnm\crfnm@capitalize #1PrintFirstPrefix\endcsname{##1},
code formatting when second subtype = \expandafter\def\csname crfnm\crfnm@capitalize #1FormatInSecond\endcsname####1{##1{####1}},
code print prefix when second subtype? = \expandafter\def\csname crfnm\crfnm@capitalize #1PrintPrefixInSecond\endcsname{##1},
code delimiter when second subtype = \expandafter\def\csname crfnm\crfnm@capitalize #1EnumDelimInSecond\endcsname{##1},
code before last reference when second subtype = \expandafter\def\csname crfnm\crfnm@capitalize #1BeforeLastInSecond\endcsname{##1},
code group subtypes? = \expandafter\def\csname crfnm\crfnm@capitalize #1GroupSubtypes\endcsname{##1},
code continuous numbering? = \expandafter\def\csname crfnm\crfnm@capitalize #1NumberingContinuousAcrossDocument\endcsname{##1},
code order = \expandafter\def\csname crfnm\crfnm@capitalize #1Order\endcsname{##1}
}%
\ekvset{crfnm@type@#1}{#2}%
}
\fi
%%% Initialization: Default configuration %%%
% Prefixes