/*
ASCIIMathML.js
==============
This file contains JavaScript functions to convert ASCII math notation
to Presentation MathML. The conversion is done while the XHTML page 
loads, and should work with Internet Explorer 6 + MathPlayer 
(http://www.dessci.com/en/products/mathplayer/) and Mozilla/Netscape 7+.
This is a convenient and inexpensive solution for authoring MathML.

Version 1.1 Jan 9 2004, (c) Peter Jipsen http://www.chapman.edu/~jipsen
Latest version at http://www.chapman.edu/~jipsen/mathml/ASCIIMathML.js
If you use it on a webpage, please send the URL to jipsen@chapman.edu

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License (at http://www.gnu.org/copyleft/gpl.html) 
for more details.
*/

if (document.getElementById==null) 
  alert("This webpage requires a recent browser such as\
\nMozilla/Netscape 7+ or Internet Explorer 6+MathPlayer")

mathcolor = "Red"; // change it to Black if you prefer that
mathdelimit = "`"; // change it to $ if you prefer TeX

// character lists for Mozilla/Netscape fonts
cal = [0xEF35,0x212C,0xEF36,0xEF37,0x2130,0x2131,0xEF38,0x210B,0x2110,0xEF39,0xEF3A,0x2112,0x2133,0xEF3B,0xEF3C,0xEF3D,0xEF3E,0x211B,0xEF3F,0xEF40,0xEF41,0xEF42,0xEF43,0xEF44,0xEF45,0xEF46];
frk = [0xEF5D,0xEF5E,0x212D,0xEF5F,0xEF60,0xEF61,0xEF62,0x210C,0x2111,0xEF63,0xEF64,0xEF65,0xEF66,0xEF67,0xEF68,0xEF69,0xEF6A,0x211C,0xEF6B,0xEF6C,0xEF6D,0xEF6E,0xEF6F,0xEF70,0xEF71,0x2128];
bbb = [0xEF8C,0xEF8D,0x2102,0xEF8E,0xEF8F,0xEF90,0xEF91,0x210D,0xEF92,0xEF93,0xEF94,0xEF95,0xEF96,0x2115,0xEF97,0x2119,0x211A,0x211D,0xEF98,0xEF99,0xEF9A,0xEF9B,0xEF9C,0xEF9D,0xEF9E,0x2124];

symbols = [
//some greek symbols
{input:"alpha",  tag:"mi", output:"\u03B1"},
{input:"beta",   tag:"mi", output:"\u03B2"},
{input:"chi",    tag:"mi", output:"\u03C7"},
{input:"delta",  tag:"mi", output:"\u03B4"},
{input:"Delta",  tag:"mo", output:"\u0394"},
{input:"epsilon", tag:"mi", output:"\u03B5"},
{input:"varepsilon", tag:"mi", output:"\u025B"},
{input:"eta",    tag:"mi", output:"\u03B7"},
{input:"gamma",  tag:"mi", output:"\u03B3"},
{input:"Gamma",  tag:"mo", output:"\u0393"},
{input:"iota",   tag:"mi", output:"\u03B9"},
{input:"kappa",  tag:"mi", output:"\u03BA"},
{input:"lambda", tag:"mi", output:"\u03BB"},
{input:"Lambda", tag:"mo", output:"\u039B"},
{input:"mu",     tag:"mi", output:"\u03BC"},
{input:"nu",     tag:"mi", output:"\u03BD"},
{input:"omega",  tag:"mi", output:"\u03C9"},
{input:"Omega",  tag:"mo", output:"\u03A9"},
{input:"phi",    tag:"mi", output:"\u03C6"},
{input:"varphi", tag:"mi", output:"\u03D5"},
{input:"Phi",    tag:"mo", output:"\u03A6"},
{input:"pi",     tag:"mi", output:"\u03C0"},
{input:"Pi",     tag:"mo", output:"\u03A0"},
{input:"psi",    tag:"mi", output:"\u03C8"},
{input:"rho",    tag:"mi", output:"\u03C1"},
{input:"sigma",  tag:"mi", output:"\u03C3"},
{input:"Sigma",  tag:"mo", output:"\u03A3"},
{input:"tau",    tag:"mi", output:"\u03C4"},
{input:"theta",  tag:"mi", output:"\u03B8"},
{input:"vartheta", tag:"mi", output:"\u03D1"},
{input:"Theta",  tag:"mo", output:"\u0398"},
{input:"upsilon", tag:"mi", output:"\u03C5"},
{input:"xi",     tag:"mi", output:"\u03BE"},
{input:"Xi",     tag:"mo", output:"\u039E"},
{input:"zeta",   tag:"mi", output:"\u03B6"},

//binary operation symbols
{input:"*",  tag:"mo", output:"\u22C5", tex:"cdot"},
{input:"**", tag:"mo", output:"\u22C6", tex:"star"},
{input:"//", tag:"mo", output:"/"},
{input:"\\\\", tag:"mo", output:"\\", tex:"backslash"},
{input:"xx", tag:"mo", output:"\u00D7", tex:"times"},
{input:"-:", tag:"mo", output:"\u00F7", tex:"divide"},
{input:"@",  tag:"mo", output:"\u2218", tex:"circ"},
{input:"o+", tag:"mo", output:"\u2295", tex:"oplus"},
{input:"ox", tag:"mo", output:"\u2297", tex:"otimes"},
{input:"sum", tag:"mo", output:"\u2211", underover:"true"},
{input:"prod", tag:"mo", output:"\u220F", underover:"true"},
{input:"^^",  tag:"mo", output:"\u2227", tex:"wedge"},
{input:"^^^", tag:"mo", output:"\u22C0", tex:"bigwedge", underover:"true"},
{input:"vv",  tag:"mo", output:"\u2228", tex:"vee"},
{input:"vvv", tag:"mo", output:"\u22C1", tex:"bigvee", underover:"true"},
{input:"nn",  tag:"mo", output:"\u2229", tex:"cap"},
{input:"nnn", tag:"mo", output:"\u22C2", tex:"bigcap", underover:"true"},
{input:"uu",  tag:"mo", output:"\u222A", tex:"cup"},
{input:"uuu", tag:"mo", output:"\u22C3", tex:"bigcup", underover:"true"},

//binary relation symbols
{input:"!=",  tag:"mo", output:"\u2260", tex:"ne"},
{input:"lt",  tag:"mo", output:"<"},
{input:"<=",  tag:"mo", output:"\u2264", tex:"le"},
{input:"lt=", tag:"mo", output:"\u2264", tex:"leq"},
{input:">=",  tag:"mo", output:"\u2265", tex:"ge"},
{input:"geq", tag:"mo", output:"\u2265"},
{input:"-<",  tag:"mo", output:"\u227A", tex:"prec"},
{input:"-lt", tag:"mo", output:"\u227A"},
{input:">-",  tag:"mo", output:"\u227B", tex:"succ"},
{input:"in",  tag:"mo", output:"\u2208"},
{input:"!in", tag:"mo", output:"\u2209", tex:"notin"},
{input:"sub", tag:"mo", output:"\u2282", tex:"subset"},
{input:"sup", tag:"mo", output:"\u2283", tex:"supset"},
{input:"sube", tag:"mo", output:"\u2286", tex:"subseteq"},
{input:"supe", tag:"mo", output:"\u2287", tex:"supseteq"},
{input:"-=",  tag:"mo", output:"\u2261", tex:"equiv"},
{input:"~=",  tag:"mo", output:"\u2245", tex:"cong"},
{input:"~~",  tag:"mo", output:"\u2248", tex:"approx"},
{input:"prop", tag:"mo", output:"\u221D", tex:"propto"},

//logical symbols
{input:"and", tag:"mtext", output:"and", space:"1ex"},
{input:"or",  tag:"mtext", output:"or", space:"1ex"},
{input:"not", tag:"mo", output:"\u00AC", tex:"neg"},
{input:"=>",  tag:"mo", output:"\u21D2", tex:"implies"},
{input:"if",  tag:"mo", output:"if", space:"1ex"},
{input:"iff", tag:"mo", output:"\u21D4"},
{input:"AA",  tag:"mo", output:"\u2200", tex:"forall"},
{input:"EE",  tag:"mo", output:"\u2203", tex:"exists"},
{input:"_|_", tag:"mo", output:"\u22A5", tex:"bot"},
{input:"TT",  tag:"mo", output:"\u22A4", tex:"top"},
{input:"|-",  tag:"mo", output:"\u22A2", tex:"vdash"},
{input:"|=",  tag:"mo", output:"\u22A8", tex:"models"},

//grouping brackets
{input:"(", tag:"mo", output:"(", leftBracket:true},
{input:")", tag:"mo", output:")", rightBracket:true},
{input:"[", tag:"mo", output:"[", leftBracket:true},
{input:"]", tag:"mo", output:"]", rightBracket:true},
{input:"{", tag:"mo", output:"{", leftBracket:true},
{input:"}", tag:"mo", output:"}", rightBracket:true},
{input:"(:", tag:"mo", output:"\u2329", leftBracket:true, tex:"langle"},
{input:":)", tag:"mo", output:"\u232A", rightBracket:true, tex:"rangle"},
{input:"{:", tag:"mo", output:"{:", leftBracket:true, invisible:true},
{input:":}", tag:"mo", output:":}", rightBracket:true, invisible:true},

//miscellaneous symbols
{input:"int",  tag:"mo", output:"\u222B"},
{input:"oint", tag:"mo", output:"\u222E"},
{input:"del",  tag:"mo", output:"\u2202", tex:"partial"},
{input:"grad", tag:"mo", output:"\u2207", tex:"nabla"},
{input:"+-",   tag:"mo", output:"\u00B1", tex:"pm"},
{input:"O/",   tag:"mo", output:"\u2205", tex:"emptyset"},
{input:"oo",   tag:"mo", output:"\u221E", tex:"infty"},
{input:"aleph", tag:"mo", output:"\u2135"},
{input:"...",  tag:"mo", output:"...", tex:"ldots"},
{input:"\\ ",  tag:"mo", output:"\u00A0"},
{input:"quad", tag:"mo", output:"\u00A0\u00A0"},
{input:"qquad", tag:"mo", output:"\u00A0\u00A0\u00A0\u00A0"},
{input:"cdots", tag:"mo", output:"\u22EF"},
{input:"diamond", tag:"mo", output:"\u22C4"},
{input:"square", tag:"mo", output:"\u25A1"},
{input:"|_", tag:"mo", output:"\u230A", tex:"lfloor"},
{input:"_|", tag:"mo", output:"\u230B", tex:"rfloor"},
{input:"|~", tag:"mo", output:"\u2308", tex:"lceiling"},
{input:"~|", tag:"mo", output:"\u2309", tex:"rceiling"},
{input:"CC",  tag:"mo", output:"\u2102"},
{input:"NN",  tag:"mo", output:"\u2115"},
{input:"QQ",  tag:"mo", output:"\u211A"},
{input:"RR",  tag:"mo", output:"\u211D"},
{input:"ZZ",  tag:"mo", output:"\u2124"},

//standard functions
{input:"lim",  tag:"mo", output:"lim", underover:"true"},
{input:"sin",  tag:"mo", output:"sin"},
{input:"cos",  tag:"mo", output:"cos"},
{input:"tan",  tag:"mo", output:"tan"},
{input:"sinh", tag:"mo", output:"sinh"},
{input:"cosh", tag:"mo", output:"cosh"},
{input:"tanh", tag:"mo", output:"tanh"},
{input:"cot",  tag:"mo", output:"cot"},
{input:"sec",  tag:"mo", output:"sec"},
{input:"csc",  tag:"mo", output:"csc"},
{input:"log",  tag:"mo", output:"log"},
{input:"ln",   tag:"mo", output:"ln"},
{input:"det",  tag:"mo", output:"det"},
{input:"dim",  tag:"mo", output:"dim"},
{input:"mod",  tag:"mo", output:"mod"},
{input:"gcd",  tag:"mo", output:"gcd"},
{input:"lcm",  tag:"mo", output:"lcm"},

//arrows
{input:"uarr", tag:"mo", output:"\u2191", tex:"uparrow"},
{input:"darr", tag:"mo", output:"\u2193", tex:"downarrow"},
{input:"rarr", tag:"mo", output:"\u2192", tex:"rightarrow"},
{input:"->",   tag:"mo", output:"\u2192", tex:"to"},
{input:"larr", tag:"mo", output:"\u2190", tex:"leftarrow"},
{input:"harr", tag:"mo", output:"\u2194", tex:"leftrightarrow"},
{input:"rArr", tag:"mo", output:"\u21D2", tex:"Rightarrow"},
{input:"lArr", tag:"mo", output:"\u21D0", tex:"Leftarrow"},
{input:"hArr", tag:"mo", output:"\u21D4", tex:"Leftrightarrow"},

//commands with argument
sqrt = {input:"sqrt", tag:"msqrt", output:"sqrt", unary:true},
root = {input:"root", tag:"mroot", output:"root", binary:true},
frac = {input:"frac", tag:"mfrac", output:"/", binary:true},
div = {input:"/", tag:"mfrac", output:"/", infix:true},
sub = {input:"_", tag:"msub",  output:"_", infix:true},
sup = {input:"^", tag:"msup",  output:"^", infix:true},
text = {input:"text", tag:"mtext", output:"text", unary:true},
mbox = {input:"mbox", tag:"mtext", output:"mbox", unary:true},
{input:"bb", tag:"mstyle", atname:"fontweight", atval:"bold", output:"bb", unary:true},
{input:"mathbf", tag:"mstyle", atname:"fontweight", atval:"bold", output:"mathbf", unary:true},
{input:"bbb", tag:"mstyle", atname:"mathvariant", atval:"double-struck", output:"bbb", unary:true, codes:bbb},
{input:"mathbb", tag:"mstyle", atname:"mathvariant", atval:"double-struck", output:"mathbb", unary:true, codes:bbb},
{input:"cc",  tag:"mstyle", atname:"mathvariant", atval:"script", output:"cal", unary:true, codes:cal},
{input:"mathcal", tag:"mstyle", atname:"mathvariant", atval:"script", output:"mathcal", unary:true, codes:cal},
{input:"tt",  tag:"mstyle", atname:"fontfamily", atval:"monospace", output:"tt", unary:true},
{input:"mathtt", tag:"mstyle", atname:"fontfamily", atval:"monospace", output:"mathtt", unary:true},
{input:"fr",  tag:"mstyle", atname:"mathvariant", atval:"fraktur", output:"fr", unary:true, codes:frk},
{input:"mathfrak",  tag:"mstyle", atname:"mathvariant", atval:"fraktur", output:"mathfrak", unary:true, codes:frk}
];

function compareNames(s1,s2) {
  if (s1.input > s2.input) return 1
  else return -1;
}

names = []; //list of input symbols

function initSymbols() {
  var texsymbols = [];
  for (var i=0; i<symbols.length; i++)
    if (symbols[i].tex) 
      texsymbols[texsymbols.length] = 
        {input:symbols[i].tex, tag:"mo", output:symbols[i].output};
  symbols = symbols.concat(texsymbols);
  symbols.sort(compareNames);
  for (var i=0; i<symbols.length; i++) names[i] = symbols[i].input;
}

function myCreateElementXHTML(t) {
  if (document.createElementNS==null)
    return document.createElement(t);
  else
    return document.createElementNS("http://www.w3.org/1999/xhtml",t);
}

function myCreateElementMathML(t) {
  if (document.createElementNS==null)
    return document.createElement("mml:"+t);
  else
    return document.createElementNS("http://www.w3.org/1998/Math/MathML",t);
}

function createMmlNode(name,frag) {
  var node = myCreateElementMathML(name);
  node.appendChild(frag);
  return node;
}

function removeCharsAndBlanks(str,n) {
//remove n characters and any following blanks
  var st;
  if (str.charAt(n)=="\\" && str.charAt(n+1)!="\\" && str.charAt(n+1)!=" ") 
    st = str.slice(n+1);
  else st = str.slice(n);
  for (var i=0; i<st.length && st.charCodeAt(i)<=32; i=i+1);
  return st.slice(i);
}

function position(arr, str, n) { 
// return position >=n where str appears or would be inserted
// assumes arr is sorted
  if (n==0) {
    var h,m;
    n = -1;
    h = arr.length;
    while (n+1<h) {
      m = (n+h) >> 1;
      if (arr[m]<str) n = m; else h = m;
    }
    return h;
  } else
    for (var i=n; i<arr.length && arr[i]<str; i++);
  return i; // i=arr.length || arr[i]>=str
}

function getSymbol(str) {
//return maximal initial substring of str that appears in names
//return null if there is none
  var k = 0; //new pos
  var j = 0; //old pos
  var mk; //match pos
  var st;
  var tagst;
  var match = "";
  var more = true;
  for (var i=1; i<=str.length && more; i++) {
    st = str.slice(0,i); //initial substring of length i
    j = k;
    k = position(names, st, j);
    if (k<names.length && str.slice(0,names[k].length)==names[k]){
      match = names[k];
      mk = k;
      i = match.length;
    }
    more = k<names.length && str.slice(0,names[k].length)>=names[k];
  }
  if (match!="") return symbols[mk]; 
// if str[0] is a digit or - return maxsubstring of digits.digits
  k = 1;
  st = str.slice(0,1);
  var pos = true;
  var integ = true;
  if (st == "-") {
    pos = false;
    st = str.slice(k,k+1);
    k++;
  }
  while ("0"<=st && st<="9" && k<=str.length) {
    st = str.slice(k,k+1);
    k++;
  }
  if (st == ".") {
    integ = false;
    st = str.slice(k,k+1);
    k++;
    while ("0"<=st && st<="9" && k<=str.length) {
      st = str.slice(k,k+1);
      k++;
    }
  }
  if ((pos && integ && k>1) || ((pos || integ) && k>2) || k>3) {
    st = str.slice(0,k-1);
    tagst = "mn";
  } else {
    k = 2;
    st = str.slice(0,1); //take 1 non-letter character
    tagst = (("A"<=st && st<="Z") || ("a"<=st && st<="z")?"mi":"mo");
  }
  return {input:str.slice(0,k-1), tag:tagst, output:st};
}

function removeBrackets(node) {
  if (node.nodeName=="mrow") {
    var st = node.firstChild.firstChild.nodeValue;
    if (st=="(" || st=="[" || st=="{") node.removeChild(node.firstChild);
  }
  if (node.nodeName=="mrow") {
    var st = node.lastChild.firstChild.nodeValue;
    if (st==")" || st=="]" || st=="}") node.removeChild(node.lastChild);
  }
}

/*Parsing ASCII math expressions with the following grammar
c ::= [A-z] | numbers | greek letters | other constant symbols
u ::= "sqrt" | "text" | "bb" | other unary symbols for font commands
b ::= "frac" | "root"          binary symbols
i ::= _ | ^ | /                infix symbols: subscript, superscript, division
l ::= ( | [ | { | (: | {:      left brackets
r ::= ) | ] | } | :) | :}      right brackets
S ::= c | lEr | uS' | bS'S'    simple expression: S' is S with brackets removed
E ::= SE | SiS' | S_S'^S'      expression
Each terminal symbol is translated into a corresponding mathml node.*/

function parseSexpr(str) { //parses str and returns [node,tailstr]
  var symbol;
  var node;
  var result;
  str = removeCharsAndBlanks(str,0);
  symbol = getSymbol(str);             //either a token or a bracket or empty
  if (symbol == null || symbol.rightBracket)
    return [null,str];
  str = removeCharsAndBlanks(str,symbol.input.length); 
  if (symbol.leftBracket) {                     //read (expr+)
    result = parseExpr(str);
    if (symbol.invisible) node = createMmlNode("mrow",result[0]);
    else {
      node = createMmlNode("mo",document.createTextNode(symbol.output));
      node = createMmlNode("mrow",node);
      node.appendChild(result[0]);
    }
    return [node,result[1]];
  }
  else if (symbol.unary) {
    if (symbol == sqrt) {
      result = parseSexpr(str);
      removeBrackets(result[0]);
      return [createMmlNode(symbol.tag,result[0]),result[1]];
    } else if (symbol == text || symbol == mbox) {
      str = removeCharsAndBlanks(str,0);
      if (str.charAt(0)=="{") var i=str.indexOf("}");
      else if (str.charAt(0)=="(") var i=str.indexOf(")");
      else if (str.charAt(0)=="[") var i=str.indexOf("]");
      else var i = 0;
      node = createMmlNode(symbol.tag,document.createTextNode(str.slice(1,i)));
      str = removeCharsAndBlanks(str,i+1);
      return [node,str];
    } else {                        // font change command
      result = parseSexpr(str);
      removeBrackets(result[0]);
      if (document.createElementNS!=null && symbol.codes!=null) {
        for (var i=0; i<result[0].childNodes.length; i++)
          if (result[0].childNodes[i].nodeName=="mi" || result[0].nodeName=="mi") {
            var st = (result[0].nodeName=="mi"?result[0].firstChild.nodeValue:
                              result[0].childNodes[i].firstChild.nodeValue);
            var newst = [];
            for (var j=0; j<st.length; j++)
              if (st.charCodeAt(j)>64 && st.charCodeAt(j)<91) newst = newst +
                String.fromCharCode(symbol.codes[st.charCodeAt(j)-65]);
              else newst = newst + st.charAt(j);
            if (result[0].nodeName=="mi")
              result[0]=myCreateElementMathML("mo").
                          appendChild(document.createTextNode(newst));
            else result[0].replaceChild(myCreateElementMathML("mo").
          appendChild(document.createTextNode(newst)),result[0].childNodes[i]);
          }
      }
      node = createMmlNode(symbol.tag,result[0]);
      node.setAttribute(symbol.atname,symbol.atval);
      return [node,result[1]];
    }
  }
  else if (symbol.binary) {
    var newFrag = document.createDocumentFragment();
    result = parseSexpr(str);
    removeBrackets(result[0]);
    var result2 = parseSexpr(result[1]);
    removeBrackets(result2[0]);
    if (symbol==root) newFrag.appendChild(result2[0]);
    newFrag.appendChild(result[0]);
    if (symbol==frac) newFrag.appendChild(result2[0]);
    return [createMmlNode(symbol.tag,newFrag),result2[1]];
  }
  else if (symbol.infix)
    return [createMmlNode("mo",document.createTextNode(symbol.output)),str];
  else if (symbol.space!=undefined) {
    var newFrag = document.createDocumentFragment();
    node = myCreateElementMathML("mspace");
    node.setAttribute("width",symbol.space);
    newFrag.appendChild(node);
    newFrag.appendChild(
      createMmlNode(symbol.tag,document.createTextNode(symbol.output)));
    node = myCreateElementMathML("mspace");
    node.setAttribute("width",symbol.space);
    newFrag.appendChild(node);
    return [createMmlNode("mrow",newFrag),str];
  }else return [createMmlNode(symbol.tag,        //its a constant
                             document.createTextNode(symbol.output)),str];
}

function parseExpr(str) {
  var symbol,sym1,sym2;
  var node;
  var result;
  var newFrag = document.createDocumentFragment();
  var nodeList = [];
  do {
    str = removeCharsAndBlanks(str,0);
    sym1 = getSymbol(str);
    result = parseSexpr(str);
    node = result[0];
    str = result[1];
    symbol = getSymbol(str);
    if (symbol.infix) {
      str = removeCharsAndBlanks(str,symbol.input.length);
      result = parseSexpr(str);
      removeBrackets(result[0]);
      str = result[1];
      if (symbol == div) removeBrackets(node);
      if (symbol == sub) {
        sym2 = getSymbol(str);
        if (sym2 == sup) {
          str = removeCharsAndBlanks(str,sym2.input.length);
          var res2 = parseSexpr(str);
          removeBrackets(res2[0]);
          str = res2[1];
            node = createMmlNode((sym1.underover?"munderover":"msubsup"),node);
            node.appendChild(result[0]);
            node.appendChild(res2[0]);
            node = createMmlNode("mrow",node);          
        } else {
          node = createMmlNode((sym1.underover?"munder":"msub"),node);
          node.appendChild(result[0]);
        }
      } else {
        node = createMmlNode(symbol.tag,node);
        node.appendChild(result[0]);
      }
      newFrag.appendChild(node);
    } 
    else if (node!=undefined) newFrag.appendChild(node);
  } while (!symbol.rightBracket && !(symbol==null) && symbol.output!="");
  if (symbol.rightBracket) {
    var len = newFrag.childNodes.length;
    if (len>0 && newFrag.childNodes[len-1].nodeName == "mrow" && len>1 &&
      newFrag.childNodes[len-2].nodeName == "mo" &&
      newFrag.childNodes[len-2].firstChild.nodeValue == ",") { //matrix
      var right = newFrag.childNodes[len-1].lastChild.firstChild.nodeValue;
      if (right==")" || right=="]") {
        var left = newFrag.childNodes[len-1].firstChild.firstChild.nodeValue;
        if (left=="(" && right==")" || left=="[" && right=="]") {
        var pos = []; // positions of commas
        var matrix = true;
        var m = newFrag.childNodes.length;
        for (var i=0; matrix && i<m; i=i+2) {
          pos[i] = [];
          node = newFrag.childNodes[i];
          if (matrix) matrix = node.nodeName=="mrow" && 
            (i==m-1 || node.nextSibling.nodeName=="mo" && 
            node.nextSibling.firstChild.nodeValue==",")&&
            node.firstChild.firstChild.nodeValue==left &&
            node.lastChild.firstChild.nodeValue==right;
          if (matrix) for (var j=0; j<node.childNodes.length; j++)
            if (node.childNodes[j].firstChild.nodeValue==",")
              pos[i][pos[i].length]=j;
          if (matrix && i>1) matrix = pos[i].length == pos[i-2].length;
        }
        if (matrix) {
          var table = document.createDocumentFragment();
          for (var i=0; i<m; i=i+2) {
            var row = document.createDocumentFragment();
            var frag = document.createDocumentFragment();
            node = newFrag.firstChild; // <mrow>(-,-,...,-,-)</mrow>
            var n = node.childNodes.length;
            var k = 0;
            node.removeChild(node.firstChild); //remove (
            for (var j=1; j<n-1; j++) {
              if (j==pos[i][k]){
                node.removeChild(node.firstChild); //remove ,
                row.appendChild(createMmlNode("mtd",frag));
                k++;
              } else frag.appendChild(node.firstChild);
            }
            row.appendChild(createMmlNode("mtd",frag));
            if (newFrag.childNodes.length>2) {
              newFrag.removeChild(newFrag.firstChild); //remove <mrow>)</mrow>
              newFrag.removeChild(newFrag.firstChild); //remove <mo>,</mo>
            }
            table.appendChild(createMmlNode("mtr",row));
          }
          node = createMmlNode("mtable",table);
          if (symbol.invisible) node.setAttribute("columnalign","left");
          newFrag.replaceChild(node,newFrag.firstChild);
        }
      }}
    }
    str = removeCharsAndBlanks(str,symbol.input.length);
    if (!symbol.invisible) {
      node = createMmlNode("mo",document.createTextNode(symbol.output));
      newFrag.appendChild(node);
    }
  }
  return [newFrag,str];
}

function parseMath(str) {
  var node = myCreateElementMathML("mstyle");
  node.setAttribute("mathcolor",mathcolor);
  node.setAttribute("displaystyle","true");
  node.appendChild(parseExpr(str)[0]);
  return createMmlNode("math",node);
}

function strarr2docFrag(arr) {
  var newFrag=document.createDocumentFragment();
  var expr = false;
  for (var i=0; i<arr.length; i++) {
    if (expr) newFrag.appendChild(parseMath(arr[i]));
    else newFrag.appendChild(myCreateElementXHTML("span").
      appendChild(document.createTextNode(arr[i])));
    expr = !expr;
  }
  return newFrag;
}

function processNode(n) {
  if (n.childNodes.length == 0 && 
    n.parentNode.nodeName!="pre" && n.parentNode.nodeName!="PRE" &&
    n.parentNode.nodeName!="textarea" && n.parentNode.nodeName!="TEXTAREA") {
    var str = n.nodeValue;
    if (!(str == null)) {
      var arr = str.split(mathdelimit);
      if (arr.length > 1) n.parentNode.replaceChild(strarr2docFrag(arr),n);
    }
  } else for (var i=0; i<n.childNodes.length; i++)
           processNode(n.childNodes[i]);
}

function translate() {
  initSymbols();
  body = document.getElementsByTagName("body")[0];
  processNode(body);
}