% \iffalse meta-comment % % File: zref-check.dtx % % This file is part of the LaTeX package "zref-check". % % Copyright (C) 2021 Gustavo Barros % % 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 % Gustavo Barros. % % This work consists of the files zref-check.dtx, % zref-check.ins, % zref-check.tex, % zref-check-code.tex, % and the derived files zref-check.sty and % zref-check.pdf, % zref-check-code.pdf. % % 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 % % % \begin{documentation} % % \section{Introduction} % % The \pkg{zref-check} package provides an user interface for making \LaTeX{} % cross-references exploiting document contextual information to enrich the % way the reference can be rendered, but at the same time ensuring the means % that these cross-references can be done consistently with the document % structure. % % The usual \LaTeX{} cross-reference is done by referring to a \texttt{label}, % associated with one or another document structural element, and this % reference will typeset for you some content based on the information which % is stored in that label. \cs{zcheck}, the main user command of % \pkg{zref-check}, has a somewhat different concept. Instead of trying to % provide the text to be typeset based on the contextual information, % \cs{zcheck} lets the user supply an arbitrary text and specify one or more % checks to be done on the label(s) being referred to. If any of the checks % fails, a warning is issued upon compilation, so that the user can go back to % that cross-reference and correct it as needed, without having to rely on % burdensome and error prone manual proof-reading. % % This grants a much increased flexibility for the cross-reference text, which % means in practice that the writing style, the variety of expressions you may % use for similar situations, does not need to be sacrificed for the % convenience. \cs{zcheck}'s cross-references do not need to ``feel'' % automated to be consistently checked. Localization is also not an issue, % since the cross-reference text is provided directly by the user. Separating % ``typesetting'' from ``checking'' also means there is a lot of document % context we can leverage for this purpose (see Section~\ref{sec:checks}). % % A standard \LaTeX{} cross-reference is made to refer to specific numbered % document elements -- chapters, sections, figures, tables, equations, etc. % The cross-reference will normally produce that number (which is the % element's ``id'') and, eventually, its ``type'' (the counter). We may also % refer to the page that element occurs and even its ``title'' (in which case, % atypically, we may even get to refer to an unnumbered section, provided we % also implicitly supply by some means the ``id''). % % For references to these usual specific document elements, \pkg{zref-check} % caters for a particular kind of cross-reference which is common: % \emph{relational} statements based on them. \cs{zcheck} can typeset and % meaningfully check cross-references such as ``above'', ``on the next page'', % ``on the facing page'', ``on the previous section'', ``later on this % chapter'' and so on. After all, if your reference is being made on page 2 % and refers to something on the same page, ``on this page'' reads much better % than ``on page~2''. If you are writing chapter~4, ``on the previous % chapter'' sounds nicer than ``on chapter~3''. % % However, there is yet another kind of ``looser'' cross-reference we % routinely do in our documents. Expressions such as ``previously'', ``as % mentioned before'', ``as will be discussed'', and so on, are a powerful % discursive instrument, which enriches the text, by offering hints to the % arguments' threads, without necessarily pressing them too hard onto the % reader. So, we might not want to say ``on footnote 57, pag.~34'', but % prefer ``previously'', not ``on Section 3.4'', but rather ``below'', or % ``later on''. Besides, we also may refer to certain passages in the text in % this way, rather than to numbered document elements. And this kind of % reference is not only hard to check and find, but also to fix. After all, % if you are making one such reference, you are taking that statement as a % premisse at the current point in the text. So, if that reference is % missing, or relocated, you may need to bring in the support to the premisse % for your argument to close, rather than just ``adjust the reference text''. % \pkg{zref-check} also provides support for this kind of cross-reference, % allowing for them to be consistently verified. % % % % \section{Loading the package} % \label{sec:loading-package} % % As usual: % % \begin{syntax} % \cs{usepackage}\oarg{options}|{zref-check}| % \end{syntax} % % % % \section{Dependencies} % % \pkg{zref} is required, of course, but in particular, its modules % \pkg{zref-user} and \pkg{zref-abspage} are loaded by default. \pkg{ifdraft} % (from the \pkg{oberdiek} bundle) is also loaded by default. A \LaTeX{} % kernel later than 2021-06-01 is required, since we rely on the new hook % system from \pkg{ltcmdhooks} for the sectioning checks. If \pkg{hyperref} % is loaded and option \pkg{hyperref} is given, \pkg{zref-check} makes use of % it, but it does not load the package for you. % % % \section{User interface} % \label{sec:user-interface} % % \begin{function}{\zcheck} % \begin{syntax} % \cs{zcheck}\oarg{checks/options}\marg{labels}\marg{text} % \end{syntax} % Typesets \meta{text}, as given, while performing a list of \meta{checks} % on each of the \meta{labels}. When \pkg{hyperref} support is enabled, % \meta{text} will be made a hyperlink to \emph{the first} \meta{label} in % \meta{labels}. The starred version of the command does the same as the % plain one, just does not form a link. The \meta{options} are (mostly) the % same as those of the package, and can be given to local effect. % \meta{checks} and \meta{options} can be given side by side as a comma % separated list in the optional argument. \meta{labels} is also a comma % separated list. % \end{function} % % \begin{function}{\zctarget} % \begin{syntax} % \cs{zctarget}\marg{label}\marg{text} % \end{syntax} % Typesets \meta{text}, as given, and places a pair of \texttt{zlabel}s, one % at the start of \meta{text}, using \meta{label} as label name, another one % (internal) at the end of \meta{text}. % \end{function} % % % \begin{function}{zcregion} % \begin{syntax} % |\begin{zcregion}|\marg{label} % | ...| % |\end{zcregion}| % \end{syntax} % An environment that does the same as \cs{zctarget}, for cases of longer % stretches of text. % \end{function} % % % \begin{function}{\zrefchecksetup} % \begin{syntax} % \cs{zrefchecksetup}\marg{options} % \end{syntax} % Sets \pkg{zref-check}'s options (see Section~\ref{sec:options}). % \end{function} % % % \section{Checks} % \label{sec:checks} % % \pkg{zref-check} provides several ``checks'' to be used with \cs{zcheck}. % The checks may be combined in a \cs{zcheck} call, e.g.\ \opt{[close, % after]}, or \opt{[thischap, before]}. In this case, each check in % \meta{checks} is performed against each of the \meta{labels}. This is done % independently for each check, which means, in practice, that the checks bear % a logical \texttt{AND} relation to the others. Whether the combination is % meaningful, is up to the user. As is the correspondence between the % \meta{checks} and the \meta{text} in \cs{zcheck}. % % The use of checks which perform ``within the page'' comparisons -- namely % \opt{above} and \opt{below} and, through them, \opt{before} and \opt{after} % -- comes with some caveats you should be acquainted with. % Section~\ref{sec:within-page-checks} discusses their limitations and expands % on the expected workflow for their use to ensure reliable results. % % Note that the naming convention of the checks adopts the perspective of % \cs{zcheck}. That is, the name of the check describes the position of the % label being referred to, relative to the \cs{zcheck} call being made. For % example, the \opt{before} check should issue no message if % \cs{ztarget}|{mylabel}{...}| occurs before % \cs{zcheck}|[before]{mylabel}{...}|. % % The available checks are the following: % % \begin{description}[leftmargin=0pt, itemindent=0pt, align=right, % labelsep=1em, font=\MacroFont] % % \item[thispage] \meta{label} occurs on the same page as \cs{zcheck}. % % \item[prevpage] \meta{label} occurs on the previous page relative to % \cs{zcheck}. % % \item[nextpage] \meta{label} occurs on the next page relative to % \cs{zcheck}. % % \item[pagegap] There is a page gap between \meta{label} and \cs{zcheck}, in % other words, \meta{label} does \emph{not} occur on \opt{thispage}, % \opt{prevpage} or \opt{nextpage}. % % \item[facing] On a \texttt{twoside} document, both \meta{label} and % \cs{zcheck} fall onto a double spread, each on one of the two facing % pages. % % \item[above] \meta{label} and \cs{zcheck} are both on the same page, and % \meta{label} occurs ``above'' \cs{zcheck}. % % \item[below] \meta{label} and \cs{zcheck} are both on the same page, and % \meta{label} occurs ``below'' \cs{zcheck}. % % \item[pagesbefore] \meta{label} occurs on any page before the one of % \cs{zcheck}. % % \item[ppbefore] Convenience alias for \opt{pagesbefore}. % % \item[pagesafter] \meta{label} occurs on any page after the one of % \cs{zcheck}. % % \item[ppafter] Convenience alias for \opt{pagesafter}. % % \item[before] Either \opt{above} or \opt{pagesbefore}. % % \item[after] Either \opt{below} or \opt{pagesafter}. % % \item[thischap] \meta{label} occurs on the same chapter as \cs{zcheck}. % % \item[prevchap] \meta{label} occurs on the previous chapter relative to the % one of \cs{zcheck}. % % \item[nextchap] \meta{label} occurs on the next chapter relative to the one % of \cs{zcheck}. % % \item[chapsbefore] \meta{label} occurs on any chapter before the one of % \cs{zcheck}. % % \item[chapsafter] \meta{label} occurs on any chapter after the one of % \cs{zcheck}. % % \item[thissec] \meta{label} occurs on the same section as \cs{zcheck}. % % \item[prevsec] \meta{label} occurs on the previous section (of the same % chapter) relative to the one of \cs{zcheck}. % % \item[nextsec] \meta{label} occurs on the next section (of the same chapter) % relative to the one of \cs{zcheck}. % % \item[secsbefore] \meta{label} occurs on any section (of the same chapter) % before the one of \cs{zcheck}. % % \item[secsafter] \meta{label} occurs on any section (of the same chapter) % after the one of \cs{zcheck}. % % \item[close] \meta{label} occurs within a page range from \opt{closerange} % pages before the one of \cs{zcheck} to \opt{closerange} pages after it % (about the \opt{closerange} option, see Section~\ref{sec:options}). % % \item[far] Not \opt{close}. % % \end{description} % % % % \section{Options} % \label{sec:options} % % Options are a standard \texttt{key=value} comma separated list, and can be % set globally either as \cs{usepackage}\oarg{options} at load-time (see % Section~\ref{sec:loading-package}), or by means of \cs{zrefchecksetup} (see % Section~\ref{sec:user-interface}) in the preamble. Most options can also be % used with local effects, through the optional argument of \cs{zcheck}. % % \DescribeOption{hyperref} % Controls the use of \pkg{hyperref} by \pkg{zref-check} and takes values % \opt{auto}, \opt{true}, \opt{false}. The default value, \opt{auto}, makes % \pkg{zref-check} use \pkg{hyperref} if it is loaded, meaning \cs{zcheck} can % be hyperlinked to the \emph{first label} in \meta{labels}. \opt{true} does % the same thing, but warns if \pkg{hyperref} is not loaded (\pkg{hyperref} is % never loaded for you). In either of these cases, if \pkg{hyperref} is % loaded, module \pkg{zref-hyperref} is also loaded by \pkg{zref-check}. % \opt{false} means not to use \pkg{hyperref} regardless of its availability. % This is a preamble only option, but \cs{zcheck} provides granular control of % hyperlinking by means of its starred version. % % % \DescribeOption{msglevel} % Sets the level of messages issued by \cs{zcheck} failed checks and takes % values \opt{warn}, \opt{info}, \opt{none}, \opt{obeydraft}, \opt{obeyfinal}. % The default value, \opt{warn}, issues messages both to the terminal and to % the log file, \opt{info} issues messages to the log file only, \opt{none} % suppresses all messages. \opt{obeydraft} corresponds to \opt{info} if % option \opt{draft} is passed to \cs{documentclass}, and to \opt{warn} % otherwise. \opt{obeyfinal} corresponds to \opt{warn} if option \opt{final} % is (explicitly) passed to \cs{documentclass} and \opt{info} otherwise. % \opt{ignore} is provided as convenience alias for \opt{msglevel=none} for % local use only. This option only affects the messages issued by the checks % in \cs{zcheck}, not other messages or warnings of the package. In % particular, it does not affect warnings issued for undefined labels, which % just use \cs{zref@refused} and thus are the same as standard \LaTeX{} ones % for this purpose. % % % \DescribeOption{onpage} % Allows to control the messaging style for ``within page checks'', and takes % values \opt{labelseq}, \opt{msg}, \opt{obeydraft}, \opt{obeyfinal}. The % default, \opt{labelseq}, uses the labels' shipout sequence, as retrieved % from the \file{.aux} file, to infer relative position within the page. % \opt{msg} also uses the same method for checking relative position, but % issues a (different) message \emph{even if the check passes}, to provide a % simple workflow for robust checking of ``false negatives'', considering the % label sequence is not fool proof (for details and workflow recommendations, % see Section~\ref{sec:within-page-checks}). \opt{msg} also issues its % messages at the same level defined in \opt{msglevel}. \opt{obeydraft} % corresponds to \opt{labelseq} if option \opt{draft} is passed to % \cs{documentclass} and to \opt{msg} otherwise. \opt{obeyfinal} corresponds % to \opt{msg} if option \opt{final} is (explicitly) passed to % \cs{documentclass}, and to \opt{labelseq} otherwise. % % % \DescribeOption{closerange} % Defines the width of the range of pages, relative to the reference, that are % considered ``close'' by the \opt{close} check. Takes a positive integer as % value, with default 5. % % % \DescribeOption{labelcmd} % Defines the command used to set the user labels in \cs{zctarget} and % \texttt{zcregion}. Takes a control sequence \emph{name} as value, and the % default sets labels with the minimal required properties, those of the % \texttt{zrefcheck} property list. This is a preamble only option. The % specified control sequence must receive one mandatory argument (the % \marg{label}) and must generate a \texttt{zref label} with at least the % properties in the \texttt{zrefcheck} property list. The intended use case % is that of the user creating a convenience macro which calls both \cs{label} % and \cs{zlabel}, as suggested in Section~\ref{sec:labels}, so that the same % labels are accessible either from the standard reference system or from % \pkg{zref}. For example: % % \begin{verbatim} % \NewDocumentCommand\mybothlabels{m}{\label{#1}\zlabel{#1}} % \zrefchecksetup{labelcmd=mybothlabels} % \end{verbatim} % % Note that the value of the underlying counter used for labels in % \cs{zctarget} and \texttt{zcregion} -- what you'd get with a plain \cs{ref} % here -- is not really meaningful. But you get to use % \cs{pageref}\marg{label}, or \pkg{hyperref}'s % \cs{hyperref}\oarg{label}\marg{text} on the labels used in \cs{zctarget} and % \texttt{zcregion} with this procedure. % % % \section{Labels} % \label{sec:labels} % % \pkg{zref-check} depends on \pkg{zref}, as the name entails, which means it % is able to work with \pkg{zref} labels, in general created by \cs{zlabel}, % but also with \cs{zctarget} and the \texttt{zcregion} environment provided % by this package. This has some advantages, particularly the data % flexibility of \pkg{zref}, and the absence of the ubiquitous ``load-order'' % and compatibility problems which are well known to afflict \LaTeX{} packages % of this area of functionality. On the other hand, the reliance on % \pkg{zref} labels may be seen as an inconvenience, since users of the % standard cross-reference infrastructure may need to add extra labels for % this. That's true. But \pkg{zref-check} is not meant to replace the % existing functionality of the kernel or of the traditional packages in this % area (to my knowledge, it only intersects directly with \pkg{varioref} and, % even so, it is quite different in scope). Indeed, it is easy to see the use % in tandem with standard references, for example: % % \begin{verbatim} % ... Figure~\ref{fig:figure-1}, \zcheck*[nextpage]{fig:figure-1}{on % the next page}. % \end{verbatim} % % Besides, \pkg{zref} does not share the label name-space with the standard % labels, so that you can call both \cs{label} and \cs{zlabel} with the same % label name (manually, or through a convenience macro), to ease the label set % administration. The example above presumes that was the case. % % All user commands of \pkg{zref-check} have their \marg{label} arguments % protected for \pkg{babel} active characters using \pkg{zref}'s % \cs{zref@wrapper@babel}, so that we should have equivalent support in that % regard, as \pkg{zref} itself does. However, \pkg{zref-check} sets labels % which either start with \texttt{zrefcheck@} or end with \texttt{@zrefcheck}, % for internal use. Label names with either of those are considered reserved % by the package. % % % \section{Limitations} % % There are three qualitatively different kinds of checks being used by % \cs{zcheck}, according to the source and reliability of the information they % mobilize: page number checks, within page checks, and sectioning checks. % % % \subsection{Page number checks} % % Page number checks -- \opt{thispage}, \opt{prevpage}, \opt{nextpage}, % \opt{pagesbefore}, \opt{pagesafter}, \opt{facing} -- use the % \texttt{abspage} property provided by the \pkg{zref-abspage} module. This % is a solid piece of information, on which we can rely upon. However, % despite that, page number checks may still become ill-defined, if the % \meta{text} argument in \cs{zcheck}, when typeset, crosses page boundaries, % starting in one page, and finishing in another. The same can happen with % the text in \cs{zctarget} and the \texttt{zcregion} environment. % % This is why the user commands of this package set a pair or labels around % \meta{text}. So, when checking \cs{zcheck} against a regular % \texttt{zlabel} both the start and the end of the \meta{text} are checked % against the label, and the check fails if either of them fails. When % checking \cs{zcheck} against a \cs{zctarget} or a \texttt{zcregion}, both % beginnings and ends are checked against each other two by two, and if any of % them fails, the check fails. In other words, if a page number checks % passes, we know that the entire \meta{text} arguments pass it. % % This is a corner case (albeit relevant) which must be taken care of, and it % is possible to do so robustly. Hence, we can expect reliable results in % these tests. % % % \subsection{Within page checks} % \label{sec:within-page-checks} % % When both label and reference fall on the same page things become much % trickier. This is basically the case of the checks \opt{above} and % \opt{below} (and, through them, \opt{before} and \opt{after}). There is no % equally reliable information (that I know of) as we have for the page number % checks for this, especially when floats come into play. Which, of course, % is the interesting case to handle. % % To infer relative position of label and reference on the same page, % \pkg{zref-check} uses the labels' shipout sequence, which is retrieved at % load-time from the order in which the labels occur in the \file{.aux} file. % Indeed, \pkg{zref} writes labels to the \file{.aux} file at shipout (and, % hence, in shipout order), and needs to do so, because a number of its % properties are only available at that point. % % However, even if this method will buy us a correct check for a regular float % on a regular page (which, to be fair, is a good result), it is not difficult % do conceive situations in which this sequence may not be meaningful, or even % correct, for the case. A number of cases which may do so are: two column % documents, text wrapping, scaling, overlays, etc. (I don't know if those % make the method fail, I just don't know if they don't). Therefore, the % \texttt{labelseq} should be taken as a \emph{proxy} and not fully reliable, % meaning that the user should be watchful of its results. % % For this reason, \pkg{zref-check} provides an easy way to do so, by allowing % specific control of the messaging style of the checks which do within page % comparisons though the option \opt{onpage}. The concern is not really with % false positives (getting a warning when it was not due), but with false % negatives (not getting a warning when it was due). Hence, setting % \opt{onpage} to \opt{msg} at a final typesetting stage (or just set it to % \opt{obeydraft} or \opt{obeyfinal} if that's part of your workflow) provides % a way to easily identify all cases of such checks (failing or passing), and % double-check them. In case the test is passing though, the message is % different from that of a failing check, to quickly convey why you are % getting the message. This option can also be set at the local level, if the % page in question is known to be problematic, or just atypical. % % % \subsection{Sectioning checks} % % The information used by sectioning checks is provided by means of dedicated % counters for chapters and sections, similarly as standard counters for them, % but which are stepped and reset regardless of whether these sectioning % commands are numbered or not (that is, starred or not). And this for two % reasons. First, we don't need the absolute counter value to be able to make % the kind of relative statement we want to do here. Second, this allows us % to have these checks work for numbered and unnumbered sectioning commands % without having to worry about how those are used within the document. % % The caveat is that the package does this by hooking into \cs{chapter} and % \cs{section}, which poses two restrictions for the proper working of these % checks. First, we are using the new hook system for this, as provided by % \pkg{ltcmdhooks}, which means a \LaTeX{} kernel later than 2021-06-01 is % required. Second, since we are hooking into \cs{chapter} and \cs{section}, % these checks presume these commands are being used by the document class for % this purpose (either directly, or internally as, for example, KOMA-Script's % \cs{addchap} and \cs{addsec} do). If that's not the case, additional setup % may be needed for these checks to work as expected. % % % % \section{Change history} % % A change log with relevant changes for each version, eventual upgrade % instructions, and upcoming changes, is maintained in the package's % repository, at % \url{https://github.com/gusbrs/zref-check/blob/main/CHANGELOG.md}. % % % % \end{documentation} % % % \begin{implementation} % % \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. % \begin{macrocode} \providecommand\IfFormatAtLeastTF{\@ifl@t@r\fmtversion} \IfFormatAtLeastTF{2021-06-01} {} {% \PackageError{zref-check}{LaTeX kernel too old} {% 'zref-check' requires a LaTeX kernel newer than 2021-06-01.% \MessageBreak Loading will abort!% }% \endinput }% % \end{macrocode} % % Identify the package. % \begin{macrocode} \ProvidesExplPackage {zref-check} {2021-09-16} {0.2.1} {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} % % \begin{variable}{\g_@@_abschap_int, \g_@@_abssec_int} % 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. About the proper place to make % the hooks for this purpose, see % \url{https://tex.stackexchange.com/q/605533/105447} (thanks Ulrike % Fischer). % \begin{macrocode} \int_new:N \g_@@_abschap_int \int_new:N \g_@@_abssec_int % \end{macrocode} % \end{variable} % % % 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 } { \int_gincr:N \g_@@_abschap_int \int_zero:N \g_@@_abssec_int } \zref@newprop { zc@abschap } [0] { \int_use:N \g_@@_abschap_int } \zref@addprop \ZREF@mainlist { zc@abschap } % \end{macrocode} % % \begin{macrocode} \AddToHook { cmd / section / before } { \int_gincr:N \g_@@_abssec_int } \zref@newprop { zc@abssec } [0] { \int_use:N \g_@@_abssec_int } \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. % \begin{macrocode} \zref@newlist { zrefcheck-check } \zref@addprops { zrefcheck-check } { page , % for messages abspage , zc@abschap , zc@abssec } \zref@newlist { zrefcheck-target } \zref@addprops { zrefcheck-target } { page , % so that \zpageref can refer to it abspage , zc@abschap , zc@abssec } \zref@newlist { zrefcheck-end } \zref@addprops { zrefcheck-end } { abspage , zc@abschap , zc@abssec } % \end{macrocode} % % % \section{Plumbing} % % \subsection{Messages} % % \begin{macro}{\@@_message:nnnn, \@@_message:nnnx} % \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 { nnnx } % \end{macrocode} % \end{macro} % % \begin{macrocode} \msg_new:nnn { zref-check } { check-failed } { Failed~check~'#1'~for~label~'#2'~on~page~#3~\msg_line_context:. } \msg_new:nnn { zref-check } { double-check } { Double-check~'#1'~for~label~'#2'~on~page~#3~\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~'\noexpand\zcheck'~instead. } \msg_new:nnn { zref-check } { missing-hyperref } { Missing~'hyperref'~package. \iow_newline: Setting~'hyperref=false'. } \msg_new:nnn { zref-check } { ignore-document-only } { Option~'ignore'~only~available~in~the~document. \iow_newline: Use~option~'msglevel'~instead. } \msg_new:nnn { zref-check } { option-preamble-only } { Option~'#1'~only~available~in~the~preamble~\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. } % \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 } { 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 } \zref@addprop { zrefcheck-target } { anchor } } } { \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 } { 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 } { 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 / obeydraft .code:n = { \ifdraft { \tl_set:Nn \l_@@_msglevel_tl { info } } { \tl_set:Nn \l_@@_msglevel_tl { warning } } } , msglevel / obeyfinal .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} is a convenience alias for \opt{msglevel=none}, but only for % use in the document body. % \begin{macrocode} ignore .code:n = { \msg_warning:nn { zref-check } { ignore-document-only } } , ignore .value_forbidden:n = true } % \end{macrocode} % \end{variable} % % \begin{macrocode} \AddToHook { begindocument } { \keys_define:nn { zref-check } { ignore .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 } { 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 / obeydraft .code:n = { \ifdraft { \bool_set_false:N \l_@@_msgonpage_bool } { \bool_set_true:N \l_@@_msgonpage_bool } } , onpage / obeyfinal .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 } { 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*{\opt{labelcmd} option} % % \begin{variable}{\l_@@_target_label_tl} % I'd love to receive the macro itself rather than it's name, but this would % bring unwarranted complications: % \url{https://tex.stackexchange.com/a/489570}. % \begin{macrocode} \tl_new:N \l_@@_target_label_tl \bool_new:N \l_@@_target_label_bool \keys_define:nn { zref-check } { labelcmd .code:n = { \tl_set:Nn \l_@@_target_label_tl {#1} \bool_set_true:N \l_@@_target_label_bool } , labelcmd .value_required:n = true , } % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_target_label:n} % Default definition of the function for user label setting in \cs{zctarget} % and \texttt{zcregion}. It may be redefined at \texttt{begindocument} % according to option \opt{labelcmd}. % \begin{macrocode} \cs_new_protected:Npn \@@_target_label:n #1 { \zref@labelbylist {#1} { zrefcheck-target } } % \end{macrocode} % \end{macro} % % \begin{macrocode} \AddToHook { begindocument } { \bool_if:NT \l_@@_target_label_bool { \tl_if_blank:VT \l_@@_target_label_tl { \tl_clear:N \l_@@_target_label_tl } \cs_if_exist:cTF { \l_@@_target_label_tl } { \cs_set_protected:Npx \@@_target_label:n #1 { \exp_not:o { \cs:w \l_@@_target_label_tl \cs_end: } {#1} } } { \exp_args:Nnno \msg_warning:nnn { zref-check } { labelcmd-undefined } { \l_@@_target_label_tl } } } \keys_define:nn { zref-check } { labelcmd .code:n = { \msg_warning:nnn { zref-check } { option-preamble-only } { labelcmd } } } } % \end{macrocode} % % % \subsubsection*{Package options} % % Process load-time package options % (\url{https://tex.stackexchange.com/a/15840}). % \begin{macrocode} \RequirePackage { l3keys2e } \ProcessKeysOptions { zref-check } % \end{macrocode} % % % \begin{macro}[int]{\zrefchecksetup} % Provide \cs{zrefchecksetup}. % \begin{macrocode} \NewDocumentCommand \zrefchecksetup { m } { \keys_set:nn { zref-check } {#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_set: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:Nxx \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. % % % % \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} { \tl_set:Nx #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. % \begin{macrocode} \keys_set:nn { zref-check / zcheck } {#3} \seq_set_from_clist:Nn \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:Nx \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:Nx \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_function:NN \l_@@_zcheck_labels_seq \zref@refused % \end{macrocode} % Run the checks. % \begin{macrocode} \@@_run_checks:nnx { \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 \@@_target_label:n {#1} #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 \@@_target_label:n {#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_new:N \c_@@_single_label_checks_seq \seq_set_from_clist:Nn \c_@@_single_label_checks_seq { thischap , prevchap , nextchap , chapsbefore , chapsafter , thissec , prevsec , nextsec , secsbefore , secsafter , } % \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}. % \begin{macrocode} \keys_define:nn { } { zref-check / zcheck .inherit:n = zref-check } % \end{macrocode} % % Then we add the checks to it. % \begin{macrocode} \clist_map_inline:nn { thispage , prevpage , nextpage , facing , pagegap , above , below , pagesbefore , ppbefore , pagesafter , ppafter , before , after , thischap , prevchap , nextchap , chapsbefore , chapsafter , thissec , prevsec , nextsec , secsbefore , secsafter , close , far , } { \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_map_inline:Nn #1 { \@@_do_check:nnn {####1} {##1} {#3} } } \group_end: } \cs_generate_variant:Nn \@@_run_checks:nnn { nnx } % \end{macrocode} % \end{macro} % % % \begin{variable} % { % \l_@@_passedcheck_bool , % \l_@@_onpage_bool , % \c_@@_onpage_checks_seq % } % \begin{macrocode} \bool_new:N \l_@@_passedcheck_bool \bool_new:N \l_@@_onpage_bool \seq_new:N \c_@@_onpage_checks_seq \seq_set_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: % \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. And there is no point in checking ``relative position'' of an % undefined label. Hence, in the absence of |#2|, we do nothing at all here. % \begin{macrocode} \zref@ifrefundefined {#2} {} { \tl_if_empty:nF {#1} { \bool_set_true:N \l_@@_passedcheck_bool \bool_set_false:N \l_@@_onpage_bool \cs_if_exist:cTF { @@_check_ #1 :nnF } { % ``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 } \@@_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 } } } \bool_if:NTF \l_@@_passedcheck_bool { \bool_if:nT { \l_@@_msgonpage_bool && \l_@@_onpage_bool } { \@@_message:nnnx { double-check } {#1} {#2} { \zref@extractdefault {#3} {page} {'unknown'} } } } { \@@_message:nnnx { check-failed } {#1} {#2} { \zref@extractdefault {#3} {page} {'unknown'} } } } { \msg_warning:nnn { zref-check } { check-missing } {#1} } } } \group_end: } % \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} % \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: } % \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} % % % % \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:Nx \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:nnx { \l_@@_zcheck_checks_seq } {#1} { \l_@@_checkbeg_tl } } % \end{macrocode} % \end{macro} % % % \begin{macrocode} % % \end{macrocode} % % \PrintIndex % % \end{implementation} %