Skip to content

xkeyval.sty.ltxml's "pretend keyval loaded" also suppresses raw keyval.sty, so \KV@do is undefined (fancyvrb \DefineVerbatimEnvironment) #2864

Description

@dginev

xkeyval.sty.ltxml L23 pretends keyval is loaded:

AssignValue('keyval.sty_loaded' => 1, 'global');    # pretend keyval loaded too.

The intent is clear and right — keyval's plain \setkeys/\define@key must not
clobber xkeyval's extended ones. The side effect is that keyval.sty_loaded is
the flag both load paths gate on:

  • Package.pm:loadLTXML L2328-2330 (checks $trequest_loaded, i.e. keyval.sty_loaded), and
  • Package.pm:loadTeXDefinitions L2363 (checks $request_loaded, same key).

keyval.sty.ltxml gets keyval's internals from InputDefinitions('keyval', type => 'sty', noltxml => 1) — from the raw keyval.sty. After the pretense
that read never happens, and nothing else defines \KV@do (keyval.sty L31),
\KV@split, \KV@errx or \KV@@sp@def.

Raw packages that LaTeXML reads call those internals directly. fancyvrb.sty
L112-117:

\def\FV@UseKeyValues{%
  \ifx\FV@KeyValues\@empty\else
    \def\KV@prefix{KV@FV@}%
    \expandafter\KV@do\FV@KeyValues,\relax,%
    \def\FV@KeyValues{}%
  \fi}

MWE

\documentclass{article}
\usepackage{xkeyval}
\usepackage{fancyvrb}
\DefineVerbatimEnvironment{myBox}{Verbatim}{
}
\begin{document}
\begin{myBox}
text
\end{myBox}
\end{document}
$ latexml --dest=1.xml 1.tex
Error:undefined:\KV@do The token T_CS[\KV@do] is not defined. at 1.tex; line 7 col 1
Conversion complete: 1 error; 1 undefined macro[\KV@do]

(LaTeXML 0.8.8, TeX Live 2025. pdflatex is clean.)

Two things are needed to reproduce, both easy to miss:

  • the options argument must be non-empty — the MWE's {\n } tokenizes to
    one space, while {} makes \ifx\FV@KeyValues\@empty short-circuit before
    \KV@do is ever reached;
  • xkeyval must be loaded before fancyvrb — the other order loads keyval for
    real and everything works.

Why real LaTeX has no such problem

xkeyval.sty L39 is \ifx\XKeyValLoaded\endinput\else\input xkeyval \fi, and
the xkeyval bundle's own keyval.tex defines \KV@do at L52. So loading
xkeyval in real LaTeX genuinely provides keyval's internals — the "pretend"
is the only half that is missing.

Suggested fix

Have xkeyval.sty.ltxml really RequirePackage('keyval') at the top, in place
of the AssignValue, mirroring real xkeyval's order: keyval first, xkeyval's
extended \setkeys/\define@key after, so xkeyval still has the last word.
RequirePackage sets keyval.sty_loaded itself, so a later
\RequirePackage{keyval} stays the no-op the pretense was protecting.


Found via a downstream report (latexml-oxide#500),
where the Rust port reaches it on a plain \usepackage{standalone} +
\usepackage{fancyvrb} preamble: its standalone binding carries real
standalone.sty L107's \RequirePackage{xkeyval}, which standalone.sty.ltxml
omits — so on that MWE Perl is clean and only the port errors. We have applied
the fix above on our side.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions