[Tex/LaTex] Dynamically hide LaTeX beamer frame based on tags

beamer

I guess I'm in a configuration that will ring the bell to many: in the context of a project that run for many years, I developed a very large Beamer presentation featuring a lot of slides. The LaTeX sources are split in many files and as I have to often redo the presentation of the project, I include (or nor) these files in the main document depending on the time / audience.

Now I would like to somehow automate the generation of the presentation by controlling the frame I wish to include.
It seems to be possible statically with the <handout:0|beamer:0> parameters upon frame definition:

\begin{frame}
Hello.
\end{frame}

% this frame won't be included
\begin{frame}<handout:0|beamer:0>
  Hidden
\end{frame}

I would like to render the approach more generic. Thus i was thinking about the possibility to

  • associate a [set of] tag upon frame definition
  • decide later (in some way) about the set of tags to use when compiling the presentation such that only the frame having this tag are considered (and the other are thus hidden).

Does anybody knows a way to do that?

Best Answer

This is a really nice question!

This is a start of a solution highly inspired by Werner's comment and with few limitations. They are due to the fact that the solution is on top of Beamer. For such a reason I mark it community wiki and every body can improve it (and there's lot of room).

It provides a simple user interface with the aim of

  • creating tags for the differentiating version(s);
  • deciding which version(s) output.

The first task is provided by the keys

  1. create new version
  2. create multiple versions

and the names are quite clear. To decide which frames output, exploit:

  1. compile version
  2. compile frames to select a subset of frames within one version
  3. compile generic frames to select frames from different versions.

As said, the mechanism is pretty much described in Werner's comment: creating a new version simply means that a new counter (or more) is instantiated and updated each time a frame is tagged that way. Compiling versions is traduced behind the scenes with the creation of a list that is passed to \includeonlyframes. So far so good.

Let us see now limits and week points of the approach. Tagging frames in the aforementioned way without going really underneath Beamer, requires a bit of trickery, namely a new environment. For the purpose, a sebframe environment has been defined which accepts an optional argument with the following syntax

<tag>-<list of beamer options>

The character - is used to split the tag from other Beamer options; incidentally, this misses up with fragile.

The mwe. It is commented and take it as a proof of concept: a few things can be improved as comments point out.

\documentclass{beamer}
\usepackage{lmodern} % https://tex.stackexchange.com/a/58088/13304
\usepackage{mwe} % for dummy image

\newcommand{\enlarge}{\centering\scalebox{13}}% Just for this example

% = = = = = = = = = = = = = = = = = = = = = = = =
% core of the solution

\usepackage{pgffor,pgfkeys,xstring}

\newcommand{\deflistframes}[1]{
\gdef\listframes{}% global list
\foreach \x[count=\xi] in {#1}{\global\let\maxitems\xi}% count the max number of items
\foreach \x[count=\xi] in {#1}{
 \ifnum\xi=\maxitems
   \xdef\listframes{\listframes \vers\x}
 \else
   \xdef\listframes{\listframes \vers\x,}
 \fi
}
}

\newcommand{\defcustomlistframes}[1]{
\gdef\listframes{}% global list
\foreach \x[count=\xi] in {#1}{\global\let\maxitems\xi}% count the max number of items
\foreach \x[count=\xi] in {#1}{
 \ifnum\xi=\maxitems
   \xdef\listframes{\listframes \x}
 \else
   \xdef\listframes{\listframes \x,}
 \fi
}
}

\makeatletter
% https://tex.stackexchange.com/a/74890/13304
\newcommand{\defversion}[1]{\@namedef{vers#1}{}}
\makeatother

% test for multiple versions compilation
\newif\ifmultipleversion
\multipleversionfalse


\pgfkeys{/seb-beamer/.cd,
  create new version/.code={\gdef\vers{#1}\newcounter{#1}},
  create multiple versions/.code={%
    \gdef\listofversions{}
    \foreach \v in {#1}{%
      \defversion{\v}
      \newcounter{\v}
      \xdef\listofversions{\listofversions \v,}
    }%
    \multipleversiontrue
  },
  compile version/.code={% version-based: need to store in .aux a
    % "total frame" counter per version (incremented in custom enviroment)
    % and use it as final value (hard-coded here for simplicity)
    \deflistframes{1,...,1000}
    % list of frames in output
    \expandafter\includeonlyframes\expandafter{\listframes}%
  }, 
  compile frames/.code={% this should be version-based -> second argument (TBA)
    \deflistframes{#1}
    % for the list
    \expandafter\includeonlyframes\expandafter{\listframes}%
  }, 
  compile generic frames/.code={% generic for multi version compilations
   \defcustomlistframes{#1}
   % list of frames in output
   \expandafter\includeonlyframes\expandafter{\listframes}%
  }, 
}

\makeatletter

% see: https://tex.stackexchange.com/a/132411/13304
\newenvironment{sebframe}[1][]{
% cut the string to separate version from other options
\StrCut{#1}{-}\compvers\opt
\edef\seboptions{\opt}
\def\mylabel{}
\ifmultipleversion
  \@ifundefined{c@\compvers}{}{% test if the counter is undefined
    \stepcounter{\compvers}
    % increment here the counter total frames per version
    \edef\mylabel{label=\compvers\the\value{\compvers}}
    }
\else
 \IfStrEq{\vers}{\compvers}{% true
  \stepcounter{\compvers}
  % increment here the counter total frames per version
  \edef\mylabel{label=\compvers\the\value{\compvers}}
 }{% false
  \edef\mylabel{}
 }
\fi
\begingroup\edef\x{\endgroup\noexpand\begin{frame}[\mylabel,\seboptions]}\x}{\end{frame}}

\makeatother

\newcommand{\SebCompileOptions}[1]{%
 \pgfkeys{/seb-beamer/.cd,#1}
}

% = = = = = = = = = = = = = = = = = = = = = = = =
% user interface

%\SebCompileOptions{create new version=A, compile version=A}
%\SebCompileOptions{%
% create multiple versions={A,B}, 
% compile generic frames={A1,A3,B1,B2,C3}
%}
\SebCompileOptions{create new version=C, compile frames={1,3}}


\begin{document}

\begin{sebframe}[A-t,allowframebreaks]
  \frametitle{Version A}
  \enlarge{A1}
\end{sebframe}

\begin{sebframe}[B]
  \frametitle{Version B}
  \enlarge{B1}
\end{sebframe}

\begin{sebframe}[C]
  \frametitle{Version C}
  \enlarge{C1}
\end{sebframe}

\begin{sebframe}[A]
  \frametitle{Version A}
  \enlarge{A2}
\end{sebframe}

\begin{sebframe}[B]
  \frametitle{Version B}
  \enlarge{B2}
\end{sebframe}

\begin{sebframe}[C]
  \frametitle{Version C}
  \enlarge{C2}
\end{sebframe}

\begin{frame}
  \frametitle{Version B or C}
  \enlarge{BC}
\end{frame}

\begin{sebframe}[A-t,b,containsverbatim]% just to point out that passing multiple beamer options works (unless fragile)
  \frametitle{Version A}
  \enlarge{A3}
\end{sebframe}

\begin{sebframe}[C]
  \frametitle{Version C}
  \enlarge{C3}
  \vskip1ex
  \includegraphics<2>[scale=0.5]{example-image}
\end{sebframe}

\begin{sebframe}[A]
  \frametitle{Version A}
  \enlarge{A4}
\end{sebframe}

\begin{frame}[fragile,label=A5]% add manually a label: displayed only in A version though
  \frametitle{Version A \& B \& C}
  \enlarge{ABC}
\end{frame}


\end{document}

As we selected:

\SebCompileOptions{create new version=A, compile version=A}

the output is:

enter image description here

By selecting

\SebCompileOptions{create new version=C, compile frames={1,3}}

enter image description here

Notice that overleyed-aware commands still work and frame C2 is not displayed. Let us try

\SebCompileOptions{%
 create multiple versions={A,B}, 
 compile generic frames={A1,A3,B1,B2,C3}
}

which produces:

enter image description here

As expected, since version C is not defined, the frame C3 is not displayed. The order in which one provides the list does not affect the order of appaearance in the PDF: only page numbers counts for that.

Related Question