我想检查命令的参数是否完全包含在一对花括号中,而无需使用任何额外的包。为此,我定义了两个宏,分别包含{
和}
,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}
在最后添加了一个额外的测试:
\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}
如果你想处理边缘情况并让其返回 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}