# /=====================================================================\ #
# |  LaTeXML::Post::MathML                                              | #
# | MathML generator 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=/ #

# ================================================================================
# LaTeXML::MathML  Math Formatter for LaTeXML's Parsed Math.
#   Cooperate with the parsed math structure generated by LaTeXML::Math and
# convert into presentation MathML.
# ================================================================================
# Some clarity to work out:
#  We're trying to convert either parsed or unparsed math (sometimes intertwined).
# How clearly do these have to be separated?
# at least, sub/superscripts do not attach to anything meaningful.
# ================================================================================

package LaTeXML::Post::MathML;
use strict;
use LaTeXML::Common::XML;
use base qw(LaTeXML::Post);

our $mmlURI = "http://www.w3.org/1998/Math/MathML";

#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# See END for specific converters.
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Top level
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
sub process {
  my($self,$doc)=@_;
  if(my @maths = $self->find_math_nodes($doc)){
    $self->Progress($doc,"Converting ".scalar(@maths)." formulae");
#    $doc->addNamespace($mmlURI,'m');
    foreach my $math (@maths){
      $self->processNode($doc,$math); }
    $doc->adjust_latexml_doctype('MathML'); } # Add MathML if LaTeXML dtd.
  $doc; }

sub setParallel {
  my($self,@moreprocessors)=@_;
  $$self{parallel}=1;
  $$self{math_processors} = [@moreprocessors]; }

sub find_math_nodes {  $_[1]->findnodes('//ltx:Math'); }

sub getQName {
  $LaTeXML::Post::DOCUMENT->getQName(@_); }

# $self->processNode($doc,$mathnode) is the top-level conversion
# It converts the XMath within $mathnode, and adds it to the $mathnode,
sub processNode {
  my($self,$doc,$math)=@_;
  my $mode = $math->getAttribute('mode')||'inline';
  my $xmath = $doc->findnode('ltx:XMath',$math);
  my $style = ($mode eq 'display' ? 'display' : 'text');
  if($$self{parallel}){
    $doc->addNodes($math,$self->translateParallel($doc,$xmath,$style,'ltx:Math')); }
  else {
    $doc->addNodes($math,$self->translateNode($doc,$xmath,$style,'ltx:Math')); }}

# $self->translateNode($doc,$XMath,$style,$embedding)
# returns the translation of the XMath node (but doesn't insert it)
# $style will be either 'display' or 'text' (if relevant),
# The result should be wrapped as necessary for the result to
# be embedded within the tag $embedding.
# Eg. for parallel markup.

# See END for presentation, content and parallel versions.

# Hook for subclasses to annotate the transformation.
sub augmentNode {
  my($self,$node,$mathml)=@_;
  $mathml; }

#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# General translation utilities.
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

sub realize {
  my($node)=@_;
  $LaTeXML::Post::DOCUMENT->realizeXMNode($node); }

# For a node that is a (possibly embellished) operator,
# find the underlying role.
our %EMBELLISHING_ROLE=(SUPERSCRIPTOP=>1,SUBSCRIPTOP=>1,STACKED=>1,
			OVERACCENT=>1,UNDERACCENT=>1,MODIFIER=>1,MODIFIEROP=>1);
sub getOperatorRole {
  my($node)=@_;
  if(!$node){
    undef; }
  elsif(my $role = $node->getAttribute('role')){
    $role; }
  elsif(getQName($node) eq 'ltx:XMApp'){
    my($op,$base)= element_nodes($node);
    ($EMBELLISHING_ROLE{$op->getAttribute('role')||''}
     ? getOperatorRole($base)
     : undef); }}

#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Table of Translators for presentation|content
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# All translators take XMath XML::LibXML nodes as arguments,
# and return an intermediate form (ie. array form) of MathML to be added.

our $MMLTable_P={};
our $MMLTable_C={};

sub DefMathML {
  my($key,$presentation,$content) =@_;
  $$MMLTable_P{$key} = $presentation if $presentation;
  $$MMLTable_C{$key} = $content if $content; }

sub lookupPresenter {
  my($mode,$role,$name)=@_;
  $name = '?' unless $name;
  $role = '?' unless $role;
  $$MMLTable_P{"$mode:$role:$name"} || $$MMLTable_P{"$mode:?:$name"}
    || $$MMLTable_P{"$mode:$role:?"} || $$MMLTable_P{"$mode:?:?"}; }

sub lookupContent {
  my($mode,$role,$name)=@_;
  $name = '?' unless $name;
  $role = '?' unless $role;
  $$MMLTable_C{"$mode:$role:$name"} || $$MMLTable_C{"$mode:?:$name"}
    || $$MMLTable_C{"$mode:$role:?"} || $$MMLTable_C{"$mode:?:?"}; }


#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Support functions for Presentation MathML
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

sub pmml_top {
  my($self,$node,$style)=@_;
  local $LaTeXML::MathML::PROCESSOR = $self;
  local $LaTeXML::MathML::STYLE = $style;
  local $LaTeXML::MathML::FONT  = find_inherited_attribute($node,'font');
  local $LaTeXML::MathML::SIZE  = find_inherited_attribute($node,'size');
  local $LaTeXML::MathML::COLOR = find_inherited_attribute($node,'color');
  pmml($node); }

sub find_inherited_attribute {
  my($node,$attribute)=@_;
  while($node && isElementNode($node)){
    if(my $value = $node->getAttribute($attribute)){
      return $value; }
    $node = $node->parentNode; }
  return undef; }

our %stylestep=(display=>'text', text=>'script',
	       script=>'scriptscript', scriptscript=>'scriptscript');
our %stylemap
  = (display     =>{text        =>[displaystyle=>'false'],
		    script      =>[displaystyle=>'false',scriptlevel=>'+1'],
		    scriptscript=>[displaystyle=>'false',scriptlevel=>'+2']},
     text        =>{display     =>[displaystyle=>'true'],
		    script      =>[scriptlevel=>'+1'],
		    scriptscript=>[scriptlevel=>'+2']},
     script      =>{display     =>[displaystyle=>'true',scriptlevel=>'-1'],
		    text        =>[scriptlevel=>'-1'],
		    scriptscript=>[scriptlevel=>'+1']},
     scriptscript=>{display     =>[displaystyle=>'true',scriptlevel=>'-2'],
		    text        =>[scriptlevel=>'-2'],
		    script      =>[scriptlevel=>'-1']});

sub pmml_smaller {
  my($node)=@_;
  local $LaTeXML::MathML::STYLE = $stylestep{$LaTeXML::MathML::STYLE};
  pmml($node); }

sub pmml {
  my($node)=@_;
  my $o = $node->getAttribute('open');
  my $c = $node->getAttribute('close');
  my $p = $node->getAttribute('punctuation');
  # Do the core conversion.
  my $result = (getQName($node) eq 'ltx:XMRef'
		? pmml(realize($node))
		: $LaTeXML::MathML::PROCESSOR->augmentNode($node,pmml_internal($node)));
  # Handle generic things: open/close delimiters, punctuation
  $result = pmml_parenthesize($result,$o,$c) if $o || $c;
  $result = ['m:mrow',{},$result,pmml_mo($p)] if $p;
  $result; }

our $NBSP = pack('U',0xA0);

sub pmml_internal {
  my($node)=@_;
  return ['m:merror',{},['m:mtext',{},"Missing Subexpression"]] unless $node;
  my $tag = getQName($node);
  my $role = $node->getAttribute('role');
  if($tag eq 'ltx:XMath'){
    pmml_row(map(pmml($_), element_nodes($node))); } # Really multiple nodes???
  elsif($tag eq 'ltx:XMDual'){
    my($content,$presentation) = element_nodes($node);
    pmml($presentation); }
  elsif(($tag eq 'ltx:XMWrap')||($tag eq 'ltx:XMArg')){	# Only present if parsing failed!
    pmml_row(map(pmml($_),element_nodes($node))); }
  elsif($tag eq 'ltx:XMApp'){
    my($op,@args) = element_nodes($node);
    if(!$op){
      ['m:merror',{},['m:mtext',{},"Missing Operator"]]; }
    elsif($role && ($role =~ /^(FLOAT|POST)(SUB|SUPER)SCRIPT$/)){
      pmml_unparsed_script($1,$2,$op); }
    else {
      my $rop = realize($op);  # NOTE: Could loose open/close on XMRef ???
      my $style = $op->getAttribute('style');
      my $styleattr = $style && $stylemap{$LaTeXML::MathML::STYLE}{$style};
      local $LaTeXML::MathML::STYLE 
	= ($style && $stylestep{$style} ? $style : $LaTeXML::MathML::STYLE);
      my $result = &{ lookupPresenter('Apply',getOperatorRole($rop),$rop->getAttribute('meaning'))
		    }($op,@args);
      $result = ['m:mstyle',{@$styleattr},$result] if $styleattr;
      $result; }}
  elsif($tag eq 'ltx:XMTok'){
    &{ lookupPresenter('Token',$role,$node->getAttribute('meaning')) }($node); }
  elsif($tag eq 'ltx:XMHint'){
    &{ lookupPresenter('Hint',$role,$node->getAttribute('meaning')) }($node); }
  elsif($tag eq 'ltx:XMArray'){
    my $style = $node->getAttribute('style');
    my $styleattr = $style && $stylemap{$LaTeXML::MathML::STYLE}{$style};
    local $LaTeXML::MathML::STYLE 
      = ($style && $stylestep{$style} ? $style : $LaTeXML::MathML::STYLE);
    my @rows = ();
    foreach my $row (element_nodes($node)){
      my @cols = ();
      foreach my $col (element_nodes($row)){
	my $a = $col->getAttribute('align');
	my $b = $col->getAttribute('border');
	my $h = (($col->getAttribute('thead')||'') eq 'true') && 'thead';
	my $c = ($b ? ($h ? "$b $h" : $b) : $h);
	my $cs = $col->getAttribute('colspan');
	my $rs = $col->getAttribute('rowspan');
	push(@cols,['m:mtd',{($a ? (columnalign=>$a):()),
			     ($c ? (class=>$c):()),
			     ($cs ? (columnspan=>$cs):()),
			     ($rs ? (rowspan=>$rs):())},
		    map(pmml($_),element_nodes($col))]); }
      push(@rows,['m:mtr',{},@cols]); }
    my $result = ['m:mtable',{rowspacing=>"0.2ex", columnspacing=>"0.4em"},@rows];
    $result = ['m:mstyle',{@$styleattr},$result] if $styleattr;
    $result; }
  elsif($tag eq 'ltx:XMText'){
    pmml_row(map(pmml_text($_), $node->childNodes)); }
  else {
    my $text = $node->textContent; #  Spaces are significant here
    $text =~ s/^\s+/$NBSP/;
    $text =~ s/\s+$/$NBSP/;
    ['m:mtext',{},$text]; }}

sub pmml_row {
  my(@items)=@_;
  @items = grep($_,@items);
  (scalar(@items) == 1 ? $items[0] : ['m:mrow',{},@items]); }

sub pmml_unrow {
  my($mml)=@_;
  if($mml && (ref $mml)  && ($mml->[0] eq 'm:mrow') && !scalar(keys %{$mml->[1]})){
    my($tag,$attr,@children)=@$mml;
    @children; }
  else {
    ($mml); }}

sub pmml_parenthesize {
  my($item,$open,$close)=@_;
  if(!$open && !$close){
    $item; }
  # OR, maybe we should just use mfenced?
  else {
    ['m:mfenced', {open=>($open||''), close=>($close||'')}, $item]; }}
# ## Maybe better not open the contained mrow; seems to affect bracket size in Moz.???
#   elsif($item && (ref $item)  && ($item->[0] eq 'm:mrow')){
#     my($tag,$attr,@children)=@$item;
#     ['m:mrow',$attr,
#      ($open ? (pmml_mo($open)):()),
#      @children,
#      ($close ? (pmml_mo($close)):())]; }
#   else {
#     ['m:mrow',{},
#      ($open ? (pmml_mo($open,role=>'OPEN')):()),
#      $item,
#      ($close ? (pmml_mo($close,role=>'CLOSE')):())]; }}

sub pmml_punctuate {
  my($separators,@items)=@_;
  $separators='' unless defined $separators;
  my $lastsep=', ';
  my @arglist;
  if(@items){
    push(@arglist,pmml(shift(@items)));
    while(@items){
      $separators =~ s/^(.)//;
      $lastsep = $1 if $1;
      push(@arglist,pmml_mo($lastsep),pmml(shift(@items))); }}
  pmml_row(@arglist); }


# args are XMath nodes
sub pmml_infix {
  my($op,@args)=@_;
  $op = realize($op);
  return ['m:mrow',{}] unless $op && @args; # ??
  my @items=();
  if(scalar(@args) == 1){	# Infix with 1 arg is presumably Prefix!
    push(@items,(ref $op ? pmml($op) : pmml_mo($op)),pmml($args[0])); }
  else {
    ## push(@items, pmml(shift(@args)));
    # Experiment at flattening?
    my $role = getOperatorRole($op);
    my $arg1 = realize(shift(@args));
    if(($role eq 'ADDOP')
       && (getQName($arg1) eq 'ltx:XMApp')
       && !$arg1->getAttribute('open') && !$arg1->getAttribute('close')
       && (getOperatorRole((element_nodes($arg1))[0]) eq $role)){
      push(@items, pmml_unrow(pmml($arg1))); }
    else {
      push(@items, pmml($arg1)); }
    while(@args){
      push(@items,(ref $op ? pmml($op) : pmml_mo($op)));
      push(@items,pmml(shift(@args))); }}
  pmml_row(@items); }

# Mappings between internal fonts & sizes.
# Default math font is roman|medium|upright.
our %mathvariants = ('upright'          =>'normal',
		     'serif'            =>'normal',
		     'medium'           =>'normal',
		     'bold'             =>'bold',
		     'bold upright'     =>'bold',
		     'italic'           =>'italic',
		     'serif italic'     =>'italic',
		     'medium italic'    =>'italic',
		     'bold italic'      =>'bold-italic',
		     'doublestruck'     =>'double-struck',
		     'blackboard'       =>'double-struck',
		     'blackboard upright'=>'double-struck',
		     'fraktur bold'     => 'bold-fraktur',
		     'script'           => 'script',
		     'script italic'    => 'script',
		     'script bold'      => 'bold-script',
		     'caligraphic'      => 'script',
		     'caligraphic bold' => 'bold-script',
		     'fraktur'          => 'fraktur',
		     'sansserif'        => 'sans-serif',
		     'sansserif bold'   => 'bold-sans-serif',
		     'sansserif italic' => 'sans-serif-italic',
		     'sansserif bold italic'   => 'sans-serif-bold-italic',
		     'typewriter'       => 'monospace');

# The font differences (from the containing context) have been deciphered
# into font, size and color attributes.  The font should match
# one of the above... (?)

our %sizes=(tiny=>'small',script=>'small',footnote=>'small',small=>'small',
	    normal=>'normal',
	    large=>'big',Large=>'big',LARGE=>'big',huge=>'big',Huge=>'big',
	    big=>'1.1em', Big=>'1.5em', bigg=>'2.0em', Bigg=>'2.5em');

# These are the strings that should be known as fences in a normal operator dictionary.
our %fences=('('=>1,')'=>1, '['=>1, ']'=>1, '{'=>1, '}'=>1, "\x{201C}"=>1,"\x{201D}"=>1,
	     "\`"=>1, "'"=>1, "<"=>1,">"=>1, "\x{2329}"=>1,"\x{232A}"=>1,
	     "\x{230A}"=>1, "\x{230B}"=>1, "\x{2308}"=>1,"\x{2309}"=>1);

sub pmml_mi {
  my($item,%attr)=@_;
  my $font  = (ref $item ? $item->getAttribute('font') : $attr{font}) ||  $LaTeXML::MathML::FONT;
  my $size  = (ref $item ? $item->getAttribute('size') : $attr{size}) || $LaTeXML::MathML::SIZE;
  my $color = (ref $item ? $item->getAttribute('color') : $attr{color}) || $LaTeXML::MathML::COLOR;
  my $text  = (ref $item ?  $item->textContent : $item);
  my $variant = ($font ? $mathvariants{$font} : '');
  if($font && !$variant){
    warn "Unrecognized font variant \"$font\""; $variant=''; }
  if($text =~ /^.$/){	# Single char in mi?
    if($variant eq 'italic'){ $variant = ''; } # Defaults to italic
    elsif(!$variant){ $variant = 'normal'; }}  # must say so explicitly.
  ['m:mi',{($variant ? (mathvariant=>$variant):()),
	   ($size    ? (mathsize=>$sizes{$size}):()),
	   ($color   ? (mathcolor=>$color):())},
   $text]; }

# Really, the same issues as with mi.
sub pmml_mn {
  my($item,%attr)=@_;
  my $font  = (ref $item ? $item->getAttribute('font') : $attr{font}) ||  $LaTeXML::MathML::FONT;
  my $size  = (ref $item ? $item->getAttribute('size') : $attr{size}) || $LaTeXML::MathML::SIZE;
  my $color = (ref $item ? $item->getAttribute('color') : $attr{color}) || $LaTeXML::MathML::COLOR;
  my $text  = (ref $item ?  $item->textContent : $item);
  my $variant = ($font ? $mathvariants{$font} : '');
  if($font && !$variant){
    warn "Unrecognized font variant \"$font\""; $variant=''; }
  ['m:mn',{($variant ? (mathvariant=>$variant):()),
	   ($size    ? (mathsize=>$sizes{$size}):()),
	   ($color   ? (mathcolor=>$color):())},
   $text]; }

sub pmml_mo {
  my($item,%attr)=@_;
  my $font  = (ref $item ? $item->getAttribute('font') : $attr{font});
  my $size  = (ref $item ? $item->getAttribute('size') : $attr{size});
  my $color = (ref $item ? $item->getAttribute('color') : $attr{color});
  my $text  = (ref $item ?  $item->textContent : $item);
  my $variant = ($font ? $mathvariants{$font} : '');
  my $role  = (ref $item ? $item->getAttribute('role') : $attr{role});
  my $style = (ref $item ? $item->getAttribute('style') : $attr{style});
  my $isstretchy = $style && ($style =~ /\bstretchy\b/);
  my $isfence = $role && ($role =~/^(OPEN|CLOSE)$/);
  my $pos   = (ref $item && $item->getAttribute('scriptpos')) || 'post';
  ['m:mo',{($variant ? (mathvariant=>$variant):()),
	   ($size    ? (mathsize=>$sizes{$size}):()),
	   ($color   ? (mathcolor=>$color):()),
	   ($isfence    && !$fences{$text} ? (fence=>'true'):()),
	   ($isstretchy ? (stretchy=>'true') : ()),
	   # If an operator has specifically located it's scripts,
	   # don't let mathml move them.
	   (($pos =~ /mid/) || $LaTeXML::MathML::NOMOVABLELIMITS
	    ? (movablelimits=>'false'):())},
   $text]; }

## (FLOAT|POST)(SUB|SUPER)SCRIPT's should NOT remain in successfully parsed math.
# This gives something `presentable', though not correct.
# What to use for base? I can't reasonably go up & grap the preceding token...
# I doubt an empty <mi/> is valid, but what is?
sub pmml_unparsed_script {
  my($x,$y,$script)=@_;
  [ ($y eq 'SUB' ? 'm:msub' : 'm:msup' ), {}, ['m:mi'],
    pmml_smaller($script)]; }

sub pmml_script {
  my($script)=@_;
  ($script ? pmml_smaller($script) : ['m:none']); }

# Since we're keeping track of display style, under/over vs. sub/super
# We've got to override MathML's desire to do it for us.
# Here, we make sure the eventual inner operator (if any) has
# movablelimits disabled.
# NOTE: Another issue is when the base is "embellished", in particular
# has sub/superscripts of it's own.
# Mozilla (at least?) centers the over/under wrong in that case.
# The OVERUNDERHACK makes the sub & superscripts have 0 width 
# in this situation.
# Worried that this will end up biting me, though...
sub do_overunder {
  my($tag,$base,@scripts)=@_;
  { local $LaTeXML::MathML::NOMOVABLELIMITS=1;
    local $LaTeXML::MathML::OVERUNDERHACKS=1;
    $base = pmml($base); }
  my $form = [$tag,{},$base,map(pmml_smaller($_),@scripts)];
#  if($LaTeXML::MathML::STYLE ne 'display'){ # Workaround Mozilla bug (?)
#    ['m:mstyle',{displaystyle=>'false'},$form]; }
#  else {
    $form; }
#}

sub do_subsup {
  my($tag,$base,@scripts)=@_;
  $base = pmml($base);
  @scripts = map(pmml_smaller($_),@scripts);
  if($LaTeXML::MathML::OVERUNDERHACKS){
    @scripts = map(['m:mpadded',{width=>'0'},$_],@scripts); }
  [$tag,{},$base,@scripts]; }

sub pmml_script_handler {
  my($op,$base,$script)=@_;
  my(@pres,@posts);
  my($prelevel,$postlevel)=(0,0);
  my ($y) = ($op->getAttribute('role')||'') =~ /^(SUPER|SUB)SCRIPTOP$/;
  my ($x,$l)= ($op->getAttribute('scriptpos')||'post0')
    =~ /^(pre|mid|post)?(\d+)?$/;
  if($x eq 'pre'){
    if($y eq 'SUB'){
      push(@pres,[$script,undef]); $prelevel=$l; }
    elsif($y eq 'SUPER'){
      push(@pres,[undef,$script]); $prelevel=$l; }}
  else {
    if($y eq 'SUB'){
      push(@posts,[$script,undef]); $postlevel=$l; }
    elsif($y eq 'SUPER'){
      push(@posts,[undef,$script]); $postlevel=$l; }}

  # Examine $base to see if there are nested scripts.
  # We'll fold them together they seem to be on the appropriate levels
  # Keep from having multiple scripts when $loc is stack!!!
  while(1){
    last unless getQName($base) eq 'ltx:XMApp';
    last if $base->getAttribute('open') || $base->getAttribute('close');
    my($xop,$xbase,$xscript) = element_nodes($base);
    last unless (getQName($xop) eq 'ltx:XMTok');
    my ($ny) = ($xop->getAttribute('role')||'') =~ /^(SUPER|SUB)SCRIPTOP$/;
    last unless $ny;
    my ($nx,$nl)= ($xop->getAttribute('scriptpos')||'post0')
      =~ /^(pre|mid|post)?(\d+)?$/;
#    last unless ($x ne 'mid') || ($nx eq 'mid');
    # what did that mean? Doesn't it mean this???
    last if ($x eq 'mid') || ($nx eq 'mid');

    my $spos = ($ny eq 'SUB' ? 0 : 1);
    if($nx eq 'pre'){
      push(@pres,[undef,undef]) # New empty pair (?)
	if($prelevel ne $nl) || $pres[-1][$spos];
      $pres[-1][$spos] = $xscript; $prelevel = $nl; }
    else {
      unshift(@posts,[undef,undef]) # New empty pair (?)
	if($postlevel ne $nl) || $posts[0][$spos];
      $posts[0][$spos] = $xscript; $postlevel = $nl; }
    $base = $xbase;
  }
  if(scalar(@pres) > 0){
    ['m:mmultiscripts',{},
     pmml($base),
     map( (pmml_script($_->[0]),pmml_script($_->[1])), @posts),
     ['m:mprescripts'],
     map( (pmml_script($_->[0]),pmml_script($_->[1])), @pres)]; }
  elsif(scalar(@posts) > 1){
    ['m:mmultiscripts',{},
     pmml($base),
     map( (pmml_script($_->[0]),pmml_script($_->[1])), @posts)]; }
  elsif(!defined $posts[0][1]){
    if($x eq 'mid'){ do_overunder('m:munder',$base,$posts[0][0]); }
    else           { do_subsup('m:msub',$base,$posts[0][0]); }}
  elsif(!defined $posts[0][0]){
    if($x eq 'mid'){ do_overunder('m:mover',$base,$posts[0][1]); }
    else           { do_subsup('m:msup',$base,$posts[0][1]); }}
  else {
    if($x eq 'mid'){ do_overunder('m:munderover',$base,$posts[0][0],$posts[0][1]); }
    else           { do_subsup('m:msubsup',$base,$posts[0][0],$posts[0][1]); }}}

# Handle text contents.
# Note that (currently) MathML doesn't allow math nested in m:mtext,
# nor in fact any other markup within m:mtext,
# but LaTeXML creates that, if the document is structured that way.
# Here we try to flatten the contents to strings, but keep the math as math
sub pmml_text {
  my($node)=@_;
  return () unless $node;
  my $type = $node->nodeType;
  if($type == XML_TEXT_NODE){
    my $string = $node->textContent;
    $string =~ s/^\s/$NBSP/;     $string =~ s/\s$/$NBSP/;
    (['m:mtext',{},$string]); }
  elsif($type == XML_DOCUMENT_FRAG_NODE){
    map(pmml_text($_), $node->childNodes); }
  elsif($type == XML_ELEMENT_NODE){
    my $tag = getQName($node);
    if($tag eq 'ltx:Math'){
      my $xmath = $LaTeXML::Post::DOCUMENT->findnode('ltx:XMath',$node);
      ($xmath ? pmml($xmath) : ()); }
    else {			# Just recurse on raw content????
      map(pmml_text($_), $node->childNodes); }}
  else {
    (); }}

#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Support functions for Content MathML
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

sub cmml_top {
  my($self,$node)=@_;
  local $LaTeXML::MathML::PROCESSOR = $self;
  local $LaTeXML::MathML::STYLE = 'text';
  local $LaTeXML::MathML::FONT  = find_inherited_attribute($node,'font');
  local $LaTeXML::MathML::SIZE  = find_inherited_attribute($node,'size');
  local $LaTeXML::MathML::COLOR = find_inherited_attribute($node,'color');
  cmml($node); }

sub cmml {
  my($node)=@_;
  return ['m:merror',{},['m:mtext',{},"Missing Subexpression"]] unless $node;
  $node = realize($node) if getQName($node) eq 'ltx:XMRef';
  my $tag = getQName($node);
  if($tag eq 'ltx:XMath'){
    my($item,@rest)=  element_nodes($node);
    print STDERR "Warning! got extra nodes for content!\n" if @rest;
    cmml($item); }
  elsif($tag eq 'ltx:XMDual'){
    my($content,$presentation) = element_nodes($node);
    cmml($content); }
  elsif($tag eq 'ltx:XMWrap'){	# Only present if parsing failed!
    pmml_row(map(pmml($_),element_nodes($node))); } # ????
  elsif($tag eq 'ltx:XMApp'){
    # Experiment: If XMApp has role ID, we treat it as a "Decorated Symbol"
    if(($node->getAttribute('role')||'') eq 'ID'){
      cmml_decoratedSymbol($node); }
    else {
      my($op,@args) = element_nodes($node);
      if(!$op){
	['m:merror',{},['m:mtext',{},"Missing Operator"]]; }
      else {
	my $rop = realize($op);		# NOTE: Could loose open/close on XMRef ???
	&{ lookupContent('Apply',$rop->getAttribute('role'),$rop->getAttribute('meaning')) }($op,@args); }}}
  elsif($tag eq 'ltx:XMTok'){
    &{ lookupContent('Token',$node->getAttribute('role'),$node->getAttribute('meaning')) }($node); }
  elsif($tag eq 'ltx:XMHint'){	# ????
    &{ lookupContent('Hint',$node->getAttribute('role'),$node->getAttribute('meaning')) }($node); }
  else {
    ['m:mtext',{},$node->textContent]; }}

# Or csymbol if there's some kind of "defining" attribute?
sub cmml_ci {
  my($item)=@_;
  my $content = (ref $item ?  $item->textContent : $item);
  ['m:ci',{},$content]; }

# Experimental; for an XMApp with role=ID, we treat it as a ci
# or ultimately as csymbol, if it had defining attributes,
# but we format its contents as pmml
sub cmml_decoratedSymbol {
  my($item)=@_;
  ['m:ci',{},pmml($item)]; }

#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Tranlators
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Organized according to the MathML Content element lists.
# As a general rule, presentation conversions are based on role
# (eg "Token:role:?"), whereas content conversions are based
# on meaning or name (eg. "Token:?:meaning").

#======================================================================
# Token elements:
#   cn, ci, csymbol

DefMathML("Token:?:?",           \&pmml_mi, \&cmml_ci);
DefMathML("Token:PUNCT:?",       \&pmml_mo, undef);
DefMathML("Token:PERIOD:?",      \&pmml_mo, undef);
DefMathML("Token:OPEN:?",        \&pmml_mo, undef);
DefMathML("Token:CLOSE:?",       \&pmml_mo, undef);
DefMathML("Token:MIDDLE:?",      \&pmml_mo, undef);
DefMathML("Token:VERTBAR:?",     \&pmml_mo, undef);
DefMathML("Token:ARROW:?",       \&pmml_mo, undef);
DefMathML("Token:OVERACCENT:?",  \&pmml_mo, undef);
DefMathML("Token:UNDERACCENT:?", \&pmml_mo, undef);

DefMathML("Token:NUMBER:?",      \&pmml_mn, sub { ['m:cn',{},$_[0]->textContent]; });
DefMathML("Token:?:absent", sub { ['m:none']} );
DefMathML('Hint:?:?', sub { undef; }, sub { undef; }); # Should Disappear!

# At presentation level, these are essentially adorned tokens.
# args are (accent,base)
DefMathML('Apply:OVERACCENT:?', sub {
  my($accent,$base)=@_;
  ['m:mover',{accent=>'true'}, pmml($base),pmml_smaller($accent)]; });

DefMathML('Apply:UNDERACCENT:?', sub {
  my($accent,$base)=@_;
  ['m:munder',{accent=>'true'}, pmml($base),pmml_smaller($accent)]; });

#======================================================================
# Basic Content elements:
#   apply, interval, inverse, sep, condition, declare, lambda, compose, ident,
#   domain, codomain, image, domainofapplication, piecewise, piece, otherwise

DefMathML("Token:APPLYOP:?",  \&pmml_mo, undef); # APPLYOP is (only) \x{2061}; FUNCTION APPLICATION
DefMathML("Token:OPERATOR:?", \&pmml_mo, undef);

DefMathML('Apply:?:?', sub {
  my($op,@args)=@_;
  ['m:mrow',{},
   pmml($op),pmml_mo("\x{2061}"),	# FUNCTION APPLICATION
   pmml_parenthesize(pmml_punctuate($op->getAttribute('separators'),@args),
		     $op->getAttribute('argopen'),$op->getAttribute('argclose'))]; },
  sub {
    my($op,@args)=@_;
    ['m:apply',{},cmml($op), map(cmml($_),@args)]; });
DefMathML('Apply:COMPOSEOP:?', \&pmml_infix, undef);

DefMathML("Token:?:open-interval",       undef,sub{['m:interval',{closure=>"open"}];});
DefMathML("Token:?:closed-interval",     undef,sub{['m:interval',{closure=>"closed"}];});
DefMathML("Token:?:closed-open-interval",undef,sub{['m:interval',{closure=>"closed-open"}];});
DefMathML("Token:?:open-closed-interval",undef,sub{['m:interval',{closure=>"open-closed"}];});

DefMathML("Token:?:inverse",    undef, sub{['m:inverse'];});
DefMathML("Token:?:lambda",     undef, sub{['m:lambda'];});
DefMathML("Token:?:compose",    undef, sub{['m:compose'];});
DefMathML("Token:?:identity",   undef, sub{['m:ident'];});
DefMathML("Token:?:domain",     undef, sub{['m:domain'];});
DefMathML("Token:?:codomain",   undef, sub{['m:codomain'];});
DefMathML("Token:?:image",      undef, sub{['m:image'];});
DefMathML("Token:?:piecewise",  undef, sub{['m:piecewise'];});
DefMathML("Token:?:piece",      undef, sub{['m:piece'];});
DefMathML("Token:?:otherwise",  undef, sub{['m:otherwise'];});

#======================================================================
# Arithmetic, Algebra and Logic:
#   quotient, factorial, divide, max, min, minus, plus, power, rem, times, root
#   gcd, and, or, xor, not, implies, forall, exists, abs, conjugate, arg, real,
#   imaginary, lcm, floor, ceiling.

# BRM:

DefMathML("Token:ADDOP:?",       \&pmml_mo,    undef);
DefMathML("Token:ADDOP:plus",     undef,       sub { ['m:plus'];});
DefMathML("Token:ADDOP:minus",    undef,       sub { ['m:minus'];});
DefMathML('Apply:ADDOP:?',       \&pmml_infix, undef);

DefMathML("Token:MULOP:?",       \&pmml_mo,    undef);
DefMathML('Apply:MULOP:?',       \&pmml_infix, undef);
DefMathML('Apply:?:divide', sub {
  my($op,$num,$den)=@_;
  my $style = $op->getAttribute('style');
  my $thickness = $op->getAttribute('thickness');
#  ['m:mfrac',{($thickness ? (linethickness=>$thickness):()),
#	    ($style && ($style eq 'inline') ? (bevelled=>'true'):())},
#   pmml_smaller($num),pmml_smaller($den)]; });
  # Bevelled looks crappy (operands too small) in Mozilla, so just open-code it.
  if($style && ($style eq 'inline')){
    ['m:mrow',{},pmml($num),pmml_mo('/'),pmml($den)]; }
  else {
    ['m:mfrac',{($thickness ? (linethickness=>$thickness):())},
     pmml_smaller($num),pmml_smaller($den)]; }});

DefMathML("Token:SUPOP:?",         \&pmml_mo,   undef);
DefMathML('Apply:SUPERSCRIPTOP:?', \&pmml_script_handler, undef);
DefMathML('Apply:SUBSCRIPTOP:?',   \&pmml_script_handler, undef);
DefMathML('Token:SUPERSCRIPTOP:?', undef, sub{['m:csymbol',{cd=>'ambiguous'},'superscript'];});
DefMathML('Token:SUBSCRIPTOP:?',   undef, sub{['m:csymbol',{cd=>'ambiguous'},'subscript'];});

DefMathML('Apply:POSTFIX:?', sub {
  ['m:mrow',{},pmml($_[1]),pmml($_[0])]; });

DefMathML('Apply:?:square-root', sub { ['m:msqrt',{},pmml($_[1])]; });
DefMathML('Apply:?:root', sub { ['m:mroot',{},pmml($_[2]),pmml_smaller($_[1])]; });

# Note MML's distinction between quotient and divide: quotient yeilds an integer
DefMathML("Token:?:quotient",  undef, sub{['m:quotient'];});
DefMathML("Token:?:factorial", undef, sub{['m:factorial'];});
DefMathML("Token:?:divide",    undef, sub{['m:divide'];});
DefMathML("Token:?:maximum",   undef, sub{['m:max'];});
DefMathML("Token:?:minimum",   undef, sub{['m:min'];});
DefMathML("Token:?:minus",     undef, sub{['m:minus'];});
DefMathML("Token:?:uminus",    undef, sub{['m:uminus'];});
DefMathML("Token:?:plus",      undef, sub{['m:plus'];});
DefMathML("Token:?:power",     undef, sub{['m:power'];});
DefMathML("Token:?:remainder", undef, sub{['m:rem'];});
DefMathML("Token:?:times",     undef, sub{['m:times'];});
DefMathML("Token:?:root",      undef, sub{['m:root'];});
DefMathML("Token:?:gcd",       undef, sub{['m:gcd'];});
DefMathML("Token:?:and",       undef, sub{['m:and'];});
DefMathML("Token:?:or",        undef, sub{['m:or'];});
DefMathML("Token:?:xor",       undef, sub{['m:xor'];});
DefMathML("Token:?:not",       undef, sub{['m:not'];});
DefMathML("Token:?:implies",   undef, sub{['m:implies'];});
DefMathML("Token:?:forall",    undef, sub{['m:forall'];});
DefMathML("Token:?:exists",    undef, sub{['m:exists'];});
DefMathML("Token:?:absolute-value",undef, sub{['m:abs'];});
DefMathML("Token:?:conjugate", undef, sub{['m:conjugate'];});
DefMathML("Token:?:argument",  undef, sub{['m:arg'];});
DefMathML("Token:?:real-part", undef, sub{['m:real'];});
DefMathML("Token:?:imaginary-part", undef, sub{['m:imaginary'];});
DefMathML("Token:?:lcm",       undef, sub{['m:lcm'];});
DefMathML("Token:?:floor",     undef, sub{['m:floor'];});
DefMathML("Token:?:ceiling",   undef, sub{['m:ceiling'];});

#======================================================================
# Relations:
#   eq, neq, gt, lt, geq, leq, equivalent, approx, factorof

DefMathML("Token:RELOP:?",         \&pmml_mo);
DefMathML("Token:?:equals",               undef, sub{['m:eq'];});
DefMathML("Token:?:not-equals",           undef, sub{['m:neq'];});
DefMathML("Token:?:greater-than",         undef, sub{['m:gt'];});
DefMathML("Token:?:less-than",            undef, sub{['m:lt'];});
DefMathML("Token:?:greater-than-or-equals",undef, sub{['m:geq'];});
DefMathML("Token:?:less-than-or-equals",  undef, sub{['m:leq'];});
DefMathML("Token:?:equivalent-to",        undef, sub{['m:equivalent'];});
DefMathML("Token:?:approximately-equals", undef, sub{['m:approx'];});
DefMathML("Token:?:factor-of",            undef, sub{['m:factorof'];});

DefMathML("Token:METARELOP:?",     \&pmml_mo);
DefMathML('Apply:RELOP:?',         \&pmml_infix);
DefMathML('Apply:METARELOP:?',     \&pmml_infix);

# Top level relations
DefMathML('Apply:?:formulae',sub { 
  my($op,@elements)=@_;
  pmml_punctuate($op->getAttribute('separators'),@elements); });
# TRICKY: How should this get converted to cmml ???
DefMathML('Apply:?:multirelation',sub { 
  my($op,@elements)=@_;
  pmml_row(map(pmml($_),@elements)); });

#======================================================================
# Calculus and Vector Calculus:
#   int, diff, partialdiff, lowlimit, uplimit, bvar, degree, 
#   divergence, grad, curl, laplacian.

DefMathML("Token:INTOP:?",       \&pmml_mo);
DefMathML("Token:LIMITOP:?",     \&pmml_mo);
DefMathML('Apply:ARROW:?',       \&pmml_infix);

DefMathML("Token:?:integral",             undef, sub{['m:int'];});
DefMathML("Token:?:differential",         undef, sub{['m:diff'];});
DefMathML("Token:?:partial-differential", undef, sub{['m:partialdiff'];});
# lowlimit, uplimit, degree ?
DefMathML("Token:?:divergence",           undef, sub{['m:divergence'];});
DefMathML("Token:?:gradient",             undef, sub{['m:grad'];});
DefMathML("Token:?:curl",                 undef, sub{['m:curl'];});
DefMathML("Token:?:laplacian",            undef, sub{['m:laplacian'];});

#======================================================================
# Theory of Sets,
#   set, list, union, intersect, in, notin, subset, prsubset, notsubset, notprsubset,
#   setdiff, card, cartesianproduct.

DefMathML("Token:?:set",              undef, sub{['m:set'];});
DefMathML("Token:?:list",             undef, sub{['m:list'];});
DefMathML("Token:?:union",            undef, sub{['m:union'];});
DefMathML("Token:?:intersection",     undef, sub{['m:intersect'];});
DefMathML("Token:?:in",               undef, sub{['m:in'];});
DefMathML("Token:?:not-in",           undef, sub{['m:notin'];});
DefMathML("Token:?:subset",           undef, sub{['m:subset'];});
DefMathML("Token:?:proper-subset",    undef, sub{['m:prsubset'];});
DefMathML("Token:?:not-subset",       undef, sub{['m:notsubset'];});
DefMathML("Token:?:not-proper-subset",undef, sub{['m:notprsubset'];});
DefMathML("Token:?:set-difference",   undef, sub{['m:setdiff'];});
DefMathML("Token:?:cardinality",      undef, sub{['m:card'];});
DefMathML("Token:?:cartesian-product",undef, sub{['m:cartesianproduct'];});

#======================================================================
# Sequences and Series:
#   sum, product, limit, tendsto
# (but see calculus for limit too!!)

DefMathML("Token:SUMOP:?",       \&pmml_mo);
sub pmml_bigop {
  my($op,$body)=@_;
  ['m:mrow',{}, pmml($op), pmml_unrow(pmml($body))]; }
DefMathML('Apply:BIGOP:?',\&pmml_bigop);
DefMathML('Apply:INTOP:?',\&pmml_bigop);
DefMathML('Apply:SUMOP:?',\&pmml_bigop);

DefMathML('Apply:?:limit-from', sub {
  my($op,$arg,$dir)=@_;
  ['m:mrow',{},pmml($arg),pmml($dir)]; });

DefMathML('Apply:?:annotated', sub {
  my($op,$var,$annotation)=@_;
  ['m:mrow',{},pmml($var),pmml($annotation)];});

# NOTE: Markup probably isn't right here....
DefMathML('Apply:?:evaluated-at', sub {
  my($op,$expr,$value1,$value2)=@_;
#   if($value2){
#     pmml_row(pmml($expr),['m:msubsup',{},pmml_mo('|'),pmml_smaller($value1),pmml_smaller($value2)]); }
#   else {
#     pmml_row(pmml($expr),['m:msub',{},pmml_mo('|'),pmml_smaller($value1)]); }});
  # Try with mfenced
  if($value2){
    ['m:msubsup',{},
     ['m:mfenced',{open=>'',close=>'|'},pmml($expr)],
     pmml_smaller($value1),pmml_smaller($value2)]; }
  else {
    ['m:msub',{},
     ['m:mfenced',{open=>'',close=>'|'},pmml($expr)],
     pmml_smaller($value1)]; }});

DefMathML("Token:?:sum",          undef, sub{['m:sum'];});
DefMathML("Token:?:prod",         undef, sub{['m:prod'];});
DefMathML("Token:?:limit",        undef, sub{['m:limit'];});
DefMathML("Token:?:tends-to",     undef, sub{['m:tendsto'];});

#======================================================================
# Elementary Classical Functions,
#   exp, ln, log, sin, cos tan, sec, csc, cot, sinh, cosh, tanh, sech, csch, coth,
#   arcsin, arccos, arctan, arccosh, arccot, arccoth, arccsc, arccsch, arcsec, arcsech,
#   arcsinh, arctanh

DefMathML("Token:?:exponential",                   undef, sub { ['m:exp']; });
DefMathML("Token:?:natural-logarithm",             undef, sub { ['m:ln']; });
DefMathML("Token:?:logarithm",                     undef, sub { ['m:log']; });
DefMathML("Token:?:sine",                          undef, sub { ['m:sin']; });
DefMathML("Token:?:cosine",                        undef, sub { ['m:cos']; });
DefMathML("Token:?:tangent",                       undef, sub { ['m:tan']; });
DefMathML("Token:?:secant",                        undef, sub { ['m:sec']; });
DefMathML("Token:?:cosecant",                      undef, sub { ['m:csc']; });
DefMathML("Token:?:cotangent",                     undef, sub { ['m:cot']; });
DefMathML("Token:?:hyperbolic-sine",               undef, sub { ['m:sinh']; });
DefMathML("Token:?:hyperbolic-cosine",             undef, sub { ['m:cosh']; });
DefMathML("Token:?:hyperbolic-tangent",            undef, sub { ['m:tanh']; });
DefMathML("Token:?:hyperbolic-secant",             undef, sub { ['m:sech']; });
DefMathML("Token:?:hyperbolic-cosecant",           undef, sub { ['m:csch']; });
DefMathML("Token:?:hyperbolic-cotantent",          undef, sub { ['m:coth']; });
DefMathML("Token:?:inverse-sine",                  undef, sub { ['m:arcsin']; });
DefMathML("Token:?:inverse-cosine",                undef, sub { ['m:arccos']; });
DefMathML("Token:?:inverse-tangent",               undef, sub { ['m:arctan']; });
DefMathML("Token:?:inverse-secant",                undef, sub { ['m:arcsec']; });
DefMathML("Token:?:inverse-cosecant",              undef, sub { ['m:arccsc']; });
DefMathML("Token:?:inverse-cotangent",             undef, sub { ['m:arccot']; });
DefMathML("Token:?:inverse-hyperbolic-sine",       undef, sub { ['m:arcsinh']; });
DefMathML("Token:?:inverse-hyperbolic-cosine",     undef, sub { ['m:arccosh']; });
DefMathML("Token:?:inverse-hyperbolic-tangent",    undef, sub { ['m:arctanh']; });
DefMathML("Token:?:inverse-hyperbolic-secant",     undef, sub { ['m:arcsech']; });
DefMathML("Token:?:inverse-hyperbolic-cosecant",   undef, sub { ['m:arccsch']; });
DefMathML("Token:?:inverse-hyperbolic-cotangent",  undef, sub { ['m:arccoth']; });

#======================================================================
# Statistics:
#   mean, sdev, variance, median, mode, moment, momentabout

DefMathML("Token:?:mean",               undef, sub{['m:mean'];});
DefMathML("Token:?:standard-deviation", undef, sub{['m:sdev'];});
DefMathML("Token:?:variance",           undef, sub{['m:var'];});
DefMathML("Token:?:median",             undef, sub{['m:median'];});
DefMathML("Token:?:mode",               undef, sub{['m:mode'];});
DefMathML("Token:?:moment",             undef, sub{['m:moment'];});
# momentabout ???

#======================================================================
# Linear Algebra:
#   vector, matrix, matrixrow, determinant, transpose, selector, 
#   vectorproduct, scalarproduct, outerproduct.

DefMathML("Token:?:vector",         undef, sub{['m:vector'];});
DefMathML("Token:?:matrix",         undef, sub{['m:matrix'];});
DefMathML("Token:?:determinant",    undef, sub{['m:determinant'];});
DefMathML("Token:?:transpose",      undef, sub{['m:transpose'];});
DefMathML("Token:?:selector",       undef, sub{['m:selector'];});
DefMathML("Token:?:vector-product", undef, sub{['m:vectorproduct'];});
DefMathML("Token:?:scalar-product", undef, sub{['m:scalarproduct'];});
DefMathML("Token:?:outer-product",  undef, sub{['m:outerproduct'];});

#======================================================================
# Semantic Mapping Elements
#   annotation, semantics, annotation-xml
#======================================================================
# Constant and Symbol Elements
#   integers, reals, rationals, naturalnumbers, complexes, primes,
#   exponentiale, imaginaryi, notanumber, true, false, emptyset, pi,
#   eulergamma, infinity

DefMathML("Token:ID:integers",       undef, sub{['m:integers'];});
DefMathML("Token:ID:reals",          undef, sub{['m:reals'];});
DefMathML("Token:ID:rationals",      undef, sub{['m:rationals'];});
DefMathML("Token:ID:numbers",        undef, sub{['m:naturalnumbers'];});
DefMathML("Token:ID:complexes",      undef, sub{['m:complexes'];});
DefMathML("Token:ID:primes",         undef, sub{['m:primes'];});
DefMathML("Token:ID:exponential-e",  undef, sub{['m:exponentiale'];});
DefMathML("Token:ID:imaginary-i",    undef, sub{['m:imaginaryi'];});
DefMathML("Token:ID:notanumber",     undef, sub{['m:notanumber'];});
DefMathML("Token:ID:true",           undef, sub{['m:true'];});
DefMathML("Token:ID:false",          undef, sub{['m:false'];});
DefMathML("Token:ID:empty-set",      undef, sub{['m:emptyset'];});
DefMathML("Token:ID:circular-pi",    undef, sub{['m:pi'];});
DefMathML("Token:ID:Euler-constant", undef, sub{['m:eulergamma'];});
DefMathML("Token:ID:infinity",       undef, sub{['m:infinity'];});

#======================================================================
# Purely presentational constructs.
# An issue here:
#  Some constructs are pretty purely presentational.  Hopefully, these would
# only appear in XWrap's or in the presentation branch of an XMDual, so we won't
# attempt to convert them to content.  But if we do, should we warn?

DefMathML('Apply:FENCED:?',sub {
  my($op,@elements)=@_;
  pmml_parenthesize(pmml_punctuate($op->getAttribute('separators'),@elements),
		    $op->getAttribute('argopen'), $op->getAttribute('argclose')); });

# Note how annoyingly MML's arrays don't change the style the same
# way TeX does!
DefMathML('Apply:STACKED:?', sub {
  my($op,$over,$under)=@_;
  my $stack = ['m:mtable',{rowspacing=>"0.2ex", columnspacing=>"0.4em"},
	       ['m:mtr',{},['m:mtd',{},pmml($over)]],
	       ['m:mtr',{},['m:mtd',{},pmml($under)]]];
  if($LaTeXML::MathML::STYLE =~/^(text|script)$/){
    ['m:mstyle',{scriptlevel=>'+1'},$stack]; }
  else {
    $stack; }});

# ================================================================================
# cfrac! Ugh!

# Have to deal w/ screwy structure:
# If denom is a sum/diff then last summand can be: cdots, cfrac 
#  or invisibleTimes of cdots and something which could also be a cfrac!
# NOTE: Deal with cfracstyle!!
# OR: Can XMDual build the right stuff?
# AND, the propogation of style is likely wrong...
sub do_cfrac {
  my($numer,$denom)=@_;
  if(getQName($denom) eq 'ltx:XMApp'){ # Denominator is some kind of application
    my ($denomop,@denomargs)=element_nodes($denom);
    if(($denomop->getAttribute('role')||'') eq 'ADDOP'){ # Is it a sum or difference?
      my $last = pop(@denomargs);			# Check last operand in denominator.
      # this is the current contribution to the cfrac (if we match the last term)
      my $curr = ['m:mfrac',{},pmml_smaller($numer),
		  ['m:mrow',{},
		   (@denomargs > 1 ? pmml_infix($denomop,@denomargs) : pmml($denomargs[0])),
		   pmml_smaller($denomop)]];
      if(($last->textContent ||'') eq "\x{22EF}"){ # Denom ends w/ \cdots
	return ($curr,pmml($last));}		   # bring dots up to toplevel
      elsif(getQName($last) eq 'ltx:XMApp'){	   # Denom ends w/ application --- what kind?
	my($lastop,@lastargs)=element_nodes($last);
	if(($lastop->getAttribute('meaning')||'') eq 'continued-fraction'){ # Denom ends w/ cfrac, pull it to toplevel
	  return ($curr,do_cfrac(@lastargs)); }
#	  return ($curr,pmml($last)); }
	elsif((($lastop->textContent||'') eq "\x{2062}")  # Denom ends w/ * (invisible)
	      && (scalar(@lastargs)==2) && (($lastargs[0]->textContent||'') eq "\x{22EF}")){
	  return ($curr,pmml($lastargs[0]),pmml($lastargs[1])); }}}}
  (['m:mfrac',{},pmml_smaller($numer),pmml_smaller($denom)]); }

DefMathML('Apply:?:continued-fraction', sub {
  my($op,$numer,$denom)=@_;
  my $style = $op->getAttribute('style')||'display';
  if($style eq 'inline'){
    pmml_row(do_cfrac($numer,$denom)); }
  else {
    local $LaTeXML::MathML::STYLE = $stylestep{'display'};
    ['m:mfrac',{},pmml($numer),pmml($denom)]; }});

#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Specific converters for Presentation, Content, or Parallel.
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

# This works for either pmml or cmml.
sub translateParallel {
  my($self,$doc,$xmath,$style,$embedding)=@_;
  $doc->addNamespace($mmlURI,'m');
  my @trans = ['m:semantics',{},
	       $self->translateNode($doc,$xmath,$style,'m:semantics'),
	       map( ['m:annotation-xml',{encoding=>$_->getEncodingName},
		     $_->translateNode($doc,$xmath,$style,'m:annotation-xml')],
		    @{$$self{math_processors}}) ];
  # Wrap unless already embedding within MathML.
  ($embedding =~ /^m:/ ? @trans 
   : ['m:math',{display=>($style eq 'display' ? 'block' : 'inline')},@trans]); }

#================================================================================
# Presentation MathML
package LaTeXML::Post::MathML::Presentation;
use strict;
use base qw(LaTeXML::Post::MathML);

sub translateNode {
  my($self,$doc,$xmath,$style,$embedding)=@_;
  $doc->addNamespace($mmlURI,'m');
  my @trans = $self->pmml_top($xmath,$style);
  my $m = (scalar(@trans)> 1 ? ['m:mrow',{},@trans] : $trans[0]);
  # Wrap unless already embedding within MathML.
  ($embedding =~ /^m:/ ? @trans 
   : ['m:math',{display=>($style eq 'display' ? 'block' : 'inline')},$m]); }

sub getEncodingName { 'MathML-Presentation'; }

#================================================================================
# Presentation MathML with Line breaking
# Not at all sure how this will integrate with Parallel markup...
package LaTeXML::Post::MathML::PresentationLineBreak;
use strict;
use base qw(LaTeXML::Post::MathML::Presentation);
use LaTeXML::Util::MathMLLinebreaker;

sub getEncodingName { 'MathML-Presentation'; }

sub processNode {
  my($self,$doc,$math)=@_;
  my $mode = $math->getAttribute('mode')||'inline';
  my $xmath = $doc->findnode('ltx:XMath',$math);
  my $style = ($mode eq 'display' ? 'display' : 'text');

  # If this is in a MathBranch, it's safe to line-break.
  # Although, we don't really know what length to break to!!!
  if($doc->findnodes('ancestor::ltx:MathBranch',$math)){
    $doc->addNodes($math,$self->translateNodeLinebreaks($doc,$xmath,$style)); }
  # If it's otherwise in a MathFork, it's the main branch.
  # Or, if it's an unmolested inline math,
  # Just do a straight conversion to pmml
  elsif($doc->findnodes('ancestor::ltx:MathFork',$math)
	|| ($mode eq 'inline')){
    $doc->addNodes($math,$self->translateNode($doc,$xmath,$style,'ltx:Math')); }
  # Finally, for a display, we'll want to REPLACE the math by a fork
  else {
    $doc->addNodes($math->parentNode,['ltx:MathFork',{}]);
    my $fork=$math->parentNode->lastChild;
    $math->parentNode->insertBefore($fork,$math);
    $fork->appendChild($math);
    $doc->addNodes($math,$self->translateNode($doc,$xmath,$style,'ltx:Math'));
    $doc->addNodes($fork,['ltx:MathBranch',{},
			  ['ltx:Math',
			   {map(($_=>$math->getAttribute($_)),
				qw(mode tex content-tex text imagesrc imagewidth imageheight))},
			   $self->translateNodeLinebreaks($doc,$xmath,$style)]]);
 }}

sub translateNodeLinebreaks {
  my($self,$doc,$xmath,$style)=@_;
  $doc->addNamespace($mmlURI,'m');
  my @trans = $self->pmml_top($xmath,$style);
  my $mml = (scalar(@trans)> 1 ? ['m:mrow',{},@trans] : $trans[0]);
  my $linelength = $$self{linelength} || 80;
  my $breaker = LaTeXML::Util::MathMLLinebreaker->new();

  ['m:math',{display=>($style eq 'display' ? 'block' : 'inline')},
   $breaker->fitToWidth($xmath,$mml,$linelength,1)]; }

#================================================================================
# Content MathML
package LaTeXML::Post::MathML::Content;
use strict;
use base qw(LaTeXML::Post::MathML);

sub translateNode {
  my($self,$doc,$xmath,$style,$embedding)=@_;
  $doc->addNamespace($mmlURI,'m');
  my @trans = $self->cmml_top($xmath);
  # Wrap unless already embedding within MathML.
  ($embedding =~ /^m:/ ? @trans 
   : ['m:math',{},@trans]); }

sub getEncodingName { 'MathML-Content'; }

#================================================================================
# Parallel MathML
package LaTeXML::Post::MathML::Parallel;
use strict;
use base qw(LaTeXML::Post::MathML);

sub translateNode {
  my($self,$doc,$xmath,$style,$embedding)=@_;
  my($main_proc,@annotation_procs)=@{$$self{math_processors}};
  $doc->addNamespace($mmlURI,'m');
  my @trans = ['m:semantics',{},
	       $main_proc->translateNode($doc,$xmath,$style,'m:semantics'),
	       map( ['m:annotation-xml',{encoding=>$_->getEncodingName},
		     $_->translateNode($doc,$xmath,$style,'m:annotation-xml')],
		    @annotation_procs) ];
  # Wrap unless already embedding within MathML.
  ($embedding =~ /^m:/ ? @trans 
   : ['m:math',{display=>($style eq 'display' ? 'block' : 'inline')},@trans]); }

sub getEncodingName { 'MathML-Parallel'; }

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

1;
