% \iffalse meta-comment
%
%% File: latex-lab-table.dtx (C) Copyright 2023-2025 LaTeX Project
%
% 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
%
%
% The latex-lab bundle is developed in the LaTeX2e GitHub.
% Issues may be reported at
%
%    https://github.com/latex3/latex2e/issues
%
\def\ltlabtbldate{2026-01-16}
\def\ltlabtblversion{0.85s}
%<*driver>
\DocumentMetadata{tagging=on,pdfstandard=ua-2}
\documentclass[kernel]{l3in2edoc}

\EnableCrossrefs
\CodelineIndex
\begin{document}
  \DocInput{latex-lab-table.dtx}
\end{document}
%</driver>
%
% \fi
%
%
%
%
% \title{The \textsf{latex-lab-table} package\\
%        Changes related to the tagging of tables}
% \author{Frank \& Ulrike, \LaTeX{} Project\thanks{Initial implementation done by Frank Mittelbach}}
% \date{v\ltlabtblversion\ \ltlabtbldate}
%
% \maketitle
%
% \newcommand{\xt}[1]{\textsl{\textsf{#1}}}
% \newcommand{\TODO}[1]{\textbf{[TODO:} #1\textbf{]}}
% \newcommand{\docclass}{document class \marginpar{\raggedright document class
% customizations}}
%
%
% \begin{abstract}
%   The following code implements a first draft for the tagging of
%   tables. It still has a large number of limitations and restrictions!
% \end{abstract}
%
% \tableofcontents
%
% \section{Documentation}
%
% In \LaTeX{} the word \texttt{table} is used as the name of the float environment that can contain
% a data table\footnote{But it does not really have to, you can put
% other material into such environments.} along with a caption and some additional text. The environments for
% actual data tables have various names like \texttt{tabular}, \texttt{tabular*},
% \texttt{tabularx} and \texttt{longtable}---the last should not be used inside a float
% and supports its own caption command.
%
% In this documentation \enquote{table} always means such data tables and not the float
% environment.
%
% Tagging of tables is on one side doesn't look very difficult: one only has to
% surround rows and cells by TR and TH or TD structures.
% But there are difficulties:
% \begin{itemize}
% \item
%   One is that over the years various packages related to tables have
%   been written that all change some of the internals. Inserting the
%   tagging commands and testing all the variants and various nestings
%   is not trivial.
%
% \item
%   The other difficulty is that most of the existing environments to
%   create tables do not know the concept of headers as a semantic
%   structures.
%
% \item
%   Headers are only produced through visual formatting, e.g., by
%   making them bold or by underlying some color. But accessible
%   tables need headers (and the PDF/UA standards requires them) and
%   this means that additional syntax to declare headers (when they
%   can't be guessed) must be developed. This is still an area for
%   research.
% \end{itemize}
%
% Right now, this module therefore does implement only some basic
% support for the tagging of tables. A list of the known limitations
% is shown below.
%
% \subsection{Loading}
% The module is not loaded automatically (i.e., not yet integrated
% into any \texttt{phase-XX})  and by itself it doesn't activate
% tagging. For experimenting with table tagging it is therefore best
% to load it in combination
% with phase-III in \cs{DocumentMetadata}, i.e.:
%
% \begin{verbatim}
% \DocumentMetadata{testphase={phase-III,table}}
% \end{verbatim}
%
% It will then automatically tag all table environments it already supports with
% the exception of tables in the header and footer of the page (where tagging is disabled).
% Such tables can be nested. 
% 
% Inside cells the automatic tagging of paragraphs is disabled with the exception of
% p/m/b-type cells. 
%
% Rows do not need to contain a full number of \&, missing cells are automatically
% added with an empty TD-structure.
% 
% You should not insert meaningful text with \verb+!{...}+ or \verb+@{...}+ or \cs{noalign}
% between the rows or columns of the table.
% With pdflatex such text will be unmarked, with lualatex it will be marked as artifact.
% Either case means that it will be currently ignored in the
% structure.\footnote{While it is theoretically possible
% to collect such text and move it into a structure it would require manual markup
% from the author to clarify where this text belongs too.}
% 
% As mentioned below the \pkg{colortbl} doesn't work fully with the tagging,
% but when it does, colors inside the table are currently ignored. If such a color
% has a semantic meaning (like \enquote{important value}) this meaning will be lost.
%
% \subsection{Untagged and presentation tables}
% If a table should not be tagged as table, for example because it is merely used
% as a layout to ensure that the content is properly aligned
% or because it is a not yet (fully) supported table structure, 
% the tagging can be disabled with
% \verb|\tagpdfsetup{table/tagging=false}| or changed with 
% \verb|\tagpdfsetup{table/tagging=presentation}| or \verb|\tagpdfsetup{table/tagging=div}|%
% \footnote{The key has been renamed. The old name `table-tagging` still works but is 
% deprecated. The value \texttt{presentation} refers to the ARIA role \enquote{presentation}.}
% The first option disables the table tagging code and the content of the tabular
% is then treated more or less like running text. This works ok for simple tables using
% only hmode-cells (l/c/r) with normal text content, but fails if the table uses vmode-cells
% (p/m/b). In such cases the second option works better: it keeps the tagging code active,
% but adds an ARIA-role as attribute to indicate that this is a presentation table. 
% When deriving to html this gives the best result
% as it keeps the layout.
% The last option changes the tag names and creates simply a number of DIV structures. 
% Both options also
% (re)set the \texttt{table/header-rows} and \texttt{table/header-columns} keys to empty. 
% 
% To reset to the normal tagging in the current group (e.g. inside a presentation
% table) one can use \verb|\tagpdfsetup{table/tagging=true}| (and if needed reenable the headers).
% 
%
% \subsection{Header rows and columns}
% There is some basic support\footnote{This is not meant to be the
% final interface, though.} for headers. With\footnote{The old key name \texttt{table-header-rows} still
% works but is deprecated.}
% \begin{quote}
%   \verb|\tagpdfsetup{table/header-rows={|\meta{list of row numbers}\verb|}}|\\
%   \verb|\tagpdfsetup{table/header-columns={|\meta{list of column numbers}\verb|}}|
% \end{quote}
% you can
% declare which (absolute) row and column numbers should be tagged as header rows and header columns.
% It applies to all tables until it is changed to a different list of row or
% columns numbers or undone by setting
% the keys to \meta{empty}.
% A row or column number can be
% negative, then the counting starts from the end of the table. 
% In a \env{longtable} the code will currently use the \cs{endhead} or
% \cs{endfirsthead} rows as header if one of these commands has been
% used and in that case the code
% ignores a \texttt{table/header-rows} setting.
%
% \subsection{Spanning of cells}
% 
% \cs{multicolumn} is supported out of the box and will create a structure with a 
% suitable \texttt{ColSpan} attribute\footnote{The code uses actually an attribute \emph{class}. 
% The validator PAC doesn't handle this correctly currently and complains about a missing attribute.}
% 
% For cells spanning rows some preliminary support exists\footnote{The interface is bound to change!}:
% If you add 
% \begin{quote}
%   \verb|\tagpdfsetup{table/multirow={|\meta{number of rows}\verb|}|
% \end{quote} 
% to a cell the code will add the suitable \texttt{RowSpan} attribute and suppress the tagging of affected
% cells in the following rows. This will also work if the current cell is a \cs{multicolumn}, then
% the cell will span both rows and columns. It is the duty of the author to
% ensure that all cells below and covered by a \texttt{RowSpan} are empty! 
% The code neither checks that nor does
% it tries to suppress content.  
% 
% Feedback and problems with the code can be reported at
% \url{https://github.com/latex3/tagging-project} either in form of
% explicit issues or as a \enquote{discussion topic}, whatever works best.
%
%
% \section{Limitations}
%
% \begin{itemize}
% \item The code loads the \pkg{array} package and so does not work without it (that is
% not really a limitation, but can affect existing tables).
%
% \item It supports only a restricted number of tables types. Currently
% \env{tabular}, \env{tabular*}, \env{tabularx}, and \env{longtable}.
%
% \item the \env{array} environment is assumed to be part of math and tagging as a table is disabled for
% it.
%
% \item Row spans can only be added manually (the \pkg{multirow} package is untested).
%
% \item The \pkg{colortbl} package breaks tagging if there are nested tables. 
% 
% \item The \pkg{tabularray} package use a completed different
% method to create tables and will not be supported by this code.
% 
% \item The \pkg{nicematrix} package is currently incompatible. 
% 
% \item Most other packages related to tables in \LaTeX{} are not fully tested,
% that includes packages that change rules like \pkg{booktabs}, \pkg{hhline},
% \pkg{arydshln}, \pkg{hvdashln}. Some problems have been resolved, either
% in the packages or through a firstaid which can be loaded the \verb+testphase=firstaid+.
%
% \item \env{longtable} currently only works with lualatex.
%  With other engines it breaks as its output
%  routine clashes with the code which closes open MC-chunks at pagebreaks and
%  if this is resolved there will probably be problems with the head and foot boxes
%  (but this can't be tested currently).
%
% \item Not every table should be tagged as a Table structure, often they are
% only used as layout help, e.g. to align authors in a title pages. In such uses
% the tagging of the table must be deactivated with \verb|\tagpdfsetup{table/tagging=false}|
% or changed with \verb|\tagpdfsetup{table/tagging=presentation}| or 
% \verb|\tagpdfsetup{table/tagging=div}|.
%
% \item Only simple header rows and columns are currently supported. Complex headers with
% subheaders will be handled later as that needs some syntax changes. Tables
% with more than one header row or column are probably not pdf/UA as the headers array in
% the cells is missing.
%
% \item A \pkg{longtable} \cs{caption} is currently simply formatted as a multicolumn and
% not tagged as a \texttt{Caption} structure.
%
% \item The \pkg{caption} package will quite probably break the \pkg{longtable} caption.
%
% \item The setup for \pkg{longtable} requires lots of patches to internal \pkg{longtable}
% commands and so can easily break if other packages try to patch \pkg{longtable} too.
% 
% \item The \env{longtable} environment supports footnotes in p-type columns, but it hasn't been
% tested yet if this works also with the tagging code. 
% 
% \item Vertical boxes (\cs{parbox}, \texttt{minipage}, \ldots) inside cells can be problematic. 
%
% \item The code is quite noisy and fills the log with lots of
%   messages.\footnote{Helpful for us at this stage.}
% \end{itemize}
%
%
%
% \section{Introduction}
%
%
% \section{Technical details and problems}
%
%  The implementation has to take care of various details.
%
%  \subsection{TODOs}
%
%  \begin{itemize}
%  \item
%     Test \texttt{array-006-longtable.lvt} and
%     \texttt{array-007-longtable.lvt} have errors with pdftex (para
%    tagging)
%
%  \item
%     Instead of before/after hooks we should add sockets directly
%     into the code.
%
%  \item Debugging code and messages must be improved.
%
%  \item Cells need an \texttt{Headers} array.
%
%  \item Row spans should be supported (but perhaps need syntax support)
%
%  \item Longtable captions should be properly supported.
%
%
%  \item More packages must be tested.
%  \end{itemize}
%
%
% \section{Implementation}
%    \begin{macrocode}
%<@@=tbl>
%<*package>
%    \end{macrocode}
%    \begin{macrocode}
\ProvidesExplPackage {latex-lab-testphase-table} {\ltlabtbldate} {\ltlabtblversion}
  {Code related to the tagging of tables}
%    \end{macrocode}
% This builds on \pkg{array} so we load it by default:
%    \begin{macrocode}
\RequirePackage{array}
%    \end{macrocode}
%
%
% \subsection{Variables}
% \begin{macro}
%  {
%    \l_@@_celltag_tl
%    \l_@@_pcelltag_tl
%   ,\l_@@_rowtag_tl
%   ,\l_@@_table_tl
%   ,\l_@@_cellattribute_tl
%   ,\l_@@_rowattribute_tl
%   ,\l_@@_tmpa_clist
%   ,\l_@@_tmpa_tl
%   ,\l_@@_tmpa_str
%  }
%    This is for the celltag, e.g. TD or TH:
%    \begin{macrocode}
\tl_new:N  \l_@@_celltag_tl
\tl_set:Nn \l_@@_celltag_tl {TD}
\tl_new:N  \l_@@_pcelltag_tl
\tl_set:Nn \l_@@_pcelltag_tl {TD}
%    \end{macrocode}
%    For the rowtag, probably always TR:
%    \begin{macrocode}
\tl_new:N  \l_@@_rowtag_tl
\tl_set:Nn \l_@@_rowtag_tl {TR}
%    \end{macrocode}
%    For the tabletag, probably always Table:
%    \begin{macrocode}
\tl_new:N  \l_@@_tabletag_tl
\tl_set:Nn \l_@@_tabletag_tl {Table}
%    \end{macrocode}
%    And here cell and row attributes:
%    \begin{macrocode}
\tl_new:N  \l_@@_cellattribute_tl
\tl_set:Nn \l_@@_cellattribute_tl {}
\tl_new:N  \l_@@_rowattribute_tl
\tl_set:Nn \l_@@_rowattribute_tl {}
%    \end{macrocode}
% Variable to store cell info like which cell must be skipped when tagging multirows.
%    \begin{macrocode}
\prop_new:N\g_@@_untagged_cells_prop
\prop_new:N\l_@@_saved_untagged_cells_prop
%    \end{macrocode}
% Temp variables
%    \begin{macrocode}
\clist_new:N \l_@@_tmpa_clist
\tl_new:N \l_@@_tmpa_tl
\str_new:N \l_@@_tmpa_str
%    \end{macrocode}
% \end{macro}
%
% \subsection{Tagging support sockets}
%
%
% This are the standard plugs for tagging of cells and rows.
% 
% The following two sockets are defined in lttagging, but we check 
% for their existence until the release 11/2024.
%    \begin{macrocode}
\socket_if_exist:nF {tagsupport/tbl/init/celldata}
 {
   \NewTaggingSocket{tbl/init/celldata}{0}
   \NewTaggingSocket{tbl/restore/celldata}{0}
 }
%    \end{macrocode}
% \begin{plugdecl}{default}
% This socket is used inside \cs{tbl_init_cell_data_for_table} for the
% case that this a nested table in a cell.
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/init/celldata}{default}
  {
    \prop_set_eq:NN\l_@@_saved_untagged_cells_prop \g_@@_untagged_cells_prop
    \prop_gclear:N \g_@@_untagged_cells_prop
  }
\AssignTaggingSocketPlug{tbl/init/celldata}{default}  
%    \end{macrocode}
% \end{plugdecl}

% \begin{plugdecl}{default}
% This socket is used inside \cs{tbl_restore_outer_cell_data:} and restores
% values when a nested table is ended.
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/restore/celldata}{default}
  {
    \prop_gset_eq:NN\g_@@_untagged_cells_prop \l_@@_saved_untagged_cells_prop
  }
\AssignTaggingSocketPlug{tbl/restore/celldata}{default}  
%    \end{macrocode}
% \end{plugdecl}
% 
% \begin{plugdecl}{TD}
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/cell/begin}{TD}
  {
%    \end{macrocode}
%    Next line was previously outside of the plug, so if we want to execute it
%    always even if the noop plug is in force this needs a different solution.
%    \begin{macrocode}
    \@@_show_curr_cell_data:
%    \end{macrocode}
% We test if the cell should be tagged at all.
%    \begin{macrocode}
    \@@_if_tag_cell:nnT {\g_@@_row_int } { \g_@@_col_int }
      {
%    \end{macrocode}
% this sets row headers
%    \begin{macrocode}
        \clist_if_in:NVT \l_@@_header_columns_clist\g_@@_col_int
          { 
            \tl_set:Nn \l_@@_celltag_tl {TH} 
            \tl_set:Ne \l_@@_cellattribute_tl {\l_@@_cellattribute_tl,TH-row}
          }
%    \end{macrocode}
% Here we handle negative value for row headers:
%    \begin{macrocode}
        \tl_set:Ne \l_@@_tmpa_tl 
          {\int_eval:n { \g_@@_col_int - 1 - \g_@@_table_cols_tl }} 
        \clist_if_in:NoT \l_@@_header_columns_clist { \l_@@_tmpa_tl }
          { 
            \tl_set:Nn \l_@@_celltag_tl {TH} 
            \tl_set:Ne \l_@@_cellattribute_tl {\l_@@_cellattribute_tl,TH-row}
          }        
        \tag_struct_begin:n
          {
            tag             =\l_@@_celltag_tl,
            attribute-class ={\l_@@_cellattribute_tl}
          }
        \seq_gput_right:Ne \g_@@_struct_cur_seq { \tag_get:n {struct_num} }
%    \end{macrocode}
%   we store the cells of multicolumns as negative number. This allow to skip them
%   or to use them as needed.
%    \begin{macrocode}
        \int_step_inline:nn { \g_@@_span_tl - 1 }
          {
            \seq_gput_right:Ne \g_@@_struct_cur_seq { -\tag_get:n {struct_num} }
          }
        \tag_mc_begin:n{}
      }  
  }
%    \end{macrocode}
% \end{plugdecl}
%
% \begin{plugdecl}{TD}
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/cell/end}{TD}
  {
    \@@_if_tag_cell:nnT {\g__tbl_row_int } { \g__tbl_col_int }
      {
        \tag_mc_end:
        \tag_struct_end:
      }        
  }
%    \end{macrocode}
% \end{plugdecl}
% 
%    In p-columns we need a slightly different plug which reactivates the
%    paragraph tagging.
%    \changes{0.85s}{2025-08-01}{fix typo celltag to pcelltag, tagging issue 965}
% \begin{plugdecl}{TDpbox}
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/pcell/begin}{TDpbox}
  {
    \@@_show_curr_cell_data:
    \@@_if_tag_cell:nnT {\g__tbl_row_int } { \g__tbl_col_int }
      {
        \clist_if_in:NVT \l_@@_header_columns_clist\g_@@_col_int
          { 
            \tl_set:Nn \l_@@_pcelltag_tl {TH} 
            \tl_set:Ne \l_@@_cellattribute_tl {\l_@@_cellattribute_tl,TH-row}
          }        
        \tag_struct_begin:n
          {
            tag             =\l_@@_pcelltag_tl,
            attribute-class ={\l_@@_cellattribute_tl}
          }
        \seq_gput_right:Ne \g_@@_struct_cur_seq { \tag_get:n {struct_num} }
        \int_step_inline:nn { \g_@@_span_tl - 1 }
          {
            \seq_gput_right:Ne \g_@@_struct_cur_seq { -\tag_get:n {struct_num} }
          }
        \tagpdfparaOn
        \tl_set:Nn \l__tag_para_main_tag_tl {Div}
     }
  }
%    \end{macrocode}
% \end{plugdecl}
% 
% \begin{plugdecl}{TDpbox}
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/pcell/end}{TDpbox}
  {
    \@@_if_tag_cell:nnT {\g_@@_row_int } { \g_@@_col_int }
      { 
        \tag_struct_end: 
        \legacy_if:nT {@endpe}{\par}
        \mode_if_vertical:T{ \tagpdfparaOff }  
      }
  }  
%    \end{macrocode}
% \end{plugdecl}
%
% \begin{plugdecl}{TR}
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/row/begin}{TR}
  {
    \seq_gclear:N \g_@@_struct_cur_seq
    \tag_struct_begin:n
     {
       tag            =\l_@@_rowtag_tl,
       attribute-class=\l_@@_rowattribute_tl
     }
   \seq_gput_right:Ne \g_@@_struct_rows_seq { \tag_get:n {struct_num} }
  }
%    \end{macrocode}
% \end{plugdecl}
%
% \begin{plugdecl}{TR}
% \changes{0.85t}{2025-07-02}{Removed unneeded \cs{tag_if_active:T}}   
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/row/end}{TR}
  {
    \@@_add_missing_cells:
    \seq_gput_right:Ne \g_@@_struct_cells_seq
      {
        \seq_use:Nn \g_@@_struct_cur_seq {,}
      }
    \int_compare:nNnTF { \g_@@_row_int } =
                       { \seq_count:N \g_@@_struct_cells_seq }
      {
        \@@_trace:n
          {==>~
            structure~stored~for~row~\int_use:N\g_@@_row_int :~
            \seq_use:Nn \g_@@_struct_cur_seq {,}
          }
      }
      { \ERRORtbl/row } % should not happen ...
    \tag_struct_end:
  }
%    \end{macrocode}
% \end{plugdecl}
%
% And the plugs for the table as whole. The code can be different for
% normal tables which can also be used inline and nested and
% \enquote{vmode} tables like longtable.
%
%  \begin{macro}{\l__tag_block_flattened_level_int}
%   Count the levels of nested blockenvs starting with the first that
%   is \enquote{flattened}. The counter is defined in lttagging.dtx,
%   but until the next release 11/24 we set it up here too so that we can 
%   use it in the following socket
%    \begin{macrocode}
\int_if_exist:NF \l__tag_block_flattened_level_int
  {
    \int_new:N \l__tag_block_flattened_level_int
  }
%    \end{macrocode}
%  \end{macro}
%  
% \begin{plugdecl}{Table}
%  Inside a TD or TR Part is not allowed as child. For structures that restore
%  paragraph settings we therefore need a special plug that adjust the settings.
%  Currently we set the para main tag to Div. This leads to double Div-structures
%  but flattening the paragraph doesn't work, it errors if there is a list inside. 
%    \begin{macrocode}
\NewTaggingSocketPlug{para/restore}{Table}
  {
    \tl_set:Nn    \l__tag_para_main_tag_tl {Div}
    \tl_set_eq:NN \l__tag_para_tag_tl\l__tag_para_tag_default_tl
    \bool_set_true:N \l__tag_para_bool
    \bool_set_false:N \l__tag_para_flattened_bool
    \int_zero:N \l__tag_block_flattened_level_int
  }
%    \end{macrocode}
% \end{plugdecl}
% 
% \begin{plugdecl}{Table}
%    Inside a table we currently only disable paratagging. We assume
%    that these sockets are called in a group, so there is no
%    need to reenable paratagging.
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/init}{Table}
  {
     \bool_set_false:N \l__tag_para_bool
     \AssignTaggingSocketPlug{para/restore}{Table}
%    \end{macrocode}
%    We also initialize the structure data variables a this point.
%    \begin{macrocode}
     \@@_init_struct_data:
  }
%    \end{macrocode}
% \end{plugdecl}
%
%
% \begin{plugdecl}{Table}
%  This plug will fine tune the structure.
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/finalize}{Table}
  {
    \@@_set_header_rows:
%    \end{macrocode}
%    Similarly, we restore the outer values of the structure data when
%    we leave the table.
%    \begin{macrocode}
    \@@_restore_struct_data:
  }
%    \end{macrocode}
% \end{plugdecl}
%
%
%
%
% \begin{plugdecl}{Table}
%  This plug will initialize the structure in longtable.
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/longtable/init}{Table}
 {
   \seq_gclear:N\g_@@_struct_rows_seq
   \seq_gclear:N\g_@@_struct_cells_seq
   \seq_gclear:N\g_@@_struct_cur_seq
   \seq_gclear:N\g_@@_LT@firsthead_rows_seq
   \seq_gclear:N\g_@@_LT@head_rows_seq
   \seq_gclear:N\g_@@_LT@lastfoot_rows_seq
   \seq_gclear:N\g_@@_LT@foot_rows_seq
 }
%    \end{macrocode}
% \end{plugdecl}
%
%


% \begin{plugdecl}{Table}
%  This plug will fine tune the structure in longtable.
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/longtable/finalize}{Table}
  {
%    \end{macrocode}
%    If neither \cs{endhead} nor \cs{endfirsthead} has been used
%    we use the standard header command:
%    \begin{macrocode}
     \bool_lazy_and:nnTF
       { \seq_if_empty_p:N \g_@@_LT@head_rows_seq  }
       { \seq_if_empty_p:N \g_@@_LT@firsthead_rows_seq }
       { \@@_set_header_rows: }
%    \end{macrocode}
%    Otherwise, if firsthead has not been used we use head. For this
%    we simple retrieve the row numbers and then call the header
%    command.
%    \begin{macrocode}
       {
         \seq_if_empty:NTF \g_@@_LT@firsthead_rows_seq
           {
             \clist_set:Ne \l_@@_header_rows_clist
               {\seq_use:Nn \g_@@_LT@head_rows_seq {,}}
             \@@_set_header_rows:
           }
%    \end{macrocode}
%    In the other case we use firsthead.
%    \begin{macrocode}
           {
             \clist_set:Ne \l_@@_header_rows_clist
               { \seq_use:Nn \g_@@_LT@firsthead_rows_seq {,} }
             \@@_set_header_rows:
%    \end{macrocode}
%    Additionally we have to remove the head to avoid duplication. The
%    one option here is to remove the rows from the kid sequence of
%    the table (which will lead to orphaned structure elements), the
%    other to make them artifact.  For now we use the first option for
%    pdf 1.7 and the second for pdf 2.0.
%    \begin{macrocode}
             \pdf_version_compare:NnTF < {2.0}
              {
                \seq_map_inline:Nn \g_@@_LT@head_rows_seq
                  {
                    \seq_gset_item:cnn
                      {g__tag_struct_kids_ \g_@@_struct_table_tl _seq}
                      { ##1 }
                      {}
%    \end{macrocode}
%    Not sure if needed, but if needed we can remove also the P tag.
%    This is currently disabled as it produce warnings.  TODO: This
%    needs also a tagpdf command which takes care of debug code.
%    \begin{macrocode}
                    \tl_set:Ne \l_@@_tmpa_tl
                      { \seq_item:Nn\g_@@_struct_rows_seq {##1} }
                    \prop_if_exist:cT
                      { g__tag_struct_ \l_@@_tmpa_tl _prop }
                      {
                        %\prop_gremove:cn {g__tag_struct_ \l_@@_tmpa_tl _prop} {P}
                      }
                  }
              }
              {
                \seq_map_inline:Nn \g_@@_LT@head_rows_seq
                  {
                    \tl_set:Ne \l_@@_tmpa_tl
                      { \seq_item:Nn\g_@@_struct_rows_seq {##1} }
                    \prop_if_exist:cT
                      { g__tag_struct_ \l_@@_tmpa_tl _prop }
                      {
                        \__tag_struct_prop_gput:onn { \l_@@_tmpa_tl } {S}{/Artifact}
                      }
                   }
               }
            }
       }
%    \end{macrocode}
%    The foot is handled similar, the difference is
%    that we have to move it to the end one of them
%    is not empty, but do nothing if they aren't there.
%    \begin{macrocode}
     \bool_lazy_and:nnF
       { \seq_if_empty_p:N \g_@@_LT@foot_rows_seq  }
       { \seq_if_empty_p:N \g_@@_LT@lastfoot_rows_seq }
       {
%    \end{macrocode}
%    If lastfoot is empty move foot to the end.
%    \begin{macrocode}
         \seq_if_empty:NTF \g_@@_LT@lastfoot_rows_seq
           {
             \seq_map_inline:Nn \g_@@_LT@foot_rows_seq
               {
                 \tl_set:Ne \l_@@_tmpa_tl
                   {
                     \seq_item:cn
                       {g__tag_struct_kids_ \g_@@_struct_table_tl _seq}
                       {##1}
                   }
                 \seq_gset_item:cnn
                   {g__tag_struct_kids_ \g_@@_struct_table_tl _seq}
                   { ##1 }
                   {}
                 \seq_gput_right:cV
                   {g__tag_struct_kids_ \g_@@_struct_table_tl _seq}
                   \l_@@_tmpa_tl
               }
           }
%    \end{macrocode}
%    If lastfoot is not empty we move that.
%    \begin{macrocode}
           {
             \seq_map_inline:Nn \g_@@_LT@lastfoot_rows_seq
               {
                 \tl_set:Ne \l_@@_tmpa_tl
                   {
                     \seq_item:cn
                       {g__tag_struct_kids_ \g_@@_struct_table_tl _seq}
                       {##1}
                   }
                 \seq_gset_item:cnn
                   {g__tag_struct_kids_ \g_@@_struct_table_tl _seq}
                   { ##1 }
                   {}
                 \seq_gput_right:cV
                   {g__tag_struct_kids_ \g_@@_struct_table_tl _seq}
                   \l_@@_tmpa_tl
               }
%    \end{macrocode}
%    and we hide foot
%    \begin{macrocode}
            \pdf_version_compare:NnTF < {2.0}
              {
                \seq_map_inline:Nn \g_@@_LT@foot_rows_seq
                  {
                    \seq_gset_item:cnn
                      {g__tag_struct_kids_ \g_@@_struct_table_tl _seq}
                      { ##1 }
                      {}
%    \end{macrocode}
%    Not sure if needed, but if needed we can remove also the P tag.
%    This is currently disabled as it produce warnings.
%    TODO: This needs also
%    a tagpdf command which takes care of debug code.
%    \begin{macrocode}
                    \tl_set:Ne \l_@@_tmpa_tl
                      { \seq_item:Nn\g_@@_struct_rows_seq {##1} }
                    \prop_if_exist:cT
                      { g__tag_struct_ \l_@@_tmpa_tl _prop }
                      {
                        %\prop_gremove:cn {g__tag_struct_ \l_@@_tmpa_tl _prop} {P}
                      }
                  }
              }
              {
                \seq_map_inline:Nn \g_@@_LT@foot_rows_seq
                  {
                    \tl_set:Ne \l_@@_tmpa_tl
                      { \seq_item:Nn\g_@@_struct_rows_seq {##1} }
                    \prop_if_exist:cT
                      { g__tag_struct_ \l_@@_tmpa_tl _prop }
                      {
                        \__tag_struct_prop_gput:onn {\l_@@_tmpa_tl} {S}{/Artifact}
                      }
                   }
               }
           }
       }
  }
%    \end{macrocode}
% \end{plugdecl}
%
%
%
% \begin{plugdecl}{Table}
%  
%    We must avoid that the reuse of the header foot box leads to duplicated
%    content, thus reset attribute of the box:
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/longtable/head}{Table}
 {
  \tagmcbegin{artifact}
   \tag_mc_reset_box:N\LT@head
  \tagmcend
 }
%    \end{macrocode}
% \end{plugdecl}
%
% \begin{plugdecl}{Table}
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/longtable/foot}{Table}
 {
   \tagmcbegin{artifact}
   \tag_mc_reset_box:N \LT@foot
   \tagmcend
 }
%    \end{macrocode}
% \end{plugdecl}
%
% \begin{plugdecl}{Table}
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/hmode/begin}{Table}
  {
    \tag_mc_end_push:
%    \end{macrocode}
%    Close the P-chunk. This assumes that para-tagging is active.
%    For nested tables that is not necessarily true, so we test for it.
%    \begin{macrocode}
     \bool_lazy_and:nnT
      { \bool_if_exist_p:N \l__tag_para_bool } { \l__tag_para_bool }
      { \tag_struct_end:n { text } }
    \tag_struct_begin:n {tag=\l_@@_tabletag_tl}
    \tl_gset:Ne \g_@@_struct_table_tl { \tag_get:n {struct_num} }
  }
%    \end{macrocode}
% \end{plugdecl}
% 
% \begin{plugdecl}{LayoutTable}
% \changes{v0.85p}{2025/01/05}{new plugs for layout tables (tagging/778)}
% This plug is meant for presentation tables, it sets the ARIA role.
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/hmode/begin}{LayoutTable}
  {
    \tag_mc_end_push:
%    \end{macrocode}
%    Close the P-chunk. This assumes that para-tagging is active.
%    For nested tables that is not necessarily true, so we test for it.
%    \begin{macrocode}
     \bool_lazy_and:nnT
      { \bool_if_exist_p:N \l__tag_para_bool } { \l__tag_para_bool }
      { \tag_struct_end:n { text } }
    \tag_struct_begin:n {tag=\l_@@_tabletag_tl,attribute-class=ARIA-role-presentation}
    \tl_gset:Ne \g_@@_struct_table_tl { \tag_get:n {struct_num} }
  }
%    \end{macrocode}
% \end{plugdecl}
%
% \begin{plugdecl}{Table}
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/hmode/end}{Table}
  {
    \tag_struct_end:
%    \end{macrocode}
% reopen the P-chunk. This assumes that para-tagging is active.
% For nested tables that is not necessarily true, so we test for it.
%    \begin{macrocode}
    \bool_lazy_and:nnT
      { \bool_if_exist_p:N \l__tag_para_bool } { \l__tag_para_bool }
      { \tag_struct_begin:n { tag=\l__tag_para_tag_tl } }
    \tag_mc_begin_pop:n{}
  }
%    \end{macrocode}
% \end{plugdecl}
%
% \begin{plugdecl}{Table}
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/vmode/begin}{Table}
  {
    \tag_struct_begin:n {tag=\l_@@_tabletag_tl}
    \tl_gset:Ne \g_@@_struct_table_tl { \tag_get:n {struct_num} }
  }
%    \end{macrocode}
% \end{plugdecl}
% 
% \begin{plugdecl}{Table}
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/vmode/begin}{LayoutTable}
  {
    \tag_struct_begin:n {tag=\l_@@_tabletag_tl,attribute-class=ARIA-role-presentation}
    \tl_gset:Ne \g_@@_struct_table_tl { \tag_get:n {struct_num} }
  }
%    \end{macrocode}
% \end{plugdecl}
%
%
% \begin{plugdecl}{Table}
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/vmode/end}{Table}
  {
    \tag_struct_end:
    \par
  }
%    \end{macrocode}
% \end{plugdecl}
%
%
% \begin{plugdecl}{code}
%    This socket takes a number, checks if is larger than one,
%    checks if the colspan attribute already exists (we can't predefine an
%    arbitrary number), and updates \cs{l_@@_cellattribute_tl}.
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/colspan}{code}
  {
    \int_compare:nNnT {#1}>{1}
     {
       \prop_get:NeNF \g__tag_attr_entries_prop
          {colspan-\int_eval:n{#1}}
          \l_@@_tmpa_tl
          {
            \__tag_attr_new_entry:ee
              {colspan-\int_eval:n{#1}}
              {/O /Table /ColSpan~\int_eval:n{#1}}
          }
       \tl_set:Ne \l_@@_cellattribute_tl
          {\l_@@_cellattribute_tl,colspan-\int_eval:n{#1}}
     }
  }
%    \end{macrocode}
% \end{plugdecl}
%
%
%
%
% \begin{plugdecl}{code}
%    The sockets will be in lttagging.dtx, but that may only happen in
%    the next main release, so for now we test if they are in the
%    format and if not define them now.
%    \begin{macrocode}
\str_if_exist:cF { l__socket_tagsupport/tbl/leaders/end_plug_str }
    {
      \NewTaggingSocket{tbl/leaders/begin}{0}
      \NewTaggingSocket{tbl/leaders/end}{0}
    }
%    \end{macrocode}
%  
%    \begin{macrocode}
\NewTaggingSocketPlug{tbl/leaders/begin}{code}
  { \tag_mc_begin:n{artifact} }
\NewTaggingSocketPlug{tbl/leaders/end}{code}
  { \tag_mc_end: }
%    \end{macrocode}
% \end{plugdecl}
%
%
%
%\subsection{Environments}
%
% Currently we support only tabular, tabular*, tabularx and longtable
% (and possibly environments build directly on top of them). 
%
% The \env{array} environment is math. So we disable table tagging for now.
% We use the command hook to catch also cases where \cs{array} is used directly
% in other environments like matrix environments.
% Perhaps table tagging should be disable for math generally, but then
% we have to handle text insertions. 
%    \begin{macrocode}
\AddToHook{cmd/array/before}{\__tag_tbl_disable:}
%    \end{macrocode}
%
%
% \subsection{Interfaces to tagging}
%
% \subsubsection{Tagging helper commands}

% \subsubsection{Disabling/enabling}
%
%  For now we have only the option true/false but this will probably be extended
%  to allow different setups like first row header etc.

%  \begin{macro}{\__tag_tbl_disable:}
%
%    \begin{macrocode}
\cs_new_protected:Npn \__tag_tbl_disable:
 {
   \AssignTaggingSocketPlug{tbl/cell/begin}{noop}
   \AssignTaggingSocketPlug{tbl/cell/end}{noop}
   \AssignTaggingSocketPlug{tbl/pcell/begin}{noop}
   \AssignTaggingSocketPlug{tbl/pcell/end}{noop}
   \AssignTaggingSocketPlug{tbl/row/begin}{noop}
   \AssignTaggingSocketPlug{tbl/row/end}{noop}
   \AssignTaggingSocketPlug{tbl/init}{noop}
   \AssignTaggingSocketPlug{tbl/finalize}{noop}
   \AssignTaggingSocketPlug{tbl/longtable/init}{noop}
   \AssignTaggingSocketPlug{tbl/longtable/head}{noop}
   \AssignTaggingSocketPlug{tbl/longtable/foot}{noop}
   \AssignTaggingSocketPlug{tbl/longtable/finalize}{noop}
   \AssignTaggingSocketPlug{tbl/hmode/begin}{noop}
   \AssignTaggingSocketPlug{tbl/hmode/end}{noop}
   \AssignTaggingSocketPlug{tbl/vmode/begin}{noop}
   \AssignTaggingSocketPlug{tbl/vmode/end}{noop}
   \AssignTaggingSocketPlug{tbl/colspan}{noop}
   \AssignTaggingSocketPlug{tbl/leaders/begin}{noop}
   \AssignTaggingSocketPlug{tbl/leaders/end}{noop}
 }
%    \end{macrocode}
%  \end{macro}


%  \begin{macro}{\__tag_tbl_enable:}
%
%    \begin{macrocode}
\cs_new_protected:Npn \__tag_tbl_enable:
 {
   \AssignTaggingSocketPlug{tbl/cell/begin}{TD}
   \AssignTaggingSocketPlug{tbl/cell/end}{TD}
   \AssignTaggingSocketPlug{tbl/pcell/begin}{TDpbox}
   \AssignTaggingSocketPlug{tbl/pcell/end}{TDpbox}
   \AssignTaggingSocketPlug{tbl/row/begin}{TR}
   \AssignTaggingSocketPlug{tbl/row/end}{TR}
   \AssignTaggingSocketPlug{tbl/init}{Table}
   \AssignTaggingSocketPlug{tbl/finalize}{Table}
   \AssignTaggingSocketPlug{tbl/longtable/head}{Table}
   \AssignTaggingSocketPlug{tbl/longtable/foot}{Table}
   \AssignTaggingSocketPlug{tbl/longtable/init}{Table}
   \AssignTaggingSocketPlug{tbl/longtable/finalize}{Table}
   \AssignTaggingSocketPlug{tbl/hmode/begin}{Table}
   \AssignTaggingSocketPlug{tbl/hmode/end}{Table}
   \AssignTaggingSocketPlug{tbl/vmode/begin}{Table}
   \AssignTaggingSocketPlug{tbl/vmode/end}{Table}
   \AssignTaggingSocketPlug{tbl/colspan}{code}
   \AssignTaggingSocketPlug{tbl/leaders/begin}{code}
   \AssignTaggingSocketPlug{tbl/leaders/end}{code}
 }
%    \end{macrocode}
%  \end{macro}
%
% \begin{macro}{\@@_init_tagnames_default:,\@@_init_tagnames_div:} 
% These commands are used to switch the tagnames. 
%    \begin{macrocode}
\cs_new_protected:Npn\@@_init_tagnames_default:
  {
    \tl_set:Nn\l_@@_rowtag_tl   {TR}
    \tl_set:Nn\l_@@_pcelltag_tl {TD}
    \tl_set:Nn\l_@@_celltag_tl  {TD}
    \tl_set:Nn\l_@@_tabletag_tl {Table} 
  }     

\cs_new_protected:Npn\@@_init_tagnames_div:
  {
    \tl_set:Nn\l_@@_rowtag_tl   {NonStruct}
    \tl_set:Nn\l_@@_pcelltag_tl {NonStruct}
    \tl_set:Nn\l_@@_celltag_tl  {text}
    \tl_set:Nn\l_@@_tabletag_tl {Div} 
  }     
%    \end{macrocode}
% \end{macro}

%    \begin{macrocode}
% See tagpdfsetup-keys.md in tagpdf/doc for the naming scheme.
\keys_define:nn { __tag / setup }
  {
    table/tagging .choices:nn = { true, on }
      { 
        \__tag_tbl_enable: 
        \@@_init_tagnames_default:
      },
    table/tagging .choices:nn = { false, off }
      { \__tag_tbl_disable: },
    table/tagging .choice:,
    table/tagging / presentation .code:n = 
      {
        \__tag_tbl_enable:
        \@@_init_tagnames_default:
        \AssignTaggingSocketPlug{tbl/hmode/begin}{LayoutTable}
        \AssignTaggingSocketPlug{tbl/vmode/begin}{LayoutTable}
        \clist_clear:N \l_@@_header_rows_clist
        \clist_clear:N \l_@@_header_columns_clist  
      },  
    table/tagging / div .code:n = 
      {
        \__tag_tbl_enable:
        \@@_init_tagnames_div:
        \AssignTaggingSocketPlug{tbl/hmode/begin}{LayoutTable}
        \AssignTaggingSocketPlug{tbl/vmode/begin}{LayoutTable}
        \clist_clear:N \l_@@_header_rows_clist
        \clist_clear:N \l_@@_header_columns_clist  
      },          
    table/tagging .default:n = true,
    table/tagging .initial:n = true
  }
%    \end{macrocode}
% This are the old key names kept for now for 
% compatibility. They will got at some time.
%    \begin{macrocode}
\keys_define:nn { __tag / setup }
  {
    table-tagging .meta:n = {table/tagging={#1}}
  }
%    \end{macrocode}
%
%
% \subsubsection{Header support}
%
% Accessible table must have header cells declaring the meaning of the
% data in a row or column. To allow a data cell to find it header cell(s)
% a number of things must be done:
% \begin{itemize}
% \item every cell meant as a header should use the tag \texttt{TH}.
%
% \item header cells should have a \texttt{Scope} attribute with the
%   value \texttt{Column}, \texttt{Row} or \texttt{Both}.  This is not
%   needed in the first row or column of a table.
% 
% \item For more complex cases both \texttt{TD} and \texttt{TH} cell
%   can contain a \texttt{Headers} attribute, that is an array of
%   \texttt{ID}s of \texttt{TH} cell.
% \end{itemize}
%
% For now we support only header rows.
%
% At first we define attributes for the three standard cases:
% \changes{v0.85p}{2025/01/05}{add ARIA-role for presentation (tagging/778)}
% \changes{v0.85t}{2025-07-02}{Remove \cs{tag_if_active:}}     
%    \begin{macrocode}
\AddToHook{begindocument}
 {
   \tagpdfsetup
     {
       role/new-attribute =
        {TH-col}{/O /Table /Scope /Column},
       role/new-attribute =
         {TH-row}{/O /Table /Scope /Row},
       role/new-attribute =
         {TH-both}{/O /Table /Scope /Both},
       role/new-attribute =
        {ARIA-role-presentation}{/O /ARIA-1.1/role (presentation)}
     }
%    \end{macrocode}
%
% And we put all three into the class map (perhaps the next tagpdf
% should do that directly with role/new-attribute):
%
%    \begin{macrocode}
    \seq_gput_left:Ne\g__tag_attr_class_used_seq
      {\pdf_name_from_unicode_e:n{TH-col}}
    \seq_gput_left:Ne\g__tag_attr_class_used_seq
      {\pdf_name_from_unicode_e:n{TH-row}}
    \seq_gput_left:Ne\g__tag_attr_class_used_seq
      {\pdf_name_from_unicode_e:n{TH-both}}     
  }    
%    \end{macrocode}
%
% \begin{variable}{\l_@@_header_rows_clist,\l_@@_header_columns_clist}
%    This holds the numbers of the header rows and columns. Negative numbers are
%    possible and count from the last column backwards. 
%    \begin{macrocode}
\clist_new:N  \l_@@_header_rows_clist
\clist_new:N  \l_@@_header_columns_clist
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_set_header_rows:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_set_header_rows:
  {
    \clist_map_inline:Nn \l_@@_header_rows_clist
      {
        \clist_set:Ne\l_@@_tmpa_clist
          {
            \seq_item:Nn \g_@@_struct_cells_seq {##1}
          }
        \clist_map_inline:Nn \l_@@_tmpa_clist
          {
%    \end{macrocode}
%    We can have negative numbers in the list from the multicolumn.
%    \begin{macrocode}
            \prop_if_exist:cT { g__tag_struct_####1_prop }
              {
                \__tag_struct_prop_gput:nnn{ ####1 }{S}{/TH}
%    \end{macrocode}
%    This sets the scope class. If the header has already a row attribute
%    we replace by TH-both.
%    \begin{macrocode}
                \prop_get:cnNTF
                  { g__tag_struct_####1_prop }
                  { C }
                  \l_@@_tmpa_tl
                  { 
                    \str_set:Ne \l_@@_tmpa_str {\l_@@_tmpa_tl}
                    \str_remove_once:Nn \l_@@_tmpa_str {[}
                    \str_remove_once:Nn \l_@@_tmpa_str {]}                   
                    \str_if_in:NnTF\l_@@_tmpa_str{/TH-row}
                     {
                       \str_replace_once:Nnn \l_@@_tmpa_str {/TH-row}{/TH-both}
                       \__tag_struct_prop_gput:nne{ ####1 }{C}{[\l_@@_tmpa_str]}
                     }
                     {
                       \__tag_struct_prop_gput:nne{ ####1 }{C}{[/TH-col~\l_@@_tmpa_str]}
                     } 
                  }                  
                  {\__tag_struct_prop_gput:nnn{ ####1 }{C}{/TH-col}}
             }
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%  
% And some key support:
% (See tagpdfsetup-keys.md for the naming scheme.) 
%    \begin{macrocode}
\keys_define:nn { __tag / setup }
  {
    table/header-rows .clist_set:N = \l_@@_header_rows_clist,
    table/header-columns .clist_set:N = \l_@@_header_columns_clist,
%    \end{macrocode}
% obsolete older name:
%    \begin{macrocode}
    table-header-rows .meta:n = {table/header-rows={#1}}
  }
%    \end{macrocode}
%
% \subsection{Multirow support}
% 
% \begin{macro}{\@@_multirow:n}
% This command makes the current cell into a multirow cell:
% it creates, if needed, an RowSpan-attribute, adds it to the attributes
% of the cell structure, and marks all following cells spanned by the multirow
% as cells that should not be tagged. The argument is the number of spanned row
% (including the current row).
%    \begin{macrocode}
\cs_if_exist:NT \__tag_struct_prop_gput:nnn
 {
   \cs_generate_variant:Nn \__tag_struct_prop_gput:nnn {one}
 }  
\cs_new_protected:Npn \@@_multirow:n #1
 { 
%    \end{macrocode}
% Create an attribute if needed:
%    \begin{macrocode}
   \prop_get:NeNF \g__tag_attr_entries_prop
      {rowspan-\int_eval:n{#1}}
      \l_@@_tmpa_tl
      {
        \__tag_attr_new_entry:ee
          {rowspan-\int_eval:n{#1}}
          {/O /Table /RowSpan~\int_eval:n{#1}}
      }
%    \end{macrocode}
% ensure that the attribute is marked as used:
%    \begin{macrocode}
   \seq_gput_left:Ne\g__tag_attr_class_used_seq
     {\pdf_name_from_unicode_e:n{rowspan-\int_eval:n{#1}}}   
%    \end{macrocode}
% Get the structure number of the current cell
%    \begin{macrocode}
   \seq_get_right:NN\g_@@_struct_cur_seq \l_@@_tmpb_tl
%    \end{macrocode}
% If we are in a multicolumn the number can be negative and 
% this must be changed
%    \begin{macrocode}
   \tl_set:Ne \l_@@_tmpb_tl { \int_abs:n{\l_@@_tmpb_tl} }
%    \end{macrocode}
% no we must update an existing attribute.
% TODO: simplify this ... (see also colspan handling).
%    \begin{macrocode}
   \prop_get:cnNTF
      { g__tag_struct_\l_@@_tmpb_tl _prop }
      { C }
      \l_@@_tmpa_tl
      {
        \tl_remove_once:Nn \l_@@_tmpa_tl {[}
        \tl_remove_once:Nn \l_@@_tmpa_tl {]}
        \__tag_struct_prop_gput:one{ \l_@@_tmpb_tl }
          {C}
          {[/rowspan-\int_eval:n{#1}~\l_@@_tmpa_tl]} 
      } 
      {
        \__tag_struct_prop_gput:one{ \l_@@_tmpb_tl }
          {C}
          {[/rowspan-\int_eval:n{#1}]} 
      } 
%    \end{macrocode}
% Now mark the spanned cells that should be ignored.
%    \begin{macrocode}
    \@@_gset_untagged_row_cells:nn {#1-1}{\g_@@_span_tl}   
 }
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
\keys_define:nn{ __tag / setup }
 { table/multirow .code:n = {\@@_multirow:n {#1} }
  ,table/multirow .default:n = 1
 } 
%    \end{macrocode}
%
% \begin{macro}{\@@_gset_untagged_row_cells:nn}
% This command stores the row and column numbers of the cells in the following row(s) 
% that should not be tagged as they are a part of a rowspan.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_gset_untagged_row_cells:nn #1 #2 
  % #1 number of rows, #2 number of columns
  {
    \int_step_inline:nn {#1}
     { 
       \int_step_inline:nn {#2}
        {
          \prop_gput:Nee \g_@@_untagged_cells_prop
           { 
             \int_eval:n {\g_@@_row_int + ##1},
             \int_eval:n{\g_@@_col_int + ####1 -1 } 
           }{}
        }   
     }  
  }
%    \end{macrocode}
% \end{macro}
% 
% \begin{macro}{\@@_if_tag_cell:nn}
% We must be able to detect if a cell should be tagged. For this
% we define a conditional --- if more options are needed it can be extended.
%    \begin{macrocode}
\prg_new_protected_conditional:Npnn\@@_if_tag_cell:nn #1 #2 %#1 row, #2 col
  { T,TF }
  {
    \prop_get:NeNTF \g_@@_untagged_cells_prop 
      {\int_eval:n{#1},\int_eval:n{#2}}\l__tbl_tmpa_tl
      { \prg_return_false:}
      { \prg_return_true: } 
  }
%    \end{macrocode}
% \end{macro}
% 
% \subsection{Misc stuff}
%
%  \begin{macro}{\@@_show_curr_cell_data:}
%    Show the row/column index and span count for current table cell
%    for debugging.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_show_curr_cell_data: {
   \@@_trace:n { ==>~ current~cell~data:~
                 \int_use:N \g_@@_row_int ,
                 \int_use:N \g_@@_col_int ,
                 \g_@@_span_tl
               }
}
%    \end{macrocode}
%  \end{macro}
%
%
%
%  \begin{macro}{\@@_add_missing_cells:}
%    The storing and use of the number of missing cells must happen at
%    different places as the testing happens at the end of the last
%    cell of a row, but still inside that cell, so we use two
%    commands. The one adding is used in the row/end socket.
%    \begin{macrocode}
\cs_new:Npn \@@_add_missing_cells:
  {
%    \end{macrocode}
%    The TD-socket messages are issued after the message about the
%    end-row socket, but the structure is ok, so better issue a
%    message for now to avoid confusion:
%    \begin{macrocode}
    \int_compare:nNnT \g_@@_missing_cells_int > 0
      {
       \@@_trace:n {==>~
         ~Inserting~\int_use:N \g_@@_missing_cells_int \space
          additional~cell(s)~into~previous~row:}
       \int_step_inline:nn { \g_@@_missing_cells_int }
           {
             \int_gincr:N\g_@@_col_int
             \UseTaggingSocket{tbl/cell/begin}
             \UseTaggingSocket{tbl/cell/end}
           }
      }
  }
%    \end{macrocode}
%  \end{macro}
%
%
%
%
%
%
% \begin{macro}
%   {
%     \g_@@_struct_table_tl, \l_@@_saved_struct_table_tl,
%     \g_@@_struct_rows_seq,\l_@@_saved_struct_rows_seq,
%     \g_@@_struct_cells_seq,\l_@@_saved_struct_cells_seq,
%     \g_@@_struct_cur_seq,\l_@@_saved_struct_cur_seq
%   }
%    We need to store the structure numbers for the fine tuning in the
%    finalize socket.  For now we use a rather simple system: A
%    sequence that hold the numbers for the row structures, and one
%    that holds comma lists for the cells.
%
%    \cs{g_@@_struct_table_tl} will hold the structure number of the
%    table, \cs{g_@@_struct_rows_seq} will hold at index i the
%    structure number of row i, \cs{g_@@_struct_cells_seq} will hold
%    at index i a comma list of the cell structure numbers of row i.
%    \cs{g_@@_struct_cur_seq} is used as a temporary store for the
%    cell structures of the current row.  We need also local version
%    to store and restore the values.
%
%    \begin{macrocode}
\tl_new:N  \g_@@_struct_table_tl
\tl_new:N  \l_@@_saved_struct_table_tl
\seq_new:N \g_@@_struct_rows_seq
\seq_new:N \l_@@_saved_struct_rows_seq
\seq_new:N \g_@@_struct_cells_seq
\seq_new:N \l_@@_saved_struct_cells_seq
\seq_new:N \g_@@_struct_cur_seq
\seq_new:N \l_@@_saved_struct_cur_seq
%    \end{macrocode}
% \end{macro}



%  \begin{macro}{\@@_init_struct_data:}
%    Save the global structure data variables locally so the we can
%    restore them when the table ends (important in case of nested
%    tables).
%
%    This is also the right point to initialize
%    them. \cs{g_@@_struct_table_tl} is set elsewhere.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_init_struct_data: {
  \tl_set_eq:NN  \l_@@_saved_struct_table_tl \g_@@_struct_table_tl
  \seq_set_eq:NN \l_@@_saved_struct_rows_seq \g_@@_struct_rows_seq
  \seq_set_eq:NN \l_@@_saved_struct_cells_seq \g_@@_struct_cells_seq
  \seq_set_eq:NN \l_@@_saved_struct_cur_seq \g_@@_struct_cur_seq
%
  \seq_gclear:N\g_@@_struct_rows_seq
  \seq_gclear:N\g_@@_struct_cells_seq
  \seq_gclear:N\g_@@_struct_cur_seq
}
%    \end{macrocode}
%  \end{macro}




%  \begin{macro}{\@@_restore_struct_data:}
%    
%    \begin{macrocode}
\cs_new_protected:Npn \@@_restore_struct_data: {
  \tl_gset_eq:NN  \g_@@_struct_table_tl \l_@@_saved_struct_table_tl
  \seq_gset_eq:NN \g_@@_struct_rows_seq \l_@@_saved_struct_rows_seq
  \seq_gset_eq:NN \g_@@_struct_cells_seq\l_@@_saved_struct_cells_seq
  \seq_gset_eq:NN \g_@@_struct_cur_seq  \l_@@_saved_struct_cur_seq
}
%    \end{macrocode}
%  \end{macro}


%
%
% \subsection{longtable}
%
%
%
%
%
% \begin{macro}{\@@_patch_\LT@makecaption}
%     This patch is quite similar to the one for LaTeX's \cs{@makecaption}
%     we also have to change the parbox sockets.
%    \begin{macrocode}
\def\@@_patch_LT@makecaption#1#2#3{%
  \LT@mcol\LT@cols c{%
    \AssignTaggingSocketPlug{parbox/before}{noop}
    \AssignTaggingSocketPlug{parbox/after}{noop}
    \hbox to\z@{\hss\parbox[t]\LTcapwidth{%
    \reset@font
    \tag_suspend:n{caption}
    \sbox\@tempboxa{#1{#2:~}#3}%
    \tag_resume:n{caption}
    \ifdim\wd\@tempboxa>\hsize
      #1{#2:~}#3%
    \else
      \hbox to\hsize{\hfil#1{#2:~}#3\hfil}%
    \fi
    \endgraf\vskip\baselineskip}%
  \hss}}}
%    \end{macrocode}
% \end{macro}
%
%
%
% Overwrite the longtable definition. That will probably break somewhere as
% they are various package which patch too.
%    \begin{macrocode}
\AddToHook{package/longtable/after}
  {
   \cs_set_eq:NN \LT@makecaption\@@_patch_LT@makecaption
  }
%    \end{macrocode}


%    \begin{macrocode}
%</package>
%    \end{macrocode}
%
%
%    \begin{macrocode}
%<*latex-lab>
\ProvidesFile{table-latex-lab-testphase.ltx}
        [\ltlabtbldate\space v\ltlabtblversion\space latex-lab wrapper table]
\RequirePackage{latex-lab-testphase-table}
%</latex-lab>
%    \end{macrocode}

%    \begin{macrocode}
%<*latex-lab-alias>
\ProvidesFile{tabular-latex-lab-testphase.ltx}
        [\ltlabtbldate\space v\ltlabtblversion\space latex-lab wrapper tabular]
\RequirePackage{latex-lab-testphase-table}
%</latex-lab-alias>
%    \end{macrocode}
