我想测试一个字符串是否包含子字符串。我在这个问题。但是我遇到了使用 expl3 的第一个 egreg 答案的问题,而 Werner 的答案没有出现同样的问题。问题出现在我使用 中的宏时\setlength
(我得到了一个“缺失数字,视为”零的错误)。我想知道为什么,以及它是否可以调整。我得到了与 Werner 的答案相同的错误,但把 放在\protect
之前\instring
。\instring
脆弱和\instringTF
坚固?这就是为什么一个有效而另一个无效的原因吗?我可能完全错了。我有点惊讶问题会来自坚固的那个,我以为通常是相反的。
\documentclass{article}
\usepackage{xparse}
%egreg answer
\ExplSyntaxOn
\NewDocumentCommand{\instringTF}{mmmm}
{%
\oleks_instring:nnnn {#1}{#2}{#3}{#4}
}%
\tl_new:N \l__oleks_instring_test_tl
\cs_new_protected:Nn \oleks_instring:nnnn
{%
\tl_set:Nn \l__oleks_instring_test_tl {#1}
\regex_match:nnTF {\u{l__oleks_instring_test_tl}}{#2}{#3}{#4}
}%
\ExplSyntaxOff
%Werner answer
\newcommand{\instring}[4]{%
\ifnum\pdfmatch{#1}{#2}=1
#3%
\else
#4%
\fi
}
\begin{document}
The sky is \instringTF{str}{string}{blue}{green}. %working
The sky is \instring{str}{string}{blue}{green}.\\ %working
With instringTF (expl3) from egreg:
\begin{itemize}
%\setlength{\itemsep}{\instringTF{str}{string}{1cm}{2cm}} %not working
\item first item
\item second item
\end{itemize}
With instring (pdfmatch) from Werner:
\begin{itemize}
\setlength{\itemsep}{\instring{str}{string}{1cm}{2cm}} %working
\item first item
\item second item
\end{itemize}
\end{document}
答案1
\setlength
根据 TeX 的语法,的第二个参数应该可以读作 ⟨glue⟩。TeX 使用以下方式读取 ⟨glue⟩仅扩展,因此那里使用的任何命令都必须是“可扩展的” 1(并且递归扩展的最终结果必须是⟨glue⟩)。沃纳技术依赖于 TeX\ifnum
和 pdfTeX 的\pdfmatch
原语,它们都满足这个约束,而埃格雷格的技术使用\tl_set:Nn
和\regex_match:nnTF
,它们都不可扩展(使用 TeXbook 中的类比,它们都使用需要在 TeX 的胃中而不是嘴中处理的低级命令)。
但是,可以将 egreg 的命令拆分为两部分:一部分需要扩展 + 执行(即,嘴巴 + 胃);另一部分只需要扩展(TeX 的嘴巴中发生的事情)。在下面的代码中:
前者被调用
\testIfInString
并执行实际的子字符串测试。它不能在仅扩展的上下文中使用,例如在的第二个参数中\setlength
,但通常可以在它之前找到一个合适的位置,让它正常工作。后者是完全可扩展的,只需根据上次使用 的结果选择正确的分支即可
\testIfInString
。第二个命令称为\ifInStringTF
,我提供了变体\ifInStringT
和\ifInStringF
以方便您使用。\ifInStringTF
,\ifInStringT
和\ifInStringF
可以在仅扩展的上下文中安全使用,例如: 的第二个参数中\setlength
, 的第二个参数中\setcounter
, 之后\if
,\ifcat
\ifnum
,\ifdim
,\ifodd
,\ifcase
,\number
, , 在或 的\romannumeral
替换文本内,以及许多其他地方。\edef
\xdef
\documentclass{article}
\usepackage{xparse}
\usepackage{multicol} % only for the demo code
\ExplSyntaxOn
\NewDocumentCommand { \testIfInString } { m m }
{
\oleks_test_if_in_string:nn {#1} {#2}
}
\NewExpandableDocumentCommand { \ifInStringTF } { }
{ \oleks_if_in_string:TF }
\NewExpandableDocumentCommand { \ifInStringT } { }
{ \oleks_if_in_string:T }
\NewExpandableDocumentCommand { \ifInStringF } { }
{ \oleks_if_in_string:F }
\tl_new:N \l__oleks_instring_test_tl
\bool_new:N \l__oleks_is_in_string_bool
\cs_new_protected:Nn \oleks_test_if_in_string:nn
{
\tl_set:Nn \l__oleks_instring_test_tl {#1}
\regex_match:nnTF { \u{l__oleks_instring_test_tl} } {#2}
{ \bool_set_true:N \l__oleks_is_in_string_bool }
{ \bool_set_false:N \l__oleks_is_in_string_bool }
}
\prg_new_conditional:Npnn \oleks_if_in_string: { T, F, TF } % you can also add 'p'
{
\bool_if:NTF \l__oleks_is_in_string_bool
{ \prg_return_true: }
{ \prg_return_false: }
}
\ExplSyntaxOff
\begin{document}
\testIfInString{str}{string}%
The sky is \ifInStringTF{blue}{green}. \ifInStringT{A}\ifInStringF{B}.
\testIfInString{foobar}{string}%
The sky is not \ifInStringTF{blue}{green}. \ifInStringT{A}\ifInStringF{B}.
With \verb|\setlength|:
\begin{multicols}{2}
\raggedcolumns % we don't want the first column material to be stretched
\begin{itemize}
\testIfInString{str}{string}%
\setlength{\itemsep}{\ifInStringTF{1cm}{2cm}}
\item first item
\item second item
\end{itemize}
\columnbreak
\begin{itemize}
\testIfInString{not in 'string'}{string}%
\setlength{\itemsep}{\ifInStringTF{1cm}{2cm}}
\item first item
\item second item
\end{itemize}
\end{multicols}
\end{document}
\instring
沃纳宏的改进
原因我已经解释过了昨天,如果您决定采用 Werner 的方法,我建议使用其宏的修改版本。以下示例中\instring
调用了修改后的版本。\MyInString
\documentclass{article}
% From Werner in <https://tex.stackexchange.com/a/446691/73317>
\newcommand{\instring}[4]{%
% \instring{<pattern>}{<string>}{<true>}{<false>}
% pattern = sought after string
% string = string to search
\ifnum\pdfmatch{#1}{#2}=1
#3%
\else
#4%
\fi
}
\makeatletter
% I suggest this instead
\newcommand{\MyInString}[2]{%
\ifnum\pdfmatch{#1}{#2}=1
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
\makeatother
\begin{document}
\newcommand{\mystring}{abcdef}
\instring{a}{abcdef}{Yes}{No} \MyInString{a}{abcdef}{Yes}{No}% Yes
\instring{abc}{\mystring}{Yes}{No} \MyInString{abc}{\mystring}{Yes}{No}% Yes
\instring{acb}{\mystring}{Yes}{No} \MyInString{acb}{\mystring}{Yes}{No}% No
\medskip
%\instring{cde}{abcdef}{\textbf}{\textit}{some text}% ERROR: Too many }'s.
%\instring{zzz}{abcdef}{\textbf}{\textit}{other text}% ERROR: Too many }'s.
\MyInString{cde}{abcdef}{\textbf}{\textit}{some text} % bold
\MyInString{zzz}{abcdef}{\textbf}{\textit}{other text}% italics
\end{document}
注意:这两种方法在语义上非常不同:使用\regex_match:nnTF { \u{l__oleks_instring_test_tl} } ...
,一种执行精确的子字符串测试(其中“字符串”实际上是“标记列表”)。 中的标记\l__oleks_instring_test_tl
必须匹配确切地,\ifnum\pdfmatch{#1}{#2}=1
一个接一个地进行测试,以确保测试成功。与此相反,基于正则表达式匹配: #1
被解释为 POSIX 扩展正则表达式。这意味着,例如,\MyInString{(abc)+de}{zzabcabcabcdef}{yes}{no}
扩展为,yes
因为在 中zzabcabcabcdef
有abc
一次或多次,紧接着de
。
脚注
- 人们有时会用“完全可扩展”来形容同一件事。