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.
xkeyval.sty.ltxmlL23 pretends keyval is loaded:The intent is clear and right — keyval's plain
\setkeys/\define@keymust notclobber xkeyval's extended ones. The side effect is that
keyval.sty_loadedisthe flag both load paths gate on:
Package.pm:loadLTXMLL2328-2330 (checks$trequest_loaded, i.e.keyval.sty_loaded), andPackage.pm:loadTeXDefinitionsL2363 (checks$request_loaded, same key).keyval.sty.ltxmlgets keyval's internals fromInputDefinitions('keyval', type => 'sty', noltxml => 1)— from the rawkeyval.sty. After the pretensethat read never happens, and nothing else defines
\KV@do(keyval.sty L31),\KV@split,\KV@errxor\KV@@sp@def.Raw packages that LaTeXML reads call those internals directly.
fancyvrb.styL112-117:
MWE
(LaTeXML 0.8.8, TeX Live 2025.
pdflatexis clean.)Two things are needed to reproduce, both easy to miss:
{\n }tokenizes toone space, while
{}makes\ifx\FV@KeyValues\@emptyshort-circuit before\KV@dois ever reached;real and everything works.
Why real LaTeX has no such problem
xkeyval.styL39 is\ifx\XKeyValLoaded\endinput\else\input xkeyval \fi, andthe xkeyval bundle's own
keyval.texdefines\KV@doat L52. So loadingxkeyval in real LaTeX genuinely provides keyval's internals — the "pretend"
is the only half that is missing.
Suggested fix
Have
xkeyval.sty.ltxmlreallyRequirePackage('keyval')at the top, in placeof the
AssignValue, mirroring real xkeyval's order: keyval first, xkeyval'sextended
\setkeys/\define@keyafter, so xkeyval still has the last word.RequirePackagesetskeyval.sty_loadeditself, 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: itsstandalonebinding carries realstandalone.styL107's\RequirePackage{xkeyval}, whichstandalone.sty.ltxmlomits — so on that MWE Perl is clean and only the port errors. We have applied
the fix above on our side.