# -*- mode: Perl -*-
# /=====================================================================\ #
# |  LaTeX                                                              | #
# | Implementation for LaTeXML                                          | #
# |=====================================================================| #
# | Part of LaTeXML:                                                    | #
# |  Public domain software, produced as part of work done by the       | #
# |  United States Government & not subject to copyright in the US.     | #
# |---------------------------------------------------------------------| #
# | Bruce Miller <bruce.miller@nist.gov>                        #_#     | #
# | http://dlmf.nist.gov/LaTeXML/                              (o o)    | #
# \=========================================================ooo==U==ooo=/ #
package LaTeXML::Package::Pool;
use strict;
use warnings;
use LaTeXML::Package;
use LaTeXML::Util::Pathname;

#**********************************************************************
# Organized following
#  "LaTeX: A Document Preparation System"
#   by Leslie Lamport
#   2nd edition
# Addison Wesley, 1994
# Appendix C. Reference Manual
#**********************************************************************
# NOTE: This will be loaded after TeX.pool.ltxml, so it inherits.
#**********************************************************************

LoadPool('TeX');

# Apparently LaTeX does NOT define \magnification,
# and babel uses that to determine whether we're runing LaTeX!!!
Let('\magnification', '\@undefined');
#**********************************************************************
# Basic \documentclass & \documentstyle

#AssignValue('2.09_COMPATIBILITY'=>0);
DefConditionalI('\if@compatibility', undef, sub { LookupValue('2.09_COMPATIBILITY'); });
DefMacro('\@compatibilitytrue',  '');
DefMacro('\@compatibilityfalse', '');

Let('\@currentlabel', '\@empty');

# Let's try just starting with this set (since we've loaded LaTeX)
AssignValue(inPreamble => 1);    # \begin{document} will clear this.

DefConstructor('\documentclass OptionalSemiverbatim SkipSpaces Semiverbatim []',
  "<?latexml class='#2' ?#1(options='#1')?>",
  afterDigest => sub {
    my ($stomach, $whatsit) = @_;
    my $options = $whatsit->getArg(1);
    LoadClass(ToString($whatsit->getArg(2)),
      options => [($options ? split(/,\s*/, ToString($options)) : ())],
      after => Tokens(T_CS('\AtBeginDocument'), T_CS('\warn@unusedclassoptions')));
    return; });

AssignValue('@unusedoptionlist', []);
DefPrimitiveI('\warn@unusedclassoptions', undef, sub {
    if (my @unused = @{ LookupValue('@unusedoptionlist') }) {
      Info('unexpected', 'options', $_[0], "Unused global options: " . join(',', @unused));
      AssignValue('@unusedoptionlist', []); }
    return; });

DefConstructor('\documentstyle OptionalSemiverbatim SkipSpaces Semiverbatim []',
  "<?latexml class='#2' ?#1(options='#1') oldstyle='true'?>",
  beforeDigest => sub {
    Info('unexpected', '\documentstyle', $_[0], "Entering LaTeX 2.09 Compatibility mode");
    AssignValue('2.09_COMPATIBILITY' => 1, 'global'); },
  afterDigest => sub {
    my ($stomach, $whatsit) = @_;
    my $class   = ToString($whatsit->getArg(2));
    my $options = $whatsit->getArg(1);
    $options = [($options ? split(/,\s*/, ToString($options)) : ())];
    # Watch out; In principle, compatibility mode wants a .sty, not a .cls!!!
    # But, we'd prefer .cls, since we'll have better bindings.
    # And in fact, nobody's likely to write a binding for a .sty that wants to be a class anyway.
    # So, we'll just try for a .cls, punting to OmniBus if needed.
    # If we start wanting to read style files by default, we'll still need to handle this
    # specially, since class (or sty files pretending to be) cover so much more.
    LoadClass($class, options => $options, after => Tokens(T_CS('\compat@loadpackages')));
    return; });

DefPrimitiveI('\compat@loadpackages', undef, sub {
    my $name       = ToString(Digest(T_CS('\@currname')));
    my $type       = ToString(Digest(T_CS('\@currext')));
    my $hadmissing = 0;
    foreach my $option (@{ LookupValue('@unusedoptionlist') }) {
      if (FindFile($option, type => 'sty')) {
        RequirePackage($option); }
      else {
        $hadmissing = 1;
        Info('unexpected', $option, $_[0], "Unexpected option '$option' passed to $name.$type"); } }
    # Often, in compatibility mode, the options are used to load what are effectively
    # document classes for specific journals, etc that introduce a bunch of new frontmatter!
    # To try to recover from this, we'll go ahead & load the OmniBus class.
    if ($hadmissing && !LookupValue('OmniBus.cls_loaded')) {
      Info('note', 'OmniBus', $_[0], "Loading OmniBus class to attempt to cover missing options");
      LoadClass('OmniBus'); }
    AssignValue('@unusedoptionlist', []); });

sub onlyPreamble {
  my ($cs) = @_;
  Error('unexpected', $cs, $STATE->getStomach,
    "The current command '" . ToString($cs) . "' can only appear in the preamble")
    unless LookupValue("inPreamble");
  return; }
#**********************************************************************
# C.1.  Commands and Environments.
#**********************************************************************

#======================================================================
# C.1.1 Command Names and Arguments
#======================================================================
# Nothing...

#======================================================================
# C.1.2 Environments
#======================================================================

# In LaTeX, \newenvironment{env} defines \env and \endenv.
# \begin{env} & \end{env} open/close a group, and invoke these.
# In fact, the \env & \endenv don't have to have been created by
# \newenvironment; And in fact \endenv doesn't even have to be defined!
# [it is created by \csname, and equiv to \relax if no previous defn]

# We need to respect these usages here, but we also want to be able
# to define environment constructors that `capture' the body so that
# it can be processed specially, if needed.  These are the magic
# '\begin{env}', '\end{env}' control sequences created by DefEnvironment.

AssignValue(current_environment => '', 'global');
DefMacro('\@currenvir', '');
DefPrimitive('\lx@setcurrenvir{}', sub {
    DefMacro('\@currenvir', $_[1]);
    AssignValue(current_environment => ToString($_[1])); });
Let('\@currenvline', '\@empty');

DefMacro('\begin{}', sub {
    my ($gullet, $env) = @_;
    my $name = $env && ToString($env);
    if (IsDefined("\\begin{$name}")) {
      T_CS("\\begin{$name}"); }    # Magic cs!
    else {
      my $token = T_CS("\\$name");
      if (!IsDefined($token)) {
        my $undef = '{' . $name . '}';
        $STATE->noteStatus(undefined => $undef);
        Error('undefined', $undef, $gullet, "The environment " . $undef . " is not defined.");
        $STATE->installDefinition(LaTeXML::Core::Definition::Constructor->new($token, undef,
            sub { LaTeXML::Core::Stomach::makeError($_[0], 'undefined', $undef); })); }
      (T_CS('\begingroup'), Invocation(T_CS('\lx@setcurrenvir'), $env), $token); } });

DefMacro('\end{}', sub {
    my ($gullet, $env) = @_;
    my $name = $env && ToString($env);
    my $t;
    if (IsDefined($t = T_CS("\\end{$name}"))) { $t; }                         # Magic CS!
    elsif (IsDefined($t = T_CS("\\end$name"))) { ($t, T_CS('\endgroup')); }
    else { (T_CS('\endgroup')); } });

#======================================================================
# C.1.3 Fragile Commands
#======================================================================
# Because of the way we `move information', revertable and pre-processed,
# I don't think we actually need to do anything ...
# [Course that means we're not _really_ TeX!]

# \protect is already in TeX for obscure reasons...
#Let('\@typeset@protect','\relax');
RawTeX(<<'EOTeX');
\def\@ignorefalse{\global\let\if@ignore\iffalse}
\def\@ignoretrue {\global\let\if@ignore\iftrue}
\def\zap@space#1 #2{%
  #1%
  \ifx#2\@empty\else\expandafter\zap@space\fi
  #2}
\def\@unexpandable@protect{\noexpand\protect\noexpand}
\def\x@protect#1{%
   \ifx\protect\@typeset@protect\else
      \@x@protect#1%
   \fi
}
\def\@x@protect#1\fi#2#3{%
   \fi\protect#1%
}
\let\@typeset@protect\relax
\def\set@display@protect{\let\protect\string}
\def\set@typeset@protect{\let\protect\@typeset@protect}
\def\protected@edef{%
   \let\@@protect\protect
   \let\protect\@unexpandable@protect
   \afterassignment\restore@protect
   \edef
}
\def\protected@xdef{%
   \let\@@protect\protect
   \let\protect\@unexpandable@protect
   \afterassignment\restore@protect
   \xdef
}
\def\unrestored@protected@xdef{%
   \let\protect\@unexpandable@protect
   \xdef
}
\def\restore@protect{\let\protect\@@protect}
\set@typeset@protect
\def\@nobreakfalse{\global\let\if@nobreak\iffalse}
\def\@nobreaktrue {\global\let\if@nobreak\iftrue}
\@nobreakfalse

\newif\ifv@
\newif\ifh@
\newif\ifdt@p
\newif\if@pboxsw
\newif\if@rjfield
\newif\if@firstamp
\newif\if@negarg
\newif\if@ovt
\newif\if@ovb
\newif\if@ovl
\newif\if@ovr
\newdimen\@ovxx
\newdimen\@ovyy
\newdimen\@ovdx
\newdimen\@ovdy
\newdimen\@ovro
\newdimen\@ovri
\newif\if@noskipsec \@noskipsectrue

EOTeX

#======================================================================
# C.1.4 Declarations
#======================================================================
# actual implementation later.
#======================================================================
# C.1.5 Invisible Commands
#======================================================================
# actual implementation later.

#======================================================================
# C.1.6 The \\ Command
#======================================================================
# In math, \\ is just a formatting hint, unless within an array, cases, .. environment.
DefConstructor("\\\\ OptionalMatch:* [Glue]",
  "?#isMath(<ltx:XMHint name='newline'/>)(<ltx:break/>)",
  reversion => Tokens(T_CS("\\\\"), T_CR));
Let('\@normalcr', "\\\\");
PushValue(TEXT_MODE_BINDINGS => [T_CS("\\\\"), T_CS('\@normalcr')]);

DefMacro('\@nolnerr', '');
DefMacro('\@centercr', '\ifhmode\unskip\else\@nolnerr\fi'
    . '\par\@ifstar{\nobreak\@xcentercr}\@xcentercr');
DefMacro('\@xcentercr',   '\addvspace{-\parskip}\@ifnextchar[\@icentercr\ignorespaces');
DefMacro('\@icentercr[]', '\vskip #1\ignorespaces');

#**********************************************************************
# C.2. The Structure of the Document
#**********************************************************************
#   prepended files (using filecontents environment)
#   preamble (starting with \documentclass)
#   \begin{document}
#    text
#   \end{document}

DefMacro('\AtBeginDocument{}', sub {
    AssignValue('@at@begin@document', []) unless LookupValue('@at@begin@document');
    PushValue('@at@begin@document', $_[1]->unlist); });
DefMacro('\AtEndDocument{}', sub {
    AssignValue('@at@end@document', []) unless LookupValue('@at@end@document');
    PushValue('@at@end@document', $_[1]->unlist); });

DefEnvironment('{document}', sub {
    #       "<ltx:document xml:id='#id'>#body</ltx:document>",
    my ($document, %props) = @_;
    my $id   = ToString($props{id});
    my $body = $props{body};
    if (my $docel = $document->findnode('/ltx:document')) {    # Already (auto) created?
      $document->setAttribute($docel, 'xml:id' => $id) if $id;
      $document->absorb($body); }
    else {
      $document->insertElement('ltx:document', $body, 'xml:id' => $id); } },
  beforeDigest => sub { AssignValue(inPreamble => 0); },
  afterDigestBegin => sub {
    $_[1]->setProperty(id => Expand(T_CS('\thedocument@ID')));
    if (my $ops = LookupValue('@at@begin@document')) {
      my @boxes = Digest(Tokens(@$ops));
      $_[1]->setFont(LookupValue('font'));                     # Start w/ whatever font was selected.
      return @boxes; }
    else {
      return; } },
  beforeDigestEnd => sub {
    $_[0]->getGullet->flush;
    if (my $ops = LookupValue('@at@end@document')) {
      Digest(Tokens(@$ops)); }
    else {
      return; } },
  mode => 'text');

#**********************************************************************
# C.3. Sentences and Paragraphs
#**********************************************************************

#======================================================================
# C.3.1 Making Sentences
#======================================================================
# quotes;  should these be handled in DOM/construction?
# dashes:  We'll need some sort of Ligature analog, or something like
# Omega's OTP, to combine sequences of "-" into endash, emdash,
# Perhaps it also applies more semantically?
# Such as interpreting certain sequences as section headings,
# or math constructs.

# Spacing; in TeX.pool.ltxml

# Special Characters; in TeX.pool.ltxml

# Logos
# \TeX is in TeX.pool.ltxml
DefConstructorI('\LaTeX',  undef, 'LaTeX');
DefConstructorI('\LaTeXe', undef, 'LaTeX2e');
DefMacroI('\fmtname',    undef, 'LaTeX2e');
DefMacroI('\fmtversion', undef, 'XXXX/XX/XX');

DefMacroI('\today', undef, sub { ExplodeText(today()); });

DefConstructor('\emph{}', "<ltx:emph>#1</ltx:emph>", mode => 'text');

#======================================================================
# C.3.2 Making Paragraphs
#======================================================================
# \noindent, \indent, \par in TeX.pool.ltxml

Let('\@@par', '\par');
# Style parameters
# \parindent, \baselineskip, \parskip alreadin in TeX.pool.ltxml

DefPrimitive('\linespread{}', undef);

# ?
DefMacro('\@noligs', '');
DefConditional('\if@endpe');
DefMacro('\@doendpe', '');
DefMacro('\@bsphack', '\relax');    # what else?
DefMacro('\@esphack', '\relax');
DefMacro('\@Esphack', '\relax');
#======================================================================
# C.3.3 Footnotes
#======================================================================

NewCounter('footnote');
DefMacroI('\thefootnote', undef, '\arabic{footnote}');
NewCounter('mpfootnote');
DefMacroI('\thempfn',       undef, '\thefootnote');
DefMacroI('\thempfootnote', undef, '\arabic{mpfootnote}');

DefConstructor('\footnote[]{}',
  "<ltx:note role='footnote' mark='#refnum'>#2</ltx:note>",
  mode => 'text', bounded => 1,
  beforeDigest => sub { reenterTextMode(1); Digest('\normalfont'); },
  properties => sub {
    ($_[1] ? (refnum => $_[1]) : RefStepCounter('footnote')) });
DefConstructor('\footnotemark[]',
  "<ltx:note role='footnotemark' mark='#refnum'></ltx:note>",
  mode       => 'text',
  properties => sub {
    ($_[1] ? (refnum => $_[1]) : RefStepCounter('footnote')) });
DefConstructor('\footnotetext[]{}',
  "<ltx:note role='footnotetext' mark='#refnum'>#2</ltx:note>",
  mode       => 'text',
  properties => sub {
    ($_[1] ? (refnum => $_[1]) : (refnum => Digest(T_CS("\\thefootnote")))); });

Tag('ltx:note', afterClose => \&relocateFootnote);

sub relocateFootnote {
  my ($document, $node) = @_;
  if (($node->getAttribute('role') || '') =~ /^(\w+?)text$/) {
    my $notetype = $1;    # Eg "footnote", "endnote",...
    if (my $mark = $node->getAttribute('mark')) {
      foreach my $note ($document->findnodes(".//ltx:note[\@role='${notetype}mark'][\@mark='$mark']")) {
        $node->parentNode->removeChild($node);
        $document->appendClone($note, $node->childNodes);
        $document->setAttribute($note, role => $notetype);
        if (my $labels = $node->getAttribute('labels')) {
          GenerateID($document, $note);
          $document->setAttribute($note, labels => $labels); } } } }
  return; }

# Style parameters
DefRegister('\footnotesep' => Dimension(0));
DefPrimitiveI('\footnoterule', undef, undef);

#======================================================================
# C.3.4 Accents and Special Symbols
#======================================================================
# See TeX.pool.ltxml

# See Section 3.3.2 Mathematical Symbols, below

# Should this be here?
DefMath('\mathring{}', "\x{030A}", operator_role => 'OVERACCENT');

#**********************************************************************
# C.4 Sectioning and Table of Contents
#**********************************************************************

#======================================================================
# C.4.1 Sectioning Commands.
#======================================================================
# Note that LaTeX allows fairly arbitrary stuff in \the<ctr>, although
# it can get you in trouble.  However, in almost all cases, the result
# is plain text.  So, I'm putting refnum as an attribute, where I like it!
# You want something else? Redefine!

# Also, we're adding an id to each, that is parallel to the refnum, but
# valid as an ID.  You can tune the representation by defining, eg. \thesection@ID

# A little more messy than seems necessary:
#  We don't know whether to step the counter and update \@currentlabel until we see the '*',
# but we have to know it before we digest the title, since \label can be there!

# These are defined in terms of \@startsection so that
# casual user redefinitions work, too.
DefMacroI('\chapter', undef, '\@startsection{chapter}{0}{}{}{}{}', locked => 1);
DefMacroI('\part', undef, '\@startsection{part}{-1}{}{}{}{}'); # not locked since sometimes redefined as partition?
DefMacroI('\section', undef, '\@startsection{section}{1}{}{}{}{}', locked => 1);
DefMacroI('\subsection',    undef, '\@startsection{subsection}{2}{}{}{}{}',    locked => 1);
DefMacroI('\subsubsection', undef, '\@startsection{subsubsection}{3}{}{}{}{}', locked => 1);
DefMacroI('\paragraph',     undef, '\@startsection{paragraph}{4}{}{}{}{}',     locked => 1);
DefMacroI('\subparagraph',  undef, '\@startsection{subparagraph}{5}{}{}{}{}',  locked => 1);
map { Tag("ltx:$_", autoClose => 1) }
  qw(part chapter section subsection subsubsection paragraph subparagraph);

DefMacro('\secdef {}{} OptionalMatch:*', sub { ($_[3] ? ($_[2]) : ($_[1])); });

DefMacroI('\@startsection@hook', undef, Tokens());
NewCounter('secnumdepth');
SetCounter('secnumdepth', Number(3));
DefMacro('\@startsection{}{}{}{}{}{} OptionalMatch:*', sub {
    my ($gullet, $type, $level, $ignore3, $ignore4, $ignore5, $ignore6, $flag) = @_;
    my $ctr = LookupValue('counter_for_' . ToString($type)) || ToString($type);
    $level = ToString($level);
    if ($flag || (($level ne '') && ($level > CounterValue('secnumdepth')->valueOf))) {
      RefStepID($ctr);
      (T_CS('\@startsection@hook'), T_CS('\\@@unnumbered@section'), T_BEGIN, $type->unlist, T_END); }
    else {
      RefStepCounter($ctr);
      (T_CS('\@startsection@hook'), T_CS('\\@@numbered@section'), T_BEGIN, $type->unlist, T_END); } });

# Redefine these if you want to assemble the name (eg. \chaptername), refnum and titles differently
# \@@numbered@section{type}[toctitle]{title}
DefMacro('\@@numbered@section{}[]{}',
  '\@@section{#1}{\@currentID}{\@currentlabel}'
    . '{\lx@fnum@@{#1}}'
    . '{\format@toctitle@{#1}{\ifx.#2.#3\else#2\fi}}'
    . '{\format@title@{#1}{#3}}');
# NOTE: Unclear here, whether the "formatted refnum" should be empty, or just the type abbreviation?
DefMacro('\@@unnumbered@section{}[]{}',
  '\@@section{#1}{\@currentID}{}{}{#2}{#3}');

#----------------------------------------------------------------------
# The following macros provide a few layers of customization
# in particular for supporting localization for different languages.
#----------------------------------------------------------------------
# \format@title@{type}{title}
# Format a title (or caption) appropriately for type.
# This is usually somewhat verbose, but establishes the context that this is a Chapter, or Figure, or whatever
# invokes \format@title@type{title} if that macro is defined, else composes \lx@fnum@@{type} title.
# Define \format@title@type{title} if the default is not appropriate.
DefMacro('\format@title@{}{}',
'{\@ifundefined{format@title@#1}{\@@compose@title{\lx@fnum@@{#1}}{#2}}{\csname format@title@#1\endcsname{#2}}}');

# \format@toctitle@{type}{toctitle}
# Format a toctitle (or toccaption) appropriately for type.
# This is usually somewhat concise, and the context implies that this is a Chapter, Figure or whatever
# invokes \format@toctitle@type{title} if that macro is defined, else composes \lx@fnum@toc@@{type} title
# Define \format@toctitle@type{title} if the default is not appropriate.
DefMacro('\format@toctitle@{}{}',
'{\@ifundefined{format@toctitle@#1}{\@@compose@title{\lx@fnum@toc@@{#1}}{#2}}{\csname format@toctitle@#1\endcsname{#2}}}');

DefMacro('\@@compose@title{}{}', '\@tag[][ ]{#1}#2');
DefConstructor('\@tag[][]{}', "?#3(<ltx:tag open='#1' close='#2'>#3</ltx:tag>)()");

## NOTE that a 3rd form seems desirable: an concise form that cannot rely on context for the type.
## This would be useful for the titles in links; thus can be plain (unicode) text.
## However, I hate setting up even more machinery & options and dragging yet another form around....
# \@@section{type}{id}{refnum}{formattedrefnum}{toctitle}{title}
DefConstructor('\\@@section{}{}{}{}{}{}', sub {
    my ($document, $type, $id, $refnum, $frefnum, $toctitle, $title, %props) = @_;
    $frefnum = undef if $frefnum && $refnum && (ToString($frefnum) eq ToString($refnum));
    $document->openElement("ltx:" . ToString($type), 'xml:id' => CleanID($id), refnum => $refnum, frefnum => $frefnum);
    $document->insertElement('ltx:title',    $title);
    $document->insertElement('ltx:toctitle', $toctitle)
      if $toctitle && $toctitle->unlist && (ToString($toctitle) ne ToString($title)); },
  bounded => 1);

# Not sure if this is best, but if no explicit \section'ing...
#### Tag('ltx:section',autoOpen=>1);

#======================================================================
# C.4.2 The Appendix
#======================================================================
# Handled in article,report or book.
DefMacroI('\appendixname',   undef, 'Appendix');
DefMacroI('\appendixesname', undef, 'Appendixes');

#======================================================================
# C.4.3 Table of Contents
#======================================================================
# Insert stubs that will be filled in during post processing.
DefMacroI('\contentsname', undef, 'Contents');
DefConstructorI('\tableofcontents', undef,
  "<ltx:TOC role='contents' select='#select' name='#name'/>",
  properties => sub {
    my $td = CounterValue('tocdepth')->valueOf + 1;
    my @s  = (qw(ltx:part ltx:chapter ltx:section ltx:subsection ltx:subsubsection
        ltx:paragraph ltx:subparagraph));
    $td = $#s if $#s < $td;
    @s = map { $s[$_] } 0 .. $td;
    push(@s, (qw(ltx:appendix ltx:index ltx:bibliography))) if @s;
    (select => join(' | ', @s),
      name => Digest(T_CS('\contentsname'))); });

DefMacroI('\listfigurename', undef, 'List of Figures');
DefConstructorI('\listoffigures', undef,
  "<ltx:TOC role='figures' select='ltx:figure' name='#name' scope='global'/>",
  properties => sub { (name => Digest(T_CS('\listfigurename'))); });

DefMacroI('\listtablename', undef, 'List of Tables');
DefConstructorI('\listoftables', undef,
  "<ltx:TOC role='tables' select='ltx:table' name='#name' scope='global'/>",
  properties => sub { (name => Digest(T_CS('\listtablename'))); });

DefPrimitive('\addcontentsline{}{}{}', undef);
DefPrimitive('\numberline{}{}',        undef);
DefPrimitive('\addtocontents{}{}',     undef);

#======================================================================
# C.4.4 Style registers
#======================================================================
NewCounter('tocdepth');

#**********************************************************************
# C.5 Classes, Packages and Page Styles
#**********************************************************************

#======================================================================
# C.5.1 Document Class
#======================================================================
# Style Parameters
DefRegister('\bibindent'     => Dimension(0));
DefRegister('\columnsep'     => Dimension(0));
DefRegister('\columnseprule' => Dimension(0));
DefRegister('\mathindent'    => Dimension(0));

#======================================================================
# C.5.2 Packages
#======================================================================
# We'll prefer to load package.pm, but will try package.sty or
# package.tex (the latter being unlikely to work, but....)
# See Stomach.pm for details
# Ignorable packages ??
# pre-defined packages??

DefMacroI('\@clsextension', undef, 'cls');
DefMacroI('\@pkgextension', undef, 'sty');
Let('\@currext',  '\@empty');
Let('\@currname', '\@empty');

DefConstructor('\usepackage OptionalSemiverbatim Semiverbatim []',
  "<?latexml package='#2' ?#1(options='#1')?>",
  beforeDigest => sub { onlyPreamble('\usepackage'); },
  afterDigest => sub { my ($stomach, $whatsit) = @_;
    my $options  = $whatsit->getArg(1);
    my $packages = $whatsit->getArg(2);
    my @pkgs     = grep { $_ } grep { !/^\s*%/ } split(/,\s*/, ToString($packages));
    $options = [($options ? split(/,\s*/, (ToString($options))) : ())];
    map { RequirePackage($_, options => $options) } @pkgs;
    return });

DefConstructor('\RequirePackage OptionalSemiverbatim Semiverbatim []',
  "<?latexml package='#2' ?#1(options='#1')?>",
  beforeDigest => sub { onlyPreamble('\RequirePackage'); },
  afterDigest => sub { my ($stomach, $whatsit) = @_;
    my $options  = $whatsit->getArg(1);
    my $packages = $whatsit->getArg(2);
    my @pkgs     = grep { $_ } grep { !/^\s*%/ } split(/,\s*/, ToString($packages));
    $options = [($options ? split(/,\s*/, (ToString($options))) : ())];
    map { RequirePackage($_, options => $options) } @pkgs;
    return });

DefConstructor('\LoadClass OptionalSemiverbatim Semiverbatim []',
  "<?latexml class='#2' ?#1(options='#1')?>",
  beforeDigest => sub { onlyPreamble('\LoadClass'); },
  afterDigest => sub { my ($stomach, $whatsit) = @_;
    my $options = $whatsit->getArg(1);
    my $class   = $whatsit->getArg(2);
    $options = [($options ? split(/,\s*/, (ToString($options))) : ())];
    LoadClass(ToString($class), options => $options);
    return; });

# Related internal macros for package definition
# Internals used in Packages
DefMacro('\NeedsTeXFormat{}[]', Tokens());

DefPrimitive('\ProvidesClass{}[]', sub {
    my ($stomach, $class, $version) = @_;
    DefMacroI("\\ver@" . ToString($class) . ".cls", undef, $version || Tokens(), scope => 'global');
    return; });

# Note that these, like LaTeX, define macros like \var@mypkg.sty to give the version info.
DefMacro('\ProvidesPackage{}[]', sub {
    my ($stomach, $package, $version) = @_;
    DefMacroI("\\ver@" . ToString($package) . ".sty", undef, $version || Tokens(), scope => 'global');
    return; });

DefMacro('\ProvidesFile{}[]', sub {
    my ($stomach, $file, $version) = @_;
    DefMacroI("\\ver@" . ToString($file), undef, $version || Tokens(), scope => 'global');
    return; });

DefPrimitive('\DeclareOption{}{}', sub {
    my ($stomach, $option, $code) = @_;
    ((ToString($option) eq '*') ?
        DeclareOption(undef,             $code) :
        DeclareOption(ToString($option), $code)); });

DefPrimitive('\PassOptionsToPackage{}{}', sub {
    my ($stomach, $name, $options) = @_;
    PassOptions($name, 'pkg', split(/,\s*/, ToString(Digest($options)))); });

DefPrimitive('\PassOptionsToClass{}{}', sub {
    my ($stomach, $name, $options) = @_;
    PassOptions($name, 'cls', split(/,\s*/, ToString(Digest($options)))); });

DefConstructor('\RequirePackageWithOptions Semiverbatim []',
  "<?latexml package='#1'?>",
  beforeDigest => sub { onlyPreamble('\RequirePackage'); },
  afterDigest => sub { my ($stomach, $whatsit) = @_;
    my $package = $whatsit->getArg(1);
    RequirePackage(ToString($package), withoptions => 1);
    return; });

DefConstructor('\LoadClassWithOptions Semiverbatim []',
  "<?latexml class='#1'?>",
  beforeDigest => sub { onlyPreamble('\LoadClassWithOptions'); },
  afterDigest => sub { my ($stomach, $whatsit) = @_;
    my $class = $whatsit->getArg(1);
    LoadClass(ToString($class), withoptions => 1);
    return; });

DefMacroI('\CurrentOption', undef, Tokens());

DefPrimitiveI('\OptionNotUsed', undef, sub {
    if (my $option = ToString(Digest(T_CS('\CurrentOption')))) {
      my $type = ToString(Digest(T_CS('\@currext')));
      if ($type eq 'cls') {
        PushValue('@unusedoptionlist', $option); } }
    return; });

DefPrimitiveI('\@unknownoptionerror', undef, sub {
    if (my $option = ToString(Digest(T_CS('\CurrentOption')))) {
      my $name = ToString(Digest(T_CS('\@currname')));
      my $type = ToString(Digest(T_CS('\@currext')));
      Info('unexpected', $option, $_[0], "Unexpected option '$option' passed to $name.$type"); }
    return; });

DefPrimitive('\ExecuteOptions{}', sub {
    my ($gullet, $options) = @_;
    ExecuteOptions(split(/,\s*/, ToString(Digest($options)))); });

DefPrimitive('\ProcessOptions OptionalMatch:*', sub {
    my ($stomach, $star) = @_;
    ProcessOptions(($star ? (inorder => 1) : ())); });

DefMacro('\AtEndOfPackage{}', sub {
    my ($gullet, $code) = @_;
    my $name = ToString(Digest(T_CS('\@currname')));
    my $type = ToString(Digest(T_CS('\@currext')));
    AddToMacro(T_CS('\\' . $name . '.' . $type . '-h@@k'), $code); });

DefMacro('\@ifpackageloaded', '\@ifl@aded\@pkgextension');
DefMacro('\@ifclassloaded',   '\@ifl@aded\@clsextension');
DefMacro('\@ifl@aded{}{}', sub {
    my ($gullet, $ext, $name) = @_;
    my $path = ToString(Expand($name)) . '.' . ToString(Expand($ext));
    # If EITHER the raw TeX or ltxml version of this file was loaded.
    if (LookupValue($path . '_loaded') || LookupValue($path . '.ltxml_loaded')) {
      T_CS('\@firstoftwo'); }
    else {
      T_CS('\@secondoftwo'); } });

DefMacro('\@ifpackagewith', '\@if@ptions\@pkgextension');
DefMacro('\@ifclasswith',   '\@if@ptions\@clsextension');
DefMacro('\@if@ptions{}{}{}', sub {
    my ($gullet, $ext, $name, $option) = @_;
    $option = ToString(Expand($option));
    my $values = LookupValue('opt@' . ToString(Expand($name)) . '.' . ToString(Expand($ext)));
    if (grep { $option eq $_ } @$values) {
      T_CS('\@firstoftwo'); }
    else {
      T_CS('\@secondoftwo'); } });

DefMacro('\g@addto@macro DefToken {}', sub { AddToMacro($_[1], $_[2]); });
DefMacro('\addto@hook DefToken {}', '#1\expandafter{\the#1#2}');

Let('\AtEndOfClass', '\AtEndOfPackage');

DefMacro('\AtBeginDvi {}', Tokens());

#======================================================================
# Somewhat related I/O stuff
DefMacro('\filename@parse{}', sub {
    my ($gullet, $pathname) = @_;
    my ($dir, $name, $ext) = pathname_split(ToString(Expand($pathname)));
    $dir .= '/' if $dir;
    DefMacroI('\filename@area', undef, Tokens(ExplodeText($dir)));
    DefMacroI('\filename@base', undef, Tokens(ExplodeText($name)));
    DefMacroI('\filename@ext', undef, ($ext ? Tokens(ExplodeText($ext)) : T_CS('\relax'))); });

DefMacroI('\@filelist', undef, Tokens());
DefMacro('\@addtofilelist{}', sub {
    DefMacroI('\@filelist', undef,
      Expand(T_CS('\@filelist'), T_OTHER(','), $_[1]->unlist)); });
#======================================================================
# C.5.3 Page Styles
#======================================================================
# Ignored
NewCounter('page');
DefPrimitive('\pagestyle{}',     undef);
DefPrimitive('\thispagestyle{}', undef);
DefPrimitive('\markright{}',     undef);
DefPrimitive('\markboth{}{}',    undef);
DefPrimitiveI('\leftmark',  undef, undef);
DefPrimitiveI('\rightmark', undef, undef);
DefPrimitive('\pagenumbering{}', undef);
DefPrimitive('\twocolumn[]',     undef);
DefPrimitiveI('\onecolumn', undef, undef);

# Style parameters from Fig. C.3, p.182
DefRegister('\paperheight'     => Dimension(0));
DefRegister('\paperwidth'      => Dimension(0));
DefRegister('\textheight'      => Dimension(0));
DefRegister('\textwidth'       => Dimension('6in'));
DefRegister('\topmargin'       => Dimension(0));
DefRegister('\headheight'      => Dimension(0));
DefRegister('\headsep'         => Dimension(0));
DefRegister('\footskip'        => Dimension(0));
DefRegister('\footheight'      => Dimension(0));
DefRegister('\evensidemargin'  => Dimension(0));
DefRegister('\oddsidemargin'   => Dimension(0));
DefRegister('\marginparwidth'  => Dimension(0));
DefRegister('\marginparsep'    => Dimension(0));
DefRegister('\columnwidth'     => Dimension('6in'));
DefRegister('\linewidth  '     => Dimension('6in'));
DefRegister('\baselinestretch' => Dimension(0));

#======================================================================
# C.5.4 The Title Page and Abstract
#======================================================================

# See frontmatter support in TeX.ltxml
DefMacro('\title{}', '\@add@frontmatter{ltx:title}{#1}');

DefMacro('\date{}',
  '\@add@frontmatter{ltx:date}[role=creation,'
    . 'name={\@ifundefined{datename}{}{\datename}}]{#1}');

DefConstructor('\person@thanks{}', "^ <ltx:contact role='thanks'>#1</ltx:contact>",
  alias => '\thanks', mode => 'text');
DefConstructor('\@personname{}', "<ltx:personname>#1</ltx:personname>",
  beforeDigest => sub { Let('\thanks', '\person@thanks'); },
  bounded => 1, mode => 'text');

DefConstructorI('\and', undef, " and ");

AssignValue(NUMBER_OF_AUTHORS => 0);
DefPrimitive('\lx@count@author', sub {
    AssignValue(NUMBER_OF_AUTHORS => LookupValue('NUMBER_OF_AUTHORS') + 1, 'global') });
DefMacro('\@author{}',
  '\lx@count@author'
    . '\@add@frontmatter{ltx:creator}[role=author]{\lx@author@prefix\@personname{#1}}');
DefMacro('\lx@author@sep',  '\qquad');
DefMacro('\lx@author@conj', '\qquad');
DefConstructor('\lx@author@prefix', sub {
    my ($document) = @_;
    my $node       = $document->getElement;
    my $nauthors   = LookupValue('NUMBER_OF_AUTHORS');
    my $i          = scalar(@{ $document->findnodes('//ltx:creator[@role="author"]') });
    if ($i <= 1) { }
    elsif ($i == $nauthors) {
      $document->setAttribute($node, before => ToString(Digest(T_CS('\lx@author@conj')))); }
    else {
      $document->setAttribute($node, before => ToString(Digest(T_CS('\lx@author@sep')))); }
});

DefMacro('\author{}', sub { andSplit(T_CS('\@author'), $_[1]); });

DefPrimitive('\ltx@authors@oneline', sub {
    AssignMapping('DOCUMENT_CLASSES', ltx_authors_1line => 1);
    return; });
DefPrimitive('\ltx@authors@multiline', sub {
    AssignMapping('DOCUMENT_CLASSES', ltx_authors_multiline => 1);
    return; });

DefMacro('\@add@conversion@date', '\@add@frontmatter{ltx:date}[role=creation]{\today}');

# Doesn't produce anything (we're already inserting frontmatter),
# But, it does make the various frontmatter macros into no-ops.
DefMacroI('\maketitle', undef,
  '\@startsection@hook'
    . '\global\let\thanks\relax'
    . '\global\let\maketitle\relax'
    . '\global\let\@maketitle\relax'
    . '\global\let\@thanks\@empty'
    . '\global\let\@author\@empty'
    . '\global\let\@date\@empty'
    . '\global\let\@title\@empty'
    . '\global\let\title\relax'
    . '\global\let\author\relax'
    . '\global\let\date\relax'
    . '\global\let\and\relax');

DefConstructor('\thanks{}', "<ltx:note role='thanks'>#1</ltx:note>");

# Abstract SHOULD have been so simple, but seems to be a magnet for abuse.
# For one thing, we'd like to just write
#   DefEnvironment('{abstract}','<ltx:abstract>#body</ltx:abstract>');
# However, we don't want to place the <ltx:abstract> environment directly where
# we found it, but we want to add it to frontmatter. This requires capturing the
# recently digested list and storing it in the frontmatter structure.

# The really messy stuff comes from the way authors -- and style designers -- misuse it.
# Basic LaTeX wants it to be an environment WITHIN the document environment,
# and AFTER the \maketitle.
# However, since all it really does is typeset "Abstract" in bold, it allows:
#   \abstract stuff...
# without even an \endabstract!  We MUST know when the abstract ends, so we've got
# to recognize when we've moved on to other stuff... \sections at the VERY LEAST.

# Additional complications come from certain other classes and styles that
# redefine abstract to take the text as an argument. And some treat it
# like \title, \author, and such, that are expected to appear in the preamble!!
# The treatment below allows an abstract environment in the preamble,
# (even though straight latex doesn't) but does not cover the 1-arg case in preamble!
#
# Probably there are other places (eg in titlepage?) that should force the close??

DefEnvironment('{abstract}', '',
  afterDigestBegin => sub {
    AssignValue(inPreamble => 0);
    AddToMacro(T_CS('\@startsection@hook'), T_CS('\maybe@end@abstract')); },
  afterDigest => sub {
    my $frontmatter = LookupValue('frontmatter');
    push(@{ $$frontmatter{'ltx:abstract'} },
      ['ltx:abstract',
        { name => Digest(Tokens(T_CS('\format@title@abstract'),
              T_BEGIN, T_CS('\abstractname'), T_END)) },
        @LaTeXML::LIST]);
    DefMacroI('\maybe@end@abstract', undef, Tokens(), scope => 'global');
    return; },
  locked => 1, mode => 'text');
# If we get a plain \abstract, instead of an environment, look for \abstract{the abstract}
AssignValue('\abstract:locked' => 0);    # REDEFINE the above locked definition!
DefMacro('\abstract', sub {
    my ($gullet) = @_;
    ($gullet->ifNext(T_BEGIN) ? T_CS('\abstract@onearg') : T_CS('\begin{abstract}')); },
  locked => 1);
DefMacro('\abstract@onearg{}', '\begin{abstract}#1\end{abstract}');

DefMacroI('\maybe@end@abstract', undef, '\endabstract');

DefMacroI('\abstractname', undef, 'Abstract');
DefMacro('\format@title@abstract{}', '#1');

# Hmm, titlepage is likely to be hairy, low-level markup,
# without even title, author, etc, specified as such!
# Hmm, should this even redefine author, title, etc so that they
# are simply output?
# This is horrible hackery; What we really need, I think, is the
# ability to bind some sort of "Do <this> when we create a text box"...
# ON Second Thought...
# For the time being, ignore titlepage!
# Maybe we could do some of this if there is no title/author
# otherwise defined? Ugh!

#DefEnvironment('{titlepage}','');
# Or perhaps it's better just to ignore the markers?
#DefMacro('\titlepage','');
#DefMacro('\endtitlepage','');

# Or perhaps not....
# There's a title and other stuff in here, but how could we guess?
# Well, there's likely to be a sequence of <p><text font="xx" fontsize="yy">...</text></p>
# Presumably the earlier, larger one is title, rest are authors/affiliations...
# Particularly, if they start with a pseudo superscript or other "marker", they're probably affil!
# For now, we just give an info message
DefEnvironment('{titlepage}', '<ltx:titlepage>#body',
  beforeDigest => sub { Let('\centering', '\relax');
    DefEnvironment('{abstract}',
      '<ltx:abstract>#body</ltx:abstract>');
    Info('unexpected', 'titlepage', $_[0],
      "When using titlepage, Frontmatter will not be well-structured");
    return; },
  beforeDigestEnd => sub { Digest(T_CS('\maybe@end@title')); },
  locked => 1, mode => 'text');

DefConstructorI('\maybe@end@title', undef, sub {
    my ($document) = @_;
    if ($document->isCloseable('ltx:titlepage')) {
      $document->closeElement('ltx:titlepage'); } });

DefMacro('\sectionmark{}',       Tokens());
DefMacro('\subsectionmark{}',    Tokens());
DefMacro('\subsubsectionmark{}', Tokens());
DefMacro('\paragraphmark{}',     Tokens());
DefMacro('\subparagraphmark{}',  Tokens());
DefMacroI('\@oddfoot',  undef, Tokens());
DefMacroI('\@oddhed',   undef, Tokens());
DefMacroI('\@evenfoot', undef, Tokens());
DefMacroI('\@evenfoot', undef, Tokens());
#**********************************************************************
# C.6 Displayed Paragraphs
#**********************************************************************

DefEnvironment('{center}', sub {
    $_[0]->maybeCloseElement('ltx:p');    # this starts a new vertical block
    aligningEnvironment('center', 'ltx_centering', @_); },    # aligning will take care of \\\\ "rows"
  beforeDigest => sub {
    Let('\par', '\inner@par');
    Let('\\\\', '\inner@par'); });
# HOWEVER, define a plain \center to act like \centering (?)
DefMacroI('\center',    undef, '\centering');
DefMacroI('\endcenter', undef, '');

DefEnvironment('{flushleft}', sub {
    $_[0]->maybeCloseElement('ltx:p');                        # this starts a new vertical block
    aligningEnvironment('left', 'ltx_align_left', @_); },
  beforeDigest => sub {
    Let('\par', '\inner@par');
    Let('\\\\', '\inner@par'); });
DefEnvironment('{flushright}', sub {
    $_[0]->maybeCloseElement('ltx:p');                        # this starts a new vertical block
    aligningEnvironment('right', 'ltx_align_right', @_); },
  beforeDigest => sub {
    Let('\par', '\inner@par');
    Let('\\\\', '\inner@par'); });

# These add an operation to be carried out on the current node & following siblings, when the current group ends.
# These operators will add alignment (class) attributes to each "line" in the current block.
#DefPrimitiveI('\centering',   undef, sub { UnshiftValue(beforeAfterGroup=>T_CS('\@add@centering')); });
# NOTE: THere's a problem here.  The current method seems to work right for these operators
# appearing within the typical environments.  HOWEVER, it doesn't work for a simple \bgroup or \begingroup!!!
# (they don't create a node! or even a whatsit!)
DefConstructorI('\centering', undef,
  sub { AssignValue(ALIGNING_NODE => $_[0]->getElement); return; },
  beforeDigest => sub { UnshiftValue(beforeAfterGroup => T_CS('\@add@centering')); });
DefConstructorI('\raggedright', undef,
  sub { AssignValue(ALIGNING_NODE => $_[0]->getElement); return; },
  beforeDigest => sub { UnshiftValue(beforeAfterGroup => T_CS('\@add@raggedright')); });
DefConstructorI('\raggedleft', undef,
  sub { AssignValue(ALIGNING_NODE => $_[0]->getElement); return; },
  beforeDigest => sub { UnshiftValue(beforeAfterGroup => T_CS('\@add@raggedleft')); });

DefConstructorI('\@add@centering', undef,
  sub { if (my $node = LookupValue('ALIGNING_NODE')) {
      map { setAlignOrClass($_[0], $_, 'center', 'ltx_centering') }
        $_[0]->getChildElements($node); } });
# Note that \raggedright is essentially align left
DefConstructorI('\@add@raggedright', undef,
  sub { if (my $node = LookupValue('ALIGNING_NODE')) {
      map { setAlignOrClass($_[0], $_, undef, 'ltx_align_left') }
        $_[0]->getChildElements($node); } });
DefConstructorI('\@add@raggedleft', undef,
  sub { if (my $node = LookupValue('ALIGNING_NODE')) {
      map { setAlignOrClass($_[0], $_, undef, 'ltx_align_right') }
        $_[0]->getChildElements($node); } });

DefConstructorI('\@add@flushright', undef,
  sub { if (my $node = LookupValue('ALIGNING_NODE')) {
      map { setAlignOrClass($_[0], $_, 'right', 'ltx_align_right') }
        $_[0]->getChildElements($node); } });
DefConstructorI('\@add@flushleft', undef,
  sub { if (my $node = LookupValue('ALIGNING_NODE')) {
      map { setAlignOrClass($_[0], $_, 'left', 'ltx_align_left') }
        $_[0]->getChildElements($node); } });

#======================================================================-
# C.6.1 Quotations and Verse
#======================================================================-
DefConstructor('\@block@cr[Dimension]', "<ltx:break/>\n",
  reversion => Tokens(T_CS("\\\\"), T_CR));
DefEnvironment('{quote}',
  '<ltx:quote>#body</ltx:quote>',
  beforeDigest => sub { Let('\\\\', '\@block@cr'); Let('\par', '\@block@cr') },
  mode => 'text');
DefEnvironment('{quotation}',
  '<ltx:quote>#body</ltx:quote>',
  beforeDigest => sub { Let('\\\\', '\@block@cr'); Let('\par', '\@block@cr') },
  mode => 'text');
# NOTE: Handling of \\ within these environments?
DefEnvironment('{verse}',
  '<ltx:quote role="verse">#body</ltx:quote>',
  beforeDigest => sub { Let('\\\\', '\@block@cr'); Let('\par', '\@block@cr') },
  mode => 'text');

#======================================================================
# C.6.2 List-Making environments
#======================================================================
Tag('ltx:item',        autoClose => 1);
Tag('ltx:inline-item', autoClose => 1);

# These are for the (not quite legit) case where \item appears outside
# of an itemize, enumerate, etc, environment.
DefConstructor('\item[]',
  "<ltx:item>?&defined(#1)(<ltx:tag>#1</ltx:tag>)");
DefConstructor('\subitem[]',
  "<ltx:item>?&defined(#1)(<ltx:tag>#1</ltx:tag>)");
DefConstructor('\subsubitem[]',
  "<ltx:item>?&defined(#1)(<ltx:tag>#1</ltx:tag>)");
AssignValue(itemlevel => 0, 'global');

# protection against lower-level code...
DefConditional('\if@noitemarg');
DefMacro('\@item',      '\item');    # Hopefully no circles...
DefMacro('\@itemlabel', '');         # Maybe needs to be same as \item will be using?

# Prepare for an list (itemize/enumerate/description/etc)
# by determining the right counter (level)
# and binding the right \item ( \$type@item, if $type is defined)
sub beginItemize {
  my ($type, $counter, $nolevel) = @_;
  $counter = '@@item' unless $counter;
  my $level = LookupValue('itemlevel') + 1;
  AssignRegister('\itemsep' => LookupDimension('\lx@default@itemsep'));
  AssignValue(itemlevel         => $level);
  AssignValue(itemization_items => 0);
  my $postfix = ToString(Tokens(roman($level)));
  my $usecounter = ($nolevel ? $counter : $counter . $postfix);
  Let('\item' => "\\" . $type . '@item') if defined $type;
  DefMacroI('\@listctr', undef, Tokens(Explode($usecounter)));
  AssignValue(itemcounter => $usecounter);
  ResetCounter($usecounter);
  return RefStepCounter('@itemize' . $postfix); }

# Create id, and refnum attributes for an itemize type \item
sub RefStepItemCounter {
  my ($tag)   = @_;
  my $counter = LookupValue('itemcounter');
  my $n       = LookupValue('itemization_items');
  AssignValue(itemization_items => $n + 1);
  my %attr = ();
  my $sep  = LookupDimension('\itemsep');
  if (($n > 0) && $sep && ($sep->valueOf != LookupDimension('\lx@default@itemsep')->valueOf)) {
    $attr{itemsep} = $sep; }
  if (defined $tag) {
    my $ttag      = (ref $tag ? $tag : T_OTHER($tag));
    my @props     = RefStepID($counter);
    my $refnum    = Digest(Tokens(T_BEGIN, $ttag->unlist, T_END));
    my $frefnum   = Digest(Tokens(T_BEGIN, Invocation(T_CS('\makelabel'), $ttag), T_END));
    my $s_refnum  = ToString($refnum);
    my $s_frefnum = ToString($frefnum);
    return (@props, refnum => $refnum,
      ($frefnum && (!$refnum || ($s_frefnum ne $s_refnum)) ? (frefnum => $frefnum) : ()), %attr); }
  else {
    return (RefStepCounter($counter), %attr); } }

# The following two aren't used here; they're defined here so they
# can be used in paralist.sty, enumerate.sty (perhaps others?)

# This isn't really satisfactory.
# We should record the marker used for the item,
# but it really should NOT be #refnum (which should be quasi unique)
# and is not \theenumi.. (which should be a counter value)
sub setItemizationStyle {
  my ($stuff, $level) = @_;
  if (defined $stuff) {
    $level = LookupValue('itemlevel') unless defined $level;
    $level = ToString(Tokens(roman($level)));
    DefMacroI('\labelitem' . $level, undef, $stuff); }
  return; }

sub setEnumerationStyle {
  my ($stuff, $level) = @_;
  if (defined $stuff) {
    $level = LookupValue('itemlevel') unless defined $level;
    $level = ToString(Tokens(roman($level)));
    my @in  = $stuff->unlist;
    my @out = ();
    my $ctr = T_OTHER('enum' . $level);

    while (my $t = shift(@in)) {
      if (Equals($t, T_BEGIN)) {
        push(@out, $t);
        my $brlevel = 1;
        while ($brlevel && ($t = shift(@in))) {
          if    (Equals($t, T_BEGIN)) { $brlevel++; }
          elsif (Equals($t, T_END))   { $brlevel--; }
          push(@out, $t); } }
      elsif (Equals($t, T_LETTER('A'))) {
        DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\Alph'), $ctr));
        push(@out, T_CS('\theenum' . $level)); }
      elsif (Equals($t, T_LETTER('a'))) {
        DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\alph'), $ctr));
        push(@out, T_CS('\theenum' . $level)); }
      elsif (Equals($t, T_LETTER('I'))) {
        DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\Roman'), $ctr));
        push(@out, T_CS('\theenum' . $level)); }
      elsif (Equals($t, T_LETTER('i'))) {
        DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\roman'), $ctr));
        push(@out, T_CS('\theenum' . $level)); }
      elsif (Equals($t, T_LETTER('1'))) {
        DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\arabic'), $ctr));
        push(@out, T_CS('\theenum' . $level)); }
      else {
        push(@out, $t); } }
    DefMacroI('\labelenum' . $level, undef, Tokens(T_BEGIN, @out, T_END)); }
  return; }

# id, but NO refnum (et.al) attributes on itemize \item ...
# unless the optional tag argument was given!
# We'll make the <ltx:tag> from either the optional arg, or from \labelitemi..
DefConstructor('\itemize@item OptionalUndigested',
  "<ltx:item xml:id='#id' ?#1(refnum='#refnum') itemsep='#itemsep'>"
    . "?&defined(#frefnum)(<ltx:tag>#frefnum</ltx:tag>)(?&defined(#refnum)(<ltx:tag>#refnum</ltx:tag>))",
  properties => sub { RefStepItemCounter($_[1]); });
DefConstructor('\inline@itemize@item OptionalUndigested',
  "<ltx:inline-item xml:id='#id' ?#1(refnum='#refnum')>"
    . "?&defined(#frefnum)(<ltx:tag>#frefnum</ltx:tag>)(?&defined(#refnum)(<ltx:tag>#refnum</ltx:tag>))",
  properties => sub { RefStepItemCounter($_[1]); });

DefConstructor('\enumerate@item OptionalUndigested',
  "<ltx:item xml:id='#id' refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum' itemsep='#itemsep'>"
    . "?&defined(#frefnum)(<ltx:tag>#frefnum</ltx:tag>)(?&defined(#refnum)(<ltx:tag>#refnum</ltx:tag>))",
  properties => sub { RefStepItemCounter($_[1]); });
DefConstructor('\inline@enumerate@item OptionalUndigested',
  "<ltx:inline-item xml:id='#id' refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum'>"
    . "?&defined(#frefnum)(<ltx:tag>#frefnum</ltx:tag>)(?&defined(#refnum)(<ltx:tag>#refnum</ltx:tag>))",
  properties => sub { RefStepItemCounter($_[1]); });

DefConstructor('\description@item OptionalUndigested',
  "<ltx:item xml:id='#id' refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum' itemsep='#itemsep'>"
    . "?&defined(#frefnum)(<ltx:tag>#frefnum</ltx:tag>)(?&defined(#refnum)(<ltx:tag>#refnum</ltx:tag>))",
  properties => sub { RefStepItemCounter($_[1]); });
DefConstructor('\inline@description@item OptionalUndigested',
  "<ltx:inline-item xml:id='#id'>"
    . "?&defined(#frefnum)(<ltx:tag>#frefnum</ltx:tag>)(?&defined(#refnum)(<ltx:tag>#refnum</ltx:tag>))",
  properties => sub { RefStepItemCounter($_[1]); });

DefEnvironment('{itemize}',
  "<ltx:itemize xml:id='#id'>#body</ltx:itemize>",
  properties => sub { beginItemize('itemize', '@@item'); },
  locked => 1, mode => 'text');
DefEnvironment('{enumerate}',
  "<ltx:enumerate  xml:id='#id'>#body</ltx:enumerate>",
  properties => sub { beginItemize('enumerate', 'enum'); },
  locked => 1, mode => 'text');
DefEnvironment('{description}',
  "<ltx:description  xml:id='#id'>#body</ltx:description>",
  beforeDigest => sub { Let('\makelabel', '\descriptionlabel'); },
  properties => sub { beginItemize('description', '@@desc'); },
  locked => 1, mode => 'text');

DefMacro('\makelabel{}', '#1');

# Basic enumeration bits
DefMacroI('\labelenumi',   undef, '\theenumi.');
DefMacroI('\labelenumii',  undef, '(\theenumii)');
DefMacroI('\labelenumiii', undef, '\theenumiii.');
DefMacroI('\labelenumiv',  undef, '\theenumiv.');
# These hookup latexml's numbering to normal latex's
DefMacroI('\fnum@enumi',   undef, '{\makelabel{\labelenumi}}');
DefMacroI('\fnum@enumii',  undef, '{\makelabel{\labelenumii}}');
DefMacroI('\fnum@enumiii', undef, '{\makelabel{\labelenumiii}}');
DefMacroI('\fnum@enumiv',  undef, '{\makelabel{\labelenumiv}}');
# You _could_ define \refnum@enumi, etc, to specify how to refer to the item!

# Basic itemize bits
DefMacroI('\labelitemi',   undef, '\textbullet');
DefMacroI('\labelitemii',  undef, '\normalfont\bfseries \textendash');
DefMacroI('\labelitemiii', undef, '\textasteriskcentered');
DefMacroI('\labelitemiv',  undef, '\textperiodcentered');
# These really aren't normal latex counters, but we use them for ids
NewCounter('@@itemi',   '@itemizei',   idprefix => 'i');
NewCounter('@@itemii',  '@itemizeii',  idprefix => 'i');
NewCounter('@@itemiii', '@itemizeiii', idprefix => 'i');
NewCounter('@@itemiv',  '@itemizeiv',  idprefix => 'i');
NewCounter('@@itemv',   '@itemizev',   idprefix => 'i');
NewCounter('@@itemvi',  '@itemizevi',  idprefix => 'i');
# These hookup latexml's numbering to normal latex's
DefMacroI('\fnum@@@itemi',   undef, '{\makelabel{\labelitemi}}');
DefMacroI('\fnum@@@itemii',  undef, '{\makelabel{\labelitemii}}');
DefMacroI('\fnum@@@itemiii', undef, '{\makelabel{\labelitemiii}}');
DefMacroI('\fnum@@@itemiv',  undef, '{\makelabel{\labelitemiv}}');

# Basic description list bits
DefMacro('\descriptionlabel{}', '\normalfont\bfseries #1');
# These really aren't normal latex counters, but we use them for ids
NewCounter('@@desci',   '@itemizei',   idprefix => 'i');
NewCounter('@@descii',  '@itemizeii',  idprefix => 'i');
NewCounter('@@desciii', '@itemizeiii', idprefix => 'i');
NewCounter('@@desciv',  '@itemizeiv',  idprefix => 'i');
NewCounter('@@descv',   '@itemizev',   idprefix => 'i');
NewCounter('@@descvi',  '@itemizevi',  idprefix => 'i');
# These hookup latexml's numbering to normal latex's
DefMacroI('\fnum@@@desci',   undef, '{\descriptionlabel{}}');
DefMacroI('\fnum@@@descii',  undef, '{\descriptionlabel{}}');
DefMacroI('\fnum@@@desciii', undef, '{\descriptionlabel{}}');
DefMacroI('\fnum@@@desciv',  undef, '{\descriptionlabel{}}');

#======================================================================
# C.6.3 The list and trivlist environments.
#======================================================================
# Generic lists are given a way to format the item label, and presumably
# a counter.

DefConditional('\if@nmbrlist');
DefMacro('\@listctr', '');
DefPrimitive('\usecounter{}', sub {
    my ($stomach, $counter) = @_;
    $counter = ToString(Expand($counter));
    beginItemize('list', $counter, ($counter ? 1 : 0));
    return; });

DefMacro('\list{}{}',
'\let\@listctr\@empty#2\ifx\@listctr\@empty\usecounter{}\fi\expandafter\def\csname fnum@\@listctr\endcsname{#1}\lx@list');
DefMacro('\endlist', '\endlx@list');

DefConstructor('\lx@list DigestedBody',
  "<ltx:itemize>#1</ltx:itemize>",
  beforeDigest => sub { $_[0]->bgroup; });
DefPrimitive('\endlx@list', sub { $_[0]->egroup; });

DefConstructor('\list@item OptionalUndigested',
  "<ltx:item xml:id='#id' refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum' itemsep='#itemsep'>"
    . "?&defined(#frefnum)(<ltx:tag>#frefnum</ltx:tag>)(?&defined(#refnum)(<ltx:tag>#refnum</ltx:tag>))",
  properties => sub { RefStepItemCounter($_[1]); });

# This isn't quite right, although it seems right for deep, internal uses with a single \item.
# Perhaps we need to check trivlist's afterwards and if they are just a single item,
# reduce it to an ltx:p ??
# DefMacro('\trivlist@item[]', '');
# DefEnvironment('{trivlist}',
#   '<ltx:p>#body</ltx:p>',
#   beforeDigest => sub { Let('\item', '\trivlist@item'); });

DefEnvironment('{trivlist}',
  "<ltx:itemize>#body</ltx:itemize>",
  properties => sub { beginItemize('trivlist'); });

DefConstructor('\trivlist@item OptionalUndigested',
  "<ltx:item xml:id='#id' refnum='#refnum' itemsep='#itemsep'>"
    . "<ltx:tag>#refnum</ltx:tag>",    # At least an empty tag!
  properties => sub { ($_[1] ? (refnum => Digest(Expand($_[1]))) : ()); });

DefRegister('\topsep'             => Glue(0));
DefRegister('\partopsep'          => Glue(0));
DefRegister('\lx@default@itemsep' => Glue(0));
DefRegister('\itemsep'            => Glue(0));
DefRegister('\parsep'             => Glue(0));
DefRegister('\@topsep'            => Glue(0));
DefRegister('\@topsepadd'         => Glue(0));
DefRegister('\@outerparskip'      => Glue(0));
DefRegister('\leftmargin'         => Dimension(0));
DefRegister('\rightmargin'        => Dimension(0));
DefRegister('\listparindent'      => Dimension(0));
DefRegister('\itemindent'         => Dimension(0));
DefRegister('\labelwidth'         => Dimension(0));
DefRegister('\labelsep'           => Dimension(0));
DefRegister('\@totalleftmargin'   => Dimension(0));
DefRegister('\leftmargini'        => Dimension(0));
DefRegister('\leftmarginii'       => Dimension(0));
DefRegister('\leftmarginiii'      => Dimension(0));
DefRegister('\leftmarginiv'       => Dimension(0));
DefRegister('\leftmarginv'        => Dimension(0));
DefRegister('\leftmarginvi'       => Dimension(0));
DefRegister('\@listdepth'         => Number(0));
DefRegister('\@itempenalty'       => Number(0));
DefRegister('\@beginparpenalty'   => Number(0));
DefRegister('\@endparpenalty'     => Number(0));
DefRegister('\labelwidthi'        => Dimension(0));
DefRegister('\labelwidthii'       => Dimension(0));
DefRegister('\labelwidthiii'      => Dimension(0));
DefRegister('\labelwidthiv'       => Dimension(0));
DefRegister('\labelwidthv'        => Dimension(0));
DefRegister('\labelwidthvi'       => Dimension(0));

DefRegister('\@itemdepth' => Number(0));

#======================================================================
# C.6.4 Verbatim
#======================================================================

# NOTE: how's the best way to get verbatim material through?
DefEnvironment('{verbatim}',  '<ltx:verbatim>#body</ltx:verbatim>');
DefEnvironment('{verbatim*}', '<ltx:verbatim>#body</ltx:verbatim>');
Let('\@verbatim', '\verbatim');    # Close enough?
# verbatim is a bit of special case;
# It looks like an environment, but it only ends with an explicit "\end{verbatim}" on it's own line.
# So, we'll end up doing things more manually.
# We're going to sidestep the Gullet for inputting,
# and also the usual environment capture.
DefConstructorI(T_CS('\begin{verbatim}'), undef,
  "<ltx:verbatim font='#font'>#body</ltx:verbatim>",
  beforeDigest => [sub { $_[0]->bgroup;
      AssignValue(current_environment => 'verbatim');
      DefMacroI('\@currenv', undef, 'verbatim');
      MergeFont(family => 'typewriter');
      # Digest(T_CS('\par')); # NO! See beforeConstruct!
      }],
  afterDigest => [sub {
      my ($stomach, $whatsit) = @_;
      $stomach->egroup;
      my $font   = $whatsit->getFont;
      my $loc    = $whatsit->getLocator;
      my $end    = "\\end{verbatim}";
      my @lines  = ();
      my $gullet = $stomach->getGullet;
      while (defined(my $line = $gullet->readRawLine)) {

        if ($line =~ /^(.*?)\\end\{verbatim\}(.*?)$/) {
          push(@lines, $1 . "\n"); $gullet->unread(Tokenize($2), T_CR);
          last; }
        push(@lines, $line . "\n"); }
      pop(@lines) if $lines[-1] eq "\n";
      # Note last line ends up as Whatsit's "trailer"
      $whatsit->setBody(map { Box($_, $font, $loc, T_OTHER($_)) } @lines, $end);
      return; }],
  beforeConstruct => sub { $_[0]->maybeCloseElement('ltx:p'); });

DefPrimitiveI('\@vobeyspaces', undef, sub {
    AssignCatcode(" " => 13);
    Let(T_ACTIVE(" "), '\nobreakspace');
    return });

# WARNING: Need to be careful about what catcodes are active here
DefMacroI('\verb', undef, sub {
    my ($gullet) = @_;
    my $mouth = $gullet->getMouth;
    my ($init, $body);
    StartSemiverbatim('%', '\\', '{', '}');
    $init = $mouth->readToken;
    $init = $mouth->readToken if ToString($init) eq '*';    # Should I bother handling \verb* ?

    if (!$init) {    # typically something read too far got \verb and the content is somewhere else..?
      Error('expected', 'delimiter', $gullet,
        "Verbatim argument lost", "Bindings for preceding code is probably broken");
      EndSemiverbatim();
      return (); }
    $body = $mouth->readTokens($init);
    EndSemiverbatim();
    Invocation((LookupValue('IN_MATH') ? T_CS('\@math@verb') : T_CS('\@text@verb')),
      Tokens($init), $body)->unlist; });

DefConstructor('\@text@verb{}{}', "<ltx:verbatim font='#font'>#2</ltx:verbatim>",
  beforeDigest => [sub { $_[0]->bgroup; MergeFont(family => 'typewriter'); }],
  afterDigest => sub { $_[0]->egroup; },
  # Since ltx:verbatim is both inline & block, we have to fudge inline mode
  beforeConstruct => sub {
    $_[0]->canContain($_[0]->getElement, '#PCDATA')
      || $_[0]->openElement('ltx:p'); },
  reversion => '\verb#1#2#1');
DefConstructor('\@math@verb{}{}', "#2",    # Will already end up wrapped as XMTok!
  beforeDigest => [sub { $_[0]->bgroup; MergeFont(family => 'typewriter'); }],
  afterDigest => sub { $_[0]->egroup; },
  reversion => '\verb#1#2#1');

# This is defined by the alltt package.
# Environment('alltt', ?);

# Actually, latex sets catcode to 13 ... is this close enough?
DefPrimitiveI('\obeycr',    undef, sub { AssignValue('PRESERVE_NEWLINES' => 1); });
DefPrimitiveI('\restorecr', undef, sub { AssignValue('PRESERVE_NEWLINES' => 0); });

DefMacroI('\normalsfcodes', undef, Tokens());

#**********************************************************************
# C.7 Mathematical Formulas
#**********************************************************************

#======================================================================
# C.7.1 Math Mode Environments
#======================================================================
DefMacroI('\@eqnnum', undef, '(\theequation)');
DefMacro('\fnum@equation',   '\@eqnnum');
DefMacro('\refnum@equation', '\@eqnnum');

# Redefined from TeX.pool, since with LaTeX we presumably have a more complete numbering system
DefConstructorI('\@@BEGINDISPLAYMATH', undef,
  "<ltx:equation xml:id='#id'>"
    . "<ltx:Math mode='display'>"
    . "<ltx:XMath>"
    . "#body"
    . "</ltx:XMath>"
    . "</ltx:Math>"
    . "</ltx:equation>",
  alias        => '$$',
  beforeDigest => sub { $_[0]->beginMode('display_math'); },
  properties   => sub { RefStepID('equation') },
  captureBody  => 1);

DefEnvironment('{displaymath}',
  "<ltx:equation xml:id='#id'>"
    . "<ltx:Math mode='display'>"
    . "<ltx:XMath>"
    . "#body"
    . "</ltx:XMath>"
    . "</ltx:Math>"
    . "</ltx:equation>",
  mode       => 'display_math',
  properties => sub { RefStepID('equation') },
  locked     => 1);
DefEnvironment('{math}',
  "<ltx:Math mode='inline'>"
    . "<ltx:XMath>"
    . "#body"
    . "</ltx:XMath>"
    . "</ltx:Math>",
  mode => 'inline_math',
);
# My first inclination is to Lock {math}, but it is surprisingly common to redefine it in silly ways... So...?

DefEnvironment('{equation}',
  "<ltx:equation xml:id='#id' refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum'>"
    . "<ltx:Math mode='display'>"
    . "<ltx:XMath>"
    . "#body"
    . "</ltx:XMath>"
    . "</ltx:Math>"
    . "</ltx:equation>",
  mode       => 'display_math',
  properties => sub { RefStepCounter('equation') },
  locked     => 1);
DefEnvironment('{equation*}',
  "<ltx:equation xml:id='#id'>"
    . "<ltx:Math mode='display'>"
    . "<ltx:XMath>"
    . "#body"
    . "</ltx:XMath>"
    . "</ltx:Math>"
    . "</ltx:equation>",
  mode       => 'display_math',
  properties => sub { RefStepID('equation') },
  locked     => 1);

# Define \( ..\) and \[ ... \] to act like environments.
# I would have thought these should be locked, but it seems relatively common to
# redefine them as \left[ \right] and \left( \right) !
DefConstructorI('\[', undef,
  "<ltx:equation xml:id='#id'>"
    . "<ltx:Math mode='display'>"
    . "<ltx:XMath>"
    . "#body"
    . "</ltx:XMath>"
    . "</ltx:Math>"
    . "</ltx:equation>",
  beforeDigest => sub { $_[0]->beginMode('display_math'); },
  captureBody  => 1,
  properties   => sub { RefStepID('equation') });
DefConstructorI('\]', undef, "", beforeDigest => sub { $_[0]->endMode('display_math'); });
DefConstructorI('\(', undef,
  "<ltx:Math mode='inline'>"
    . "<ltx:XMath>"
    . "#body"
    . "</ltx:XMath>"
    . "</ltx:Math>",
  beforeDigest => sub { $_[0]->beginMode('inline_math'); },
  captureBody => 1);
DefConstructorI('\)', undef, "", beforeDigest => sub { $_[0]->endMode('inline_math'); });

# Keep from expanding too early, if in alignments, or such.
DefMacroI('\ensuremath', undef,
  Tokens(T_CS('\protect'), T_CS('\@ensuremath')));
DefMacro('\@ensuremath{}', sub {
    my ($gullet, $stuff) = @_;
    if (LookupValue('IN_MATH')) { $stuff->unlist; }
    else { (T_MATH, $stuff->unlist, T_MATH); } });

# Magic check that math-mode trigger follows
our $MATHENVS = 'math|displaymath|equation*?|eqnarray*?'
  . '|multline*?|align*?|falign*?|alignat*?|xalignat*?|xxalignat*?|gather*?';
DefMacro('\ensuremathfollows', sub {
    my ($gullet) = @_;
    $gullet->closeMouth unless ($gullet->getMouth->hasMoreInput);
    if (my $tok = $gullet->readToken()) {
      my $csname = $tok->getCSName;
      if ($csname eq '\begin') {
        my $arg = $gullet->readArg();
        $csname = $arg->toString;
        $gullet->unread(T_BEGIN, $arg->unlist, T_END);
      }
      $gullet->unread($tok);
      # We need to determine whether the TeX we're given needs to be wrapped in \(...\)
      # Does it have $'s around it? Does it have a display math environment?
      if ($csname !~ /^Math|\\\(|\\\[|(?:$MATHENVS)/o) {
        AssignValue('automath_triggered' => 1, 'global');
        return T_CS('\\('); } }
    return;
});

DefMacro('\ensuremathpreceeds', sub {
    return LookupValue('automath_triggered') ? T_CS('\\)') : ();
});

# Since the arXMLiv folks keep wanting ids on all math, let's try this!
Tag('ltx:Math', afterOpen => sub { GenerateID(@_, 'm'); });

# ========================================
# eqnarray, etal
# Tricky! There's a conflict between a math-level alignment (which
# intermingles non-math things like labels, refnums, intertext),
# and a text-level alignment (which fragments the math's logical structure).
# Our solution is to attempt to synthesize a logical structure of
# an equationgroup containing equations, but using MathFork structures
# to hide the intended aligmnents.
# Then, XSLT can be used in the end to either display in a logical
# (but non-aligned format), or display fully aligned (but hiding the semantics).
#======================================================================
# Equation Groups
#   <equationgroup> representing aligned collections of equations
#   in particular, eqnarray and amsmath's align, ...
# The intended usage is a sequence of patterns like
#     LHS & REL & RHS
# When the LHS is empty, it likely implies a continuation of the previous (or multirelation).
# When both the LHS & REL are empty, it likely implies a continuation of the previous RHS
#======================================================================
# The strategy here is to use the alignment mechanism to construct
# an initial form:
#  <equationgroup>   as the overall container
#    <equation>      for each row; this can receive id, refnum & label attributes
#      <_Capture_>   to capture each columns math.
# After the initial construction (but before any rewriting & parsing)
# we scan through the equations combining the ones that appear (heuristically)
# to be single equations on several rows (either as multi-relations, or
# rhs's of multiple rows)
#  The combinations are represented by a MathFork container which
# holds both the apparently meaningful complete equation, along with
# the rows & columns that show the desired alignment.
# Each of those columns contains a <Math>, thus these can also be parsed,
# and converted to MathML or images.
#
# Thus, both forms are present: A presentation-oriented stylesheet can
# thus represent the eqnarray by a table with aligned math chunks.
# A content-oriented stylesheet can select the composed meaningful pieces.

# ========================================
# The following set deal with numbering the equations (rows) that make up an equationgroup.
# Note EQUATIONGROUP_NUMBER controls the numbering of the equations contained within the equationgroup,
# not the numbering of the equationgroup itself.
DefPrimitiveI('\@equationgroup@number', undef, sub { AssignValue(EQUATIONGROUP_NUMBER => '_AUTO_'); });
DefPrimitiveI('\@equationgroup@nonumber', undef, sub { AssignValue(EQUATIONGROUP_NUMBER => 0); });
DefPrimitiveI('\nonumber', undef, sub { AssignValue(EQUATIONROW_NUMBER => 0, 'global'); });

# Alternate versions of alignment open/close@row that deal with numbering.
# Depending on how an eqnarray ends, we might end up with an empty equation, which will be deleted.
# If non-empty, we'll set an id, and perhaps a refnum.
DefPrimitiveI('\@equationgroup@close@row', undef, sub { $_[0]->egroup; });
DefConstructor('\@equationgroup@open@row SkipSpaces DigestedBody',
  "",
  reversion => '#1',
  beforeDigest => sub { $_[0]->bgroup;
    LookupValue('Alignment')->newRow;
    AssignValue(EQUATIONROW_NUMBER
        => LookupValue('EQUATIONGROUP_NUMBER'), 'global');
    return; },
  afterDigest => sub {
    my ($stomach, $whatsit) = @_;
    #  if($whatsit->getArg(1)->unlist){ # If non-empty row, possibly we'll number
    # The idea is to avoid numbering an empty rows.... or should we be avoiding the empty rows!?!?!
    # Some confusion here; some cases latex DOES give you empty, numbered rows (end of ams' align),
    # Others, it ignores the empty line (\\ optional/ignorable before ams' \intertext)
    # So, I suspect we should unconditionally do this branch, but work harder at avoiding
    # empty rows in the first place.
    if (1) {
      my $num   = LookupValue('EQUATIONROW_NUMBER');
      my %props = ();
      if    (!$num)            { %props = RefStepID('equation'); }
      elsif ($num eq '_AUTO_') { %props = RefStepCounter('equation'); }
      else { %props = RefStepID('equation');
        $props{refnum} = $num; }
      # GACK: Store the refnum & id in the alignment's row structure!
      # [wouldn't it be nicer to be using Whatsits inside the alignment?]
      my $row = LookupValue('Alignment')->currentRow;
      $$row{id}      = $props{id};
      $$row{refnum}  = $props{refnum};
      $$row{frefnum} = $props{frefnum};
      $$row{rrefnum} = $props{rrefnum}; }
    return; });

# ========================================
# Some special kinds of rows...
DefConditionalI('\if@in@firstcolumn', undef, sub {
    my $x = LookupValue('Alignment');
    $x = ($x ? $x->currentColumnNumber : 9);
    $x < 2; });
# A bit more defensiveness, since people abuse eqnarray so badly.
# Eg. &&\lefteqn{...}  whatever?!?!
DefMacro('\lefteqn{}',
'\if@in@firstcolumn\multicolumn{3}{l}{\@ADDCLASS{ltx_eqn_lefteqn}\@@BEGININLINEMATH \displaystyle #1\@@ENDINLINEMATH\mbox{}}'
    . '\else\rlap{\@@BEGININLINEMATH\displaystyle #1\@@ENDINLINEMATH}\fi');
# \intertext (in amsmath)

# ========================================
# eqnarray
DefMacroI('\eqnarray', undef,
  '\@eqnarray@bindings\@@eqnarray'
    . '\@equationgroup@number\@start@alignment',
  locked => 1);
DefMacroI('\endeqnarray', undef,
  '\@finish@alignment\end@eqnarray',
  locked => 1);
DefMacro('\csname eqnarray*\endcsname',
  '\@eqnarray@bindings\@@eqnarray\@equationgroup@nonumber\@start@alignment',
  locked => 1);
DefMacro('\csname endeqnarray*\endcsname',
  '\@finish@alignment\end@eqnarray',
  locked => 1);

DefPrimitive('\@eqnarray@bindings', sub {
    eqnarrayBindings(); });

sub eqnarrayBindings {
  my $col1 = { before => Tokens(T_CS('\hfil'), T_MATH, T_CS('\@hidden@bgroup'), T_CS('\displaystyle')),
    after => Tokens(T_CS('\@hidden@egroup'), T_MATH) };
  my $col2 = { before => Tokens(T_CS('\hfil'), T_MATH, T_CS('\@hidden@bgroup'), T_CS('\displaystyle')),
    after => Tokens(T_CS('\@hidden@egroup'), T_MATH, T_CS('\hfil')) };
  my $col3 = { before => Tokens(T_MATH, T_CS('\@hidden@bgroup'), T_CS('\displaystyle')),
    after => Tokens(T_CS('\@hidden@egroup'), T_MATH, T_CS('\hfil')) };

  my %attributes = (
    'class'  => 'ltx_eqn_eqnarray',
    'colsep' => LookupDimension('\arraycolsep')->multiply(2));
  my $cur_jot = LookupDimension('\jot');
  if ($cur_jot && ($cur_jot->valueOf != LookupDimension('\lx@default@jot')->valueOf)) {
    $attributes{rowsep} = $cur_jot; }

  AssignValue(Alignment => LaTeXML::Core::Alignment->new(
      template => LaTeXML::Core::Alignment::Template->new(columns => [$col1, $col2, $col3]),
      openContainer => sub { my %attr = RefStepID('@equationgroup');
        $attr{'xml:id'} = $attr{id}; delete $attr{id};
        $attr{class} = 'ltx_eqn_eqnarray';
        $_[0]->openElement('ltx:equationgroup', %attr, @_[1 .. $#_]); },
      closeContainer => sub { $_[0]->closeElement('ltx:equationgroup'); },
      openRow        => sub { $_[0]->openElement('ltx:equation', @_[1 .. $#_]); },
      closeRow       => sub { $_[0]->closeElement('ltx:equation'); },
      openColumn     => sub { $_[0]->openElement('ltx:_Capture_', @_[1 .. $#_]); },
      closeColumn    => sub { $_[0]->closeElement('ltx:_Capture_'); },
      properties => { preserve_structure => 1, attributes => {%attributes} }));

  Let(T_ALIGN,       '\@alignment@align');
  Let("\\\\",        '\@alignment@newline');
  Let('\cr',         '\@alignment@cr');
  Let('\@open@row',  '\@equationgroup@open@row');
  Let('\@close@row', '\@equationgroup@close@row');
  return; }

DefConstructor('\@@eqnarray SkipSpaces DigestedBody',
  '#1',
  beforeDigest => sub { $_[0]->bgroup; },
  afterConstruct => sub { rearrangeEqnarray($_[0], $_[0]->getNode->lastChild); });
DefPrimitiveI('\end@eqnarray', undef, sub { $_[0]->egroup; });

# ========================================
# Some tools for analyzing the equationgroup after we've constructed it.
# ========================================

# ========================================
# For eqnarray, the "meaningful" unit will be at least one row,
# but often multiple rows.
# When the 1st column is empty, the row is assumed to continue the previous equation (if any!)
# When the 2nd column is also empty, it presumably continues the previous RHS.
# We'll combine these cases into a single equation, but remember the alignment structure.
# However, if more than 1 such row has refnums, we probably don't want to combine;
# But we really need to find a better way of representing the information!
# Note that there are common misuses of eqnarray;
# One type tries to get the equivalent of amsmath's gather environment by
# using a single column for the equations; the equations are right, centered or
# left aligned, depending on which column was used.
# Can we detect continuations, and can we distinguish continuations of equations vs. RHS?
# Probably a similar misuse where only 2 columns are used?
sub rearrangeEqnarray {
  my ($document, $equationgroup) = @_;
  # Scan the "equations" (rows) within the $equationgroup
  # to see what pattern of columns are present.
  my @rows = ();
  foreach my $rownode ($document->findnodes('ltx:equation', $equationgroup)) {
    my @cells = $document->findnodes('ltx:_Capture_', $rownode);    # representing each column.
    push(@rows, { node => $rownode, cols => [@cells],
        L => ($cells[0] && $cells[0]->hasChildNodes),
        M => ($cells[1] && $cells[1]->hasChildNodes),
        R => ($cells[2] && $cells[2]->hasChildNodes),
        numbered => $rownode->hasAttribute('refnum'),
        labelled => $rownode->hasAttribute('label') }); }
  my $nL = scalar(grep { $$_{L} } @rows);
  my $nM = scalar(grep { $$_{M} } @rows);
  my $nR = scalar(grep { $$_{R} } @rows);

  # Only a single column was used.  Remove the empty ones.
  # A heuristic: if any rows begin with a relation, there are probably continuations,
  # but maybe we don't want to try to distinguish the kinds?
  if (($nL && !$nM && !$nR)    # All left column
    || (!$nL && $nM  && !$nR)      # All center column
    || (!$nL && !$nM && $nR)) {    # All right column
                                   # We REALLY should remove the empty columns entirely!
    my $keepcol = ($nL ? 0 : ($nM ? 1 : 2));
    # REMOVE empty columns!
    foreach my $c (2, 1, 0) {
      next if $c == $keepcol;
      foreach my $row (@rows) {
        $document->removeNode($$row{cols}[$c]);
        splice(@{ $$row{cols} }, $c, 1); } }
    # If (some) columns begin with a relation, presumably a single equation?
    # Or maybe we should only connect those to previous row?
    my $t;
    if (grep { ($t = $$_{cols}[0]) && ($t = $document->getFirstChildElement($t))
          && (($t->getAttribute('role') || '') eq 'RELOP') } @rows) {
      equationgroupJoinRows($document, $equationgroup, map { $$_{node} } @rows); }
    else {
      # Really, these shouldn't even end up with MathFork!
      foreach my $row (@rows) {
        equationgroupJoinCols($document, 1, $$row{node}); } }
    return; }
  # What about case where only TWO columns get used? Worth analyzing?
  # Remaining case is when all 3 columns are used.
  my @eqs      = ();
  my $numbered = 0;
  my $oddness  = 0;
  foreach my $row (@rows) {
    my $class = 'unknown';
    if ($$row{L}) {    # 1st column non-empty; Presumably a new "equation"
      $class = 'new'; }
    elsif ($$row{M}) {    # 1st is empty & 2nd non-empty (Rel?); Probably continues
      if (!scalar(@eqs)) {    # But if no previous equation?
        $class = 'odd'; }
      elsif ($numbered && $$row{numbered}) {    # Separately numbered continuation?
        $class = 'new'; }                       # Keep it as separate equation, even though missing LHS
      else {
        $class = 'continue'; } }                # Continues as multiequation
    else {                                      # 1st & 2nd is empty
      if (!scalar(@eqs)) {                      # But if no previous equation?
        $class = 'odd'; }
      elsif ($numbered && $$row{numbered}       # Separately numbered AND labeled?
        && $$row{labelled}) {
        $class = 'odd'; }                       # must keep separate, but weird!
      else {
        $class = 'continue'; } }                # Else, continues RHS.

    if (($class eq 'new') || ($class eq 'odd')) {
      $numbered = $$row{numbered};
      push(@eqs, [$$row{node}]);
      $oddness++ if $class eq 'odd'; }
    else {
      $numbered |= $$row{numbered};
      push(@{ $eqs[-1] }, $$row{node}); }
  }

  Warn('unexpected', 'eqnarray', $equationgroup,
    "Unrecognized equation patterns ($oddness) in eqnarray")
    if (scalar(@rows) > 1) && $oddness;
  # Now rearrange things appropriately.
  foreach my $eqset (@eqs) {
    equationgroupJoinRows($document, $equationgroup, @$eqset); }
  return; }

# Style Parameters
#  \abovedisplayskip \abovedisplayshortskip, \jot are in TeX.pool
DefRegister('\mathindent' => Dimension(0));

#======================================================================
# C.7.2 Common Structures
#======================================================================
# sub, superscript and prime are in TeX.pool
# Underlying support in TeX.pool.ltxml
DefConstructor('\frac InFractionStyle InFractionStyle',
  "<ltx:XMApp>"
    . "<ltx:XMTok meaning='divide' role='FRACOP' mathstyle='#mathstyle'/>"
    . "<ltx:XMArg>#1</ltx:XMArg><ltx:XMArg>#2</ltx:XMArg>"
    . "</ltx:XMApp>",
  sizer => sub { fracSizer($_[0]->getArg(1), $_[0]->getArg(2)); },
  properties => { mathstyle => sub { LookupValue('font')->getMathstyle; } });

# Ellipsis: See TeX.pool

#======================================================================
# C.7.3 Mathematical Symbols
#======================================================================
# See Tables 3.3 through 3.8 (pp 41--44)
# Defined in TeX.pool
# [Possibly some are strictly LaTeX and should be moved here?]

#======================================================================
# C.7.4 Arrays
#======================================================================
#  See Section C.10.2

#======================================================================-
# C.7.5 Delimiters
#======================================================================-
# All this is already in TeX.pool

DefConstructor('\stackrel{}{}',
  "<ltx:XMApp role='RELOP'>"
    . "<ltx:XMTok role='SUPERSCRIPTOP' scriptpos='#scriptpos'/>"
    . "<ltx:XMArg>#2</ltx:XMArg>"
    . "<ltx:XMArg>#1</ltx:XMArg>"
    . "</ltx:XMApp>",
  properties => { scriptpos => sub { "mid" . $_[0]->getBoxingLevel; } }
);
#======================================================================-
# C.7.6 Putting One Thing Above Another
#======================================================================-
# All this is already in TeX.pool

DefMacro('\skew{}{}{}', '');    # ? just some subtle spacing, right????
#======================================================================-
# C.7.7 Spacing
#======================================================================-
# All this is already in TeX.pool

#======================================================================
# C.7.8 Changing Style
#======================================================================
# For Math style changes, we record the current font, which is then merged
# into the Whatsit's created for letters, etc.  The merging depends on
# the type of letter, greek, symbol, etc.
# Apparently, with the normal TeX setup, these fonts don't really merge,
# rather they override all of family, series and shape.
DefConstructor('\mathrm{}', '#1', bounded => 1, requireMath => 1,
  font => { family => 'serif', series => 'medium', shape => 'upright' });
DefConstructor('\mathit{}', '#1', bounded => 1, requireMath => 1,
  font => { shape => 'italic', family => 'serif', series => 'medium' });
DefConstructor('\mathbf{}', '#1', bounded => 1, requireMath => 1,
  font => { series => 'bold', family => 'serif', shape => 'upright' });
DefConstructor('\mathsf{}', '#1', bounded => 1, requireMath => 1,
  font => { family => 'sansserif', series => 'medium', shape => 'upright' });
DefConstructor('\mathtt{}', '#1', bounded => 1, requireMath => 1,
  font => { family => 'typewriter', series => 'medium', shape => 'upright' });
DefConstructor('\mathcal{}', '#1', bounded => 1, requireMath => 1,
  font => { family => 'caligraphic', series => 'medium', shape => 'upright' });
DefConstructor('\mathscr{}', '#1', bounded => 1, requireMath => 1,
  font => { family => 'script', series => 'medium', shape => 'upright' });
DefConstructor('\mathnormal{}', '#1', bounded => 1, requireMath => 1,
  font => { family => 'math', shape => 'italic', series => 'medium' });

DefMacroI('\fontsubfuzz',  undef, '.4pt');
DefMacroI('\oldstylenums', undef, Tokens());

DefPrimitiveI('\operator@font', undef, undef,
  font => { family => 'serif', series => 'medium', shape => 'upright' });

#**********************************************************************
# C.8 Definitions, Numbering and Programming
#**********************************************************************

#======================================================================
# C.8.1 Defining Commands
#======================================================================

DefMacro('\@tabacckludge {}', '\csname\string#1\endcsname');

DefPrimitive('\newcommand OptionalMatch:* DefToken [Number][]{}', sub {
    my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
    if (!isDefinable($cs)) {
      Info('ignore', $cs, $stomach,
        "Ignoring redefinition (\\newcommand) of '" . Stringify($cs) . "'")
        unless LookupValue(ToString($cs) . ':locked');
      return; }
    DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });

DefPrimitive('\CheckCommand OptionalMatch:* DefToken [Number][]{}', undef);

DefPrimitive('\renewcommand OptionalMatch:* DefToken [Number][]{}', sub {
    my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
    DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });

DefPrimitive('\providecommand OptionalMatch:* DefToken [Number][]{}', sub {
    my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
    return unless isDefinable($cs);
    DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });

# Need to figure out exactly what `robust' means to LaTeXML...
DefPrimitive('\DeclareRobustCommand OptionalMatch:* DefToken [Number][]{}', sub {
    my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
    DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });

# There are a bunch of ways in LaTeX to assign a command to
# a particular point within a font (which has one of many encodings)
# Since we have no practical way of knowing what that point is,
# and we really want to create unicode, we just ignore this stuff (but warn).
sub ignoredDefinition {
  my ($stomach, $command, $cs) = @_;
  #  Warn('ignore',$cs,$stomach,"ignoring ".ToString($command)." definition of ".ToString($cs));
  return; }

#------------------------------------------------------------
# The following commands define encoding-specific expansions
# or glyphs.  The control-sequence is defined to use the expansion for
# the current encoding, if any, or the default expansion (for encoding "?").
# We don't want to redefine control-sequence if it already has a definition:
# It may be that we've already defined it to expand into the above conditional.
# But more importantly, we don't want to override a hand-written definition (if any).
#------------------------------------------------------------
DefPrimitive('\DeclareTextCommand DefToken {}[Number][]{}', sub {
    my ($gullet, $cs, $encoding, $nargs, $opt, $expansion) = @_;
    my $css = ToString($cs);
    $encoding = ToString(Expand($encoding));
    if (!IsDefined($cs)) {    # If not already defined...
      DefMacroI($cs, undef,
'\expandafter\ifx\csname\cf@encoding\string' . $css . '\endcsname\relax\csname?\string' . $css . '\endcsname'
          . '\else\csname\cf@encoding\string' . $css . '\endcsname\fi'); }
    my $ecs = T_CS('\\' . $encoding . $css);
    DefMacroI($ecs, convertLaTeXArgs($nargs, $opt), $expansion);
    return; });

DefMacro('\DeclareTextCommandDefault DefToken', '\DeclareTextCommand{#1}{?}');

DefPrimitive('\ProvideTextCommand DefToken {}[Number][]{}', sub {
    my ($gullet, $cs, $encoding, $nargs, $opt, $expansion) = @_;
    my $css = ToString($cs);
    $encoding = ToString(Expand($encoding));
    if (isDefinable($cs)) {    # If not already defined...
      DefMacroI($cs, undef,
'\expandafter\ifx\csname\cf@encoding\string' . $css . '\endcsname\relax\csname?\string' . $css . '\endcsname'
          . '\else\csname\cf@encoding\string' . $css . '\endcsname\fi'); }
    my $ecs = T_CS('\\' . $encoding . $css);
    if (!IsDefined($ecs)) {    # If not already defined...
      DefMacroI($ecs, convertLaTeXArgs($nargs, $opt), $expansion); }
    return; });

DefMacro('\ProvideTextCommandDefault DefToken', '\ProvideTextCommand{#1}{?}');

#------------------------------------------------------------

DefPrimitive('\DeclareTextSymbol DefToken {}{Number}', sub {
    my ($gullet, $cs, $encoding, $code) = @_;
    $code = $code->valueOf;
    my $css = ToString($cs);
    $encoding = ToString(Expand($encoding));
    if (isDefinable($cs)) {    # If not already defined...
      DefMacroI($cs, undef,
'\expandafter\ifx\csname\cf@encoding\string' . $css . '\endcsname\relax\csname?\string' . $css . '\endcsname'
          . '\else\csname\cf@encoding\string' . $css . '\endcsname\fi'); }
    my $ecs = T_CS('\\' . $encoding . $css);
    DefPrimitiveI($ecs, undef, FontDecode($code, $encoding));
    return; });

# hmmm... what needs doing here; basically it means use this encoding as the default for the symbol
DefMacro('\DeclareTextSymbolDefault DefToken {}', '');    # '\DeclareTextSymbol{#1}{?}');

#------------------------------------------------------------
DefPrimitive('\DeclareTextAccent DefToken {}{}', sub {
    ignoredDefinition('DeclareTextAccent', $_[1]); });
DefPrimitive('\DeclareTextAccentDefault{}{}',
  sub { ignoredDefinition('DeclareTextAccentDefault', $_[1]); });

#------------------------------------------------------------
DefPrimitive('\DeclareTextComposite{}{}{}{}',
  sub { ignoredDefinition('DeclareTextComposite', $_[1]); });
DefPrimitive('\DeclareTextCompositeCommand{}{}{}{}',
  sub { ignoredDefinition('DeclareTextCompositeCommand', $_[1]); });

DefPrimitive('\UndeclareTextCommand{}{}', undef);
DefMacro('\UseTextSymbol{}{}', '{\fontencoding{#1}#2}');
DefMacro('\UseTextAccent{}{}', '{\fontencoding{#1}#2{#3}}');

DefPrimitive('\DeclareMathAccent{}{}{}{}',
  sub { ignoredDefinition('DeclareMathAccent', $_[1]); });
DefPrimitive('\DeclareMathDelimiter{}{}{}{}',
  sub { ignoredDefinition('DeclareMathAccent', $_[1]); });
DefPrimitive('\DeclareMathRadical{}{}{}{}{}',
  sub { ignoredDefinition('DeclareMathAccent', $_[1]); });
DefPrimitive('\DeclareMathVersion{}',          undef);
DefPrimitive('\DeclarePreloadSizes{}{}{}{}{}', undef);

# The next font declaration commands are based on
# http://tex.loria.fr/general/new/fntguide.html
# we ignore font encoding
DefPrimitive('\DeclareSymbolFont{}{}{}{}{}', sub {
    my ($stomach, $name, $enc, $family, $series, $shape) = @_;
    AssignValue('fontdeclaration@' . ToString($name),
      { family => ToString($family),
        series => ToString($series),
        shape  => ToString($shape) }); });
DefPrimitive('\DeclareSymbolFontAlphabet{}{}', sub {
    my ($stomach, $cs, $name) = @_;
    my $font = LookupValue('fontdeclarations@' . ToString($name)) || {};
    DefPrimitiveI(T_CS(ToString($cs)), undef, undef, font => $font); });

DefPrimitive('\DeclareMathSizes{}{}{}{}', undef);
DefPrimitive('\DeclareMathAlphabet{}{}{}{}{}', sub {
    my ($stomach, $cs, $enc, $family, $series, $shape) = @_;
    my %font = LaTeXML::Common::Font::lookupTeXFont($family, $series, $shape);
    DefPrimitiveI(T_CS(ToString($cs)), undef, undef, font => {%font}); });

DefMacro('\newmathalphabet{}{}{}', Tokens());    # or expand int DeclareMathAlphabet?
DefPrimitive('\DeclareFontShape{}{}{}{}{}{}', undef);
DefPrimitive('\DeclareFontFamily{}{}{}',      undef);
DefPrimitive('\DeclareSizeFunction{}{}',      undef);

DefPrimitive('\DeclareMathSymbol{}{}{}{}',
  sub { ignoredDefinition('DeclareMathSymbol', $_[1]); });

DefPrimitive('\DeclareFixedFont{}{}{}{}{}{}', undef);
DefPrimitive('\DeclareErrorFont{}{}{}{}{}',   undef);

DefMacroI('\cdp@list', undef, '\@empty');
Let('\cdp@elt', '\relax');
DefPrimitive('\DeclareFontEncoding{}{}{}', sub {
    my ($stomach, $encoding, $x, $y) = @_;
    AddToMacro(T_CS('\cdp@list'), T_CS('\cdp@elt'),
      T_BEGIN, $_[1]->unlist, T_END,
      T_BEGIN, T_CS('\default@family'), T_END,
      T_BEGIN, T_CS('\default@series'), T_END,
      T_BEGIN, T_CS('\default@shape'),  T_END);
    my $e = Expand($encoding);
    DefMacroI('\LastDeclaredEncoding', undef, $e);
    DefMacroI('\T@' . ToString($e),    undef, $x);
    DefMacroI('\M@' . ToString($e), undef, Tokens(T_CS('\default@M'), $y->unlist));
});
DefMacroI('\LastDeclaredEncoding', undef, '');
DefPrimitive('\DeclareFontSubstitution{}{}{}{}', undef);
DefPrimitive('\DeclareFontEncodingDefaults{}{}', undef);
DefMacroI('\LastDeclaredEncoding', undef, Tokens());

DefPrimitive('\SetSymbolFont{}{}{}{}{}{}',   undef);
DefPrimitive('\SetMathAlphabet{}{}{}{}{}{}', undef);
DefPrimitive('\addtoversion{}{}',            undef);
DefPrimitive('\TextSymbolUnavailable{}',     undef);

#======================================================================
# C.8.2 Defining Environments
#======================================================================
# Note that \env & \endenv defined by \newenvironment CAN be
# invoked directly.

DefPrimitive('\newenvironment OptionalMatch:* {}[Number][]{}{}', sub {
    my ($stomach, $star, $name, $nargs, $opt, $begin, $end) = @_;
    $name = ToString(Digest($name));
    if (IsDefined(T_CS("\\$name"))) {
      Info('ignore', $name, $stomach,
        "Ignoring redefinition (\\newenvironment) of Environment '$name'")
        unless (LookupValue('\\' . $name . ':locked')
        || LookupValue('\\begin{' . $name . '}:locked'));
      return; }
    DefMacroI(T_CS("\\$name"), convertLaTeXArgs($nargs, $opt), $begin);
    DefMacroI(T_CS("\\end$name"), undef, $end); });

DefPrimitive('\renewenvironment OptionalMatch:* {}[Number][]{}{}', sub {
    my ($stomach, $star, $name, $nargs, $opt, $begin, $end) = @_;
    $name = ToString(Digest($name));
    DefMacroI(T_CS("\\$name"), convertLaTeXArgs($nargs, $opt), $begin);
    DefMacroI(T_CS("\\end$name"), undef, $end); });

#======================================================================
# C.8.3 Theorem-like Environments
#======================================================================
# The core non-customizable part is defined here.
# For customizable theorems, see amsthm.
AssignValue('thm@swap' => 'N');
DefRegister('\thm@style'        => Tokenize('plain'));
DefRegister('\thm@headfont'     => Tokens(T_CS('\bfseries')));
DefRegister('\thm@bodyfont'     => Tokens(T_CS('\itshape')));
DefRegister('\thm@headpunct'    => Tokens());
DefRegister('\thm@styling'      => Tokens());
DefRegister('\thm@headstyling'  => Tokens());
DefRegister('\thm@prework'      => Tokens());
DefRegister('\thm@postwork'     => Tokens());
DefRegister('\thm@symbol'       => Tokens());
DefRegister('\thm@numbering'    => Tokens(T_OTHER('arabic')));
DefRegister('\thm@headformater' => undef);
#  AssignValue('\thm@headfont' => T_CS('\bfseries'));
#  AssignValue('\thm@bodyfont' => T_CS('\itshape'));

DefPrimitive('\th@plain', sub {
    #  AssignValue('\thm@headfont' => T_CS('\bfseries'));
    AssignValue('\thm@bodyfont' => T_CS('\itshape'));
    #  AssignValue('\thm@headpunct'=> T_OTHER('.'));
    AssignValue('\thm@headstyling' => T_CS('\lx@makerunin'));
    return; });

DefMacroI('\lx@makerunin',   undef, '\@ADDCLASS{ltx_runin}');
DefMacroI('\lx@makeoutdent', undef, '\@ADDCLASS{ltx_outdent}');

DefMacroI('\@thmcountersep', undef, '.');
DefMacroI('\thm@doendmark',  undef, '');

DefMacro('\newtheorem OptionalMatch:* {}[]{}[]', sub {
    my ($stomach, $flag, $thmset, $otherthmset, $type, $reset) = @_;
    defineNewTheorem($stomach, $flag, $thmset, $otherthmset, $type, $reset);
    # Reset these!
    DefRegister('\thm@prework'  => Tokens());
    DefRegister('\thm@postwork' => Tokens()); },
  locked => 1);

# Some packages may want to add '\thm@headfont'
AssignValue(THEOREM_PARAMETERS => ['\thm@bodyfont', '\thm@headpunct',
    '\thm@styling', '\thm@headstyling',
    'thm@swap']);
###RawTeX('\AtBeginDocument{\th@plain}');           # Activate the default style.
RawTeX('\th@plain');    # Activate the default style.
# [or just make it's values the defaults...]

sub defineNewTheorem {
  my ($stomach, $flag, $thmset, $otherthmset, $type, $reset) = @_;
  $thmset      = ToString($thmset);
  $otherthmset = $otherthmset && ToString($otherthmset);
  $type        = undef unless $type->unlist;
  $reset       = $reset ? ToString($reset) : undef;

  my $counter = $otherthmset || $thmset;
  $counter =~ s/\*$//;    # Strip trailing * off theoremclass for ntheorem.sty
  my $style     = ToString(LookupValue('\thm@style'));        # The name of the style
  my $numbering = ToString(LookupValue('\thm@numbering'));    # The number style macro
  $flag = 1 unless $numbering;
  if (!$otherthmset) {
    my $idprefix = "Thm$counter";
    $idprefix =~ s/\*/./g;

    if (!LookupValue('\c@' . $counter)) {                     # if isn't already defined...
      NewCounter($counter, $reset, idprefix => $idprefix); }
    DefMacroI(T_CS("\\the$counter"), undef,
      ($reset
        ? "\\csname the" . $reset . "\\endcsname\\\@thmcountersep\\" . $numbering . "{" . $counter . "}"
        : "\\" . $numbering . "{" . $counter . "}"),
      scope => 'global') if $numbering; }

  my @parameters = @{ LookupValue('THEOREM_PARAMETERS') };
  AssignValue('THEOREM_' . $thmset . '_PARAMETERS'
      => { map { ($_ => LookupValue($_)) } @parameters }, 'global');

  my $thmname = '\\' . $thmset . 'name';
  DefMacroI($thmname, undef, $type, scope => 'global');
  my $swap = LookupValue('thm@swap') eq 'S';

  DefMacroI('\fnum@' . $thmset, undef,
    Tokens($flag || !$counter
      ? (T_CS($thmname))
      : ($swap
        ? (T_CS('\the' . $counter), ($type ? (T_SPACE) : ()), T_CS($thmname))
        : (T_CS($thmname), ($type ? (T_SPACE) : ()), T_CS('\the' . $counter)))),
    scope => 'global');
  # amsthm allows you to define your own title formatter
  # if there was one defined, use it, else define a new one.
  my $headformatter = LookupValue('\thm@headformatter');
  DefMacroI('\format@title@' . $thmset, convertLaTeXArgs(1, 0),
    ($headformatter
      ? Tokens($headformatter->unlist, T_BEGIN, ($type ? $type->unlist : ()), T_END,
        T_CS('\the' . $counter), T_BEGIN, Tokens(T_PARAM, T_OTHER('1')), T_END,
        T_CS('\the'), T_CS('\thm@headpunct'))
      : '\@tag{\csname fnum@' . $thmset . '\endcsname}'
        . ($type ? '\ifx.#1.\else\space(#1)\fi' : '#1') . '\the\thm@headpunct'),
    scope => 'global');
  DefEnvironment("{$thmset} OptionalUndigested",
    "<ltx:theorem xml:id='#id' class='#class'"
      . " refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum'>"
      . "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
      . "#body"
      . "</ltx:theorem>",
    beforeDigest => sub {
      my $params = LookupValue('THEOREM_' . $thmset . '_PARAMETERS');
      foreach my $v (keys %$params) {
        AssignValue($v => $$params{$v}); }
      Digest('\normalfont\the\thm@prework'); },
    afterDigestBegin => sub { Digest('\the\thm@bodyfont\the\thm@styling'); },
    beforeDigestEnd  => sub { Digest('\thm@doendmark'); },
    afterDigest      => sub { Digest('\the\thm@postwork'); },
    properties       => sub {
      my %ctr = ($counter ? ($flag ? RefStepID($counter) : RefStepCounter($counter)) : ());
      my $title = Digest(Tokens(T_BEGIN,
          T_CS('\the'), T_CS('\thm@headfont'),
          T_CS('\the'), T_CS('\thm@headstyling'),
          T_CS('\format@title@' . $thmset),
          T_BEGIN, ($_[1] ? $_[1]->unlist : ()), T_END,
          T_END));
      (%ctr,
        frefnum   => Digest(Tokens(T_CS('\fnum@' . $thmset))),
        title     => $title,
        titlefont => $title->getFont,
        class     => 'ltx_theorem_' . CleanClassName($thmset),
        ); },
    scope => 'global');
  return; }

#======================================================================
# C.8.4 Numbering
#======================================================================
# For LaTeX documents, We want id's on para, as well as sectional units.
# However, para get created implicitly on Document construction, rather than
# explicitly during digestion (via a whatsit), we can't use the usual LaTeX counter mechanism.
Tag('ltx:para', afterOpen => sub { GenerateID(@_, 'p'); });

DefPrimitive('\newcounter{}[]', sub {
    NewCounter(ToString(Expand($_[1])), $_[2] && ToString(Expand($_[2])));
    return; });
DefPrimitive('\setcounter{}{Number}', sub { SetCounter(ToString(Expand($_[1])), $_[2]); });
DefPrimitive('\addtocounter{}{Number}', sub { AddToCounter(ToString(Expand($_[1])), $_[2]); });
DefPrimitive('\stepcounter{}',    sub { StepCounter(ToString(Expand($_[1])));    return; });
DefPrimitive('\refstepcounter{}', sub { RefStepCounter(ToString(Expand($_[1]))); return; });

DefPrimitive('\@addtoreset{}{}', sub {
    my ($stomach, $ctr, $within) = @_;
    $ctr    = ToString(Expand($ctr));
    $within = ToString(Expand($within));
    my $unctr = "UN$ctr";    # UNctr is counter for generating ID's for UN-numbered items.
    AssignValue("\\cl\@$within" =>
        Tokens(T_CS($ctr), T_CS($unctr),
        (LookupValue("\\cl\@$within") ? LookupValue("\\cl\@$within")->unlist : ())),
      'global');
    # This counter might be doing double duty generating ID's as well, so we may need to patch up.
    my $prefix = LookupValue('@ID@prefix@' . $ctr);
    if (defined $prefix) {
      DefMacroI(T_CS("\\the$ctr\@ID"), undef,
        "\\expandafter\\ifx\\csname the$within\@ID\\endcsname\\\@empty"
          . "\\else\\csname the$within\@ID\\endcsname.\\fi"
          . " $prefix\\csname \@$ctr\@ID\\endcsname",
        scope => 'global');
      DefMacroI(T_CS("\\\@$ctr\@ID"), undef, "0", scope => 'global'); }
    return; });

DefMacro('\value{}', sub {
    ExplodeText(CounterValue(ToString(Expand($_[1])))->valueOf); });
DefMacro('\@arabic{Number}', sub {
    ExplodeText(ToString($_[1]->valueOf)); });
DefMacro('\arabic{}', sub {
    ExplodeText(CounterValue(ToString(Expand($_[1])))->valueOf); });
DefMacro('\@roman{Number}', sub {
    ExplodeText(radix_roman(ToString($_[1]->valueOf))); });
DefMacro('\roman{}', sub {
    ExplodeText(radix_roman(CounterValue(ToString(Expand($_[1])))->valueOf)); });
DefMacro('\@Roman{Number}', sub {
    ExplodeText(radix_Roman(ToString($_[1]->valueOf))); });
DefMacro('\Roman{}', sub {
    ExplodeText(radix_Roman(CounterValue(ToString(Expand($_[1])))->valueOf)); });
DefMacro('\@alph{Number}', sub {
    ExplodeText(radix_alpha($_[1]->valueOf)); });
DefMacro('\alph{}', sub {
    ExplodeText(radix_alpha(CounterValue(ToString(Expand($_[1])))->valueOf)); });
DefMacro('\@Alph{Number}', sub {
    ExplodeText(radix_Alpha($_[1]->valueOf)); });
DefMacro('\Alph{}', sub {
    ExplodeText(radix_Alpha(CounterValue(ToString(Expand($_[1])))->valueOf)); });

our @fnsymbols = ("*", "\x{2020}", "\x{2021}", UTF(0xA7), UTF(0xB6),
  "\x{2225}", "**", "\x{2020}\x{2020}", "\x{2021}\x{2021}");
DefMacro('\@fnsymbol{Number}', sub {
    ExplodeText(radix_format($_[1]->valueOf, @fnsymbols)); });
DefMacro('\fnsymbol{}', sub {
    ExplodeText(radix_format(CounterValue(ToString(Expand($_[1])))->valueOf, @fnsymbols)); });

#======================================================================
# C.8.5 The ifthen Package.
#======================================================================
# \ifthenelse
# and sundry conditionals...
#
# Yeah, maybe this'll get done someday....

#**********************************************************************
# C.9 Figures and Other Floating Bodies
#**********************************************************************

#======================================================================
# C.9.1 Figures and Tables
#======================================================================

# Note that, the number is associated with the caption.
# (to allow multiple figures per figure environment?).
# Whatever reason, that causes complications: We can only increment
# counters with the caption, but then have to arrange for the counters,
# refnums, ids, get passed on to the figure, table when needed.
# AND, as soon as possible, since other items may base their id's on the id of the table!

## I'd rather not define these, since the \fnum@@ defn is better....
DefMacroI('\fnum@figure', undef, '\figurename\nobreakspace\thefigure');
DefMacroI('\fnum@table',  undef, '\tablename\nobreakspace\thetable');
DefMacro('\format@title@figure{}', '\@tag[][: ]{\fnum@figure}#1');
DefMacro('\format@title@table{}',  '\@tag[][: ]{\fnum@table}#1');

DefConditional('\iflx@donecaption');
DefMacro('\caption',
'\lx@donecaptiontrue\@ifundefined{@captype}{\@@generic@caption}{\expandafter\@caption\expandafter{\@captype}}');
DefMacro('\@caption{}[]{}',
  '\@@add@caption@counters'
    . '\@@toccaption{\format@toctitle@{#1}{\ifx.#2.#3\else#2\fi}}'
    . '\@@caption{\format@title@{#1}{#3}}');

# Note that the counters only get incremented by \caption, NOT by \table, \figure, etc.
DefPrimitive('\@@add@caption@counters', sub {
    my $captype = ToString(Digest(T_CS('\@captype')));
    my %props   = RefStepCounter($captype);
    AssignValue($captype . "_refnum"  => $props{refnum},  'global');       # global??
    AssignValue($captype . "_frefnum" => $props{frefnum}, 'global');
    AssignValue($captype . "_rrefnum" => $props{rrefnum}, 'global');
    AssignValue($captype . "_id"      => $props{id},      'global'); });

sub RescueCaptionCounters {
  my ($captype, $whatsit) = @_;
  if (my $refnum = LookupValue($captype . "_refnum")) {
    AssignValue($captype . "_refnum" => undef, 'global');
    $whatsit->setProperty(refnum => $refnum); }
  if (my $frefnum = LookupValue($captype . "_frefnum")) {
    AssignValue($captype . "_frefnum" => undef, 'global');
    $whatsit->setProperty(frefnum => $frefnum); }
  if (my $rrefnum = LookupValue($captype . "_rrefnum")) {
    AssignValue($captype . "_rrefnum" => undef, 'global');
    $whatsit->setProperty(rrefnum => $rrefnum); }
  if (my $id = LookupValue($captype . "_id")) {
    AssignValue($captype . "_id" => undef, 'global');
    $whatsit->setProperty(id => $id); }
  return; }

DefConstructor('\@@generic@caption[]{}', "<ltx:text class='ltx_caption'>#2</ltx:text>",
  beforeDigest => sub {
    Error('unexpected', '\caption', $_[0],
      "Use of \\caption outside any known float"); });

# Note that even without \caption, we'd probably like to have xml:id.
Tag('ltx:figure', afterClose => sub { GenerateID(@_, 'fig'); });
Tag('ltx:table',  afterClose => sub { GenerateID(@_, 'tab'); });
Tag('ltx:float',  afterClose => sub { GenerateID(@_, 'tab'); });

# These may need to float up to where they're allowed,
# or they may need to close <p> or similar.
DefConstructor('\@@caption{}', "^^<ltx:caption>#1</ltx:caption>");
DefConstructor('\@@toccaption{}', "^^<ltx:toccaption>#1</ltx:toccaption>",
  sizer => '0');

DefEnvironment('{figure}[]',
"<ltx:figure refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum' xml:id='#id' ?#1(placement='#1')>"
    . "#body"
    . "</ltx:figure>",
  properties => { layout => 'vertical' },
  beforeDigest => sub { DefMacroI('\@captype', undef, 'figure'); },
  afterDigest => sub { RescueCaptionCounters('figure', $_[1]); });
DefEnvironment('{figure*}[]',
"<ltx:figure refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum' xml:id='#id' ?#1(placement='#1')>"
    . "#body"
    . "</ltx:figure>",
  properties => { layout => 'vertical' },
  beforeDigest => sub { DefMacroI('\@captype', undef, 'figure'); },
  afterDigest => sub { RescueCaptionCounters('figure', $_[1]); });
DefEnvironment('{table}[]',
"<ltx:table refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum' xml:id='#id' ?#1(placement='#1')>"
    . "#body"
    . "</ltx:table>",
  properties => { layout => 'vertical' },
  beforeDigest => sub { DefMacroI('\@captype', undef, 'table'); },
  afterDigest => sub { RescueCaptionCounters('table', $_[1]); });
DefEnvironment('{table*}[]',
"<ltx:table refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum' xml:id='#id' ?#1(placement='#1')>"
    . "#body"
    . "</ltx:table>",
  properties => { layout => 'vertical' },
  beforeDigest => sub { DefMacroI('\@captype', undef, 'table'); },
  afterDigest => sub { RescueCaptionCounters('table', $_[1]); });

# There are some floating figure/table packages that define environments
# to allow the figure to float left or right.  It APPEARS that they allow
# authors to either wrap with {figure} or NOT, as they like!
# The result apparently makes sense so long as they only use one \caption.
# If we get a figure containing a single figure, we want to collaps it.
sub collapseFloat {
  my ($document, $float) = @_;
  my $qname = $document->getNodeQName($float);
  my @inners = $document->findnodes($qname, $float);
  if (scalar(@inners) == 1) {    # Only 1 inner float of same type
    my $inner    = $inners[0];
    my $ocaption = $document->findnodes('ltx:caption', $float);
    my $icaption = $document->findnodes('ltx:caption', $inner);
    if (!($ocaption && $icaption)) {    # If they don't both have captions.
      foreach my $attr (map { $_->nodeName } $inner->attributes) {
        next if $attr eq 'xml:id';
        $document->setAttribute($float, $attr => $inner->getAttribute($attr)); }
      if ($icaption) {
        if (my $id = $inner->getAttribute('xml:id')) {
          $document->unRecordID($id);
          $document->setAttribute($float, 'xml:id' => $id); } }

      # Finally, replace $inner by it's children!
      # Note that since we can only append new stuff, we've got to remove the following first.
      my @save = ();
      my $following;
      while (($following = $float->lastChild) && ($$following != $$inner)) { # Remove & Save following siblings.
        unshift(@save, $float->removeChild($following)); }
      unshift(@save, $inner->childNodes);
      $float->removeChild($inner);
      map { $float->appendChild($_) } @save; }                               # Put these back.
  }
  return; }
Tag('ltx:figure', afterClose => \&collapseFloat);
Tag('ltx:table',  afterClose => \&collapseFloat);
Tag('ltx:float',  afterClose => \&collapseFloat);

DefPrimitive('\flushbottom',      undef);
DefPrimitive('\suppressfloats[]', undef);

NewCounter('topnumber');
DefMacroI('\topfraction', undef, "0.25");
NewCounter('bottomnumber');
DefMacroI('\bottomfraction', undef, "0.25");
NewCounter('totalnumber');
DefMacroI('\textfraction',      undef, "0.25");
DefMacroI('\floatpagefraction', undef, "0.25");
NewCounter('dbltopnumber');
DefMacroI('\dbltopfraction',       undef, "0.7");
DefMacroI('\dblfloatpagefraction', undef, "0.25");
DefRegister('\floatsep'        => Glue(0));
DefRegister('\textfloatsep'    => Glue(0));
DefRegister('\intextsep'       => Glue(0));
DefRegister('\dblfloatsep'     => Glue(0));
DefRegister('\dbltextfloatsep' => Glue(0));
DefRegister('\@maxsep'         => Dimension(0));
DefRegister('\@dblmaxsep'      => Dimension(0));
DefRegister('\@fptop'          => Glue(0));
DefRegister('\@fpsep'          => Glue(0));
DefRegister('\@fpbot'          => Glue(0));
DefRegister('\@dblfptop'       => Glue(0));
DefRegister('\@dblfpsep'       => Glue(0));
DefRegister('\@dblfpbot'       => Glue(0));
Let('\topfigrule', '\relax');
Let('\botfigrule', '\relax');
Let('\dblfigrule', '\relax');

DefMacroI('\figurename',  undef, 'Figure');
DefMacroI('\figuresname', undef, 'Figures');    # Never used?
DefMacroI('\tablename',   undef, 'Table');
DefMacroI('\tablesname',  undef, 'Tables');

#======================================================================
# C.9.2 Marginal Notes
#======================================================================

DefConstructor('\marginpar[]{}', "<ltx:note role='margin'>#2</ltx:note>");
DefPrimitiveI('\reversemarginpar', undef, undef);
DefPrimitiveI('\normalmarginpar',  undef, undef);
DefRegister('\marginparpush', Dimension(0));

#**********************************************************************
# C.10 Lining It Up in Columns
#**********************************************************************

#======================================================================
# C.10.1 The tabbing Environment
#======================================================================
DefRegister('\tabbingsep' => Dimension(0));

DefMacroI('\tabbing', undef,
  '\@tabbing@bindings\@@tabbing\@start@alignment');
DefMacroI('\endtabbing', undef,
  '\@finish@alignment\@end@tabbing');
DefPrimitiveI('\@end@tabbing', undef, sub { $_[0]->egroup; });
DefConstructor('\@@tabbing SkipSpaces DigestedBody',
  '#1',
  reversion    => '\begin{tabbing}#1\end{tabbing}',
  beforeDigest => sub { $_[0]->bgroup; },
  mode         => 'text');

DefMacroI('\@tabbing@tabset', undef,
  '\@close@inner@column\@close@column'
    . '\@tabbing@tabset@marker'
    . '\@open@column\@open@inner@column');
DefConstructorI('\@tabbing@tabset@marker', undef, '', reversion => '\=');
DefMacroI('\@tabbing@nexttab', undef,
  '\@close@inner@column.\@close@column'
    . '\@tabbing@nexttab@marker'
    . '\@open@column\@open@inner@column');
DefConstructorI('\@tabbing@nexttab@marker', undef, '', reversion => '\>');

DefMacro('\@tabbing@newline OptionalMatch:* [Dimension]',
  '\@close@inner@column\@close@column\@close@row'
    . '\@tabbing@newline@marker'
    . '\@open@row\@open@column\@open@inner@column\@tabbing@start@tabs');
DefConstructorI('\@tabbing@newline@marker', undef, '', reversion => Tokens(T_CS("\\\\"), T_CR));

DefMacroI('\@tabbing@kill', undef,
  '\@close@inner@column\@close@column\@close@row'
    . '\@tabbing@kill@marker'
    . '\@open@row\@open@column\@open@inner@column\@tabbing@start@tabs');
DefConstructorI('\@tabbing@kill@marker', undef, '', reversion => '\kill',
  afterDigest => sub { LookupValue('Alignment')->removeRow; return; });
AssignValue(tabbing_start_tabs => Tokens());
DefMacroI('\@tabbing@start@tabs', undef, sub { LookupValue('tabbing_start_tabs')->unlist; });
DefPrimitiveI('\@tabbing@increment', undef, sub {
    my @tabs = LookupValue('tabbing_start_tabs')->unlist;
    AssignValue(tabbing_start_tabs => Tokens(@tabs, T_CS('\>')), 'global'); });
DefPrimitiveI('\@tabbing@decrement', undef, sub {
    my ($ignore, @tabs) = LookupValue('tabbing_start_tabs')->unlist;
    AssignValue(tabbing_start_tabs => Tokens(@tabs), 'global'); });

# NOTE: \< is NOT currently handled!!!
# Ugh!! The way we're setting the initial tabs, we can't really handle this!
DefPrimitiveI('\@tabbing@untab', undef, undef);

# NOTE: \' and \` are NOT currently handled
DefPrimitiveI('\@tabbing@flushright', undef, undef);
DefPrimitiveI('\@tabbing@hfil',       undef, undef);
# NOTE: \pushtabs and \poptabs are NOT currently handled.
DefPrimitiveI('\@tabbing@pushtabs', undef, undef);
DefPrimitiveI('\@tabbing@poptabs',  undef, undef);

# Should there be some rowsep/colsep set here?
sub tabbingBindings {
  AssignValue(Alignment => LaTeXML::Core::Alignment->new(
      template => LaTeXML::Core::Alignment::Template->new(
        repeated => [{ after => Tokens(T_CS('\hfil')) }]),
      openContainer => sub { $_[0]->openElement('ltx:tabular', @_[1 .. $#_]); },
      closeContainer => sub { $_[0]->closeElement('ltx:tabular'); },
      openRow        => sub { $_[0]->openElement('ltx:tr', @_[1 .. $#_]); },
      closeRow       => sub { $_[0]->closeElement('ltx:tr'); },
      openColumn     => sub { $_[0]->openElement('ltx:td', @_[1 .. $#_]); },
      closeColumn    => sub { $_[0]->closeElement('ltx:td'); }));

  Let("\\=",       '\@tabbing@tabset');
  Let("\\>",       '\@tabbing@nexttab');
  Let("\\\\",      '\@tabbing@newline');
  Let('\kill',     '\@tabbing@kill');
  Let("\\+",       '\@tabbing@increment');
  Let("\\-",       '\@tabbing@decrement');
  Let("\\<",       '\@tabbing@untab');
  Let("\\'",       '\@tabbing@flushright');
  Let("\\`",       '\@tabbing@hfil');
  Let('\pushtabs', '\@tabbing@pushtabs');
  Let('\poptabs',  '\@tabbing@poptabs');
  #  Let('\a',        '\@tabbing@accent');
  Let('\@open@row',  '\default@open@row');
  Let('\@close@row', '\default@close@row');

  return; }

DefMacroI('\pushtabs', undef, Tokens());
DefMacroI('\poptabs',  undef, Tokens());
DefMacroI('\kill',     undef, Tokens());

DefPrimitiveI('\@tabbing@bindings', undef, sub {
    tabbingBindings(); });

# NOTE: Do it!!

#======================================================================
# C.10.2 The array and tabular Environments
#======================================================================
# Tabular are a bit tricky in that we have to arrange for tr and td to
# be openned and closed at the right times; the only real markup is
# the & and \\. Also \multicolumn has to be cooperative.
# Along with this, we have to track which column specification applies
# to the current column.
# To simulate LaTeX's tabular borders & hlines, we simply add border
# attributes to all cells.  For HTML, CSS will be necessary to display them.
# [We'll ignore HTML's frame, rules and colgroup mechanisms.]

DefRegister('\lx@default@tabcolsep', Dimension('6pt'));
DefRegister('\tabcolsep',            Dimension('6pt'));
DefMacroI('\arraystretch', undef, "1");

sub tabularBindings {
  my ($template, %properties) = @_;
  $properties{guess_headers} = 1 unless defined $properties{guess_headers};    # Defaults to yes
  if (!defined $properties{attributes}{colsep}) {
    my $sep = LookupDimension('\tabcolsep');
    if ($sep && ($sep->valueOf != LookupDimension('\lx@default@tabcolsep')->valueOf)) {
      $properties{attributes}{colsep} = $sep; } }
  if (!defined $properties{attributes}{rowsep}) {
    my $str = ToString(Expand(T_CS('\arraystretch')));
    if ($str != 1) {
      $properties{attributes}{rowsep} = Dimension(($str - 1) . 'em'); } }
  alignmentBindings($template, 'text', %properties);
  # Do like AddToMacro, but NOT global!
  foreach my $name ('@row@before', '@row@after', '@column@before', '@column@after') {
    my $cs = '\\' . $name;
    DefMacroI($cs, undef,
      Tokens(LookupDefinition(T_CS($cs))->getExpansion->unlist,
        T_CS('\@tabular' . $name))); }
  return; }

# Keyvals are for attributes for the alignment.
# Typical keys are width, vattach,...
DefKeyVal('tabular', 'width', 'Dimension');
DefPrimitive('\@tabular@bindings AlignmentTemplate OptionalKeyVals:tabular', sub {
    my ($stomach, $template, $attributes) = @_;
    my %attr = ($attributes ? $attributes->getPairs : ());
    if (my $va = $attr{vattach}) {
      $attr{vattach} = translateAttachment($va) || ToString($va); }
    tabularBindings($template, attributes => {%attr});
    return; });

DefMacroI('\@tabular@row@before',    undef, '');
DefMacroI('\@tabular@row@after',     undef, '');
DefMacroI('\@tabular@column@before', undef, '');
DefMacroI('\@tabular@column@after',  undef, '');

# The Core alignment support is in LaTeXML::Core::Alignment and in TeX.ltxml
##DefMacro('\tabular[]{}', '\@tabular@bindings{#2}\@@tabular[#1]{#2}\@start@alignment');
DefMacro('\tabular[]{}', '\@tabular@bindings{#2}[vattach=#1]\@@tabular[#1]{#2}\@start@alignment');
DefMacroI('\endtabular', undef, '\@finish@alignment\@end@tabular');
DefPrimitiveI('\@end@tabular', undef, sub { $_[0]->egroup; });
# Note that the pattern will already have been interpreted by \@tabular@bindings,
# so make it Undigested here!

DefConstructor('\@@tabular[] Undigested DigestedBody',
  '#3',
  reversion    => '\begin{tabular}[#1]{#2}#3\end{tabular}',
  beforeDigest => sub { $_[0]->bgroup; },
  sizer        => '#3',
  afterDigest  => sub {
    my ($stomach, $whatsit) = @_;
    my $attr = LookupValue('Alignment')->getProperty('attributes');
    $$attr{vattach} = translateAttachment($whatsit->getArg(1));
    return; },
  mode => 'text');

DefMacro('\csname tabular*\endcsname{Dimension}[]{}',
###  '\@tabular@bindings{#3}\@@tabular@{#1}[#2]{#3}\@start@alignment');
  '\@tabular@bindings{#3}[width=#1,vattach=#2]\@@tabular@{#1}[#2]{#3}\@start@alignment');
DefMacro('\csname endtabular*\endcsname',
  '\@finish@alignment\@end@tabular@');
DefConstructor('\@@tabular@{Dimension}[] Undigested DigestedBody',
  '#4',
  beforeDigest => sub { $_[0]->bgroup; },
  reversion    => '\begin{tabular*}{#1}[#2]{#3}#4\end{tabular*}',
  mode         => 'text');
DefPrimitive('\@end@tabular@', sub { $_[0]->egroup; });
Let('\multicolumn', '\@multicolumn');

DefPrimitiveI('\hline', undef, undef);    # Redefined inside tabular
# A weird bit that sometimes gets invoked by Cargo Cult programmers...
# to \noalign in the defn of \hline! Bizarre! (see latex.ltx)
# However, the really weird thing is the way this provides the } to close the argument
DefMacroI('\@xhline', undef, '\ifnum0=`{\fi}');

DefConstructor('\cline{}', '',
  afterDigest => sub {
    my ($stomach, $whatsit) = @_;
    my $cols = ToString($whatsit->getArg(1));
    my @cols = ();
    while ($cols =~ s/^,?(\d+)//) {
      my $n = $1;
      push(@cols, ($cols =~ s/^-(\d+)// ? ($n .. $1) : ($n))); }
    my $alignment = LookupValue('Alignment');
    $alignment->addLine('t', @cols) if $alignment;
    return; },
  properties => { isHorizontalRule => 1 });

DefConstructorI('\vline', undef, "",    # ???
  properties => { isVerticalRule => 1 },);
DefRegister('\lx@default@arraycolsep', Dimension('5pt'));
DefRegister('\arraycolsep',            Dimension('5pt'));
DefRegister('\arrayrulewidth',         Dimension('0.4pt'));
DefRegister('\doublerulesep',          Dimension('2pt'));
DefMacro('\extracolsep{}', Tokens());
DefMacroI('\tabularnewline', undef, Tokens());

#======================================================================
# Array and similar environments

DefPrimitive('\@array@bindings [] AlignmentTemplate', sub {
    my ($stomach, $pos, $template) = @_;
    my $attr = { vattach => translateAttachment($pos),
      role => 'ARRAY' };
    # Determine column and row separations, if non default
    my $colsep = LookupDimension('\arraycolsep');
    if ($colsep && ($colsep->valueOf != LookupDimension('\lx@default@arraycolsep')->valueOf)) {
      $$attr{colsep} = $colsep; }
    my $str = ToString(Expand(T_CS('\arraystretch')));
    if ($str != 1) {
      $$attr{rowsep} = Dimension(($str - 1) . 'em'); }
    alignmentBindings($template, 'math', attributes => $attr);
    return; });

DefMacro('\array[]{}',
  '\@array@bindings[#1]{#2}\@@array[#1]{#2}\@start@alignment');
DefMacroI('\endarray', undef,
  '\@finish@alignment\@end@array');
DefPrimitiveI('\@end@array', undef, sub { $_[0]->egroup; });
DefConstructor('\@@array[] Undigested DigestedBody',
  '#3',
  beforeDigest => sub { $_[0]->bgroup; },
  reversion => '\begin{array}[#1]{#2}#3\end{array}');

#**********************************************************************
# C.11 Moving Information Around
#**********************************************************************

#======================================================================
# C.11.1 Files
#======================================================================
DefPrimitive('\nofiles', undef);

#======================================================================
# C.11.2 Cross-References
#======================================================================

# \label attaches a label to the nearest parent that can accept a labels attribute
# but only those that have an xml:id (but should this require a refnum and/or title ???)
# Note that latex essentially allows redundant labels, but we can record only one!!!
DefConstructor('\label Semiverbatim', sub {
    my ($document, $olabel, %props) = @_;
    my $label = $props{label};
    if (my $savenode = $document->floatToLabel) {
      my $node = $document->getNode;
      my %labels = map { ($_ => 1) } $label, split(/\s+/, $node->getAttribute('labels') || '');
      $document->setAttribute($node, labels => join(' ', sort keys %labels));
      $document->setNode($savenode); } },
  reversion   => '',
  properties  => { alignmentSkippable => 1, alignmentPreserve => 1 },
  afterDigest => sub {
    my $label = CleanLabel(ToString($_[1]->getArg(1)));
    $_[1]->setProperty(label => $label);
    my $scope = $label; $scope =~ s/^LABEL:/label:/;
    if (my $ctr = LookupValue('current_counter')) {
      unshift(@{ LookupValue('scopes_for_counter:' . $ctr) }, $scope);
      $STATE->activateScope($scope);
      $_[0]->beginMode('text');
      AssignValue('LABEL@' . $label, Digest(T_CS('\@currentlabel')), 'global');
      $_[0]->endMode('text'); }
    return; });

# If a node has been labeled, but still  hasn't yet got an id by afterClose:late,
# we'd better generate an id for it.
Tag('ltx:*', 'afterClose:late' => sub {
    my ($document, $node) = @_;
    if ($node->hasAttribute('labels') && !($node->hasAttribute('xml:id'))) {
      GenerateID($document, $node); } });

# These will get filled in during postprocessing.
# * is added to accommodate hyperref
DefConstructor('\ref OptionalMatch:* Semiverbatim', "<ltx:ref labelref='#label' _force_font='true'/>",
  properties => sub { (label => CleanLabel($_[2])); });
DefConstructor('\pageref OptionalMatch:* Semiverbatim', "<ltx:ref labelref='#label' _force_font='true'/>", # Same??
  properties => sub { (label => CleanLabel($_[2])); });
#======================================================================
# C.11.3 Bibliography and Citation
#======================================================================

# Note that it's called \refname in LaTeX's article, but \bibname in report & book.
# And likewise, mixed up in various other classes!

DefMacroI('\thebibliography@ID', undef, Tokens());

# This sub does things that would commonly be needed when starting a bibliography
# setting the ID, etc...
sub beginBibliography {
  my ($whatsit) = @_;
  beginBibliography_clean($whatsit);
  # Fix for missing \bibitems!
  setupPseudoBibitem();
  return; }

sub beginBibliography_clean {
  my ($whatsit) = @_;
  # Try to compute a reasonable, but unique ID;
  # relative to the document's ID, if any.
  # But also, if there are multiple bibliographies,
  my $bibnumber = LookupValue('n_bibliographies') || 0;
  AssignValue(n_bibliographies => ++$bibnumber, 'global');
  my $docid = ToString(Expand(T_CS('\thedocument@ID')));
  my $bibid = ($docid ? $docid . '.' : '') . 'bib' . radix_alpha($bibnumber - 1);
  DefMacroI(T_CS('\thebibliography@ID'), undef, T_OTHER($bibid), scope => 'global');
  #  $whatsit->setProperty(id=>ToString(Expand(T_CS('\thebibliography@ID'))));
  $whatsit->setProperty(id => $bibid);
  my $title = DigestIf('\refname') || DigestIf('\bibname');
  $whatsit->setProperty(title     => $title)          if $title;
  $whatsit->setProperty(titlefont => $title->getFont) if $title;
  $whatsit->setProperty(bibstyle  => LookupValue('BIBSTYLE'));
  $whatsit->setProperty(citestyle => LookupValue('CITE_STYLE'));
  #  $whatsit->setProperty(sort=> ???
  # And prepare for the likely nonsense that appears within bibliographies
  ResetCounter('enumiv');
  return; }

DefMacro('\bibliography Semiverbatim', '\lx@ifusebbl{#1}{\input{\jobname.bbl}}{\@bibliography{#1}}');
DefMacro('\lx@ifusebbl{}{}{}', sub {
    my ($gullet, $bib_files, $bbl_clause, $bib_clause) = @_;
    $bib_files = ToString(Expand($bib_files));
    return unless $bib_files;
    my $jobname = ToString(Expand(T_CS('\jobname')));

    my $bbl_path = FindFile($jobname, type => 'bbl');
    my $missing_bibs = '';
    for my $bf (split(',', $bib_files)) {
      my $bib_path = FindFile($bf, type => 'bib');
      if (not $bib_path) {
        $missing_bibs .= ',' unless length($missing_bibs) == 0;
        $missing_bibs .= $bf; } }

    if (length($missing_bibs) == 0 or not $bbl_path) {
      return $bib_clause->unlist; }
    else {
      Info('expected', $missing_bibs, $_[0], "Couldn't find all bib files, using " . $jobname . ".bbl instead");
      return $bbl_clause->unlist; } });
DefConstructor('\@bibliography Semiverbatim',
  "<ltx:bibliography files='#1' xml:id='#id' "
    . "bibstyle='#bibstyle' citestyle='#citestyle' sort='#sort'>"
    . "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
    . "</ltx:bibliography>",
  afterDigest => sub { $_[0]->begingroup;    # wrapped so redefns don't take effect!
    beginBibliography($_[1]);
    $_[0]->endgroup; });

# NOTE: This totally needs to be made extensible (parsing *.bst!?!? OMG!)
our $BIBSTYLES = {
  plain    => { citestyle => 'numbers', sort => 'true' },
  unsrt    => { citestyle => 'numbers', sort => 'false' },
  alpha    => { citestyle => 'AY',      sort => 'true' },
  abbrv    => { citestyle => 'numbers', sort => 'true' },
  plainnat => { citestyle => 'numbers', sort => 'true' },
  unsrtnat => { citestyle => 'numbers', sort => 'false' },
  alphanat => { citestyle => 'AY',      sort => 'true' },
  abbrvnat => { citestyle => 'numbers', sort => 'true' } };

DefConstructor('\bibstyle{}', sub {
    my ($document, $style) = @_;
    $style = ToString($style);
    if (my $bib = $document->findnode('//ltx:bibliography')) {
      $document->setAttribute($bib, bibstyle => $style);
      if (my $parms = $$BIBSTYLES{$style}) {
        $document->setAttribute($bib, citestyle => $$parms{citestyle});
        $document->setAttribute($bib, sort      => $$parms{sort});
      } } },
  afterDigest => sub {
    my $style = ToString($_[1]->getArg(1));
    AssignValue(BIBSTYLE => $style, 'global');
    if (my $parms = $$BIBSTYLES{$style}) {
      AssignValue(CITE_STYLE => $$parms{citestyle}); }
    else {
      Info('unexpected', $style, $_[0], "Unknown bibstyle '$style', it will be ignored"); }
    return; });

DefMacro('\bibliographystyle{}', '\bibstyle{#1}');

# Should be an environment, but people seem to want to misuse it.
DefConstructorI('\thebibliography', undef,
  "<ltx:bibliography xml:id='#id'>"
    . "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
    . "<ltx:biblist>",
  beforeDigest => sub { AssignValue(inPreamble => 0); },
  afterDigest => sub {
    my ($stomach) = @_;
    # NOTE that in some perverse situations (revtex?)
    # it seems to be allowable to omit the argument
    # It's ignorable for latexml anyway, so we'll just read it if its there.
    my $gullet = $stomach->getGullet;
    $gullet->skipSpaces;
    $gullet->readArg if $gullet->ifNext(T_BEGIN);

    beginBibliography($_[1]); },
  locked => 1);

# Close the bibliography
DefConstructorI('\endthebibliography', undef,
  "</ltx:biblist></ltx:bibliography>",
  afterDigest => sub { my $t = T_CS('\@appendix');
    Digest($t) if IsDefined($t);
    return; },
  locked => 1);
# auto close the bibliography and contained biblist.
Tag('ltx:biblist',      autoClose => 1);
Tag('ltx:bibliography', autoClose => 1);

# Since SOME people seem to write bibliographies w/o \bibitem,
# just blank lines between apparent entries,
# Making \par do a \bibitem{} works, but screws up valid
# bibliographies with blank lines!
# So, let's do some redirection!
sub setupPseudoBibitem {
  Let('\save@bibitem', '\bibitem');
  Let('\save@par',     '\par');
  Let('\bibitem',      '\restoring@bibitem');
  Let('\par',          '\par@in@bibliography');
  # Moreover, some people use \item instead of \bibitem
  Let('\item', '\item@in@bibliography');
  return; }

DefMacroI('\par@in@bibliography', undef, sub {
    my ($gullet) = @_;
    $gullet->skipSpaces;
    my $tok = $gullet->readToken;
    # If next token is another \par, or a REAL \bibitem,
    if (Equals($tok, T_CS('\par')) || Equals($tok, T_CS('\bibitem'))) {
      ($tok); }    # then this \par expands into what followed
    else {         # Else, put it back, and start a bibitem.
      $gullet->unread($tok);
      (T_CS('\save@bibitem'), T_BEGIN, T_END); } });

DefMacroI('\item@in@bibliography', undef, '\save@bibitem{}');

# If we hit a real \bibitem, put \par & \bibitem back to correct defn, and then \bibitem.
# A bibitem with now key or label...
DefMacro('\restoring@bibitem',
  '\let\bibitem\save@bibitem\let\par\save@par\bibitem');

NewCounter('@bibitem', 'bibliography', idprefix => 'bib');
DefMacroI('\the@bibitem', undef, '\arabic{@bibitem}');
DefConstructor('\bibitem[] Semiverbatim',
  "<ltx:bibitem key='#key' xml:id='#id'>"
    . "<ltx:bibtag role='refnum'>#refnum</ltx:bibtag>"
    . "<ltx:bibblock>",
  afterDigest => sub {
    my $tag = $_[1]->getArg(1);
    my $key = CleanBibKey($_[1]->getArg(2));
    $_[1]->setProperties(key => $key,
      ($tag ? (RefStepID('@bibitem'), refnum => $tag) : RefStepCounter('@bibitem'))); });
DefConstructorI('\newblock', undef, "<ltx:bibblock>");
Tag('ltx:bibitem',  autoClose => 1);
Tag('ltx:bibblock', autoClose => 1);

#----------------------------------------------------------------------
# We've got the same problem as LaTeX: Lather, Rinse, Repeat.
# It would be nice to know the bib info at digestion time
#  * whether author lists will collapse
#  * whether there are "a","b".. extensions on the year.
# We could process the bibliography first, (IF it is a separate *.bib!)
# but won't know which entries are included (and so can't resolve the a/b/c..)
# until we've finished looking at (all of) the source(s) that will refer to them!
#
# We can do this in 2 passes, however
#  (1) convert (latexml) both the source document(s) and the bibliography
#  (2) extract the required bibitems and integrate (latexmlpost) it into the documents.
# [Note that for mult-document sites, step (2) becomes 2 stages: scan and integrate]
#
# Here's the general layout.
#   <ltx:cite> contains everything that the citations produce,
#     including parens, pre-note, punctunation that precede the <ltx:bibcite>
#     and punctuation, post-note, parens, that follow it.
#   <ltx:bibcite show="string" bibrefs="keys" sep="" yysep="">phrases</ltx:bibcite>
#     encodes the actual citation.
#
#     bibrefs : lists the bibliographic keys that will be used
#     show    : gives the pattern for formatting using data from the bibliography
#       It can contain:
#         authors or fullauthors
#         year
#         number
#         phrase1,phrase2,... selects one of the phrases from the content of the <ltx:bibref>
#     This format is used as follows:
#       If author and year is present, and a subset of the citations share the same authors,
#         then the format is used, but the year is repeated for each citation in the subset,
#         as a link to the bib entry.
#       Otherwise, the format is applied to each entry.
#
# The design is intended to support natbib, as well as plain LaTeX.

AssignValue(CITE_STYLE          => 'numbers');
AssignValue(CITE_OPEN           => T_OTHER('['));
AssignValue(CITE_CLOSE          => T_OTHER(']'));
AssignValue(CITE_SEPARATOR      => T_OTHER(','));
AssignValue(CITE_YY_SEPARATOR   => T_OTHER(','));
AssignValue(CITE_NOTE_SEPARATOR => T_OTHER(','));

DefConstructor('\@@cite []{}', "<ltx:cite ?#1(class='ltx_citemacro_#1')>#2</ltx:cite>",
  mode => 'text');

# \@@bibref{what to show}{bibkeys}{phrase1}{phrase2}
DefConstructor('\@@bibref Semiverbatim Semiverbatim {}{}',
  "<ltx:bibref show='#1' bibrefs='#bibrefs'"
    . " separator='#separator' yyseparator='#yyseparator'>#3#4</ltx:bibref>",
  properties => sub { (bibrefs => CleanBibKey($_[2]),
      separator   => ToString(Digest(LookupValue('CITE_SEPARATOR'))),
      yyseparator => ToString(Digest(LookupValue('CITE_YY_SEPARATOR')))); });

# Simple container for any phrases used in the bibref
DefConstructor('\@@citephrase{}', "<ltx:bibrefphrase>#1</ltx:bibrefphrase>",
  mode => 'text');

DefMacro('\cite[] Semiverbatim', sub {
    my ($gullet, $post, $keys) = @_;
    my ($style, $open, $close, $ns)
      = map { LookupValue($_) } qw(CITE_STYLE CITE_OPEN CITE_CLOSE CITE_NOTE_SEPARATOR);
    $post = undef unless $post && $post->unlist;
    Invocation(T_CS('\@@cite'),
      Tokens(Explode('cite')),
      Tokens($open,
        Invocation(T_CS('\@@bibref'), Tokens(Explode("Refnum")), $keys, undef, undef),
        ($post ? ($ns, T_SPACE, $post) : ()), $close)); });

# NOTE: Eventually needs to be recognized by MakeBibliography
DefConstructor('\nocite Semiverbatim',
  "<ltx:cite><ltx:bibref show='nothing' bibrefs='#bibrefs'/></ltx:cite>",
  properties => sub { (bibrefs => CleanBibKey($_[1])) });

#======================================================================
# C.11.4 Splitting the input
#======================================================================
Let('\@@input', '\input');    # Save TeX's version.
# LaTeX's \input is a bit different...
DefMacroI('\input', undef, '\@ifnextchar\bgroup\@iinput\@@input');
DefPrimitive('\@iinput {}', sub { Input(Expand($_[1])); });

# Note that even excluded files SHOULD have the effects of their inclusion
# simulated by having read the corresponding aux file;
# But we're not bothering with that.
DefPrimitive('\include{}', sub {
    my ($stomach, $path) = @_;
    $path = ToString($path);
    my $table = LookupValue('including@only');
    if (!$table || $$table{$path}) {
      Input($path); }
    return; });

# [note, this will load name.tex, if it exists, else name]
DefPrimitive('\includeonly{}', sub {
    my ($stomach, $paths) = @_;
    $paths = ToString($paths);
    my $table = LookupValue('including@only');
    AssignValue('including@only', $table = {}, 'global') unless $table;
    map { $$table{$_} = 1 } split(/,\s*/, $paths);
    return; });

# NOTE: In the long run, we want to SAVE the contents and associate them with the given file name
#  AND, arrange so that when a file is read, we'll use the contents!
DefConstructorI(T_CS("\\begin{filecontents}"), "Semiverbatim",
  '',
  reversion   => '',
  afterDigest => [sub {
      my ($stomach, $whatsit) = @_;
      my $filename = ToString($whatsit->getArg(1));
      my @lines    = ();
      my $gullet   = $stomach->getGullet;
      my $line;
      while (defined($line = $gullet->readRawLine) && ($line ne '\end{filecontents}')) {
        push(@lines, $line); }
      AssignValue($filename . '_contents' => join("\n", @lines), 'global');
      NoteProgress("[Cached filecontents for $filename (" . scalar(@lines) . " lines)]"); }]);
DefConstructorI(T_CS("\\begin{filecontents*}"), "Semiverbatim",
  '',
  reversion   => '',
  afterDigest => [sub {
      my ($stomach, $whatsit) = @_;
      my $filename = ToString($whatsit->getArg(1));
      my @lines    = ();
      my $gullet   = $stomach->getGullet;
      my $line;
      while (defined($line = $gullet->readRawLine) && ($line ne '\end{filecontents*}')) {
        push(@lines, $line); }
      AssignValue($filename . '_contents' => join("\n", @lines), 'global');
      NoteProgress("[Cached filecontents* for $filename (" . scalar(@lines) . " lines)]"); }]);
DefMacro('\endfilecontents', '');
DefPrimitive('\listfiles', undef);

#======================================================================
# C.11.5 Index and Glossary
#======================================================================

# ---- The index commands
# Format of Index entries:
#   \index{entry!entry}  gives multilevel index
# Each entry:
#   foo@bar  sorts on "foo" but prints "bar"
# The entries can end with a |expression:
#   \index{...|(}    this page starts a range for foo
#   \index{...|)}    this page ends a range
#           The last two aren't handled in any particular way.
#           We _could_ mark start & end, and then the postprocessor would
#           need to fill in all likely links... ???
#   \index{...|see{key}}  cross reference.
#   \index{...|seealso{key}}  cross reference.
#   \index{...|textbf}  (etc) causes the number to be printed in bold!
#
# I guess the formula is that
#    \index{foo|whatever{pi}{pa}{po}}  => \whatever{pi}{pa}{po}{page}
# How should this get interpreted??
our %index_style = (textbf => 'bold', bf => 'bold', textrm => '', rm => '',
  textit => 'italic', it => 'italic', emph => 'italic');    # What else?
# A bit screwy, but....
# Expand \index{a!b!...} into \@index{\@indexphrase{a}\@indexphrase{b}...}
sub process_index_phrases {
  my ($gullet, $phrases) = @_;
  my @expansion = ();
  # Split the text into phrases, separated by "!"
  my @tokens = $phrases->unlist;
  return unless @tokens;
  push(@tokens, T_OTHER('!')) unless $tokens[-1]->getString eq '!';    # Add terminal !
  my @phrase = ();
  my @sortas = ();
  my $style;
  while (@tokens) {
    my $tok    = shift(@tokens);
    my $string = $tok->getString;
    if ($string eq '"') {
      push(@phrase, shift(@tokens)); }
    elsif ($string eq '@') {
      while (@phrase && ($phrase[-1]->getString =~ /\s/)) { pop(@phrase); }    # Trim
      @sortas = @phrase; @phrase = (); }
    elsif (($string eq '!') || ($string eq '|')) {
      while (@phrase && ($phrase[-1]->getString =~ /\s/)) { pop(@phrase); }    # Trim
      push(@expansion, T_CS('\@indexphrase'),
        (@sortas ? (T_OTHER('['), @sortas, T_OTHER(']')) : ()),
        T_BEGIN, @phrase, T_END)
        if @phrase;
      @phrase = (); @sortas = ();
      if ($string eq '|') {
        pop(@tokens);    # Remove the extra "!" stopbit.
        my $extra = ToString(Tokens(@tokens));
        if ($extra =~ /^see\s*{/) { push(@expansion, T_CS('\@indexsee'), @tokens[3 .. $#tokens]); }
        elsif ($extra =~ /^seealso\s*\{/) { push(@expansion, T_CS('\@indexseealso'), @tokens[7 .. $#tokens]); }
        elsif ($extra eq '(') { $style = 'rangestart'; }                     # ?
        elsif ($extra eq ')') { $style = 'rangeend'; }                       # ?
        else                  { $style = $index_style{$extra} || $extra; }
        @tokens = (); } }
    elsif (!@phrase && ($string =~ /\s/)) { }                                # Skip leading whitespace
    else {
      push(@phrase, $tok); } }
  @expansion = (T_CS('\@index'),
    ($style ? (T_OTHER('['), T_OTHER($style), T_OTHER(']')) : ()),
    T_BEGIN, @expansion, T_END);
  return @expansion; }

DefMacro('\index{}', \&process_index_phrases);

Tag('ltx:indexphrase',    afterClose => \&addIndexPhraseKey);
Tag('ltx:glossaryphrase', afterClose => \&addIndexPhraseKey);
### ltx:indexsee does NOT get a key (at this stage)!

sub addIndexPhraseKey {
  my ($document, $node) = @_;
  if (!$node->getAttribute('key')) {
    $node->setAttribute(key => CleanIndexKey($node->textContent)); }
  return; }

DefConstructor('\@index[]{}', "^<ltx:indexmark style='#1'>#2</ltx:indexmark>",
  mode => 'text', reversion => '', sizer => 0);
DefConstructor('\@indexphrase[]{}',
  "<ltx:indexphrase key='#key'>#2</ltx:indexphrase>",
  properties => { key => sub { CleanIndexKey($_[1]); } });
DefConstructor('\@indexsee{}',
  "<ltx:indexsee key='#key' name='#name'>#1</ltx:indexsee>",
  properties => { name => sub { DigestIf('\seename') } });

DefConstructor('\@indexseealso{}',
  "<ltx:indexsee key='#key' name='#name'>#1</ltx:indexsee>",
  properties => { name => sub { DigestIf('\alsoname') } });

DefConstructor('\glossary{}',
  "<ltx:glossaryphrase role='glossary' key='#key'>#1</ltx:glossaryphrase>",
  properties => { key => sub { CleanIndexKey($_[1]); } },
  sizer => 0);

#======================================================================
# This converts an indexphrase node into a sortable string.
# Seems the XML nodes are the best place to handle it (rather than Boxes),
# although some of the special cases (see, @, may end up tricky)
sub indexify {
  my ($node, $document) = @_;
  my $type = $node->nodeType;
  if ($type == XML_TEXT_NODE) {
    my $string = $node->textContent;
    $string =~ s/\W//g;    # to be safe (if perhaps non-unique?)
    $string =~ s/\s//g;    # Or remove entirely? Eventually worry about many=>1 mapping???
    return $string; }
  elsif ($type == XML_ELEMENT_NODE) {
    if ($document->getModel->getNodeQName($node) eq 'ltx:Math') {
      return indexify_tex($node->getAttribute('tex')); }
    else {
      return join('', map { indexify($_, $document) } $node->childNodes); } }
  elsif ($type == XML_DOCUMENT_FRAG_NODE) {
    return join('', map { indexify($_, $document) } content_nodes($node)); }
  else {
    return ""; } }

# Try to clean up a TeX string into something
# Could walk the math tree and handle XMDual specially, but need to xref args.
# But also we'd have unicode showing up, which we'd like to latinize...
sub indexify_tex {
  my ($string) = @_;
  $string =~ s/(\\\@|\\,|\\:|\\;|\\!|\\ |\\\/|)//g;
  $string =~ s/(\\mathrm|\\mathit|\\mathbf|\\mathsf|\\mathtt|\\mathcal|\\mathscr|\\mbox|\\rm|\\it|\\bf|\\tt|\\small|\\tiny)//g;
  $string =~ s/\\left\b//g; $string =~ s/\\right\b//g;
  $string =~ s/(\\|\{|\})//g;
  $string =~ s/\W//g;    # to be safe (if perhaps non-unique?)
  $string =~ s/\s//g;    # Or remove entirely? Eventually worry about many=>1 mapping???
  return $string; }

# ---- Creating the index itself

AssignValue(INDEXLEVEL => 0);

Tag('ltx:indexentry', autoClose => 1);

sub closeIndexPhrase {
  my ($document) = @_;
  if ($document->isCloseable('ltx:indexphrase')) {
    $document->closeElement('ltx:indexphrase'); }
  return; }

sub doIndexItem {
  my ($document, $level) = @_;
  $document->closeElement('ltx:indexrefs') if $document->isCloseable('ltx:indexrefs');
  closeIndexPhrase($document);
  my $l = LookupValue('INDEXLEVEL');
  while ($l < $level) {
    $document->openElement('ltx:indexlist'); $l++; }
  while ($l > $level) {
    $document->closeElement('ltx:indexlist'); $l--; }
  AssignValue(INDEXLEVEL => $l);
  if ($level) {
    $document->openElement('ltx:indexentry');
    $document->openElement('ltx:indexphrase'); }
  return; }

DefConstructorI('\index@dotfill', undef, sub {
    my ($document) = @_;
    closeIndexPhrase($document);
    $document->openElement('ltx:indexrefs'); });
DefConstructorI('\index@item',       undef, sub { doIndexItem($_[0], 1); });
DefConstructorI('\index@subitem',    undef, sub { doIndexItem($_[0], 2); });
DefConstructorI('\index@subsubitem', undef, sub { doIndexItem($_[0], 3); });
DefConstructorI('\index@done',       undef, sub { doIndexItem($_[0], 0); });

DefMacroI('\indexname', undef, 'Index');
DefEnvironment('{theindex}',
  "<ltx:index xml:id='#id'>"
    . "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
    . "#body"
    . "</ltx:index>",
  beforeDigest => sub {
    Let('\item',       '\index@item');
    Let('\subitem',    '\index@subitem');
    Let('\subsubitem', '\index@subsubitem');
    Let('\dotfill',    '\index@dotfill'); },
  beforeDigestEnd => sub { Digest(T_CS('\index@done')); },
  afterDigestBegin => sub {
    my $docid = ToString(Expand(T_CS('\thedocument@ID')));
    my $title = DigestIf('\indexname');
    $_[1]->setProperties(id => ($docid ? "$docid.idx" : 'idx'),
      title     => $title,
      titlefont => $title->getFont); });

DefPrimitiveI('\indexspace',   undef, undef);
DefPrimitiveI('\makeindex',    undef, undef);
DefPrimitiveI('\makeglossary', undef, undef);

#======================================================================
# C.11.6 Terminal Input and Output
#======================================================================

DefPrimitive('\typeout{}', sub {
    my ($stomach, $stuff) = @_;
    print STDERR ToString(Expand($stuff)) . "\n" if LookupValue('VERBOSITY') > -1;
    return; });

DefPrimitive('\typein[]{}', undef);

#**********************************************************************
# C.12 Line and Page Breaking
#**********************************************************************

#======================================================================
# C.12.1 Line Breaking
#======================================================================
DefPrimitive('\linebreak[]',   undef);
DefPrimitive('\nolinebreak[]', undef);
# \\ already defined
DefMacroI('\newline', undef, "\\\\\\relax");
DefPrimitiveI('\-', undef, undef);    # We don't do hyphenation.
# \hyphenation in TeX.pool

DefPrimitiveI('\sloppy', undef, undef);
DefPrimitiveI('\fussy',  undef, undef);
DefEnvironment('{sloppypar}', '#body');
DefMacroI('\nobreakdashes', undef, T_OTHER('-'));

DefMacro('\showhyphens{}', '#1');     # ?
#======================================================================
# C.12.2 Page Breaking
#======================================================================
DefMacro('\pagebreak[]', '\vadjust{\clearpage}');
DefPrimitive('\nopagebreak[]', undef);
DefPrimitiveI('\columnbreak', undef, undef);    # latex? or multicol?
DefPrimitive('\enlargethispage OptionalMatch:* {}', undef);

DefMacroI('\clearpage',       undef, '\LTX@newpage');
DefMacroI('\cleardoublepage', undef, '\LTX@newpage');
DefPrimitiveI('\samepage', undef, undef);

#**********************************************************************
# C.13 Lengths, Spaces and Boxes
#**********************************************************************

#####
#####
#  Complete to here
#  [except for NOTE'd entries, of course]
#####
#####

#======================================================================
# C.13.1 Length
#======================================================================
# \fill
DefMacro('\stretch{}', '0pt plus #1fill\relax');

DefPrimitive('\@check@length DefToken', sub {
    my ($stomach, $cs) = @_;
    my $defn = LookupDefinition($cs);
    if (!$defn || !$defn->isRegister) {
      Warn('undefined', $cs, $stomach, "'" . ToString($cs) . "' is not a length; defining it now");
      DefRegisterI($cs, undef, Dimension(0)); }
    return; });

DefPrimitive('\newlength DefToken', sub {
    my ($stomach, $cs) = @_;
    DefRegisterI($cs, undef, Dimension(0)); });
DefMacro('\setlength{}{}',   '\@check@length{#1}#1#2\relax');
DefMacro('\addtolength{}{}', '\@check@length{#1}\advance#1 #2\relax');

DefMacro('\@settodim{}{}{}',
  '\setbox\@tempboxa\hbox{{#3}}#2#1\@tempboxa\setbox\@tempboxa\box\voidb@x');
DefMacroI('\settoheight', undef, '\@settodim\ht');
DefMacroI('\settodepth',  undef, '\@settodim\dp');
DefMacroI('\settowidth',  undef, '\@settodim\wd');

# Assuming noone tries to get clever with figuring out the allocation of
# numbers, these become simple DefRegister's
DefPrimitive('\newcount DefToken',  sub { DefRegisterI($_[1], undef, Number(0)); });
DefPrimitive('\newdimen DefToken',  sub { DefRegisterI($_[1], undef, Dimension(0)); });
DefPrimitive('\newskip DefToken',   sub { DefRegisterI($_[1], undef, Glue(0)); });
DefPrimitive('\newmuskip DefToken', sub { DefRegisterI($_[1], undef, MuGlue(0)); });
DefPrimitive('\newtoks DefToken',   sub { DefRegisterI($_[1], undef, Tokens()); });

DefRegister('\fill', Glue(0, '1fill'));

#======================================================================
# C.13.2 Space
#======================================================================
DefMacro('\hspace  OptionalMatch:* {Dimension}',
  '\ifmmode\@math@hskip #2\relax\else\@text@hskip #2\relax\fi');

DefPrimitive('\vspace OptionalMatch:* {}', undef);
DefPrimitive('\addvspace {}',              undef);
DefPrimitive('\addpenalty {}',             undef);
# \hfill, \vfill

#======================================================================
# C.13.3 Boxes
#======================================================================
# Can't really get these?
DefMacroI('\height',      undef, '0pt');
DefMacroI('\totalheight', undef, '0pt');
DefMacroI('\depth',       undef, '0pt');
DefMacroI('\width',       undef, '0pt');

DefConstructor('\mbox {}',
  "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text', bounded => 1,
  sizer => '#1',
  beforeDigest => sub { reenterTextMode(); });

our %makebox_alignment = (l => 'left', r => 'right', s => 'justified');
DefMacro('\makebox', '\@ifnextchar(\pic@makebox\@makebox');
DefConstructor('\@makebox[Dimension][]{}',
  "<ltx:text ?#width(width='#width') ?#align(align='#align') _noautoclose='1'>#3</ltx:text>",
  mode => 'text', bounded => 1, alias => '\makebox', sizer => '#3',
  beforeDigest => sub { reenterTextMode(); },
  properties => sub {
    (($_[2] ? (align => $makebox_alignment{ ToString($_[2]) }) : ()),
      ($_[1] ? (width => $_[1]) : ())) });

DefRegister('\fboxrule', Dimension('.4pt'));
DefRegister('\fboxsep',  Dimension('3pt'));

# Peculiar special case!
#  These are nominally text mode macros. However, there is a somewhat common idiom:
#     $ ... \framebox{$operator$} ... $
# in which case the operator gets boxed and really should be treated as a math object.
# (and ultimately converted to mml:menclose)
# So, we need to switch to text mode, as usual, but FIRST note whether we started in math mode!
# Afterwards, if we were in math mode, and the content is math, we'll convert the whole thing
# to a framed math object.
# Second special issue:
#   Although framebox doesn't allow flowed content inside, it is also somewhat common
# to put a vbox or some other block construct inside.
# Seemingly, the ultimate html gets somewhat tangled (browser bugs?)
# At any rate, since we're wrapping with an ltx:text, we'll try to unwrap it,
# if the contents are a single child that can handle the framing.

DefMacro('\fbox{}',   '\@framebox{#1}');
DefMacro('\framebox', '\@ifnextchar(\pic@framebox\@framebox');
DefConstructor('\@framebox[][]{}',
  "?#mathframe(<ltx:XMArg enclose='box'>#inner</ltx:XMArg>)"
    . "(<ltx:text ?#width(width='#width') ?#align(align='#align')"
    . " framed='rectangle' framecolor='#framecolor'"
    . " _noautoclose='1'>#3</ltx:text>)",
  alias => '\framebox', sizer => '#3',
  beforeDigest => sub {
    my ($stomach) = @_;
    my $wasmath = LookupValue('IN_MATH');
    $stomach->beginMode('text');
    AssignValue(FRAME_IN_MATH => $wasmath); },
  properties => sub {
    (($_[2] ? (align => $makebox_alignment{ ToString($_[2]) }) : ()),
      framecolor => LookupValue('font')->getColor,
      ($_[1] ? (width => $_[1]) : ())); },
  afterDigest => sub {
    my ($stomach, $whatsit) = @_;
    my $wasmath = LookupValue('FRAME_IN_MATH');
    my $arg     = $whatsit->getArg(3);
    $stomach->endMode('text');
    if ($wasmath && $arg->isMath) {
      $whatsit->setProperties(mathframe => 1, inner => $arg->getBody); }
    return; },
  afterConstruct => sub {
    my ($document, $whatsit) = @_;
    my $node = $document->getNode->lastChild;
    # If the generated node, has only a single (non space) child
    my @c = grep { ($_->nodeType != XML_TEXT_NODE) || ($_->textContent =~ /[^\s\n]/) }
      $node->childNodes;
    my $model = $document->getModel;
    # and that child can have the framed attribute
    if ((scalar(@c) == 1)
      && $document->canHaveAttribute($model->getNodeQName($c[0]), 'framed')) {
      # unwrap, copying the attributes
      $document->unwrapNodes($node);
      foreach my $k (qw(width align framed)) {
        if (my $v = $node->getAttribute($k)) {
          $document->setAttribute($c[0], $k => $v); } } } }
);

AssignValue(SAVEBOX => 100);
DefPrimitive('\newsavebox DefToken', sub {
    my $n = LookupValue('SAVEBOX') + 1;
    AssignValue(SAVEBOX => $n, 'global');
    DefRegisterI($_[1], undef, Number($n));
    AssignValue('box' . $n, List()); });

DefPrimitive('\sbox {Register} {}', sub {
    my ($defn)   = @{ $_[1] };
    my $value    = $defn->valueOf()->valueOf;
    my $contents = Digest($_[2]);
    AssignValue('box' . $value, $contents); return; });

DefMacro('\savebox{}', '\@ifnextchar({\pic@savebox#1}{\@savebox#1}');
DefPrimitive('\@savebox DefToken[][]{}', sub {
    my ($defn, @args) = @{ LookupDefinition($_[1]) };
    my $value = $defn->valueOf(@args);
    AssignValue('box' . $value, Digest($_[4])); return; });
DefPrimitive('\@savebox Register [][]{}', sub {
    my ($defn)   = @{ $_[1] };
    my $value    = $defn->valueOf()->valueOf;
    my $contents = Digest($_[4]);
    #    AssignValue('box' . $value, Digest($_[4])); return; });
    AssignValue('box' . $value, $contents); return; });

DefMacroI('\begin{lrbox}', '{Token}',
  '\@begin@lrbox #1');
DefPrimitiveI('\end{lrbox}', undef, sub { $_[0]->egroup; });
DefPrimitive('\@begin@lrbox Token', sub {
    my ($stomach, $token) = @_;
    $stomach->bgroup;
    #  my $font = $STATE->lookupValue('font');
    #  my $loc  = $stomach->getGullet->getLocator;
    #  my $box = LaTeXML::Core::Box->new($stomach->digestNextBody(),$font,$loc);
    my $box = List($stomach->digestNextBody());
    AssignValue('box' . ToString($token), $box); });

DefPrimitive('\usebox {Register}', sub {
    my ($defn) = @{ $_[1] };
    my $value = $defn->valueOf()->valueOf;
    LookupValue('box' . $value) || Box(); });

# NOTE: There are 2 extra arguments (See LaTeX Companion, p.866)
# for height and inner-pos.  We're ignoring them, for now, though.
DefConstructor('\parbox[][Dimension][]{Dimension}{}', sub {
    my ($document, $attachment, $b, $c, $width, $body, %props) = @_;
    insertBlock($document, $body,
      width   => $width,
      vattach => $props{vattach},
      class   => 'ltx_parbox');
    return; },
  sizer      => '#5',
  properties => sub {
    (width => $_[4],
      vattach => translateAttachment($_[1]),
      height  => $_[2]); },
  mode => 'text', bounded => 1,
  beforeDigest => sub {
    AssignValue('\hsize' => $_[4]);
    Let('\\\\', '\par'); });

DefConditional('\if@minipage');
DefEnvironment('{minipage}[][][]{Dimension}', sub {
    my ($document, $attachment, $b, $c, $width, %props) = @_;
    my $vattach = translateAttachment($attachment);
    insertBlock($document, $props{body},
      width   => $width,
      vattach => $vattach,
      class   => 'ltx_minipage');
    return; },
  mode => 'text',
  properties => sub { (
      width   => $_[4],
      vattach => translateAttachment($_[1])); },
  beforeDigest => sub {
    Digest(T_CS('\@minipagetrue'));
    AssignValue('\hsize' => $_[4]);
    # this conflicts (& not needed?) with insertBlock
    Let('\\\\', '\par'); });

DefConstructor('\rule[Dimension]{Dimension}{Dimension}',
  "<ltx:rule ?#offset(yoffset='#offset') width='#width' height='#height'/>",
  properties => sub { (offset => $_[1], width => $_[2], height => $_[3]) });
DefConstructor('\raisebox{Dimension}[Dimension][Dimension]{}',
  "<ltx:text yoffset='#1' _noautoclose='1'>#4</ltx:text>",
  mode => 'text', bounded => 1,
  beforeDigest => sub { reenterTextMode(); },
  sizer => sub { raisedSizer($_[0]->getArg(4), $_[0]->getArg(1)); });

sub raisedSizer {
  my ($box, $y) = @_;
  my ($w, $h, $d) = $box->getSize;
  my $z = Dimension(0);
  $h = $h->add($y)->larger($z);
  $d = $d->subtract($y)->larger($z);
  return ($w, $h, $d); }

#**********************************************************************
# C.14 Pictures and Color
#**********************************************************************

# These are stubs for color or xcolor packages
Let('\set@color',        '\relax');
Let('\color@begingroup', '\relax');
Let('\color@endgroup',   '\relax');
Let('\color@setgroup',   '\relax');
Let('\color@hbox',       '\relax');
Let('\color@vbox',       '\relax');
Let('\color@endbox',     '\relax');

#======================================================================
# C.14.1 The picture environment
#======================================================================

#----------------------------------------------------------------------

sub ResolveReader {
  my ($type) = @_;
  return $type if ref $type eq 'CODE';    # It's already a reader (hopefully).
  $type = ToString($type);
  if (my $descriptor = LookupMapping('PARAMETER_TYPES', $type)) {
    return $$descriptor{reader}; }
  # Otherwise, try to find a function named Read<Type>
  else {
    return LaTeXML::Core::Parameter::checkReaderFunction('Read' . $type); } }

# This defines a Pair parameter type,
# that reads a parenthesized, comma separated pair of subtype.
# By default, the subtype is Float, but you can write Pair:Number, or Pair:Dimension
sub ReadPair {
  my ($gullet, $itemtype, $xarg, $yarg) = @_;
  my $itemreader;
  if   (!$itemtype) { $itemreader = \&ReadFloat; }
  else              { $itemreader = ResolveReader($itemtype); }
  if (!$itemreader) {
    Error('misdefined', $itemtype, $gullet, "Can't find reader for Pair items from '$itemtype'");
    return Pair(Dimension(0), Dimension(0)); }    # Assume something like this?
  $gullet->skipSpaces;
  if ($gullet->ifNext(T_OTHER('('))) {
    $gullet->readToken; $gullet->skipSpaces;
    if ($gullet->ifNext(T_OTHER('!'))) {          # maybe only for pstricks???
      Error('unexpected', 'postscript', $gullet,
        "Cannot process escape to postscript");
      $gullet->readUntil(T_OTHER(')')); $gullet->skipSpaces;
      return Pair(Dimension(0), Dimension(0)); }
    my $x = &$itemreader($gullet, $xarg);
    $gullet->skipSpaces; $gullet->readUntil(T_OTHER(','), T_OTHER(';')); $gullet->skipSpaces;
    my $y = &$itemreader($gullet, $yarg);
    $gullet->skipSpaces; $gullet->readUntil(T_OTHER(')')); $gullet->skipSpaces;
    return Pair($x, $y); }
  else {
    return; } }

sub ptValue {
  my ($value) = @_;
  return $value ? $value->ptValue : undef; }

# This eases conversion of a Pair to 2 attributes.
sub PairAttr {
  my ($pair, $xattr, $yattr) = @_;
### Why does perlcritic think that this is comma separating statements?
###  return ($pair ? { $xattr => ptValue($pair->getX), $yattr => ptValue($pair->getY) } : {}); }
  return ($pair ? { $xattr => $pair->getX->ptValue, $yattr => $pair->getY->ptValue } : {}); }

sub SimplePairAttr {
  my ($pair, $xattr, $yattr) = @_;
  return ($pair ? { $xattr => $pair->getX, $yattr => $pair->getY } : {}); }

#----------------------------------------------------------------------
# Picture parameters.
DefRegister('\unitlength', Dimension('1pt'));
DefPrimitiveI('\thinlines',  undef, sub { AssignValue('\@wholewidth', Dimension('0.4pt')); });
DefPrimitiveI('\thicklines', undef, sub { AssignValue('\@wholewidth', Dimension('0.8pt')); });
DefRegister('\@wholewidth' => Dimension('0.4pt'));
DefRegister('\@halfwidth'  => Dimension('0.2pt'));
DefMacro('\linethickness{}', '\@wholewidth #1\relax');

DefPrimitive('\arrowlength{Dimension}', sub { AssignValue('\arrowlength', $_[1]); });

#----------------------------------------------------------------------
# Picture transformation support
sub slopeToPicCoord {
  my ($slope, $xlength) = @_;
  my ($mx, $my) = ($slope->getX, $slope->getY);
  my $s = $mx->sign();
  $xlength = picScale($xlength);
  return Pair($xlength->multiply($s),
    $xlength->multiply(($s == 0) ? $my->sign() :
        $my->valueOf / $mx->absolute->valueOf)); }

sub picScale {
  my ($value) = @_;
  return ($value ? $value->multiply(LookupValue('\unitlength')) : undef); }

sub picProperties {
  my (%props) = @_;
  if (($props{stroke} || 'black') ne 'none') {
    $props{thick} = ptValue(LookupValue('\@wholewidth')); }
  if (my $arrowlength = LookupValue('\arrowlength')) {
    $props{arrowlength} = ptValue($arrowlength); }
  return %props; }

#----------------------------------------------------------------------
# the code
DefMacroI('\qbeziermax', undef, '500');

sub before_picture {
  #  Let('\line',     '\pic@line'); # Only pic?
  #  Let('\vector',   '\pic@vector'); # Only pic
  #  Let('\circle',   '\pic@circle'); # ???
  #  Let('\oval',     '\pic@oval');   # Only pic
  #  Let('\qbezier',  '\pic@qbezier'); # Only pic
  #  Let('\makebox',  '\pic@makebox'); # CHECK for (
  #  Let('\savebox',  '\pic@savebox'); # CHECK for (
  #  Let('\framebox', '\pic@framebox'); # CHECK for (
  Let('\raisebox', '\pic@raisebox');    # ? needs special treatment within picture
                                        #  Let('\dashbox',  '\pic@dashbox');  # Only pic
                                        #  Let('\frame',    '\pic@frame');    # ?
  return; }

sub after_picture {
  return; }

# Since these ultimately generate external resources, it can be useful to have a handle on them.
Tag('ltx:graphics', afterOpen => sub { GenerateID(@_, 'g'); });
Tag('ltx:picture',  afterOpen => sub { GenerateID(@_, 'pic'); });

# Ugh... Is this safe?  Apparently, picture stuff is allowed w/o a {picture} environment???
### Tag('ltx:picture', autoOpen=>1, autoClose=>1);
### Actually, NO! It triggers a deep recursion
### NOTE: This needs to be researched!!!

# Note: Untex should prefix a setting of unitlength!!!
# First pair is (width,height)
# Second pair is the coordinate of the lower-left corner.
# [Note that for SVG the root viewport origin is at the TOP-left corner!
#  but that is currently handled in the SVG postprocessing module]
DefEnvironment('{picture} Pair OptionalPair',
  "<ltx:picture %&SimplePairAttr(#size,width,height)"
    . " fill='black' stroke='black' unitlength='#unitlength'>"
    . "?#pos(<ltx:g transform='#pos'>#body</ltx:g>)(#body)"
    . "</ltx:picture>",
  mode         => 'text',
  beforeDigest => \&before_picture,

  properties => sub { (unitlength => LookupValue('\unitlength'),
      ($_[2] ? (pos => 'translate(' . ptValue(picScale($_[2]->negate)) . ')') : ()),
      picProperties(size => picScale($_[1]))); },
  afterDigest => \&after_picture);

DefConstructor('\put Pair {}',
  "<ltx:g transform='#pos'>#2</ltx:g>",
  properties => sub { pos => 'translate(' . ptValue(picScale($_[1])) . ')'; },
  mode => 'text');

#DefConstructor('\pic@line Pair:Number {Float}',
DefConstructor('\line Pair:Number {Float}',
  "<ltx:line points='#points' stroke-width='#thick'/>",
  alias => '\line',
  properties => sub { picProperties(points => '0,0 ' . slopeToPicCoord($_[1], $_[2])->ptValue()); });

#DefConstructor('\pic@vector Pair:Number {Float}',
DefConstructor('\vector Pair:Number {Float}',
  "<ltx:line points='#points' stroke-width='#thick' terminators='->'"
    . " arrowlength='#arrowlength'/>",
  alias => '\vector',
  properties => sub { picProperties(points => '0,0 ' . slopeToPicCoord($_[1], $_[2])->ptValue()); });

#DefConstructor('\pic@circle OptionalMatch:* {Float}',
DefConstructor('\circle OptionalMatch:* {Float}',
  "<ltx:circle x='0' y='0' r='&ptValue(#radius)' fill='#fill' stroke='#stroke'"
    . " stroke-width='#thick'/>",
  alias      => '\circle',
  properties => sub {
    my ($stomach, $filled, $dia) = @_;
    $dia = picScale($dia);
    $dia = $dia->add(LookupValue('\@wholewidth')) unless $filled;
    picProperties(radius => $dia->multiply(0.5),
      ($filled ? 'stroke' : 'fill') => 'none'); });

##DefConstructor('\pic@oval [Float] Pair []',
DefConstructor('\oval [Float] Pair []',
  "<ltx:rect %&PairAttr(#pos,x,y) %&PairAttr(#size,width,height) rx='&ptValue(#radius)'"
    . "  fill='none' part='#3' stroke-width='#thick'/>",
  alias      => '\oval',
  properties => sub {
    my ($stomach, $r, $size, $part) = @_;
    $size = picScale($size);
    my $halfsize = $size->multiply(0.5);
    my $pos = Pair($halfsize->getX->negate, $halfsize->getY->negate);
    $r = ($r ? picScale($r) : Dimension('40pt'));
    $r = $r->smaller($halfsize->getX->absolute);
    $r = $r->smaller($halfsize->getY->absolute);
    picProperties(size => $size, pos => $pos, radius => $r); });

##DefConstructor('\pic@qbezier [Number] Pair Pair Pair',
DefConstructor('\qbezier [Number] Pair Pair Pair',
  "<ltx:bezier ?#1(displayedpoints='#1') points='&ptValue(#pt)' stroke-width='#thick' />",
  alias      => '\qbezier',
  properties => sub {
    picProperties(pt => PairList(picScale($_[2]), picScale($_[3]), picScale($_[4]))); });
DefConstructor('\bezier Number Pair Pair Pair',
  "<ltx:bezier ?#1(displayedpoints='#1') points='&ptValue(#pt)' stroke-width='#thick' />",
  alias      => '\bezier',
  properties => sub {
    picProperties(pt => PairList(picScale($_[2]), picScale($_[3]), picScale($_[4]))); });

DefConstructor('\pic@makebox Pair []{}',
  "<ltx:g %&PairAttr(#size,width,height) pos='#2'>#3</ltx:g>",
  alias        => '\makebox',
  beforeDigest => sub { reenterTextMode(); },
  properties   => sub { picProperties(size => picScale($_[1])); });

DefMacro('\pic@savebox DefToken Pair []{}', '\pic@@savebox{#1}{\pic@makebox #2[#3]{#4}}');
DefPrimitive('\pic@@savebox DefToken {}', sub {
    AssignValue('box' . ToString($_[1]), Digest($_[2])); return; });

DefConstructor('\pic@raisebox{Dimension}[Dimension][Dimension]{}',
  "<ltx:g y='#1'>#4</ltx:g>",
  alias => '\raisebox');

DefConstructor('\pic@framebox Pair []{}',
  "<ltx:rect x='0' y='0' %&PairAttr(#size,width,height) stroke-width='#thick' fill='none'/>"
    . "<ltx:g %&PairAttr(#size,width,height) pos='#2'>#3</ltx:g>",
  alias        => '\framebox',
  beforeDigest => sub { reenterTextMode(); },
  properties   => sub { picProperties(size => picScale($_[1])); });

#DefConstructor('\pic@dashbox {Float} Pair [] {}',
DefConstructor('\dashbox {Float} Pair [] {}',
  "<ltx:rect x='0' y='0' %&PairAttr(#size,width,height)"
    . " stroke-width='#thick' stroke-dasharray='&ptValue(#dash)' fill='none'/>" .
    "<ltx:g %&PairAttr(#size,width,height) pos='#3'>#4</ltx:g>",
  alias => '\dashbox',
  properties => sub { picProperties(dash => picScale($_[1]), size => picScale($_[2])); });

#DefConstructor('\pic@frame{}',
DefConstructor('\frame{}',
  "<ltx:g framed='true' stroke-width='#thick'>#1</ltx:g>",
  alias => '\frame',
  properties => sub { picProperties(); });

our %alignments = (l => 'left', c => 'center', r => 'right');
# Not sure that ltx:p is the best to use here, but ... (see also \vbox, \vtop)
DefConstructor('\@shortstack@cr',
  "</ltx:p><ltx:p>",
  reversion => Tokens(T_CS("\\\\"), T_CR),
  beforeDigest => sub { $_[0]->egroup; },
  afterDigest  => sub { $_[0]->bgroup; });

DefConstructor('\shortstack[]{}  OptionalMatch:* [Dimension]',
  "<ltx:inline-block align='#align'><ltx:p>#2</ltx:p></ltx:inline-block>",
  bounded => 1,
  beforeDigest => sub { reenterTextMode();
    # then RE-RE-define this one!!!
    Let("\\\\", '\@shortstack@cr');
    $_[0]->bgroup; },
  afterDigest => sub { $_[0]->egroup; },
  properties => { align => sub { ($_[1] ? $alignments{ ToString($_[0]) } : undef); } },
  mode => 'text');

DefMacro('\multiput Pair Pair {}{}', sub {
    my ($gullet, $pos, $d, $nt, $body) = @_;
    my ($x, $y, $dx, $dy, $n) = map { ToString($_) } ($pos->getX, $pos->getY, $d->getX, $d->getY, $nt);
    my @exp = ();
    for (my $i = 0 ; $i < $n ; $i++) {
      push(@exp, T_CS('\put'), T_OTHER('('), Explode($x), T_OTHER(','), Explode($y), T_OTHER(')'),
        T_BEGIN, $body->unlist, T_END);
      $x += $dx; $y += $dy; }
    @exp; });

Tag('ltx:picture',
  afterOpen => sub {
    my ($document, $node, $thing) = @_;
    if ($thing && (ref $thing eq 'LaTeXML::Core::Whatsit') && !$thing->getProperty('_added_tex')) {
      my $tex = UnTeX($thing);
      $document->setAttribute($node, tex => $tex);
      $thing->setProperty('_added_tex', 1); } });

Tag('ltx:g', afterClose => sub {
    my ($document, $node) = @_;
    $node->parentNode->removeChild($node) unless $node->hasChildNodes; });

# \savebox -- already defined differntly in C.13 above ?

#**********************************************************************
# C.15 Font Selection
#**********************************************************************
#======================================================================
# C.15.1 Changing the Type Style
#======================================================================
# Text styles.

DefMacroI('\rmdefault',       undef, 'cmr');
DefMacroI('\sfdefault',       undef, 'cmss');
DefMacroI('\ttdefault',       undef, 'cmtt');
DefMacroI('\bfdefault',       undef, 'bx');
DefMacroI('\mddefault',       undef, 'm');
DefMacroI('\itdefault',       undef, 'it');
DefMacroI('\sldefault',       undef, 'sl');
DefMacroI('\scdefault',       undef, 'sc');
DefMacroI('\updefault',       undef, 'n');
DefMacroI('\encodingdefault', undef, 'OT1');
DefMacroI('\familydefault',   undef, '\rmdefault');
DefMacroI('\seriesdefault',   undef, '\mddefault');
DefMacroI('\shapedefault',    undef, '\updefault');

Let('\mediumseries', '\mdseries');
Let('\normalshape',  '\upshape');

# ? DefMacro('\f@encoding','cm');
DefMacro('\f@family', 'cm');
DefMacro('\f@series', '');
DefMacro('\f@shape',  '');
DefMacro('\f@size',   '');

# These do NOT immediately effect the font!
DefMacro('\fontfamily{}', '\edef\f@family{#1}');
DefMacro('\fontseries{}', '\edef\f@series{#1}');
DefMacro('\fontshape{}',  '\edef\f@shape{#1}');

# For fonts not allowed in math!!!
DefPrimitive('\not@math@alphabet@@ {}', sub {
    if ($STATE->lookupValue('IN_MATH')) {
      my $c = ToString($_[1]);
      Warn('unexpected', $c, $_[0], "Command $c invalid in math mode"); }
    return; });

# These DO immediately effect the font!
DefMacroI('\mdseries', undef, '\not@math@alphabet@@{\mddefault}\fontseries{\mddefault}\selectfont');
DefMacroI('\bfseries', undef, '\not@math@alphabet@@{\bfdefault}\fontseries{\bfdefault}\selectfont');

DefMacroI('\rmfamily', undef, '\not@math@alphabet@@{\rmdefault}\fontfamily{\rmdefault}\selectfont');
DefMacroI('\sffamily', undef, '\not@math@alphabet@@{\sfdefault}\fontfamily{\sfdefault}\selectfont');
DefMacroI('\ttfamily', undef, '\not@math@alphabet@@{\ttdefault}\fontfamily{\ttdefault}\selectfont');

DefMacroI('\upshape', undef, '\not@math@alphabet@@{\updefault}\fontshape{\updefault}\selectfont');
DefMacroI('\itshape', undef, '\not@math@alphabet@@{\itdefault}\fontshape{\itdefault}\selectfont');
DefMacroI('\slshape', undef, '\not@math@alphabet@@{\sldefault}\fontshape{\sldefault}\selectfont');
DefMacroI('\scshape', undef, '\not@math@alphabet@@{\scdefault}\fontshape{\scdefault}\selectfont');

DefMacroI('\normalfont', undef,
  '\fontfamily{\rmdefault}\fontseries{\mddefault}\fontshape{\updefault}\selectfont');
DefMacroI('\verbatim@font', undef,
  '\fontfamily{\ttdefault}\fontseries{\mddefault}\fontshape{\updefault}\selectfont');

Let('\reset@font', '\normalfont');

DefPrimitive('\selectfont', sub {
    my $family = ToString(Expand(T_CS('\f@family')));
    my $series = ToString(Expand(T_CS('\f@series')));
    my $shape  = ToString(Expand(T_CS('\f@shape')));
    if (my $sh = LaTeXML::Common::Font::lookupFontFamily($family)) { MergeFont(%$sh); }
    else { Info('unexpected', $family, $_[0], "Unrecognized font family '$family'."); }
    if (my $sh = LaTeXML::Common::Font::lookupFontSeries($series)) { MergeFont(%$sh); }
    else { Info('unexpected', $series, $_[0], "Unrecognized font series '$series'."); }
    if (my $sh = LaTeXML::Common::Font::lookupFontShape($shape)) { MergeFont(%$sh); }
    else { Info('unexpected', $shape, $_[0], "Unrecognized font shape '$shape'."); }
    return; });

DefMacro('\usefont{}{}{}{}',
  '\fontencoding{#1}\fontfamily{#2}\fontseries{#3}\fontshape{#4}\selectfont');

# If these series or shapes appear in math, they revert it to roman, medium, upright (?)
DefConstructor('\textmd@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded => 1, font => { series => 'medium' }, alias => '\textmd',
  beforeDigest => sub { DefMacro('\f@series', 'm'); });
DefConstructor('\textbf@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded => 1, font => { series => 'bold' }, alias => '\textbf',
  beforeDigest => sub { DefMacro('\f@series', 'b'); });
DefConstructor('\textrm@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded => 1, font => { family => 'serif' }, alias => '\textrm',
  beforeDigest => sub { DefMacro('\f@family', 'cm'); });
DefConstructor('\textsf@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded => 1, font => { family => 'sansserif' }, alias => '\textsf',
  beforeDigest => sub { DefMacro('\f@family', 'cmss'); });
DefConstructor('\texttt@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded => 1, font => { family => 'typewriter' }, alias => '\texttt',
  beforeDigest => sub { DefMacro('\f@family', 'cmtt'); });

DefConstructor('\textup@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded => 1, font => { shape => 'upright' }, alias => '\textup',
  beforeDigest => sub { DefMacro('\f@shape', ''); });
DefConstructor('\textit@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded => 1, font => { shape => 'italic' }, alias => '\textit',
  beforeDigest => sub { DefMacro('\f@shape', 'i'); });
DefConstructor('\textsl@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded => 1, font => { shape => 'slanted' }, alias => '\textsl',
  beforeDigest => sub { DefMacro('\f@shape', 'sl'); });
DefConstructor('\textsc@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded => 1, font => { shape => 'smallcaps' }, alias => '\textsc',
  beforeDigest => sub { DefMacro('\f@shape', 'sc'); });
DefConstructor('\textnormal@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded => 1, font => { family => 'serif', series => 'medium', shape => 'upright' }, alias => '\textnormal',
  beforeDigest => sub { DefMacro('\f@family', 'cmtt');
    DefMacro('\f@series', 'm');
    DefMacro('\f@shape',  'n'); });
# These really should be robust! which is a source of expand timing issues!
DefMacro('\textmd{}',     '\protect\ifmmode\textmd@math{#1}\else{\mdseries #1}\fi');
DefMacro('\textbf{}',     '\protect\ifmmode\textbf@math{#1}\else{\bfseries #1}\fi');
DefMacro('\textrm{}',     '\protect\ifmmode\textrm@math{#1}\else{\rmfamily #1}\fi');
DefMacro('\textsf{}',     '\protect\ifmmode\textsf@math{#1}\else{\sffamily #1}\fi');
DefMacro('\texttt{}',     '\protect\ifmmode\texttt@math{#1}\else{\ttfamily #1}\fi');
DefMacro('\textup{}',     '\protect\ifmmode\textup@math{#1}\else{\upshape #1}\fi');
DefMacro('\textit{}',     '\protect\ifmmode\textit@math{#1}\else{\itshape #1}\fi');
DefMacro('\textsl{}',     '\protect\ifmmode\textsl@math{#1}\else{\slshape #1}\fi');
DefMacro('\textsc{}',     '\protect\ifmmode\textsc@math{#1}\else{\scshape #1}\fi');
DefMacro('\textnormal{}', '\protect\ifmmode\textnormal@math{#1}\else{\normalfont #1}\fi');

DefPrimitive('\DeclareTextFontCommand{}{}', sub {
    my ($stomach, $cmd, $font) = @_;
    DefConstructorI($cmd, "{}",
      "?#isMath(<ltx:text _noautoclose='1'>#1</ltx:text>)(#1)",
      mode => 'text', bounded => 1,
      beforeDigest => sub { Digest($font); (); });
    return; });

DefPrimitive('\mathversion{}', sub {
    my ($stomach, $version) = @_;
    $version = ToString($version);
    if ($version eq 'bold') {
      AssignValue(mathfont => LookupValue('mathfont')->merge(forcebold => 1), 'local'); }
    elsif ($version eq 'normal') {
      AssignValue(mathfont => LookupValue('mathfont')->merge(forcebold => 0), 'local'); }
    else { Error('unexpected', $version, $stomach, "Unknown math verison '$version'"); } });

DefMacro('\newfont{}{}', '\font#1=#2\relax');

Let('\normalcolor', '\relax');

#======================================================================
# C.15.2 Changing the Type Size
#======================================================================
# Handled in TeX.pool.ltxml

#======================================================================
# C.15.3 Special Symbol
#======================================================================
DefMacro('\symbol{}', '\char#1\relax');

# These in LaTeX, but not in the book...
DefPrimitiveI('\textdollar',           undef, "\$");
DefPrimitiveI('\textemdash',           undef, "\x{2014}");    # EM DASH
DefPrimitiveI('\textendash',           undef, "\x{2013}");    # EN DASH
DefPrimitiveI('\textexclamdown',       undef, UTF(0xA1));     # INVERTED EXCLAMATION MARK
DefPrimitiveI('\textquestiondown',     undef, UTF(0xBF));     # INVERTED QUESTION MARK
DefPrimitiveI('\textquotedblleft',     undef, "\x{201C}");    # LEFT DOUBLE QUOTATION MARK
DefPrimitiveI('\textquotedblright',    undef, "\x{201D}");    # RIGHT DOUBLE QUOTATION MARK
DefPrimitiveI('\textquotedbl',         undef, "\"");          # plain ascii DOUBLE QUOTATION
DefPrimitiveI('\textquoteleft',        undef, "\x{2018}");    # LEFT SINGLE QUOTATION MARK
DefPrimitiveI('\textquoteright',       undef, "\x{2019}");    # RIGHT SINGLE QUOTATION MARK
DefPrimitiveI('\textsterling',         undef, UTF(0xA3));     # POUND SIGN
DefPrimitiveI('\textasteriskcentered', undef, "*");
DefPrimitiveI('\textbackslash',        undef, UTF(0x5C));     # REVERSE SOLIDUS
DefPrimitiveI('\textbar',              undef, "|");
DefPrimitiveI('\textbraceleft',        undef, "{");
DefPrimitiveI('\textbraceright',       undef, "}");
DefPrimitiveI('\textbullet',           undef, "\x{2022}");    # BULLET
DefPrimitiveI('\textdaggerdbl',        undef, "\x{2021}");    # DOUBLE DAGGER
DefPrimitiveI('\textdagger',           undef, "\x{2020}");    # DAGGER
DefPrimitiveI('\textparagraph',        undef, UTF(0xB6));     # PILCROW SIGN
DefPrimitiveI('\textperiodcentered',   undef, "\x{22C5}");    # DOT OPERATOR
DefPrimitiveI('\textsection',          undef, UTF(0xA7));     # SECTION SIGN
DefAccent('\textcircled', UTF(0x20DD), UTF(0x25EF));          # Defined in TeX.pool
DefPrimitiveI('\textless',         undef, "<");
DefPrimitiveI('\textgreater',      undef, ">");
DefPrimitiveI('\textcopyright',    undef, UTF(0xA9));         # COPYRIGHT SIGN
DefPrimitiveI('\textasciicircum',  undef, "^");
DefPrimitiveI('\textasciitilde',   undef, "~");
DefPrimitiveI('\textcompwordmark', undef, "");                # ???
DefPrimitiveI('\textunderscore',   undef, "_");
DefPrimitiveI('\textvisiblespace', undef, "\x{2423}"); # SYMBOL FOR SPACE;  Not really the right symbol!
DefPrimitiveI('\textellipsis',   undef, "\x{2026}");   # HORIZONTAL ELLIPSIS
DefPrimitiveI('\textregistered', undef, UTF(0xAE));    # REGISTERED SIGN
DefPrimitiveI('\texttrademark',  undef, "\x{2122}");   # TRADE MARK SIGN
DefConstructor('\textsuperscript{}', "<ltx:sup>#1</ltx:sup>",
  mode => 'text');
# This is something coming from xetex/xelatex ? Why define this way?
#DefConstructor('\realsuperscript{}', "<ltx:text yoffset='0.5em' _noautoclose='1'>#1</ltx:text>");
DefConstructor('\realsuperscript{}', "<ltx:sup>#1</ltx:sup>",
  mode => 'text');
DefPrimitiveI('\textordfeminine',  undef, UTF(0xAA));    # FEMININE ORDINAL INDICATOR
DefPrimitiveI('\textordmasculine', undef, UTF(0xBA));    # MASCULINE ORDINAL INDICATOR
DefPrimitiveI('\SS',               undef, 'SS');         # ?

DefMacroI('\dag',  undef, '\ifmmode{\dagger}\else\textdagger\fi');
DefMacroI('\ddag', undef, '\ifmmode{\ddagger}\else\textdaggerdbl\fi');

DefConstructor('\sqrtsign Digested',
  "<ltx:XMApp><ltx:XMTok meaning='square-root'/><ltx:XMArg>#1</ltx:XMArg></ltx:XMApp>");

DefPrimitiveI('\mathparagraph',  undef, UTF(0xB6));
DefPrimitiveI('\mathsection',    undef, UTF(0xA7));
DefPrimitiveI('\mathdollar',     undef, '$');
DefPrimitiveI('\mathsterling',   undef, UTF(0xA3));
DefPrimitiveI('\mathunderscore', undef, '_');
DefPrimitiveI('\mathellipsis',   undef, "\x{2026}");

# Are these glyph "pieces" or use alone?
DefMathI('\arrowvert', undef, "|",        role => 'VERTBAR');
DefMathI('\Arrowvert', undef, "\x{2225}", role => 'VERTBAR');

# The following are glyph "pieces"...
DefPrimitiveI('\braceld', undef, "\x{239D}");    #   left brace down part
DefPrimitiveI('\bracelu', undef, "\x{239B}");    #   left brace up part
DefPrimitiveI('\bracerd', undef, "\x{23A0}");    #   right brace down part
DefPrimitiveI('\braceru', undef, "\x{239E}");    #   right brace up part

DefMathI('\cdotp', undef, "\x{22C5}", role => 'MULOP');
DefMathI('\ldotp', undef, ".",        role => 'MULOP');
DefMathI('\intop', undef, "\x{222B}", role => 'INTOP', meaning => 'integral',
  scriptpos => \&doScriptpos, mathstyle => \&doVariablesizeOp);
DefMathI('\ointop', undef, "\x{222E}", role => 'INTOP', meaning => 'contour-integral',
  scriptpos => \&doScriptpos, mathstyle => \&doVariablesizeOp);
# WHat are these? They look like superscripted parentheses, or combining accents!
# \lhook
# \rhook
Let('\gets', '\leftarrow');

DefPrimitiveI('\lmoustache', undef, "\x{23B0}");
DefPrimitiveI('\rmoustache', undef, "\x{23B1}");
DefMathI('\mapstochar', undef, "\x{21A6}", role => 'ARROW', meaning => 'maps-to');
DefMathI('\owns',       undef, "\x{220B}", role => 'RELOP', meaning => 'contains');

# \skew{}{}{} ????

# \symbol lookup symbol in font by index?
#**********************************************************************
# Other stuff
#**********************************************************************
# Some stuff that got missed in the appendices ?
RawTeX(<<'EoTeX');
\def\@namedef#1{\expandafter\def\csname #1\endcsname}
\def\@nameuse#1{\csname #1\endcsname}
\def\@cons#1#2{\begingroup\let\@elt\relax\xdef#1{#1\@elt #2}\endgroup}
\def\@car#1#2\@nil{#1}
\def\@cdr#1#2\@nil{#2}
\def\@carcube#1#2#3#4\@nil{#1#2#3}
EoTeX

DefMacro('\@ifdefinable Token {}', sub {
    my ($gullet, $token, $if) = @_;
    if (isDefinable($token)) {
      return $if->unlist }
    else {
      my ($slash, @s) = ExplodeText($token->toString);
      DefMacroI('\reserved@a', undef, Tokens(@s));
      return (T_CS('\@notdefinable')); } });

DefMacroI('\@notdefinable', undef,
  '\@latex@error{%
   Command \@backslashchar\reserved@a\space
   already defined.
   Or name \@backslashchar\@qend... illegal,
   see p.192 of the manual}');

DefMacroI('\@qend',   undef, Tokens(Explode('end')));
DefMacroI('\@qrelax', undef, Tokens(Explode('relax')));
DefMacroI('\@spaces', undef, '\space\space\space\space');
Let('\@sptoken', T_SPACE);

DefMacroI('\@uclclist', undef, '\oe\OE\o\O\ae\AE\dh\DH\dj\DJ\l\L\ng\NG\ss\SS\th\TH');

DefMacro('\MakeUppercase{}', sub {
    my @t = LookupDefinition(T_CS('\@uclclist'))->getExpansion->unlist;
    my @x = (T_CS('\def'), T_CS('\i'), T_BEGIN, T_LETTER('I'), T_END,
      T_CS('\def'), T_CS('\j'), T_BEGIN, T_LETTER('J'), T_END);
    while (@t) { push(@x, T_CS('\let'), shift(@t), shift(@t)); }
    my $arg = Expand(Tokens(T_BEGIN, @x, $_[1]->unlist, T_END));
    (T_CS('\uppercase'), T_BEGIN, $arg->unlist, T_END); });

DefMacro('\MakeLowercase{}', sub {
    my @t = LookupDefinition(T_CS('\@uclclist'))->getExpansion->unlist;
    my @x = ();
    while (@t) { my $y = shift(@t); push(@x, T_CS('\let'), shift(@t), $y); }
    my $arg = Expand(Tokens(T_BEGIN, @x, $_[1]->unlist, T_END));
    (T_CS('\lowercase'), T_BEGIN, $arg->unlist, T_END); });

#======================================================================

DefMacroI('\@ehc', undef, "I can't help");

DefMacro('\@gobble{}',           Tokens());
DefMacro('\@gobbletwo{}{}',      Tokens());
DefMacro('\@gobblefour{}{}{}{}', Tokens());
DefMacro('\@firstofone{}',       sub { $_[1]; });
Let('\@iden', '\@firstofone');
DefMacro('\@firstoftwo{}{}',     sub { $_[1]; });
DefMacro('\@secondoftwo{}{}',    sub { $_[2]; });
DefMacro('\@thirdofthree{}{}{}', sub { $_[3]; });
DefMacro('\@expandtwoargs{}{}{}', sub {
    ($_[1]->unlist, T_BEGIN, Expand($_[2])->unlist, T_END, T_BEGIN, Expand($_[3])->unlist, T_END); });

RawTeX(<<'EoTeX');
{\catcode`\^^M=13 \gdef\obeycr{\catcode`\^^M13 \def^^M{\\\relax}%
    \@gobblecr}%
{\catcode`\^^M=13 \gdef\@gobblecr{\@ifnextchar
\@gobble\ignorespaces}}%
\gdef\restorecr{\catcode`\^^M5 }}
EoTeX
RawTeX(<<'EoTeX');
\begingroup
  \catcode`P=12
  \catcode`T=12
  \lowercase{
    \def\x{\def\rem@pt##1.##2PT{##1\ifnum##2>\z@.##2\fi}}}
  \expandafter\endgroup\x
\def\strip@pt{\expandafter\rem@pt\the}
\def\strip@prefix#1>{}
\def\@makeother#1{\catcode`#1=12\relax}
\def\@sanitize{\@makeother\ \@makeother\\\@makeother\$\@makeother\&%
\@makeother\#\@makeother\^\@makeother\_\@makeother\%\@makeother\~}
\def \@onelevel@sanitize #1{%
  \edef #1{\expandafter\strip@prefix
           \meaning #1}%
}
\def\dospecials{\do\ \do\\\do\{\do\}\do\$\do\&%
  \do\#\do\^\do\_\do\%\do\~}
EoTeX

DefMacroI('\nfss@catcodes', undef, <<'EOMacro');
    \makeatletter
    \catcode`\ 9%
     \catcode`\^^I9%
     \catcode`\^^M9%
     \catcode`\\\z@
     \catcode`\{\@ne
     \catcode`\}\tw@
     \catcode`\#6%
     \catcode`\^7%
     \catcode`\%14%
   \@makeother\<%
   \@makeother\>%
   \@makeother\*%
   \@makeother\.%
   \@makeother\-%
   \@makeother\/%
   \@makeother\[%
   \@makeother\]%
   \@makeother\`%
   \@makeother\'%
   \@makeother\"%
EOMacro

sub make_message {
  my ($cmd, @args) = @_;
  my $stomach = $STATE->getStomach;
  $stomach->bgroup;
  Let('\protect', '\string');
  my $message = join("\n", map { ToString(Expand($_)) } @args);
  $stomach->egroup;
  return ('latex', $cmd, $stomach, $message); }

DefPrimitive('\@onlypreamble{}', sub { onlyPreamble('\@onlypreamble'); }); # Don't bother enforcing this.
DefPrimitive('\GenericError{}{}{}{}', sub { Error(make_message('\GenericError', $_[2], $_[3], $_[4])); });
DefPrimitive('\GenericWarning{}{}', sub { Warn(make_message('\GenericWarning', $_[2], $_[3])); });
DefPrimitive('\GenericInfo{}{}',    sub { Info(make_message('\GenericInfo',    $_[1], $_[2])); });

Let('\MessageBreak', '\relax');
RawTeX(<<'EoTeX');
\gdef\PackageError#1#2#3{%
  \GenericError{%
      (#1)\@spaces\@spaces\@spaces\@spaces
   }{%
      Package #1 Error: #2%
   }{%
      See the #1 package documentation for explanation.%
   }{#3}%
}
\def\PackageWarning#1#2{%
  \GenericWarning{%
      (#1)\@spaces\@spaces\@spaces\@spaces
   }{%
      Package #1 Warning: #2%
   }%
}
\def\PackageWarningNoLine#1#2{%
  \PackageWarning{#1}{#2\@gobble}}
\def\PackageInfo#1#2{%
  \GenericInfo{%
      (#1) \@spaces\@spaces\@spaces
   }{%
      Package #1 Info: #2%
   }%
}
\def\ClassError#1#2#3{%
  \GenericError{%
      (#1) \space\@spaces\@spaces\@spaces
   }{%
      Class #1 Error: #2%
   }{%
      See the #1 class documentation for explanation.%
   }{#3}%
}
\def\ClassWarning#1#2{%
  \GenericWarning{%
      (#1) \space\@spaces\@spaces\@spaces
   }{%
      Class #1 Warning: #2%
   }%
}
\def\ClassWarningNoLine#1#2{%
  \ClassWarning{#1}{#2\@gobble}}
\def\ClassInfo#1#2{%
  \GenericInfo{%
      (#1) \space\space\@spaces\@spaces
   }{%
      Class #1 Info: #2%
   }%
}
\def\@latex@error#1#2{%
  \GenericError{%
      \space\space\space\@spaces\@spaces\@spaces
   }{%
      LaTeX Error: #1%
   }{%
      See the LaTeX manual or LaTeX Companion for explanation.%
   }{#2}%
}
\def\@latex@warning#1{%
  \GenericWarning{%
      \space\space\space\@spaces\@spaces\@spaces
   }{%
      LaTeX Warning: #1%
   }%
}
\def\@latex@warning@no@line#1{%
  \@latex@warning{#1\@gobble}}
\def\@latex@info#1{%
  \GenericInfo{%
      \@spaces\@spaces\@spaces
   }{%
      LaTeX Info: #1%
   }%
}
\def\@latex@info@no@line#1{%
  \@latex@info{#1\@gobble}}
EoTeX

DefPrimitive('\@setsize{}{}{}{}', undef);
Let('\@warning',  '\@latex@warning');
Let('\@@warning', '\@latex@warning@no@line');

DefMacro('\G@refundefinedtrue', '');

DefMacro('\@nomath{}',
  '\relax\ifmmode\@font@warning{Command \noexpand#1invalid in math mode}\fi');
DefMacro('\@font@warning{}',
  '\GenericWarning{(Font)\@spaces\@spaces\@spaces\space\space}{LaTeX Font Warning: #1}');

#======================================================================

RawTeX(<<'EOTeX');
  \chardef\@xxxii=32
  \mathchardef\@Mi=10001
  \mathchardef\@Mii=10002
  \mathchardef\@Miii=10003
  \mathchardef\@Miv=10004
EOTeX
DefMacroI('\@vpt',    undef, '5');
DefMacroI('\@vipt',   undef, '6');
DefMacroI('\@viipt',  undef, '7');
DefMacroI('\@viiipt', undef, '8');
DefMacroI('\@ixpt',   undef, '9');
DefMacroI('\@xpt',    undef, '10');
DefMacroI('\@xipt',   undef, '10.95');
DefMacroI('\@xiipt',  undef, '12');
DefMacroI('\@xivpt',  undef, '14.4');
DefMacroI('\@xviipt', undef, '17.28');
DefMacroI('\@xxpt',   undef, '20.74');
DefMacroI('\@xxvpt',  undef, '24.88');

DefMacroI('\@tempa',  undef, '');
DefMacroI('\@tempb',  undef, '');
DefMacroI('\@tempc',  undef, '');
DefMacroI('\@gtempa', undef, '');

RawTeX(<<'EOTeX');
\long\def \loop #1\repeat{%
  \def\iterate{#1\relax  % Extra \relax
               \expandafter\iterate\fi
               }%
  \iterate
  \let\iterate\relax
}
\newdimen\@ydim 							
\let\@@hyph=\-
\newbox\@arstrutbox							
\newbox\@begindvibox							
\newcount\@botnum							
\newdimen\@botroom							 
\newcount\@chclass							 
\newcount\@chnum							
\newdimen\@clnht							
\newdimen\@clnwd							
\newdimen\@colht							
\newcount\@colnum							 
\newdimen\@colroom							
\newbox\@curfield							 
\newbox\@curline							 
\newcount\@currtype							 
\newcount\@curtab							 
\newcount\@curtabmar							
\newbox\@dashbox							
\newcount\@dashcnt							 
\newdimen\@dashdim							 
\newcount\@dbltopnum							
\newdimen\@dbltoproom							
\let\@dischyph=\-
\newcount\@enumdepth 
\newcount\@floatpenalty							
\newdimen\@fpmin							
\newcount \@fpstype							
\newcount\@highpenalty							
\newcount\@hightab							
\newbox\@holdpg								
\newinsert \@kludgeins
\newcount\@lastchclass							
\newbox\@leftcolumn							
\newbox\@linechar							
\newdimen\@linelen							
\newcount\@lowpenalty							
\newdimen\@maxdepth							 
\newcount\@medpenalty							
\newdimen\@mparbottom \@mparbottom\z@
\newinsert\@mpfootins							 
\newcount\@mplistdepth							
\newcount\@multicnt							
\newcount\@nxttabmar							
\newbox\@outputbox							
\newdimen\@pagedp							
\newdimen\@pageht							
\newbox\@picbox								
\newdimen\@picht							
\newdimen \@reqcolroom							
\newskip\@rightskip \@rightskip \z@skip					
\newcount\@savsf							
\newdimen\@savsk							
\newcount\@secpenalty							
\def\@sqrt[#1]{\root #1\of}
\newbox\@tabfbox							
\newcount\@tabpush							
\newdimen \@textfloatsheight						
\newdimen\@textmin							
\newcount\@topnum							
\newdimen\@toproom							
\newcount\@xarg								
\newdimen\@xdim								
\newcount\@yarg								
\newdimen\@ydim								
\newcount\@yyarg							
\newtoks\every@math@size
\newif \if@fcolmade							 
\newdimen\lower@bound							
\newcount\par@deathcycles						 
\newdimen\upper@bound							
\newif\if@insert
\newif\if@colmade
\newif\if@specialpage   \@specialpagefalse
\newif\if@firstcolumn   \@firstcolumntrue
\newif\if@twocolumn     \@twocolumnfalse
\newif\if@twoside       \@twosidefalse
\newif\if@reversemargin \@reversemarginfalse
\newif\if@mparswitch    \@mparswitchfalse
\newcount\col@number    \@ne
\newread\@inputcheck
\newwrite\@unused
\newwrite\@mainaux
\newwrite\@partaux
\let\@auxout=\@mainaux
\openout\@mainaux\jobname.aux
\newcount\@clubpenalty
\@clubpenalty \clubpenalty
\newif\if@filesw \@fileswtrue
\newif\if@partsw \@partswfalse
\def\@tempswafalse{\let\if@tempswa\iffalse}
\def\@tempswatrue{\let\if@tempswa\iftrue}
\let\if@tempswa\iffalse
\newcount\@tempcnta
\newcount\@tempcntb
\newif\if@tempswa
\newdimen\@tempdima
\newdimen\@tempdimb
\newdimen\@tempdimc
\newbox\@tempboxa
\newskip\@tempskipa
\newskip\@tempskipb
\newtoks\@temptokena
\newskip\@flushglue \@flushglue = 0pt plus 1fil
\newif\if@afterindent\@afterindenttrue
\newbox\rootbox

\newcount\@eqcnt
\newcount\@eqpen
\newif\if@eqnsw\@eqnswtrue
\newskip\@centering
\@centering = 0pt plus 1000pt
\let\@eqnsel=\relax

 \long\def\@whilenum#1\do #2{\ifnum #1\relax #2\relax\@iwhilenum{#1\relax
      #2\relax}\fi}
 \long\def\@iwhilenum#1{\ifnum #1\expandafter\@iwhilenum
          \else\expandafter\@gobble\fi{#1}}
 \long\def\@whiledim#1\do #2{\ifdim #1\relax#2\@iwhiledim{#1\relax#2}\fi}
 \long\def\@iwhiledim#1{\ifdim #1\expandafter\@iwhiledim
         \else\expandafter\@gobble\fi{#1}}
 \long\def\@whilesw#1\fi#2{#1#2\@iwhilesw{#1#2}\fi\fi}
 \long\def\@iwhilesw#1\fi{#1\expandafter\@iwhilesw
          \else\@gobbletwo\fi{#1}\fi}
\def\@nnil{\@nil}
\def\@fornoop#1\@@#2#3{}
\long\def\@for#1:=#2\do#3{%
  \expandafter\def\expandafter\@fortmp\expandafter{#2}%
  \ifx\@fortmp\@empty \else
    \expandafter\@forloop#2,\@nil,\@nil\@@#1{#3}\fi}
\long\def\@forloop#1,#2,#3\@@#4#5{\def#4{#1}\ifx #4\@nnil \else
       #5\def#4{#2}\ifx #4\@nnil \else#5\@iforloop #3\@@#4{#5}\fi\fi}
\long\def\@iforloop#1,#2\@@#3#4{\def#3{#1}\ifx #3\@nnil
       \expandafter\@fornoop \else
      #4\relax\expandafter\@iforloop\fi#2\@@#3{#4}}
\def\@tfor#1:={\@tf@r#1 }
\long\def\@tf@r#1#2\do#3{\def\@fortmp{#2}\ifx\@fortmp\space\else
    \@tforloop#2\@nil\@nil\@@#1{#3}\fi}
\long\def\@tforloop#1#2\@@#3#4{\def#3{#1}\ifx #3\@nnil
       \expandafter\@fornoop \else
      #4\relax\expandafter\@tforloop\fi#2\@@#3{#4}}
\long\def\@break@tfor#1\@@#2#3{\fi\fi}
\def\remove@to@nnil#1\@nnil{}
\def\remove@angles#1>{\set@simple@size@args}
\def\remove@star#1*{#1}
\def\@defaultunits{\afterassignment\remove@to@nnil}

\newif\ifmath@fonts \math@fontstrue
\newbox\@labels
\newif\if@inlabel \@inlabelfalse
\newif\if@newlist   \@newlistfalse
\newif\if@noparitem \@noparitemfalse
\newif\if@noparlist \@noparlistfalse
\newif\if@noitemarg \@noitemargfalse
\newif\if@nmbrlist  \@nmbrlistfalse

EOTeX

DefMacroI('\@height', undef, 'height');
DefMacroI('\@width',  undef, 'width');
DefMacroI('\@depth',  undef, 'depth');
DefMacroI('\@minus',  undef, 'minus');
DefMacroI('\@plus',   undef, 'plus');

DefMacroI('\hmode@bgroup', undef, '\leavevmode\bgroup');

DefMacroI('\@backslashchar', undef, T_OTHER('\\'));
DefMacroI('\@percentchar',   undef, T_OTHER('%'));
DefMacroI('\@charlb',        undef, T_LETTER('{'));
DefMacroI('\@charrb',        undef, T_LETTER('}'));
#======================================================================

DefMacroI('\check@mathfonts', undef, Tokens());
DefMacro('\fontsize{}{}', Tokens());

DefMacroI('\defaultscriptratio',       undef, '.7');
DefMacroI('\defaultscriptscriptratio', undef, '.5');

#======================================================================
DefMacroI('\loggingoutput', undef, Tokens());
DefMacroI('\loggingall',    undef, Tokens());
DefMacroI('\tracingfonts',  undef, Tokens());
DefMacroI('\showoverfull',  undef, Tokens());
DefMacroI('\showoutput',    undef, Tokens());
DefMacro('\wlog{}', Tokens());

#======================================================================
# Various symbols, accents, etc from Chapter 3 defined in TeX.pool

#**********************************************************************
# Semi-Undocumented stuff
#**********************************************************************
DefMacro('\@ifnextchar DefToken {}{}', sub {
    my ($gullet, $token, $if, $else) = @_;
    my $next = $gullet->readNonSpace;
    # NOTE: Not actually substituting, but collapsing ## pairs!!!!
    # use \egroup for $next, if we've fallen off end?
    (LaTeXML::Core::Definition::Expandable::substituteTokens(
        XEquals($token, (defined $next ? $next : T_END)) ? $if : $else),
      (defined $next ? ($next) : ())); });
Let('\kernel@ifnextchar', '\@ifnextchar');
Let('\@ifnext',           '\@ifnextchar');    # ????

DefMacro('\@ifstar {}{}', sub {
    my ($gullet, $if, $else) = @_;
    my $next = $gullet->readNonSpace;
    # NOTE: Not actually substituting, but collapsing ## pairs!!!!
    if (T_OTHER('*')->equals($next)) {
      LaTeXML::Core::Definition::Expandable::substituteTokens($if); }
    else {
      (LaTeXML::Core::Definition::Expandable::substituteTokens($else), $next); } });

DefMacro('\@dblarg {}',    '\kernel@ifnextchar[{#1}{\@xdblarg{#1}}');
DefMacro('\@xdblarg {}{}', '#1[{#2}]{#2}');

DefMacro('\@testopt{}{}', sub {
    my ($gullet, $cmd, $option) = @_;
    ($gullet->ifNext(T_OTHER('[')) ? $cmd->unlist
      : ($cmd->unlist, T_OTHER('['), $option->unlist, T_OTHER(']'))); });
Let('\@protected@testopt', '\@testopt');    # ?

Let('\l@ngrel@x', '\relax');                # Never actually used anywhere, but...
DefMacro('\@star@or@long{}', '\@ifstar{\let\l@ngrel@x\relax#1}{\let\l@ngrel@x\long#1}');

# maybe this is easiest just to punt.
RawTeX(<<'EoTeX');
\def\in@#1#2{%
 \def\in@@##1#1##2##3\in@@{%
  \ifx\in@##2\in@false\else\in@true\fi}%
 \in@@#2#1\in@\in@@}
\newif\ifin@
EoTeX

DefMacro('\IfFileExists{}{}{}', sub {
    my ($gullet, $file, $if, $else) = @_;
    $file = ToString(Expand($file));
    (FindFile($file) ? $if->unlist : $else->unlist); });

DefMacro('\InputIfFileExists{}{}{}', sub {
    my ($gullet, $file, $if, $else) = @_;
    $file = ToString(Expand($file));
    if (FindFile($file)) {
      Input($file);
      $if->unlist; }
    else { $else->unlist; } });

#======================================================================
# Hair
DefPrimitiveI('\makeatletter', undef, sub { AssignCatcode('@' => CC_LETTER, 'local'); });
DefPrimitiveI('\makeatother',  undef, sub { AssignCatcode('@' => CC_OTHER,  'local'); });

#**********************************************************************
#**********************************************************************
# Sundry (is this ams ?)
DefPrimitiveI('\textprime', undef, UTF(0xB4));    # ACUTE ACCENT

Let('\endgraf', '\par');
Let('\endline', '\cr');
#**********************************************************************
# Should be defined in each (or many) package, but it's not going to
# get set correctly or maintained, so...
DefMacroI('\fileversion', undef, Tokens());
DefMacroI('\filedate',    undef, Tokens());

# Ultimately these may be overridden by babel,
# various of these are defined in various places by different classes.
# And, at any rate, they're currently unused.
DefMacroI('\chaptername', undef, 'Chapter');
DefMacroI('\partname',    undef, 'Part');
# The rest of these are defined in some classes, but not most.
# DefMacroI('\sectionname',undef,'');
# DefMacroI('\subsectionname',undef,'');
# DefMacroI('\subsubsectionname',undef,Tokens());
# DefMacroI('\paragraphname',undef,Tokens());
# DefMacroI('\subparagraphname',undef,Tokens());

#**********************************************************************
# Stuff that would appear in the aux file... maybe somebody uses it?
DefMacro('\bibdata{}',          Tokens());
DefMacro('\bibcite{}{}',        Tokens());
DefMacro('\citation{}',         Tokens());
DefMacro('\contentsline{}{}{}', Tokens());
DefMacro('\newlabel{}{}',       Tokens());

DefMacroI('\stop', undef, sub { $_[0]->closeMouth(1); return; });
DefMacroI('\ignorespacesafterend', undef, Tokens());
DefMacroI('\mathgroup',            undef, Tokens());
DefMacroI('\mathalpha',            undef, Tokens());

#\def\mathhexbox#1#2#3{\mbox{$\m@th \mathchar"#1#2#3$}}
DefPrimitive('\mathhexbox {}{}{}', sub {
    my ($stomach, $a, $b, $c) = @_;
    my $n = ToString($a) * 256 + ToString($b) * 16 + ToString($c);
    my ($role, $glyph) = decodeMathChar($n);
    return Box($glyph, LookupValue('font')->specialize($glyph)); });

DefMacroI('\nocorrlist', undef, ',.');
Let('\nocorr',    '\relax');
Let('\check@icl', '\@empty');
Let('\check@icr', '\@empty');
DefMacro('\text@command{}',                          '');    # ?
DefMacro('\check@nocorr@ Until:\nocorr Until:\@nil', '');
RawTeX('\newif\ifmaybe@ic');

DefMacroI('\maybe@ic',  undef, '');
DefMacroI('\maybe@ic@', undef, '');
# \t@st@ic
DefMacroI('\sw@slant',    undef, '');
DefMacroI('\fix@penalty', undef, '');

DefPrimitiveI('\@@end', undef, sub { $_[0]->getGullet->flush; return; });

#**********************************************************************
# Modern pdflatex seems to come with hyphenation tables predefined
# for many languages. We don't need or use hyphenation tables,
# but some (versions of some) software (babel), check for
# the presence of these \l@<language> macros
# But also see \iflanguage (re)defined in babel.def.ltxml
RawTeX(<<'EoTeX');
\newlanguage\l@english
\newlanguage\l@usenglishmax
\newlanguage\l@dumylang
\newlanguage\l@nohyphenation
\newlanguage\l@arabic
\newlanguage\l@basque
\newlanguage\l@bulgarian
\newlanguage\l@coptic
\newlanguage\l@welsh
\newlanguage\l@czech
\newlanguage\l@slovak
\newlanguage\l@german
\newlanguage\l@ngerman
\newlanguage\l@danish
\newlanguage\l@esperanto
\newlanguage\l@spanish
\newlanguage\l@catalan
\newlanguage\l@galician
\newlanguage\l@estonian
\newlanguage\l@farsi
\newlanguage\l@finnish
\newlanguage\l@french
\newlanguage\l@greek
\newlanguage\l@monogreek
\newlanguage\l@ancientgreek
\newlanguage\l@croatian
\newlanguage\l@hungarian
\newlanguage\l@interlingua
\newlanguage\l@ibycus
\newlanguage\l@indonesian
\newlanguage\l@icelandic
\newlanguage\l@italian
\newlanguage\l@latin
\newlanguage\l@mongolian
\newlanguage\l@dutch
\newlanguage\l@norsk
\newlanguage\l@polish
\newlanguage\l@portuguese
\newlanguage\l@pinyin
\newlanguage\l@romanian
\newlanguage\l@russian
\newlanguage\l@slovenian
\newlanguage\l@uppersorbian
\newlanguage\l@serbian
\newlanguage\l@swedish
\newlanguage\l@turkish
\newlanguage\l@ukenglish
\newlanguage\l@ukrainiane
EoTeX

#**********************************************************************
DefPrimitive('\protected@write Number {}{}', sub {
    my ($stomach, $port, $prelude, $tokens) = @_;
    $port = ToString($port);
    $stomach->bgroup;
    Let('\thepage', '\relax');
    my @stuff = Digest($prelude);
    Let('\protect', '\@unexpandable@protect');
    if (my $filename = LookupValue('output_file:' . $port)) {
      my $handle   = $filename . '_contents';
      my $contents = LookupValue($handle);
      AssignValue($handle => $contents . UnTeX($tokens) . "\n", 'global'); }
    else {
      print STDERR UnTeX($tokens) . "\n"; }
    $stomach->egroup;
    return @stuff; });

#**********************************************************************
1;