\instring沃纳宏的改进

\instring沃纳宏的改进

我想测试一个字符串是否包含子字符串。我在这个问题。但是我遇到了使用 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因为在 中zzabcabcabcdefabc一次或多次,紧接着de


脚注

  1. 人们有时会用“完全可扩展”来形容同一件事。

相关内容