在列表环境中用“expl3”代码替换“\def”和“\@ifnextchar”

在列表环境中用“expl3”代码替换“\def”和“\@ifnextchar”

我试图回答我在论坛上提出的另一个问题(如何修改shortlst中“\item”之间的水平和垂直间距?)关于shortlst包裹,最后(我认为)不可能这样做。

话虽如此,我还是决定采用shortlst宏并对其进行修改以实现这一点,基本上是一个水平枚举列表。我能够将大部分代码传递给expl3,但是有几个宏和布尔值我无法转换。

代码是可以运行的(虽然与 hyperref 不兼容),但不是很简洁,具体来说:

% Internals (ugly mix plain TeX|expl3)
\def\sh@rtitem{
  \endsh@rtitem
  \int_incr:N \l_shenum_item_int
  \int_gincr:N \g_shenum_item_int
  \@ifnextchar [\sh@rt@item{\@noitemargtrue \sh@rt@item[\tl_use:N \l_shenum_label_tl]}}

\def\sh@rt@item[#1]{
   \cs_set_eq:NN \endsh@rtitem \_end_short_item:
   \legacy_if:nT {@noitemarg}
     {
       \legacy_if_set_false:n {@noitemarg}
       \legacy_if:nT {@nmbrlist}
         {
          \refstepcounter{\@listctr}
         }
     }
   \group_begin:
     \lrbox{\l_shenum_item_text_box}
     \makebox[\labelwidth][r]{#1}
     \skip_horizontal:N \labelsep\ignorespaces
     }

shorlst包没有重新定义item通常的做法,而是采用了另一种技巧来实现列的效果,而我所有的尝试都失败了:(

以下是(非最小的)工作示例:

\documentclass{article}
\usepackage[showframe]{geometry}
\usepackage{amsmath}% , hyperref not work
% mypkg
\begin{filecontents*}[overwrite]{myshortenum.sty}
\NeedsTeXFormat{LaTeX2e}[2023-06-01]
\ProvidesExplPackage{myshortenum}{2023/10/13}{0.1}{Short Horizontal enumerate}
\ExplSyntaxOn
% vars
\newcounter{mycount}
\tl_new:N \l_shenum_counter_tl
\tl_set:Nn \l_shenum_counter_tl { mycount }
\tl_new:N \g_shenum_widest_label_tl
\tl_new:N \g_shenum_counter_styles_tl
\box_new:N \l_shenum_label_width_by_box
\dim_new:N \l_shenum_current_widest_dim
\int_new:N \l_shenum_level_int
\int_new:N \l_shenum_item_int
\int_new:N \g_shenum_item_int
\int_new:N \l_shenum_tmpa_int
\box_new:N \l_shenum_item_text_box
\bool_new:N \l_shenum_columns_sep_bool
\dim_new:N \l_shenum_columns_sep_dim
\dim_new:N \l_shenum_item_text_width_dim
\dim_new:N \l_shenum_tmpa_dim
% msg
\cs_generate_variant:Nn \msg_warning:nnn {nnV,nnx}
\msg_new:nnn { shenum } { nested }
  {
    The ~ environment ~ 'shortenumerate' ~ can't ~ be ~ nested ~ \msg_line_context:.
  }
\msg_new:nnn { shenum } {item-too-wide}
  {
    Text ~ item ~ is ~ too ~ wide ~ >  ~  #1 ~ pt ~\msg_line_context:. ~  \\
    Use ~ '\c_backslash_str parbox[t]{\c_backslash_str itemwidth}{item ~ text}'.
  }
% Counters and labels
\cs_new_protected:Npn \__shenum_register_counter_style:Nn #1 #2
  {
    \tl_const:cn { c_shenum_widest_ \cs_to_str:N #1 _tl } {#2}
    \tl_gput_right:Nn \g_shenum_counter_styles_tl {#1}
  }
\__shenum_register_counter_style:Nn \arabic { 0 }
\__shenum_register_counter_style:Nn \Alph { M }
\__shenum_register_counter_style:Nn \alph { m }
\__shenum_register_counter_style:Nn \Roman { VIII }
\__shenum_register_counter_style:Nn \roman { viii }

\cs_new_protected:Npn \__shenum_label_width_by_box:Nn #1#2
  {
    \hbox_set:Nn \l_shenum_label_width_by_box {#2}
    \dim_set:Nn #1 { \box_wd:N \l_shenum_label_width_by_box }
  }

\cs_new_protected:Npn \__shenum_label_style:Nnn #1#2#3
  {
    \tl_clear_new:N #1
    \tl_put_right:Nx #1 { \tl_trim_spaces:n {#3} }
    \tl_gset_eq:NN \g_shenum_widest_label_tl #1
    \tl_map_inline:Nn \g_shenum_counter_styles_tl
      {
        \tl_replace_all:Nne #1 { ##1* } { \exp_not:N ##1 {#2} }
        \tl_greplace_all:Nne \g_shenum_widest_label_tl { ##1* }
           { \tl_use:c { c_shenum_widest_ \cs_to_str:N ##1 _tl } }
      }
     \__shenum_label_width_by_box:Nn \l_shenum_current_widest_dim
       { \tl_use:N \g_shenum_widest_label_tl }
     \tl_set_eq:cN { the #2 } #1
  }
% keys
\keys_define:nn { shenum }
  {
    labelsep    .dim_set:N  = \l_shenum_labelsep_dim,
    labelsep    .initial:n  = 0.5em,
    labelwidth  .dim_set:N  = \l_shenum_labelwidth_dim,
    label       .code:n     = {
                                \__shenum_label_style:Nnn \l_shenum_label_tl { \l_shenum_counter_tl } {#1}
                                \dim_set_eq:NN \l_shenum_labelwidth_dim  \l_shenum_current_widest_dim
                              },
    label       .initial:n = \arabic*.,
    topsep      .skip_set:N = \l_shenum_topsep_skip,
    topsep      .initial:n  = 5pt,
    itemsep     .skip_set:N = \l_shenum_itemsep_skip,
    itemsep     .initial:n  = 3pt,
    columns-sep .code:n     = \bool_set_true:N \l_shenum_columns_sep_bool
                              \dim_set:Nn \l_shenum_columns_sep_dim {#1},
    columns     .int_set:N  = \l_shenum_columns_int,
    columns     .initial:n  = 2,
  }
% Set internal for columns and \itemwidth
\cs_new_protected:Nn \__shenum_default_columns_set:
  {
    \bool_if:NF \l_shenum_columns_sep_bool
      {
        \dim_set:Nn \l_shenum_columns_sep_dim
          {
            ( \l_shenum_labelwidth_dim + \l_shenum_labelsep_dim ) / 2
          }
      }
    \dim_set:Nn \l_shenum_item_text_width_dim
      {
        \linewidth / \l_shenum_columns_int -\labelwidth -\labelsep
      }
    % For use in \parbox | minipage
    \dim_zero_new:N \itemwidth
    \dim_set:Nn \itemwidth
      {
        \linewidth / \l_shenum_columns_int -\labelwidth -\labelsep -0.5\l_shenum_columns_sep_dim
      }
   }
% Set second arg in list
\cs_new_protected:Nn \__shenum_list_second_arg:
  {
    \skip_set_eq:NN \topsep \l_shenum_topsep_skip
    \skip_set_eq:NN \parsep \l_shenum_itemsep_skip
    \skip_zero:N \itemsep
    \skip_zero:N \partopsep
    \dim_add:Nn \leftmargin { \itemindent - \labelwidth - \labelsep }
    \dim_zero:N \itemindent
    \dim_zero:N \listparindent
    \dim_set_eq:NN \labelwidth \l_shenum_labelwidth_dim
    \dim_set_eq:NN \labelsep \l_shenum_labelsep_dim
  }
% Remove extra \parsep when items fit exact \item's / columns = exact
\cs_new_protected:Nn \__shenum_remove_extra_parsep:
  {
    \int_compare:nNnT
      { \int_mod:nn {  \g_shenum_item_int } { \l_shenum_columns_int } } = { \c_zero_int }
      {
        \skip_sub:Nn \parsep { \topsep }
        \par\addvspace{-\parsep }
        \int_gzero:N \g_shenum_item_int
      }
  }
% env
\NewDocumentEnvironment{shortenumerate}{ O{} }
  {
    \int_incr:N \l_shenum_level_int
    \int_compare:nNnT { \l_shenum_level_int } > { 1 }
      {
        \msg_error:nn { shenum } { nested }
      }
    \IfValueT{#1}{ \keys_set:nn { shenum } {#1} }
    \list{ }{ \__shenum_list_second_arg: }
      \__shenum_default_columns_set:
      \item \scan_stop:
      \cs_set_eq:NN \endsh@rtitem \noindent
      \cs_set_eq:NN \item \sh@rtitem
      \usecounter{mycount}
  }
  {
    \endsh@rtitem
    \__shenum_remove_extra_parsep:
    \endlist
  }
% Internals (ugly mix plain TeX|expl3)
\def\sh@rtitem{
  \endsh@rtitem
  \int_incr:N \l_shenum_item_int
  \int_gincr:N \g_shenum_item_int
  \@ifnextchar [\sh@rt@item{\@noitemargtrue \sh@rt@item[\tl_use:N \l_shenum_label_tl]}}

\def\sh@rt@item[#1]{
   \cs_set_eq:NN \endsh@rtitem \_end_short_item:
   \legacy_if:nT {@noitemarg}
     {
       \legacy_if_set_false:n {@noitemarg}
       \legacy_if:nT {@nmbrlist}
         {
          \refstepcounter{\@listctr}
         }
     }
   \group_begin:
     \lrbox{\l_shenum_item_text_box}
     \makebox[\labelwidth][r]{#1}
     \skip_horizontal:N \labelsep\ignorespaces
     }
% Internal
\cs_new_protected:Nn \_end_short_item:
  {
      \endlrbox
    \group_end:
    \dim_set:Nn \l_shenum_tmpa_dim { \box_wd:N \l_shenum_item_text_box }
    \dim_compare:nNnT { \l_shenum_tmpa_dim } > { \l_shenum_item_text_width_dim + \labelwidth + \labelsep }
      {
        \msg_warning:nnx { shenum } {item-too-wide} { \dim_to_decimal:n { \itemwidth } }
      }
    \box_set_wd:Nn \l_shenum_item_text_box
      {
        \l_shenum_item_text_width_dim + \labelwidth + \labelsep - 0.5\l_shenum_columns_sep_dim
      }
    \hbadness=10000
    \box_use:N \l_shenum_item_text_box
    \int_compare:nNnTF { \l_shenum_item_int } = { \l_shenum_columns_int }
     {
       \hspace{\l_shenum_columns_sep_dim }
       \par\noindent
       \int_zero:N \l_shenum_item_int
     }
     { \hspace{\l_shenum_columns_sep_dim } }
  }
\ExplSyntaxOff
\endinput
\end{filecontents*}
\usepackage{myshortenum}
\setlength{\parskip}{0pt}
\setlength{\parindent}{0pt}
\begin{document}
Above

\begin{shortenumerate}[label=(\arabic*.),columns=2,columns-sep=20pt,itemsep=3pt,topsep=1cm]
\item \parbox[t]{\itemwidth}{A little paragraph that will be too long to fit on one line no matter what ands no more lightsss\strut.}
\item A short item A short item A short item.\label{A}
\item $b_n = \frac{(n^3 - 5n)^4 - n^{12}}{n^{11}}$
\item $c_n = \frac{n^{n + 1}}{n!}$\strut
\item $e_n = \frac{2^{(n^3)}}{n!5^{(n^2)} - n^n}$
\item $f_n = \sqrt{n + \sqrt{2n}} - \sqrt{n + \sqrt{2n}}x$
\end{shortenumerate}

Below \ref{A}

Above
\begin{shortenumerate}[columns=2]
\item \parbox[t]{\itemwidth}{A little paragraph that will be too long to fit on one line no matter what ands no more lightsss\strut.}
\item A short item A short item A short item.\label{B}
\item $b_n = \frac{(n^3 - 5n)^4 - n^{12}}{n^{11}}$
\item $c_n = \frac{n^{n + 1}}{n!}$\strut
\item $f_n = \sqrt{n + \sqrt{2n}} - \sqrt{n + \sqrt{2n}}x$
\end{shortenumerate}

Below \ref{B}
\end{document}

输出的图像(这正是我们所期望的):

输出

我可以去掉\def\@ifnextchar或者用其他方法(\item例如重新定义)吗?

答案1

这是我的疑问的答案,感谢 David Carlisle、Joseph Wright 和 Ulrich Diez 的评论,这个问题终于解决了。我能够删除\def\@ifnextchar现在它与 兼容了hyperref:D。代码如下所示:

\documentclass{article}
\usepackage[showframe]{geometry}
\usepackage{amsmath}%
% mypkg
\begin{filecontents*}[overwrite]{myshortenum.sty}
\NeedsTeXFormat{LaTeX2e}[2023-06-01]
\ProvidesExplPackage{myshortenum}{2023/10/14}{0.2}{Short Horizontal enumerate}
\ExplSyntaxOn
% vars
\newcounter{mycount}
\tl_new:N \l_shenum_counter_tl
\tl_set:Nn \l_shenum_counter_tl { mycount }
\bool_new:N \g_shenum_hyperref_bool
\tl_new:N \g_shenum_widest_label_tl
\tl_new:N \g_shenum_counter_styles_tl
\box_new:N \l_shenum_label_width_by_box
\dim_new:N \l_shenum_current_widest_dim
\int_new:N \l_shenum_level_int
\int_new:N \l_shenum_item_int
\int_new:N \g_shenum_item_int
\int_new:N \l_shenum_tmpa_int
\box_new:N \l_shenum_item_text_box
\bool_new:N \l_shenum_columns_sep_bool
\dim_new:N \l_shenum_columns_sep_dim
\dim_new:N \l_shenum_item_text_width_dim
\dim_new:N \l_shenum_tmpa_dim
% msg
\cs_generate_variant:Nn \msg_warning:nnn {nnV,nnx}
\msg_new:nnn { shenum } { nested }
  {
    The ~ environment ~ 'shortenumerate' ~ can't ~ be ~ nested ~ \msg_line_context:.
  }
\msg_new:nnn { shenum } {item-too-wide}
  {
    Text ~ item ~ is ~ too ~ wide ~ >  ~  #1 ~ pt ~\msg_line_context:. ~  \\
    Use ~ '\c_backslash_str parbox[t]{\c_backslash_str itemwidth}{item ~ text}'.
  }
% hyperref
\hook_gput_code:nnn { begindocument } { myshortenum } { \__shenum_after_hyperref: }
\hook_gset_rule:nnnn { begindocument } { myshortenum } { after } { hyperref }
\cs_new_protected:Nn \__shenum_after_hyperref:
  {
    \IfPackageLoadedTF { hyperref }
       { \bool_gset_true:N \g_shenum_hyperref_bool } { }
  }

% Counters and labels
\cs_new_protected:Npn \__shenum_register_counter_style:Nn #1 #2
  {
    \tl_const:cn { c_shenum_widest_ \cs_to_str:N #1 _tl } {#2}
    \tl_gput_right:Nn \g_shenum_counter_styles_tl {#1}
  }
\__shenum_register_counter_style:Nn \arabic { 0 }
\__shenum_register_counter_style:Nn \Alph { M }
\__shenum_register_counter_style:Nn \alph { m }
\__shenum_register_counter_style:Nn \Roman { VIII }
\__shenum_register_counter_style:Nn \roman { viii }

\cs_new_protected:Npn \__shenum_label_width_by_box:Nn #1#2
  {
    \hbox_set:Nn \l_shenum_label_width_by_box {#2}
    \dim_set:Nn #1 { \box_wd:N \l_shenum_label_width_by_box }
  }

\cs_new_protected:Npn \__shenum_label_style:Nnn #1#2#3
  {
    \tl_clear_new:N #1
    \tl_put_right:Nx #1 { \tl_trim_spaces:n {#3} }
    \tl_gset_eq:NN \g_shenum_widest_label_tl #1
    \tl_map_inline:Nn \g_shenum_counter_styles_tl
      {
        \tl_replace_all:Nne #1 { ##1* } { \exp_not:N ##1 {#2} }
        \tl_greplace_all:Nne \g_shenum_widest_label_tl { ##1* }
           { \tl_use:c { c_shenum_widest_ \cs_to_str:N ##1 _tl } }
      }
     \__shenum_label_width_by_box:Nn \l_shenum_current_widest_dim
       { \tl_use:N \g_shenum_widest_label_tl }
     \bool_if:NTF \g_shenum_hyperref_bool
       {
         \tl_set_eq:cN { theH #2 } #1
       }
       { \tl_set_eq:cN { the #2 } #1 }
  }
% keys
\keys_define:nn { shenum }
  {
    labelsep    .dim_set:N  = \l_shenum_labelsep_dim,
    labelsep    .initial:n  = 0.5em,
    labelwidth  .dim_set:N  = \l_shenum_labelwidth_dim,
    label       .code:n     = {
                                \__shenum_label_style:Nnn \l_shenum_label_tl { \l_shenum_counter_tl } {#1}
                                \dim_set_eq:NN \l_shenum_labelwidth_dim  \l_shenum_current_widest_dim
                              },
    label       .initial:n = \arabic*.,
    topsep      .skip_set:N = \l_shenum_topsep_skip,
    topsep      .initial:n  = 5pt,
    itemsep     .skip_set:N = \l_shenum_itemsep_skip,
    itemsep     .initial:n  = 3pt,
    columns-sep .code:n     = \bool_set_true:N \l_shenum_columns_sep_bool
                              \dim_set:Nn \l_shenum_columns_sep_dim {#1},
    columns     .int_set:N  = \l_shenum_columns_int,
    columns     .initial:n  = 2,
  }
% Set internal for columns and \itemwidth
\cs_new_protected:Nn \__shenum_default_columns_set:
  {
    \bool_if:NF \l_shenum_columns_sep_bool
      {
        \dim_set:Nn \l_shenum_columns_sep_dim
          {
            ( \l_shenum_labelwidth_dim + \l_shenum_labelsep_dim ) / 2
          }
      }
    \dim_set:Nn \l_shenum_item_text_width_dim
      {
        \linewidth / \l_shenum_columns_int -\labelwidth -\labelsep
      }
    % For use in \parbox | minipage
    \dim_zero_new:N \itemwidth
    \dim_set:Nn \itemwidth
      {
        \linewidth / \l_shenum_columns_int -\labelwidth -\labelsep -0.5\l_shenum_columns_sep_dim
      }
   }
% Set second arg in list
\cs_new_protected:Nn \__shenum_list_second_arg:
  {
    \skip_set_eq:NN \topsep \l_shenum_topsep_skip
    \skip_set_eq:NN \parsep \l_shenum_itemsep_skip
    \skip_zero:N \itemsep
    \skip_zero:N \partopsep
    \dim_add:Nn \leftmargin { \itemindent - \labelwidth - \labelsep }
    \dim_zero:N \itemindent
    \dim_zero:N \listparindent
    \dim_set_eq:NN \labelwidth \l_shenum_labelwidth_dim
    \dim_set_eq:NN \labelsep \l_shenum_labelsep_dim
  }
% Remove extra \parsep when items fit exact \item's / columns = exact
\cs_new_protected:Nn \__shenum_remove_extra_parsep:
  {
    \int_compare:nNnT
      { \int_mod:nn {  \g_shenum_item_int } { \l_shenum_columns_int } } = { \c_zero_int }
      {
        \skip_sub:Nn \parsep { \topsep }
        \par\addvspace{-\parsep }
        \int_gzero:N \g_shenum_item_int
      }
  }
% env
\NewDocumentEnvironment{shortenumerate}{ o }
  {
    \int_incr:N \l_shenum_level_int
    \int_compare:nNnT { \l_shenum_level_int } > { 1 }
      {
        \msg_error:nn { shenum } { nested }
      }
    \IfValueT{#1}{ \keys_set:nn { shenum } {#1} }
    \list{ }{ \__shenum_list_second_arg: }
      \__shenum_default_columns_set:
      \item \scan_stop:
      \cs_set_eq:NN \_shenum_end_item_tmp: \noindent
      \cs_set_eq:NN \item \__shenum_item:
      \usecounter{mycount}
  }
  {
    \_shenum_end_item_tmp:
    \__shenum_remove_extra_parsep:
    \endlist
  }
% Internals (ugly mix legacy 2e and expl3)
\cs_set_nopar:Nn \__shenum_item:
  {
    \int_incr:N \l_shenum_item_int
    \int_gincr:N \g_shenum_item_int
    \peek_remove_spaces:n
      {
        \_shenum_end_item_tmp:
        \peek_meaning:NTF [
          {
            \__shenum_item:w
          }
          {
            \legacy_if_set_true:n {@noitemarg}
            \__shenum_item:w [\tl_use:N \l_shenum_label_tl]
          }
      }
  }
\cs_set_nopar:Npn \__shenum_item:w [#1]
  {
    \cs_set_eq:NN \_shenum_end_item_tmp: \_shenum_end_item:
    \legacy_if:nT {@noitemarg}
      {
        \legacy_if_set_false:n {@noitemarg}
        \legacy_if:nT {@nmbrlist}
          {
            \refstepcounter{\@listctr}
          }
      }
    \group_begin:
      \lrbox{\l_shenum_item_text_box}
      \makebox[\labelwidth][r]{#1}
      \skip_horizontal:N \labelsep\ignorespaces
  }
% Internal
\cs_set_nopar:Nn \_shenum_end_item:
  {
      \endlrbox
    \group_end:
    \dim_set:Nn \l_shenum_tmpa_dim { \box_wd:N \l_shenum_item_text_box }
    \dim_compare:nNnT { \l_shenum_tmpa_dim } > { \l_shenum_item_text_width_dim + \labelwidth + \labelsep }
      {
        \msg_warning:nnx { shenum } {item-too-wide} { \dim_to_decimal:n { \itemwidth } }
      }
    \box_set_wd:Nn \l_shenum_item_text_box
      {
        \l_shenum_item_text_width_dim + \labelwidth + \labelsep - 0.5\l_shenum_columns_sep_dim
      }
    \hbadness=10000
    \box_use:N \l_shenum_item_text_box
    \int_compare:nNnTF { \l_shenum_item_int } = { \l_shenum_columns_int }
     {
       \hspace{\l_shenum_columns_sep_dim }
       \par\noindent
       \int_zero:N \l_shenum_item_int
     }
     { \hspace{\l_shenum_columns_sep_dim } }
  }
\ExplSyntaxOff
\endinput
\end{filecontents*}
\usepackage{myshortenum}
\usepackage{hyperref}
\setlength{\parskip}{0pt}
\setlength{\parindent}{0pt}
\begin{document}
Above

\begin{shortenumerate}[label=(\arabic*.),columns=2,columns-sep=20pt,itemsep=3pt,topsep=1cm]
\item \parbox[t]{\itemwidth}{A little paragraph that will be too long to fit on one line no matter what ands no more lightsss\strut.}
\item A short item A short item A short item.\label{A}
\item $b_n = \frac{(n^3 - 5n)^4 - n^{12}}{n^{11}}$
\item $c_n = \frac{n^{n + 1}}{n!}$\strut
\item $e_n = \frac{2^{(n^3)}}{n!5^{(n^2)} - n^n}$
\item $f_n = \sqrt{n + \sqrt{2n}} - \sqrt{n + \sqrt{2n}}x$
\end{shortenumerate}

Below \ref{A}

Above
\begin{shortenumerate}[columns=2]
\item \parbox[t]{\itemwidth}{A little paragraph that will be too long to fit on one line no matter what ands no more lightsss\strut.}
\item A short item A short item A short item.\label{B}
\item $b_n = \frac{(n^3 - 5n)^4 - n^{12}}{n^{11}}$
\item $c_n = \frac{n^{n + 1}}{n!}$\strut
\item $f_n = \sqrt{n + \sqrt{2n}} - \sqrt{n + \sqrt{2n}}x$
\end{shortenumerate}

Below \ref{B}
\end{document}

它比的原始版本更加通用,shortlst但不支持footnote,并且还有一些可以以更好的方式编写的东西,例如:

    \legacy_if:nT {@noitemarg}
      {
        \legacy_if_set_false:n {@noitemarg}
        \legacy_if:nT {@nmbrlist}
          {
            \refstepcounter{\@listctr}
          }
      }

但我想这需要重新定义\item\makelabel。由于没有其他用户的回应,我暂时接受我的。

相关内容