-
Notifications
You must be signed in to change notification settings - Fork 4
/
zref-clever.dtx
12052 lines (11795 loc) · 387 KB
/
zref-clever.dtx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
% \iffalse meta-comment
%
% File: zref-clever.dtx
%
% This file is part of the LaTeX package "zref-clever".
%
% Copyright (C) 2021-2024 gusbrs
%
% It may be distributed and/or modified under the conditions of the
% LaTeX Project Public License (LPPL), either version 1.3c of this
% license or (at your option) any later version. The latest version
% of this license is in the file:
%
% https://www.latex-project.org/lppl.txt
%
% and version 1.3 or later is part of all distributions of LaTeX
% version 2005/12/01 or later.
%
%
% This work is "maintained" (as per LPPL maintenance status) by gusbrs.
%
% This work consists of the files zref-clever.dtx,
% zref-clever.ins,
% zref-clever-doc.tex,
% zref-clever-code.tex,
% and the files generated from them.
%
% The released version of this package is available from CTAN.
%
% -----------------------------------------------------------------------
%
% The development version of the package can be found at
%
% https://github.com/gusbrs/zref-clever
%
% for those people who are interested.
%
% -----------------------------------------------------------------------
%
% \fi
%
% \iffalse
%<*driver>
\documentclass{l3doc}
\usepackage{fontspec}
\setmainfont{CMU Serif}
\setsansfont{CMU Sans Serif}
\setmonofont{CMU Typewriter Text}
% Have \GetFileInfo pick up date and version data and used in the
% documentation.
\usepackage{zref-clever}
\zcsetup{cap,nameinlink=false}
\usepackage{zref-check}
\usepackage{zref-titleref}
\begin{document}
\DocInput{zref-clever.dtx}
\end{document}
%</driver>
% \fi
%
% \DoNotIndex{\\,\{,\}}
% \DoNotIndex{\c@,\cl@,\c@enumN,\p@}
% \DoNotIndex{\the}
% \DoNotIndex{\A,\Z,\d}
%
% \NewDocumentCommand\githubissue{m}{^^A
% issue~\href{https://github.com/gusbrs/zref-clever/issues/#1}{\##1}}
%
% \NewDocumentCommand\githubPR{m}{^^A
% PR~\href{https://github.com/gusbrs/zref-clever/pull/#1}{\##1}}
%
% \NewDocumentCommand\contributor{m}{#1}
% \NewDocumentCommand\username{m}{`\texttt{#1}'}
%
% \NewDocumentCommand\opt{m}{\texttt{#1}}
%
% \pdfstringdefDisableCommands{^^A
% \def\opt#1{#1}
% }
%
% ^^A Have the Index at 'section' level rather than 'part'. Otherwise it is
% ^^A just the same definition from 'l3doc.cls'.
% \IndexPrologue{^^A
% \section*{Index}
% \markboth{Index}{Index}
% \addcontentsline{toc}{section}{Index}
% The italic numbers denote the pages where the corresponding entry is
% described, numbers underlined point to the definition, all others indicate
% the places where it is used.^^A
% }
%
%
% \GetFileInfo{zref-clever.sty}
%
% \title{^^A
% The \pkg{zref-clever} package^^A
% \texorpdfstring{\\{}\medskip{}}{ - }^^A
% Code documentation^^A
% \texorpdfstring{\medskip{}}{}^^A
% }
%
% \author{^^A
% \texorpdfstring{\texttt{gusbrs}\\[0.8em]
% \url{https://github.com/gusbrs/zref-clever}\\
% \url{https://www.ctan.org/pkg/zref-clever}}{gusbrs}}
%
% \date{Version \fileversion\ -- \filedate}
%
% \maketitle
%
% \begin{center}
% \textbf{EXPERIMENTAL}
% \end{center}
%
%
% \tableofcontents
%
%
% \section{Initial setup}
%
% Start the \pkg{DocStrip} guards.
% \begin{macrocode}
%<*package>
% \end{macrocode}
%
% Identify the internal prefix (\LaTeX3 \pkg{DocStrip} convention).
% \begin{macrocode}
%<@@=zrefclever>
% \end{macrocode}
%
% Taking a stance on backward compatibility of the package. During initial
% development, we have used freely recent features of the kernel (albeit
% refraining from \pkg{l3candidates}). We presume \pkg{xparse} (which made to
% the kernel in the 2020-10-01 release), and \pkg{expl3} as well (which made
% to the kernel in the 2020-02-02 release). We also just use UTF-8 for the
% language files (which became the default input encoding in the 2018-04-01
% release). Also, a couple of changes came with the 2021-11-15 kernel
% release, which are important here. First, a fix was made to the new hook
% management system (\pkg{ltcmdhooks}), with implications to the hook we add
% to \cs{appendix} (by \contributor{Phelype Oleinik} at
% \url{https://tex.stackexchange.com/q/617905} and
% \url{https://github.com/latex3/latex2e/pull/699}). Second, the support for
% \cs{@currentcounter} has been improved, including \cs{footnote} and
% \pkg{amsmath} (by \contributor{Frank Mittelbach} and \contributor{Ulrike
% Fischer} at \url{https://github.com/latex3/latex2e/issues/687}).
% Critically, the new \texttt{label} hook introduced in the 2023-06-01
% release, alongside the corresponding new hooks with arguments, just
% simplifies and improves label setting so much, by allowing \cs{zlabel} to be
% set with \cs{label}, that it is definitely a must for \pkg{zref-clever}, so
% we require that too. Finally, since we followed the move to \texttt{e}-type
% expansion, to play safe we require the 2023-11-01 kernel or newer.
%
% \begin{macrocode}
\def\zrefclever@required@kernel{2023-11-01}
\NeedsTeXFormat{LaTeX2e}[\zrefclever@required@kernel]
\providecommand\IfFormatAtLeastTF{\@ifl@t@r\fmtversion}
\IfFormatAtLeastTF{\zrefclever@required@kernel}
{}
{%
\PackageError{zref-clever}{LaTeX kernel too old}
{%
'zref-clever' requires a LaTeX kernel \zrefclever@required@kernel\space or newer.%
}%
}%
% \end{macrocode}
%
%
% Identify the package.
% \begin{macrocode}
\ProvidesExplPackage {zref-clever} {2024-11-28} {0.5.1}
{Clever LaTeX cross-references based on zref}
% \end{macrocode}
%
%
% \section{Dependencies}
%
% Required packages. Besides these, \pkg{zref-hyperref} may also be loaded
% depending on user options. \pkg{zref-clever} also requires UTF-8 input
% encoding (see discussion with \contributor{David Carlisle} at
% \url{https://chat.stackexchange.com/transcript/message/62644791#62644791}).
%
% \begin{macrocode}
\RequirePackage { zref-base }
\RequirePackage { zref-user }
\RequirePackage { zref-abspage }
\RequirePackage { ifdraft }
% \end{macrocode}
%
%
% \section{\pkg{zref} setup}
%
% For the purposes of the package, we need to store some information with the
% labels, some of it standard, some of it not so much. So, we have to setup
% \pkg{zref} to do so.
%
% Some basic properties are handled by \pkg{zref} itself, or some of its
% modules. The \texttt{default} and \texttt{page} properties are provided by
% \pkg{zref-base}, while \pkg{zref-abspage} provides the \texttt{abspage}
% property which gives us a safe and easy way to sort labels for page
% references.
%
% The \texttt{counter} property, in most cases, will be just the kernel's
% \cs{@currentcounter}, set by \cs{refstepcounter}. However, not everywhere
% is it assured that \cs{@currentcounter} gets updated as it should, so we
% need to have some means to manually tell \pkg{zref-clever} what the current
% counter actually is. This is done with the \opt{currentcounter} option, and
% stored in \cs{l_@@_current_counter_tl}, whose default is
% \cs{@currentcounter}.
%
% \begin{macrocode}
\zref@newprop { zc@counter } { \l_@@_current_counter_tl }
\zref@addprop \ZREF@mainlist { zc@counter }
% \end{macrocode}
%
% The reference itself, stored by \pkg{zref-base} in the \texttt{default}
% property, is somewhat a disputed real estate. In particular, the use of
% \cs{labelformat} (previously from \pkg{varioref}, now in the kernel) will
% include there the reference ``prefix'' and complicate the job we are trying
% to do here. Hence, we isolate \cs[no-index]{the}\meta{counter} and store it
% ``clean'' in \texttt{thecounter} for reserved use. Since
% \cs{@currentlabel}, which populates the \texttt{default} property, is
% \emph{more reliable} than \cs{@currentcounter}, \texttt{thecounter} is meant
% to be kept as an \emph{option} (\opt{ref} option), in case there's need to
% use \pkg{zref-clever} together with \cs{labelformat}. Based on the
% definition of \cs{@currentlabel} done inside \cs{refstepcounter} in
% \texttt{texdoc source2e}, section \texttt{ltxref.dtx}. We just drop the
% \cs[no-index]{p@...} prefix.
%
% \begin{macrocode}
\zref@newprop { thecounter }
{
\cs_if_exist:cTF { c@ \l_@@_current_counter_tl }
{ \use:c { the \l_@@_current_counter_tl } }
{
\cs_if_exist:cT { c@ \@currentcounter }
{ \use:c { the \@currentcounter } }
}
}
\zref@addprop \ZREF@mainlist { thecounter }
% \end{macrocode}
%
%
% Much of the work of \pkg{zref-clever} relies on the association between a
% label's ``counter'' and its ``type'' (see the User manual section on
% ``Reference types''). Superficially examined, one might think this relation
% could just be stored in a global property list, rather than in the label
% itself. However, there are cases in which we want to distinguish different
% types for the same counter, depending on the document context. Hence, we
% need to store the ``type'' of the ``counter'' for each ``label''. In
% setting this, the presumption is that the label's type has the same name as
% its counter, unless it is specified otherwise by the \opt{countertype}
% option, as stored in \cs{l_@@_counter_type_prop}.
%
% \begin{macrocode}
\zref@newprop { zc@type }
{
\tl_if_empty:NTF \l_@@_reftype_override_tl
{
\exp_args:NNe \prop_if_in:NnTF \l_@@_counter_type_prop
\l_@@_current_counter_tl
{
\exp_args:NNe \prop_item:Nn \l_@@_counter_type_prop
{ \l_@@_current_counter_tl }
}
{ \l_@@_current_counter_tl }
}
{ \l_@@_reftype_override_tl }
}
\zref@addprop \ZREF@mainlist { zc@type }
% \end{macrocode}
%
%
% Since the \texttt{default}/\texttt{thecounter} and \texttt{page} properties
% store the ``\emph{printed} representation'' of their respective counters,
% for sorting and compressing purposes, we are also interested in their
% numeric values. So we store them in \texttt{zc@cntval} and
% \texttt{zc@pgval}. For this, we use \cs[no-index]{c@}\meta{counter}, which
% contains the counter's numerical value (see `texdoc source2e', section
% `ltcounts.dtx'). Also, even if we can't find a valid \cs{@currentcounter},
% we set the value of 0 to the property, so that it is never empty (the
% property's default is not sufficient to avoid that), because we rely on this
% value being a number and an empty value there will result in ``Missing
% number, treated as zero.'' error. A typical situation where this might
% occur is the user setting a label before \cs{refstepcounter} is called for
% the first time in the document. A user error, no doubt, but we should avoid
% a hard crash.
% \begin{macrocode}
\zref@newprop { zc@cntval } [0]
{
\bool_lazy_and:nnTF
{ ! \tl_if_empty_p:N \l_@@_current_counter_tl }
{ \cs_if_exist_p:c { c@ \l_@@_current_counter_tl } }
{ \int_use:c { c@ \l_@@_current_counter_tl } }
{
\bool_lazy_and:nnTF
{ ! \tl_if_empty_p:N \@currentcounter }
{ \cs_if_exist_p:c { c@ \@currentcounter } }
{ \int_use:c { c@ \@currentcounter } }
{ 0 }
}
}
\zref@addprop \ZREF@mainlist { zc@cntval }
\zref@newprop* { zc@pgval } [0] { \int_use:c { c@page } }
\zref@addprop \ZREF@mainlist { zc@pgval }
% \end{macrocode}
%
%
% However, since many counters (may) get reset along the document, we require
% more than just their numeric values. We need to know the reset chain of a
% given counter, in order to sort and compress a group of references. Also
% here, the ``printed representation'' is not enough, not only because it is
% easier to work with the numeric values but, given we occasionally group
% multiple counters within a single type, sorting this group requires to know
% the actual counter reset chain.
%
% Furthermore, even if it is true that most of the definitions of counters,
% and hence of their reset behavior, is likely to be defined in the preamble,
% this is not necessarily true. Users can create counters, newtheorems
% mid-document, and alter their reset behavior along the way. Was that not
% the case, we could just store the desired information at
% \texttt{begindocument} in a variable and retrieve it when needed. But since
% it is, we need to store the information with the label, with the values as
% current when the label is set.
%
% Though counters can be reset at any time, and in different ways at that, the
% most important use case is the automatic resetting of counters when some
% other counter is stepped, as performed by the standard mechanisms of the
% kernel (optional argument of \cs{newcounter}, \cs{@addtoreset},
% \cs{counterwithin}, and related infrastructure). The canonical optional
% argument of \cs{newcounter} establishes that the counter being created (the
% mandatory argument) gets reset every time the ``enclosing counter'' gets
% stepped (this is called in the usual sources ``within-counter'', ``old
% counter'', ``supercounter'', ``parent counter'' etc.). This information is
% somewhat tricky to get. For starters, the counters which may reset the
% current counter are not retrievable from the counter itself, because this
% information is stored with the counter that does the resetting, not with the
% one that gets reset (the list is stored in \cs[no-index]{cl@}\meta{counter}
% with format
% \texttt{\cs{@elt}\{countera\}\cs{@elt}\{counterb\}\cs{@elt}\{counterc\}},
% see \file{ltcounts.dtx} in \texttt{texdoc source2e}). Besides, there may be
% a chain of resetting counters, which must be taken into account: if
% \texttt{counterC} gets reset by \texttt{counterB}, and \texttt{counterB}
% gets reset by \texttt{counterA}, stepping the latter affects all three of
% them.
%
% The procedure below examines a set of counters, those in
% \cs{l_@@_counter_resetters_seq}, and for each of them retrieves the set of
% counters it resets, as stored in \cs[no-index]{cl@}\meta{counter}, looking
% for the counter for which we are trying to set a label
% (\cs{l_@@_current_counter_tl}, by default \cs{@currentcounter}, passed as an
% argument to the functions). There is one relevant caveat to this procedure:
% \cs{l_@@_counter_resetters_seq} is populated by hand with the ``usual
% suspects'', there is no way (that I know of) to ensure it is exhaustive.
% However, it is not that difficult to create a reasonable ``usual suspects''
% list which, of course, should include the counters for the sectioning
% commands to start with, and it is easy to add more counters to this list if
% needed, with the option \opt{counterresetters}. Unfortunately, not all
% counters are created alike, or reset alike. Some counters, even some kernel
% ones, get reset by other mechanisms (notably, the \texttt{enumerate}
% environment counters do not use the regular counter machinery for resetting
% on each level, but are nested nevertheless by other means). Therefore,
% inspecting \cs[no-index]{cl@}\meta{counter} cannot possibly fully account
% for all of the automatic counter resetting which takes place in the
% document. And there's also no other ``general rule'' we could grab on for
% this, as far as I know. So we provide a way to manually tell
% \pkg{zref-clever} of these cases, by means of the \opt{counterresetby}
% option, whose information is stored in \cs{l_@@_counter_resetby_prop}. This
% manual specification has precedence over the search through
% \cs{l_@@_counter_resetters_seq}, and should be handled with care, since
% there is no possible verification mechanism for this.
%
%
% \begin{macro}[EXP]
% {
% \@@_get_enclosing_counters:n ,
% \@@_get_enclosing_counters_value:n ,
% }
% Recursively generate a \emph{sequence} of ``enclosing counters'' and
% values, for a given \meta{counter} and leave it in the input stream.
% These functions must be expandable, since they get called from
% \cs{zref@newprop} and are the ones responsible for generating the desired
% information when the label is being set. Note that the order in which we
% are getting this information is reversed, since we are navigating the
% counter reset chain bottom-up. But it is very hard to do otherwise here
% where we need expandable functions, and easy to handle at the reading
% side.
% \begin{syntax}
% \cs{@@_get_enclosing_counters:n} \Arg{counter}
% \cs{@@_get_enclosing_counters_value:n} \Arg{counter}
% \end{syntax}
% \begin{macrocode}
\cs_new:Npn \@@_get_enclosing_counters:n #1
{
\cs_if_exist:cT { c@ \@@_counter_reset_by:n {#1} }
{
{ \@@_counter_reset_by:n {#1} }
\@@_get_enclosing_counters:e
{ \@@_counter_reset_by:n {#1} }
}
}
\cs_new:Npn \@@_get_enclosing_counters_value:n #1
{
\cs_if_exist:cT { c@ \@@_counter_reset_by:n {#1} }
{
{ \int_use:c { c@ \@@_counter_reset_by:n {#1} } }
\@@_get_enclosing_counters_value:e
{ \@@_counter_reset_by:n {#1} }
}
}
% \end{macrocode}
%
% \begin{macrocode}
\cs_generate_variant:Nn \@@_get_enclosing_counters:n { e }
\cs_generate_variant:Nn \@@_get_enclosing_counters_value:n { e }
% \end{macrocode}
% \end{macro}
%
%
% \begin{macro}[EXP]{\@@_counter_reset_by:n}
% Auxiliary function for \cs{@@_get_enclosing_counters:n} and
% \cs{@@_get_enclosing_counters_value:n}, and useful on its own standing.
% It is broken in parts to be able to use the expandable mapping functions.
% \cs{@@_counter_reset_by:n} leaves in the stream the ``enclosing counter''
% which resets \meta{counter}.
% \begin{syntax}
% \cs{@@_counter_reset_by:n} \Arg{counter}
% \end{syntax}
% \begin{macrocode}
\cs_new:Npn \@@_counter_reset_by:n #1
{
\bool_if:nTF
{ \prop_if_in_p:Nn \l_@@_counter_resetby_prop {#1} }
{ \prop_item:Nn \l_@@_counter_resetby_prop {#1} }
{
\seq_map_tokens:Nn \l_@@_counter_resetters_seq
{ \@@_counter_reset_by_aux:nn {#1} }
}
}
\cs_new:Npn \@@_counter_reset_by_aux:nn #1#2
{
\cs_if_exist:cT { c@ #2 }
{
\tl_if_empty:cF { cl@ #2 }
{
\tl_map_tokens:cn { cl@ #2 }
{ \@@_counter_reset_by_auxi:nnn {#2} {#1} }
}
}
}
\cs_new:Npn \@@_counter_reset_by_auxi:nnn #1#2#3
{
\str_if_eq:nnT {#2} {#3}
{ \tl_map_break:n { \seq_map_break:n {#1} } }
}
% \end{macrocode}
% \end{macro}
%
%
% Finally, we create the \texttt{zc@enclval} property, and add it to the
% \texttt{main} property list.
% \begin{macrocode}
\zref@newprop { zc@enclval }
{
\@@_get_enclosing_counters_value:e
{ \l_@@_current_counter_tl }
}
\zref@addprop \ZREF@mainlist { zc@enclval }
% \end{macrocode}
%
% The \texttt{zc@enclcnt} property is provided for the purpose of easing the
% debugging of counter reset chains, thus it is not added \texttt{main}
% property list by default.
% \begin{macrocode}
\zref@newprop { zc@enclcnt }
{ \@@_get_enclosing_counters:e \l_@@_current_counter_tl }
% \end{macrocode}
%
%
% Another piece of information we need is the page numbering format being used
% by \cs{thepage}, so that we know when we can (or not) group a set of page
% references in a range. Unfortunately, \texttt{page} is not a typical
% counter in ways which complicates things. First, it does commonly get reset
% along the document, not necessarily by the usual counter reset chains, but
% rather with \cs{pagenumbering} or variations thereof. Second, the format of
% the page number commonly changes in the document (roman, arabic, etc.), not
% necessarily, though usually, together with a reset. Trying to ``parse''
% \cs{thepage} to retrieve such information is bound to go wrong: we don't
% know, and can't know, what is within that macro, and that's the business of
% the user, or of the documentclass, or of the loaded packages. The technique
% used by \pkg{cleveref}, is simple and smart: store with the label what
% \cs{thepage} would return, if the counter \cs{c@page} was ``\(1\)''. That
% would not allow us to \emph{sort} the references, luckily however, we have
% \texttt{abspage} which solves this problem. But we can decide whether two
% labels can be compressed into a range or not based on this format: if they
% are identical, we can compress them, otherwise, we can't. However,
% expanding \cs{thepage} can lead to errors for some \pkg{babel} packages
% which redefine \cs{roman} containing non-expandable material (see
% \url{https://chat.stackexchange.com/transcript/message/63810027#63810027},
% \url{https://chat.stackexchange.com/transcript/message/63810318#63810318},
% \url{https://chat.stackexchange.com/transcript/message/63810720#63810720}
% and discussion). So I went for something a little different. As mentioned,
% we want to know if \cs{thepage} is the same for different labels, or if it
% has changed. We can thus test this directly, by comparing \cs{thepage} with
% a stored value of it, \cs{g_@@_prev_page_format_tl}, and stepping a counter
% every time they differ. Of course, this cannot be done at label setting
% time, since it is not expandable. But we can do that comparison before
% shipout and then define the label property as starred
% (\texttt{\cs{zref@newprop}*\{zc@pgfmt\}}), so that the label comes after the
% counter, and we can get the correct value of the counter.
%
% \begin{macrocode}
\int_new:N \g_@@_page_format_int
\tl_new:N \g_@@_prev_page_format_tl
\AddToHook { shipout / before }
{
\tl_if_eq:NNF \g_@@_prev_page_format_tl \thepage
{
\int_gincr:N \g_@@_page_format_int
\tl_gset_eq:NN \g_@@_prev_page_format_tl \thepage
}
}
\zref@newprop* { zc@pgfmt } { \int_use:N \g_@@_page_format_int }
\zref@addprop \ZREF@mainlist { zc@pgfmt }
% \end{macrocode}
%
%
% Still some other properties which we don't need to handle at the data
% provision side, but need to cater for at the retrieval side, are the ones
% from the \pkg{zref-xr} module, which are added to the labels imported from
% external documents, and needed to construct hyperlinks to them and to
% distinguish them from the current document ones at sorting and compressing:
% \texttt{urluse}, \texttt{url} and \texttt{externaldocument}.
%
%
%
% \section{Plumbing}
%
%
% \subsection{Auxiliary}
%
%
% \begin{macro}
% {
% \@@_if_package_loaded:n ,
% \@@_if_class_loaded:n ,
% }
% Just a convenience, since sometimes we just need one of the branches, and
% it is particularly easy to miss the empty F branch after a long T one.
% \begin{macrocode}
\prg_new_conditional:Npnn \@@_if_package_loaded:n #1 { T , F , TF }
{ \IfPackageLoadedTF {#1} { \prg_return_true: } { \prg_return_false: } }
\prg_new_conditional:Npnn \@@_if_class_loaded:n #1 { T , F , TF }
{ \IfClassLoadedTF {#1} { \prg_return_true: } { \prg_return_false: } }
% \end{macrocode}
% \end{macro}
%
%
% \begin{macro}
% {
% \l_@@_tmpa_tl ,
% \l_@@_tmpb_tl ,
% \l_@@_tmpa_seq ,
% \g_@@_tmpa_seq ,
% \l_@@_tmpa_bool ,
% \l_@@_tmpa_int ,
% }
% Temporary scratch variables.
% \begin{macrocode}
\tl_new:N \l_@@_tmpa_tl
\tl_new:N \l_@@_tmpb_tl
\seq_new:N \l_@@_tmpa_seq
\seq_new:N \g_@@_tmpa_seq
\bool_new:N \l_@@_tmpa_bool
\int_new:N \l_@@_tmpa_int
% \end{macrocode}
% \end{macro}
%
%
% \subsection{Messages}
%
%
% \begin{macrocode}
\msg_new:nnn { zref-clever } { option-not-type-specific }
{
Option~'#1'~is~not~type-specific~\msg_line_context:.~
Set~it~in~'\iow_char:N\\zcLanguageSetup'~before~first~'type'~
switch~or~as~package~option.
}
\msg_new:nnn { zref-clever } { option-only-type-specific }
{
No~type~specified~for~option~'#1'~\msg_line_context:.~
Set~it~after~'type'~switch.
}
\msg_new:nnn { zref-clever } { key-requires-value }
{ The~'#1'~key~'#2'~requires~a~value~\msg_line_context:. }
\msg_new:nnn { zref-clever } { language-declared }
{ Language~'#1'~is~already~declared~\msg_line_context:.~Nothing~to~do. }
\msg_new:nnn { zref-clever } { unknown-language-alias }
{
Language~'#1'~is~unknown~\msg_line_context:.~Can't~alias~to~it.~
See~documentation~for~'\iow_char:N\\zcDeclareLanguage'~and~
'\iow_char:N\\zcDeclareLanguageAlias'.
}
\msg_new:nnn { zref-clever } { unknown-language-setup }
{
Language~'#1'~is~unknown~\msg_line_context:.~Can't~set~it~up.~
See~documentation~for~'\iow_char:N\\zcDeclareLanguage'~and~
'\iow_char:N\\zcDeclareLanguageAlias'.
}
\msg_new:nnn { zref-clever } { unknown-language-opt }
{
Language~'#1'~is~unknown~\msg_line_context:.~
See~documentation~for~'\iow_char:N\\zcDeclareLanguage'~and~
'\iow_char:N\\zcDeclareLanguageAlias'.
}
\msg_new:nnn { zref-clever } { unknown-language-variant }
{
Can't~set~variant~'#1'~for~unknown~language~'#2'~\msg_line_context:.~
See~documentation~for~'\iow_char:N\\zcDeclareLanguage'~and~
'\iow_char:N\\zcDeclareLanguageAlias'.
}
\msg_new:nnn { zref-clever } { language-no-variants-ref }
{
Language~'#1'~has~no~declared~variants~\msg_line_context:.~
Nothing~to~do~with~option~'v=#2'.
}
\msg_new:nnn { zref-clever } { language-no-gender }
{
Language~'#1'~has~no~declared~gender~\msg_line_context:.~
Nothing~to~do~with~option~'#2=#3'.
}
\msg_new:nnn { zref-clever } { language-no-variants-setup }
{
Language~'#1'~has~no~declared~variants~\msg_line_context:.~
Nothing~to~do~with~option~'variant=#2'.
}
\msg_new:nnn { zref-clever } { unknown-variant }
{
Variant~'#1'~unknown~for~language~'#2'~\msg_line_context:.~
Using~default~variant.
}
\msg_new:nnn { zref-clever } { nudge-multitype }
{
Reference~with~multiple~types~\msg_line_context:.~
You~may~wish~to~separate~them~or~review~language~around~it.
}
\msg_new:nnn { zref-clever } { nudge-comptosing }
{
Multiple~labels~have~been~compressed~into~singular~type~name~
for~type~'#1'~\msg_line_context:.
}
\msg_new:nnn { zref-clever } { nudge-plural-when-sg }
{
Option~'sg'~signals~that~a~singular~type~name~was~expected~
\msg_line_context:.~But~type~'#1'~has~plural~type~name.
}
\msg_new:nnn { zref-clever } { gender-not-declared }
{ Language~'#1'~has~no~'#2'~gender~declared~\msg_line_context:. }
\msg_new:nnn { zref-clever } { nudge-gender-mismatch }
{
Gender~mismatch~for~type~'#1'~\msg_line_context:.~
You've~specified~'g=#2'~but~type~name~is~'#3'~for~language~'#4'.
}
\msg_new:nnn { zref-clever } { nudge-gender-not-declared-for-type }
{
You've~specified~'g=#1'~\msg_line_context:.~
But~gender~for~type~'#2'~is~not~declared~for~language~'#3'.
}
\msg_new:nnn { zref-clever } { nudgeif-unknown-value }
{ Unknown~value~'#1'~for~'nudgeif'~option~\msg_line_context:. }
\msg_new:nnn { zref-clever } { option-document-only }
{ Option~'#1'~is~only~available~after~\iow_char:N\\begin\{document\}. }
\msg_new:nnn { zref-clever } { langfile-loaded }
{ Loaded~'#1'~language~file. }
\msg_new:nnn { zref-clever } { zref-property-undefined }
{
Option~'ref=#1'~requested~\msg_line_context:.~
But~the~property~'#1'~is~not~declared,~falling-back~to~'default'.
}
\msg_new:nnn { zref-clever } { endrange-property-undefined }
{
Option~'endrange=#1'~requested~\msg_line_context:.~
But~the~property~'#1'~is~not~declared,~'endrange'~not~set.
}
\msg_new:nnn { zref-clever } { hyperref-preamble-only }
{
Option~'hyperref'~only~available~in~the~preamble~\msg_line_context:.~
To~inhibit~hyperlinking~locally,~you~can~use~the~starred~version~of~
'\iow_char:N\\zcref'.
}
\msg_new:nnn { zref-clever } { missing-hyperref }
{ Missing~'hyperref'~package.~Setting~'hyperref=false'. }
\msg_new:nnn { zref-clever } { option-preamble-only }
{ Option~'#1'~only~available~in~the~preamble~\msg_line_context:. }
\msg_new:nnn { zref-clever } { unknown-compat-module }
{
Unknown~compatibility~module~'#1'~given~to~option~'nocompat'.~
Nothing~to~do.
}
\msg_new:nnn { zref-clever } { refbounds-must-be-four }
{
The~value~of~option~'#1'~must~be~a~comma~sepatared~list~
of~four~items.~We~received~'#2'~items~\msg_line_context:.~
Option~not~set.
}
\msg_new:nnn { zref-clever } { missing-zref-check }
{
Option~'check'~requested~\msg_line_context:.~
But~package~'zref-check'~is~not~loaded,~can't~run~the~checks.
}
\msg_new:nnn { zref-clever } { zref-check-too-old }
{
Option~'check'~requested~\msg_line_context:.~
But~'zref-check'~newer~than~'#1'~is~required,~can't~run~the~checks.
}
\msg_new:nnn { zref-clever } { missing-type }
{ Reference~type~undefined~for~label~'#1'~\msg_line_context:. }
\msg_new:nnn { zref-clever } { missing-property }
{ Reference~property~'#1'~undefined~for~label~'#2'~\msg_line_context:. }
\msg_new:nnn { zref-clever } { missing-name }
{ Reference~format~option~'#1'~undefined~for~type~'#2'~\msg_line_context:. }
\msg_new:nnn { zref-clever } { single-element-range }
{ Range~for~type~'#1'~resulted~in~single~element~\msg_line_context:. }
\msg_new:nnn { zref-clever } { compat-package }
{ Loaded~support~for~'#1'~package. }
\msg_new:nnn { zref-clever } { compat-class }
{ Loaded~support~for~'#1'~documentclass. }
\msg_new:nnn { zref-clever } { option-deprecated }
{
Option~'#1'~has~been~deprecated~\msg_line_context:.\iow_newline:
Use~'#2'~instead.
}
\msg_new:nnn { zref-clever } { load-time-options }
{
'zref-clever'~does~not~accept~load-time~options.~
To~configure~package~options,~use~'\iow_char:N\\zcsetup'.
}
% \end{macrocode}
%
%
% \subsection{Data extraction}
%
%
% \begin{macro}{\@@_extract_default:Nnnn}
% Extract property \meta{prop} from \meta{label} and sets variable \meta{tl
% var} with extracted value. Ensure \cs{zref@extractdefault} is expanded
% exactly twice, but no further to retrieve the proper value. In case the
% property is not found, set \meta{tl var} with \meta{default}.
% \begin{syntax}
% \cs{@@_extract_default:Nnnn} \Arg{tl var}
% ~~\Arg{label} \Arg{prop} \Arg{default}
% \end{syntax}
% \begin{macrocode}
\cs_new_protected:Npn \@@_extract_default:Nnnn #1#2#3#4
{
\exp_args:NNNo \exp_args:NNo \tl_set:Nn #1
{ \zref@extractdefault {#2} {#3} {#4} }
}
\cs_generate_variant:Nn \@@_extract_default:Nnnn { NVnn , Nnvn }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_extract_unexp:nnn}
% Extract property \meta{prop} from \meta{label}. Ensure that, in the
% context of an e expansion, \cs{zref@extractdefault} is expanded exactly
% twice, but no further to retrieve the proper value. Thus, this is meant
% to be use in an e expansion context, not in other situations. In case the
% property is not found, leave \meta{default} in the stream.
% \begin{syntax}
% \cs{@@_extract_unexp:nnn}\Arg{label}\Arg{prop}\Arg{default}
% \end{syntax}
% \begin{macrocode}
\cs_new:Npn \@@_extract_unexp:nnn #1#2#3
{
\exp_args:NNo \exp_args:No
\exp_not:n { \zref@extractdefault {#1} {#2} {#3} }
}
\cs_generate_variant:Nn \@@_extract_unexp:nnn { Vnn , nvn , Vvn }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_extract:nnn}
% An internal version for \cs{zref@extractdefault}.
% \begin{syntax}
% \cs{@@_extract:nnn}\Arg{label}\Arg{prop}\Arg{default}
% \end{syntax}
% \begin{macrocode}
\cs_new:Npn \@@_extract:nnn #1#2#3
{ \zref@extractdefault {#1} {#2} {#3} }
% \end{macrocode}
% \end{macro}
%
%
% \subsection{Option infra}
%
% This section provides the functions in which the variables naming scheme of
% the package options is embodied, and some basic general functions to query
% these option variables.
%
% I had originally implemented the option handling of the package based on
% property lists, which are definitely very convenient. But as the number of
% options grew, I started to get concerned about the performance implications.
% That there was a toll was noticeable, even when we could live with it, of
% course. Indeed, at the time of writing, the typesetting of a reference
% queries about 24 different option values, most of them once per type-block,
% each of these queries can be potentially made in up to 5 option scope
% levels. Considering the size of the built-in language files is running at
% the hundreds, the package does have a lot of work to do in querying option
% values alone, and thus it is best to smooth things in this area as much as
% possible. This also gives me some peace of mind that the package will scale
% well in the long term. For some interesting discussion about alternative
% methods and their performance implications, see
% \url{https://tex.stackexchange.com/q/147966}. \contributor{Phelype Oleinik}
% also offered some insight on the matter at
% \url{https://tex.stackexchange.com/questions/629946/#comment1571118_629946}.
% The only real downside of this change is that we can no longer list the
% whole set of options in place at a given moment, which was useful for the
% purposes of regression testing, since we don't know what the whole set of
% active options is.
%
%
% \begin{macro}[EXP]{\@@_opt_varname_general:nn}
% Defines, and leaves in the input stream, the csname of the variable used
% to store the general \meta{option}. The data type of the variable must be
% specified (\texttt{tl}, \texttt{seq}, \texttt{bool}, etc.).
% \begin{syntax}
% \cs{@@_opt_varname_general:nn} \Arg{option} \Arg{data type}
% \end{syntax}
% \begin{macrocode}
\cs_new:Npn \@@_opt_varname_general:nn #1#2
{ l_@@_opt_general_ #1 _ #2 }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]{\@@_opt_varname_type:nnn}
% Defines, and leaves in the input stream, the csname of the variable used
% to store the type-specific \meta{option} for \meta{ref type}.
% \begin{syntax}
% \cs{@@_opt_varname_type:nnn} \Arg{ref type} \Arg{option} \Arg{data type}
% \end{syntax}
% \begin{macrocode}
\cs_new:Npn \@@_opt_varname_type:nnn #1#2#3
{ l_@@_opt_type_ #1 _ #2 _ #3 }
\cs_generate_variant:Nn \@@_opt_varname_type:nnn { enn , een }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]{\@@_opt_varname_language:nnn}
% Defines, and leaves in the input stream, the csname of the variable used
% to store the language \meta{option} for \meta{lang} (for general language
% options, those set with \cs{zcDeclareLanguage}). The
% ``\texttt{lang_unknown}'' branch should be guarded against, such as we
% normally should not get there, but this function \emph{must} return some
% valid csname. The random part is there so that, in the circumstance this
% could not be avoided, we (hopefully) don't retrieve the value for an
% ``unknown language'' inadvertently.
% \begin{syntax}
% \cs{@@_opt_varname_language:nnn} \Arg{lang} \Arg{option} \Arg{data type}
% \end{syntax}
% \begin{macrocode}
\cs_new:Npn \@@_opt_varname_language:nnn #1#2#3
{
\@@_language_if_declared:nTF {#1}
{
g_@@_opt_language_
\tl_use:c { \@@_language_varname:n {#1} }
_ #2 _ #3
}
{ g_@@_opt_lang_unknown_ \int_rand:n { 1000000 } _ #3 }
}
\cs_generate_variant:Nn \@@_opt_varname_language:nnn { enn }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]{\@@_opt_varname_lang_default:nnn}
% Defines, and leaves in the input stream, the csname of the variable used
% to store the language-specific default reference format \meta{option} for
% \meta{lang}.
% \begin{syntax}
% \cs{@@_opt_varname_lang_default:nnn} \Arg{lang} \Arg{option} \Arg{data type}
% \end{syntax}
% \begin{macrocode}
\cs_new:Npn \@@_opt_varname_lang_default:nnn #1#2#3
{
\@@_language_if_declared:nTF {#1}
{
g_@@_opt_lang_
\tl_use:c { \@@_language_varname:n {#1} }
_default_ #2 _ #3
}
{ g_@@_opt_lang_unknown_ \int_rand:n { 1000000 } _ #3 }
}
\cs_generate_variant:Nn \@@_opt_varname_lang_default:nnn { enn }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]{\@@_opt_varname_lang_type:nnnn}
% Defines, and leaves in the input stream, the csname of the variable used
% to store the language- and type-specific reference format \meta{option}
% for \meta{lang} and \meta{ref type}.
% \begin{syntax}
% \cs{@@_opt_varname_lang_type:nnnn} \Arg{lang} \Arg{ref type}
% ~~\Arg{option} \Arg{data type}
% \end{syntax}
% \begin{macrocode}
\cs_new:Npn \@@_opt_varname_lang_type:nnnn #1#2#3#4
{
\@@_language_if_declared:nTF {#1}
{
g_@@_opt_lang_
\tl_use:c { \@@_language_varname:n {#1} }
_type_ #2 _ #3 _ #4
}
{ g_@@_opt_lang_unknown_ \int_rand:n { 1000000 } _ #4 }
}
\cs_generate_variant:Nn
\@@_opt_varname_lang_type:nnnn { eenn , eeen }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]{\@@_opt_varname_fallback:nn}
% Defines, and leaves in the input stream, the csname of the variable used
% to store the fallback \meta{option}.
% \begin{syntax}
% \cs{@@_opt_varname_fallback:nn} \Arg{option} \Arg{data type}
% \end{syntax}
% \begin{macrocode}
\cs_new:Npn \@@_opt_varname_fallback:nn #1#2
{ c_@@_opt_fallback_ #1 _ #2 }
% \end{macrocode}
% \end{macro}
%
%
% \begin{macro}[EXP]{\@@_opt_var_set_bool:n}
% The \LaTeX3 programming layer does not have the concept of a variable
% \emph{existing} only locally, it also considers an ``error'' if an
% assignment is made to a variable which was not previously declared, but
% declaration is always global, which means that ``setting a local variable
% at a local scope'', given these requirements, results in it existing, and
% being empty, globally. Therefore, we need an independent mechanism from
% the mere existence of a variable to keep track of whether variables are
% ``set'' or ``unset'', within the logic of the precedence rules for options
% in different scopes. \cs{@@_opt_var_set_bool:n} expands to the name of
% the boolean variable used to track this state for \meta{option var}. See
% discussion with \contributor{Phelype Oleinik} at
% \url{https://tex.stackexchange.com/questions/633341/#comment1579825_633347}
% \begin{syntax}
% \cs{@@_opt_var_set_bool:n} \Arg{option var}
% \end{syntax}
% \begin{macrocode}
\cs_new:Npn \@@_opt_var_set_bool:n #1
{ \cs_to_str:N #1 _is_set_bool }
% \end{macrocode}
% \end{macro}
%
%
%
% \begin{macro}
% {
% \@@_opt_tl_set:Nn ,
% \@@_opt_tl_clear:N ,
% \@@_opt_tl_gset:Nn ,
% \@@_opt_tl_gclear:N ,
% }
% \begin{syntax}
% \cs{@@_opt_tl_set:N} \Arg{option tl} \Arg{value}
% \cs{@@_opt_tl_clear:N} \Arg{option tl}
% \cs{@@_opt_tl_gset:N} \Arg{option tl} \Arg{value}
% \cs{@@_opt_tl_gclear:N} \Arg{option tl}
% \end{syntax}
% \begin{macrocode}
\cs_new_protected:Npn \@@_opt_tl_set:Nn #1#2
{
\tl_if_exist:NF #1
{ \tl_new:N #1 }
\tl_set:Nn #1 {#2}
\bool_if_exist:cF { \@@_opt_var_set_bool:n {#1} }
{ \bool_new:c { \@@_opt_var_set_bool:n {#1} } }
\bool_set_true:c { \@@_opt_var_set_bool:n {#1} }
}
\cs_generate_variant:Nn \@@_opt_tl_set:Nn { cn }
\cs_new_protected:Npn \@@_opt_tl_clear:N #1
{
\tl_if_exist:NF #1
{ \tl_new:N #1 }
\tl_clear:N #1
\bool_if_exist:cF { \@@_opt_var_set_bool:n {#1} }
{ \bool_new:c { \@@_opt_var_set_bool:n {#1} } }
\bool_set_true:c { \@@_opt_var_set_bool:n {#1} }
}
\cs_generate_variant:Nn \@@_opt_tl_clear:N { c }
\cs_new_protected:Npn \@@_opt_tl_gset:Nn #1#2
{
\tl_if_exist:NF #1
{ \tl_new:N #1 }