像 enumitem 的 itemjoin 这样的垂直列表定制?

像 enumitem 的 itemjoin 这样的垂直列表定制?

我喜欢自定义内联列表所提供的编辑灵活性enumitem;例如:

\documentclass{article}
\usepackage{enumitem}

\newlist{inlineitem}{itemize*}{1}
\setlist[inlineitem]{       % Customize itemize* environ
    before=\unskip{:},     % Colon before list
    itemjoin={{;}},        % Join with semicolons
    itemjoin*={{; and}},   % Last join has "; and "
    label={},               % No label
    after=.}                % Period at end of last item

\begin{document}

    Here is some text that will have a list of
    \begin{inlineitem}
        \item a list item that could go first but also last
        \item another list item that I'm not sure about
        \item this one should go last unless I think of another one
    \end{inlineitem}

    It's convenient because I can add, remove, and rearrange items without worrying about punctuation.

\end{document}

在此处输入图片描述

我想使用非内联列表做同样的事情;但是,enumitem的文档指出“itemjoin在垂直模式下被忽略”,事实确实如此。

答案1

自从我第一次写下这个答案以来,它已经发展了一些。我想出了三个解决方案,并且不想丢弃其中任何一个。

  1. 第一个(原始)解决方案很简单,因为它没有在倒数第二项添加“and”。
  2. 第二种解决方案确实可以做到这一点,但它有点儿像黑客攻击。我无法保证它在enumitem收到重大更新后还能继续工作。
  3. 第三种解决方案与enumitem的内联列表非常相似。但它有一个很大的限制:每个项目应该只是一个段落,并且不能包含显示方程式。

1. 简单解决方案(没有“和”)

这是一个可能的解决方案,通过重新定义 来实现\item。这可能看起来有点笨拙,但enumitems的内联列表也是这样做的。这不会将“and”添加到倒数第二项(请参阅下文)。

我使用SetEnumitemKey来定义一个新键,sentence它可以提供给任何enumitem知道的列表环境。它将在最后一项后附加一个句点 (.),并在其他每一项后附加一个分号 (;)。您可以使用itemjoin sentence将分号改为其他内容。

\documentclass{article}

\usepackage{enumitem}
\SetEnumitemKey{sentence}{%
  before*=\sentencelistprep,
  after*={\unskip.},
  itemjoin={;},
}

\let\sentenceitemjoin\empty
\edef\sentenceitem{\noexpand\sentenceitemjoin\unexpanded\expandafter{\item}}%
\makeatletter %% <- make @ usable in command sequences
\newcommand*\sentencelistprep{%
  \def\sentenceitemjoin{\def\sentenceitemjoin{\unskip\enit@itemjoin}}%
  \let\item\sentenceitem
}
\makeatother  %% <- revert @

\begin{document}

Here is an \texttt{itemize} environment whose items are part of this sentence and therefore come with appropriate punctuation:
\begin{itemize}[sentence]
\item a list item that could go first but also last
\item another list item that I'm not sure about that is long enough to span two lines
\item this one should go last unless I think of another one
\end{itemize}

It's convenient because I can add, remove, and rearrange items without worrying about punctuation.

\end{document}

输出

警告:您不能使用任何其他列表环境里面一个带有sentence键,因为的重新定义\item也将适用于那些,并且也会在那里插入额外的分号。
你可能不想在连续的句子中嵌套列表,但如果必须的话,你可以添加\let\sentenceitemjoin\empty在内部列表中添加列表。


评论

  • 我没有添加冒号 (:),因为感觉它不是列表的一部分(如果项目重新排序也不会受到影响)。如果您确实希望自动插入冒号,可以将before*上面的值替换为{\unskip:\sentencelistprep}
  • 如果你不想sentence每次都提供密钥,你可以创建一个包含该密钥的自定义列表环境,例如

    \newlist{myitemize}{itemize}{1}
    \setlist[myitemize]{label=\textbullet,sentence}
    
  • 如果稍后提供,和beforeafter将被覆盖sentence,但反之则不成立,因为sentence使用带星号的版本before*after*

  • \sentencelistprep在带有 的列表开头调用sentence,它会执行以下操作:它将 定义\sentenceitemjoin为 ,\def\sentenceitemjoin{\unskip\enit@itemjoin}\item用包含 的版本替换 。因此,\sentenceitemjoin第一个重新定义为(键的值),之后的每个都会在其前面插入一个。\item\sentenceitemjoin\unskip\enit@itemjoinitemjoin\item;


2. 有点不靠谱的解决方案(包括“和”)

根据您的要求,这里有一种方法可以将“; and”添加到倒数第二项。我无法通过简单地修改另一个解决方案来做到这一点,因为\item列表环境中的 s 无法真正知道它是否是最后一个。

下面的代码首先扫描整个列表环境的内容,然后;在除最后两个项目之外的每个项目的末尾插入 s,然后添加到; and倒数第二个项目。这有点像 hack,但确实有效。

您可以使用itemjoinitemjoin*和键将、和itemjoin**更改为其他内容。;; and.

\documentclass{article}

\usepackage{enumitem}
\usepackage{environ} %% <- for \Collect@Body

\makeatletter %% <- make @ usable in command sequences
\long\def\sentencelist@afterfi#1\fi{ %% <- insert sentencelist code after \fi (hack!)
  #1\fi\Collect@Body\sentencelist    %% <- apply \sentencelist to the body of the environment
}
\long\def\sentencelist#1{\@sentencelist#1\item\@sentencelist}
\long\def\@sentencelist#1\item#2\@sentencelist{
  #1\@@sentencelist#2\@@sentencelist           %% <- run preamble as normal
}
\long\def\@@sentencelist#1\item#2\@@sentencelist{%
  \if\relax\detokenize{#2}\relax               %% <- if last item
    \if@newlist\else\unskip\enit@itemjoin@s\fi %% <- ..insert "; and" if not also the first item
    \item #1\unskip\enit@itemjoin@ss%          %% <- ..insert the \item and a "."
  \else                                        %% <- otherwise
    \if@newlist\else\unskip\enit@itemjoin\fi   %% <- .. insert ";" if not also the first item
    \item #1%                                  %% <- ..insert the item and a ;
    \@@sentencelist#2\@@sentencelist           %% <- ..and repeat
  \fi
}
\let\enit@itemjoin@ss\@empty
\enitkv@key{}{itemjoin**}{% %% <- create "itemjoin**" key
  \def\enit@itemjoin@ss{#1}}

\SetEnumitemKey{sentence}{% %% <- create "sentence" key
  before*=\sentencelist@afterfi,
  itemjoin={;}, itemjoin*={; and}, itemjoin**={.}
}
\makeatother  %% <- revert @

\begin{document}

Here is an \texttt{itemize} environment whose items are part of this sentence and therefore come with appropriate punctuation:
\begin{itemize}[sentence]
\item a list item that could go first but also last
\item another list item that I'm not sure about that is long enough to span two lines
\item this one should go last unless I think of another one
\end{itemize}

It's convenient because I can add, remove, and rearrange items without worrying about punctuation.

\end{document}

输出


3. 模仿enumitem内联列表的解决方案

这里有一种方法与 的内联列表的工作方式非常相似enumitem。它非常巧妙,我想我自己想不出它。您可以使用itemjoinitemjoin*itemjoin**键对其进行自定义。

A大的限制是此列表中的项目只能是单个段落:它们不能包含段落分隔符、显示方程式、其他列表等。我认为对此没有太多可以做的事情(不切换到其他方法)并且我怀疑这就是为什么 的enumitemitemjoin尚未在普通列表中实现的原因。

\documentclass{article}

\usepackage{enumitem,amsmath}

\begin{document}

\makeatletter
\newif\ifsentence@firstitem
\newcommand*\sentencebefore{%
  \let\sentence@olditem\item
  \let\item\sentence@item
  \sentence@firstitemtrue
}
\newcommand*\sentenceafter{%
  \egroup
  \ifhmode\unskip\enit@itemjoin@s\fi
  \sentence@olditem\unhbox\enit@inbox\unskip\enit@itemjoin@ss
}
\def\sentence@item{%
  \ifhmode
    \egroup
    \ifsentence@firstitem
      \sentence@firstitemfalse
    \else
      \unskip\enit@itemjoin
    \fi
    \sentence@olditem \unhbox\enit@inbox
  \fi
  \setbox\enit@inbox=\hbox\bgroup%\parshape1\@totalleftmargin\linewidth
}
\let\enit@itemjoin@ss\@empty
\enitkv@key{}{itemjoin**}{%
  \def\enit@itemjoin@ss{#1}}
\makeatother

\SetEnumitemKey{sentence}{%
  before*=\sentencebefore, after*=\sentenceafter,
  itemjoin=;, itemjoin*={; and}, itemjoin**=.
}

Here is an \texttt{itemize} environment whose items are part of this sentence and therefore come with appropriate punctuation:
\begin{itemize}[sentence]
\item a list item that could go first but also last
\item another list item that I'm not sure about that is long enough to span two lines
\item this one should go last unless I think of another one
\end{itemize}

It's convenient because I can add, remove, and rearrange items without worrying about punctuation.

\end{document}

输出

答案2

这是一个版本有点重现显示条目,允许使用 的功能。以下是使用(问题中提供的)的itemize*相同内容,以及用于比较目的的常规内容:inlineitemdisplayitemitemize

在此处输入图片描述

笔记:

  • 添加了红色,以便于比较三个版本。你可以\Show

    \newcommand*{\Show}[1]{#1}
    

    消除突出显示或只是删除对\Show宏的所有引用。

  • “有点”是因为我无法让第一个项目的垂直间距与结果相匹配itemize

代码:

\documentclass{article}
\usepackage{enumitem}
\usepackage{xcolor}

\newcommand*{\NextLine}{%
    \newline
    \vspace*{0.5\baselineskip}%
    \hspace*{\dimexpr\labelindent+\labelwidth-\labelsep\relax}%
}%
\newcommand*{\Show}[1]{\textcolor{red}{\bfseries#1}}% Make it easier to see difference



\newlist{inlineitem}{itemize*}{1}
\setlist[inlineitem]{             % Customize itemize* environ
    before={\unskip\Show:},       % Colon before list
    itemjoin={{\Show;}},          % Join with semicolons
    itemjoin*={{\Show{; and}}},   % Last join has "; and "
    label={},                     % No label
    after=.,                      % Period at end of last item
}

\newlist{displayitem}{itemize*}{1}
\setlist[displayitem]{                      % Customize itemize* environ
    before={\unskip\Show:\NextLine},        % Colon before list
    itemjoin={\Show;\NextLine},             % Join with semicolons
    itemjoin*={\Show{; and}\NextLine},      % Last join has "; and "
    label={$\bullet$},                      % Label
    after={\unskip\Show.\endgraf\noindent}, % Period at end of last item
}

\pagecolor{white}
\begin{document}
    \noindent
    Here is some text in a \verb|inlineitem|
    \begin{inlineitem}
        \item a list item that could go first but also last
        \item another list item that I'm not sure about
        \item this one should go last unless I think of another one
    \end{inlineitem}
    Some text after \verb|inlineitem|.

    \bigskip

    \noindent
    Here is the same text in a \verb|displayitem|
    \begin{displayitem}
        \item a list item that could go first but also last
        \item another list item that I'm not sure about
        \item this one should go last unless I think of another one
    \end{displayitem}
    Some text after \verb|displayitem|.

    \bigskip

   \noindent
   Here is the same text in an \verb|itemize|
    \begin{itemize}
        \item a list item that could go first but also last
        \item another list item that I'm not sure about
        \item this one should go last unless I think of another one
    \end{itemize}
    Some text after regular \verb|itemize|.
\end{document}

答案3

这不允许嵌套列表,但无论如何,您心中的列表应该是最内层。

这需要xparse发布 2019-03-05 或更高版本(已经可用于 MiKTeX,即将可用于 TeX Live 2019)。

与内联列表的主要区别在于,应注意处理空行,因此,尾随空格和\par标记会随 一起被删除,\__dan_genlist_purify:N同时会放回在\item主体被吸收和拆分时被删除的 。将构建一个新序列,并在末尾使用所需的分隔符。

\documentclass{article}
\usepackage{enumitem,xparse}

\ExplSyntaxOn
\NewDocumentEnvironment{genlist}{mO{}+b}
 {% #1 = list type, #2 = options, #3 = body
  \dan_genlist_make:nnn { #1 } { #2 } { #3 }
 }
 {}

\seq_new:N \l_dan_genlist_body_in_seq
\seq_new:N \l_dan_genlist_body_out_seq
\clist_new:N \l_dan_genlist_opt_clist
\cs_generate_variant:Nn \seq_use:Nnnn { NVVV }

% borrow itemjoin, itemjoin* and after from enumitem;
% all other options are passed to enumitem
\keys_define:nn { dan/genlist }
 {
  itemjoin  .tl_set:N = \l_dan_genlist_more_tl,
  itemjoin* .tl_set:N = \l_dan_genlist_last_tl,
  after     .tl_set:N = \l_dan_genlist_after_tl,
  unknown   .code:n   = \clist_put_right:Nx \l_dan_genlist_opt_clist
                         { \l_keys_key_tl = #1 },
 }

\cs_new_protected:Nn \dan_genlist_make:nnn
 {
  \keys_set:nn { dan/genlist } { #2 }
  \seq_set_split:Nnn \l_dan_genlist_body_in_seq { \item } { #3 }
  \seq_pop_left:NN \l_dan_genlist_body_in_seq \l_tmpa_tl % discard the empty item
  \seq_clear:N \l_dan_genlist_body_out_seq
  \seq_map_variable:NNn \l_dan_genlist_body_in_seq \l_dan_genlist_temp_tl
   {
    \__dan_genlist_purify:N \l_dan_genlist_temp_tl
   }
  \use:e { \exp_not:N \begin{#1}[ \clist_use:Nn \l_dan_genlist_opt_clist {,} ] }
  \seq_use:NVVV \l_dan_genlist_body_out_seq
   \l_dan_genlist_last_tl
   \l_dan_genlist_more_tl
   \l_dan_genlist_last_tl
  \tl_use:N \l_dan_genlist_after_tl
  \end{#1}
 }

\cs_new_protected:Nn \__dan_genlist_purify:N
 {
  \regex_replace_once:nnN { \s* \c{par}* \Z } { } #1
  \tl_put_left:Nn #1 { \item }
  \seq_put_right:NV \l_dan_genlist_body_out_seq #1
 }

\ExplSyntaxOff

\begin{document}

\begin{genlist}{itemize}[
    itemjoin=;,      % Join with semicolons
    itemjoin*=; and, % Last join has "; and "
    label={},        % No label
    nosep,
    after=.          % Period at end of last item
]
\item a list item that could go first but also last

\item another list item that I'm not sure about

\item this one should go last unless I think of another one

\end{genlist}

\end{document}

在此处输入图片描述

相关内容