我尝试创建一个环境,如果 @showtrue 则显示其内容,否则不显示。当我尝试运行下面的 MWE 时,我收到一条错误消息:
! Extra }, or forgotten \endgroup.
\endToShowOrNotToShow ->\egroup
\ignorespacesafterend
l.24 \end{ToShowOrNotToShow}
我的 MWE:
\documentclass{article}
\makeatletter
\newif\if@show
\def\to@show#1{#1}
\def\not@to@show#1{}
\newenvironment{ToShowOrNotToShow}{%
\if@show%
\to@show\bgroup%
\hfill\\ {\bfseries Behold: }%
\else%
\not@to@show\bgroup%
\fi%
\ignorespaces%
}%
{%
\egroup%
\ignorespacesafterend%
}
\makeatother
\begin{document}
Normal text
\begin{ToShowOrNotToShow}
Text that is not necessarily shown.
\end{ToShowOrNotToShow}
Rest
\end{document}
我希望它能够扩展为:
Normal text
\not@to@show\bgroup\ignorespaces Text that is not necessarily shown.\egroup\ignorespacesafterend
据我所知,这应该和
Normal text
\not@to@show{Text that is not necessarily shown.}
我的推理的缺陷在哪里?
答案1
(回答“我的推理哪里有缺陷?”部分以便理解……)
调试
首先,让我们调试一下发生了什么。您的 MWE 定义了一个\newenvironment
,它根据所\newif
调用的执行一件事或另一件事\if@show
。在示例中,错误来自“false”路径,因此我们可以专门化 MWE 以仅考虑该部分(并且为了简单起见,还可以省略所有 catcode 和\ignorespaces
内容)。因此,您的 MWE 进一步最小化为:
\documentclass{article}
\def\notToShow#1{}
\newenvironment{ToShowOrNotToShow}{%
\notToShow\bgroup%
}%
{%
\egroup%
}
\begin{document}
Normal text
\begin{ToShowOrNotToShow}
Text that is not necessarily shown.
\end{ToShowOrNotToShow}
Rest
\end{document}
这与问题中的错误相同。更进一步,我们实际上可以做我们想象的事情\newenvironment
,即在使用环境时将其两个参数放在内部内容之前和之后。(这正是你在问题中所做的,你写了“我希望它扩展为...”。)所以:
\documentclass{article}
\def\notToShow#1{}
\begin{document}
Normal text
\notToShow\bgroup
Text that is not necessarily shown.
\egroup
Rest
\end{document}
现在错误稍有不同(如果我们感兴趣,我们可以稍后再回过头来研究为什么错误不同),但仍然是一个错误:
! Too many }'s.
l.7 \egroup
错误是什么可能已经很清楚了,但如果不清楚的话,您可以添加:
\tracingonline=1
\tracingmacros=1
(或者只是\tracingall
在丰富的输出中搜索)以在 TeX 输出中看到:
\notToShow #1->
#1<-\bgroup
这显示了发生了什么:宏\notToShow
获取标记\bgroup
作为其参数(#1
),然后(根据其定义)用空值替换该标记。稍后,当\egroup
遇到 时,它与任何先前的 都不对应\bgroup
(回想一下,它\notToShow
消耗了该标记),这解释了错误。
建议
在学习了一点 TeX 和编写宏等知识后,我发现一个似乎有用的技巧。这个技巧就是不是开始写出宏的定义,就像你用另一种语言编写函数一样。相反,回想一下,宏只是文本(或标记)替换(即它们通过扩展工作),然后写出扩展部分第一的。确保它能正常工作。仅然后开始编写宏,纯粹是为了避免每次都输入整个内容而使用的快捷方式。(毕竟,这是在 TeX 中包含该功能的最初目的。)
(实际上,我学到的更重要的教训是,如果可能的话,完全避免编写宏,而是在外部程序或 LuaTeX 回调中进行文本处理……)
在这种情况下,如果你从完全展开的
\toShow\bgroup
Text that is not necessarily shown.
\egroup
和
\notToShow\bgroup
Text that is not necessarily shown.
\egroup
(分别针对if
和else
案例),您可能已经遇到了else
案例中的问题。(您不会遇到if
案例中的问题,因为\def\toShow#1{#1}
应用于\bgroup
(非预期)只是放回\bgroup
,因此\egroup
并不意外。)
解决方案
对于宏扩展,不能使用 来调用宏\foo {bar}
,而不能使用\foo \bgroup bar\egroup
。TeX 不是这样工作的;它会查找{
具有该 catcode (1) 的任何字符的显式 或 。(事实上,如果它允许在这里使用\bgroup
和\egroup
,那么这将违背它们的目的,您可以直接在任何地方写{
或}
。)
但是\bgroup
和\egroup
适用于许多其他 TeX 原语,并且您可以在此处使用(如评论中所建议)的是\vbox
。
因此,要编写宏,首先验证您是否拥有所需的内容,然后完全展开:
\documentclass{article}
\begin{document}
% To show the text, just insert it into a vbox.
Normal text
\vbox\bgroup
Text that is not necessarily shown.
\egroup
Rest
% To hide the text, assign to box 0 and don't use it.
Normal text
\setbox0\vbox\bgroup
Text that is not necessarily shown.
\egroup
Rest
\end{document}
(实际上这看起来并不理想,但它或多或少与您在另一个答案中所说的“这可以完成我想要它做的事情”相符。\ignorespaces
为了简单起见,我删除了“Behold”等,但想象一下你在这里有适当的东西。)
接下来介绍一下\if
:
\documentclass{article}
\begin{document}
\newif\ifshow
\showtrue % Try with and without this
Normal text
\ifshow
\vbox\bgroup
\else
\setbox0\vbox\bgroup
\fi
Text that is not necessarily shown.
\egroup
Rest
\end{document}
最后,只有当您满意时,才定义您的环境:
\documentclass{article}
\newif\ifshow
\newenvironment{ToShowOrNotToShow}{%
\ifshow
\vbox\bgroup
\else
\setbox0\vbox\bgroup
\fi
}{%
\egroup
}
\begin{document}
\showtrue % Try with and without this
Normal text
\begin{ToShowOrNotToShow}
Text that is not necessarily shown.
\end{ToShowOrNotToShow}
Rest
\end{document}
您可以在此处添加\ignorespacesafterend
特定于 LaTeX 环境的其他内容。
答案2
根据马丁·沙雷尔我制作了下面的示例。我分步创建和打印框。此外,我不再需要\not@to@show
和\to@show
。
\documentclass{article}
\makeatletter
\newif\if@show
\newenvironment{ToShowOrNotToShow}{%
\if@show%
\vbox\bgroup%
\hfill\\ {\bfseries Behold: }%
\else%
\setbox0\vbox\bgroup%
\fi%
\ignorespaces%
}%
{%
\egroup%
\ignorespacesafterend%
}
\makeatother
\begin{document}
Normal text
\begin{ToShowOrNotToShow}
Text that is not necessarily shown.
\end{ToShowOrNotToShow}
Rest
\end{document}
这就是我想要它做的事情。