% \iffalse meta-comment % % File: zref-check.dtx % % This file is part of the LaTeX package "zref-check". % % 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-check.dtx, % zref-check.ins, % zref-check-doc.tex, % zref-check-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-check % % for those people who are interested. % % ----------------------------------------------------------------------- % % \fi % % \iffalse %<*driver> \documentclass{l3doc} % Have \GetFileInfo pick up date and version data \usepackage{zref-check} \NewDocumentCommand\opt{m}{\texttt{#1}} \MakeShortVerb{\|} \begin{document} \DocInput{zref-check.dtx} \end{document} % % \fi % % % % \section{Initial setup} % % Start the \pkg{DocStrip} guards. % \begin{macrocode} %<*package> % \end{macrocode} % % Identify the internal prefix (\LaTeX3 \pkg{DocStrip} convention). % \begin{macrocode} %<@@=zrefcheck> % \end{macrocode} % % % For the \texttt{chapter} and \texttt{section} checks, \pkg{zref-check} uses % the new hook system in \pkg{ltcmdhooks}, which was released with the % 2021/06/01 \LaTeX{} kernel. And, 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\zrefcheck@required@kernel{2023-11-01} \NeedsTeXFormat{LaTeX2e}[\zrefcheck@required@kernel] \providecommand\IfFormatAtLeastTF{\@ifl@t@r\fmtversion} \IfFormatAtLeastTF{\zrefcheck@required@kernel} {} {% \PackageError{zref-check}{LaTeX kernel too old} {% 'zref-check' requires a LaTeX kernel \zrefcheck@required@kernel\space or newer.% }% }% % \end{macrocode} % % Identify the package. % \begin{macrocode} \ProvidesExplPackage {zref-check} {2024-11-28} {0.3.7} {Flexible cross-references with contextual checks based on zref} % \end{macrocode} % % \section{Dependencies} % % \begin{macrocode} \RequirePackage { zref-user } \RequirePackage { zref-abspage } \RequirePackage { ifdraft } % \end{macrocode} % % % \section{\pkg{zref} setup} % % Provide absolute counters for section and chapter, and respective \pkg{zref} % properties, so that we can make checks about relation of chapters/sections % regardless of internal counters, since we don't get those for the unnumbered % (starred) ones. Thanks Ulrike Fischer for suggestions at TeX.SX about the % proper place to make the hooks for this purpose. % \begin{macrocode} \newcounter { zc@abschap } \newcounter { zc@abssec } [ zc@abschap ] % \end{macrocode} % % % If the documentclass does not define \cs{chapter} the only thing that % happens is that the chapter counter is never incremented, and the section % one never reset. % \begin{macrocode} \AddToHook { cmd / chapter / before } { \stepcounter { zc@abschap } } \zref@newprop { zc@abschap } [0] { \int_use:N \c@zc@abschap } \zref@addprop \ZREF@mainlist { zc@abschap } % \end{macrocode} % % \begin{macrocode} \AddToHook { cmd / section / before } { \stepcounter { zc@abssec } } \zref@newprop { zc@abssec } [0] { \int_use:N \c@zc@abssec } \zref@addprop \ZREF@mainlist { zc@abssec } % \end{macrocode} % % % These are the lists of properties to be used by \pkg{zref-check}, that is, % the list of properties the references and targets store. This is the % minimum set required, more properties may be added according to options. % For user facing labels, we must use the \texttt{main} property list, so that % \pkg{zref-clever} can also retrieve the properties it needs to refer to % them. % \begin{macrocode} \zref@newlist { zrefcheck-check } \zref@addprops { zrefcheck-check } { page , % for messages abspage , zc@abschap , zc@abssec } \zref@newlist { zrefcheck-end } \zref@addprops { zrefcheck-end } { abspage , zc@abschap , zc@abssec } % \end{macrocode} % For \pkg{zref-vario} we only need page information, since we only perform % \opt{above} and \opt{below} checks there. % \begin{macrocode} \zref@newlist { zrefcheck-zrefvario } \zref@addprops { zrefcheck-zrefvario } { page , % for messages abspage , } % \end{macrocode} % % % \section{Plumbing} % % \subsection{Auxiliary} % % \begin{macro} % { % \l_@@_tmpa_tl , % \l_@@_tmpb_tl , % \g_@@_tmpa_tl , % \l_@@_tmpa_int , % \l_@@_tmpa_bool , % \g_@@_tmpa_ior , % } % Temporary scratch variables. % \begin{macrocode} \tl_new:N \l_@@_tmpa_tl \tl_new:N \l_@@_tmpb_tl \tl_new:N \g_@@_tmpa_tl \int_new:N \l_@@_tmpa_int \bool_new:N \l_@@_tmpa_bool \ior_new:N \g_@@_tmpa_ior % \end{macrocode} % \end{macro} % % \subsection{Messages} % % \begin{macro}{\@@_message:nnnn, \@@_message:nnne} % \begin{macrocode} \cs_new_protected:Npn \@@_message:nnnn #1#2#3#4 { \use:c { msg_ \l_@@_msglevel_tl :nnnnn } { zref-check } {#1} {#2} {#3} {#4} } \cs_generate_variant:Nn \@@_message:nnnn { nnne } % \end{macrocode} % \end{macro} % % \begin{macrocode} \msg_new:nnn { zref-check } { check-failed } { Check~failed~\msg_line_context:.~ Failed~check~'#1'~for~label~'#2'~on~page~#3. } \msg_new:nnn { zref-check } { double-check } { Same~page~check~\msg_line_context:.~ Double-check~'#1'~for~label~'#2'~on~page~#3. } \msg_new:nnn { zref-check } { empty-label } { Check~failed~\msg_line_context:.~ Failed~check~'#1'~for~empty~label. } \msg_new:nnn { zref-check } { no-checks } { No~checks~for~'\iow_char:N\\zcheck'~\msg_line_context:. } % \end{macrocode} % % \begin{macrocode} \msg_new:nnn { zref-check } { check-missing } { Check~'#1'~not~defined~\msg_line_context:. } \msg_new:nnn { zref-check } { property-undefined } { Property~'#1'~not~defined~\msg_line_context:. } \msg_new:nnn { zref-check } { property-not-in-label } { Label~'#1'~has~no~property~'#2'~\msg_line_context:. } \msg_new:nnn { zref-check } { property-not-integer } { Property~'#1'~for~label~'#2'~not~an~integer~\msg_line_context:. } % \end{macrocode} % % \begin{macrocode} \msg_new:nnn { zref-check } { hyperref-preamble-only } { Option~'hyperref'~only~available~in~the~preamble. \iow_newline: Use~the~starred~version~of~'\iow_char:N\\zcheck'~instead. } \msg_new:nnn { zref-check } { missing-hyperref } { Missing~'hyperref'~package. \iow_newline: Setting~'hyperref=false'. } \msg_new:nnn { zref-check } { ignore-ok-document-only } { Option~'#1'~only~available~in~the~document. \iow_newline: Use~option~'msglevel'~instead. } \msg_new:nnn { zref-check } { option-preamble-only } { Option~'#1'~is~preamble~only~\msg_line_context:. } \msg_new:nnn { zref-check } { closerange-not-positive-integer } { Option~'closerange'~not~a~positive~integer~\msg_line_context:.~ Using~default~value. } \msg_new:nnn { zref-check } { labelcmd-undefined } { Control~sequence~named~'#1'~used~in~option~'labelcmd'~is~not~defined.~ Using~default~value. } \msg_new:nnn { zref-check } { option-deprecated-with-alternative } { Option~'#1'~has~been~deprecated~\msg_line_context:.\iow_newline: Use~'#2'~instead. } \msg_new:nnn { zref-check } { option-deprecated } { Option~'#1'~has~been~deprecated~\msg_line_context:. } \msg_new:nnn { zref-check } { load-time-options } { 'zref-check'~does~not~accept~load-time~options.~ To~configure~package~options,~use~'\iow_char:N\\zrefchecksetup'. } % \end{macrocode} % % % \subsection{Integer testing} % % \begin{macro}{\@@_is_integer:n, \@@_int_to_roman:w} % From \url{https://tex.stackexchange.com/a/244405} (thanks Enrico Gregorio, % aka `egreg'), also see \url{https://tex.stackexchange.com/a/19769}. % Following the \texttt{l3styleguide}, I made a copy of % \cs{__int_to_roman:w}, since it is an internal function from the % \texttt{int} module, but we still get a warning from \texttt{l3build doc}, % complaining about it. And we're using \cs{tl_if_empty:oTF} instead of % \cs{tl_if_blank:oTF} as in egreg's answer, since \cs{romannumeral} is % defined so that ``the expansion is empty if the number is zero or % negative'', not ``blank''. A couple of comments about this technique: the % underlying \cs{romannumeral} ignores space tokens and explicit signs % (\texttt{+} and \texttt{-}) in the expansion and hence it can only be used % to test positive integers; also the technique cannot distinguish whether % it received an empty argument or if ``the expansion was empty'' as a % result of receiving number as argument, so this must also be controlled % for since, in our use case, this may happen. % \begin{macrocode} \cs_new_eq:NN \@@_int_to_roman:w \__int_to_roman:w \prg_new_conditional:Npnn \@@_is_integer:n #1 { p, T , F , TF } { \tl_if_empty:oTF {#1} { \prg_return_false: } { \tl_if_empty:oTF { \@@_int_to_roman:w -0#1 } { \prg_return_true: } { \prg_return_false: } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_is_integer_rgx:n} % A possible alternative to \cs{@@_is_integer:n} is to use a straightforward % regexp match (see \url{https://tex.stackexchange.com/a/427559}). It does % not suffer from the mentioned caveats from the \cs{__int_to_roman:w} % technique, however, while \cs{@@_is_integer:n} is expandable, % \cs{@@_is_integer_rgx:n} is not. Also, \cs{@@_is_integer_rgx:n} is % probably slower. % \begin{macrocode} \prg_new_protected_conditional:Npnn \@@_is_integer_rgx:n #1 { TF } { \regex_match:nnTF { \A\d+\Z } {#1} { \prg_return_true: } { \prg_return_false: } } % \end{macrocode} % \end{macro} % % % % \subsection{Options} % % % \subsubsection*{\opt{hyperref} option} % % \begin{variable}{\l_@@_use_hyperref_bool, \l_@@_warn_hyperref_bool} % \begin{macrocode} \bool_new:N \l_@@_use_hyperref_bool \bool_new:N \l_@@_warn_hyperref_bool \keys_define:nn { zref-check/setup } { hyperref .choice: , hyperref / auto .code:n = { \bool_set_true:N \l_@@_use_hyperref_bool \bool_set_false:N \l_@@_warn_hyperref_bool } , hyperref / true .code:n = { \bool_set_true:N \l_@@_use_hyperref_bool \bool_set_true:N \l_@@_warn_hyperref_bool } , hyperref / false .code:n = { \bool_set_false:N \l_@@_use_hyperref_bool \bool_set_false:N \l_@@_warn_hyperref_bool } , hyperref .initial:n = auto , hyperref .default:n = auto } % \end{macrocode} % \end{variable} % % \begin{macrocode} \AddToHook { begindocument } { \@ifpackageloaded { hyperref } { \bool_if:NT \l_@@_use_hyperref_bool { \RequirePackage { zref-hyperref } } } { \bool_if:NT \l_@@_warn_hyperref_bool { \msg_warning:nn { zref-check } { missing-hyperref } } \bool_set_false:N \l_@@_use_hyperref_bool } \keys_define:nn { zref-check/setup } { hyperref .code:n = { \msg_warning:nn { zref-check } { hyperref-preamble-only } } } } % \end{macrocode} % % % \subsubsection*{\opt{msglevel} option} % % \begin{variable}{\l_@@_msglevel_tl} % \begin{macrocode} \tl_new:N \l_@@_msglevel_tl \keys_define:nn { zref-check/setup } { msglevel .choice: , msglevel / warn .code:n = { \tl_set:Nn \l_@@_msglevel_tl { warning } } , msglevel / info .code:n = { \tl_set:Nn \l_@@_msglevel_tl { info } } , msglevel / none .code:n = { \tl_set:Nn \l_@@_msglevel_tl { none } } , msglevel / infoifdraft .code:n = { \ifdraft { \tl_set:Nn \l_@@_msglevel_tl { info } } { \tl_set:Nn \l_@@_msglevel_tl { warning } } } , msglevel / warniffinal .code:n = { \ifoptionfinal { \tl_set:Nn \l_@@_msglevel_tl { warning } } { \tl_set:Nn \l_@@_msglevel_tl { info } } } , msglevel .value_required:n = true , msglevel .initial:n = warn , % \end{macrocode} % \opt{ignore} and \opt{ok} are convenience aliases for \opt{msglevel=none}, % but only for use in the document body. % \begin{macrocode} ignore .code:n = { \msg_warning:nnn { zref-check } { ignore-ok-document-only } { ignore } } , ignore .value_forbidden:n = true , ok .code:n = { \msg_warning:nnn { zref-check } { ignore-ok-document-only } { ok } } , ok .value_forbidden:n = true , } % \end{macrocode} % \end{variable} % % \begin{macrocode} \AddToHook { begindocument } { \keys_define:nn { zref-check/setup } { ignore .meta:n = { msglevel = none } , ok .meta:n = { msglevel = none } , } } % \end{macrocode} % % % \subsubsection*{\opt{onpage} option} % % \begin{variable}{\l_@@_msgonpage_bool} % \begin{macrocode} \bool_new:N \l_@@_msgonpage_bool \keys_define:nn { zref-check/setup } { onpage .choice: , onpage / labelseq .code:n = { \bool_set_false:N \l_@@_msgonpage_bool } , onpage / msg .code:n = { \bool_set_true:N \l_@@_msgonpage_bool } , onpage / labelseqifdraft .code:n = { \ifdraft { \bool_set_false:N \l_@@_msgonpage_bool } { \bool_set_true:N \l_@@_msgonpage_bool } } , onpage / msgiffinal .code:n = { \ifoptionfinal { \bool_set_true:N \l_@@_msgonpage_bool } { \bool_set_false:N \l_@@_msgonpage_bool } } , onpage .value_required:n = true , onpage .initial:n = labelseq } % \end{macrocode} % \end{variable} % % % \subsubsection*{\opt{closerange} option} % % \begin{variable}{\l_@@_close_range_int} % \begin{macrocode} \int_new:N \l_@@_close_range_int \keys_define:nn { zref-check/setup } { closerange .code:n = { \@@_is_integer_rgx:nTF {#1} { \int_set:Nn \l_@@_close_range_int { \int_eval:n {#1} } } { \msg_warning:nn { zref-check } { closerange-not-positive-integer } \int_set:Nn \l_@@_close_range_int { 5 } } } , closerange .value_required:n = true , closerange .initial:n = 5 } % \end{macrocode} % \end{variable} % % % \subsubsection*{Package options} % % \pkg{zref-check} does not accept load-time options. Despite the tradition % of so doing, Joseph Wright has a point in recommending otherwise at % \url{https://chat.stackexchange.com/transcript/message/60360822#60360822}: % separating ``loading the package'' from ``configuring the package'' grants % less trouble with ``option clashes'' and with expansion of options at % load-time. % \begin{macrocode} \bool_lazy_and:nnT { \tl_if_exist_p:c { opt@ zref-check.sty } } { ! \tl_if_empty_p:c { opt@ zref-check.sty } } { \msg_warning:nn { zref-check } { load-time-options } } % \end{macrocode} % % % \begin{macro}[int]{\zrefchecksetup} % Provide \cs{zrefchecksetup}. % \begin{macrocode} \NewDocumentCommand \zrefchecksetup { m } { \keys_set:nn { zref-check/setup } {#1} } % \end{macrocode} % \end{macro} % % % % \subsection{Position on page} % % Method for determining relative position within the page: the sequence in % which the labels get shipped out, inferred from the sequence in which the % labels occur in the \file{.aux} file. % % Some relevant info about the sequence of things: % \url{https://tex.stackexchange.com/a/120978} and \texttt{texdoc lthooks}, % section ``Hooks provided by |\begin{document}|''. % % % One first attempt at this was to use \cs{zref@newlabel}, which is the macro % in which \pkg{zref} stores the label information in the aux file. When the % \file{.aux} file is read at the beginning of the compilation, this macro is % expanded for each of the labels. So, by redefining this macro we can feed a % variable (a L3 sequence), and then do what it usually does, which is to % define each label with the internal macro \cs{@newl@bel}, when the % \file{.aux} file is read. % % Patching this macro for this is not possible. First, \cs{zref@newlabel} is % one of those ``commands that look ahead'' mentioned in \pkg{ltcmdhooks} % documentation. Indeed, \cs{@newl@bel} receives 3 arguments, and % \cs{zref@newlabel} just passes the first, the following two will be scanned % ahead. Second, the \pkg{ltcmdhooks} hooks are not actually available when % the \file{.aux} file is read, they come only after |\begin{document}|. % Hence, redefinition would be the only alternative. My attempts at this % ended up registered at \url{https://tex.stackexchange.com/a/604744}. But % the best result in these lines was: % % \begin{verbatim} % \ZREF@Robust\edef\zref@newlabel#1{ % \noexpand\seq_gput_right:Nn \noexpand\g__zrefcheck_auxfile_lblseq_seq {#1} % \noexpand\@newl@bel{\ZREF@RefPrefix}{#1} % } % \end{verbatim} % % % However, better than the above is to just read it from the \file{.aux} file % directly, which relieves us from hacking into any internals. That's what % David Carlisle's answer at \url{https://tex.stackexchange.com/a/147705} % does. This answer has actually been converted into the package % \pkg{listlbls} by Norbert Melzer, but it is made to work with regular % labels, not with \pkg{zref}'s. And it also does not really expose the % information in a retrievable way (as far as I can tell). So, the below is % adapted from Carlisle's answer's technique (a poor man's version of it...). % % There is some subtlety here as to whether this approach makes it safe for us % to read the labels at this point without \cs{zref@wrapper@babel}. The % common wisdom is that babel's shorthands are only active after % |\begin{document}| (e.g., \url{https://tex.stackexchange.com/a/98897}). % Alas, it is more complicated than that. Babel's documentation says (in % section 9.5 Shorthands): ``To prevent problems with the loading of other % packages after babel we reset the catcode of the character to the original % one at the end of the package and of each language file (except with % KeepShorthandsActive). It is re-activate[d] again at |\begin{document}|. % We also need to make sure that the shorthands are active during the % processing of the \file{.aux} file. Otherwise some citations may give % unexpected results in the printout when a shorthand was used in the optional % argument of |\bibitem| for example.'' This is done with % |\if@filesw \immediate\write\@mainaux{...}|. In other words, the % catcode change is written in the \file{.aux} file itself! Indeed, if you % inspect the file, you'll find them there. Besides, there is still the % ominous ``except with KeepShorthandsActive''. % % However, the \emph{method} we're using here is not quite the same as the % usual run of the \file{.aux} file, because we're actively discarding the % lines for which the first token is not equal to \cs{zref@newlabel}. I have % tested the famous sensitive case for this: \pkg{babel} \opt{french} and % labels with colons. And things worked as expected. Well, \emph{if} % \opt{KeepShorthandsActive} is enabled \emph{with \opt{french}} and we load % the package \emph{after babel} things do break, but not quite because of the % colons in the labels. Even \pkg{siunitx} breaks in the same % conditions\dots{} % % For reference: About what are valid characters for use in labels: % \url{https://tex.stackexchange.com/a/18312}. About some problems with % active colons: \url{https://tex.stackexchange.com/a/89470}. About the % difference between L3 strings and token lists, see % \url{https://tex.stackexchange.com/a/446381}, in particular Joseph Wright's % comment: ``Strings are for data that will never be typeset, for example file % names, identifiers, etc.: if the material may be used in typesetting, it % should be a token list.'' See also moewe's (CW) answer in the same lines. % Which suggests using L3 strings for the reference labels might be a good % catch all approach, and possibly more robust. David Carlisle's comment % about \pkg{inputenc} and how the strings work is a caveat (see % \url{https://tex.stackexchange.com/q/446123#comment1516961_446381}, thanks % David Carlisle). Still\dots{} let's stick to tradition as long as it works, % \pkg{zref} already does a great job in this regard anyway. % % % \begin{variable}{\g_@@_auxfile_lblseq_prop} % \begin{macrocode} \prop_new:N \g_@@_auxfile_lblseq_prop % \end{macrocode} % \end{variable} % % \begin{macrocode} \tl_gset:Nn \g_@@_tmpa_tl { \c_sys_jobname_str .aux } \file_if_exist:nT { \g_@@_tmpa_tl } { % \end{macrocode} % Retrieve the information from the \file{.aux} file, and store it in a % property list, so that the sequence can be retrieved in key-value fashion. % \begin{macrocode} \ior_open:Nn \g_@@_tmpa_ior { \g_@@_tmpa_tl } \group_begin: \int_zero:N \l_@@_tmpa_int \tl_clear:N \l_@@_tmpa_tl \tl_clear:N \l_@@_tmpb_tl \bool_set_false:N \l_@@_tmpa_bool \ior_map_variable:NNn \g_@@_tmpa_ior \l_@@_tmpa_tl { \tl_map_variable:NNn \l_@@_tmpa_tl \l_@@_tmpb_tl { \tl_if_eq:NnTF \l_@@_tmpb_tl { \zref@newlabel } { % \end{macrocode} % Found a \cs{zref@label}, signal it. % \begin{macrocode} \bool_set_true:N \l_@@_tmpa_bool } { \bool_if:NTF \l_@@_tmpa_bool { \bool_set_false:N \l_@@_tmpa_bool \int_incr:N \l_@@_tmpa_int \prop_gput:Nee \g_@@_auxfile_lblseq_prop { \l_@@_tmpb_tl } { \int_use:N \l_@@_tmpa_int } } { % \end{macrocode} % If there is not a match of the first token with \cs{zref@newlabel}, break % the loop and discard the rest of the line, to ensure no babel calls to % \cs{catcode} in the \file{.aux} file get expanded. This also breaks the % loop and discards the rest of the \cs{zref@newlabel} lines after we got the % label we wanted, since we reset \cs{l_@@_tmpa_bool} in the \texttt{T} % branch. % \begin{macrocode} \tl_map_break: } } } } \group_end: \ior_close:N \g_@@_tmpa_ior } % \end{macrocode} % % % % The alternate method I had considered (more than that...) for this was using % yx coordinates supplied by \pkg{zref}'s \pkg{savepos} module. However, this % approach brought in a number of complexities, including the need to patch % either \cs{zref@label} or \cs{ZREF@label}. In addition, the technique was % at the bottom fundamentally flawed. Ulrike Fischer was very much right when % she said that ``structure and position are two different beasts'' % (\url{https://github.com/ho-tex/zref/issues/12#issuecomment-880022576}). It % is true that the checks based on it behaved decently, in normal % circumstances, and except for outrageous label placement by the user, it % would return the expected results. We don't really need exact coordinates % to decide ``above/below''. Besides, it would do an exact job for the % dedicated target macros of this package. It is also true that the ``page'' % for \cs{pageref} is stored with the value of where the \cs{label} is placed, % wherever that may be. However, I could not conceive a situation where the % \texttt{yx} criterion would perform clearly better than the % \texttt{labelseq} one. And, if that's the case, and considering the % complications it brings, this check was a slippery slope. All in all, I've % decided to drop it. % % There's an interesting answer by David Carlisle at % \url{https://tex.stackexchange.com/a/419189} to decide whether to typeset % ``above'' or ``below'' using a method which essentially boils down to % ``position in the \file{.aux} file''. % % % % \subsection{Counter} % % We need a dedicated counter for the labels generated by the checks and % targets. The value of the counter is not relevant, we just need it to be % able to set proper anchors with \cs{refstepcounter}. And, since I couldn't % find a \cs{refstepcounter} equivalent in L3, we use a standard 2e counter % here. I'm also using the technique to ensure the counter is never reset % that is used by \file{zref-abspage.sty} and \cs{zref@require@unique}. % Indeed, the requirements are the same, we need numbers ensured to be % \emph{unique} in the counter. % % \begin{macrocode} \begingroup \let \@addtoreset \ltx@gobbletwo \newcounter { zrefcheck } \endgroup \setcounter { zrefcheck } { 0 } % \end{macrocode} % % % % \subsection{Label formats} % % \begin{macro}{\@@_check_lblfmt:n} % \begin{syntax} % \cs{@@_check_lblfmt:n} \Arg{check id int} % \end{syntax} % \begin{macrocode} \cs_new:Npn \@@_check_lblfmt:n #1 { zrefcheck@ \int_use:N #1 } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_end_lblfmt:n} % \begin{syntax} % \cs{@@_end_lblfmt:n} \Arg{label} % \end{syntax} % \begin{macrocode} \cs_new:Npn \@@_end_lblfmt:n #1 { #1 @zrefcheck } % \end{macrocode} % \end{macro} % % % % \subsection{Property values} % % % \begin{macro}[int]{\zrefcheck_get_astl:nnn} % A convenience function to retrieve property values from labels. Uses % \cs{g_@@_auxfile_lblseq_prop} for \texttt{lblseq}, and calls % \cs{zref@extractdefault} for everything else. % % We cannot use the ``return value'' of \cs{@@_get_astl:nnn} or % \cs{@@_get_asint:nnn} directly, because we need to use the retrieved % property values as arguments in the checks, however we use here a number % of non-expandable operations. Hence, we receive a local \texttt{tl/int} % variable as third argument and set that, so that it is available (and % expandable) at the place of use, and also make these functions `protected' % (see egreg's \url{https://tex.stackexchange.com/a/572903}: ``a function % that performs assignments should be \texttt{protected}''). For this % reason, we do not group here, because we are passing a local variable % around, but it is expected this function will be called within a group. % % We're returning \cs{c_empty_tl} in case of failure to find the intended % property value (explicitly in \cs{zref@extractdefault}, but that is also % what \cs{tl_clear:N} does). % % \begin{syntax} % \cs{zrefcheck_get_astl:nnn} \Arg{label} \Arg{prop} \Arg{tl var} % \end{syntax} % \begin{macrocode} \cs_new_protected:Npn \zrefcheck_get_astl:nnn #1#2#3 { \tl_clear:N #3 \tl_if_eq:nnTF {#2} { lblseq } { \prop_get:NnNF \g_@@_auxfile_lblseq_prop {#1} #3 { \msg_warning:nnnn { zref-check } { property-not-in-label } {#1} {#2} } } { % \end{macrocode} % There are three things we need to check to ensure the information we are % trying to retrieve here exists: the existence of \Arg{label}, the existence % of \Arg{prop}, and whether the particular label being queried actually % contains the property. If that's all in place, the value is passed to the % checks, and it's their responsibility to verify the consistency of this % value. % % The existence of the label is an user facing issue, and a warning for this % is placed in \cs{@@_zcheck:nnnnn} (and done with \cs{zref@refused}). We do % check here though for definition with \cs{zref@ifrefundefined} and silently % do nothing if it is undefined, to reduce irrelevant warnings in a fresh % compilation round. The other two are more ``internal'' problems, either % some problem with the checks, or with the configuration of \pkg{zref} for % their consumption. % \begin{macrocode} \zref@ifrefundefined {#1} {} { \zref@ifpropundefined {#2} { \msg_warning:nnnn { zref-check } { property-undefined } {#2} } { \zref@ifrefcontainsprop {#1} {#2} { \exp_args:NNNo \exp_args:NNo \tl_set:Nn #3 { \zref@extractdefault {#1} {#2} { \c_empty_tl } } } { \msg_warning:nnnn { zref-check } { property-not-in-label } {#1} {#2} } } } } } % \end{macrocode} % \end{macro} % % % \begin{variable}{\l_@@_integer_bool} % \cs{zrefcheck_get_asint:nnn} is a very convenient wrapper around the more % general \cs{zrefcheck_get_astl:nnn}, since almost always we'll be wanting % to compare numbers in the checks. However, it is quite hard for it to % ensure an integer is \emph{always} returned in the case of errors. And % those do occur, even in a well structured document (e.g., in a first round % of compilation). To complicate things, the L3 integer predicates are % \emph{very} sensitive to receiving any other kind of data, and they % \emph{scream}. To handle this \cs{zrefcheck_get_asint:nnn} uses % \cs{l_@@_integer_bool} to signal if an integer could not be returned. To % use this function always set \cs{l_@@_integer_bool} to true first, then % call it as much as you need. If any of these calls got is returning % anything which is not an integer, \cs{l_@@_integer_bool} will have been % set to false, and you should check that this hasn't happened before % actually comparing the integers (\cs{bool_lazy_and:nnTF} is your friend). % \begin{macrocode} \bool_new:N \l_@@_integer_bool % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_propval_tl} % \begin{macrocode} \tl_new:N \l_@@_propval_tl % \end{macrocode} % \end{variable} % % \begin{macro}[int]{\zrefcheck_get_asint:nnn} % \begin{syntax} % \cs{zrefcheck_get_asint:nnn} \Arg{label} \Arg{prop} \Arg{int var} % \end{syntax} % \begin{macrocode} \cs_new_protected:Npn \zrefcheck_get_asint:nnn #1#2#3 { \zrefcheck_get_astl:nnn {#1} {#2} { \l_@@_propval_tl } \@@_is_integer:nTF { \l_@@_propval_tl } { % \end{macrocode} % Make it an integer data type. % \begin{macrocode} \int_set:Nn #3 { \int_eval:n { \l_@@_propval_tl } } } { \bool_set_false:N \l_@@_integer_bool \zref@ifrefundefined {#1} % \end{macrocode} % Keep silent if ref is undefined to reduce irrelevant warnings in a fresh % compilation round. Again, this is also not the point to check for undefined % references, that's a task for \cs{@@_zcheck:nnnnn}. % \begin{macrocode} { } { \msg_warning:nnnn { zref-check } { property-not-integer } {#2} {#1} } } } % \end{macrocode} % \end{macro} % % % % % \section{User interface} % % % \subsection{\cs{zcheck}} % % \begin{macro}[int]{\zcheck} % The \marg{text} argument of \cs{zcheck} should not be long, since % \cs{hyperlink} cannot receive a long argument. Besides, there is no % reason for it to be. Note, also, that hyperlinks crossing page boundaries % have some known issues: \url{https://tex.stackexchange.com/a/182769}, % \url{https://tex.stackexchange.com/a/54607}, % \url{https://tex.stackexchange.com/a/179907}. % % \begin{syntax} % \cs{zcheck}\meta{*}\oarg{checks/options}\marg{labels}\marg{text} % \end{syntax} % % \begin{macrocode} \NewDocumentCommand \zcheck { s O { } m m } { \zref@wrapper@babel \@@_zcheck:nnnn {#3} {#1} {#2} {#4} } % \end{macrocode} % \end{macro} % % % \begin{variable} % { % \l_@@_zcheck_labels_seq , % \g_@@_id_int , % \l_@@_checkbeg_tl , % \l_@@_link_label_tl , % \l_@@_link_anchor_tl , % \l_@@_link_star_bool % } % \begin{macrocode} \seq_new:N \l_@@_zcheck_labels_seq \int_new:N \g_@@_id_int \tl_new:N \l_@@_checkbeg_tl \tl_new:N \l_@@_link_label_tl \tl_new:N \l_@@_link_anchor_tl \bool_new:N \l_@@_link_star_bool % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_zcheck:nnnn} % An intermediate internal function, which does the actual heavy lifting, % and places \Arg{labels} as first argument, so that it can be protected by % \cs{zref@wrapper@babel} in \cs{zcheck}. This is the same procedure as the % one used in the definition of \cs{zref} in \file{zref-user.sty} for % protection of \pkg{babel} active characters. % % \begin{syntax} % \cs{@@_zcheck:nnnn} \Arg{labels} \Arg{*} \Arg{checks/options} \Arg{text} % \end{syntax} % % \begin{macrocode} \cs_new_protected:Npn \@@_zcheck:nnnn #1#2#3#4 { \group_begin: % \end{macrocode} % Process local options and checks. We use \cs{seq_set_split:Nnn} to set % \cs{l_@@_zcheck_labels_seq} -- instead of \cs{seq_set_from_clist:Nn} -- to % support empty labels. % \begin{macrocode} \keys_set:nn { zref-check/zcheck } {#3} \seq_set_split:Nnn \l_@@_zcheck_labels_seq { , } {#1} % \end{macrocode} % Names of the labels for this zcheck call. % \begin{macrocode} \int_gincr:N \g_@@_id_int \tl_set:Ne \l_@@_checkbeg_tl { \@@_check_lblfmt:n { \g_@@_id_int } } % \end{macrocode} % Set checkbeg label. % \begin{macrocode} \zref@labelbylist { \l_@@_checkbeg_tl } { zrefcheck-check } % \end{macrocode} % Typeset \marg{text}, with hyperlink when appropriate. Even though the first % argument can receive a list of labels, there is no meaningful way to set % links to multiple targets. Hence, only the first one is considered for % hyperlinking. % \begin{macrocode} \seq_get:NN \l_@@_zcheck_labels_seq \l_@@_link_label_tl \bool_set:Nn \l_@@_link_star_bool {#2} \zref@ifrefundefined { \l_@@_link_label_tl } % \end{macrocode} % If the reference is undefined, just typeset. % \begin{macrocode} {#4} { \bool_if:nTF { \l_@@_use_hyperref_bool && ! \l_@@_link_star_bool } { \exp_args:Ne \zrefcheck_get_astl:nnn { \l_@@_link_label_tl } { anchor } { \l_@@_link_anchor_tl } \hyperlink { \l_@@_link_anchor_tl } {#4} } {#4} } % \end{macrocode} % Set checkend label. % \begin{macrocode} \bool_if:NT \l_@@_zcheck_end_label_bool { \zref@labelbylist { \@@_end_lblfmt:n { \l_@@_checkbeg_tl } } { zrefcheck-end } } % \end{macrocode} % Check if \meta{labels} are defined. % \begin{macrocode} \seq_map_inline:Nn \l_@@_zcheck_labels_seq { \tl_if_empty:nF {##1} { \zref@refused {##1} } } % \end{macrocode} % Run the checks. % \begin{macrocode} \@@_run_checks:nne { \l_@@_zcheck_checks_seq } { \l_@@_zcheck_labels_seq } { \l_@@_checkbeg_tl } \group_end: } % \end{macrocode} % \end{macro} % % % \subsection{Targets} % % \begin{macro}[int]{\zctarget} % \begin{syntax} % \cs{zctarget}\marg{label}\marg{text} % \end{syntax} % \begin{macrocode} \NewDocumentCommand \zctarget { m +m } { % \end{macrocode} % Group contents of \cs{zctarget} to avoid leaking the effects of % \cs{refstepcounter} over \cs{@currentlabel}. The same care is not needed % for \texttt{zcregion}, since the environment is already grouped. % \begin{macrocode} \group_begin: \refstepcounter { zrefcheck } \zref@wrapper@babel \zref@label {#1} #2 \tl_if_empty:nF {#2} { \zref@wrapper@babel \zref@labelbylist { \@@_end_lblfmt:n {#1} } { zrefcheck-end } } \group_end: } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{zcregion} % \begin{syntax} % |\begin{zcregion}|\marg{label} % | ...| % |\end{zcregion}| % \end{syntax} % \begin{macrocode} \NewDocumentEnvironment {zcregion} { m } { \refstepcounter { zrefcheck } \zref@wrapper@babel \zref@label {#1} } { \zref@wrapper@babel \zref@labelbylist { \@@_end_lblfmt:n {#1} } { zrefcheck-end } } % \end{macrocode} % \end{macro} % % % % \section{Checks} % % What is needed define a \pkg{zref-check} check? % % First, a conditional function defined with: % % \cs{prg_new_protected_conditional:Npnn} \cs{@@_check_\meta{check}:nn} |#1#2 { F }| % % \noindent where \meta{check} is the name of the check, the first argument is % the \Arg{label} and the second the \Arg{reference}. The existence of the % check is verified by the existence of the function with this name-scheme % (and signatures). As usual, this function must return either % \cs{prg_return_true:} or \cs{prg_return_false:}. Of course, you can define % other variants if you need them internally, it is just that what the package % does expect and verifies is the existence of the \texttt{:nnF} variant. % % Note that the naming convention of the checks adopts the perspective of the % \meta{reference}. That is, the ``before'' check should return true if the % \meta{label} occurs before the ``reference''. % % The check conditionals are expected to retrieve \pkg{zref}'s label % information with \cs{zrefcheck_get_astl:nnn} or % \cs{zrefcheck_get_asint:nnn}. Also, technically speaking, the % \meta{reference} argument is also a label, actually a pair of them, as set % by \cs{zcheck}. For the ``labels'', any \pkg{zref} property in \pkg{zref}'s % main list is available, the ``references'' store the properties in the % \texttt{zrefcheck} list. Besides those, there is also the \texttt{lblseq} % (fake) property (for either ``labels'' or ``references''), stored in % \cs{g_@@_auxfile_lblseq_prop}. % % Second, the required properties of labels and references must be duly % registered for \pkg{zref}. This can be done with \cs{zref@newprop}, % \cs{zref@addprop} and friends, as usual. % % Third, the check must be registered as a key which gets setup in % \cs{zcheck} by the \texttt{ zref-check / zcheck } key set. % % Fourth, if the check requires only a single label to work, it should be % registered in \cs{c_@@_single_label_checks_seq}. % % % \subsection{Single label checks} % % % Some checks do not require an ``end label'' in \cs{zcheck}, notably the % sectioning ones, which don't rely on page boundaries. Hence, in case % \cs{zcheck} only calls checks in this set, we can spare the setting of the % end label. % % \begin{variable}{\c_@@_single_label_checks_seq} % \begin{macrocode} \seq_const_from_clist:Nn \c_@@_single_label_checks_seq { thischap , prevchap , nextchap , chapsbefore , chapsafter , thissec , prevsec , nextsec , secsbefore , secsafter , manual , } % \end{macrocode} % \end{variable} % % % % \subsection{Setup} % % \begin{variable}{\l_@@_zcheck_checks_seq,\l_@@_end_label_required_bool} % \begin{macrocode} \seq_new:N \l_@@_zcheck_checks_seq \bool_new:N \l_@@_zcheck_end_label_bool % \end{macrocode} % \end{variable} % % % First, we inherit all the main options into the keys of \texttt{zref-check / % zcheck}. See \url{https://github.com/latex3/latex3/issues/1254}. % ^^A zref-check/zcheck % \begin{macrocode} \keys_define:nn { zref-check } { zcheck .inherit:n = zref-check/setup } % \end{macrocode} % % Then we add the checks to it. % \begin{macrocode} \clist_map_inline:nn { thispage , prevpage , nextpage , facing , otherpage , pagegap , above , below , pagesbefore , ppbefore , pagesafter , ppafter , before , after , thischap , prevchap , nextchap , chapsbefore , chapsafter , thissec , prevsec , nextsec , secsbefore , secsafter , close , far , manual , } { \keys_define:nn { zref-check/zcheck } { #1 .code:n = { \seq_put_right:Nn \l_@@_zcheck_checks_seq {#1} \seq_if_in:NnF \c_@@_single_label_checks_seq {#1} { \bool_set_true:N \l_@@_zcheck_end_label_bool } } , #1 .value_forbidden:n = true , } } % \end{macrocode} % % % % \subsection{Running} % % \begin{macro}{\@@_run_checks:nnn} % \begin{syntax} % \cs{@@_run_checks:nnn} \Arg{checks} \Arg{labels} \Arg{reference} % \end{syntax} % \meta{checks} are expected to be received as a sequence variable. % \begin{macrocode} \cs_new_protected:Npn \@@_run_checks:nnn #1#2#3 { \group_begin: \seq_map_inline:Nn #2 { \seq_if_empty:NTF #1 { \@@_message:nnnn { no-checks } { } { } { } } { \seq_map_inline:Nn #1 { \@@_do_check:nnn {####1} {##1} {#3} } } } \group_end: } \cs_generate_variant:Nn \@@_run_checks:nnn { nne } % \end{macrocode} % \end{macro} % % % \begin{variable} % { % \l_@@_passedcheck_bool , % \l_@@_onpage_bool , % \l_@@_empty_label_bool , % \c_@@_onpage_checks_seq % } % \begin{macrocode} \bool_new:N \l_@@_passedcheck_bool \bool_new:N \l_@@_onpage_bool \bool_new:N \l_@@_empty_label_bool \seq_const_from_clist:Nn \c_@@_onpage_checks_seq { above , below , before , after } % \end{macrocode} % \end{variable} % % % Variant not provided by \pkg{expl3}. % \begin{macrocode} \cs_generate_variant:Nn \exp_args:Nnno { Nnoo } % \end{macrocode} % % \begin{macro}{\@@_do_check:nnn} % \begin{syntax} % \cs{@@_do_check:nnn} \Arg{check} \Arg{label beg} \Arg{reference beg} % \end{syntax} % \begin{macrocode} \cs_new_protected:Npn \@@_do_check:nnn #1#2#3 { \group_begin: \bool_set_true:N \l_@@_passedcheck_bool \bool_set_false:N \l_@@_onpage_bool \bool_set_false:N \l_@@_empty_label_bool \cs_if_exist:cTF { @@_check_ #1 :nnF } { % \end{macrocode} % \meta{label beg} may be defined or not, it is arbitrary user input. Whether % this is the case is checked in \cs{@@_zcheck:nnnnn}, and due warning already % ensues. So there's no need to do it again here. The only exception is the % case of empty labels, for which we want to issue a failed check warning. % \begin{macrocode} \zref@ifrefundefined {#2} { \tl_if_empty:nT {#2} { \bool_set_false:N \l_@@_passedcheck_bool \bool_set_true:N \l_@@_empty_label_bool } } { % ``label beg'' vs ``reference beg''. \use:c { @@_check_ #1 :nnF } {#2} {#3} { \bool_set_false:N \l_@@_passedcheck_bool } % ``reference end'' \emph{may} exist or not depending on the % checks. \zref@ifrefundefined { \@@_end_lblfmt:n {#3} } { % ``label end'' \emph{may} have been created by the % target commands. \zref@ifrefundefined { \@@_end_lblfmt:n {#2} } {} { % ``label end'' vs ``reference beg''. \exp_args:Nno \use:c { @@_check_ #1 :nnF } { \@@_end_lblfmt:n {#2} } {#3} { \bool_set_false:N \l_@@_passedcheck_bool } } } { % ``label beg'' vs ``reference end''. \exp_args:Nnno \use:c { @@_check_ #1 :nnF } {#2} { \@@_end_lblfmt:n {#3} } { \bool_set_false:N \l_@@_passedcheck_bool } % ``label end'' \emph{may} have been created by the % target commands. \zref@ifrefundefined { \@@_end_lblfmt:n {#2} } {} { % ``label end'' vs ``reference beg''. \exp_args:Nno \use:c { @@_check_ #1 :nnF } { \@@_end_lblfmt:n {#2} } {#3} { \bool_set_false:N \l_@@_passedcheck_bool } % ``label end'' vs ``reference end''. \exp_args:Nnoo \use:c { @@_check_ #1 :nnF } { \@@_end_lblfmt:n {#2} } { \@@_end_lblfmt:n {#3} } { \bool_set_false:N \l_@@_passedcheck_bool } } } % \end{macrocode} % Handle option \opt{onpage=msg}. This is only granted for tests which % perform ``within this page'' checks (\opt{above}, \opt{below}, \opt{before}, % \opt{after}) \emph{and} if any of the two by two checks uses a ``within this % page'' comparison. If both conditions are met, signal. % \begin{macrocode} \seq_if_in:NnT \c_@@_onpage_checks_seq {#1} { \@@_check_thispage:nnT {#2} {#3} { \bool_set_true:N \l_@@_onpage_bool } \zref@ifrefundefined { \@@_end_lblfmt:n {#3} } { \zref@ifrefundefined { \@@_end_lblfmt:n {#2} } {} { \@@_check_thispage:nnT { \@@_end_lblfmt:n {#2} } {#3} { \bool_set_true:N \l_@@_onpage_bool } } } { \@@_check_thispage:nnT {#2} { \@@_end_lblfmt:n {#3} } { \bool_set_true:N \l_@@_onpage_bool } \zref@ifrefundefined { \@@_end_lblfmt:n {#2} } {} { \@@_check_thispage:nnT { \@@_end_lblfmt:n {#2} } {#3} { \bool_set_true:N \l_@@_onpage_bool } \@@_check_thispage:nnT { \@@_end_lblfmt:n {#2} } { \@@_end_lblfmt:n {#3} } { \bool_set_true:N \l_@@_onpage_bool } } } } } } { \msg_warning:nnn { zref-check } { check-missing } {#1} } \bool_if:NTF \l_@@_passedcheck_bool { \bool_if:nT { \l_@@_msgonpage_bool && \l_@@_onpage_bool } { \@@_message:nnne { double-check } {#1} {#2} { \zref@extractdefault {#3} {page} {'unknown'} } } } { \bool_if:NTF \l_@@_empty_label_bool { \@@_message:nnnn { empty-label } {#1} { } { } } { \@@_message:nnne { check-failed } {#1} {#2} { \zref@extractdefault {#3} {page} {'unknown'} } } } \group_end: } \cs_generate_variant:Nn \@@_do_check:nnn { nnV } % \end{macrocode} % \end{macro} % % % \subsection{Conditionals} % % \begin{variable} % { % \l_@@_lbl_int , % \l_@@_ref_int , % \l_@@_lbl_b_int , % \l_@@_ref_b_int % } % More readable scratch variables for the tests. % \begin{macrocode} \int_new:N \l_@@_lbl_int \int_new:N \l_@@_ref_int \int_new:N \l_@@_lbl_b_int \int_new:N \l_@@_ref_b_int % \end{macrocode} % \end{variable} % % % \subsubsection{This page} % % \begin{macro}{\@@_check_thispage:nn, \@@_check_otherpage:nn} % \begin{macrocode} \prg_new_protected_conditional:Npnn \@@_check_thispage:nn #1#2 { T , F , TF } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { abspage } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { abspage } { \l_@@_ref_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_int } = { \l_@@_ref_int } && % \end{macrocode} % `0' is the default value of \texttt{abspage}, but this value should not % happen normally for this property, since even the first page, after it gets % shipped out, will receive value `1'. So, if we do find `0' here, better % signal something is wrong. This comment extends to all page number checks. % \begin{macrocode} ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } && ! \int_compare_p:nNn { \l_@@_ref_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } \prg_new_protected_conditional:Npnn \@@_check_otherpage:nn #1#2 { T , F , TF } { \@@_check_thispage:nnTF {#1} {#2} { \prg_return_false: } { \prg_return_true: } } % \end{macrocode} % \end{macro} % % % \subsubsection{On page} % % \begin{macro}{\@@_check_above:nn, \@@_check_below:nn} % \begin{macrocode} \prg_new_protected_conditional:Npnn \@@_check_above:nn #1#2 { F , TF } { \group_begin: \@@_check_thispage:nnTF {#1} {#2} { \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { lblseq } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { lblseq } { \l_@@_ref_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_int } < { \l_@@_ref_int } && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } && ! \int_compare_p:nNn { \l_@@_ref_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } } { \group_insert_after:N \prg_return_false: } \group_end: } \prg_new_protected_conditional:Npnn \@@_check_below:nn #1#2 { F , TF } { \@@_check_thispage:nnTF {#1} {#2} { \@@_check_above:nnTF {#1} {#2} { \prg_return_false: } { \prg_return_true: } } { \prg_return_false: } } % \end{macrocode} % \end{macro} % % % \subsubsection{Before / After} % % \begin{macro}{\@@_check_before:nn, \@@_check_after:nn} % \begin{macrocode} \prg_new_protected_conditional:Npnn \@@_check_before:nn #1#2 { F } { \@@_check_pagesbefore:nnTF {#1} {#2} { \prg_return_true: } { \@@_check_above:nnTF {#1} {#2} { \prg_return_true: } { \prg_return_false: } } } \prg_new_protected_conditional:Npnn \@@_check_after:nn #1#2 { F } { \@@_check_pagesafter:nnTF {#1} {#2} { \prg_return_true: } { \@@_check_below:nnTF {#1} {#2} { \prg_return_true: } { \prg_return_false: } } } % \end{macrocode} % \end{macro} % % % \subsubsection{Pages} % % \begin{macro} % { % \@@_check_nextpage:nn , % \@@_check_prevpage:nn , % \@@_check_pagesbefore:nn , % \@@_check_ppbefore:nn , % \@@_check_pagesafter:nn , % \@@_check_ppafter:nn , % \@@_check_pagegap:nn , % \@@_check_facing:nn % } % \begin{macrocode} \prg_new_protected_conditional:Npnn \@@_check_nextpage:nn #1#2 { F } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { abspage } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { abspage } { \l_@@_ref_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_int } = { \l_@@_ref_int + 1 } && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } && ! \int_compare_p:nNn { \l_@@_ref_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } \prg_new_protected_conditional:Npnn \@@_check_prevpage:nn #1#2 { F } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { abspage } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { abspage } { \l_@@_ref_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_int } = { \l_@@_ref_int - 1 } && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } && ! \int_compare_p:nNn { \l_@@_ref_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } \prg_new_protected_conditional:Npnn \@@_check_pagesbefore:nn #1#2 { F , TF } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { abspage } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { abspage } { \l_@@_ref_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_int } < { \l_@@_ref_int } && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } && ! \int_compare_p:nNn { \l_@@_ref_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } \cs_new_eq:NN \@@_check_ppbefore:nnF \@@_check_pagesbefore:nnF \prg_new_protected_conditional:Npnn \@@_check_pagesafter:nn #1#2 { F , TF } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { abspage } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { abspage } { \l_@@_ref_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_int } > { \l_@@_ref_int } && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } && ! \int_compare_p:nNn { \l_@@_ref_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } \cs_new_eq:NN \@@_check_ppafter:nnF \@@_check_pagesafter:nnF \prg_new_protected_conditional:Npnn \@@_check_pagegap:nn #1#2 { F } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { abspage } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { abspage } { \l_@@_ref_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \int_abs:n { \l_@@_lbl_int - \l_@@_ref_int } } > { 1 } && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } && ! \int_compare_p:nNn { \l_@@_ref_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } \prg_new_protected_conditional:Npnn \@@_check_facing:nn #1#2 { F } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { abspage } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { abspage } { \l_@@_ref_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { % \end{macrocode} % There exists no ``facing'' page if the document is not twoside. % \begin{macrocode} \legacy_if_p:n { @twoside } && % \end{macrocode} % Now we test ``facing''. % \begin{macrocode} ( ( \int_if_odd_p:n { \l_@@_ref_int } && \int_compare_p:nNn { \l_@@_lbl_int } = { \l_@@_ref_int - 1 } ) || ( \int_if_even_p:n { \l_@@_ref_int } && \int_compare_p:nNn { \l_@@_lbl_int } = { \l_@@_ref_int + 1 } ) ) && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } && ! \int_compare_p:nNn { \l_@@_ref_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } % \end{macrocode} % \end{macro} % % % \subsubsection{Close / Far} % % \begin{macro}{\@@_check_close:nn, \@@_check_far:nn} % \begin{macrocode} \prg_new_protected_conditional:Npnn \@@_check_close:nn #1#2 { F , TF } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { abspage } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { abspage } { \l_@@_ref_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \int_abs:n { \l_@@_lbl_int - \l_@@_ref_int } } < { \l_@@_close_range_int + 1 } && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } && ! \int_compare_p:nNn { \l_@@_ref_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } \prg_new_protected_conditional:Npnn \@@_check_far:nn #1#2 { F } { \@@_check_close:nnTF {#1} {#2} { \prg_return_false: } { \prg_return_true: } } % \end{macrocode} % \end{macro} % % % \subsubsection{Chapter} % % \begin{macro} % { % \@@_check_thischap:nn , % \@@_check_nextchap:nn , % \@@_check_prevchap:nn , % \@@_check_chapsafter:nn , % \@@_check_chapsbefore:nn % } % \begin{macrocode} \prg_new_protected_conditional:Npnn \@@_check_thischap:nn #1#2 { F } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { zc@abschap } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { zc@abschap } { \l_@@_ref_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_int } = { \l_@@_ref_int } && % \end{macrocode} % `0' is the default value of \texttt{zc@abschap} property, and means here no % \cs{chapter} has yet been issued, therefore it cannot be ``this chapter'', % nor ``the next chapter'', nor ``the previous chapter'', it is just ``no % chapter''. Note, however, that a statement about a ``future'' chapter does % not require the ``current'' one to exist. This comment extends to all % chapter checks. % \begin{macrocode} ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } && ! \int_compare_p:nNn { \l_@@_ref_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } \prg_new_protected_conditional:Npnn \@@_check_nextchap:nn #1#2 { F } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { zc@abschap } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { zc@abschap } { \l_@@_ref_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_int } = { \l_@@_ref_int + 1 } && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } \prg_new_protected_conditional:Npnn \@@_check_prevchap:nn #1#2 { F } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { zc@abschap } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { zc@abschap } { \l_@@_ref_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_int } = { \l_@@_ref_int - 1 } && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } && ! \int_compare_p:nNn { \l_@@_ref_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } \prg_new_protected_conditional:Npnn \@@_check_chapsafter:nn #1#2 { F } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { zc@abschap } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { zc@abschap } { \l_@@_ref_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_int } > { \l_@@_ref_int } && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } \prg_new_protected_conditional:Npnn \@@_check_chapsbefore:nn #1#2 { F } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { zc@abschap } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { zc@abschap } { \l_@@_ref_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_int } < { \l_@@_ref_int } && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } && ! \int_compare_p:nNn { \l_@@_ref_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } % \end{macrocode} % \end{macro} % % % \subsubsection{Section} % % \begin{macro} % { % \@@_check_thissec:nn , % \@@_check_nextsec:nn , % \@@_check_prevsec:nn , % \@@_check_secsafter:nn , % \@@_check_secsbefore:nn % } % \begin{macrocode} \prg_new_protected_conditional:Npnn \@@_check_thissec:nn #1#2 { F } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { zc@abssec } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { zc@abssec } { \l_@@_ref_int } \zrefcheck_get_asint:nnn {#1} { zc@abschap } { \l_@@_lbl_b_int } \zrefcheck_get_asint:nnn {#2} { zc@abschap } { \l_@@_ref_b_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_b_int } = { \l_@@_ref_b_int } && \int_compare_p:nNn { \l_@@_lbl_int } = { \l_@@_ref_int } && % \end{macrocode} % `0' is the default value of \texttt{zc@abssec} property, and means here no % \cs{section} has yet been issued since its counter has been reset, which % occurs at the beginning of the document and at every chapter. Hence, as is % the case for chapters, `0' is just ``not a section''. The same observation % about the need of the ``current'' section to exist to be able to refer to a % ``future'' one also holds. This comment extends to all section checks. % \begin{macrocode} ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } && ! \int_compare_p:nNn { \l_@@_ref_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } \prg_new_protected_conditional:Npnn \@@_check_nextsec:nn #1#2 { F } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { zc@abssec } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { zc@abssec } { \l_@@_ref_int } \zrefcheck_get_asint:nnn {#1} { zc@abschap } { \l_@@_lbl_b_int } \zrefcheck_get_asint:nnn {#2} { zc@abschap } { \l_@@_ref_b_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_b_int } = { \l_@@_ref_b_int } && \int_compare_p:nNn { \l_@@_lbl_int } = { \l_@@_ref_int + 1 } && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } \prg_new_protected_conditional:Npnn \@@_check_prevsec:nn #1#2 { F } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { zc@abssec } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { zc@abssec } { \l_@@_ref_int } \zrefcheck_get_asint:nnn {#1} { zc@abschap } { \l_@@_lbl_b_int } \zrefcheck_get_asint:nnn {#2} { zc@abschap } { \l_@@_ref_b_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_b_int } = { \l_@@_ref_b_int } && \int_compare_p:nNn { \l_@@_lbl_int } = { \l_@@_ref_int - 1 } && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } && ! \int_compare_p:nNn { \l_@@_ref_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } \prg_new_protected_conditional:Npnn \@@_check_secsafter:nn #1#2 { F } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { zc@abssec } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { zc@abssec } { \l_@@_ref_int } \zrefcheck_get_asint:nnn {#1} { zc@abschap } { \l_@@_lbl_b_int } \zrefcheck_get_asint:nnn {#2} { zc@abschap } { \l_@@_ref_b_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_b_int } = { \l_@@_ref_b_int } && \int_compare_p:nNn { \l_@@_lbl_int } > { \l_@@_ref_int } && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } \prg_new_protected_conditional:Npnn \@@_check_secsbefore:nn #1#2 { F } { \group_begin: \bool_set_true:N \l_@@_integer_bool \zrefcheck_get_asint:nnn {#1} { zc@abssec } { \l_@@_lbl_int } \zrefcheck_get_asint:nnn {#2} { zc@abssec } { \l_@@_ref_int } \zrefcheck_get_asint:nnn {#1} { zc@abschap } { \l_@@_lbl_b_int } \zrefcheck_get_asint:nnn {#2} { zc@abschap } { \l_@@_ref_b_int } \bool_lazy_and:nnTF { \l_@@_integer_bool } { \int_compare_p:nNn { \l_@@_lbl_b_int } = { \l_@@_ref_b_int } && \int_compare_p:nNn { \l_@@_lbl_int } < { \l_@@_ref_int } && ! \int_compare_p:nNn { \l_@@_lbl_int } = { 0 } && ! \int_compare_p:nNn { \l_@@_ref_int } = { 0 } } { \group_insert_after:N \prg_return_true: } { \group_insert_after:N \prg_return_false: } \group_end: } % \end{macrocode} % \end{macro} % % % % \subsubsection{Manual} % % \begin{macro}{ \@@_check_manual:nn } % \begin{macrocode} \prg_new_protected_conditional:Npnn \@@_check_manual:nn #1#2 { F } { \prg_return_false: } % \end{macrocode} % \end{macro} % % % % \section{\pkg{zref-clever} integration} % % % There are four tasks \pkg{zref-clever} needs to do, in order to offer % integration with \pkg{zref-check} from the options of \cs{zcref}: i) set the % ``beg label''; ii) set the checks options; iii) run the checks; iv) % (possibly) set the ``end label''. Since `ii)' can be done directly by % running |\keys_set:nn { zref-check/zcheck }| on the options received, we % provide convenience functions for the other three tasks. % % % \begin{macro} % { % \zrefcheck_zcref_beg_label: , % \zrefcheck_zcref_end_label_maybe: , % \zrefcheck_zcref_run_checks_on_labels:n % } % \begin{macrocode} \cs_new_protected:Npn \zrefcheck_zcref_beg_label: { \int_gincr:N \g_@@_id_int \tl_set:Ne \l_@@_checkbeg_tl { \@@_check_lblfmt:n { \g_@@_id_int } } \zref@labelbylist { \l_@@_checkbeg_tl } { zrefcheck-check } } \cs_new_protected:Npn \zrefcheck_zcref_end_label_maybe: { \bool_if:NT \l_@@_zcheck_end_label_bool { \zref@labelbylist { \@@_end_lblfmt:n { \l_@@_checkbeg_tl } } { zrefcheck-end } } } \cs_new_protected:Npn \zrefcheck_zcref_run_checks_on_labels:n #1 { \@@_run_checks:nne { \l_@@_zcheck_checks_seq } {#1} { \l_@@_checkbeg_tl } } % \end{macrocode} % \end{macro} % % % % \section{\pkg{zref-vario} integration} % % % \begin{macro} % { % \zrefcheck_zrefvario_label: , % \zrefcheck_zrefvario_run_check_on_label:n % } % \begin{macrocode} \cs_new_protected:Npn \zrefcheck_zrefvario_label: { \int_gincr:N \g_@@_id_int \tl_set:Ne \l_@@_checkbeg_tl { \@@_check_lblfmt:n { \g_@@_id_int } } \zref@labelbylist { \l_@@_checkbeg_tl } { zrefcheck-zrefvario } } \cs_new_protected:Npn \zrefcheck_zrefvario_run_check_on_label:nn #1#2 { \@@_do_check:nnV {#1} {#2} \l_@@_checkbeg_tl } \cs_generate_variant:Nn \zrefcheck_zrefvario_run_check_on_label:nn { Vn } % \end{macrocode} % \end{macro} % % % \begin{macrocode} % % \end{macrocode} % % \PrintIndex %