# pgfkeys with multiple values for a class option key has incompatibilities with some packages

Like discussed on the chat TexAndFriends, I'm currently adding the ability to define languages in my own class file.

Here is my minimal (non working) example. My class file with the packages I'm using. I have not added the customizations here, as these make several hundreds if not thousands lines.

testtemplate.cls:

% !TEX encoding = UTF-8
\NeedsTeXFormat{LaTeX2e}[1994/06/01]
% Date mandatory otherwise error message:
%! Missing number, treated as zero.
\ProvidesClass{testtemplate}

\RequirePackage{iftex}

\RequirePackage{tikz}
\RequirePackage{pgfkeys}
\RequirePackage{pgfopts}

\pgfkeys{
/wgetdoc/.cd,
languages/.code=\typeout{The languages you specified are: #1},
someOtherKey/.store in=\@wgetdoc@options@someotherkey
}

\ProcessPgfOptions{/wgetdoc}

\@ifundefined{@wgetdoc@options@someotherkey}{
\typeout{someotherkey found}
}{
\typeout{someotherkey value:\@wgetdoc@options@someotherkey}
}

%\RequirePackage[english]{babel}
\RequirePackage{etoolbox}
\RequirePackage{iflang}
\RequirePackage{translations}
%\RequirePackage[no-math]{fontspec}
%\RequirePackage{xeCJK}
%\RequirePackage{csquotes}
\RequirePackage{setspace}
\RequirePackage{parskip}
\RequirePackage{titlesec}
\RequirePackage{enumitem}
%\RequirePackage{tcolorbox}
%\RequirePackage{caption}
\RequirePackage{epigraph}
\RequirePackage{longtable}
%\RequirePackage{tabu}
\RequirePackage{booktabs}
\RequirePackage{graphicx}
\RequirePackage{float}
\RequirePackage{datetime}
\RequirePackage{fancyhdr}
\RequirePackage[backend=biber,sorting=none,alldates=short]{biblatex}
\RequirePackage{hyperref}
\RequirePackage{pdfpages}
\RequirePackage{amssymb}
\RequirePackage{MnSymbol}
\RequirePackage{wasysym}
%\RequirePackage{metalogo}


someFile.tex:

\documentclass[someOtherKey=hello world,languages={dutch,french}]{testtemplate}

\begin{document}
Hello world
\end{document}


As soon you add the babel \RequirePackage line, XeLaTeX refuses to compile any more and complains with the following lines (actually a snippet):

[...]
(/usr/local/texlive/2015/texmf-dist/tex/latex/pgfopts/pgfopts.sty)
The languages you specified are: dutch,french
someotherkey value:helloworld
(/usr/local/texlive/2015/texmf-dist/tex/generic/babel/babel.sty

! LaTeX Error: Missing \begin{document}.

See the LaTeX manual or LaTeX Companion for explanation.
Type  H <return>  for immediate help.
...

l.176 \ProcessOptions*


I have read in the pgfkeys documentation, section 42, page 518, TIKZ figures using pgfkeys could have issues with babel as the latter is modifying heavily catcodes (especially true for French, where, for example, spaces must be added after colons ":"). In such a use case, the documentation precises to use either \usepgflibrary{babel} or \usetikzlibrary{babel} respectively if using only pgfkeys or also KITZ. But in my use case specifying one of these commands either before or after the babel package call is not solving anything :-(

The same problem applies when I try to uncomment fontspec, xeCJK, csquotes, tcolorbox, caption, tabu, and metalogo.

Your help is really much appreciated here. I used keyval, but the latter was annoying as I needed to reinvent the wheel, xkeyval does not allow key={value1,value2} without crashing at compilation, and pgfkeys seems to be the perfect solution here, since this could help me in resolving that bug.

probably you can start the class so that it looks like

% !TEX encoding = UTF-8
\NeedsTeXFormat{LaTeX2e}[1994/06/01]
% Date mandatory otherwise error message:
%! Missing number, treated as zero.
\ProvidesClass{testtemplate}

\let\oldin@\in@
\def\zzz#1#2#{\oldin@{#2}{#1}\zzzb}
\def\in@#1#2{\zzz{#2}#1{}\@nil}
\def\zzzb#1\@nil{}



(although the comment on line 3 isn't really true)

this just removes brace groups from options as seen by the standard option parser but you can use any key value parser to handle the list yourself.

• Ok if I understand correctly, using the pgfkeys command \ProcessPgfOptions{/wgetdoc} actually calls the LaTeX command \ProcessOptions* which triggers some code in each concerned packages. In babel, like you said in the chat, the code handling the ProcessOptions "event" is declared as it \ifx\bbl@opt@main\@nnil \edef\bbl@tempa{\@classoptionslist,\bbl@language@opts} \let\bbl@tempc\@empty \bbl@for\bbl@tempb\bbl@tempa{% \@expandtwoargs\in@{,\bbl@tempb,}{,\bbl@loaded,}% As we can see, the code handling is surrounded by in@, your hack is actually removing it.
– wget
Aug 3, 2016 at 21:31
• @wget not sure I understood your comment, the patch doesn't remove all the code you quote it simply makes the \in@ test ignore any tokens in (or after) a {} group in an option value. Aug 3, 2016 at 21:35
• I didn't say your patch removed the aforementioned code, just the \in@ tests are being somewhat replaced. And the code I mentioned is the one from babel used to handle the package options.
– wget
Aug 3, 2016 at 21:39
• For posterity: "\in@ is doing the same tests it was (so tests for classic options such as a4paper or german will work, it just removes brace groups from any options before testing. So languages={a,b,c} is tested as languages=. The test still happens and the true or false branch is executed" This code is just removing the "braces groups from any options", changing the way LaTeX tests for class and package options. "In that way, the {} group is ignored while testing. The actual option list isn't changed and the option itself still has the group."
– wget
Aug 4, 2016 at 16:35

An option with values in braces confuses the parser. Perhaps there are better ways, but something like

\RequirePackage{etoolbox}

\let\@wgetdoc@languages\@empty

\pgfkeys{
/wgetdoc/.cd,
language/.code=\appto\@wgetdoc@languages{#1,},
mainlanguage/.code=\appto\@wgetdoc@languages{main=#1,},
someOtherKey/.store in=\@wgetdoc@options@someotherkey,
}

\ProcessPgfOptions{/wgetdoc}

\ifdefempty{\@wgetdoc@languages}{}{%
\RequirePackage[\@wgetdoc@languages]{babel}%
}


seems to work; the options should be in one of the following way:

## With main language

Here the main language would be Dutch

\documentclass[
mainlanguage=dutch,
language=french,
]{testtemplate}


## Without main language

Here the main language would be French (the last specified)

\documentclass[
language=dutch,
language=french,
]{testtemplate}


## No language

Here the babel package would not be loaded.

\documentclass{testtemplate}