% \iffalse meta-comment
%
%% File: tagpdf-roles.dtx
%
% Copyright (C) 2019-2025 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{}
\documentclass{l3doc}
\usepackage{array,booktabs,caption}
\hypersetup{pdfauthor=Ulrike Fischer,
 pdftitle=tagpdf-checks module (tagpdf)}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
% \title{^^A
%   The \pkg{tagpdf-roles} module\\ Tags, roles and namespace code   ^^A
%   \\ Part of the tagpdf package
% }
%
% \author{^^A
%  Ulrike Fischer\thanks
%    {^^A
%      E-mail:
%        \href{mailto:fischer@troubleshooting-tex.de}
%          {fischer@troubleshooting-tex.de}^^A
%    }^^A
% }
%
% \date{Version 0.99w, released 2025-10-31}
% \maketitle
% \begin{documentation}
% \begin{function}
%  {
%    add-new-tag (setup-key),
%    tag (rolemap-key),
%    namespace (rolemap-key),
%    role (rolemap-key),
%    role-namespace (rolemap-key),
%  }
% The \texttt{add-new-tag} key can be used in \cs{tagpdfsetup} to declare and rolemap new tags.
% It takes as value a key-value list or a simple |new-tag/old-tag|.
%
% The key-value list knows the following keys:
% \begin{description}
% \item[\texttt{tag}] This is the name of the new tag as it should
% then be used in \cs{tagstructbegin}.
% \item[\texttt{namespace}] This is the namespace of the new tag.
%   The value should be a shorthand of a namespace.
%   The allowed values are currently |pdf|, |pdf2|, |mathml|,|latex|, |latex-book| and |user|.
%   The default value (and recommended value for a new tag) is |user|.
%   The public name of the user namespace is |tag/NS/user|. This can be used to reference
%   the namespace e.g. in attributes.
% \item[\texttt{role}] This is the tag the tag should be mapped too.
%  In a PDF 1.7 or earlier this is normally a tag from the |pdf| set,
%  in PDF 2.0 from the |pdf|, |pdf2| and |mathml| set.
%  It can also be a user tag. The tag must be declared before, as the code retrieves
%  the class of the new tag from it.
%  The PDF format allows mapping to be done transitively.
%  But tagpdf can't/won't check such unusual role mapping.
% \item[\texttt{role-namespace}] If the role is a known tag
%  the default value is the default namespace of this tag.
%  With this key a specific namespace can be forced.
% \end{description}
%
% Namespaces are mostly a PDF 2.0 property, but it doesn't harm to
% set them also in a PDF 1.7 or earlier.
% \end{function}
%
% \begin{function}[TF]{\tag_check_child:nn}
% \begin{syntax}
% \cs{tag_check_child:nnTF} \Arg{tag} \Arg{namespace} \Arg{true code} \Arg{false code}
% \end{syntax}
% This checks if the tag \meta{tag} from the name space \meta{namespace}
% can be used at the current position. In tagpdf-base it is always true.
% \end{function}
% \end{documentation}
% \begin{implementation}
%    \begin{macrocode}
%<@@=tag>
%<*header>
\ProvidesExplPackage {tagpdf-roles-code} {2025-10-31} {0.99w}
 {part of tagpdf - code related to roles and structure names}
%</header>
%    \end{macrocode}
% \section{Code related to roles and structure names}
%    \begin{macrocode}
%<*package>
%    \end{macrocode}
%
%
% \subsection{Variables}
% Tags are used in structures (\cs{tagstructbegin}) and mc-chunks (\cs{tagmcbegin}).
%
% They have a name (a string), in lua a number (for the lua attribute), and
% in PDF 2.0 belong to one or more name spaces, with one being the default
% name space.
%
% Tags of structures are classified, e.g. as grouping,
% inline or block level structure (and a few special classes like lists and tables),
% and must follow containments rules depending on their classification
% (for example a inline structure can not contain
% a block level structure). New tags inherit their
% classification from their rolemapping to the standard namespaces (\texttt{pdf}
% and/or \texttt{pdf2}).
% We store this classification as it will probably
% be needed for tests but currently the data is not much used.
% The classification for math (and the containment rules)
% is unclear currently and so not set.
%
% The attribute number is only relevant in lua and only for the MC chunks
% (so tags with the same name from different names spaces can have the same number),
% and so only stored if luatex is detected.
%
% Due to the namespaces the storing and processing of tags and there data
% are different in various places for PDF~2.0 and PDF~<2.0, which makes
% things a bit difficult and leads to some duplications. Perhaps at some time
% there should be a clear split.
%
% This are the main variables used by the code:
% \begin{description}
% \item[\cs{g_@@_role_tags_NS_prop}]
% This is the core list of tag names. It uses tags as keys
% and the shorthand (e.g. pdf2, or mathml) of the default name space as value.
%
% In pdf 2.0 the value is needed in the structure dictionaries.
%
% \item[\cs{g_@@_role_tags_class_prop}]
% This contains for each tag a classification type. It is used in pdf <2.0.
%
% \item[\cs{g_@@_role_NS_prop}] This contains the names spaces. The
% values are the object references. They are used in pdf 2.0.
%
% \item[\cs{g_@@_role_rolemap_prop}]
% This contains for each tag the role to a standard tag.
% It is used in pdf<2.0 for tag checking and to fill at the end the
% RoleMap dictionary.
%
% \item[\texttt{g\_@@\_role/RoleMap\_dict}] This dictionary contains
% the standard rolemaps. It is relevant only for pdf <2.0.
%
% \item[\cs{g_@@_role_NS_<ns>_prop}] This prop contains the tags of
% a name space and their role. The props are also use for
% remapping. As value they contain two brace groups: tag and namespace.
% In pdf <2.0 the namespace is empty.
%
% \item[\cs{g_@@_role_NS_<ns>_class_prop}]
% This prop contains the tags of
% a name space and their type. The value is only needed for pdf 2.0.
%
% \item[\cs{g_@@_role_index_prop}]
% This prop contains the standard tags (pdf in pdf<2.0,
% pdf,pdf2 + mathml in pdf 2.0) as keys, the values are a two-digit
% number. These numbers are used to get the containment rule of two tags
% from the intarray.
%
% \end{description}
%
%
% \begin{variable}{\g_@@_role_tags_NS_prop}
% This is the core list of tag names. It uses tags as keys
% and the shorthand (e.g. pdf2, or mathml) of the default name space as value.
% We store the default name space also in pdf <2.0, even if not needed:
% it doesn't harm and simplifies the code.
% There is no need to access this from lua, so we use the standard prop commands.
%    \begin{macrocode}
\prop_new_linked:N    \g_@@_role_tags_NS_prop
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\g_@@_role_tags_class_prop}
% With pdf 2.0 we store the class in the NS dependent props.
% With pdf <2.0 we store for now the type(s) of a tag in a common
% prop.
% Tags that are rolemapped should get the type from
% the target.
%    \begin{macrocode}
\prop_new:N    \g_@@_role_tags_class_prop
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\g_@@_role_NS_prop}
% This holds the list of supported name spaces.
% The keys are the name tagpdf will use, the values the object reference.
% The urls identifier are stored in related dict object.
% \begin{description}
% \item[mathml] http://www.w3.org/1998/Math/MathML
% \item[pdf2]   http://iso.org/pdf2/ssn
% \item[pdf]    http://iso.org/pdf/ssn (default)
% \item[user]   \cs{c_@@_role_userNS_id_str} (random id, for user tags)
% \item[latex]  https://www.latex-project.org/ns/dflt
% \item[latex-book] https://www.latex-project.org/ns/book
% \end{description}
% More namespaces are possible and
% their objects references and their rolemaps must be collected
% so that an array can be written to the StructTreeRoot at the end (see tagpdf-tree).
% We use a prop to store the object reference as it will be needed rather
% often.
%    \begin{macrocode}
\prop_new:N \g_@@_role_NS_prop
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\g_@@_role_index_prop}
% This prop contains the standard tags (pdf in pdf<2.0,
% pdf,pdf2 + mathml in pdf 2.0) as keys, the values are a two-digit
% number. These numbers are used to get the containment rule of two tags
% from the intarray.
%    \begin{macrocode}
\prop_new:N \g_@@_role_index_prop
%    \end{macrocode}
% \end{variable}
% \begin{variable}{\l_@@_role_debug_prop}
% This variable is used to pass more infos to debug messages.
%    \begin{macrocode}
\prop_new:N \l_@@_role_debug_prop
%    \end{macrocode}
% \end{variable}
% We need also a bunch of temporary variables.
% \begin{variable}
%  {
%     ,\l_@@_role_tag_tmpa_tl
%     ,\l_@@_role_tag_namespace_tmpa_tl
%     ,\l_@@_role_tag_namespace_tmpb_tl      %
%     ,\l_@@_role_role_tmpa_tl
%     ,\l_@@_role_role_namespace_tmpa_tl
%     ,\l_@@_role_tmpa_seq
%  }
%    \begin{macrocode}
\tl_new:N \l_@@_role_tag_tmpa_tl
\tl_new:N \l_@@_role_tag_namespace_tmpa_tl
\tl_new:N \l_@@_role_tag_namespace_tmpb_tl
\tl_new:N \l_@@_role_role_tmpa_tl
\tl_new:N \l_@@_role_role_namespace_tmpa_tl
\seq_new:N\l_@@_role_tmpa_seq
%    \end{macrocode}
% \end{variable}
%
% \subsection{Namespaces}
% The following commands setups a name space.
% With pdf version $<$2.0 this is only a prop with the
% rolemap. With pdf 2.0 a dictionary must be set up.
% Such a name space dictionaries can
% contain an optional |/Schema| and |/RoleMapNS| entry. We only reserve the
% objects but delay the writing to the finish code, where we can test if the
% keys and the name spaces are actually needed.
% This commands setups objects for the name space and its rolemap. It also
% initialize a dict to collect the rolemaps if needed, and a property
% with the tags of the name space and their rolemapping for loops.
% It is unclear if a reference to a schema file will be ever needed,
% but it doesn't harm \ldots.
%
% \begin{variable}{g_@@_role/RoleMap_dict,\g_@@_role_rolemap_prop}
% This is the object which contains the normal RoleMap. It is probably not
% needed in pdf 2.0 but currently kept.
%    \begin{macrocode}
\pdfdict_new:n {g_@@_role/RoleMap_dict}
\@@_prop_new:N \g_@@_role_rolemap_prop
%    \end{macrocode}
% \end{variable}
%
% \begin{function}{\@@_role_NS_new:nnn}
%  \begin{syntax}
%   \cs{@@_role_NS_new:nnn} \Arg{shorthand} \Arg{URI-ID} \Arg{Schema}
%  \end{syntax}
% \end{function}
% \begin{macro}{\@@_role_NS_new:nnn}
%    \begin{macrocode}
\pdf_version_compare:NnTF < {2.0}
 {
   \cs_new_protected:Npn \@@_role_NS_new:nnn #1 #2 #3
    {
      \@@_prop_new:c { g_@@_role_NS_#1_prop }
      \prop_new:c { g_@@_role_NS_#1_class_prop }
      \prop_gput:Nne \g_@@_role_NS_prop {#1}{}
    }
 }
 {
  \cs_new_protected:Npn \@@_role_NS_new:nnn #1 #2 #3
    {
      \@@_prop_new:c { g_@@_role_NS_#1_prop }
      \prop_new:c { g_@@_role_NS_#1_class_prop }
      \pdf_object_new:n {tag/NS/#1}
      \pdfdict_new:n     {g_@@_role/Namespace_#1_dict}
      \pdf_object_new:n {@@/RoleMapNS/#1}
      \pdfdict_new:n     {g_@@_role/RoleMapNS_#1_dict}
      \pdfdict_gput:nnn
        {g_@@_role/Namespace_#1_dict}
        {Type}
        {/Namespace}
      \pdf_string_from_unicode:nnN{utf8/string}{#2}\l_@@_tmpa_str
      \tl_if_empty:NF \l_@@_tmpa_str
        {
          \pdfdict_gput:nne
            {g_@@_role/Namespace_#1_dict}
            {NS}
            {\l_@@_tmpa_str}
        }
      %RoleMapNS is added in tree
      \tl_if_empty:nF  {#3}
       {
         \pdfdict_gput:nne{g_@@_role/Namespace_#1_dict}
          {Schema}{#3}
       }
      \prop_gput:Nne \g_@@_role_NS_prop {#1}{\pdf_object_ref:n{tag/NS/#1}~}
    }
 }
%    \end{macrocode}
% \end{macro}
% We need an id for the user space. For the tests it should be possible
% to set it to a fix value. So we use random numbers which can
% be fixed by setting a seed. We fake a sort of
% GUID but do not try to be really exact as it doesn't matter ...
%
% \begin{variable}{\c_@@_role_userNS_id_str}
%    \begin{macrocode}
\str_const:Ne \c_@@_role_userNS_id_str
  { data:,
    \int_to_Hex:n{\int_rand:n {65535}}
    \int_to_Hex:n{\int_rand:n {65535}}
    -
    \int_to_Hex:n{\int_rand:n {65535}}
    -
    \int_to_Hex:n{\int_rand:n {65535}}
    -
    \int_to_Hex:n{\int_rand:n {65535}}
    -
    \int_to_Hex:n{\int_rand:n {16777215}}
    \int_to_Hex:n{\int_rand:n {16777215}}
  }
%    \end{macrocode}
% \end{variable}
% Now we setup the standard names spaces.
% The mathml space is loaded also for pdf < 2.0
% but not added to RoleMap unless a boolean is set to true with
% |tagpdf-setup{mathml-tags}|.
%    \begin{macrocode}
\bool_new:N \g_@@_role_add_mathml_bool
\@@_role_NS_new:nnn {pdf}   {http://iso.org/pdf/ssn}{}
\@@_role_NS_new:nnn {pdf2}  {http://iso.org/pdf2/ssn}{}
\@@_role_NS_new:nnn {mathml}{http://www.w3.org/1998/Math/MathML}{}
\@@_role_NS_new:nnn {latex} {https://www.latex-project.org/ns/dflt}{}
\@@_role_NS_new:nnn {latex-book} {https://www.latex-project.org/ns/book}{}
\exp_args:Nne
  \@@_role_NS_new:nnn {user}{\c_@@_role_userNS_id_str}{}
%    \end{macrocode}
%
% \subsection{Adding a new tag}
% Both when reading the files and when setting up a tag manually
% we have to store data in various places.
%
% \begin{macro}{\@@_role_alloctag:nnn}
% This command allocates a new tag without role mapping. In the
% lua backend it will also record the attribute value.
%    \begin{macrocode}
\pdf_version_compare:NnTF < {2.0}
  {
   \sys_if_engine_luatex:TF
    {
      \cs_new_protected:Npn \@@_role_alloctag:nnn #1 #2 #3 %#1 tagname, ns, type
       {
         \lua_now:e { ltx.@@.func.alloctag ('#1') }
         \prop_gput:Nnn \g_@@_role_tags_NS_prop   {#1}{#2}
         \@@_prop_gput:cnn {g_@@_role_NS_#2_prop}  {#1}{{}{}}
         \prop_gput:Nnn \g_@@_role_tags_class_prop {#1}{#3}
         \prop_gput:cnn {g_@@_role_NS_#2_class_prop}  {#1}{--UNUSED--}
       }
    }
    {
      \cs_new_protected:Npn \@@_role_alloctag:nnn #1 #2 #3
       {
         \prop_gput:Nnn \g_@@_role_tags_NS_prop   {#1}{#2}
         \@@_prop_gput:cnn {g_@@_role_NS_#2_prop}  {#1}{{}{}}
         \prop_gput:Nnn \g_@@_role_tags_class_prop {#1}{#3}
         \prop_gput:cnn {g_@@_role_NS_#2_class_prop}  {#1}{--UNUSED--}
       }
    }
  }
  {
   \sys_if_engine_luatex:TF
    {
      \cs_new_protected:Npn \@@_role_alloctag:nnn #1 #2 #3 %#1 tagname, ns, type
       {
         \lua_now:e { ltx.@@.func.alloctag ('#1') }
         \prop_gput:Nnn \g_@@_role_tags_NS_prop   {#1}{#2}
         \@@_prop_gput:cnn {g_@@_role_NS_#2_prop}  {#1}{{}{}}
         \prop_gput:Nnn \g_@@_role_tags_class_prop {#1}{--UNUSED--}
         \prop_gput:cnn {g_@@_role_NS_#2_class_prop}  {#1}{#3}
       }
    }
    {
      \cs_new_protected:Npn \@@_role_alloctag:nnn #1 #2 #3
       {
         \prop_gput:Nnn \g_@@_role_tags_NS_prop   {#1}{#2}
         \@@_prop_gput:cnn {g_@@_role_NS_#2_prop}  {#1}{{}{}}
         \prop_gput:Nnn \g_@@_role_tags_class_prop {#1}{--UNUSED--}
         \prop_gput:cnn {g_@@_role_NS_#2_class_prop}  {#1}{#3}
       }
    }
  }
\cs_generate_variant:Nn  \@@_role_alloctag:nnn {nno}
%    \end{macrocode}
% \end{macro}
%
% \subsubsection{pdf 1.7 and earlier}
%
% \begin{macro}{\@@_role_add_tag:nn}
% The pdf 1.7 version has only two arguments: new and rolemap name.
% The role must be an existing tag and should not be empty.
% We allow to change the role of an existing tag: as the rolemap is written
% at the end not confusion can happen.
%    \begin{macrocode}
\cs_new_protected:Nn \@@_role_add_tag:nn % (new) name, reference to old
  {
%    \end{macrocode}
% checks and messages
%    \begin{macrocode}
    \@@_check_add_tag_role:nn {#1}{#2}
    \prop_get:NnNF \g_@@_role_tags_NS_prop {#1}\l_@@_tmp_unused_tl
      {
        \int_compare:nNnT {\l_@@_loglevel_int} > { 0 }
          {
            \msg_info:nnn { tag }{new-tag}{#1}
          }
      }
%    \end{macrocode}
% now the addition
%    \begin{macrocode}
    \prop_get:NnNF \g_@@_role_tags_class_prop {#2}\l_@@_tmpa_tl
      {
        \tl_set:Nn\l_@@_tmpa_tl{--UNKNOWN--}
      }
    \@@_role_alloctag:nno {#1}{user} { \l_@@_tmpa_tl }
%    \end{macrocode}
% We resolve rolemapping recursively so that all targets are stored as standard
% tags.
%    \begin{macrocode}
    \tl_if_empty:nF { #2 }
      {
        \prop_get:NnNTF \g_@@_role_rolemap_prop {#2}\l_@@_tmpa_tl
          {
            \@@_prop_gput:Nno \g_@@_role_rolemap_prop {#1}{\l_@@_tmpa_tl}
          }
          {
            \@@_prop_gput:Nne \g_@@_role_rolemap_prop {#1}{\tl_to_str:n{#2}}
          }
      }
  }
\cs_generate_variant:Nn \@@_role_add_tag:nn {oo,ne}
%    \end{macrocode}
% \end{macro}
%
% For the parent-child test we must be able to get the role.
% We use the same number of arguments as for the 2.0 command.
% If there is no role, we assume a standard tag.
% Note: this is quite fast and a move to lua doesn't improve speed.
% \begin{macro}{\@@_role_get:nnNN}
%    \begin{macrocode}
\pdf_version_compare:NnT < {2.0}
 {
   \cs_new:Npn \@@_role_get:nnNN #1#2#3#4 %#1 tag, #2 NS, #3 tlvar which hold the role tag #4 empty
    {
      \prop_get:NnNF \g_@@_role_rolemap_prop {#1}#3
        {
          \tl_set:Nn #3 {#1}
        }
      \tl_set:Nn #4 {}
    }
   \cs_generate_variant:Nn \@@_role_get:nnNN {ooNN}
 }

%    \end{macrocode}
% \end{macro}
% \subsubsection{The pdf 2.0 version}
% \begin{macro}{\@@_role_add_tag:nnnn}
% The pdf 2.0 version takes four arguments:
% tag/namespace/role/namespace
%    \begin{macrocode}
\cs_new_protected:Nn \@@_role_add_tag:nnnn %tag/namespace/role/namespace
  {
    \@@_check_add_tag_role:nnn {#1/#2}{#3}{#4}
    \int_compare:nNnT {\l_@@_loglevel_int} > { 0 }
      {
        \msg_info:nnn { tag }{new-tag}{#1}
      }
    \prop_if_exist:cTF
     { g_@@_role_NS_#4_class_prop }
     {
       \prop_get:cnN { g_@@_role_NS_#4_class_prop } {#3}\l_@@_tmpa_tl
       \quark_if_no_value:NT \l_@@_tmpa_tl
        {
          \tl_set:Nn\l_@@_tmpa_tl{--UNKNOWN--}
        }
     }
     { \tl_set:Nn\l_@@_tmpa_tl{--UNKNOWN--} }
    \@@_role_alloctag:nno {#1}{#2}{ \l_@@_tmpa_tl }
%    \end{macrocode}
% Do not remap standard tags. TODO add warning?
%    \begin{macrocode}
    \tl_if_in:nnF {-pdf-pdf2-mathml-}{-#2-}
     {
       \pdfdict_gput:nne {g_@@_role/RoleMapNS_#2_dict}{#1}
          {
            [
              \pdf_name_from_unicode_e:n{#3}
              \c_space_tl
              \pdf_object_ref:n {tag/NS/#4}
            ]
          }
     }
%    \end{macrocode}
% We resolve rolemapping recursively so that all targets are stored as standard
% tags for the tests.
%    \begin{macrocode}
    \tl_if_empty:nF { #2 }
      {
        \prop_get:cnN { g_@@_role_NS_#4_prop } {#3}\l_@@_tmpa_tl
        \quark_if_no_value:NTF \l_@@_tmpa_tl
          {
            \@@_prop_gput:cne { g_@@_role_NS_#2_prop } {#1}
              {{\tl_to_str:n{#3}}{\tl_to_str:n{#4}}}
          }
          {
            \@@_prop_gput:cno { g_@@_role_NS_#2_prop } {#1}{\l_@@_tmpa_tl}
          }
      }
%    \end{macrocode}
% We also store into the pdf 1.7 rolemapping so that we can
% add that as fallback for pdf 1.7 processor
%    \begin{macrocode}
     \bool_if:NT \l__tag_role_update_bool
       {
         \tl_if_empty:nF { #3 }
          {
            \tl_if_eq:nnF{#1}{#3}
             {
              \prop_get:NnN \g_@@_role_rolemap_prop {#3}\l_@@_tmpa_tl
               \quark_if_no_value:NTF \l_@@_tmpa_tl
                {
                  \@@_prop_gput:Nne \g_@@_role_rolemap_prop {#1}{\tl_to_str:n{#3}}
                }
                {
                  \@@_prop_gput:Nno \g_@@_role_rolemap_prop {#1}{\l_@@_tmpa_tl}
                }
              }
           }
       }
   }
\cs_generate_variant:Nn \@@_role_add_tag:nnnn {oooo}
%    \end{macrocode}
% \end{macro}
%
% For the parent-child test we must be able to get the role.
% We use the same number of arguments as for the <2.0 command.
% Note: this is quite fast and a move to lua doesn't improve speed.
% \begin{macro}{\@@_role_get:nnNN}
%    \begin{macrocode}
\pdf_version_compare:NnF < {2.0}
 {
   \cs_new:Npn \@@_role_get:nnNN #1#2#3#4
     %#1 tag, #2 NS,
     %#3 tlvar which hold the role tag
     %#4 tlvar which hold the name of the target NS
    {
      \prop_if_exist:cTF {g_@@_role_NS_#2_prop}
       {
        \prop_get:cnNTF {g_@@_role_NS_#2_prop} {#1}\l_@@_get_tmpc_tl
          {
           \tl_set:Ne #3 {\exp_last_unbraced:No\use_i:nn  {\l_@@_get_tmpc_tl}}
           \tl_set:Ne #4 {\exp_last_unbraced:No\use_ii:nn {\l_@@_get_tmpc_tl}}
          }
          {
           \msg_warning:nnn { tag } {role-unknown-tag} { #1 }
           \tl_set:Nn #3 {#1}
           \tl_set:Nn #4 {#2}
          }
       }
       {
          \msg_warning:nnn { tag } {role-unknown-NS} { #2 }
          \tl_set:Nn #3 {#1}
          \tl_set:Nn #4 {#2}
       }
    }
   \cs_generate_variant:Nn \@@_role_get:nnNN {ooNN}
 }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Helper command to read the data from files}
% In this section we setup the helper command to read namespace files.

% \begin{macro}{\@@_role_read_namespace_line:nw}
% This command will process a line in the name space file.
% The first argument is the name of the name space.
% The definition differ for pdf 2.0. as we have proper name spaces there.
% With pdf<2.0 special name spaces shouldn't update the default role or add to the rolemap
% again, they only store the values for later uses. We use a boolean here.
%    \begin{macrocode}
\bool_new:N\l_@@_role_update_bool
\bool_set_true:N \l_@@_role_update_bool
%    \end{macrocode}
%
%    \begin{macrocode}
\pdf_version_compare:NnTF < {2.0}
 {
  \cs_new_protected:Npn \@@_role_read_namespace_line:nw #1#2,#3,#4,#5,#6\q_stop %
   % #1 NS, #2 tag, #3 rolemap, #4 NS rolemap #5 type
    {
      \tl_if_empty:nF { #2 }
       {
        \bool_if:NTF \l_@@_role_update_bool
         {
          \tl_if_empty:nTF {#5}
            {
              \prop_get:NnN \g_@@_role_tags_class_prop  {#3}\l_@@_tmpa_tl
              \quark_if_no_value:NT \l_@@_tmpa_tl
                {
                  \tl_set:Nn\l_@@_tmpa_tl{--UNKNOWN--}
                }
            }
            {
              \tl_set:Nn \l_@@_tmpa_tl {#5}
            }
          \@@_role_alloctag:nno {#2} {#1} { \l_@@_tmpa_tl }
          \tl_if_eq:nnF {#2}{#3}
           {
            \@@_role_add_tag:nn {#2}{#3}
           }
          \@@_prop_gput:cnn {g_@@_role_NS_#1_prop}  {#2}{{#3}{}}
         }
         {
           \@@_prop_gput:cnn {g_@@_role_NS_#1_prop}  {#2}{{#3}{}}
           \prop_gput:cnn {g_@@_role_NS_#1_class_prop}  {#2}{--UNUSED--}
         }
       }
    }
 }
 {
   \cs_new_protected:Npn \@@_role_read_namespace_line:nw #1#2,#3,#4,#5,#6\q_stop %
    % #1 NS, #2 tag, #3 rolemap, #4 NS rolemap #5 type
    {
      \tl_if_empty:nF {#2}
       {
        \tl_if_empty:nTF {#5}
         {
           \prop_get:cnN { g_@@_role_NS_#4_class_prop } {#3}\l_@@_tmpa_tl
           \quark_if_no_value:NT \l_@@_tmpa_tl
             {
               \tl_set:Nn\l_@@_tmpa_tl{--UNKNOWN--}
             }
         }
         {
           \tl_set:Nn \l_@@_tmpa_tl {#5}
         }
        \@@_role_alloctag:nno {#2} {#1} { \l_@@_tmpa_tl }
        \bool_lazy_and:nnT
           { ! \tl_if_empty_p:n {#3} }{! \str_if_eq_p:nn {#1}{pdf2}}
           {
            \@@_role_add_tag:nnnn {#2}{#1}{#3}{#4}
           }
        \@@_prop_gput:cnn {g_@@_role_NS_#1_prop}  {#2}{{#3}{#4}}
       }
    }
 }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_role_read_namespace:nn}
% This command reads a namespace file in the format
% tagpdf-ns-XX.def
%    \begin{macrocode}
\cs_new_protected:Npn \@@_role_read_namespace:nn #1 #2 %name of namespace #2 name of file
  {
    \prop_if_exist:cF {g_@@_role_NS_#1_prop}
      { \msg_warning:nnn {tag}{namespace-unknown}{#1} }
    \file_if_exist:nTF { tagpdf-ns-#2.def }
     {
       \ior_open:Nn \g_tmpa_ior {tagpdf-ns-#2.def}
       \msg_info:nnn {tag}{read-namespace}{#2}
       \ior_map_inline:Nn \g_tmpa_ior
         {
           \@@_role_read_namespace_line:nw {#1} ##1,,,,\q_stop
         }
       \ior_close:N\g_tmpa_ior
     }
     {
      \msg_info:nnn{tag}{namespace-missing}{#2}
     }
  }

%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_role_read_namespace:n}
% This command reads the default namespace file.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_role_read_namespace:n #1 %name of namespace
  {
    \@@_role_read_namespace:nn {#1}{#1}
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Reading the default data}
% The order is important as we want pdf2 and latex as default: if two
% namespace define the same tag, the last one defines which one is used
% if the namespace is not explicitly given.
%    \begin{macrocode}
\@@_role_read_namespace:n {pdf}
\@@_role_read_namespace:n {pdf2}
\@@_role_read_namespace:n {mathml}
%    \end{macrocode}
% in pdf 1.7 the following namespaces should only store
% the settings for later use:
%    \begin{macrocode}
\bool_set_false:N\l_@@_role_update_bool
\@@_role_read_namespace:n {latex-book}
\bool_set_true:N\l_@@_role_update_bool
\@@_role_read_namespace:n {latex}
\@@_role_read_namespace:nn {latex} {latex-lab}
\@@_role_read_namespace:n {pdf}
\@@_role_read_namespace:n {pdf2}
%    \end{macrocode}
%
% But is the class provides a \cs{chapter} command then we switch
%    \begin{macrocode}
\pdf_version_compare:NnTF < {2.0}
  {
    \hook_gput_code:nnn {begindocument}{tagpdf}
      {
        \bool_lazy_and:nnT
         {
           \cs_if_exist_p:N \chapter
         }
         {
           \cs_if_exist_p:N \c@chapter
         }
         {
           \prop_map_inline:cn{g_@@_role_NS_latex-book_prop}
             {
               \@@_role_add_tag:ne {#1}{\use_i:nn #2\c_empty_tl\c_empty_tl}
             }
         }
      }
  }
  {
    \hook_gput_code:nnn {begindocument}{tagpdf}
      {
        \bool_lazy_and:nnT
         {
           \cs_if_exist_p:N \chapter
         }
         {
           \cs_if_exist_p:N \c@chapter
         }
         {
           \prop_map_inline:cn{g_@@_role_NS_latex-book_prop}
             {
               \prop_gput:Nnn \g_@@_role_tags_NS_prop    { #1 }{ latex-book }
               \@@_prop_gput:Nne
                \g_@@_role_rolemap_prop {#1}{\use_i:nn  #2\c_empty_tl\c_empty_tl}
             }
         }
      }
  }
%    \end{macrocode}
% \subsection{Parent-child rules}
% PDF define various rules about which tag can be a child of another tag.
% The following code implements the matrix to allow to use it in tests.
% \begin{variable}{\g_@@_role_parent_child_intarray}
% This intarray will store the rule as a number. For parent nm and child ij
% (n,m,i,j digits) the rule is at position nmij. As we have around 56 tags,
% we need roughly a size 6000.
%    \begin{macrocode}
\intarray_new:Nn \g_@@_role_parent_child_intarray {6000}
%    \end{macrocode}
% \end{variable}
% \begin{macro}{\c_@@_role_rules_prop,\c_@@_role_rules_num_prop}
% These two properties map the rule strings to numbers and back.
% There are in tagpdf-data.dtx near the csv files for easier maintenance.
% \end{macro}
%
% \begin{macro}{\@@_store_parent_child_rule:nnn}
% The helper command is used to store the rule.
% It assumes that parent and child are given as 2-digit number!
%    \begin{macrocode}
\sys_if_engine_luatex:TF
{
  \cs_new_protected:Npn \@@_store_parent_child_rule:nnn #1 #2 #3 % num parent, num child, #3 string
    {
      \prop_get:NeNTF \c_@@_role_rules_prop{#3} \l_@@_tmp_unused_tl
        {
          \intarray_gset:Nnn \g_@@_role_parent_child_intarray
           { #1#2 }{0\l_@@_tmp_unused_tl}
          \lua_now:e
           {
            ltx.@@.role.matrix[#1] = ltx.@@.role.matrix[#1] or {}
            ltx.@@.role.matrix[#1][#2] = 0\l_@@_tmp_unused_tl
           }
        }
        {
          \intarray_gset:Nnn \g_@@_role_parent_child_intarray
           { #1#2 }{0}
          \lua_now:e
           {
            ltx.@@.role.matrix[#1] = ltx.@@.role.matrix[#1] or {}
            ltx.@@.role.matrix[#1][#2] = 0
           }
        }
    }
}
{
  \cs_new_protected:Npn \@@_store_parent_child_rule:nnn #1 #2 #3 % num parent, num child, #3 string
    {
      \prop_get:NeNTF \c_@@_role_rules_prop{#3} \l_@@_tmp_unused_tl
        {
          \intarray_gset:Nnn \g_@@_role_parent_child_intarray
           { #1#2 }{0\l_@@_tmp_unused_tl}
        }
        {
          \intarray_gset:Nnn \g_@@_role_parent_child_intarray
           { #1#2 }{0}
        }
    }
}
%    \end{macrocode}
% \end{macro}
%
% \subsubsection{Reading in the csv-files}
% This counter will be used to identify the first (non-comment) line
%    \begin{macrocode}
\int_zero:N  \l_@@_tmpa_int
%    \end{macrocode}
% Open the file depending on the PDF version
%    \begin{macrocode}
\pdf_version_compare:NnTF < {2.0}
  {
    \ior_open:Nn \g_tmpa_ior {tagpdf-parent-child.csv}
  }
  {
    \ior_open:Nn \g_tmpa_ior {tagpdf-parent-child-2.csv}
  }
%    \end{macrocode}

%  Now the main loop over the file
%    \begin{macrocode}
\ior_map_inline:Nn \g_tmpa_ior
  {
%    \end{macrocode}
% ignore lines containing only comments
%    \begin{macrocode}
    \tl_if_empty:nF{#1}
      {
%    \end{macrocode}
% count the lines ...
%    \begin{macrocode}
        \int_incr:N\l_@@_tmpa_int
%    \end{macrocode}
%  put the line into a seq. Attention! empty cells are dropped.
%    \begin{macrocode}
        \seq_set_from_clist:Nn\l_@@_tmpa_seq { #1 }
        \int_compare:nNnTF {\l_@@_tmpa_int}=1
%    \end{macrocode}
% This handles the header line. It gives the tags 2-digit numbers.
%
%    \begin{macrocode}
          {
            \seq_map_indexed_inline:Nn \l_@@_tmpa_seq
              {
                \prop_gput:Nne\g_@@_role_index_prop
                  {##2}
                  {\int_compare:nNnT{##1}<{10}{0}##1}
              }
          }
%    \end{macrocode}
% now the data lines.
%    \begin{macrocode}
         {
           \seq_set_from_clist:Nn\l_@@_tmpa_seq { #1 }
%    \end{macrocode}
% get the name of the child tag from the first column
%    \begin{macrocode}
           \seq_pop_left:NN\l_@@_tmpa_seq\l_@@_tmpa_tl
%    \end{macrocode}
% get the number of the child, and store it in  \cs{l_@@_tmpb_tl}
%    \begin{macrocode}
           \prop_get:NoN \g_@@_role_index_prop { \l_@@_tmpa_tl } \l_@@_tmpb_tl
%    \end{macrocode}
% remove column 2+3
%    \begin{macrocode}
           \seq_pop_left:NN\l_@@_tmpa_seq\l_@@_tmpa_tl
           \seq_pop_left:NN\l_@@_tmpa_seq\l_@@_tmpa_tl
%    \end{macrocode}
% Now map over the rest. The index \verb+##1+ gives us the
% number of the parent, \verb+##2+ is the data.
%    \begin{macrocode}
           \seq_map_indexed_inline:Nn \l_@@_tmpa_seq
             {
               \exp_args:Nne
               \@@_store_parent_child_rule:nnn {##1}{\l_@@_tmpb_tl}{ ##2 }
             }
         }
      }
  }
%    \end{macrocode}
% close the read handle.
%    \begin{macrocode}
\ior_close:N\g_tmpa_ior
%    \end{macrocode}
% The Root,
% Hn and mathml tags are special and need to be added explicitly
%    \begin{macrocode}
\prop_get:NnN\g_@@_role_index_prop{StructTreeRoot}\l_@@_tmpa_tl
\prop_gput:Nne\g_@@_role_index_prop{Root}{\l_@@_tmpa_tl}
\prop_get:NnN\g_@@_role_index_prop{Hn}\l_@@_tmpa_tl
\pdf_version_compare:NnTF < {2.0}
  {
    \int_step_inline:nn{6}
      {
        \prop_gput:Nne\g_@@_role_index_prop{H#1}{\l_@@_tmpa_tl}
      }
  }
  {
    \int_step_inline:nn{10}
      {
        \prop_gput:Nne\g_@@_role_index_prop{H#1}{\l_@@_tmpa_tl}
      }
%    \end{macrocode}
% all mathml tags are currently handled identically with the exception of
% math and mtext
%    \begin{macrocode}
    \prop_get:NnN\g_@@_role_index_prop {mathml}\l_@@_tmpa_tl
    \prop_get:NnN\g_@@_role_index_prop {math}\l_@@_tmpb_tl
    \prop_get:NnN\g_@@_role_index_prop {mtext}\l_@@_tmpc_tl
    \prop_map_inline:Nn \g_@@_role_NS_mathml_prop
      {
        \prop_gput:Nno\g_@@_role_index_prop {#1} {\l_@@_tmpa_tl}
      }
    \prop_gput:Nno\g_@@_role_index_prop{math}{\l_@@_tmpb_tl}
    \prop_gput:Nno\g_@@_role_index_prop{mtext}{\l_@@_tmpc_tl}
  }
\sys_if_engine_luatex:T
 {
   \prop_map_inline:Nn\g_@@_role_index_prop
    {
      \lua_now:e { ltx.@@.role.index['#1']=#2 }
    }
 }
%    \end{macrocode}
%
% \subsubsection{Retrieving the parent-child rule}
%
%
% \begin{macro}{\@@_role_get_parent_child_rule:nnN}
%  This command retrieves the rule (as a number) and stores it in the tl-var.
%  It assumes that the tags in \#1 and \#2 are standard tags after role mapping 
%  for which a rule exist.  If the parent is one of Part, Div, NonStruct
%  the result can be state 7, which means that a check must be
%  repeated for the \enquote{real parent}. 
%  
%  TODO check temporary variables. Check if the tl-var should be fix.
%    \begin{macrocode}
\tl_new:N \l_@@_parent_child_check_tl
\sys_if_engine_luatex:TF
 {
   \cs_new_protected:Npn \@@_role_get_parent_child_rule:nnN #1 #2 #3
     % #1 parent (string, standard tag after rolemapping!) 
     % #2 child (string, standard tag after rolemapping!) 
     % #3 tl for state
     {
       \tl_set:Ne#3
         {
           \lua_now:e{tex.print(\int_use:N\c_document_cctab,ltx.@@.func.role_get_parent_child_rule('#1','#2'))}
         }
%    \end{macrocode}
% Debugging messages, this can perhaps go into debug mode.
%    \begin{macrocode}
         \int_compare:nNnT {\l_@@_loglevel_int} > { 0 }
           {
             \prop_get:NoNF\c_@@_role_rules_num_prop {#3} \l_@@_tmpa_tl
               {
                 \tl_set:Nn \l_@@_tmpa_tl {unknown}
               }
             \tl_set:Nn \l_@@_tmpb_tl {#1}
             \msg_note:nneee
               { tag }
               { role-parent-child-result }
               { #1 }
               { #2 }
               {
                 #3~(='\l_@@_tmpa_tl')
               }
           }
        \int_compare:nNnT {#3} = { 0 }
         {            
          \msg_warning:nneee
            { tag }
            {role-parent-child-result}
            { #1 }
            { #2 }
            { unknown! }
         }         
         
     }
 }
 {
   \cs_new_protected:Npn \@@_role_get_parent_child_rule:nnN #1 #2 #3
     % #1 parent (string, standard tag after rolemapping) 
     % #2 child (string, standard tag after rolemapping) 
     % #3 tl for state
     {
%    \end{macrocode}
%
%    \begin{macrocode}
       \prop_get:NnN \g_@@_role_index_prop{#1}\l_@@_tmpa_tl
       \prop_get:NnN \g_@@_role_index_prop{#2}\l_@@_tmpb_tl
       \bool_lazy_and:nnTF
         { ! \quark_if_no_value_p:N \l_@@_tmpa_tl }
         { ! \quark_if_no_value_p:N \l_@@_tmpb_tl }
         {
%    \end{macrocode}
% Get the rule from the intarray
%    \begin{macrocode}
            \tl_set:Ne#3
              {
                \intarray_item:Nn
                 \g_@@_role_parent_child_intarray
                 {\l_@@_tmpa_tl\l_@@_tmpb_tl}
              }
           }
           {
             \tl_set:Nn#3 {0}
           }              
%    \end{macrocode}
% Debugging messages, this can perhaps go into debug mode.
%    \begin{macrocode}
         \int_compare:nNnT {\l_@@_loglevel_int} > { 0 }
           {
             \prop_get:NoNF\c_@@_role_rules_num_prop {#3} \l_@@_tmpa_tl
               {
                 \tl_set:Nn \l_@@_tmpa_tl {unknown}
               }
             \tl_set:Nn \l_@@_tmpb_tl {#1}
             \msg_note:nneee
               { tag }
               { role-parent-child-result }
               { #1 }
               { #2 }
               {
                 #3~(='\l_@@_tmpa_tl')
               }
           }
        \int_compare:nNnT {#3} = { 0 }
         {            
          \msg_warning:nneee
            { tag }
            {role-parent-child-result}
            { #1 }
            { #2 }
            { unknown! }
         }
     }
  }
\cs_generate_variant:Nn\@@_role_get_parent_child_rule:nnN {ooN}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_role_check_parent_child:nnnnN}
% This command rolemaps its arguments and then
% calls \cs{@@_role_get_parent_child_rule:nnN} to retrieve the
% parent-child rule between both.
% It does not try to resolve inheritation rules of \texttt{Part},
% \texttt{Div} and \texttt{NonStruct} but
% instead gives back the state 7. It is then the task
% of the caller command to find the real parent and run the 
% check again. 
% In pdf 2.0 the name spaces of the tags are relevant, so we
% have arguments for them, but in pdf <2.0 they are ignored and can
% be left empty.
%    \begin{macrocode}
\pdf_version_compare:NnTF < {2.0}
  {
   \cs_new_protected:Npn \@@_role_check_parent_child:nnnnN #1 #2 #3 #4 #5
    % #1 parent tag,% not necessarly rolemapped, but often the case
    % #2 NS (empty in pdf 1.x)
    % #3 child tag, % not necessarly rolemapped, but often the case
    % #4 NS (empty in pdf 1.x) 
    % #5 tl var: to give the result back. 
     {
%    \end{macrocode}
% get the standard tags through rolemapping if needed
% at first the parent
%    \begin{macrocode}
       \prop_get:NnNTF \g_@@_role_index_prop {#1}\l_@@_tmpa_tl
         {
           \tl_set:Nn \l_@@_tmpa_tl {#1}
         }
         {
           \prop_get:NnNF \g_@@_role_rolemap_prop {#1}\l_@@_tmpa_tl
             {
               \tl_set:Nn \l_@@_tmpa_tl {\q_no_value}
             }
         }
%    \end{macrocode}
% now the child
%    \begin{macrocode}
       \prop_get:NnNTF \g_@@_role_index_prop {#3}\l_@@_tmpb_tl
         {
           \tl_set:Nn \l_@@_tmpb_tl {#3}
         }
         {
           \prop_get:NnNF \g_@@_role_rolemap_prop {#3}\l_@@_tmpb_tl
             {
               \tl_set:Nn \l_@@_tmpb_tl {\q_no_value}
             }
         }
%    \end{macrocode}
% if we got tags for parent and child we call the checking command
%    \begin{macrocode}
       \bool_lazy_and:nnTF
         { ! \quark_if_no_value_p:N \l_@@_tmpa_tl }
         { ! \quark_if_no_value_p:N \l_@@_tmpb_tl }
         {
           \@@_role_get_parent_child_rule:ooN
             { \l_@@_tmpa_tl}
             { \l_@@_tmpb_tl}
             #5
         }
         {
           \tl_set:Nn #5 {0}
           \msg_warning:nneee
            { tag }
            {role-parent-child-result}
            { #1 }
            { #3 }
            { unknown! }
         }
     }
  }
%    \end{macrocode}
% and now the pdf 2.0 version
%    \begin{macrocode}
  {
   \cs_new_protected:Npn \@@_role_check_parent_child:nnnnN #1 #2 #3 #4 #5 %tag,NS,tag,NS, tl var
      {
 %    \end{macrocode}
%  If the namespace is empty, we assume a standard tag,
%  otherwise we retrieve the rolemapping from the namespace
%    \begin{macrocode}
        \tl_if_empty:nTF  {#2}
          {
            \tl_set:Nn \l_@@_tmpa_tl {#1}
          }
          {
            \prop_if_exist:cTF { g_@@_role_NS_#2_prop }
              {
               \prop_get:cnNTF
                  { g_@@_role_NS_#2_prop }
                  {#1}
                  \l_@@_tmpa_tl
                  {
                    \tl_set:Ne \l_@@_tmpa_tl {\tl_head:N\l_@@_tmpa_tl}
                    \tl_if_empty:NT\l_@@_tmpa_tl
                      {
                        \tl_set:Nn \l_@@_tmpa_tl {#1}
                      }
                  }
                  {
                    \tl_set:Nn \l_@@_tmpa_tl {\q_no_value}
                  }
              }
              {
                \msg_warning:nnn { tag } {role-unknown-NS} { #2}
                \tl_set:Nn \l_@@_tmpa_tl {\q_no_value}
              }
          }
%    \end{macrocode}
% and the same for the child
%  If the namespace is empty, we assume a standard tag,
%  otherwise we retrieve the rolemapping from the namespace
%    \begin{macrocode}
        \tl_if_empty:nTF  {#4}
          {
            \tl_set:Nn \l_@@_tmpb_tl {#3}
          }
          {
            \prop_if_exist:cTF { g_@@_role_NS_#4_prop }
              {
               \prop_get:cnNTF
                 { g_@@_role_NS_#4_prop }
                 {#3}
                 \l_@@_tmpb_tl
                 {
                   \tl_set:Ne \l_@@_tmpb_tl { \tl_head:N\l_@@_tmpb_tl }
                   \tl_if_empty:NT\l_@@_tmpb_tl
                     {
                       \tl_set:Nn \l_@@_tmpb_tl {#3}
                     }
                 }
                 {
                   \tl_set:Nn \l_@@_tmpb_tl {\q_no_value}
                 }
              }
              {
                \msg_warning:nnn { tag } {role-unknown-NS} { #4}
                \tl_set:Nn \l_@@_tmpb_tl {\q_no_value}
              }
          }
%    \end{macrocode}
% and now get the relation
%    \begin{macrocode}
       \bool_lazy_and:nnTF
         { ! \quark_if_no_value_p:N \l_@@_tmpa_tl }
         { ! \quark_if_no_value_p:N \l_@@_tmpb_tl }
         {
           \@@_role_get_parent_child_rule:ooN
             { \l_@@_tmpa_tl }
             { \l_@@_tmpb_tl }
             #5
         }
         {
           \tl_set:Nn #5 {0}
           \msg_warning:nneee
            { tag }
            {role-parent-child-result}
            { #2 : #1 }
            { #4 : #3 }
            { unknown! }
         }
     }
  }
\cs_generate_variant:Nn\@@_role_check_parent_child:nnnnN {oonnN,ooooN}
%</package>
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[TF]{\tag_check_child:nn}
%    \begin{macrocode}
%<base>\prg_new_protected_conditional:Npnn \tag_check_child:nn #1 #2 {T,F,TF}{\prg_return_true:}
%<*package>
\prg_set_protected_conditional:Npnn \tag_check_child:nn #1 #2 {T,F,TF} %#1 tag, #2 NS
 {
   \seq_get:NN\g_@@_struct_stack_seq\l_@@_tmpa_tl
   \@@_struct_get_role:enNN
      {\l_@@_tmpa_tl}
      {rolemap}
      \l_@@_get_parent_tmpa_tl
      \l_@@_get_parent_tmpb_tl
   \@@_role_check_parent_child:oonnN
      { \l_@@_get_parent_tmpa_tl }
      { \l_@@_get_parent_tmpb_tl }
      {#1}{#2}
      \l_@@_parent_child_check_tl
   \int_compare:nNnT {\l_@@_parent_child_check_tl} = { \c_@@_role_rule_checkparent_tl }
      {
        \seq_get:NN\g_@@_struct_stack_seq\l_@@_tmpa_tl
        \@@_struct_get_role:enNN
           {\l_@@_tmpa_tl}
           {parentrole}
           \l_@@_get_parent_tmpa_tl
           \l_@@_get_parent_tmpb_tl
        \@@_role_check_parent_child:oonnN
           { \l_@@_get_parent_tmpa_tl }
           { \l_@@_get_parent_tmpb_tl }
           {#1}{#2}
           \l_@@_parent_child_check_tl
      }
   \int_compare:nNnTF {  \l_@@_parent_child_check_tl } < {0}
      {\prg_return_false:}
      {\prg_return_true:}
 }
%    \end{macrocode}
% \end{macro}
%
%
% \subsection{Key-val user interface}
%  The user interface uses the key |add-new-tag|, which takes either a
%  keyval list as argument, or a tag/role.
%  \begin{macro}
%    {
%     tag (rolemap-key),
%     tag-namespace (rolemap-key),
%     role (rolemap-key),
%     role-namespace (rolemap-key),
%     role/new-tag (setup-key),
%     add-new-tag (deprecated)}
%    \begin{macrocode}
\keys_define:nn { @@ / tag-role }
  {
    ,tag .tl_set:N = \l_@@_role_tag_tmpa_tl
    ,tag-namespace  .tl_set:N = \l_@@_role_tag_namespace_tmpa_tl
    ,role .tl_set:N = \l_@@_role_role_tmpa_tl
    ,role-namespace .tl_set:N = \l_@@_role_role_namespace_tmpa_tl
  }

\keys_define:nn { @@ / setup }
  {
     role/mathml-tags .bool_gset:N = \g_@@_role_add_mathml_bool
    ,role/new-tag .code:n =
     {
       \keys_set_known:nnnN
         {@@/tag-role}
         {
           tag-namespace=user,
           role-namespace=, %so that we can test for it.
          #1
         }{@@/tag-role}\l_@@_tmpa_tl
       \tl_if_empty:NF \l_@@_tmpa_tl
         {
           \exp_args:NNno \seq_set_split:Nnn \l_@@_tmpa_seq { / } {\l_@@_tmpa_tl/}
           \tl_set:Ne \l_@@_role_tag_tmpa_tl  { \seq_item:Nn \l_@@_tmpa_seq {1} }
           \tl_set:Ne \l_@@_role_role_tmpa_tl { \seq_item:Nn \l_@@_tmpa_seq {2} }
         }
      \tl_if_empty:NT \l_@@_role_role_namespace_tmpa_tl
         {
           \prop_get:NoNTF
             \g_@@_role_tags_NS_prop
             { \l_@@_role_role_tmpa_tl }
             \l_@@_role_role_namespace_tmpa_tl
             {
                \prop_get:NoNF
                  \g_@@_role_NS_prop
                  { \l_@@_role_role_namespace_tmpa_tl }
                  \l_@@_tmp_unused_tl
                  {
                    \tl_set:Nn \l_@@_role_role_namespace_tmpa_tl {user}
                  }
             }
             {
               \tl_set:Nn \l_@@_role_role_namespace_tmpa_tl {user}
             }
         }
      \pdf_version_compare:NnTF < {2.0}
       {
        %TODO add check for emptyness?
          \@@_role_add_tag:oo
             { \l_@@_role_tag_tmpa_tl }
             { \l_@@_role_role_tmpa_tl }
       }
       {
         \@@_role_add_tag:oooo
          { \l_@@_role_tag_tmpa_tl }
          { \l_@@_role_tag_namespace_tmpa_tl }
          { \l_@@_role_role_tmpa_tl }
          { \l_@@_role_role_namespace_tmpa_tl }
       }
    }
   ,role/map-tags .choice:
   ,role/map-tags/false .code:n = { \socket_assign_plug:nn { tag/struct/tag } {latex-tags} }
   ,role/map-tags/pdf   .code:n = { \socket_assign_plug:nn { tag/struct/tag } {pdf-tags} }
%    \end{macrocode}
% \changes{0.99l}{2025-01-12}{allow setting of user-NS}
%    \begin{macrocode}
   ,role/user-NS .code:n =
     {
      \pdf_version_compare:NnF < {2.0}
       {
         \pdf_string_from_unicode:nnN{utf8/string}{https://www.latex-project.org/ns/local/#1}\l_@@_tmpa_str
          \tl_if_empty:NF \l_@@_tmpa_str
            {
              \pdfdict_gput:nne
                {g_@@_role/Namespace_user_dict}
                {NS}
                {\l_@@_tmpa_str}
            }
       }
     }
%    \end{macrocode}
% deprecated names
%    \begin{macrocode}
   , mathml-tags .bool_gset:N = \g_@@_role_add_mathml_bool
   , add-new-tag .meta:n = {role/new-tag={#1}}
  }
%</package>
%    \end{macrocode}
% \end{macro}
% \end{implementation}
% \PrintIndex
