From 8abdfcec525ef0d777e485bd64c048b264f127ed Mon Sep 17 00:00:00 2001
From: Yukai Chou <muzimuzhi@gmail.com>
Date: Sat, 16 Nov 2024 01:10:41 +0800
Subject: [PATCH] feat: keep braces when splitting

So special cells needs only one group of braces.
---
 build.lua                     |  2 +-
 tabularray.sty                | 25 +++++++++++++----
 testfiles-old/library-009.tex |  4 +--
 testfiles-old/table-001.tlg   |  3 ++
 testfiles/loading-001.tlg     |  3 ++
 zutil.sty                     | 52 +++++++++++++++++++++++++++++++++++
 6 files changed, 80 insertions(+), 9 deletions(-)
 create mode 100644 zutil.sty

diff --git a/build.lua b/build.lua
index 2e9badb..e098647 100644
--- a/build.lua
+++ b/build.lua
@@ -1,7 +1,7 @@
 
 module = "tabularray"
 
-sourcefiles  = { "tabularray.sty" }
+sourcefiles  = { "tabularray.sty", "zutil.sty" }
 
 checkengines = { "pdftex", "xetex", "luatex" }
 stdengine    = "pdftex"
diff --git a/tabularray.sty b/tabularray.sty
index 8e18117..03a81a5 100644
--- a/tabularray.sty
+++ b/tabularray.sty
@@ -33,6 +33,8 @@
   \usepackage{xparse}
 }
 
+\RequirePackage{zutil}
+
 \AtBeginDocument{
   \@ifpackageloaded{xcolor}{\RequirePackage{ninecolors}}{}
   \@ifpackageloaded{hyperref}{
@@ -2726,7 +2728,7 @@
 \cs_new_protected:Npn \__tblr_split_table_to_lines:NN #1 #2
   {
     \__tblr_insert_braces:N #1
-    \seq_set_split:NnV \l__tblr_tmpa_seq { \\ } #1
+    \zutil_seq_set_split_keep_braces:NnV \l__tblr_tmpa_seq { \\ } #1
     \seq_clear:N #2
     \seq_map_inline:Nn \l__tblr_tmpa_seq
       {
@@ -2773,7 +2775,7 @@
 %% #1: row number, #2 the line text
 \cs_new_protected:Npn \__tblr_split_one_line:nn #1 #2
   {
-    \seq_set_split:Nnn \l__tblr_tmpa_seq { & } { #2 }
+    \zutil_seq_set_split_keep_braces:Nnn \l__tblr_tmpa_seq { & } { #2 }
     \int_set:Nn \c@rownum {#1}
     \int_zero:N \c@colnum
     \seq_map_inline:Nn \l__tblr_tmpa_seq
@@ -2849,7 +2851,8 @@
               { \__tblr_save_real_cell_text:w #1 }
           }
       }
-      { \__tblr_save_real_cell_text:w {#1} }
+      % prefix #1 with \prg_do_nothing: to prevent brace stripping
+      { \__tblr_save_real_cell_text_aux:w \prg_do_nothing: {#1} }
   }
 
 \cs_new_protected:Npn \__tblr_extract_one_table_command:N #1
@@ -2895,6 +2898,12 @@
     \tl_set:Nn \l__tblr_saved_cell_text_after_table_commands_tl {#1}
   }
 
+\cs_new_protected:Npn \__tblr_save_real_cell_text_aux:w #1 \q_stop
+  {
+    % o-type expansion is used to digest \prg_do_nothing:
+    \tl_set:No \l__tblr_saved_cell_text_after_table_commands_tl {#1}
+  }
+
 %%% --------------------------------------------------------
 %%> \section{Initialize Table Inner Specifications}
 %%% --------------------------------------------------------
@@ -3590,11 +3599,15 @@
     \group_begin:
     \tl_set:Ne \l__tblr_c_tl { \__tblr_spec_item:ne { text } {[#1][#2]} }
     %% when the cell text is guarded by a pair of curly braces,
-    %% we unbrace it and ignore cmd option of the cell, see issue #90.
+    %% we unbrace then trim spaces from it and ignore cmd option of the cell,
+    %% see issue #90.
     \bool_lazy_and:nnTF
       { \tl_if_single_p:N \l__tblr_c_tl }
       { \exp_args:NV \tl_if_head_is_group_p:n \l__tblr_c_tl }
-      { \exp_last_unbraced:NNV \tl_set:Nn \l__tblr_c_tl \l__tblr_c_tl }
+      {
+        \tl_set:Ne \l__tblr_c_tl
+          { \exp_after:wN \tl_trim_spaces:n \l__tblr_c_tl }
+      }
       {
         \tl_set:Ne \l__tblr_cell_cmd_tl
           { \__tblr_data_item:neen { cell } {#1} {#2} { cmd } }
@@ -3696,7 +3709,7 @@
   {
     \tl_set_eq:NN \l__tblr_tmpb_tl \l__tblr_c_tl
     \__tblr_insert_braces:N \l__tblr_tmpb_tl
-    \seq_set_split:NnV \l__tblr_tmpa_seq { \\ } \l__tblr_tmpb_tl
+    \zutil_seq_set_split_keep_braces:NnV \l__tblr_tmpa_seq { \\ } \l__tblr_tmpb_tl
     \tl_set:Nn \l__tblr_w_tl { 0pt }
     \seq_map_variable:NNn \l__tblr_tmpa_seq \l__tblr_tmpa_tl
       {
diff --git a/testfiles-old/library-009.tex b/testfiles-old/library-009.tex
index fce7fb2..237fc3b 100644
--- a/testfiles-old/library-009.tex
+++ b/testfiles-old/library-009.tex
@@ -20,7 +20,7 @@
 \BEGINTEST{siunitx: testing multiline cells}%#90
 \centering
 \begin{tblr}{colspec=lSS,hlines,vlines}
-{two line \\ column header} & {{{column header}}} & {{{two line \\ column header}}} \\
+{two line \\ column header} & {column header} & {two line \\ column header} \\
 \end{tblr}
 \ENDTEST
 
@@ -29,7 +29,7 @@
 \BEGINTEST{testing multiline cells with three pairs of braces}%#90
 \centering
 \begin{tblr}{hlines,vlines}
-{{{one line cell}}} & {{{two line \\ text}}} \\
+{one line cell} & {two line \\ text} \\
 \end{tblr}
 \ENDTEST
 
diff --git a/testfiles-old/table-001.tlg b/testfiles-old/table-001.tlg
index af5fa96..8e710cb 100644
--- a/testfiles-old/table-001.tlg
+++ b/testfiles-old/table-001.tlg
@@ -2,6 +2,9 @@ This is a generated file for the l3build validation system.
 Don't change this file in any respect.
 (tabularray.sty
 Package: tabularray ....-..-.. v... Typeset tabulars and arrays with LaTeX3
+(zutil.sty
+Package: zutil ....-..-.. v... Z's utilities, l3seq part
+)
 \l__tblr_a_int=\count...
 \l__tblr_dp_dim=\dimen...
 \l__tblr_ht_dim=\dimen...
diff --git a/testfiles/loading-001.tlg b/testfiles/loading-001.tlg
index 832ef26..33085a3 100644
--- a/testfiles/loading-001.tlg
+++ b/testfiles/loading-001.tlg
@@ -2,6 +2,9 @@ This is a generated file for the l3build validation system.
 Don't change this file in any respect.
 (tabularray.sty
 Package: tabularray ....-..-.. v... Typeset tabulars and arrays with LaTeX3
+(zutil.sty
+Package: zutil ....-..-.. v... Z's utilities, l3seq part
+)
 \l__tblr_a_int=\count...
 \l__tblr_dp_dim=\dimen...
 \l__tblr_ht_dim=\dimen...
diff --git a/zutil.sty b/zutil.sty
new file mode 100644
index 0000000..31ce610
--- /dev/null
+++ b/zutil.sty
@@ -0,0 +1,52 @@
+\ProvidesExplPackage {zutil} {2024-11-15} {0.1}
+  {Z's utilities, l3seq part}
+
+%%
+%% l3seq extras
+%%
+\msg_new:nnnn { zutil } { seq/empty-delimiter }
+  { Empty~delimiter~is~not~supported~in~#1. }
+  { I~will~skip~setting~#2. }
+
+\cs_new_protected:Npn \zutil_seq_set_split_keep_braces:Nnn #1
+  {
+    \__zutil_seq_set_split:NNNNnn
+      \__kernel_tl_set:Nx \__zutil_seq_trim_spaces:n #1
+      \zutil_seq_set_split_keep_braces:Nnn
+  }
+\cs_generate_variant:Nn \zutil_seq_set_split_keep_braces:Nnn { NnV }
+
+% This implementation is quicker than adding another \prg_do_nothing:
+% and \exp_args:No trick to \__zutil_seq_set_split:Nw and
+% \__zutil_seq_set_split:w, resp.
+\cs_new:Npn \__zutil_seq_trim_spaces:n #1 { { \tl_trim_spaces:n {#1} } }
+
+% Compared to \__seq_set_split:NNNnn, a forth N-arg is added which
+% holds the user function, i.e. \zutil_seq_set_split_keep_braces:Nnn,
+% for use in error message.
+%
+% l3seq internals \__seq_set_split:Nw, \__seq_set_split_end:, and
+% \l__seq_internal_a_tl are used.
+\cs_new_protected:Npn \__zutil_seq_set_split:NNNNnn #1#2#3#4#5#6
+  {
+    \tl_if_empty:nTF {#5}
+      {
+        \msg_error:nnnn { zutil } { seq/empty-delimiter } {#4} {#3}
+      }
+      {
+        \tl_set:Nn \l__seq_internal_a_tl
+          {
+            \__seq_set_split:Nw #2 \prg_do_nothing:
+            #6
+            \__seq_set_split_end:
+          }
+        \tl_replace_all:Nnn \l__seq_internal_a_tl {#5}
+          {
+            \__seq_set_split_end:
+            \__seq_set_split:Nw #2 \prg_do_nothing:
+          }
+        \__kernel_tl_set:Nx \l__seq_internal_a_tl { \l__seq_internal_a_tl }
+        #1 #3 { \s__seq \l__seq_internal_a_tl }
+      }
+  }
+