tabl-xtb.mkvi / last modification: 2020-01-30 14:16
% macros=mkvi

%D \module
%D   [       file=tabl-xtb,
%D        version=2011.10.26,
%D          title=\CONTEXT\ Table Macros,
%D       subtitle=Xtreme,
%D         author=Hans Hagen,
%D           date=\currentdate,
%D      copyright={PRAGMA ADE \& \CONTEXT\ Development Team}]
%C
%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

\writestatus{loading}{ConTeXt Table Macros / Xtreme}

\registerctxluafile{tabl-xtb}{}

% todo:
%
% - yes or no: foregroundstyle/color <- style/color
% - template alignment
% - maybe split horizontal (a la linetables)
% - before/after and wrapping (linecorrection)
% - maybe also some before/after commands
% - maybe correction when non float usage
% - tagging needs to be checked
% - maybe only tag the box
% - scale to fit
%
% - buffers permit verbatim but are not always handy

%D This module started as an afternoon experiment and surprisingly could be
%D mostly finished the same evening. Of course it builds upon existing
%D functionality. The main reason for writing it is that we occasionally
%D run into pretty large tables that take tens of pages and need to be split
%D into floats. Speed is one issue there, avoiding to use vsplit is another.
%D
%D \starttyping
%D \definextable [tag] | [tag][parent]
%D \setupxtable [settings] | [tag][settings]
%D
%D \startxtable[tag|settings]
%D     \startxtablehead|\startxtablenext|\startxtablebody|\startxtablefoot
%D         \startxrowgroup[tag|settings]
%D             \startxrow[settings]
%D                 \startxcellgroup[tag|settings]
%D                     \startxcell[settings] ... \stopxcell
%D                 \stopxcellgroup
%D             \stopxrow
%D         \startxrowgroup
%D     \stopxtablehead|\stopxtablenext|\stopxtablebody|\stopxtablefoot
%D \stopxtable
%D \stoptyping
%D
%D See xtables-001.tex etc for some examples.

% We can avoid some checking by using the fastoptionalcheckcs helpers
% instead of dosingleempty but the speed gain is neglectable.

\unprotect

% option=stretch         : equal distribution
% option={stretch,width} : proportional distribution
% option={max}           : prefer max over forced width/height
%
% cells: option=fixed    : nils autostretch (not yet complete)

% \setbox\scratchbox\hbox attr \taggedattribute \attribute\taggedattribute {...}
%
% \let\tsplitbeforeresult\donothing
% \let\tsplitafterresult \donothing
% \let\tsplitinbetween   \donothing
% \let\tsplitbefore      \donothing
% \let\tsplitafter       \donothing
% \let\postprocesstsplit \donothing

\let\dotagxtablecell  \relax % names will change
\let\dotagxtablesignal\relax % names will change

\appendtoks
    \def\dotagxtablecell
      {\clf_settagtablecell
         \numexpr\tablecellrows\relax
         \numexpr\tablecellcolumns\relax
         \numexpr\raggedstatus\relax}%
    \def\dotagxtablesignal
      {\signalcharacter}% not used
\to \everyenableelements

\newdimen\d_tabl_x_width
\newdimen\d_tabl_x_height
\newdimen\d_tabl_x_depth        % not used
\newdimen\d_tabl_x_distance
\newcount\c_tabl_x_nx
\newcount\c_tabl_x_ny
\newcount\c_tabl_x_mode
\newbox  \b_tabl_x
\newcount\c_tabl_x_state        % 0=empty 1=content 3=splitleft
\newdimen\d_tabl_x_final_width
\newcount\c_tabl_x_nesting
\newcount\c_tabl_x_skip_mode    % 1 = skip
\newdimen\d_tabl_x_textwidth
\newcount\c_tabl_x_swapped
\newcount\c_tabl_x_swapped_max

\let\m_tabl_x_swapped_settings\empty

\let\currentxtablerow   \clf_x_table_r
\let\currentxtablecolumn\clf_x_table_c

% \setupxtable[one][parent][a=b,c=d]
% \setupxtable[one]        [a=b,c=d]
% \setupxtable             [a=b,c=d]

\installcorenamespace{xtable}
\installcorenamespace{xtablecheck}
\installcorenamespace{xtableswap}

\installframedautocommandhandler \??xtable {xtable} \??xtable

\appendtoks
    \checkxtableparent % so we can deal with undefined settings, not that it's efficient
\to \everysetupxtable

\setupxtable[%
    \c!nr=\plusone,
    \c!nc=\plusone,
    \c!nx=\plusone, % slow
    \c!ny=\plusone, % slow
    \c!align=\v!table, % {\v!flushleft,\v!broad,\v!high}, % just as \bTABLE .. \eTABLE
    \c!frameoffset=.5\linewidth,
    \c!backgroundoffset=\v!frame,
  % \c!framecolor=\s!black,
  % \c!foregroundstyle=\xtableparameter\c!style, % not clean, better capture elsewhere
  % \c!foregroundcolor=\xtableparameter\c!color, % not clean, better capture elsewhere
  % \c!bodyfont=,
    \c!width=\v!fit,
    \c!height=\v!fit,
    \c!maxwidth=8\emwidth,
    \c!autowidth=\v!yes,              % controls framed
    \c!rulethickness=\linewidth,
    \c!strut=\v!yes,
    \c!autostrut=\v!no,
    \c!split=\v!auto,                 % a number will take that many lines
    \c!splitoffset=\zeropoint,        % extra space taken
    \c!aligncharacter=\v!no,
    \c!alignmentcharacter={,},
    \c!alignmentleftsample=,
    \c!alignmentrightsample=,
    \c!alignmentleftwidth=\zeropoint,
    \c!alignmentrightwidth=\zeropoint,
  % \c!option=,                       % \v!stretch {\v!stretch,\v!width}
  % \c!footer=,
  % \c!header=,
    \c!spaceinbetween=,
    \c!textwidth=\v!local,            % was \hsize,
    \c!textheight=\vsize,             % used for vertical spread
    \c!distance=\zeropoint,           % individual column
    \c!columndistance=\zeropoint,     % each column (whole table)
    \c!leftmargindistance=\zeropoint, % whole table
    \c!rightmargindistance=\zeropoint,% whole table
]

\unexpanded\def\startxtable
  {\dosingleempty\tabl_x_start_table}

\let\stopxtable\relax

\def\tabl_x_default_buffer{x_table_\number\c_tabl_x_nesting}
\let\tabl_x_current_buffer\empty

\unexpanded\def\tabl_x_start_table[#settings]% maybe two arguments: [tag][settings] | [tag] | [settings]
  {\bgroup
   \tabl_x_prepare{#settings}%
   \edef\tabl_x_current_buffer{\tabl_x_default_buffer}%
   \buff_pickup{\tabl_x_current_buffer}{startxtable}{stopxtable}\relax\tabl_x_process\zerocount}

\unexpanded\def\processxtablebuffer
  {\dosingleempty\tabl_x_process_buffer_directly}

% These direct buffers can be somewhat faster but it's probably neglectable.
% Anyway, no nesting is supported as we then need to catch (e.g.) rows and
% keep track of nesting and have a more complex redefinition of nested
% instanced \unknown\ it's not worth the trouble. Only use them when you
% really need them and use the embeddedxtable command when nesting them.
% Implementing nesting would be slower than not using direct buffers.

\unexpanded\def\tabl_x_process_buffer_directly[#name]%
  {\bgroup
   \let\tabl_x_start_table\tabl_x_process_buffer
   \edef\tabl_x_current_buffer{#name}%
   \tabl_x_get_buffer %      settings
   \tabl_x_process}

\unexpanded\def\tabl_x_start_ignore[#settings]%
  {}

\unexpanded\def\tabl_x_process_buffer[#settings]%
  {\tabl_x_prepare{#settings}%
   \let\tabl_x_start_table\tabl_x_start_ignore
   \gobbleuntil\stopxtable} % nested xtables are not supported,

%D A bonus: you have to use the following construct inside a macro or
%D direct buffer.

\unexpanded\def\startembeddedxtable
  {\dosingleempty\tabl_x_embedded_start}

\unexpanded\def\tabl_x_embedded_start[#settings]#content\stopembeddedxtable
  {\tabl_x_prepare{#settings}%
   \clf_assignbuffer{embedded_x_table}{\detokenize{#content}}\catcodetable\relax
   \bgroup
   \let\tabl_x_start_table\tabl_x_process_buffer
   \edef\tabl_x_current_buffer{embedded_x_table}%
   \tabl_x_process}

\let\stopembeddedxtable\relax

%D We can also define xtables.

\appendtoks
    \setuevalue{\e!start\currentxtable}{\tabl_x_start_named{\currentxtable}}%
    \setuevalue{\e!stop \currentxtable}{\tabl_x_stop_named}%
\to \everydefinextable

\unexpanded\def\tabl_x_start_named#tag%
  {\bgroup
   \edef\currentxtable{#tag}%
   \dosingleempty\tabl_x_start_named_indeed}

\unexpanded\def\tabl_x_start_named_indeed[#settings]%
  {\advance\c_tabl_x_nesting\plusone
   \dostarttaggedchained\t!table\empty\??xtable
   \iffirstargument
     \setupcurrentxtable[#settings]%
   \fi
   \tabl_x_check_textwidth
  %\forgetall % else whitespace mess
   \edef\tabl_x_current_buffer{\tabl_x_default_buffer}%
   \normalexpanded{\buff_pickup{\tabl_x_current_buffer}{\e!start\currentxtable}{\e!stop\currentxtable}\relax\tabl_x_process\zerocount}}

\unexpanded\def\tabl_x_stop_named
  {}

%D Now we come to processing:

\unexpanded\def\tabl_x_check_textwidth
  {\edef\p_textwidth{\xtableparameter\c!textwidth}%
   \ifx\p_textwidth\v!local
     \d_tabl_x_textwidth\availablehsize
   \else
     \d_tabl_x_textwidth\p_textwidth
   \fi}

\newtoks\everypreparextable

\unexpanded\def\tabl_x_prepare#settings% assumes \iffirstargument to be set
  {\advance\c_tabl_x_nesting\plusone
   \dostarttaggedchained\t!table\empty\??xtable
   \iffirstargument
     \tabl_x_set_checked{#settings}%
   \fi
   \tabl_x_check_textwidth
   \the\everypreparextable
   }% else whitespace mess

\def\tabl_x_get_buffer
  {\clf_gettexbuffer{\tabl_x_current_buffer}}

\let\tabl_x_start_row_yes \relax
\let\tabl_x_start_row_nop \relax
\let\tabl_x_stop_row      \relax
\let\tabl_x_start_cell_yes\relax
\let\tabl_x_start_cell_nop\relax
\let\tabl_x_stop_cell     \relax

\newtoks\t_table_x_cleanup

\unexpanded\def\tabl_x_process
  {\begingroup % *
   \forgetall % moved here
   \dontcomplain % for the moment here till we figure out where we get the overflow
   \usebodyfontparameter\xtableparameter
   \setbox\scratchbox\vbox
     {\doifsomething{\xtableparameter\c!spaceinbetween}{\blank[\xtableparameter\c!spaceinbetween]}}%
   \clf_x_table_create
        option              {\xtableparameter\c!option}%
        textwidth           \d_tabl_x_textwidth
        textheight          \dimexpr\xtableparameter\c!textheight\relax
        maxwidth            \dimexpr\xtableparameter\c!maxwidth\relax
        lineheight          \openlineheight
        columndistance      \dimexpr\xtableparameter\c!columndistance\relax
        leftmargindistance  \dimexpr\xtableparameter\c!leftmargindistance\relax
        rightmargindistance \dimexpr\xtableparameter\c!rightmargindistance\relax
        rowdistance         \ht\scratchbox
        header              {\xtableparameter\c!header}%
        footer              {\xtableparameter\c!footer}%
   \relax
   %
   \letxtableparameter\c!option\empty
   % not so nice but needed as we use this in the setup
   \linewidth\xtableparameter\c!rulethickness\relax
   % so we freeze it
   \c_tabl_x_swapped_max\zerocount
   \begingroup
     \let\tabl_x_start_row_yes \tabl_x_start_row_reflow_width_yes
     \let\tabl_x_start_row_nop \tabl_x_start_row_reflow_width_nop
     \let\tabl_x_stop_row      \tabl_x_stop_row_reflow_width
     \let\tabl_x_start_cell_yes\tabl_x_start_cell_reflow_width_yes
     \let\tabl_x_start_cell_nop\tabl_x_start_cell_reflow_width_nop
     \let\tabl_x_stop_cell     \tabl_x_stop_cell_reflow_width
     \settrialtypesetting
     \tabl_x_get_buffer
     \ifcase\c_tabl_x_swapped_max
     \else
       \tabl_x_flush_swapped
     \fi
     \clf_x_table_reflow_width
   \endgroup
   \begingroup
     \let\tabl_x_start_row_yes \tabl_x_start_row_reflow_height_yes
     \let\tabl_x_start_row_nop \tabl_x_start_row_reflow_height_nop
     \let\tabl_x_stop_row      \tabl_x_stop_row_reflow_height
     \let\tabl_x_start_cell_yes\tabl_x_start_cell_reflow_height_yes
     \let\tabl_x_start_cell_nop\tabl_x_start_cell_reflow_height_nop
     \let\tabl_x_stop_cell     \tabl_x_stop_cell_reflow_height
     \settrialtypesetting
     \ifcase\c_tabl_x_swapped_max
       \tabl_x_get_buffer
     \else
       \tabl_x_flush_swapped
     \fi
     \clf_x_table_reflow_height
   \endgroup
   \begingroup
     \let\tabl_x_start_row_yes \tabl_x_start_row_construct_yes
     \let\tabl_x_start_row_nop \tabl_x_start_row_construct_nop
     \let\tabl_x_stop_row      \tabl_x_stop_row_construct
     \let\tabl_x_start_cell_yes\tabl_x_start_cell_construct_yes
     \let\tabl_x_start_cell_nop\tabl_x_start_cell_construct_nop
     \let\tabl_x_stop_cell     \tabl_x_stop_cell_construct
     \ifcase\c_tabl_x_swapped_max
       \tabl_x_get_buffer
     \else
       \tabl_x_flush_swapped
     \fi
     \clf_x_table_construct
   \endgroup
   \endgroup % *
   \ifinsidesplitfloat
     \tabl_x_flush_float_split
   \else\ifinsidefloat
     \tabl_x_flush_float_normal
   \else
     \tabl_x_flush_text_checked
   \fi\fi
   \clf_x_table_cleanup
   \dostoptagged
   \resetbuffer[\tabl_x_current_buffer]%
   \resetcharacteralign
   \the\t_table_x_cleanup
   \egroup}

% text flow split modes

\installcorenamespace{xtableflushsplit}

\unexpanded\def\tabl_x_flush_text_checked
  {\expandnamespaceparameter\??xtableflushsplit\xtableparameter\c!split\v!no}

% in text flow: headers and footers only once

\setvalue{\??xtableflushsplit\v!yes}%
  {\clf_x_table_flush
     method {\v!split}%
   \relax}

% in text flow: headers and footers only once

\setvalue{\??xtableflushsplit\v!no}%
  {% \noindent       % gives extra line after table
   % \noindentation  % messes up the next indentation
   % \dontleavehmode % no leftskip
   \kern\zeropoint   % yet another guess
   \ignorespaces
   \clf_x_table_flush
     method {\v!normal}%
   \relax
   \removeunwantedspaces}

% in text flow: headers and footers get repeated

\setvalue{\??xtableflushsplit\v!repeat}%
  {\doloop
     {\clf_x_table_flush
        method {\v!split}%
        height \ifdim\pagegoal=\maxdimen\textheight\else\pagegoal\fi
      \relax
      \ifcase\c_tabl_x_state
        \exitloop
      \else
        \page
      \fi}}

% \setvalue{\??xtableflushsplit\v!setups}%
%   {\directsetup{xtable:split:user}}
%
% \startsetups[xtable:split:user]
%     \doloop {
%         \xtablesplitflush % uses \xtablesplitvsize (a macro)
%         \ifcase\xtablesplitstate
%             \exitloop
%         \else
%             \page
%         \fi
%     }
% \stopsetups
%
% \unexpanded\def\xtablesplitflush
%   {\clf_x_table_flush
%      method {\v!split}%
%      height \dimexpr\xtablesplitvsize\relax
%    \relax}
%
% \def\xtablesplitvsize
%   {\ifdim\pagegoal=\maxdimen\textheight\else\pagegoal\fi}
%
% \let\xtablesplitstate\c_tabl_x_state

\let\extratxtablesplitheight\zeropoint % might disappear so don't depend on it

\unexpanded\def\tabl_x_flush_float_normal
  {\clf_x_table_flush
     method {\v!normal}%
   \relax}

\unexpanded\def\tabl_x_flush_float_split
  {\resetdirecttsplit
   \edef\extrasplitfloatlines  {\xtableparameter\c!split}%
   \edef\tsplitminimumfreespace{\the\dimexpr\extratxtablesplitheight+\xtableparameter\c!splitoffset\relax}%
 % \edef\tsplitminimumfreelines{2}% not needed here as we're precise enough
   \let\tsplitdirectsplitter\tabl_x_split_splitter
   \let\tsplitdirectwidth   \d_tabl_x_final_width
   \handledirecttsplit}

\unexpanded\def\tabl_x_split_splitter#height%
  {\setbox\tsplitresult\vbox
     {\clf_x_table_flush
        method {\v!split}%
        height \dimexpr#height\relax
      \relax}%
   \ifcase\c_tabl_x_state
     \global\setfalse\somenextsplitofffloat
   \else
     \global\settrue \somenextsplitofffloat
   \fi}

\unexpanded\def\startxrow
  {\begingroup
   \doifelsenextoptionalcs\tabl_x_start_row_yes\tabl_x_start_row_nop}

\unexpanded\def\tabl_x_start_row_reflow_width_yes[#settings]%
  {\setupcurrentxtable[#settings]%
   \clf_x_table_next_row}

\unexpanded\def\tabl_x_start_row_reflow_width_nop
  {\clf_x_table_next_row}

\unexpanded\def\tabl_x_stop_row_reflow_width
  {}

\let\tabl_x_start_row_reflow_height_yes\tabl_x_start_row_reflow_width_yes
\let\tabl_x_start_row_reflow_height_nop\tabl_x_start_row_reflow_width_nop
\let\tabl_x_stop_row_reflow_height     \tabl_x_stop_row_reflow_width

\unexpanded\def\tabl_x_start_row_construct_yes[#settings]%
  {\setupcurrentxtable[#settings]%
   \dostarttagged\t!tablerow\empty
   \clf_x_table_next_row_option{\xtableparameter\c!samepage}}

\unexpanded\def\tabl_x_start_row_construct_nop
  {\dostarttagged\t!tablerow\empty
   \clf_x_table_next_row}

\unexpanded\def\tabl_x_stop_row_construct
  {\clf_x_table_finish_row
   \dostoptagged}

\unexpanded\def\stopxrow
  {\tabl_x_stop_row
   \endgroup}

\unexpanded\def\startxcell
  {\doifelsenextoptionalcs\tabl_x_start_cell_yes\tabl_x_start_cell_nop}

\unexpanded\def\stopxcell
  {\tabl_x_stop_cell}

% \unexpanded\def\dummyxcell
%   {\tabl_x_start_cell_nop
%    \tabl_x_stop_cell}

\unexpanded\def\dummyxcell
  {\begingroup
   \let\inheritedxtableframed\relax
   \tabl_x_start_cell_nop
   \tabl_x_stop_cell
   \endgroup}

% \unexpanded\def\tabl_x_begin_of_cell
%   {%\inhibitblank % already in framed
%    \everypar{\delayedbegstrut}}

\def\tabl_x_setup_character_align
  {\edef\p_left {\directxtableparameter\c!alignmentleftsample}%
   \edef\p_right{\directxtableparameter\c!alignmentrightsample}%
   \ifx\p_left\empty
     \scratchdimenone\dimexpr\directxtableparameter\c!alignmentleftwidth\relax
   \else
     \setbox\scratchbox\hbox{\p_left}%
     \scratchdimenone\wd\scratchbox
   \fi
   \ifx\p_right\empty
     \scratchdimentwo\dimexpr\directxtableparameter\c!alignmentrightwidth\relax
   \else
     \setbox\scratchbox\hbox{\p_right}%
     \scratchdimentwo\wd\scratchbox
   \fi
   \clf_setcharacteraligndetail
     \clf_x_table_c
     {\directxtableparameter\c!alignmentcharacter}%
     \scratchdimenone
     \scratchdimentwo
   \relax}

\newtoks\t_tabl_x_every_cell

% \appendtoks
%     \inhibitblank % already in framed
% \to \t_tabl_x_every_cell

\appendtoks
    \edef\p_characteralign{\directxtableparameter\c!aligncharacter}%
    \ifx\p_characteralign\v!yes
        \ifcase\clf_x_table_r\or
            \tabl_x_setup_character_align
        \fi
        \signalcharacteralign\clf_x_table_c\clf_x_table_r
    \fi
\to \t_tabl_x_every_cell

\unexpanded\def\tabl_x_begin_of_cell
  {\the\t_tabl_x_every_cell
   \everypar{\delayedbegstrut}}

\unexpanded\def\tabl_x_end_of_cell
  {\ifhmode
     \delayedendstrut
     \par
   \else
     \par
     \ifdim\prevdepth<\zeropoint % =-1000pt ?
       \vskip-\strutdp
     \else
       \removebottomthings
     \fi
   \fi}

% For historic reasons we support both nx/nc and ny/nr : maybe nx/ny becomes
% obsolete some day. The let as well as the direct speed things up a bit. We
% could also consider a \defaultxtableparameter.
%
% \c_tabl_x_nx\defaultxtableparameter\c!nc{\defaultxtableparameter\c!nx\plusone}
% \c_tabl_x_ny\defaultxtableparameter\c!nr{\defaultxtableparameter\c!ny\plusone}
%
% Although this becomes kind of messy. It saves already time that we only check
% for it when we have settings.

% \def\tabl_x_set_hsize
%   {\hsize.25\maxdimen} % let's be reasonable

% \def\tabl_x_set_hsize
%   {\edef\p_width{\xtableparameter\c!width}%
%    \ifx\p_width\empty
%      \hsize.25\maxdimen % is this really needed
%    \fi}

\let\tabl_x_set_hsize\relax

\unexpanded\def\tabl_x_start_cell_reflow_width_yes[#settings]%
  {\setbox\b_tabl_x\hpack\bgroup
   \ifnum\c_tabl_x_nesting>\plusone
     \letxtableparameter\c!width \v!fit  % overloads given width
     \letxtableparameter\c!height\v!fit  % overloads given height
   \fi
   %
   \letxtableparameter\c!nx\plusone
   \letxtableparameter\c!ny\plusone
   \letxtableparameter\c!nc\plusone
   \letxtableparameter\c!nr\plusone
   %
   \setupcurrentxtable[#settings]%
   %
   \c_tabl_x_nx\directxtableparameter\c!nc\relax
   \c_tabl_x_ny\directxtableparameter\c!nr\relax
   \ifnum\c_tabl_x_nx=\plusone
     \c_tabl_x_nx\directxtableparameter\c!nx\relax
   \fi
   \ifnum\c_tabl_x_ny=\plusone
     \c_tabl_x_ny\directxtableparameter\c!ny\relax
   \fi
   %
   \d_tabl_x_distance\xtableparameter\c!distance\relax
   \clf_x_table_init_reflow_width_option{\xtableparameter\c!option}%
   \inheritedxtableframed\bgroup
   \tabl_x_begin_of_cell
   \tabl_x_set_hsize}

\unexpanded\def\tabl_x_start_cell_reflow_width_nop
  {\setbox\b_tabl_x\hpack\bgroup
   \ifnum\c_tabl_x_nesting>\plusone
     \letxtableparameter\c!width \v!fit  % overloads given width
     \letxtableparameter\c!height\v!fit  % overloads given height
   \fi
   \c_tabl_x_nx\plusone
   \c_tabl_x_ny\plusone
   \d_tabl_x_distance\xtableparameter\c!distance\relax
   \clf_x_table_init_reflow_width
   \inheritedxtableframed\bgroup
   \tabl_x_begin_of_cell
   \tabl_x_set_hsize}

\unexpanded\def\tabl_x_stop_cell_reflow_width
  {\tabl_x_end_of_cell
   \egroup
   \egroup
   \clf_x_table_set_reflow_width}

\unexpanded\def\tabl_x_start_cell_reflow_height_yes[#settings]%
  {\setbox\b_tabl_x\hpack\bgroup
   \clf_x_table_init_reflow_height
   \ifcase\c_tabl_x_skip_mode % can be sped up
     \ifnum\c_tabl_x_nesting>\plusone
       \letxtableparameter\c!height\v!fit  % overloads given height
     \fi
     \setupcurrentxtable[#settings]%
     \relax
     \letxtableparameter\c!width\d_tabl_x_width  % overloads given width
     \inheritedxtableframed\bgroup
     \tabl_x_begin_of_cell
   \fi}

\unexpanded\def\tabl_x_start_cell_reflow_height_nop
  {\setbox\b_tabl_x\hpack\bgroup
   \clf_x_table_init_reflow_height
   \ifcase\c_tabl_x_skip_mode % can be sped up
     \ifnum\c_tabl_x_nesting>\plusone
       \letxtableparameter\c!height\v!fit  % overloads given height
     \fi
     \relax
     \letxtableparameter\c!width\d_tabl_x_width  % overloads given width
     \inheritedxtableframed\bgroup
     \tabl_x_begin_of_cell
   \fi}

\unexpanded\def\tabl_x_stop_cell_reflow_height
  {\ifcase\c_tabl_x_skip_mode
     \tabl_x_end_of_cell
     \egroup
   \fi
   \egroup
   \clf_x_table_set_reflow_height}

\unexpanded\def\tabl_x_start_cell_construct_yes[#settings]%
  {\dostarttagged\t!tablecell\empty % can't we just tag the box
   \setbox\b_tabl_x\hpack\bgroup
   \setupcurrentxtable[#settings]%
   \letxtableparameter\c!width \d_tabl_x_width  % overloads given width
   \letxtableparameter\c!height\d_tabl_x_height % overloads given height
   \clf_x_table_init_construct
   \inheritedxtableframed\bgroup
   \tabl_x_begin_of_cell
   \dotagxtablecell}

\unexpanded\def\tabl_x_start_cell_construct_nop
  {\dostarttagged\t!tablecell\empty % can't we just tag the box
   \setbox\b_tabl_x\hpack\bgroup
   \letxtableparameter\c!width \d_tabl_x_width  % overloads given width
   \letxtableparameter\c!height\d_tabl_x_height % overloads given height (commenting it ... nice option)
   \clf_x_table_init_construct
   \inheritedxtableframed\bgroup
   \tabl_x_begin_of_cell
   \dotagxtablecell}

\unexpanded\def\tabl_x_stop_cell_construct
  {\tabl_x_end_of_cell
   \egroup
   \dotagxtablesignal % harmless spot
   \egroup
   \clf_x_table_set_construct
   \dostoptagged}

\unexpanded\def\startxcellgroup
  {\begingroup
   \dosingleempty\tabl_x_start_cell_group}

\unexpanded\def\stopxcellgroup
  {\endgroup}

\unexpanded\def\tabl_x_start_cell_group[#settings]%
  {\iffirstargument
     \tabl_x_set_checked{#settings}%
   \fi}

\unexpanded\def\startxrowgroup
  {\begingroup
   \dosingleempty\tabl_x_start_row_group}

\unexpanded\def\stopxrowgroup
  {\dostoptagged
   \endgroup}

\unexpanded\def\tabl_x_start_row_group[#settings]%
  {\iffirstargument
     \tabl_x_set_checked{#settings}%
   \fi}

% \def\tabl_x_set_checked#settings
%   {\doifassignmentelse{#settings}
%      {\setupcurrentxtable[#settings]}
%      {\ifcsname\namedxtablehash{#settings}\s!parent\endcsname
%         \edef\currentxtable{#settings}%
%       \fi}}

\unexpanded\def\tabl_x_set_checked#settings%
  {\ifcsname\namedxtablehash{#settings}\s!parent\endcsname
     \edef\currentxtable{#settings}%
   \else
     \setupcurrentxtable[#settings]%
   \fi}

\unexpanded\def\startxtablehead{\begingroup\c_tabl_x_mode\plusone  \dosingleempty\tabl_x_start_partition}
\unexpanded\def\startxtablefoot{\begingroup\c_tabl_x_mode\plustwo  \dosingleempty\tabl_x_start_partition}
\unexpanded\def\startxtablenext{\begingroup\c_tabl_x_mode\plusthree\dosingleempty\tabl_x_start_partition}
\unexpanded\def\startxtablebody{\begingroup\c_tabl_x_mode\plusfour \dosingleempty\tabl_x_start_partition}

\unexpanded\def\tabl_x_start_partition[#settings]%
  {\iffirstargument
     \tabl_x_set_checked{#settings}%
   \fi}

\unexpanded\def\tabl_x_stop_partition
  {\endgroup}

\let\stopxtablehead\tabl_x_stop_partition
\let\stopxtablefoot\tabl_x_stop_partition
\let\stopxtablenext\tabl_x_stop_partition
\let\stopxtablebody\tabl_x_stop_partition

%D This is an experiment! Beware: you can create loops by using nested
%D references to already chained settings.
%D
%D \startbuffer
%D \setupxtable[suffix][align=middle,foregroundcolor=red]
%D \setupxtable[blabla][foregroundstyle=slanted]
%D \setupxtable[crap]  [foregroundcolor=blue]
%D \setupxtable[bold]  [crap][foregroundstyle=bold]
%D
%D \startxtable[frame=off]
%D     \startxtablehead
%D         \startxrow[bold]
%D             \startxcell[suffix] a 0 \stopxcell
%D             \startxcell[blabla] a 1 \stopxcell
%D             \startxcell         a 2 \stopxcell
%D         \stopxrow
%D     \stopxtablehead
%D     \startxtablebody
%D         \startxrow \startxcell[suffix][ny=2] a 1 \stopxcell \startxcell b 1 \stopxcell \startxcell c 1 \stopxcell \stopxrow
%D         \startxrow                                          \startxcell b 2 \stopxcell \startxcell c 2 \stopxcell \stopxrow
%D         \startxrow \startxcell[suffix]       a 3 \stopxcell \startxcell b 3 \stopxcell \startxcell c 3 \stopxcell \stopxrow
%D         \startxrow \startxcell[suffix]       a 4 \stopxcell \startxcell b 4 \stopxcell \startxcell c 4 \stopxcell \stopxrow
%D         \startxrow \startxcell[suffix]       a 5 \stopxcell \startxcell b 5 \stopxcell \startxcell c 5 \stopxcell \stopxrow
%D     \stopxtablebody
%D \stopxtable
%D \stopbuffer
%D
%D \typebuffer \placetable{}{\getbuffer}

\appendtoks
    \letvalue{\??xtablecheck\currentxtable}\relax % faster than checking parent
\to \everysetupxtable

% \definefontfamily[newtimes][serif][TeX Gyre Termes]
% \setupxtable[newtimes][foregroundstyle=\newtimes]
% \startxcell[newtimes]                  ...\stopxcell
% \startxcell[foregroundstyle=\newtimes] ...\stopxcell

% % \ifcsname\namedxtablehash{#tag}\s!parent\endcsname
% % \ifcsname\??xtablecheck#tag\endcsname
%   \ifcsname\??xtablecheck\detokenize\expandafter{\normalexpanded{#tag}}\endcsname % two times slower on keywords
%     \expandafter\whatever                                                         % but more tolerant for tricky key=value
%   \else
%     \expandafter\whatever
%   \fi[#tag]

% groups

\unexpanded\def\startxgroup
  {\begingroup
   \doifelsenextoptionalcs\tabl_x_start_group_delayed_one\relax}

\unexpanded\def\stopxgroup
  {\endgroup}

\unexpanded\def\tabl_x_start_group_delayed_one[#tag]%
  {\ifcsname\??xtablecheck\detokenize\expandafter{\normalexpanded{#tag}}\endcsname
     \expandafter\tabl_x_start_group_delayed_two
   \else
     \expandafter\setupcurrentxtable
   \fi[#tag]}

\unexpanded\def\tabl_x_start_group_delayed_two[#tag]%
  {\ifx\currentxtable\empty \else
     \chaintocurrentxtable{#tag}%
   \fi
   \edef\currentxtable{#tag}%
   \doifelsenextoptionalcs\setupcurrentxtable\relax}

\let\startxrowgroup \startxgroup
\let\stopxrowgroup  \stopxgroup
\let\startxcellgroup\startxgroup
\let\stopxcellgroup \stopxgroup

% cells (maybe also check for 1 etc but it becomes messy)

\unexpanded\def\startxcell
  {\begingroup
   \doifelsenextoptionalcs\tabl_x_start_cell_delayed_one\tabl_x_start_cell_nop}

\unexpanded\def\tabl_x_start_cell_delayed_one[#tag]%
  {\ifcsname\??xtablecheck\detokenize\expandafter{\normalexpanded{#tag}}\endcsname
     \expandafter\tabl_x_start_cell_delayed_two
   \else
     \expandafter\tabl_x_start_cell_yes
   \fi[#tag]}

\unexpanded\def\tabl_x_start_cell_delayed_two[#tag]%
  {\ifx\currentxtable\empty \else
     \chaintocurrentxtable{#tag}%
   \fi
   \edef\currentxtable{#tag}%
   \doifelsenextoptionalcs\tabl_x_start_cell_yes\tabl_x_start_cell_nop}

\unexpanded\def\stopxcell
  {\tabl_x_stop_cell
   \endgroup}

% rows

\unexpanded\def\startxrow
  {\begingroup
   \doifelsenextoptionalcs\tabl_x_start_row_delayed_one\tabl_x_start_row_nop}

\unexpanded\def\tabl_x_start_row_delayed_one[#tag]%
  {\ifcsname\??xtablecheck\detokenize\expandafter{\normalexpanded{#tag}}\endcsname
     \expandafter\tabl_x_start_row_delayed_two
   \else
     \expandafter\tabl_x_start_row_yes
   \fi[#tag]}

\unexpanded\def\tabl_x_start_row_delayed_two[#tag]%
  {\ifx\currentxtable\empty \else
     \chaintocurrentxtable{#tag}%
   \fi
   \edef\currentxtable{#tag}%
   \doifelsenextoptionalcs\tabl_x_start_row_yes\tabl_x_start_row_nop}

\unexpanded\def\stopxrow
  {\tabl_x_stop_row
   \endgroup}

%D A bonus, not advertised but some like it this way:

\unexpanded\def\tabl_x_nc
  {\startxrow
   \let\NC\tabl_x_nc_next
   \let\NR\tabl_x_nr
   \startxcell}

\unexpanded\def\tabl_x_nc_next
  {\stopxcell
   \startxcell}

\unexpanded\def\tabl_x_nr
  {\stopxcell
   \stopxrow
   \let\NC\tabl_x_nc}

\appendtoks
   \let\NC\tabl_x_nc
   \let\NR\tabl_x_nr
\to \everypreparextable

%D Another bonus, suggested by Taco at the 2018 \CONTEXT\ meeting.

\unexpanded\def\tabl_x_c_cell_start#settings%
  {\begingroup
   \tabl_x_set_checked{#settings}%
   \doifelsenextoptionalcs\tabl_x_start_cell_yes\tabl_x_start_cell_nop}

\unexpanded\def\tabl_x_c_cell_stop
  {\tabl_x_stop_cell
   \endgroup}

% \unexpanded\def\dummyxcell
%   {\tabl_x_start_cell_nop
%    \tabl_x_stop_cell}

\def\tabl_x_flush_swapped
  {\dorecurse\c_tabl_x_swapped_max
     {\expandafter
      \startxrow
        \the\csname\??xtableswap##1\endcsname\relax
      \stopxrow}}

\def\tabl_x_collect_allocate
  {\expandafter\newtoks\csname\??xtableswap\number\c_tabl_x_swapped\endcsname
   \expandafter\let\expandafter\t_tabl_x_swapped\csname\??xtableswap\number\c_tabl_x_swapped\endcsname}

\def\tabl_x_collect_advance
  {\global\advance\c_tabl_x_swapped\plusone
   \ifnum\c_tabl_x_swapped>\c_tabl_x_swapped_max
     \global\c_tabl_x_swapped_max\c_tabl_x_swapped
   \fi
   \expandafter\let\expandafter\t_tabl_x_swapped\csname\??xtableswap\number\c_tabl_x_swapped\endcsname
   \ifx\t_tabl_x_swapped\relax
     \tabl_x_collect_allocate
   \fi}

\unexpanded\def\tabl_x_collect_cell_start
  {\doifelsenextoptionalcs
     \tabl_x_collect_cell_start_yes
     \tabl_x_collect_cell_start_nop}

\def\tabl_x_collect_cell_start_nop#content\stopxcell
  {\tabl_x_collect_advance
   \ifx\m_tabl_x_swapped_settings\empty
     \gtoksapp\t_tabl_x_swapped{\tabl_x_c_cell_start{}#content\tabl_x_c_cell_stop}%
   \else
     \gtoksapp\t_tabl_x_swapped\expandafter{\expandafter\tabl_x_c_cell_start\expandafter{\m_tabl_x_swapped_settings}#content\tabl_x_c_cell_stop}%
   \fi}

\def\tabl_x_collect_cell_start_yes[#settings]#content\stopxcell
  {\tabl_x_collect_advance
   \ifx\m_tabl_x_swapped_settings\empty
     \gtoksapp\t_tabl_x_swapped{\tabl_x_c_cell_start{}[#settings]#content\tabl_x_c_cell_stop}%
   \else
     \gtoksapp\t_tabl_x_swapped\expandafter{\expandafter\tabl_x_c_cell_start\expandafter{\m_tabl_x_swapped_settings}[#settings]#content\tabl_x_c_cell_stop}%
   \fi
   \getdummyparameters[\c!ny=1,#settings]%
   \scratchcounter\numexpr\dummyparameter\c!ny-\plusone\relax
   \ifcase\scratchcounter\else
     \dorecurse\scratchcounter\tabl_x_collect_advance
   \fi}

\unexpanded\def\startxcolumn % todo: arguments
  {\begingroup
   \global\c_tabl_x_swapped\zerocount
   \let\startxcell\tabl_x_collect_cell_start
   \let\stopxcell \relax
   \doifelsenextoptionalcs\tabl_x_start_column_yes\tabl_x_start_column_nop}

\def\tabl_x_start_column_yes[#1]%
  {\xdef\m_tabl_x_swapped_settings{#1}}

\def\tabl_x_start_column_nop
  {\glet\m_tabl_x_swapped_settings\empty}

\unexpanded\def\stopxcolumn
  {\endgroup}

\appendtoks
    \dorecurse\c_tabl_x_swapped_max
      {\global\csname\??xtableswap\number#1\endcsname\emptytoks}%
\to \t_table_x_cleanup

%D \stopbuffer
%D \setupxtable[one][foregroundcolor=red]
%D \setupxtable[two][foregroundcolor=blue]
%D
%D \startlinecorrection
%D \startxtable
%D    \startxrow[one]
%D        \startxcell[width=5cm] Row 1, Column 1 \stopxcell
%D        \startxcell Row 1, Column 2 \stopxcell
%D        \startxcell Row 1, Column 3 \stopxcell
%D    \stopxrow
%D    \startxrow[two]
%D        \startxcell Row 2, Column 1 \stopxcell
%D        \startxcell Row 2, Column 2 \stopxcell
%D        \startxcell Row 2, Column 3 \stopxcell
%D    \stopxrow
%D \stopxtable
%D \stoplinecorrection
%D
%D \startlinecorrection
%D \startxtable
%D    \startxcolumn[one]
%D        \startxcell[width=5cm] Row 1, Column 1 \stopxcell
%D        \startxcell Row 1, Column 2 \stopxcell
%D        \startxcell Row 1, Column 3 \stopxcell
%D    \stopxcolumn
%D    \startxcolumn[two]
%D        \startxcell Row 2, Column 1 \stopxcell
%D        \startxcell Row 2, Column 2 \stopxcell
%D        \startxcell Row 2, Column 3 \stopxcell
%D    \stopxcolumn
%D \stopxtable
%D \stoplinecorrection
%D \stopbuffer
%D
%D \typebuffer \getbuffer

\protect \endinput