T-F-S / tcolorbox

A LaTeX package to create highly customizable colored boxes.
http://www.ctan.org/pkg/tcolorbox
LaTeX Project Public License v1.3c
213 stars 15 forks source link

Stricter checks on box names in `\newtcbox` #264

Open muzimuzhi opened 6 months ago

muzimuzhi commented 6 months ago

According to change entries of v5.1.0,

  • Internal implementation of all \new* commands, e.g. \newtcolorbox changed. Note that a missing backlash for tcbox-like commands e.g. \newtcbox{mybox} instead of \newtcbox{\mybox} is no longer tolerated.

But I find \newtcbox{mybox}{...} works like \newtcbox{\ybox}{...} quietly. https://github.com/T-F-S/tcolorbox/blob/067f384e2ba85ba3fd91958eb6adb8a1bcea6c29/tex/latex/tcolorbox/tcolorbox.sty#L2315-L2320

More corner cases:

\documentclass{article}
\usepackage{tcolorbox}

\newtcbox{\mybox a}{} % defines \myboxa
\newtcbox{mybox}{}    % defines \ybox
\newtcbox{m}{}        % defines the empty cs (\csname\endcsname)

\begin{document}
\end{document}

Here I suggest to enforce stricter checks on box names, so that a valid box name must be a single control sequence after space trimming.

% stricter checks on box names
\cs_new:Npn \__tcobox_set_backslash_removed:Nn #1#2
  {
    \tl_set:Ne #1 { \tl_trim_spaces:n {#2} }
    \bool_lazy_and:nnTF
      { \tl_if_single_p:N #1 }
      { \exp_after:wN \token_if_cs_p:N #1 }
      { \tl_set:Ne #1 { \exp_after:wN \cs_to_str:N #1 } }
      { } % raise an error
  }

To further totally ignore the whole box (re)definition if invalid box name is detected, it seems a bigger refactor is needed.

Below is one such attempt for \newtcbox which can be extended to other box creation commands. It avoids assignment to \l__tcobox_tmpa_tl and limit use of \NewDocumentCommand to user commands only (no internals like \__tcobox_new_tcbox_i:w), but results in longer definition of \newtcbox.

I'd like to find a more generic way, perhaps one (higher-order) function for the \(re)newcommand routine, and another one for \NewDocumentCommand-like routine.

\documentclass{article}
\usepackage{tcolorbox}

\ExplSyntaxOn
\msg_new:nnn { tcolorbox } { invalid-box-name }
  {
    Invalid~box~name~`` \tl_to_str:n {#1} ''.~
    A~single~command~is~expected~after~space~trimming.~
    I'll~drop~this~box~definition.
  }

\prg_new_conditional:Npnn \__tcobox_if_valid_box_name:n #1 { TF }
  {
    \bool_lazy_and:nnTF
      { \tl_if_single_p:n {#1} }
      { \token_if_cs_p:N #1 }
      { \prg_return_true: }
      { \prg_return_false: }
  }

% \newtcbox[<init options>]{\<name>}[<number>][<default>]{<options>}
\RenewDocumentCommand \newtcbox { +O{} m o +o +m }
  {
    \bool_set_true:N \l__tcobox_prevent_init_overwrite_bool
    \__tcobox_new_tcbox_auxi:e { \tl_trim_spaces:n {#2} }
      \newcommand {#1} {#3} {#4} {#5}
  }

% #1 \<name>
\cs_new_protected:Npn \__tcobox_new_tcbox_auxi:n #1
  {
    \__tcobox_if_valid_box_name:nTF { #1 }
      {
        % get <name> from \<name> only once
        \__tcobox_new_tcbox_auxii:NeNnnnn #1 { \cs_to_str:N #1 }
      }
      {
        % or use \tcb@error
        \msg_error:nnn { tcolorbox } { invalid-box-name } { #1 }
        \use_none:nnnnn
      }
  }
\cs_generate_variant:Nn \__tcobox_new_tcbox_auxi:n {e}

% #1 \<name>, #2 <name>, #3 \(re)newcommand
% #4 init options, #5 number, #6 default, #7 options
\cs_new_protected:Npn \__tcobox_new_tcbox_auxii:NnNnnnn #1#2#3#4#5#6#7
  {
    \__tcobox_process_newtcolorbox:nn { #4 }{ #2 }
    \tl_if_novalue:nTF { #5 }
      {     #3 #1             { \tcbox[{#7,options@for=#2}] } }
      {
        \tl_if_novalue:nTF { #6 }
          { #3 #1 [ #5 ]      { \tcbox[{#7,options@for=#2}] } }
          { #3 #1 [ #5 ][ #6 ]{ \tcbox[{#7,options@for=#2}] } }
      }
  }
\cs_generate_variant:Nn \__tcobox_new_tcbox_auxii:NnNnnnn {Ne}

\ExplSyntaxOff

\begin{document}
% valid box names
\newtcbox{\myboxa}{}
\newtcbox{ \myboxb}{}

% invalid box names
\newtcbox{\mybox c}{}
\newtcbox{d}{}
\newtcbox{eee}{}
\newtcbox{}{}
\end{document}

Came into this idea when viewing \tl_set:Nx to \tl_set:Ne change in \__tcobox_set_backslash_removed:Nn in 067f384 (6.2.0, 2024-01-10).

T-F-S commented 5 months ago

Thanks for the new idea. Currently, I have no time, but I will look into it end of next week,

T-F-S commented 5 months ago

I will add stricter checks to \__tcobox_set_backslash_removed:Nn, but the box creation commands will stay as they are for a while. Nevertheless, I keep the idea of overworking the box creation commands in mind.