检查组作为参数(可扩展)

检查组作为参数(可扩展)

我想检查命令的参数是否完全包含在一对花括号中,而无需使用任何额外的包。为此,我定义了两个宏,分别包含{},catcode 为 12。然后我使用将输入转换为字符串\detokenize,并比较我找到的第一个和最后一个标记。

宏应该忽略组周围的前导和尾随空格或将其视为假(忽略具有更高的优先级 - 两个版本一个忽略一个不忽略是最好的)。

因此,对于忽略版本,应该输出以下内容:

\MyIfGroup{{}}{true}{false}   -> true
\MyIfGroup{ {}}{true}{false}  -> true
\MyIfGroup{{} }{true}{false}  -> true
\MyIfGroup{ {} }{true}{false} -> true
\MyIfGroup{ }{true}{false}    -> false
\MyIfGroup{a}{true}{false}    -> false
\MyIfGroup{{}a}{true}{false}  -> false

不忽略版本应该只对上述第一种情况返回 true。括号内的任何内容(甚至嵌套括号)都不会产生影响(除非括号不匹配)。这种情况\MyIfGroup{{}{}}(我自己认为)属于极端情况,无需正确检测——不过,如果正确检测就更好了。

以下代码是一个忽略版本,它检测除{}{}(没关系)之外的所有内容,如果括号后有空格,则在这种情况下抛出“失控参数”错误。

\documentclass[]{article}

\makeatletter
\newcommand\MyIfEmptyTF[1]
  {%
    \if\relax\detokenize{#1}\relax
      \expandafter\@firstoftwo
    \else
      \expandafter\@secondoftwo
    \fi
  }
\newcommand\MyIfGroupTF[1]
  {%
    \MyIfEmptyTF{#1}{\@secondoftwo}
      {%
        \expandafter\MyIfGroup@preopen\expandafter{\detokenize{#1}}%
      }%
  }
\newcommand\MyIfGroup@preopen[1]
  {%
    \MyIfDetSpaceTF{#1}
      {\@secondoftwo}
      {\MyIfGroup@open#1\end@MyIfGroup}%
  }
\long\def\MyIfGroup@open#1#2\end@MyIfGroup%
  {%
    \if\Mylbraceother#1%
      \expandafter\@firstoftwo
    \else
      \expandafter\@secondoftwo
    \fi
    {\MyIfGroup@close#2\end@MyIfGroup}
    {\@secondoftwo}%
  }
\long\def\MyIfGroup@close#1#2\end@MyIfGroup
  {%
    \MyIfEmptyTF{#2}
      {%
        \MyIfDetSpaceTF{#1}
          {\@secondoftwo}
          {%
            \if\Myrbraceother#1%
              \expandafter\@firstoftwo
            \else
              \expandafter\@secondoftwo
            \fi
          }%
      }
      {\MyIfGroup@close#2\end@MyIfGroup}
  }
\newcommand\MyIfDetSpaceTF[1]
  {%
    \if#1\detokenize{ }%
      \expandafter\@firstoftwo
    \else
      \expandafter\@secondoftwo
    \fi
  }
\bgroup
\catcode`\{=12
\catcode`\}=12
\catcode`\(=1
\catcode`\)=2
\gdef\Mylbraceother({)
\gdef\Myrbraceother(})
\egroup
\makeatother

\begin{document}
Works: \MyIfGroupTF{{i}}{true}{false}.

Works: \MyIfGroupTF{ {}}{true}{false}.

Works: \MyIfGroupTF{ }{true}{false}.

Works: \MyIfGroupTF{ a }{true}{false}.

Works: \MyIfGroupTF{a}{true}{false}.

Wrong: \MyIfGroupTF{{}{}}{true}{false}.

%Doesn't compile (fails in @close): \MyIfGroupTF{{} }{true}{false}.
\end{document}

答案1

我不明白你到底想要什么,但也许下面的代码可以帮助你。(编辑:我添加了完全可扩展的新版本的代码)

\def\MyIfGroup#1{\expandafter\MyIfGroupA\romannumeral-`\.#1.{}\end}
\def\MyIfGroupA#1#{\ifx\end#1\end\expandafter\MyIfGroupB
   \else\expandafter\MyIfGroupE\fi}
\def\MyIfGroupB#1#2#3\end{\ifx\end#3\end\expandafter\MyIfGroupT
   \else\expandafter\MyIfGroupF\fi}
\def\MyIfGroupE#1\end{\MyIfGroupF}
\def\MyIfGroupF#1#2{#2}
\def\MyIfGroupT#1#2{#1}

\tt
\MyIfGroup{{}}{true}{false}   -> true

\MyIfGroup{ {}}{true}{false}  -> true

\MyIfGroup{{} }{true}{false}  -> true

\MyIfGroup{ {} }{true}{false} -> true

\MyIfGroup{ }{true}{false}    -> false

\MyIfGroup{a}{true}{false}    -> false

\MyIfGroup{{}a}{true}{false}  -> false

\MyIfGroup{ {a} }{true}{false} -> true

\MyIfGroup{ {a}{} }{true}{false} -> false

\MyIfGroup{{}{}}{true}{false} -> false    
\bye

编辑:工作原理:潜在的第一个空格由 删除,\romannumeral-``\.其余部分由\MyIfGroupA宏读取,该宏将参数读取#1到 的第一次出现{(没有它)。如果没有这样的括号,则读取后跟点的原始参数,因为我们添加到了.{}\end输入流中。这意味着不为空,因此#1进行处理(此宏从输入流中删除其余部分并转到错误分支。如果 存在但在它之前有东西,那么结果是一样的。否则为空。然后处理。它读取(第一个)括号参数,然后读取一个标记 并将输入流的其余部分读取到中。 的读取会忽略潜在的空格,如果没有其他内容,则会读取并且 中什么都没有,因为读取 时外部括号会被删除。这正是我们需要转到真分支的情况。否则第一个括号参数之后还有其他内容,我们就会转到假分支。\MyIfGroupA\MyIfGroupE{#1\MyIfGroupA\MyIfGroupB#1#2\end#3#2.#2#3#3

答案2

这不会忽略空间,我认为使用 strcmp 更容易,我{a}在最后添加了一个额外的测试:

enter image description here

\documentclass{article}

\makeatletter

\def\zz#1\zz{#1}
\def\MyIfGroup#1{%
\ifnum\expandafter\pdfstrcmp\expandafter{\zz#1\zz}{#1}=0
\expandafter\@secondoftwo\else\expandafter\@firstoftwo
\fi}
\makeatother
\begin{document}


\MyIfGroup{{}}{true}{false}

\MyIfGroup{ {}}{true}{false}

\MyIfGroup{{} }{true}{false}

\MyIfGroup{ {} }{true}{false}

\MyIfGroup{ }{true}{false}

\MyIfGroup{a}{true}{false}

\MyIfGroup{{}a}{true}{false}

\MyIfGroup{{a}}{true}{false}

\end{document}

答案3

根据中的可扩展循环代码expl3,但根据要求不使用任何包:

\documentclass{article}
\makeatletter
\newcommand\MyIfGroupTF[1]{%
  \MyIfGroup@loop@i#1\q@mark\q@stop
}
\long\def\MyIfGroup@loop@i#1\q@stop{%
  \MyIfGroup@if@brace{#1}%
    {\MyIfGroup@brace@i}
    {%
      \MyIfGroup@if@normal{#1}
        {\MyIfGroup@end}
        {\MyIfGroup@space@i}%
    }%
      #1\q@stop
}
\expandafter\long\expandafter\def\expandafter\MyIfGroup@space@i\space#1\q@stop{%
  \MyIfGroup@loop@i#1\q@stop
}
\newcommand\MyIfGroup@brace@i[1]{\MyIfGroup@loop@ii}
\long\def\MyIfGroup@loop@ii#1\q@stop{%
  \MyIfGroup@if@brace{#1}%
    {\MyIfGroup@end}
    {%
      \MyIfGroup@if@normal{#1}
        {\MyIfGroup@normal@ii}
        {\MyIfGroup@space@ii}%
    }%
      #1\q@stop
}
\newcommand\MyIfGroup@normal@ii[1]{%
  \ifx#1\q@mark
    \expandafter\MyIfGroup@normal@cleanup
  \else
    \expandafter\MyIfGroup@end
  \fi
}
\long\def\MyIfGroup@normal@cleanup#1\q@stop{\@firstoftwo}
\expandafter\long\expandafter\def\expandafter\MyIfGroup@space@ii\space#1\q@stop{%
  \MyIfGroup@loop@ii#1\q@stop
}

\long\def\MyIfGroup@end#1\q@stop{\@secondoftwo}


\newcommand\MyIfGroup@if@brace[1]{%
  \ifcat\expandafter\@gobble\expandafter{\expandafter{\string#1?}}**%
    \expandafter\@secondoftwo
  \else
    \expandafter\@firstoftwo
  \fi
}
\newcommand\MyIfGroup@if@normal[1]{%
  \ifcat\iffalse{\fi\MyIfGroup@if@normal@aux?#1 }%
    \expandafter\@gobble\expandafter{\expandafter{\string#1?}}**%
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}
\long\def\MyIfGroup@if@normal@aux#1 {%
 \if\relax\detokenize\expandafter{\@gobble#1}\relax
   ^%
 \fi
 \expandafter\@gobble\expandafter{\iffalse}\fi
}
\newcommand*\q@mark{\q@mark}
\newcommand*\q@stop{\q@stop}
\makeatother
\begin{document}

Works: \MyIfGroupTF{{i}}{true}{false}.

Works: \MyIfGroupTF{ {}}{true}{false}.

Works: \MyIfGroupTF{ }{true}{false}.

Works: \MyIfGroupTF{ a }{true}{false}.

Works: \MyIfGroupTF{a}{true}{false}.

Works: \MyIfGroupTF{{}{}}{true}{false}.

Works: \MyIfGroupTF{{} }{true}{false}.
\end{document}

基本思想是使用分隔参数来让我们测试输入的头部是否为 space/group/'normal' 之一,然后根据需要进行分支。(有关l3tl.dtx详细信息,请参阅代码中的“逐个标记更改”。)有两个循环,第一个循环用于检查我们是否有括号组,第二个循环用于确保它是唯一的括号组确保它之外没有其他标记。可以扩展代码以扩展任何宏,尽管这会让生活变得更“有趣”。


也可以使用以下版本大卫的回答结合空间修剪方法(见https://tex.stackexchange.com/a/69771):

\documentclass{article}
\makeatletter
\long\def\trim@spaces#1{%
  \@@trim@spaces{\q@mark#1}%
}
\def\@tempa#1{%
  \long\def\@@trim@spaces##1{%
    \@@trim@spaces@i##1\q@nil\q@mark#1{}\q@mark
      \@@trim@spaces@ii
      \@@trim@spaces@iii
      #1\q@nil
      \@@trim@spaces@iv
      \q@stop
  }%
  \long\def\@@trim@spaces@i##1\q@mark#1##2\q@mark##3{%
    ##3%
    \@@trim@spaces@i
    \q@mark
    ##2%
    \q@mark#1{##1}%
  }%
  \long\def\@@trim@spaces@ii\@@trim@spaces@i\q@mark\q@mark##1{%
    \@@trim@spaces@iii
    ##1%
  }%
  \long\def\@@trim@spaces@iii##1#1\q@nil##2{%
    ##2%
    ##1\q@nil
    \@@trim@spaces@iii
  }%
  \long\def\@@trim@spaces@iv##1\q@nil##2\q@stop{%
    \unexpanded\expandafter{\@gobble##1}%
  }%  
}
\@tempa{ }
\def\zz#1\relax{#1}
\newcommand\MyIfGroup[1]{%
\ifnum\expandafter\pdfstrcmp\expandafter
  {\expandafter\zz\romannumeral-`0\trim@spaces{#1}\relax}%
  {\trim@spaces{#1}}%
    =0 %
\expandafter\@secondoftwo\else\expandafter\@firstoftwo
\fi}
\begin{document}

\MyIfGroup{{}}{true}{false}

\MyIfGroup{ {}}{true}{false}

\MyIfGroup{{} }{true}{false}

\MyIfGroup{ {} }{true}{false}

\MyIfGroup{ }{true}{false}

\MyIfGroup{a}{true}{false}

\MyIfGroup{{}a}{true}{false}

\MyIfGroup{{a}}{true}{false}

\end{document}

答案4

这将忽略空格(但不处理边缘情况):

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\MyIfGroup}{mmm}
 {
  \regex_match:nnTF { \A\s*\cB\{.*\cE\}\s*\Z } { #1 } { #2 } { #3 }
 }
\ExplSyntaxOff

\begin{document}

\MyIfGroup{{}}{true}{false}   $\to$ true

\MyIfGroup{ {}}{true}{false}  $\to$ true

\MyIfGroup{{} }{true}{false}  $\to$ true

\MyIfGroup{ {} }{true}{false} $\to$ true

\MyIfGroup{ }{true}{false}    $\to$ false

\MyIfGroup{a}{true}{false}    $\to$ false

\MyIfGroup{{}a}{true}{false}  $\to$ false

\end{document}

enter image description here

如果你想处理边缘情况并让其返回 false:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\MyIfGroup}{mmm}
 {
  \regex_match:nnTF { \A\s*\cB\{.*\cE\}\s*\Z } { #1 }
   {
    \int_compare:nNnTF { \tl_count:n { #1 } } > { 1 } { #3 } { #2 }
   }
   { #3 }
 }
\ExplSyntaxOff

\begin{document}

\MyIfGroup{{}}{true}{false}   $\to$ true

\MyIfGroup{ {}}{true}{false}  $\to$ true

\MyIfGroup{{} }{true}{false}  $\to$ true

\MyIfGroup{ {} }{true}{false} $\to$ true

\MyIfGroup{ }{true}{false}    $\to$ false

\MyIfGroup{a}{true}{false}    $\to$ false

\MyIfGroup{{}a}{true}{false}  $\to$ false

\MyIfGroup{{}{}}{true}{false} $\to$ false

\end{document}

enter image description here

相关内容