% \iffalse meta-comment
%
%% File: tagpdf-checks.dtx
%
% Copyright (C) 2019-2026 Ulrike Fischer
%
% 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
%
% This file is part of the "tagpdf bundle" (The Work in LPPL)
% and all files in that bundle must be distributed together.
%
% -----------------------------------------------------------------------
%
% The development version of the bundle can be found at
%
%    https://github.com/latex3/tagpdf
%
% for those people who are interested.
%
%
%<*driver>
\DocumentMetadata{tagging=on}
\documentclass{l3in2edoc}
\usepackage{array,booktabs,caption,longtable}
\hypersetup{pdfauthor=Ulrike Fischer,
 pdftitle=tagpdf-checks module (tagpdf)}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
% \title{^^A
%   The \pkg{tagpdf-checks} module\\ Messages and check code   ^^A
% }
%
% \author{^^A
%  Ulrike Fischer\thanks
%    {^^A
%      E-mail:
%        \href{mailto:fischer@troubleshooting-tex.de}
%          {fischer@troubleshooting-tex.de}^^A
%    }^^A
% }
%
% \date{Version 1.0d, released 2026-07-25}
% \maketitle
% \begin{documentation}
% \section{Commands}
% \begin{function}[pTF,EXP]{\tag_if_active:}
% This command tests if tagging is active. It only gives true if all tagging has
% been activated, \emph{and} if tagging hasn't been stopped locally.
% \end{function}
% \begin{function}[EXP]{\tag_get:n}
%  \begin{syntax}
%  \cs{tag_get:n} \Arg{keyword}
%  \end{syntax}
% This is a generic command to retrieve data for the current structure or
% mc-chunk. Currently
% the only sensible values for the argument \meta{keyword}
% are |mc_tag|, |struct_tag|, |struct_id| and |struct_num|.
% \end{function}
%
% \begin{function}[pTF,EXP]{\tag_if_box_tagged:N}
%  \begin{syntax}
%  \cs{tag_if_box_tagged:NTF} \meta{box} \Arg{true code} \Arg{false code}
%  \end{syntax}
% This tests if a box contains tagging commands.
% It relies currently on that the code, that saved the box, correctly sets
% the command \verb+\l_tag_box_\int_use:N #1_tl+ to a positive value.
% The LaTeX commands will do that automatically
% at some time but it is in the responsibility of the user to
% ensure that when using low-level code.
% If the internal command doesn't exist the box is assumed to be untagged.
% \end{function}
% \section{Description of log messages}
% \subsection{\cs{ShowTagging} command}
%  \begin{tabular}{lll}
%   Argument                          & type           & note\\
%  \cs{ShowTagging}{mc-data = num}    & log+term       & lua-only\\
%  \cs{ShowTagging}{mc-current} & log+term       & \\
%  \cs{ShowTagging}{struck-stack= [log\verb+|+show]}  & log or term+stop \\
%  \cs{ShowTagging}{debug/structures = num} & log+termn & debug mode only
%  \end{tabular}
%
% \begin{function}[pTF]{\tag_if_in:n}
%  \begin{syntax}
%  \cs{tag_if_in:n} \Arg{standard structure type}
%  \end{syntax}
% This command tests if we are currently \enquote{in} a specific structure by checking
% if the argument is on the stack. The argument must be a standard structure type as no role mapping is applied.
%
% The main use case is to check if there is an open P structure that should be closed.
% \end{function}

% \subsection{Messages in checks and commands}
% \scriptsize
% \begin{tabular}{llll}
%   command                & message  & action   & remark\\
%   |\@@_check_structure_has_tag:n|
% & |struct-missing-tag|
% & error
% \\
%   |\@@_check_structure_tag:N|
% & |role-unknown-tag|
% & warning
% \\
%   |\@@_check_info_closing_struct:n|
% & |struct-show-closing|
% & info
% & log-level>0
% \\
%   |\@@_check_no_open_struct:|
% & struct-faulty-nesting
% & error
% & TODO: error only with 1?
% \\
%   |\@@_check_struct_used:n|
% & |struct-used-twice|
% & warning
% \\
%   |\@@_check_add_tag_role:nn|
% & |role-missing|, |role-tag|, |role-unknown|
% & warning, info (>0), warning
% \\
%   |\@@_check_mc_if_nested:|,
% & |mc-nested|
% & warning
% \\
%   |\@@_check_mc_if_open:|
% & |mc-not-open|
% & warning
% & only generic (?)
% \\
%   |\@@_check_mc_pushed_popped:nn|
% & |mc-pushed|, |mc-popped|
% & info (2), info+|seq_log| (>2)
% \\
%   |\@@_check_mc_tag:N|
% & |mc-tag-missing|, |role-unknown-tag|
% & error (missing), warning (unknown).
% \\
%   |\@@_check_mc_used:n|
% & |mc-used-twice|
% & warning
% & TODO: review the sense of this test!
% \\
%   |\@@_check_show_MCID_by_page:|
% &
% &
% & currently unused
% \\
%   |\tag_mc_use:n|
% & |mc-label-unknown|, |mc-used-twice|
% & warning
% & in mc-shared
% \\
%   |\role_add_tag:nn|
% & |new-tag|
% &  info (>0)
% & in roles
% \\
% & |sys-no-interwordspace|
% &  warning
% & space module, only xetex/dvi
% \\
%   |\@@_struct_write_obj:n|
% & |struct-no-objnum|
% & error
% & in struct module
% \\
%   |\@@_struct_write_obj:n|
% & |struct-orphan|
% & warning
% & in struct module
% \\
%   |\tag_struct_begin:n|
% & |struct-faulty-nesting|
% & error
% \\
%   |\@@_struct_insert_annot:nn|
% & |struct-faulty-nesting|
% & error
% \\
%   |tag_struct_use:n|
% & |struct-label-unknown|
% &  warning
% \\
%   |attribute-class|, |attribute|
% & |attr-unknown|
% & error
% \\
%  |\@@_tree_fill_parenttree:|
% & |tree-mcid-index-wrong|
% & warning
% TODO: should trigger a standard  rerun message.
% \\
%  in |enddocument/info|-hook
% & |para-hook-count-wrong|
% & error (warning?)
% \end{tabular}
%
% \normalsize
% \subsection{Messages from the ptagging code}
% A few messages are issued in generic
% mode from the code which reinserts missing TMB/TME.
% This is currently done if log-level is larger than zero.
% TODO: reconsider log-level and messages when this code
% settles down.
%
%
% \subsection{Warning messages from the lua-code}
% The messages are triggered if the log-level is at least equal to the number.
%
% \begin{tabular}[t]{>{\ttfamily}lll}
% message & log-level & remark \\\hline
% WARN TAG-NOT-TAGGED:       &1                   \\
% WARN TAG-OPEN-MC:          &1                   \\
% WARN SHIPOUT-MC-OPEN:      &1                    \\
% WARN SHIPOUT-UPS:          &0 & shouldn't happen \\
% WARN TEX-MC-INSERT-MISSING:&0 & shouldn't happen \\
% WARN TEX-MC-INSERT-NO-KIDS:&2 & e.g. from empty hbox
% \end{tabular}
%
% \subsection{Info messages from the lua-code}
% The messages are triggered if the log-level is at least equal to the number.
% |TAG| messages are from the traversing function, |TEX| from code used in the tagpdf-mc module.
% |PARENTREE| is the code building the parenttree.
%
% \begin{longtable}{>{\ttfamily}lll}
% message & log-level & remark \\\hline\endhead
% INFO SHIPOUT-INSERT-LAST-EMC & 3 & finish of shipout code \\
% INFO SPACE-FUNCTION-FONT     & 3 & interwordspace code \\
% INFO TAG-ABSPAGE & 3  \\
% INFO TAG-ARGS & 4     \\
% INFO TAG-ENDHEAD & 4  \\
% INFO TAG-ENDHEAD & 4  \\
% INFO TAG-HEAD & 3     \\
% INFO TAG-INSERT-ARTIFACT & 3  \\
% INFO TAG-INSERT-BDC & 3  \\
% INFO TAG-INSERT-EMC & 3  \\
% INFO TAG-INSERT-TAG & 3  \\
% INFO TAG-KERN-SUBTYPE & 4\\
% INFO TAG-MATH-SUBTYPE & 4 \\
% INFO TAG-MC-COMPARE & 4   \\
% INFO TAG-MC-INTO-PAGE & 3 \\
% INFO TAG-NEW-MC-NODE & 4  \\
% INFO TAG-NODE & 3  \\
% INFO TAG-NO-HEAD & 3 \\
% INFO TAG-NOT-TAGGED & 2 & replaced by artifact\\
% INFO TAG-QUITTING-BOX & 4 \\
% INFO TAG-STORE-MC-KID & 4 \\
% INFO TAG-TRAVERSING-BOX 3 \\
% INFO TAG-USE-ACTUALTEXT & 3 \\
% INFO TAG-USE-ALT & 3 \\
% INFO TAG-USE-RAW & 3 \\
% INFO TEX-MC-INSERT-KID & 3 \\
% INFO TEX-MC-INSERT-KID-TEST & 4 \\
% INFO TEX-MC-INTO-STRUCT & 3 \\
% INFO TEX-STORE-MC-DATA & 3  \\
% INFO TEX-STORE-MC-KID &  3  \\
% INFO PARENTTREE-CHUNKS &  3 \\
% INFO PARENTTREE-NO-DATA & 3\\
% INFO PARENTTREE-NUM & 3 \\
% INFO PARENTTREE-NUMENTRY & 3 \\
% INFO PARENTTREE-STRUCT-OBJREF & 4
% \end{longtable}
%
% \subsection{Debug mode messages and code}
% If the package tagpdf-debug is loaded a number of commands are redefined and
% enhanced with additional commands which can be used to output debug messages or
% collect statistics. The commands are present but do nothing if the log-level is zero.
% \begin{tabular}{llll}
%   command             & name     & action   & remark\\\hline
%   |\tag_mc_begin:n|   & mc-begin-insert & msg   \\
%                       & mc-begin-ignore & msg     & if inactive \\
% \end{tabular}
%
% \subsection{Messages}
% \begin{function}{
%  mc-nested,
%  mc-tag-missing,
%  mc-label-unknown,
%  mc-used-twice,
%  mc-used-twice,
%  mc-not-open,
%  mc-pushed,
%  mc-popped,
%  mc-current}
%  Various messages related to mc-chunks. TODO document their meaning.
% \end{function}
% \begin{function}{struct-unknown,struct-no-objnum,struct-orphan,struct-faulty-nesting,
%  struct-missing-tag,struct-used-twice,struct-label-unknown,struct-show-closing}
%  Various messages related to structure. Check the definition in the code for their
%  meaning and the arguments they take.
% \end{function}
%
% \begin{function}{tree-struct-still-open}
% Message issued at the end of the compilation
% if there are (beside Root) other open structures on the stack.
% \end{function}
%
% \begin{function}{tree-statistic}
% Message issued at the end of the compilation
% showing the number of objects to write
% \end{function}
%
% \begin{function}{show-struct,show-kids}
% These two messages are used in debug mode
% to show the current structures in the log and terminal.
% \end{function}
%

% \begin{function}{attr-unknown}
% Message if an attribute i sunknown.
% \end{function}
%
% \begin{function}{role-missing,role-unknown,role-unknown-tag,role-unknown-NS,role-tag,new-tag,
% role-parent-child-result,role-remapping}
% Messages related to role mapping.
% \end{function}
%
% \begin{function}{tree-mcid-index-wrong}
% Used in the tree code, typically indicates the document must be rerun.
% \end{function}
%
% \begin{function}{sys-no-interwordspace}
% Message if an engine doesn't support inter word spaces
% \end{function}
%
% \begin{function}{para-hook-count-wrong}
% Message if the number of begin paragraph and end paragraph differ. This normally means faulty structure.
% \end{function}
% \end{documentation}
% \begin{implementation}
%    \begin{macrocode}
%<@@=tag>
%<*header>
\ProvidesExplPackage {tagpdf-checks-code} {2026-07-25} {1.0d}
 {part of tagpdf - code related to checks, conditionals, debugging and messages}
%</header>
%    \end{macrocode}
% \section{Messages}
%
% \subsection{Messages related to mc-chunks}
% \begin{macro}{mc-nested}
% This message is issue is a mc is opened before the previous has been closed.
% This is not relevant for luamode, as the attributes don't care about this.
% It is used in the |\@@_check_mc_if_nested:| test.
%    \begin{macrocode}
%<*package>
\msg_new:nnn { tag } {mc-nested} { nested~marked~content~found~-~mcid~#1 }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{mc-tag-missing}
% If the tag is missing
%    \begin{macrocode}
\msg_new:nnn { tag } {mc-tag-missing} { MC-tag~missing;~#1~used~instead~-~mcid~#2 }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{mc-label-unknown}
% If the label of a mc that is used in another place is not known (yet)
% or has been undefined as the mc was already used.
%    \begin{macrocode}
\msg_new:nnn { tag } {mc-label-unknown}
  { label~#1~unknown~or~has~already~been~used.\\
    Either~rerun~or~remove~one~of~the~uses. }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{mc-used-twice}
% An mc-chunk can be inserted only in one structure. This indicates wrong coding
% and so should at least give a warning.
%    \begin{macrocode}
\msg_new:nnn { tag } {mc-used-twice} { mc~#1~has~already~been~used }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{mc-not-open}
% This is issued if a |\tag_mc_end:| is issued wrongly, wrong coding.
%    \begin{macrocode}
\msg_new:nnn { tag } {mc-not-open} { there~is~no~mc~to~end~at~#1 }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{mc-pushed,mc-popped}
% Informational messages about mc-pushing.
%    \begin{macrocode}
\msg_new:nnn { tag } {mc-pushed} { #1~has~been~pushed~to~the~mc~stack}
\msg_new:nnn { tag } {mc-popped} { #1~has~been~removed~from~the~mc~stack }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{mc-current}
% Informational messages about current mc state.
%    \begin{macrocode}
\msg_new:nnn { tag } {mc-current}
  { current~MC:~
    \bool_if:NTF\g_@@_in_mc_bool
      {abscnt=\@@_get_mc_abs_cnt:,~tag=\g_@@_mc_key_tag_tl}
      {no~MC~open,~current~abscnt=\@@_get_mc_abs_cnt:"}
  }
%    \end{macrocode}
% \end{macro}
% \subsection{Messages related to structures}
% \begin{macro}{struct-unknown}
% if for example a parent key value points to structure that doesn't exist (yet)
%    \begin{macrocode}
\msg_new:nnn { tag } {struct-unknown}
   { structure~with~number~#1~doesn't~exist\\ #2 }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{struct-no-objnum}
% Should not happen \ldots
%    \begin{macrocode}
\msg_new:nnn { tag } {struct-no-objnum} { objnum~missing~for~structure~#1 }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{struct-orphan}
% This indicates that there is a structure which has kids but no parent.
% This can happen if a structure is stashed but then not used.
%    \begin{macrocode}
\msg_new:nnn { tag } {struct-orphan}
  {
    Structure~#1~has~#2~kids~but~no~parent.\\
    It~is~turned~into~an~artifact.\\
    Did~you~stash~a~structure~and~then~not~use~it?
  }

%    \end{macrocode}
% \end{macro}
% \begin{macro}{struct-faulty-nesting}
% This indicates that there is somewhere one |\tag_struct_end:| too much.
% This should be normally an error.
%    \begin{macrocode}
\msg_new:nnn { tag }
  {struct-faulty-nesting}
  { there~is~no~open~structure~on~the~stack }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{struct-missing-tag}
% A structure must have a tag.
%    \begin{macrocode}
\msg_new:nnn { tag } {struct-missing-tag} { a~structure~must~have~a~tag! }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{struct-used-twice}
%    \begin{macrocode}
\msg_new:nnn { tag } {struct-used-twice}
  { structure~with~label~#1~has~already~been~used}
%    \end{macrocode}
% \end{macro}
% \begin{macro}{struct-label-unknown}
% label is unknown, typically needs a rerun.
%    \begin{macrocode}
\msg_new:nnn { tag } {struct-label-unknown}
  { structure~with~label~#1~is~unknown~rerun}
%    \end{macrocode}
% \end{macro}
% \begin{macro}{struct-show-closing}
% Informational message shown if log-mode is high enough
%    \begin{macrocode}
\msg_new:nnn { tag } {struct-show-closing}
  { closing~structure~#1~tagged~\use:e{\prop_item:cn{g_@@_struct_#1_prop}{S}} }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{struct-Ref-unknown}
% This message is issued at the end, when the Ref keys are updated.
% TODO: in debug mode it should report more info about the structure.
%    \begin{macrocode}
\msg_new:nnn { tag } {struct-Ref-unknown}
 {
     #1~has~no~related~structure.\\
    /Ref~not~updated.
 }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{tree-struct-still-open}
% Message issued at the end if there are beside Root other
% open structures on the stack.
%    \begin{macrocode}
\msg_new:nnn { tag } {tree-struct-still-open}
  {
    There~are~still~open~structures~on~the~stack!\\
    The~stack~contains~\seq_use:Nn\g_@@_struct_tag_stack_seq{,}.\\
    The~structures~are~automatically~closed,\\
    but~their~nesting~can~be~wrong.
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{tree-statistic}
% Message issued at the end showing the estimated number of
% structures and MC-childs
%    \begin{macrocode}
\msg_new:nnn { tag } {tree-statistic}
  {
    Finalizing~the~tagging~structure:\\
    Writing~out~\c_tilde_str
    \int_use:N\c@g_@@_struct_abs_int\c_space_tl~structure~objects\\
    with~\c_tilde_str
    \int_use:N\c@g_@@_MCID_abs_int\c_space_tl'MC'~leaf~nodes.\\
    Be~patient~if~there~are~lots~of~objects!
  }
%</package>
%    \end{macrocode}
% \end{macro}
% The following messages are only needed in debug mode.
% \begin{macro}{show-struct,show-kids}
% This two messages are used to show the current structures in the log and terminal.
%    \begin{macrocode}
%<*debug>
\msg_new:nnn { tag/debug } { show-struct }
  {
    =========================\\
    The~structure~#1~
    \tl_if_empty:nTF {#2}
      { is~empty \\>~ . }
      { contains: #2  }
    \\
  }
\msg_new:nnn { tag/debug } { show-kids }
  {
    The~structure~has~the~following~kids:
    \tl_if_empty:nTF {#2}
      { \\>~ NONE }
      { #2  }
    \\
    =========================
  }
%</debug>
%    \end{macrocode}
% \end{macro}
% \subsection{Attributes}
% Not much yet, as attributes aren't used so much.
% \begin{macro}{attr-unknown}
%    \begin{macrocode}
%<*package>
\msg_new:nnn { tag } {attr-unknown}  { attribute~#1~is~unknown}
%    \end{macrocode}
% \end{macro}
%
% \subsection{Roles}
% \begin{macro}{role-missing,role-unknown,role-unknown-tag,role-unknown-NS}
% Warning message if either the tag or the role is missing
%    \begin{macrocode}
\msg_new:nnn { tag } {role-missing}     { tag~#1~has~no~role~assigned  }
\msg_new:nnn { tag } {role-unknown}     { role~#1~is~not~known  }
\msg_new:nnn { tag } {role-unknown-tag} { tag~#1~is~not~known  }
\msg_new:nnn { tag } {role-unknown-NS}  { \tl_if_empty:nTF{#1}{Empty~NS}{NS~#1~is~not~known} }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{role-parent-child-check}
% This is an info message that inform which elements are checked, typically
% used to show the original tags, not the rolemapped one.
%    \begin{macrocode}
\msg_new:nnn { tag } {role-parent-child-check}
  { Checking~Parent-Child~'#1'~-->~'#2' }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{role-parent-child-result}
%  This is info and warning message about the containment rules between child and
%  parent tags.
%    \begin{macrocode}
\msg_new:nnn { tag } {role-parent-child-result}
  { Parent-Child~'#1'~-->~'#2'.\\Relation~is~#3~\msg_line_context:}
%    \end{macrocode}
% \end{macro}
% \begin{macro}{role-struct-parent-child-forbidden}
%  The most important message is that the relation
%  is not allowed between two structures.
%  Argument \#1 is the parent structure number, \#2 is the child structure number,
%  \#3 NS:tag info of the parent (TODO perhaps rolemapped),
%  \#4 NS:tag of the child. (TODO )
%    \begin{macrocode}
\msg_new:nnn { tag } {role-struct-parent-child-forbidden}
  {
    Parent-Child~'#3'~-->~'#4'.\\
    Relation~is~not~allowed! ~\msg_line_context:\\
    struct~#1,~
    \exp_last_unbraced:Ne\use_i:nn  { \prop_item:cn{ g_@@_struct_#1_prop}{tag} }
    \c_space_tl-->\c_space_tl
    struct~#2,~
    \exp_last_unbraced:Ne\use_i:nn  { \prop_item:cn{ g_@@_struct_#2_prop}{tag} }
  }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{role-MC-child-forbidden}
%  In case that MC is forbidden we use
%  a special message.
%  Argument \#1 is the parent structure number. \#2 NS:tag of the parent,
%    \begin{macrocode}
\msg_new:nnn { tag } {role-MC-child-forbidden}
  {
    Parent-Child~'#2'~-->~'MC~(real~content)'.\\
    Relation~is~not~allowed! ~\msg_line_context:\\
    struct~#1,~
    \exp_last_unbraced:Ne\use_i:nn { \prop_item:cn{ g_@@_struct_#1_prop}{tag} }
  }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{role-parent-child-forbidden}
%  The most important message is that the relation is not allowed.
%  Argument \#1 is the parent structure number. \#2 NS:tag of the parent,
%  \#3 NS:tag of the child.
%    \begin{macrocode}
\msg_new:nnn { tag } {role-parent-child-forbidden}
  {
    Parent-Child~'#2'~-->~'#3'.\\
    Relation~is~not~allowed! ~\msg_line_context:\\
    struct~#1,~\prop_item:cn{ g_@@_struct_#1_prop}{S}
    \c_space_tl
    \str_if_eq:nnF{#3}{MC~(realcontent)}
      {-->~struct~\int_eval:n {\c@g_@@_struct_abs_int}}
  }
%    \end{macrocode}
% \end{macro}

% \begin{macro}{\@@_check_forbidden_parent_child:nnnn}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_forbidden_parent_child:nnnn #1#2#3#4
% #1 check number, #2 number of parent struct
% #3 parent info,  #4 child info
 {
   \int_compare:nNnT {#1} < {0}
     {
       \msg_warning:nneee
         { tag }
         {role-parent-child-forbidden}
         { #2}
         { #3 }
         { #4 }
     }
 }
\cs_generate_variant:Nn \@@_check_forbidden_parent_child:nnnn {nnee}

% new with structure numbers:
\cs_new_protected:Npn \@@_check_struct_forbidden_parent_child:nnn #1#2#3
% #1 check number,
% #2 number of parent struct
% #3 number of child struct
 {
   \int_compare:nNnT {#1} < {0}
     {
       \prop_get:cnN {g_@@_struct_#2_prop}{parentrole}\l_@@_get_parent_tmpc_tl
       \prop_get:cnN {g_@@_struct_#3_prop}{rolemap}\l_@@_get_child_tmpc_tl
       \msg_warning:nneeee
         { tag }
         {role-struct-parent-child-forbidden}
         { #2 }
         { #3 }
         {
           \exp_last_unbraced:No \use_ii:nn  { \l_@@_get_parent_tmpc_tl }
           :
           \exp_last_unbraced:No \use_i:nn  {\l_@@_get_parent_tmpc_tl }
         }
         {
           \exp_last_unbraced:No \use_ii:nn { \l_@@_get_child_tmpc_tl }
           :
           \exp_last_unbraced:No \use_i:nn { \l_@@_get_child_tmpc_tl }
         }
     }
 }
\cs_generate_variant:Nn\@@_check_struct_forbidden_parent_child:nnn{onn}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{role-parent-child-unresolved}
%  If a structure is stashed and then used later and its root is one
%  of Part, Div or NonStruct, then we can not check the parent-child
%  rules. This would require to know all children. In this case we only
%  warn.
%  resolved a
%  Argument \#1 is the parent structure number. \#2 NS:tag of the parent,
%  \#3 NS:tag of the child.
%    \begin{macrocode}
\msg_new:nnn { tag } {role-parent-child-unresolved}
  {
    Structure~\int_eval:n {\c@g_@@_struct_abs_int}~was~moved~into~structure~#1.\\
    Parent-Child~'#2'~-->~'#3'~can~not~checked.
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_check_unresolved_parent_child:nnnn}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_unresolved_parent_child:nnnn #1#2#3#4
% #1 check number, #2 number of parent struct
% #3 parent info, #4 child info
 {
   \int_compare:nNnT { #1 } = {\c_@@_role_rule_checkparent_tl}
     {
       \msg_warning:nneee
         { tag }
         {role-parent-child-unresolved}
         { #2 }
         { #3 }
         { #4 }
     }
 }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{tag/check/parent-child,tag/check/parent-child-end,}
% Sockets used around the parent-child checks so that we can disable them.
%    \begin{macrocode}
\socket_new:nn{tag/check/parent-child}{1}
\socket_new:nn{tag/check/parent-child-end}{0}
\socket_new_plug:nnn {tag/check/parent-child-end}{check}
  {
   \sys_if_engine_luatex:T
     {
       \lua_now:e
        {
          ltx.@@.func.check_parent_child_rules ( 2 )
        }
     }
  }
%    \end{macrocode}
%    And a key to disable the check
%    \begin{macrocode}
\keys_define:nn { @@ / setup}
  {
    debug / parent-child-check .choice:,
    debug / parent-child-check / on .code:n =
     {
       \socket_assign_plug:nn {tag/check/parent-child}{identity}
     },
    debug / parent-child-check / off .code:n=
     {
       \socket_assign_plug:nn {tag/check/parent-child}{noop}
       \socket_assign_plug:nn {tag/check/parent-child-end}{noop}
     },
    debug / parent-child-check / atend .code:n=
     {
       \socket_assign_plug:nn {tag/check/parent-child}{noop}
       \socket_assign_plug:nn {tag/check/parent-child-end}{check}
     }
  }
%    \end{macrocode}
% \end{macro}
%
%
% \begin{macro}{role-remapping}
%  This is info and warning message about role-remapping
%    \begin{macrocode}
\msg_new:nnn { tag } {role-remapping}
  { remapping~tag~to~#1 }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{role-tag,new-tag}
% Info messages.
%    \begin{macrocode}
\msg_new:nnn { tag } {role-tag}         { mapping~tag~#1~to~role~#2  }
\msg_new:nnn { tag } {new-tag}          { adding~new~tag~#1 }
\msg_new:nnn { tag } {read-namespace}   { reading~namespace~definitions~tagpdf-ns-#1.def }
\msg_new:nnn { tag } {namespace-missing}{ namespace~definitions~tagpdf-ns-#1.def~not~found }
\msg_new:nnn { tag } {namespace-unknown}{ namespace~#1~is~not~declared }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Miscellaneous}
% \begin{macro}{wrong-pdfversion}
% Used a begin document if the pdfversion has been changed after the reading.
%    \begin{macrocode}
\msg_new:nnn { tag } {wrong-pdfversion}
  {
    The~PDF~version~has~changed~after~the~loading~of~tagpdf~from~#1~to~#2.\\
    The~structure~will~be~faulty.\\
    Trying~to~revert~to~#1.
  }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{tree-mcid-index-wrong}
% Used in the tree code, typically indicates the document must be rerun.
%    \begin{macrocode}
\msg_new:nnn { tag } {tree-mcid-index-wrong}
  {something~is~wrong~with~the~mcid--rerun}
%    \end{macrocode}
% \end{macro}
% \begin{macro}{sys-no-interwordspace}
% Currently only pdflatex and lualatex have some support for real spaces.
%    \begin{macrocode}
\msg_new:nnn { tag } {sys-no-interwordspace}
  {engine/output~mode~#1~doesn't~support~interword~spaces}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_check_typeout_v:n}
% A simple logging function. By default is gobbles its argument, but
% the log-keys sets it to typeout.
%    \begin{macrocode}
\cs_set_eq:NN \@@_check_typeout_v:n \use_none:n
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{para-hook-count-wrong}
% At the end of the document we check if the count of para-begin and para-end is
% identical. If not we issue a warning: this is normally a coding error and
% and breaks the structure.
%    \begin{macrocode}
\msg_new:nnnn { tag } {para-hook-count-wrong}
  {The~number~of~automatic~begin~(#1)~and~end~(#2)~#3~para~hooks~differ!}
  {This~is~quite~probably~a~coding~error~and~the~structure~will~be~wrong!}
%</package>
%    \end{macrocode}
% \end{macro}
% \section{Retrieving data}
% \begin{macro}[EXP]{\tag_get:n}
% This retrieves some data.
% This is a generic command to retrieve data. Currently
% the only sensible values for the argument are |mc_tag|, |struct_tag|
% and |struct_num|.
%    \begin{macrocode}
%<base>\cs_new:Npn \tag_get:n #1   { \use:c {@@_get_data_#1: } }
%    \end{macrocode}
% \end{macro}
%
% \section{PDF version check}
%    \begin{macrocode}
%<*package>
\tl_const:Ne\c_@@_check_pdfversion_tl {\pdf_version:}
\AddToHook{begindocument/before}
  {
    \tl_if_eq:eeF{\c_@@_check_pdfversion_tl}{\pdf_version:}
      {
        \msg_error:nnee {tag}{wrong-pdfversion}{\c_@@_check_pdfversion_tl}{\pdf_version:}
        \pdf_version_gset:e {\c_@@_check_pdfversion_tl}
        \pdf_object_unnamed_write:nn{dict}{}
      }
  }
%</package>
%    \end{macrocode}
% \section{User conditionals}
% \begin{macro}[pTF]{\tag_if_active:}
% This tests if tagging is active. This allows packages
% to add conditional code.
% The test is true if all booleans, the global and the two local one are true.
% \changes{v0.99j}{2024-11-22}{test if it exists already in the kernel}
%    \begin{macrocode}
%<*base>
\cs_if_exist:NF\tag_if_active:T
 {
   \prg_new_conditional:Npnn \tag_if_active: { p , T , TF, F }
     { \prg_return_false: }
 }
%</base>
%<*package>
\prg_set_conditional:Npnn \tag_if_active: { p , T , TF, F }
  {
     \bool_lazy_all:nTF
       {
         {\g_@@_active_struct_bool}
         {\g_@@_active_mc_bool}
         {\g_@@_active_tree_bool}
         {\l_@@_active_struct_bool}
         {\l_@@_active_mc_bool}
       }
       {
         \prg_return_true:
       }
       {
         \prg_return_false:
       }
  }
%</package>
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[pTF]{\tag_if_box_tagged:N}
% This tests if a box contains tagging commands.
% It relies on that the code that saved the box correctly set
% \verb+\l_tag_box_<box number>_tl+ to a positive value.
% The LaTeX commands will do that automatically
% at some time but it is in the responsibility of the user to
% ensure that when using low-level code.
% If the internal command doesn't exist the box is assumed to be untagged.
%    \begin{macrocode}
%<*base>
\prg_new_conditional:Npnn \tag_if_box_tagged:N #1 {p,T,F,TF}
    {
      \tl_if_exist:cTF {l_tag_box_\int_use:N #1_tl}
       {
         \int_compare:nNnTF {0\tl_use:c{l_tag_box_\int_use:N #1_tl}}>{0}
          { \prg_return_true:  }
          { \prg_return_false: }
       }
       {
         \prg_return_false:
         % warning??
       }
    }
%</base>
%    \end{macrocode}
% \end{macro}
%
%
% \begin{macro}{\tag_if_in:n}
% This tests if we are in a specific structure, so if the structure is part of the
% current stack. The argument must be a standard structure
% type as no role mapping is applied. The main use case is to test if we are in a
% paragraph P.
%    \begin{macrocode}
%<*package>
\prg_new_protected_conditional:Npnn\tag_if_in:n #1 {T,F,TF}
 {
   \seq_if_in:NnTF \g_@@_struct_roletag_stack_seq {#1}
    {\prg_return_true:}
    {\prg_return_false:}
 }
%</package>
%    \end{macrocode}
% \end{macro}
%
% \section{Internal checks}
% These are checks used in various places in the code.
%
% \subsection{checks for active tagging}
% \begin{macro}[TF]{\@@_check_if_active_mc:,\@@_check_if_active_struct:}
% This checks if mc are active.
%    \begin{macrocode}
%<*package>
\prg_new_conditional:Npnn \@@_check_if_active_mc: {T,F,TF}
  {
    \bool_lazy_and:nnTF { \g_@@_active_mc_bool } { \l_@@_active_mc_bool }
      {
         \prg_return_true:
      }
      {
         \prg_return_false:
      }
  }
\prg_new_conditional:Npnn \@@_check_if_active_struct: {T,F,TF}
  {
    \bool_lazy_and:nnTF { \g_@@_active_struct_bool } { \l_@@_active_struct_bool }
      {
         \prg_return_true:
      }
      {
         \prg_return_false:
      }
  }
%    \end{macrocode}
% \end{macro}
% \subsection{Checks related to structures}
% \begin{macro}{\@@_check_structure_has_tag:n}
% Structures must have a tag, so we check if the S entry is in the property.
% It is an error if this is missing. The argument is a number.
% The tests for existence and type is split in structures,
% as the tags are stored differently to the mc case.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_structure_has_tag:n #1 %#1 struct num
  {
    \prop_get:cnNF
      { g_@@_struct_#1_prop }
      {S}
      \l_@@_tmp_unused_tl
      {
        \msg_error:nn { tag } {struct-missing-tag}
      }
  }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{\@@_check_structure_tag:N}
% This checks if the name of the tag is known,
% either because it is a standard type or has been rolemapped.
% This always used with commands, so the argument is N.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_structure_tag:N #1
  {
    \prop_get:NoNF \g_@@_role_tags_NS_prop {#1}\l_@@_tmp_unused_tl
      {
        \msg_warning:nne { tag } {role-unknown-tag} {#1}
      }
  }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{\@@_check_info_closing_struct:n}
% This info message is issued at a closing structure, the use should be guarded by
% log-level.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_info_closing_struct:n #1 %#1 struct num
  {
    \int_compare:nNnT {\l_@@_loglevel_int} > { 0 }
      {
        \msg_info:nnn { tag } {struct-show-closing} {#1}
      }
  }

\cs_generate_variant:Nn \@@_check_info_closing_struct:n {o,e}
%    \end{macrocode}
% \end{macro}
% \begin{macro}{\@@_check_no_open_struct:}
% This checks if there is an open structure. It should be used when trying to
% close a structure. It errors if false.%
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_no_open_struct:
  {
    \msg_error:nn { tag } {struct-faulty-nesting}
  }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{\@@_check_struct_used:n}
% This checks if a stashed structure has already been used.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_struct_used:n #1 %#1 label
  {
    \prop_get:cnNT
      {g_@@_struct_\property_ref:enn{tagpdfstruct-#1}{tagstruct}{unknown}_prop}
      {parentnum}
      \l_@@_tmpa_tl
      {
        \msg_warning:nnn { tag } {struct-used-twice} {#1}
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Checks related to roles}
% \begin{macro}{\@@_check_add_tag_role:nn}
% This check is used when defining a new role mapping.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_add_tag_role:nn #1 #2 %#1 tag, #2 role
  {
    \tl_if_empty:nTF {#2}
      {
        \msg_error:nnn { tag } {role-missing} {#1}
      }
      {
        \prop_get:NnNTF \g_@@_role_tags_NS_prop {#2} \l_@@_tmpa_tl
          {
            \int_compare:nNnT {\l_@@_loglevel_int} > { 0 }
              {
                \msg_info:nnnn { tag } {role-tag} {#1} {#2}
              }
          }
          {
            \msg_error:nnn { tag } {role-unknown} {#2}
          }
      }
  }
%    \end{macrocode}
% Similar with a namespace
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_add_tag_role:nnn #1 #2 #3 %#1 tag/NS, #2 role #3 namespace
  {
    \tl_if_empty:nTF {#2}
      {
        \msg_error:nnn { tag } {role-missing} {#1}
      }
      {
        \prop_get:cnNTF { g_@@_role_NS_#3_prop } {#2} \l_@@_tmpa_tl
          {
            \int_compare:nNnT {\l_@@_loglevel_int} > { 0 }
              {
                \msg_info:nnnn { tag } {role-tag} {#1} {#2/#3}
              }
          }
          {
            \msg_error:nnn { tag } {role-unknown} {#2/#3}
          }
      }
  }
%    \end{macrocode}
% \end{macro}

% \subsection{Check related to mc-chunks}
%
% \begin{macro}{\@@_check_mc_if_nested:,\@@_check_mc_if_open:}
% Two tests if a mc is currently open. One for the true (for begin
% code),  one for the false part (for end code).
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_mc_if_nested:
  {
    \@@_mc_if_in:T
      {
        \msg_warning:nne { tag } {mc-nested} { \@@_get_mc_abs_cnt: }
      }
  }

\cs_new_protected:Npn \@@_check_mc_if_open:
  {
    \@@_mc_if_in:F
      {
        \msg_warning:nne { tag } {mc-not-open} { \@@_get_mc_abs_cnt: }
      }
  }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{\@@_check_mc_pushed_popped:nn}
% This creates an information message if mc's are pushed or popped.
% The first argument is a word (pushed or popped), the second the tag name.
% With larger log-level the stack is shown too.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_mc_pushed_popped:nn #1 #2
  {
    \int_compare:nNnT
      { \l_@@_loglevel_int } ={ 2 }
      { \msg_info:nne {tag}{mc-#1}{#2} }
    \int_compare:nNnT
      { \l_@@_loglevel_int } > { 2 }
      {
        \msg_info:nne {tag}{mc-#1}{#2}
        \seq_log:N \g_@@_mc_stack_seq
      }
  }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{\@@_check_mc_tag:N}
% This checks if the mc has a (known) tag, if it is empty
% (e.g. if due to a call to the output routine, see issue https://github.com/latex3/tagpdf/issues/111)
% then  we fall back to the structure name.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_mc_tag:N #1  %#1 is var with a tag name in it
  {
    \tl_if_empty:NTF #1
      {
        \tl_set:No #1 { \g_@@_struct_tag_tl }
        \msg_info:nnee { tag } {mc-tag-missing} { \g_@@_struct_tag_tl }{ \@@_get_mc_abs_cnt: }
      }
      {
       \prop_get:NoNF \g_@@_role_tags_NS_prop {#1}\l_@@_tmp_unused_tl
         {
           \msg_warning:nne { tag } {role-unknown-tag} {#1}
         }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\g_@@_check_mc_used_intarray, \@@_check_init_mc_used:}
% This variable holds the list of used mc numbers.
% Every time we store a mc-number we will add one the relevant array index
% If everything is right at the end there should be only 1 until the max count
% of the mcid. 2 indicates that one mcid was used twice, 0 that we lost one.
% In engines other than luatex the total number of all intarray entries
% are restricted so we use only  a rather small value of 65536, and we initialize
% the array only at first used, guarded by the log-level.
% This check is probably only needed for debugging.
% TODO does this really make sense to check? When can it happen??
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_init_mc_used:
  {
    \intarray_new:Nn \g_@@_check_mc_used_intarray { 65536 }
    \cs_gset_eq:NN \@@_check_init_mc_used: \prg_do_nothing:
  }
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_check_mc_used:n}
% This checks if a mc is used twice.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_mc_used:n #1 %#1 mcid abscnt
  {
    \int_compare:nNnT {\l_@@_loglevel_int} > { 2 }
      {
        \@@_check_init_mc_used:
        \intarray_gset:Nnn \g_@@_check_mc_used_intarray
          {#1}
          { \intarray_item:Nn \g_@@_check_mc_used_intarray {#1} + 1 }
        \int_compare:nNnT
          {
            \intarray_item:Nn \g_@@_check_mc_used_intarray {#1}
          }
          >
          { 1 }
          {
            \msg_warning:nnn { tag } {mc-used-twice} {#1}
          }
      }
  }
%    \end{macrocode}
% \end{macro}
% \begin{macro}{\@@_check_show_MCID_by_page:}
% This allows to show the mc on a page. Currently unused.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_show_MCID_by_page:
  {
    \tl_set:Ne \l_@@_tmpa_tl
      {
        \@@_property_ref_lastpage:nn
          {abspage}
          {-1}
      }
    \int_step_inline:nnnn {1}{1}
      {
        \l_@@_tmpa_tl
      }
      {
        \seq_clear:N \l_@@_tmpa_seq
        \int_step_inline:nnnn
          {1}
          {1}
          {
            \@@_property_ref_lastpage:nn
              {tagmcabs}
              {-1}
          }
          {
            \int_compare:nT
              {
                \property_ref:enn
                  {mcid-####1}
                  {tagabspage}
                  {-1}
                =
                ##1
             }
             {
               \seq_gput_right:Ne \l_@@_tmpa_seq
                 {
                   Page##1-####1-
                   \property_ref:enn
                     {mcid-####1}
                     {tagmcid}
                     {-1}
                 }
             }
          }
          \seq_show:N \l_@@_tmpa_seq
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Checks related to the state of MC on a page or in a split stream}
% The following checks are currently only usable in generic mode as they
% rely on the marks defined in the mc-generic module. They are used to detect
% if a mc-chunk has been split by a page break or similar and additional
% end/begin commands are needed.
%
% \begin{macro}[pTF]{\@@_check_mc_in_galley:}
% At first we need a test to decide if |\tag_mc_begin:n| (tmb) and |\tag_mc_end:|
% (tme) has been used at all on the current galley. As each command issues
% two slightly different marks we can do it by comparing firstmarks and botmarks.
% The test assumes that the marks have been already mapped into the sequence with
% |\@@_mc_get_marks:|. As |\seq_if_eq:NNTF| doesn't exist we use the tl-test.
%    \begin{macrocode}
\prg_new_conditional:Npnn \@@_check_if_mc_in_galley: { T,F,TF }
 {
   \tl_if_eq:NNTF \l_@@_mc_firstmarks_seq \l_@@_mc_botmarks_seq
    { \prg_return_false: }
    { \prg_return_true: }
 }
%    \end{macrocode}
% \end{macro}

% \begin{macro}[pTF]{\@@_check_if_mc_tmb_missing:}
% This checks if a extra top mark (\enquote{extra-tmb}) is needed.
% According to the analysis this the case if the firstmarks start with
% |e-| or |b+|.
% Like above we assume that the marks content is already in the seq's.
%    \begin{macrocode}
\prg_new_conditional:Npnn \@@_check_if_mc_tmb_missing: { T,F,TF }
 {
  \bool_if:nTF
    {
      \str_if_eq_p:ee {\seq_item:Nn \l_@@_mc_firstmarks_seq {1}}{e-}
      ||
      \str_if_eq_p:ee {\seq_item:Nn \l_@@_mc_firstmarks_seq {1}}{b+}
    }
    { \prg_return_true: }
    { \prg_return_false: }
 }
%    \end{macrocode}
% \end{macro}

% \begin{macro}[pTF]{\@@_check_if_mc_tme_missing:}
% This checks if a extra bottom mark (\enquote{extra-tme}) is needed.
% According to the analysis this the case if the botmarks starts with
% |b+|.
% Like above we assume that the marks content is already in the seq's.
%    \begin{macrocode}
\prg_new_conditional:Npnn \@@_check_if_mc_tme_missing: { T,F,TF }
 {
   \str_if_eq:eeTF {\seq_item:Nn \l_@@_mc_botmarks_seq {1}}{b+}
    { \prg_return_true: }
    { \prg_return_false: }
 }
%    \end{macrocode}
% \end{macro}
%    \begin{macrocode}
%</package>
%    \end{macrocode}
%    \begin{macrocode}
%<*debug>
%    \end{macrocode}
% Code for tagpdf-debug. This will probably change over time.
% At first something for the mc commands.
%    \begin{macrocode}
\msg_new:nnn { tag / debug } {mc-begin} { MC~begin~#1~with~options:~\tl_to_str:n{#2}~[\msg_line_context:] }
\msg_new:nnn { tag / debug } {mc-end}   { MC~end~#1~[\msg_line_context:] }

\cs_new_protected:Npn \@@_debug_mc_begin_insert:n #1
 {
   \int_compare:nNnT { \l_@@_loglevel_int } > {0}
     {
        \msg_note:nnnn { tag / debug } {mc-begin} {inserted} { #1 }
     }
 }
\cs_new_protected:Npn \@@_debug_mc_begin_ignore:n #1
 {
   \int_compare:nNnT { \l_@@_loglevel_int } > {0}
     {
        \msg_note:nnnn { tag / debug } {mc-begin } {ignored} { #1 }
     }
 }
\cs_new_protected:Npn \@@_debug_mc_end_insert:
 {
   \int_compare:nNnT { \l_@@_loglevel_int } > {0}
     {
        \msg_note:nnn { tag / debug } {mc-end} {inserted}
     }
 }
\cs_new_protected:Npn \@@_debug_mc_end_ignore:
 {
   \int_compare:nNnT { \l_@@_loglevel_int } > {0}
     {
        \msg_note:nnn { tag / debug } {mc-end } {ignored}
     }
 }
%    \end{macrocode}
% And now something for the structures
%    \begin{macrocode}
\msg_new:nnn { tag / debug } {struct-begin}
  {
    Struct~\tag_get:n{struct_num}~begin~#1~with~options:~\tl_to_str:n{#2}~\\[\msg_line_context:]
  }
\msg_new:nnn { tag / debug } {struct-end}
  {
    Struct~end~#1~[\msg_line_context:]
  }
\msg_new:nnn { tag / debug } {struct-end-wrong}
  {
    Struct~end~'#1'~doesn't~fit~start~'#2'~[\msg_line_context:]
  }

\cs_new_protected:Npn \@@_debug_struct_begin_insert:n #1
 {
   \int_compare:nNnT { \l_@@_loglevel_int } > {0}
     {
        \msg_note:nnnn { tag / debug } {struct-begin} {inserted} { #1 }
        \seq_log:N \g_@@_struct_tag_stack_seq
     }
 }
\cs_new_protected:Npn \@@_debug_struct_begin_ignore:n #1
 {
   \int_compare:nNnT { \l_@@_loglevel_int } > {0}
     {
        \msg_note:nnnn { tag / debug } {struct-begin } {ignored} { #1 }
     }
 }
\cs_new_protected:Npn \@@_debug_struct_end_insert:
 {
   \int_compare:nNnT { \l_@@_loglevel_int } > {0}
     {
        \msg_note:nnn { tag / debug } {struct-end} {inserted}
        \seq_log:N \g_@@_struct_tag_stack_seq
     }
 }
\cs_new_protected:Npn \@@_debug_struct_end_ignore:
 {
   \int_compare:nNnT { \l_@@_loglevel_int } > {0}
     {
        \msg_note:nnn { tag / debug } {struct-end } {ignored}
     }
 }
\cs_new_protected:Npn \@@_debug_struct_end_check:n #1
 {
   \int_compare:nNnT { \l_@@_loglevel_int } > {0}
    {
      \seq_get:NNT \g_@@_struct_tag_stack_seq \l_@@_tmpa_tl
        {
          \str_if_eq:eeF
           {#1}
           {\exp_last_unbraced:No \use_i:nn { \l_@@_tmpa_tl }}
           {
             \msg_warning:nnee { tag/debug }{ struct-end-wrong }
              {#1}
              {\exp_last_unbraced:No \use_i:nn { \l_@@_tmpa_tl }}
           }
        }
     }
 }
%    \end{macrocode}
% This tracks tag suspend and resume.
% The tag-suspend message should go before the int is increased.
% The tag-resume message after the int is decreased.
%    \begin{macrocode}
\msg_new:nnn { tag / debug } {tag-suspend}
  {
    \int_if_zero:nTF
      {#1}
      {Tagging~suspended}
      {Tagging~(not)~suspended~(already~inactive)}\\
    level:~#1~==>~\int_eval:n{#1+1}\tl_if_empty:nF{#2}{,~label:~#2}~[\msg_line_context:]
  }
\msg_new:nnn { tag / debug } {tag-resume}
  {
    \int_if_zero:nTF
      {#1}
      {Tagging~resumed}
      {Tagging~(not)~resumed}\\
    level:~\int_eval:n{#1+1}~==>~#1\tl_if_empty:nF{#2}{,~label:~#2}~[\msg_line_context:]
  }
%    \end{macrocode}
%    \begin{macrocode}
%</debug>
%    \end{macrocode}
%
% \subsection{Benchmarks}
% It doesn't make much sense to do benchmarks in debug mode or in combination
% with a log-level as this would slow down the compilation.
% So we add simple commands that can be activated if l3benchmark has been loaded.
% TODO: is a warning needed?
%    \begin{macrocode}
%<*package>
\cs_new_protected:Npn \@@_check_benchmark_tic:{}
\cs_new_protected:Npn \@@_check_benchmark_toc:{}
\cs_new_protected:Npn \tag_check_benchmark_on:
  {
    \cs_if_exist:NT \benchmark_tic:
     {
       \cs_set_eq:NN \@@_check_benchmark_tic: \benchmark_tic:
       \cs_set_eq:NN \@@_check_benchmark_toc: \benchmark_toc:
     }
  }
%</package>
%    \end{macrocode}
% \end{implementation}
% \PrintIndex
