% \iffalse meta-comment % %% File: latex-lab-table.dtx (C) Copyright 2023-2024 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 development version of the bundle can be found below % % https://github.com/latex3/latex2e/required/latex-lab % % for those people who are interested or want to report an issue. % \def\ltlabtbldate{2024-02-07} \def\ltlabtblversion{0.85k} %<*driver> \documentclass{l3doc} \EnableCrossrefs \CodelineIndex \begin{document} \DocInput{latex-lab-table.dtx} \end{document} % % % \fi % % \providecommand\hook[1]{\texttt{#1\DescribeHook[noprint]{#1}}} % \providecommand\socket[1]{\texttt{#1\DescribeSocket[noprint]{#1}}} % \providecommand\plug[1]{\texttt{#1\DescribePlug[noprint]{#1}}} % % \NewDocElement[printtype=\textit{socket},idxtype=socket,idxgroup=Sockets]{Socket}{socketdecl} % \NewDocElement[printtype=\textit{hook},idxtype=hook,idxgroup=Hooks]{Hook}{hookdecl} % \NewDocElement[printtype=\textit{plug},idxtype=plug,idxgroup=Plugs]{Plug}{plugdecl} % % % % % \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. % % 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. % % 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 with \verb|\tagpdfsetup{table/tagging=presentation}|% % \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 he 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 changes the tag names to \texttt{Div} and the grouping structure \texttt{NonStruct}. % % 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. % % 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|}| % \end{quote} % you can % declare which (absolute) row numbers should be tagged as header rows. % It applies to all tables until it is changed to a different list of row numbers or undone by setting % the key to \meta{empty}. % A row number can be % negative, then the counting starts from the end of the table. There is no support % for header columns yet. 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. % % 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 yet work properly with the tagging, % but once it does, then colors inside the table will probably be % simply ignored (at least initially). If such a color % has a semantic meaning (like \enquote{important value}) this meaning will be lost. % % 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 are not yet supported (and the \pkg{multirow} package is untested). % % \item The \pkg{colortbl} package breaks tagging if there are nested tables. It also breaks % the filling up of incomplete rows. % % \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 yet tested, % that includes packages that change rules like \pkg{booktabs}, \pkg{hhline}, % \pkg{arydshln}, \pkg{hvdashln}. % % \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 \verb|\tagpdfsetup{table/tagging=presentation}|. % % \item Only simple header rows are currently supported. Columns and complex headers with % subheaders will be handled later as that needs some syntax changes. Tables % with more than one header row 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 Handle p-cells better. para-tagging should probably be enabled, % but Part can't be a child of TD, so this should probably be changed to Div here. % Also there is a stray MC somewhere. % % \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_seq % ,\l_@@_tmpa_tl % } % 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} % Temp variables % \begin{macrocode} \clist_new:N \l_@@_tmpa_clist \tl_new:N \l_@@_tmpa_tl % \end{macrocode} % \end{macro} % % \subsection{Tagging support sockets} % % % This are the standard plugs for tagging of cells and rows. % % \begin{plugdecl}{TD} % \begin{macrocode} \NewSocketPlug{tagsupport/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: \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} \NewSocketPlug{tagsupport/tbl/cell/end}{TD} { \tag_mc_end: \tag_struct_end: } % \end{macrocode} % \end{plugdecl} % % In p-columns we need a slightly different plug which reactivates the % paragraph tagging. % \begin{plugdecl}{TDpbox} % \begin{macrocode} \NewSocketPlug{tagsupport/tbl/pcell/begin}{TDpbox} { \@@_show_curr_cell_data: \tag_struct_begin:n { tag =\l__tbl_pcelltag_tl, attribute-class =\l__tbl_cellattribute_tl } \seq_gput_right:Ne \g__tbl_struct_cur_seq { \tag_get:n {struct_num} } \int_step_inline:nn { \g__tbl_span_tl - 1 } { \seq_gput_right:Ne \g__tbl_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} \NewSocketPlug{tagsupport/tbl/pcell/end}{TDpbox} { \tag_struct_end: } % \end{macrocode} % \end{plugdecl} % % \begin{plugdecl}{TR} % \begin{macrocode} \NewSocketPlug{tagsupport/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} % \begin{macrocode} \NewSocketPlug{tagsupport/tbl/row/end}{TR} { \tag_if_active:T { \@@_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 {==>~ stucture~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{plugdecl}{Table} % Inside a table we currently only disable paratagging. We assume % that these sockets are in an environment group, so there is no % need to reenable paratagging. % \begin{macrocode} \NewSocketPlug{tagsupport/tbl/init}{Table} { \tag_if_active:T { \bool_set_false:N \l__tag_para_bool % \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} \NewSocketPlug{tagsupport/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} \NewSocketPlug{tagsupport/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} \NewSocketPlug{tagsupport/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 } { \@@_struct_prop_gput:Vnn \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 } { \@@_struct_prop_gput:Vnn \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} \NewSocketPlug{tagsupport/tbl/longtable/head}{Table} { \tagmcbegin{artifact} \tag_mc_reset_box:N\LT@head \tagmcend } % \end{macrocode} % \end{plugdecl} % % \begin{plugdecl}{Table} % \begin{macrocode} \NewSocketPlug{tagsupport/tbl/longtable/foot}{Table} { \tagmcbegin{artifact} \tag_mc_reset_box:N \LT@foot \tagmcend } % \end{macrocode} % \end{plugdecl} % % % % % \begin{plugdecl}{Table} % \begin{macrocode} \NewSocketPlug{tagsupport/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 necessarly 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}{Table} % \begin{macrocode} \NewSocketPlug{tagsupport/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 necessarly 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} \NewSocketPlug{tagsupport/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} \NewSocketPlug{tagsupport/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} \NewSocketPlug{tagsupport/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 {colspan-\int_eval:n{#1}} } } % \end{macrocode} % TODO: move to tagpdf % \begin{macrocode} \tag_if_active:T { \cs_generate_variant:Nn \__tag_attr_new_entry:nn {ee} } % \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: { \AssignSocketPlug{tagsupport/tbl/cell/begin}{noop} \AssignSocketPlug{tagsupport/tbl/cell/end}{noop} \AssignSocketPlug{tagsupport/tbl/pcell/begin}{noop} \AssignSocketPlug{tagsupport/tbl/pcell/end}{noop} \AssignSocketPlug{tagsupport/tbl/row/begin}{noop} \AssignSocketPlug{tagsupport/tbl/row/end}{noop} \AssignSocketPlug{tagsupport/tbl/init}{noop} \AssignSocketPlug{tagsupport/tbl/finalize}{noop} \AssignSocketPlug{tagsupport/tbl/longtable/init}{noop} \AssignSocketPlug{tagsupport/tbl/longtable/head}{noop} \AssignSocketPlug{tagsupport/tbl/longtable/foot}{noop} \AssignSocketPlug{tagsupport/tbl/longtable/finalize}{noop} \AssignSocketPlug{tagsupport/tbl/hmode/begin}{noop} \AssignSocketPlug{tagsupport/tbl/hmode/end}{noop} \AssignSocketPlug{tagsupport/tbl/vmode/begin}{noop} \AssignSocketPlug{tagsupport/tbl/vmode/end}{noop} \AssignSocketPlug{tagsupport/tbl/colspan}{noop} } % \end{macrocode} % \end{macro} % \begin{macro}{\__tag_tbl_enable:} % % \begin{macrocode} \cs_new_protected:Npn \__tag_tbl_enable: { \AssignSocketPlug{tagsupport/tbl/cell/begin}{TD} \AssignSocketPlug{tagsupport/tbl/cell/end}{TD} \AssignSocketPlug{tagsupport/tbl/pcell/begin}{TDpbox} \AssignSocketPlug{tagsupport/tbl/pcell/end}{TDpbox} \AssignSocketPlug{tagsupport/tbl/row/begin}{TR} \AssignSocketPlug{tagsupport/tbl/row/end}{TR} \AssignSocketPlug{tagsupport/tbl/init}{Table} \AssignSocketPlug{tagsupport/tbl/finalize}{Table} \AssignSocketPlug{tagsupport/tbl/longtable/head}{Table} \AssignSocketPlug{tagsupport/tbl/longtable/foot}{Table} \AssignSocketPlug{tagsupport/tbl/longtable/init}{Table} \AssignSocketPlug{tagsupport/tbl/longtable/finalize}{Table} \AssignSocketPlug{tagsupport/tbl/hmode/begin}{Table} \AssignSocketPlug{tagsupport/tbl/hmode/end}{Table} \AssignSocketPlug{tagsupport/tbl/vmode/begin}{Table} \AssignSocketPlug{tagsupport/tbl/vmode/end}{Table} \AssignSocketPlug{tagsupport/tbl/colspan}{code} } % \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: }, table/tagging .choices:nn = { false, off } { \__tag_tbl_disable: }, table/tagging .choice:, table/tagging / presentation .code:n = { \__tag_tbl_enable: \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} }, table/tagging .default:n = true, table/tagging .initial:n = true } % \end{macrocode} % This are the old key names kept for now for % compability. 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: % We delay to begin document % as we can't know if tagpdf is already loaded. % \begin{macrocode} \AddToHook{begindocument} { \tag_if_active:T { \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}, } % \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} % This holds the numbers of the header rows. Negative numbers are % possible and count from the back. % \begin{macrocode} \clist_new:N \l_@@_header_rows_clist % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_set_header_rows:} % TEMP: Next tagpdf will have the right command which also updates % the debug info. % For now a temporary command: % \begin{macrocode} \cs_if_free:NTF \__tag_struct_prop_gput:nnn { \cs_new_protected:Npn \@@_struct_prop_gput:nnn #1#2#3 { \prop_gput:cnn { g__tag_struct_#1_prop }{#2}{#3} } } { \cs_new_protected:Npn \@@_struct_prop_gput:nnn #1#2#3 { \__tag_struct_prop_gput:nnn {#1}{#2}{#3} } } \cs_generate_variant:Nn \@@_struct_prop_gput:nnn {nne,Vnn} % \end{macrocode} % % \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 } { \@@_struct_prop_gput:nnn{ ####1 }{S}{/TH} % \end{macrocode} % This need refinement once row headers (and perhaps other attributes) % are used too, but for now it should be ok. % \begin{macrocode} \prop_get:cnNTF { g__tag_struct_####1_prop } { C } \l_@@_tmpa_tl {\@@_struct_prop_gput:nne{ ####1 }{C}{[/TH-col~\l_@@_tmpa_tl]} } {\@@_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, % \end{macrocode} % obsolete older name: % \begin{macrocode} table-header-rows .meta:n = {table/header-rows={#1}} } % \end{macrocode} % % % % \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} \NewSocket{tbl/celldata/missingcount}{1} \NewSocketPlug{tbl/celldata/missingcount}{code}{\tbl_count_missing_cells:n{#1}} \AssignSocketPlug{tbl/celldata/missingcount}{code} % \end{macrocode} % % \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 } { \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{% % test can go after merge \str_if_exist:cT { l__socket_tagsupport/parbox/before_plug_str } { \AssignSocketPlug{tagsupport/parbox/before}{noop} \AssignSocketPlug{tagsupport/parbox/after}{noop} } \hbox to\z@{\hss\parbox[t]\LTcapwidth{% \reset@font \tag_stop:n{caption} \sbox\@tempboxa{#1{#2:~}#3}% \tag_start: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} % % \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} % % \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} % % \end{macrocode}