crossrefenum/tex/crossrefenum.tex

1512 lines
50 KiB
TeX
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

%***********************************************************************
\def\crfnmName{crossrefenum}
\def\crfnmShortDesc{Smart typesetting of enumerated cross-references for various TeX formats}
\def\crfnmAuthor{Bastien Dumont}
\def\crfnmDate{2024/04/13}
\def\crfnmVersion{1.1}
%
% Copyright 2022-2024 by Bastien Dumont (bastien.dumont@posteo.net)
%
% crossrefenum.tex is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% crossrefenum.tex is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with crossrefenum.tex. If not, see https://www.gnu.org/licenses/.
%
%***********************************************************************
% Terminology:
% A simple reference is a reference by only one criterion (e.g. “page” or “note”).
% A double reference is a reference by two criteria (e.g. “page and note”),
% so has two subtypes: the primary and the secondary subtypes.
% Their labelling as primary and secondary is independant from their printed order :
% the primary subtype corresponds to the wider typographical unit,
% in which the secondary subtype is contained (so for “page and note”,
% the primary subtype is “page” and the secondary is “note”).
% A single reference is a reference to one location (e.g. “p. 1”)
% A range is a reference to a span of text delimited by two single references (e.g. “pp. 15”).
% An enumeration is a group containing a sequence of one or more references enclosed in groups.
% Format-specific implementation notes:
% In ConTeXt, the argument of \expanded cannot contain parameters:
% hence the ugly bridges of \expandafter that unfortunately cannot be
% replaced with a combination of \expanded and \noexpand.
% How to add support for a new format:
% Add a macro expanding to the name of the format at the beginning
% of the section “Initialization: Format-specific”;
% Add a case in all blocks beginning with \crfnm@case[\fmtname]
% to setup the macros defined there with the required format-specific code.
% OUTLINE
%
% Initialization
% Catcodes
% Programming macros
% Format-specific
% Auxiliary file
% Constants
% Conditionals
% Auxiliary macros related to the data structure of \crossrefenum
% Default configuration
%
% \crossrefenum
% Public macro with optional arguments
% Main private macro
% Processing the individual references in the enumeration
%%% Initialization: Catcodes %%%
\newcount\crfnmOriginalCatcodeAt
% We can't write "crfnm@" here since the catcode
% of @ has not been redefined yet.
\crfnmOriginalCatcodeAt=\catcode`\@
\catcode`\@=11
%%% Initialization: Programming macros %%%
% \crfnm@case is a standard case statement.
% #1 is the string or the purely expandable macro to be tested.
% #2 is a sequence of tests of the form:
% value: token or group used if #1 is equal to value
% The sequence ends with \crfnm@endCases.
% In the groups to be executed, arguments in a macro definition
% have to be doubled.
% If all tests fails, does nothing and prints a warning on the terminal.
\def\crfnm@case[#1] #2\crfnm@endCases{%
\begingroup
\edef\crfnm@comparandum{#1}%
\crfnm@@case #2%
\crfnm@comparandum: {%
\crfnm@warn{%
All tests failed in \unexpanded{\crfnm@case[#1]
#2 \crfnm@endCases}, doing nothing%
}%
}
\crfnm@endCases
}
\def\crfnm@@case #1: #2{%
\edef\crfnm@comparans{#1}%
\ifx\crfnm@comparans\crfnm@comparandum
\def\crfnm@todo{\endgroup #2\crfnm@gobbleNextCases}%
\else
\def\crfnm@todo{\expandafter\crfnm@@case\crfnm@gobbleSpaces}%
\fi
\crfnm@todo
}
\def\crfnm@gobbleSpaces#1{#1}
\def\crfnm@gobbleNextCases #1\crfnm@endCases{}
\def\crfnm@newCsnameAlias[#1]#2{%
% #1 is a control sequence (e.g. \mymacro).
% #2 is a cs name corresponding to an already defined
% control sequence (e.g. mappedto\tobereplaced).
\expandafter\let\expandafter#1\csname #2\endcsname
}
\def\crfnm@capitalize#1{%
\expandafter\crfnm@uppercaseFirstLetter #1%
}
\def\crfnm@uppercaseFirstLetter#1{%
% \uppercase, \lowercase and \crfnm@case
% are not purely expandable
\ifx#1aA%
\else\ifx#1bB%
\else\ifx#1cC%
\else\ifx#1dD%
\else\ifx#1eE%
\else\ifx#1fF%
\else\ifx#1gG%
\else\ifx#1hH%
\else\ifx#1iI%
\else\ifx#1jJ%
\else\ifx#1kK%
\else\ifx#1lL%
\else\ifx#1mM%
\else\ifx#1nN%
\else\ifx#1oO%
\else\ifx#1pP%
\else\ifx#1qQ%
\else\ifx#1rR%
\else\ifx#1sS%
\else\ifx#1tT%
\else\ifx#1uU%
\else\ifx#1vV%
\else\ifx#1wW%
\else\ifx#1xX%
\else\ifx#1yY%
\else\ifx#1zZ%
% In forks, the first argument of \crossrefenum is \crfnm@secondarySubtype,
% so it is already capitalized.
\else #1%
\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi
}
\def\crfnm@ifequal[#1][#2]#3#4{%
\edef\crfnm@comparans{#1}%
\edef\crfnm@comparandum{#2}%
\ifx\crfnm@comparans\crfnm@comparandum #3\else #4\fi
}
\def\crfnm@loopOverArgs #1with #2{%
\crfnm@loopOver@args[#2]#1\crfnm@end
}
\def\crfnm@loopOver@args[#1]#2{%
% #1 is the macro with one argument
% to be called with #2
\edef\crfnm@arg{#2}%
\ifx\crfnm@arg\crfnm@end
\def\crfnm@todo{}%
\else
#1{#2}%
\def\crfnm@todo{\crfnm@loopOver@args[#1]}%
\fi
\crfnm@todo
}
\def\crfnm@ifIsOneOf[#1][#2]#3#4{%
% #1 expands to a string, #2 expands to a list
\crfnm@foundfalse
\def\crfnm@setIfFound ##1{%
\edef\crfnm@itemSearched{#1}%
\edef\crfnm@toBeTested{##1}%
\ifx\crfnm@toBeTested\crfnm@itemSearched
\crfnm@foundtrue
\fi
}%
\expandafter\crfnm@loopOverArgs #2with \crfnm@setIfFound
\ifcrfnm@found
\def\crfnm@todo{#3}%
\else
\def\crfnm@todo{#4}%
\fi
\crfnm@todo
}
%%% Initialization: Format-specific %%%
% \fmtname changes between MKIV and LMTX in ConTeXt,
% so we use the value of \contextformat (to which \fmtname
% is made let-equal in ConTeXt).
% See https://source.contextgarden.net/tex/context/base/mkiv/context.mkiv?search=%5Cfmtname#l49
\edef\crfnm@context{\csname contextformat\endcsname}
\def\crfnm@latex{LaTeX2e}
\def\crfnm@optex{OpTeX}
% Supported types
\crfnm@case[\fmtname]
\crfnm@context: {
\def\crfnm@supportedTypes{{\crfnm@page}{\crfnm@note}{\crfnm@line}{\crfnm@pagenote}{\crfnm@pageline}}
}
\crfnm@latex: {
\def\crfnm@supportedTypes{{\crfnm@page}{\crfnm@note}{\crfnm@edpage}{\crfnm@edline}{\crfnm@pagenote}{\crfnm@edpageline}}
}
\crfnm@endCases
% The following format-specific instructions are necessary to get
% the raw page number, which is used in comparisons.
% The raw page number must be unique (e.g. absolute page number).
% It must be possible to get it via a purely expandable macro.
\crfnm@case[\fmtname]
\crfnm@context: {
% Since I have not found any ConTeXt macro to get the raw values,
% we look directly in the auxiliary file from the second pass on.
\directlua{
ordered_ref_data = structures.lists.collected
arbitrary_ref_data = structures.references.collected
function get_raw_ref_number(label, type)
found = false
if arbitrary_ref_data then
for _, ref_data in pairs(arbitrary_ref_data) do
if ref_data[label] then
label_data = ref_data[label]
for _, data_part in pairs(label_data) do
if data_part[type] then
found = true
tex.print(data_part[type])
end
end
end
end
end
if not(found) and ordered_ref_data then
for i = 1, \luaescapestring{\utfchar{0x0023}ordered_ref_data} do
if ordered_ref_data[i].references.reference == label then
found = true
tex.print(ordered_ref_data[i].references[type])
end
end
end
if not(found) then tex.print(0) end
end
}
}
\crfnm@latex: {
% If you use nameref (e.g. through hyperref),
% please make sure to load it before this code
% so that it does not erase our redefinition of \label.
% More details here:
% https://comp.text.tex.narkive.com/PI1P2Nlt/hyperref-and-redefining-label-ref-and-pageref-again
\RequirePackage[abspage]{zref}
\zref@setdefault{0}
\let\crfnm@label@beforezref\label
\def\label##1{%
\crfnm@label@beforezref{##1}%
\zref@labelbyprops{##1}{abspage, default}%
}
}
\crfnm@endCases
% Macros for getting raw reference numbers.
% They must be purely expandable.
\crfnm@case[\fmtname]
\crfnm@context: {
\def\crfnm@getPageNumber##1{\directlua{get_raw_ref_number('##1', 'realpage')}}
\def\crfnm@getNoteNumber##1{\directlua{get_raw_ref_number('##1', 'order')}}
\def\crfnm@getLineNumber##1{\directlua{get_raw_ref_number('lr:b:##1', 'linenumber')}}
}
\crfnm@latex: {
\def\crfnm@getPageNumber##1{\zref@extract{##1}{abspage}}
\def\crfnm@getNoteNumber##1{\zref@extract{##1}{default}}
\def\crfnm@getEdpageNumber##1{\xpageref{##1}}
\def\crfnm@getEdlineNumber##1{\xlineref{##1}}
}
\crfnm@endCases
% Macros for typesetting the references.
\crfnm@case[\fmtname]
\crfnm@context: {
\def\crfnm@PageRef##1{\at[##1]}
\def\crfnm@NoteRef##1{\in[##1]}
\def\crfnm@LineRef##1{\in[lr:b:##1]}
}
\crfnm@latex: {
\def\crfnm@PageRef##1{\pageref{##1}}
\def\crfnm@NoteRef##1{\ref{##1}}
\def\crfnm@EdpageRef##1{\edpageref{##1}}
\def\crfnm@EdlineRef##1{\edlineref{##1}}
}
\crfnm@endCases
% Formatting macros
\crfnm@case[\fmtname]
\crfnm@context: {
\let\crfnmSuperscript\high
\let\crfnmSubscript\low
}
\crfnm@latex: {
\let\crfnmSuperscript\textsuperscript
\let\crfnmSubscript\textsubscript
}
\crfnm@endCases
% Issue warnings
% \crfnm@warn@newPassNeeded is needed only for those format
% that may not reprocess the TeX file automatically
% when the auxiliary file changed.
\crfnm@case[\fmtname]
\crfnm@latex: {
\let\crfnm@warn@newPassNeeded\@latex@warning@no@line
}
\fmtname: {\let\crfnm@warn@newPassNeeded\relax}
\crfnm@endCases
%%% Initialization: Auxiliary file %%%
% Configure the auxiliary file.
\crfnm@case[\fmtname]
\crfnm@context: {
\definedataset[printedRefsNb]
}
\crfnm@latex: {
\let\crfnm@auxfile\@auxout
}
\crfnm@endCases
% Initialize the counter used in the auxiliary file
% to identify the informations associated with each
% call to \crossrefenum
\newcount\crfnm@ienum
\crfnm@ienum=0
% In double references, we need to count separately
% the number of typeset references (after collapsing)
% for each part. To do this, we divide by 2 the maximum value
% that a counter can have and we use the result
% as the start of the index for \crfnm@ienum when used
% on the secondary subtype of a double reference.
% As a consequence, it is not possible to use \crossrefenum
% more than 2^30/2 times in the same document.
\newcount\crfnm@ienum@secondaryOfDouble
\crfnm@ienum@secondaryOfDouble=0
\def\crfnm@secondaryOfDouble@istart{536870912} % = 2^30/2
% This counter is used to register the total number
% of typeset references for every invocation of \crossrefenum
% (for a simple reference) or for each part of a double reference.
\newcount\crfnm@printedRefsNb
\crfnm@printedRefsNb=0
%%% Initialization: Constants %%%
% Keywords and parameter values
\def\crfnm@empty{}
\def\crfnm@always{always}
\def\crfnm@yes{yes}
\def\crfnm@plural{pl}
\def\crfnm@first{first}
\def\crfnm@singleFirst{singlefirst}
\def\crfnm@end{crfnm@end}
\def\crfnm@labelRangeSep{ to }
\def\crfnm@withPrefix{withprefix}
\def\crfnm@enumend{crfnm@enumend}
\def\crfnm@normal{normal}
\def\crfnm@crossrefenum@secondArg@possibleValues{{withprefix}{noprefix}{yes}{no}}
% Reference types
% Reledmac \pstartref is not supported, since users know if two consecutive references
% are in the same paragraph or not. They can alternate between direct use of \pstartref
% and \crossrefenum for lines and/or pages.
% \annotationref is not supported because I don't have any experience of it.
\def\crfnm@page{Page}
\def\crfnm@note{Note}
\def\crfnm@line{Line}
\def\crfnm@edpage{Edpage}
\def\crfnm@edline{Edline}
\def\crfnm@pagenote{Pagenote}
\def\crfnm@pageline{Pageline}
\def\crfnm@edpageline{Edpageline}
\let\crfnm@PagenotePrimary\crfnm@page
\let\crfnm@PagenoteSecondary\crfnm@note
\let\crfnm@PagelinePrimary\crfnm@page
\let\crfnm@PagelineSecondary\crfnm@line
\let\crfnm@EdpagelinePrimary\crfnm@edpage
\let\crfnm@EdpagelineSecondary\crfnm@edline
%%% Initialization: Conditionals %%%
\newif\ifcrfnm@found
\newif\ifcrfnm@enumIsFinished
\newif\ifcrfnm@simulated
\newif\ifcrfnm@areSingleAndRange
\newif\ifcrfnm@isFirstToken
\newif\ifcrfnm@singleFirst
%%% Initialization: Auxiliary macros related to the data structure of \crossrefenum %%%
\edef\crfnm@simpleRefTypes{{\crfnm@page}{\crfnm@note}{\crfnm@line}{\crfnm@edpage}{\crfnm@edline}}
\edef\crfnm@doubleRefTypes{{\crfnm@pagenote}{\crfnm@pageline}{\crfnm@edpageline}}
\edef\crfnm@customizableDefaultConfig{{Collapsable}{EnumDelim}{EnumDelimInSecond}{BeforeLastInEnum}{BeforeLastInSecond}{RangeSep}}
\edef\crfnm@customizableDefaultDoubleConfig{{Collapsable}{EnumDelim}{BeforeLastInEnum}{RangeSep}{SubtypesSep}{PrintFirstPrefix}{GroupSubtypes}{Order}}
\edef\crfnm@customizableDefaultSecondaryOfDoubleConfig{{Collapsable}{NumberingContinuousAcrossDocument}{PrintPrefixInSecond}{FormatInSecond}}
\newif\ifcrfnm@isDoubleRef
\def\crfnm@ifIsDoubleRef#1#2{\ifcrfnm@isDoubleRef #1\else #2\fi}
\def\crfnm@ifIsRange#1#2#3{%
\expandafter\crfnm@ifIs\crfnm@labelRangeSep @in {#1} {#2} {#3}%
}
\def\crfnm@ifIs#1@in #2#3#4{%
\def\crfnm@ifIsIn##1#1##2\@nil{%
\def\crfnm@afterSubstring{##2}%
\ifx\crfnm@afterSubstring\crfnm@empty #4\else #3\fi
}%
\expandafter\crfnm@ifIsIn#2#1\@nil
}
\def\crfnm@enumid#1{%
\crfnm@ifIsSecondaryOfDouble[ienum: #1]{%
secondaryofdouble@%
\romannumeral\numexpr #1-\crfnm@secondaryOfDouble@istart\relax
@\romannumeral\the\crfnm@ienum@secondaryOfDouble
}{%
\romannumeral #1%
}%
}
\def\crfnm@currEnumId{\crfnm@enumid{\the\crfnm@ienum}}
\def\crfnm@ifIsSecondaryOfDouble[ienum: #1]#2#3{%
\ifnum #1 > \crfnm@secondaryOfDouble@istart
#2%
\else
#3%
\fi
}
\def\crfnm@setIfIsDoubleRef{%
\crfnm@ifIsOneOf[\crfnm@refType][\crfnm@doubleRefTypes]{%
\crfnm@isDoubleReftrue
}{%
\crfnm@isDoubleReffalse
}%
}
\def\crfnm@ifIsList[#1]#2#3{%
\expandafter\futurelet\expandafter\crfnm@nextToken
\expandafter\crfnm@ifIsBgroup #1\endofcheck{#2}{#3}%
}
\def\crfnm@ifIsBgroup#1\endofcheck#2#3{%
% \crfnm@nextToken is the first token in the #1 of \crfnm@ifIsList.
% All the #1 of \crfnm@ifIsList is stored here in #1 and discarded.
\ifx\crfnm@nextToken\bgroup #2\else #3\fi
}
\def\crfnm@newListFrom[#1][#2] -> #3{%
% #1 is either a list or a reference.
% #2 is the reference appended to #1.
% #3 is the control sequence which the resulting list will be bound to.
\crfnm@ifIsList[#1]{%
\edef#3{#1{#2}}%
}{%
\edef#3{{#1}{#2}}%
}%
}
\def\crfnm@addToList[#1][#2]{\crfnm@newListFrom[#1][#2] -> #1}
\def\crfnm@declareType[#1][#2]{%
% #1 is "simple" or "double", #2 is the type
\expandafter\crfnm@addToList\expandafter[\csname crfnm@#1RefTypes\endcsname][#2]%
\crfnm@addToList[\crfnm@supportedTypes][#2]%
}
\def\crfnm@replaceFirstInList[#1]#2{%
% #1 is a token, #2 is a list of tokens
{#1}\crfnm@gobbleFirst #2%
}
\def\crfnm@gobbleFirst#1{}
%%% Initialization: Default configuration %%%
% Prefixes
\def\crfnmPage{p.~}
\def\crfnmPages{pp.~}
\def\crfnmNote{n.~}
\def\crfnmNotes{nn.~}
\def\crfnmLine{l. }
\def\crfnmLines{ll.}
\let\crfnmEdpage\crfnmPage
\let\crfnmEdpages\crfnmPages
\def\crfnmEdline{l.~}
\def\crfnmEdlines{ll.~}
% Macros with typed and default variants
\def\crfnmDefaultCollapsable{yes}
\def\crfnmNoteCollapsable{no}
\def\crfnmDefaultNumberingContinuousAcrossDocument{yes}
\def\crfnmDefaultEnumDelim{, }
\let\crfnmDefaultEnumDelimInSecond\crfnmDefaultEnumDelim
\def\crfnmDefaultBeforeLastInEnum{ and }
\let\crfnmDefaultBeforeLastInSecond\crfnmDefaultBeforeLastInEnum
\def\crfnmDefaultRangeSep{}
\def\crfnmDefaultSubtypesSep{, }
\def\crfnmDefaultPrintFirstPrefix{always}
\def\crfnmDefaultFormatInSecond#1{#1}
\def\crfnmDefaultPrintPrefixInSecond{yes}
\def\crfnmDefaultGroupSubtypes{no}
\let\crfnmDefaultOrder\crfnm@normal
%%% \crossrefenum %%%
%%% \crossrefenum: Public macro with optional arguments %%%
\crfnm@case[\fmtname]
\crfnm@context: {
\unexpanded\def\crossrefenum{\crfnm@crossrefenum}
}
\crfnm@latex: {
\protected\def\crossrefenum{\crfnm@crossrefenum}
}
\crfnm@endCases
% \crossrefenum has two optional arguments.
% See the definition of \crfnm@enum below for the recognized values.
\def\crfnm@firstArg@default{page}
\def\crfnm@secondArg@default{withprefix}
\def\crfnm@crossrefenum{%
\futurelet\crfnm@nextToken\crfnm@setEnumMacro
}
\def\crfnm@setEnumMacro{%
\ifx\crfnm@nextToken [%
\def\crfnm@todo{\crfnm@setArgAndContinue[first]}%
\else
\def\crfnm@todo{%
\expandafter\expandafter\expandafter\crfnm@enum
\expandafter\expandafter\expandafter
% The following line break must be commented out.
[\expandafter\crfnm@firstArg@default\expandafter]%
\expandafter[\crfnm@secondArg@default]%
}%
\fi
\crfnm@todo
}
\def\crfnm@setArgAndContinue[#1][#2]{%
% #1 is "first" or "second"
% #2 is one of the optional arguments
% passed to the main macro
\def\crfnm@argPos{#1}%
\def\crfnm@argValue{#2}%
\futurelet\crfnm@nextToken\crfnm@set@argAndContinue
}
\def\crfnm@set@argAndContinue{%
\ifx\crfnm@argPos\crfnm@first
\ifx\crfnm@nextToken [%
\edef\crfnm@firstArg{[\crfnm@argValue]}%
\def\crfnm@todo{\crfnm@setArgAndContinue[second]}%
\else
\crfnm@ifIsOneOf[\crfnm@argValue][\crfnm@crossrefenum@secondArg@possibleValues]{%
\def\crfnm@todo{%
\crfnm@enum[\crfnm@firstArg@default][\crfnm@argValue]%
}%
}{%
\def\crfnm@todo{%
\crfnm@enum[\crfnm@argValue][\crfnm@secondArg@default]%
}%
}%
\fi
\else
\def\crfnm@todo{%
\expandafter\crfnm@enum\crfnm@firstArg[\crfnm@argValue]%
}%
\fi
\crfnm@todo
}
%%% \crossrefenum: Main private macro %%%
\def\crfnm@enum[#1][#2]#3{%
% #1 = reference type
% #2 = withprefix / noprefix or yes / no
% #3 = the enumeration
{%
% Initializes the environment for this invocation,
% then passes the enumeration to the parsing
% and formatting macro \crfnm@formatEnum.
\global\advance\crfnm@ienum by 1
% The reference type is capitalized so that it can be used
% to refer to macro names typed in camelCase
% (e.g. in \crfnm@initializeCsnames).
\edef\crfnm@refType{\crfnm@capitalize{#1}}%
\crfnm@ifIsOneOf[\crfnm@refType][\crfnm@supportedTypes]{}{%
\errmessage{crossrefenum: Unsupported type
#1 for format \fmtname{}.}
}%
\crfnm@setIfIsDoubleRef
\crfnm@applyDefaultConfigIfUndefined
\crfnm@initializeCsnames
\edef\crfnm@hasPrefix{#2}%
\ifx\crfnm@hasPrefix\crfnm@withPrefix
\let\crfnm@hasPrefix\crfnm@yes
\fi
\crfnm@enumIsFinishedfalse
\crfnm@isFirstTokentrue
\crfnm@ifIsSecondaryOfDouble[ienum: \the\crfnm@ienum]{%
\global\advance\crfnm@ienum@secondaryOfDouble by 1
}{}%
% We get the number of references typeset for the current
% invocation of \crossrefenum in the last compilation to know
% whether to use the singular or plural form of the prefix.
\edef\crfnm@printedRefsNb@previousPass{%
\crfnm@getPrintedRefsNb@previousPass
}%
% The following macro will process sequentially
% all references in the enumeration.
\expandafter\crfnm@formatEnum#3{crfnm@enumend}%
}%
}
\def\crfnm@applyDefaultConfigIfUndefined{%
\def\crfnm@applyToThisType{\crfnm@applyDefaultMacroToType[\crfnm@refType]}%
\crfnm@ifIsDoubleRef{%
\expandafter\crfnm@loopOverArgs \crfnm@customizableDefaultDoubleConfig with \crfnm@applyToThisType
\crfnm@setSubtypesOrder
\def\crfnm@applyToPrimarySubtype{\crfnm@applyDefaultMacroToType[\csname crfnm@\crfnm@refType Primary\endcsname]}%
\expandafter\crfnm@loopOverArgs \crfnm@customizableDefaultConfig with \crfnm@applyToPrimarySubtype
\def\crfnm@applyToSecondarySubtype{\crfnm@applyDefaultMacroToType[\csname crfnm@\crfnm@refType Secondary\endcsname]}%
\expandafter\crfnm@loopOverArgs \crfnm@customizableDefaultSecondaryOfDoubleConfig with \crfnm@applyToSecondarySubtype
}{%
\expandafter\crfnm@loopOverArgs \crfnm@customizableDefaultConfig with \crfnm@applyToThisType
}%
}
\def\crfnm@applyDefaultMacroToType[#1]#2{%
% #1 = type, #2 = csname without "crfnmDefault"
% \csname crfnm#1#2\endcsname is the csname for this type
\expandafter\ifx\csname crfnm#1#2\endcsname\relax
% The csname must be generated before it is passed
% to \let in \crfnm@newCsnameAlias
\expandafter\crfnm@newCsnameAlias\expandafter[\csname crfnm#1#2\endcsname]
{crfnmDefault#2}%
\fi
}
\def\crfnm@initializeCsnames{%
\crfnm@newCsnameAlias[\crfnm@rangeSep]{crfnm\crfnm@refType RangeSep}%
\crfnm@ifIsDoubleRef{%
\crfnm@newCsnameAlias[\crfnm@doubleRefOrder]{crfnm\crfnm@refType Order}%
\crfnm@newCsnameAlias[\crfnm@firstSubtype]{crfnm@\crfnm@refType First}%
\crfnm@newCsnameAlias[\crfnm@secondSubtype]{crfnm@\crfnm@refType Second}%
\crfnm@newCsnameAlias[\crfnm@primarySubtype]{crfnm@\crfnm@refType Primary}%
\crfnm@newCsnameAlias[\crfnm@secondarySubtype]{crfnm@\crfnm@refType Secondary}%
\crfnm@newCsnameAlias[\crfnm@getRawValuePrimary]{crfnm@get\crfnm@primarySubtype Number}%
\crfnm@newCsnameAlias[\crfnm@getRawValueSecondary]{crfnm@get\crfnm@secondarySubtype Number}%
\crfnm@newCsnameAlias[\crfnm@primaryCollapsable]{crfnm\crfnm@primarySubtype Collapsable}%
\crfnm@newCsnameAlias[\crfnm@secondaryCollapsable]{crfnm\crfnm@secondarySubtype Collapsable}%
\crfnm@newCsnameAlias[\crfnm@secondaryNumberingContinuous]{crfnm\crfnm@secondarySubtype NumberingContinuousAcrossDocument}%
\crfnm@newCsnameAlias[\crfnm@enumDelim]{crfnm\crfnm@refType EnumDelim}%
\crfnm@newCsnameAlias[\crfnm@beforeLastInEnum]{crfnm\crfnm@refType BeforeLastInEnum}%
\crfnm@newCsnameAlias[\crfnm@separatorBetweenSubtypes]{crfnm\crfnm@refType SubtypesSep}%
\crfnm@newCsnameAlias[\crfnm@formatSecondary]{crfnm\crfnm@secondarySubtype FormatInSecond}%
\crfnm@newCsnameAlias[\crfnm@printFirstPrefix]{crfnm\crfnm@refType PrintFirstPrefix}%
\crfnm@newCsnameAlias[\crfnm@isSecondaryPrefixPrinted]{crfnm\crfnm@secondarySubtype PrintPrefixInSecond}%
\crfnm@newCsnameAlias[\crfnm@groupSubtypes]{crfnm\crfnm@refType GroupSubtypes}%
}{%
\crfnm@ifIsSecondaryOfDouble[ienum: \the\crfnm@ienum]{%
\crfnm@newCsnameAlias[\crfnm@enumDelim]{crfnm\crfnm@refType EnumDelimInSecond}%
\crfnm@newCsnameAlias[\crfnm@beforeLastInEnum]{crfnm\crfnm@refType BeforeLastInSecond}%
}{%
\crfnm@newCsnameAlias[\crfnm@enumDelim]{crfnm\crfnm@refType EnumDelim}%
\crfnm@newCsnameAlias[\crfnm@beforeLastInEnum]{crfnm\crfnm@refType BeforeLastInEnum}%
}%
\crfnm@newCsnameAlias[\crfnm@collapsable]{crfnm\crfnm@refType Collapsable}%
\crfnm@newCsnameAlias[\crfnm@getRawValue]{crfnm@get\crfnm@refType Number}%
\crfnm@newCsnameAlias[\crfnm@typesetSingleRef]{crfnm@\crfnm@refType Ref}%
}%
}
\def\crfnm@setSubtypesOrder{%
\crfnm@newCsnameAlias[\crfnm@thisTypePrimary]{crfnm@\crfnm@refType Primary}%
\crfnm@newCsnameAlias[\crfnm@thisTypePrimary]{crfnm@\crfnm@refType Primary}%
\expandafter\ifx\csname crfnm\crfnm@refType Order\endcsname\crfnm@normal
\expandafter\let\csname crfnm@\crfnm@refType First\endcsname%
\crfnm@thisTypePrimary
\expandafter\let\csname crfnm@\crfnm@refType Second\endcsname%
\crfnm@thisTypeSecondary
\else
\expandafter\let\csname crfnm@\crfnm@refType First\endcsname%
\crfnm@thisTypeSecondary
\expandafter\let\csname crfnm@\crfnm@refType Second\endcsname%
\crfnm@thisTypePrimary
\fi
}
\def\crfnm@ifIsInverted#1#2{%
\ifx\crfnm@doubleRefOrder\crfnm@normal #2\else #1\fi
}
% Get the number of the parts of the current enumeration
% in the preceding pass from the auxiliary file.
% The macro must be purely expandable and return a number.
\crfnm@case[\fmtname]
\crfnm@context: {
\def\crfnm@getPrintedRefsNb@previousPass{%
\directlua{
registeredValue = '\datasetvariable{printedRefsNb}{\crfnm@currEnumId}{value}'
if registeredValue == '' then tex.print(0) else tex.print(registeredValue) end
}%
}
}
\fmtname: {
\def\crfnm@getPrintedRefsNb@previousPass{%
\expandafter
\ifx\csname crfnm@printedrefsnb@\crfnm@currEnumId\endcsname\relax
0
\else
\csname crfnm@printedrefsnb@\crfnm@currEnumId\endcsname
\fi
}
}
\crfnm@endCases
%%% \crossrefenum: Processing the individual references in the enumeration %%%
\def\crfnm@formatEnum#1{%
% #1 is a string consisting of either:
% * <label>
% * <label1> to <label2>
% * crfnm@enumend
\crfnm@ifIsBeginOfEnum{%
\crfnm@setCurrentRef{#1}%
% We typeset the prefix at the beginning of the enumeration
% for simple references for it appears once at the beginning of the enumeration.
% For double references, it is typeset at the beginning
% of every part of the enumeration.
\crfnm@ifIsDoubleRef{}{\crfnm@typesetPrefix}%
}{%
\crfnm@triggerWarnings{#1}%
\crfnm@advanceInEnumWith{#1}%
% The following macro compares the current reference
% with the preceding one and either merges them
% or typesets the preceding reference.
\crfnm@combine
}%
\crfnm@ifIsEndOfEnum{%
\ifnum\crfnm@printedRefsNb@previousPass=\the\crfnm@printedRefsNb\relax\else
\crfnm@warn@newPassNeeded{%
crossrefenum changed some enumerations.
Rerun to get all prefixes right.%
}%
\fi
\crfnm@registerPrintedRefsNb
\crfnm@ifIsDoubleRef{\global\crfnm@ienum@secondaryOfDouble=0}{}%
}{%
\expandafter\crfnm@formatEnum
}%
}
\def\crfnm@setCurrentRef#1{%
\crfnm@ifIsDoubleRef{%
\def\crfnm@currentPrimary{#1}%
\def\crfnm@currentSecondary{#1}%
}{%
\def\crfnm@current{#1}%
}%
}
\def\crfnm@ifIsBeginOfEnum#1#2{%
\edef\crfnm@csnameCurrent{%
crfnm@current\ifcrfnm@isDoubleRef Primary\fi%
}%
\expandafter\ifx\csname\crfnm@csnameCurrent\endcsname\relax
#1%
\else
#2%
\fi
}
\def\crfnm@typesetPrefix{%
\ifx\crfnm@hasPrefix\crfnm@yes
\crfnm@ifIsDoubleRef{%
\crfnm@ifIsInverted{%
\crfnm@typeset@@prefix[sg]%
}{%
\ifx\crfnm@printFirstPrefix\crfnm@always
\crfnm@typeset@@prefix[sg]%
\else
\ifcrfnm@isFirstToken\crfnm@typeset@prefix\fi
\fi
}%
}{%
\crfnm@typeset@prefix
}%
\fi
}
\def\crfnm@typeset@prefix{%
\ifnum\crfnm@printedRefsNb@previousPass>1
\crfnm@typeset@@prefix[pl]%
\else
\crfnm@typeset@@prefix[sg]%
\fi
}
\def\crfnm@typeset@@prefix[#1]{%
\def\crfnm@prefixform{#1}%
\csname crfnm%
\crfnm@ifIsDoubleRef{%
\crfnm@ifIsInverted{\crfnm@secondSubtype}{\crfnm@firstSubtype}%
}{%
\crfnm@refType
}%
\ifx\crfnm@prefixform\crfnm@plural s\fi
\endcsname
}
\def\crfnm@triggerWarnings#1{%
% Raises the “undefined label” or “references have changed” warnings
% even if the label doesn't get used in this pass, thus causing a new
% pass to be performed.
% Works in LaTeX because warnings are sent via \immediate\write.
% It should also work in ConTeXt because it writes the logs through
% a Lua call, not \write.
\def\crfnm@tested{#1}%
\ifx\crfnm@tested\crfnm@enumend\else
\setbox0=\hbox{\crfnm@simulateTypesetting{#1}}%
\fi
}
\def\crfnm@simulateTypesetting#1{%
\crfnm@simulatedtrue
\crfnm@ifIsDoubleRef{%
% We can't use \crfnm@typesetdouble here, for it would result
% in nested calls to \setbox0.
% Nevertheless we have to test for both subtypes,
% since the value of each of them may change
% while that of the other remains the same.
\let\crfnm@realRefType\crfnm@refType%
\crfnm@isDoubleReffalse
\edef\crfnm@refType{\crfnm@primarySubtype}%
\crfnm@initializeCsnames
\crfnm@wrapInDisplayMacro{#1}%
\edef\crfnm@refType{\crfnm@secondarySubtype}%
\crfnm@initializeCsnames
\crfnm@wrapInDisplayMacro{#1}%
\let\crfnm@refType\crfnm@realRefType
\crfnm@isDoubleReftrue
\crfnm@initializeCsnames
}{%
\crfnm@wrapInDisplayMacro{#1}%
}%
\crfnm@simulatedfalse
}
\def\crfnm@advanceInEnumWith#1{%
\crfnm@ifIsDoubleRef{%
\let\crfnm@precedingPrimary\crfnm@currentPrimary
\let\crfnm@precedingSecondary\crfnm@currentSecondary
\def\crfnm@currentPrimary{#1}%
\def\crfnm@currentSecondary{#1}%
}{%
\let\crfnm@preceding\crfnm@current
\def\crfnm@current{#1}%
}%
}
\def\crfnm@combine{%
\crfnm@ifIsEndOfEnum{%
\crfnm@enumIsFinishedtrue
\ifcrfnm@isFirstToken\else\crfnm@beforeLastInEnum\fi
\crfnm@typesetPrecedingRef
}{%
\crfnm@compareTypes
\ifcrfnm@areSingleAndRange
\crfnm@combineSingleAndRange
\else
\edef\crfnm@maybeRange{\csname crfnm@current\crfnm@ifIsDoubleRef{Primary}{}\endcsname}%
\crfnm@ifIsRange\crfnm@maybeRange{%
\crfnm@combineRanges
}{%
\crfnm@combineSingles
}%
\fi
}%
}
\def\crfnm@ifIsEndOfEnum#1#2{%
\edef\crfnm@currentInEnum{%
\crfnm@ifIsDoubleRef{%
\crfnm@currentPrimary
}{%
\crfnm@current
}%
}%
\ifx\crfnm@currentInEnum\crfnm@enumend
#1%
\else
#2%
\fi
}
% Write the number of the parts of the current enumeration
% to the auxiliary file.
\def\crfnm@registerPrintedRefsNb{%
\crfnm@case[\fmtname]
\crfnm@context: {%
\ifx\fmtname\crfnm@context
\setdataset[printedRefsNb][\crfnm@currEnumId][value={\the\crfnm@printedRefsNb}]%
\fi
}
\fmtname: {%
\immediate\write\crfnm@auxfile{%
\gdef\expandafter\noexpand\csname crfnm@printedrefsnb@\crfnm@currEnumId\endcsname
{\the\crfnm@printedRefsNb}%
}%
}
\crfnm@endCases
}
\def\crfnm@typesetPrecedingRef{%
\crfnm@ifIsDoubleRef{%
\crfnm@typesetDoubleRef{\crfnm@precedingPrimary}{\crfnm@precedingSecondary}%
}{%
\crfnm@wrapInDisplayMacro{\crfnm@preceding}%
}%
}
\def\crfnm@compareTypes{%
\crfnm@setPossibleRangeCs
\crfnm@ifIsRange{\crfnm@possibleRange@preceding}{%
\crfnm@ifIsRange{\crfnm@possibleRange@current}{%
\crfnm@areSingleAndRangefalse
}{%
\crfnm@areSingleAndRangetrue
}%
}{%
\crfnm@ifIsRange{\crfnm@possibleRange@current}{%
\crfnm@areSingleAndRangetrue
}{%
\crfnm@areSingleAndRangefalse
}%
}%
}
\def\crfnm@setPossibleRangeCs{%
% We use the secondary subtype, since it can become a range
% as an effect of \crfnm@combineSingles while the primary subtype
% keeps being a single; the reverse can't be true.
\crfnm@newCsnameAlias[\crfnm@possibleRange@preceding]{crfnm@preceding\ifcrfnm@isDoubleRef Secondary\fi}%
\crfnm@newCsnameAlias[\crfnm@possibleRange@current]{crfnm@current\ifcrfnm@isDoubleRef Secondary\fi}%
}
\def\crfnm@combineSingles{%
\crfnm@ifIsDoubleRef{%
\crfnm@combine@singles@double
}{%
\crfnm@combine@singles@simple
}%
}
\def\crfnm@combine@singles@double{%
\edef\crfnm@raw@precedingPrimary{\crfnm@getRawValuePrimary\crfnm@precedingPrimary}%
\edef\crfnm@raw@currentPrimary{\crfnm@getRawValuePrimary\crfnm@currentPrimary}%
\crfnm@ifequal[\crfnm@raw@precedingPrimary][\crfnm@raw@currentPrimary]{%
\edef\crfnm@currentPrimary{\crfnm@precedingPrimary}%
\crfnm@newListFrom[\crfnm@precedingSecondary][\crfnm@currentSecondary] -> \crfnm@currentSecondary
}{%
\crfnm@typesetPrecedingRange
}%
}
\def\crfnm@combine@singles@simple{%
\edef\crfnm@raw@preceding{\crfnm@getRawValue\crfnm@preceding}%
\edef\crfnm@raw@current{\crfnm@getRawValue\crfnm@current}%
\crfnm@ifequal[\crfnm@raw@preceding][\crfnm@raw@current]{%
%Do nothing, so discard \crfnm@preceding.
}{%
\crfnm@ifConsecutiveCollapsable[\crfnm@raw@preceding][\crfnm@raw@current]{%
\edef\crfnm@current{\crfnm@preceding\crfnm@labelRangeSep\crfnm@current}%
}{%
\ifcrfnm@isFirstToken\else
\crfnm@enumDelim
\fi
\crfnm@wrapInDisplayMacro{\crfnm@preceding}%
}%
}%
}
\def\crfnm@combineRanges{%
\crfnm@ifIsDoubleRef{%
\crfnm@combine@ranges@doubles
}{%
\crfnm@combine@ranges@simples
}%
}
\def\crfnm@getLabelInRange@begin[#1]{%
\expandafter\crfnm@get@labelInRange@begin\expandafter[#1]%
}
\def\crfnm@getLabelInRange@end[#1]{%
\expandafter\crfnm@get@labelInRange@end\expandafter[#1]%
}
\expandafter\def\expandafter\crfnm@get@labelInRange@begin\expandafter[\expandafter#\expandafter1\crfnm@labelRangeSep#2]{#1}%
\expandafter\def\expandafter\crfnm@get@labelInRange@end\expandafter[\expandafter#\expandafter1\crfnm@labelRangeSep#2]{#2}%
\def\crfnm@combine@ranges@simples{%
\edef\crfnm@precedingBegin{\crfnm@getLabelInRange@begin[\crfnm@preceding]}%
\edef\crfnm@precedingEnd{\crfnm@getLabelInRange@end[\crfnm@preceding]}%
\edef\crfnm@currentBegin{\crfnm@getLabelInRange@begin[\crfnm@current]}%
\edef\crfnm@currentEnd{\crfnm@getLabelInRange@end[\crfnm@current]}%
\edef\crfnm@raw@precedingBegin{\crfnm@getRawValue\crfnm@precedingBegin}%
\edef\crfnm@raw@precedingEnd{\crfnm@getRawValue\crfnm@precedingEnd}%
\edef\crfnm@raw@currentBegin{\crfnm@getRawValue\crfnm@currentBegin}%
\edef\crfnm@raw@currentEnd{\crfnm@getRawValue\crfnm@currentEnd}%
\def\crfnm@mergeRanges{%
\edef\crfnm@current{\crfnm@precedingBegin\crfnm@labelRangeSep\crfnm@currentEnd}%
}%
\crfnm@ifequal[\crfnm@raw@precedingEnd][\crfnm@raw@currentBegin]{%
\crfnm@mergeRanges
}{%
\crfnm@ifConsecutiveCollapsable[\crfnm@raw@precedingEnd][\crfnm@raw@currentBegin]{%
\crfnm@mergeRanges
}{%
\ifcrfnm@isFirstToken\else\crfnm@enumDelim\fi
\crfnm@wrapInDisplayMacro{\crfnm@preceding}%
}%
}%
}
\def\crfnm@combine@ranges@doubles{%
% \crfnm@<pre/cur>Secondary are identical with \crfnm@<pre/cur>Primary
% at the beginning and at the end of this macro
% because the secondary value may not be an enumeration
% at either ends of a range.
% That is why we don't use them in our comparisons here.
% Note: when the lineation is not continuous, we cannot handle
% properly the case where the end of the first range is on the last
% line of a page and the beginning of the second range is on the
% first line of the following page. This is because we cannot know
% if a given line is the last on the page.
\edef\crfnm@precedingBegin{\crfnm@getLabelInRange@begin[\crfnm@precedingPrimary]}%
\edef\crfnm@precedingEnd{\crfnm@getLabelInRange@end[\crfnm@precedingPrimary]}%
\edef\crfnm@currentBegin{\crfnm@getLabelInRange@begin[\crfnm@currentPrimary]}%
\edef\crfnm@currentEnd{\crfnm@getLabelInRange@end[\crfnm@currentPrimary]}%
\edef\crfnm@primarySubtype@precedingEnd{\crfnm@getRawValuePrimary\crfnm@precedingEnd}%
\edef\crfnm@primarySubtype@currentBegin{\crfnm@getRawValuePrimary\crfnm@currentBegin}%
\edef\crfnm@secondarySubtype@precedingEnd{\crfnm@getRawValueSecondary\crfnm@precedingEnd}%
\edef\crfnm@secondarySubtype@currentBegin{\crfnm@getRawValueSecondary\crfnm@currentBegin}%
\crfnm@ifequal[\crfnm@primarySubtype@precedingEnd][\crfnm@primarySubtype@currentBegin]{%
\crfnm@ifequal[\crfnm@secondarySubtype@precedingEnd][\crfnm@secondarySubtype@currentBegin]{%
\crfnm@mergeRanges
}{%
\crfnm@ifConsecutiveCollapsable[secondary]%
[\crfnm@secondarySubtype@precedingEnd][\crfnm@secondarySubtype@currentBegin]
{%
\crfnm@mergeRanges
}{%
\edef\crfnm@primarySubtype@precedingBegin{\crfnm@getRawValuePrimary\crfnm@precedingBegin}%
\edef\crfnm@primarySubtype@currentEnd{\crfnm@getRawValuePrimary\crfnm@currentEnd}%
\crfnm@ifequal[\crfnm@primarySubtype@precedingBegin][\crfnm@primarySubtype@currentEnd]{%
% Two discountinuous ranges of the secondary subtype on the same page.
\crfnm@newListFrom[\crfnm@precedingSecondary][\crfnm@currentSecondary] -> \crfnm@currentSecondary
}{%
\crfnm@typesetPrecedingRange
}%
}%
}%
}{%
\ifx\crfnm@secondaryNumberingContinuous\crfnm@yes
% It would make no sense to test for identical line numbers here.
\crfnm@ifConsecutiveCollapsable[primary]%
[\crfnm@primarySubtype@precedingEnd][\crfnm@primarySubtype@currentBegin]
{%
\crfnm@ifConsecutiveCollapsable[secondary]%
[\crfnm@secondarySubtype@precedingEnd][\crfnm@secondarySubtype@currentBegin]
{%
\crfnm@mergeRanges
}{%
\crfnm@typesetPrecedingRange
}%
}{%
\crfnm@typesetPrecedingRange
}%
\else
\crfnm@typesetPrecedingRange
\fi
}%
}
\def\crfnm@mergeRanges{%
\edef\crfnm@currentPrimary{\crfnm@precedingBegin\crfnm@labelRangeSep\crfnm@currentEnd}%
\let\crfnm@currentSecondary\crfnm@currentPrimary
}
\def\crfnm@combineSingleAndRange{%
\crfnm@setPossibleRangeCs
\crfnm@ifIsRange{\crfnm@possibleRange@current}{%
\crfnm@combine@singleAndRange[singlefirst]%
}{%
\crfnm@combine@singleAndRange[reversed]%
}%
}
\def\crfnm@typesetPrecedingRange{%
\ifcrfnm@isFirstToken\else
\crfnm@enumDelim
\fi
\crfnm@typesetDoubleRef{\crfnm@precedingPrimary}{\crfnm@precedingSecondary}%
}%
\def\crfnm@combine@singleAndRange[#1]{%
\crfnm@setIfIsSingleFirst[#1]%
\crfnm@ifIsDoubleRef{%
\crfnm@combine@single@and@range@double
}{%
\ifcrfnm@singleFirst
\let\crfnm@single\crfnm@preceding
\let\crfnm@range\crfnm@current
\else
\let\crfnm@single\crfnm@current
\let\crfnm@range\crfnm@preceding
\fi
\crfnm@combine@single@and@range@simple
}%
}
\def\crfnm@setIfIsSingleFirst[#1]{%
\def\crfnm@singlePos{#1}% expected: singlefirst or reversed
\ifx\crfnm@singlePos\crfnm@singleFirst
\crfnm@singleFirsttrue
\else
\crfnm@singleFirstfalse
\fi
}
\def\crfnm@combine@single@and@range@simple{%
\edef\crfnm@begin@range{\crfnm@getLabelInRange@begin[\crfnm@range]}%
\edef\crfnm@end@range{\crfnm@getLabelInRange@end[\crfnm@range]}%
\edef\crfnm@raw@single{\crfnm@getRawValue\crfnm@single}%
\edef\crfnm@raw@begin@range{\crfnm@getRawValue\crfnm@begin@range}%
\edef\crfnm@raw@end@range{\crfnm@getRawValue\crfnm@end@range}%
\ifcrfnm@singleFirst
\crfnm@ifAreEqualOrConsecutiveCollapsable[][\crfnm@raw@single][\crfnm@raw@begin@range]{%
\edef\crfnm@current{%
\crfnm@newRangeWithReplacement[change: \crfnm@current, with: \crfnm@preceding, at: beg]%
}%
}{%
\crfnm@typesetInEnum{\crfnm@preceding}%
}%
\else
\crfnm@ifequal[\crfnm@raw@end@range][\crfnm@raw@single]{%
\edef\crfnm@current{\crfnm@preceding}%
}{%
\crfnm@ifConsecutiveCollapsable[\crfnm@raw@end@range][\crfnm@raw@single]{%
\edef\crfnm@current{%
\crfnm@newRangeWithReplacement[change: \crfnm@preceding, with: \crfnm@current, at: end]%
}%
}{%
\crfnm@typesetInEnum{\crfnm@preceding}%
}%
}%
\fi
}
\def\crfnm@combine@single@and@range@double{%
\ifcrfnm@singleFirst
\edef\crfnm@currentBegin{\crfnm@getLabelInRange@begin[\crfnm@currentPrimary]}%
\edef\crfnm@primaryRawValue@preceding{\crfnm@getRawValuePrimary\crfnm@precedingPrimary}%
\edef\crfnm@primaryRawValue@current{\crfnm@getRawValuePrimary\crfnm@currentBegin}%
\edef\crfnm@secondaryRawValue@preceding{\crfnm@getRawValueSecondary\crfnm@precedingSecondary}%
\edef\crfnm@secondaryRawValue@current{\crfnm@getRawValueSecondary\crfnm@currentBegin}%
\let\crfnm@typesetPreceding\crfnm@typesetPrecedingRef
\def\crfnm@rangeRoot{crfnm@current}%
\def\crfnm@singleRoot{crfnm@preceding}%
\else
\edef\crfnm@precedingEnd{\crfnm@getLabelInRange@end[\crfnm@precedingPrimary]}%
\edef\crfnm@primaryRawValue@preceding{\crfnm@getRawValuePrimary\crfnm@precedingEnd}%
\edef\crfnm@primaryRawValue@current{\crfnm@getRawValuePrimary\crfnm@currentPrimary}%
\edef\crfnm@secondaryRawValue@preceding{\crfnm@getRawValueSecondary\crfnm@precedingEnd}%
\edef\crfnm@secondaryRawValue@current{\crfnm@getRawValueSecondary\crfnm@currentSecondary}%
\let\crfnm@typesetPreceding\crfnm@typesetPrecedingRange
\def\crfnm@rangeRoot{crfnm@preceding}%
\def\crfnm@singleRoot{crfnm@current}%
\fi
\crfnm@ifequal[\crfnm@primaryRawValue@current][\crfnm@primaryRawValue@preceding]{%
\crfnm@ifAreEqualOrConsecutiveCollapsable[secondary]%
[\crfnm@secondaryRawValue@current][\crfnm@secondaryRawValue@preceding]%
{%
\crfnm@mergeSingleAndRangeDouble
}{%
\crfnm@typesetPreceding
}%
}{%
\crfnm@ifConsecutiveCollapsable[secondary]%
[\crfnm@secondaryRawValue@current][\crfnm@secondaryRawValue@preceding]%
{%
\crfnm@mergeSingleAndRangeDouble
}{%
\crfnm@typesetPreceding
}%
}%
}
\def\crfnm@mergeSingleAndRangeDouble{%
\edef\crfnm@changedBoundary{\ifcrfnm@singleFirst beg\else end\fi}%
\crfnm@mergeSingleAndRangeDouble@subtype
[changeRoot: \crfnm@rangeRoot, withRoot: \crfnm@singleRoot, at: \crfnm@changedBoundary][Primary]%
\crfnm@mergeSingleAndRangeDouble@subtype
[changeRoot: \crfnm@rangeRoot, withRoot: \crfnm@singleRoot, at: \crfnm@changedBoundary][Secondary]%
}
\def\crfnm@mergeSingleAndRangeDouble@subtype[changeRoot: #1, withRoot: #2, at: #3][#4]{%
\edef\crfnm@rangeToBeChanged{\csname #1#4\endcsname}%
\edef\crfnm@singleForChange{\csname #2#4\endcsname}%
\expandafter\edef\csname crfnm@current#4\endcsname{%
\crfnm@newRangeWithReplacement
[change: \crfnm@rangeToBeChanged, with: \crfnm@singleForChange, at: #3]%
}%
}
\def\crfnm@ifAreEqualOrConsecutiveCollapsable[#1][#2][#3]#4#5{%
% #1 is “primary”, “secondary” or empty (for simple types).
\crfnm@ifequal[#2][#3]{#4}{%
\crfnm@ifConsecutiveCollapsable[#1][#2][#3]{#4}{#5}%
}%
}
\def\crfnm@ifConsecutiveCollapsable[#1]{%
% If the current reference has a double type, this macro must carry a first argument
% indicating if the current subtype is “primary”, “secondary”.
% With a simple type, it may be missing or empty.
% The other two arguments are the raw reference values to be compared.
% The comparison itself is performed by \crfnm@if@consecutiveCollapsable,
% which takes the type indication as a mandatory argument.
% The following code here simply sets the type if it is not provided by the user.
\crfnm@ifIsOneOf[#1][{{primary}{secondary}{}}]{%
\def\crfnm@macroWithParam{\crfnm@if@consecutiveCollapsable[#1]}%
}{%
\def\crfnm@macroWithParam{\crfnm@if@consecutiveCollapsable[][#1]}%
}%
\crfnm@macroWithParam
}
\def\crfnm@if@consecutiveCollapsable[#1][#2][#3]#4#5{%
% #1 is “primary”, “secondary” or empty (for simple types).
% #2 and #3 are the raw numbers for the first and the second references.
\crfnm@newCsnameAlias[\crfnm@thisTypeCollapsable]{crfnm@\crfnm@ifIsDoubleRef{#1C}{c}ollapsable}%
\crfnm@newCsnameAlias[\crfnm@testedType]{crfnm@\crfnm@ifIsDoubleRef{#1Subtype}{refType}}%
\ifx\crfnm@thisTypeCollapsable\crfnm@yes
\crfnm@ifSimpleOrPrimaryType{% Uses \crfnm@testedType
\crfnm@ifAreConsecutive[#2][#3]{#4}{#5}%
}{%
\ifx\crfnm@secondaryNumberingContinuous\crfnm@yes
\crfnm@ifAreConsecutive[#2][#3]{#4}{#5}%
\else #5\fi
}%
\else #5\fi
}
\def\crfnm@ifSimpleOrPrimaryType#1#2{%
\crfnm@ifIsOneOf[\crfnm@testedType][\crfnm@simpleRefTypes]{#1}{%
\ifx\crfnm@testedType\crfnm@primarySubtype #1\else #2\fi
}%
}
\def\crfnm@ifAreConsecutive[#1][#2]#3#4{%
\ifnum\numexpr#1+1\relax=#2 #3\else #4\fi
}
\def\crfnm@newRangeWithReplacement[change: #1, with: #2, at: #3#4]{%
% #3#4 is “beg” or “end” (we test only the first letter).
% This macro must be purely expandable.
\ifx #3b%
#2 to \crfnm@getLabelInRange@end[#1]%
\else
\crfnm@getLabelInRange@begin[#1] to #2%
\fi
}
\def\crfnm@typesetInEnum#1{%
\ifcrfnm@isFirstToken\else\crfnm@enumDelim\fi
\crfnm@wrapInDisplayMacro{#1}%
}
\def\crfnm@wrapInDisplayMacro#1{%
\crfnm@countInPrintedRefs
\crfnm@wrapRangeOrSingle{#1}%
\crfnm@isFirstTokenfalse
}
\def\crfnm@countInPrintedRefs{%
\ifcrfnm@simulated\else
% Since the incrementation is local,
% \crfnm@printedRefsNb will be automatically reset to 0
% at the end of the current invocation of \crossrefenum.
\advance\crfnm@printedRefsNb by 1
\fi
}
\def\crfnm@wrapRangeOrSingle#1{%
\edef\crfnm@toBeWrapped{#1}%
\crfnm@ifIsRange{\crfnm@toBeWrapped}{%
\crfnm@wrapRange{\crfnm@toBeWrapped}%
}{%
\crfnm@ifIsDoubleRef{%
% We are in the primary part of a double type,
% since the secondary one is handled by \crfnm@fork
% like a simple type.
\crfnm@newCsnameAlias[\crfnm@typesetSingleRef]{crfnm@\crfnm@primarySubtype Ref}%
}{}%
\crfnm@typesetSingleRef{\crfnm@toBeWrapped}%
}%
}
\def\crfnm@wrapRange#1{%
\expandafter\crfnm@wrap@range\expandafter[#1]%
}
\expandafter\def\expandafter\crfnm@wrap@range\expandafter[\expandafter#\expandafter1\crfnm@labelRangeSep#2]{%
\crfnm@typesetRange{#1}{#2}%
}
\def\crfnm@typesetRange#1#2{%
\edef\crfnm@refTypeForRange{%
\crfnm@ifIsDoubleRef{\crfnm@primarySubtype}{\crfnm@refType}%
}%
\edef\crfnm@beginRangeToTypeset{#1}%
\edef\crfnm@endRangeToTypeset{#2}%
\crfnm@typeset@range{\crfnm@beginRangeToTypeset}{\crfnm@endRangeToTypeset}[%
cs to get the raw reference number: crfnm@get\crfnm@refTypeForRange Number,
cs to print the reference: crfnm@\crfnm@refTypeForRange Ref%
]%
}
\def\crfnm@typeset@range#1#2[%
cs to get the raw reference number: #3,
cs to print the reference: #4%
]{%
% #1 and #2 are the labels
\def\crfnm@getCountCsname{#3}%
\edef\crfnm@firstNumber{\csname\crfnm@getCountCsname\endcsname{#1}}%
\edef\crfnm@secondNumber{\csname\crfnm@getCountCsname\endcsname{#2}}%
\def\crfnm@typeset{\expandafter\csname #4\endcsname}%
\ifx\crfnm@firstNumber\crfnm@secondNumber
\crfnm@typeset{#1}%
\else
\crfnm@countInPrintedRefs
\crfnm@typeset{#1}\crfnm@rangeSep\crfnm@typeset{#2}%
\fi
}
\def\crfnm@typesetDoubleRef#1#2{%
% #1 is a label
% #2 is a list of labels to be passed to \crossrefenum
\crfnm@ifIsInverted{%
\crfnm@typesetDoubleRef@inverted{#1}{#2}%
}{%
\crfnm@typesetDoubleRef@normal{#1}{#2}%
}%
}
\def\crfnm@typesetDoubleRef@normal#1#2{%
\ifx\crfnm@groupSubtypes\crfnm@yes
\crfnm@typesetDoubleRef@normal@grouped{#1}{#2}%
\else
\crfnm@typesetDoubleRef@normal@split{#1}{#2}%
\fi
}
\def\crfnm@typesetDoubleRef@normal@grouped#1#2{%
\crfnm@typesetPrefix
\crfnm@wrapInDisplayMacro{#1}%
\crfnm@separatorBetweenSubtypes
\crfnm@formatSecondary{%
\crfnm@fork{%
\crfnm@ifIsList[#2]{%
\edef\crfnm@enumOfSecondary{#2}%
}{%
\edef\crfnm@enumOfSecondary{{#2}}%
}%
\crfnm@enum[\crfnm@secondarySubtype][\crfnm@isSecondaryPrefixPrinted]{\crfnm@enumOfSecondary}%
}%
}%
}
\def\crfnm@typesetDoubleRef@normal@split#1#2{%
\edef\crfnm@labelForPrimary{#1}%
\crfnm@ifIsRange\crfnm@labelForPrimary{%
\crfnm@typesetDoubleRef@normal@split@range{#1}{#2}%
}{%
\crfnm@typesetDoubleRef@normal@split@single{#1}{#2}%
}%
}
\def\crfnm@typesetDoubleRef@normal@split@range#1#2{%
\edef\crfnm@labelForPrimary{#1}%
\edef\crfnm@beginRangeLabel{%
\crfnm@getLabelInRange@begin[\crfnm@labelForPrimary]%
}%
\edef\crfnm@endRangeLabel{%
\crfnm@getLabelInRange@end[\crfnm@labelForPrimary]%
}%
\edef\crfnm@beginPrimaryRaw{\crfnm@getRawValuePrimary\crfnm@beginRangeLabel}%
\edef\crfnm@endPrimaryRaw{\crfnm@getRawValuePrimary\crfnm@endRangeLabel}%
\ifx\crfnm@beginPrimaryRaw\crfnm@endPrimaryRaw
\edef\crfnm@labelsForSecondary{#2}%
\crfnm@typesetDoubleRef@normal@grouped{\crfnm@beginRangeLabel}{#2}%
\else
\crfnm@ifIsList[#2]{%
\edef\crfnm@allSecondaryLabels{#2}%
}{%
\edef\crfnm@allSecondaryLabels{{#2}}%
}%
\edef\crfnm@labelsForSecondary{%
\crfnm@replaceFirstInList[\crfnm@endRangeLabel]{\crfnm@allSecondaryLabels}%
}%
\crfnm@typesetPrefix
\crfnm@wrapInDisplayMacro{\crfnm@beginRangeLabel}%
\crfnm@separatorBetweenSubtypes
\crfnm@formatSecondary{%
\crfnm@fork{%
\crfnm@enum[\crfnm@secondarySubtype][\crfnm@isSecondaryPrefixPrinted]{%
{\crfnm@beginRangeLabel}%
}%
}%
}%
\crfnm@rangeSep
\crfnm@wrapInDisplayMacro{\crfnm@endRangeLabel}%
\crfnm@separatorBetweenSubtypes
\crfnm@formatSecondary{%
\crfnm@fork{%
\crfnm@enum[\crfnm@secondarySubtype][\crfnm@isSecondaryPrefixPrinted]{\crfnm@labelsForSecondary}%
}%
}%
\fi
}
\def\crfnm@typesetDoubleRef@normal@split@single#1#2{%
\crfnm@typesetDoubleRef@normal@grouped{#1}{#2}%
}
\def\crfnm@typesetDoubleRef@inverted#1#2{%
\crfnm@formatSecondary{%
\crfnm@fork{%
\crfnm@ifIsList[#2]{%
\edef\crfnm@enumOfSecondary{#2}%
}{%
\edef\crfnm@enumOfSecondary{{#2}}%
}%
\crfnm@enum[\crfnm@secondarySubtype][withprefix]{\crfnm@enumOfSecondary}%
}%
}%
\crfnm@separatorBetweenSubtypes
\crfnm@typesetPrefix
\crfnm@wrapInDisplayMacro{#1}%
}
\def\crfnm@fork#1{%
\crfnm@saveState
\global\advance\crfnm@ienum by \crfnm@secondaryOfDouble@istart
\global\advance\crfnm@ienum by -1
\crfnm@printedRefsNb=0
#1%
\crfnm@restoreState
}
\def\crfnm@saveState{%
\edef\crfnm@parentIenum{\the\crfnm@ienum}%
\edef\crfnm@parentPrintedRefsNb{\the\crfnm@printedRefsNb}%
\let\crfnm@parentCurrentPrimary\crfnm@currentPrimary
\let\crfnm@parentCurrentSecondary\crfnm@currentSecondary
\let\crfnm@parentPrecedingPrimary\crfnm@precedingPrimary
\let\crfnm@parentPrecedingSecondary\crfnm@precedingSecondary
\let\crfnm@parentRefType\crfnm@refType
}
\def\crfnm@restoreState{%
\crfnm@isDoubleReftrue
\global\crfnm@ienum=\crfnm@parentIenum
\crfnm@printedRefsNb=\crfnm@parentPrintedRefsNb
\let\crfnm@currentPrimary\crfnm@parentCurrentPrimary
\let\crfnm@currentSecondary\crfnm@parentCurrentSecondary
\let\crfnm@precedingPrimary\crfnm@parentPrecedingPrimary
\let\crfnm@precedingSecondary\crfnm@parentPrecedingSecondary
\let\crfnm@refType\crfnm@parentRefType
}
\catcode`\@=\crfnmOriginalCatcodeAt