我想知道是否可以将多个标记传递给 xparse 命令中的 «u» 参数,以便通过“OR”规则选择它们?最好使用正则表达式,这样可以选择更多内容。
假设我有这样一个命令,它应该变为粗体,直到遇到,
、、或任何大写字母。.
(
)
期望有类似这样的事情:
\documentclass{article}
\DeclareDocumentCommand{\example}{ u{/[,.\(\)A-Z]/g} }{\textbf{#1}}
\begin{document}
Some \example text goes here, also bold \example this part as well But should stop before 'B'.
\end{document}
答案1
参数u
类型xparse
使用下面的 TeX 分隔宏来获取命令的参数。定义:
\NewDocumentCommand\example{u{X}}{\textbf{#1}}
相当于
\def\example#1X{\textbf{#1}}
尽管前者有更多健全性检查,但最终还是xparse
使用后者来获取参数。而且 TeX 不允许条件分隔的宏:如果在宏定义中插入分隔符,则有要使用的。
也就是说,可以编写一个解析器来查找你的标记列表并在你需要时停止。
我提供两种截然不同的实现方式。版本 2是一种仅限正则表达式的方法,更加灵活,但风险也更大。版本 1稍微保守一些,速度更快,但灵活性也较差。
版本 2
此实现定义:
\GrabUntil[*]\command{<regex>} <text>
和
\GrabUntil[*]{<inline code>}{<regex>} <text>
该命令\GrabUntil
将提前扫描并将的标记单独添加<text>
到内部<token list>
(<tl>
简称)。添加每个标记后,该命令将检查是否<tl>
匹配<regex>
。如果匹配,则<regex>
从中提取<tl>
,命令留\command{<tl>}<match>
在输入流中。如果*
给出了可选项,则<match>
不会重新插入。如果<inline code>
给出了,则将参数 grabbed 传递给<inline code>
as #1
(这本质上将定义<inline code>
为临时宏,然后将其用作\command
)。
{
并且}
可以在中使用,<regex>
但它们必须是平衡的,因为代码将始终抓取平衡的标记列表。使用此解析器时要小心,因为它的停止条件可能比来自的停止条件更难满足版本 1,它可能会吃掉文档的其余部分而不引起注意。代码假设<match>
在提取时位于的末尾<tl>
,否则不应依赖此行为。建议以 结尾<regex>
以$
确保这一点。
这个解析器可以(我敢说很容易)添加xparse
为参数类型,但不建议弄乱xparse
其内部结构,因此它留给读者练习 ;-)
使用示例文本运行示例可得到:
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand \GrabUntil { s m +m }
{
\tl_if_single_token:nTF {#2}
{ \cs_set_eq:NN \__antshar_cmd:n #2 }
{ \cs_set_protected:Npn \__antshar_cmd:n ##1 {#2} }
\IfBooleanTF {#1}
{ \antshar_grab_delimited_regex:NNn \c_true_bool }
{ \antshar_grab_delimited_regex:NNn \c_false_bool }
\__antshar_cmd:n {#3}
}
\tl_new:N \l__antshar_arg_tl
\tl_new:N \l__antshar_tmp_tl
\seq_new:N \l__antshar_return_seq
\bool_new:N \l__antshar_remove_bool
\cs_new_eq:NN \__antshar_cmd:n ?
\cs_new_eq:NN \__antshar_run_cmd:n ?
\regex_new:N \l__antshar_delim_regex
\cs_new_protected:Npn \antshar_grab_delimited_regex:NNn #1 #2 #3
{
\tl_clear:N \l__antshar_arg_tl
\bool_set_eq:NN \l__antshar_remove_bool #1
\cs_set_protected:Npn \__antshar_run_cmd:n ##1 { #2 {##1} }
\regex_set:Nn \l__antshar_delim_regex {#3}
\__antshar_scan:w
}
\cs_generate_variant:Nn \__antshar_run_cmd:n { V }
\cs_new_protected:Npn \__antshar_scan:w
{
\peek_meaning:NTF \c_group_begin_token
{ \__antshar_add_group:n }
{
\peek_meaning:NTF \c_space_token
{ \__antshar_add_space:w }
{ \__antshar_add_token:N }
}
}
\cs_new_protected:Npn \__antshar_add_arg:n #1
{
\tl_put_right:Nn \l__antshar_arg_tl {#1}
\regex_match:NVTF \l__antshar_delim_regex \l__antshar_arg_tl
{
\regex_extract_once:NVN
\l__antshar_delim_regex \l__antshar_arg_tl
\l__antshar_return_seq
\regex_replace_once:NnN
\l__antshar_delim_regex { }
\l__antshar_arg_tl
\__antshar_finish:
}
{ \__antshar_scan:w }
}
\cs_generate_variant:Nn \regex_extract_once:NnN { NV }
\prg_generate_conditional_variant:Nnn \regex_match:Nn { NV } { TF }
\exp_last_unbraced:NNo
\cs_new_protected:Npn \__antshar_add_space:w \c_space_tl
{ \__antshar_add_arg:n { ~ } }
\cs_new_protected:Npn \__antshar_add_group:n #1
{ \__antshar_add_arg:n { {#1} } }
\cs_new_protected:Npn \__antshar_add_token:N #1
{ \__antshar_add_arg:n {#1} }
\cs_new_protected:Npn \__antshar_finish:
{
\use:x
{
\__antshar_run_cmd:V \exp_not:N \l__antshar_arg_tl
\bool_if:NF \l__antshar_remove_bool
{ \seq_use:Nn \l__antshar_return_seq { } }
}
}
\msg_new:nnn { antshar } { braced-tokens }
{
Unsupported~braced~tokens~`#1'~found~
in~argument~to~\iow_char:N\\GrabUntil.
}
\ExplSyntaxOff
\begin{document}
\NewDocumentCommand{\example}{}%
{\GrabUntil\textbf{\ [A-Z]|[.,()]$}}
\NewDocumentCommand{\inline}{}%
{\GrabUntil{\textbf{(##1)}}{\ [A-Z]|[.,()]$}}
Some \example text goes here, also bold \example this part as well But should stop before 'B'.
Some \inline text goes here, also bold \inline this part as well But should stop before 'B'.
% -----
\NewDocumentCommand{\exampleA}{}%
{\GrabUntil*\textbf{\ [A-Z]|[.,()]$}}
\NewDocumentCommand{\inlineA}{}%
{\GrabUntil*{\textbf{(##1)}}{\ [A-Z]|[.,()]$}}
Some \exampleA text goes here, also bold \exampleA this part as well But should stop before 'B'.
Some \inlineA text goes here, also bold \inlineA this part as well But should stop before 'B'.
\end{document}
版本 1
下面的实现定义:
\GrabUntil[*]\command{<tokens|regex>} <text>
和
\GrabUntil[*]{<inline code>}{<tokens|regex>} <text>
该命令\GrabUntil
将向前扫描,逐个查看 中的每个标记<text>
,如果 中出现任何标记<tokens>
,则将其放回输入流并\command
使用迄今为止收集的标记进行调用。如果*
给出了可选项,则该命令将改为使用来将 中的标记与给定的\regex_match:NnTF
进行匹配。如果改为给出 ,则将 grabbed 的参数传递给as (这本质上将 定义为临时宏,然后将其用作)。<text>
<regex>
<inline code>
<inline code>
#1
<inline code>
\command
一个限制是{
和}
不能用作分隔符(虽然\{
和\}
可以),并且标记组是完整传递的,因此使用\GrabUntil\test{abc} x{abc}za
,您将得到x{abc}z
作为 的参数\test
。另一个限制是这只允许单个标记作为分隔符,因为扫描器单独查看标记,因此正则表达式插入字符类([
... ]
)中,以便只有单个标记匹配。
使用示例文本运行示例可得到:
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand \GrabUntil { s m +m }
{
\tl_if_single_token:nTF {#2}
{ \cs_set_eq:NN \__antshar_cmd:n #2 }
{ \cs_set_protected:Npn \__antshar_cmd:n ##1 {#2} }
\IfBooleanTF {#1}
{ \antshar_grab_delimited_regex:Nn \__antshar_cmd:n {#3} }
{ \antshar_grab_delimited:Nn \__antshar_cmd:n {#3} }
}
\tl_new:N \l__antshar_arg_tl
\tl_new:N \l__antshar_delim_tl
\regex_new:N \l__antshar_delim_regex
\cs_new_eq:NN \__antshar_cmd:n ?
\cs_new_eq:NN \__antshar_run_cmd:n ?
\cs_new_eq:NN \__antshar_examine_next:n ?
\cs_new_protected:Npn \antshar_grab_delimited:Nn #1 #2
{
\cs_set_protected:Npn \__antshar_run_cmd:n ##1 { #1 {##1} }
\tl_clear:N \l__antshar_arg_tl
\tl_clear:N \l__antshar_delim_tl
\tl_set:Nn \l__antshar_delim_tl {#2}
\cs_set_eq:NN \__antshar_examine_next:n \__antshar_examine_tl:n
\__antshar_scan:w
}
\cs_new_protected:Npn \antshar_grab_delimited_regex:Nn #1 #2
{
\cs_set_protected:Npn \__antshar_run_cmd:n ##1 { #1 {##1} }
\tl_clear:N \l__antshar_arg_tl
\regex_set:Nn \l__antshar_delim_regex { [#2] }
\cs_set_eq:NN \__antshar_examine_next:n \__antshar_examine_regex:n
\__antshar_scan:w
}
\cs_generate_variant:Nn \__antshar_run_cmd:n { V }
\cs_new_protected:Npn \__antshar_scan:w
{
\peek_meaning:NTF \c_group_begin_token
{ \__antshar_add_group:n }
{
\peek_meaning:NTF \c_space_token
{ \__antshar_add_space:w }
{ \__antshar_add_token:N }
}
}
\cs_new_protected:Npn \__antshar_examine_tl:n #1
{
\tl_if_in:NnTF \l__antshar_delim_tl {#1}
{ \__antshar_finish: #1 }
{ \__antshar_continue:n {#1} }
}
\cs_new_protected:Npn \__antshar_examine_regex:n #1
{
\regex_match:NnTF \l__antshar_delim_regex {#1}
{ \__antshar_finish: #1 }
{ \__antshar_continue:n {#1} }
}
\cs_new_protected:Npn \__antshar_continue:n #1
{
\tl_put_right:Nn \l__antshar_arg_tl {#1}
\__antshar_scan:w
}
\exp_last_unbraced:NNo
\cs_new_protected:Npn \__antshar_add_space:w \c_space_tl
{ \__antshar_examine_next:n { ~ } }
\cs_new_protected:Npn \__antshar_add_group:n #1
{ \__antshar_examine_next:n { {#1} } }
\cs_new_protected:Npn \__antshar_add_token:N #1
{ \__antshar_examine_next:n {#1} }
\cs_new_protected:Npn \__antshar_finish:
{ \__antshar_run_cmd:V \l__antshar_arg_tl }
\msg_new:nnn { antshar } { braced-tokens }
{
Unsupported~braced~tokens~`#1'~found~
in~argument~to~\iow_char:N\\GrabUntil.
}
\ExplSyntaxOff
\begin{document}
\NewDocumentCommand{\example}{}%
{\GrabUntil\textbf{,.()ABCDEFGHIJKLMNOPQRZTUVWXYZ}}
\NewDocumentCommand{\exampleregex}{}%
{\GrabUntil*\textbf{,\.\(\)A-Z}}
\NewDocumentCommand{\exampleinline}{}%
{\GrabUntil*{\textbf{(##1)}}{,\.\(\)A-Z}}
Some \example text goes here, also bold \example this part as well But should stop before 'B'.
Some \exampleregex text goes here, also bold \exampleregex this part as well But should stop before 'B'.
Some \exampleinline text goes here, also bold \exampleinline this part as well But should stop before 'B'.
\end{document}