从 xparse 选项设置 beamer 标签

从 xparse 选项设置 beamer 标签

我想编写一个自定义环境来处理默认投影仪幻灯片无法处理的一些布局。

我使用xparse\NewDocumentEnvironment实现这一点,但在传递标签时遇到问题。

如果我做这样的事情:

\documentclass{beamer}
\usepackage{xparse}

\NewDocumentEnvironment{slide}{ o +b }
{%
    \begin{frame}[environment=slide,\IfValueT{#1}{#1}]
        % not important implementation details

        #2
    \end{frame}
}{}

\begin{document}

\begin{slide}[label=slide-label-a]
    test
\end{slide}

\end{document}

我明白了,! Package keyval Error: label=slide-label-a undefined.因为整个选项被解析为一个键。

如果我做这样的事情,我只传递标签的值,它可以工作,但会生成很多空标签(对于我不使用标签的每个帧):

\documentclass{beamer}
\usepackage{xparse}

\NewDocumentEnvironment{slide}{ o +b }
{%
    \begin{frame}[environment=slide,label=\IfValueT{#1}{#1}]
        % not important implementation details

        #2
    \end{frame}
}{}

\begin{document}

\begin{slide}[slide-label-a]
    test
\end{slide}

\end{document}

最佳解决方案是第一种方案,这样我就可以将任意的 keyval 选项传递给 beamer。但如果这不起作用,第二种方案是采用一种好方法来设置单个选项。

答案1

您可以通过对可选参数使用空的默认值来避免此问题。这样,您就可以始终将其传递给框架:

\documentclass{beamer}


\NewDocumentEnvironment{slide}{ O{} +b }
{    \begin{frame}[environment=slide,#1]
        % not important implementation details

        #2
    \end{frame}
}{}

\begin{document}

\begin{slide}
    test \ref{slide-label-a}
\end{slide}


\begin{slide}[label=slide-label-a]
    test
\end{slide}


\end{document}

答案2

谢谢!作为我理解的后续内容:所以我假设检查选项的宏实际上导致了这里的问题?大概是因为它们导致了分组?

不,问题不在于分组,而在于扩展。key=value 解析器不会完全扩展每个列表元素并检查结果是否为空,而是解析列表元素,查看它是否为非空白(意味着它包含的内容不仅仅是空格或空),并将其作为元素进行进一步解析(按等号拆分,检查键是否已定义,将值提供给键的回调)。

因此,如果您使用,label=\IfValueT{#1}{#1}-callback 将使用label参数进行调用\IfValueT{#1}{#1},并且它对该回调执行的操作取决于该回调的代码(我们不关心这个解释)。

如果您改用\IfValueT{#1}{#1}并传入,label=<stuff>#1效果是列表元素非空,并考虑进一步解析。接下来发生的事情取决于所使用的 key=value 解析器。在 的情况下,keyval它会尝试在等号处拆分(它找不到任何等号,因为等号将隐藏在括号内),然后它会将其视为没有值的键,并尝试使用\IfValue{#1}{#1}内部的全部 来检查键的回调\csname...\endcsname(其他 key=value 解析器也使用它\detokenize来防止键名在此处进一步扩展),它将扩展为#1确实是一个值,但它已经在等号处拆分了,因此 的全部label=<stuff>将被检查为键名,它未定义,因此您遇到错误。


因此,技术解释已经解决了,您可以做什么(除了@samcarter_is_at_topanswers.xyz 的答案中提供的好代码),比如如果您只想将其label作为可选参数(而不是底层frame环境的任意选项):您需要

  1. 检查值并相应地进行分支,从而产生两个都将使用的代码路径\begin{frame}(可能会惹恼某些编辑器的语法高亮、语法检查等——但将是有效的代码),

  2. 或者你确保你的钥匙已经扩展 keyval解析它们。

其中第一个相当简单:

\NewDocumentEnvironment{slide}{ o +b }
{%
    \IfValueTF{#1}
      {\begin{frame}[environment=slide,label={#1}]}
      {\begin{frame}[environment=slide]}
        % not important implementation details

        #2
    \end{frame}
}{}

第二个乍一看可能有点令人困惑,但也不太复杂:

\NewDocumentEnvironment{slide}{ o +b }
{%
    \expanded{\noexpand\begin{frame}[environment=slide, \IfValueT{#1}{label={\unexpanded{#1}}}]}
        % not important implementation details

        #2
    \end{frame}
}{}

放在一起作为一个小示例文档:

\documentclass{beamer}
\usepackage{xparse}

\NewDocumentEnvironment{slide}{ o +b }
{%
    \expanded{\noexpand\begin{frame}[environment=slide, \IfValueT{#1}{label={\unexpanded{#1}}}]}
        % not important implementation details

        #2
    \end{frame}
}{}

\begin{document}

\begin{slide}[slide-label-a]
    test
\end{slide}

\end{document}

相关内容