crossrefenum/tex/crossrefenum.tex

1498 lines
49 KiB
TeX
Raw Normal View History

2022-11-13 18:32:36 +00:00
%***********************************************************************
\def\crfnmName{crossrefenum}
\def\crfnmShortDesc{Smart typesetting of enumerated cross-references for various TeX formats}
\def\crfnmAuthor{Bastien Dumont}
\def\crfnmDate{2022/11/11}
\def\crfnmVersion{0.1}
%
% Copyright 2022 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 a reference.
% #2 is 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@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 %%%
% \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\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