summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/source/latex/l3kernel/l3clist.dtx
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2018-04-30 22:39:09 +0000
committerKarl Berry <karl@freefriends.org>2018-04-30 22:39:09 +0000
commita28e1bfccd6fe522fcd296426e4d89c7fb4f19b6 (patch)
treeb72347e08575b56fcfed87c4d837b48dcfd1e02a /Master/texmf-dist/source/latex/l3kernel/l3clist.dtx
parentb40c37a9e22b8235ff9c0bc256d00f7af94304c3 (diff)
l3 (30apr18)
git-svn-id: svn://tug.org/texlive/trunk@47516 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/source/latex/l3kernel/l3clist.dtx')
-rw-r--r--Master/texmf-dist/source/latex/l3kernel/l3clist.dtx485
1 files changed, 303 insertions, 182 deletions
diff --git a/Master/texmf-dist/source/latex/l3kernel/l3clist.dtx b/Master/texmf-dist/source/latex/l3kernel/l3clist.dtx
index a11c54cb818..8825e0c6dab 100644
--- a/Master/texmf-dist/source/latex/l3kernel/l3clist.dtx
+++ b/Master/texmf-dist/source/latex/l3kernel/l3clist.dtx
@@ -2,7 +2,7 @@
%
%% File: l3clist.dtx Copyright (C) 2004-2011 Frank Mittelbach,
%% The LaTeX3 project
-%% (C) 2012-2017 The LaTeX3 Project
+%% (C) 2012-2018 The LaTeX3 Project
%
% It may be distributed and/or modified under the conditions of the
% LaTeX Project Public License (LPPL), either version 1.3c of this
@@ -43,34 +43,58 @@
% }^^A
% }
%
-% \date{Released 2018/03/05}
+% \date{Released 2018-04-30}
%
% \maketitle
%
% \begin{documentation}
%
% Comma lists contain ordered data where items can be added to the left
-% or right end of the list. The resulting ordered list can then
-% be mapped over using \cs{clist_map_function:NN}. Several items can
-% be added at once, and spaces are removed from both sides of each item
-% on input. Hence,
+% or right end of the list. The features of this data type largely
+% overlap with those of the sequence data type, for instance applying a
+% function to every item, removing duplicate items, extracting a given
+% item, using the comma list with specified separators, and so on.
+%
+% Several items can be added at once. To ease input of comma lists from
+% data provided by a user outside an \cs{ExplSyntaxOn} \ldots{}
+% \cs{ExplSyntaxOff} block, spaces are removed from both sides of each
+% comma-delimited argument upon input. Blank arguments are ignored, to
+% allow for trailing commas or repeated commas (which may otherwise
+% arise when concatenating comma lists \enquote{by hand}). In addition,
+% a set of braces is removed if the result of space-trimming is braced:
+% this allows the storage of any item in a comma list. For instance,
% \begin{verbatim}
% \clist_new:N \l_my_clist
-% \clist_put_left:Nn \l_my_clist { ~ a ~ , ~ {b} ~ }
-% \clist_put_right:Nn \l_my_clist { ~ { c ~ } , d }
+% \clist_put_left:Nn \l_my_clist { ~a~ , ~{b}~ , c~\d }
+% \clist_put_right:Nn \l_my_clist { ~{e~} , , {{f}} , }
% \end{verbatim}
-% results in |\l_my_clist| containing |a,{b},{c~},d|.
-% Comma lists cannot contain empty items, thus
+% results in |\l_my_clist| containing |a,b,c~\d,{e~},{{f}}| namely the
+% five items |a|, |b|, |c~\d|, |e~| and~|{f}|. Comma lists normally do
+% not contain empty items so the following gives an empty comma list:
% \begin{verbatim}
% \clist_clear_new:N \l_my_clist
% \clist_put_right:Nn \l_my_clist { , ~ , , }
% \clist_if_empty:NTF \l_my_clist { true } { false }
% \end{verbatim}
-% leaves \texttt{true} in the input stream. To include an item
-% which contains a comma, or starts or ends with a space,
-% surround it with braces. The sequence data type should be preferred
-% to comma lists if items are to contain |{|, |}|, or |#| (assuming the
-% usual \TeX{} category codes apply).
+% and it leaves \texttt{true} in the input stream. To include an
+% \enquote{unsafe} item (empty, or one that contains a comma, or starts
+% or ends with a space, or is a single brace group), surround it with
+% braces.
+%
+% Comma lists are particularly useful when implementing user interfaces,
+% or when interfacing with \LaTeXe{} or other code that expects or
+% provides comma list data. Almost all operations on comma lists are
+% noticeably slower than those on sequences so converting the data to
+% sequences using \cs{seq_set_from_clist:Nn} (see \pkg{l3seq}) may be
+% advisable if speed is important. The exception is that
+% \cs{clist_if_in:NnTF} and \cs{clist_remove_duplicates:N} may be faster
+% than their sequence analogues for large lists. However, these
+% functions work slowly for \enquote{unsafe} items that must be braced,
+% and may produce errors when their argument contains |{|, |}| or |#|
+% (assuming the usual \TeX{} category codes apply). In addition, comma
+% lists cannot store quarks \cs{q_mark} or \cs{q_stop}. The sequence
+% data type should thus certainly be preferred to comma lists to store
+% such items.
%
% \section{Creating and initialising comma lists}
%
@@ -189,7 +213,12 @@
% \end{syntax}
% Sets \meta{comma list} to contain the \meta{items},
% removing any previous content from the variable.
-% Spaces are removed from both sides of each item.
+% Blank items are omitted, spaces are removed from both sides of each
+% item, then a set of braces is removed if the resulting space-trimmed
+% item is braced.
+% To store some \meta{tokens} as a single \meta{item} even if the
+% \meta{tokens} contain commas or spaces, add a set of braces:
+% \cs{clist_set:Nn} \meta{comma list} |{| \Arg{tokens} |}|.
% \end{function}
%
% \begin{function}[updated = 2011-09-05]
@@ -207,7 +236,12 @@
% \cs{clist_put_left:Nn} \meta{comma list} |{|\meta{item_1},\ldots{},\meta{item_n}|}|
% \end{syntax}
% Appends the \meta{items} to the left of the \meta{comma list}.
-% Spaces are removed from both sides of each item.
+% Blank items are omitted, spaces are removed from both sides of each
+% item, then a set of braces is removed if the resulting space-trimmed
+% item is braced.
+% To append some \meta{tokens} as a single \meta{item} even if the
+% \meta{tokens} contain commas or spaces, add a set of braces:
+% \cs{clist_put_left:Nn} \meta{comma list} |{| \Arg{tokens} |}|.
% \end{function}
%
% \begin{function}[updated = 2011-09-05]
@@ -225,7 +259,12 @@
% \cs{clist_put_right:Nn} \meta{comma list} |{|\meta{item_1},\ldots{},\meta{item_n}|}|
% \end{syntax}
% Appends the \meta{items} to the right of the \meta{comma list}.
-% Spaces are removed from both sides of each item.
+% Blank items are omitted, spaces are removed from both sides of each
+% item, then a set of braces is removed if the resulting space-trimmed
+% item is braced.
+% To append some \meta{tokens} as a single \meta{item} even if the
+% \meta{tokens} contain commas or spaces, add a set of braces:
+% \cs{clist_put_right:Nn} \meta{comma list} |{| \Arg{tokens} |}|.
% \end{function}
%
% \section{Modifying comma lists}
@@ -250,7 +289,7 @@
% This function iterates through every item in the \meta{comma list} and
% does a comparison with the \meta{items} already checked. It is therefore
% relatively slow with large comma lists.
-% Furthermore, it does not work if any of the items in the
+% Furthermore, it may fail if any of the items in the
% \meta{comma list} contains |{|, |}|, or |#|
% (assuming the usual \TeX{} category codes apply).
% \end{texnote}
@@ -268,7 +307,7 @@
% The \meta{item} comparison takes place on a token basis, as for
% \cs{tl_if_eq:nn(TF)}.
% \begin{texnote}
-% The \meta{item} may not contain |{|, |}|, or |#|
+% The function may fail if the \meta{item} contains |{|, |}|, or |#|
% (assuming the usual \TeX{} category codes apply).
% \end{texnote}
% \end{function}
@@ -289,7 +328,9 @@
% \cs{clist_reverse:n} \Arg{comma list}
% \end{syntax}
% Leaves the items in the \meta{comma list} in the input stream in
-% reverse order. Braces and spaces are preserved by this process.
+% reverse order. Contrarily to other what is done for other
+% \texttt{n}-type \meta{comma list} arguments, braces and spaces are
+% preserved by this process.
% \begin{texnote}
% The result is returned within \tn{unexpanded}, which means that the
% comma list does not expand further when appearing in an
@@ -341,17 +382,15 @@
% \cs{clist_if_in:NnTF} \meta{comma list} \Arg{item} \Arg{true code} \Arg{false code}
% \end{syntax}
% Tests if the \meta{item} is present in the \meta{comma list}.
-% In the case of an \texttt{n}-type \meta{comma list},
-% spaces are stripped from each item, but braces are not removed.
-% Hence,
+% In the case of an \texttt{n}-type \meta{comma list}, the usual rules
+% of space trimming and brace stripping apply. Hence,
% \begin{verbatim}
% \clist_if_in:nnTF { a , {b}~ , {b} , c } { b } {true} {false}
% \end{verbatim}
-% yields \texttt{false}.
+% yields \texttt{true}.
% \begin{texnote}
-% The \meta{item} may not contain |{|, |}|, or |#|
-% (assuming the usual \TeX{} category codes apply),
-% and should not contain |,| nor start or end with a space.
+% The function may fail if the \meta{item} contains |{|, |}|, or |#|
+% (assuming the usual \TeX{} category codes apply).
% \end{texnote}
% \end{function}
%
@@ -364,7 +403,7 @@
% spaces are trimmed around each item.
% If the result of trimming spaces is empty, the item is ignored.
% Otherwise, if the item is surrounded by braces, one set is removed,
-% and the result is passed to the mapped function. Thus, if your
+% and the result is passed to the mapped function. Thus, if the
% comma list that is being mapped is \verb*|{a , {{b} }, ,{}, {c},}|
% then the arguments passed to the mapped function are
% `\verb*|a|', `\verb*|{b} |', an empty argument, and `\verb*|c|'.
@@ -413,7 +452,7 @@
% \begin{syntax}
% \cs{clist_map_break:}
% \end{syntax}
-% Used to terminate a |\clist_map_...| function before all
+% Used to terminate a \cs[no-index]{clist_map_\ldots{}} function before all
% entries in the \meta{comma list} have been processed. This
% normally takes place within a conditional statement, for example
% \begin{verbatim}
@@ -426,7 +465,7 @@
% }
% }
% \end{verbatim}
-% Use outside of a |\clist_map_...| scenario leads to low
+% Use outside of a \cs[no-index]{clist_map_\ldots{}} scenario leads to low
% level \TeX{} errors.
% \begin{texnote}
% When the mapping is broken, additional tokens may be inserted
@@ -440,7 +479,7 @@
% \begin{syntax}
% \cs{clist_map_break:n} \Arg{code}
% \end{syntax}
-% Used to terminate a |\clist_map_...| function before all
+% Used to terminate a \cs[no-index]{clist_map_\ldots{}} function before all
% entries in the \meta{comma list} have been processed, inserting
% the \meta{code} after the mapping has ended. This
% normally takes place within a conditional statement, for example
@@ -454,7 +493,7 @@
% }
% }
% \end{verbatim}
-% Use outside of a |\clist_map_...| scenario leads to low
+% Use outside of a \cs[no-index]{clist_map_\ldots{}} scenario leads to low
% level \TeX{} errors.
% \begin{texnote}
% When the mapping is broken, additional tokens may be inserted
@@ -472,7 +511,7 @@
% Leaves the number of items in the \meta{comma list} in the input
% stream as an \meta{integer denotation}. The total number of items
% in a \meta{comma list} includes those which are duplicates,
-% \emph{i.e.}~every item in a \meta{comma list} is unique.
+% \emph{i.e.}~every item in a \meta{comma list} is counted.
% \end{function}
%
% \section{Using the content of comma lists directly}
@@ -624,7 +663,8 @@
% \cs{clist_push:Nn} \meta{comma list} \Arg{items}
% \end{syntax}
% Adds the \Arg{items} to the top of the \meta{comma list}.
-% Spaces are removed from both sides of each item.
+% Spaces are removed from both sides of each item as for any
+% \texttt{n}-type comma list.
% \end{function}
%
% \section{Using a single item}
@@ -740,6 +780,110 @@
% \end{macrocode}
% \end{macro}
%
+% \subsection{Removing spaces around items}
+%
+% \begin{macro}{\@@_trim_next:w}
+% Called as \cs{exp:w} \cs{@@_trim_next:w} \cs{prg_do_nothing:}
+% \meta{comma list} \ldots{} it expands to \Arg{trimmed item} where
+% the \meta{trimmed item} is the first non-empty result from removing
+% spaces from both ends of comma-delimited items in the \meta{comma
+% list}. The \cs{prg_do_nothing:} marker avoids losing braces. The
+% test for blank items is a somewhat optimized \cs{tl_if_empty:oTF}
+% construction; if blank, another item is sought, otherwise trim
+% spaces.
+% \begin{macrocode}
+\cs_new:Npn \@@_trim_next:w #1 ,
+ {
+ \tl_if_empty:oTF { \use_none:nn #1 ? }
+ { \@@_trim_next:w \prg_do_nothing: }
+ { \tl_trim_spaces_apply:oN {#1} \exp_end: }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[rEXP]{\@@_sanitize:n}
+% \begin{macro}{\@@_sanitize:Nn}
+% The auxiliary \cs{@@_sanitize:Nn} receives a delimiter
+% (\cs{c_empty_tl} the first time, afterwards a comma) and that item
+% as arguments. Unless we are done with the loop it calls
+% \cs{@@_wrap_item:w} to unbrace the item (using a comma delimiter is
+% safe since |#2| came from removing spaces from an argument delimited
+% by a comma) and possibly re-brace it if needed.
+% \begin{macrocode}
+\cs_new:Npn \@@_sanitize:n #1
+ {
+ \exp_after:wN \@@_sanitize:Nn \exp_after:wN \c_empty_tl
+ \exp:w \@@_trim_next:w \prg_do_nothing:
+ #1 , \q_recursion_tail , \q_recursion_stop
+ }
+\cs_new:Npn \@@_sanitize:Nn #1#2
+ {
+ \quark_if_recursion_tail_stop:n {#2}
+ #1 \@@_wrap_item:w #2 ,
+ \exp_after:wN \@@_sanitize:Nn \exp_after:wN ,
+ \exp:w \@@_trim_next:w \prg_do_nothing:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[TF]{\@@_if_wrap:n}
+% \begin{macro}{\@@_if_wrap:w}
+% True if the argument must be wrapped to avoid getting altered by some
+% clist operations. That is the case whenever the argument
+% \begin{itemize}
+% \item starts or end with a space or contains a comma,
+% \item is empty, or
+% \item consists of a single braced group.
+% \end{itemize}
+% All \pkg{l3clist} functions go through the same test when they need
+% to determine whether to brace an item, so it is not a problem that
+% this test has false positives such as \enquote{\cs{q_mark}
+% \texttt{?}}. If the argument starts or end with a space or contains
+% a comma then one of the three arguments of \cs{@@_if_wrap:w} will
+% have its end delimiter (partly) in one of the three copies of |#1|
+% in \cs{@@_if_wrap:nTF}; this has a knock-on effect meaning that the
+% result of the expansion is not empty; in that case, wrap.
+% Otherwise, the argument is safe unless it starts with a brace group
+% (or is empty) and it is empty or consists of a single
+% \texttt{n}-type argument.
+% \begin{macrocode}
+\prg_new_conditional:Npnn \@@_if_wrap:n #1 { TF }
+ {
+ \tl_if_empty:oTF
+ {
+ \@@_if_wrap:w
+ \q_mark ? #1 ~ \q_mark ? ~ #1 \q_mark , ~ \q_mark #1 ,
+ }
+ {
+ \tl_if_head_is_group:nTF { #1 { } }
+ {
+ \tl_if_empty:nTF {#1}
+ { \prg_return_true: }
+ {
+ \tl_if_empty:oTF { \use_none:n #1}
+ { \prg_return_true: }
+ { \prg_return_false: }
+ }
+ }
+ { \prg_return_false: }
+ }
+ { \prg_return_true: }
+ }
+\cs_new:Npn \@@_if_wrap:w #1 \q_mark ? ~ #2 ~ \q_mark #3 , { }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_wrap_item:w}
+% Safe items are put in \cs{exp_not:n}, otherwise we put an extra set
+% of braces.
+% \begin{macrocode}
+\cs_new:Npn \@@_wrap_item:w #1 ,
+ { \@@_if_wrap:nTF {#1} { \exp_not:n { {#1} } } { \exp_not:n {#1} } }
+% \end{macrocode}
+% \end{macro}
+%
% \subsection{Allocation and initialisation}
%
% \begin{macro}{\clist_new:N, \clist_new:c}
@@ -756,12 +900,11 @@
% \clist_const:Nn, \clist_const:cn,
% \clist_const:Nx, \clist_const:cx
% }
-% Creating and initializing a constant comma list is done in a way
-% similar to \cs{clist_set:Nn} and \cs{clist_gset:Nn}, being careful
-% to strip spaces.
+% Creating and initializing a constant comma list is done by
+% sanitizing all items (stripping spaces and braces).
% \begin{macrocode}
\cs_new_protected:Npn \clist_const:Nn #1#2
- { \tl_const:Nx #1 { \@@_trim_spaces:n {#2} } }
+ { \tl_const:Nx #1 { \@@_sanitize:n {#2} } }
\cs_generate_variant:Nn \clist_const:Nn { c , Nx , cx }
% \end{macrocode}
% \end{macro}
@@ -829,13 +972,11 @@
% \clist_gset_from_seq:Nc, \clist_gset_from_seq:cc
% }
% \UnitTested
-% \begin{macro}{\@@_set_from_seq:NNNN}
-% \begin{macro}{\@@_wrap_item:n}
-% \begin{macro}{\@@_set_from_seq:w}
-% Setting a comma list from a comma-separated list is done using a simple
-% mapping. We wrap most items with \cs{exp_not:n}, and a comma. Items which
-% contain a comma or a space are surrounded by an extra set of braces. The
-% first comma must be removed, except in the case of an empty comma-list.
+% \begin{macro}{\@@_set_from_seq:NNNN, \@@_set_from_seq:n}
+% Setting a comma list from a comma-separated list is done using a
+% simple mapping. Safe items are put in \cs{exp_not:n}, otherwise we
+% put an extra set of braces. The first comma must be removed, except
+% in the case of an empty comma-list.
% \begin{macrocode}
\cs_new_protected:Npn \clist_set_from_seq:NN
{ \@@_set_from_seq:NNNN \clist_clear:N \tl_set:Nx }
@@ -848,19 +989,18 @@
{
#2 #3
{
- \exp_last_unbraced:Nf \use_none:n
- { \seq_map_function:NN #4 \@@_wrap_item:n }
+ \exp_after:wN \use_none:n \exp:w \exp_end_continue_f:w
+ \seq_map_function:NN #4 \@@_set_from_seq:n
}
}
}
-\cs_new:Npn \@@_wrap_item:n #1
+\cs_new:Npn \@@_set_from_seq:n #1
{
,
- \tl_if_empty:oTF { \@@_set_from_seq:w #1 ~ , #1 ~ }
- { \exp_not:n {#1} }
+ \@@_if_wrap:nTF {#1}
{ \exp_not:n { {#1} } }
+ { \exp_not:n {#1} }
}
-\cs_new:Npn \@@_set_from_seq:w #1 , #2 ~ { }
\cs_generate_variant:Nn \clist_set_from_seq:NN { Nc }
\cs_generate_variant:Nn \clist_set_from_seq:NN { c , cc }
\cs_generate_variant:Nn \clist_gset_from_seq:NN { Nc }
@@ -869,8 +1009,6 @@
% \end{macro}
% \end{macro}
% \end{macro}
-% \end{macro}
-% \end{macro}
%
% \begin{macro}{\clist_concat:NNN, \clist_concat:ccc}
% \UnitTested
@@ -911,66 +1049,6 @@
% \end{macrocode}
% \end{macro}
%
-% \subsection{Removing spaces around items}
-%
-% \begin{macro}[EXP]{\@@_trim_spaces_generic:nw}
-% \begin{macro}{\@@_trim_spaces_generic:nn}
-% \begin{syntax}
-% \cs{@@_trim_spaces_generic:nw} \Arg{code} \cs{q_mark} \meta{item} |,|
-% \end{syntax}
-% This expands to the \meta{code}, followed by a brace group
-% containing the \meta{item}, with leading and trailing spaces
-% removed. The calling function is responsible for inserting
-% \cs{q_mark} in front of the \meta{item}, as well as testing for the
-% end of the list. We reuse a \pkg{l3tl} internal function, whose
-% first argument must start with \cs{q_mark}. That trims the item
-% |#2|, then feeds the result (after having to do an \texttt{o}-type
-% expansion) to \cs{@@_trim_spaces_generic:nn} which places the
-% \meta{code} in front of the \meta{trimmed item}.
-% \begin{macrocode}
-\cs_new:Npn \@@_trim_spaces_generic:nw #1#2 ,
- {
- \__tl_trim_spaces:nn {#2}
- { \exp_args:No \@@_trim_spaces_generic:nn } {#1}
- }
-\cs_new:Npn \@@_trim_spaces_generic:nn #1#2 { #2 {#1} }
-% \end{macrocode}
-% \end{macro}
-% \end{macro}
-%
-% \begin{macro}[rEXP]{\@@_trim_spaces:n}
-% \begin{macro}{\@@_trim_spaces:nn}
-% The first argument of \cs{@@_trim_spaces:nn} is initially empty,
-% and later a comma, namely, as soon as we have added an item to the
-% resulting list. The auxiliary tests for the end of the list,
-% and also prevents empty arguments from finding their way into the
-% output.
-% \begin{macrocode}
-\cs_new:Npn \@@_trim_spaces:n #1
- {
- \@@_trim_spaces_generic:nw
- { \@@_trim_spaces:nn { } }
- \q_mark #1 ,
- \q_recursion_tail, \q_recursion_stop
- }
-\cs_new:Npn \@@_trim_spaces:nn #1 #2
- {
- \quark_if_recursion_tail_stop:n {#2}
- \tl_if_empty:nTF {#2}
- {
- \@@_trim_spaces_generic:nw
- { \@@_trim_spaces:nn {#1} } \q_mark
- }
- {
- #1 \exp_not:n {#2}
- \@@_trim_spaces_generic:nw
- { \@@_trim_spaces:nn { , } } \q_mark
- }
- }
-% \end{macrocode}
-% \end{macro}
-% \end{macro}
-%
% \subsection{Adding data to comma lists}
%
% \begin{macro}
@@ -989,9 +1067,9 @@
% }
% \begin{macrocode}
\cs_new_protected:Npn \clist_set:Nn #1#2
- { \tl_set:Nx #1 { \@@_trim_spaces:n {#2} } }
+ { \tl_set:Nx #1 { \@@_sanitize:n {#2} } }
\cs_new_protected:Npn \clist_gset:Nn #1#2
- { \tl_gset:Nx #1 { \@@_trim_spaces:n {#2} } }
+ { \tl_gset:Nx #1 { \@@_sanitize:n {#2} } }
\cs_generate_variant:Nn \clist_set:Nn { NV , No , Nx , c , cV , co , cx }
\cs_generate_variant:Nn \clist_gset:Nn { NV , No , Nx , c , cV , co , cx }
% \end{macrocode}
@@ -1015,8 +1093,9 @@
% }
% \UnitTested
% \begin{macro}{\@@_put_left:NNNn}
-% Comma lists cannot hold empty values: there are therefore a couple
-% of sanity checks to avoid accumulating commas.
+% Everything is based on concatenation after storing in
+% \cs{l_@@_internal_clist}. This avoids having to worry here about
+% space-trimming and so on.
% \begin{macrocode}
\cs_new_protected:Npn \clist_put_left:Nn
{ \@@_put_left:NNNn \clist_concat:NNN \clist_set:Nn }
@@ -1078,7 +1157,11 @@
% \UnitTested
% \begin{macro}{\@@_get:wN}
% Getting an item from the left of a comma list is pretty easy: just
-% trim off the first item using the comma.
+% trim off the first item using the comma. No need to trim spaces as
+% comma-list \emph{variables} are assumed to have \enquote{cleaned-up}
+% items. (Note that grabbing a comma-delimited item removes an outer
+% pair of braces if present, exactly as needed to uncover the
+% underlying item.)
% \begin{macrocode}
\cs_new_protected:Npn \clist_get:NN #1#2
{
@@ -1211,10 +1294,11 @@
%
% \subsection{Modifying comma lists}
%
-% \begin{variable}{\l_@@_internal_remove_clist}
-% An internal comma list for the removal routines.
+% \begin{variable}{\l_@@_internal_remove_clist, \l_@@_internal_remove_seq}
+% An internal comma list and a sequence for the removal routines.
% \begin{macrocode}
\clist_new:N \l_@@_internal_remove_clist
+\seq_new:N \l_@@_internal_remove_seq
% \end{macrocode}
% \end{variable}
%
@@ -1250,11 +1334,20 @@
% \UnitTested
% \begin{macro}{\clist_gremove_all:Nn, \clist_gremove_all:cn}
% \UnitTested
-% \begin{macro}{\@@_remove_all:NNn}
+% \begin{macro}{\@@_remove_all:NNNn}
% \begin{macro}{\@@_remove_all:w}
% \begin{macro}{\@@_remove_all:}
-% The method used here is very similar to \cs{tl_replace_all:Nnn}.
-% Build a function delimited by the \meta{item} that should be removed,
+% The method used here for safe items is very similar to
+% \cs{tl_replace_all:Nnn}. However, if the item contains commas or
+% leading/trailing spaces, or is empty, or consists of a single brace
+% group, we know that it can only appear within braces so the code
+% would fail; instead just convert to a sequence and do the removal
+% with \pkg{l3seq} code (it involves somewhat elaborate code to do
+% most of the work expandably but the final token list comparisons
+% non-expandably).
+%
+% For \enquote{safe} items, build a function delimited by the
+% \meta{item} that should be removed,
% surrounded with commas, and call that function followed by
% the expanded comma list, and another copy of the \meta{item}.
% The loop is controlled by the argument grabbed by
@@ -1276,28 +1369,36 @@
% the second step removes it.
% \begin{macrocode}
\cs_new_protected:Npn \clist_remove_all:Nn
- { \@@_remove_all:NNn \tl_set:Nx }
+ { \@@_remove_all:NNNn \clist_set_from_seq:NN \tl_set:Nx }
\cs_new_protected:Npn \clist_gremove_all:Nn
- { \@@_remove_all:NNn \tl_gset:Nx }
-\cs_new_protected:Npn \@@_remove_all:NNn #1#2#3
+ { \@@_remove_all:NNNn \clist_gset_from_seq:NN \tl_gset:Nx }
+\cs_new_protected:Npn \@@_remove_all:NNNn #1#2#3#4
{
- \cs_set:Npn \@@_tmp:w ##1 , #3 ,
- {
- ##1
- , \q_mark , \use_none_delimit_by_q_stop:w ,
- \@@_remove_all:
- }
- #1 #2
+ \@@_if_wrap:nTF {#4}
{
- \exp_after:wN \@@_remove_all:
- #2 , \q_mark , #3 , \q_stop
+ \seq_set_from_clist:NN \l_@@_internal_remove_seq #3
+ \seq_remove_all:Nn \l_@@_internal_remove_seq {#4}
+ #1 #3 \l_@@_internal_remove_seq
}
- \clist_if_empty:NF #2
{
- #1 #2
+ \cs_set:Npn \@@_tmp:w ##1 , #4 ,
+ {
+ ##1
+ , \q_mark , \use_none_delimit_by_q_stop:w ,
+ \@@_remove_all:
+ }
+ #2 #3
+ {
+ \exp_after:wN \@@_remove_all:
+ #3 , \q_mark , #4 , \q_stop
+ }
+ \clist_if_empty:NF #3
{
- \exp_args:No \exp_not:o
- { \exp_after:wN \use_none:n #2 }
+ #2 #3
+ {
+ \exp_args:No \exp_not:o
+ { \exp_after:wN \use_none:n #3 }
+ }
}
}
}
@@ -1425,26 +1526,46 @@
% \clist_if_in:cn, \clist_if_in:cV, \clist_if_in:co,
% \clist_if_in:nn, \clist_if_in:nV, \clist_if_in:no
% }
-% \begin{macro}{\@@_if_in_return:nn}
+% \begin{macro}{\@@_if_in_return:nnN}
% \UnitTested
-% See description of the \cs{tl_if_in:Nn} function for details.
-% We simply surround the comma list, and the item, with commas.
+% For \enquote{safe} items, we simply surround the comma list, and the
+% item, with commas, then use the same code as for \cs{tl_if_in:Nn}.
+% For \enquote{unsafe} items we follow the same route as
+% \cs{seq_if_in:Nn}, mapping through the list a comparison function.
+% If found, return \texttt{true} and remove \cs{prg_return_false:}.
% \begin{macrocode}
\prg_new_protected_conditional:Npnn \clist_if_in:Nn #1#2 { T , F , TF }
{
- \exp_args:No \@@_if_in_return:nn #1 {#2}
+ \exp_args:No \@@_if_in_return:nnN #1 {#2} #1
}
\prg_new_protected_conditional:Npnn \clist_if_in:nn #1#2 { T , F , TF }
{
\clist_set:Nn \l_@@_internal_clist {#1}
- \exp_args:No \@@_if_in_return:nn \l_@@_internal_clist {#2}
+ \exp_args:No \@@_if_in_return:nnN \l_@@_internal_clist {#2}
+ \l_@@_internal_clist
}
-\cs_new_protected:Npn \@@_if_in_return:nn #1#2
+\cs_new_protected:Npn \@@_if_in_return:nnN #1#2#3
{
- \cs_set:Npn \@@_tmp:w ##1 ,#2, { }
- \tl_if_empty:oTF
- { \@@_tmp:w ,#1, {} {} ,#2, }
- { \prg_return_false: } { \prg_return_true: }
+ \@@_if_wrap:nTF {#2}
+ {
+ \cs_set:Npx \@@_tmp:w ##1
+ {
+ \exp_not:N \tl_if_eq:nnT {##1}
+ \exp_not:n
+ {
+ {#2}
+ { \clist_map_break:n { \prg_return_true: \use_none:n } }
+ }
+ }
+ \clist_map_function:NN #3 \@@_tmp:w
+ \prg_return_false:
+ }
+ {
+ \cs_set:Npn \@@_tmp:w ##1 ,#2, { }
+ \tl_if_empty:oTF
+ { \@@_tmp:w ,#1, {} {} ,#2, }
+ { \prg_return_false: } { \prg_return_true: }
+ }
}
\prg_generate_conditional_variant:Nnn \clist_if_in:Nn
{ NV , No , c , cV , co } { T , F , TF }
@@ -1463,8 +1584,8 @@
% that comma-list would be seen as consisting of one empty item).
% Then loop over the comma-list, grabbing one comma-delimited
% item at a time. The end is marked by \cs{q_recursion_tail}.
-% The auxiliary function \cs{@@_map_function:Nw} is used
-% directly in \cs{clist_map_inline:Nn}. Change with care.
+% The auxiliary function \cs{@@_map_function:Nw} is also used
+% in \cs{clist_map_inline:Nn}.
% \begin{macrocode}
\cs_new:Npn \clist_map_function:NN #1#2
{
@@ -1472,12 +1593,12 @@
{
\exp_last_unbraced:NNo \@@_map_function:Nw #2 #1
, \q_recursion_tail ,
- \__prg_break_point:Nn \clist_map_break: { }
+ \prg_break_point:Nn \clist_map_break: { }
}
}
\cs_new:Npn \@@_map_function:Nw #1#2 ,
{
- \__quark_if_recursion_tail_break:nN {#2} \clist_map_break:
+ \quark_if_recursion_tail_break:nN {#2} \clist_map_break:
#1 {#2}
\@@_map_function:Nw #1
}
@@ -1492,25 +1613,24 @@
% \begin{macro}{\@@_map_unbrace:Nw}
% The \texttt{n}-type mapping function is a bit more awkward,
% since spaces must be trimmed from each item.
-% Space trimming is again based on \cs{@@_trim_spaces_generic:nw}.
-% The auxiliary \cs{@@_map_function_n:Nn} receives
-% as arguments the function, and the result of removing leading
-% and trailing spaces from the item which lies until the next comma.
-% Empty items are ignored, then one level of braces is removed
-% by \cs{@@_map_unbrace:Nw}.
+% Space trimming is again based on \cs{@@_trim_next:w}.
+% The auxiliary \cs{@@_map_function_n:Nn} receives as arguments the
+% function, and the next non-empty item (after space trimming but
+% before brace removal). One level of braces is removed by
+% \cs{@@_map_unbrace:Nw}.
% \begin{macrocode}
\cs_new:Npn \clist_map_function:nN #1#2
{
- \@@_trim_spaces_generic:nw { \@@_map_function_n:Nn #2 }
- \q_mark #1, \q_recursion_tail,
- \__prg_break_point:Nn \clist_map_break: { }
+ \exp_after:wN \@@_map_function_n:Nn \exp_after:wN #2
+ \exp:w \@@_trim_next:w \prg_do_nothing: #1 , \q_recursion_tail ,
+ \prg_break_point:Nn \clist_map_break: { }
}
\cs_new:Npn \@@_map_function_n:Nn #1 #2
{
- \__quark_if_recursion_tail_break:nN {#2} \clist_map_break:
- \tl_if_empty:nF {#2} { \@@_map_unbrace:Nw #1 #2, }
- \@@_trim_spaces_generic:nw { \@@_map_function_n:Nn #1 }
- \q_mark
+ \quark_if_recursion_tail_break:nN {#2} \clist_map_break:
+ \@@_map_unbrace:Nw #1 #2,
+ \exp_after:wN \@@_map_function_n:Nn \exp_after:wN #1
+ \exp:w \@@_trim_next:w \prg_do_nothing:
}
\cs_new:Npn \@@_map_unbrace:Nw #1 #2, { #1 {#2} }
% \end{macrocode}
@@ -1538,14 +1658,14 @@
{
\clist_if_empty:NF #1
{
- \int_gincr:N \g__prg_map_int
+ \int_gincr:N \g__kernel_prg_map_int
\cs_gset_protected:cpn
- { __prg_map_ \int_use:N \g__prg_map_int :w } ##1 {#2}
+ { @@_map_ \int_use:N \g__kernel_prg_map_int :w } ##1 {#2}
\exp_last_unbraced:Nco \@@_map_function:Nw
- { __prg_map_ \int_use:N \g__prg_map_int :w }
+ { @@_map_ \int_use:N \g__kernel_prg_map_int :w }
#1 , \q_recursion_tail ,
- \__prg_break_point:Nn \clist_map_break:
- { \int_gdecr:N \g__prg_map_int }
+ \prg_break_point:Nn \clist_map_break:
+ { \int_gdecr:N \g__kernel_prg_map_int }
}
}
\cs_new_protected:Npn \clist_map_inline:nn #1
@@ -1567,6 +1687,8 @@
% additionally we store each item in the given variable.
% As for inline mappings, space trimming for the \texttt{n}
% variant is done by storing the comma list in a variable.
+% The strange \cs{use:n} avoids unlikely problems when |#2| would
+% contain \cs{q_recursion_stop}.
% \begin{macrocode}
\cs_new_protected:Npn \clist_map_variable:NNn #1#2#3
{
@@ -1576,7 +1698,7 @@
{ \@@_map_variable:Nnw #2 {#3} }
#1
, \q_recursion_tail , \q_recursion_stop
- \__prg_break_point:Nn \clist_map_break: { }
+ \prg_break_point:Nn \clist_map_break: { }
}
}
\cs_new_protected:Npn \clist_map_variable:nNn #1
@@ -1598,12 +1720,12 @@
% \end{macro}
%
% \begin{macro}{\clist_map_break:, \clist_map_break:n}
-% The break statements use the general \cs{__prg_map_break:Nn} mechanism.
+% The break statements use the general \cs{prg_map_break:Nn} mechanism.
% \begin{macrocode}
\cs_new:Npn \clist_map_break:
- { \__prg_map_break:Nn \clist_map_break: { } }
+ { \prg_map_break:Nn \clist_map_break: { } }
\cs_new:Npn \clist_map_break:n
- { \__prg_map_break:Nn \clist_map_break: }
+ { \prg_map_break:Nn \clist_map_break: }
% \end{macrocode}
% \end{macro}
%
@@ -1769,6 +1891,7 @@
% \@@_item_n:nw,
% \@@_item_n_loop:nw,
% \@@_item_n_end:n,
+% \@@_item_n_strip:n,
% \@@_item_n_strip:w}
% This starts in the same way as \cs{clist_item:Nn} by counting the items
% of the comma list. The final item should be space-trimmed before being
@@ -1800,10 +1923,8 @@
}
}
\cs_new:Npn \@@_item_n_end:n #1 #2 \q_stop
- {
- \__tl_trim_spaces:nn { \q_mark #1 }
- { \exp_last_unbraced:No \@@_item_n_strip:w } ,
- }
+ { \tl_trim_spaces_apply:nN {#1} \@@_item_n_strip:n }
+\cs_new:Npn \@@_item_n_strip:n #1 { \@@_item_n_strip:w #1 , }
\cs_new:Npn \@@_item_n_strip:w #1 , { \exp_not:n {#1} }
% \end{macrocode}
% \end{macro}
@@ -1812,7 +1933,7 @@
% \subsection{Viewing comma lists}
%
% \begin{macro}{\clist_show:N, \clist_show:c, \clist_log:N, \clist_log:c, \@@_show:NN}
-% Apply the general \cs{__kernel_check_defined:NT} and
+% Apply the general \cs{__kernel_chk_defined:NT} and
% \cs{msg_show:nnnnnn}.
% \begin{macrocode}
\cs_new_protected:Npn \clist_show:N { \@@_show:NN \msg_show:nnxxxx }
@@ -1821,7 +1942,7 @@
\cs_generate_variant:Nn \clist_log:N { c }
\cs_new_protected:Npn \@@_show:NN #1#2
{
- \__kernel_check_defined:NT #2
+ \__kernel_chk_defined:NT #2
{
#1 { LaTeX/kernel } { show-clist }
{ \token_to_str:N #2 }